Mercurial > hg > th-libs
view tests.c @ 509:b506bff0a7ab
Some cleanup work and refactoring on the configuration file parser and writer.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Thu, 26 Dec 2019 08:10:54 +0200 |
parents | fe5e7bf704e5 |
children | 5c3bfe034915 |
line wrap: on
line source
#include "th_types.h" #include "th_args.h" #include "th_util.h" #include "th_string.h" #include "th_crypto.h" #include "th_config.h" #define SET_BUF_SIZE 128 #define SET_BUF_SIZE_2 ((SET_BUF_SIZE) + 32) #define SET_MAX_TESTS 64 #define SET_SENTINEL_BYTE 0x0e5 enum { TST_SUPERFLUOUS = 0x0001, TST_CORNERCASE = 0x0002, TST_OVERFLOW = 0x0004, TST_BROKEN = 0x1000, TST_ALL = 0xffff, }; typedef struct { char *header; BOOL shown; } test_ctx; // Globals int tests_failed, tests_passed, tests_total, sets_total, sets_nenabled; int sets_enabled[SET_MAX_TESTS]; char buf1[SET_BUF_SIZE_2], buf2[SET_BUF_SIZE_2]; int optFlags = TST_ALL; // Define option arguments static const th_optarg arg_opts[] = { { 0, '?', "help", "Show this help", OPT_NONE }, { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, { 2, 's', "sets", "Perform test sets -s <set>[,<set2>..]", OPT_ARGREQ }, { 3, 't', "tests", "Perform only tests (see below)", OPT_ARGREQ }, }; static const int arg_nopts = sizeof(arg_opts) / sizeof(arg_opts[0]); BOOL tprintv(const int level, const char *fmt, va_list ap) { if (level <= th_verbosity) { vfprintf(stdout, fmt, ap); return TRUE; } else return FALSE; } BOOL tprint(const int level, const char *fmt, ...) { BOOL retv; va_list ap; va_start(ap, fmt); retv = tprintv(level, fmt, ap); va_end(ap); return retv; } void arg_show_help(void) { th_print_banner(stdout, th_prog_name, "[options]"); th_args_help(stdout, arg_opts, arg_nopts, 0); } BOOL arg_handle_opt(const int optN, char *optArg, char *currArg) { switch (optN) { case 0: arg_show_help(); exit(0); break; case 1: th_verbosity++; break; case 2: { BOOL ret = TRUE; char *pos, *pstr, *next; pos = pstr = th_strdup(optArg); memset(sets_enabled, 0, sizeof(sets_enabled)); do { next = strchr(pos, ','); if (next != NULL) *next = 0; char *tmp = th_strdup_trim(pos, TH_TRIM_BOTH); if (tmp != NULL) { int val = atoi(tmp); if (val > 0 && val <= SET_MAX_TESTS) sets_enabled[val - 1] = 1; else { THERR("Invalid test number #%d, out of range [%d .. %d]\n", val, 1, SET_MAX_TESTS); ret = FALSE; } th_free(tmp); } if (next != NULL) pos = next + 1; } while (next != NULL); th_free(pstr); return ret; } break; case 3: optFlags = atoi(optArg); break; default: THERR("Unknown option '%s'.\n", currArg); return FALSE; } return TRUE; } void test_end(test_ctx *ctx) { th_free_r(&ctx->header); } void test_start_v(test_ctx *ctx, const char *fmt, va_list ap) { tests_total++; memset(ctx, 0, sizeof(test_ctx)); ctx->header = th_strdup_vprintf(fmt, ap); } void test_start(test_ctx *ctx, const char *fmt, ...) { va_list ap; va_start(ap, fmt); test_start_v(ctx, fmt, ap); va_end(ap); } void test_result_msg_v(test_ctx *ctx, BOOL check, const char *fmt, va_list ap) { if (check) { if (!ctx->shown && tprint(2, "%s: OK\n", ctx->header)) ctx->shown = TRUE; tests_passed++; } else { if (!ctx->shown && tprint(0, "%s: FAIL\n", ctx->header)) ctx->shown = TRUE; if (fmt != NULL) { tprint(0, " - "); tprintv(0, fmt, ap); tprint(0, "\n"); } tests_failed++; } } BOOL test_result_msg(test_ctx *ctx, BOOL check, const char *fmt, ...) { va_list ap; va_start(ap, fmt); test_result_msg_v(ctx, check, fmt, ap); va_end(ap); return check; } BOOL test_result(test_ctx *ctx, BOOL check) { test_result_msg_v(ctx, check, NULL, NULL); return check; } void test_snprintf_do(size_t len, const char *msg, const char *fmt, va_list ap) { int ret1, ret2; va_list tmp; test_ctx ctx; // Test basic *printf() functionality test_start(&ctx, "th_vsnprintf(%" PRIu_SIZE_T ", \"%s\", %s)", len, fmt, msg); memset(buf1, SET_SENTINEL_BYTE, SET_BUF_SIZE_2); buf1[SET_BUF_SIZE_2-1] = 0; memset(buf2, SET_SENTINEL_BYTE, SET_BUF_SIZE_2); buf2[SET_BUF_SIZE_2-1] = 0; va_copy(tmp, ap); ret1 = th_vsnprintf(buf1, len, fmt, tmp); va_copy(tmp, ap); ret2 = vsnprintf(buf2, len, fmt, tmp); test_result_msg(&ctx, ret1 == ret2, "retval mismatch %d [th] != %d [libc]", ret1, ret2); test_result_msg(&ctx, strcmp(buf1, buf2) == 0, "result mismatch '%s' [th] != '%s' [libc]", buf1, buf2); if (optFlags & TST_OVERFLOW) { test_result_msg(&ctx, (unsigned char) buf1[len] == SET_SENTINEL_BYTE, "buffer #1 overflow, sentinel 0x%02x", buf1[len]); test_result_msg(&ctx, (unsigned char) buf2[len] == SET_SENTINEL_BYTE, "buffer #2 overflow, sentinel 0x%02x", buf2[len]); } test_end(&ctx); } void test_snprintf(const char *msg, const char *fmt, ...) { test_ctx ctx; va_list ap, tmp; va_start(ap, fmt); if (optFlags & TST_CORNERCASE) { va_copy(tmp, ap); test_snprintf_do(0, msg, fmt, tmp); va_copy(tmp, ap); test_snprintf_do(1, msg, fmt, tmp); va_copy(tmp, ap); test_snprintf_do(2, msg, fmt, tmp); va_copy(tmp, ap); test_snprintf_do(16, msg, fmt, tmp); } va_copy(tmp, ap); test_snprintf_do(SET_BUF_SIZE, msg, fmt, tmp); // Test th_strdup_vprintf() if (optFlags & TST_SUPERFLUOUS) { test_start(&ctx, "th_strdup_vprintf('%s')", fmt); va_copy(tmp, ap); char *str = th_strdup_vprintf(fmt, tmp); test_result_msg(&ctx, str != NULL, "result NULL"); th_free(str); test_end(&ctx); } va_end(ap); tprint(2, "-----------------------------------------------------\n"); } BOOL test_set_start(const char *str) { if (sets_enabled[sets_total++]) { sets_nenabled++; tprint(1, "======================================================\n" " Set #%d : %s tests\n" "======================================================\n", sets_total, str); return TRUE; } else return FALSE; } #define NCOUNT(xxx) (sizeof(xxx) / sizeof(xxx[0])) #define TEST2(fun, str1, str2, ret) do { \ test_ctx ctx; \ test_start(&ctx, # fun "('%s', '%s')", str1, str2); \ test_result(&ctx, ( fun (str1, str2) == 0) == ret); \ test_end(&ctx); \ } while (0) #define TEST2B(fun, str1, str2, ret) do { \ test_ctx ctx; \ test_start(&ctx, # fun "('%s', '%s')", str1, str2); \ test_result(&ctx, fun (str1, str2) == ret); \ test_end(&ctx); \ } while (0) #define TEST2C(fun, str1, str2, ret) do { \ test_ctx ctx; \ test_start(&ctx, # fun "('%s', '%s')", str1, str2); \ test_result(&ctx, (fun (str1, str2) != NULL) == ret); \ test_end(&ctx); \ } while (0) #define TEST3(fun, str1, str2, len, ret) do { \ test_ctx ctx; \ test_start(&ctx, # fun "('%s', '%s', %d)", str1, str2, len); \ test_result(&ctx, ( fun (str1, str2, len) == 0) == ret); \ test_end(&ctx); \ } while (0) void test_config_values(th_cfgitem_t *cfg, int *nsubtest) { th_cfgitem_t *item; test_ctx ctx; test_start(&ctx, "Test configuration value search #%d", ++(*nsubtest)); test_result(&ctx, (item = th_cfg_find(cfg, "inside_sect", "intval", -1)) != NULL && *item->v.val_int == 112); test_end(&ctx); test_start(&ctx, "Test configuration value search #%d", ++(*nsubtest)); test_result(&ctx, (item = th_cfg_find(cfg, "another_sect", "boolval", -1)) != NULL && *item->v.val_bool); test_end(&ctx); test_start(&ctx, "Test configuration value search #%d", ++(*nsubtest)); test_result(&ctx, th_cfg_find(cfg, "no_match", NULL, -1) == NULL); test_end(&ctx); test_start(&ctx, "Test configuration value search #%d", ++(*nsubtest)); test_result(&ctx, th_cfg_find(cfg, NULL, "no_match", -1) == NULL); test_end(&ctx); test_start(&ctx, "Test configuration value search #%d", ++(*nsubtest)); test_result(&ctx, (item = th_cfg_find(cfg, NULL, "hexval", -1)) != NULL && *item->v.val_uint == 0x11223344); test_end(&ctx); test_start(&ctx, "Test configuration value search #%d", ++(*nsubtest)); test_result(&ctx, (item = th_cfg_find(cfg, NULL, "a_string_setting", -1)) != NULL && strcmp(*item->v.val_str, "v_str1") == 0); test_end(&ctx); } void test_free(th_llist_t *node) { th_free_r(&node->data); } void test_ioctx_error(th_ioctx *fh, const int val, const char *msg) { (void) fh; (void) val; tprint(0, "IOCTX ERROR: %s", msg); } void test_ioctx_msg(th_ioctx *fh, const int val, const char *msg) { (void) fh; (void) val; tprint(1, "IOCTX MSG: %s", msg); } int test_strcmp(const void *v1, const void *v2) { return strcmp((const char *) v1, (const char *) v2); } static const char *test_strings[] = { "zoo", "foo", "bar", }; static const int ntest_strings = sizeof(test_strings) / sizeof(test_strings[0]); void test_config(void) { test_ctx ctx; th_ioctx *fh = NULL; th_cfgitem_t *sect1, *sect2, *cfg = NULL, *item; char *v_str1 = NULL; unsigned int v_uint1; int v_int1; BOOL v_bool1, v_bool2; th_llist_t *v_str_list = NULL; int nsubtest = 0; static const char *filename = "config.test"; // Create v_str_list for (int n = 0; n < ntest_strings; n++) th_llist_append(&v_str_list, th_strdup(test_strings[n])); // Create the configuration structure tprint(2, "Creating configuration structure\n"); sect1 = NULL; th_cfg_add_comment(§1, "A comment that\nspans multiple\nlines automatically"); th_cfg_add_string(§1, "a_string_setting", &v_str1, "v_str1"); th_cfg_add_comment(§1, "Hex triplet value setting"); th_cfg_add_hexvalue(§1, "hexval", &v_uint1, 0x11223344); th_cfg_add_comment(§1, "A boolean value"); th_cfg_add_bool(§1, "boolval", &v_bool1, FALSE); th_cfg_add_comment(§1, "A string list"); th_cfg_add_string_list(§1, "string_list", &v_str_list); th_cfg_add_section(&cfg, "general", sect1); sect1 = NULL; th_cfg_add_comment(§1, "Another section"); th_cfg_add_bool(§1, "boolval", &v_bool2, TRUE); sect2 = NULL; th_cfg_add_comment(§2, "Section inside a section"); th_cfg_add_int(§2, "intval", &v_int1, 112); th_cfg_add_section(§1, "inside_sect", sect2); th_cfg_add_section(&cfg, "another_sect", sect1); // Test value finding test_config_values(cfg, &nsubtest); // Attempt to write the file th_io_set_handlers(fh, test_ioctx_error, test_ioctx_msg); if (th_io_fopen(&fh, &th_stdio_io_ops, filename, "w") != THERR_OK) { int err = th_get_error(); THERR("Could not create configuration to file '%s', %d: %s\n", filename, err, th_error_str(err)); goto out; } th_cfg_write(fh, cfg); th_io_close(fh); // Free the data for v_str_list th_llist_free_func_node(v_str_list, test_free); v_str_list = NULL; // Attempt to read the previously written file if (th_io_fopen(&fh, &th_stdio_io_ops, filename, "r") != THERR_OK) { int err = th_get_error(); THERR("Could not open configuration file '%s', %d: %s\n", filename, err, th_error_str(err)); goto out; } th_cfg_read(fh, cfg); // Retest values test_config_values(cfg, &nsubtest); // Additional tests test_start(&ctx, "Test configuration string list ptr"); test_result(&ctx, (item = th_cfg_find(cfg, NULL, "string_list", -1)) != NULL && item->v.data == &v_str_list); test_end(&ctx); for (nsubtest = 0; nsubtest < ntest_strings; nsubtest++) { test_start(&ctx, "Test configuration string list values #%d", nsubtest); test_result(&ctx, th_llist_find_func(v_str_list, test_strings[nsubtest], test_strcmp) != NULL); test_end(&ctx); } test_start(&ctx, "Test configuration string list values #%d", ++nsubtest); test_result(&ctx, th_llist_find_func(v_str_list, "opas", test_strcmp) == NULL); test_end(&ctx); test_end(&ctx); out: th_llist_free_func_node(v_str_list, test_free); th_io_free(fh); } int main(int argc, char *argv[]) { size_t i1, i2, i3, i4; char buf[64], buf2[64]; // // Initialization // th_init("th-test", "th-libs unit tests", "0.2", NULL, NULL); th_verbosity = 0; if (sizeof(char) != sizeof(unsigned char)) { THERR("sizeof(char) != sizeof(unsigned char)???\n"); return -1; } tests_failed = tests_passed = tests_total = sets_total = sets_nenabled = 0; for (i1 = 0; i1 < SET_MAX_TESTS; i1++) sets_enabled[i1] = 1; // // Parse command line arguments // if (!th_args_process(argc, argv, arg_opts, arg_nopts, arg_handle_opt, NULL, 0)) return 0; tprint(1, "Enabled test types are 0x%04x.\n", optFlags); // // Test series for printf() // char *i_fmts[] = { "", "05", "5", ".5", "8.5", "08.5", "3", "3.2", "3", ".0", "0" }; char *i_mods[] = { "", "-", "+", "#", }; char *i_types[] = { "d", "u", "i", "x", "X", "o", }; if (test_set_start("printf() integer")) { int i_vals[] = { 0, -0, -1, 2, -2, 512, -1024, 612342, -612342, 0x1fff, 0x8000000, -123456789 }; for (i1 = 0; i1 < NCOUNT(i_vals); i1++) { snprintf(buf, sizeof(buf), "%d", i_vals[i1]); for (i4 = 0; i4 < NCOUNT(i_mods); i4++) for (i3 = 0; i3 < NCOUNT(i_types); i3++) for (i2 = 0; i2 < NCOUNT(i_fmts); i2++) { snprintf(buf2, sizeof(buf2), "%%%s%s%s", i_mods[i4], i_fmts[i2], i_types[i3]); test_snprintf(buf, buf2, i_vals[i1]); } } } if (test_set_start("printf() integer 64bit")) { int64_t i_vals64[] = { 0, -0, -1, 2, -2, 612342, -612342, 0x3342344341fff, 0x1f8000000, }; for (i1 = 0; i1 < NCOUNT(i_vals64); i1++) { snprintf(buf, sizeof(buf), "%" PRId64, i_vals64[i1]); for (i4 = 0; i4 < NCOUNT(i_mods); i4++) for (i3 = 0; i3 < NCOUNT(i_types); i3++) for (i2 = 0; i2 < NCOUNT(i_fmts); i2++) { snprintf(buf2, sizeof(buf2), "%%%s%sll%s", i_mods[i4], i_fmts[i2], i_types[i3]); test_snprintf(buf, buf2, i_vals64[i1]); } } } if (test_set_start("printf() float")) { double f_vals[] = { 1, 2, 3, 2.02, 612342.234, -2.07, -612342.12, 437692.9876543219, 0x1fff, 0x8000000, 0.15625 }; char *f_fmts[] = { "%f", "%1.1f", "%8.5f", "%5f", "%-5f", "", "%-5.2f", "%08.5f" }; for (i1 = 0; i1 < NCOUNT(f_vals); i1++) { snprintf(buf, sizeof(buf), "%f", f_vals[i1]); for (i2 = 0; i2 < NCOUNT(f_fmts); i2++) test_snprintf(buf, f_fmts[i2], f_vals[i1]); } } if (test_set_start("printf() string")) { char *s_vals[] = { "", "XYZXYZ", "xxx yyy zzz ppp fff", NULL, "X", "abcde", "dx", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", }; char *s_fmts[] = { "%s", "%2s", "%-2s", "%5s", "%-5s", "%16s", "%-16s", "%1s", "%-1s", "% 2s", "%03s", "% -12s", "% 03s", "%-.15s", "%.8s" }; for (i1 = 0; i1 < NCOUNT(s_vals); i1++) { for (i2 = 0; i2 < NCOUNT(s_fmts); i2++) test_snprintf(s_vals[i1], s_fmts[i2], s_vals[i1]); } } if (test_set_start("printf() char")) { const char c_val = 'x'; const char *c_msg = "x"; char *c_fmts[] = { "a%cBC", "%c", "", "%0c", "%1c", "% c", "%-3c", "%3c", "%.3c", "%-.3c", "%-3.3c", "%.c", "%05c", "%-05c", }; for (i1 = 0; i1 < NCOUNT(c_fmts); i1++) test_snprintf(c_msg, c_fmts[i1], c_val); } if (test_set_start("printf() pointers")) { char *p_fmts[] = { "%p", "%2p", "%.2p", "%03p", "%04p", "%-3p", "%0.3p", "%8p", "%32p", "%032p", "%-32p", "%-032p", "%16.8p", "%016.8p" }; void *p_vals[] = { NULL, (void *) 1, &p_fmts, }; for (i1 = 0; i1 < NCOUNT(p_vals); i1++) { snprintf(buf, sizeof(buf), "%p", p_vals[i1]); for (i2 = 0; i2 < NCOUNT(p_fmts); i2++) test_snprintf(buf, p_fmts[i2], p_vals[i1]); } } // // String matching functions // if (test_set_start("String compare #1")) { TEST2(th_strcasecmp, "aSdFq", "asdfq", TRUE); TEST2(th_strcasecmp, "aSdFq", "asFfq", FALSE); TEST2(th_strcasecmp, "abcde", "abcde", TRUE); TEST2(th_strcasecmp, "öäå", "öäå", TRUE); TEST2(th_strcasecmp, "aöäå", "aöäå", TRUE); } if (test_set_start("String compare #2")) { TEST3(th_strncasecmp, "aSdFq", "asFfqB", 4, FALSE); TEST3(th_strncasecmp, "aSdFq", "asFfqQ", 2, TRUE); TEST3(th_strncasecmp, "aSdFq", "asDfq", 3, TRUE); TEST3(th_strncasecmp, "aSdFq", "asDfq", 2, TRUE); TEST3(th_strncasecmp, "aSdFq", "asDfq", 0, TRUE); TEST3(th_strncasecmp, "aSdFq", "QsDfq", 0, TRUE); TEST3(th_strncasecmp, "aSdFq", "QsDfq", 1, FALSE); } if (test_set_start("String compare #3")) { TEST2C(th_strrcasecmp, "foo aSdFq", " asdfq", TRUE); TEST2C(th_strrcasecmp, "aSdFq", " asdfq", FALSE); TEST2C(th_strrcasecmp, "foo aSdFq baz", "asdfq", FALSE); } if (test_set_start("String matching #1")) { TEST2B(th_strmatch, "abba ABBAkukka lol", "*lol", TRUE); TEST2B(th_strmatch, "abba ABBAkukka lol", "*lo*", TRUE); TEST2B(th_strmatch, "abba ABBAkukka lol", "*lo", FALSE); TEST2B(th_strmatch, "abba ABBAkukka lol", "abba", FALSE); TEST2B(th_strmatch, "abba ABBAkukka lol", "*bba*", TRUE); TEST2B(th_strmatch, "abba ABBAkukka lol", "abba*", TRUE); TEST2B(th_strmatch, "abba ABBAkukka lol", "abbak*", FALSE); TEST2B(th_strmatch, "abba ABBAöökukka lol", "*abbaö?", FALSE); } if (test_set_start("String matching #2")) { TEST2B(th_strcasematch, "abba ABBAkukka lol", "abbak*", FALSE); TEST2B(th_strcasematch, "abba ABBAkukka lol", "*abbak*", TRUE); TEST2B(th_strcasematch, "abba ABBAkukka lol", "*ab?ak*", TRUE); TEST2B(th_strcasematch, "abba ABBAkukka lol", "*abbak?", FALSE); TEST2B(th_strcasematch, "abba ABBAkukka lol", "?bba?abba*", TRUE); } // Tests that test for things that do not work correctly yet // Unicode / multibyte UTF-8 causes problems here if ((optFlags & TST_BROKEN) && test_set_start("Invalid UTF-8 handling")) { TEST2(th_strcasecmp, "ÖÄÅ", "öäå", FALSE); // SHOULD match TEST3(th_strncasecmp, "Aäöå", "aöå", 2, TRUE); // should NOT match TEST2B(th_strmatch, "öriÖRI! lol", "?ri?RI!*", FALSE); // should match } // // printf() PRI* format specifiers, also a compile time test // if (test_set_start("PRI* specifiers")) { char tmp[32]; uint32_t u32 = 0xaabbccdd; uint64_t u64 = 0xaabbccdd11223344; size_t usiz = #if TH_ARCH == 32 0x11223344; #elif TH_ARCH == 64 0xaabbccdd11223344; #else #error Unsupported TH_ARCH value. #endif snprintf(tmp, sizeof(tmp), "%16" PRIx_SIZE_T "h", usiz); #if TH_ARCH == 32 TEST2(strcmp, tmp, "0000000011223344h", TRUE); #else TEST2(strcmp, tmp, "aabbccdd11223344h", TRUE); #endif snprintf(tmp, sizeof(tmp), "%08" PRIx32 "h", u32); TEST2(strcmp, tmp, "aabbccddh", TRUE); snprintf(tmp, sizeof(tmp), "%16" PRIx64 "h", u64); TEST2(strcmp, tmp, "aabbccdd11223344h", TRUE); } // // Configuration file handling // if (test_set_start("Config file handling")) { test_config(); } // // Print summary and exit // tprint(1, "======================================================\n"); tprint(0, "%d tests failed, %d passed (%d main tests), %d test sets of %d sets total.\n\n", tests_failed, tests_passed, tests_total, sets_nenabled, sets_total); return 0; }