GCC Code Coverage Report


Directory: include/tts/
File: include/tts/tools/options.hpp
Date: 2026-10-14 16:28:14
Exec Total Coverage
Lines: 87 88 98.9%
Functions: 26 28 92.9%
Branches: 57 71 80.3%

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 #include <tts/tools/output.hpp>
11 #include <tts/tools/preprocessor.hpp>
12
13 TTS_DISABLE_WARNING_PUSH
14 TTS_DISABLE_WARNING_CRT_SECURE
15
16 namespace tts::_
17 {
18 struct option
19 {
20 1701 option() = default;
21
22 // Parses a raw CLI argument, e.g. "--capture=report.txt" or "--verbose".
23 51 explicit option(char const* arg)
24 51 : token(arg)
25 51 , position(-1)
26 {
27
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
51 assert(arg && "Token cannot be null");
28 51 auto it = strchr(arg, '=');
29
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 5 times.
51 position = it ? static_cast<int>(it - token) : static_cast<int>(strlen(token)); // NOSONAR
30 51 }
31
32 // Wraps an already-split name/value pair (e.g. a flag and the environment variable value
33 // standing in for it) - no '='-parsing needed, name and value are independent pointers.
34 5 option(char const* name, char const* value)
35 5 : token(name)
36 5 , position(static_cast<int>(strlen(name))) // NOSONAR
37 5 , env_value(value)
38 {
39
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 assert(name && "Name cannot be null");
40
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 assert(value && "Value cannot be null");
41 5 }
42
43 67 bool has_flag(char const* f) const
44 {
45
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 67 times.
67 assert(f && "Flag cannot be null");
46
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 67 times.
67 if(position == -1) return false;
47
48 67 int len = static_cast<int>(strlen(f)); // NOSONAR
49
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 53 times.
67 if(len > position) return false;
50
51 53 return strncmp(token, f, static_cast<size_t>(position)) == 0;
52 }
53
54 1738 bool is_valid() const
55 {
56 1738 return position > 0;
57 }
58
59 31 template<typename T> T get(T const& def = T {}) const
60 {
61 31 T that = {};
62
63
1/2
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
31 if(is_valid())
64 {
65
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 13 times.
31 char const* raw = env_value ? env_value : token + position + 1;
66 31 int n = 0;
67 if constexpr(std::integral<T>)
68 {
69 decltype(sizeof(void*)) v;
70 3 n = sscanf(raw, "%zu", &v);
71 3 that = static_cast<T>(v);
72 }
73 else if constexpr(std::floating_point<T>)
74 {
75 double v;
76 4 n = sscanf(raw, "%lf", &v);
77 4 that = static_cast<T>(v);
78 }
79 else
80 {
81 24 n = 1;
82 24 that = T {raw};
83 }
84
85
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
31 if(n != 1) that = def;
86 }
87 else
88 {
89 ✗ that = def;
90 }
91
92 31 return that;
93 }
94
95 char const* token = "";
96 int position = -1;
97 char const* env_value = nullptr;
98 };
99
100 // Derives "TTS_<NAME>" from a long-form flag like "--shard" (-> "TTS_SHARD") or
101 // "--allow-empty" (-> "TTS_ALLOW_EMPTY"). Returns an empty text for short flags (single
102 // dash, e.g. "-v") since those don't map to a sensible environment variable name.
103 2561 inline text env_var_name(char const* flag)
104 {
105
6/8
✓ Branch 0 taken 2560 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2560 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1705 times.
✓ Branch 5 taken 855 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1705 times.
2561 if(!flag || flag[ 0 ] != '-' || flag[ 1 ] != '-' || flag[ 2 ] == '\0') return text {};
106
107 1705 char buffer[ 64 ] = "TTS_"; // NOSONAR - avoids std::string, this project avoids that header
108 1705 std::size_t pos = 4;
109
2/2
✓ Branch 0 taken 10068 times.
✓ Branch 1 taken 1705 times.
11773 for(char const* p = flag + 2; *p; ++p)
110 {
111
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10068 times.
10068 if(pos >= sizeof(buffer) - 1) break;
112
113 10068 char c = *p;
114
2/2
✓ Branch 0 taken 198 times.
✓ Branch 1 taken 9870 times.
10068 if(c == '-') c = '_';
115
2/4
✓ Branch 0 taken 9870 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9870 times.
✗ Branch 3 not taken.
9870 else if(c >= 'a' && c <= 'z') c = static_cast<char>(c - 'a' + 'A');
116 10068 buffer[ pos++ ] = c;
117 }
118 1705 buffer[ pos ] = '\0';
119
120 1705 return text {buffer};
121 }
122 }
123
124 namespace tts
125 {
126 //====================================================================================================================
127 /**
128 @defgroup tools-config Configuration Utilities
129 **/
130 //====================================================================================================================
131
132 //====================================================================================================================
133 /**
134 @ingroup tools-config
135 @public
136 @brief Command line arguments lightweight processor
137
138 @see option
139
140 @groupheader{Example}
141
142 @code
143 #define TTS_MAIN // No need for main()
144 #include <tts/tts.hpp>
145
146 TTS_CASE( "CLI test - flag" )
147 {
148 // Will fail if the test is not called via
149 // ./mytest.exe --pass--the-test or ./mytest.exe -W
150 TTS_EXPECT( (tts::arguments()("--pass--the-test","-W")) );
151
152 // Will fail if the test is not called via
153 // ./mytest.exe --at_last
154 TTS_EXPECT( tts::arguments()["--at_last"] );
155 };
156
157 TTS_CASE( "CLI test - setting" )
158 {
159 // Will fail if the test is not called via ./mytest.exe --xxx=13.37
160 TTS_EQUAL( tts::arguments().value<double>("--xxx"), 13.37);
161
162 // Will fail if the test is not called via ./mytest.exe --www=9 or ./mytest.exe -zzz=9
163 TTS_EQUAL( (tts::arguments().value(0,"--www","-zzz")), 9);
164 };
165 @endcode
166 **/
167 //====================================================================================================================
168 struct options
169 {
170 /// Checks if the flag `f` is set on the command line
171 bool operator[](char const* f) const
172 {
173 return find(f).is_valid();
174 }
175
176 /// Checks if qny flag `fs` is set on the command line
177 2090 template<std::same_as<char const*>... Flags> bool operator()(Flags... fs) const
178 {
179 2090 return find(fs...).is_valid();
180 }
181
182 /// Returns a value of type `T` if a flag matches any of the strings in `fs` or `T{}` otherwise
183 806 template<typename T, std::same_as<char const*>... Flags> T value(Flags... fs) const
184 {
185 806 T that = {};
186
2/2
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 391 times.
806 if(auto o = find(fs...); o.is_valid()) that = o.template get<T>(that);
187 806 return that;
188 }
189
190 /// Returns a value of type `T` if a flag matches any of the strings in `fs` or `that` otherwise
191 280 template<typename T, std::same_as<char const*>... Flags> T value(T that, Flags... fs) const
192 {
193
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 258 times.
280 if(auto o = find(fs...); o.is_valid()) that = o.template get<T>(that);
194 280 return that;
195 }
196
197 /// Checks if current options set is not empty
198 123 bool is_valid() const
199 {
200
3/4
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 96 times.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
123 return argc && argv != nullptr;
201 }
202
203 int argc;
204 char const** argv;
205
206 private:
207 3412 template<std::same_as<char const*>... Flags> _::option find(Flags... fs) const
208 {
209 3412 char const* flags[] = {fs...};
210
211
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 1703 times.
3482 for(int i = 1; i < argc; ++i)
212 {
213 98 _::option o(argv[ i ]);
214
2/2
✓ Branch 0 taken 62 times.
✓ Branch 1 taken 35 times.
194 for(auto f: flags)
215 {
216
2/2
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 48 times.
124 if(o.has_flag(f)) return o;
217 }
218 }
219
220 // No matching CLI flag: fall back to a TTS_<FLAG_NAME> environment variable, if set.
221 // CLI always takes precedence over the environment.
222
5/5
✓ Branch 1 taken 1701 times.
✓ Branch 2 taken 854 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2557 times.
✓ Branch 5 taken 1701 times.
13520 for(auto f: flags)
223 {
224 5070 auto name = _::env_var_name(f);
225
2/2
✓ Branch 1 taken 854 times.
✓ Branch 2 taken 1703 times.
5070 if(name.is_empty()) continue;
226
227
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1701 times.
3384 if(char const* value = getenv(name.data())) return _::option {f, value};
228 }
229
230 3380 return _::option {};
231 }
232 };
233
234 namespace _
235 {
236 inline options current_arguments = {0, nullptr};
237 inline int current_seed = -1;
238 }
239
240 //====================================================================================================================
241 /**
242 @brief Initializes a TTS test suite
243 @ingroup customization-points
244
245 This function is to be called when writing a custom entry point.
246
247 @param argc Number of command line argument fetched from `main`
248 @param argv Command line arguments pointer fetched from `main`
249
250 @see TTS_CUSTOM_DRIVER_FUNCTION
251 **/
252 //====================================================================================================================
253 123 inline void initialize(int argc, char const** argv)
254 {
255
2/2
✓ Branch 1 taken 96 times.
✓ Branch 2 taken 27 times.
123 if(!_::current_arguments.is_valid()) _::current_arguments = options {argc, argv};
256 123 }
257 }
258
259 namespace tts
260 {
261 //====================================================================================================================
262 /**
263 @ingroup tools-config
264 @public
265 @brief Retrieve the current list of command line argument
266
267 @see options
268 @return The pre-processed command line arguments as a reference to a tts::options instance.
269 **/
270 //====================================================================================================================
271 1713 inline options const& arguments()
272 {
273 1713 return _::current_arguments;
274 }
275
276 //====================================================================================================================
277 /**
278 @ingroup tools-random
279 @public
280 @brief Initialize the random seed for tests
281
282 Initializes and retrieves the random seed used by TTS for random number generation. If the
283 seed has not been initialized yet, it uses the value provided as argument. If that value is
284 -1, it uses the current time as seed. Once initialized, calling this function will return the same
285 seed each time.
286
287 @groupheader{Example}
288 @snippet doc/random_seed.cpp snippet
289
290 @param base_seed Seed to use for random number generations.
291 @return The current random seed used by TTS.
292 **/
293 //====================================================================================================================
294 105 inline int random_seed(int base_seed = -1)
295 {
296
2/2
✓ Branch 0 taken 95 times.
✓ Branch 1 taken 10 times.
105 if(_::current_seed == -1)
297 {
298 95 auto s = arguments().value(base_seed, "--seed");
299
2/2
✓ Branch 0 taken 94 times.
✓ Branch 1 taken 1 times.
95 if(s == -1) s = static_cast<int>(time(nullptr));
300 95 _::current_seed = s;
301 }
302
303 105 return _::current_seed;
304 }
305
306 //====================================================================================================================
307 /**
308 @ingroup tools-config
309 @public
310 @brief Check if verbose mode is enabled
311
312 @return `true` if verbose mode is enabled, `false` otherwise.
313 Verbose mode is enabled when the `--verbose` or `-v` command line argument is provided.
314 **/
315 //====================================================================================================================
316 1602059 inline bool is_verbose()
317 {
318 1602059 return _::current_verbosity.verbose;
319 }
320
321 //====================================================================================================================
322 /**
323 @ingroup tools-config
324 @public
325 @brief Check if quiet mode is enabled
326
327 @return `true` if quiet mode is enabled, `false` otherwise.
328 Quiet mode is enabled when the `--quiet` or `-q` command line argument is provided.
329 **/
330 //====================================================================================================================
331 756 inline bool is_quiet()
332 {
333 756 return _::current_verbosity.quiet;
334 }
335
336 //====================================================================================================================
337 /**
338 @ingroup tools-config
339 @public
340 @brief Check if verbose-only details should be reported
341
342 @return `true` if verbose mode is enabled and quiet mode is not, `false` otherwise. This is the
343 condition under which extra detail lines - like passing test confirmations or type
344 hints - get reported: quiet mode always takes precedence over verbose mode.
345 **/
346 //====================================================================================================================
347 1601709 inline bool is_detailed()
348 {
349
4/4
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 1601685 times.
✓ Branch 4 taken 17 times.
✓ Branch 5 taken 7 times.
1601709 return is_verbose() && !is_quiet();
350 }
351 }
352
353 TTS_DISABLE_WARNING_POP
354