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

thetic / mutiny / 24374861549

14 Apr 2026 12:51AM UTC coverage: 98.944%. Remained the same
24374861549

Pull #51

github

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

14 of 15 new or added lines in 2 files covered. (93.33%)

2 existing lines in 2 files now uncovered.

5530 of 5589 relevant lines covered (98.94%)

3444.32 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)
17✔
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;
14✔
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,531✔
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(
6✔
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(
6✔
267
        ApproxEqualFailure<T>(this, file_name, line_number, expected, actual, threshold, text)
12✔
268
    );
269
    test_terminator.exit_current_test();
6✔
NEW
270
  }
×
271
  /** @brief Macro backend: generic equality failure with pre-formatted strings.
272
   */
273
  virtual void assert_equals(
274
      bool failed,
275
      const char* expected,
276
      const char* actual,
277
      const char* text,
278
      const char* file,
279
      size_t line_number,
280
      const Terminator& test_terminator = get_current_test_terminator()
281
  );
282
  /** @brief Macro backend: generic equality failure with String arguments. */
283
  virtual void assert_equals(
284
      bool failed,
285
      String expected,
286
      String actual,
287
      const char* text,
288
      const char* file,
289
      size_t line_number,
290
      const Terminator& test_terminator = get_current_test_terminator()
291
  );
292
  /** @brief Macro backend: assert @p length bytes of two memory regions match.
293
   */
294
  virtual void assert_binary_equal(
295
      const void* expected,
296
      const void* actual,
297
      size_t length,
298
      const char* text,
299
      const char* file_name,
300
      size_t line_number,
301
      const Terminator& test_terminator = get_current_test_terminator()
302
  );
303
  /** @brief Macro backend: assert a relational comparison holds. */
304
  virtual void assert_compare(
305
      bool comparison,
306
      const char* check_string,
307
      const char* comparison_string,
308
      const char* text,
309
      const char* file_name,
310
      size_t line_number,
311
      const Terminator& test_terminator = get_current_test_terminator()
312
  );
313
  /**
314
   * @brief Unconditionally fail the test with a message.
315
   *
316
   * @param text        Human-readable failure reason.
317
   * @param file_name   Source file (__FILE__).
318
   * @param line_number Source line (__LINE__).
319
   * @param test_terminator  Controls how the test is aborted after the failure.
320
   */
321
  virtual void fail(
322
      const char* text,
323
      const char* file_name,
324
      size_t line_number,
325
      const Terminator& test_terminator = get_current_test_terminator()
326
  );
327
  /**
328
   * @brief Exit the test body immediately without marking it as failed.
329
   *
330
   * @param test_terminator  Controls how control leaves the test body.
331
   */
332
  virtual void exit_test(
333
      const Terminator& test_terminator = get_current_test_terminator()
334
  );
335

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

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

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

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

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

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

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

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

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

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

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

408
  static Shell* current_test_;
409
  static Result* test_result_;
410

411
  static const Terminator* current_test_terminator_;
412
  static const Terminator* current_test_terminator_without_exceptions_;
413
  static bool rethrow_exceptions_;
414
};
415

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

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

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

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

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

576
} // namespace test
577
} // namespace tiny
578
} // namespace mu
579

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

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

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

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

623
/** @brief CHECK_EQUAL with a custom failure message. @see CHECK_EQUAL */
624
#define CHECK_EQUAL_TEXT(expected, actual, text)                               \
625
  CHECK_EQUAL_LOCATION(expected, actual, text, __FILE__, __LINE__)
626

627
#define CHECK_EQUAL_LOCATION(expected, actual, text, file, line)               \
628
  mu::tiny::test::check_equal((expected), (actual), text, file, line)
629

630
/** @brief Fail if @p actual != 0. Equivalent to CHECK_EQUAL(0, actual). */
631
#define CHECK_EQUAL_ZERO(actual) CHECK_EQUAL(0, (actual))
632

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

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

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

661
#define CHECK_COMPARE_LOCATION(first, relop, second, text, file, line)         \
662
  mu::tiny::test::check_compare(                                               \
663
      (first), (second), (first)relop(second), #relop, text, file, line        \
664
  )
665

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

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

684
#define STRCMP_EQUAL_LOCATION(expected, actual, text, file, line)              \
685
  do {                                                                         \
686
    mu::tiny::test::Shell::get_current()->assert_cstr_equal(                   \
687
        expected, actual, text, file, line                                     \
688
    );                                                                         \
689
  } while (0)
690

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

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

708
#define STRNCMP_EQUAL_LOCATION(expected, actual, length, text, file, line)     \
709
  do {                                                                         \
710
    mu::tiny::test::Shell::get_current()->assert_cstr_n_equal(                 \
711
        expected, actual, length, text, file, line                             \
712
    );                                                                         \
713
  } while (0)
714

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

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

731
#define STRCMP_CONTAINS_LOCATION(expected, actual, text, file, line)           \
732
  do {                                                                         \
733
    mu::tiny::test::Shell::get_current()->assert_cstr_contains(                \
734
        expected, actual, text, file, line                                     \
735
    );                                                                         \
736
  } while (0)
737

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

754
/** @brief CHECK_APPROX with a custom failure message. @see CHECK_APPROX */
755
#define CHECK_APPROX_TEXT(expected, actual, threshold, text)                   \
756
  CHECK_APPROX_LOCATION(expected, actual, threshold, text, __FILE__, __LINE__)
757

758
#define CHECK_APPROX_LOCATION(expected, actual, threshold, text, file, line)   \
759
  mu::tiny::test::check_approx(                                                \
760
      (expected), (actual), (threshold), text, file, line                      \
761
  )
762

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

775
/** @brief MEMCMP_EQUAL with a custom failure message. @see MEMCMP_EQUAL */
776
#define MEMCMP_EQUAL_TEXT(expected, actual, size, text)                        \
777
  MEMCMP_EQUAL_LOCATION(expected, actual, size, text, __FILE__, __LINE__)
778

779
#define MEMCMP_EQUAL_LOCATION(expected, actual, size, text, file, line)        \
780
  do {                                                                         \
781
    mu::tiny::test::Shell::get_current()->assert_binary_equal(                 \
782
        expected, actual, size, text, file, line                               \
783
    );                                                                         \
784
  } while (0)
785

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

797
/** @brief ENUMS_EQUAL_INT with a custom failure message. @see ENUMS_EQUAL_INT
798
 */
799
#define ENUMS_EQUAL_INT_TEXT(expected, actual, text)                           \
800
  ENUMS_EQUAL_TYPE_TEXT(int, expected, actual, text)
801

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

820
/** @brief ENUMS_EQUAL_TYPE with a custom failure message. @see ENUMS_EQUAL_TYPE
821
 */
822
#define ENUMS_EQUAL_TYPE_TEXT(underlying_type, expected, actual, text)         \
823
  ENUMS_EQUAL_TYPE_LOCATION(                                                   \
824
      underlying_type, expected, actual, text, __FILE__, __LINE__              \
825
  )
826

827
#define ENUMS_EQUAL_TYPE_LOCATION(                                             \
828
    underlying_type, expected, actual, text, file, line                        \
829
)                                                                              \
830
  mu::tiny::test::check_enum_equal<underlying_type>(                           \
831
      (expected), (actual), text, file, line                                   \
832
  )
833

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

847
#define FAIL_LOCATION(text, file, line)                                        \
848
  do {                                                                         \
849
    mu::tiny::test::Shell::get_current()->fail(text, file, line);              \
850
  } while (0)
851
#endif
852

853
/** @brief Unconditionally fail the current test. Use when FAIL is already
854
 * defined. @see FAIL */
855
#define FAIL_TEST(text) FAIL_TEST_LOCATION(text, __FILE__, __LINE__)
856

857
#define FAIL_TEST_LOCATION(text, file, line)                                   \
858
  do {                                                                         \
859
    mu::tiny::test::Shell::get_current()->fail(text, file, line);              \
860
  } while (0)
861

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

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

908
#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