]> git.ipfire.org Git - thirdparty/kmod.git/blob - testsuite/testsuite.c
testsuite: create additional pipe to monitor child
[thirdparty/kmod.git] / testsuite / testsuite.c
1 /*
2 * Copyright (C) 2012 ProFUSION embedded systems
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <getopt.h>
21 #include <limits.h>
22 #include <stdio.h>
23 #include <stdarg.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <sys/epoll.h>
28 #include <sys/prctl.h>
29 #include <sys/stat.h>
30 #include <sys/wait.h>
31
32 #include "libkmod-util.h"
33 #include "testsuite.h"
34
35 static const char *ANSI_HIGHLIGHT_GREEN_ON = "\x1B[1;32m";
36 static const char *ANSI_HIGHLIGHT_RED_ON = "\x1B[1;31m";
37 static const char *ANSI_HIGHLIGHT_OFF = "\x1B[0m";
38
39 static const char *progname;
40 static int oneshot = 0;
41 static const char options_short[] = "lhn";
42 static const struct option options[] = {
43 { "list", no_argument, 0, 'l' },
44 { "help", no_argument, 0, 'h' },
45 { NULL, 0, 0, 0 }
46 };
47
48 #define OVERRIDE_LIBDIR ABS_TOP_BUILDDIR "/testsuite/.libs/"
49
50 struct _env_config {
51 const char *key;
52 const char *ldpreload;
53 } env_config[_TC_LAST] = {
54 [TC_UNAME_R] = { S_TC_UNAME_R, OVERRIDE_LIBDIR "uname.so" },
55 [TC_ROOTFS] = { S_TC_ROOTFS, OVERRIDE_LIBDIR "path.so" },
56 [TC_INIT_MODULE_RETCODES] = { S_TC_INIT_MODULE_RETCODES, OVERRIDE_LIBDIR "init_module.so" },
57 [TC_DELETE_MODULE_RETCODES] = { S_TC_DELETE_MODULE_RETCODES, OVERRIDE_LIBDIR "delete_module.so" },
58 };
59
60 static void help(void)
61 {
62 const struct option *itr;
63 const char *itr_short;
64
65 printf("Usage:\n"
66 "\t%s [options] <test>\n"
67 "Options:\n", basename(progname));
68
69 for (itr = options, itr_short = options_short;
70 itr->name != NULL; itr++, itr_short++)
71 printf("\t-%c, --%s\n", *itr_short, itr->name);
72 }
73
74 static void test_list(const struct test *tests[])
75 {
76 size_t i;
77
78 printf("Available tests:\n");
79 for (i = 0; tests[i] != NULL; i++)
80 printf("\t%s, %s\n", tests[i]->name, tests[i]->description);
81 }
82
83 int test_init(int argc, char *const argv[], const struct test *tests[])
84 {
85 progname = argv[0];
86
87 for (;;) {
88 int c, idx = 0;
89 c = getopt_long(argc, argv, options_short, options, &idx);
90 if (c == -1)
91 break;
92 switch (c) {
93 case 'l':
94 test_list(tests);
95 return 0;
96 case 'h':
97 help();
98 return 0;
99 case 'n':
100 oneshot = 1;
101 break;
102 case '?':
103 return -1;
104 default:
105 ERR("unexpected getopt_long() value %c\n", c);
106 return -1;
107 }
108 }
109
110 if (isatty(STDOUT_FILENO) == 0) {
111 ANSI_HIGHLIGHT_OFF = "";
112 ANSI_HIGHLIGHT_RED_ON = "";
113 ANSI_HIGHLIGHT_GREEN_ON = "";
114 }
115
116 return optind;
117 }
118
119 const struct test *test_find(const struct test *tests[], const char *name)
120 {
121 size_t i;
122
123 for (i = 0; tests[i] != NULL; i++) {
124 if (strcmp(tests[i]->name, name) == 0)
125 return tests[i];
126 }
127
128 return NULL;
129 }
130
131 static int test_spawn_test(const struct test *t)
132 {
133 const char *const args[] = { progname, "-n", t->name, NULL };
134
135 execv(progname, (char *const *) args);
136
137 ERR("failed to spawn %s for %s: %m\n", progname, t->name);
138 return EXIT_FAILURE;
139 }
140
141 static int test_run_spawned(const struct test *t)
142 {
143 int err = t->func(t);
144 exit(err);
145
146 return EXIT_FAILURE;
147 }
148
149 int test_spawn_prog(const char *prog, const char *const args[])
150 {
151 execv(prog, (char *const *) args);
152
153 ERR("failed to spawn %s\n", prog);
154 ERR("did you forget to build tools?\n");
155 return EXIT_FAILURE;
156 }
157
158 static void test_export_environ(const struct test *t)
159 {
160 char *preload = NULL;
161 size_t preloadlen = 0;
162 size_t i;
163
164 unsetenv("LD_PRELOAD");
165
166 for (i = 0; i < _TC_LAST; i++) {
167 const char *ldpreload;
168 size_t ldpreloadlen;
169 char *tmp;
170
171 if (t->config[i] == NULL)
172 continue;
173
174 setenv(env_config[i].key, t->config[i], 1);
175
176 ldpreload = env_config[i].ldpreload;
177 ldpreloadlen = strlen(ldpreload);
178 tmp = realloc(preload, preloadlen + 2 + ldpreloadlen);
179 if (tmp == NULL) {
180 ERR("oom: test_export_environ()\n");
181 return;
182 }
183 preload = tmp;
184
185 if (preloadlen > 0)
186 preload[preloadlen++] = ' ';
187 memcpy(preload + preloadlen, ldpreload, ldpreloadlen);
188 preloadlen += ldpreloadlen;
189 preload[preloadlen] = '\0';
190 }
191
192 if (preload != NULL)
193 setenv("LD_PRELOAD", preload, 1);
194
195 free(preload);
196 }
197
198 static inline int test_run_child(const struct test *t, int fdout[2],
199 int fderr[2], int fdmonitor[2])
200 {
201 /* kill child if parent dies */
202 prctl(PR_SET_PDEATHSIG, SIGTERM);
203
204 test_export_environ(t);
205
206 /* Close read-fds and redirect std{out,err} to the write-fds */
207 if (t->output.stdout != NULL) {
208 close(fdout[0]);
209 if (dup2(fdout[1], STDOUT_FILENO) < 0) {
210 ERR("could not redirect stdout to pipe: %m\n");
211 exit(EXIT_FAILURE);
212 }
213 }
214
215 if (t->output.stderr != NULL) {
216 close(fderr[0]);
217 if (dup2(fderr[1], STDERR_FILENO) < 0) {
218 ERR("could not redirect stdout to pipe: %m\n");
219 exit(EXIT_FAILURE);
220 }
221 }
222
223 close(fdmonitor[0]);
224
225 if (t->config[TC_ROOTFS] != NULL) {
226 const char *stamp = TESTSUITE_ROOTFS "../stamp-rootfs";
227 const char *rootfs = t->config[TC_ROOTFS];
228 struct stat rootfsst, stampst;
229
230 if (stat(stamp, &stampst) != 0) {
231 ERR("could not stat %s\n - %m", stamp);
232 exit(EXIT_FAILURE);
233 }
234
235 if (stat(rootfs, &rootfsst) != 0) {
236 ERR("could not stat %s\n - %m", rootfs);
237 exit(EXIT_FAILURE);
238 }
239
240 if (stat_mstamp(&rootfsst) > stat_mstamp(&stampst)) {
241 ERR("rootfs %s is dirty, please run 'make rootfs' before runnning this test\n",
242 rootfs);
243 exit(EXIT_FAILURE);
244 }
245 }
246
247 if (t->need_spawn)
248 return test_spawn_test(t);
249 else
250 return test_run_spawned(t);
251 }
252
253 static inline bool test_run_parent_check_outputs(const struct test *t,
254 int fdout, int fderr, int fdmonitor)
255 {
256 struct epoll_event ep_outpipe, ep_errpipe, ep_monitor;
257 int err, fd_ep, fd_matchout = -1, fd_matcherr = -1;
258
259 fd_ep = epoll_create1(EPOLL_CLOEXEC);
260 if (fd_ep < 0) {
261 ERR("could not create epoll fd: %m\n");
262 return false;
263 }
264
265 if (t->output.stdout != NULL) {
266 fd_matchout = open(t->output.stdout, O_RDONLY);
267 if (fd_matchout < 0) {
268 err = -errno;
269 ERR("could not open %s for read: %m\n",
270 t->output.stdout);
271 goto out;
272 }
273 memset(&ep_outpipe, 0, sizeof(struct epoll_event));
274 ep_outpipe.events = EPOLLIN;
275 ep_outpipe.data.ptr = &fdout;
276 if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fdout, &ep_outpipe) < 0) {
277 err = -errno;
278 ERR("could not add fd to epoll: %m\n");
279 goto out;
280 }
281 } else
282 fdout = -1;
283
284 if (t->output.stderr != NULL) {
285 fd_matcherr = open(t->output.stderr, O_RDONLY);
286 if (fd_matcherr < 0) {
287 err = -errno;
288 ERR("could not open %s for read: %m\n",
289 t->output.stderr);
290 goto out;
291
292 }
293 memset(&ep_errpipe, 0, sizeof(struct epoll_event));
294 ep_errpipe.events = EPOLLIN;
295 ep_errpipe.data.ptr = &fderr;
296 if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fderr, &ep_errpipe) < 0) {
297 err = -errno;
298 ERR("could not add fd to epoll: %m\n");
299 goto out;
300 }
301 } else
302 fderr = -1;
303
304 memset(&ep_monitor, 0, sizeof(struct epoll_event));
305 ep_monitor.events = EPOLLHUP;
306 ep_monitor.data.ptr = &fdmonitor;
307 if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fdmonitor, &ep_monitor) < 0) {
308 err = -errno;
309 ERR("could not add monitor fd to epoll: %m\n");
310 goto out;
311 }
312
313 for (err = 0; fdmonitor >= 0 || fdout >= 0 || fderr >= 0;) {
314 int fdcount, i;
315 struct epoll_event ev[4];
316
317 fdcount = epoll_wait(fd_ep, ev, 4, -1);
318 if (fdcount < 0) {
319 if (errno == EINTR)
320 continue;
321 err = -errno;
322 ERR("could not poll: %m\n");
323 goto out;
324 }
325
326 for (i = 0; i < fdcount; i++) {
327 int *fd = ev[i].data.ptr;
328
329 if (ev[i].events & EPOLLIN) {
330 ssize_t r, done = 0;
331 char buf[4096];
332 char bufmatch[4096];
333 int fd_match;
334
335 /*
336 * compare the output from child with the one
337 * saved as correct
338 */
339
340 r = read(*fd, buf, sizeof(buf) - 1);
341 if (r <= 0)
342 continue;
343
344 if (*fd == fdout)
345 fd_match = fd_matchout;
346 else if (*fd == fderr)
347 fd_match = fd_matcherr;
348 else {
349 ERR("Unexpected activity on monitor pipe\n");
350 err = -EINVAL;
351 goto out;
352 }
353
354 for (;;) {
355 int rmatch = read(fd_match,
356 bufmatch + done, r - done);
357 if (rmatch == 0)
358 break;
359
360 if (rmatch < 0) {
361 if (errno == EINTR)
362 continue;
363 err = -errno;
364 ERR("could not read match fd %d\n",
365 fd_match);
366 goto out;
367 }
368
369 done += rmatch;
370 }
371
372 buf[r] = '\0';
373 bufmatch[r] = '\0';
374 if (strcmp(buf, bufmatch) != 0) {
375 ERR("Outputs do not match on %s:\n",
376 fd_match == fd_matchout ? "stdout" : "stderr");
377 ERR("correct:\n%s\n", bufmatch);
378 ERR("wrong:\n%s\n", buf);
379 err = -1;
380 goto out;
381 }
382 } else if (ev[i].events & EPOLLHUP) {
383 if (epoll_ctl(fd_ep, EPOLL_CTL_DEL,
384 *fd, NULL) < 0) {
385 ERR("could not remove fd %d from epoll: %m\n",
386 *fd);
387 }
388 *fd = -1;
389 }
390 }
391
392 }
393 out:
394 if (fd_matchout >= 0)
395 close(fd_matchout);
396 if (fd_matcherr >= 0)
397 close(fd_matcherr);
398 if (fd_ep >= 0)
399 close(fd_ep);
400 return err == 0;
401 }
402
403 static inline int test_run_parent(const struct test *t, int fdout[2],
404 int fderr[2], int fdmonitor[2])
405 {
406 pid_t pid;
407 int err;
408 bool matchout;
409
410 /* Close write-fds */
411 if (t->output.stdout != NULL)
412 close(fdout[1]);
413 if (t->output.stderr != NULL)
414 close(fderr[1]);
415 close(fdmonitor[1]);
416
417 matchout = test_run_parent_check_outputs(t, fdout[0], fderr[0],
418 fdmonitor[0]);
419
420 /*
421 * break pipe on the other end: either child already closed or we want
422 * to stop it
423 */
424 if (t->output.stdout != NULL)
425 close(fdout[0]);
426 if (t->output.stderr != NULL)
427 close(fderr[0]);
428 close(fdmonitor[0]);
429
430 do {
431 pid = wait(&err);
432 if (pid == -1) {
433 ERR("error waitpid(): %m\n");
434 return EXIT_FAILURE;
435 }
436 } while (!WIFEXITED(err) && !WIFSIGNALED(err));
437
438 if (WIFEXITED(err)) {
439 if (WEXITSTATUS(err) != 0)
440 ERR("'%s' [%u] exited with return code %d\n",
441 t->name, pid, WEXITSTATUS(err));
442 else
443 LOG("'%s' [%u] exited with return code %d\n",
444 t->name, pid, WEXITSTATUS(err));
445 } else if (WIFSIGNALED(err)) {
446 ERR("'%s' [%u] terminated by signal %d (%s)\n", t->name, pid,
447 WTERMSIG(err), strsignal(WTERMSIG(err)));
448 }
449
450 if (t->expected_fail == false) {
451 if (err == 0) {
452 if (matchout)
453 LOG("%sPASSED%s: %s\n",
454 ANSI_HIGHLIGHT_GREEN_ON, ANSI_HIGHLIGHT_OFF,
455 t->name);
456 else {
457 ERR("%sFAILED%s: exit ok but outputs do not match: %s\n",
458 ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF,
459 t->name);
460 err = EXIT_FAILURE;
461 }
462 } else
463 ERR("%sFAILED%s: %s\n",
464 ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF,
465 t->name);
466 } else {
467 if (err == 0) {
468 if (matchout) {
469 LOG("%sUNEXPECTED PASS%s: %s\n",
470 ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF,
471 t->name);
472 err = EXIT_FAILURE;
473 } else
474 LOG("%sEXPECTED FAIL%s: exit ok but outputs do not match: %s\n",
475 ANSI_HIGHLIGHT_GREEN_ON, ANSI_HIGHLIGHT_OFF,
476 t->name);
477 } else {
478 ERR("%sEXPECTED FAIL%s: %s\n",
479 ANSI_HIGHLIGHT_GREEN_ON, ANSI_HIGHLIGHT_OFF,
480 t->name);
481 err = EXIT_SUCCESS;
482 }
483 }
484
485 return err;
486 }
487
488 static int prepend_path(const char *extra)
489 {
490 char *oldpath, *newpath;
491 int r;
492
493 if (extra == NULL)
494 return 0;
495
496 oldpath = getenv("PATH");
497 if (oldpath == NULL)
498 return setenv("PATH", extra, 1);
499
500 if (asprintf(&newpath, "%s:%s", extra, oldpath) < 0) {
501 ERR("failed to allocate memory to new PATH\n");
502 return -1;
503 }
504
505 r = setenv("PATH", newpath, 1);
506 free(newpath);
507
508 return r;
509 }
510
511 int test_run(const struct test *t)
512 {
513 pid_t pid;
514 int fdout[2];
515 int fderr[2];
516 int fdmonitor[2];
517
518 if (t->need_spawn && oneshot)
519 test_run_spawned(t);
520
521 if (t->output.stdout != NULL) {
522 if (pipe(fdout) != 0) {
523 ERR("could not create out pipe for %s\n", t->name);
524 return EXIT_FAILURE;
525 }
526 }
527
528 if (t->output.stderr != NULL) {
529 if (pipe(fderr) != 0) {
530 ERR("could not create err pipe for %s\n", t->name);
531 return EXIT_FAILURE;
532 }
533 }
534
535 if (pipe(fdmonitor) != 0) {
536 ERR("could not create monitor pipe for %s\n", t->name);
537 return EXIT_FAILURE;
538 }
539
540 if (prepend_path(t->path) < 0) {
541 ERR("failed to prepend '%s' to PATH\n", t->path);
542 return EXIT_FAILURE;
543 }
544
545 LOG("running %s, in forked context\n", t->name);
546
547 pid = fork();
548 if (pid < 0) {
549 ERR("could not fork(): %m\n");
550 LOG("FAILED: %s\n", t->name);
551 return EXIT_FAILURE;
552 }
553
554 if (pid > 0)
555 return test_run_parent(t, fdout, fderr, fdmonitor);
556
557 return test_run_child(t, fdout, fderr, fdmonitor);
558 }