Skip to content

Commit e48a6b0

Browse files
tests: mock sd_event timers with trio MockClock
In tests, replace all sd_event timer usages with trio.testing.MockClock backed I/O sources. Because we still have real timeouts (D-Bus call to mctpd subprocess, mctpd SO_RCVTIMEO wait, ...), autojump_threshold (how much to wait before skipping Trio timeouts) is set to a reasonable value to take that into account. Signed-off-by: Khang D Nguyen <[email protected]>
1 parent c508744 commit e48a6b0

File tree

9 files changed

+192
-13
lines changed

9 files changed

+192
-13
lines changed

meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ executable('mctp',
6565
mctp_test = executable('test-mctp',
6666
sources: ['src/mctp.c'] + netlink_sources + util_sources + test_ops_sources,
6767
include_directories: include_directories('src'),
68+
dependencies: [libsystemd],
6869
)
6970

7071
executable('mctp-req',

src/mctp-ops.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <unistd.h>
1111
#include <linux/netlink.h>
12+
#include <systemd/sd-event.h>
1213
#include <err.h>
1314

1415
#include "mctp.h"
@@ -74,6 +75,10 @@ const struct mctp_ops mctp_ops = {
7475
.recvfrom = mctp_op_recvfrom,
7576
.close = mctp_op_close,
7677
},
78+
.sd_event = {
79+
.add_time_relative = sd_event_add_time_relative,
80+
.source_set_time_relative = sd_event_source_set_time_relative,
81+
},
7782
.bug_warn = mctp_bug_warn,
7883
};
7984

src/mctp-ops.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
*/
88
#pragma once
99

10+
#include <stdint.h>
1011
#include <sys/socket.h>
1112
#include <stdarg.h>
13+
#include <systemd/sd-event.h>
1214

1315
#define _GNU_SOURCE
1416

@@ -24,9 +26,19 @@ struct socket_ops {
2426
int (*close)(int sd);
2527
};
2628

29+
struct sd_event_ops {
30+
int (*add_time_relative)(sd_event *e, sd_event_source **ret,
31+
clockid_t clock, uint64_t usec,
32+
uint64_t accuracy,
33+
sd_event_time_handler_t callback,
34+
void *userdata);
35+
int (*source_set_time_relative)(sd_event_source *s, uint64_t usec);
36+
};
37+
2738
struct mctp_ops {
2839
struct socket_ops mctp;
2940
struct socket_ops nl;
41+
struct sd_event_ops sd_event;
3042
void (*bug_warn)(const char *fmt, va_list args);
3143
};
3244

src/mctpd.c

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -497,8 +497,9 @@ static int wait_fd_timeout(int fd, short events, uint64_t timeout_usec)
497497
if (rc < 0)
498498
goto out;
499499

500-
rc = sd_event_add_time_relative(ev, NULL, CLOCK_MONOTONIC, timeout_usec,
501-
0, cb_exit_loop_timeout, NULL);
500+
rc = mctp_ops.sd_event.add_time_relative(ev, NULL, CLOCK_MONOTONIC,
501+
timeout_usec, 0,
502+
cb_exit_loop_timeout, NULL);
502503
if (rc < 0)
503504
goto out;
504505

@@ -3239,8 +3240,8 @@ static int peer_endpoint_recover(sd_event_source *s, uint64_t usec,
32393240

32403241
reschedule:
32413242
if (peer->recovery.npolls > 0) {
3242-
rc = sd_event_source_set_time_relative(peer->recovery.source,
3243-
peer->recovery.delay);
3243+
rc = mctp_ops.sd_event.source_set_time_relative(
3244+
peer->recovery.source, peer->recovery.delay);
32443245
if (rc >= 0) {
32453246
rc = sd_event_source_set_enabled(peer->recovery.source,
32463247
SD_EVENT_ONESHOT);
@@ -3275,7 +3276,7 @@ static int method_endpoint_recover(sd_bus_message *call, void *data,
32753276
peer->recovery.npolls = MCTP_I2C_TSYM_MN1_MIN + 1;
32763277
peer->recovery.delay =
32773278
(MCTP_I2C_TSYM_TRECLAIM_MIN_US / 2) - ctx->mctp_timeout;
3278-
rc = sd_event_add_time_relative(
3279+
rc = mctp_ops.sd_event.add_time_relative(
32793280
ctx->event, &peer->recovery.source, CLOCK_MONOTONIC, 0,
32803281
ctx->mctp_timeout, peer_endpoint_recover, peer);
32813282
if (rc < 0) {

tests/conftest.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import pytest
55
import asyncdbus
6+
import trio.testing
67

78
import mctpenv
89

@@ -35,3 +36,10 @@ async def mctpd(nursery, dbus, sysnet, config):
3536
@pytest.fixture
3637
async def mctp(nursery, sysnet):
3738
return mctpenv.MctpWrapper(nursery, sysnet)
39+
40+
@pytest.fixture
41+
def autojump_clock():
42+
"""
43+
Custom autojump clock with a reasonable threshold for non-time I/O waits
44+
"""
45+
return trio.testing.MockClock(autojump_threshold=0.01)

tests/mctp-ops-test.c

Lines changed: 119 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#define _GNU_SOURCE
99

10+
#include <assert.h>
1011
#include <err.h>
1112
#include <errno.h>
1213
#include <stdlib.h>
@@ -18,6 +19,7 @@
1819
#include <sys/socket.h>
1920
#include <sys/un.h>
2021

22+
#include <systemd/sd-event.h>
2123
#include <linux/netlink.h>
2224

2325
#include "mctp-ops.h"
@@ -38,10 +40,12 @@ static int mctp_op_socket(int type)
3840
struct iovec iov;
3941
int rc, var, sd;
4042

41-
if (type == AF_MCTP)
43+
if (type == CONTROL_OP_SOCKET_MCTP)
4244
req.type = CONTROL_OP_SOCKET_MCTP;
43-
else if (type == AF_NETLINK)
45+
else if (type == CONTROL_OP_SOCKET_NL)
4446
req.type = CONTROL_OP_SOCKET_NL;
47+
else if (type == CONTROL_OP_TIMER)
48+
req.type = CONTROL_OP_TIMER;
4549
else
4650
errx(EXIT_FAILURE, "invalid socket type?");
4751

@@ -72,12 +76,12 @@ static int mctp_op_socket(int type)
7276

7377
static int mctp_op_mctp_socket(void)
7478
{
75-
return mctp_op_socket(AF_MCTP);
79+
return mctp_op_socket(CONTROL_OP_SOCKET_MCTP);
7680
}
7781

7882
static int mctp_op_netlink_socket(void)
7983
{
80-
return mctp_op_socket(AF_NETLINK);
84+
return mctp_op_socket(CONTROL_OP_SOCKET_NL);
8185
}
8286

8387
static int mctp_op_bind(int sd, struct sockaddr *addr, socklen_t addrlen)
@@ -221,6 +225,113 @@ static void mctp_bug_warn(const char *fmt, va_list args)
221225
abort();
222226
}
223227

228+
struct wrapped_time_userdata {
229+
sd_event_time_handler_t callback;
230+
void *userdata;
231+
};
232+
233+
int wrapped_time_callback(sd_event_source *source, int fd, uint revents,
234+
void *userdata)
235+
{
236+
struct wrapped_time_userdata *wrapud = userdata;
237+
uint64_t usec;
238+
ssize_t rc;
239+
240+
rc = read(fd, &usec, sizeof(usec));
241+
if (rc != 8)
242+
errx(EXIT_FAILURE, "ops protocol error");
243+
244+
rc = wrapud->callback(source, usec, wrapud->userdata);
245+
warnx("%ld", rc);
246+
247+
return 0;
248+
}
249+
250+
void wrapped_time_destroy(void *wrapud)
251+
{
252+
free(wrapud);
253+
}
254+
255+
static int mctp_op_sd_event_add_time_relative(
256+
sd_event *e, sd_event_source **ret, clockid_t clock, uint64_t usec,
257+
uint64_t accuracy, sd_event_time_handler_t callback, void *userdata)
258+
{
259+
struct wrapped_time_userdata *wrapud = NULL;
260+
sd_event_source *source = NULL;
261+
int sd = -1;
262+
int rc = 0;
263+
264+
sd = mctp_op_socket(CONTROL_OP_TIMER);
265+
if (sd < 0)
266+
return -errno;
267+
268+
rc = write(sd, &usec, sizeof(usec));
269+
if (rc != 8)
270+
errx(EXIT_FAILURE, "ops protocol error");
271+
272+
wrapud = malloc(sizeof(*wrapud));
273+
if (!wrapud) {
274+
rc = -ENOMEM;
275+
goto fail;
276+
}
277+
278+
wrapud->callback = callback;
279+
wrapud->userdata = userdata;
280+
281+
rc = sd_event_add_io(e, &source, sd, EPOLLIN, wrapped_time_callback,
282+
wrapud);
283+
if (rc < 0)
284+
goto fail;
285+
286+
rc = sd_event_source_set_destroy_callback(source, wrapped_time_destroy);
287+
if (rc < 0)
288+
goto fail;
289+
290+
wrapud = NULL;
291+
292+
rc = sd_event_source_set_io_fd_own(source, 1);
293+
if (rc < 0)
294+
goto fail;
295+
296+
sd = -1;
297+
298+
rc = sd_event_source_set_enabled(source, SD_EVENT_ONESHOT);
299+
if (rc < 0)
300+
goto fail;
301+
302+
if (!ret) {
303+
rc = sd_event_source_set_floating(source, 1);
304+
if (rc < 0)
305+
goto fail;
306+
307+
sd_event_source_unref(source);
308+
} else {
309+
*ret = source;
310+
}
311+
312+
return 0;
313+
314+
fail:
315+
if (sd > 0)
316+
close(sd);
317+
free(wrapud);
318+
sd_event_source_disable_unref(*ret);
319+
return rc;
320+
}
321+
322+
static int mctp_op_sd_event_source_set_time_relative(sd_event_source *s,
323+
uint64_t usec)
324+
{
325+
int sd = sd_event_source_get_io_fd(s);
326+
ssize_t rc;
327+
328+
rc = write(sd, &usec, sizeof(usec));
329+
if (rc != 8)
330+
errx(EXIT_FAILURE, "ops protocol error");
331+
332+
return 0;
333+
}
334+
224335
const struct mctp_ops mctp_ops = {
225336
.mctp = {
226337
.socket = mctp_op_mctp_socket,
@@ -238,6 +349,10 @@ const struct mctp_ops mctp_ops = {
238349
.recvfrom = mctp_op_recvfrom,
239350
.close = mctp_op_close,
240351
},
352+
.sd_event = {
353+
.add_time_relative = mctp_op_sd_event_add_time_relative,
354+
.source_set_time_relative = mctp_op_sd_event_source_set_time_relative,
355+
},
241356
.bug_warn = mctp_bug_warn,
242357
};
243358

tests/mctpenv/__init__.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import array
33
import enum
44
import errno
5+
import math
56
import os
67
import signal
78
import socket
@@ -1113,6 +1114,32 @@ async def notify_delroute(self, route):
11131114
await self._notify_route(route, rtnl.RTM_DELROUTE);
11141115

11151116

1117+
class TimerSocket(BaseSocket):
1118+
def __init__(self, sock):
1119+
super().__init__(sock)
1120+
self.delay = sys.maxsize
1121+
1122+
async def run(self):
1123+
while True:
1124+
try:
1125+
with trio.move_on_after(self.delay / 1000000) as scope:
1126+
# now = math.floor(trio.current_time() * 1000000)
1127+
# await self.sock.send(struct.pack('@Q', now))
1128+
data = await self.sock.recv(8)
1129+
if len(data) == 0:
1130+
break
1131+
1132+
(next_delay,) = struct.unpack('@Q', data)
1133+
self.delay = next_delay
1134+
1135+
# timed out
1136+
if scope.cancelled_caught:
1137+
await self.sock.send(struct.pack('@Q', math.floor(trio.current_time() * 1000000)))
1138+
self.delay = sys.maxsize
1139+
except ConnectionResetError as ex:
1140+
break
1141+
1142+
11161143
async def send_fd(sock, fd):
11171144
fdarray = array.array("i", [fd])
11181145
await sock.sendmsg([b'x'], [
@@ -1158,6 +1185,14 @@ async def handle_control(self, nursery):
11581185
remote.close()
11591186
nursery.start_soon(nl.run)
11601187

1188+
elif op == 0x03:
1189+
# Timer socket
1190+
(local, remote) = self.socketpair()
1191+
sd = TimerSocket(local)
1192+
await send_fd(self.sock_local, remote.fileno())
1193+
remote.close()
1194+
nursery.start_soon(sd.run)
1195+
11611196
else:
11621197
print(f"unknown op {op}")
11631198

tests/test-proto.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ enum {
99
CONTROL_OP_INIT,
1010
CONTROL_OP_SOCKET_MCTP,
1111
CONTROL_OP_SOCKET_NL,
12+
CONTROL_OP_TIMER,
1213
};
1314

1415
struct control_msg_req {

tests/test_mctpd.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ async def _introspect_path_recursive(dbus, path, node_set):
5050

5151
return dups
5252

53+
5354
""" Test that the dbus object tree is sensible: we can introspect all
5455
objects, and that there are no duplicates
5556
"""
@@ -187,7 +188,7 @@ def ep_connectivity_changed(iface, changed, invalidated):
187188
# to transition 'Connectivity' to 'Available', which is a test failure.
188189
assert not expected.cancelled_caught
189190

190-
async def test_recover_endpoint_removed(dbus, mctpd):
191+
async def test_recover_endpoint_removed(dbus, mctpd, autojump_clock):
191192
iface = mctpd.system.interfaces[0]
192193
dev = mctpd.network.endpoints[0]
193194
mctp = await dbus.get_proxy_object(MCTPD_C, MCTPD_MCTP_P)
@@ -224,7 +225,7 @@ def ep_removed(ep_path, interfaces):
224225

225226
assert not expected.cancelled_caught
226227

227-
async def test_recover_endpoint_reset(dbus, mctpd):
228+
async def test_recover_endpoint_reset(dbus, mctpd, autojump_clock):
228229
iface = mctpd.system.interfaces[0]
229230
dev = mctpd.network.endpoints[0]
230231
mctp = await dbus.get_proxy_object(MCTPD_C, MCTPD_MCTP_P)
@@ -260,7 +261,7 @@ def ep_connectivity_changed(iface, changed, invalidated):
260261

261262
assert not expected.cancelled_caught
262263

263-
async def test_recover_endpoint_exchange(dbus, mctpd):
264+
async def test_recover_endpoint_exchange(dbus, mctpd, autojump_clock):
264265
iface = mctpd.system.interfaces[0]
265266
dev = mctpd.network.endpoints[0]
266267
mctp = await dbus.get_proxy_object(MCTPD_C, MCTPD_MCTP_P)
@@ -628,7 +629,7 @@ async def test_network_local_eids_none(dbus, mctpd):
628629

629630
assert eids == []
630631

631-
async def test_concurrent_recovery_setup(dbus, mctpd):
632+
async def test_concurrent_recovery_setup(dbus, mctpd, autojump_clock):
632633
iface = mctpd.system.interfaces[0]
633634
mctp_i = await mctpd_mctp_iface_obj(dbus, iface)
634635

0 commit comments

Comments
 (0)