]> git.ozlabs.org Git - ccan/blobdiff - ccan/failtest/failtest.c
failtest: Fix incorrect reuse of va_list in test/run-malloc.c.
[ccan] / ccan / failtest / failtest.c
index ad7e0feac1ec00be4b161c545e3e5c497a8cd8f6..e2e6c64fec64d188fed1b92184fbf64ad9951fd1 100644 (file)
@@ -17,8 +17,7 @@
 #include <ccan/failtest/failtest.h>
 #include <ccan/build_assert/build_assert.h>
 
-bool (*failtest_hook)(struct failtest_call *history, unsigned num)
-= failtest_default_hook;
+enum failtest_result (*failtest_hook)(struct failtest_call *, unsigned);
 
 static int tracefd = -1;
 
@@ -48,6 +47,7 @@ static struct failtest_call *history = NULL;
 static unsigned int history_num = 0;
 static int control_fd = -1;
 static struct timeval start;
+static unsigned int probe_count = 0;
 
 static struct write_call *child_writes = NULL;
 static unsigned int child_writes_num = 0;
@@ -56,7 +56,7 @@ static pid_t lock_owner;
 static struct lock_info *locks = NULL;
 static unsigned int lock_num = 0;
 
-static const char info_to_arg[] = "mceoprwf";
+static const char info_to_arg[] = "mceoxprwf";
 
 /* Dummy call used for failtest_undo wrappers. */
 static struct failtest_call unrecorded_call;
@@ -86,11 +86,6 @@ static struct failtest_call *add_history_(enum failtest_call_type type,
 #define set_cleanup(call, clean, type)                 \
        (call)->cleanup = (void *)((void)sizeof(clean((type *)NULL)), (clean))
 
-bool failtest_default_hook(struct failtest_call *history, unsigned num)
-{
-       return true;
-}
-
 static bool read_write_info(int fd)
 {
        struct write_call *w;
@@ -224,7 +219,7 @@ static void trace_str(const char *str)
 {
        ssize_t ret;
 
-       while ((ret = write(tracefd, str, strlen(str))) <= 0) {
+       while ((ret = write(tracefd, str, strlen(str))) > 0) {
                str += ret;
                if (!*str)
                        return;
@@ -305,6 +300,47 @@ static void restore_files(struct saved_file *s)
        }
 }
 
+/* Free up memory, so valgrind doesn't report leaks. */
+static void free_everything(void)
+{
+       unsigned int i;
+
+       /* We don't do this in cleanup: needed even for failed opens. */
+       for (i = 0; i < history_num; i++) {
+               if (history[i].type == FAILTEST_OPEN)
+                       free((char *)history[i].u.open.pathname);
+       }
+       free(history);
+}
+
+static NORETURN void failtest_cleanup(bool forced_cleanup, int status)
+{
+       int i;
+
+       /* For children, we don't care if they "failed" the testing. */
+       if (control_fd != -1)
+               status = 0;
+
+       if (forced_cleanup)
+               history_num--;
+
+       /* Cleanup everything, in reverse order. */
+       for (i = history_num - 1; i >= 0; i--) {
+               if (!history[i].cleanup)
+                       continue;
+               if (!forced_cleanup) {
+                       printf("Leak at %s:%u\n",
+                              history[i].file, history[i].line);
+                       status = 1;
+               }
+               history[i].cleanup(&history[i].u);
+       }
+
+       free_everything();
+       tell_parent(SUCCESS);
+       exit(status);
+}
+
 static bool should_fail(struct failtest_call *call)
 {
        int status;
@@ -314,6 +350,10 @@ static bool should_fail(struct failtest_call *call)
        size_t outlen = 0;
        struct saved_file *files;
 
+       /* Are we probing? */
+       if (probe_count && --probe_count == 0)
+               failtest_cleanup(true, 0);
+
        if (call == &unrecorded_call)
                return false;
 
@@ -353,9 +393,24 @@ static bool should_fail(struct failtest_call *call)
                }
        }
 
-       if (!failtest_hook(history, history_num)) {
-               call->fail = false;
-               return false;
+       if (failtest_hook) {
+               switch (failtest_hook(history, history_num)) {
+               case FAIL_OK:
+                       break;
+               case FAIL_DONT_FAIL:
+                       call->fail = false;
+                       return false;
+               case FAIL_PROBE:
+                       /* Already down probe path?  Stop now. */
+                       if (probe_count)
+                               failtest_cleanup(true, 0);
+                       /* FIXME: We should run *parent* and run probe until
+                        * calls match up again. */
+                       probe_count = 3;
+                       break;
+               default:
+                       abort();
+               }
        }
 
        files = save_files();
@@ -455,9 +510,13 @@ static bool should_fail(struct failtest_call *call)
        close(output[0]);
        close(control[0]);
        waitpid(child, &status, 0);
-       if (!WIFEXITED(status))
-               child_fail(out, outlen, "Killed by signal %u: ",
-                          WTERMSIG(status));
+       if (!WIFEXITED(status)) {
+               if (WTERMSIG(status) == SIGUSR1)
+                       child_fail(out, outlen, "Timed out");
+               else
+                       child_fail(out, outlen, "Killed by signal %u: ",
+                                  WTERMSIG(status));
+       }
        /* Child printed failure already, just pass up exit code. */
        if (type == FAILURE) {
                fprintf(stderr, "%.*s", (int)outlen, out);
@@ -821,9 +880,20 @@ add_lock(struct lock_info *locks, int fd, off_t start, off_t end, int type)
 }
 
 /* We trap this so we can record it: we don't fail it. */
-int failtest_close(int fd)
+int failtest_close(int fd, const char *file, unsigned line)
 {
        int i;
+       struct close_call call;
+       struct failtest_call *p;
+
+       call.fd = fd;
+       p = add_history(FAILTEST_CLOSE, file, line, &call);
+       p->fail = false;
+
+       /* Consume close from failpath. */
+       if (failpath)
+               if (should_fail(p))
+                       abort();
 
        if (fd < 0)
                return close(fd);
@@ -913,7 +983,6 @@ int failtest_fcntl(int fd, const char *file, unsigned line, int cmd, ...)
        }
 
        p = add_history(FAILTEST_FCNTL, file, line, &call);
-       get_locks();
 
        if (should_fail(p)) {
                p->u.fcntl.ret = -1;
@@ -922,6 +991,7 @@ int failtest_fcntl(int fd, const char *file, unsigned line, int cmd, ...)
                else
                        p->error = EDEADLK;
        } else {
+               get_locks();
                p->u.fcntl.ret = fcntl(p->u.fcntl.fd, p->u.fcntl.cmd,
                                       &p->u.fcntl.arg.fl);
                if (p->u.fcntl.ret == -1)
@@ -959,39 +1029,12 @@ void failtest_init(int argc, char *argv[])
        gettimeofday(&start, NULL);
 }
 
-/* Free up memory, so valgrind doesn't report leaks. */
-static void free_everything(void)
-{
-       unsigned int i;
-
-       /* We don't do this in cleanup: needed even for failed opens. */
-       for (i = 0; i < history_num; i++) {
-               if (history[i].type == FAILTEST_OPEN)
-                       free((char *)history[i].u.open.pathname);
-       }
-       free(history);
-}
-
 void failtest_exit(int status)
 {
-       int i;
-
-       if (control_fd == -1) {
-               free_everything();
-               exit(status);
-       }
-
        if (failtest_exit_check) {
                if (!failtest_exit_check(history, history_num))
                        child_fail(NULL, 0, "failtest_exit_check failed\n");
        }
 
-       /* Cleanup everything, in reverse order. */
-       for (i = history_num - 1; i >= 0; i--)
-               if (history[i].cleanup)
-                       history[i].cleanup(&history[i].u);
-
-       free_everything();
-       tell_parent(SUCCESS);
-       exit(0);
+       failtest_cleanup(false, status);
 }