• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

thetic / mutiny / 24021969425

06 Apr 2026 06:44AM UTC coverage: 98.873% (-0.1%) from 98.971%
24021969425

Pull #40

github

web-flow
Merge bd22c7a9c into b70bdccaa
Pull Request #40: WIP: generalize CHECK_EQUAL

21 of 22 new or added lines in 6 files covered. (95.45%)

4 existing lines in 2 files now uncovered.

5525 of 5588 relevant lines covered (98.87%)

3427.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.0
/include/mutiny/test/Shell.hpp
1
/**
2
 * @file
3
 * @brief Assertion macros and the Shell base class.
4
 *
5
 * This header is the primary source of assertion macros (@ref CHECK, @ref
6
 * CHECK_EQUAL, @ref STRCMP_EQUAL, @ref DOUBLES_EQUAL, etc.) and the @ref
7
 * mu::tiny::test::Shell class that backs them. It is included transitively by
8
 * @ref mutiny/test.hpp; you rarely need to include it directly.
9
 */
10

11
#ifndef INCLUDED_MUTINY_TEST_SHELL_HPP
12
#define INCLUDED_MUTINY_TEST_SHELL_HPP
13

14
#include "mutiny/test/Terminator.hpp"
15

16
#include "mutiny/String.hpp"
17
#include "mutiny/export.h"
18
#include "mutiny/features.hpp"
19

20
#include <stdint.h>
21

22
namespace mu {
23
namespace tiny {
24
namespace test {
25

26
class Result;
27
class Plugin;
28
class Failure;
29
class Filter;
30

31
/**
32
 * @brief Returns true if @p d1 and @p d2 differ by at most @p threshold.
33
 *
34
 * Used by the @ref DOUBLES_EQUAL macro. Handles NaN correctly: if either
35
 * operand is NaN the function returns false.
36
 *
37
 * @param d1         First value.
38
 * @param d2         Second value.
39
 * @param threshold  Maximum allowed absolute difference (must be >= 0).
40
 * @return true if |d1 - d2| <= threshold.
41
 */
42
MUTINY_EXPORT bool doubles_equal(double d1, double d2, double threshold);
43

44
/**
45
 * @brief Shell for a single test — tracks metadata and drives execution.
46
 *
47
 * Each @ref TEST() macro instantiates a Shell subclass and registers it with
48
 * the @ref Registry at static initialisation time. The test runner then calls
49
 * @ref run_one_test() for each registered shell.
50
 *
51
 * Users interact with Shell primarily through the assertion macros
52
 * (@ref CHECK, @ref CHECK_EQUAL, @ref FAIL, etc.), which forward to the
53
 * assert_* virtual methods. This design allows the testing framework's own
54
 * tests to substitute a mock shell and verify assertion behaviour.
55
 *
56
 * The static methods control global runner state: the currently running test,
57
 * the active @ref Terminator, and crash-on-failure mode.
58
 */
59
class MUTINY_EXPORT Shell
60
{
61
public:
62
  /** @return The Shell currently executing, or nullptr between tests. */
63
  static Shell* get_current();
64

65
  /** @return The active test terminator (throws FailedException by default). */
66
  static const Terminator& get_current_test_terminator();
67

68
  /**
69
   * @return The active test terminator that never throws (used internally
70
   *         when exception support is disabled or inside CHECK_THROWS).
71
   */
72
  static const Terminator& get_current_test_terminator_without_exceptions();
73

74
  /**
75
   * @brief Make the process crash (SIGABRT) instead of throwing on failure.
76
   *
77
   * Useful when running under a debugger: the crash drops you straight into
78
   * the failing assertion with the full call stack.
79
   */
80
  static void set_crash_on_fail();
81

82
  /** @brief Restore the default (exception-based) Terminator. */
83
  static void restore_default_test_terminator();
84

85
  /**
86
   * @brief Control whether CHECK_THROWS re-throws unexpected exceptions.
87
   *
88
   * When @p rethrow_exceptions is true, an exception of the wrong type caught
89
   * inside CHECK_THROWS is re-thrown rather than recorded as a failure. This
90
   * makes it easier to diagnose unexpected crashes during test development.
91
   *
92
   * @param rethrow_exceptions  true to propagate unexpected exceptions.
93
   */
94
  static void set_rethrow_exceptions(bool rethrow_exceptions);
95

96
  /** @return true if unexpected exceptions are currently re-thrown. */
97
  static bool is_rethrowing_exceptions();
98

99
public:
100
  /**
101
   * @brief Construct a Shell with source-location metadata.
102
   *
103
   * Called by the @ref TEST() macro; users do not construct Shells directly.
104
   *
105
   * @param group_name   Name of the test group.
106
   * @param test_name    Name of the individual test.
107
   * @param file_name    Source file path (__FILE__).
108
   * @param line_number  Source line number (__LINE__).
109
   */
110
  Shell(
111
      const char* group_name,
112
      const char* test_name,
113
      const char* file_name,
114
      size_t line_number
115
  ) noexcept;
116
  virtual ~Shell() = default;
32,276✔
117

118
  /** @brief Append @p test to this shell's linked list. @return @p test. */
119
  virtual Shell* add_test(Shell* test);
120
  /** @return The next Shell in the registered list, or nullptr. */
121
  virtual Shell* get_next() const;
122
  /** @return Total number of test shells reachable from this node. */
123
  virtual size_t count_tests();
124

125
  /**
126
   * @brief Decide whether this test should run given the active filters.
127
   *
128
   * @param group_filters  Filters applied to the group name (may be nullptr).
129
   * @param name_filters   Filters applied to the test name (may be nullptr).
130
   * @return true if the test should be executed.
131
   */
132
  bool should_run(
133
      const Filter* group_filters,
134
      const Filter* name_filters
135
  ) const;
136
  /** @return The test name string. */
137
  const char* get_name() const;
138
  /** @return The group name string. */
139
  const char* get_group() const;
140
  /** @return A formatted "group::name" string for display. */
141
  virtual String get_formatted_name() const;
142
  /** @return The source file path for this test. */
143
  const char* get_file() const;
144
  /** @return The source line number for this test. */
145
  size_t get_line_number() const;
146
  /** @return true if the test will actually run (not ignored). */
147
  virtual bool will_run() const;
148
  /** @return true if the test has recorded at least one failure. */
149
  virtual bool has_failed() const;
150
  /** @return true if this is an ordered test. */
151
  virtual bool is_ordered() const;
152
  /** @brief Increment the assertion-check count in the active Result. */
153
  void count_check();
154

155
  /** @brief Macro backend: assert @p condition is true. */
156
  virtual void assert_true(
157
      bool condition,
158
      const char* check_string,
159
      const char* condition_string,
160
      const char* text,
161
      const char* file_name,
162
      size_t line_number,
163
      const Terminator& test_terminator = get_current_test_terminator()
164
  );
165
  /** @brief Macro backend: assert two C strings are equal (strcmp). */
166
  virtual void assert_cstr_equal(
167
      const char* expected,
168
      const char* actual,
169
      const char* text,
170
      const char* file_name,
171
      size_t line_number,
172
      const Terminator& test_terminator = get_current_test_terminator()
173
  );
174
  /** @brief Macro backend: assert first @p length bytes of two C strings match.
175
   */
176
  virtual void assert_cstr_n_equal(
177
      const char* expected,
178
      const char* actual,
179
      size_t length,
180
      const char* text,
181
      const char* file_name,
182
      size_t line_number,
183
      const Terminator& test_terminator = get_current_test_terminator()
184
  );
185
  /** @brief Macro backend: assert @p actual contains the substring @p expected.
186
   */
187
  virtual void assert_cstr_contains(
188
      const char* expected,
189
      const char* actual,
190
      const char* text,
191
      const char* file_name,
192
      size_t line_number
193
  );
194
  /**
195
   * @brief C-interface backend: assert two signed integer values are equal.
196
   *
197
   * Both operands have been widened to @c long @c long (the same range as
198
   * @c intmax_t on all supported platforms) by the caller.
199
   */
200
  virtual void assert_intmax_equal(
201
      intmax_t expected,
202
      intmax_t actual,
203
      const char* text,
204
      const char* file_name,
205
      size_t line_number,
206
      const Terminator& test_terminator = get_current_test_terminator()
207
  );
208
  /**
209
   * @brief C-interface backend: assert two unsigned integer values are equal.
210
   *
211
   * Both operands have been widened to @c unsigned @c long @c long (the same
212
   * range as @c uintmax_t on all supported platforms) by the caller.
213
   */
214
  virtual void assert_uintmax_equal(
215
      uintmax_t expected,
216
      uintmax_t actual,
217
      const char* text,
218
      const char* file_name,
219
      size_t line_number,
220
      const Terminator& test_terminator = get_current_test_terminator()
221
  );
222
  /** @brief Macro backend: assert two pointers are equal. */
223
  virtual void assert_pointers_equal(
224
      const void* expected,
225
      const void* actual,
226
      const char* text,
227
      const char* file_name,
228
      size_t line_number,
229
      const Terminator& test_terminator = get_current_test_terminator()
230
  );
231
  /** @brief Macro backend: assert two function pointers are equal. */
232
  virtual void assert_function_pointers_equal(
233
      void (*expected)(),
234
      void (*actual)(),
235
      const char* text,
236
      const char* file_name,
237
      size_t line_number,
238
      const Terminator& test_terminator = get_current_test_terminator()
239
  );
240
  /** @brief Macro backend: assert two doubles are equal within @p threshold. */
241
  virtual void assert_doubles_equal(
242
      double expected,
243
      double actual,
244
      double threshold,
245
      const char* text,
246
      const char* file_name,
247
      size_t line_number,
248
      const Terminator& test_terminator = get_current_test_terminator()
249
  );
250
  /** @brief Macro backend: generic equality failure with pre-formatted strings.
251
   */
252
  virtual void assert_equals(
253
      bool failed,
254
      const char* expected,
255
      const char* actual,
256
      const char* text,
257
      const char* file,
258
      size_t line_number,
259
      const Terminator& test_terminator = get_current_test_terminator()
260
  );
261
  /** @brief Macro backend: generic equality failure with String arguments. */
262
  virtual void assert_equals(
263
      bool failed,
264
      String expected,
265
      String actual,
266
      const char* text,
267
      const char* file,
268
      size_t line_number,
269
      const Terminator& test_terminator = get_current_test_terminator()
270
  );
271
  /** @brief Macro backend: assert @p length bytes of two memory regions match.
272
   */
273
  virtual void assert_binary_equal(
274
      const void* expected,
275
      const void* actual,
276
      size_t length,
277
      const char* text,
278
      const char* file_name,
279
      size_t line_number,
280
      const Terminator& test_terminator = get_current_test_terminator()
281
  );
282
  /** @brief Macro backend: assert a relational comparison holds. */
283
  virtual void assert_compare(
284
      bool comparison,
285
      const char* check_string,
286
      const char* comparison_string,
287
      const char* text,
288
      const char* file_name,
289
      size_t line_number,
290
      const Terminator& test_terminator = get_current_test_terminator()
291
  );
292
  /**
293
   * @brief Unconditionally fail the test with a message.
294
   *
295
   * @param text        Human-readable failure reason.
296
   * @param file_name   Source file (__FILE__).
297
   * @param line_number Source line (__LINE__).
298
   * @param test_terminator  Controls how the test is aborted after the failure.
299
   */
300
  virtual void fail(
301
      const char* text,
302
      const char* file_name,
303
      size_t line_number,
304
      const Terminator& test_terminator = get_current_test_terminator()
305
  );
306
  /**
307
   * @brief Exit the test body immediately without marking it as failed.
308
   *
309
   * @param test_terminator  Controls how control leaves the test body.
310
   */
311
  virtual void exit_test(
312
      const Terminator& test_terminator = get_current_test_terminator()
313
  );
314

315
  /** @brief Print a message only when verbose output is active. */
316
  virtual void print_very_verbose(const char* text);
317

318
  /** @brief Update the source file stored in this shell (used by ordered
319
   * tests). */
320
  void set_file_name(const char* file_name);
321
  /** @brief Update the source line stored in this shell. */
322
  void set_line_number(size_t line_number);
323
  /** @brief Update the group name stored in this shell. */
324
  void set_group_name(const char* group_name);
325
  /** @brief Update the test name stored in this shell. */
326
  void set_test_name(const char* test_name);
327

328
  /** @brief Invoke the registered crash function (default: abort). */
329
  static void crash();
330
  /**
331
   * @brief Replace the function called by crash().
332
   *
333
   * @param crashme  Function to call when a crash is requested; must not
334
   * return.
335
   */
336
  static void set_crash_method(void (*crashme)());
337
  /** @brief Restore the default crash function. */
338
  static void reset_crash_method();
339

340
  /** @brief Mark this test to run even though it is an ignored test. */
341
  virtual void set_run_ignored();
342

343
  /** @brief Allocate the Test object for this shell's test group. */
344
  virtual class Test* create_test();
345
  /** @brief Destroy a Test object previously returned by create_test(). */
346
  virtual void destroy_test(class Test* test);
347

348
  /** @brief Run this test (create → setup → body → teardown → destroy). */
349
  virtual void run_one_test(Plugin* plugin, Result& result);
350
  /** @brief Run this test in the current process (no forking). */
351
  virtual void run_one_test_in_current_process(Plugin* plugin, Result& result);
352

353
  /** @brief Record a test failure into the active Result. */
354
  virtual void add_failure(const Failure& failure);
355
  /**
356
   * @brief Attach a key/value property to this test.
357
   *
358
   * Properties appear in JUnit XML output. Prefer the @ref TEST_PROPERTY macro.
359
   *
360
   * @param name   Property name.
361
   * @param value  Property value.
362
   */
363
  virtual void add_test_property(const char* name, const char* value);
364

365
protected:
366
  /** @brief Default constructor for use by subclasses (e.g. IgnoredShell).
367
   */
368
  Shell() noexcept;
369

370
  /** @return The macro keyword used in formatted output (e.g. "TEST"). */
371
  virtual String get_macro_name() const;
372
  /** @return The Result for the current run. */
373
  Result* get_test_result();
374

375
private:
376
  const char* group_;
377
  const char* name_;
378
  const char* file_;
379
  size_t line_number_;
380
  Shell* next_;
381
  bool has_failed_;
382

383
  void set_test_result(Result* result);
384
  void set_current_test(Shell* test);
385
  bool match(const char* target, const Filter* filters) const;
386

387
  static Shell* current_test_;
388
  static Result* test_result_;
389

390
  static const Terminator* current_test_terminator_;
391
  static const Terminator* current_test_terminator_without_exceptions_;
392
  static bool rethrow_exceptions_;
393
};
394

395
/**
396
 * @brief Thrown (or longjmp'd) when a test assertion fails.
397
 *
398
 * The test runner catches this to terminate the current test body while still
399
 * allowing teardown() to run. Users rarely interact with this class directly;
400
 * it is part of the Terminator mechanism.
401
 */
402
class FailedException
403
{
404
public:
405
  int dummy;
406
};
407

408
/**
409
 * @brief Implementation helper for CHECK_EQUAL.
410
 *
411
 * Uses the conditional expression's arithmetic-conversion rules to select a
412
 * common comparison type, avoiding signed/unsigned mismatch warnings. Prefer
413
 * the CHECK_EQUAL macro.
414
 *
415
 * @param expected  Expected value.
416
 * @param actual    Actual value.
417
 * @param text      Optional failure message.
418
 * @param file      Source file path.
419
 * @param line      Source line number.
420
 */
421
template<typename T, typename U>
422
void check_equal(
531✔
423
    const T& expected,
424
    const U& actual,
425
    const char* text,
426
    const char* file,
427
    size_t line
428
)
429
{
430
  // Compare with the natural types so that mixed signed/unsigned comparisons
431
  // produce the same compiler diagnostic they would outside the macro.
432
  if (expected != actual) {
531✔
433
    Shell::get_current()->assert_equals(
20✔
434
        true,
435
        string_from(expected).c_str(),
436
        string_from(actual).c_str(),
437
        text,
438
        file,
439
        line
440
    );
441
  } else {
442
    Shell::get_current()->count_check();
521✔
443
  }
444
}
521✔
445

446
/**
447
 * @brief Implementation helper for CHECK_COMPARE.
448
 *
449
 * The boolean result of the relational operation is pre-evaluated at the
450
 * call site (inside the macro), so @p first and @p second are used only for
451
 * display via string_from(). Prefer the CHECK_COMPARE macro.
452
 *
453
 * @param first      Left-hand operand (display only).
454
 * @param second     Right-hand operand (display only).
455
 * @param success    Pre-evaluated result of @p first relop @p second.
456
 * @param relop_str  String representation of the relational operator.
457
 * @param text       Optional failure message.
458
 * @param file       Source file path.
459
 * @param line       Source line number.
460
 */
461
template<typename T, typename U>
462
void check_compare(
2✔
463
    const T& first,
464
    const U& second,
465
    bool success,
466
    const char* relop_str,
467
    const char* text,
468
    const char* file,
469
    size_t line
470
)
471
{
472
  // The bool result of the relop is pre-computed at the call site (in the
473
  // macro), so no sign-compare issue here. The parameters first/second are
474
  // used only for string_from(), not compared against each other.
475
  if (!success) {
2✔
476
    String condition;
2✔
477
    condition += string_from(first);
2✔
478
    condition += " ";
2✔
479
    condition += relop_str;
2✔
480
    condition += " ";
2✔
481
    condition += string_from(second);
2✔
482
    Shell::get_current()->assert_compare(
2✔
483
        false, "CHECK_COMPARE", condition.c_str(), text, file, line
484
    );
485
  } else {
2✔
UNCOV
486
    Shell::get_current()->count_check();
×
487
  }
UNCOV
488
}
×
489

490
/**
491
 * @brief Implementation helper for ENUMS_EQUAL_TYPE.
492
 *
493
 * Casts both enum values to @p UNDERLYING_TYPE before comparing and
494
 * formatting. Prefer the ENUMS_EQUAL_TYPE or ENUMS_EQUAL_INT macros.
495
 *
496
 * @tparam UNDERLYING_TYPE  Integer type used for display (e.g. @c int).
497
 * @tparam ENUM_TYPE        Enum type being compared.
498
 * @param expected          Expected enum value.
499
 * @param actual            Actual enum value.
500
 * @param text              Optional failure message.
501
 * @param file              Source file path.
502
 * @param line              Source line number.
503
 */
504
template<typename UNDERLYING_TYPE, typename ENUM_TYPE>
505
void check_enum_equal(
9✔
506
    ENUM_TYPE expected,
507
    ENUM_TYPE actual,
508
    const char* text,
509
    const char* file,
510
    size_t line
511
)
512
{
513
  auto e = static_cast<UNDERLYING_TYPE>(expected);
9✔
514
  auto a = static_cast<UNDERLYING_TYPE>(actual);
9✔
515
  if (e != a) {
9✔
516
    Shell::get_current()->assert_equals(
12✔
517
        true, string_from(e).c_str(), string_from(a).c_str(), text, file, line
518
    );
519
  } else {
520
    Shell::get_current()->count_check();
3✔
521
  }
522
}
3✔
523

524
} // namespace test
525
} // namespace tiny
526
} // namespace mu
527

528
// Different checking macros
529

530
/**
531
 * @brief Fail the current test if @p condition is false.
532
 *
533
 * Equivalent to CHECK_TRUE. Prefer CHECK when the condition is a plain
534
 * boolean; use CHECK_TRUE_TEXT when you also want to supply a message.
535
 *
536
 * @param condition  Any expression convertible to bool.
537
 *
538
 * See also @ref CHECK_TEXT, @ref CHECK_TRUE, and @ref CHECK_FALSE.
539
 */
540
#define CHECK(condition)                                                       \
541
  CHECK_TRUE_LOCATION(condition, "CHECK", #condition, "", __FILE__, __LINE__)
542

543
/** @brief @ref CHECK with a custom failure message. */
544
#define CHECK_TEXT(condition, text)                                            \
545
  CHECK_TRUE_LOCATION(                                                         \
546
      static_cast<bool>(condition),                                            \
547
      "CHECK",                                                                 \
548
      #condition,                                                              \
549
      text,                                                                    \
550
      __FILE__,                                                                \
551
      __LINE__                                                                 \
552
  )
553

554
/** @brief Fail if @p condition is false (explicit TRUE variant of @ref CHECK).
555
 */
556
#define CHECK_TRUE(condition)                                                  \
557
  CHECK_TRUE_LOCATION(                                                         \
558
      static_cast<bool>(condition),                                            \
559
      "CHECK_TRUE",                                                            \
560
      #condition,                                                              \
561
      "",                                                                      \
562
      __FILE__,                                                                \
563
      __LINE__                                                                 \
564
  )
565

566
/** @brief CHECK_TRUE with a custom failure message. @see CHECK_TRUE */
567
#define CHECK_TRUE_TEXT(condition, text)                                       \
568
  CHECK_TRUE_LOCATION(                                                         \
569
      condition, "CHECK_TRUE", #condition, text, __FILE__, __LINE__            \
570
  )
571

572
/** @brief Fail if @p condition is true. @see CHECK_FALSE_TEXT */
573
#define CHECK_FALSE(condition)                                                 \
574
  CHECK_FALSE_LOCATION(                                                        \
575
      condition, "CHECK_FALSE", #condition, "", __FILE__, __LINE__             \
576
  )
577

578
/** @brief CHECK_FALSE with a custom failure message. @see CHECK_FALSE */
579
#define CHECK_FALSE_TEXT(condition, text)                                      \
580
  CHECK_FALSE_LOCATION(                                                        \
581
      condition, "CHECK_FALSE", #condition, text, __FILE__, __LINE__           \
582
  )
583

584
/** @brief Location-explicit variant of CHECK. Prefer CHECK in test code. */
585
#define CHECK_TRUE_LOCATION(                                                   \
586
    condition, checkString, conditionString, text, file, line                  \
587
)                                                                              \
588
  do {                                                                         \
589
    mu::tiny::test::Shell::get_current()->assert_true(                         \
590
        (condition), checkString, conditionString, text, file, line            \
591
    );                                                                         \
592
  } while (0)
593

594
/** @brief Location-explicit variant of CHECK_FALSE. Prefer CHECK_FALSE in test
595
 * code. */
596
#define CHECK_FALSE_LOCATION(                                                  \
597
    condition, checkString, conditionString, text, file, line                  \
598
)                                                                              \
599
  do {                                                                         \
600
    mu::tiny::test::Shell::get_current()->assert_true(                         \
601
        !(condition), checkString, conditionString, text, file, line           \
602
    );                                                                         \
603
  } while (0)
604

605
/**
606
 * @brief Fail if @p expected != @p actual.
607
 *
608
 * Requires operator!=() and a string_from() overload for the operand types.
609
 * Uses common-type arithmetic conversion so signed/unsigned pairs are
610
 * promoted consistently without -Wsign-compare warnings.
611
 *
612
 * @param expected  Expected value.
613
 * @param actual    Actual value.
614
 */
615
#define CHECK_EQUAL(expected, actual)                                          \
616
  CHECK_EQUAL_LOCATION(expected, actual, "", __FILE__, __LINE__)
617

618
/** @brief CHECK_EQUAL with a custom failure message. @see CHECK_EQUAL */
619
#define CHECK_EQUAL_TEXT(expected, actual, text)                               \
620
  CHECK_EQUAL_LOCATION(expected, actual, text, __FILE__, __LINE__)
621

622
/** @brief Location-explicit variant of CHECK_EQUAL. Prefer CHECK_EQUAL in test
623
 * code. */
624
#define CHECK_EQUAL_LOCATION(expected, actual, text, file, line)               \
625
  mu::tiny::test::check_equal((expected), (actual), text, file, line)
626

627
/** @brief Fail if @p actual != 0. Equivalent to CHECK_EQUAL(0, actual). */
628
#define CHECK_EQUAL_ZERO(actual) CHECK_EQUAL(0, (actual))
629

630
/** @brief CHECK_EQUAL_ZERO with a custom failure message. @see CHECK_EQUAL_ZERO
631
 */
632
#define CHECK_EQUAL_ZERO_TEXT(actual, text)                                    \
633
  CHECK_EQUAL_TEXT(0, (actual), (text))
634

635
/**
636
 * @brief Fail if @p first relop @p second is false.
637
 *
638
 * @p relop must be a relational operator token: @c <, @c <=, @c >, @c >=,
639
 * @c ==, or @c !=.
640
 *
641
 * @code{.cpp}
642
 * CHECK_COMPARE(a, <, b);   // fails if a >= b
643
 * @endcode
644
 *
645
 * @param first   Left-hand operand.
646
 * @param relop   Relational operator.
647
 * @param second  Right-hand operand.
648
 *
649
 * @see CHECK_COMPARE_TEXT
650
 */
651
#define CHECK_COMPARE(first, relop, second)                                    \
652
  CHECK_COMPARE_TEXT(first, relop, second, "")
653

654
/** @brief CHECK_COMPARE with a custom failure message. @see CHECK_COMPARE */
655
#define CHECK_COMPARE_TEXT(first, relop, second, text)                         \
656
  CHECK_COMPARE_LOCATION(first, relop, second, text, __FILE__, __LINE__)
657

658
/** @brief Location-explicit variant of CHECK_COMPARE. Prefer CHECK_COMPARE in
659
 * test code. */
660
#define CHECK_COMPARE_LOCATION(first, relop, second, text, file, line)         \
661
  mu::tiny::test::check_compare(                                               \
662
      (first), (second), (first)relop(second), #relop, text, file, line        \
663
  )
664

665
/**
666
 * @brief Fail if the C strings @p expected and @p actual differ (strcmp).
667
 *
668
 * Use this instead of CHECK_EQUAL for @c const char* values — CHECK_EQUAL
669
 * compares the pointer addresses, not the string content.
670
 *
671
 * @param expected  Expected C string (may be nullptr).
672
 * @param actual    Actual C string (may be nullptr).
673
 *
674
 * @see STRCMP_EQUAL_TEXT, STRNCMP_EQUAL, STRCMP_CONTAINS
675
 */
676
#define STRCMP_EQUAL(expected, actual)                                         \
677
  STRCMP_EQUAL_LOCATION(expected, actual, "", __FILE__, __LINE__)
678

679
/** @brief STRCMP_EQUAL with a custom failure message. @see STRCMP_EQUAL */
680
#define STRCMP_EQUAL_TEXT(expected, actual, text)                              \
681
  STRCMP_EQUAL_LOCATION(expected, actual, text, __FILE__, __LINE__)
682

683
/** @brief Location-explicit variant of STRCMP_EQUAL. Prefer STRCMP_EQUAL in
684
 * test code. */
685
#define STRCMP_EQUAL_LOCATION(expected, actual, text, file, line)              \
686
  do {                                                                         \
687
    mu::tiny::test::Shell::get_current()->assert_cstr_equal(                   \
688
        expected, actual, text, file, line                                     \
689
    );                                                                         \
690
  } while (0)
691

692
/**
693
 * @brief Fail if the first @p length characters of @p expected and @p actual
694
 * differ.
695
 *
696
 * @param expected  Expected C string prefix.
697
 * @param actual    Actual C string.
698
 * @param length    Number of characters to compare.
699
 *
700
 * @see STRNCMP_EQUAL_TEXT
701
 */
702
#define STRNCMP_EQUAL(expected, actual, length)                                \
703
  STRNCMP_EQUAL_LOCATION(expected, actual, length, "", __FILE__, __LINE__)
704

705
/** @brief STRNCMP_EQUAL with a custom failure message. @see STRNCMP_EQUAL */
706
#define STRNCMP_EQUAL_TEXT(expected, actual, length, text)                     \
707
  STRNCMP_EQUAL_LOCATION(expected, actual, length, text, __FILE__, __LINE__)
708

709
/** @brief Location-explicit variant of STRNCMP_EQUAL. */
710
#define STRNCMP_EQUAL_LOCATION(expected, actual, length, text, file, line)     \
711
  do {                                                                         \
712
    mu::tiny::test::Shell::get_current()->assert_cstr_n_equal(                 \
713
        expected, actual, length, text, file, line                             \
714
    );                                                                         \
715
  } while (0)
716

717
/**
718
 * @brief Fail if @p actual does not contain the substring @p expected.
719
 *
720
 * @param expected  Substring that must be present.
721
 * @param actual    String to search within.
722
 *
723
 * @see STRCMP_CONTAINS_TEXT
724
 */
725
#define STRCMP_CONTAINS(expected, actual)                                      \
726
  STRCMP_CONTAINS_LOCATION(expected, actual, "", __FILE__, __LINE__)
727

728
/** @brief STRCMP_CONTAINS with a custom failure message. @see STRCMP_CONTAINS
729
 */
730
#define STRCMP_CONTAINS_TEXT(expected, actual, text)                           \
731
  STRCMP_CONTAINS_LOCATION(expected, actual, text, __FILE__, __LINE__)
732

733
/** @brief Location-explicit variant of STRCMP_CONTAINS. */
734
#define STRCMP_CONTAINS_LOCATION(expected, actual, text, file, line)           \
735
  do {                                                                         \
736
    mu::tiny::test::Shell::get_current()->assert_cstr_contains(                \
737
        expected, actual, text, file, line                                     \
738
    );                                                                         \
739
  } while (0)
740

741
/**
742
 * @brief Fail if pointer values @p expected and @p actual differ.
743
 *
744
 * Both operands are cast to @c const void* before comparison, so pointers to
745
 * any object type (including nullptr) can be compared. Raw integer values are
746
 * not accepted; use reinterpret_cast first if needed.
747
 *
748
 * @param expected  Expected pointer.
749
 * @param actual    Actual pointer.
750
 *
751
 * @see POINTERS_EQUAL_TEXT, FUNCTIONPOINTERS_EQUAL
752
 */
753
#define POINTERS_EQUAL(expected, actual)                                       \
754
  POINTERS_EQUAL_LOCATION((expected), (actual), "", __FILE__, __LINE__)
755

756
/** @brief POINTERS_EQUAL with a custom failure message. @see POINTERS_EQUAL */
757
#define POINTERS_EQUAL_TEXT(expected, actual, text)                            \
758
  POINTERS_EQUAL_LOCATION((expected), (actual), text, __FILE__, __LINE__)
759

760
/** @brief Location-explicit variant of POINTERS_EQUAL. */
761
#define POINTERS_EQUAL_LOCATION(expected, actual, text, file, line)            \
762
  do {                                                                         \
763
    mu::tiny::test::Shell::get_current()->assert_pointers_equal(               \
764
        static_cast<const void*>(expected),                                    \
765
        static_cast<const void*>(actual),                                      \
766
        text,                                                                  \
767
        file,                                                                  \
768
        line                                                                   \
769
    );                                                                         \
770
  } while (0)
771

772
/**
773
 * @brief Fail if function pointer values @p expected and @p actual differ.
774
 *
775
 * Both operands are cast to @c void(*)() before comparison. Pass
776
 * @c static_cast<void(*)()>(nullptr) to compare against a null function
777
 * pointer.
778
 *
779
 * @param expected  Expected function pointer.
780
 * @param actual    Actual function pointer.
781
 *
782
 * @see FUNCTIONPOINTERS_EQUAL_TEXT
783
 */
784
#define FUNCTIONPOINTERS_EQUAL(expected, actual)                               \
785
  FUNCTIONPOINTERS_EQUAL_LOCATION((expected), (actual), "", __FILE__, __LINE__)
786

787
/** @brief FUNCTIONPOINTERS_EQUAL with a custom failure message. @see
788
 * FUNCTIONPOINTERS_EQUAL */
789
#define FUNCTIONPOINTERS_EQUAL_TEXT(expected, actual, text)                    \
790
  FUNCTIONPOINTERS_EQUAL_LOCATION(                                             \
791
      (expected), (actual), text, __FILE__, __LINE__                           \
792
  )
793

794
/** @brief Location-explicit variant of FUNCTIONPOINTERS_EQUAL. */
795
#define FUNCTIONPOINTERS_EQUAL_LOCATION(expected, actual, text, file, line)    \
796
  do {                                                                         \
797
    mu::tiny::test::Shell::get_current()->assert_function_pointers_equal(      \
798
        reinterpret_cast<void (*)()>(expected),                                \
799
        reinterpret_cast<void (*)()>(actual),                                  \
800
        text,                                                                  \
801
        file,                                                                  \
802
        line                                                                   \
803
    );                                                                         \
804
  } while (0)
805

806
/**
807
 * @brief Fail if @p expected and @p actual differ by more than @p threshold.
808
 *
809
 * Handles NaN correctly: a NaN operand always fails the check.
810
 *
811
 * @param expected   Expected double value.
812
 * @param actual     Actual double value.
813
 * @param threshold  Maximum allowed absolute difference (must be >= 0).
814
 *
815
 * @see DOUBLES_EQUAL_TEXT, doubles_equal
816
 */
817
#define DOUBLES_EQUAL(expected, actual, threshold)                             \
818
  DOUBLES_EQUAL_LOCATION(expected, actual, threshold, "", __FILE__, __LINE__)
819

820
/** @brief DOUBLES_EQUAL with a custom failure message. @see DOUBLES_EQUAL */
821
#define DOUBLES_EQUAL_TEXT(expected, actual, threshold, text)                  \
822
  DOUBLES_EQUAL_LOCATION(expected, actual, threshold, text, __FILE__, __LINE__)
823

824
/** @brief Location-explicit variant of DOUBLES_EQUAL. */
825
#define DOUBLES_EQUAL_LOCATION(expected, actual, threshold, text, file, line)  \
826
  do {                                                                         \
827
    mu::tiny::test::Shell::get_current()->assert_doubles_equal(                \
828
        expected, actual, threshold, text, file, line                          \
829
    );                                                                         \
830
  } while (0)
831

832
/**
833
 * @brief Fail if @p size bytes starting at @p expected and @p actual differ.
834
 *
835
 * @param expected  Pointer to the expected memory region.
836
 * @param actual    Pointer to the actual memory region.
837
 * @param size      Number of bytes to compare.
838
 *
839
 * @see MEMCMP_EQUAL_TEXT
840
 */
841
#define MEMCMP_EQUAL(expected, actual, size)                                   \
842
  MEMCMP_EQUAL_LOCATION(expected, actual, size, "", __FILE__, __LINE__)
843

844
/** @brief MEMCMP_EQUAL with a custom failure message. @see MEMCMP_EQUAL */
845
#define MEMCMP_EQUAL_TEXT(expected, actual, size, text)                        \
846
  MEMCMP_EQUAL_LOCATION(expected, actual, size, text, __FILE__, __LINE__)
847

848
/** @brief Location-explicit variant of MEMCMP_EQUAL. */
849
#define MEMCMP_EQUAL_LOCATION(expected, actual, size, text, file, line)        \
850
  do {                                                                         \
851
    mu::tiny::test::Shell::get_current()->assert_binary_equal(                 \
852
        expected, actual, size, text, file, line                               \
853
    );                                                                         \
854
  } while (0)
855

856
/**
857
 * @brief Fail if two enum values differ, casting both to @c int for display.
858
 *
859
 * @param expected  Expected enum value.
860
 * @param actual    Actual enum value.
861
 *
862
 * @see ENUMS_EQUAL_TYPE
863
 */
864
#define ENUMS_EQUAL_INT(expected, actual)                                      \
865
  ENUMS_EQUAL_TYPE(int, expected, actual)
866

867
/** @brief ENUMS_EQUAL_INT with a custom failure message. @see ENUMS_EQUAL_INT
868
 */
869
#define ENUMS_EQUAL_INT_TEXT(expected, actual, text)                           \
870
  ENUMS_EQUAL_TYPE_TEXT(int, expected, actual, text)
871

872
/**
873
 * @brief Fail if two enum values differ, casting both to @p underlying_type for
874
 * display.
875
 *
876
 * Use this when the enum's underlying type is not @c int (e.g. @c unsigned
877
 * or @c uint8_t) to get meaningful output on failure.
878
 *
879
 * @param underlying_type  Integer type to cast enum values to for display.
880
 * @param expected         Expected enum value.
881
 * @param actual           Actual enum value.
882
 *
883
 * @see ENUMS_EQUAL_INT, ENUMS_EQUAL_TYPE_TEXT
884
 */
885
#define ENUMS_EQUAL_TYPE(underlying_type, expected, actual)                    \
886
  ENUMS_EQUAL_TYPE_LOCATION(                                                   \
887
      underlying_type, expected, actual, "", __FILE__, __LINE__                \
888
  )
889

890
/** @brief ENUMS_EQUAL_TYPE with a custom failure message. @see ENUMS_EQUAL_TYPE
891
 */
892
#define ENUMS_EQUAL_TYPE_TEXT(underlying_type, expected, actual, text)         \
893
  ENUMS_EQUAL_TYPE_LOCATION(                                                   \
894
      underlying_type, expected, actual, text, __FILE__, __LINE__              \
895
  )
896

897
/** @brief Location-explicit variant of ENUMS_EQUAL_TYPE. */
898
#define ENUMS_EQUAL_TYPE_LOCATION(                                             \
899
    underlying_type, expected, actual, text, file, line                        \
900
)                                                                              \
901
  mu::tiny::test::check_enum_equal<underlying_type>(                           \
902
      (expected), (actual), text, file, line                                   \
903
  )
904

905
/**
906
 * @brief Unconditionally fail the current test with a message.
907
 *
908
 * FAIL may already be defined by another library; in that case use FAIL_TEST
909
 * instead. Both macros behave identically.
910
 *
911
 * @param text  Human-readable failure message.
912
 *
913
 * @see FAIL_TEST, FAIL_LOCATION
914
 */
915
#ifndef FAIL
916
#define FAIL(text) FAIL_LOCATION(text, __FILE__, __LINE__)
917

918
/** @brief Location-explicit variant of FAIL. */
919
#define FAIL_LOCATION(text, file, line)                                        \
920
  do {                                                                         \
921
    mu::tiny::test::Shell::get_current()->fail(text, file, line);              \
922
  } while (0)
923
#endif
924

925
/** @brief Unconditionally fail the current test. Use when FAIL is already
926
 * defined. @see FAIL */
927
#define FAIL_TEST(text) FAIL_TEST_LOCATION(text, __FILE__, __LINE__)
928

929
/** @brief Location-explicit variant of FAIL_TEST. */
930
#define FAIL_TEST_LOCATION(text, file, line)                                   \
931
  do {                                                                         \
932
    mu::tiny::test::Shell::get_current()->fail(text, file, line);              \
933
  } while (0)
934

935
/**
936
 * @brief Exit the current test body immediately without marking it as failed.
937
 *
938
 * Useful when a prerequisite check fails and continuing would produce
939
 * confusing cascading failures. Unlike FAIL, the test is counted as passed.
940
 */
941
#define TEST_EXIT                                                              \
942
  do {                                                                         \
943
    mu::tiny::test::Shell::get_current()->exit_test();                         \
944
  } while (0)
945

946
#if MUTINY_HAVE_EXCEPTIONS
947
/**
948
 * @brief Fail if @p expression does not throw an exception of type @p expected.
949
 *
950
 * The test fails if @p expression throws nothing or throws an exception of a
951
 * different type. Only available when exceptions are enabled
952
 * (@ref MUTINY_HAVE_EXCEPTIONS is non-zero).
953
 *
954
 * @param expected    Exception type that must be thrown (not a string).
955
 * @param expression  Expression that should throw.
956
 */
957
#define CHECK_THROWS(expected, expression)                                     \
958
  do {                                                                         \
959
    mu::tiny::String failure_msg(                                              \
960
        "expected to throw " #expected "\nbut threw nothing"                   \
961
    );                                                                         \
962
    bool caught_expected = false;                                              \
963
    try {                                                                      \
964
      (expression);                                                            \
965
    } catch (const expected&) {                                                \
966
      caught_expected = true;                                                  \
967
    } catch (...) {                                                            \
968
      failure_msg =                                                            \
969
          "expected to throw " #expected "\nbut threw a different type";       \
970
    }                                                                          \
971
    if (!caught_expected) {                                                    \
972
      mu::tiny::test::Shell::get_current()->fail(                              \
973
          failure_msg.c_str(), __FILE__, __LINE__                              \
974
      );                                                                       \
975
    } else {                                                                   \
976
      mu::tiny::test::Shell::get_current()->count_check();                     \
977
    }                                                                          \
978
  } while (0)
979
#endif /* MUTINY_HAVE_EXCEPTIONS */
980

981
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc