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

thetic / mutiny / 24377296683

14 Apr 2026 02:14AM UTC coverage: 98.944%. Remained the same
24377296683

Pull #51

github

web-flow
Merge 792e49b09 into 18e0cc9d5
Pull Request #51: Templatize ApproxEqualFailure and assert_approx_equal

13 of 14 new or added lines in 2 files covered. (92.86%)

5530 of 5589 relevant lines covered (98.94%)

3458.07 hits per line

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

97.44
/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 CHECK_APPROX, 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/Failure.hpp"
15
#include "mutiny/test/Terminator.hpp"
16

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

21
#include <stdint.h>
22

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

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

32
/**
33
 * @brief Returns true if @p d1 and @p d2 differ by at most @p threshold.
34
 *
35
 * Used by the @ref CHECK_APPROX macro. Handles NaN and infinity correctly.
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 approx_equal(double d1, double d2, double threshold);
43

44
/**
45
 * @brief approx_equal for non-double arithmetic types.
46
 *
47
 * For floating-point @p T, NaN in any operand returns false. Infinity and
48
 * signed-overflow edge cases are not handled specially; if you need them,
49
 * convert to @c double and call the @c double overload.
50
 *
51
 * For integral @p T the comparison is done entirely in integer arithmetic —
52
 * no conversion to floating-point occurs.
53
 */
54
template<typename T>
55
bool approx_equal(T d1, T d2, T threshold)
22✔
56
{
57
  // d != d is true iff d is NaN (IEEE 754); always false for integral types.
58
  if (d1 != d1 || d2 != d2 || threshold != threshold)
7✔
59
    return false;
3✔
60
  return (d1 >= d2 ? d1 - d2 : d2 - d1) <= threshold;
19✔
61
}
62

63
/**
64
 * @brief Shell for a single test — tracks metadata and drives execution.
65
 *
66
 * Each @ref TEST() macro instantiates a Shell subclass and registers it with
67
 * the @ref Registry at static initialisation time. The test runner then calls
68
 * @ref run_one_test() for each registered shell.
69
 *
70
 * Users interact with Shell primarily through the assertion macros
71
 * (@ref CHECK, @ref CHECK_EQUAL, @ref FAIL, etc.), which forward to the
72
 * assert_* virtual methods. This design allows the testing framework's own
73
 * tests to substitute a mock shell and verify assertion behaviour.
74
 *
75
 * The static methods control global runner state: the currently running test,
76
 * the active @ref Terminator, and crash-on-failure mode.
77
 */
78
class MUTINY_EXPORT Shell
79
{
80
public:
81
  /** @return The Shell currently executing, or nullptr between tests. */
82
  static Shell* get_current();
83

84
  /** @return The active test terminator (throws FailedException by default). */
85
  static const Terminator& get_current_test_terminator();
86

87
  /**
88
   * @return The active test terminator that never throws (used internally
89
   *         when exception support is disabled or inside CHECK_THROWS).
90
   */
91
  static const Terminator& get_current_test_terminator_without_exceptions();
92

93
  /**
94
   * @brief Make the process crash (SIGABRT) instead of throwing on failure.
95
   *
96
   * Useful when running under a debugger: the crash drops you straight into
97
   * the failing assertion with the full call stack.
98
   */
99
  static void set_crash_on_fail();
100

101
  /** @brief Restore the default (exception-based) Terminator. */
102
  static void restore_default_test_terminator();
103

104
  /**
105
   * @brief Control whether CHECK_THROWS re-throws unexpected exceptions.
106
   *
107
   * When @p rethrow_exceptions is true, an exception of the wrong type caught
108
   * inside CHECK_THROWS is re-thrown rather than recorded as a failure. This
109
   * makes it easier to diagnose unexpected crashes during test development.
110
   *
111
   * @param rethrow_exceptions  true to propagate unexpected exceptions.
112
   */
113
  static void set_rethrow_exceptions(bool rethrow_exceptions);
114

115
  /** @return true if unexpected exceptions are currently re-thrown. */
116
  static bool is_rethrowing_exceptions();
117

118
public:
119
  /**
120
   * @brief Construct a Shell with source-location metadata.
121
   *
122
   * Called by the @ref TEST() macro; users do not construct Shells directly.
123
   *
124
   * @param group_name   Name of the test group.
125
   * @param test_name    Name of the individual test.
126
   * @param file_name    Source file path (__FILE__).
127
   * @param line_number  Source line number (__LINE__).
128
   */
129
  Shell(
130
      const char* group_name,
131
      const char* test_name,
132
      const char* file_name,
133
      size_t line_number
134
  ) noexcept;
135
  virtual ~Shell() = default;
32,639✔
136

137
  /** @brief Append @p test to this shell's linked list. @return @p test. */
138
  virtual Shell* add_test(Shell* test);
139
  /** @return The next Shell in the registered list, or nullptr. */
140
  virtual Shell* get_next() const;
141
  /** @return Total number of test shells reachable from this node. */
142
  virtual size_t count_tests();
143

144
  /**
145
   * @brief Decide whether this test should run given the active filters.
146
   *
147
   * @param group_filters  Filters applied to the group name (may be nullptr).
148
   * @param name_filters   Filters applied to the test name (may be nullptr).
149
   * @return true if the test should be executed.
150
   */
151
  bool should_run(
152
      const Filter* group_filters,
153
      const Filter* name_filters
154
  ) const;
155
  /** @return The test name string. */
156
  const char* get_name() const;
157
  /** @return The group name string. */
158
  const char* get_group() const;
159
  /** @return A formatted "group::name" string for display. */
160
  virtual String get_formatted_name() const;
161
  /** @return The source file path for this test. */
162
  const char* get_file() const;
163
  /** @return The source line number for this test. */
164
  size_t get_line_number() const;
165
  /** @return true if the test will actually run (not ignored). */
166
  virtual bool will_run() const;
167
  /** @return true if the test has recorded at least one failure. */
168
  virtual bool has_failed() const;
169
  /** @return true if this is an ordered test. */
170
  virtual bool is_ordered() const;
171
  /** @brief Increment the assertion-check count in the active Result. */
172
  void count_check();
173

174
  /** @brief Macro backend: assert @p condition is true. */
175
  virtual void assert_true(
176
      bool condition,
177
      const char* check_string,
178
      const char* condition_string,
179
      const char* text,
180
      const char* file_name,
181
      size_t line_number,
182
      const Terminator& test_terminator = get_current_test_terminator()
183
  );
184
  /** @brief Macro backend: assert two C strings are equal (strcmp). */
185
  virtual void assert_cstr_equal(
186
      const char* expected,
187
      const char* actual,
188
      const char* text,
189
      const char* file_name,
190
      size_t line_number,
191
      const Terminator& test_terminator = get_current_test_terminator()
192
  );
193
  /** @brief Macro backend: assert first @p length bytes of two C strings match.
194
   */
195
  virtual void assert_cstr_n_equal(
196
      const char* expected,
197
      const char* actual,
198
      size_t length,
199
      const char* text,
200
      const char* file_name,
201
      size_t line_number,
202
      const Terminator& test_terminator = get_current_test_terminator()
203
  );
204
  /** @brief Macro backend: assert @p actual contains the substring @p expected.
205
   */
206
  virtual void assert_cstr_contains(
207
      const char* expected,
208
      const char* actual,
209
      const char* text,
210
      const char* file_name,
211
      size_t line_number
212
  );
213
  /**
214
   * @brief C-interface backend: assert two signed integer values are equal.
215
   *
216
   * Both operands have been widened to @c long @c long (the same range as
217
   * @c intmax_t on all supported platforms) by the caller.
218
   */
219
  virtual void assert_intmax_equal(
220
      intmax_t expected,
221
      intmax_t actual,
222
      const char* text,
223
      const char* file_name,
224
      size_t line_number,
225
      const Terminator& test_terminator = get_current_test_terminator()
226
  );
227
  /**
228
   * @brief C-interface backend: assert two unsigned integer values are equal.
229
   *
230
   * Both operands have been widened to @c unsigned @c long @c long (the same
231
   * range as @c uintmax_t on all supported platforms) by the caller.
232
   */
233
  virtual void assert_uintmax_equal(
234
      uintmax_t expected,
235
      uintmax_t actual,
236
      const char* text,
237
      const char* file_name,
238
      size_t line_number,
239
      const Terminator& test_terminator = get_current_test_terminator()
240
  );
241
  /** @brief Macro backend: assert two pointers are equal. */
242
  virtual void assert_pointers_equal(
243
      const void* expected,
244
      const void* actual,
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
  /**
251
   * @brief Macro backend: assert two values are equal within @p threshold.
252
   *
253
   * @tparam T  Numeric type; must have a @ref mu::tiny::string_from() overload.
254
   */
255
  template<typename T>
256
  void assert_approx_equal(
9✔
257
      T expected,
258
      T actual,
259
      T threshold,
260
      const char* text,
261
      const char* file_name,
262
      size_t line_number,
263
      const Terminator& test_terminator = get_current_test_terminator()
264
  )
265
  {
266
    add_failure(
9✔
267
        ApproxEqualFailure<T>(
18✔
268
            this, file_name, line_number, expected, actual, threshold, text
269
        )
270
    );
271
    test_terminator.exit_current_test();
9✔
NEW
272
  }
×
273
  /** @brief Macro backend: generic equality failure with pre-formatted strings.
274
   */
275
  virtual void assert_equals(
276
      bool failed,
277
      const char* expected,
278
      const char* actual,
279
      const char* text,
280
      const char* file,
281
      size_t line_number,
282
      const Terminator& test_terminator = get_current_test_terminator()
283
  );
284
  /** @brief Macro backend: generic equality failure with String arguments. */
285
  virtual void assert_equals(
286
      bool failed,
287
      String expected,
288
      String actual,
289
      const char* text,
290
      const char* file,
291
      size_t line_number,
292
      const Terminator& test_terminator = get_current_test_terminator()
293
  );
294
  /** @brief Macro backend: assert @p length bytes of two memory regions match.
295
   */
296
  virtual void assert_binary_equal(
297
      const void* expected,
298
      const void* actual,
299
      size_t length,
300
      const char* text,
301
      const char* file_name,
302
      size_t line_number,
303
      const Terminator& test_terminator = get_current_test_terminator()
304
  );
305
  /** @brief Macro backend: assert a relational comparison holds. */
306
  virtual void assert_compare(
307
      bool comparison,
308
      const char* check_string,
309
      const char* comparison_string,
310
      const char* text,
311
      const char* file_name,
312
      size_t line_number,
313
      const Terminator& test_terminator = get_current_test_terminator()
314
  );
315
  /**
316
   * @brief Unconditionally fail the test with a message.
317
   *
318
   * @param text        Human-readable failure reason.
319
   * @param file_name   Source file (__FILE__).
320
   * @param line_number Source line (__LINE__).
321
   * @param test_terminator  Controls how the test is aborted after the failure.
322
   */
323
  virtual void fail(
324
      const char* text,
325
      const char* file_name,
326
      size_t line_number,
327
      const Terminator& test_terminator = get_current_test_terminator()
328
  );
329
  /**
330
   * @brief Exit the test body immediately without marking it as failed.
331
   *
332
   * @param test_terminator  Controls how control leaves the test body.
333
   */
334
  virtual void exit_test(
335
      const Terminator& test_terminator = get_current_test_terminator()
336
  );
337

338
  /** @brief Print a message only when verbose output is active. */
339
  virtual void print_very_verbose(const char* text);
340

341
  /** @brief Update the source file stored in this shell (used by ordered
342
   * tests). */
343
  void set_file_name(const char* file_name);
344
  /** @brief Update the source line stored in this shell. */
345
  void set_line_number(size_t line_number);
346
  /** @brief Update the group name stored in this shell. */
347
  void set_group_name(const char* group_name);
348
  /** @brief Update the test name stored in this shell. */
349
  void set_test_name(const char* test_name);
350

351
  /** @brief Invoke the registered crash function (default: abort). */
352
  static void crash();
353
  /**
354
   * @brief Replace the function called by crash().
355
   *
356
   * @param crashme  Function to call when a crash is requested; must not
357
   * return.
358
   */
359
  static void set_crash_method(void (*crashme)());
360
  /** @brief Restore the default crash function. */
361
  static void reset_crash_method();
362

363
  /** @brief Mark this test to run even though it is an ignored test. */
364
  virtual void set_run_ignored();
365

366
  /** @brief Allocate the Test object for this shell's test group. */
367
  virtual class Test* create_test();
368
  /** @brief Destroy a Test object previously returned by create_test(). */
369
  virtual void destroy_test(class Test* test);
370

371
  /** @brief Run this test (create → setup → body → teardown → destroy). */
372
  virtual void run_one_test(Plugin* plugin, Result& result);
373
  /** @brief Run this test in the current process (no forking). */
374
  virtual void run_one_test_in_current_process(Plugin* plugin, Result& result);
375

376
  /** @brief Record a test failure into the active Result. */
377
  virtual void add_failure(const Failure& failure);
378
  /**
379
   * @brief Attach a key/value property to this test.
380
   *
381
   * Properties appear in JUnit XML output. Prefer the @ref TEST_PROPERTY macro.
382
   *
383
   * @param name   Property name.
384
   * @param value  Property value.
385
   */
386
  virtual void add_test_property(const char* name, const char* value);
387

388
protected:
389
  /** @brief Default constructor for use by subclasses (e.g. IgnoredShell).
390
   */
391
  Shell() noexcept;
392

393
  /** @return The macro keyword used in formatted output (e.g. "TEST"). */
394
  virtual String get_macro_name() const;
395
  /** @return The Result for the current run. */
396
  Result* get_test_result();
397

398
private:
399
  const char* group_;
400
  const char* name_;
401
  const char* file_;
402
  size_t line_number_;
403
  Shell* next_;
404
  bool has_failed_;
405

406
  void set_test_result(Result* result);
407
  void set_current_test(Shell* test);
408
  bool match(const char* target, const Filter* filters) const;
409

410
  static Shell* current_test_;
411
  static Result* test_result_;
412

413
  static const Terminator* current_test_terminator_;
414
  static const Terminator* current_test_terminator_without_exceptions_;
415
  static bool rethrow_exceptions_;
416
};
417

418
/**
419
 * @brief Thrown (or longjmp'd) when a test assertion fails.
420
 *
421
 * The test runner catches this to terminate the current test body while still
422
 * allowing teardown() to run. Users rarely interact with this class directly;
423
 * it is part of the Terminator mechanism.
424
 */
425
class FailedException
426
{
427
public:
428
  int dummy;
429
};
430

431
/**
432
 * @brief Implementation helper for CHECK_EQUAL.
433
 *
434
 * Uses the conditional expression's arithmetic-conversion rules to select a
435
 * common comparison type, avoiding signed/unsigned mismatch warnings. Prefer
436
 * the CHECK_EQUAL macro.
437
 *
438
 * @param expected  Expected value.
439
 * @param actual    Actual value.
440
 * @param text      Optional failure message.
441
 * @param file      Source file path.
442
 * @param line      Source line number.
443
 */
444
template<typename T, typename U>
445
void check_equal(
646✔
446
    const T& expected,
447
    const U& actual,
448
    const char* text,
449
    const char* file,
450
    size_t line
451
)
452
{
453
  // Compare with the natural types so that mixed signed/unsigned comparisons
454
  // produce the same compiler diagnostic they would outside the macro.
455
  if (expected != actual) {
644✔
456
    Shell::get_current()->assert_equals(
30✔
457
        true,
458
        string_from(expected).c_str(),
459
        string_from(actual).c_str(),
460
        text,
461
        file,
462
        line
463
    );
464
  } else {
465
    Shell::get_current()->count_check();
631✔
466
  }
467
}
631✔
468

469
/**
470
 * @brief Implementation helper for CHECK_COMPARE.
471
 *
472
 * The boolean result of the relational operation is pre-evaluated at the
473
 * call site (inside the macro), so @p first and @p second are used only for
474
 * display via string_from(). Prefer the CHECK_COMPARE macro.
475
 *
476
 * @param first      Left-hand operand (display only).
477
 * @param second     Right-hand operand (display only).
478
 * @param success    Pre-evaluated result of @p first relop @p second.
479
 * @param relop_str  String representation of the relational operator.
480
 * @param text       Optional failure message.
481
 * @param file       Source file path.
482
 * @param line       Source line number.
483
 */
484
template<typename T, typename U>
485
void check_compare(
3✔
486
    const T& first,
487
    const U& second,
488
    bool success,
489
    const char* relop_str,
490
    const char* text,
491
    const char* file,
492
    size_t line
493
)
494
{
495
  // The bool result of the relop is pre-computed at the call site (in the
496
  // macro), so no sign-compare issue here. The parameters first/second are
497
  // used only for string_from(), not compared against each other.
498
  if (!success) {
3✔
499
    String condition;
2✔
500
    condition += string_from(first);
2✔
501
    condition += " ";
2✔
502
    condition += relop_str;
2✔
503
    condition += " ";
2✔
504
    condition += string_from(second);
2✔
505
    Shell::get_current()->assert_compare(
2✔
506
        false, "CHECK_COMPARE", condition.c_str(), text, file, line
507
    );
508
  } else {
2✔
509
    Shell::get_current()->count_check();
1✔
510
  }
511
}
1✔
512

513
/**
514
 * @brief Implementation helper for ENUMS_EQUAL_TYPE.
515
 *
516
 * Casts both enum values to @p UNDERLYING_TYPE before comparing and
517
 * formatting. Prefer the ENUMS_EQUAL_TYPE or ENUMS_EQUAL_INT macros.
518
 *
519
 * @tparam UNDERLYING_TYPE  Integer type used for display (e.g. @c int).
520
 * @tparam ENUM_TYPE        Enum type being compared.
521
 * @param expected          Expected enum value.
522
 * @param actual            Actual enum value.
523
 * @param text              Optional failure message.
524
 * @param file              Source file path.
525
 * @param line              Source line number.
526
 */
527
template<typename UNDERLYING_TYPE, typename ENUM_TYPE>
528
void check_enum_equal(
9✔
529
    ENUM_TYPE expected,
530
    ENUM_TYPE actual,
531
    const char* text,
532
    const char* file,
533
    size_t line
534
)
535
{
536
  auto e = static_cast<UNDERLYING_TYPE>(expected);
9✔
537
  auto a = static_cast<UNDERLYING_TYPE>(actual);
9✔
538
  if (e != a) {
9✔
539
    Shell::get_current()->assert_equals(
12✔
540
        true, string_from(e).c_str(), string_from(a).c_str(), text, file, line
541
    );
542
  } else {
543
    Shell::get_current()->count_check();
3✔
544
  }
545
}
3✔
546

547
/**
548
 * @brief Implementation helper for CHECK_APPROX.
549
 *
550
 * All three operands share the same type @p T so mismatched-type calls produce
551
 * a compiler diagnostic rather than silent promotion. Prefer the CHECK_APPROX
552
 * macro.
553
 *
554
 * @param expected   Expected value.
555
 * @param actual     Actual value.
556
 * @param threshold  Maximum allowed absolute difference.
557
 * @param text       Optional failure message.
558
 * @param file       Source file path.
559
 * @param line       Source line number.
560
 */
561
template<typename T>
562
void check_approx(
41✔
563
    T expected,
564
    T actual,
565
    T threshold,
566
    const char* text,
567
    const char* file,
568
    size_t line
569
)
570
{
571
  if (!approx_equal(expected, actual, threshold)) {
41✔
572
    Shell::get_current()->assert_approx_equal(
7✔
573
        expected, actual, threshold, text, file, line
574
    );
575
  } else {
576
    Shell::get_current()->count_check();
34✔
577
  }
578
}
34✔
579

580
} // namespace test
581
} // namespace tiny
582
} // namespace mu
583

584
/**
585
 * @brief Fail the current test if @p condition is false.
586
 *
587
 * @param condition  Any expression convertible to bool.
588
 *
589
 * See also @ref CHECK_TEXT.
590
 */
591
#define CHECK(condition)                                                       \
592
  CHECK_LOCATION(condition, "CHECK", #condition, "", __FILE__, __LINE__)
593

594
/** @brief @ref CHECK with a custom failure message. */
595
#define CHECK_TEXT(condition, text)                                            \
596
  CHECK_LOCATION(                                                              \
597
      static_cast<bool>(condition),                                            \
598
      "CHECK",                                                                 \
599
      #condition,                                                              \
600
      text,                                                                    \
601
      __FILE__,                                                                \
602
      __LINE__                                                                 \
603
  )
604

605
#define CHECK_LOCATION(                                                        \
606
    condition, checkString, conditionString, text, file, line                  \
607
)                                                                              \
608
  do {                                                                         \
609
    mu::tiny::test::Shell::get_current()->assert_true(                         \
610
        (condition), checkString, conditionString, text, file, line            \
611
    );                                                                         \
612
  } while (0)
613

614
/**
615
 * @brief Fail if @p expected != @p actual.
616
 *
617
 * Requires operator!=() and a string_from() overload for the operand types.
618
 * Uses common-type arithmetic conversion so signed/unsigned pairs are
619
 * promoted consistently without -Wsign-compare warnings.
620
 *
621
 * @param expected  Expected value.
622
 * @param actual    Actual value.
623
 */
624
#define CHECK_EQUAL(expected, actual)                                          \
625
  CHECK_EQUAL_LOCATION(expected, actual, "", __FILE__, __LINE__)
626

627
/** @brief CHECK_EQUAL with a custom failure message. @see CHECK_EQUAL */
628
#define CHECK_EQUAL_TEXT(expected, actual, text)                               \
629
  CHECK_EQUAL_LOCATION(expected, actual, text, __FILE__, __LINE__)
630

631
#define CHECK_EQUAL_LOCATION(expected, actual, text, file, line)               \
632
  mu::tiny::test::check_equal((expected), (actual), text, file, line)
633

634
/** @brief Fail if @p actual != 0. Equivalent to CHECK_EQUAL(0, actual). */
635
#define CHECK_EQUAL_ZERO(actual) CHECK_EQUAL(0, (actual))
636

637
/** @brief CHECK_EQUAL_ZERO with a custom failure message. @see CHECK_EQUAL_ZERO
638
 */
639
#define CHECK_EQUAL_ZERO_TEXT(actual, text)                                    \
640
  CHECK_EQUAL_TEXT(0, (actual), (text))
641

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

661
/** @brief CHECK_COMPARE with a custom failure message. @see CHECK_COMPARE */
662
#define CHECK_COMPARE_TEXT(first, relop, second, text)                         \
663
  CHECK_COMPARE_LOCATION(first, relop, second, text, __FILE__, __LINE__)
664

665
#define CHECK_COMPARE_LOCATION(first, relop, second, text, file, line)         \
666
  mu::tiny::test::check_compare(                                               \
667
      (first), (second), (first)relop(second), #relop, text, file, line        \
668
  )
669

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

684
/** @brief STRCMP_EQUAL with a custom failure message. @see STRCMP_EQUAL */
685
#define STRCMP_EQUAL_TEXT(expected, actual, text)                              \
686
  STRCMP_EQUAL_LOCATION(expected, actual, text, __FILE__, __LINE__)
687

688
#define STRCMP_EQUAL_LOCATION(expected, actual, text, file, line)              \
689
  do {                                                                         \
690
    mu::tiny::test::Shell::get_current()->assert_cstr_equal(                   \
691
        expected, actual, text, file, line                                     \
692
    );                                                                         \
693
  } while (0)
694

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

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

712
#define STRNCMP_EQUAL_LOCATION(expected, actual, length, text, file, line)     \
713
  do {                                                                         \
714
    mu::tiny::test::Shell::get_current()->assert_cstr_n_equal(                 \
715
        expected, actual, length, text, file, line                             \
716
    );                                                                         \
717
  } while (0)
718

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

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

735
#define STRCMP_CONTAINS_LOCATION(expected, actual, text, file, line)           \
736
  do {                                                                         \
737
    mu::tiny::test::Shell::get_current()->assert_cstr_contains(                \
738
        expected, actual, text, file, line                                     \
739
    );                                                                         \
740
  } while (0)
741

742
/**
743
 * @brief Fail if @p expected and @p actual differ by more than @p threshold.
744
 *
745
 * Accepts any numeric type (floating-point or integral); all three operands
746
 * must share the same type so mismatched pairs produce a compiler diagnostic.
747
 * Handles NaN correctly: a NaN operand always fails the check.
748
 *
749
 * @param expected   Expected value.
750
 * @param actual     Actual value.
751
 * @param threshold  Maximum allowed absolute difference (must be >= 0).
752
 *
753
 * @see CHECK_APPROX_TEXT, approx_equal
754
 */
755
#define CHECK_APPROX(expected, actual, threshold)                              \
756
  CHECK_APPROX_LOCATION(expected, actual, threshold, "", __FILE__, __LINE__)
757

758
/** @brief CHECK_APPROX with a custom failure message. @see CHECK_APPROX */
759
#define CHECK_APPROX_TEXT(expected, actual, threshold, text)                   \
760
  CHECK_APPROX_LOCATION(expected, actual, threshold, text, __FILE__, __LINE__)
761

762
#define CHECK_APPROX_LOCATION(expected, actual, threshold, text, file, line)   \
763
  mu::tiny::test::check_approx(                                                \
764
      (expected), (actual), (threshold), text, file, line                      \
765
  )
766

767
/**
768
 * @brief Fail if @p size bytes starting at @p expected and @p actual differ.
769
 *
770
 * @param expected  Pointer to the expected memory region.
771
 * @param actual    Pointer to the actual memory region.
772
 * @param size      Number of bytes to compare.
773
 *
774
 * @see MEMCMP_EQUAL_TEXT
775
 */
776
#define MEMCMP_EQUAL(expected, actual, size)                                   \
777
  MEMCMP_EQUAL_LOCATION(expected, actual, size, "", __FILE__, __LINE__)
778

779
/** @brief MEMCMP_EQUAL with a custom failure message. @see MEMCMP_EQUAL */
780
#define MEMCMP_EQUAL_TEXT(expected, actual, size, text)                        \
781
  MEMCMP_EQUAL_LOCATION(expected, actual, size, text, __FILE__, __LINE__)
782

783
#define MEMCMP_EQUAL_LOCATION(expected, actual, size, text, file, line)        \
784
  do {                                                                         \
785
    mu::tiny::test::Shell::get_current()->assert_binary_equal(                 \
786
        expected, actual, size, text, file, line                               \
787
    );                                                                         \
788
  } while (0)
789

790
/**
791
 * @brief Fail if two enum values differ, casting both to @c int for display.
792
 *
793
 * @param expected  Expected enum value.
794
 * @param actual    Actual enum value.
795
 *
796
 * @see ENUMS_EQUAL_TYPE
797
 */
798
#define ENUMS_EQUAL_INT(expected, actual)                                      \
799
  ENUMS_EQUAL_TYPE(int, expected, actual)
800

801
/** @brief ENUMS_EQUAL_INT with a custom failure message. @see ENUMS_EQUAL_INT
802
 */
803
#define ENUMS_EQUAL_INT_TEXT(expected, actual, text)                           \
804
  ENUMS_EQUAL_TYPE_TEXT(int, expected, actual, text)
805

806
/**
807
 * @brief Fail if two enum values differ, casting both to @p underlying_type for
808
 * display.
809
 *
810
 * Use this when the enum's underlying type is not @c int (e.g. @c unsigned
811
 * or @c uint8_t) to get meaningful output on failure.
812
 *
813
 * @param underlying_type  Integer type to cast enum values to for display.
814
 * @param expected         Expected enum value.
815
 * @param actual           Actual enum value.
816
 *
817
 * @see ENUMS_EQUAL_INT, ENUMS_EQUAL_TYPE_TEXT
818
 */
819
#define ENUMS_EQUAL_TYPE(underlying_type, expected, actual)                    \
820
  ENUMS_EQUAL_TYPE_LOCATION(                                                   \
821
      underlying_type, expected, actual, "", __FILE__, __LINE__                \
822
  )
823

824
/** @brief ENUMS_EQUAL_TYPE with a custom failure message. @see ENUMS_EQUAL_TYPE
825
 */
826
#define ENUMS_EQUAL_TYPE_TEXT(underlying_type, expected, actual, text)         \
827
  ENUMS_EQUAL_TYPE_LOCATION(                                                   \
828
      underlying_type, expected, actual, text, __FILE__, __LINE__              \
829
  )
830

831
#define ENUMS_EQUAL_TYPE_LOCATION(                                             \
832
    underlying_type, expected, actual, text, file, line                        \
833
)                                                                              \
834
  mu::tiny::test::check_enum_equal<underlying_type>(                           \
835
      (expected), (actual), text, file, line                                   \
836
  )
837

838
/**
839
 * @brief Unconditionally fail the current test with a message.
840
 *
841
 * FAIL may already be defined by another library; in that case use FAIL_TEST
842
 * instead. Both macros behave identically.
843
 *
844
 * @param text  Human-readable failure message.
845
 *
846
 * @see FAIL_TEST, FAIL_LOCATION
847
 */
848
#ifndef FAIL
849
#define FAIL(text) FAIL_LOCATION(text, __FILE__, __LINE__)
850

851
#define FAIL_LOCATION(text, file, line)                                        \
852
  do {                                                                         \
853
    mu::tiny::test::Shell::get_current()->fail(text, file, line);              \
854
  } while (0)
855
#endif
856

857
/** @brief Unconditionally fail the current test. Use when FAIL is already
858
 * defined. @see FAIL */
859
#define FAIL_TEST(text) FAIL_TEST_LOCATION(text, __FILE__, __LINE__)
860

861
#define FAIL_TEST_LOCATION(text, file, line)                                   \
862
  do {                                                                         \
863
    mu::tiny::test::Shell::get_current()->fail(text, file, line);              \
864
  } while (0)
865

866
/**
867
 * @brief Exit the current test body immediately without marking it as failed.
868
 *
869
 * Useful when a prerequisite check fails and continuing would produce
870
 * confusing cascading failures. Unlike FAIL, the test is counted as passed.
871
 */
872
#define TEST_EXIT                                                              \
873
  do {                                                                         \
874
    mu::tiny::test::Shell::get_current()->exit_test();                         \
875
  } while (0)
876

877
#if MUTINY_HAVE_EXCEPTIONS
878
/**
879
 * @brief Fail if @p expression does not throw an exception of type @p expected.
880
 *
881
 * The test fails if @p expression throws nothing or throws an exception of a
882
 * different type. Only available when exceptions are enabled
883
 * (@ref MUTINY_HAVE_EXCEPTIONS is non-zero).
884
 *
885
 * @param expected    Exception type that must be thrown (not a string).
886
 * @param expression  Expression that should throw.
887
 */
888
#define CHECK_THROWS(expected, expression)                                     \
889
  do {                                                                         \
890
    mu::tiny::String failure_msg(                                              \
891
        "expected to throw " #expected "\nbut threw nothing"                   \
892
    );                                                                         \
893
    bool caught_expected = false;                                              \
894
    try {                                                                      \
895
      (expression);                                                            \
896
    } catch (const expected&) {                                                \
897
      caught_expected = true;                                                  \
898
    } catch (...) {                                                            \
899
      failure_msg =                                                            \
900
          "expected to throw " #expected "\nbut threw a different type";       \
901
    }                                                                          \
902
    if (!caught_expected) {                                                    \
903
      mu::tiny::test::Shell::get_current()->fail(                              \
904
          failure_msg.c_str(), __FILE__, __LINE__                              \
905
      );                                                                       \
906
    } else {                                                                   \
907
      mu::tiny::test::Shell::get_current()->count_check();                     \
908
    }                                                                          \
909
  } while (0)
910
#endif /* MUTINY_HAVE_EXCEPTIONS */
911

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

© 2026 Coveralls, Inc