t/unit-tests/test-lib.c

Summary

Maintainability
Test Coverage
#include "test-lib.h"

enum result {
    RESULT_NONE,
    RESULT_FAILURE,
    RESULT_SKIP,
    RESULT_SUCCESS,
    RESULT_TODO
};

static struct {
    enum result result;
    int count;
    unsigned failed :1;
    unsigned lazy_plan :1;
    unsigned running :1;
    unsigned skip_all :1;
    unsigned todo :1;
} ctx = {
    .lazy_plan = 1,
    .result = RESULT_NONE,
};

/*
 * Visual C interpolates the absolute Windows path for `__FILE__`,
 * but we want to see relative paths, as verified by t0080.
 * There are other compilers that do the same, and are not for
 * Windows.
 */
#include "dir.h"

static const char *make_relative(const char *location)
{
    static char prefix[] = __FILE__, buf[PATH_MAX], *p;
    static size_t prefix_len;
    static int need_bs_to_fs = -1;

    /* one-time preparation */
    if (need_bs_to_fs < 0) {
        size_t len = strlen(prefix);
        char needle[] = "t\\unit-tests\\test-lib.c";
        size_t needle_len = strlen(needle);

        if (len < needle_len)
            die("unexpected prefix '%s'", prefix);

        /*
         * The path could be relative (t/unit-tests/test-lib.c)
         * or full (/home/user/git/t/unit-tests/test-lib.c).
         * Check the slash between "t" and "unit-tests".
         */
        prefix_len = len - needle_len;
        if (prefix[prefix_len + 1] == '/') {
            /* Oh, we're not Windows */
            for (size_t i = 0; i < needle_len; i++)
                if (needle[i] == '\\')
                    needle[i] = '/';
            need_bs_to_fs = 0;
        } else {
            need_bs_to_fs = 1;
        }

        /*
         * prefix_len == 0 if the compiler gives paths relative
         * to the root of the working tree.  Otherwise, we want
         * to see that we did find the needle[] at a directory
         * boundary.  Again we rely on that needle[] begins with
         * "t" followed by the directory separator.
         */
        if (fspathcmp(needle, prefix + prefix_len) ||
            (prefix_len && prefix[prefix_len - 1] != needle[1]))
            die("unexpected suffix of '%s'", prefix);
    }

    /*
     * Does it not start with the expected prefix?
     * Return it as-is without making it worse.
     */
    if (prefix_len && fspathncmp(location, prefix, prefix_len))
        return location;

    /*
     * If we do not need to munge directory separator, we can return
     * the substring at the tail of the location.
     */
    if (!need_bs_to_fs)
        return location + prefix_len;

    /* convert backslashes to forward slashes */
    strlcpy(buf, location + prefix_len, sizeof(buf));
    for (p = buf; *p; p++)
        if (*p == '\\')
            *p = '/';
    return buf;
}

static void msg_with_prefix(const char *prefix, const char *format, va_list ap)
{
    fflush(stderr);
    if (prefix)
        fprintf(stdout, "%s", prefix);
    vprintf(format, ap); /* TODO: handle newlines */
    putc('\n', stdout);
    fflush(stdout);
}

void test_msg(const char *format, ...)
{
    va_list ap;

    va_start(ap, format);
    msg_with_prefix("# ", format, ap);
    va_end(ap);
}

void test_plan(int count)
{
    assert(!ctx.running);

    fflush(stderr);
    printf("1..%d\n", count);
    fflush(stdout);
    ctx.lazy_plan = 0;
}

int test_done(void)
{
    assert(!ctx.running);

    if (ctx.lazy_plan)
        test_plan(ctx.count);

    return ctx.failed;
}

void test_skip(const char *format, ...)
{
    va_list ap;

    assert(ctx.running);

    ctx.result = RESULT_SKIP;
    va_start(ap, format);
    if (format)
        msg_with_prefix("# skipping test - ", format, ap);
    va_end(ap);
}

void test_skip_all(const char *format, ...)
{
    va_list ap;
    const char *prefix;

    if (!ctx.count && ctx.lazy_plan) {
        /* We have not printed a test plan yet */
        prefix = "1..0 # SKIP ";
        ctx.lazy_plan = 0;
    } else {
        /* We have already printed a test plan */
        prefix = "Bail out! # ";
        ctx.failed = 1;
    }
    ctx.skip_all = 1;
    ctx.result = RESULT_SKIP;
    va_start(ap, format);
    msg_with_prefix(prefix, format, ap);
    va_end(ap);
}

int test__run_begin(void)
{
    assert(!ctx.running);

    ctx.count++;
    ctx.result = RESULT_NONE;
    ctx.running = 1;

    return ctx.skip_all;
}

static void print_description(const char *format, va_list ap)
{
    if (format) {
        fputs(" - ", stdout);
        vprintf(format, ap);
    }
}

int test__run_end(int was_run UNUSED, const char *location, const char *format, ...)
{
    va_list ap;

    assert(ctx.running);
    assert(!ctx.todo);

    fflush(stderr);
    va_start(ap, format);
    if (!ctx.skip_all) {
        switch (ctx.result) {
        case RESULT_SUCCESS:
            printf("ok %d", ctx.count);
            print_description(format, ap);
            break;

        case RESULT_FAILURE:
            printf("not ok %d", ctx.count);
            print_description(format, ap);
            break;

        case RESULT_TODO:
            printf("not ok %d", ctx.count);
            print_description(format, ap);
            printf(" # TODO");
            break;

        case RESULT_SKIP:
            printf("ok %d", ctx.count);
            print_description(format, ap);
            printf(" # SKIP");
            break;

        case RESULT_NONE:
            test_msg("BUG: test has no checks at %s",
                 make_relative(location));
            printf("not ok %d", ctx.count);
            print_description(format, ap);
            ctx.result = RESULT_FAILURE;
            break;
        }
    }
    va_end(ap);
    ctx.running = 0;
    if (ctx.skip_all)
        return 1;
    putc('\n', stdout);
    fflush(stdout);
    ctx.failed |= ctx.result == RESULT_FAILURE;

    return ctx.result != RESULT_FAILURE;
}

static void test_fail(void)
{
    assert(ctx.result != RESULT_SKIP);

    ctx.result = RESULT_FAILURE;
}

static void test_pass(void)
{
    assert(ctx.result != RESULT_SKIP);

    if (ctx.result == RESULT_NONE)
        ctx.result = RESULT_SUCCESS;
}

static void test_todo(void)
{
    assert(ctx.result != RESULT_SKIP);

    if (ctx.result != RESULT_FAILURE)
        ctx.result = RESULT_TODO;
}

int test_assert(const char *location, const char *check, int ok)
{
    assert(ctx.running);

    if (ctx.result == RESULT_SKIP) {
        test_msg("skipping check '%s' at %s", check,
             make_relative(location));
        return 1;
    }
    if (!ctx.todo) {
        if (ok) {
            test_pass();
        } else {
            test_msg("check \"%s\" failed at %s", check,
                 make_relative(location));
            test_fail();
        }
    }

    return !!ok;
}

void test__todo_begin(void)
{
    assert(ctx.running);
    assert(!ctx.todo);

    ctx.todo = 1;
}

int test__todo_end(const char *location, const char *check, int res)
{
    assert(ctx.running);
    assert(ctx.todo);

    ctx.todo = 0;
    if (ctx.result == RESULT_SKIP)
        return 1;
    if (res) {
        test_msg("todo check '%s' succeeded at %s", check,
             make_relative(location));
        test_fail();
    } else {
        test_todo();
    }

    return !res;
}

int check_bool_loc(const char *loc, const char *check, int ok)
{
    return test_assert(loc, check, ok);
}

union test__tmp test__tmp[2];

int check_int_loc(const char *loc, const char *check, int ok,
          intmax_t a, intmax_t b)
{
    int ret = test_assert(loc, check, ok);

    if (!ret) {
        test_msg("   left: %"PRIdMAX, a);
        test_msg("  right: %"PRIdMAX, b);
    }

    return ret;
}

int check_uint_loc(const char *loc, const char *check, int ok,
           uintmax_t a, uintmax_t b)
{
    int ret = test_assert(loc, check, ok);

    if (!ret) {
        test_msg("   left: %"PRIuMAX, a);
        test_msg("  right: %"PRIuMAX, b);
    }

    return ret;
}

static void print_one_char(char ch, char quote)
{
    if ((unsigned char)ch < 0x20u || ch == 0x7f) {
        /* TODO: improve handling of \a, \b, \f ... */
        printf("\\%03o", (unsigned char)ch);
    } else {
        if (ch == '\\' || ch == quote)
            putc('\\', stdout);
        putc(ch, stdout);
    }
}

static void print_char(const char *prefix, char ch)
{
    printf("# %s: '", prefix);
    print_one_char(ch, '\'');
    fputs("'\n", stdout);
}

int check_char_loc(const char *loc, const char *check, int ok, char a, char b)
{
    int ret = test_assert(loc, check, ok);

    if (!ret) {
        fflush(stderr);
        print_char("   left", a);
        print_char("  right", b);
        fflush(stdout);
    }

    return ret;
}

static void print_str(const char *prefix, const char *str)
{
    printf("# %s: ", prefix);
    if (!str) {
        fputs("NULL\n", stdout);
    } else {
        putc('"', stdout);
        while (*str)
            print_one_char(*str++, '"');
        fputs("\"\n", stdout);
    }
}

int check_str_loc(const char *loc, const char *check,
          const char *a, const char *b)
{
    int ok = (!a && !b) || (a && b && !strcmp(a, b));
    int ret = test_assert(loc, check, ok);

    if (!ret) {
        fflush(stderr);
        print_str("   left", a);
        print_str("  right", b);
        fflush(stdout);
    }

    return ret;
}