/* This software was written by Dirk Engling It is considered beerware. Prost. Skol. Cheers or whatever. $id$ */ /* System */ #include #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_IN, 4711), 0); CHECK_INT(write(sv[1], "x", 1), 1); CHECK_INT(wait_for(sv[0], EVENT_IN, 1000, &generation), 1); CHECK_INT(generation, 4711); test_case("and keeps being reported until it is read"); CHECK_INT(wait_for(sv[0], EVENT_IN, 1000, NULL), 1); { char scratch[4]; CHECK_INT(read(sv[0], scratch, sizeof(scratch)), 1); } CHECK_INT(wait_for(sv[0], EVENT_IN, 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_OUT, 1), 0); CHECK_INT(wait_for(sv[0], EVENT_OUT, 1000, NULL), 1); test_case("and is not, once we say we no longer care"); CHECK_INT(event_modify(sv[0], EVENT_IN, 1), 0); CHECK_INT(wait_for(sv[0], EVENT_OUT, 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_OUT, 1), 0); CHECK_INT(write(sv[1], "x", 1), 1); CHECK_INT(wait_for(sv[0], EVENT_IN, 50, NULL), 0); test_case("until we ask about it"); CHECK_INT(event_modify(sv[0], EVENT_IN | EVENT_OUT, 1), 0); CHECK_INT(wait_for(sv[0], EVENT_IN, 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_IN, 1), 0); CHECK_INT(write(sv[1], "x", 1), 1); CHECK_INT(wait_for(sv[0], EVENT_IN, 1000, NULL), 1); CHECK_INT(event_del(sv[0]), 0); CHECK_INT(wait_for(sv[0], EVENT_IN, 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_IN, 100), 0); CHECK_INT(event_add(other[0], EVENT_IN, 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_IN, 1000, &generation), 1); CHECK_INT(generation, 100); CHECK_INT(wait_for(other[0], EVENT_IN, 1000, &generation), 1); CHECK_INT(generation, 200); test_case("and re-registering one changes only its own"); CHECK_INT(event_modify(sv[0], EVENT_IN, 101), 0); CHECK_INT(wait_for(sv[0], EVENT_IN, 1000, &generation), 1); CHECK_INT(generation, 101); CHECK_INT(wait_for(other[0], EVENT_IN, 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_IN, (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_IN, 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_IN, 99), 0); CHECK_INT(wait_for(pair[2][0], EVENT_IN, 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_IN, 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_IN, 1), -1); CHECK_INT(event_del(4096), -1); } /* event_close exists so that the mechanisms which drop a closed descriptor by themselves are not asked to do it twice. The one that keeps its own list has to be told, and forgetting would leave the entry there to be reported for ever, so what is checked here is the silence afterwards. */ static void case_close_takes_it_out(void) { int other[2], closed, count, i, strays = 0; test_case("a closed descriptor is gone from the set and really closed"); pair_open(); CHECK(sv[0] != -1); CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, other), 0); CHECK_INT(event_add(sv[0], EVENT_IN, 1), 0); CHECK_INT(event_add(other[0], EVENT_IN, 2), 0); closed = sv[0]; CHECK_INT(event_close(closed), 0); sv[0] = -1; CHECK_INT(fcntl(closed, F_GETFD), -1); /* The survivor is made ready so the wait has something to come back with, and anything left over from the closed one shows up beside it */ CHECK_INT((int)write(other[1], "x", 1), 1); count = event_wait(ready, EVENTS, 200); CHECK(count >= 1); for (i = 0; i < count; ++i) if (ready[i].fd != other[0]) ++strays; CHECK_INT(strays, 0); event_del(other[0]); close(other[0]); close(other[1]); pair_close(); } 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(); case_close_takes_it_out(); event_deinit(); }