Add t_malloc_snapshot() which writes a snapshot of the allocator state to

a caller-provided buffer.  Use it to warn about leaks in each individual
test case.  Note that we can't fail a test case for leaking, because
individual test cases in a unit may modify shared state which is cleaned
up at the end of the series.
This commit is contained in:
Dag-Erling Smørgrav 2015-11-02 19:34:03 +00:00 committed by des
parent 3e88a08c10
commit 955cbc3013
4 changed files with 58 additions and 14 deletions

View file

@ -136,7 +136,7 @@ AM_CONDITIONAL([WITH_RSAREF], [test x"$with_rsaref" = x"yes"])
AC_ARG_ENABLE([developer-warnings], AC_ARG_ENABLE([developer-warnings],
AS_HELP_STRING([--enable-developer-warnings], AS_HELP_STRING([--enable-developer-warnings],
[enable strict warnings (default is NO)]), [enable strict warnings (default is NO)]),
[CFLAGS="${CFLAGS} -Wall -Wextra -Wcast-qual"]) [CFLAGS="${CFLAGS} -Wall -Wextra -Wcast-qual -Wunreachable-code"])
AC_ARG_ENABLE([debugging-symbols], AC_ARG_ENABLE([debugging-symbols],
AS_HELP_STRING([--enable-debugging-symbols], AS_HELP_STRING([--enable-debugging-symbols],
[enable debugging symbols (default is NO)]), [enable debugging symbols (default is NO)]),

View file

@ -125,6 +125,7 @@ extern const uint8_t t_seq8[256];
extern int t_malloc_fail; extern int t_malloc_fail;
extern int t_malloc_fail_after; extern int t_malloc_fail_after;
extern int t_malloc_fatal; extern int t_malloc_fatal;
size_t t_malloc_snapshot(void *, size_t);
#ifdef _IONBF /* proxy for <stdio.h> */ #ifdef _IONBF /* proxy for <stdio.h> */
void t_malloc_printstats(FILE *); void t_malloc_printstats(FILE *);
#endif #endif

View file

@ -48,6 +48,9 @@ const char *t_progname;
/* verbose flag */ /* verbose flag */
static int verbose; static int verbose;
/* whether to check for leaks */
static int leaktest;
/* /*
* If verbose flag is set, print an array of bytes in hex * If verbose flag is set, print an array of bytes in hex
*/ */
@ -161,9 +164,13 @@ t_handle_signal(int signo)
static int static int
t_run_test(struct t_test *t, int n) t_run_test(struct t_test *t, int n)
{ {
unsigned long snap1[16], snap2[16];
size_t snaplen;
char *desc; char *desc;
int i, ret, signo; int i, ret, signo;
if (leaktest && verbose)
t_malloc_snapshot(snap1, sizeof snap1);
for (i = 0; sigs[i] > 0; ++i) for (i = 0; sigs[i] > 0; ++i)
signal(sigs[i], t_handle_signal); signal(sigs[i], t_handle_signal);
desc = t->desc; desc = t->desc;
@ -192,6 +199,13 @@ t_run_test(struct t_test *t, int n)
free(desc); free(desc);
for (i = 0; sigs[i] > 0; ++i) for (i = 0; sigs[i] > 0; ++i)
signal(sigs[i], SIG_DFL); signal(sigs[i], SIG_DFL);
if (leaktest && verbose) {
snaplen = t_malloc_snapshot(snap2, sizeof snap2);
if (snaplen > sizeof snap2)
snaplen = sizeof snap2;
if (memcmp(snap1, snap2, snaplen) != 0)
t_verbose("WARNING: allocator state changed\n");
}
return (ret); return (ret);
} }
@ -202,7 +216,7 @@ static void
usage(void) usage(void)
{ {
fprintf(stderr, "usage: %s [-v]\n", t_progname); fprintf(stderr, "usage: %s [-Llv]\n", t_progname);
exit(1); exit(1);
} }
@ -228,9 +242,22 @@ main(int argc, char *argv[])
else else
t_progname = argv[0]; t_progname = argv[0];
/* check for leaks, unless doing coverage analysis */
#if CRYB_COVERAGE
leaktest = t_str_is_true(getenv("CRYB_LEAKTEST"));
#else
leaktest = !t_str_is_false(getenv("CRYB_LEAKTEST"));
#endif
/* parse command line options */ /* parse command line options */
while ((opt = getopt(argc, argv, "v")) != -1) while ((opt = getopt(argc, argv, "Llv")) != -1)
switch (opt) { switch (opt) {
case 'L':
leaktest = 0;
break;
case 'l':
leaktest = 1;
break;
case 'v': case 'v':
verbose = 1; verbose = 1;
break; break;
@ -246,17 +273,8 @@ main(int argc, char *argv[])
if (t_plan_len == 0) if (t_plan_len == 0)
errx(1, "no plan\n"); errx(1, "no plan\n");
/* do not check for leaks when doing coverage analysis */
nt = t_plan_len;
#if CRYB_COVERAGE
nt = t_str_is_true(getenv("CRYB_LEAKTEST")) ?
t_plan_len + 1 : t_plan_len;
#else
nt = t_str_is_false(getenv("CRYB_LEAKTEST")) ?
t_plan_len : t_plan_len + 1;
#endif
/* run the tests */ /* run the tests */
nt = leaktest ? t_plan_len + 1 : t_plan_len;
printf("1..%zu\n", nt); printf("1..%zu\n", nt);
for (n = pass = fail = 0; n < t_plan_len; ++n) for (n = pass = fail = 0; n < t_plan_len; ++n)
t_run_test(t_plan[n], n + 1) ? ++pass : ++fail; t_run_test(t_plan[n], n + 1) ? ++pass : ++fail;
@ -270,7 +288,7 @@ main(int argc, char *argv[])
} }
free(t_plan); free(t_plan);
setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0);
if (nt > t_plan_len) { if (leaktest) {
if (verbose) if (verbose)
t_malloc_printstats(stderr); t_malloc_printstats(stderr);
t_run_test(&t_memory_leak, nt) ? ++pass : ++fail; t_run_test(&t_memory_leak, nt) ? ++pass : ++fail;

View file

@ -466,6 +466,31 @@ free(void *p)
abort(); abort();
} }
/*
* Return a snapshot of the allocator state
*/
size_t
t_malloc_snapshot(void *buf, size_t len)
{
unsigned long snapshot[BUCKET_MAX_SHIFT * 2];
unsigned int i;
if (buf == NULL)
return (sizeof snapshot);
snapshot[0] = nmapalloc;
snapshot[1] = nmapfree;
for (i = 2; i < BUCKET_MIN_SHIFT; ++i)
snapshot[i * 2 - 2] = snapshot[i * 2 - 1] = 0;
for (i = BUCKET_MIN_SHIFT; i <= BUCKET_MAX_SHIFT; ++i) {
snapshot[i * 2 - 2] = buckets[i].nalloc;
snapshot[i * 2 - 1] = buckets[i].nfree;
}
if (len > sizeof snapshot)
len = sizeof snapshot;
memcpy(buf, snapshot, len);
return (sizeof snapshot);
}
/* /*
* Print allocator statistics * Print allocator statistics
*/ */