Anklang 0.3.0-460-gc4ef46ba
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
testing.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "testing.hh"
3#include "main.hh"
4#include "utils.hh"
5#include "internal.hh"
6#include <algorithm>
7#include <unistd.h>
8#include <stdio.h>
9#include <string.h>
10#include <sys/types.h>
11#include <sys/stat.h>
12#include <fcntl.h>
13#include <float.h>
14#include <ase/randomhash.hh>
15
16#define TDEBUG(...) Ase::debug ("Test", __VA_ARGS__)
17
18namespace Ase {
19
24namespace Test {
25
26Timer::Timer (double deadline_in_secs) :
27 deadline_ (deadline_in_secs), test_duration_ (0), n_reps_ (0)
28{}
29
30Timer::~Timer ()
31{}
32
33double
34Timer::bench_time ()
35{
36 /* timestamp_benchmark() counts nano seconds since program start, so
37 * it's not going to exceed the 52bit double mantissa too fast.
38 */
39 return timestamp_benchmark() / 1000000000.0;
40}
41
42#define DEBUG_LOOPS_NEEDED(...) while (0) printerr (__VA_ARGS__)
43
45Timer::loops_needed ()
46{
47 if (samples_.size() < 3)
48 {
49 n_reps_ = MAX (1, n_reps_);
50 DEBUG_LOOPS_NEEDED ("loops_needed: %d\n", n_reps_);
51 return n_reps_; // force significant number of test runs
52 }
53 double resolution = timestamp_resolution() / 1000000000.0;
54 const double deadline = MAX (deadline_ == 0.0 ? 0.005 : deadline_, resolution * 10000.0);
55 if (test_duration_ < deadline * 0.2)
56 {
57 // increase the number of tests per run to gain more accuracy
58 n_reps_ = MAX (n_reps_ + 1, int64 (n_reps_ * 1.5)) | 1;
59 DEBUG_LOOPS_NEEDED ("loops_needed: %d\n", n_reps_);
60 return n_reps_;
61 }
62 if (test_duration_ < deadline)
63 {
64 DEBUG_LOOPS_NEEDED ("loops_needed: %d\n", n_reps_);
65 return n_reps_;
66 }
67 DEBUG_LOOPS_NEEDED ("loops_needed: %d\n", 0);
68 return 0;
69}
70
71void
72Timer::submit (double elapsed, int64 repetitions)
73{
74 test_duration_ += elapsed;
75 double resolution = timestamp_resolution() / 1000000000.0;
76 if (elapsed >= resolution * 500.0) // force error below 5%
77 samples_.push_back (elapsed / repetitions);
78 else
79 n_reps_ = (n_reps_ + n_reps_) | 1; // double n_reps_ to yield significant times
80}
81
82void
83Timer::reset()
84{
85 samples_.resize (0);
86 test_duration_ = 0;
87 n_reps_ = 0;
88}
89
90double
92{
93 double m = DBL_MAX;
94 for (size_t i = 0; i < samples_.size(); i++)
95 m = MIN (m, samples_[i]);
96 return m;
97}
98
99double
101{
102 double m = 0;
103 for (size_t i = 0; i < samples_.size(); i++)
104 m = MAX (m, samples_[i]);
105 return m;
106}
107
108static String
109ensure_newline (const String &s)
110{
111 if (!s.size() || s[s.size()-1] != '\n')
112 return s + "\n";
113 return s;
114}
115
116static __thread String *thread_test_start = NULL;
117
118void
119test_output (int kind, const String &msg)
120{
121 if (!thread_test_start)
122 thread_test_start = new String();
123 String &test_start = *thread_test_start;
124 String prefix, sout;
125 bool aborting = false;
126 switch (kind)
127 {
128 case 'S': // TSTART()
129 if (!test_start.empty())
130 return test_output ('F', string_format ("Unfinished Test: %s\n", test_start));
131 test_start = msg;
132 sout = " START… " + ensure_newline (msg);
133 break;
134 case 'D': // TDONE() - test passed
135 if (test_start.empty())
136 return test_output ('F', "Extraneous TDONE() call");
137 test_start = "";
138 sout = " …DONE " + ensure_newline (msg);
139 break;
140 case 'I': // TNOTE() - verbose test message
141 if (verbose())
142 sout = " NOTE " + ensure_newline (msg);
143 break;
144 case 'P':
145 sout = " PASS " + ensure_newline (msg);
146 break;
147 case 'B':
148 sout = " BENCH " + ensure_newline (msg);
149 break;
150 case 'F':
151 sout = " FAIL " + ensure_newline (msg);
152 aborting = true;
153 break;
154 default:
155 sout = " INFO " + ensure_newline (msg);
156 break;
157 }
158 if (!sout.empty()) // test message output
159 {
160 fflush (stderr);
161 fputs (sout.c_str(), stdout);
162 fflush (stdout);
163 }
164 if (aborting)
165 {
166 breakpoint();
167 }
168}
169
170bool
172{
173 static bool cached_slow = string_to_bool (String (string_option_find_value (getenv ("ASE_TEST"), "slow", "0", "0", true)));
174 return cached_slow;
175}
176
177bool
179{
180 static bool cached_verbose = string_to_bool (String (string_option_find_value (getenv ("ASE_TEST"), "verbose", "0", "0", true)));
181 return cached_verbose;
182}
183
186{
187 return Ase::random_int64();
188}
189
191random_irange (int64_t begin, int64_t end)
192{
193 return Ase::random_irange (begin, end);
194}
195
196double
198{
199 return Ase::random_float();
200}
201
202double
203random_frange (double begin, double end)
204{
205 return Ase::random_frange (begin, end);
206}
207
208// == TestChain ==
209static const TestChain *global_test_chain = NULL;
210
211TestChain::TestChain (std::function<void()> tfunc, const std::string &tname, Kind kind) :
212 name_ (tname), func_ (tfunc), next_ (global_test_chain), kind_ (kind)
213{
214 assert_return (next_ == global_test_chain);
215 global_test_chain = this;
216}
217
218static bool
219match_testname (const std::string &name, const StringS &test_names)
220{
221 for (const auto &tname : test_names)
222 if (name == tname)
223 return true;
224 return false;
225}
226
227void
228TestChain::run (ptrdiff_t internal_token, const StringS *test_names)
229{
230 assert_return (internal_token == ptrdiff_t (global_test_chain));
232 for (const TestChain *t = global_test_chain; t; t = t->next_)
233 tests.push_back (t);
234 std::sort (tests.begin(), tests.end(), [] (const TestChain *a, const TestChain *b) {
235 return std::string (a->name_) < b->name_;
236 });
237 for (const TestChain *t : tests)
238 {
239 if (test_names && !match_testname (t->name_, *test_names))
240 continue;
241 if (!test_names && (t->kind_ == SLOW ||
242 t->kind_ == BENCH ||
243 t->kind_ == BROKEN))
244 continue;
245 fflush (stderr);
246 printout (" RUN… %s\n", t->name_);
247 fflush (stdout);
248 t->func_();
249 fflush (stderr);
250 printout (" PASS %s\n", t->name_);
251 fflush (stdout);
252 }
253}
254
255int
256run (const StringS &test_names)
257{
258 IntegrityCheck::deferred_init(); // register INTEGRITY tests
259 TestChain::run (ptrdiff_t (global_test_chain), &test_names);
260 return 0;
261}
262
263int
264run (void)
265{
266 IntegrityCheck::deferred_init(); // register INTEGRITY tests
267 TestChain::run (ptrdiff_t (global_test_chain), NULL);
268 return 0;
269}
270
271TestEntries
272list_tests ()
273{
274 IntegrityCheck::deferred_init(); // register INTEGRITY tests
275 TestEntries entries;
276 for (const TestChain *t = global_test_chain; t; t = t->next())
277 {
278 entries.resize (entries.size() + 1);
279 TestEntry &entry = entries.back();
280 entry.ident = t->name();
281 entry.flags = t->flags();
282 }
283 // sort tests by identifier
284 std::stable_sort (entries.begin(), entries.end(), // cmp_lesser
285 [] (const TestEntry &a, const TestEntry &b) {
286 return a.ident < b.ident;
287 });
288 // check for duplicate test names
289 std::string last;
290 for (const auto &entry : entries)
291 if (last == entry.ident)
292 Ase::fatal_error ("duplicate test entry: %s", entry.ident);
293 else
294 last = entry.ident;
295 // sort tests by classification
296 std::stable_sort (entries.begin(), entries.end(), // cmp_lesser
297 [] (const TestEntry &a, const TestEntry &b) {
298 const bool aintegrity = a.flags & INTEGRITY;
299 const bool bintegrity = b.flags & INTEGRITY;
300 return aintegrity > bintegrity; // sort INTEGRITY tests first
301 });
302 return entries;
303}
304
305int
306run_test (const std::string &test_identifier)
307{
308 IntegrityCheck::deferred_init(); // register INTEGRITY tests
309 for (const TestChain *t = global_test_chain; t; t = t->next())
310 if (test_identifier == t->name())
311 {
312 fflush (stderr);
313 printout (" RUN… %s\n", t->name());
314 fflush (stdout);
315 t->run();
316 fflush (stderr);
317 printout (" PASS %s\n", t->name());
318 fflush (stdout);
319 return 1; // ran and passed
320 }
321 return -1; // none found
322}
323
324// == IntegrityCheck ==
325IntegrityCheck *IntegrityCheck::first_ = nullptr; // see internal.hh
326
327void
328IntegrityCheck::deferred_init() // see internal.hh
329{
330 static uint integritycheck_count = [] () {
331 uint c = 0;
332 for (IntegrityCheck *current = first_; current; current = current->next_)
333 {
334 auto *t = new Ase::Test::TestChain (current->func_, current->name_, Ase::Test::INTEGRITY);
335 (void) t; // leak integrity test entries
336 c += 1;
337 }
338 return c;
339 } ();
340 assert_return (integritycheck_count > 0);
341}
342
343} } // Ase::Test
T c_str(T... args)
double min_elapsed() const
Minimum time benchmarked for a callee() call.
Definition testing.cc:91
Timer(double deadline_in_secs=0)
Create a Timer() instance, specifying an optional upper bound for test durations.
Definition testing.cc:26
double max_elapsed() const
Maximum time benchmarked for a callee() call.
Definition testing.cc:100
T empty(T... args)
fflush
fputs
getenv
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
Definition internal.hh:29
#define MIN(a, b)
Yield minimum of a and b.
Definition internal.hh:55
#define MAX(a, b)
Yield maximum of a and b.
Definition internal.hh:52
int run(const StringS &test_names)
Run named tests.
Definition testing.cc:256
bool verbose()
Indicates whether tests should run verbosely.
Definition testing.cc:178
int64_t random_irange(int64_t begin, int64_t end)
Return random int within range for reproduceble tests.
Definition testing.cc:191
uint64_t random_int64()
Return random int for reproduceble tests.
Definition testing.cc:185
double random_float()
Return random double for reproduceble tests.
Definition testing.cc:197
double random_frange(double begin, double end)
Return random double within range for reproduceble tests.
Definition testing.cc:203
bool slow()
Indicates whether slow tests should be run.
Definition testing.cc:171
The Anklang C++ API namespace.
Definition api.hh:9
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
uint64 timestamp_resolution()
Provide resolution of timestamp_benchmark() in nano-seconds.
Definition platform.cc:593
void breakpoint()
Cause a debugging breakpoint, for development only.
Definition platform.hh:202
uint64 timestamp_benchmark()
Returns benchmark timestamp in nano-seconds, clock starts around program startup.
Definition platform.cc:601
std::vector< String > StringS
Convenience alias for a std::vector<std::string>.
Definition cxxaux.hh:36
int64_t int64
A 64-bit unsigned integer, use PRI*64 in format strings.
Definition cxxaux.hh:29
int64_t random_irange(int64_t begin, int64_t end)
std::string String
Convenience alias for std::string.
Definition cxxaux.hh:35
bool string_to_bool(const String &string, bool fallback)
Definition strings.cc:467
double random_float()
uint32_t uint
Provide 'uint' as convenience type.
Definition cxxaux.hh:18
double random_frange(double begin, double end)
std::string_view string_option_find_value(const char *string, const char *feature, const char *fallback, const char *denied, bool matchallnone)
Low level option search, avoids dynamic allocations.
Definition strings.cc:1397
uint64_t random_int64()
T push_back(T... args)
T resize(T... args)
T size(T... args)
T sort(T... args)
T stable_sort(T... args)
typedef uint64_t