selftests/harness: Handle timeouts cleanly
authorKees Cook <keescook@chromium.org>
Fri, 13 Mar 2020 23:12:52 +0000 (16:12 -0700)
committerShuah Khan <skhan@linuxfoundation.org>
Thu, 26 Mar 2020 21:27:52 +0000 (15:27 -0600)
When a selftest would timeout before, the program would just fall over
and no accounting of failures would be reported (i.e. it would result in
an incomplete TAP report). Instead, add an explicit SIGALRM handler to
cleanly catch and report the timeout.

Before:

[==========] Running 2 tests from 2 test cases.
[ RUN      ] timeout.finish
[       OK ] timeout.finish
[ RUN      ] timeout.too_long
Alarm clock

After:

[==========] Running 2 tests from 2 test cases.
[ RUN      ] timeout.finish
[       OK ] timeout.finish
[ RUN      ] timeout.too_long
timeout.too_long: Test terminated by timeout
[     FAIL ] timeout.too_long
[==========] 1 / 2 tests passed.
[  FAILED  ]

Signed-off-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
tools/testing/selftests/kselftest_harness.h

index c7b67e3792196e8ae7e14468cd6411b39f70dffa..2902f6a78f8a313fa1da2511984bc47579a46916 100644 (file)
@@ -639,7 +639,8 @@ struct __test_metadata {
        int termsig;
        int passed;
        int trigger; /* extra handler after the evaluation */
-       int timeout;
+       int timeout;    /* seconds to wait for test timeout */
+       bool timed_out; /* did this test timeout instead of exiting? */
        __u8 step;
        bool no_print; /* manual trigger when TH_LOG_STREAM is not available */
        struct __test_metadata *prev, *next;
@@ -696,15 +697,63 @@ static inline int __bail(int for_realz, bool no_print, __u8 step)
        return 0;
 }
 
+struct __test_metadata *__active_test;
+static void __timeout_handler(int sig, siginfo_t *info, void *ucontext)
+{
+       struct __test_metadata *t = __active_test;
+
+       /* Sanity check handler execution environment. */
+       if (!t) {
+               fprintf(TH_LOG_STREAM,
+                       "no active test in SIGARLM handler!?\n");
+               abort();
+       }
+       if (sig != SIGALRM || sig != info->si_signo) {
+               fprintf(TH_LOG_STREAM,
+                       "%s: SIGALRM handler caught signal %d!?\n",
+                       t->name, sig != SIGALRM ? sig : info->si_signo);
+               abort();
+       }
+
+       t->timed_out = true;
+       kill(t->pid, SIGKILL);
+}
+
 void __wait_for_test(struct __test_metadata *t)
 {
+       struct sigaction action = {
+               .sa_sigaction = __timeout_handler,
+               .sa_flags = SA_SIGINFO,
+       };
+       struct sigaction saved_action;
        int status;
 
+       if (sigaction(SIGALRM, &action, &saved_action)) {
+               t->passed = 0;
+               fprintf(TH_LOG_STREAM,
+                       "%s: unable to install SIGARLM handler\n",
+                       t->name);
+               return;
+       }
+       __active_test = t;
+       t->timed_out = false;
        alarm(t->timeout);
        waitpid(t->pid, &status, 0);
        alarm(0);
+       if (sigaction(SIGALRM, &saved_action, NULL)) {
+               t->passed = 0;
+               fprintf(TH_LOG_STREAM,
+                       "%s: unable to uninstall SIGARLM handler\n",
+                       t->name);
+               return;
+       }
+       __active_test = NULL;
 
-       if (WIFEXITED(status)) {
+       if (t->timed_out) {
+               t->passed = 0;
+               fprintf(TH_LOG_STREAM,
+                       "%s: Test terminated by timeout\n", t->name);
+       } else if (WIFEXITED(status)) {
                t->passed = t->termsig == -1 ? !WEXITSTATUS(status) : 0;
                if (t->termsig != -1) {
                        fprintf(TH_LOG_STREAM,