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

randombit / botan / 20579846577

29 Dec 2025 06:24PM UTC coverage: 90.415% (+0.2%) from 90.243%
20579846577

push

github

web-flow
Merge pull request #5167 from randombit/jack/src-size-reductions

Changes to reduce unnecessary inclusions

101523 of 112285 relevant lines covered (90.42%)

12817276.56 hits per line

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

78.95
/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 <deque>
19
#include <fstream>
20
#include <iomanip>
21
#include <set>
22
#include <sstream>
23
#include <unordered_set>
24

25
#if defined(BOTAN_HAS_BIGINT)
26
   #include <botan/bigint.h>
27
#endif
28

29
#if defined(BOTAN_HAS_CPUID)
30
   #include <botan/internal/cpuid.h>
31
#endif
32

33
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
34
   #include <botan/ec_point.h>
35
#endif
36

37
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
38
   #include <stdlib.h>
39
   #include <unistd.h>
40
#endif
41

42
#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
43
   #include <version>
44
   #if defined(__cpp_lib_filesystem)
45
      #include <filesystem>
46
   #endif
47
#endif
48

49
namespace Botan_Tests {
50

51
Test::Test() = default;
412✔
52

53
Test::~Test() = default;
557✔
54

55
Test::Result::Result(std::string who) : m_who(std::move(who)), m_timestamp(Test::timestamp()) {}
74,927✔
56

57
void Test::Result::merge(const Result& other, bool ignore_test_name) {
120,287✔
58
   if(who() != other.who()) {
120,287✔
59
      if(!ignore_test_name) {
73✔
60
         throw Test_Error("Merging tests from different sources");
×
61
      }
62

63
      // When deliberately merging results with different names, the code location is
64
      // likely inconsistent and must be discarded.
65
      m_where.reset();
73✔
66
   } else {
67
      m_where = other.m_where;
120,214✔
68
   }
69

70
   m_timestamp = std::min(m_timestamp, other.m_timestamp);
120,287✔
71
   m_ns_taken += other.m_ns_taken;
120,287✔
72
   m_tests_passed += other.m_tests_passed;
120,287✔
73
   m_fail_log.insert(m_fail_log.end(), other.m_fail_log.begin(), other.m_fail_log.end());
120,287✔
74
   m_log.insert(m_log.end(), other.m_log.begin(), other.m_log.end());
120,287✔
75
}
120,287✔
76

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

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

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

101
void Test::Result::note_missing(const std::string& whatever) {
275✔
102
   static std::set<std::string> s_already_seen;
275✔
103

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

110
bool Test::Result::ThrowExpectations::check(const std::string& test_name, Test::Result& result) {
49,210✔
111
   m_consumed = true;
49,210✔
112

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

135
   return result.test_success(test_name + " behaved as expected");
98,406✔
136
}
137

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

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

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

150
bool Test::Result::test_success(const std::string& note) {
3,524,140✔
151
   if(Test::options().log_success()) {
3,524,008✔
152
      test_note(note);
×
153
   }
154
   ++m_tests_passed;
3,524,140✔
155
   return true;
724,618✔
156
}
157

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

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

167
bool Test::Result::test_failure(const std::string& err) {
25✔
168
   m_fail_log.push_back(err);
25✔
169

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

176
namespace {
177

178
bool same_contents(const uint8_t x[], const uint8_t y[], size_t len) {
277,305✔
179
   return (len == 0) ? true : std::memcmp(x, y, len) == 0;
275,934✔
180
}
181

182
}  // namespace
183

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

195
bool Test::Result::test_eq(const char* producer,
275,857✔
196
                           const std::string& what,
197
                           const uint8_t produced[],
198
                           size_t produced_size,
199
                           const uint8_t expected[],
200
                           size_t expected_size) {
201
   if(produced_size == expected_size && same_contents(produced, expected, expected_size)) {
275,857✔
202
      return test_success();
551,712✔
203
   }
204

205
   std::ostringstream err;
1✔
206

207
   err << who();
1✔
208

209
   if(producer != nullptr) {
1✔
210
      err << " producer '" << producer << "'";
×
211
   }
212

213
   err << " unexpected result for " << what;
1✔
214

215
   if(produced_size != expected_size) {
1✔
216
      err << " produced " << produced_size << " bytes expected " << expected_size;
1✔
217
   }
218

219
   std::vector<uint8_t> xor_diff(std::min(produced_size, expected_size));
2✔
220
   size_t bytes_different = 0;
1✔
221

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

229
   err << "\nProduced: " << Botan::hex_encode(produced, produced_size)
1✔
230
       << "\nExpected: " << Botan::hex_encode(expected, expected_size);
3✔
231

232
   if(bytes_different > 0) {
1✔
233
      err << "\nXOR Diff: " << Botan::hex_encode(xor_diff);
1✔
234
   }
235

236
   return test_failure(err.str());
1✔
237
}
1✔
238

239
bool Test::Result::test_is_nonempty(const std::string& what_is_it, const std::string& to_examine) {
34,525✔
240
   if(to_examine.empty()) {
34,525✔
241
      return test_failure(what_is_it + " was empty");
1✔
242
   }
243
   return test_success();
69,048✔
244
}
245

246
bool Test::Result::test_eq(const std::string& what, const std::string& produced, const std::string& expected) {
73,722✔
247
   return test_is_eq(what, produced, expected);
73,722✔
248
}
249

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

254
bool Test::Result::test_eq(const std::string& what, size_t produced, size_t expected) {
136,308✔
255
   return test_is_eq(what, produced, expected);
136,308✔
256
}
257

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

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

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

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

285
   return test_success();
11,278✔
286
}
287

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

295
   return test_success();
2,043,270✔
296
}
297

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

307
   return test_success();
2,284,858✔
308
}
309

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

319
   return test_success();
29,914✔
320
}
321

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

327
   return test_failure(who() + " " + what + " produced matching strings " + str1);
4✔
328
}
329

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

335
   std::ostringstream err;
1✔
336
   err << who() << " " << what << " produced " << produced << " unexpected value";
1✔
337
   return test_failure(err.str());
1✔
338
}
1✔
339

340
#if defined(BOTAN_HAS_BIGINT)
341
bool Test::Result::test_eq(const std::string& what, const BigInt& produced, const BigInt& expected) {
252,640✔
342
   return test_is_eq(what, produced, expected);
252,640✔
343
}
344

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

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

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

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

370
bool Test::Result::test_eq(const std::string& what, bool produced, bool expected) {
349,025✔
371
   return test_is_eq(what, produced, expected);
349,025✔
372
}
373

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

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

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

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

407
   return test_success();
762✔
408
}
409

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

415
Botan::RandomNumberGenerator& Test::rng() const {
453,950✔
416
   if(!m_test_rng) {
453,950✔
417
      m_test_rng = Test::new_rng(m_test_name);
145✔
418
   }
419

420
   return *m_test_rng;
453,950✔
421
}
422

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

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

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

442
   return r;
83,765✔
443
}
×
444

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

449
//static
450
std::string Test::format_time(uint64_t nanoseconds) {
1,465✔
451
   std::ostringstream o;
1,465✔
452

453
   if(nanoseconds > 1000000000) {
1,465✔
454
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000000.0 << " sec";
153✔
455
   } else {
456
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000.0 << " msec";
1,312✔
457
   }
458

459
   return o.str();
2,930✔
460
}
1,465✔
461

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

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

472
   if(tests_run() == 0 && !verbose) {
2,533✔
473
      return "";
20✔
474
   }
475

476
   std::ostringstream report;
2,513✔
477

478
   report << who() << " ran ";
2,513✔
479

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

487
   if(m_ns_taken > 0) {
2,513✔
488
      report << " in " << format_time(m_ns_taken);
2,928✔
489
   }
490

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

497
   report << "\n";
2,513✔
498

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

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

513
   return report.str();
2,513✔
514
}
2,513✔
515

516
namespace {
517

518
class Test_Registry {
519
   public:
520
      static Test_Registry& instance() {
1,256✔
521
         static Test_Registry registry;
1,257✔
522
         return registry;
1,256✔
523
      }
524

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

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

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

542
         if(smoke_test) {
412✔
543
            m_smoke_tests.push_back(name);
10✔
544
         }
545

546
         if(needs_serialization) {
412✔
547
            m_mutexed_tests.push_back(name);
21✔
548
         }
549

550
         m_tests.emplace(name, std::move(maker_fn));
412✔
551
         m_categories.emplace(category, name);
412✔
552
      }
412✔
553

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

562
      std::vector<std::string> registered_tests() const {
×
563
         std::vector<std::string> s;
×
564
         s.reserve(m_tests.size());
×
565
         for(auto&& i : m_tests) {
×
566
            s.push_back(i.first);
×
567
         }
568
         return s;
×
569
      }
×
570

571
      std::vector<std::string> registered_test_categories() const {
×
572
         std::vector<std::string> s;
×
573
         s.reserve(m_categories.size());
×
574
         for(auto&& i : m_categories) {
×
575
            s.push_back(i.first);
×
576
         }
577
         return s;
×
578
      }
×
579

580
      std::vector<std::string> filter_registered_tests(const std::vector<std::string>& requested,
1✔
581
                                                       const std::vector<std::string>& to_be_skipped) {
582
         std::vector<std::string> result;
1✔
583

584
         std::set<std::string> to_be_skipped_set(to_be_skipped.begin(), to_be_skipped.end());
1✔
585
         // TODO: this is O(n^2), but we have a relatively small number of tests.
586
         auto insert_if_not_exists_and_not_skipped = [&](const std::string& test_name) {
413✔
587
            if(!Botan::value_exists(result, test_name) && !to_be_skipped_set.contains(test_name)) {
412✔
588
               result.push_back(test_name);
402✔
589
            }
590
         };
413✔
591

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

616
         return result;
1✔
617
      }
1✔
618

619
      bool needs_serialization(const std::string& test_name) const {
431✔
620
         return Botan::value_exists(m_mutexed_tests, test_name);
19✔
621
      }
622

623
   private:
624
      Test_Registry() = default;
1✔
625

626
   private:
627
      std::map<std::string, std::function<std::unique_ptr<Test>()>> m_tests;
628
      std::multimap<std::string, std::string> m_categories;
629
      std::vector<std::string> m_smoke_tests;
630
      std::vector<std::string> m_mutexed_tests;
631
};
632

633
}  // namespace
634

635
// static Test:: functions
636

637
//static
638
void Test::register_test(const std::string& category,
412✔
639
                         const std::string& name,
640
                         bool smoke_test,
641
                         bool needs_serialization,
642
                         std::function<std::unique_ptr<Test>()> maker_fn) {
643
   Test_Registry::instance().register_test(category, name, smoke_test, needs_serialization, std::move(maker_fn));
824✔
644
}
412✔
645

646
//static
647
uint64_t Test::timestamp() {
173,236✔
648
   auto now = std::chrono::system_clock::now().time_since_epoch();
173,236✔
649
   return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
2,373✔
650
}
651

652
//static
653
std::vector<Test::Result> Test::flatten_result_lists(std::vector<std::vector<Test::Result>> result_lists) {
4✔
654
   std::vector<Test::Result> results;
4✔
655
   for(auto& result_list : result_lists) {
26✔
656
      for(auto& result : result_list) {
71✔
657
         results.emplace_back(std::move(result));
49✔
658
      }
659
   }
660
   return results;
4✔
661
}
×
662

663
//static
664
std::vector<std::string> Test::registered_tests() {
×
665
   return Test_Registry::instance().registered_tests();
×
666
}
667

668
//static
669
std::vector<std::string> Test::registered_test_categories() {
×
670
   return Test_Registry::instance().registered_test_categories();
×
671
}
672

673
//static
674
std::unique_ptr<Test> Test::get_test(const std::string& test_name) {
412✔
675
   return Test_Registry::instance().get_test(test_name);
412✔
676
}
677

678
//static
679
bool Test::test_needs_serialization(const std::string& test_name) {
412✔
680
   return Test_Registry::instance().needs_serialization(test_name);
412✔
681
}
682

683
//static
684
std::vector<std::string> Test::filter_registered_tests(const std::vector<std::string>& requested,
1✔
685
                                                       const std::vector<std::string>& to_be_skipped) {
686
   return Test_Registry::instance().filter_registered_tests(requested, to_be_skipped);
1✔
687
}
688

689
//static
690
std::string Test::temp_file_name(const std::string& basename) {
21✔
691
   // TODO add a --tmp-dir option to the tests to specify where these files go
692

693
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
694

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

698
   const int fd = ::mkstemp(mkstemp_basename.data());
21✔
699

700
   // error
701
   if(fd < 0) {
21✔
702
      return "";
×
703
   }
704

705
   ::close(fd);
21✔
706

707
   return mkstemp_basename;
21✔
708
#else
709
   // For now just create the temp in the current working directory
710
   return basename;
711
#endif
712
}
21✔
713

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

726
std::string Test::read_data_file(const std::string& path) {
38✔
727
   const std::string fsname = Test::data_file(path);
38✔
728
   std::ifstream file(fsname.c_str());
38✔
729
   if(!file.good()) {
38✔
730
      throw Test_Error("Error reading from " + fsname);
×
731
   }
732

733
   return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
76✔
734
}
38✔
735

736
std::vector<uint8_t> Test::read_binary_data_file(const std::string& path) {
34✔
737
   const std::string fsname = Test::data_file(path);
34✔
738
   std::ifstream file(fsname.c_str(), std::ios::binary);
34✔
739
   if(!file.good()) {
34✔
740
      throw Test_Error("Error reading from " + fsname);
×
741
   }
742

743
   std::vector<uint8_t> contents;
34✔
744

745
   while(file.good()) {
74✔
746
      std::vector<uint8_t> buf(4096);
40✔
747
      file.read(reinterpret_cast<char*>(buf.data()), buf.size());
40✔
748
      const size_t got = static_cast<size_t>(file.gcount());
40✔
749

750
      if(got == 0 && file.eof()) {
40✔
751
         break;
752
      }
753

754
      contents.insert(contents.end(), buf.data(), buf.data() + got);
40✔
755
   }
40✔
756

757
   return contents;
68✔
758
}
34✔
759

760
// static member variables of Test
761

762
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
763
Test_Options Test::m_opts;
764
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
765
std::string Test::m_test_rng_seed;
766

767
//static
768
void Test::set_test_options(const Test_Options& opts) {
1✔
769
   m_opts = opts;
1✔
770
}
1✔
771

772
namespace {
773

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

782
      void clear() override { m_x = 0; }
×
783

784
      bool accepts_input() const override { return true; }
×
785

786
      bool is_seeded() const override { return true; }
277,762✔
787

788
      void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> input) override {
10,228,405✔
789
         for(const auto byte : input) {
10,228,405✔
790
            mix(byte);
×
791
         }
792

793
         for(auto& byte : output) {
73,089,844✔
794
            byte = mix();
62,861,439✔
795
         }
796
      }
10,228,405✔
797

798
      Testsuite_RNG(std::string_view seed, std::string_view test_name) : m_x(0) {
276✔
799
         for(const char c : seed) {
8,280✔
800
            this->mix(static_cast<uint8_t>(c));
8,004✔
801
         }
802
         for(const char c : test_name) {
5,578✔
803
            this->mix(static_cast<uint8_t>(c));
5,302✔
804
         }
805
      }
276✔
806

807
   private:
808
      uint8_t mix(uint8_t input = 0) {
62,874,745✔
809
         m_x ^= input;
62,874,745✔
810
         m_x *= 0xF2E16957;
62,874,745✔
811
         m_x += 0xE50B590F;
62,874,745✔
812
         return static_cast<uint8_t>(m_x >> 27);
62,874,745✔
813
      }
814

815
      uint64_t m_x;
816
};
817

818
}  // namespace
819

820
//static
821
void Test::set_test_rng_seed(std::span<const uint8_t> seed, size_t epoch) {
1✔
822
   m_test_rng_seed = Botan::fmt("seed={} epoch={}", Botan::hex_encode(seed), epoch);
1✔
823
}
1✔
824

825
//static
826
std::unique_ptr<Botan::RandomNumberGenerator> Test::new_rng(std::string_view test_name) {
268✔
827
   return std::make_unique<Testsuite_RNG>(m_test_rng_seed, test_name);
268✔
828
}
829

830
//static
831
std::shared_ptr<Botan::RandomNumberGenerator> Test::new_shared_rng(std::string_view test_name) {
8✔
832
   return std::make_shared<Testsuite_RNG>(m_test_rng_seed, test_name);
8✔
833
}
834

835
//static
836
std::string Test::data_file(const std::string& file) {
759✔
837
   return options().data_dir() + "/" + file;
1,518✔
838
}
839

840
//static
841
std::string Test::data_dir(const std::string& subdir) {
1✔
842
   return options().data_dir() + "/" + subdir;
2✔
843
}
844

845
//static
846
std::vector<std::string> Test::files_in_data_dir(const std::string& subdir) {
393✔
847
   auto fs = Botan::get_files_recursive(options().data_dir() + "/" + subdir);
1,179✔
848
   if(fs.empty()) {
393✔
849
      throw Test_Error("Test::files_in_data_dir encountered empty subdir " + subdir);
×
850
   }
851
   return fs;
393✔
852
}
×
853

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

868
//static
869
std::vector<std::string> Test::provider_filter(const std::vector<std::string>& providers) {
48,423✔
870
   if(m_opts.provider().empty()) {
48,423✔
871
      return providers;
48,423✔
872
   }
873
   for(auto&& provider : providers) {
×
874
      if(provider == m_opts.provider()) {
×
875
         return std::vector<std::string>{provider};
×
876
      }
877
   }
878
   return std::vector<std::string>{};
×
879
}
×
880

881
std::string Test::random_password(Botan::RandomNumberGenerator& rng) {
222✔
882
   const size_t len = 1 + rng.next_byte() % 32;
222✔
883
   return Botan::hex_encode(rng.random_vec(len));
444✔
884
}
885

886
size_t Test::random_index(Botan::RandomNumberGenerator& rng, size_t max) {
8,062✔
887
   return Botan::load_be(rng.random_array<8>()) % max;
8,062✔
888
}
889

890
void VarMap::clear() {
33,997✔
891
   m_vars.clear();
×
892
}
×
893

894
namespace {
895

896
bool varmap_pair_lt(const std::pair<std::string, std::string>& kv, const std::string& k) {
1,678,067✔
897
   return kv.first < k;
1,678,067✔
898
}
899

900
}  // namespace
901

902
void VarMap::add(const std::string& key, const std::string& value) {
154,995✔
903
   auto i = std::lower_bound(m_vars.begin(), m_vars.end(), key, varmap_pair_lt);
154,995✔
904

905
   if(i != m_vars.end() && i->first == key) {
154,995✔
906
      i->second = value;
44,401✔
907
   } else {
908
      m_vars.emplace(i, key, value);
110,594✔
909
   }
910
}
154,995✔
911

912
std::optional<std::string> VarMap::get_var(const std::string& key) const {
555,910✔
913
   auto i = std::lower_bound(m_vars.begin(), m_vars.end(), key, varmap_pair_lt);
555,910✔
914

915
   if(i != m_vars.end() && i->first == key) {
555,910✔
916
      return i->second;
486,144✔
917
   } else {
918
      return {};
69,766✔
919
   }
920
}
921

922
bool VarMap::has_key(const std::string& key) const {
212,738✔
923
   return get_var(key).has_value();
212,738✔
924
}
925

926
std::string VarMap::get_req_str(const std::string& key) const {
252,235✔
927
   auto var = get_var(key);
252,235✔
928
   if(var) {
252,235✔
929
      return *var;
504,470✔
930
   } else {
931
      throw Test_Error("Test missing variable " + key);
×
932
   }
933
}
252,235✔
934

935
std::vector<std::vector<uint8_t>> VarMap::get_req_bin_list(const std::string& key) const {
12✔
936
   const auto var = get_req_str(key);
12✔
937

938
   std::vector<std::vector<uint8_t>> bin_list;
12✔
939

940
   for(auto&& part : Botan::split_on(var, ',')) {
62✔
941
      try {
50✔
942
         bin_list.push_back(Botan::hex_decode(part));
100✔
943
      } catch(std::exception& e) {
×
944
         std::ostringstream oss;
×
945
         oss << "Bad input '" << part << "'"
×
946
             << " in binary list key " << key << " - " << e.what();
×
947
         throw Test_Error(oss.str());
×
948
      }
×
949
   }
12✔
950

951
   return bin_list;
12✔
952
}
12✔
953

954
std::vector<uint8_t> VarMap::get_req_bin(const std::string& key) const {
156,335✔
955
   const auto var = get_req_str(key);
156,335✔
956

957
   try {
156,335✔
958
      if(var.starts_with("0x")) {
156,335✔
959
         if(var.size() % 2 == 0) {
×
960
            return Botan::hex_decode(var.substr(2));
×
961
         } else {
962
            std::string z = var;
×
963
            std::swap(z[0], z[1]);  // swap 0x to x0 then remove x
×
964
            return Botan::hex_decode(z.substr(1));
×
965
         }
×
966
      } else {
967
         return Botan::hex_decode(var);
156,335✔
968
      }
969
   } catch(std::exception& e) {
×
970
      std::ostringstream oss;
×
971
      oss << "Bad input '" << var << "'"
×
972
          << " for key " << key << " - " << e.what();
×
973
      throw Test_Error(oss.str());
×
974
   }
×
975
}
156,335✔
976

977
std::string VarMap::get_opt_str(const std::string& key, const std::string& def_value) const {
14,376✔
978
   if(auto v = get_var(key)) {
14,376✔
979
      return *v;
55✔
980
   } else {
981
      return def_value;
28,697✔
982
   }
14,376✔
983
}
984

985
bool VarMap::get_req_bool(const std::string& key) const {
39✔
986
   const auto var = get_req_str(key);
39✔
987

988
   if(var == "true") {
39✔
989
      return true;
990
   } else if(var == "false") {
23✔
991
      return false;
992
   } else {
993
      throw Test_Error("Invalid boolean for key '" + key + "' value '" + var + "'");
×
994
   }
995
}
39✔
996

997
size_t VarMap::get_req_sz(const std::string& key) const {
4,547✔
998
   return Botan::to_u32bit(get_req_str(key));
4,547✔
999
}
1000

1001
uint8_t VarMap::get_req_u8(const std::string& key) const {
17✔
1002
   const size_t s = this->get_req_sz(key);
17✔
1003
   if(s > 256) {
17✔
1004
      throw Test_Error("Invalid " + key + " expected uint8_t got " + std::to_string(s));
×
1005
   }
1006
   return static_cast<uint8_t>(s);
17✔
1007
}
1008

1009
uint32_t VarMap::get_req_u32(const std::string& key) const {
14✔
1010
   return static_cast<uint32_t>(get_req_sz(key));
14✔
1011
}
1012

1013
uint64_t VarMap::get_req_u64(const std::string& key) const {
17✔
1014
   const auto var = get_req_str(key);
17✔
1015
   try {
17✔
1016
      return std::stoull(var);
17✔
1017
   } catch(std::exception&) {
×
1018
      throw Test_Error("Invalid u64 value '" + var + "'");
×
1019
   }
×
1020
}
17✔
1021

1022
size_t VarMap::get_opt_sz(const std::string& key, const size_t def_value) const {
31,379✔
1023
   if(auto v = get_var(key)) {
31,379✔
1024
      return Botan::to_u32bit(*v);
12,244✔
1025
   } else {
1026
      return def_value;
1027
   }
31,379✔
1028
}
1029

1030
uint64_t VarMap::get_opt_u64(const std::string& key, const uint64_t def_value) const {
3,538✔
1031
   if(auto v = get_var(key)) {
3,538✔
1032
      try {
641✔
1033
         return std::stoull(*v);
3,538✔
1034
      } catch(std::exception&) {
×
1035
         throw Test_Error("Invalid u64 value '" + *v + "'");
×
1036
      }
×
1037
   } else {
1038
      return def_value;
1039
   }
3,538✔
1040
}
1041

1042
std::vector<uint8_t> VarMap::get_opt_bin(const std::string& key) const {
41,564✔
1043
   if(auto v = get_var(key)) {
41,564✔
1044
      try {
11,919✔
1045
         return Botan::hex_decode(*v);
11,919✔
1046
      } catch(std::exception&) {
×
1047
         throw Test_Error("Test invalid hex input '" + *v + "'" + +" for key " + key);
×
1048
      }
×
1049
   } else {
1050
      return {};
29,645✔
1051
   };
41,564✔
1052
}
1053

1054
#if defined(BOTAN_HAS_BIGINT)
1055
Botan::BigInt VarMap::get_req_bn(const std::string& key) const {
38,502✔
1056
   const auto var = get_req_str(key);
38,502✔
1057

1058
   try {
38,502✔
1059
      return Botan::BigInt(var);
38,502✔
1060
   } catch(std::exception&) {
×
1061
      throw Test_Error("Test invalid bigint input '" + var + "' for key " + key);
×
1062
   }
×
1063
}
38,502✔
1064

1065
Botan::BigInt VarMap::get_opt_bn(const std::string& key, const Botan::BigInt& def_value) const {
80✔
1066
   if(auto v = get_var(key)) {
80✔
1067
      try {
56✔
1068
         return Botan::BigInt(*v);
56✔
1069
      } catch(std::exception&) {
×
1070
         throw Test_Error("Test invalid bigint input '" + *v + "' for key " + key);
×
1071
      }
×
1072
   } else {
1073
      return def_value;
24✔
1074
   }
80✔
1075
}
1076
#endif
1077

1078
class Text_Based_Test::Text_Based_Test_Data {
1079
   public:
1080
      Text_Based_Test_Data(const std::string& data_src,
180✔
1081
                           const std::string& required_keys_str,
1082
                           const std::string& optional_keys_str) :
180✔
1083
            m_data_src(data_src) {
360✔
1084
         if(required_keys_str.empty()) {
180✔
1085
            throw Test_Error("Invalid test spec");
×
1086
         }
1087

1088
         m_required_keys = Botan::split_on(required_keys_str, ',');
180✔
1089
         std::vector<std::string> optional_keys = Botan::split_on(optional_keys_str, ',');
180✔
1090

1091
         m_all_keys.insert(m_required_keys.begin(), m_required_keys.end());
180✔
1092
         m_all_keys.insert(optional_keys.begin(), optional_keys.end());
180✔
1093
         m_output_key = m_required_keys.at(m_required_keys.size() - 1);
180✔
1094
      }
180✔
1095

1096
      std::string get_next_line();
1097

1098
      const std::string& current_source_name() const { return m_cur_src_name; }
×
1099

1100
      bool known_key(const std::string& key) const;
1101

1102
      const std::vector<std::string>& required_keys() const { return m_required_keys; }
48,104✔
1103

1104
      const std::string& output_key() const { return m_output_key; }
154,995✔
1105

1106
      const std::vector<std::string>& cpu_flags() const { return m_cpu_flags; }
47,965✔
1107

1108
      void set_cpu_flags(std::vector<std::string> flags) { m_cpu_flags = std::move(flags); }
19✔
1109

1110
      const std::string& initial_data_src_name() const { return m_data_src; }
180✔
1111

1112
   private:
1113
      std::string m_data_src;
1114
      std::vector<std::string> m_required_keys;
1115
      std::unordered_set<std::string> m_all_keys;
1116
      std::string m_output_key;
1117

1118
      bool m_first = true;
1119
      std::unique_ptr<std::istream> m_cur;
1120
      std::string m_cur_src_name;
1121
      std::deque<std::string> m_srcs;
1122
      std::vector<std::string> m_cpu_flags;
1123
};
1124

1125
Text_Based_Test::Text_Based_Test(const std::string& data_src,
180✔
1126
                                 const std::string& required_keys,
1127
                                 const std::string& optional_keys) :
180✔
1128
      m_data(std::make_unique<Text_Based_Test_Data>(data_src, required_keys, optional_keys)) {}
180✔
1129

1130
Text_Based_Test::~Text_Based_Test() = default;
180✔
1131

1132
std::string Text_Based_Test::Text_Based_Test_Data::get_next_line() {
155,998✔
1133
   while(true) {
156,255✔
1134
      if(m_cur == nullptr || m_cur->good() == false) {
156,255✔
1135
         if(m_srcs.empty()) {
448✔
1136
            if(m_first) {
360✔
1137
               if(m_data_src.ends_with(".vec")) {
180✔
1138
                  m_srcs.push_back(Test::data_file(m_data_src));
336✔
1139
               } else {
1140
                  const auto fs = Test::files_in_data_dir(m_data_src);
12✔
1141
                  m_srcs.assign(fs.begin(), fs.end());
12✔
1142
                  if(m_srcs.empty()) {
12✔
1143
                     throw Test_Error("Error reading test data dir " + m_data_src);
×
1144
                  }
1145
               }
12✔
1146

1147
               m_first = false;
180✔
1148
            } else {
1149
               return "";  // done
180✔
1150
            }
1151
         }
1152

1153
         m_cur = std::make_unique<std::ifstream>(m_srcs[0]);
356✔
1154
         m_cur_src_name = m_srcs[0];
268✔
1155

1156
#if defined(BOTAN_HAS_CPUID)
1157
         // Reinit cpuid on new file if needed
1158
         if(m_cpu_flags.empty() == false) {
268✔
1159
            m_cpu_flags.clear();
17✔
1160
            Botan::CPUID::initialize();
17✔
1161
         }
1162
#endif
1163

1164
         if(!m_cur->good()) {
268✔
1165
            throw Test_Error("Could not open input file '" + m_cur_src_name);
×
1166
         }
1167

1168
         m_srcs.pop_front();
268✔
1169
      }
1170

1171
      while(m_cur->good()) {
226,211✔
1172
         std::string line;
225,954✔
1173
         std::getline(*m_cur, line);
225,954✔
1174

1175
         if(line.empty()) {
225,954✔
1176
            continue;
55,015✔
1177
         }
1178

1179
         if(line[0] == '#') {
170,939✔
1180
            if(line.starts_with("#test ")) {
15,140✔
1181
               return line;
19✔
1182
            } else {
1183
               continue;
15,121✔
1184
            }
1185
         }
1186

1187
         return line;
155,799✔
1188
      }
225,954✔
1189
   }
1190
}
1191

1192
bool Text_Based_Test::Text_Based_Test_Data::known_key(const std::string& key) const {
154,995✔
1193
   return m_all_keys.contains(key);
154,995✔
1194
}
1195

1196
namespace {
1197

1198
// strips leading and trailing but not internal whitespace
1199
std::string strip_ws(const std::string& in) {
309,990✔
1200
   const char* whitespace = " ";
309,990✔
1201

1202
   const auto first_c = in.find_first_not_of(whitespace);
309,990✔
1203
   if(first_c == std::string::npos) {
309,990✔
1204
      return "";
1,033✔
1205
   }
1206

1207
   const auto last_c = in.find_last_not_of(whitespace);
308,957✔
1208

1209
   return in.substr(first_c, last_c - first_c + 1);
308,957✔
1210
}
1211

1212
std::vector<std::string> parse_cpuid_bits(const std::vector<std::string>& tok) {
19✔
1213
   std::vector<std::string> bits;
19✔
1214

1215
#if defined(BOTAN_HAS_CPUID)
1216
   for(size_t i = 1; i < tok.size(); ++i) {
90✔
1217
      if(auto bit = Botan::CPUID::bit_from_string(tok[i])) {
71✔
1218
         bits.push_back(bit->to_string());
90✔
1219
      }
1220
   }
1221
#else
1222
   BOTAN_UNUSED(tok);
1223
#endif
1224

1225
   return bits;
19✔
1226
}
×
1227

1228
}  // namespace
1229

1230
bool Text_Based_Test::skip_this_test(const std::string& /*header*/, const VarMap& /*vars*/) {
32,325✔
1231
   return false;
32,325✔
1232
}
1233

1234
std::vector<Test::Result> Text_Based_Test::run() {
180✔
1235
   std::vector<Test::Result> results;
180✔
1236

1237
   std::string header;
180✔
1238
   std::string header_or_name = m_data->initial_data_src_name();
180✔
1239
   VarMap vars;
180✔
1240
   size_t test_cnt = 0;
180✔
1241

1242
   while(true) {
155,998✔
1243
      const std::string line = m_data->get_next_line();
155,998✔
1244
      if(line.empty()) {
155,998✔
1245
         // EOF
1246
         break;
1247
      }
1248

1249
      if(line.starts_with("#test ")) {
155,818✔
1250
         std::vector<std::string> pragma_tokens = Botan::split_on(line.substr(6), ' ');
19✔
1251

1252
         if(pragma_tokens.empty()) {
19✔
1253
            throw Test_Error("Empty pragma found in " + m_data->current_source_name());
×
1254
         }
1255

1256
         if(pragma_tokens[0] != "cpuid") {
19✔
1257
            throw Test_Error("Unknown test pragma '" + line + "' in " + m_data->current_source_name());
×
1258
         }
1259

1260
         if(!Test_Registry::instance().needs_serialization(this->test_name())) {
19✔
1261
            throw Test_Error(Botan::fmt("'{}' used cpuid control but is not serialized", this->test_name()));
×
1262
         }
1263

1264
         m_data->set_cpu_flags(parse_cpuid_bits(pragma_tokens));
38✔
1265

1266
         continue;
19✔
1267
      } else if(line[0] == '#') {
155,818✔
1268
         throw Test_Error("Unknown test pragma '" + line + "' in " + m_data->current_source_name());
×
1269
      }
1270

1271
      if(line[0] == '[' && line[line.size() - 1] == ']') {
155,799✔
1272
         header = line.substr(1, line.size() - 2);
804✔
1273
         header_or_name = header;
804✔
1274
         test_cnt = 0;
804✔
1275
         vars.clear();
804✔
1276
         continue;
804✔
1277
      }
1278

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

1281
      auto equal_i = line.find_first_of('=');
154,995✔
1282

1283
      if(equal_i == std::string::npos) {
154,995✔
1284
         results.push_back(Test::Result::Failure(header_or_name, "invalid input '" + line + "'"));
×
1285
         continue;
×
1286
      }
1287

1288
      const std::string key = strip_ws(std::string(line.begin(), line.begin() + equal_i - 1));
309,990✔
1289
      const std::string val = strip_ws(std::string(line.begin() + equal_i + 1, line.end()));
309,990✔
1290

1291
      if(!m_data->known_key(key)) {
154,995✔
1292
         auto r = Test::Result::Failure(header_or_name, Botan::fmt("{} failed unknown key {}", test_id, key));
×
1293
         results.push_back(r);
×
1294
      }
×
1295

1296
      vars.add(key, val);
154,995✔
1297

1298
      if(key == m_data->output_key()) {
154,995✔
1299
         try {
48,104✔
1300
            for(const auto& req_key : m_data->required_keys()) {
244,434✔
1301
               if(!vars.has_key(req_key)) {
196,330✔
1302
                  auto r =
×
1303
                     Test::Result::Failure(header_or_name, Botan::fmt("{} missing required key {}", test_id, req_key));
×
1304
                  results.push_back(r);
×
1305
               }
×
1306
            }
1307

1308
            if(skip_this_test(header, vars)) {
48,104✔
1309
               continue;
139✔
1310
            }
1311

1312
            ++test_cnt;
47,965✔
1313

1314
            const uint64_t start = Test::timestamp();
47,965✔
1315

1316
            Test::Result result = run_one_test(header, vars);
47,965✔
1317
#if defined(BOTAN_HAS_CPUID)
1318
            if(!m_data->cpu_flags().empty()) {
47,965✔
1319
               for(const auto& cpuid_str : m_data->cpu_flags()) {
24,713✔
1320
                  if(const auto bit = Botan::CPUID::Feature::from_string(cpuid_str)) {
17,695✔
1321
                     if(Botan::CPUID::has(*bit)) {
17,695✔
1322
                        Botan::CPUID::clear_cpuid_bit(*bit);
14,391✔
1323
                        // now re-run the test
1324
                        result.merge(run_one_test(header, vars));
14,391✔
1325
                     }
1326
                  }
1327
               }
1328
               Botan::CPUID::initialize();
7,018✔
1329
            }
1330
#endif
1331
            result.set_ns_consumed(Test::timestamp() - start);
47,965✔
1332

1333
            if(result.tests_failed() > 0) {
47,965✔
1334
               std::ostringstream oss;
×
1335
               oss << "Test # " << test_cnt << " ";
×
1336
               if(!header.empty()) {
×
1337
                  oss << header << " ";
×
1338
               }
1339
               oss << "failed ";
×
1340

1341
               for(const auto& k : m_data->required_keys()) {
×
1342
                  oss << k << "=" << vars.get_req_str(k) << " ";
×
1343
               }
1344

1345
               result.test_note(oss.str());
×
1346
            }
×
1347
            results.push_back(result);
47,965✔
1348
         } catch(std::exception& e) {
47,965✔
1349
            std::ostringstream oss;
×
1350
            oss << "Test # " << test_cnt << " ";
×
1351
            if(!header.empty()) {
×
1352
               oss << header << " ";
×
1353
            }
1354

1355
            for(const auto& k : m_data->required_keys()) {
×
1356
               oss << k << "=" << vars.get_req_str(k) << " ";
×
1357
            }
1358

1359
            oss << "failed with exception '" << e.what() << "'";
×
1360

1361
            results.push_back(Test::Result::Failure(header_or_name, oss.str()));
×
1362
         }
×
1363

1364
         if(clear_between_callbacks()) {
47,965✔
1365
            vars.clear();
188,049✔
1366
         }
1367
      }
1368
   }
156,096✔
1369

1370
   if(results.empty()) {
180✔
1371
      return results;
1372
   }
1373

1374
   try {
180✔
1375
      std::vector<Test::Result> final_tests = run_final_tests();
180✔
1376
      results.insert(results.end(), final_tests.begin(), final_tests.end());
180✔
1377
   } catch(std::exception& e) {
180✔
1378
      results.push_back(Test::Result::Failure(header_or_name, "run_final_tests exception " + std::string(e.what())));
×
1379
   }
×
1380

1381
   return results;
1382
}
180✔
1383

1384
}  // 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