GCC Code Coverage Report


Directory: include/tts/
File: include/tts/engine/case.hpp
Date: 2026-10-14 16:28:14
Exec Total Coverage
Lines: 59 60 98.3%
Functions: 270 271 99.6%
Branches: 129 188 68.6%

Line Branch Exec Source
1 //======================================================================================================================
2 //! @file
3 /*
4 TTS - Tiny Test System
5 Copyright : TTS Contributors & Maintainers
6 SPDX-License-Identifier: BSL-1.0
7 */
8 //======================================================================================================================
9 #pragma once
10
11 #include <tts/engine/info.hpp>
12 #include <tts/engine/test.hpp>
13 #include <tts/engine/generator.hpp>
14 #include <tts/tools/options.hpp>
15 #include <tts/tools/preprocessor.hpp>
16 #include <tts/tools/types.hpp>
17
18 //======================================================================================================================
19 // "Seed" capture
20 //======================================================================================================================
21 namespace tts::_
22 {
23 struct capture
24 {
25 229 capture(char const* id) // NOSONAR
26 229 : name(id)
27 {
28 229 }
29 6 capture(tagged_id id) // NOSONAR
30 6 : name(id.name)
31 6 , tag(id.tag)
32 6 , timeout_ns(id.timeout_ns)
33 6 , timeout_set(id.timeout_set)
34 {
35 6 }
36 235 auto operator+(auto body) const
37 {
38 470 return test::acknowledge({name, body, /*types=*/ {}, tag, timeout_ns, timeout_set});
39 470 }
40 char const* name;
41 ::tts::expected_outcome tag = ::tts::expected_outcome::pass;
42 nanoseconds timeout_ns = 0;
43 bool timeout_set = false;
44 };
45
46 // Global storage for current type used in a given test
47 inline text current_type = {};
48
49 // Builds a ", "-joined list of type names, known at registration time so --dry can display it
50 // without running the test.
51 inline text joined_type_names()
52 {
53 return text {};
54 }
55
56 352 template<typename T, typename... Rest> inline text joined_type_names()
57 {
58 352 text out = as_text(typename_<T>);
59 if constexpr(sizeof...(Rest) > 0)
60 {
61
1/1
✓ Branch 1 taken 134 times.
268 out += ", ";
62
2/2
✓ Branch 1 taken 134 times.
✓ Branch 4 taken 134 times.
268 out += joined_type_names<Rest...>();
63 }
64 352 return out;
65 ✗ }
66
67 template<typename... Types> struct captures
68 {
69 48 captures(char const* id) // NOSONAR
70 48 : name(id)
71 {
72 48 }
73 1 captures(tagged_id id) // NOSONAR
74 1 : name(id.name)
75 1 , tag(id.tag)
76 1 , timeout_ns(id.timeout_ns)
77 1 , timeout_set(id.timeout_set)
78 {
79 1 }
80
81 49 auto operator+(auto body) const
82 {
83 // We register all the types in a single test to keep the compile-time O(1)
84 // Registering different type in different tests generate far too much callable::invoker
85 // symbol that make compile-time O(N)
86 98 return test::acknowledge(
87 49 {name,
88 54 [ body ]()
89 {
90 // We setup the current type name before each test so we know
91 (
92
48/48
✓ Branch 1 taken 13 times.
✓ Branch 5 taken 13 times.
✓ Branch 9 taken 9 times.
✓ Branch 11 taken 3 times.
✓ Branch 13 taken 6 times.
✓ Branch 15 taken 3 times.
✓ Branch 16 taken 1 times.
✓ Branch 17 taken 4 times.
✓ Branch 19 taken 3 times.
✓ Branch 20 taken 1 times.
✓ Branch 21 taken 2 times.
✓ Branch 23 taken 3 times.
✓ Branch 25 taken 2 times.
✓ Branch 26 taken 3 times.
✓ Branch 27 taken 3 times.
✓ Branch 29 taken 2 times.
✓ Branch 30 taken 3 times.
✓ Branch 31 taken 3 times.
✓ Branch 33 taken 1 times.
✓ Branch 34 taken 2 times.
✓ Branch 35 taken 3 times.
✓ Branch 36 taken 1 times.
✓ Branch 37 taken 1 times.
✓ Branch 38 taken 2 times.
✓ Branch 39 taken 3 times.
✓ Branch 40 taken 1 times.
✓ Branch 41 taken 1 times.
✓ Branch 42 taken 1 times.
✓ Branch 44 taken 1 times.
✓ Branch 45 taken 1 times.
✓ Branch 46 taken 2 times.
✓ Branch 48 taken 1 times.
✓ Branch 50 taken 2 times.
✓ Branch 52 taken 1 times.
✓ Branch 54 taken 1 times.
✓ Branch 56 taken 1 times.
✓ Branch 58 taken 1 times.
✓ Branch 60 taken 1 times.
✓ Branch 62 taken 1 times.
✓ Branch 66 taken 1 times.
✓ Branch 70 taken 1 times.
✓ Branch 74 taken 1 times.
✓ Branch 76 taken 1 times.
✓ Branch 80 taken 1 times.
✓ Branch 81 taken 1 times.
✓ Branch 84 taken 1 times.
✓ Branch 85 taken 1 times.
✓ Branch 88 taken 1 times.
115 ((current_type = as_text(typename_<Types>)),
93 115 (::tts::is_detailed()
94
48/105
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 13 times.
✗ Branch 12 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 9 times.
✗ Branch 16 not taken.
✓ Branch 17 taken 3 times.
✗ Branch 19 not taken.
✗ Branch 21 not taken.
✓ Branch 22 taken 6 times.
✗ Branch 23 not taken.
✓ Branch 24 taken 3 times.
✓ Branch 25 taken 1 times.
✗ Branch 26 not taken.
✗ Branch 28 not taken.
✓ Branch 29 taken 4 times.
✗ Branch 30 not taken.
✓ Branch 31 taken 3 times.
✓ Branch 32 taken 1 times.
✗ Branch 33 not taken.
✗ Branch 35 not taken.
✓ Branch 36 taken 2 times.
✗ Branch 37 not taken.
✓ Branch 38 taken 3 times.
✗ Branch 40 not taken.
✓ Branch 41 taken 3 times.
✗ Branch 42 not taken.
✓ Branch 43 taken 2 times.
✗ Branch 44 not taken.
✓ Branch 45 taken 3 times.
✗ Branch 47 not taken.
✓ Branch 48 taken 3 times.
✗ Branch 49 not taken.
✓ Branch 50 taken 2 times.
✗ Branch 51 not taken.
✓ Branch 52 taken 3 times.
✗ Branch 54 not taken.
✓ Branch 55 taken 2 times.
✗ Branch 56 not taken.
✓ Branch 57 taken 2 times.
✗ Branch 58 not taken.
✓ Branch 59 taken 3 times.
✗ Branch 61 not taken.
✓ Branch 62 taken 2 times.
✗ Branch 63 not taken.
✓ Branch 64 taken 2 times.
✓ Branch 65 taken 1 times.
✓ Branch 66 taken 3 times.
✗ Branch 68 not taken.
✓ Branch 69 taken 1 times.
✗ Branch 70 not taken.
✓ Branch 71 taken 1 times.
✓ Branch 72 taken 1 times.
✓ Branch 73 taken 1 times.
✗ Branch 75 not taken.
✓ Branch 76 taken 1 times.
✗ Branch 77 not taken.
✓ Branch 78 taken 1 times.
✗ Branch 79 not taken.
✓ Branch 80 taken 1 times.
✗ Branch 82 not taken.
✓ Branch 83 taken 1 times.
✗ Branch 84 not taken.
✓ Branch 85 taken 1 times.
✗ Branch 87 not taken.
✗ Branch 88 not taken.
✓ Branch 89 taken 1 times.
✓ Branch 90 taken 1 times.
✗ Branch 93 not taken.
✗ Branch 94 not taken.
✗ Branch 95 not taken.
✓ Branch 96 taken 1 times.
✓ Branch 97 taken 1 times.
✗ Branch 100 not taken.
✗ Branch 101 not taken.
✗ Branch 103 not taken.
✓ Branch 104 taken 1 times.
✓ Branch 105 taken 1 times.
✗ Branch 108 not taken.
✗ Branch 109 not taken.
✗ Branch 111 not taken.
✓ Branch 112 taken 1 times.
✗ Branch 116 not taken.
✗ Branch 118 not taken.
✓ Branch 119 taken 1 times.
✗ Branch 120 not taken.
✓ Branch 121 taken 1 times.
✗ Branch 123 not taken.
✗ Branch 125 not taken.
✗ Branch 127 not taken.
✓ Branch 128 taken 1 times.
✓ Branch 129 taken 1 times.
✗ Branch 132 not taken.
✗ Branch 133 not taken.
✗ Branch 134 not taken.
✓ Branch 135 taken 1 times.
✓ Branch 136 taken 1 times.
✗ Branch 139 not taken.
✗ Branch 140 not taken.
✗ Branch 141 not taken.
✓ Branch 142 taken 1 times.
✗ Branch 146 not taken.
142 ? (::tts::output().writeln("> With <T = %s>", current_type.data()), 0)
95 : 0),
96
19/19
✓ Branch 1 taken 12 times.
✓ Branch 4 taken 12 times.
✓ Branch 7 taken 11 times.
✓ Branch 10 taken 9 times.
✓ Branch 13 taken 7 times.
✓ Branch 16 taken 7 times.
✓ Branch 19 taken 7 times.
✓ Branch 22 taken 7 times.
✓ Branch 25 taken 7 times.
✓ Branch 28 taken 7 times.
✓ Branch 31 taken 3 times.
✓ Branch 34 taken 3 times.
✓ Branch 37 taken 2 times.
✓ Branch 40 taken 2 times.
✓ Branch 43 taken 2 times.
✓ Branch 46 taken 2 times.
✓ Branch 49 taken 2 times.
✓ Branch 52 taken 2 times.
✓ Branch 55 taken 1 times.
115 body(type<Types>())),
97 ...);
98 // Clear the current type
99 27 current_type = text {""};
100 },
101 joined_type_names<Types...>(),
102 49 tag,
103 49 timeout_ns,
104 98 timeout_set});
105 98 }
106 char const* name;
107 ::tts::expected_outcome tag = ::tts::expected_outcome::pass;
108 nanoseconds timeout_ns = 0;
109 bool timeout_set = false;
110 };
111
112 // Specialisation for types lists
113 template<typename... Types> struct captures<types<Types...>> : captures<Types...>
114 {
115 };
116
117 // Specialisation for types list generator
118 template<typename Generator>
119 requires requires { typename Generator::types_list; }
120 struct captures<Generator> : captures<typename Generator::types_list>
121 {
122 };
123
124 template<typename Types, auto... Generators> struct test_generators;
125
126 template<typename... Type, auto... Generators>
127 struct test_generators<types<Type...>, Generators...>
128 {
129 char const* name;
130 ::tts::expected_outcome tag = ::tts::expected_outcome::pass;
131 nanoseconds timeout_ns = 0;
132 bool timeout_set = false;
133
134 16 test_generators(char const* id) // NOSONAR
135 16 : name(id)
136 {
137 16 }
138 test_generators(tagged_id id) // NOSONAR
139 : name(id.name)
140 , tag(id.tag)
141 , timeout_ns(id.timeout_ns)
142 , timeout_set(id.timeout_set)
143 {
144 }
145
146 116 template<typename... Args> static void process_call(auto body, Args&&... args)
147 {
148 380 body(std::forward<Args>(args)...);
149 116 }
150
151 118 template<typename T> static void process_type(auto body)
152 {
153
1/1
✓ Branch 1 taken 59 times.
118 current_type = as_text(typename_<T>);
154
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 59 times.
✗ Branch 6 not taken.
118 if(::tts::is_detailed()) ::tts::output().writeln("> With <T = %s>", current_type.data());
155
9/9
✓ Branch 1 taken 55 times.
✓ Branch 2 taken 4 times.
✓ Branch 4 taken 55 times.
✓ Branch 7 taken 29 times.
✓ Branch 10 taken 23 times.
✓ Branch 13 taken 20 times.
✓ Branch 16 taken 20 times.
✓ Branch 19 taken 20 times.
✓ Branch 22 taken 20 times.
118 process_call(body, ::tts::produce(type<T> {}, Generators)...);
156 118 }
157
158 16 friend auto operator<<(test_generators tg, auto body)
159 {
160 32 return test::acknowledge({tg.name,
161 28 [ body ]() mutable
162 {
163 14 (process_type<Type>(body), ...);
164 14 current_type = text {""};
165 },
166 joined_type_names<Type...>(),
167 16 tg.tag,
168 16 tg.timeout_ns,
169 32 tg.timeout_set});
170 32 }
171 };
172 }
173
174 //======================================================================================================================
175 /**
176 @defgroup test-scenario Tests definition macros
177 @brief Macros for defining tests scenarios.
178 @{
179 **/
180 //======================================================================================================================
181
182 //======================================================================================================================
183 /**
184 @name Scenarios Definitions
185 @{
186 **/
187 //======================================================================================================================
188
189 //======================================================================================================================
190 /**
191 @def TTS_CASE
192 @brief Introduces a new test scenario and registers it into the current test driver.
193
194 The code block following TTS_CASE contains user-defined code for a given test case.
195 Test cases performing no actual tests will be reported as invalid.
196
197 @param ID A literal string describing the scenario intents.
198
199 @groupheader{Example}
200 @snippet doc/case.cpp snippet
201 **/
202 //======================================================================================================================
203 #if defined(TTS_DOXYGEN_INVOKED)
204 #define TTS_CASE(ID)
205 #else
206 #define TTS_CASE(ID) \
207 [[maybe_unused]] static auto const TTS_CAT(case_, TTS_FUNCTION) = \
208 ::tts::_::capture {ID} + +[]() /**/
209 #endif
210
211 //======================================================================================================================
212 /**
213 @def TTS_CASE_TPL
214 @brief Introduces a template test case and registers it into the current test driver.
215
216 The code block following TTS_CASE contains user-defined code for a given test case.
217 Those tests are parametrized by a template type of your choice passed as lambda function
218 parameters of the template type `tts::type` and instantiated for each type in the types list.
219
220 Such types list can be provided as:
221 + a variadic list of types separated by commas
222 + an instance of the tts::types template class
223 + an instance of a Type Generator, ie a type exposing a public `types_list` type definition
224
225 Test cases performing no actual tests will be reported as invalid.
226
227 @param ID A literal string describing the scenario intents.
228 @param ... Lists of types to generate the test case from.
229
230 @groupheader{Example}
231 @snippet doc/case_tpl.cpp snippet
232 **/
233 //======================================================================================================================
234 #if defined(TTS_DOXYGEN_INVOKED)
235 #define TTS_CASE_TPL(ID, ...)
236 #else
237 #define TTS_CASE_TPL(ID, ...) \
238 [[maybe_unused]] static bool const TTS_CAT(case_, TTS_FUNCTION) = \
239 ::tts::_::captures<__VA_ARGS__> {ID} + [] /**/
240 #endif
241
242 //======================================================================================================================
243 /**
244 @def TTS_CASE_WITH
245 @brief Introduces a template test case providing dynamically generated data to the test code.
246
247 The following code block will contain tests parametrized by a template type of your choice passed
248 as lambda function parameters and generated for each type in the types list.
249
250 Such types list can be provided as:
251 + a parenthesised list of types separated by comma.
252 + an instance of the tts::types template class.
253 + an instance of a Type Generator, ie a type exposing a public `types_list` type definition
254
255 Test cases performing no actual tests will be reported as invalid.
256
257 @param ID A literal string describing the scenario intents.
258 @param TYPES Lists of types to generate the test case from.
259 @param ... Lists of generator function
260
261 @groupheader{Example}
262 @snippet doc/case_with.cpp snippet
263 **/
264 //======================================================================================================================
265 #if defined(TTS_DOXYGEN_INVOKED)
266 #define TTS_CASE_WITH(ID, TYPES, ...)
267 #else
268 #define TTS_CASE_WITH(ID, TYPES, ...) \
269 [[maybe_unused]] static bool const TTS_CAT(case_, TTS_FUNCTION) = \
270 ::tts::_::test_generators<::tts::as_type_list_t<TTS_REMOVE_PARENS(TYPES)>, __VA_ARGS__> {ID} \
271 << [] /**/
272 #endif
273
274 //======================================================================================================================
275 /**
276 @def TTS_XFAIL
277 @brief Tags a @ref TTS_CASE (or @ref TTS_CASE_TPL / @ref TTS_CASE_WITH) ID as expected to fail.
278
279 Wraps the case's ID instead of changing the enclosing macro's signature, so it composes with
280 @ref TTS_CASE, @ref TTS_CASE_TPL and @ref TTS_CASE_WITH unchanged: `TTS_CASE(TTS_XFAIL("..."))`.
281
282 The case is expected to produce at least one failing assertion. If it runs with no failures
283 instead, that mismatch is reported and fails the suite - matching the "X = expected" naming
284 convention already used by other test frameworks (pytest's `xfail`, DejaGnu, Perl's
285 `Test::More`), so it reads the same way to anyone coming from one of those.
286
287 An empty case (no assertion ran at all) does not satisfy `TTS_XFAIL` either - use
288 @ref TTS_XINVALID for that.
289
290 @param ID A literal string describing the scenario intents.
291
292 @see TTS_MAYFAIL
293 @see TTS_XINVALID
294
295 @groupheader{Example}
296 @snippet doc/xfail.cpp snippet
297 **/
298 //======================================================================================================================
299 #if defined(TTS_DOXYGEN_INVOKED)
300 #define TTS_XFAIL(ID)
301 #else
302 #define TTS_XFAIL(ID) ::tts::expect_fail(ID)
303 #endif
304
305 //======================================================================================================================
306 /**
307 @def TTS_MAYFAIL
308 @brief Tags a @ref TTS_CASE (or @ref TTS_CASE_TPL / @ref TTS_CASE_WITH) ID as allowed to fail.
309
310 Unlike @ref TTS_XFAIL, nothing specific is expected: the case may pass or fail, either is
311 accepted and neither is reported. Useful for flagging a work-in-progress or a known issue
312 that shouldn't block the suite without pretending to require a specific outcome.
313
314 An empty case is still reported, exactly as for an untagged @ref TTS_CASE - `TTS_MAYFAIL`
315 only relaxes the pass/fail expectation, not the "did anything actually run" one.
316
317 @param ID A literal string describing the scenario intents.
318
319 @see TTS_XFAIL
320 @see TTS_XINVALID
321
322 @groupheader{Example}
323 @snippet doc/mayfail.cpp snippet
324 **/
325 //======================================================================================================================
326 #if defined(TTS_DOXYGEN_INVOKED)
327 #define TTS_MAYFAIL(ID)
328 #else
329 #define TTS_MAYFAIL(ID) ::tts::may_fail(ID)
330 #endif
331
332 //======================================================================================================================
333 /**
334 @def TTS_XINVALID
335 @brief Tags a @ref TTS_CASE (or @ref TTS_CASE_TPL / @ref TTS_CASE_WITH) ID as expected to be
336 empty.
337
338 The case is expected to register no assertion at all. If it runs any assertion instead
339 (passing or failing), that mismatch is reported and fails the suite.
340
341 @param ID A literal string describing the scenario intents.
342
343 @see TTS_XFAIL
344 @see TTS_MAYFAIL
345
346 @groupheader{Example}
347 @snippet doc/xinvalid.cpp snippet
348 **/
349 //======================================================================================================================
350 #if defined(TTS_DOXYGEN_INVOKED)
351 #define TTS_XINVALID(ID)
352 #else
353 #define TTS_XINVALID(ID) ::tts::expect_invalid(ID)
354 #endif
355
356 //======================================================================================================================
357 /**
358 @def TTS_TIMEOUT
359 @brief Gives a @ref TTS_CASE (or @ref TTS_CASE_TPL / @ref TTS_CASE_WITH) a deadline of its own.
360
361 The case is killed once it has been running for MS milliseconds, and the run ends there. `0`
362 removes the deadline `--timeout` set for that one case. Wrapping the ID is how the tag travels,
363 so it nests with @ref TTS_XFAIL in either order.
364
365 @param MS Deadline in milliseconds.
366 @param ID A literal string describing the scenario intents.
367
368 @see TTS_XFAIL
369
370 @groupheader{Example}
371 @snippet doc/timeout.cpp snippet
372 **/
373 //======================================================================================================================
374 #if defined(TTS_DOXYGEN_INVOKED)
375 #define TTS_TIMEOUT(MS, ID)
376 #else
377 #define TTS_TIMEOUT(MS, ID) ::tts::with_timeout(MS, ID)
378 #endif
379
380 //======================================================================================================================
381 /// @}
382 //======================================================================================================================
383
384 //======================================================================================================================
385 /// @}
386 //======================================================================================================================
387