aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2016-12-18 18:07:12 -0800
committerGarrett D'Amore <garrett@damore.org>2016-12-18 18:07:12 -0800
commit5293a691d6036d9bccdfceab250443ca3dc92051 (patch)
tree235809edc6988419f1a8f96a5fd1fca61eaff6d3
parentda927294ee63ff0995592863ea6d3d70a239c61d (diff)
downloadnng-5293a691d6036d9bccdfceab250443ca3dc92051.tar.gz
nng-5293a691d6036d9bccdfceab250443ca3dc92051.tar.bz2
nng-5293a691d6036d9bccdfceab250443ca3dc92051.zip
Add test groups, free memory used by tests.
-rw-r--r--tests/demo.c14
-rw-r--r--tests/test.c185
-rw-r--r--tests/test.h146
3 files changed, 260 insertions, 85 deletions
diff --git a/tests/demo.c b/tests/demo.c
index c38dd3b9..26210391 100644
--- a/tests/demo.c
+++ b/tests/demo.c
@@ -1,7 +1,10 @@
#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
#include "test.h"
-test_main("Things work", {
+test_main_group({
+ test_group("Things work", {
int x;
int y;
x = 1;
@@ -47,4 +50,13 @@ test_main("Things work", {
});
});
+ });
+
+ test_group("Second group", {
+ int x = 1;
+ test_convey("x is 1", {
+ sleep(2);
+ test_so(x == 1);
+ });
+ });
})
diff --git a/tests/test.c b/tests/test.c
index 1868e409..98c7d333 100644
--- a/tests/test.c
+++ b/tests/test.c
@@ -50,6 +50,12 @@ static int nasserts = 0;
static int nskips = 0;
static const char *color_asserts = "";
+#define TEXIT_OK 0
+#define TEXIT_USAGE 1
+#define TEXIT_FAIL 2
+#define TEXIT_FATAL 3
+#define TEXIT_NOMEM 4
+
typedef struct tperfcnt {
uint64_t pc_base;
uint64_t pc_count;
@@ -66,7 +72,8 @@ typedef struct tlog {
typedef struct tctx {
char t_name[256];
struct tctx *t_parent;
- struct tctx *t_root;
+ struct tctx *t_root; /* the root node on the list */
+ struct tctx *t_next; /* root list only, for cleanup */
int t_level;
int t_done;
int t_started;
@@ -82,9 +89,9 @@ typedef struct tctx {
int t_skip;
int t_printed;
tperfcnt_t t_perfcnt;
- tlog_t t_fatallog;
- tlog_t t_faillog;
- tlog_t t_debuglog;
+ tlog_t *t_fatallog;
+ tlog_t *t_faillog;
+ tlog_t *t_debuglog;
} tctx_t;
#define PARENT(t) ((t_ctx_t *)(t->t_parent->t_data))
@@ -110,29 +117,35 @@ static tctx_t *get_ctx(void);
static void log_vprintf(tlog_t *, const char *, va_list);
static void log_printf(tlog_t *, const char *, ...);
static void log_dump(tlog_t *, const char *, const char *);
+static void log_free(tlog_t *);
+static tlog_t *log_alloc(void);
/*
- * test_i_print prints the test results. It prints more verbose information
+ * print_result prints the test results. It prints more verbose information
* in verbose mode. Note that its possible for assertion checks done at
* a given block to be recorded in a deeper block, since we can't easily
* go back up to the old line and print it.
+ *
+ * We also leverage this point to detect completion of a root context, and
+ * deallocate the child contexts. The root context should never be reentered
+ * here.
*/
-void
+static void
print_result(tctx_t *t)
{
int secs, usecs;
- if ((t->t_root == t) && !t->t_printed) {
+ stop_perfcnt(&t->t_perfcnt); /* This is idempotent */
- t->t_printed = 1;
+ if (t->t_root == t) {
stop_perfcnt(&t->t_perfcnt);
read_perfcnt(&t->t_perfcnt, &secs, &usecs);
- log_dump(&t->t_fatallog, "Errors:", color_red);
- log_dump(&t->t_faillog, "Failures:", color_yellow);
+ log_dump(t->t_fatallog, "Errors:", color_red);
+ log_dump(t->t_faillog, "Failures:", color_yellow);
if (debug) {
- log_dump(&t->t_debuglog, "Log:", color_none);
+ log_dump(t->t_debuglog, "Log:", color_none);
}
if (!verbose) {
(void) printf("%-8s%-52s%4d.%03ds\n",
@@ -152,7 +165,19 @@ print_result(tctx_t *t)
"PASS", t->t_name, secs, usecs / 10000);
}
- /* XXX: EMIT LOGS */
+ /* Remove the context, because we cannot reenter here */
+ set_specific(NULL);
+
+ while (t != NULL) {
+ tctx_t *freeit = t;
+ if (t->t_root == t) {
+ log_free(t->t_debuglog);
+ log_free(t->t_faillog);
+ log_free(t->t_fatallog);
+ }
+ t = t->t_next;
+ free(freeit);
+ }
}
}
@@ -170,6 +195,7 @@ test_i_start(test_ctx_t *ctx, test_ctx_t *parent, const char *name)
{
tctx_t *t;
+
if ((t = ctx->T_data) != NULL) {
if (t->t_done) {
print_result(t);
@@ -179,8 +205,7 @@ test_i_start(test_ctx_t *ctx, test_ctx_t *parent, const char *name)
}
ctx->T_data = (t = calloc(1, sizeof (tctx_t)));
if (t == NULL) {
- /* PANIC */
- return (1);
+ goto allocfail;
}
t->t_jmp = &ctx->T_jmp;
@@ -189,11 +214,35 @@ test_i_start(test_ctx_t *ctx, test_ctx_t *parent, const char *name)
t->t_parent = parent->T_data;
t->t_root = t->t_parent->t_root;
t->t_level = t->t_parent->t_level + 1;
+ /* unified logging against the root context */
+ t->t_debuglog = t->t_root->t_debuglog;
+ t->t_faillog = t->t_root->t_faillog;
+ t->t_fatallog = t->t_root->t_fatallog;
+ t->t_next = t->t_root->t_next;
+ t->t_root->t_next = t;
} else {
t->t_parent = t;
t->t_root = t;
+ if (((t->t_fatallog = log_alloc()) == NULL) ||
+ ((t->t_faillog = log_alloc()) == NULL) ||
+ ((t->t_debuglog = log_alloc()) == NULL)) {
+ goto allocfail;
+ }
}
return (0);
+allocfail:
+ if (t != NULL) {
+ log_free(t->t_fatallog);
+ log_free(t->t_debuglog);
+ log_free(t->t_faillog);
+ free(t);
+ ctx->T_data = NULL;
+ return (1);
+ }
+ if (parent != NULL) {
+ test_fatal("Unable to allocate context");
+ }
+ return (1);
}
/*
@@ -231,7 +280,7 @@ test_i_loop(test_ctx_t *ctx, int unwind)
if (verbose) {
if (t->t_root == t) {
- printf("\n=== RUN: %s\n", t->t_name);
+ printf("=== RUN: %s\n", t->t_name);
} else {
printf("\n");
for (i = 0; i < t->t_level; i++) {
@@ -255,6 +304,8 @@ test_i_finish(test_ctx_t *ctx, int *rvp)
{
tctx_t *t;
if ((t = ctx->T_data) == NULL) {
+ /* allocation failure */
+ *rvp = 4;
return;
}
t->t_done = 1;
@@ -278,7 +329,7 @@ test_i_skip(const char *file, int line, const char *reason)
if (verbose) {
(void) printf("%s%s%s", color_yellow, sym_skip, color_none);
}
- log_printf(&t->t_root->t_debuglog, "* Skipping rest of %s: %s: %d: %s",
+ log_printf(t->t_debuglog, "* Skipping rest of %s: %s: %d: %s",
t->t_name, file, line, reason);
t->t_done = 1; /* This forces an end */
nskips++;
@@ -299,12 +350,11 @@ test_i_assert_fail(const char *cond, const char *file, int line)
color_asserts = color_yellow;
t->t_fail++;
t->t_done = 1; /* This forces an end */
- log_printf(&t->t_root->t_faillog, "* %s (Assertion Failed)\n",
- t->t_name);
- log_printf(&t->t_root->t_faillog, "File: %s\n", file);
- log_printf(&t->t_root->t_faillog, "Line: %d\n", line);
- log_printf(&t->t_root->t_faillog, "Test: %s\n\n", cond);
- log_printf(&t->t_root->t_debuglog, "* %s (%s:%d) (FAILED)\n",
+ log_printf(t->t_faillog, "* %s (Assertion Failed)\n", t->t_name);
+ log_printf(t->t_faillog, "File: %s\n", file);
+ log_printf(t->t_faillog, "Line: %d\n", line);
+ log_printf(t->t_faillog, "Test: %s\n\n", cond);
+ log_printf(t->t_debuglog, "* %s (%s:%d) (FAILED)\n",
t->t_name, file, line);
longjmp(*t->t_jmp, 1);
}
@@ -317,7 +367,7 @@ test_i_assert_pass(const char *cond, const char *file, int line)
if (verbose) {
(void) printf("%s%s%s", color_green, sym_pass, color_none);
}
- log_printf(&t->t_root->t_debuglog, "* %s (%s:%d) (Passed)\n",
+ log_printf(t->t_debuglog, "* %s (%s:%d) (Passed)\n",
t->t_name, file, line);
}
@@ -329,7 +379,7 @@ test_i_assert_skip(const char *cond, const char *file, int line)
if (verbose) {
(void) printf("%s%s%s", color_yellow, sym_pass, color_none);
}
- log_printf(&t->t_root->t_debuglog, "* %s (%s:%d) (SKIPPED)\n",
+ log_printf(t->t_debuglog, "* %s (%s:%d) (SKIPPED)\n",
t->t_name, file, line);
}
@@ -347,12 +397,11 @@ test_i_assert_fatal(const char *cond, const char *file, int line)
color_asserts = color_red;
t->t_fail++;
t->t_done = 1; /* This forces an end */
- log_printf(&t->t_root->t_fatallog, "* %s (Fatal Assertion Failed)\n",
- t->t_name);
- log_printf(&t->t_root->t_fatallog, "File: %s\n", file);
- log_printf(&t->t_root->t_fatallog, "Line: %d\n", line);
- log_printf(&t->t_root->t_fatallog, "Test: %s\n\n", cond);
- log_printf(&t->t_root->t_debuglog, "* %s (%s:%d) (FAILED)\n",
+ log_printf(t->t_fatallog, "* %s (Fatal Assertion Failed)\n", t->t_name);
+ log_printf(t->t_fatallog, "File: %s\n", file);
+ log_printf(t->t_fatallog, "Line: %d\n", line);
+ log_printf(t->t_fatallog, "Test: %s\n\n", cond);
+ log_printf(t->t_debuglog, "* %s (%s:%d) (FAILED)\n",
t->t_name, file, line);
longjmp(*t->t_jmp, 1);
@@ -584,6 +633,23 @@ log_dump(tlog_t *log, const char *header, const char *color)
}
}
+static void
+log_free(tlog_t *log)
+{
+ if (log != NULL) {
+ if (log->l_size != 0) {
+ free(log->l_buf);
+ }
+ free(log);
+ }
+}
+
+static tlog_t *
+log_alloc(void)
+{
+ return (calloc(1, sizeof (tlog_t)));
+}
+
/*
* test_init initializes some common global stuff. Call it from main(),
* if you don't use the framework provided main.
@@ -616,7 +682,7 @@ test_debugf(const char *fmt, ...)
tctx_t *ctx = get_ctx()->t_root;
va_start(va, fmt);
- log_vprintf(&ctx->t_debuglog, fmt, va);
+ log_vprintf(ctx->t_debuglog, fmt, va);
va_end(va);
}
@@ -624,9 +690,8 @@ void
test_i_fail(const char *file, int line, const char *reason)
{
tctx_t *t = get_ctx()->t_root;
- tlog_t *faillog = &t->t_root->t_faillog;
- tlog_t *debuglog = &t->t_root->t_debuglog;
- char buffer[1024];
+ tlog_t *faillog = t->t_faillog;
+ tlog_t *debuglog = t->t_debuglog;
log_printf(debuglog, "* %s (%s:%d) (Failed): %s\n",
t->t_name, file, line, reason);
@@ -648,8 +713,8 @@ void
test_i_fatal(const char *file, int line, const char *reason)
{
tctx_t *t = get_ctx()->t_root;
- tlog_t *faillog = &t->t_root->t_fatallog;
- tlog_t *debuglog = &t->t_root->t_debuglog;
+ tlog_t *faillog = t->t_fatallog;
+ tlog_t *debuglog = t->t_debuglog;
log_printf(debuglog, "* %s (%s:%d) (Error): %s\n",
t->t_name, file, line, reason);
@@ -705,6 +770,16 @@ int
test_i_main(int argc, char **argv)
{
int i;
+ const char *status;
+ const char *prog;
+ tperfcnt_t pc;
+ int secs, usecs;
+
+ if ((argc > 0) && (argv[0] != NULL)) {
+ prog = argv[0];
+ } else {
+ prog = "<unknown>";
+ }
/*
* Poor man's getopt. Very poor. We should add a way for tests
@@ -722,8 +797,40 @@ test_i_main(int argc, char **argv)
}
}
if (test_init() != 0) {
- fprintf(stderr, "Cannot initialize test framework\n");
- exit(1);
+ (void) fprintf(stderr, "Cannot initialize test framework\n");
+ exit(TEXIT_NOMEM);
+ }
+
+ init_perfcnt(&pc);
+ start_perfcnt(&pc);
+ i = test_main_impl();
+ stop_perfcnt(&pc);
+
+ switch (i) {
+ case TEXIT_NOMEM:
+ (void) fprintf(stderr, "Cannot initialize root test context\n");
+ exit(TEXIT_NOMEM);
+ case TEXIT_OK:
+ if (verbose) {
+ (void) printf("PASS\n");
+ }
+ status = "ok";
+ break;
+ case TEXIT_FAIL:
+ status = "FAIL";
+ if (verbose) {
+ (void) printf("FAIL\n");
+ }
+ break;
+ default:
+ status = "FATAL";
+ if (verbose) {
+ (void) printf("FATAL\n");
+ }
+ break;
}
- return (test_main_impl());
+
+ read_perfcnt(&pc, &secs, &usecs);
+ (void) printf("%-8s%-52s%4d.%03ds\n", status, prog, secs, usecs / 1000);
+ exit(i);
} \ No newline at end of file
diff --git a/tests/test.h b/tests/test.h
index 0ddf428f..cf7bc76b 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -102,64 +102,109 @@ extern void test_i_fatal(const char *, int, const char *);
* code. It has to be here exposed, in order for setjmp() to work.
* and for the code block to be inlined.
*/
-#define test_i_run(T_xparent, T_xname, T_code, T_rvp) \
-do { \
- static test_ctx_t T_ctx; \
- int T_unwind; \
- if (test_i_start(&T_ctx, T_xparent, T_xname) != 0) { \
- break; \
- } \
- T_unwind = setjmp(T_ctx.T_jmp); \
- if (test_i_loop(&T_ctx, T_unwind) != 0) { \
- break; \
- } \
+#define test_i_run(T_parent, T_name, T_code, T_reset, T_rvp) \
do { \
- T_code \
- } while (0); \
- test_i_finish(&T_ctx, T_rvp); \
-} while (0)
+ static test_ctx_t T_ctx; \
+ int T_unwind; \
+ if (test_i_start(&T_ctx, T_parent, T_name) != 0) { \
+ break; \
+ } \
+ T_unwind = setjmp(T_ctx.T_jmp); \
+ if (T_unwind) { \
+ do { \
+ T_reset \
+ } while (0); \
+ } \
+ if (test_i_loop(&T_ctx, T_unwind) != 0) { \
+ break; \
+ } \
+ do { \
+ T_code \
+ } while (0); \
+ test_i_finish(&T_ctx, T_rvp); \
+ } while (0)
/*
* test_main is used to wrap the top-level of your test suite, and is
- * used in lieu of a normal main() function.
+ * used in lieu of a normal main() function. This is the usual case where
+ * the executable only contains a single top level test group.
*/
#define test_main(T_name, T_code) \
-int test_main_impl(void) { \
- int T_rv; \
- test_i_run(NULL, T_name, T_code, &T_rv); \
- return (T_rv); \
-} \
-int main(int argc, char **argv) { \
- return (test_i_main(argc, argv)); \
-}
+ int test_main_impl(void) { \
+ int T_rv; \
+ test_i_run(NULL, T_name, T_code, /*NOP*/, &T_rv); \
+ return (T_rv); \
+ } \
+ int main(int argc, char **argv) { \
+ return (test_i_main(argc, argv)); \
+ }
/*
- * test_block declares an enclosing test block. The block is inlined,
- * and a new context is created. The purpose is to create a new block
- * in a thread, or other function, instead of using main(). Note that
- * tests that run concurrently may lead to confused output. A suitable
- * parent context can be obtained with test_get_context(). If NULL is
- * supplied instead, then a new root context is created.
+ * If you want multiple top-level tests in your test suite, the test
+ * code should create a test_main_group(), with multiple calls to
+ * test_group() in the intervening section. This will cause a new main
+ * to be emitted that runs all the main groups. If a rest for the group
+ * is required, it can be supplied with test_group_reset.
*/
-#define test_block(T_parent, T_name, T_code) \
- test_i_run(T_parent, T_name, T_code, NULL)
+#define test_main_group(T_code) \
+ static int test_main_rv; \
+ int test_main_impl(void) { \
+ do { \
+ T_code \
+ } while (0); \
+ return (test_main_rv); \
+ } \
+ int main(int argc, char **argv) { \
+ return (test_i_main(argc, argv)); \
+ }
+
+#define test_group_reset(T_name, T_code, T_reset) \
+ do { \
+ int T_rv; \
+ test_i_run(NULL, T_name, T_code, T_reset, &T_rv); \
+ if (T_rv > test_main_rv) { \
+ test_main_rv = T_rv; \
+ } \
+ } while (0)
+
+#define test_group(T_name, T_code) \
+ test_group_reset(T_name, T_code, /*NOP*/)
+
+/*
+ * If you don't want to use the test framework's main routine, but
+ * prefer (or need, because of threading for example) to have your
+ * test code driven separately, you can use inject test_block() in
+ * your function. It works like test_main(). These must not be
+ * nested within test_main, test_main_group, or test_block itself:
+ * results are undefined if you try that. The final T_rvp pointer may
+ * be NULL, or is a pointer to an integer to receive the an integer
+ * result from the test. (0 is success, 4 indicates a failure to allocate
+ * memory in the test framework, and anything else indicates a
+ * an error or failure in the code being tested.
+ */
+#define test_block(T_name, T_code, T_reset) \
+ test_i_run(NULL, T_name, T_code, T_reset, NULL)
/*
* test_assert and test_so allow you to run assertions.
*/
#define test_assert(T_cond) \
- if (!(T_cond)) { \
- test_i_assert_fatal(#T_cond, __FILE__, __LINE__); \
- } else { \
- test_i_assert_pass(#T_cond, __FILE__, __LINE__); \
- }
+ do { \
+ if (!(T_cond)) { \
+ test_i_assert_fatal(#T_cond, __FILE__, __LINE__);\
+ } else { \
+ test_i_assert_pass(#T_cond, __FILE__, __LINE__);\
+ } \
+ } while (0)
#define test_so(T_cond) \
- if (!(T_cond)) { \
- test_i_assert_fail(#T_cond, __FILE__, __LINE__); \
- } else { \
- test_i_assert_pass(#T_cond, __FILE__, __LINE__); \
- }
+ do { \
+ if (!(T_cond)) { \
+ test_i_assert_fail(#T_cond, __FILE__, __LINE__);\
+ } else { \
+ test_i_assert_pass(#T_cond, __FILE__, __LINE__);\
+ } \
+ } while (0)
/*
* These are convenience versions that use the "global" context.
@@ -172,7 +217,18 @@ int main(int argc, char **argv) { \
* the body. The <code> is its scope, and may be called repeatedly
* within the body of a loop.
*/
-#define test_convey(T_name, T_code) test_i_run(T_C, T_name, T_code, NULL)
+#define test_convey(T_name, T_code) \
+ test_i_run(T_C, T_name, T_code, /*NOP*/, NULL)
+
+/*
+ * test_convey_reset is like convey, but offers the ability to specify
+ * a reset block of code, which will run to clean up after each nested
+ * convey, or when the code completes. (Note that any code placed *after*
+ * conveys is executed as part of the current context, which can be
+ * another way to clean up code just once.)
+ */
+#define test_convey_reset(T_name, T_code, T_reset) \
+ test_i_run(T_C, T_name, T_code, T_reset, NULL)
/*
* test_skip() just stops processing of the rest of the current context,
@@ -198,8 +254,8 @@ int main(int argc, char **argv) { \
/*
* test_get_context obtains the current context. It uses thread local
- * storage in the event that threading is in use, allowing concurrent threads
- * to "sort" of work.
+ * storage in the event that threading is in use. This means by default
+ * conveys started in new threads will have their own root contexts.
*/
extern test_ctx_t *test_get_context(void);