Skip to content

Commit 003f37e

Browse files
committed
TASK-023: failing tests for smart-pointer register_resource overloads (RED)
Add test/unit/webserver_register_smartptr_test.cpp covering the TASK-023 acceptance criteria as compile-time and runtime assertions: - Compile-time: new unique_ptr and shared_ptr overloads return void; raw-pointer overload is removed; bool family parameter is still accepted (TASK-024 owns the family-flag removal). - Runtime (acceptance): unique_ptr ownership transfer, dtor runs on webserver destruction, shared_ptr caller-retains-ref semantics. - Runtime (validation): null and duplicate registrations throw std::invalid_argument (replaces v1's silent `return false`). Wire the new TU into test/Makefile.am check_PROGRAMS. The TU does not build today: webserver still exposes only the raw-pointer overload. This is the deliberate failing baseline for the API swap that follows.
1 parent 4bd6036 commit 003f37e

2 files changed

Lines changed: 259 additions & 1 deletion

File tree

test/Makefile.am

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ LDADD += -lcurl
2626

2727
AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/httpserver/ -DHTTPSERVER_COMPILATION
2828
METASOURCES = AUTO
29-
check_PROGRAMS = basic file_upload http_utils threaded nodelay string_utilities http_endpoint ban_system ws_start_stop authentication deferred http_resource http_response create_webserver new_response_types daemon_info uri_log feature_unavailable header_hygiene_iovec header_hygiene iovec_entry http_method constants body http_response_sbo http_response_factories webserver_pimpl http_request_pimpl create_test_request http_request_arena http_request_const_getters http_request_tls_accessors
29+
check_PROGRAMS = basic file_upload http_utils threaded nodelay string_utilities http_endpoint ban_system ws_start_stop authentication deferred http_resource http_response create_webserver new_response_types daemon_info uri_log feature_unavailable header_hygiene_iovec header_hygiene iovec_entry http_method constants body http_response_sbo http_response_factories webserver_pimpl http_request_pimpl create_test_request http_request_arena http_request_const_getters http_request_tls_accessors webserver_register_smartptr
3030

3131
MOSTLYCLEANFILES = *.gcda *.gcno *.gcov
3232

@@ -147,6 +147,15 @@ http_request_const_getters_LDADD =
147147
http_request_tls_accessors_SOURCES = unit/http_request_tls_accessors_test.cpp
148148
http_request_tls_accessors_LDADD =
149149

150+
# webserver_register_smartptr: TASK-023. Compile-time signature
151+
# assertions that the new unique_ptr/shared_ptr register_resource
152+
# overloads exist and the raw-pointer overload is gone, plus runtime
153+
# tests for ownership semantics (unique_ptr ownership transfer,
154+
# shared_ptr caller-retains-ref, throw-on-null, throw-on-duplicate).
155+
# Default LDADD (libhttpserver + curl) is sufficient; no microhttpd
156+
# direct link needed.
157+
webserver_register_smartptr_SOURCES = unit/webserver_register_smartptr_test.cpp
158+
150159
noinst_HEADERS = littletest.hpp
151160
AM_CXXFLAGS += -Wall -fPIC -Wno-overloaded-virtual
152161

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
/*
2+
This file is part of libhttpserver
3+
Copyright (C) 2011-2026 Sebastiano Merlino
4+
5+
This library is free software; you can redistribute it and/or
6+
modify it under the terms of the GNU Lesser General Public
7+
License as published by the Free Software Foundation; either
8+
version 2.1 of the License, or (at your option) any later version.
9+
10+
This library is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public
16+
License along with this library; if not, write to the Free Software
17+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
18+
USA
19+
*/
20+
21+
// TASK-023: smart-pointer register_resource overloads.
22+
//
23+
// This TU pins both the compile-time signature contract for the new
24+
// register_resource overloads and the runtime ownership semantics:
25+
// - unique_ptr overload: webserver takes ownership; resource dtor
26+
// runs when the webserver is destroyed (acceptance criterion).
27+
// - shared_ptr overload: caller retains a reference; resource lives
28+
// as long as either the webserver or the caller holds it.
29+
// - Null smart-pointer arguments throw std::invalid_argument.
30+
// - Duplicate registrations throw std::invalid_argument (replacing
31+
// the v1 "return false" silent-fail behavior, which is gone with
32+
// the new void return type).
33+
34+
#include <curl/curl.h>
35+
36+
#include <atomic>
37+
#include <memory>
38+
#include <stdexcept>
39+
#include <string>
40+
#include <type_traits>
41+
42+
#include "./httpserver.hpp"
43+
#include "./littletest.hpp"
44+
45+
using httpserver::create_webserver;
46+
using httpserver::http_request;
47+
using httpserver::http_resource;
48+
using httpserver::http_response;
49+
using httpserver::webserver;
50+
51+
#define PORT 8080
52+
53+
namespace {
54+
55+
size_t writefunc(void* ptr, size_t size, size_t nmemb, std::string* s) {
56+
s->append(reinterpret_cast<char*>(ptr), size * nmemb);
57+
return size * nmemb;
58+
}
59+
60+
class ok_resource : public http_resource {
61+
public:
62+
std::shared_ptr<http_response> render_get(const http_request&) override {
63+
return std::make_shared<http_response>(http_response::string("OK"));
64+
}
65+
};
66+
67+
// Resource whose destructor increments a static counter, so tests can
68+
// observe ownership-driven destruction.
69+
class counted_resource : public http_resource {
70+
public:
71+
static std::atomic<int> dtor_count;
72+
73+
counted_resource() = default;
74+
~counted_resource() override { ++dtor_count; }
75+
76+
std::shared_ptr<http_response> render_get(const http_request&) override {
77+
return std::make_shared<http_response>(http_response::string("OK"));
78+
}
79+
};
80+
81+
std::atomic<int> counted_resource::dtor_count{0};
82+
83+
} // namespace
84+
85+
// ---- Compile-time signature contract -----------------------------------
86+
//
87+
// Probing overloaded members through `decltype` of a call expression: a
88+
// well-formed call means the overload exists with the given argument
89+
// shape; an ill-formed expression would fail SFINAE inside the
90+
// surrounding decltype. We exercise each overload through its natural
91+
// call syntax.
92+
93+
// (1) unique_ptr overload exists and returns void.
94+
static_assert(std::is_same_v<
95+
decltype(std::declval<webserver&>().register_resource(
96+
std::declval<const std::string&>(),
97+
std::declval<std::unique_ptr<http_resource>>())),
98+
void>,
99+
"register_resource(const string&, unique_ptr<http_resource>) "
100+
"must exist and return void");
101+
102+
// (2) shared_ptr overload exists and returns void.
103+
static_assert(std::is_same_v<
104+
decltype(std::declval<webserver&>().register_resource(
105+
std::declval<const std::string&>(),
106+
std::declval<std::shared_ptr<http_resource>>())),
107+
void>,
108+
"register_resource(const string&, shared_ptr<http_resource>) "
109+
"must exist and return void");
110+
111+
// (3) The bool-family parameter remains on both overloads so TASK-024
112+
// can do the register_path/register_prefix split in one go without
113+
// re-touching every call site twice.
114+
static_assert(std::is_same_v<
115+
decltype(std::declval<webserver&>().register_resource(
116+
std::declval<const std::string&>(),
117+
std::declval<std::unique_ptr<http_resource>>(),
118+
true)),
119+
void>,
120+
"unique_ptr overload must accept a trailing bool family arg");
121+
static_assert(std::is_same_v<
122+
decltype(std::declval<webserver&>().register_resource(
123+
std::declval<const std::string&>(),
124+
std::declval<std::shared_ptr<http_resource>>(),
125+
true)),
126+
void>,
127+
"shared_ptr overload must accept a trailing bool family arg");
128+
129+
// (4) Negative: the raw-pointer overload must be gone. Use a SFINAE
130+
// helper so this is observable as a compile-time bool.
131+
template <typename = void>
132+
struct has_raw_register_resource : std::false_type {};
133+
134+
template <>
135+
struct has_raw_register_resource<std::void_t<
136+
decltype(std::declval<webserver&>().register_resource(
137+
std::declval<const std::string&>(),
138+
std::declval<http_resource*>()))>> : std::true_type {};
139+
140+
static_assert(!has_raw_register_resource<>::value,
141+
"the raw-pointer register_resource overload must be removed");
142+
143+
// ---- Runtime ownership tests ------------------------------------------
144+
145+
LT_BEGIN_SUITE(webserver_register_smartptr_suite)
146+
void set_up() {
147+
counted_resource::dtor_count = 0;
148+
}
149+
150+
void tear_down() {}
151+
LT_END_SUITE(webserver_register_smartptr_suite)
152+
153+
// Acceptance criterion (verbatim from TASK-023 spec):
154+
// "auto r = std::make_unique<my_resource>();
155+
// ws.register_resource('/foo', std::move(r)); compiles and serves."
156+
LT_BEGIN_AUTO_TEST(webserver_register_smartptr_suite,
157+
unique_ptr_overload_compiles_and_serves)
158+
webserver ws = create_webserver(PORT);
159+
auto r = std::make_unique<ok_resource>();
160+
ws.register_resource("/foo", std::move(r));
161+
ws.start(false);
162+
163+
curl_global_init(CURL_GLOBAL_ALL);
164+
std::string s;
165+
CURL* curl = curl_easy_init();
166+
curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/foo");
167+
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
168+
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
169+
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s);
170+
CURLcode res = curl_easy_perform(curl);
171+
LT_ASSERT_EQ(res, 0);
172+
LT_CHECK_EQ(s, std::string("OK"));
173+
curl_easy_cleanup(curl);
174+
ws.stop();
175+
LT_END_AUTO_TEST(unique_ptr_overload_compiles_and_serves)
176+
177+
// Acceptance criterion: "A test verifies the resource destructor runs
178+
// when the webserver is destroyed."
179+
LT_BEGIN_AUTO_TEST(webserver_register_smartptr_suite,
180+
unique_ptr_dtor_runs_on_webserver_destruction)
181+
LT_CHECK_EQ(counted_resource::dtor_count.load(), 0);
182+
{
183+
webserver ws = create_webserver(PORT + 1);
184+
ws.register_resource("/x", std::make_unique<counted_resource>());
185+
// No start/stop needed — registration alone must transfer
186+
// ownership; webserver destruction must run the dtor.
187+
}
188+
LT_CHECK_EQ(counted_resource::dtor_count.load(), 1);
189+
LT_END_AUTO_TEST(unique_ptr_dtor_runs_on_webserver_destruction)
190+
191+
// shared_ptr semantics: caller's shared_ptr keeps the resource alive
192+
// past webserver destruction. The dtor runs only once both refs drop.
193+
LT_BEGIN_AUTO_TEST(webserver_register_smartptr_suite,
194+
shared_ptr_caller_keeps_resource_alive)
195+
LT_CHECK_EQ(counted_resource::dtor_count.load(), 0);
196+
auto sp = std::make_shared<counted_resource>();
197+
{
198+
webserver ws = create_webserver(PORT + 2);
199+
ws.register_resource("/x", sp);
200+
}
201+
// Webserver destroyed; caller still holds a ref.
202+
LT_CHECK_EQ(counted_resource::dtor_count.load(), 0);
203+
sp.reset();
204+
LT_CHECK_EQ(counted_resource::dtor_count.load(), 1);
205+
LT_END_AUTO_TEST(shared_ptr_caller_keeps_resource_alive)
206+
207+
LT_BEGIN_AUTO_TEST(webserver_register_smartptr_suite,
208+
null_unique_ptr_throws)
209+
webserver ws = create_webserver(PORT + 3);
210+
bool threw = false;
211+
try {
212+
ws.register_resource("/x", std::unique_ptr<http_resource>{});
213+
} catch (const std::invalid_argument&) {
214+
threw = true;
215+
}
216+
LT_CHECK_EQ(threw, true);
217+
LT_END_AUTO_TEST(null_unique_ptr_throws)
218+
219+
LT_BEGIN_AUTO_TEST(webserver_register_smartptr_suite,
220+
null_shared_ptr_throws)
221+
webserver ws = create_webserver(PORT + 4);
222+
bool threw = false;
223+
try {
224+
ws.register_resource("/x", std::shared_ptr<http_resource>{});
225+
} catch (const std::invalid_argument&) {
226+
threw = true;
227+
}
228+
LT_CHECK_EQ(threw, true);
229+
LT_END_AUTO_TEST(null_shared_ptr_throws)
230+
231+
// New void-returning API replaces v1's silent `return false` on
232+
// duplicate registration with a throw, matching the existing
233+
// throw-on-null behavior.
234+
LT_BEGIN_AUTO_TEST(webserver_register_smartptr_suite,
235+
duplicate_registration_throws)
236+
webserver ws = create_webserver(PORT + 5);
237+
ws.register_resource("/dup", std::make_shared<ok_resource>());
238+
bool threw = false;
239+
try {
240+
ws.register_resource("/dup", std::make_shared<ok_resource>());
241+
} catch (const std::invalid_argument&) {
242+
threw = true;
243+
}
244+
LT_CHECK_EQ(threw, true);
245+
LT_END_AUTO_TEST(duplicate_registration_throws)
246+
247+
LT_BEGIN_AUTO_TEST_ENV()
248+
AUTORUN_TESTS()
249+
LT_END_AUTO_TEST_ENV()

0 commit comments

Comments
 (0)