rework test to implement flags, test_results, test_stats, TEST_WILL_FAIL
authorTero Marttila <terom@fixme.fi>
Fri, 08 May 2009 02:51:20 +0300
changeset 195 42aedce3e2eb
parent 194 808b1b047620
child 196 873796250c60
rework test to implement flags, test_results, test_stats, TEST_WILL_FAIL
src/error.h
src/log.c
src/log.h
src/lua_objs.c
src/test/fail.c
src/test/test.c
src/test/test.h
src/test/test_list.c
src/test/test_list.inc
--- a/src/error.h	Fri May 08 01:43:02 2009 +0300
+++ b/src/error.h	Fri May 08 02:51:20 2009 +0300
@@ -252,6 +252,6 @@
  * Macro used to mark code segments that should never be executed (e.g. switch-default), kind of like assert
  */
 #include <stdlib.h>
-#define NOT_REACHED() abort()
+#define NOT_REACHED(val) abort()
 
 #endif
--- a/src/log.c	Fri May 08 01:43:02 2009 +0300
+++ b/src/log.c	Fri May 08 02:51:20 2009 +0300
@@ -100,7 +100,7 @@
     _log_output_ctx.func(buf, _log_output_ctx.arg);
 }
 
-void log_msg (enum log_level level, const char *func, const char *format, ...)
+void _log_msg (enum log_level level, const char *func, const char *format, ...)
 {
     va_list vargs;
     
@@ -140,3 +140,15 @@
     va_end(vargs);
 }
 
+void _log_exit (enum log_level level, int exit_code, const char *func, const char *format, ...)
+{
+    va_list vargs;
+
+    // formatted output without any suffix
+    va_start(vargs, format);
+    log_output_tag(level, "EXIT", func, format, vargs, NULL);
+    va_end(vargs);
+
+    // exit
+    exit(exit_code);
+}
--- a/src/log.h	Fri May 08 01:43:02 2009 +0300
+++ b/src/log.h	Fri May 08 02:51:20 2009 +0300
@@ -65,47 +65,50 @@
 /**
  * Log a message with the given level
  */
-void log_msg (enum log_level level, const char *func, const char *format, ...)
+#define log_msg(level, ...) _log_msg(level, __func__, __VA_ARGS__)
+void _log_msg (enum log_level level, const char *func, const char *format, ...)
     __attribute__ ((format (printf, 3, 4)));
 
 /**
  * Shorthand for log_msg
  */
-#define log_debug(...) log_msg(LOG_DEBUG, __func__, __VA_ARGS__)
-#define log_info(...)  log_msg(LOG_INFO,  __func__, __VA_ARGS__)
-#define log_warn(...)  log_msg(LOG_WARN,  __func__, __VA_ARGS__)
+#define log_debug(...) log_msg(LOG_DEBUG, __VA_ARGS__)
+#define log_info(...)  log_msg(LOG_INFO,  __VA_ARGS__)
+#define log_warn(...)  log_msg(LOG_WARN,  __VA_ARGS__)
 /* #define log_error(...) log_msg(LOG_ERROR, __func__, __VA_ARGS__) */
-#define log_fatal(...) log_msg(LOG_FATAL, __func__, __VA_ARGS__)
+#define log_fatal(...) log_msg(LOG_FATAL, __VA_ARGS__)
 
 /**
  * Log a message with the given level, appending the formatted error code name
  */
+#define log_err(err_code, ...) _log_err(LOG_ERROR, err_code, __func__, __VA_ARGS__)
+#define log_warn_err(err_code, ...) _log_err(LOG_WARN, err_code, __func__, __VA_ARGS__)
 void _log_err (enum log_level level, err_t err, const char *func, const char *format, ...)
     __attribute__ ((format (printf, 4, 5)));
 
 /**
  * Log a message with the given level, appending the formatted error message
  */
+#define log_error(err_info, ...) _log_error(LOG_ERROR, err_info, __func__, __VA_ARGS__)
+#define log_warn_error(err_info, ...) _log_error(LOG_WARN, err_info, __func__, __VA_ARGS__)
 void _log_error (enum log_level level, const error_t *err, const char *func, const char *format, ...)
     __attribute__ ((format (printf, 4, 5)));
 
 /**
  * Log using errno.
  */
+#define log_perr(...) _log_perr(LOG_ERROR, __func__, __VA_ARGS__)
 void _log_perr (enum log_level level, const char *func, const char *format, ...)
     __attribute__ ((format (printf, 3, 4)));
 
 /**
- * Shorthand for _log_*
- *
- * XXX: kill log_err_info, add log_err_code
+ * Log with an [EXIT] tag at given level, and then exit with given exit code
  */
-#define log_err(err_code, ...) _log_err(LOG_ERROR, err_code, __func__, __VA_ARGS__)
-#define log_error(err_info, ...) _log_error(LOG_ERROR, err_info, __func__, __VA_ARGS__)
-#define log_perr(...) _log_perr(LOG_ERROR, __func__, __VA_ARGS__)
+#define log_exit(level, exit_code, ...) _log_exit(level, exit_code, __func__, __VA_ARGS__)
+void _log_exit (enum log_level level, int exit_code, const char *func, const char *format, ...)
+    __attribute__ ((format (printf, 4, 5)))
+    __attribute__ ((noreturn));
 
-#define log_warn_err(err_code, ...) _log_err(LOG_WARN, err_code, __func__, __VA_ARGS__)
-#define log_warn_error(err_info, ...) _log_error(LOG_WARN, err_info, __func__, __VA_ARGS__)
 
 /**
  * log_fatal + exit failure
@@ -127,4 +130,12 @@
  */
 #define FATAL_PERROR(...) do { _log_perr(LOG_FATAL, __func__, __VA_ARGS__); abort(); } while (0)
 
+/**
+ * Exit with given code, also logging a message at LOG_INFO with anĀ [EXIT] tag
+ */
+#define EXIT_INFO(exit_code, ...)  log_exit(LOG_INFO,  exit_code, __VA_ARGS__)
+#define EXIT_WARN(exit_code, ...)  log_exit(LOG_WARN,  exit_code, __VA_ARGS__)
+#define EXIT_ERROR(exit_code, ...) log_exit(LOG_ERROR, exit_code, __VA_ARGS__)
+#define EXIT_FATAL(exit_code, ...) log_exit(LOG_FATAL, exit_code, __VA_ARGS__)
+
 #endif /* LOG_H */
--- a/src/lua_objs.c	Fri May 08 01:43:02 2009 +0300
+++ b/src/lua_objs.c	Fri May 08 02:51:20 2009 +0300
@@ -382,7 +382,7 @@
     const char *msg = luaL_checkstring(L, 2);
 
     // log it
-    log_msg(level, "lua", "%s", msg);
+    _log_msg(level, "lua", "%s", msg);
 
     // ok
     return 0;
--- a/src/test/fail.c	Fri May 08 01:43:02 2009 +0300
+++ b/src/test/fail.c	Fri May 08 02:51:20 2009 +0300
@@ -1,4 +1,5 @@
 #include "fail.h"
+#include "test.h"
 #include "../str.h"
 #include "../log.h"
 #include "backtrace.h"
@@ -12,9 +13,9 @@
 
     // print out a stack dump, not including this function or the backtrace func
     test_backtrace(skip + 2);
-
-    // then exit with a failure code
-    abort();
+    
+    // then jump out
+    longjmp(_test_ctx.jmp_fail, TEST_FAIL);
 }
 
 void test_fail (const char *func, const char *file, int line, int skip, const char *fmt, ...)
@@ -28,3 +29,7 @@
     va_end(vargs);
 }
 
+void test_test_fail (void)
+{
+    fail_if(1, "testing failure: %s", "xxx");
+}
--- a/src/test/test.c	Fri May 08 01:43:02 2009 +0300
+++ b/src/test/test.c	Fri May 08 02:51:20 2009 +0300
@@ -31,6 +31,7 @@
     OPT_DEBUG           = 'd',
     OPT_QUIET           = 'q',
     OPT_LIST            = 'l',
+    OPT_FORCE           = 'f',
     
     /** Options without short names */
     _OPT_EXT_BEGIN      = 0x00ff,
@@ -44,6 +45,7 @@
     {"debug",           0,  NULL,   OPT_DEBUG       },
     {"quiet",           0,  NULL,   OPT_QUIET       },
     {"list",            0,  NULL,   OPT_LIST        },
+    {"force",           0,  NULL,   OPT_FORCE       },
     {0,                 0,  0,      0               },
 };
 
@@ -58,6 +60,7 @@
     printf(" --debug / -d           display DEBUG log messages\n");
     printf(" --quiet / -q           supress INFO log messages\n");
     printf(" --list / -l            list all tests\n");
+    printf(" --force / -f           force tests to continue running after one fails\n");
 }
 
 /**
@@ -75,49 +78,139 @@
 }
 
 /**
- * Run the given test, updating the given counter
+ * Map the actual test_result for a test into the used test_result, handling things like TEST_WILL_FAIL
  */
-static void run_test (const struct test *test, size_t *test_count)
+static enum test_result map_test_result (const struct test *test, enum test_result result)
 {
+    if (result == TEST_ERROR)
+        return TEST_ERROR;
+
+    else if (test->flags & TEST_WILL_FAIL)
+        return (result == TEST_FAIL) ? TEST_PASS : TEST_FAIL;
+    
+    else 
+        return result;
+}
+
+/**
+ * Run the given test.
+ *
+ * Returns the result of running the test (PASS, FAIL, ERROR).
+ */
+static enum test_result run_test (struct test_stats *stats, const struct test *test)
+{
+    enum test_result result;
+
+    // log it
     log_info("%s", test->name);
 
-    // count and run
-    (*test_count)++;
-    test->func();
+    // set the failure jump point
+    if (setjmp(_test_ctx.jmp_fail)) {
+        // failed
+        result = TEST_FAIL;
+    
+    } else {
+        // count it
+        stats->run++;
+
+        // run
+        test->func();
+        
+        // ok
+        result = TEST_PASS;    
+    }
+
+    // map it
+    switch ((result = map_test_result(test, result))) {
+        case TEST_PASS:
+            log_info("%s: passed", test->name);
+
+            stats->pass++;
+            
+            break;
+        
+        case TEST_FAIL:
+            log_warn("%s: failed", test->name);
+
+            stats->fail++;
+
+            break;
+        
+        case TEST_ERROR:
+            log_msg(LOG_ERROR, "%s: error: xxx", test->name);
+
+            stats->error++;
+
+            break;
+
+        default:
+            NOT_REACHED(result);
+    }
+    
+    return result;
 }
 
 /**
  * Run the given NULL-terminated list of tests, optionally filtering against the given filter.
  *
- * Returns the number of tests run, which may be zero.
+ * Updates the given \a stats.
+ *
+ * Returns a test_result representing the final state.
  */
-static size_t run_tests (const struct test tests[], const char *filter)
+static enum test_result run_tests (struct test_stats *stats, const struct test tests[], const char *filter)
 {
-    size_t test_count = 0;
     const struct test *test;
+    enum test_result res;
+    bool skip = false;
 
     // run each test in turn
     for (test = tests; test->name; test++) {
         // filter out if given
-        if ((filter && strcmp(test->name, filter)) || (!filter && test->optional))
+        if ((filter && strcmp(test->name, filter)) || (!filter && (test->flags & TEST_OPTIONAL)))
             continue;
         
+        // count it
+        stats->tests++;
+
+        // skip it?
+        if (skip)
+            continue;
+
         // run it
-        run_test(test, &test_count);
+        if ((res = run_test(stats, test)) != TEST_PASS && !_test_ctx.options.force)
+            // mark the rest of the tests to be skipped
+            skip = true;
     }
-    
-    return test_count;
+
+    // compute return status
+    if (!stats->run)
+        return TEST_EMPTY;
+
+    else if (stats->error)
+        return TEST_ERROR;
+
+    else if (stats->fail)
+        return TEST_FAIL;
+
+    else
+        return TEST_PASS;
 }
 
 int main (int argc, char **argv)
 {
     int opt, option_index;
     const char *filter = NULL;
-
-    size_t test_count;
+    
+    struct test_stats stats;
+    enum test_result result;
+    enum log_level level;
+    
+    // initialize
+    memset(&_test_ctx, 0, sizeof(_test_ctx));
+    memset(&stats, 0, sizeof(stats));
 
     // parse options
-    while ((opt = getopt_long(argc, argv, "hdql", options, &option_index)) != -1) {
+    while ((opt = getopt_long(argc, argv, "hdqlf", options, &option_index)) != -1) {
         switch (opt) {
             case OPT_HELP:
                 usage(argv[0]);
@@ -134,6 +227,10 @@
             case OPT_LIST:
                 list_tests(_tests);
                 exit(EXIT_SUCCESS);
+    
+            case OPT_FORCE:
+                _test_ctx.options.force = true;
+                break;
 
             case '?':
                 usage(argv[0]);
@@ -156,14 +253,36 @@
     // setup the sockets stuff
     _test_ctx.ev_base = setup_sock();
 
-    // run tests
-    if ((test_count = run_tests(_tests, filter)) == 0)
-        FATAL("no tests run");
-    
-    // log
-    log_info("done, ran %zu tests", test_count);
-    
-    // ok
-    return EXIT_SUCCESS;
+    // run all tests
+    switch ((result = run_tests(&stats, _tests, filter))) {
+        case TEST_EMPTY:
+            EXIT_WARN(result, "no tests run");
+        
+        // map test result to log level
+        case TEST_ERROR:
+            level = LOG_ERROR;
+
+            break;
+
+        case TEST_FAIL:
+            level = LOG_WARN;
+
+            break;
+
+        case TEST_PASS:
+            level = LOG_INFO;
+
+            break;
+        
+        default :
+            level = LOG_FATAL;
+
+            break;
+    }
+
+    // exit with stats
+    log_exit(level, result, "ran %zu/%zu tests, %zu passes, %zu errors, %zu failures", 
+        stats.run, stats.tests, stats.pass, stats.error, stats.fail
+    );
 }
 
--- a/src/test/test.h	Fri May 08 01:43:02 2009 +0300
+++ b/src/test/test.h	Fri May 08 02:51:20 2009 +0300
@@ -14,6 +14,7 @@
 #include <event2/event.h>
 #include <string.h>
 #include <stdbool.h>
+#include <setjmp.h>
 
 /**
  * Global test-running state
@@ -22,8 +23,66 @@
     /** The event_base that we have setup */
     struct event_base *ev_base;
 
+    /** Failure jump point */
+    jmp_buf jmp_fail;
+    
+    /**
+     * Command-line options for running the tests
+     */
+    struct test_options {
+        /** Don't stop running tests if one fails */
+        bool force;
+    } options;
+
 } _test_ctx;
 
+/**
+ * Flags for tests
+ */
+enum test_flag {
+    /** Do not run this test by default */
+    TEST_OPTIONAL   = 0x01,
+
+    /** This test is expected to fail */
+    TEST_WILL_FAIL  = 0x02,
+};
+
+/**
+ * Test results. These all function as process exit codes.
+ */
+enum test_result {
+    /** The test ran sucesfully */
+    TEST_PASS   = 0,
+
+    /** The test failed */
+    TEST_FAIL   = 1,
+
+    /** The test error'd out */
+    TEST_ERROR  = 2,
+
+    /** No tests run */
+    TEST_EMPTY  = 3,
+};
+
+/**
+ * Test stats
+ */
+struct test_stats {
+    /** How many tests were defined */
+    size_t tests;
+
+    /** How many tests were actually run */
+    size_t run;
+
+    /** How many of those passed */
+    size_t pass;
+
+    /** How many of those failed */
+    size_t fail;
+
+    /** How many of those error'd */
+    size_t error;
+};
 
 /**
  * Global list of test definitions
@@ -35,10 +94,10 @@
     /** Test func */
     void (*func) (void);
     
-    /** Do not run by default */    
-    bool optional;
+    /** Flags from enum test_flag */
+    int flags;
+
 } _tests[];
 
 
-
 #endif
--- a/src/test/test_list.c	Fri May 08 01:43:02 2009 +0300
+++ b/src/test/test_list.c	Fri May 08 02:51:20 2009 +0300
@@ -3,7 +3,7 @@
 /**
  * Test function prototypes
  */
-#define TEST(name) extern void test_ ##name (void);
+#define TEST(name, flags) extern void test_ ##name (void);
 
     #include "test_list.inc"
 
@@ -11,9 +11,8 @@
 /**
  * The array of test structs
  */
-#define TEST(name) { #name, test_ ## name, false },
-#define TEST_OPTIONAL(name) { #name, test_ ## name, true },
-#define TEST_END { NULL, NULL, false }
+#define TEST(name, flags) { #name, test_ ## name, flags },
+#define TEST_END { NULL, NULL, 0 }
 
 const struct test _tests[] = {
     #include "test_list.inc"
--- a/src/test/test_list.inc	Fri May 08 01:43:02 2009 +0300
+++ b/src/test/test_list.inc	Fri May 08 02:51:20 2009 +0300
@@ -4,8 +4,7 @@
  * All tests must be added to this list.
  *
  * TEST() macro signature:
- *  #define TEST(name)
- *  #define TEST_OPTIONAL(name)
+ *  #define TEST(name, flags)
  *  #define TEST_END
  */
 
@@ -14,46 +13,35 @@
     #error TEST macro not defined
 #endif
 
-#ifndef TEST_OPTIONAL
-    /* Default to the same value as TEST() */
-    #define TEST_OPTIONAL(name) TEST(name)
-#endif
-
-
 /* Tests*/
-TEST ( str_quote                    )
-TEST ( str_format                   )
-TEST ( dump_str                     )
-
-TEST ( resolve_addr                 )
-TEST ( resolve_result               )
-TEST ( resolve                      )
-
-TEST ( transport_test               )
-
-TEST ( fifo                         )
-TEST ( tcp                          )
-
-TEST ( line_proto                   )
-
-TEST ( irc_cmd                      )
-TEST ( irc_line_parse               )
-TEST ( irc_line_build               )
-
-TEST ( irc_queue                    )
-TEST ( irc_conn                     )
-TEST ( irc_conn_self_nick           )
-TEST ( irc_net                      )
-TEST ( irc_chan_add_offline         )
-TEST ( irc_chan_namreply            )
-TEST ( irc_chan_user_join           )
-TEST ( irc_chan_user_part           )
-TEST ( irc_chan_user_kick           )
-TEST ( irc_chan_self_kick           )
-TEST ( irc_chan_user_nick           )
-TEST ( irc_chan_user_quit           )
-TEST ( irc_chan_CTCP_ACTION         )
-TEST ( irc_chan_privmsg             )
+TEST ( test_fail,               TEST_WILL_FAIL  )
+TEST ( str_quote,               0               )
+TEST ( str_format,              0               )
+TEST ( dump_str,                0               )
+TEST ( resolve_addr,            0               )
+TEST ( resolve_result,          0               )
+TEST ( resolve,                 0               )
+TEST ( transport_test,          0               )
+TEST ( fifo,                    TEST_OPTIONAL   )
+TEST ( tcp,                     0               )
+TEST ( line_proto,              0               )
+TEST ( irc_cmd,                 0               )
+TEST ( irc_line_parse,          0               )
+TEST ( irc_line_build,          0               )
+TEST ( irc_queue,               0               )
+TEST ( irc_conn,                0               )
+TEST ( irc_conn_self_nick,      0               )
+TEST ( irc_net,                 0               )
+TEST ( irc_chan_add_offline,    0               )
+TEST ( irc_chan_namreply,       0               )
+TEST ( irc_chan_user_join,      0               )
+TEST ( irc_chan_user_part,      0               )
+TEST ( irc_chan_user_kick,      0               )
+TEST ( irc_chan_self_kick,      0               )
+TEST ( irc_chan_user_nick,      0               )
+TEST ( irc_chan_user_quit,      0               )
+TEST ( irc_chan_CTCP_ACTION,    0               )
+TEST ( irc_chan_privmsg,        0               )
 
 /*
  * End of list