aboutsummaryrefslogblamecommitdiffstats
path: root/testhelp.c
blob: 74997ebfe956a79999f695b7126bc18bb9e42e74 (plain) (tree)













































































































































































































































































































































































                                                                                                                                 
/* See LICENSE file for copyright and license details. */
#define TEST
#include "common.h"


#if defined(__GNUC__)
# pragma GCC diagnostic ignored "-Wswitch"
# pragma GCC diagnostic ignored "-Wswitch-enum"
#endif


static char *
quote(const char *s, char **out)
{
	char *ret, *p;
	int safe = 1;
	*out = p = ret = malloc(strlen(s) * 4 + 3);
	if (!ret) {
		fprintf(stderr, "ran out of memory during printing of assertion failure\n");
		exit(1);
	}
	*p++ = '"';
	for (; *s; s++) {
		if (*s == '\r')
			p = stpcpy(p, "\\r"), safe = 1;
		else if (*s == '\t')
			p = stpcpy(p, "\\t"), safe = 1;
		else if (*s == '\a')
			p = stpcpy(p, "\\a"), safe = 1;
		else if (*s == '\f')
			p = stpcpy(p, "\\f"), safe = 1;
		else if (*s == '\v')
			p = stpcpy(p, "\\v"), safe = 1;
		else if (*s == '\b')
			p = stpcpy(p, "\\b"), safe = 1;
		else if (*s == '\n')
			p = stpcpy(p, "\\n"), safe = 1;
		else if (*s == '\"')
			p = stpcpy(p, "\\\""), safe = 1;
		else if (*s == '\\')
			p = stpcpy(p, "\\\\"), safe = 1;
		else if (*s < ' ' || *s >= 127)
			p += sprintf(p, "\\x%02X", (unsigned char)*s), safe = 0;
		else if (*s == '?' && p[-1] == '?')
			p += sprintf(p, "\"\"?"), safe = 1;
		else if (isxdigit(*s) && !safe)
			p += sprintf(p, "\"\"%c", *s), safe = 1;
		else
			*p++ = *s, safe = 1;
	}
	*p++ = '"';
	*p++ = '\0';
	return ret;
}


#define CASE(HOW, OP)\
	case HOW:\
		if ((*have) OP (*expect))\
			return;\
		symbol = #OP;\
		break


#define CASE_LOGIC(HOW, OP1, OP2, OPERAND3_IS_EXPECT)\
	case HOW:\
		if (((*have) OP1 (*expect)) OP2 (OPERAND3_IS_EXPECT ? (*expect) : 0))\
			return;\
		symbol = #OP1;\
		symbol2 = #OP2;\
		operand3_is_expect = (OPERAND3_IS_EXPECT);\
		break


static void
test_assert_uint(const char *file, int line, enum assert_how how, const char *have_string,
                 const char *expect_string, const uintmax_t *have, const uintmax_t *expect)
{
	const char *symbol;
	const char *symbol2 = NULL;
	int operand3_is_expect;
	switch (how) {
	case ASSERT_NOT_LT: how = ASSERT_NOT_GE; break;
	case ASSERT_NOT_LE: how = ASSERT_NOT_GT; break;
	case ASSERT_NOT_GT: how = ASSERT_NOT_LE; break;
	case ASSERT_NOT_GE: how = ASSERT_NOT_LT; break;
	}
	switch (how) {
		CASE(ASSERT_EQ, ==);
		CASE(ASSERT_NE, !=);
		CASE(ASSERT_LT, <);
		CASE(ASSERT_LE, <=);
		CASE(ASSERT_GT, >);
		CASE(ASSERT_GE, >=);
		CASE_LOGIC(ASSERT_CONTAINS_ALL, &, ==, 1);
		CASE_LOGIC(ASSERT_CONTAINS_ANY, &, !=, 0);
		CASE_LOGIC(ASSERT_CONTAINS_NOT_ALL, &, !=, 1);
		CASE_LOGIC(ASSERT_CONTAINS_NOT_ANY, &, ==, 0);
		CASE_LOGIC(ASSERT_IS_IN, &~, ==, 0);
		CASE_LOGIC(ASSERT_NOT_IN, &~, !=, 0);
	default:
		abort();
	}
	if (!symbol2)
		fprintf(stderr, "TEST FAILED at %s:%i: asserting (%s) %s (%s), actualised as %ju %s %ju\n",
		        file, line, have_string, symbol, expect_string, *have, symbol, *expect);
	else if (!operand3_is_expect)
		fprintf(stderr, "TEST FAILED at %s:%i: asserting ((%s) %s (%s)) %s 0, actualised as (%#jx %s %#jx) %s 0\n",
		        file, line, have_string, symbol, expect_string, symbol2, *have, symbol, *expect, symbol2);
	else
		fprintf(stderr, "TEST FAILED at %s:%i: asserting ((%s) %s (%s)) %s (%s), actualised as (%#jx %s %#jx) %s %#jx\n",
		        file, line, have_string, symbol, expect_string, symbol2, expect_string,
		        *have, symbol, *expect, symbol2, *expect);
	exit(1);
}


static void
test_assert_int(const char *file, int line, enum assert_how how, const char *have_string,
                const char *expect_string, const intmax_t *have, const intmax_t *expect)
{
	const char *symbol;
	switch (how) {
	case ASSERT_NOT_LT: how = ASSERT_NOT_GE; break;
	case ASSERT_NOT_LE: how = ASSERT_NOT_GT; break;
	case ASSERT_NOT_GT: how = ASSERT_NOT_LE; break;
	case ASSERT_NOT_GE: how = ASSERT_NOT_LT; break;
	}
	switch (how) {
		CASE(ASSERT_EQ, ==);
		CASE(ASSERT_NE, !=);
		CASE(ASSERT_LT, <);
		CASE(ASSERT_LE, <=);
		CASE(ASSERT_GT, >);
		CASE(ASSERT_GE, >=);
	default:
		test_assert_uint(file, line, how, have_string, expect_string,
		                 (const uintmax_t *)have, (const uintmax_t *)expect);
		return;
	}
	fprintf(stderr, "TEST FAILED at %s:%i: asserting (%s) %s (%s), actualised as %ji %s %ji\n",
	        file, line, have_string, symbol, expect_string, *have, symbol, *expect);
	exit(1);
}


#define PTR_CASE(HOW, OP)\
	case HOW:\
		if ((have) OP (expect))\
			return;\
		symbol = #OP;\
		break


static void
test_assert_ptr(const char *file, int line, enum assert_how how, const char *have_string,
                const char *expect_string, const char *have, const char *expect)
{
	const char *symbol;
	switch (how) {
	case ASSERT_NOT_LT: how = ASSERT_NOT_GE; break;
	case ASSERT_NOT_LE: how = ASSERT_NOT_GT; break;
	case ASSERT_NOT_GT: how = ASSERT_NOT_LE; break;
	case ASSERT_NOT_GE: how = ASSERT_NOT_LT; break;
	}
	switch (how) {
		PTR_CASE(ASSERT_EQ, ==);
		PTR_CASE(ASSERT_NE, !=);
		PTR_CASE(ASSERT_LT, <);
		PTR_CASE(ASSERT_LE, <=);
		PTR_CASE(ASSERT_GT, >);
		PTR_CASE(ASSERT_GE, >=);
	default:
		abort();
	}
	fprintf(stderr, "TEST FAILED at %s:%i: asserting (%s) %s (%s), actualised as %p %s %p\n",
	        file, line, have_string, symbol, expect_string, have, symbol, expect);
	exit(1);
}


#define STR_CASE(HOW, FUNC)\
	case HOW:\
		if (FUNC(have, expect))\
			return;\
		function = #FUNC;\
		break

#define SWAP_STR_CASE(HOW, NEW_HOW)\
	case HOW:\
		test_assert_str(file, line, how, expect_string, have_string, expect, have, rev);\
		return


static void
test_assert_str(const char *file, int line, enum assert_how how, const char *have_string,
                const char *expect_string, const char *have, const char *expect, int rev)
{
	const char *function;
	size_t have_len;
	size_t expect_len;
	char *have_quote = NULL;
	char *expect_quote = NULL;

	if (!have && !expect)
		fprintf(stderr, "TEST FAILED at %s:%i: asserting failed because (%s) and (%s) are NULL\n",
		        file, line, have_string, expect_string);
	else if (!have)
		fprintf(stderr, "TEST FAILED at %s:%i: asserting failed because (%s) was NULL\n",
		        file, line, have_string);
	else if (!expect)
		fprintf(stderr, "TEST FAILED at %s:%i: asserting failed because (%s) was NULL\n",
		        file, line, expect_string);
	if (!have || !expect)
		exit(1);

	switch (how) {
		STR_CASE(ASSERT_EQ, !strcmp);
		STR_CASE(ASSERT_NE, strcmp);
		STR_CASE(ASSERT_IS_IN, strstr);
		STR_CASE(ASSERT_NOT_IN, !strstr);
		SWAP_STR_CASE(ASSERT_CONTAINS_ALL, ASSERT_IS_IN);
		SWAP_STR_CASE(ASSERT_CONTAINS_NOT_ALL, ASSERT_NOT_INT);
		SWAP_STR_CASE(ASSERT_LE, ASSERT_GE);
		SWAP_STR_CASE(ASSERT_LT, ASSERT_GT);
		SWAP_STR_CASE(ASSERT_NOT_LE, ASSERT_NOT_GE);
		SWAP_STR_CASE(ASSERT_NOT_LT, ASSERT_NOT_GT);
	default:
		have_len = strlen(have);
		expect_len = strlen(expect);
		if (rev)
			goto assert_end;
		else
			goto assert_start;
	}
	fprintf(stderr, "TEST FAILED at %s:%i: asserting %s(%s, %s), actualised as %s(%s, %s)\n",
	        file, line, function, have_string, expect_string,
	        function, quote(have, &have_quote), quote(expect, &expect_quote));
free_and_exit:
	free(have_quote);
	free(expect_quote);
	exit(1);

assert_start:
	switch (how) {
	case ASSERT_GE:
		if (have_len >= expect_len && !strncmp(have, expect, expect_len))
			return;
		fprintf(stderr, "TEST FAILED at %s:%i: "
		        "asserting strlen(%s) >= strlen(%s) && !strncmp(%s, %s, strlen(%s)), "
		        "actualised as %zu >= %zu && !strncmp(%s, %s, %zu)\n",
		        file, line, have_string, expect_string, have_string, expect_string, expect_string,
		        have_len, expect_len, quote(have, &have_quote), quote(expect, &expect_quote), expect_len);
		break;
	case ASSERT_NOT_GE:
		if (have_len < expect_len || strncmp(have, expect, expect_len))
			return;
		fprintf(stderr, "TEST FAILED at %s:%i: "
		        "asserting strlen(%s) < strlen(%s) || strncmp(%s, %s, strlen(%s)), "
		        "actualised as %zu < %zu && !strncmp(%s, %s, %zu)\n",
		        file, line, have_string, expect_string, have_string, expect_string, expect_string,
		        have_len, expect_len, quote(have, &have_quote), quote(expect, &expect_quote), expect_len);
		break;
	case ASSERT_GT:
		if (have_len > expect_len && !strncmp(have, expect, expect_len))
			return;
		fprintf(stderr, "TEST FAILED at %s:%i: "
		        "asserting strlen(%s) > strlen(%s) && !strncmp(%s, %s, strlen(%s)), "
		        "actualised as %zu > %zu && !strncmp(%s, %s, %zu)\n",
		        file, line, have_string, expect_string, have_string, expect_string, expect_string,
		        have_len, expect_len, quote(have, &have_quote), quote(expect, &expect_quote), expect_len);
		break;
	case ASSERT_NOT_GT:
		if (have_len <= expect_len || strncmp(have, expect, expect_len))
			return;
		fprintf(stderr, "TEST FAILED at %s:%i: "
		        "asserting strlen(%s) <= strlen(%s) || strncmp(%s, %s, strlen(%s)), "
		        "actualised as %zu <= %zu || strncmp(%s, %s, %zu)\n",
		        file, line, have_string, expect_string, have_string, expect_string, expect_string,
		        have_len, expect_len, quote(have, &have_quote), quote(expect, &expect_quote), expect_len);
		break;
	default:
		abort();
	}
	goto free_and_exit;

assert_end:
	switch (how) {
	case ASSERT_GE:
		if (have_len >= expect_len && !strcmp(&have[have_len - expect_len], expect))
			return;
		fprintf(stderr, "TEST FAILED at %s:%i: "
		        "asserting strlen(%s) >= strlen(%s) && !strcmp(&(%s)[strlen(%s) - strlen(%s)], %s), "
		        "actualised as %zu >= %zu && !strcmp(&%s[%ti], %s)\n",
		        file, line, have_string, expect_string, have_string, have_string, expect_string,
		        expect_string, have_len, expect_len, quote(have, &have_quote),
		        (ptrdiff_t)have_len - (ptrdiff_t)expect_len, quote(expect, &expect_quote));
		break;
	case ASSERT_NOT_GE:
		if (have_len > expect_len || strcmp(&have[have_len - expect_len], expect))
			return;
		fprintf(stderr, "TEST FAILED at %s:%i: "
		        "asserting strlen(%s) < strlen(%s) || strcmp(&(%s)[strlen(%s) - strlen(%s)], %s), "
		        "actualised as %zu < %zu && !strcmp(&%s[%ti], %s)\n",
		        file, line, have_string, expect_string, have_string, have_string, expect_string,
		        expect_string, have_len, expect_len, quote(have, &have_quote),
		        (ptrdiff_t)have_len - (ptrdiff_t)expect_len, quote(expect, &expect_quote));
		break;
	case ASSERT_GT:
		if (have_len > expect_len && !strcmp(&have[have_len - expect_len], expect))
			return;
		fprintf(stderr, "TEST FAILED at %s:%i: "
		        "asserting strlen(%s) > strlen(%s) && !strcmp(&(%s)[strlen(%s) - strlen(%s)], %s), "
		        "actualised as %zu > %zu && !strcmp(&%s[%ti], %s)\n",
		        file, line, have_string, expect_string, have_string, have_string, expect_string,
		        expect_string, have_len, expect_len, quote(have, &have_quote),
		        (ptrdiff_t)have_len - (ptrdiff_t)expect_len, quote(expect, &expect_quote));
		break;
	case ASSERT_NOT_GT:
		if (have_len <= expect_len || strcmp(&have[have_len - expect_len], expect))
			return;
		fprintf(stderr, "TEST FAILED at %s:%i: "
		        "asserting strlen(%s) <= strlen(%s) || strcmp(&(%s)[strlen(%s) - strlen(%s)], %s), "
		        "actualised as %zu <= %zu || strcmp(&%s[%ti], %s)\n",
		        file, line, have_string, expect_string, have_string, have_string, expect_string,
		        expect_string, have_len, expect_len, quote(have, &have_quote),
		        (ptrdiff_t)have_len - (ptrdiff_t)expect_len, quote(expect, &expect_quote));
		break;
	default:
		abort();
	}
	goto free_and_exit;
}


void
test_assert(const char *file, int line, enum assert_type type, enum assert_how how,
            const char *have_string, const char *expect_string, const void *have, const void *expect)
{
	switch (type) {
	case ASSERT_ENUM:
	case ASSERT_UINT:
		test_assert_uint(file, line, how, have_string, expect_string, have, expect);
		break;

	case ASSERT_INT:
		test_assert_int(file, line, how, have_string, expect_string, have, expect);
		break;

	case ASSERT_NULL:
	case ASSERT_PTR:
		test_assert_ptr(file, line, how, have_string, expect_string, have, expect);
		break;

	case ASSERT_STR:
		test_assert_str(file, line, how, have_string, expect_string, have, expect, 0);
		break;

	case ASSERT_STR_REV:
		test_assert_str(file, line, how, have_string, expect_string, have, expect, 1);
		break;

	default:
		abort();
	}
}