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

randombit / botan / 20295000893

17 Dec 2025 07:24AM UTC coverage: 90.52% (+0.2%) from 90.36%
20295000893

Pull #5167

github

web-flow
Merge 70b91cb18 into 3d96b675e
Pull Request #5167: Changes to reduce unnecessary inclusions

101154 of 111748 relevant lines covered (90.52%)

12785105.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.75
/src/tests/tests.cpp
1
/*
2
* (C) 2014,2015 Jack Lloyd
3
*
4
* Botan is released under the Simplified BSD License (see license.txt)
5
*/
6

7
#include "tests.h"
8

9
#include <botan/hex.h>
10
#include <botan/rng.h>
11
#include <botan/internal/filesystem.h>
12
#include <botan/internal/fmt.h>
13
#include <botan/internal/loadstor.h>
14
#include <botan/internal/parsing.h>
15
#include <botan/internal/stl_util.h>
16
#include <botan/internal/target_info.h>
17
#include <chrono>
18
#include <fstream>
19
#include <iomanip>
20
#include <sstream>
21

22
#if defined(BOTAN_HAS_BIGINT)
23
   #include <botan/bigint.h>
24
#endif
25

26
#if defined(BOTAN_HAS_CPUID)
27
   #include <botan/internal/cpuid.h>
28
#endif
29

30
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
31
   #include <botan/ec_point.h>
32
#endif
33

34
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
35
   #include <stdlib.h>
36
   #include <unistd.h>
37
#endif
38

39
#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
40
   #include <version>
41
   #if defined(__cpp_lib_filesystem)
42
      #include <filesystem>
43
   #endif
44
#endif
45

46
namespace Botan_Tests {
47

48
Test::Test() = default;
412✔
49

50
Test::~Test() = default;
557✔
51

52
Test::Result::Result(std::string who) : m_who(std::move(who)), m_timestamp(Test::timestamp()) {}
74,571✔
53

54
void Test::Result::merge(const Result& other, bool ignore_test_name) {
119,931✔
55
   if(who() != other.who()) {
119,931✔
56
      if(!ignore_test_name) {
73✔
57
         throw Test_Error("Merging tests from different sources");
×
58
      }
59

60
      // When deliberately merging results with different names, the code location is
61
      // likely inconsistent and must be discarded.
62
      m_where.reset();
73✔
63
   } else {
64
      m_where = other.m_where;
119,858✔
65
   }
66

67
   m_timestamp = std::min(m_timestamp, other.m_timestamp);
119,931✔
68
   m_ns_taken += other.m_ns_taken;
119,931✔
69
   m_tests_passed += other.m_tests_passed;
119,931✔
70
   m_fail_log.insert(m_fail_log.end(), other.m_fail_log.begin(), other.m_fail_log.end());
119,931✔
71
   m_log.insert(m_log.end(), other.m_log.begin(), other.m_log.end());
119,931✔
72
}
119,931✔
73

74
void Test::Result::start_timer() {
1,187✔
75
   if(m_started == 0) {
1,187✔
76
      m_started = Test::timestamp();
2,374✔
77
   }
78
}
1,187✔
79

80
void Test::Result::end_timer() {
1,299✔
81
   if(m_started > 0) {
1,299✔
82
      m_ns_taken += Test::timestamp() - m_started;
2,372✔
83
      m_started = 0;
1,186✔
84
   }
85
}
1,299✔
86

87
void Test::Result::test_note(const std::string& note, const char* extra) {
2,766✔
88
   if(!note.empty()) {
2,766✔
89
      std::ostringstream out;
2,766✔
90
      out << who() << " " << note;
2,766✔
91
      if(extra != nullptr) {
2,766✔
92
         out << ": " << extra;
12✔
93
      }
94
      m_log.push_back(out.str());
2,766✔
95
   }
2,766✔
96
}
2,766✔
97

98
void Test::Result::note_missing(const std::string& whatever) {
275✔
99
   static std::set<std::string> s_already_seen;
275✔
100

101
   if(!s_already_seen.contains(whatever)) {
275✔
102
      test_note("Skipping tests due to missing " + whatever);
5✔
103
      s_already_seen.insert(whatever);
5✔
104
   }
105
}
275✔
106

107
bool Test::Result::ThrowExpectations::check(const std::string& test_name, Test::Result& result) {
49,210✔
108
   m_consumed = true;
49,210✔
109

110
   try {
49,210✔
111
      m_fn();
49,210✔
112
      if(!m_expect_success) {
1,646✔
113
         return result.test_failure(test_name + " failed to throw expected exception");
2✔
114
      }
115
   } catch(const std::exception& ex) {
47,564✔
116
      if(m_expect_success) {
47,562✔
117
         return result.test_failure(test_name + " threw unexpected exception: " + ex.what());
2✔
118
      }
119
      if(m_expected_exception_check_fn && !m_expected_exception_check_fn(std::current_exception())) {
134,505✔
120
         return result.test_failure(test_name + " threw unexpected exception: " + ex.what());
4✔
121
      }
122
      if(m_expected_message.has_value() && m_expected_message.value() != ex.what()) {
47,559✔
123
         return result.test_failure(test_name + " threw exception with unexpected message (expected: '" +
3✔
124
                                    m_expected_message.value() + "', got: '" + ex.what() + "')");
6✔
125
      }
126
   } catch(...) {
47,564✔
127
      if(m_expect_success || m_expected_exception_check_fn || m_expected_message.has_value()) {
2✔
128
         return result.test_failure(test_name + " threw unexpected unknown exception");
1✔
129
      }
130
   }
2✔
131

132
   return result.test_success(test_name + " behaved as expected");
98,406✔
133
}
134

135
bool Test::Result::test_throws(const std::string& what, const std::function<void()>& fn) {
3,919✔
136
   return ThrowExpectations(fn).check(what, *this);
7,838✔
137
}
138

139
bool Test::Result::test_throws(const std::string& what, const std::string& expected, const std::function<void()>& fn) {
174✔
140
   return ThrowExpectations(fn).expect_message(expected).check(what, *this);
348✔
141
}
142

143
bool Test::Result::test_no_throw(const std::string& what, const std::function<void()>& fn) {
1,645✔
144
   return ThrowExpectations(fn).expect_success().check(what, *this);
3,290✔
145
}
146

147
bool Test::Result::test_success(const std::string& note) {
3,519,912✔
148
   if(Test::options().log_success()) {
3,519,780✔
149
      test_note(note);
×
150
   }
151
   ++m_tests_passed;
3,519,912✔
152
   return true;
723,924✔
153
}
154

155
bool Test::Result::test_failure(const std::string& what, const std::string& error) {
1✔
156
   return test_failure(who() + " " + what + " with error " + error);
4✔
157
}
158

159
void Test::Result::test_failure(const std::string& what, const uint8_t buf[], size_t buf_len) {
1✔
160
   test_failure(who() + ": " + what + " buf len " + std::to_string(buf_len) + " value " +
5✔
161
                Botan::hex_encode(buf, buf_len));
1✔
162
}
1✔
163

164
bool Test::Result::test_failure(const std::string& err) {
25✔
165
   m_fail_log.push_back(err);
25✔
166

167
   if(Test::options().abort_on_first_fail() && m_who != "Failing Test") {
25✔
168
      std::abort();
×
169
   }
170
   return false;
25✔
171
}
172

173
namespace {
174

175
bool same_contents(const uint8_t x[], const uint8_t y[], size_t len) {
274,485✔
176
   return (len == 0) ? true : std::memcmp(x, y, len) == 0;
273,114✔
177
}
178

179
}  // namespace
180

181
bool Test::Result::test_ne(const std::string& what,
3,409✔
182
                           const uint8_t produced[],
183
                           size_t produced_len,
184
                           const uint8_t expected[],
185
                           size_t expected_len) {
186
   if(produced_len == expected_len && same_contents(produced, expected, expected_len)) {
3,409✔
187
      return test_failure(who() + ": " + what + " produced matching");
6✔
188
   }
189
   return test_success();
6,814✔
190
}
191

192
bool Test::Result::test_eq(const char* producer,
273,040✔
193
                           const std::string& what,
194
                           const uint8_t produced[],
195
                           size_t produced_size,
196
                           const uint8_t expected[],
197
                           size_t expected_size) {
198
   if(produced_size == expected_size && same_contents(produced, expected, expected_size)) {
273,040✔
199
      return test_success();
546,078✔
200
   }
201

202
   std::ostringstream err;
1✔
203

204
   err << who();
1✔
205

206
   if(producer != nullptr) {
1✔
207
      err << " producer '" << producer << "'";
×
208
   }
209

210
   err << " unexpected result for " << what;
1✔
211

212
   if(produced_size != expected_size) {
1✔
213
      err << " produced " << produced_size << " bytes expected " << expected_size;
1✔
214
   }
215

216
   std::vector<uint8_t> xor_diff(std::min(produced_size, expected_size));
2✔
217
   size_t bytes_different = 0;
1✔
218

219
   for(size_t i = 0; i != xor_diff.size(); ++i) {
4✔
220
      xor_diff[i] = produced[i] ^ expected[i];
3✔
221
      if(xor_diff[i] > 0) {
3✔
222
         bytes_different++;
3✔
223
      }
224
   }
225

226
   err << "\nProduced: " << Botan::hex_encode(produced, produced_size)
1✔
227
       << "\nExpected: " << Botan::hex_encode(expected, expected_size);
3✔
228

229
   if(bytes_different > 0) {
1✔
230
      err << "\nXOR Diff: " << Botan::hex_encode(xor_diff);
1✔
231
   }
232

233
   return test_failure(err.str());
1✔
234
}
1✔
235

236
bool Test::Result::test_is_nonempty(const std::string& what_is_it, const std::string& to_examine) {
34,169✔
237
   if(to_examine.empty()) {
34,169✔
238
      return test_failure(what_is_it + " was empty");
1✔
239
   }
240
   return test_success();
68,336✔
241
}
242

243
bool Test::Result::test_eq(const std::string& what, const std::string& produced, const std::string& expected) {
73,010✔
244
   return test_is_eq(what, produced, expected);
73,010✔
245
}
246

247
bool Test::Result::test_eq(const std::string& what, const char* produced, const char* expected) {
29✔
248
   return test_is_eq(what, std::string(produced), std::string(expected));
29✔
249
}
250

251
bool Test::Result::test_eq(const std::string& what, size_t produced, size_t expected) {
136,304✔
252
   return test_is_eq(what, produced, expected);
136,304✔
253
}
254

255
bool Test::Result::test_eq_sz(const std::string& what, size_t produced, size_t expected) {
45,786✔
256
   return test_is_eq(what, produced, expected);
45,786✔
257
}
258

259
bool Test::Result::test_eq(const std::string& what,
107✔
260
                           const Botan::OctetString& produced,
261
                           const Botan::OctetString& expected) {
262
   std::ostringstream out;
107✔
263
   out << m_who << " " << what;
107✔
264

265
   if(produced == expected) {
107✔
266
      out << " produced expected result " << produced.to_string();
214✔
267
      return test_success(out.str());
107✔
268
   } else {
269
      out << " produced unexpected result '" << produced.to_string() << "' expected '" << expected.to_string() << "'";
×
270
      return test_failure(out.str());
×
271
   }
272
}
107✔
273

274
bool Test::Result::test_lt(const std::string& what, size_t produced, size_t expected) {
5,640✔
275
   if(produced >= expected) {
5,640✔
276
      std::ostringstream err;
1✔
277
      err << m_who << " " << what;
1✔
278
      err << " unexpected result " << produced << " >= " << expected;
1✔
279
      return test_failure(err.str());
1✔
280
   }
1✔
281

282
   return test_success();
11,278✔
283
}
284

285
bool Test::Result::test_lte(const std::string& what, size_t produced, size_t expected) {
1,021,636✔
286
   if(produced > expected) {
1,021,636✔
287
      std::ostringstream err;
1✔
288
      err << m_who << " " << what << " unexpected result " << produced << " > " << expected;
1✔
289
      return test_failure(err.str());
1✔
290
   }
1✔
291

292
   return test_success();
2,043,270✔
293
}
294

295
bool Test::Result::test_gte(const std::string& what, size_t produced, size_t expected) {
1,142,074✔
296
   if(produced < expected) {
1,142,074✔
297
      std::ostringstream err;
1✔
298
      err << m_who;
1✔
299
      err << " " << what;
1✔
300
      err << " unexpected result " << produced << " < " << expected;
1✔
301
      return test_failure(err.str());
1✔
302
   }
1✔
303

304
   return test_success();
2,284,146✔
305
}
306

307
bool Test::Result::test_gt(const std::string& what, size_t produced, size_t expected) {
14,957✔
308
   if(produced <= expected) {
14,957✔
309
      std::ostringstream err;
×
310
      err << m_who;
×
311
      err << " " << what;
×
312
      err << " unexpected result " << produced << " <= " << expected;
×
313
      return test_failure(err.str());
×
314
   }
×
315

316
   return test_success();
29,914✔
317
}
318

319
bool Test::Result::test_ne(const std::string& what, const std::string& str1, const std::string& str2) {
26✔
320
   if(str1 != str2) {
26✔
321
      return test_success(str1 + " != " + str2);
50✔
322
   }
323

324
   return test_failure(who() + " " + what + " produced matching strings " + str1);
4✔
325
}
326

327
bool Test::Result::test_ne(const std::string& what, size_t produced, size_t expected) {
118✔
328
   if(produced != expected) {
118✔
329
      return test_success();
234✔
330
   }
331

332
   std::ostringstream err;
1✔
333
   err << who() << " " << what << " produced " << produced << " unexpected value";
1✔
334
   return test_failure(err.str());
1✔
335
}
1✔
336

337
#if defined(BOTAN_HAS_BIGINT)
338
bool Test::Result::test_eq(const std::string& what, const BigInt& produced, const BigInt& expected) {
252,641✔
339
   return test_is_eq(what, produced, expected);
252,641✔
340
}
341

342
bool Test::Result::test_ne(const std::string& what, const BigInt& produced, const BigInt& expected) {
97✔
343
   if(produced != expected) {
97✔
344
      return test_success();
192✔
345
   }
346

347
   std::ostringstream err;
1✔
348
   err << who() << " " << what << " produced " << produced << " prohibited value";
1✔
349
   return test_failure(err.str());
1✔
350
}
1✔
351
#endif
352

353
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
354
bool Test::Result::test_eq(const std::string& what, const Botan::EC_Point& a, const Botan::EC_Point& b) {
3,248✔
355
   //return test_is_eq(what, a, b);
356
   if(a == b) {
3,248✔
357
      return test_success();
6,496✔
358
   }
359

360
   std::ostringstream err;
×
361
   err << who() << " " << what << " a=(" << a.get_affine_x() << "," << a.get_affine_y() << ")"
×
362
       << " b=(" << b.get_affine_x() << "," << b.get_affine_y();
×
363
   return test_failure(err.str());
×
364
}
×
365
#endif
366

367
bool Test::Result::test_eq(const std::string& what, bool produced, bool expected) {
349,042✔
368
   return test_is_eq(what, produced, expected);
349,042✔
369
}
370

371
bool Test::Result::test_rc_init(const std::string& func, int rc) {
110✔
372
   if(rc == 0) {
110✔
373
      return test_success();
220✔
374
   } else {
375
      std::ostringstream msg;
×
376
      msg << m_who;
×
377
      msg << " " << func;
×
378

379
      // -40 is BOTAN_FFI_ERROR_NOT_IMPLEMENTED
380
      if(rc == -40) {
×
381
         msg << " returned not implemented";
×
382
      } else {
383
         msg << " unexpectedly failed with error code " << rc;
×
384
      }
385

386
      if(rc == -40) {
×
387
         this->test_note(msg.str());
×
388
      } else {
389
         this->test_failure(msg.str());
×
390
      }
391
      return false;
×
392
   }
×
393
}
394

395
bool Test::Result::test_rc(const std::string& func, int expected, int rc) {
382✔
396
   if(expected != rc) {
382✔
397
      std::ostringstream err;
1✔
398
      err << m_who;
1✔
399
      err << " call to " << func << " unexpectedly returned " << rc;
1✔
400
      err << " but expecting " << expected;
1✔
401
      return test_failure(err.str());
1✔
402
   }
1✔
403

404
   return test_success();
762✔
405
}
406

407
void Test::initialize(std::string test_name, CodeLocation location) {
412✔
408
   m_test_name = std::move(test_name);
412✔
409
   m_registration_location = location;
412✔
410
}
412✔
411

412
Botan::RandomNumberGenerator& Test::rng() const {
454,549✔
413
   if(!m_test_rng) {
454,549✔
414
      m_test_rng = Test::new_rng(m_test_name);
145✔
415
   }
416

417
   return *m_test_rng;
454,549✔
418
}
419

420
std::vector<uint8_t> Test::mutate_vec(const std::vector<uint8_t>& v,
83,765✔
421
                                      Botan::RandomNumberGenerator& rng,
422
                                      bool maybe_resize,
423
                                      size_t min_offset) {
424
   std::vector<uint8_t> r = v;
83,765✔
425

426
   if(maybe_resize && (r.empty() || rng.next_byte() < 32)) {
93,693✔
427
      // TODO: occasionally truncate, insert at random index
428
      const size_t add = 1 + (rng.next_byte() % 16);
1,662✔
429
      r.resize(r.size() + add);
1,662✔
430
      rng.randomize(&r[r.size() - add], add);
1,662✔
431
   }
432

433
   if(r.size() > min_offset) {
83,765✔
434
      const size_t offset = std::max<size_t>(min_offset, rng.next_byte() % r.size());
82,343✔
435
      const uint8_t perturb = rng.next_nonzero_byte();
82,343✔
436
      r[offset] ^= perturb;
82,343✔
437
   }
438

439
   return r;
83,765✔
440
}
×
441

442
std::vector<std::string> Test::possible_providers(const std::string& /*alg*/) {
×
443
   return Test::provider_filter({"base"});
×
444
}
445

446
//static
447
std::string Test::format_time(uint64_t nanoseconds) {
1,465✔
448
   std::ostringstream o;
1,465✔
449

450
   if(nanoseconds > 1000000000) {
1,465✔
451
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000000.0 << " sec";
151✔
452
   } else {
453
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000.0 << " msec";
1,314✔
454
   }
455

456
   return o.str();
2,930✔
457
}
1,465✔
458

459
Test::Result::Result(std::string who, const std::vector<Result>& downstream_results) : Result(std::move(who)) {
14✔
460
   for(const auto& result : downstream_results) {
82✔
461
      merge(result, true /* ignore non-matching test names */);
68✔
462
   }
463
}
14✔
464

465
// TODO: this should move to `StdoutReporter`
466
std::string Test::Result::result_string() const {
2,533✔
467
   const bool verbose = Test::options().verbose();
2,533✔
468

469
   if(tests_run() == 0 && !verbose) {
2,533✔
470
      return "";
20✔
471
   }
472

473
   std::ostringstream report;
2,513✔
474

475
   report << who() << " ran ";
2,513✔
476

477
   if(tests_run() == 0) {
2,513✔
478
      report << "ZERO";
×
479
   } else {
480
      report << tests_run();
2,513✔
481
   }
482
   report << " tests";
2,513✔
483

484
   if(m_ns_taken > 0) {
2,513✔
485
      report << " in " << format_time(m_ns_taken);
2,928✔
486
   }
487

488
   if(tests_failed() > 0) {
2,513✔
489
      report << " " << tests_failed() << " FAILED";
25✔
490
   } else {
491
      report << " all ok";
2,488✔
492
   }
493

494
   report << "\n";
2,513✔
495

496
   for(size_t i = 0; i != m_fail_log.size(); ++i) {
2,538✔
497
      report << "Failure " << (i + 1) << ": " << m_fail_log[i];
25✔
498
      if(m_where) {
25✔
499
         report << " (at " << m_where->path << ":" << m_where->line << ")";
×
500
      }
501
      report << "\n";
25✔
502
   }
503

504
   if(!m_fail_log.empty() || tests_run() == 0 || verbose) {
2,513✔
505
      for(size_t i = 0; i != m_log.size(); ++i) {
25✔
506
         report << "Note " << (i + 1) << ": " << m_log[i] << "\n";
×
507
      }
508
   }
509

510
   return report.str();
2,513✔
511
}
2,513✔
512

513
namespace {
514

515
class Test_Registry {
516
   public:
517
      static Test_Registry& instance() {
1,254✔
518
         static Test_Registry registry;
1,255✔
519
         return registry;
1,254✔
520
      }
521

522
      void register_test(const std::string& category,
412✔
523
                         const std::string& name,
524
                         bool smoke_test,
525
                         bool needs_serialization,
526
                         std::function<std::unique_ptr<Test>()> maker_fn) {
527
         if(m_tests.contains(name)) {
412✔
528
            throw Test_Error("Duplicate registration of test '" + name + "'");
×
529
         }
530

531
         if(m_tests.contains(category)) {
412✔
532
            throw Test_Error("'" + category + "' cannot be used as category, test exists");
×
533
         }
534

535
         if(m_categories.contains(name)) {
412✔
536
            throw Test_Error("'" + name + "' cannot be used as test name, category exists");
×
537
         }
538

539
         if(smoke_test) {
412✔
540
            m_smoke_tests.push_back(name);
10✔
541
         }
542

543
         if(needs_serialization) {
412✔
544
            m_mutexed_tests.push_back(name);
21✔
545
         }
546

547
         m_tests.emplace(name, std::move(maker_fn));
412✔
548
         m_categories.emplace(category, name);
412✔
549
      }
412✔
550

551
      std::unique_ptr<Test> get_test(const std::string& test_name) const {
412✔
552
         auto i = m_tests.find(test_name);
412✔
553
         if(i != m_tests.end()) {
412✔
554
            return i->second();
412✔
555
         }
556
         return nullptr;
×
557
      }
558

559
      std::set<std::string> registered_tests() const {
×
560
         std::set<std::string> s;
×
561
         for(auto&& i : m_tests) {
×
562
            s.insert(i.first);
×
563
         }
564
         return s;
×
565
      }
×
566

567
      std::set<std::string> registered_test_categories() const {
×
568
         std::set<std::string> s;
×
569
         for(auto&& i : m_categories) {
×
570
            s.insert(i.first);
×
571
         }
572
         return s;
×
573
      }
×
574

575
      std::vector<std::string> filter_registered_tests(const std::vector<std::string>& requested,
1✔
576
                                                       const std::set<std::string>& to_be_skipped) {
577
         std::vector<std::string> result;
1✔
578

579
         // TODO: this is O(n^2), but we have a relatively small number of tests.
580
         auto insert_if_not_exists_and_not_skipped = [&](const std::string& test_name) {
413✔
581
            if(!Botan::value_exists(result, test_name) && !to_be_skipped.contains(test_name)) {
412✔
582
               result.push_back(test_name);
402✔
583
            }
584
         };
413✔
585

586
         if(requested.empty()) {
1✔
587
            /*
588
            If nothing was requested on the command line, run everything. First
589
            run the "essentials" to smoke test, then everything else in
590
            alphabetical order.
591
            */
592
            result = m_smoke_tests;
1✔
593
            for(const auto& [test_name, _] : m_tests) {
413✔
594
               insert_if_not_exists_and_not_skipped(test_name);
412✔
595
            }
596
         } else {
597
            for(const auto& r : requested) {
×
598
               if(m_tests.contains(r)) {
×
599
                  insert_if_not_exists_and_not_skipped(r);
×
600
               } else if(auto elems = m_categories.equal_range(r); elems.first != m_categories.end()) {
×
601
                  for(; elems.first != elems.second; ++elems.first) {
×
602
                     insert_if_not_exists_and_not_skipped(elems.first->second);
×
603
                  }
604
               } else {
605
                  throw Test_Error("Unknown test suite or category: " + r);
×
606
               }
607
            }
608
         }
609

610
         return result;
1✔
611
      }
×
612

613
      bool needs_serialization(const std::string& test_name) const {
429✔
614
         return Botan::value_exists(m_mutexed_tests, test_name);
17✔
615
      }
616

617
   private:
618
      Test_Registry() = default;
1✔
619

620
   private:
621
      std::map<std::string, std::function<std::unique_ptr<Test>()>> m_tests;
622
      std::multimap<std::string, std::string> m_categories;
623
      std::vector<std::string> m_smoke_tests;
624
      std::vector<std::string> m_mutexed_tests;
625
};
626

627
}  // namespace
628

629
// static Test:: functions
630

631
//static
632
void Test::register_test(const std::string& category,
412✔
633
                         const std::string& name,
634
                         bool smoke_test,
635
                         bool needs_serialization,
636
                         std::function<std::unique_ptr<Test>()> maker_fn) {
637
   Test_Registry::instance().register_test(category, name, smoke_test, needs_serialization, std::move(maker_fn));
824✔
638
}
412✔
639

640
//static
641
uint64_t Test::timestamp() {
172,880✔
642
   auto now = std::chrono::high_resolution_clock::now().time_since_epoch();
172,880✔
643
   return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
2,373✔
644
}
645

646
//static
647
std::vector<Test::Result> Test::flatten_result_lists(std::vector<std::vector<Test::Result>> result_lists) {
4✔
648
   std::vector<Test::Result> results;
4✔
649
   for(auto& result_list : result_lists) {
26✔
650
      for(auto& result : result_list) {
71✔
651
         results.emplace_back(std::move(result));
49✔
652
      }
653
   }
654
   return results;
4✔
655
}
×
656

657
//static
658
std::set<std::string> Test::registered_tests() {
×
659
   return Test_Registry::instance().registered_tests();
×
660
}
661

662
//static
663
std::set<std::string> Test::registered_test_categories() {
×
664
   return Test_Registry::instance().registered_test_categories();
×
665
}
666

667
//static
668
std::unique_ptr<Test> Test::get_test(const std::string& test_name) {
412✔
669
   return Test_Registry::instance().get_test(test_name);
412✔
670
}
671

672
//static
673
bool Test::test_needs_serialization(const std::string& test_name) {
412✔
674
   return Test_Registry::instance().needs_serialization(test_name);
412✔
675
}
676

677
//static
678
std::vector<std::string> Test::filter_registered_tests(const std::vector<std::string>& requested,
1✔
679
                                                       const std::set<std::string>& to_be_skipped) {
680
   return Test_Registry::instance().filter_registered_tests(requested, to_be_skipped);
1✔
681
}
682

683
//static
684
std::string Test::temp_file_name(const std::string& basename) {
21✔
685
   // TODO add a --tmp-dir option to the tests to specify where these files go
686

687
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
688

689
   // POSIX only calls for 6 'X' chars but OpenBSD allows arbitrary amount
690
   std::string mkstemp_basename = "/tmp/" + basename + ".XXXXXXXXXX";
42✔
691

692
   const int fd = ::mkstemp(mkstemp_basename.data());
21✔
693

694
   // error
695
   if(fd < 0) {
21✔
696
      return "";
×
697
   }
698

699
   ::close(fd);
21✔
700

701
   return mkstemp_basename;
21✔
702
#else
703
   // For now just create the temp in the current working directory
704
   return basename;
705
#endif
706
}
21✔
707

708
bool Test::copy_file(const std::string& from, const std::string& to) {
1✔
709
#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(__cpp_lib_filesystem)
710
   std::error_code ec;  // don't throw, just return false on error
1✔
711
   return std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing, ec);
1✔
712
#else
713
   // TODO: implement fallbacks to POSIX or WIN32
714
   // ... but then again: it's 2023 and we're using C++20 :o)
715
   BOTAN_UNUSED(from, to);
716
   throw Botan::No_Filesystem_Access();
717
#endif
718
}
719

720
std::string Test::read_data_file(const std::string& path) {
38✔
721
   const std::string fsname = Test::data_file(path);
38✔
722
   std::ifstream file(fsname.c_str());
38✔
723
   if(!file.good()) {
38✔
724
      throw Test_Error("Error reading from " + fsname);
×
725
   }
726

727
   return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
76✔
728
}
38✔
729

730
std::vector<uint8_t> Test::read_binary_data_file(const std::string& path) {
34✔
731
   const std::string fsname = Test::data_file(path);
34✔
732
   std::ifstream file(fsname.c_str(), std::ios::binary);
34✔
733
   if(!file.good()) {
34✔
734
      throw Test_Error("Error reading from " + fsname);
×
735
   }
736

737
   std::vector<uint8_t> contents;
34✔
738

739
   while(file.good()) {
74✔
740
      std::vector<uint8_t> buf(4096);
40✔
741
      file.read(reinterpret_cast<char*>(buf.data()), buf.size());
40✔
742
      const size_t got = static_cast<size_t>(file.gcount());
40✔
743

744
      if(got == 0 && file.eof()) {
40✔
745
         break;
746
      }
747

748
      contents.insert(contents.end(), buf.data(), buf.data() + got);
40✔
749
   }
40✔
750

751
   return contents;
68✔
752
}
34✔
753

754
// static member variables of Test
755

756
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
757
Test_Options Test::m_opts;
758
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
759
std::string Test::m_test_rng_seed;
760

761
//static
762
void Test::set_test_options(const Test_Options& opts) {
1✔
763
   m_opts = opts;
1✔
764
}
1✔
765

766
namespace {
767

768
/*
769
* This is a fast, simple, deterministic PRNG that's used for running
770
* the tests. It is not intended to be cryptographically secure.
771
*/
772
class Testsuite_RNG final : public Botan::RandomNumberGenerator {
8✔
773
   public:
774
      std::string name() const override { return "Testsuite_RNG"; }
×
775

776
      void clear() override { m_x = 0; }
×
777

778
      bool accepts_input() const override { return true; }
×
779

780
      bool is_seeded() const override { return true; }
276,307✔
781

782
      void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> input) override {
10,168,166✔
783
         for(const auto byte : input) {
10,168,166✔
784
            mix(byte);
×
785
         }
786

787
         for(auto& byte : output) {
72,561,720✔
788
            byte = mix();
62,393,554✔
789
         }
790
      }
10,168,166✔
791

792
      Testsuite_RNG(std::string_view seed, std::string_view test_name) : m_x(0) {
276✔
793
         for(const char c : seed) {
8,280✔
794
            this->mix(static_cast<uint8_t>(c));
8,004✔
795
         }
796
         for(const char c : test_name) {
5,578✔
797
            this->mix(static_cast<uint8_t>(c));
5,302✔
798
         }
799
      }
276✔
800

801
   private:
802
      uint8_t mix(uint8_t input = 0) {
62,406,860✔
803
         m_x ^= input;
62,406,860✔
804
         m_x *= 0xF2E16957;
62,406,860✔
805
         m_x += 0xE50B590F;
62,406,860✔
806
         return static_cast<uint8_t>(m_x >> 27);
62,406,860✔
807
      }
808

809
      uint64_t m_x;
810
};
811

812
}  // namespace
813

814
//static
815
void Test::set_test_rng_seed(std::span<const uint8_t> seed, size_t epoch) {
1✔
816
   m_test_rng_seed = Botan::fmt("seed={} epoch={}", Botan::hex_encode(seed), epoch);
1✔
817
}
1✔
818

819
//static
820
std::unique_ptr<Botan::RandomNumberGenerator> Test::new_rng(std::string_view test_name) {
268✔
821
   return std::make_unique<Testsuite_RNG>(m_test_rng_seed, test_name);
268✔
822
}
823

824
//static
825
std::shared_ptr<Botan::RandomNumberGenerator> Test::new_shared_rng(std::string_view test_name) {
8✔
826
   return std::make_shared<Testsuite_RNG>(m_test_rng_seed, test_name);
8✔
827
}
828

829
//static
830
std::string Test::data_file(const std::string& file) {
759✔
831
   return options().data_dir() + "/" + file;
1,518✔
832
}
833

834
//static
835
std::string Test::data_dir(const std::string& subdir) {
1✔
836
   return options().data_dir() + "/" + subdir;
2✔
837
}
838

839
//static
840
std::vector<std::string> Test::files_in_data_dir(const std::string& subdir) {
393✔
841
   auto fs = Botan::get_files_recursive(options().data_dir() + "/" + subdir);
1,179✔
842
   if(fs.empty()) {
393✔
843
      throw Test_Error("Test::files_in_data_dir encountered empty subdir " + subdir);
×
844
   }
845
   return fs;
393✔
846
}
×
847

848
//static
849
std::string Test::data_file_as_temporary_copy(const std::string& what) {
1✔
850
   auto tmp_basename = what;
1✔
851
   std::replace(tmp_basename.begin(), tmp_basename.end(), '/', '_');
1✔
852
   auto temp_file = temp_file_name("tmp-" + tmp_basename);
1✔
853
   if(temp_file.empty()) {
1✔
854
      return "";
×
855
   }
856
   if(!Test::copy_file(data_file(what), temp_file)) {
1✔
857
      return "";
×
858
   }
859
   return temp_file;
1✔
860
}
1✔
861

862
//static
863
std::vector<std::string> Test::provider_filter(const std::vector<std::string>& providers) {
48,067✔
864
   if(m_opts.provider().empty()) {
48,067✔
865
      return providers;
48,067✔
866
   }
867
   for(auto&& provider : providers) {
×
868
      if(provider == m_opts.provider()) {
×
869
         return std::vector<std::string>{provider};
×
870
      }
871
   }
872
   return std::vector<std::string>{};
×
873
}
×
874

875
std::string Test::random_password(Botan::RandomNumberGenerator& rng) {
222✔
876
   const size_t len = 1 + rng.next_byte() % 32;
222✔
877
   return Botan::hex_encode(rng.random_vec(len));
444✔
878
}
879

880
size_t Test::random_index(Botan::RandomNumberGenerator& rng, size_t max) {
8,062✔
881
   return Botan::load_be(rng.random_array<8>()) % max;
8,062✔
882
}
883

884
std::vector<std::vector<uint8_t>> VarMap::get_req_bin_list(const std::string& key) const {
12✔
885
   auto i = m_vars.find(key);
12✔
886
   if(i == m_vars.end()) {
12✔
887
      throw Test_Error("Test missing variable " + key);
×
888
   }
889

890
   std::vector<std::vector<uint8_t>> bin_list;
12✔
891

892
   for(auto&& part : Botan::split_on(i->second, ',')) {
62✔
893
      try {
50✔
894
         bin_list.push_back(Botan::hex_decode(part));
100✔
895
      } catch(std::exception& e) {
×
896
         std::ostringstream oss;
×
897
         oss << "Bad input '" << part << "'"
×
898
             << " in binary list key " << key << " - " << e.what();
×
899
         throw Test_Error(oss.str());
×
900
      }
×
901
   }
12✔
902

903
   return bin_list;
12✔
904
}
×
905

906
std::vector<uint8_t> VarMap::get_req_bin(const std::string& key) const {
155,623✔
907
   auto i = m_vars.find(key);
155,623✔
908
   if(i == m_vars.end()) {
155,623✔
909
      throw Test_Error("Test missing variable " + key);
×
910
   }
911

912
   try {
155,623✔
913
      if(i->second.starts_with("0x")) {
155,623✔
914
         if(i->second.size() % 2 == 0) {
×
915
            return Botan::hex_decode(i->second.substr(2));
×
916
         } else {
917
            std::string z = i->second;
×
918
            std::swap(z[0], z[1]);  // swap 0x to x0 then remove x
×
919
            return Botan::hex_decode(z.substr(1));
×
920
         }
×
921
      } else {
922
         return Botan::hex_decode(i->second);
155,623✔
923
      }
924
   } catch(std::exception& e) {
×
925
      std::ostringstream oss;
×
926
      oss << "Bad input '" << i->second << "'"
×
927
          << " for key " << key << " - " << e.what();
×
928
      throw Test_Error(oss.str());
×
929
   }
×
930
}
×
931

932
std::string VarMap::get_opt_str(const std::string& key, const std::string& def_value) const {
14,376✔
933
   auto i = m_vars.find(key);
14,376✔
934
   if(i == m_vars.end()) {
14,376✔
935
      return def_value;
14,321✔
936
   }
937
   return i->second;
55✔
938
}
939

940
bool VarMap::get_req_bool(const std::string& key) const {
39✔
941
   auto i = m_vars.find(key);
39✔
942
   if(i == m_vars.end()) {
39✔
943
      throw Test_Error("Test missing variable " + key);
×
944
   }
945

946
   if(i->second == "true") {
39✔
947
      return true;
948
   } else if(i->second == "false") {
23✔
949
      return false;
950
   } else {
951
      throw Test_Error("Invalid boolean for key '" + key + "' value '" + i->second + "'");
×
952
   }
953
}
954

955
size_t VarMap::get_req_sz(const std::string& key) const {
4,547✔
956
   auto i = m_vars.find(key);
4,547✔
957
   if(i == m_vars.end()) {
4,547✔
958
      throw Test_Error("Test missing variable " + key);
×
959
   }
960
   return Botan::to_u32bit(i->second);
4,547✔
961
}
962

963
uint8_t VarMap::get_req_u8(const std::string& key) const {
17✔
964
   const size_t s = this->get_req_sz(key);
17✔
965
   if(s > 256) {
17✔
966
      throw Test_Error("Invalid " + key + " expected uint8_t got " + std::to_string(s));
×
967
   }
968
   return static_cast<uint8_t>(s);
17✔
969
}
970

971
uint32_t VarMap::get_req_u32(const std::string& key) const {
14✔
972
   return static_cast<uint32_t>(get_req_sz(key));
14✔
973
}
974

975
uint64_t VarMap::get_req_u64(const std::string& key) const {
17✔
976
   auto i = m_vars.find(key);
17✔
977
   if(i == m_vars.end()) {
17✔
978
      throw Test_Error("Test missing variable " + key);
×
979
   }
980
   try {
17✔
981
      return std::stoull(i->second);
17✔
982
   } catch(std::exception&) {
×
983
      throw Test_Error("Invalid u64 value '" + i->second + "'");
×
984
   }
×
985
}
986

987
size_t VarMap::get_opt_sz(const std::string& key, const size_t def_value) const {
31,379✔
988
   auto i = m_vars.find(key);
31,379✔
989
   if(i == m_vars.end()) {
31,379✔
990
      return def_value;
991
   }
992
   return Botan::to_u32bit(i->second);
12,244✔
993
}
994

995
uint64_t VarMap::get_opt_u64(const std::string& key, const uint64_t def_value) const {
3,538✔
996
   auto i = m_vars.find(key);
3,538✔
997
   if(i == m_vars.end()) {
3,538✔
998
      return def_value;
999
   }
1000
   try {
641✔
1001
      return std::stoull(i->second);
3,538✔
1002
   } catch(std::exception&) {
×
1003
      throw Test_Error("Invalid u64 value '" + i->second + "'");
×
1004
   }
×
1005
}
1006

1007
std::vector<uint8_t> VarMap::get_opt_bin(const std::string& key) const {
41,564✔
1008
   auto i = m_vars.find(key);
41,564✔
1009
   if(i == m_vars.end()) {
41,564✔
1010
      return std::vector<uint8_t>();
29,645✔
1011
   }
1012

1013
   try {
11,919✔
1014
      return Botan::hex_decode(i->second);
11,919✔
1015
   } catch(std::exception&) {
×
1016
      throw Test_Error("Test invalid hex input '" + i->second + "'" + +" for key " + key);
×
1017
   }
×
1018
}
1019

1020
std::string VarMap::get_req_str(const std::string& key) const {
52,783✔
1021
   auto i = m_vars.find(key);
52,783✔
1022
   if(i == m_vars.end()) {
52,783✔
1023
      throw Test_Error("Test missing variable " + key);
×
1024
   }
1025
   return i->second;
52,783✔
1026
}
1027

1028
#if defined(BOTAN_HAS_BIGINT)
1029
Botan::BigInt VarMap::get_req_bn(const std::string& key) const {
38,502✔
1030
   auto i = m_vars.find(key);
38,502✔
1031
   if(i == m_vars.end()) {
38,502✔
1032
      throw Test_Error("Test missing variable " + key);
×
1033
   }
1034

1035
   try {
38,502✔
1036
      return Botan::BigInt(i->second);
38,502✔
1037
   } catch(std::exception&) {
×
1038
      throw Test_Error("Test invalid bigint input '" + i->second + "' for key " + key);
×
1039
   }
×
1040
}
1041

1042
Botan::BigInt VarMap::get_opt_bn(const std::string& key, const Botan::BigInt& def_value) const {
80✔
1043
   auto i = m_vars.find(key);
80✔
1044
   if(i == m_vars.end()) {
80✔
1045
      return def_value;
24✔
1046
   }
1047

1048
   try {
56✔
1049
      return Botan::BigInt(i->second);
56✔
1050
   } catch(std::exception&) {
×
1051
      throw Test_Error("Test invalid bigint input '" + i->second + "' for key " + key);
×
1052
   }
×
1053
}
1054
#endif
1055

1056
Text_Based_Test::Text_Based_Test(const std::string& data_src,
180✔
1057
                                 const std::string& required_keys_str,
1058
                                 const std::string& optional_keys_str) :
180✔
1059
      m_data_src(data_src) {
540✔
1060
   if(required_keys_str.empty()) {
180✔
1061
      throw Test_Error("Invalid test spec");
×
1062
   }
1063

1064
   std::vector<std::string> required_keys = Botan::split_on(required_keys_str, ',');
180✔
1065
   std::vector<std::string> optional_keys = Botan::split_on(optional_keys_str, ',');
180✔
1066

1067
   m_required_keys.insert(required_keys.begin(), required_keys.end());
180✔
1068
   m_optional_keys.insert(optional_keys.begin(), optional_keys.end());
180✔
1069
   m_output_key = required_keys.at(required_keys.size() - 1);
180✔
1070
}
180✔
1071

1072
std::string Text_Based_Test::get_next_line() {
155,996✔
1073
   while(true) {
156,253✔
1074
      if(m_cur == nullptr || m_cur->good() == false) {
156,253✔
1075
         if(m_srcs.empty()) {
448✔
1076
            if(m_first) {
360✔
1077
               if(m_data_src.ends_with(".vec")) {
180✔
1078
                  m_srcs.push_back(Test::data_file(m_data_src));
336✔
1079
               } else {
1080
                  const auto fs = Test::files_in_data_dir(m_data_src);
12✔
1081
                  m_srcs.assign(fs.begin(), fs.end());
12✔
1082
                  if(m_srcs.empty()) {
12✔
1083
                     throw Test_Error("Error reading test data dir " + m_data_src);
×
1084
                  }
1085
               }
12✔
1086

1087
               m_first = false;
180✔
1088
            } else {
1089
               return "";  // done
180✔
1090
            }
1091
         }
1092

1093
         m_cur = std::make_unique<std::ifstream>(m_srcs[0]);
356✔
1094
         m_cur_src_name = m_srcs[0];
268✔
1095

1096
#if defined(BOTAN_HAS_CPUID)
1097
         // Reinit cpuid on new file if needed
1098
         if(m_cpu_flags.empty() == false) {
268✔
1099
            m_cpu_flags.clear();
15✔
1100
            Botan::CPUID::initialize();
15✔
1101
         }
1102
#endif
1103

1104
         if(!m_cur->good()) {
268✔
1105
            throw Test_Error("Could not open input file '" + m_cur_src_name);
×
1106
         }
1107

1108
         m_srcs.pop_front();
268✔
1109
      }
1110

1111
      while(m_cur->good()) {
226,207✔
1112
         std::string line;
225,950✔
1113
         std::getline(*m_cur, line);
225,950✔
1114

1115
         if(line.empty()) {
225,950✔
1116
            continue;
55,013✔
1117
         }
1118

1119
         if(line[0] == '#') {
170,937✔
1120
            if(line.starts_with("#test ")) {
15,138✔
1121
               return line;
17✔
1122
            } else {
1123
               continue;
15,121✔
1124
            }
1125
         }
1126

1127
         return line;
155,799✔
1128
      }
225,950✔
1129
   }
1130
}
1131

1132
namespace {
1133

1134
// strips leading and trailing but not internal whitespace
1135
std::string strip_ws(const std::string& in) {
309,990✔
1136
   const char* whitespace = " ";
309,990✔
1137

1138
   const auto first_c = in.find_first_not_of(whitespace);
309,990✔
1139
   if(first_c == std::string::npos) {
309,990✔
1140
      return "";
1,033✔
1141
   }
1142

1143
   const auto last_c = in.find_last_not_of(whitespace);
308,957✔
1144

1145
   return in.substr(first_c, last_c - first_c + 1);
308,957✔
1146
}
1147

1148
std::vector<std::string> parse_cpuid_bits(const std::vector<std::string>& tok) {
17✔
1149
   std::vector<std::string> bits;
17✔
1150

1151
#if defined(BOTAN_HAS_CPUID)
1152
   for(size_t i = 1; i < tok.size(); ++i) {
82✔
1153
      if(auto bit = Botan::CPUID::bit_from_string(tok[i])) {
65✔
1154
         bits.push_back(bit->to_string());
78✔
1155
      }
1156
   }
1157
#else
1158
   BOTAN_UNUSED(tok);
1159
#endif
1160

1161
   return bits;
17✔
1162
}
×
1163

1164
}  // namespace
1165

1166
bool Text_Based_Test::skip_this_test(const std::string& /*header*/, const VarMap& /*vars*/) {
32,325✔
1167
   return false;
32,325✔
1168
}
1169

1170
std::vector<Test::Result> Text_Based_Test::run() {
180✔
1171
   std::vector<Test::Result> results;
180✔
1172

1173
   std::string header;
180✔
1174
   std::string header_or_name = m_data_src;
180✔
1175
   VarMap vars;
180✔
1176
   size_t test_cnt = 0;
180✔
1177

1178
   while(true) {
155,996✔
1179
      const std::string line = get_next_line();
155,996✔
1180
      if(line.empty())  // EOF
155,996✔
1181
      {
1182
         break;
1183
      }
1184

1185
      if(line.starts_with("#test ")) {
155,816✔
1186
         std::vector<std::string> pragma_tokens = Botan::split_on(line.substr(6), ' ');
17✔
1187

1188
         if(pragma_tokens.empty()) {
17✔
1189
            throw Test_Error("Empty pragma found in " + m_cur_src_name);
×
1190
         }
1191

1192
         if(pragma_tokens[0] != "cpuid") {
17✔
1193
            throw Test_Error("Unknown test pragma '" + line + "' in " + m_cur_src_name);
×
1194
         }
1195

1196
         if(!Test_Registry::instance().needs_serialization(this->test_name())) {
17✔
1197
            throw Test_Error(Botan::fmt("'{}' used cpuid control but is not serialized", this->test_name()));
×
1198
         }
1199

1200
         m_cpu_flags = parse_cpuid_bits(pragma_tokens);
17✔
1201

1202
         continue;
17✔
1203
      } else if(line[0] == '#') {
155,816✔
1204
         throw Test_Error("Unknown test pragma '" + line + "' in " + m_cur_src_name);
×
1205
      }
1206

1207
      if(line[0] == '[' && line[line.size() - 1] == ']') {
155,799✔
1208
         header = line.substr(1, line.size() - 2);
804✔
1209
         header_or_name = header;
804✔
1210
         test_cnt = 0;
804✔
1211
         vars.clear();
804✔
1212
         continue;
804✔
1213
      }
1214

1215
      const std::string test_id = "test " + std::to_string(test_cnt);
309,990✔
1216

1217
      auto equal_i = line.find_first_of('=');
154,995✔
1218

1219
      if(equal_i == std::string::npos) {
154,995✔
1220
         results.push_back(Test::Result::Failure(header_or_name, "invalid input '" + line + "'"));
×
1221
         continue;
×
1222
      }
1223

1224
      const std::string key = strip_ws(std::string(line.begin(), line.begin() + equal_i - 1));
309,990✔
1225
      const std::string val = strip_ws(std::string(line.begin() + equal_i + 1, line.end()));
309,990✔
1226

1227
      if(!m_required_keys.contains(key) && !m_optional_keys.contains(key)) {
154,995✔
1228
         auto r = Test::Result::Failure(header_or_name, Botan::fmt("{} failed unknown key {}", test_id, key));
×
1229
         results.push_back(r);
×
1230
      }
×
1231

1232
      vars.add(key, val);
154,995✔
1233

1234
      if(key == m_output_key) {
154,995✔
1235
         try {
48,104✔
1236
            for(const auto& req_key : m_required_keys) {
244,434✔
1237
               if(!vars.has_key(req_key)) {
392,660✔
1238
                  auto r =
×
1239
                     Test::Result::Failure(header_or_name, Botan::fmt("{} missing required key {}", test_id, req_key));
×
1240
                  results.push_back(r);
×
1241
               }
×
1242
            }
1243

1244
            if(skip_this_test(header, vars)) {
48,104✔
1245
               continue;
139✔
1246
            }
1247

1248
            ++test_cnt;
47,965✔
1249

1250
            const uint64_t start = Test::timestamp();
47,965✔
1251

1252
            Test::Result result = run_one_test(header, vars);
47,965✔
1253
#if defined(BOTAN_HAS_CPUID)
1254
            if(!m_cpu_flags.empty()) {
47,965✔
1255
               for(const auto& cpuid_str : m_cpu_flags) {
23,221✔
1256
                  if(const auto bit = Botan::CPUID::Feature::from_string(cpuid_str)) {
16,559✔
1257
                     if(Botan::CPUID::has(*bit)) {
16,559✔
1258
                        Botan::CPUID::clear_cpuid_bit(*bit);
14,035✔
1259
                        // now re-run the test
1260
                        result.merge(run_one_test(header, vars));
14,035✔
1261
                     }
1262
                  }
1263
               }
1264
               Botan::CPUID::initialize();
6,662✔
1265
            }
1266
#endif
1267
            result.set_ns_consumed(Test::timestamp() - start);
47,965✔
1268

1269
            if(result.tests_failed() > 0) {
47,965✔
1270
               std::ostringstream oss;
×
1271
               oss << "Test # " << test_cnt << " ";
×
1272
               if(!header.empty()) {
×
1273
                  oss << header << " ";
×
1274
               }
1275
               oss << "failed ";
×
1276

1277
               for(const auto& k : m_required_keys) {
×
1278
                  oss << k << "=" << vars.get_req_str(k) << " ";
×
1279
               }
1280

1281
               result.test_note(oss.str());
×
1282
            }
×
1283
            results.push_back(result);
47,965✔
1284
         } catch(std::exception& e) {
47,965✔
1285
            std::ostringstream oss;
×
1286
            oss << "Test # " << test_cnt << " ";
×
1287
            if(!header.empty()) {
×
1288
               oss << header << " ";
×
1289
            }
1290

1291
            for(const auto& k : m_required_keys) {
×
1292
               oss << k << "=" << vars.get_req_str(k) << " ";
×
1293
            }
1294

1295
            oss << "failed with exception '" << e.what() << "'";
×
1296

1297
            results.push_back(Test::Result::Failure(header_or_name, oss.str()));
×
1298
         }
×
1299

1300
         if(clear_between_callbacks()) {
47,965✔
1301
            vars.clear();
33,193✔
1302
         }
1303
      }
1304
   }
156,094✔
1305

1306
   if(results.empty()) {
180✔
1307
      return results;
1308
   }
1309

1310
   try {
180✔
1311
      std::vector<Test::Result> final_tests = run_final_tests();
180✔
1312
      results.insert(results.end(), final_tests.begin(), final_tests.end());
180✔
1313
   } catch(std::exception& e) {
180✔
1314
      results.push_back(Test::Result::Failure(header_or_name, "run_final_tests exception " + std::string(e.what())));
×
1315
   }
×
1316

1317
   m_first = true;
180✔
1318

1319
   return results;
180✔
1320
}
180✔
1321

1322
std::map<std::string, std::string> Test_Options::report_properties() const {
1✔
1323
   std::map<std::string, std::string> result;
1✔
1324

1325
   for(const auto& prop : m_report_properties) {
3✔
1326
      const auto colon = prop.find(':');
2✔
1327
      // props without a colon separator or without a name are not allowed
1328
      if(colon == std::string::npos || colon == 0) {
2✔
1329
         throw Test_Error("--report-properties should be of the form <key>:<value>,<key>:<value>,...");
×
1330
      }
1331

1332
      result.insert_or_assign(prop.substr(0, colon), prop.substr(colon + 1, prop.size() - colon - 1));
4✔
1333
   }
1334

1335
   return result;
1✔
1336
}
×
1337

1338
}  // namespace Botan_Tests
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