Commit c9f915e0 authored by Rusty Russell's avatar Rusty Russell

failtest: hook can return FAIL_PROBE

tdb2 has various places where it recovers from failure (eg. falling
back when it can't open /dev/urandom, or allocation for error
logging).  We want to test those paths, but doing so thoroughly causes
cominatorial explosion.

Add FAIL_PROBE for such cases: in this case it goes only 3 more calls
deep.
parent c96ab569
...@@ -17,8 +17,7 @@ ...@@ -17,8 +17,7 @@
#include <ccan/failtest/failtest.h> #include <ccan/failtest/failtest.h>
#include <ccan/build_assert/build_assert.h> #include <ccan/build_assert/build_assert.h>
bool (*failtest_hook)(struct failtest_call *history, unsigned num) enum failtest_result (*failtest_hook)(struct failtest_call *, unsigned);
= failtest_default_hook;
static int tracefd = -1; static int tracefd = -1;
...@@ -48,6 +47,7 @@ static struct failtest_call *history = NULL; ...@@ -48,6 +47,7 @@ static struct failtest_call *history = NULL;
static unsigned int history_num = 0; static unsigned int history_num = 0;
static int control_fd = -1; static int control_fd = -1;
static struct timeval start; static struct timeval start;
static unsigned int probe_count = 0;
static struct write_call *child_writes = NULL; static struct write_call *child_writes = NULL;
static unsigned int child_writes_num = 0; static unsigned int child_writes_num = 0;
...@@ -86,11 +86,6 @@ static struct failtest_call *add_history_(enum failtest_call_type type, ...@@ -86,11 +86,6 @@ static struct failtest_call *add_history_(enum failtest_call_type type,
#define set_cleanup(call, clean, type) \ #define set_cleanup(call, clean, type) \
(call)->cleanup = (void *)((void)sizeof(clean((type *)NULL)), (clean)) (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) static bool read_write_info(int fd)
{ {
struct write_call *w; struct write_call *w;
...@@ -305,6 +300,40 @@ static void restore_files(struct saved_file *s) ...@@ -305,6 +300,40 @@ 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;
if (forced_cleanup)
history_num--;
/* 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();
if (control_fd == -1)
exit(status);
tell_parent(SUCCESS);
exit(0);
}
static bool should_fail(struct failtest_call *call) static bool should_fail(struct failtest_call *call)
{ {
int status; int status;
...@@ -314,6 +343,10 @@ static bool should_fail(struct failtest_call *call) ...@@ -314,6 +343,10 @@ static bool should_fail(struct failtest_call *call)
size_t outlen = 0; size_t outlen = 0;
struct saved_file *files; struct saved_file *files;
/* Are we probing? */
if (probe_count && --probe_count == 0)
failtest_cleanup(true, 0);
if (call == &unrecorded_call) if (call == &unrecorded_call)
return false; return false;
...@@ -353,9 +386,24 @@ static bool should_fail(struct failtest_call *call) ...@@ -353,9 +386,24 @@ static bool should_fail(struct failtest_call *call)
} }
} }
if (!failtest_hook(history, history_num)) { if (failtest_hook) {
call->fail = false; switch (failtest_hook(history, history_num)) {
return false; 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(); files = save_files();
...@@ -974,39 +1022,12 @@ void failtest_init(int argc, char *argv[]) ...@@ -974,39 +1022,12 @@ void failtest_init(int argc, char *argv[])
gettimeofday(&start, NULL); 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) void failtest_exit(int status)
{ {
int i;
if (failtest_exit_check) { if (failtest_exit_check) {
if (!failtest_exit_check(history, history_num)) if (!failtest_exit_check(history, history_num))
child_fail(NULL, 0, "failtest_exit_check failed\n"); child_fail(NULL, 0, "failtest_exit_check failed\n");
} }
if (control_fd == -1) { failtest_cleanup(false, status);
free_everything();
exit(status);
}
/* 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);
} }
...@@ -143,30 +143,42 @@ struct failtest_call { ...@@ -143,30 +143,42 @@ struct failtest_call {
} u; } u;
}; };
enum failtest_result {
/* Yes try failing this call. */
FAIL_OK,
/* No, don't try failing this call. */
FAIL_DONT_FAIL,
/* Try failing this call but don't go too far down that path. */
FAIL_PROBE,
};
/** /**
* failtest_hook - whether a certain call should fail or not. * failtest_hook - whether a certain call should fail or not.
* @history: the ordered history of all failtest calls. * @history: the ordered history of all failtest calls.
* @num: the number of elements in @history (greater than 0) * @num: the number of elements in @history (greater than 0)
* *
* The default value of this hook is failtest_default_hook(), which returns * The default value of this hook is failtest_default_hook(), which returns
* true (ie. yes, fail the call). * FAIL_OK (ie. yes, fail the call).
* *
* You can override it, and avoid failing certain calls. The parameters * You can override it, and avoid failing certain calls. The parameters
* of the call (but not the return value(s)) will be filled in for the last * of the call (but not the return value(s)) will be filled in for the last
* call. * call.
* *
* Example: * Example:
* static bool dont_fail_allocations(struct failtest_call *history, * static enum failtest_result dont_fail_alloc(struct failtest_call *hist,
* unsigned num) * unsigned num)
* { * {
* return history[num-1].type != FAILTEST_MALLOC * if (hist[num-1].type == FAILTEST_MALLOC
* && history[num-1].type != FAILTEST_CALLOC * || hist[num-1].type == FAILTEST_CALLOC
* && history[num-1].type != FAILTEST_REALLOC; * || hist[num-1].type == FAILTEST_REALLOC)
* return FAIL_DONT_FAIL;
* return FAIL_OK;
* } * }
* ... * ...
* failtest_hook = dont_fail_allocations; * failtest_hook = dont_fail_alloc;
*/ */
extern bool (*failtest_hook)(struct failtest_call *history, unsigned num); extern enum failtest_result
(*failtest_hook)(struct failtest_call *history, unsigned num);
/** /**
* failtest_exit_check - hook for additional checks on a failed child. * failtest_exit_check - hook for additional checks on a failed child.
...@@ -183,9 +195,6 @@ extern bool (*failtest_hook)(struct failtest_call *history, unsigned num); ...@@ -183,9 +195,6 @@ extern bool (*failtest_hook)(struct failtest_call *history, unsigned num);
extern bool (*failtest_exit_check)(struct failtest_call *history, extern bool (*failtest_exit_check)(struct failtest_call *history,
unsigned num); unsigned num);
/* This usually fails the call. */
bool failtest_default_hook(struct failtest_call *history, unsigned num);
/** /**
* failtest_timeout_ms - how long to wait before killing child. * failtest_timeout_ms - how long to wait before killing child.
* *
......
...@@ -10,9 +10,10 @@ ...@@ -10,9 +10,10 @@
#define SIZE 8 #define SIZE 8
/* We don't want to fork and fail; we're just testing lock recording. */ /* We don't want to fork and fail; we're just testing lock recording. */
static bool dont_fail(struct failtest_call *history, unsigned num) static enum failtest_result dont_fail(struct failtest_call *history,
unsigned num)
{ {
return false; return FAIL_DONT_FAIL;
} }
static bool place_lock(int fd, char lockarr[], unsigned pos, unsigned size, static bool place_lock(int fd, char lockarr[], unsigned pos, unsigned size,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment