aboutsummaryrefslogtreecommitdiff
path: root/utest/utest.h
blob: 218b2b70054240c157ebc921cffc73706b098ac3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/* SPDX-License-Identifier: LGPL-2.1 */
/**
 * utest.h - header-only unit testing micro "framework". v1.0.0
 *
 * Copyright (c) 2021-2023 Yaroslav de la Peña Smirnov
 */
#ifndef UTEST_H
#define UTEST_H
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

/*
 * TODO?: test runners that run in different processes in order to not crash the
 * whole test unit when a single test crashes.
 */

/**
 * Constants
 * =========
 */

/**
 * Terminal escape codes
 * ---------------------
 *
 * TBLD:	bold text.
 * TRED:	red text.
 * TGRN:	green text.
 * TBLU:	blue text.
 * TRST:	reset colors.
 * TDEL:	delete current line.
 */
#ifndef NOCOLOR
#define TBLD "\033[1m"
#define TRED "\033[31m"
#define TGRN "\033[32m"
#define TBLU "\033[34m"
#define TRST "\033[0m"
#else
#define TBLD ""
#define TRED ""
#define TGRN ""
#define TBLU ""
#define TRST ""
#endif
#define TDEL "\33[2K\r"

/**
 * Typedefs
 * ========
 */

typedef bool (*test_fn)(void);
typedef void (*setup_fn)(void);

/**
 * Internal variables
 * ==================
 */

static setup_fn tests_init	  = NULL;
static setup_fn tests_destroy = NULL;

/**
 * Functions and function-like macros
 * ==================================
 */

/**
 * FAIL_TEST - fail the test and print the @reason.
 * @reason:	a variable list of arguments, where the first argument is the format
 * 			text.
 */
#define FAIL_TEST(reason...)                                                  \
	printf(TDEL " [" TBLD TRED "×" TRST "]\t%s: " TBLD TRED "FAILED!\n" TRST, \
		   __this_name);                                                      \
	printf("\t﹂%s:%d: ", __FILE__, __LINE__);                                \
	printf(reason);                                                           \
	printf("\n");                                                             \
	__ret = false;                                                            \
	goto test_out

/**
 * asserteq - assert @a and @b's equality; fail test if not equal.
 * @a:	argument to compare to b.
 * @b:	argument to compare to a.
 */
#define asserteq(a, b)                                                     \
	({                                                                     \
		if ((a) != (b)) {                                                      \
			FAIL_TEST("assertion " TBLD TBLU #a " == " #b TRST " failed"); \
		}                                                                  \
	})

/**
 * assertneq - assert @a and @b's inequality; fail test if equal.
 * @a:	argument to compare to b.
 * @b:	argument to compare to a.
 */
#define assertneq(a, b)                                                    \
	({                                                                     \
		if ((a) == (b)) {                                                      \
			FAIL_TEST("assertion " TBLD TBLU #a " != " #b TRST " failed"); \
		}                                                                  \
	})

/**
 * expect - fail the test on !@cond and print the reason.
 * @cond:	condition to test.
 * @reason:	a variable list of arguments, where the first argument is the format
 * 			text.
 */
#define expect(cond, reason...) \
	({                          \
		if (!(cond)) {            \
			FAIL_TEST(reason);  \
		}                       \
	})

/**
 * TEST_BEGIN - declare test function @name.
 * @name:	name of test function to declare.
 *
 * Should be followed by TEST_OUT and TEST_END.
 */
#define TEST_BEGIN(name)                                          \
	int name(void)                                                \
	{                                                             \
		const char *__this_name = #name;                          \
		bool		__ret		= true;                           \
		printf(" [⏳]\t%s: " TBLU "running..." TRST, __this_name); \
		fflush(stdout);

/**
 * TEST_OUT - code that should be executed on test exit goes after this.
 * This declares a goto label, so that on test failure anything that needs to be
 * cleaned up is cleaned up. You need to use this even if there's nothing to be
 * cleaned up.
 */
#define TEST_OUT \
test_out:;

/**
 * TEST_END - close the test scope.
 */
#define TEST_END                                                \
	if (__ret) {                                                \
		printf(TDEL " [" TBLD TGRN "✓" TRST "]\t%s: " TBLD TGRN \
					"PASSED!\n" TRST,                           \
			   __this_name);                                    \
	}                                                           \
	return __ret;                                               \
	}

/**
 * __run_tests() - runs tests; for internal use.
 */
static inline int __run_tests(const char *const unit_name, ...)
{
	va_list args;
	test_fn test;
	size_t	passed = 0, total = 0, failed = 0;

	if (tests_init) {
		tests_init();
	}

	printf("[***]\t" TBLD "running:" TRST TBLU " %s " TRST "tests\n",
		   unit_name);

	va_start(args, unit_name);
	test = va_arg(args, test_fn);
	while (test) {
		test() ? passed++ : 0;
		total++;
		test = va_arg(args, test_fn);
	}
	va_end(args);

	failed = total - passed;

	printf("[***]\t" TBLD "finished:" TRST TBLU " %s: " TRST "tests: " TBLU
		   "%lu" TRST ", passed: " TGRN "%lu" TRST ", failed: " TRED "%lu" TRST
		   "\n",
		   unit_name, total, passed, failed);

	if (tests_destroy) {
		tests_destroy();
	}

	return failed;
}

/**
 * RUN_TEST_SUITE - runs the @tests given as arguments.
 * @init_fn:		initialization function.
 * @destroy_fn:		cleanup function.
 * @tests...:		tests to run.
 *
 * @init_fn and @destroy_fn are used to initialize and cleanup test-related data
 * respectively.
 */
#define RUN_TEST_SUITE(init_fn, destroy_fn, tests...) \
	int main(void)                                    \
	{                                                 \
		tests_init	  = init_fn;                      \
		tests_destroy = destroy_fn;                   \
		return __run_tests(__FILE__, tests, NULL);    \
	}

/**
 * RUN_TESTS - runs the @tests given as arguments.
 * @tests...:	tests to run.
 *
 * Same as RUN_TEST_SUITE, but without initialization and cleanup functions
 */
#define RUN_TESTS(tests...) RUN_TEST_SUITE(NULL, NULL, tests)

#endif /* UTEST_H */