diff --git a/test/mp/test/common.h b/test/mp/test/common.h new file mode 100644 index 00000000..527d4a2a --- /dev/null +++ b/test/mp/test/common.h @@ -0,0 +1,27 @@ +// Copyright (c) The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_TEST_COMMON_H +#define MP_TEST_COMMON_H + +#include +#include + +#include + +namespace mp { +namespace test { + +//! Default event loop log handler used by tests. Logs all messages and throws +//! on errors so calling code can assert on them. +inline void DefaultLogHandler(LogMessage log) +{ + KJ_LOG(INFO, log.level, log.message); + if (log.level == Log::Raise) throw std::runtime_error(log.message); +} + +} // namespace test +} // namespace mp + +#endif // MP_TEST_COMMON_H diff --git a/test/mp/test/connect_tests.cpp b/test/mp/test/connect_tests.cpp index 73b1a396..cad55b1d 100644 --- a/test/mp/test/connect_tests.cpp +++ b/test/mp/test/connect_tests.cpp @@ -1,24 +1,25 @@ // Copyright (c) The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "common.h" #include "unixlistener.h" #include #include #include #include #include -#include #include +#include #include #include +#include #include -#include #include +#include #include #include #include // IWYU pragma: keep -#include #include #include #include @@ -34,86 +35,63 @@ namespace { constexpr auto FAILURE_TIMEOUT = std::chrono::seconds{30}; -//! Default event loop log handler used by tests, throws so the calling code -//! can assert on errors. -void DefaultLogHandler(mp::LogMessage log) -{ - if (log.level == mp::Log::Raise) - throw std::runtime_error(log.message); -} - class TestSetup { public: - int client_fd; - int server_fd; - - mp::EventLoop* loop; - std::optional loop_ref; + EventLoop* m_loop; + std::optional m_loop_ref; //! Thread variable should be after other struct members so the thread does //! not start until the other members are initialized. - std::thread loop_thread; - - TestSetup(mp::LogFn log_handler = DefaultLogHandler) - : TestSetup( - [](int fds[2]) { - KJ_REQUIRE(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != -1); - }, - log_handler) {} + std::thread m_loop_thread; - TestSetup(const std::function& init_sockets, - mp::LogFn log_handler = DefaultLogHandler) + TestSetup(LogFn log_handler = DefaultLogHandler) { - std::promise loop_promise; - loop_thread = std::thread([&, log_handler] { - mp::EventLoop loop("mptest-connect", log_handler); + std::promise loop_promise; + m_loop_thread = std::thread([&, log_handler] { + EventLoop loop("mptest-connect", log_handler); loop_promise.set_value(&loop); loop.loop(); }); - loop = loop_promise.get_future().get(); - loop_ref.emplace(*loop); - - // Initialize and store sockets - int fds[2] = {-1, -1}; - init_sockets(fds); - - client_fd = fds[0]; - server_fd = fds[1]; + m_loop = loop_promise.get_future().get(); + m_loop_ref.emplace(*m_loop); } ~TestSetup() { - loop_ref.reset(); - loop_thread.join(); + m_loop_ref.reset(); + m_loop_thread.join(); } }; KJ_TEST("ConnectStream connects to a socket serving a valid init interface") { TestSetup setup; + auto [client_fd, server_fd] = SocketPair(); - std::thread server_thread([&setup]() { - mp::EventLoop server_loop("mptest-valid-server", DefaultLogHandler); + std::thread server_thread([&]() { + EventLoop server_loop("mptest-valid-server", DefaultLogHandler); std::unique_ptr init = std::make_unique(); - ServeStream(server_loop, MakeStream(server_loop, setup.server_fd), *init); + ServeStream(server_loop, MakeStream(server_loop, server_fd), *init); server_loop.loop(); }); - auto init = ConnectStream(*setup.loop, MakeStream(*setup.loop, setup.client_fd)); + // FooInit has a `construct()` method, so this connects to the server and + // sends an IPC request that must complete successfully. + auto init = ConnectStream(*setup.m_loop, MakeStream(*setup.m_loop, client_fd)); init.reset(); server_thread.join(); - KJ_EXPECT(true); } KJ_TEST("ConnectStream throws when the socket is already disconnected") { TestSetup setup; + auto [client_fd, server_fd] = SocketPair(); - close(setup.server_fd); + KJ_SYSCALL(close(server_fd)); try { - auto init = ConnectStream(*setup.loop, MakeStream(*setup.loop, setup.client_fd)); + auto init = ConnectStream(*setup.m_loop, MakeStream(*setup.m_loop, client_fd)); KJ_EXPECT(false); } catch (const std::runtime_error& e) { @@ -126,12 +104,13 @@ KJ_TEST("ConnectStream throws when the socket is already disconnected") KJ_TEST("ConnectStream defers disconnect failure to the first IPC request for interfaces without construct()") { TestSetup setup; + auto [client_fd, server_fd] = SocketPair(); - close(setup.server_fd); + KJ_SYSCALL(close(server_fd)); // Without a construct() method no IPC call is made during client // creation, so ConnectStream succeeds even though the peer is gone. - auto foo = ConnectStream(*setup.loop, MakeStream(*setup.loop, setup.client_fd)); + auto foo = ConnectStream(*setup.m_loop, MakeStream(*setup.m_loop, client_fd)); try { foo->add(1, 2); @@ -149,18 +128,19 @@ KJ_TEST("ConnectStream handles a disconnect when no client calls are made") std::condition_variable cv; bool warned = false; - TestSetup setup([&](mp::LogMessage log) { - if (log.level == mp::Log::Warning && log.message.find("unexpected network disconnect") != std::string::npos) { + TestSetup setup([&](LogMessage log) { + if (log.level == Log::Warning && log.message.find("unexpected network disconnect") != std::string::npos) { const std::lock_guard lock(mutex); warned = true; cv.notify_all(); } DefaultLogHandler(log); }); + auto [client_fd, server_fd] = SocketPair(); - close(setup.server_fd); + KJ_SYSCALL(close(server_fd)); - auto foo = ConnectStream(*setup.loop, MakeStream(*setup.loop, setup.client_fd)); + auto foo = ConnectStream(*setup.m_loop, MakeStream(*setup.m_loop, client_fd)); // The disconnect handler registered by ProxyClientBase should run and // delete the connection even when no calls are ever made. @@ -171,67 +151,54 @@ KJ_TEST("ConnectStream handles a disconnect when no client calls are made") KJ_TEST("ConnectStream throws when the socket disconnects after receiving data") { TestSetup setup; + auto [client_fd, server_fd] = SocketPair(); - std::thread server_thread([&setup]() { + std::thread server_thread([&]() { char buf[128]; - ssize_t bytes_received = - recv(setup.server_fd, buf, sizeof(buf), 0); - - if (bytes_received > 0) { - close(setup.server_fd); - } + recv(server_fd, buf, sizeof(buf), 0); + KJ_SYSCALL(close(server_fd)); }); try { - auto init = ConnectStream(*setup.loop, MakeStream(*setup.loop, setup.client_fd)); + auto init = ConnectStream(*setup.m_loop, MakeStream(*setup.m_loop, client_fd)); - if (server_thread.joinable()) server_thread.join(); KJ_EXPECT(false); } catch (const std::runtime_error& e) { - if (server_thread.joinable()) server_thread.join(); - std::string_view reason = e.what(); KJ_EXPECT(reason == "IPC client method call interrupted by disconnect."); } + server_thread.join(); } KJ_TEST("ConnectStream throws when a connection accepted from a listener disconnects after receiving data") { UnixListener listener; + TestSetup setup; + int client_fd = listener.MakeConnectedSocket(); + int server_fd = listener.release(); - TestSetup setup([&listener](int fds[2]) { - fds[0] = listener.MakeConnectedSocket(); // client_fd - fds[1] = listener.release(); // server_fd - }); - - std::thread server_thread([&setup]() { + std::thread server_thread([&]() { char buf[128]; - int connection_fd = accept(setup.server_fd, nullptr, nullptr); + int connection_fd = accept(server_fd, nullptr, nullptr); if (connection_fd >= 0) { - ssize_t bytes_received = - recv(connection_fd, buf, sizeof(buf), 0); - - if (bytes_received > 0) { - close(connection_fd); - } + recv(connection_fd, buf, sizeof(buf), 0); + KJ_SYSCALL(close(connection_fd)); } - close(setup.server_fd); + KJ_SYSCALL(close(server_fd)); }); try { - auto init = ConnectStream(*setup.loop, MakeStream(*setup.loop, setup.client_fd)); + auto init = ConnectStream(*setup.m_loop, MakeStream(*setup.m_loop, client_fd)); - if (server_thread.joinable()) server_thread.join(); KJ_EXPECT(false); } catch (const std::runtime_error& e) { - if (server_thread.joinable()) server_thread.join(); - std::string_view reason = e.what(); KJ_EXPECT(reason == "IPC client method call interrupted by disconnect."); } + server_thread.join(); } } // namespace diff --git a/test/mp/test/listen_tests.cpp b/test/mp/test/listen_tests.cpp index b9b0bfc2..8d06cb35 100644 --- a/test/mp/test/listen_tests.cpp +++ b/test/mp/test/listen_tests.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "common.h" #include "unixlistener.h" #include #include @@ -23,7 +24,6 @@ #include #include // IWYU pragma: keep #include -#include #include #include #include @@ -42,10 +42,7 @@ class ClientSetup public: explicit ClientSetup(int fd) : thread([this, fd] { - EventLoop loop("mptest-client", [](mp::LogMessage log) { - KJ_LOG(INFO, log.level, log.message); - if (log.level == mp::Log::Raise) throw std::runtime_error(log.message); - }); + EventLoop loop("mptest-client", DefaultLogHandler); client_promise.set_value(ConnectStream(loop, MakeStream(loop, fd))); loop.loop(); }) @@ -67,13 +64,6 @@ class ClientSetup std::thread thread; }; -//! Default server event loop log handler, throws so tests can assert on errors. -void DefaultLogHandler(mp::LogMessage log) -{ - KJ_LOG(INFO, log.level, log.message); - if (log.level == mp::Log::Raise) throw std::runtime_error(log.message); -} - //! Runs a server EventLoop on its own thread, starts ListenConnections() on a //! UnixListener socket, and records connection/disconnection counts through //! EventLoop test hooks @@ -241,7 +231,7 @@ KJ_TEST("ListenConnections handles a client that disconnects before being accept // The event loop then reports this as an uncaught task exception. We catch and ignore // this specific error here so that the corresponding CI job does not fail. // - // This is a Cap'n Proto bug, a fix is available in the v2 branch at: https://github.com/capnproto/capnproto/commit/7df5bd078f389ded313479981bd0ae06cbcdfe1b#diff-ec577ad66535f58f6d7396ea51d3e56c0065308aa8fb02751cd6a8cfaa67252fR1358-R1372 + // This is a Cap'n Proto bug, fixed by https://github.com/capnproto/capnproto/pull/2748 if (log.level == mp::Log::Error && log.message.find("Uncaught exception in daemonized task.") != std::string::npos) { Lock lock(mutex); accept_error = true;