Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ if (ODR_WITH_HTTP_SERVER)
PRIVATE
"src/http_server_test.cpp"
)
# a client to make a request with, so the server can be tested serving one
find_package(httplib REQUIRED)
target_link_libraries(odr_test PRIVATE httplib::httplib)
endif ()
target_include_directories(odr_test
PRIVATE
Expand Down
56 changes: 56 additions & 0 deletions test/src/http_server_test.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include <odr/exceptions.hpp>
#include <odr/http_server.hpp>

#include <httplib/httplib.h>

#include <gtest/gtest.h>

#include <atomic>
Expand All @@ -20,6 +22,31 @@ void wait_until_running(const HttpServer &server) {
}
}

/// Stops the server and joins the thread however the test leaves - a fatal
/// assertion returns, and a joinable `std::thread` destructor would take the
/// whole test binary down with `std::terminate` instead of reporting it.
class ListenGuard {
public:
ListenGuard(const HttpServer &server, std::thread thread)
: m_server{&server}, m_thread{std::move(thread)} {}

~ListenGuard() {
m_server->stop();
if (m_thread.joinable()) {
m_thread.join();
}
}

ListenGuard(const ListenGuard &) = delete;
ListenGuard &operator=(const ListenGuard &) = delete;
ListenGuard(ListenGuard &&) = delete;
ListenGuard &operator=(ListenGuard &&) = delete;

private:
const HttpServer *m_server;
std::thread m_thread;
};

} // namespace

TEST(HttpServer, bind_reports_the_port_it_got) {
Expand Down Expand Up @@ -150,3 +177,32 @@ TEST(HttpServer, listen_after_stop_returns) {
// to serve, but nothing went wrong either
EXPECT_NO_THROW(server.listen());
}

/// A kept-alive connection used to hold the accept loop until it idled out, so
/// stop() spanned cpp-httplib's 5 s keep-alive timeout - the whole of it, on
/// the path every consumer takes (#641). Fixed upstream in 0.47.0, where the
/// keep-alive wait watches the listening socket; this is what says so.
TEST(HttpServer, stop_is_prompt_after_serving_a_kept_alive_request) {
const HttpServer server;

const std::uint32_t port = server.bind("127.0.0.1", 0);
// stops and joins whatever happens below, including a fatal assertion
const ListenGuard guard{server, std::thread{[&server] { server.listen(); }}};
wait_until_running(server);

// alive across the stop() below, so the connection it holds is open there
httplib::Client client{"127.0.0.1", static_cast<int>(port)};
client.set_keep_alive(true);
const httplib::Result response = client.Get("/");
ASSERT_TRUE(response);
Comment thread
andiwand marked this conversation as resolved.
EXPECT_EQ(response->status, 200);

const auto before = std::chrono::steady_clock::now();
server.stop();
const auto elapsed = std::chrono::steady_clock::now() - before;

EXPECT_LT(elapsed, 2s)
<< "stop() took "
<< std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count()
<< " ms";
}
Loading