Skip to content

Commit d0cff01

Browse files
committed
test: add dedicated ListenConnections coverage
Add a separate listen_tests.cpp file with reusable UnixListener, ClientSetup, and ListenSetup helpers for exercising ListenConnections() with real Unix domain sockets. The new test covers the baseline behavior that ListenConnections() accepts an incoming connection and serves requests over it. Keeping this coverage separate from the existing general proxy tests makes the socket listener setup easier to review and provides a clearer place to extend listener-specific behavior in follow-up commits.
1 parent 3edbe8f commit d0cff01

3 files changed

Lines changed: 173 additions & 1 deletion

File tree

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ if(BUILD_TESTING AND TARGET CapnProto::kj-test)
2626
${MP_PROXY_HDRS}
2727
mp/test/foo-types.h
2828
mp/test/foo.h
29+
mp/test/listen_tests.cpp
2930
mp/test/spawn_tests.cpp
3031
mp/test/test.cpp
3132
)

test/mp/test/listen_tests.cpp

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// Copyright (c) The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <mp/test/foo.capnp.h>
6+
#include <mp/test/foo.capnp.proxy.h>
7+
8+
#include <chrono>
9+
#include <condition_variable>
10+
#include <cstdlib>
11+
#include <cstring>
12+
#include <future>
13+
#include <kj/async.h>
14+
#include <kj/common.h>
15+
#include <kj/debug.h>
16+
#include <kj/memory.h>
17+
#include <kj/test.h>
18+
#include <memory>
19+
#include <mp/proxy-io.h>
20+
#include <mutex>
21+
#include <ratio> // IWYU pragma: keep
22+
#include <stdexcept>
23+
#include <string>
24+
#include <sys/socket.h>
25+
#include <sys/un.h>
26+
#include <thread>
27+
#include <unistd.h>
28+
29+
namespace mp {
30+
namespace test {
31+
namespace {
32+
33+
class UnixListener
34+
{
35+
public:
36+
UnixListener()
37+
{
38+
char dir_template[] = "/tmp/mptest-listener-XXXXXX";
39+
char* dir = mkdtemp(dir_template);
40+
KJ_REQUIRE(dir != nullptr);
41+
m_dir = dir;
42+
m_path = m_dir + "/socket";
43+
44+
m_fd = socket(AF_UNIX, SOCK_STREAM, 0);
45+
KJ_REQUIRE(m_fd >= 0);
46+
47+
sockaddr_un addr{};
48+
addr.sun_family = AF_UNIX;
49+
KJ_REQUIRE(m_path.size() < sizeof(addr.sun_path));
50+
std::strncpy(addr.sun_path, m_path.c_str(), sizeof(addr.sun_path) - 1);
51+
KJ_REQUIRE(bind(m_fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == 0);
52+
KJ_REQUIRE(listen(m_fd, SOMAXCONN) == 0);
53+
}
54+
55+
~UnixListener()
56+
{
57+
if (m_fd >= 0) close(m_fd);
58+
if (!m_path.empty()) unlink(m_path.c_str());
59+
if (!m_dir.empty()) rmdir(m_dir.c_str());
60+
}
61+
62+
int release()
63+
{
64+
int fd = m_fd;
65+
m_fd = -1;
66+
return fd;
67+
}
68+
69+
int Connect() const
70+
{
71+
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
72+
KJ_REQUIRE(fd >= 0);
73+
74+
sockaddr_un addr{};
75+
addr.sun_family = AF_UNIX;
76+
KJ_REQUIRE(m_path.size() < sizeof(addr.sun_path));
77+
std::strncpy(addr.sun_path, m_path.c_str(), sizeof(addr.sun_path) - 1);
78+
KJ_REQUIRE(connect(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == 0);
79+
return fd;
80+
}
81+
82+
private:
83+
int m_fd{-1};
84+
std::string m_dir;
85+
std::string m_path;
86+
};
87+
88+
class ClientSetup
89+
{
90+
public:
91+
explicit ClientSetup(int fd)
92+
: thread([this, fd] {
93+
EventLoop loop("mptest-client", [](mp::LogMessage log) {
94+
if (log.level == mp::Log::Raise) throw std::runtime_error(log.message);
95+
});
96+
client_promise.set_value(ConnectStream<messages::FooInterface>(loop, fd));
97+
loop.loop();
98+
})
99+
{
100+
client = client_promise.get_future().get();
101+
}
102+
103+
~ClientSetup()
104+
{
105+
client.reset();
106+
thread.join();
107+
}
108+
109+
std::promise<std::unique_ptr<ProxyClient<messages::FooInterface>>> client_promise;
110+
std::unique_ptr<ProxyClient<messages::FooInterface>> client;
111+
std::thread thread;
112+
};
113+
114+
class ListenSetup
115+
{
116+
public:
117+
ListenSetup()
118+
: thread([this] {
119+
EventLoop loop("mptest-server", [this](mp::LogMessage log) {
120+
if (log.level == mp::Log::Raise) throw std::runtime_error(log.message);
121+
if (log.message.find("IPC server: socket connected.") != std::string::npos) {
122+
std::lock_guard<std::mutex> lock(counter_mutex);
123+
++connected_count;
124+
counter_cv.notify_all();
125+
}
126+
});
127+
FooImplementation foo;
128+
ListenConnections<messages::FooInterface>(loop, listener.release(), foo);
129+
ready_promise.set_value();
130+
loop.loop();
131+
})
132+
{
133+
ready_promise.get_future().get();
134+
}
135+
136+
~ListenSetup()
137+
{
138+
thread.join();
139+
}
140+
141+
void WaitForConnectedCount(size_t expected_count)
142+
{
143+
std::unique_lock<std::mutex> lock(counter_mutex);
144+
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
145+
const bool matched = counter_cv.wait_until(lock, deadline, [&] {
146+
return connected_count >= expected_count;
147+
});
148+
KJ_REQUIRE(matched);
149+
}
150+
151+
UnixListener listener;
152+
std::promise<void> ready_promise;
153+
std::thread thread;
154+
std::mutex counter_mutex;
155+
std::condition_variable counter_cv;
156+
size_t connected_count{0};
157+
};
158+
159+
KJ_TEST("ListenConnections accepts incoming connections")
160+
{
161+
ListenSetup setup;
162+
auto client = std::make_unique<ClientSetup>(setup.listener.Connect());
163+
164+
setup.WaitForConnectedCount(1);
165+
KJ_EXPECT(client->client->add(1, 2) == 3);
166+
}
167+
168+
} // namespace
169+
} // namespace test
170+
} // namespace mp

test/mp/test/test.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
#include <mp/test/foo.capnp.h>
66
#include <mp/test/foo.capnp.proxy.h>
77

8+
#include <string.h> // NOLINT(modernize-deprecated-headers)
9+
810
#include <atomic>
911
#include <capnp/capability.h>
1012
#include <capnp/rpc.h>
1113
#include <cassert>
1214
#include <chrono>
1315
#include <condition_variable>
1416
#include <cstdint>
15-
#include <cstring>
1617
#include <functional>
1718
#include <future>
1819
#include <kj/async.h>

0 commit comments

Comments
 (0)