// uvco (c) 2024 Lewin Bormann. See LICENSE for specific terms. #include #include "test_util.h" #include "uvco/combinators.h" #include "uvco/exception.h" #include "uvco/loop/loop.h" #include "uvco/promise/promise.h" #include "uvco/run.h" #include "uvco/uds.h" #include "uvco/uds_stream.h" #include #include #include #include #include #include namespace { using namespace uvco; constexpr std::string_view testSocketPath = "/tmp/_uvco_test.sock"; Promise pingPongServer(const Loop &loop) { UnixStreamServer server{loop, testSocketPath}; auto listener = server.listen(); try { std::optional stream = co_await listener; if (!stream) { fmt::print(stderr, "No stream\n"); co_return; } fmt::print(stderr, "Server at {}\n", stream->getSockName()); fmt::print(stderr, "Received connection from client at {}\n", stream->getPeerName()); std::optional message = co_await stream->read(); if (!message) { throw UvcoException{UV_EOF, "No data from client"}; } co_await stream->write( fmt::format("Hello, back! I received '{}'\n", message.value())); stream->close(); } catch (const UvcoException &e) { fmt::print(stderr, "Error: {}\n", e.what()); throw; } fmt::print(stderr, "Closing server\n"); fmt::print(stderr, "Listen finished\n"); } Promise pingPongClient(const Loop &loop) { UnixStreamClient client{loop}; try { UnixStream stream = co_await client.connect(testSocketPath); fmt::print(stderr, "Client connecting from {}\n", stream.getSockName()); fmt::print(stderr, "Client connected to server at {}\n", stream.getPeerName()); co_await stream.write("Hello from client"); fmt::print(stderr, "Sent; waiting for reply\n"); std::optional buf = co_await stream.read(); if (!buf) { throw UvcoException{UV_EOF, "No data from server"}; } fmt::print(stderr, "Received: {}", *buf); stream.close(); } catch (const UvcoException &e) { fmt::print(stderr, "Error: {}\n", e.what()); throw; } } TEST(UdsTest, unixStreamPingPong) { auto setup = [](const Loop &loop) -> Promise { try { Promise server = pingPongServer(loop); co_await yield(); Promise client = pingPongClient(loop); co_await server; co_await client; } catch (const UvcoException &e) { fmt::print(stderr, "Error in setup function: {}\n", e.what()); throw; } }; run_loop(setup); } TEST(UdsTest, dropServer) { auto setup = [](const Loop &loop) -> Promise { UnixStreamServer server{loop, testSocketPath}; MultiPromise listener = server.listen(); co_return; }; run_loop(setup); } TEST(UdsTest, dropPingPong) { auto setup = [](const Loop &loop) -> Promise { Promise server = pingPongServer(loop); Promise client = pingPongClient(loop); co_return; }; run_loop(setup); } // The following test cases are adapted from the tcp-test suite and check that // cancellations in various constellations work safely. Promise serverLoop(MultiPromise clients) { while (true) { std::optional maybeClient = co_await clients; if (!maybeClient) { co_return; } UnixStream client = std::move(*maybeClient); std::optional chunk = co_await client.read(); BOOST_ASSERT(chunk); co_await client.writeBorrowed(*chunk); co_await client.shutdown(); client.close(); } } Promise sendReceivePing(const Loop &loop) { UnixStreamClient client{loop}; UnixStream stream = co_await client.connect(testSocketPath); co_await stream.write("Ping"); std::optional response = co_await stream.read(); EXPECT_EQ(response, "Ping"); stream.close(); } TEST(UdsTest, repeatedConnectSingleServerCancel1) { auto setup = [&](const Loop &loop) -> Promise { UnixStreamServer server{loop, testSocketPath}; Promise serverHandler = serverLoop(server.listen()); co_await sendReceivePing(loop); co_await sendReceivePing(loop); co_await sendReceivePing(loop); }; run_loop(setup); } TEST(UdsTest, repeatedConnectSingleServerCancel2) { auto setup = [&](const Loop &loop) -> Promise { UnixStreamServer server{loop, testSocketPath}; { Promise serverHandler = serverLoop(server.listen()); co_await sendReceivePing(loop); co_await sendReceivePing(loop); co_await sendReceivePing(loop); } }; run_loop(setup); } TEST(UdsTest, repeatedConnectSingleServerCancel3) { auto setup = [&](const Loop &loop) -> Promise { UnixStreamServer server{loop, testSocketPath}; // Just go hog-wild, cancel everything willy-nilly Promise serverHandler = serverLoop(server.listen()); co_await sendReceivePing(loop); co_await sendReceivePing(loop); sendReceivePing(loop); }; run_loop(setup); } TEST(UdsTest, unixStreamFailConnect) { auto setup = [](const Loop &loop) -> Promise { try { UnixStreamClient client{loop}; UnixStream stream = co_await client.connect("/tmp/does_not_exist.sock"); } catch (const UvcoException &e) { EXPECT_EQ(UV_ENOENT, e.status); throw; } }; EXPECT_THROW({ run_loop(setup); }, UvcoException); } TEST(UdsTest, unixStreamListenerStop) { auto setup = [](const Loop &loop) -> Promise { UnixStreamServer server{loop, testSocketPath}; auto listener = server.listen(); co_await yield(); // The following line would result in a crash (intentional) because the // listener generator has returned. // co_await listener; co_return; }; run_loop(setup); } } // namespace