From 57cc9494b0daacd23372cdbdefcd31fe9320c836 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 30 Nov 2011 09:09:11 +1030 Subject: [PATCH] failtest: internally eliminate duplicate calls. If we can get a backtrace, we can automatically eliminate identical failures. Surprisingly backtrace() is quite fast, but the savings for the (naively-written) rbtree tests are impressive. ccanlint -v time drops from 43 seconds to 6 seconds. --- ccan/failtest/_info | 2 + ccan/failtest/failtest.c | 76 +++++++++++++++++++++++++++++++ ccan/failtest/failtest.h | 3 ++ ccan/failtest/test/run-failpath.c | 1 + ccan/failtest/test/run-locking.c | 1 + ccan/failtest/test/run-malloc.c | 1 + ccan/failtest/test/run-open.c | 1 + 7 files changed, 85 insertions(+) diff --git a/ccan/failtest/_info b/ccan/failtest/_info index d8b2a252..dd90c3d9 100644 --- a/ccan/failtest/_info +++ b/ccan/failtest/_info @@ -63,6 +63,8 @@ int main(int argc, char *argv[]) if (strcmp(argv[1], "depends") == 0) { printf("ccan/build_assert\n"); printf("ccan/compiler\n"); + printf("ccan/hash\n"); + printf("ccan/htable\n"); printf("ccan/read_write_all\n"); printf("ccan/str\n"); printf("ccan/time\n"); diff --git a/ccan/failtest/failtest.c b/ccan/failtest/failtest.c index 3c4cd62a..ec41e92b 100644 --- a/ccan/failtest/failtest.c +++ b/ccan/failtest/failtest.c @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include enum failtest_result (*failtest_hook)(struct tlist_calls *); @@ -46,12 +48,44 @@ struct lock_info { int type; }; +/* We hash the call location together with its backtrace. */ +static size_t hash_call(const struct failtest_call *call) +{ + return hash(call->file, strlen(call->file), + hash(&call->line, 1, + hash(call->backtrace, call->backtrace_num, + call->type))); +} + +static bool call_eq(const struct failtest_call *call1, + const struct failtest_call *call2) +{ + unsigned int i; + + if (strcmp(call1->file, call2->file) != 0 + || call1->line != call2->line + || call1->type != call2->type + || call1->backtrace_num != call2->backtrace_num) + return false; + + for (i = 0; i < call1->backtrace_num; i++) + if (call1->backtrace[i] != call2->backtrace[i]) + return false; + + return true; +} + +/* Defines struct failtable. */ +HTABLE_DEFINE_TYPE(struct failtest_call, (struct failtest_call *), hash_call, + call_eq, failtable); + bool (*failtest_exit_check)(struct tlist_calls *history); static struct tlist_calls history = TLIST_INIT(history); static int control_fd = -1; static struct timeval start; static bool probing = false; +static struct failtable failtable; static struct write_call *child_writes = NULL; static unsigned int child_writes_num = 0; @@ -67,6 +101,34 @@ static const char info_to_arg[] = "mceoxprwf"; /* Dummy call used for failtest_undo wrappers. */ static struct failtest_call unrecorded_call; +#if HAVE_BACKTRACE +#include + +static void **get_backtrace(unsigned int *num) +{ + static unsigned int max_back = 100; + void **ret; + +again: + ret = malloc(max_back * sizeof(void *)); + *num = backtrace(ret, max_back); + if (*num == max_back) { + free(ret); + max_back *= 2; + goto again; + } + return ret; +} +#else +/* This will test slightly less, since will consider all of the same + * calls as identical. But, it's slightly faster! */ +static void **get_backtrace(unsigned int *num) +{ + *num = 0; + return NULL; +} +#endif /* HAVE_BACKTRACE */ + static struct failtest_call *add_history_(enum failtest_call_type type, const char *file, unsigned int line, @@ -84,6 +146,7 @@ static struct failtest_call *add_history_(enum failtest_call_type type, call->file = file; call->line = line; call->cleanup = NULL; + call->backtrace = get_backtrace(&call->backtrace_num); memcpy(&call->u, elem, elem_size); tlist_add_tail(&history, call, list); return call; @@ -381,6 +444,7 @@ static void free_call(struct failtest_call *call) /* We don't do this in cleanup: needed even for failed opens. */ if (call->type == FAILTEST_OPEN) free((char *)call->u.open.pathname); + free(call->backtrace); tlist_del_from(&history, call, list); free(call); } @@ -392,6 +456,8 @@ static void free_everything(void) while ((i = tlist_top(&history, struct failtest_call, list)) != NULL) free_call(i); + + failtable_clear(&failtable); } static NORETURN void failtest_cleanup(bool forced_cleanup, int status) @@ -433,6 +499,7 @@ static bool should_fail(struct failtest_call *call) char *out = NULL; size_t outlen = 0; struct saved_file *files; + struct failtest_call *dup; if (call == &unrecorded_call) return false; @@ -478,6 +545,11 @@ static bool should_fail(struct failtest_call *call) if (probing) return call->fail = false; + /* Don't more than once in the same place. */ + dup = failtable_get(&failtable, call); + if (dup) + return call->fail = false; + if (failtest_hook) { switch (failtest_hook(&history)) { case FAIL_OK: @@ -493,6 +565,9 @@ static bool should_fail(struct failtest_call *call) } } + /* Add it to our table of calls. */ + failtable_add(&failtable, call); + files = save_files(); /* We're going to fail in the child. */ @@ -1123,6 +1198,7 @@ void failtest_init(int argc, char *argv[]) debugpath = argv[i] + strlen("--debugpath="); } } + failtable_init(&failtable); start = time_now(); } diff --git a/ccan/failtest/failtest.h b/ccan/failtest/failtest.h index 294c28b9..f473f14f 100644 --- a/ccan/failtest/failtest.h +++ b/ccan/failtest/failtest.h @@ -137,6 +137,9 @@ struct failtest_call { int error; /* How do we clean this up? */ void (*cleanup)(void *u); + /* Backtrace of call chain. */ + void **backtrace; + unsigned int backtrace_num; /* The actual call data. */ union { struct calloc_call calloc; diff --git a/ccan/failtest/test/run-failpath.c b/ccan/failtest/test/run-failpath.c index 3c93c2ad..84f473c7 100644 --- a/ccan/failtest/test/run-failpath.c +++ b/ccan/failtest/test/run-failpath.c @@ -11,6 +11,7 @@ int main(void) void *p; plan_tests(14); + failtest_init(0, NULL); failpath = "mceopwrMCEOPWR"; diff --git a/ccan/failtest/test/run-locking.c b/ccan/failtest/test/run-locking.c index f29f213d..13fe0b97 100644 --- a/ccan/failtest/test/run-locking.c +++ b/ccan/failtest/test/run-locking.c @@ -95,6 +95,7 @@ int main(void) unsigned int isize; plan_tests(5835); + failtest_init(0, NULL); failtest_hook = dont_fail; fd = open("run-locking-scratch", O_RDWR|O_CREAT, 0600); diff --git a/ccan/failtest/test/run-malloc.c b/ccan/failtest/test/run-malloc.c index 3912bfd0..e00ec700 100644 --- a/ccan/failtest/test/run-malloc.c +++ b/ccan/failtest/test/run-malloc.c @@ -95,6 +95,7 @@ int main(void) int status; plan_tests(3); + failtest_init(0, NULL); status = setjmp(exited); if (status == 0) { diff --git a/ccan/failtest/test/run-open.c b/ccan/failtest/test/run-open.c index e0550457..b2a5aab1 100644 --- a/ccan/failtest/test/run-open.c +++ b/ccan/failtest/test/run-open.c @@ -14,6 +14,7 @@ int main(void) struct stat st; plan_tests(12); + failtest_init(0, NULL); if (pipe(pfd)) abort(); -- 2.39.2