#include #include #include #include #include #include #include #include #include #include #include #include "UnixIpcBridge.hpp" // --------------------------------------------------------------------------- // Helper: a minimal UNIX-domain socket server that accepts one connection, // reads `count` ints, then tears down cleanly. Uses a ready-flag so the // client never races against bind/listen. // --------------------------------------------------------------------------- class FakeConsumer { public: explicit FakeConsumer(const std::string& path, int count = 1) : m_path(path), m_count(count) { // Remove stale socket from any previous failed run unlink(m_path.c_str()); } /// Start the server on a background thread. void start() { m_thread = std::thread([this] { run(); }); // Spin until the server signals it is listening (bounded wait). auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); while (!m_ready.load(std::memory_order_acquire)) { if (std::chrono::steady_clock::now() > deadline) { throw std::runtime_error("FakeConsumer: server failed to start"); } std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } /// Block until the server thread finishes. void join() { m_thread.join(); } /// Values received in order. const std::vector& received() const { return m_received; } ~FakeConsumer() { unlink(m_path.c_str()); } private: void run() { m_server_fd = socket(AF_UNIX, SOCK_STREAM, 0); ASSERT_GE(m_server_fd, 0) << "socket() failed: " << strerror(errno); struct sockaddr_un addr = {}; addr.sun_family = AF_UNIX; std::strncpy(addr.sun_path, m_path.c_str(), sizeof(addr.sun_path) - 1); ASSERT_EQ( bind(m_server_fd, reinterpret_cast(&addr), sizeof(addr)), 0) << "bind() failed: " << strerror(errno); ASSERT_EQ(listen(m_server_fd, 1), 0) << "listen() failed: " << strerror(errno); // Signal that we are ready to accept connections. m_ready.store(true, std::memory_order_release); int client_fd = accept(m_server_fd, nullptr, nullptr); ASSERT_GE(client_fd, 0) << "accept() failed: " << strerror(errno); for (int i = 0; i < m_count; ++i) { int value = 0; ssize_t n = recv(client_fd, &value, sizeof(value), MSG_WAITALL); ASSERT_EQ(n, static_cast(sizeof(value))) << "recv() short read on message " << i; m_received.push_back(value); } close(client_fd); close(m_server_fd); } std::string m_path; int m_count; int m_server_fd = -1; std::atomic m_ready{false}; std::thread m_thread; std::vector m_received; }; // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- /// Sends a single integer value and verifies the consumer receives it. TEST(UnixIpcBridgeTest, SendsSingleInt) { const std::string sock = "/tmp/test_ipc_single.sock"; FakeConsumer consumer(sock, /*count=*/1); consumer.start(); UnixIpcBridge bridge(sock); bridge.send(42); consumer.join(); ASSERT_EQ(consumer.received().size(), 1u); EXPECT_EQ(consumer.received()[0], 42); } /// Sends zero and a negative value — makes sure sign bits survive. TEST(UnixIpcBridgeTest, SendsZeroAndNegativeValues) { // Zero { const std::string sock = "/tmp/test_ipc_zero.sock"; FakeConsumer consumer(sock, 1); consumer.start(); UnixIpcBridge bridge(sock); bridge.send(0); consumer.join(); ASSERT_EQ(consumer.received().size(), 1u); EXPECT_EQ(consumer.received()[0], 0); } // Negative { const std::string sock = "/tmp/test_ipc_neg.sock"; FakeConsumer consumer(sock, 1); consumer.start(); UnixIpcBridge bridge(sock); bridge.send(-1); consumer.join(); ASSERT_EQ(consumer.received().size(), 1u); EXPECT_EQ(consumer.received()[0], -1); } } /// Sends INT_MAX / INT_MIN to check for truncation or overflow. TEST(UnixIpcBridgeTest, SendsExtremeBoundaryValues) { { const std::string sock = "/tmp/test_ipc_max.sock"; FakeConsumer consumer(sock, 1); consumer.start(); UnixIpcBridge bridge(sock); bridge.send(std::numeric_limits::max()); consumer.join(); ASSERT_EQ(consumer.received().size(), 1u); EXPECT_EQ(consumer.received()[0], std::numeric_limits::max()); } { const std::string sock = "/tmp/test_ipc_min.sock"; FakeConsumer consumer(sock, 1); consumer.start(); UnixIpcBridge bridge(sock); bridge.send(std::numeric_limits::min()); consumer.join(); ASSERT_EQ(consumer.received().size(), 1u); EXPECT_EQ(consumer.received()[0], std::numeric_limits::min()); } } /// Connecting to a non-existent socket must throw, not silently fail. TEST(UnixIpcBridgeTest, ThrowsWhenNoConsumerListening) { const std::string sock = "/tmp/test_ipc_noserver.sock"; unlink(sock.c_str()); // make sure nothing is there UnixIpcBridge bridge(sock); EXPECT_THROW(bridge.send(99), std::runtime_error); } /// Multiple sequential sends (each reopens the connection). TEST(UnixIpcBridgeTest, MultipleSendsSequentially) { const std::string sock = "/tmp/test_ipc_multi.sock"; constexpr int kMessages = 5; // Server expects exactly kMessages ints from kMessages connections. // Because the bridge reconnects every send(), we run kMessages // single-message consumers sequentially. std::vector all_received; for (int i = 0; i < kMessages; ++i) { FakeConsumer consumer(sock, 1); consumer.start(); UnixIpcBridge bridge(sock); bridge.send(i * 10); consumer.join(); ASSERT_EQ(consumer.received().size(), 1u); all_received.push_back(consumer.received()[0]); } ASSERT_EQ(all_received.size(), static_cast(kMessages)); for (int i = 0; i < kMessages; ++i) { EXPECT_EQ(all_received[i], i * 10); } }