view tests.c @ 724:388d72f4189d

Add some tests for PRI* printf specifiers to test 64bit.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 18 Oct 2021 01:56:09 +0300
parents 3ea040bb0dca
children 31bc1ed07cf5
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_ioctx.h"
#include "th_config.h"
#include "th_regex.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


#define NCOUNT(xxx) (sizeof(xxx) / sizeof(xxx[0]))


enum
{
    TST_SUPERFLUOUS = 0x0001,
    TST_CORNERCASE  = 0x0002,
    TST_OVERFLOW    = 0x0004,
    TST_BROKEN      = 0x1000,
    TST_ALL         = 0xffff,
};


typedef struct
{
    char *header;
    BOOL
        shown,	// Has test result value been shown?
        failed;	// Did the test fail? (used by some tests to show extra info)
} test_ctx;


// Global variables
int tests_failed,	// Total number of tests failed
    tests_passed,	// Total number of tests passed
    tests_total,	// Total number of tests RUN
    sets_total,		// Number of test sets
    sets_nenabled;	// Number of test sets enabled

int sets_enabled[SET_MAX_TESTS];

char buf1[SET_BUF_SIZE_2],
    buf2[SET_BUF_SIZE_2];

int optFlags = TST_ALL;

th_ioctx testio;


// Define option arguments
static const th_optarg arg_opts[] =
{
    { 0, '?', "help"    , NULL,       "Show this help", OPT_NONE },
    { 1, 'v', "verbose" , NULL,       "Be more verbose", OPT_NONE },
    { 2, 's', "sets"    , "sets",     "Perform test sets -s <set>[,<set2>..]", OPT_ARGREQ },
    { 3, 't', "tests"   , "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, 80 - 2);
}


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 {
                if ((next = strchr(pos, ',')) != 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++;

        ctx->failed = TRUE;
    }
}


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;

    // Setup printf debug value
    th_printf_debug = th_verbosity >= 3;
    th_printf_debug_prefix = "  - ";

    // Test basic *printf() functionality
    test_start(&ctx, "th_vsnprintf(buflen=%" 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]);
    }

    if (ctx.failed && !th_printf_debug && th_verbosity >= 1)
    {
        th_printf_debug = TRUE;
        va_copy(tmp, ap); ret1 = th_vsnprintf(buf1, len, fmt, tmp);
        va_copy(tmp, ap); ret2 = vsnprintf(buf2, len, fmt, tmp);
    }

    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 TEST_ASSERT(xcond) do { \
        if (!(xcond)) \
        { \
            THERR("Assertion '" # xcond "' failed.\n"); \
            return -1; \
        } \
    } while (0)

#define TEST1(fun) do { \
        test_ctx ctx; \
        test_start(&ctx, # fun ); \
        test_result(&ctx, fun); \
        test_end(&ctx); \
    } while (0)

#define TEST1A(fmt, fun, fcmp, fres) do { \
        test_ctx ctx; \
        test_start(&ctx, #fun " " #fcmp " " fmt " (" fmt ")", fres, fun); \
        test_result(&ctx, fun fcmp fres ); \
        test_end(&ctx); \
    } while (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)
{
    th_cfgitem_t *item;
    int nsubtest = 0;
    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_config_free(th_cfgitem_t *node)
{
    switch (node->type)
    {
        case ITEM_STRING:
            th_free(*(node->v.val_str));
            break;

        case ITEM_STRING_LIST:
            break;
    }
}


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_config_strcmp(const void *v1, const void *v2)
{
    return strcmp((const char *) v1, (const char *) v2);
}


static const char *test_config_strings[] =
{
    "zoo", "foo", "b\"ar",
};


void test_config_read(th_cfgitem_t *cfg, const char *filename)
{
    int nsubtest;
    th_ioctx *fh = NULL;
    test_ctx ctx;
    th_cfgitem_t *item;

    tprint(1, "Reading configuration from '%s'.\n", filename);

    // 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_io_set_handlers(fh, test_ioctx_error, test_ioctx_msg);

    th_cfg_read(fh, cfg);

    // Test read values against expected values
    test_config_values(cfg);

    // Additional tests
    item = th_cfg_find(cfg, NULL, "string_list", -1);
    test_result(&ctx, item != NULL);
    if (item != NULL)
    {
        static const char *nostr = "not to be found";
        th_llist_t **plist = (th_llist_t **) item->v.list;

        for (nsubtest = 0; nsubtest < (int) NCOUNT(test_config_strings); nsubtest++)
        {
            test_start(&ctx, "Test configuration string list values #%d: '%s'",
                nsubtest + 1, test_config_strings[nsubtest]);

            test_result(&ctx,
                th_llist_find_func(*plist,
                test_config_strings[nsubtest], test_config_strcmp) != NULL);
            test_end(&ctx);
        }

        test_start(&ctx, "Test configuration string list values #%d: '%s'",
            ++nsubtest, nostr);
        test_result(&ctx, th_llist_find_func(*plist, nostr, test_config_strcmp) == NULL);
        test_end(&ctx);

        th_llist_free_func_data(*plist, th_free);
        *plist = NULL;
    }

out:
    th_io_close(fh);
}


void test_config(void)
{
    static const char *filename = "cfg.temp";
    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;

    // Create v_str_list
    for (size_t n = 0; n < NCOUNT(test_config_strings); n++)
        th_llist_append(&v_str_list, th_strdup(test_config_strings[n]));

    // Create the configuration structure
    tprint(2, "Creating configuration structure\n");
    sect1 = NULL;
    th_cfg_add_comment(&sect1, "A comment that\nspans multiple\nlines automatically");
    th_cfg_add_string(&sect1, "a_string_setting", &v_str1, "v_str1");

    th_cfg_add_comment(&sect1, "Hex triplet value setting");
    th_cfg_add_hexvalue(&sect1, "hexval", &v_uint1, 0x11223344);

    th_cfg_add_comment(&sect1, "A boolean value");
    th_cfg_add_bool(&sect1, "boolval", &v_bool1, FALSE);

    th_cfg_add_comment(&sect1, "A string list");
    th_cfg_add_string_list(&sect1, "string_list", &v_str_list);

    th_cfg_add_section(&cfg, "general", sect1);

    sect1 = NULL;
    th_cfg_add_comment(&sect1, "Another section");
    th_cfg_add_bool(&sect1, "boolval", &v_bool2, TRUE);

    sect2 = NULL;
    th_cfg_add_comment(&sect2, "Section inside a section");
    th_cfg_add_int(&sect2, "intval", &v_int1, 112);
    th_cfg_add_section(&sect1, "inside_sect", sect2);

    th_cfg_add_section(&cfg, "another_sect", sect1);

    // Test ptr
    test_start(&ctx, "Test configuration string list ptr");
    test_result(&ctx,
        (item = th_cfg_find(cfg, NULL, "string_list", -1)) != NULL &&
        item->v.list == &v_str_list);
    test_end(&ctx);

    // Test value finding
    test_config_values(cfg);

    // Attempt to write the file
    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_io_set_handlers(fh, test_ioctx_error, test_ioctx_msg);

    th_cfg_write(fh, cfg);
    th_io_close(fh);
    fh = NULL;

    // Test against written configuration file
    test_config_read(cfg, filename);

    // Test against manually edited configuration file
    test_config_read(cfg, "cfg.test01");

out:
    // Free the data for v_str_list
    th_io_close(fh);
    th_llist_free_func_data(v_str_list, th_free);
    th_cfg_free(cfg, test_config_free);
}


#ifdef TH_EXPERIMENTAL_REGEX

typedef struct
{
    const th_char_t *str;
    const int flags;
    const th_char_t *expected[4];
} test_regex_def1;


typedef struct
{
    const th_char_t *pattern;
    const th_char_t *str;
    const int flags;
    const th_char_t *expected[4];
} test_regex_def2;


BOOL test_regex_list_matches(const th_char_t *str,
    const th_regex_match_t *matches,
    const th_char_t * const *expected,
    const BOOL testOnly)
{
    size_t nmatch = 0;
    char *match = NULL;

    for (const th_regex_match_t *mt = matches;
        mt != NULL;
        mt = (th_regex_match_t *) mt->node.next,
        nmatch++)
    {
        match = th_strndup(str + mt->start, mt->len);

        if (expected[nmatch] == NULL)
        {
            if (!testOnly)
            {
                THERR("Expected[%" PRIu_SIZE_T "] == NULL, but match '%s' returned.\n",
                    nmatch, match);
            }

            goto error;
        }
        else
        {
            BOOL seqMatch = strcmp(match, expected[nmatch]) == 0;
            if (testOnly && !seqMatch)
                goto error;

            if (th_verbosity >= 1 || !seqMatch)
            {
                tprint(0, "      [%3" PRIu_SIZE_T " ++ %3" PRIu_SIZE_T "]: '%s' == '%s': %s\n",
                    mt->start,
                    mt->len,
                    match,
                    expected[nmatch],
                    seqMatch ? "YES" : "NO!");
            }
        }

        th_free(match);
    }

    return TRUE;

error:
    th_free(match);
    return FALSE;
}


void test_regex_list1(const test_regex_def1 *list, const th_char_t *pattern)
{
    th_regex_match_t *matches = NULL;
    th_regex_t *expr = NULL;
    int res;

    if ((res = th_regex_compile(&expr, pattern)) != THERR_OK)
    {
        THERR("Regex \"%s\" compilation failed: %s\n",
            pattern,
            th_error_str(res));
        goto out;
    }

    tprint(1, "\n----------------------------------------\n"
        "\"%s\"\n", pattern);

    if (th_verbosity >= 2)
        th_regex_dump(&testio, 1, expr);

    for (const test_regex_def1 *def = list; def->str != NULL; def++)
    {
        size_t nmatches;
        BOOL matchOK;

        tprint(3, "\n----------------------------------------\n");
        if ((res = th_regex_match(expr, def->str,
            &nmatches, &matches, -1, def->flags)) != THERR_OK)
        {
            THERR("Regex match returned error: %s\n",
                th_error_str(res));

            goto out;
        }

        matchOK = test_regex_list_matches(def->str, matches, def->expected, TRUE);
        if (th_verbosity < 1 && !matchOK)
        {
            tprint(0,
                "\n----------------------------------------\n"
                "  \"%s\" vs \"%s\" failures:\n",
                def->str, pattern);
        }
        else
        {
            tprint(1, "  \"%s\": matched %" PRIu_SIZE_T " time(s)\n",
                def->str,
                nmatches);
        }

#ifdef TH_EXPERIMENTAL_REGEX_DEBUG
        if (!matchOK && th_dbg_fh == NULL)
        {
            th_dbg_fh = &testio;
            th_regex_match(expr, def->str, NULL, NULL, -1, def->flags);
            th_dbg_fh = NULL;
        }
#endif

        test_regex_list_matches(def->str, matches, def->expected, FALSE);

        th_regex_free_matches(matches);
        matches = NULL;
    }

out:
    th_regex_free_matches(matches);
    th_regex_free(expr);
}


void test_regex_list2(const test_regex_def2 *list)
{
    for (const test_regex_def2 *def = list; def->str != NULL; def++)
    {
        th_regex_t *expr = NULL;
        th_regex_match_t *matches = NULL;
        size_t nmatches;
        int res;

        if ((res = th_regex_compile(&expr, def->pattern)) != THERR_OK)
        {
            THERR("Regex \"%s\" compilation failed: %s\n",
                def->pattern,
                th_error_str(res));
            goto out;
        }

        if (th_verbosity >= 2)
            th_regex_dump(&testio, 1, expr);

        tprint(3, "----------------------------------------\n");

        if ((res = th_regex_match(expr, def->str,
            &nmatches, &matches, -1, def->flags)) != THERR_OK)
        {
            THERR("Regex match returned error: %s\n",
                th_error_str(res));
            goto out;
        }

        tprint(1, "\"%s\" vs \"%s\": matched %" PRIu_SIZE_T " time(s)\n",
            def->pattern, def->str,
            nmatches);

        test_regex_list_matches(def->str, matches, def->expected, FALSE);

out:
        th_regex_free_matches(matches);
        th_regex_free(expr);
    }
}

#endif


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;

    TEST_ASSERT(sizeof(char) == sizeof(unsigned char));
    TEST_ASSERT(sizeof(char) == 1);
    TEST_ASSERT(sizeof(uint16_t) == 2);
    TEST_ASSERT(sizeof(uint32_t) == 4);
    TEST_ASSERT(sizeof(uint64_t) == 8);

    tests_failed = tests_passed = tests_total =
        sets_total = sets_nenabled = 0;

    for (i1 = 0; i1 < SET_MAX_TESTS; i1++)
        sets_enabled[i1] = 1;

    th_io_init_stdio(&testio, stdout);

    //
    // 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", "6", ".4", "1.1", "1.0", "8.5", "08.5", "3", "2.1", "3", "1", "18", "018", ".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, -1, 10, -10, 512, -512, -1024,
            612342, -612342, 0x1fff, 0x8000000, -123456789,
            2147483647, -2147483648 };

        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 generic"))
    {
        int64_t i_vals64[] = {
            0, -0, 1, -1, 10, -10, 512, -512, -1024,
            612342, -612342, 0x1fff, 0x8000000, -123456789,
            4294967295, -2147483648,
            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() integer 64bit PRI* specifiers"))
    {
        int64_t i_vals64[] = {
            0, -0, 1, -1, 10, -10, 512, -512, -1024,
            612342, -612342, 0x1fff, 0x8000000, -123456789,
            4294967295, -2147483648,
            0x3342344341fff, 0x1f8000000, };

        for (i1 = 0; i1 < NCOUNT(i_vals64); i1++)
        {
            snprintf(buf, sizeof(buf), "%" PRId64, i_vals64[i1]);

            for (i4 = 0; i3 < NCOUNT(i_mods); i3++)
            for (i2 = 0; i2 < NCOUNT(i_fmts); i2++)
            {
                snprintf(buf2, sizeof(buf2), "%%%s%s" PRId64, i_mods[i3], i_fmts[i2]);
                test_snprintf(buf, buf2, i_vals64[i1]);

                snprintf(buf2, sizeof(buf2), "%%%s%s" PRIx64, i_mods[i3], i_fmts[i2]);
                test_snprintf(buf, buf2, i_vals64[i1]);

                snprintf(buf2, sizeof(buf2), "%%%s%s" PRIX64, i_mods[i3], i_fmts[i2]);
                test_snprintf(buf, buf2, i_vals64[i1]);
            }
        }
    }

#ifdef TH_WIP_FLOAT_SUPPORT
    if (test_set_start("printf() float"))
    {
        double f_vals[] = { 0, 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]);
        }
    }
#endif

    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" };
        char *s_fmts[] = { "", "-" " ", "0", " 0", ".", "-.", };
        int  s_widths[] = { 0, 1, 2, 5, 16, };

        for (i1 = 0; i1 < NCOUNT(s_vals); i1++)
        for (i2 = 0; i2 < NCOUNT(s_fmts); i2++)
        for (i3 = 0; i3 < NCOUNT(s_widths); i3++)
        {
            snprintf(buf, sizeof(buf), "%%%s%ds", s_fmts[i2], s_widths[i3]);
            test_snprintf(s_vals[i1], buf, 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("Configuration file handling"))
    {
        test_config();
    }

    //
    // String functions
    //
    if (test_set_start("String functions"))
    {
        unsigned int tmpUint;

        TEST1(th_get_hex_triplet("0fac11", &tmpUint) == TRUE);
        TEST1A("0x%06x", tmpUint, ==, 0x0fac11);
        TEST1(th_get_hex_triplet("120fac11", &tmpUint) == TRUE);
        TEST1A("0x%06x", tmpUint, ==, 0x120fac11);
        TEST1(th_get_hex_triplet("x120fac11", &tmpUint) == FALSE);
    }

    //
    // Regular expressions
    //
#ifdef TH_EXPERIMENTAL_REGEX
    if (test_set_start("Regular expressions"))
    {
#ifdef TH_EXPERIMENTAL_REGEX_DEBUG
        th_dbg_fh = (th_verbosity >= 3) ? &testio : NULL;
#endif

        if (1)
        {
            const char *str = "z*k+abba fabboa? [a-zA-Z_-] \\{\\} k{4} ([0-9]+ yay){1,2} foo(bar|zoo)?";
            th_regex_t *expr = NULL;
            int res = th_regex_compile(&expr, str);

            printf("REGEX: \"%s\"\n", str);

            if (res == THERR_OK)
                th_regex_dump(&testio, 1, expr);
            else
                printf("ERROR: %s\n", th_error_str(res));

            th_regex_free(expr);
        }

        if (1)
        {
            static const test_regex_def1 tlist[] =
            {
                { "abcfoabcccg"                  , 0, { "abcfoabcccg", } },
                { "sabcbcfoabcccgz"              , 0, { "abcbcfoabcccg", } },
                { "abcbcfoabccg abcbcfoabccccg"  , 0, { "abcbcfoabccg", "abcbcfoabccccg" } },
                { NULL                           , 0, { NULL } }
            };

            test_regex_list1(tlist, "a(bc){1,2}fo[oab]*cc?g");
        }

        if (1)
        {
            static const test_regex_def1 tlist[] =
            {
                { "abcfoabccg"                   , 0, { "abcfoabccg", } },
                { "abcbcfoabccg"                 , 0, { "abcbcfoabccg", } },
                { "abcbcfoabccgabcbcfoabccg"     , 0, { "abcbcfoabccg", } },
                { "ffdsafS abcbcfoabccg zasdf"   , 0, { NULL } },
                { NULL                           , 0, { NULL } }
            };

            test_regex_list1(tlist, "^a(bc){1,2}fo[oab]*cc?g");
        }

        if (1)
        {
            static const test_regex_def1 tlist[] =
            {
                { "cg"                           , 0, { "g", } },
                { "g"                            , 0, { "g", } },
                { ""                             , 0, { NULL, } },
                { "c"                            , 0, { NULL, } },
                { NULL                           , 0, { NULL } }
            };

            test_regex_list1(tlist, "g$");
        }

        if (1)
        {
            static const test_regex_def1 tlist[] =
            {
                { "kzoobarzz"                    , 0, { "zoobar", } },
                { "hehzoo lol baromg"            , 0, { "zoo lol bar", } },
                { "hoho zoo lol lol bar bar f"   , 0, { "zoo lol lol bar", } },
                { "hoho zoobar bar heh"          , 0, { "zoobar", } },
                { NULL                           , 0, { NULL } }
            };

            test_regex_list1(tlist, "zoo.*?bar");
        }

        if (1)
        {
            static const test_regex_def1 tlist[] =
            {
                { "kzoobarzz"                    , 0, { "zoobar", } },
                { "hehzoo lol baromg"            , 0, { "zoo lol bar", } },
                { "hoho zoo lol lol bar bar f"   , 0, { "zoo lol lol bar bar", } },
                { "hoho zoobar bar heh"          , 0, { "zoobar bar", } },
                { NULL                           , 0, { NULL } }
            };

            test_regex_list1(tlist, "zoo.*bar");
        }

    }
#endif

    //
    // 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;
}