#include #include "daemon/port_allocator.h" #include TEST(PortAllocator, ReturnsNonZeroPort) { uint16_t p = PortAllocator::allocateEphemeralTcp("127.0.0.1"); EXPECT_GT(p, 0u); } TEST(PortAllocator, EmptyHostTreatedAsAnyAddress) { // Empty host = "0.0.0.0". Allocator should still succeed. uint16_t p = PortAllocator::allocateEphemeralTcp(""); EXPECT_GT(p, 0u); } TEST(PortAllocator, BadHostReturnsZero) { // Allocator only accepts numeric IPv4 — hostname resolution is the // caller's responsibility. A garbage string should fail closed. uint16_t p = PortAllocator::allocateEphemeralTcp("not-an-ip"); EXPECT_EQ(p, 0u); } TEST(PortAllocator, IPv6HostAllocatesPort) { // BUG-010: the allocator hardcoded AF_INET, so an IPv6 bind target // (e.g. "::" / "::1") fell through inet_pton(AF_INET,...) and returned 0, // aborting daemon startup for any IPv6 TCP transport. "::" (any-address) // is bindable even in containers without a configured ::1, so use it. uint16_t p = PortAllocator::allocateEphemeralTcp("::"); EXPECT_GT(p, 0u) << "IPv6 any-address must allocate an ephemeral port, not fail"; } TEST(PortAllocator, ConsecutiveAllocationsAreDistinct) { // Two ephemeral allocations in a row should pick different ports // (the kernel rotates through the ephemeral range). This is the // critical property for the multi-module use case: the daemon // pre-allocates one port per module before spawning the children. std::set seen; for (int i = 0; i < 10; ++i) { uint16_t p = PortAllocator::allocateEphemeralTcp("127.0.0.1"); ASSERT_GT(p, 0u); seen.insert(p); } // Don't require *all* 10 distinct (kernels can reuse a just-freed // port immediately under SO_REUSEADDR), but expect at least a few // distinct values — a single-port answer would mean the rotation // is broken. EXPECT_GT(seen.size(), 1u); }