1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
|
/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
It is considered beerware. Prost. Skol. Cheers or whatever.
$id$ */
/* System */
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
/* 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);
}
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();
}
|