From f2f5b0450d4b7269bafdfb1bd1788303f6d8ce7b Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Mon, 26 Oct 2026 19:05:22 +0100 Subject: Add poll/epoll/kqueue wrapper to wait for events --- tests/t_event.c | 222 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 tests/t_event.c (limited to 'tests/t_event.c') diff --git a/tests/t_event.c b/tests/t_event.c new file mode 100644 index 0000000..306f8cd --- /dev/null +++ b/tests/t_event.c @@ -0,0 +1,222 @@ +/* This software was written by Dirk Engling + It is considered beerware. Prost. Skol. Cheers or whatever. + + $id$ */ + +/* System */ +#include +#include +#include +#include + +/* Opentracker */ +#include "ot_event.h" +#include "test.h" + +/* Against real socket pairs: only the kernel can say whether a descriptor is + ready, so there is nothing useful to mock here. */ + +#define EVENTS 16 + +static int sv[2]; +static ot_event ready[EVENTS]; + +static void pair_open(void) { + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) + sv[0] = sv[1] = -1; +} + +static void pair_close(void) { + if (sv[0] != -1) + close(sv[0]); + if (sv[1] != -1) + close(sv[1]); + sv[0] = sv[1] = -1; +} + +static int wait_for(int fd, int what, int timeout_ms, uint32_t *generation) { + int count = event_wait(ready, EVENTS, timeout_ms), i; + + for (i = 0; i < count; ++i) + if (ready[i].fd == fd && (ready[i].what & what)) { + if (generation) + *generation = ready[i].generation; + return 1; + } + return 0; +} + +static unsigned long elapsed_ms(struct timeval *since) { + struct timeval now; + + gettimeofday(&now, NULL); + return (unsigned long)((now.tv_sec - since->tv_sec) * 1000 + (now.tv_usec - since->tv_usec) / 1000); +} + +static void case_readable(void) { + uint32_t generation = 0; + + test_case("a descriptor with something to read is reported, with its generation"); + pair_open(); + CHECK(sv[0] != -1); + CHECK_INT(event_add(sv[0], EVENT_READ, 4711), 0); + CHECK_INT(write(sv[1], "x", 1), 1); + CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, &generation), 1); + CHECK_INT(generation, 4711); + + test_case("and keeps being reported until it is read"); + CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, NULL), 1); + { + char scratch[4]; + CHECK_INT(read(sv[0], scratch, sizeof(scratch)), 1); + } + CHECK_INT(wait_for(sv[0], EVENT_READ, 50, NULL), 0); + + CHECK_INT(event_del(sv[0]), 0); + pair_close(); +} + +static void case_writable(void) { + test_case("a descriptor with room to write is reported"); + pair_open(); + CHECK(sv[0] != -1); + CHECK_INT(event_add(sv[0], EVENT_WRITE, 1), 0); + CHECK_INT(wait_for(sv[0], EVENT_WRITE, 1000, NULL), 1); + + test_case("and is not, once we say we no longer care"); + CHECK_INT(event_modify(sv[0], EVENT_READ, 1), 0); + CHECK_INT(wait_for(sv[0], EVENT_WRITE, 50, NULL), 0); + + CHECK_INT(event_del(sv[0]), 0); + pair_close(); +} + +static void case_interest_is_separate(void) { + test_case("asking only about writing does not report reading"); + pair_open(); + CHECK(sv[0] != -1); + CHECK_INT(event_add(sv[0], EVENT_WRITE, 1), 0); + CHECK_INT(write(sv[1], "x", 1), 1); + CHECK_INT(wait_for(sv[0], EVENT_READ, 50, NULL), 0); + + test_case("until we ask about it"); + CHECK_INT(event_modify(sv[0], EVENT_READ | EVENT_WRITE, 1), 0); + CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, NULL), 1); + + CHECK_INT(event_del(sv[0]), 0); + pair_close(); +} + +static void case_removed_is_silent(void) { + test_case("a removed descriptor is not reported again"); + pair_open(); + CHECK(sv[0] != -1); + CHECK_INT(event_add(sv[0], EVENT_READ, 1), 0); + CHECK_INT(write(sv[1], "x", 1), 1); + CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, NULL), 1); + CHECK_INT(event_del(sv[0]), 0); + CHECK_INT(wait_for(sv[0], EVENT_READ, 50, NULL), 0); + pair_close(); +} + +static void case_generations_do_not_mix(void) { + int other[2]; + uint32_t generation = 0; + + test_case("two descriptors carry their own generations"); + pair_open(); + CHECK(sv[0] != -1); + CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, other), 0); + CHECK_INT(event_add(sv[0], EVENT_READ, 100), 0); + CHECK_INT(event_add(other[0], EVENT_READ, 200), 0); + CHECK_INT(write(sv[1], "x", 1), 1); + CHECK_INT(write(other[1], "y", 1), 1); + + CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, &generation), 1); + CHECK_INT(generation, 100); + CHECK_INT(wait_for(other[0], EVENT_READ, 1000, &generation), 1); + CHECK_INT(generation, 200); + + test_case("and re-registering one changes only its own"); + CHECK_INT(event_modify(sv[0], EVENT_READ, 101), 0); + CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, &generation), 1); + CHECK_INT(generation, 101); + CHECK_INT(wait_for(other[0], EVENT_READ, 1000, &generation), 1); + CHECK_INT(generation, 200); + + event_del(sv[0]); + event_del(other[0]); + close(other[0]); + close(other[1]); + pair_close(); +} + +/* Removing one of several has to leave the rest addressable: a backend that + keeps its descriptors packed moves the last one into the gap, and has to + remember where it went. */ +static void case_delete_from_the_middle(void) { + int pair[3][2]; + uint32_t generation = 0; + int i; + + test_case("deleting one of three leaves the others reported correctly"); + for (i = 0; i < 3; ++i) { + CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, pair[i]), 0); + CHECK_INT(event_add(pair[i][0], EVENT_READ, (uint32_t)(10 + i)), 0); + } + CHECK_INT(event_del(pair[1][0]), 0); + + for (i = 0; i < 3; i += 2) { + CHECK_INT(write(pair[i][1], "x", 1), 1); + CHECK_INT(wait_for(pair[i][0], EVENT_READ, 1000, &generation), 1); + CHECK_INT(generation, 10 + i); + } + + test_case("and the survivors can still be changed and removed"); + CHECK_INT(event_modify(pair[2][0], EVENT_READ, 99), 0); + CHECK_INT(wait_for(pair[2][0], EVENT_READ, 1000, &generation), 1); + CHECK_INT(generation, 99); + CHECK_INT(event_del(pair[0][0]), 0); + CHECK_INT(event_del(pair[2][0]), 0); + CHECK_INT(wait_for(pair[2][0], EVENT_READ, 50, NULL), 0); + + for (i = 0; i < 3; ++i) + close(pair[i][0]), close(pair[i][1]); +} + +static void case_timeout(void) { + struct timeval started; + + test_case("waiting on nothing comes back empty when the time is up"); + gettimeofday(&started, NULL); + CHECK_INT(event_wait(ready, EVENTS, 200), 0); + { + unsigned long took = elapsed_ms(&started); + /* Scheduling makes the upper bound generous. The point is that it waited + rather than returning at once, and that it came back at all */ + CHECK(took >= 150); + CHECK(took < 5000); + } +} + +static void case_unknown_descriptor(void) { + test_case("changing or removing one that was never added is refused"); + CHECK_INT(event_modify(4096, EVENT_READ, 1), -1); + CHECK_INT(event_del(4096), -1); +} + +void test_event(void) { + CHECK_INT(event_init(), 0); + CHECK(event_mechanism() != NULL); + + case_readable(); + case_writable(); + case_interest_is_separate(); + case_removed_is_silent(); + case_generations_do_not_mix(); + case_delete_from_the_middle(); + case_timeout(); + case_unknown_descriptor(); + + event_deinit(); +} -- cgit v1.3