Skip to content

Commit 06f0c35

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 f74a68d commit 06f0c35

File tree

9 files changed

+247
-12
lines changed

9 files changed

+247
-12
lines changed

clar.c

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,92 @@ void clar__assert_equal(
939939
clar__fail(file, function, line, err, buf, should_abort);
940940
}
941941

942+
void clar__assert_compare_i(
943+
const char *file,
944+
const char *func,
945+
size_t line,
946+
int should_abort,
947+
enum clar_comparison cmp,
948+
intmax_t value1,
949+
intmax_t value2,
950+
const char *error,
951+
const char *description,
952+
...)
953+
{
954+
int fulfilled;
955+
switch (cmp) {
956+
case CLAR_COMPARISON_EQ:
957+
fulfilled = value1 == value2;
958+
break;
959+
case CLAR_COMPARISON_LT:
960+
fulfilled = value1 < value2;
961+
break;
962+
case CLAR_COMPARISON_LE:
963+
fulfilled = value1 <= value2;
964+
break;
965+
case CLAR_COMPARISON_GT:
966+
fulfilled = value1 > value2;
967+
break;
968+
case CLAR_COMPARISON_GE:
969+
fulfilled = value1 >= value2;
970+
break;
971+
default:
972+
cl_assert(0);
973+
return;
974+
}
975+
976+
if (!fulfilled) {
977+
va_list args;
978+
va_start(args, description);
979+
clar__failv(file, func, line, should_abort, error,
980+
description, args);
981+
va_end(args);
982+
}
983+
}
984+
985+
void clar__assert_compare_u(
986+
const char *file,
987+
const char *func,
988+
size_t line,
989+
int should_abort,
990+
enum clar_comparison cmp,
991+
uintmax_t value1,
992+
uintmax_t value2,
993+
const char *error,
994+
const char *description,
995+
...)
996+
{
997+
int fulfilled;
998+
switch (cmp) {
999+
case CLAR_COMPARISON_EQ:
1000+
fulfilled = value1 == value2;
1001+
break;
1002+
case CLAR_COMPARISON_LT:
1003+
fulfilled = value1 < value2;
1004+
break;
1005+
case CLAR_COMPARISON_LE:
1006+
fulfilled = value1 <= value2;
1007+
break;
1008+
case CLAR_COMPARISON_GT:
1009+
fulfilled = value1 > value2;
1010+
break;
1011+
case CLAR_COMPARISON_GE:
1012+
fulfilled = value1 >= value2;
1013+
break;
1014+
default:
1015+
cl_assert(0);
1016+
return;
1017+
}
1018+
1019+
if (!fulfilled) {
1020+
va_list args;
1021+
va_start(args, description);
1022+
clar__failv(file, func, line, should_abort, error,
1023+
description, args);
1024+
va_end(args);
1025+
}
1026+
}
1027+
9421028
void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
9431029
{
9441030
_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
@@ -47,3 +47,13 @@ combined::failf [file:42]
4747
Test failed.
4848
some reason: foo
4949

50+
11) Failure:
51+
combined::compare_i [file:42]
52+
Expected comparison to hold: two < 1
53+
2 >= 1
54+
55+
12) Failure:
56+
combined::compare_u [file:42]
57+
Expected comparison to hold: two < 1
58+
2 >= 1
59+

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-
FFFFFFFFFF
3+
FFFFFFFFFFFF
44

55
1) Failure:
66
combined::1 [file:42]
@@ -51,4 +51,14 @@ combined::failf [file:42]
5151
Test failed.
5252
some reason: foo
5353

54+
11) Failure:
55+
combined::compare_i [file:42]
56+
Expected comparison to hold: two < 1
57+
2 >= 1
58+
59+
12) Failure:
60+
combined::compare_u [file:42]
61+
Expected comparison to hold: two < 1
62+
2 >= 1
63+
5464
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-
FFFFFFFFFF
3+
FFFFFFFFFFFF
44

55
1) Failure:
66
combined::1 [file:42]
@@ -51,4 +51,14 @@ combined::failf [file:42]
5151
Test failed.
5252
some reason: foo
5353

54+
11) Failure:
55+
combined::compare_i [file:42]
56+
Expected comparison to hold: two < 1
57+
2 >= 1
58+
59+
12) Failure:
60+
combined::compare_u [file:42]
61+
Expected comparison to hold: two < 1
62+
2 >= 1
63+
5464
written summary file to summary.xml

test/expected/tap

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,24 @@ not ok 10 - combined::failf
9999
line: 42
100100
function: 'func'
101101
...
102-
1..10
102+
not ok 11 - combined::compare_i
103+
---
104+
reason: |
105+
Expected comparison to hold: two < 1
106+
2 >= 1
107+
at:
108+
file: 'file'
109+
line: 42
110+
function: 'func'
111+
...
112+
not ok 12 - combined::compare_u
113+
---
114+
reason: |
115+
Expected comparison to hold: two < 1
116+
2 >= 1
117+
at:
118+
file: 'file'
119+
line: 42
120+
function: 'func'
121+
...
122+
1..12

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-
FFFFFFFFFF
3+
FFFFFFFFFFFF
44

55
1) Failure:
66
combined::1 [file:42]
@@ -51,3 +51,13 @@ combined::failf [file:42]
5151
Test failed.
5252
some reason: foo
5353

54+
11) Failure:
55+
combined::compare_i [file:42]
56+
Expected comparison to hold: two < 1
57+
2 >= 1
58+
59+
12) Failure:
60+
combined::compare_u [file:42]
61+
Expected comparison to hold: two < 1
62+
2 >= 1
63+

test/selftest.c

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

299299
void test_selftest__without_arguments(void)
300300
{
301-
cl_invoke(assert_output("combined", "without_arguments", 10, NULL));
301+
cl_invoke(assert_output("combined", "without_arguments", 12, NULL));
302302
}
303303

304304
void test_selftest__specific_test(void)
@@ -313,12 +313,12 @@ void test_selftest__stop_on_failure(void)
313313

314314
void test_selftest__quiet(void)
315315
{
316-
cl_invoke(assert_output("combined", "quiet", 10, "-q", NULL));
316+
cl_invoke(assert_output("combined", "quiet", 12, "-q", NULL));
317317
}
318318

319319
void test_selftest__tap(void)
320320
{
321-
cl_invoke(assert_output("combined", "tap", 10, "-t", NULL));
321+
cl_invoke(assert_output("combined", "tap", 12, "-t", NULL));
322322
}
323323

324324
void test_selftest__suite_names(void)
@@ -329,15 +329,15 @@ void test_selftest__suite_names(void)
329329
void test_selftest__summary_without_filename(void)
330330
{
331331
struct stat st;
332-
cl_invoke(assert_output("combined", "summary_without_filename", 10, "-r", NULL));
332+
cl_invoke(assert_output("combined", "summary_without_filename", 12, "-r", NULL));
333333
/* The summary contains timestamps, so we cannot verify its contents. */
334334
cl_must_pass(stat("summary.xml", &st));
335335
}
336336

337337
void test_selftest__summary_with_filename(void)
338338
{
339339
struct stat st;
340-
cl_invoke(assert_output("combined", "summary_with_filename", 10, "-rdifferent.xml", NULL));
340+
cl_invoke(assert_output("combined", "summary_with_filename", 12, "-rdifferent.xml", NULL));
341341
/* The summary contains timestamps, so we cannot verify its contents. */
342342
cl_must_pass(stat("different.xml", &st));
343343
}

test/suites/combined.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,34 @@ void test_combined__failf(void)
8888
{
8989
cl_failf("some reason: %s", "foo");
9090
}
91+
92+
void test_combined__compare_i(void)
93+
{
94+
int one = 1, two = 2;
95+
96+
cl_assert_equal_i(one, 1);
97+
cl_assert_eq_i(one, 1);
98+
cl_assert_lt_i(one, 2);
99+
cl_assert_le_i(one, 2);
100+
cl_assert_le_i(two, 2);
101+
cl_assert_gt_i(two, 1);
102+
cl_assert_ge_i(two, 2);
103+
cl_assert_ge_i(3, two);
104+
105+
cl_assert_lt_i(two, 1); /* this one fails */
106+
}
107+
108+
void test_combined__compare_u(void)
109+
{
110+
unsigned one = 1, two = 2;
111+
112+
cl_assert_eq_u(one, 1);
113+
cl_assert_lt_u(one, 2);
114+
cl_assert_le_u(one, 2);
115+
cl_assert_le_u(two, 2);
116+
cl_assert_gt_u(two, 1);
117+
cl_assert_ge_u(two, 2);
118+
cl_assert_ge_u(3, two);
119+
120+
cl_assert_lt_u(two, 1); /* this one fails */
121+
}

0 commit comments

Comments
 (0)