Skip to content

Commit 9e99603

Browse files
committed
clar: introduce type-safe integer comparisons
The macros we have to assert the state of integers are lacking due to multiple reasons: - We explicitly cast the values to `int`, which causes problems in case the values do not fit into an `int`. Furthermore, this hides issues in case one accidentally passes the wrong type to this macro. - We only have macros to compare integers for equality. Notably lacking are constructs to compare for non-equality, like "less than" or "less or equal". - We only have macros to compare _signed_ integers, but lack macros to check for _unsigned_ macros. Fix this issue by introducing `clar__assert_compare_i()` as well as an equivalent for unsigned types, `clar__assert_compare_u()`. These macros: - Get `intmax_t` and `uintmax_t` as input, respectively, which allows us to get rid of the explicit casts. Instead, the compiler can now verify types for us and print warnings when there is an incompatible type. - Get an enum as input for the various different comparisons. Like this we don't only support equality checks, but also all the other checks one would typically expect. Adapt existing macros to use `clar__assert_compare_i()`. Furthermore, introduce new macros that supersede the older variants and which allow the caller to perform integer comparisons.
1 parent 5b8d26a commit 9e99603

9 files changed

Lines changed: 237 additions & 12 deletions

File tree

clar.c

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,86 @@ void clar__assert_equal(
944944
clar__fail(file, function, line, err, buf, should_abort);
945945
}
946946

947+
void clar__assert_compare_i(
948+
const char *file,
949+
const char *func,
950+
size_t line,
951+
int should_abort,
952+
enum clar_comparison cmp,
953+
intmax_t value1,
954+
intmax_t value2,
955+
const char *error,
956+
const char *description,
957+
...)
958+
{
959+
int fulfilled;
960+
switch (cmp) {
961+
case CLAR_COMPARISON_EQ:
962+
fulfilled = value1 == value2;
963+
break;
964+
case CLAR_COMPARISON_LT:
965+
fulfilled = value1 < value2;
966+
break;
967+
case CLAR_COMPARISON_LE:
968+
fulfilled = value1 <= value2;
969+
break;
970+
case CLAR_COMPARISON_GT:
971+
fulfilled = value1 > value2;
972+
break;
973+
case CLAR_COMPARISON_GE:
974+
fulfilled = value1 >= value2;
975+
break;
976+
}
977+
978+
if (!fulfilled) {
979+
va_list args;
980+
va_start(args, description);
981+
clar__failv(file, func, line, should_abort, error,
982+
description, args);
983+
va_end(args);
984+
}
985+
}
986+
987+
void clar__assert_compare_u(
988+
const char *file,
989+
const char *func,
990+
size_t line,
991+
int should_abort,
992+
enum clar_comparison cmp,
993+
uintmax_t value1,
994+
uintmax_t value2,
995+
const char *error,
996+
const char *description,
997+
...)
998+
{
999+
int fulfilled;
1000+
switch (cmp) {
1001+
case CLAR_COMPARISON_EQ:
1002+
fulfilled = value1 == value2;
1003+
break;
1004+
case CLAR_COMPARISON_LT:
1005+
fulfilled = value1 < value2;
1006+
break;
1007+
case CLAR_COMPARISON_LE:
1008+
fulfilled = value1 <= value2;
1009+
break;
1010+
case CLAR_COMPARISON_GT:
1011+
fulfilled = value1 > value2;
1012+
break;
1013+
case CLAR_COMPARISON_GE:
1014+
fulfilled = value1 >= value2;
1015+
break;
1016+
}
1017+
1018+
if (!fulfilled) {
1019+
va_list args;
1020+
va_start(args, description);
1021+
clar__failv(file, func, line, should_abort, error,
1022+
description, args);
1023+
va_end(args);
1024+
}
1025+
}
1026+
9471027
void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
9481028
{
9491029
_clar.local_cleanup = cleanup;

clar.h

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#ifndef __CLAR_TEST_H__
88
#define __CLAR_TEST_H__
99

10+
#include <inttypes.h>
1011
#include <stdlib.h>
1112
#include <limits.h>
1213

@@ -169,9 +170,34 @@ const char *cl_fixture_basename(const char *fixture_name);
169170
#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len))
170171
#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len))
171172

172-
#define cl_assert_equal_i(i1,i2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2))
173-
#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2))
174-
#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2))
173+
/* The following three macros are essentially deprecated now in favor of the macros in subsequent blocks. */
174+
#define cl_assert_equal_i(i1,i2) cl_assert_compare_i(i1,i2,CLAR_COMPARISON_EQ,#i1 " != " #i2,"%"PRIdMAX " != %"PRIdMAX,(intmax_t)(i1),(intmax_t)(i2))
175+
#define cl_assert_equal_i_(i1,i2,note) cl_assert_compare_i(i1,i2,CLAR_COMPARISON_EQ,#i1 " != " #i2 " (" #note ")","%"PRIdMAX " != %"PRIdMAX,(intmax_t)(i1),(intmax_t)(i2))
176+
#define cl_assert_equal_i_fmt(i1,i2,fmt) cl_assert_compare_i(i1,i2,CLAR_COMPARISON_EQ,#i1 " != " #i2, fmt " != " fmt, (int)(i1), (int)(i2))
177+
178+
#define cl_assert_compare_i(i1,i2,cmp,error,description,...) clar__assert_compare_i(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,1,cmp,(i1),(i2),error,description,__VA_ARGS__)
179+
#define cl_assert_eq_i_(i1,i2,description,...) cl_assert_compare_i(i1,i2,CLAR_COMPARISON_EQ,"Expected comparison to hold: " #i1 " == " #i2,description,__VA_ARGS__)
180+
#define cl_assert_eq_i(i1,i2) cl_assert_eq_i_(i1,i2,"%"PRIdMAX " != %"PRIdMAX,(intmax_t)(i1),(intmax_t)(i2))
181+
#define cl_assert_lt_i_(i1,i2,description,...) cl_assert_compare_i(i1,i2,CLAR_COMPARISON_LT,"Expected comparison to hold: " #i1 " < " #i2,description,__VA_ARGS__)
182+
#define cl_assert_lt_i(i1,i2) cl_assert_lt_i_(i1,i2,"%"PRIdMAX " >= %"PRIdMAX,(intmax_t)(i1),(intmax_t)(i2))
183+
#define cl_assert_le_i_(i1,i2,description,...) cl_assert_compare_i(i1,i2,CLAR_COMPARISON_LE,"Expected comparison to hold: " #i1 " <= " #i2,description,__VA_ARGS__)
184+
#define cl_assert_le_i(i1,i2) cl_assert_le_i_(i1,i2,"%"PRIdMAX " > %"PRIdMAX,(intmax_t)(i1),(intmax_t)(i2))
185+
#define cl_assert_gt_i_(i1,i2,description,...) cl_assert_compare_i(i1,i2,CLAR_COMPARISON_GT,"Expected comparison to hold: " #i1 " > " #i2,description,__VA_ARGS__)
186+
#define cl_assert_gt_i(i1,i2) cl_assert_gt_i_(i1,i2,"%"PRIdMAX " <= %"PRIdMAX,(intmax_t)(i1),(intmax_t)(i2))
187+
#define cl_assert_ge_i_(i1,i2,description,...) cl_assert_compare_i(i1,i2,CLAR_COMPARISON_GE,"Expected comparison to hold: " #i1 " >= " #i2,description,__VA_ARGS__)
188+
#define cl_assert_ge_i(i1,i2) cl_assert_ge_i_(i1,i2,"%"PRIdMAX " < %"PRIdMAX,(intmax_t)(i1),(intmax_t)(i2))
189+
190+
#define cl_assert_compare_u(u1,u2,cmp,error,description,...) clar__assert_compare_u(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,1,cmp,(u1),(u2),error,description,__VA_ARGS__)
191+
#define cl_assert_eq_u_(u1,u2,description,...) cl_assert_compare_u(u1,u2,CLAR_COMPARISON_EQ,"Expected comparison to hold: " #u1 " == " #u2,description,__VA_ARGS__)
192+
#define cl_assert_eq_u(u1,u2) cl_assert_eq_u_(u1,u2,"%"PRIuMAX " != %"PRIuMAX,(uintmax_t)(u1),(uintmax_t)(u2))
193+
#define cl_assert_lt_u_(u1,u2,description,...) cl_assert_compare_u(u1,u2,CLAR_COMPARISON_LT,"Expected comparison to hold: " #u1 " < " #u2,description,__VA_ARGS__)
194+
#define cl_assert_lt_u(u1,u2) cl_assert_lt_u_(u1,u2,"%"PRIuMAX " >= %"PRIuMAX,(uintmax_t)(u1),(uintmax_t)(u2))
195+
#define cl_assert_le_u_(u1,u2,description,...) cl_assert_compare_u(u1,u2,CLAR_COMPARISON_LE,"Expected comparison to hold: " #u1 " <= " #u2,description,__VA_ARGS__)
196+
#define cl_assert_le_u(u1,u2) cl_assert_le_u_(u1,u2,"%"PRIuMAX " > %"PRIuMAX,(uintmax_t)(u1),(uintmax_t)(u2))
197+
#define cl_assert_gt_u_(u1,u2,description,...) cl_assert_compare_u(u1,u2,CLAR_COMPARISON_GT,"Expected comparison to hold: " #u1 " > " #u2,description,__VA_ARGS__)
198+
#define cl_assert_gt_u(u1,u2) cl_assert_gt_u_(u1,u2,"%"PRIuMAX " <= %"PRIuMAX,(uintmax_t)(u1),(uintmax_t)(u2))
199+
#define cl_assert_ge_u_(u1,u2,description,...) cl_assert_compare_u(u1,u2,CLAR_COMPARISON_GE,"Expected comparison to hold: " #u1 " >= " #u2,description,__VA_ARGS__)
200+
#define cl_assert_ge_u(u1,u2) cl_assert_ge_u_(u1,u2,"%"PRIuMAX " < %"PRIuMAX,(uintmax_t)(u1),(uintmax_t)(u2))
175201

176202
#define cl_assert_equal_b(b1,b2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0))
177203

@@ -214,6 +240,38 @@ void clar__assert_equal(
214240
const char *fmt,
215241
...);
216242

243+
enum clar_comparison {
244+
CLAR_COMPARISON_EQ,
245+
CLAR_COMPARISON_LT,
246+
CLAR_COMPARISON_LE,
247+
CLAR_COMPARISON_GT,
248+
CLAR_COMPARISON_GE,
249+
};
250+
251+
void clar__assert_compare_i(
252+
const char *file,
253+
const char *func,
254+
size_t line,
255+
int should_abort,
256+
enum clar_comparison cmp,
257+
intmax_t value1,
258+
intmax_t value2,
259+
const char *error,
260+
const char *description,
261+
...);
262+
263+
void clar__assert_compare_u(
264+
const char *file,
265+
const char *func,
266+
size_t line,
267+
int should_abort,
268+
enum clar_comparison cmp,
269+
uintmax_t value1,
270+
uintmax_t value2,
271+
const char *error,
272+
const char *description,
273+
...);
274+
217275
void clar__set_invokepoint(
218276
const char *file,
219277
const char *func,

test/expected/quiet

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,13 @@ selftest::suite::failf [file:42]
5252
Test failed.
5353
some reason: foo
5454

55+
12) Failure:
56+
selftest::suite::compare_i [file:42]
57+
Expected comparison to hold: 2 < 1
58+
2 >= 1
59+
60+
13) Failure:
61+
selftest::suite::compare_u [file:42]
62+
Expected comparison to hold: 2 < 1
63+
2 >= 1
64+

test/expected/summary_with_filename

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Loaded 1 suites:
22
Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')
3-
FFFFFFFFFFF
3+
FFFFFFFFFFFFF
44

55
1) Failure:
66
selftest::suite::1 [file:42]
@@ -56,4 +56,14 @@ selftest::suite::failf [file:42]
5656
Test failed.
5757
some reason: foo
5858

59+
12) Failure:
60+
selftest::suite::compare_i [file:42]
61+
Expected comparison to hold: 2 < 1
62+
2 >= 1
63+
64+
13) Failure:
65+
selftest::suite::compare_u [file:42]
66+
Expected comparison to hold: 2 < 1
67+
2 >= 1
68+
5969
written summary file to different.xml

test/expected/summary_without_filename

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Loaded 1 suites:
22
Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')
3-
FFFFFFFFFFF
3+
FFFFFFFFFFFFF
44

55
1) Failure:
66
selftest::suite::1 [file:42]
@@ -56,4 +56,14 @@ selftest::suite::failf [file:42]
5656
Test failed.
5757
some reason: foo
5858

59+
12) Failure:
60+
selftest::suite::compare_i [file:42]
61+
Expected comparison to hold: 2 < 1
62+
2 >= 1
63+
64+
13) Failure:
65+
selftest::suite::compare_u [file:42]
66+
Expected comparison to hold: 2 < 1
67+
2 >= 1
68+
5969
written summary file to summary.xml

test/expected/tap

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,24 @@ not ok 11 - selftest::suite::failf
109109
line: 42
110110
function: 'func'
111111
---
112-
1..11
112+
not ok 12 - selftest::suite::compare_i
113+
---
114+
reason: |
115+
Expected comparison to hold: 2 < 1
116+
2 >= 1
117+
at:
118+
file: 'file'
119+
line: 42
120+
function: 'func'
121+
---
122+
not ok 13 - selftest::suite::compare_u
123+
---
124+
reason: |
125+
Expected comparison to hold: 2 < 1
126+
2 >= 1
127+
at:
128+
file: 'file'
129+
line: 42
130+
function: 'func'
131+
---
132+
1..13

test/expected/without_arguments

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Loaded 1 suites:
22
Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')
3-
FFFFFFFFFFF
3+
FFFFFFFFFFFFF
44

55
1) Failure:
66
selftest::suite::1 [file:42]
@@ -56,3 +56,13 @@ selftest::suite::failf [file:42]
5656
Test failed.
5757
some reason: foo
5858

59+
12) Failure:
60+
selftest::suite::compare_i [file:42]
61+
Expected comparison to hold: 2 < 1
62+
2 >= 1
63+
64+
13) Failure:
65+
selftest::suite::compare_u [file:42]
66+
Expected comparison to hold: 2 < 1
67+
2 >= 1
68+

test/selftest.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ void test_selftest__help(void)
244244

245245
void test_selftest__without_arguments(void)
246246
{
247-
cl_invoke(run("without_arguments", 11, NULL));
247+
cl_invoke(run("without_arguments", 13, NULL));
248248
}
249249

250250
void test_selftest__specific_test(void)
@@ -259,12 +259,12 @@ void test_selftest__stop_on_failure(void)
259259

260260
void test_selftest__quiet(void)
261261
{
262-
cl_invoke(run("quiet", 11, "-q", NULL));
262+
cl_invoke(run("quiet", 13, "-q", NULL));
263263
}
264264

265265
void test_selftest__tap(void)
266266
{
267-
cl_invoke(run("tap", 11, "-t", NULL));
267+
cl_invoke(run("tap", 13, "-t", NULL));
268268
}
269269

270270
void test_selftest__suite_names(void)
@@ -275,15 +275,15 @@ void test_selftest__suite_names(void)
275275
void test_selftest__summary_without_filename(void)
276276
{
277277
struct stat st;
278-
cl_invoke(run("summary_without_filename", 11, "-r", NULL));
278+
cl_invoke(run("summary_without_filename", 13, "-r", NULL));
279279
/* The summary contains timestamps, so we cannot verify its contents. */
280280
cl_must_pass(stat("summary.xml", &st));
281281
}
282282

283283
void test_selftest__summary_with_filename(void)
284284
{
285285
struct stat st;
286-
cl_invoke(run("summary_with_filename", 11, "-rdifferent.xml", NULL));
286+
cl_invoke(run("summary_with_filename", 13, "-rdifferent.xml", NULL));
287287
/* The summary contains timestamps, so we cannot verify its contents. */
288288
cl_must_pass(stat("different.xml", &st));
289289
}

test/selftest_suite/selftest_suite.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,30 @@ void test_selftest_suite__failf(void)
9595
{
9696
cl_failf("some reason: %s", "foo");
9797
}
98+
99+
void test_selftest_suite__compare_i(void)
100+
{
101+
cl_assert_equal_i(1, 1);
102+
cl_assert_eq_i(1, 1);
103+
cl_assert_lt_i(1, 2);
104+
cl_assert_le_i(1, 2);
105+
cl_assert_le_i(2, 2);
106+
cl_assert_gt_i(2, 1);
107+
cl_assert_ge_i(2, 2);
108+
cl_assert_ge_i(3, 2);
109+
110+
cl_assert_lt_i(2, 1); /* this one fails */
111+
}
112+
113+
void test_selftest_suite__compare_u(void)
114+
{
115+
cl_assert_eq_u(1, 1);
116+
cl_assert_lt_u(1, 2);
117+
cl_assert_le_u(1, 2);
118+
cl_assert_le_u(2, 2);
119+
cl_assert_gt_u(2, 1);
120+
cl_assert_ge_u(2, 2);
121+
cl_assert_ge_u(3, 2);
122+
123+
cl_assert_lt_u(2, 1); /* this one fails */
124+
}

0 commit comments

Comments
 (0)