There are compilers other than Visual C that want to show absolute
paths.  Generalize the helper introduced by a2c5e294 (unit-tests: do
show relative file paths, 2023-09-25) so that it can also work with
a path that uses slash as the directory separator, and becomes
almost no-op once one-time preparation finds out that we are using a
compiler that already gives relative paths.  Incidentally, this also
should do the right thing on Windows with a compiler that shows
relative paths but with backslash as the directory separator (if
such a thing exists and is used to build git).
Reported-by: Randall S. Becker <rsbecker@nexbridge.com>
Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
		
	
		
			
				
	
	
		
			408 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			408 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
#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;
 | 
						|
}
 |