testsuite: add options separator to xeno-regression-test
[xenomai-head.git] / src / testsuite / xeno-test / xeno-test-run.c
blob69d10b166ad85a8f3a8a11da1a5e9dbb85387e64
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <errno.h>
5 #include <ctype.h>
7 #include <sys/types.h>
8 #include <sys/fcntl.h>
9 #include <unistd.h>
10 #include <signal.h>
11 #include <sys/wait.h>
12 #include <sys/select.h>
14 #define CHILD_SCRIPT 0
15 #define CHILD_CHECKED 1
16 #define CHILD_LOAD 2
17 #define CHILD_ANY -1
19 struct child {
20 unsigned type: 2;
21 unsigned dead: 1;
22 pid_t pid;
23 struct child *next;
24 int in, out;
25 time_t timeout;
26 int exit_status;
27 void (*handle)(struct child *, fd_set *);
30 #define fail_fprintf(f, fmt, args...) \
31 fprintf(f, "%s failed: " fmt, scriptname , ##args)
33 #define fail_perror(str) \
34 fail_fprintf(stderr, "%s: %s\n", str, strerror(errno))
36 static const char *scriptname;
37 static volatile int sigexit;
38 static time_t termload_start, sigexit_start = 0;
39 static sigset_t sigchld_mask;
40 static struct child *first_child;
41 static char default_loadcmd[] = "dohell 900";
42 static char *loadcmd = default_loadcmd;
43 static fd_set inputs;
44 static struct child script, load;
46 void handle_checked_child(struct child *child, fd_set *fds);
47 void handle_script_child(struct child *child, fd_set *fds);
48 void handle_load_child(struct child *child, fd_set *fds);
50 int child_initv(struct child *child, int type, char *argv[])
52 int pipe_in[2];
53 int pipe_out[2];
54 int err, i;
55 pid_t pid;
57 if (pipe(pipe_out) < 0)
58 return -errno;
60 /* Set the CLOEXEC flag so that we do not leak file
61 descriptors in our children. */
62 fcntl(pipe_out[0], F_SETFD,
63 fcntl(pipe_out[0], F_GETFD) | FD_CLOEXEC);
64 fcntl(pipe_out[1], F_SETFD,
65 fcntl(pipe_out[0], F_GETFD) | FD_CLOEXEC);
67 if (type == CHILD_SCRIPT) {
68 if (pipe(pipe_in) < 0)
69 goto err_close_pipe_out;
71 /* Set the CLOEXEC flag so that we do not leak file
72 descriptors in our children. */
73 fcntl(pipe_in[0], F_SETFD,
74 fcntl(pipe_out[0], F_GETFD) | FD_CLOEXEC);
75 fcntl(pipe_in[1], F_SETFD,
76 fcntl(pipe_out[0], F_GETFD) | FD_CLOEXEC);
80 sigprocmask(SIG_BLOCK, &sigchld_mask, NULL);
81 pid = vfork();
82 if (pid < 0) {
83 sigprocmask(SIG_UNBLOCK, &sigchld_mask, NULL);
84 err = -errno;
85 goto out_close_pipe;
88 if (pid == 0) {
89 sigprocmask(SIG_UNBLOCK, &sigchld_mask, NULL);
91 switch(type) {
92 case CHILD_CHECKED:
93 case CHILD_LOAD:
94 if (dup2(pipe_out[1], STDOUT_FILENO) < 0) {
95 fail_perror("dup2(pipe_out)");
96 _exit(EXIT_FAILURE);
98 if (dup2(pipe_out[1], STDERR_FILENO) < 0) {
99 fail_perror("dup2(pipe_err)");
100 _exit(EXIT_FAILURE);
102 /* Detach child from terminal,
103 to avoid child catching SIGINT */
104 setsid();
106 break;
108 case CHILD_SCRIPT:
109 if (dup2(pipe_in[0], 1022) < 0) {
110 fail_perror("dup2(pipe_in)");
111 _exit(EXIT_FAILURE);
113 if (dup2(pipe_out[1], 1023) < 0) {
114 fail_perror("dup2(pipe_out)");
115 _exit(EXIT_FAILURE);
117 break;
120 err = execvp(argv[0], argv);
121 if (err < 0) {
122 fail_fprintf(stderr, "execvp(%s): %m", argv[0]);
123 _exit(EXIT_FAILURE);
126 child->type = type;
127 child->dead = 0;
128 child->pid = pid;
130 child->next = first_child;
131 first_child = child;
132 sigprocmask(SIG_UNBLOCK, &sigchld_mask, NULL);
134 fprintf(stderr, "Started child %d:", pid);
135 for (i = 0; argv[i]; i++)
136 fprintf(stderr, " %s", argv[i]);
137 fputc('\n', stderr);
139 close(pipe_out[1]);
140 fcntl(pipe_out[0], F_SETFL,
141 fcntl(pipe_out[0], F_GETFL) | O_NONBLOCK);
142 child->out = pipe_out[0];
143 FD_SET(child->out, &inputs);
145 if (type == CHILD_SCRIPT) {
146 close(pipe_in[0]);
147 child->in = pipe_in[1];
150 time(&child->timeout);
151 child->timeout += 300;
153 switch(type) {
154 case CHILD_CHECKED:
155 child->handle = handle_checked_child;
156 break;
157 case CHILD_SCRIPT:
158 child->handle = handle_script_child;
159 break;
160 case CHILD_LOAD:
161 child->handle = handle_load_child;
162 break;
165 return 0;
167 out_close_pipe:
168 if (type == CHILD_SCRIPT) {
169 close(pipe_in[0]);
170 close(pipe_in[1]);
172 err_close_pipe_out:
173 close(pipe_out[0]);
174 close(pipe_out[1]);
175 return err;
178 int child_init(struct child *child, int type, char *cmdline)
180 char **argv = malloc(sizeof(*argv));
181 unsigned argc = 0;
182 int rc;
184 if (!argv)
185 return -ENOMEM;
187 do {
188 char **new_argv = realloc(argv, sizeof(*argv) * (argc + 2));
189 if (!new_argv) {
190 free(argv);
191 return -ENOMEM;
193 argv = new_argv;
195 argv[argc++] = cmdline;
196 cmdline = strpbrk(cmdline, " \t");
197 if (cmdline)
198 do {
199 *cmdline++ = '\0';
200 } while (isspace(*cmdline));
201 } while (cmdline && *cmdline);
202 argv[argc] = NULL;
204 rc = child_initv(child, type, argv);
206 free(argv);
208 return rc;
211 void child_cleanup(struct child *child)
213 struct child *prev;
215 if (child == first_child)
216 first_child = child->next;
217 else
218 for (prev = first_child; prev; prev = prev->next)
219 if (prev->next == child) {
220 prev->next = child->next;
221 break;
224 FD_CLR(child->out, &inputs);
225 close(child->out);
226 if (child->type == CHILD_SCRIPT)
227 close(child->in);
230 struct child *child_search_pid(pid_t pid)
232 struct child *child;
234 for (child = first_child; child; child = child->next)
235 if (child->pid == pid)
236 break;
238 return child;
241 struct child *child_search_type(int type)
243 struct child *child;
245 for (child = first_child; child; child = child->next)
246 if (child->type == type)
247 return child;
249 return NULL;
252 int children_done_p(int type)
254 struct child *child;
256 for (child = first_child; child; child = child->next)
257 if ((type == CHILD_ANY || type == child->type) && !child->dead)
258 return 0;
260 return 1;
263 int children_kill(int type, int sig)
265 struct child *child;
266 struct timespec ts;
268 if (children_done_p(type))
269 return 1;
271 for (child = first_child; child; child = child->next)
272 if ((type == CHILD_ANY || child->type == type)
273 && !child->dead)
274 kill(child->pid, sig);
276 return children_done_p(type);
279 void sigchld_handler(int sig)
281 struct child *child;
282 int status;
283 pid_t pid;
285 while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
286 child = child_search_pid(pid);
287 if (!child) {
288 fail_fprintf(stderr, "dead child %d not found!\n", pid);
289 exit(EXIT_FAILURE);
292 child->exit_status = status;
293 child->dead = 1;
297 void cleanup(void)
299 children_kill(CHILD_ANY, SIGKILL);
302 void termsig(int sig)
304 sigexit = sig;
305 sigexit_start = time(NULL);
306 children_kill(CHILD_ANY, SIGTERM);
307 signal(sig, SIG_DFL);
310 void copy(int from, int to)
312 char buffer[4096];
313 ssize_t sz;
315 do {
316 ssize_t written, wsz;
317 sz = read(from, buffer, sizeof(buffer));
318 if (sz == -1) {
319 if (errno == EAGAIN)
320 break;
321 fail_perror("read");
322 exit(EXIT_FAILURE);
325 for (written = 0; written < sz;
326 written += (wsz > 0 ? wsz : 0)) {
327 wsz = write(to, buffer + written, sz - written);
328 if (wsz == -1) {
329 fail_perror("write");
330 exit(EXIT_FAILURE);
333 } while (sz > 0);
336 void handle_checked_child(struct child *child, fd_set *fds)
338 time_t now = time(NULL);
340 if (FD_ISSET(child->out, fds)) {
341 copy(child->out, STDOUT_FILENO);
342 child->timeout = now + 300;
345 if (child->dead) {
346 int status = child->exit_status;
348 /* A checked child died, this may be abnormal if no
349 termination signal was sent. */
350 if (WIFEXITED(status)) {
351 if (sigexit || termload_start)
352 goto cleanup;
353 fail_fprintf(stderr,
354 "child %d exited with status %d\n",
355 child->pid, WEXITSTATUS(status));
358 if (WIFSIGNALED(status)) {
359 if (sigexit || termload_start) {
360 cleanup:
361 child_cleanup(child);
362 free(child);
363 return;
365 fail_fprintf(stderr, "child %d exited with signal %d\n",
366 child->pid, WTERMSIG(status));
367 if (WCOREDUMP(status))
368 fprintf(stderr, "(core dumped)\n");
371 exit(EXIT_FAILURE);
372 return;
375 if (now > child->timeout) {
376 fail_fprintf(stderr, "child %d produced no output for 5 minutes.\n",
377 child->pid);
378 exit(EXIT_FAILURE);
383 void handle_script_child(struct child *child, fd_set *fds)
385 static char buffer[4096];
386 static unsigned pos;
387 char *l, *eol;
388 ssize_t sz;
389 int rc;
391 if (child->dead) {
392 child_cleanup(child);
393 return;
396 if (!FD_ISSET(child->out, fds))
397 return;
399 sz = read(child->out, buffer + pos, sizeof(buffer) - pos);
400 for (l = buffer; (eol = strchr(l, '\n')); l = eol + 1) {
401 char buf[16];
402 *eol = '\0';
404 if (!memcmp(l, "check_alive ", 12)) {
405 struct child *new_child;
407 new_child = malloc(sizeof(*new_child));
408 if (!new_child) {
409 fail_fprintf(stderr, "allocation failed\n");
410 exit(EXIT_FAILURE);
413 rc = child_init(new_child, CHILD_CHECKED, l + 12);
414 if (rc) {
415 fail_perror("child_init");
416 exit(EXIT_FAILURE);
418 } else if (!memcmp(l, "start_load", 10)) {
419 if (!load.dead) {
420 fail_fprintf(stderr, "start_load run while load"
421 " script is already running.\n");
422 exit(EXIT_FAILURE);
424 rc = child_init(&load, CHILD_LOAD, loadcmd);
425 if (rc) {
426 fail_perror("child_init");
427 exit(EXIT_FAILURE);
429 } else {
430 fprintf(stderr, "Invalid command %s\n", l);
431 exit(EXIT_FAILURE);
434 if (l != buffer) {
435 pos = strlen(l);
436 memmove(buffer, l, pos + 1);
440 void handle_load_child(struct child *child, fd_set *fds)
442 struct child *next;
444 if (FD_ISSET(child->out, fds))
445 copy(child->out, STDOUT_FILENO);
447 if (child->dead) {
448 time_t now = time(NULL);
450 if (!termload_start) {
451 if (sigexit) {
452 child_cleanup(child);
453 return;
456 fprintf(stderr, "Load script terminated, "
457 "terminating checked scripts\n");
459 children_kill(CHILD_CHECKED, SIGTERM);
460 termload_start = now;
461 } else {
462 if (child_search_type(CHILD_CHECKED)
463 && now < termload_start + 30)
464 return;
466 if (now >= termload_start + 30) {
467 fail_fprintf(stderr, "timeout waiting for "
468 "checked children, "
469 "sending SIGKILL\n");
470 children_kill(CHILD_ANY, SIGKILL);
473 child_cleanup(child);
474 if (sigexit)
475 return;
477 write(script.in, "0\n", 2);
478 termload_start = 0;
483 void usage(const char *progname)
485 fprintf(stderr, "%s [-l \"load command\"] script arguments...\n"
486 "Run \"script\" with \"arguments\" in a shell supplemented with"
487 " a few commands\nsuitable for running Xenomai tests.\n"
488 "\"load command\" is a command line to be run in order to"
489 " generate load\nwhile running tests.\n", progname);
492 void setpath(void)
494 char *path;
495 size_t path_len;
497 path_len = strlen(getenv("PATH") ?: "") + strlen(TESTDIR) + 2;
498 path = malloc(path_len);
499 if (!path) {
500 perror("malloc");
501 exit(EXIT_FAILURE);
503 if (getenv("PATH"))
504 snprintf(path, path_len, TESTDIR ":%s", getenv("PATH"));
505 else
506 snprintf(path, path_len, TESTDIR);
508 setenv("PATH", path, 1);
511 int main(int argc, char *argv[])
513 struct sigaction action;
514 char **new_argv;
515 int rc, maxfd;
516 unsigned i;
518 if (argc < 2) {
519 usage(argv[0]);
520 exit(EXIT_FAILURE);
523 if (!strcmp(argv[1], "-h")
524 || !strcmp(argv[1], "--help")) {
525 usage(argv[0]);
526 exit(EXIT_SUCCESS);
529 if (argc >= 3) {
530 if (!strcmp(argv[2], "-l")) {
531 if (argc == 3) {
532 usage(argv[0]);
533 exit(EXIT_FAILURE);
536 loadcmd = argv[3];
538 argv[3] = argv[1];
539 argv += 2;
542 scriptname = argv[1];
544 setpath();
546 action.sa_handler = termsig;
547 sigemptyset(&action.sa_mask);
548 action.sa_flags = SA_RESTART;
549 if (sigaction(SIGTERM, &action, NULL) < 0) {
550 fail_perror("sigaction(SIGTERM)");
551 exit(EXIT_FAILURE);
553 if (sigaction(SIGINT, &action, NULL) < 0) {
554 fail_perror("sigaction(SIGTERM)");
555 exit(EXIT_FAILURE);
558 action.sa_flags |= SA_NOCLDSTOP;
559 action.sa_handler = sigchld_handler;
560 if (sigaction(SIGCHLD, &action, NULL) < 0) {
561 fail_perror("sigaction(SIGCHLD)");
562 exit(EXIT_FAILURE);
564 atexit(&cleanup);
566 load.dead = 1;
567 FD_ZERO(&inputs);
569 new_argv = malloc(sizeof(*new_argv) * (argc + 2));
570 if (!new_argv) {
571 fail_fprintf(stderr, "memory allocation failed\n");
572 exit(EXIT_FAILURE);
574 new_argv[0] = getenv("SHELL") ?: "/bin/bash";
575 new_argv[1] = TESTDIR "/xeno-test-run-wrapper";
576 for (i = 1; i < argc + 1; i++)
577 new_argv[i + 1] = argv[i];
579 rc = child_initv(&script, CHILD_SCRIPT, new_argv);
580 if (rc < 0) {
581 fail_fprintf(stderr, "script creation failed: %s\n", strerror(-rc));
582 exit(EXIT_FAILURE);
584 maxfd = script.out;
586 while (first_child) {
587 struct child *child, *next;
588 struct timeval tv;
589 fd_set in;
590 int rc;
592 tv.tv_sec = 0;
593 tv.tv_usec = 100000;
595 in = inputs;
596 rc = select(maxfd + 1, &in, NULL, NULL, &tv);
598 if (rc == -1) {
599 if (errno == EINTR)
600 continue;
601 fail_perror("select");
602 exit(EXIT_FAILURE);
605 maxfd = 0;
606 for (child = first_child; child; child = next) {
607 next = child->next;
609 if (child->out > maxfd)
610 maxfd = child->out;
612 child->handle(child, &in);
615 if (sigexit_start && time(NULL) >= sigexit_start + 30) {
616 fail_fprintf(stderr, "timeout waiting for all "
617 "children, sending SIGKILL\n");
618 children_kill(CHILD_ANY, SIGKILL);
619 sigexit_start = 0;
623 if (sigexit) {
624 signal(sigexit, SIG_DFL);
625 raise(sigexit);
627 exit(EXIT_SUCCESS);