| #define DISABLE_SIGN_COMPARE_WARNINGS | |
| #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; | |
| char location[100]; | |
| char description[100]; | |
| } 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) | |
| { | |
| if (ctx.running && ctx.location[0] && ctx.description[0]) | |
| test__run_end(1, ctx.location, "%s", ctx.description); | |
| 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); | |
| } | |
| void test__run_describe(const char *location, const char *format, ...) | |
| { | |
| va_list ap; | |
| int len; | |
| assert(ctx.running); | |
| assert(!ctx.location[0]); | |
| assert(!ctx.description[0]); | |
| xsnprintf(ctx.location, sizeof(ctx.location), "%s", | |
| make_relative(location)); | |
| va_start(ap, format); | |
| len = vsnprintf(ctx.description, sizeof(ctx.description), format, ap); | |
| va_end(ap); | |
| if (len < 0) | |
| die("unable to format message: %s", format); | |
| if (len >= sizeof(ctx.description)) | |
| BUG("ctx.description too small to format %s", format); | |
| } | |
| int test__run_begin(void) | |
| { | |
| if (ctx.running && ctx.location[0] && ctx.description[0]) | |
| test__run_end(1, ctx.location, "%s", ctx.description); | |
| assert(!ctx.running); | |
| ctx.count++; | |
| ctx.result = RESULT_NONE; | |
| ctx.running = 1; | |
| ctx.location[0] = '\0'; | |
| ctx.description[0] = '\0'; | |
| 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) | |
| { | |
| if (!ctx.running) { | |
| test_msg("BUG: check outside of test at %s", | |
| make_relative(location)); | |
| ctx.failed = 1; | |
| return 0; | |
| } | |
| 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_pointer_eq_loc(const char *loc, const char *check, int ok, | |
| const void *a, const void *b) | |
| { | |
| int ret = test_assert(loc, check, ok); | |
| if (!ret) { | |
| test_msg(" left: %p", a); | |
| test_msg(" right: %p", b); | |
| } | |
| return ret; | |
| } | |
| 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) { | |
| char esc; | |
| switch (ch) { | |
| case '\a': esc = 'a'; break; | |
| case '\b': esc = 'b'; break; | |
| case '\t': esc = 't'; break; | |
| case '\n': esc = 'n'; break; | |
| case '\v': esc = 'v'; break; | |
| case '\f': esc = 'f'; break; | |
| case '\r': esc = 'r'; break; | |
| default: esc = 0; break; | |
| } | |
| if (esc) { | |
| putc('\\', stdout); | |
| putc(esc, stdout); | |
| } else { | |
| 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; | |
| } |