|
TTS v4.0.0
The Tiny Test System
|
By default, TTS provides an entry point function for the listed tests. However, it may be required to handle such an entry point. In this case, one can define the TTS_CUSTOM_DRIVER_FUNCTION preprocessor symbol to a name of their own entry-point function as shown below.
After defining the TTS_CUSTOM_DRIVER_FUNCTION symbol, tests can be added as usual. Then, a regular main function is to be defined. This function will then perform any special operations required, then call the specified entry point function. Finally, the main function will call tts::report which will aggregate test results and validate the whole tests with respect to expected numbers of failures and invalid tests.
A crashed test case cannot resume, so TTS names it, reports it through the sinks and exits with 1. What the run already printed stays, the cases left are counted as not run, and the summary is written. A case tagged TTS_XFAIL or TTS_MAYFAIL turns that end into the expected one, and the run exits 0.
tts::set_abort_handler runs just before the exit, with the signal that brought the run down or 0 when nothing did. A test binary that has to handle an abrupt stop in its own way installs one.
The guard covers SIGSEGV, SIGBUS, SIGFPE, SIGILL and SIGABRT, that last one catching an assert in the code under test; under Windows, access violations, stack overflows, integer divisions by zero and illegal instructions. Under emscripten it is compiled out.
--no-crash-guard, or TTS_NO_CRASH_GUARD in the environment, leaves those signals to the system: that is what a debugger, or a sanitizer stack trace, needs.
A case that hangs cannot resume either, and nothing outside the process can name it. --timeout=<ms>, or TTS_TIMEOUT in the environment, gives every case a deadline; past it the case is named the same way a crash is, and the run ends the same way. TTS_TIMEOUT sets one case's deadline, and 0 exempts that case from the global one. The handler receives 0 rather than a signal number, nothing having been raised.
Under POSIX the deadline is an interval timer, so a case blocked in a system call is still interrupted; code under test that uses SIGALRM itself conflicts with it. Under Windows it is a timer queue. Under emscripten it is compiled out.
By default, whenever TTS needs to display a value in a report, it uses std::to_string or, in the case of sequence-like types, a sequence of calls to std::to_string. In case no overload for std::to_string exists for a given type, a string will be built from the type name and its byte sequence.
In the case a given type needs to be displayed in a specific manner, specialize tts::display for it and TTS will use it when necessary. The to_text overload this replaces is gone: one left behind is reported where it would have been used.
If needed, one can delegate a part of this string construction to the TTS internal string conversion function tts::as_text that will use all runtime options for display. tts::text can also be constructed from a formatting specification and other similar setup.
Beware that, in this situation, command-line arguments controlling value display like -x or -s will not be applied to the formatted string.
All equality-based checks in TTS use the compared value operator==. If needed, specialize tts::comparison to let TTS use a special comparison scheme. The trait takes both operand types, in the order they are written, and the second one defaults to the first, so a homogeneous comparison names a single type.
Similarly, TTS uses operator< to build all its ordering-based checks. The less member of the same trait covers those, and a member the specialization leaves out keeps its default. The compare_equal and compare_less overloads these replace are gone: one left behind is reported where it would have been used.
TTS_BIT_EQUAL and TTS_BIT_NOT_EQUAL use the bit_equal member of the same trait. It defaults to a memcmp over the whole object, so a type whose storage holds padding, such as a SIMD register wider than the lanes it carries, compares bytes that no operation ever writes and has to specialize it.
When dealing with floating point values, TTS measures all ULP checks through the ulp member of tts::precision. Specialize the trait to let TTS use a special ULP comparison scheme; the three members left out keep their defaults. One can also reuse the pre-existing tts::ulp_check to implement their own.
IEEE comparison consists in checking for exact equality while considering all NaN/Invalid values of floating point values. It is the ieee member of tts::precision. One can also reuse the pre-existing tts::ieee_check to implement their own.
Relative precision checks within TTS are done through the relative member of tts::precision, which reports a ratio rather than a percentage. One can also reuse the pre-existing tts::relative_check to implement their own.
Absolute precision checks within TTS are done through the absolute member of tts::precision. One can also reuse the pre-existing tts::absolute_check to implement their own.
The four free functions ulp_distance, ieee_equal, relative_distance and absolute_distance are gone. One left behind is reported where it would have been used, rather than ignored in silence, so a suite carrying them says so at the first check that needs it.
Test cases based on data sets and range checks require one or more data generators to perform. If the pre-existing data generators are not suitable, define your own by providing a constexpr callable object with the following signature:
where:
For example, the following code defines a generator that will generate values alternating between -1 and 1 every n iterations.
A generator answers for the type it is handed, so a type TTS knows nothing about needs to say how it is built. Specialize tts::generation for it. A type without a specialization keeps the default way of building, which hands a scalar straight to the generator and fills a sequence element by element.
Generator bounds go through tts::conversion, which casts a plain value to the type under test. Specialize it when a bound is a recipe rather than a value, so that a bound written once in a case answers for each type the case runs on.
produce and convert_as are the dispatchers rather than customization points, and TTS reaches them qualified, so an overload of either name in another namespace is never found. They carry a generic default, which is why a leftover cannot be reported the way the other traits report theirs.
A specialization of tts::generation keys on the type being built and cannot see the generator in an overload set. When one has to be treated apart, tts::is_randoms names the range generator by its type.