]> git.ipfire.org Git - thirdparty/kmod.git/blob - testsuite/testsuite.c
testsuite: remind users to build tools
[thirdparty/kmod.git] / testsuite / testsuite.c
1 #include <errno.h>
2 #include <fcntl.h>
3 #include <getopt.h>
4 #include <limits.h>
5 #include <stdio.h>
6 #include <stdarg.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <unistd.h>
10 #include <sys/epoll.h>
11 #include <sys/prctl.h>
12 #include <sys/wait.h>
13
14 #include "testsuite.h"
15
16 static const char *ANSI_HIGHLIGHT_GREEN_ON = "\x1B[1;32m";
17 static const char *ANSI_HIGHLIGHT_RED_ON = "\x1B[1;31m";
18 static const char *ANSI_HIGHLIGHT_OFF = "\x1B[0m";
19
20 static const char *progname;
21 static int oneshot = 0;
22 static const char options_short[] = "lhn";
23 static const struct option options[] = {
24 { "list", no_argument, 0, 'l' },
25 { "help", no_argument, 0, 'h' },
26 { NULL, 0, 0, 0 }
27 };
28
29 struct _env_config {
30 const char *key;
31 const char *ldpreload;
32 } env_config[_TC_LAST] = {
33 [TC_UNAME_R] = { S_TC_UNAME_R, ABS_TOP_BUILDDIR "/testsuite/uname.so" },
34 [TC_ROOTFS] = { S_TC_ROOTFS, ABS_TOP_BUILDDIR "/testsuite/path.so" },
35 };
36
37 static void help(void)
38 {
39 const struct option *itr;
40 const char *itr_short;
41
42 printf("Usage:\n"
43 "\t%s [options] <test>\n"
44 "Options:\n", basename(progname));
45
46 for (itr = options, itr_short = options_short;
47 itr->name != NULL; itr++, itr_short++)
48 printf("\t-%c, --%s\n", *itr_short, itr->name);
49 }
50
51 static void test_list(const struct test *tests[])
52 {
53 size_t i;
54
55 printf("Available tests:\n");
56 for (i = 0; tests[i] != NULL; i++)
57 printf("\t%s, %s\n", tests[i]->name, tests[i]->description);
58 }
59
60 int test_init(int argc, char *const argv[], const struct test *tests[])
61 {
62 progname = argv[0];
63
64 for (;;) {
65 int c, idx = 0;
66 c = getopt_long(argc, argv, options_short, options, &idx);
67 if (c == -1)
68 break;
69 switch (c) {
70 case 'l':
71 test_list(tests);
72 return 0;
73 case 'h':
74 help();
75 return 0;
76 case 'n':
77 oneshot = 1;
78 break;
79 case '?':
80 return -1;
81 default:
82 ERR("unexpected getopt_long() value %c\n", c);
83 return -1;
84 }
85 }
86
87 if (isatty(STDOUT_FILENO) == 0) {
88 ANSI_HIGHLIGHT_OFF = "";
89 ANSI_HIGHLIGHT_RED_ON = "";
90 ANSI_HIGHLIGHT_GREEN_ON = "";
91 }
92
93 return optind;
94 }
95
96 const struct test *test_find(const struct test *tests[], const char *name)
97 {
98 size_t i;
99
100 for (i = 0; tests[i] != NULL; i++) {
101 if (strcmp(tests[i]->name, name) == 0)
102 return tests[i];
103 }
104
105 return NULL;
106 }
107
108 static int test_spawn_test(const struct test *t)
109 {
110 const char *const args[] = { progname, "-n", t->name, NULL };
111
112 execv(progname, (char *const *) args);
113
114 ERR("failed to spawn %s for %s: %m\n", progname, t->name);
115 return EXIT_FAILURE;
116 }
117
118 static int test_run_spawned(const struct test *t)
119 {
120 int err = t->func(t);
121 exit(err);
122
123 return EXIT_FAILURE;
124 }
125
126 int test_spawn_prog(const char *prog, const char *const args[])
127 {
128 execv(prog, (char *const *) args);
129
130 ERR("failed to spawn %s\n", prog);
131 ERR("did you forget to build tools?\n");
132 return EXIT_FAILURE;
133 }
134
135 static void test_export_environ(const struct test *t)
136 {
137 char *preload = NULL;
138 size_t preloadlen = 0;
139 size_t i;
140
141 unsetenv("LD_PRELOAD");
142
143 for (i = 0; i < _TC_LAST; i++) {
144 const char *ldpreload;
145 size_t ldpreloadlen;
146 char *tmp;
147
148 if (t->config[i] == NULL)
149 continue;
150
151 setenv(env_config[i].key, t->config[i], 1);
152
153 ldpreload = env_config[i].ldpreload;
154 ldpreloadlen = strlen(ldpreload);
155 tmp = realloc(preload, preloadlen + 2 + ldpreloadlen);
156 if (tmp == NULL) {
157 ERR("oom: test_export_environ()\n");
158 return;
159 }
160 preload = tmp;
161
162 if (preloadlen > 0)
163 preload[preloadlen++] = ' ';
164 memcpy(preload + preloadlen, ldpreload, ldpreloadlen);
165 preloadlen += ldpreloadlen;
166 preload[preloadlen] = '\0';
167 }
168
169 if (preload != NULL)
170 setenv("LD_PRELOAD", preload, 1);
171
172 free(preload);
173 }
174
175 static inline int test_run_child(const struct test *t, int fdout[2],
176 int fderr[2])
177 {
178 /* kill child if parent dies */
179 prctl(PR_SET_PDEATHSIG, SIGTERM);
180
181 test_export_environ(t);
182
183 /* Close read-fds and redirect std{out,err} to the write-fds */
184 if (t->output.stdout != NULL) {
185 close(fdout[0]);
186 if (dup2(fdout[1], STDOUT_FILENO) < 0) {
187 ERR("could not redirect stdout to pipe: %m");
188 exit(EXIT_FAILURE);
189 }
190 }
191
192 if (t->output.stderr != NULL) {
193 close(fderr[0]);
194 if (dup2(fderr[1], STDERR_FILENO) < 0) {
195 ERR("could not redirect stdout to pipe: %m");
196 exit(EXIT_FAILURE);
197 }
198 }
199
200 if (t->need_spawn)
201 return test_spawn_test(t);
202 else
203 return test_run_spawned(t);
204 }
205
206 static inline bool test_run_parent_check_outputs(const struct test *t,
207 int fdout, int fderr)
208 {
209 struct epoll_event ep_outpipe, ep_errpipe;
210 int err, fd_ep, fd_matchout = -1, fd_matcherr = -1;
211
212 if (t->output.stdout == NULL && t->output.stderr == NULL)
213 return true;
214
215 fd_ep = epoll_create1(EPOLL_CLOEXEC);
216 if (fd_ep < 0) {
217 ERR("could not create epoll fd: %m\n");
218 return false;
219 }
220
221 if (t->output.stdout != NULL) {
222 fd_matchout = open(t->output.stdout, O_RDONLY);
223 if (fd_matchout < 0) {
224 err = -errno;
225 ERR("could not open %s for read: %m\n",
226 t->output.stdout);
227 goto out;
228 }
229 memset(&ep_outpipe, 0, sizeof(struct epoll_event));
230 ep_outpipe.events = EPOLLIN;
231 ep_outpipe.data.ptr = &fdout;
232 if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fdout, &ep_outpipe) < 0) {
233 err = -errno;
234 ERR("could not add fd to epoll: %m\n");
235 goto out;
236 }
237 } else
238 fdout = -1;
239
240 if (t->output.stderr != NULL) {
241 fd_matcherr = open(t->output.stderr, O_RDONLY);
242 if (fd_matcherr < 0) {
243 err = -errno;
244 ERR("could not open %s for read: %m\n",
245 t->output.stderr);
246 goto out;
247
248 }
249 memset(&ep_errpipe, 0, sizeof(struct epoll_event));
250 ep_errpipe.events = EPOLLIN;
251 ep_errpipe.data.ptr = &fderr;
252 if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fderr, &ep_errpipe) < 0) {
253 err = -errno;
254 ERR("could not add fd to epoll: %m\n");
255 goto out;
256 }
257 } else
258 fderr = -1;
259
260 for (err = 0; fdout >= 0 || fderr >= 0;) {
261 int fdcount, i;
262 struct epoll_event ev[4];
263
264 fdcount = epoll_wait(fd_ep, ev, 4, -1);
265 if (fdcount < 0) {
266 if (errno == EINTR)
267 continue;
268 err = -errno;
269 ERR("could not poll: %m\n");
270 goto out;
271 }
272
273 for (i = 0; i < fdcount; i++) {
274 int *fd = ev[i].data.ptr;
275
276 if (ev[i].events & EPOLLIN) {
277 ssize_t r, done = 0;
278 char buf[4096];
279 char bufmatch[4096];
280 int fd_match;
281
282 /*
283 * compare the output from child with the one
284 * saved as correct
285 */
286
287 r = read(*fd, buf, sizeof(buf) - 1);
288 if (r <= 0)
289 continue;
290
291 if (*fd == fdout)
292 fd_match = fd_matchout;
293 else
294 fd_match = fd_matcherr;
295
296 for (;;) {
297 int rmatch = read(fd_match,
298 bufmatch + done, r - done);
299 if (rmatch == 0)
300 break;
301
302 if (rmatch < 0) {
303 if (errno == EINTR)
304 continue;
305 err = -errno;
306 ERR("could not read match fd %d\n",
307 fd_match);
308 goto out;
309 }
310
311 done += rmatch;
312 }
313
314 buf[r] = '\0';
315 bufmatch[r] = '\0';
316 if (strcmp(buf, bufmatch) != 0) {
317 ERR("Outputs do not match on %s:\n",
318 fd_match == fd_matchout ? "stdout" : "stderr");
319 ERR("correct:\n%s\n", bufmatch);
320 ERR("wrong:\n%s\n", buf);
321 err = -1;
322 goto out;
323 }
324 } else if (ev[i].events & EPOLLHUP) {
325 if (epoll_ctl(fd_ep, EPOLL_CTL_DEL,
326 *fd, NULL) < 0) {
327 ERR("could not remove fd %d from epoll: %m\n",
328 *fd);
329 }
330 *fd = -1;
331 }
332 }
333
334 }
335 out:
336 if (fd_matchout >= 0)
337 close(fd_matchout);
338 if (fd_matcherr >= 0)
339 close(fd_matcherr);
340 if (fd_ep >= 0)
341 close(fd_ep);
342 return err == 0;
343 }
344
345 static inline int test_run_parent(const struct test *t, int fdout[2],
346 int fderr[2])
347 {
348 pid_t pid;
349 int err;
350 bool matchout;
351
352 /* Close write-fds */
353 if (t->output.stdout != NULL)
354 close(fdout[1]);
355 if (t->output.stderr != NULL)
356 close(fderr[1]);
357
358 matchout = test_run_parent_check_outputs(t, fdout[0], fderr[0]);
359
360 /*
361 * break pipe on the other end: either child already closed or we want
362 * to stop it
363 */
364 if (t->output.stdout != NULL)
365 close(fdout[0]);
366 if (t->output.stderr != NULL)
367 close(fderr[0]);
368
369 do {
370 pid = wait(&err);
371 if (pid == -1) {
372 ERR("error waitpid(): %m\n");
373 return EXIT_FAILURE;
374 }
375 } while (!WIFEXITED(err) && !WIFSIGNALED(err));
376
377 if (WIFEXITED(err)) {
378 if (WEXITSTATUS(err) != 0)
379 ERR("'%s' [%u] exited with return code %d\n",
380 t->name, pid, WEXITSTATUS(err));
381 else
382 LOG("'%s' [%u] exited with return code %d\n",
383 t->name, pid, WEXITSTATUS(err));
384 } else if (WIFSIGNALED(err)) {
385 ERR("'%s' [%u] terminated by signal %d (%s)\n", t->name, pid,
386 WTERMSIG(err), strsignal(WTERMSIG(err)));
387 }
388
389 if (err == 0) {
390 if (matchout)
391 LOG("%sPASSED%s: %s\n",
392 ANSI_HIGHLIGHT_GREEN_ON, ANSI_HIGHLIGHT_OFF,
393 t->name);
394 else {
395 ERR("%sFAILED%s: exit ok but outputs do not match: %s\n",
396 ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF,
397 t->name);
398 err = EXIT_FAILURE;
399 }
400 } else
401 ERR("%sFAILED%s: %s\n",
402 ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF,
403 t->name);
404
405 return err;
406 }
407
408 int test_run(const struct test *t)
409 {
410 pid_t pid;
411 int fdout[2];
412 int fderr[2];
413
414 if (t->need_spawn && oneshot)
415 test_run_spawned(t);
416
417 if (t->output.stdout != NULL) {
418 if (pipe(fdout) != 0) {
419 ERR("could not create out pipe for %s\n", t->name);
420 return EXIT_FAILURE;
421 }
422 }
423
424 if (t->output.stderr != NULL) {
425 if (pipe(fderr) != 0) {
426 ERR("could not create err pipe for %s\n", t->name);
427 return EXIT_FAILURE;
428 }
429 }
430
431 LOG("running %s, in forked context\n", t->name);
432
433 pid = fork();
434 if (pid < 0) {
435 ERR("could not fork(): %m\n");
436 LOG("FAILED: %s\n", t->name);
437 return EXIT_FAILURE;
438 }
439
440 if (pid > 0)
441 return test_run_parent(t, fdout, fderr);
442
443 return test_run_child(t, fdout, fderr);
444 }