]>
Commit | Line | Data |
---|---|---|
e701e381 | 1 | /* |
e6b0e49b | 2 | * Copyright (C) 2012-2013 ProFUSION embedded systems |
e701e381 | 3 | * |
e1b1ab24 LDM |
4 | * This program is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public | |
6 | * License as published by the Free Software Foundation; either | |
7 | * version 2.1 of the License, or (at your option) any later version. | |
e701e381 LDM |
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 | |
e1b1ab24 LDM |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. | |
e701e381 | 13 | * |
e1b1ab24 | 14 | * You should have received a copy of the GNU Lesser General Public |
dea2dfee | 15 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
e701e381 LDM |
16 | */ |
17 | ||
c2e4286b | 18 | #include <dirent.h> |
80f9e023 LDM |
19 | #include <errno.h> |
20 | #include <fcntl.h> | |
21 | #include <getopt.h> | |
22 | #include <limits.h> | |
80f9e023 | 23 | #include <stdarg.h> |
c2e4286b | 24 | #include <stdio.h> |
80f9e023 LDM |
25 | #include <stdlib.h> |
26 | #include <string.h> | |
b8e344a6 | 27 | #include <time.h> |
80f9e023 | 28 | #include <unistd.h> |
3dbb8dea | 29 | #include <sys/epoll.h> |
80f9e023 | 30 | #include <sys/prctl.h> |
0de690c9 | 31 | #include <sys/stat.h> |
80f9e023 LDM |
32 | #include <sys/wait.h> |
33 | ||
96573a02 LDM |
34 | #include <shared/util.h> |
35 | ||
80f9e023 LDM |
36 | #include "testsuite.h" |
37 | ||
51b1d1ab LDM |
38 | static const char *ANSI_HIGHLIGHT_GREEN_ON = "\x1B[1;32m"; |
39 | static const char *ANSI_HIGHLIGHT_RED_ON = "\x1B[1;31m"; | |
40 | static const char *ANSI_HIGHLIGHT_OFF = "\x1B[0m"; | |
41 | ||
80f9e023 | 42 | static const char *progname; |
ed2df4e9 | 43 | static int oneshot = 0; |
80f9e023 LDM |
44 | static const char options_short[] = "lhn"; |
45 | static const struct option options[] = { | |
46 | { "list", no_argument, 0, 'l' }, | |
47 | { "help", no_argument, 0, 'h' }, | |
48 | { NULL, 0, 0, 0 } | |
49 | }; | |
50 | ||
d2c2b8b5 LDM |
51 | #define OVERRIDE_LIBDIR ABS_TOP_BUILDDIR "/testsuite/.libs/" |
52 | ||
395478cb LDM |
53 | struct _env_config { |
54 | const char *key; | |
55 | const char *ldpreload; | |
56 | } env_config[_TC_LAST] = { | |
d2c2b8b5 LDM |
57 | [TC_UNAME_R] = { S_TC_UNAME_R, OVERRIDE_LIBDIR "uname.so" }, |
58 | [TC_ROOTFS] = { S_TC_ROOTFS, OVERRIDE_LIBDIR "path.so" }, | |
59 | [TC_INIT_MODULE_RETCODES] = { S_TC_INIT_MODULE_RETCODES, OVERRIDE_LIBDIR "init_module.so" }, | |
f6ef5d6b | 60 | [TC_DELETE_MODULE_RETCODES] = { S_TC_DELETE_MODULE_RETCODES, OVERRIDE_LIBDIR "delete_module.so" }, |
395478cb LDM |
61 | }; |
62 | ||
b8e344a6 LDM |
63 | #define USEC_PER_SEC 1000000ULL |
64 | #define USEC_PER_MSEC 1000ULL | |
65 | #define TEST_TIMEOUT_USEC 2 * USEC_PER_SEC | |
66 | static unsigned long long now_usec(void) | |
67 | { | |
68 | struct timespec ts; | |
69 | ||
70 | if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) | |
71 | return 0; | |
72 | ||
73 | return ts_usec(&ts); | |
74 | } | |
75 | ||
80f9e023 LDM |
76 | static void help(void) |
77 | { | |
78 | const struct option *itr; | |
79 | const char *itr_short; | |
80 | ||
81 | printf("Usage:\n" | |
82 | "\t%s [options] <test>\n" | |
83 | "Options:\n", basename(progname)); | |
84 | ||
85 | for (itr = options, itr_short = options_short; | |
86 | itr->name != NULL; itr++, itr_short++) | |
87 | printf("\t-%c, --%s\n", *itr_short, itr->name); | |
88 | } | |
89 | ||
c5798fea | 90 | static void test_list(const struct test *start, const struct test *stop) |
80f9e023 | 91 | { |
c5798fea | 92 | const struct test *t; |
80f9e023 LDM |
93 | |
94 | printf("Available tests:\n"); | |
c5798fea LDM |
95 | for (t = start; t < stop; t++) |
96 | printf("\t%s, %s\n", t->name, t->description); | |
80f9e023 LDM |
97 | } |
98 | ||
c5798fea LDM |
99 | int test_init(const struct test *start, const struct test *stop, |
100 | int argc, char *const argv[]) | |
80f9e023 LDM |
101 | { |
102 | progname = argv[0]; | |
103 | ||
104 | for (;;) { | |
105 | int c, idx = 0; | |
106 | c = getopt_long(argc, argv, options_short, options, &idx); | |
107 | if (c == -1) | |
108 | break; | |
109 | switch (c) { | |
110 | case 'l': | |
c5798fea | 111 | test_list(start, stop); |
80f9e023 LDM |
112 | return 0; |
113 | case 'h': | |
114 | help(); | |
115 | return 0; | |
116 | case 'n': | |
117 | oneshot = 1; | |
118 | break; | |
119 | case '?': | |
120 | return -1; | |
121 | default: | |
122 | ERR("unexpected getopt_long() value %c\n", c); | |
123 | return -1; | |
124 | } | |
125 | } | |
126 | ||
51b1d1ab LDM |
127 | if (isatty(STDOUT_FILENO) == 0) { |
128 | ANSI_HIGHLIGHT_OFF = ""; | |
129 | ANSI_HIGHLIGHT_RED_ON = ""; | |
130 | ANSI_HIGHLIGHT_GREEN_ON = ""; | |
131 | } | |
132 | ||
80f9e023 LDM |
133 | return optind; |
134 | } | |
135 | ||
c5798fea LDM |
136 | const struct test *test_find(const struct test *start, |
137 | const struct test *stop, const char *name) | |
80f9e023 | 138 | { |
c5798fea | 139 | const struct test *t; |
80f9e023 | 140 | |
c5798fea | 141 | for (t = start; t < stop; t++) { |
5c42c5fc | 142 | if (streq(t->name, name)) |
c5798fea | 143 | return t; |
80f9e023 LDM |
144 | } |
145 | ||
146 | return NULL; | |
147 | } | |
148 | ||
ed2df4e9 | 149 | static int test_spawn_test(const struct test *t) |
80f9e023 LDM |
150 | { |
151 | const char *const args[] = { progname, "-n", t->name, NULL }; | |
152 | ||
153 | execv(progname, (char *const *) args); | |
154 | ||
155 | ERR("failed to spawn %s for %s: %m\n", progname, t->name); | |
156 | return EXIT_FAILURE; | |
157 | } | |
158 | ||
ed2df4e9 LDM |
159 | static int test_run_spawned(const struct test *t) |
160 | { | |
161 | int err = t->func(t); | |
162 | exit(err); | |
163 | ||
164 | return EXIT_FAILURE; | |
165 | } | |
166 | ||
9e3b9d2e | 167 | int test_spawn_prog(const char *prog, const char *const args[]) |
80f9e023 LDM |
168 | { |
169 | execv(prog, (char *const *) args); | |
170 | ||
9e3b9d2e LDM |
171 | ERR("failed to spawn %s\n", prog); |
172 | ERR("did you forget to build tools?\n"); | |
80f9e023 LDM |
173 | return EXIT_FAILURE; |
174 | } | |
175 | ||
395478cb | 176 | static void test_export_environ(const struct test *t) |
80f9e023 | 177 | { |
395478cb LDM |
178 | char *preload = NULL; |
179 | size_t preloadlen = 0; | |
180 | size_t i; | |
34db3f2d | 181 | const struct keyval *env; |
395478cb LDM |
182 | |
183 | unsetenv("LD_PRELOAD"); | |
184 | ||
185 | for (i = 0; i < _TC_LAST; i++) { | |
186 | const char *ldpreload; | |
187 | size_t ldpreloadlen; | |
188 | char *tmp; | |
189 | ||
190 | if (t->config[i] == NULL) | |
191 | continue; | |
192 | ||
193 | setenv(env_config[i].key, t->config[i], 1); | |
194 | ||
195 | ldpreload = env_config[i].ldpreload; | |
196 | ldpreloadlen = strlen(ldpreload); | |
197 | tmp = realloc(preload, preloadlen + 2 + ldpreloadlen); | |
198 | if (tmp == NULL) { | |
199 | ERR("oom: test_export_environ()\n"); | |
200 | return; | |
201 | } | |
202 | preload = tmp; | |
203 | ||
204 | if (preloadlen > 0) | |
205 | preload[preloadlen++] = ' '; | |
206 | memcpy(preload + preloadlen, ldpreload, ldpreloadlen); | |
207 | preloadlen += ldpreloadlen; | |
208 | preload[preloadlen] = '\0'; | |
209 | } | |
210 | ||
211 | if (preload != NULL) | |
212 | setenv("LD_PRELOAD", preload, 1); | |
213 | ||
214 | free(preload); | |
34db3f2d LDM |
215 | |
216 | for (env = t->env_vars; env && env->key; env++) | |
217 | setenv(env->key, env->val, 1); | |
80f9e023 LDM |
218 | } |
219 | ||
3dbb8dea | 220 | static inline int test_run_child(const struct test *t, int fdout[2], |
ed8e93fd | 221 | int fderr[2], int fdmonitor[2]) |
80f9e023 | 222 | { |
45481ee2 LDM |
223 | /* kill child if parent dies */ |
224 | prctl(PR_SET_PDEATHSIG, SIGTERM); | |
225 | ||
226 | test_export_environ(t); | |
227 | ||
3dbb8dea | 228 | /* Close read-fds and redirect std{out,err} to the write-fds */ |
bd4e7340 | 229 | if (t->output.out != NULL) { |
3dbb8dea LDM |
230 | close(fdout[0]); |
231 | if (dup2(fdout[1], STDOUT_FILENO) < 0) { | |
050db08c | 232 | ERR("could not redirect stdout to pipe: %m\n"); |
3dbb8dea LDM |
233 | exit(EXIT_FAILURE); |
234 | } | |
235 | } | |
236 | ||
bd4e7340 | 237 | if (t->output.err != NULL) { |
3dbb8dea LDM |
238 | close(fderr[0]); |
239 | if (dup2(fderr[1], STDERR_FILENO) < 0) { | |
bd4e7340 | 240 | ERR("could not redirect stderr to pipe: %m\n"); |
3dbb8dea LDM |
241 | exit(EXIT_FAILURE); |
242 | } | |
243 | } | |
244 | ||
ed8e93fd LDM |
245 | close(fdmonitor[0]); |
246 | ||
0de690c9 LDM |
247 | if (t->config[TC_ROOTFS] != NULL) { |
248 | const char *stamp = TESTSUITE_ROOTFS "../stamp-rootfs"; | |
249 | const char *rootfs = t->config[TC_ROOTFS]; | |
250 | struct stat rootfsst, stampst; | |
251 | ||
252 | if (stat(stamp, &stampst) != 0) { | |
253 | ERR("could not stat %s\n - %m", stamp); | |
254 | exit(EXIT_FAILURE); | |
255 | } | |
256 | ||
257 | if (stat(rootfs, &rootfsst) != 0) { | |
258 | ERR("could not stat %s\n - %m", rootfs); | |
259 | exit(EXIT_FAILURE); | |
260 | } | |
261 | ||
262 | if (stat_mstamp(&rootfsst) > stat_mstamp(&stampst)) { | |
263 | ERR("rootfs %s is dirty, please run 'make rootfs' before runnning this test\n", | |
264 | rootfs); | |
265 | exit(EXIT_FAILURE); | |
266 | } | |
267 | } | |
268 | ||
45481ee2 LDM |
269 | if (t->need_spawn) |
270 | return test_spawn_test(t); | |
271 | else | |
272 | return test_run_spawned(t); | |
273 | } | |
274 | ||
03a5079f LDM |
275 | static int check_activity(int fd, bool activity, const char *path, |
276 | const char *stream) | |
277 | { | |
278 | struct stat st; | |
279 | ||
280 | /* not monitoring or monitoring and it has activity */ | |
281 | if (fd < 0 || activity) | |
282 | return 0; | |
283 | ||
284 | /* monitoring, there was no activity and size matches */ | |
285 | if (stat(path, &st) == 0 && st.st_size == 0) | |
286 | return 0; | |
287 | ||
288 | ERR("Expecting output on %s, but test didn't produce any\n", stream); | |
289 | ||
290 | return -1; | |
291 | } | |
292 | ||
3dbb8dea | 293 | static inline bool test_run_parent_check_outputs(const struct test *t, |
b8e344a6 | 294 | int fdout, int fderr, int fdmonitor, pid_t child) |
3dbb8dea | 295 | { |
ed8e93fd | 296 | struct epoll_event ep_outpipe, ep_errpipe, ep_monitor; |
3dbb8dea | 297 | int err, fd_ep, fd_matchout = -1, fd_matcherr = -1; |
03a5079f | 298 | bool fd_activityout = false, fd_activityerr = false; |
b8e344a6 | 299 | unsigned long long end_usec, start_usec; |
3dbb8dea | 300 | |
3dbb8dea LDM |
301 | fd_ep = epoll_create1(EPOLL_CLOEXEC); |
302 | if (fd_ep < 0) { | |
303 | ERR("could not create epoll fd: %m\n"); | |
304 | return false; | |
305 | } | |
306 | ||
bd4e7340 JS |
307 | if (t->output.out != NULL) { |
308 | fd_matchout = open(t->output.out, O_RDONLY); | |
3dbb8dea LDM |
309 | if (fd_matchout < 0) { |
310 | err = -errno; | |
311 | ERR("could not open %s for read: %m\n", | |
bd4e7340 | 312 | t->output.out); |
3dbb8dea LDM |
313 | goto out; |
314 | } | |
315 | memset(&ep_outpipe, 0, sizeof(struct epoll_event)); | |
316 | ep_outpipe.events = EPOLLIN; | |
317 | ep_outpipe.data.ptr = &fdout; | |
318 | if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fdout, &ep_outpipe) < 0) { | |
319 | err = -errno; | |
320 | ERR("could not add fd to epoll: %m\n"); | |
321 | goto out; | |
322 | } | |
323 | } else | |
324 | fdout = -1; | |
325 | ||
bd4e7340 JS |
326 | if (t->output.err != NULL) { |
327 | fd_matcherr = open(t->output.err, O_RDONLY); | |
3dbb8dea LDM |
328 | if (fd_matcherr < 0) { |
329 | err = -errno; | |
330 | ERR("could not open %s for read: %m\n", | |
bd4e7340 | 331 | t->output.err); |
3dbb8dea LDM |
332 | goto out; |
333 | ||
334 | } | |
335 | memset(&ep_errpipe, 0, sizeof(struct epoll_event)); | |
336 | ep_errpipe.events = EPOLLIN; | |
337 | ep_errpipe.data.ptr = &fderr; | |
338 | if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fderr, &ep_errpipe) < 0) { | |
339 | err = -errno; | |
340 | ERR("could not add fd to epoll: %m\n"); | |
341 | goto out; | |
342 | } | |
343 | } else | |
344 | fderr = -1; | |
345 | ||
ed8e93fd LDM |
346 | memset(&ep_monitor, 0, sizeof(struct epoll_event)); |
347 | ep_monitor.events = EPOLLHUP; | |
348 | ep_monitor.data.ptr = &fdmonitor; | |
349 | if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fdmonitor, &ep_monitor) < 0) { | |
350 | err = -errno; | |
351 | ERR("could not add monitor fd to epoll: %m\n"); | |
352 | goto out; | |
353 | } | |
354 | ||
b8e344a6 LDM |
355 | start_usec = now_usec(); |
356 | end_usec = start_usec + TEST_TIMEOUT_USEC; | |
357 | ||
ed8e93fd | 358 | for (err = 0; fdmonitor >= 0 || fdout >= 0 || fderr >= 0;) { |
b8e344a6 | 359 | int fdcount, i, timeout; |
3dbb8dea | 360 | struct epoll_event ev[4]; |
b8e344a6 | 361 | unsigned long long curr_usec = now_usec(); |
3dbb8dea | 362 | |
b8e344a6 LDM |
363 | if (curr_usec > end_usec) |
364 | break; | |
365 | ||
366 | timeout = (end_usec - curr_usec) / USEC_PER_MSEC; | |
367 | fdcount = epoll_wait(fd_ep, ev, 4, timeout); | |
3dbb8dea LDM |
368 | if (fdcount < 0) { |
369 | if (errno == EINTR) | |
370 | continue; | |
371 | err = -errno; | |
372 | ERR("could not poll: %m\n"); | |
373 | goto out; | |
374 | } | |
375 | ||
376 | for (i = 0; i < fdcount; i++) { | |
377 | int *fd = ev[i].data.ptr; | |
378 | ||
379 | if (ev[i].events & EPOLLIN) { | |
380 | ssize_t r, done = 0; | |
381 | char buf[4096]; | |
382 | char bufmatch[4096]; | |
383 | int fd_match; | |
384 | ||
385 | /* | |
386 | * compare the output from child with the one | |
387 | * saved as correct | |
388 | */ | |
389 | ||
390 | r = read(*fd, buf, sizeof(buf) - 1); | |
391 | if (r <= 0) | |
392 | continue; | |
393 | ||
03a5079f | 394 | if (*fd == fdout) { |
3dbb8dea | 395 | fd_match = fd_matchout; |
03a5079f LDM |
396 | fd_activityout = true; |
397 | } else if (*fd == fderr) { | |
3dbb8dea | 398 | fd_match = fd_matcherr; |
03a5079f LDM |
399 | fd_activityerr = true; |
400 | } else { | |
ed8e93fd LDM |
401 | ERR("Unexpected activity on monitor pipe\n"); |
402 | err = -EINVAL; | |
403 | goto out; | |
404 | } | |
3dbb8dea LDM |
405 | |
406 | for (;;) { | |
407 | int rmatch = read(fd_match, | |
408 | bufmatch + done, r - done); | |
409 | if (rmatch == 0) | |
410 | break; | |
411 | ||
412 | if (rmatch < 0) { | |
413 | if (errno == EINTR) | |
414 | continue; | |
415 | err = -errno; | |
416 | ERR("could not read match fd %d\n", | |
417 | fd_match); | |
418 | goto out; | |
419 | } | |
420 | ||
421 | done += rmatch; | |
422 | } | |
423 | ||
424 | buf[r] = '\0'; | |
425 | bufmatch[r] = '\0'; | |
f6dc239e LDM |
426 | |
427 | if (t->print_outputs) | |
428 | printf("%s: %s\n", | |
429 | fd_match == fd_matchout ? "STDOUT:" : "STDERR:", | |
430 | buf); | |
431 | ||
5c42c5fc | 432 | if (!streq(buf, bufmatch)) { |
3dbb8dea | 433 | ERR("Outputs do not match on %s:\n", |
03a5079f | 434 | fd_match == fd_matchout ? "STDOUT" : "STDERR"); |
3dbb8dea LDM |
435 | ERR("correct:\n%s\n", bufmatch); |
436 | ERR("wrong:\n%s\n", buf); | |
437 | err = -1; | |
438 | goto out; | |
439 | } | |
440 | } else if (ev[i].events & EPOLLHUP) { | |
441 | if (epoll_ctl(fd_ep, EPOLL_CTL_DEL, | |
442 | *fd, NULL) < 0) { | |
443 | ERR("could not remove fd %d from epoll: %m\n", | |
444 | *fd); | |
445 | } | |
446 | *fd = -1; | |
447 | } | |
448 | } | |
b8e344a6 | 449 | } |
3dbb8dea | 450 | |
03a5079f LDM |
451 | err = check_activity(fd_matchout, fd_activityout, t->output.out, "stdout"); |
452 | err |= check_activity(fd_matcherr, fd_activityerr, t->output.err, "stderr"); | |
453 | ||
b8e344a6 LDM |
454 | if (err == 0 && fdmonitor >= 0) { |
455 | err = -EINVAL; | |
456 | ERR("Test '%s' timed out, killing %d\n", t->name, child); | |
457 | kill(child, SIGKILL); | |
3dbb8dea | 458 | } |
b8e344a6 | 459 | |
3dbb8dea LDM |
460 | out: |
461 | if (fd_matchout >= 0) | |
462 | close(fd_matchout); | |
463 | if (fd_matcherr >= 0) | |
464 | close(fd_matcherr); | |
465 | if (fd_ep >= 0) | |
466 | close(fd_ep); | |
467 | return err == 0; | |
468 | } | |
469 | ||
3e451bfe LDM |
470 | static inline int safe_read(int fd, void *buf, size_t count) |
471 | { | |
472 | int r; | |
473 | ||
474 | while (1) { | |
475 | r = read(fd, buf, count); | |
2ad09ccf | 476 | if (r == -1 && errno == EINTR) |
3e451bfe LDM |
477 | continue; |
478 | break; | |
479 | } | |
480 | ||
481 | return r; | |
482 | } | |
483 | ||
484 | static bool check_generated_files(const struct test *t) | |
485 | { | |
486 | const struct keyval *k; | |
487 | ||
488 | /* This is not meant to be a diff replacement, just stupidly check if | |
489 | * the files match. Bear in mind they can be binary files */ | |
490 | for (k = t->output.files; k && k->key; k++) { | |
491 | struct stat sta, stb; | |
492 | int fda = -1, fdb = -1; | |
493 | char bufa[4096]; | |
494 | char bufb[4096]; | |
495 | ||
496 | fda = open(k->key, O_RDONLY); | |
497 | if (fda < 0) { | |
498 | ERR("could not open %s\n - %m\n", k->key); | |
499 | goto fail; | |
500 | } | |
501 | ||
502 | fdb = open(k->val, O_RDONLY); | |
503 | if (fdb < 0) { | |
504 | ERR("could not open %s\n - %m\n", k->val); | |
505 | goto fail; | |
506 | } | |
507 | ||
508 | if (fstat(fda, &sta) != 0) { | |
509 | ERR("could not fstat %d %s\n - %m\n", fda, k->key); | |
510 | goto fail; | |
511 | } | |
512 | ||
513 | if (fstat(fdb, &stb) != 0) { | |
514 | ERR("could not fstat %d %s\n - %m\n", fdb, k->key); | |
515 | goto fail; | |
516 | } | |
517 | ||
518 | if (sta.st_size != stb.st_size) { | |
519 | ERR("sizes do not match %s %s\n", k->key, k->val); | |
520 | goto fail; | |
521 | } | |
522 | ||
523 | for (;;) { | |
524 | int r, done; | |
525 | ||
526 | r = safe_read(fda, bufa, sizeof(bufa)); | |
527 | if (r < 0) | |
528 | goto fail; | |
529 | ||
530 | if (r == 0) | |
531 | /* size is already checked, go to next file */ | |
532 | goto next; | |
533 | ||
534 | for (done = 0; done < r;) { | |
535 | int r2 = safe_read(fdb, bufb + done, r - done); | |
536 | ||
537 | if (r2 <= 0) | |
538 | goto fail; | |
539 | ||
540 | done += r2; | |
541 | } | |
542 | ||
543 | if (memcmp(bufa, bufb, r) != 0) | |
544 | goto fail; | |
545 | } | |
546 | ||
547 | next: | |
548 | close(fda); | |
549 | close(fdb); | |
550 | continue; | |
551 | ||
552 | fail: | |
553 | if (fda >= 0) | |
554 | close(fda); | |
555 | if (fdb >= 0) | |
556 | close(fdb); | |
557 | ||
558 | return false; | |
559 | } | |
560 | ||
561 | return true; | |
562 | } | |
563 | ||
88ac4084 MM |
564 | static int cmp_modnames(const void *m1, const void *m2) |
565 | { | |
566 | const char *s1 = *(char *const *)m1; | |
567 | const char *s2 = *(char *const *)m2; | |
568 | int i; | |
569 | ||
570 | for (i = 0; s1[i] || s2[i]; i++) { | |
571 | char c1 = s1[i], c2 = s2[i]; | |
572 | if (c1 == '-') | |
573 | c1 = '_'; | |
574 | if (c2 == '-') | |
575 | c2 = '_'; | |
576 | if (c1 != c2) | |
577 | return c1 - c2; | |
578 | } | |
579 | return 0; | |
580 | } | |
581 | ||
582 | /* | |
583 | * Store the expected module names in buf and return a list of pointers to | |
584 | * them. | |
585 | */ | |
586 | static const char **read_expected_modules(const struct test *t, | |
587 | char **buf, int *count) | |
588 | { | |
589 | const char **res; | |
590 | int len; | |
591 | int i; | |
592 | char *p; | |
593 | ||
594 | if (t->modules_loaded[0] == '\0') { | |
595 | *count = 0; | |
596 | *buf = NULL; | |
597 | return NULL; | |
598 | } | |
599 | *buf = strdup(t->modules_loaded); | |
600 | if (!*buf) { | |
601 | *count = -1; | |
602 | return NULL; | |
603 | } | |
604 | len = 1; | |
605 | for (p = *buf; *p; p++) | |
606 | if (*p == ',') | |
607 | len++; | |
608 | res = malloc(sizeof(char *) * len); | |
609 | if (!res) { | |
610 | perror("malloc"); | |
611 | *count = -1; | |
612 | free(*buf); | |
613 | *buf = NULL; | |
614 | return NULL; | |
615 | } | |
616 | i = 0; | |
617 | res[i++] = *buf; | |
618 | for (p = *buf; i < len; p++) | |
619 | if (*p == ',') { | |
620 | *p = '\0'; | |
621 | res[i++] = p + 1; | |
622 | } | |
623 | *count = len; | |
624 | return res; | |
625 | } | |
626 | ||
627 | static char **read_loaded_modules(const struct test *t, char **buf, int *count) | |
628 | { | |
629 | char dirname[PATH_MAX]; | |
630 | DIR *dir; | |
631 | struct dirent *dirent; | |
632 | int i; | |
633 | int len = 0, bufsz; | |
634 | char **res = NULL; | |
635 | char *p; | |
636 | const char *rootfs = t->config[TC_ROOTFS] ? t->config[TC_ROOTFS] : ""; | |
637 | ||
638 | /* Store the entries in /sys/module to res */ | |
639 | if (snprintf(dirname, sizeof(dirname), "%s/sys/module", rootfs) | |
640 | >= (int)sizeof(dirname)) { | |
641 | ERR("rootfs path too long: %s\n", rootfs); | |
642 | *buf = NULL; | |
643 | len = -1; | |
644 | goto out; | |
645 | } | |
646 | dir = opendir(dirname); | |
647 | /* not an error, simply return empty list */ | |
648 | if (!dir) { | |
649 | *buf = NULL; | |
650 | goto out; | |
651 | } | |
652 | bufsz = 0; | |
653 | while ((dirent = readdir(dir))) { | |
654 | if (dirent->d_name[0] == '.') | |
655 | continue; | |
656 | len++; | |
657 | bufsz += strlen(dirent->d_name) + 1; | |
658 | } | |
659 | res = malloc(sizeof(char *) * len); | |
660 | if (!res) { | |
661 | perror("malloc"); | |
662 | len = -1; | |
663 | goto out_dir; | |
664 | } | |
665 | *buf = malloc(bufsz); | |
666 | if (!*buf) { | |
667 | perror("malloc"); | |
668 | free(res); | |
669 | res = NULL; | |
670 | len = -1; | |
671 | goto out_dir; | |
672 | } | |
673 | rewinddir(dir); | |
674 | i = 0; | |
675 | p = *buf; | |
676 | while ((dirent = readdir(dir))) { | |
677 | int size; | |
678 | ||
679 | if (dirent->d_name[0] == '.') | |
680 | continue; | |
681 | size = strlen(dirent->d_name) + 1; | |
682 | memcpy(p, dirent->d_name, size); | |
683 | res[i++] = p; | |
684 | p += size; | |
685 | } | |
686 | out_dir: | |
687 | closedir(dir); | |
688 | out: | |
689 | *count = len; | |
690 | return res; | |
691 | } | |
692 | ||
693 | static int check_loaded_modules(const struct test *t) | |
694 | { | |
695 | int l1, l2, i1, i2; | |
696 | const char **a1; | |
697 | char **a2; | |
698 | char *buf1, *buf2; | |
699 | int err = false; | |
700 | ||
701 | a1 = read_expected_modules(t, &buf1, &l1); | |
702 | if (l1 < 0) | |
703 | return err; | |
704 | a2 = read_loaded_modules(t, &buf2, &l2); | |
705 | if (l2 < 0) | |
706 | goto out_a1; | |
707 | qsort(a1, l1, sizeof(char *), cmp_modnames); | |
708 | qsort(a2, l2, sizeof(char *), cmp_modnames); | |
709 | i1 = i2 = 0; | |
710 | err = true; | |
711 | while (i1 < l1 || i2 < l2) { | |
712 | int cmp; | |
713 | ||
714 | if (i1 >= l1) | |
715 | cmp = 1; | |
716 | else if (i2 >= l2) | |
717 | cmp = -1; | |
718 | else | |
719 | cmp = cmp_modnames(&a1[i1], &a2[i2]); | |
720 | if (cmp == 0) { | |
721 | i1++; | |
722 | i2++; | |
723 | } else if (cmp < 0) { | |
724 | err = false; | |
725 | ERR("module %s not loaded\n", a1[i1]); | |
726 | i1++; | |
727 | } else { | |
728 | err = false; | |
729 | ERR("module %s is loaded but should not be \n", a2[i2]); | |
730 | i2++; | |
731 | } | |
732 | } | |
733 | free(a2); | |
734 | free(buf2); | |
735 | out_a1: | |
736 | free(a1); | |
737 | free(buf1); | |
738 | return err; | |
739 | } | |
740 | ||
3dbb8dea | 741 | static inline int test_run_parent(const struct test *t, int fdout[2], |
b8e344a6 | 742 | int fderr[2], int fdmonitor[2], pid_t child) |
45481ee2 LDM |
743 | { |
744 | pid_t pid; | |
80f9e023 | 745 | int err; |
88ac4084 | 746 | bool matchout, match_modules; |
3dbb8dea LDM |
747 | |
748 | /* Close write-fds */ | |
bd4e7340 | 749 | if (t->output.out != NULL) |
3dbb8dea | 750 | close(fdout[1]); |
bd4e7340 | 751 | if (t->output.err != NULL) |
3dbb8dea | 752 | close(fderr[1]); |
ed8e93fd | 753 | close(fdmonitor[1]); |
3dbb8dea | 754 | |
ed8e93fd | 755 | matchout = test_run_parent_check_outputs(t, fdout[0], fderr[0], |
b8e344a6 | 756 | fdmonitor[0], child); |
3dbb8dea LDM |
757 | |
758 | /* | |
759 | * break pipe on the other end: either child already closed or we want | |
760 | * to stop it | |
761 | */ | |
bd4e7340 | 762 | if (t->output.out != NULL) |
3dbb8dea | 763 | close(fdout[0]); |
bd4e7340 | 764 | if (t->output.err != NULL) |
3dbb8dea | 765 | close(fderr[0]); |
ed8e93fd | 766 | close(fdmonitor[0]); |
45481ee2 LDM |
767 | |
768 | do { | |
769 | pid = wait(&err); | |
770 | if (pid == -1) { | |
771 | ERR("error waitpid(): %m\n"); | |
f988e25c LDM |
772 | err = EXIT_FAILURE; |
773 | goto exit; | |
45481ee2 LDM |
774 | } |
775 | } while (!WIFEXITED(err) && !WIFSIGNALED(err)); | |
776 | ||
3dbb8dea LDM |
777 | if (WIFEXITED(err)) { |
778 | if (WEXITSTATUS(err) != 0) | |
779 | ERR("'%s' [%u] exited with return code %d\n", | |
780 | t->name, pid, WEXITSTATUS(err)); | |
781 | else | |
782 | LOG("'%s' [%u] exited with return code %d\n", | |
783 | t->name, pid, WEXITSTATUS(err)); | |
784 | } else if (WIFSIGNALED(err)) { | |
785 | ERR("'%s' [%u] terminated by signal %d (%s)\n", t->name, pid, | |
786 | WTERMSIG(err), strsignal(WTERMSIG(err))); | |
f988e25c LDM |
787 | err = t->expected_fail ? EXIT_SUCCESS : EXIT_FAILURE; |
788 | goto exit; | |
3dbb8dea LDM |
789 | } |
790 | ||
3e451bfe LDM |
791 | if (matchout) |
792 | matchout = check_generated_files(t); | |
88ac4084 MM |
793 | if (t->modules_loaded) |
794 | match_modules = check_loaded_modules(t); | |
795 | else | |
796 | match_modules = true; | |
3e451bfe | 797 | |
fa0046ba DR |
798 | if (t->expected_fail == false) { |
799 | if (err == 0) { | |
88ac4084 | 800 | if (matchout && match_modules) |
fa0046ba DR |
801 | LOG("%sPASSED%s: %s\n", |
802 | ANSI_HIGHLIGHT_GREEN_ON, ANSI_HIGHLIGHT_OFF, | |
803 | t->name); | |
804 | else { | |
88ac4084 | 805 | ERR("%sFAILED%s: exit ok but %s do not match: %s\n", |
fa0046ba | 806 | ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF, |
88ac4084 | 807 | matchout ? "loaded modules" : "outputs", |
fa0046ba DR |
808 | t->name); |
809 | err = EXIT_FAILURE; | |
810 | } | |
f429113a | 811 | } else { |
fa0046ba DR |
812 | ERR("%sFAILED%s: %s\n", |
813 | ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF, | |
814 | t->name); | |
f429113a | 815 | } |
fa0046ba DR |
816 | } else { |
817 | if (err == 0) { | |
818 | if (matchout) { | |
d7293a16 | 819 | ERR("%sUNEXPECTED PASS%s: exit with 0: %s\n", |
658e0471 | 820 | ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF, |
fa0046ba DR |
821 | t->name); |
822 | err = EXIT_FAILURE; | |
d7293a16 LDM |
823 | } else { |
824 | ERR("%sUNEXPECTED PASS%s: exit with 0 and outputs do not match: %s\n", | |
825 | ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF, | |
fa0046ba | 826 | t->name); |
d7293a16 LDM |
827 | err = EXIT_FAILURE; |
828 | } | |
fa0046ba | 829 | } else { |
d7293a16 LDM |
830 | if (matchout) { |
831 | LOG("%sEXPECTED FAIL%s: %s\n", | |
fa0046ba DR |
832 | ANSI_HIGHLIGHT_GREEN_ON, ANSI_HIGHLIGHT_OFF, |
833 | t->name); | |
d7293a16 LDM |
834 | err = EXIT_SUCCESS; |
835 | } else { | |
836 | LOG("%sEXPECTED FAIL%s: exit with %d but outputs do not match: %s\n", | |
837 | ANSI_HIGHLIGHT_GREEN_ON, ANSI_HIGHLIGHT_OFF, | |
838 | WEXITSTATUS(err), t->name); | |
839 | err = EXIT_FAILURE; | |
840 | } | |
3dbb8dea | 841 | } |
fa0046ba | 842 | } |
45481ee2 | 843 | |
f988e25c LDM |
844 | exit: |
845 | LOG("------\n"); | |
45481ee2 LDM |
846 | return err; |
847 | } | |
848 | ||
f31d49c8 DR |
849 | static int prepend_path(const char *extra) |
850 | { | |
851 | char *oldpath, *newpath; | |
852 | int r; | |
853 | ||
854 | if (extra == NULL) | |
855 | return 0; | |
856 | ||
857 | oldpath = getenv("PATH"); | |
858 | if (oldpath == NULL) | |
859 | return setenv("PATH", extra, 1); | |
860 | ||
861 | if (asprintf(&newpath, "%s:%s", extra, oldpath) < 0) { | |
050db08c | 862 | ERR("failed to allocate memory to new PATH\n"); |
f31d49c8 DR |
863 | return -1; |
864 | } | |
865 | ||
866 | r = setenv("PATH", newpath, 1); | |
867 | free(newpath); | |
868 | ||
869 | return r; | |
870 | } | |
871 | ||
45481ee2 LDM |
872 | int test_run(const struct test *t) |
873 | { | |
80f9e023 | 874 | pid_t pid; |
3dbb8dea LDM |
875 | int fdout[2]; |
876 | int fderr[2]; | |
ed8e93fd | 877 | int fdmonitor[2]; |
80f9e023 | 878 | |
ed2df4e9 LDM |
879 | if (t->need_spawn && oneshot) |
880 | test_run_spawned(t); | |
881 | ||
bd4e7340 | 882 | if (t->output.out != NULL) { |
3dbb8dea LDM |
883 | if (pipe(fdout) != 0) { |
884 | ERR("could not create out pipe for %s\n", t->name); | |
885 | return EXIT_FAILURE; | |
886 | } | |
887 | } | |
888 | ||
bd4e7340 | 889 | if (t->output.err != NULL) { |
3dbb8dea LDM |
890 | if (pipe(fderr) != 0) { |
891 | ERR("could not create err pipe for %s\n", t->name); | |
892 | return EXIT_FAILURE; | |
893 | } | |
894 | } | |
895 | ||
ed8e93fd LDM |
896 | if (pipe(fdmonitor) != 0) { |
897 | ERR("could not create monitor pipe for %s\n", t->name); | |
898 | return EXIT_FAILURE; | |
899 | } | |
900 | ||
f31d49c8 DR |
901 | if (prepend_path(t->path) < 0) { |
902 | ERR("failed to prepend '%s' to PATH\n", t->path); | |
903 | return EXIT_FAILURE; | |
904 | } | |
905 | ||
80f9e023 LDM |
906 | LOG("running %s, in forked context\n", t->name); |
907 | ||
908 | pid = fork(); | |
909 | if (pid < 0) { | |
910 | ERR("could not fork(): %m\n"); | |
911 | LOG("FAILED: %s\n", t->name); | |
912 | return EXIT_FAILURE; | |
913 | } | |
914 | ||
45481ee2 | 915 | if (pid > 0) |
b8e344a6 | 916 | return test_run_parent(t, fdout, fderr, fdmonitor, pid); |
80f9e023 | 917 | |
ed8e93fd | 918 | return test_run_child(t, fdout, fderr, fdmonitor); |
80f9e023 | 919 | } |