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

randombit / botan / 21786344715

07 Feb 2026 08:25PM UTC coverage: 90.068% (-0.005%) from 90.073%
21786344715

Pull #5295

github

web-flow
Merge 8d5fc3b23 into ebf8f0044
Pull Request #5295: Reduce header dependencies in tests and cli

102234 of 113507 relevant lines covered (90.07%)

11372278.81 hits per line

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

79.77
/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/symkey.h>
12
#include <botan/internal/filesystem.h>
13
#include <botan/internal/fmt.h>
14
#include <botan/internal/loadstor.h>
15
#include <botan/internal/parsing.h>
16
#include <botan/internal/stl_util.h>
17
#include <botan/internal/target_info.h>
18
#include <chrono>
19
#include <deque>
20
#include <fstream>
21
#include <iomanip>
22
#include <set>
23
#include <sstream>
24
#include <unordered_set>
25

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

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

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

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

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

50
#if defined(BOTAN_HAS_ECC_GROUP)
51
   #include <botan/ec_group.h>
52
#endif
53

54
namespace Botan_Tests {
55

56
Test::Test() = default;
419✔
57

58
Test::~Test() = default;
564✔
59

60
Test::Result::Result(std::string who) : m_who(std::move(who)), m_timestamp(Test::timestamp()) {}
81,126✔
61

62
void Test::Result::merge(const Result& other, bool ignore_test_name) {
126,761✔
63
   if(who() != other.who()) {
126,761✔
64
      if(!ignore_test_name) {
73✔
65
         throw Test_Error("Merging tests from different sources");
×
66
      }
67

68
      // When deliberately merging results with different names, the code location is
69
      // likely inconsistent and must be discarded.
70
      m_where.reset();
73✔
71
   } else {
72
      m_where = other.m_where;
126,688✔
73
   }
74

75
   m_timestamp = std::min(m_timestamp, other.m_timestamp);
126,761✔
76
   m_ns_taken += other.m_ns_taken;
126,761✔
77
   m_tests_passed += other.m_tests_passed;
126,761✔
78
   m_fail_log.insert(m_fail_log.end(), other.m_fail_log.begin(), other.m_fail_log.end());
126,761✔
79
   m_log.insert(m_log.end(), other.m_log.begin(), other.m_log.end());
126,761✔
80
}
126,761✔
81

82
void Test::Result::start_timer() {
1,192✔
83
   if(m_started == 0) {
1,192✔
84
      m_started = Test::timestamp();
2,384✔
85
   }
86
}
1,192✔
87

88
void Test::Result::end_timer() {
1,304✔
89
   if(m_started > 0) {
1,304✔
90
      m_ns_taken += Test::timestamp() - m_started;
2,382✔
91
      m_started = 0;
1,191✔
92
   }
93
}
1,304✔
94

95
void Test::Result::test_note(const std::string& who, std::span<const uint8_t> data) {
12✔
96
   const std::string hex = Botan::hex_encode(data);
12✔
97
   return test_note(who, hex.c_str());
12✔
98
}
12✔
99

100
void Test::Result::test_note(const std::string& note, const char* extra) {
2,767✔
101
   if(!note.empty()) {
2,767✔
102
      std::ostringstream out;
2,767✔
103
      out << who() << " " << note;
2,767✔
104
      if(extra != nullptr) {
2,767✔
105
         out << ": " << extra;
12✔
106
      }
107
      m_log.push_back(out.str());
2,767✔
108
   }
2,767✔
109
}
2,767✔
110

111
void Test::Result::note_missing(const std::string& whatever) {
275✔
112
   static std::set<std::string> s_already_seen;
275✔
113

114
   if(!s_already_seen.contains(whatever)) {
275✔
115
      test_note("Skipping tests due to missing " + whatever);
5✔
116
      s_already_seen.insert(whatever);
5✔
117
   }
118
}
275✔
119

120
bool Test::Result::ThrowExpectations::check(const std::string& test_name, Test::Result& result) {
67,786✔
121
   m_consumed = true;
67,786✔
122

123
   try {
67,786✔
124
      m_fn();
67,786✔
125
      if(!m_expect_success) {
1,646✔
126
         return result.test_failure(test_name + " failed to throw expected exception");
2✔
127
      }
128
   } catch(const std::exception& ex) {
66,140✔
129
      if(m_expect_success) {
66,138✔
130
         return result.test_failure(test_name + " threw unexpected exception: " + ex.what());
2✔
131
      }
132
      if(m_expected_exception_check_fn && !m_expected_exception_check_fn(std::current_exception())) {
190,233✔
133
         return result.test_failure(test_name + " threw unexpected exception: " + ex.what());
4✔
134
      }
135
      if(m_expected_message.has_value() && m_expected_message.value() != ex.what()) {
66,135✔
136
         return result.test_failure(test_name + " threw exception with unexpected message (expected: '" +
3✔
137
                                    m_expected_message.value() + "', got: '" + ex.what() + "')");
6✔
138
      }
139
   } catch(...) {
66,140✔
140
      if(m_expect_success || m_expected_exception_check_fn || m_expected_message.has_value()) {
2✔
141
         return result.test_failure(test_name + " threw unexpected unknown exception");
1✔
142
      }
143
   }
2✔
144

145
   return result.test_success(test_name + " behaved as expected");
135,558✔
146
}
147

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

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

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

160
bool Test::Result::test_success(const std::string& note) {
3,620,264✔
161
   if(Test::options().log_success()) {
3,620,132✔
162
      test_note(note);
×
163
   }
164
   ++m_tests_passed;
3,620,264✔
165
   return true;
775,709✔
166
}
167

168
bool Test::Result::test_failure(const std::string& what, const std::string& error) {
1✔
169
   return test_failure(who() + " " + what + " with error " + error);
4✔
170
}
171

172
void Test::Result::test_failure(const std::string& what, const uint8_t buf[], size_t buf_len) {
1✔
173
   test_failure(who() + ": " + what + " buf len " + std::to_string(buf_len) + " value " +
5✔
174
                Botan::hex_encode(buf, buf_len));
1✔
175
}
1✔
176

177
bool Test::Result::test_failure(const std::string& err) {
25✔
178
   m_fail_log.push_back(err);
25✔
179

180
   if(Test::options().abort_on_first_fail() && m_who != "Failing Test") {
25✔
181
      std::abort();
×
182
   }
183
   return false;
25✔
184
}
185

186
namespace {
187

188
bool same_contents(const uint8_t x[], const uint8_t y[], size_t len) {
292,303✔
189
   return (len == 0) ? true : std::memcmp(x, y, len) == 0;
290,872✔
190
}
191

192
}  // namespace
193

194
bool Test::Result::test_ne(const std::string& what,
3,408✔
195
                           const uint8_t produced[],
196
                           size_t produced_len,
197
                           const uint8_t expected[],
198
                           size_t expected_len) {
199
   if(produced_len == expected_len && same_contents(produced, expected, expected_len)) {
3,408✔
200
      return test_failure(who() + ": " + what + " produced matching");
6✔
201
   }
202
   return test_success();
6,812✔
203
}
204

205
bool Test::Result::test_eq(const std::string& what, std::span<const uint8_t> produced, const char* expected_hex) {
453✔
206
   const std::vector<uint8_t> expected = Botan::hex_decode(expected_hex);
453✔
207
   return test_eq(nullptr, what, produced.data(), produced.size(), expected.data(), expected.size());
453✔
208
}
453✔
209

210
bool Test::Result::test_eq(const char* producer,
290,856✔
211
                           const std::string& what,
212
                           const uint8_t produced[],
213
                           size_t produced_size,
214
                           const uint8_t expected[],
215
                           size_t expected_size) {
216
   if(produced_size == expected_size && same_contents(produced, expected, expected_size)) {
290,856✔
217
      return test_success();
581,710✔
218
   }
219

220
   std::ostringstream err;
1✔
221

222
   err << who();
1✔
223

224
   if(producer != nullptr) {
1✔
225
      err << " producer '" << producer << "'";
×
226
   }
227

228
   err << " unexpected result for " << what;
1✔
229

230
   if(produced_size != expected_size) {
1✔
231
      err << " produced " << produced_size << " bytes expected " << expected_size;
1✔
232
   }
233

234
   std::vector<uint8_t> xor_diff(std::min(produced_size, expected_size));
2✔
235
   size_t bytes_different = 0;
1✔
236

237
   for(size_t i = 0; i != xor_diff.size(); ++i) {
4✔
238
      xor_diff[i] = produced[i] ^ expected[i];
3✔
239
      if(xor_diff[i] > 0) {
3✔
240
         bytes_different++;
3✔
241
      }
242
   }
243

244
   err << "\nProduced: " << Botan::hex_encode(produced, produced_size)
1✔
245
       << "\nExpected: " << Botan::hex_encode(expected, expected_size);
3✔
246

247
   if(bytes_different > 0) {
1✔
248
      err << "\nXOR Diff: " << Botan::hex_encode(xor_diff);
1✔
249
   }
250

251
   return test_failure(err.str());
1✔
252
}
1✔
253

254
bool Test::Result::test_is_nonempty(const std::string& what_is_it, const std::string& to_examine) {
38,829✔
255
   if(to_examine.empty()) {
38,829✔
256
      return test_failure(what_is_it + " was empty");
1✔
257
   }
258
   return test_success();
77,656✔
259
}
260

261
bool Test::Result::test_eq(const std::string& what, const std::string& produced, const std::string& expected) {
76,783✔
262
   return test_is_eq(what, produced, expected);
76,783✔
263
}
264

265
bool Test::Result::test_eq(const std::string& what, const char* produced, const char* expected) {
29✔
266
   return test_is_eq(what, std::string(produced), std::string(expected));
29✔
267
}
268

269
bool Test::Result::test_eq(const std::string& what, size_t produced, size_t expected) {
146,706✔
270
   return test_is_eq(what, produced, expected);
146,706✔
271
}
272

273
bool Test::Result::test_eq_sz(const std::string& what, size_t produced, size_t expected) {
45,789✔
274
   return test_is_eq(what, produced, expected);
45,789✔
275
}
276

277
bool Test::Result::test_eq(const std::string& what,
107✔
278
                           const Botan::OctetString& produced,
279
                           const Botan::OctetString& expected) {
280
   std::ostringstream out;
107✔
281
   out << m_who << " " << what;
107✔
282

283
   if(produced == expected) {
107✔
284
      out << " produced expected result " << produced.to_string();
214✔
285
      return test_success(out.str());
107✔
286
   } else {
287
      out << " produced unexpected result '" << produced.to_string() << "' expected '" << expected.to_string() << "'";
×
288
      return test_failure(out.str());
×
289
   }
290
}
107✔
291

292
bool Test::Result::test_lt(const std::string& what, size_t produced, size_t expected) {
5,639✔
293
   if(produced >= expected) {
5,639✔
294
      std::ostringstream err;
1✔
295
      err << m_who << " " << what;
1✔
296
      err << " unexpected result " << produced << " >= " << expected;
1✔
297
      return test_failure(err.str());
1✔
298
   }
1✔
299

300
   return test_success();
11,276✔
301
}
302

303
bool Test::Result::test_lte(const std::string& what, size_t produced, size_t expected) {
1,021,636✔
304
   if(produced > expected) {
1,021,636✔
305
      std::ostringstream err;
1✔
306
      err << m_who << " " << what << " unexpected result " << produced << " > " << expected;
1✔
307
      return test_failure(err.str());
1✔
308
   }
1✔
309

310
   return test_success();
2,043,270✔
311
}
312

313
bool Test::Result::test_gte(const std::string& what, size_t produced, size_t expected) {
1,142,430✔
314
   if(produced < expected) {
1,142,430✔
315
      std::ostringstream err;
1✔
316
      err << m_who;
1✔
317
      err << " " << what;
1✔
318
      err << " unexpected result " << produced << " < " << expected;
1✔
319
      return test_failure(err.str());
1✔
320
   }
1✔
321

322
   return test_success();
2,284,858✔
323
}
324

325
bool Test::Result::test_gt(const std::string& what, size_t produced, size_t expected) {
22,373✔
326
   if(produced <= expected) {
22,373✔
327
      std::ostringstream err;
×
328
      err << m_who;
×
329
      err << " " << what;
×
330
      err << " unexpected result " << produced << " <= " << expected;
×
331
      return test_failure(err.str());
×
332
   }
×
333

334
   return test_success();
44,746✔
335
}
336

337
bool Test::Result::test_ne(const std::string& what, const std::string& str1, const std::string& str2) {
26✔
338
   if(str1 != str2) {
26✔
339
      return test_success(str1 + " != " + str2);
50✔
340
   }
341

342
   return test_failure(who() + " " + what + " produced matching strings " + str1);
4✔
343
}
344

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

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

355
#if defined(BOTAN_HAS_BIGINT)
356
bool Test::Result::test_eq(const std::string& what, const BigInt& produced, const BigInt& expected) {
252,645✔
357
   return test_is_eq(what, produced, expected);
252,645✔
358
}
359

360
bool Test::Result::test_ne(const std::string& what, const BigInt& produced, const BigInt& expected) {
97✔
361
   if(produced != expected) {
97✔
362
      return test_success();
192✔
363
   }
364

365
   std::ostringstream err;
1✔
366
   err << who() << " " << what << " produced " << produced << " prohibited value";
1✔
367
   return test_failure(err.str());
1✔
368
}
1✔
369
#endif
370

371
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
372
bool Test::Result::test_eq(const std::string& what, const Botan::EC_Point& a, const Botan::EC_Point& b) {
3,248✔
373
   //return test_is_eq(what, a, b);
374
   if(a == b) {
3,248✔
375
      return test_success();
6,496✔
376
   }
377

378
   std::ostringstream err;
×
379
   err << who() << " " << what << " a=(" << a.get_affine_x() << "," << a.get_affine_y() << ")"
×
380
       << " b=(" << b.get_affine_x() << "," << b.get_affine_y();
×
381
   return test_failure(err.str());
×
382
}
×
383
#endif
384

385
bool Test::Result::test_eq(const std::string& what, bool produced, bool expected) {
378,474✔
386
   return test_is_eq(what, produced, expected);
378,474✔
387
}
388

389
bool Test::Result::test_rc_ok(const std::string& func, int rc) {
2,810✔
390
   if(rc != 0) {
2,810✔
391
      std::ostringstream err;
1✔
392
      err << m_who << " " << func << " unexpectedly failed with error code " << rc;
1✔
393
      return test_failure(err.str());
1✔
394
   }
1✔
395

396
   return test_success();
5,618✔
397
}
398

399
bool Test::Result::test_rc_fail(const std::string& func, const std::string& why, int rc) {
26✔
400
   if(rc == 0) {
26✔
401
      std::ostringstream err;
1✔
402
      err << m_who << " call to " << func << " unexpectedly succeeded expecting failure because " << why;
1✔
403
      return test_failure(err.str());
1✔
404
   }
1✔
405

406
   return test_success();
50✔
407
}
408

409
bool Test::Result::test_rc_init(const std::string& func, int rc) {
117✔
410
   if(rc == 0) {
117✔
411
      return test_success();
234✔
412
   } else {
413
      std::ostringstream msg;
×
414
      msg << m_who;
×
415
      msg << " " << func;
×
416

417
      // -40 is BOTAN_FFI_ERROR_NOT_IMPLEMENTED
418
      if(rc == -40) {
×
419
         msg << " returned not implemented";
×
420
      } else {
421
         msg << " unexpectedly failed with error code " << rc;
×
422
      }
423

424
      if(rc == -40) {
×
425
         this->test_note(msg.str());
×
426
      } else {
427
         this->test_failure(msg.str());
×
428
      }
429
      return false;
×
430
   }
×
431
}
432

433
bool Test::Result::test_rc(const std::string& func, int expected, int rc) {
416✔
434
   if(expected != rc) {
416✔
435
      std::ostringstream err;
1✔
436
      err << m_who;
1✔
437
      err << " call to " << func << " unexpectedly returned " << rc;
1✔
438
      err << " but expecting " << expected;
1✔
439
      return test_failure(err.str());
1✔
440
   }
1✔
441

442
   return test_success();
830✔
443
}
444

445
void Test::initialize(std::string test_name, CodeLocation location) {
419✔
446
   m_test_name = std::move(test_name);
419✔
447
   m_registration_location = location;
419✔
448
}
419✔
449

450
Botan::RandomNumberGenerator& Test::rng() const {
459,814✔
451
   if(!m_test_rng) {
459,814✔
452
      m_test_rng = Test::new_rng(m_test_name);
145✔
453
   }
454

455
   return *m_test_rng;
459,814✔
456
}
457

458
std::optional<std::string> Test::supported_ec_group_name(std::vector<std::string> preferred_groups) {
151✔
459
#if defined(BOTAN_HAS_ECC_GROUP)
460
   if(preferred_groups.empty()) {
151✔
461
      preferred_groups = {
149✔
462
         "secp256r1",
463
         "brainpool256r1",
464
         "secp384r1",
465
         "brainpool384r1",
466
         "secp521r1",
467
         "brainpool512r1",
468
      };
1,192✔
469
   }
470

471
   for(const auto& group : preferred_groups) {
151✔
472
      if(Botan::EC_Group::supports_named_group(group)) {
151✔
473
         return group;
151✔
474
      }
475
   }
476
#else
477
   BOTAN_UNUSED(preferred_groups);
478
#endif
479

480
   return std::nullopt;
×
481
}
298✔
482

483
std::vector<uint8_t> Test::mutate_vec(const std::vector<uint8_t>& v,
96,742✔
484
                                      Botan::RandomNumberGenerator& rng,
485
                                      bool maybe_resize,
486
                                      size_t min_offset) {
487
   std::vector<uint8_t> r = v;
96,742✔
488

489
   if(maybe_resize && (r.empty() || rng.next_byte() < 32)) {
110,315✔
490
      // TODO: occasionally truncate, insert at random index
491
      const size_t add = 1 + (rng.next_byte() % 16);
2,213✔
492
      r.resize(r.size() + add);
2,213✔
493
      rng.randomize(&r[r.size() - add], add);
2,213✔
494
   }
495

496
   if(r.size() > min_offset) {
96,742✔
497
      const size_t offset = std::max<size_t>(min_offset, rng.next_byte() % r.size());
95,204✔
498
      const uint8_t perturb = rng.next_nonzero_byte();
95,204✔
499
      r[offset] ^= perturb;
95,204✔
500
   }
501

502
   return r;
96,742✔
503
}
×
504

505
std::vector<std::string> Test::possible_providers(const std::string& /*alg*/) {
×
506
   return Test::provider_filter({"base"});
×
507
}
508

509
//static
510
std::string Test::format_time(uint64_t nanoseconds) {
1,470✔
511
   std::ostringstream o;
1,470✔
512

513
   if(nanoseconds > 1000000000) {
1,470✔
514
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000000.0 << " sec";
151✔
515
   } else {
516
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000.0 << " msec";
1,319✔
517
   }
518

519
   return o.str();
2,940✔
520
}
1,470✔
521

522
Test::Result::Result(std::string who, const std::vector<Result>& downstream_results) : Result(std::move(who)) {
14✔
523
   for(const auto& result : downstream_results) {
82✔
524
      merge(result, true /* ignore non-matching test names */);
68✔
525
   }
526
}
14✔
527

528
// TODO: this should move to `StdoutReporter`
529
std::string Test::Result::result_string() const {
2,541✔
530
   const bool verbose = Test::options().verbose();
2,541✔
531

532
   if(tests_run() == 0 && !verbose) {
2,541✔
533
      return "";
20✔
534
   }
535

536
   std::ostringstream report;
2,521✔
537

538
   report << who() << " ran ";
2,521✔
539

540
   if(tests_run() == 0) {
2,521✔
541
      report << "ZERO";
×
542
   } else {
543
      report << tests_run();
2,521✔
544
   }
545
   report << " tests";
2,521✔
546

547
   if(m_ns_taken > 0) {
2,521✔
548
      report << " in " << format_time(m_ns_taken);
2,938✔
549
   }
550

551
   if(tests_failed() > 0) {
2,521✔
552
      report << " " << tests_failed() << " FAILED";
25✔
553
   } else {
554
      report << " all ok";
2,496✔
555
   }
556

557
   report << "\n";
2,521✔
558

559
   for(size_t i = 0; i != m_fail_log.size(); ++i) {
2,546✔
560
      report << "Failure " << (i + 1) << ": " << m_fail_log[i];
25✔
561
      if(m_where) {
25✔
562
         report << " (at " << m_where->path << ":" << m_where->line << ")";
×
563
      }
564
      report << "\n";
25✔
565
   }
566

567
   if(!m_fail_log.empty() || tests_run() == 0 || verbose) {
2,521✔
568
      for(size_t i = 0; i != m_log.size(); ++i) {
25✔
569
         report << "Note " << (i + 1) << ": " << m_log[i] << "\n";
×
570
      }
571
   }
572

573
   return report.str();
2,521✔
574
}
2,521✔
575

576
namespace {
577

578
class Test_Registry {
579
   public:
580
      static Test_Registry& instance() {
1,279✔
581
         static Test_Registry registry;
1,280✔
582
         return registry;
1,279✔
583
      }
584

585
      void register_test(const std::string& category,
419✔
586
                         const std::string& name,
587
                         bool smoke_test,
588
                         bool needs_serialization,
589
                         std::function<std::unique_ptr<Test>()> maker_fn) {
590
         if(m_tests.contains(name)) {
419✔
591
            throw Test_Error("Duplicate registration of test '" + name + "'");
×
592
         }
593

594
         if(m_tests.contains(category)) {
419✔
595
            throw Test_Error("'" + category + "' cannot be used as category, test exists");
×
596
         }
597

598
         if(m_categories.contains(name)) {
419✔
599
            throw Test_Error("'" + name + "' cannot be used as test name, category exists");
×
600
         }
601

602
         if(smoke_test) {
419✔
603
            m_smoke_tests.push_back(name);
10✔
604
         }
605

606
         if(needs_serialization) {
419✔
607
            m_mutexed_tests.push_back(name);
21✔
608
         }
609

610
         m_tests.emplace(name, std::move(maker_fn));
419✔
611
         m_categories.emplace(category, name);
419✔
612
      }
419✔
613

614
      std::unique_ptr<Test> get_test(const std::string& test_name) const {
419✔
615
         auto i = m_tests.find(test_name);
419✔
616
         if(i != m_tests.end()) {
419✔
617
            return i->second();
419✔
618
         }
619
         return nullptr;
×
620
      }
621

622
      std::vector<std::string> registered_tests() const {
×
623
         std::vector<std::string> s;
×
624
         s.reserve(m_tests.size());
×
625
         for(auto&& i : m_tests) {
×
626
            s.push_back(i.first);
×
627
         }
628
         return s;
×
629
      }
×
630

631
      std::vector<std::string> registered_test_categories() const {
×
632
         std::set<std::string> s;
×
633
         for(auto&& i : m_categories) {
×
634
            s.insert(i.first);
×
635
         }
636
         return std::vector<std::string>(s.begin(), s.end());
×
637
      }
×
638

639
      std::vector<std::string> filter_registered_tests(const std::vector<std::string>& requested,
1✔
640
                                                       const std::vector<std::string>& to_be_skipped) {
641
         std::vector<std::string> result;
1✔
642

643
         std::set<std::string> to_be_skipped_set(to_be_skipped.begin(), to_be_skipped.end());
1✔
644
         // TODO: this is O(n^2), but we have a relatively small number of tests.
645
         auto insert_if_not_exists_and_not_skipped = [&](const std::string& test_name) {
420✔
646
            if(!Botan::value_exists(result, test_name) && !to_be_skipped_set.contains(test_name)) {
838✔
647
               result.push_back(test_name);
409✔
648
            }
649
         };
420✔
650

651
         if(requested.empty()) {
1✔
652
            /*
653
            If nothing was requested on the command line, run everything. First
654
            run the "essentials" to smoke test, then everything else in
655
            alphabetical order.
656
            */
657
            result = m_smoke_tests;
1✔
658
            for(const auto& [test_name, _] : m_tests) {
420✔
659
               insert_if_not_exists_and_not_skipped(test_name);
419✔
660
            }
661
         } else {
662
            for(const auto& r : requested) {
×
663
               if(m_tests.contains(r)) {
×
664
                  insert_if_not_exists_and_not_skipped(r);
×
665
               } else if(auto elems = m_categories.equal_range(r); elems.first != m_categories.end()) {
×
666
                  for(; elems.first != elems.second; ++elems.first) {
×
667
                     insert_if_not_exists_and_not_skipped(elems.first->second);
×
668
                  }
669
               } else {
670
                  throw Test_Error("Unknown test suite or category: " + r);
×
671
               }
672
            }
673
         }
674

675
         return result;
1✔
676
      }
1✔
677

678
      bool needs_serialization(const std::string& test_name) const {
440✔
679
         return Botan::value_exists(m_mutexed_tests, test_name);
21✔
680
      }
681

682
   private:
683
      Test_Registry() = default;
1✔
684

685
   private:
686
      std::map<std::string, std::function<std::unique_ptr<Test>()>> m_tests;
687
      std::multimap<std::string, std::string> m_categories;
688
      std::vector<std::string> m_smoke_tests;
689
      std::vector<std::string> m_mutexed_tests;
690
};
691

692
}  // namespace
693

694
// static Test:: functions
695

696
//static
697
void Test::register_test(const std::string& category,
419✔
698
                         const std::string& name,
699
                         bool smoke_test,
700
                         bool needs_serialization,
701
                         std::function<std::unique_ptr<Test>()> maker_fn) {
702
   Test_Registry::instance().register_test(category, name, smoke_test, needs_serialization, std::move(maker_fn));
838✔
703
}
419✔
704

705
//static
706
uint64_t Test::timestamp() {
180,013✔
707
   auto now = std::chrono::system_clock::now().time_since_epoch();
180,013✔
708
   return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
2,383✔
709
}
710

711
//static
712
std::vector<Test::Result> Test::flatten_result_lists(std::vector<std::vector<Test::Result>> result_lists) {
4✔
713
   std::vector<Test::Result> results;
4✔
714
   for(auto& result_list : result_lists) {
26✔
715
      for(auto& result : result_list) {
71✔
716
         results.emplace_back(std::move(result));
49✔
717
      }
718
   }
719
   return results;
4✔
720
}
×
721

722
//static
723
std::vector<std::string> Test::registered_tests() {
×
724
   return Test_Registry::instance().registered_tests();
×
725
}
726

727
//static
728
std::vector<std::string> Test::registered_test_categories() {
×
729
   return Test_Registry::instance().registered_test_categories();
×
730
}
731

732
//static
733
std::unique_ptr<Test> Test::get_test(const std::string& test_name) {
419✔
734
   return Test_Registry::instance().get_test(test_name);
419✔
735
}
736

737
//static
738
bool Test::test_needs_serialization(const std::string& test_name) {
419✔
739
   return Test_Registry::instance().needs_serialization(test_name);
419✔
740
}
741

742
//static
743
std::vector<std::string> Test::filter_registered_tests(const std::vector<std::string>& requested,
1✔
744
                                                       const std::vector<std::string>& to_be_skipped) {
745
   return Test_Registry::instance().filter_registered_tests(requested, to_be_skipped);
1✔
746
}
747

748
//static
749
std::string Test::temp_file_name(const std::string& basename) {
21✔
750
   // TODO add a --tmp-dir option to the tests to specify where these files go
751

752
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
753

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

757
   const int fd = ::mkstemp(mkstemp_basename.data());
21✔
758

759
   // error
760
   if(fd < 0) {
21✔
761
      return "";
×
762
   }
763

764
   ::close(fd);
21✔
765

766
   return mkstemp_basename;
21✔
767
#else
768
   // For now just create the temp in the current working directory
769
   return basename;
770
#endif
771
}
21✔
772

773
bool Test::copy_file(const std::string& from, const std::string& to) {
1✔
774
#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(__cpp_lib_filesystem)
775
   std::error_code ec;  // don't throw, just return false on error
1✔
776
   return std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing, ec);
1✔
777
#else
778
   // TODO: implement fallbacks to POSIX or WIN32
779
   // ... but then again: it's 2023 and we're using C++20 :o)
780
   BOTAN_UNUSED(from, to);
781
   throw Botan::No_Filesystem_Access();
782
#endif
783
}
784

785
std::string Test::read_data_file(const std::string& path) {
38✔
786
   const std::string fsname = Test::data_file(path);
38✔
787
   std::ifstream file(fsname.c_str());
38✔
788
   if(!file.good()) {
38✔
789
      throw Test_Error("Error reading from " + fsname);
×
790
   }
791

792
   return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
76✔
793
}
38✔
794

795
std::vector<uint8_t> Test::read_binary_data_file(const std::string& path) {
34✔
796
   const std::string fsname = Test::data_file(path);
34✔
797
   std::ifstream file(fsname.c_str(), std::ios::binary);
34✔
798
   if(!file.good()) {
34✔
799
      throw Test_Error("Error reading from " + fsname);
×
800
   }
801

802
   std::vector<uint8_t> contents;
34✔
803

804
   while(file.good()) {
74✔
805
      std::vector<uint8_t> buf(4096);
40✔
806
      file.read(reinterpret_cast<char*>(buf.data()), buf.size());
40✔
807
      const size_t got = static_cast<size_t>(file.gcount());
40✔
808

809
      if(got == 0 && file.eof()) {
40✔
810
         break;
811
      }
812

813
      contents.insert(contents.end(), buf.data(), buf.data() + got);
40✔
814
   }
40✔
815

816
   return contents;
68✔
817
}
34✔
818

819
// static member variables of Test
820

821
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
822
Test_Options Test::m_opts;
823
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
824
std::string Test::m_test_rng_seed;
825

826
//static
827
void Test::set_test_options(const Test_Options& opts) {
1✔
828
   m_opts = opts;
1✔
829
}
1✔
830

831
namespace {
832

833
/*
834
* This is a fast, simple, deterministic PRNG that's used for running
835
* the tests. It is not intended to be cryptographically secure.
836
*/
837
class Testsuite_RNG final : public Botan::RandomNumberGenerator {
8✔
838
   public:
839
      std::string name() const override { return "Testsuite_RNG"; }
×
840

841
      void clear() override { m_x = 0; }
×
842

843
      bool accepts_input() const override { return true; }
×
844

845
      bool is_seeded() const override { return true; }
276,286✔
846

847
      void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> input) override {
10,305,851✔
848
         for(const auto byte : input) {
10,305,851✔
849
            mix(byte);
×
850
         }
851

852
         for(auto& byte : output) {
73,078,797✔
853
            byte = mix();
62,772,946✔
854
         }
855
      }
10,305,851✔
856

857
      Testsuite_RNG(std::string_view seed, std::string_view test_name) : m_x(0) {
276✔
858
         for(const char c : seed) {
8,280✔
859
            this->mix(static_cast<uint8_t>(c));
8,004✔
860
         }
861
         for(const char c : test_name) {
5,578✔
862
            this->mix(static_cast<uint8_t>(c));
5,302✔
863
         }
864
      }
276✔
865

866
   private:
867
      uint8_t mix(uint8_t input = 0) {
62,786,252✔
868
         m_x ^= input;
62,786,252✔
869
         m_x *= 0xF2E16957;
62,786,252✔
870
         m_x += 0xE50B590F;
62,786,252✔
871
         return static_cast<uint8_t>(m_x >> 27);
62,786,252✔
872
      }
873

874
      uint64_t m_x;
875
};
876

877
}  // namespace
878

879
//static
880
void Test::set_test_rng_seed(std::span<const uint8_t> seed, size_t epoch) {
1✔
881
   m_test_rng_seed = Botan::fmt("seed={} epoch={}", Botan::hex_encode(seed), epoch);
1✔
882
}
1✔
883

884
//static
885
std::unique_ptr<Botan::RandomNumberGenerator> Test::new_rng(std::string_view test_name) {
268✔
886
   return std::make_unique<Testsuite_RNG>(m_test_rng_seed, test_name);
268✔
887
}
888

889
//static
890
std::shared_ptr<Botan::RandomNumberGenerator> Test::new_shared_rng(std::string_view test_name) {
8✔
891
   return std::make_shared<Testsuite_RNG>(m_test_rng_seed, test_name);
8✔
892
}
893

894
//static
895
std::string Test::data_file(const std::string& file) {
773✔
896
   return options().data_dir() + "/" + file;
1,546✔
897
}
898

899
//static
900
std::string Test::data_dir(const std::string& subdir) {
1✔
901
   return options().data_dir() + "/" + subdir;
2✔
902
}
903

904
//static
905
std::vector<std::string> Test::files_in_data_dir(const std::string& subdir) {
393✔
906
   auto fs = Botan::get_files_recursive(options().data_dir() + "/" + subdir);
1,179✔
907
   if(fs.empty()) {
393✔
908
      throw Test_Error("Test::files_in_data_dir encountered empty subdir " + subdir);
×
909
   }
910
   return fs;
393✔
911
}
×
912

913
//static
914
std::string Test::data_file_as_temporary_copy(const std::string& what) {
1✔
915
   auto tmp_basename = what;
1✔
916
   std::replace(tmp_basename.begin(), tmp_basename.end(), '/', '_');
1✔
917
   auto temp_file = temp_file_name("tmp-" + tmp_basename);
1✔
918
   if(temp_file.empty()) {
1✔
919
      return "";
×
920
   }
921
   if(!Test::copy_file(data_file(what), temp_file)) {
1✔
922
      return "";
×
923
   }
924
   return temp_file;
1✔
925
}
1✔
926

927
//static
928
std::vector<std::string> Test::provider_filter(const std::vector<std::string>& providers) {
49,022✔
929
   if(m_opts.provider().empty()) {
49,022✔
930
      return providers;
49,022✔
931
   }
932
   for(auto&& provider : providers) {
×
933
      if(provider == m_opts.provider()) {
×
934
         return std::vector<std::string>{provider};
×
935
      }
936
   }
937
   return std::vector<std::string>{};
×
938
}
×
939

940
std::string Test::random_password(Botan::RandomNumberGenerator& rng) {
222✔
941
   const size_t len = 1 + rng.next_byte() % 32;
222✔
942
   return Botan::hex_encode(rng.random_vec(len));
444✔
943
}
944

945
size_t Test::random_index(Botan::RandomNumberGenerator& rng, size_t max) {
8,062✔
946
   return Botan::load_be(rng.random_array<8>()) % max;
8,062✔
947
}
948

949
void VarMap::clear() {
34,281✔
950
   m_vars.clear();
×
951
}
×
952

953
namespace {
954

955
bool varmap_pair_lt(const std::pair<std::string, std::string>& kv, const std::string& k) {
1,713,618✔
956
   return kv.first < k;
1,713,618✔
957
}
958

959
}  // namespace
960

961
void VarMap::add(const std::string& key, const std::string& value) {
156,235✔
962
   auto i = std::lower_bound(m_vars.begin(), m_vars.end(), key, varmap_pair_lt);
156,235✔
963

964
   if(i != m_vars.end() && i->first == key) {
156,235✔
965
      i->second = value;
44,405✔
966
   } else {
967
      m_vars.emplace(i, key, value);
111,830✔
968
   }
969
}
156,235✔
970

971
std::optional<std::string> VarMap::get_var(const std::string& key) const {
568,446✔
972
   auto i = std::lower_bound(m_vars.begin(), m_vars.end(), key, varmap_pair_lt);
568,446✔
973

974
   if(i != m_vars.end() && i->first == key) {
568,446✔
975
      return i->second;
498,591✔
976
   } else {
977
      return {};
69,855✔
978
   }
979
}
980

981
bool VarMap::has_key(const std::string& key) const {
213,574✔
982
   return get_var(key).has_value();
213,574✔
983
}
984

985
std::string VarMap::get_req_str(const std::string& key) const {
259,629✔
986
   auto var = get_var(key);
259,629✔
987
   if(var) {
259,629✔
988
      return *var;
519,258✔
989
   } else {
990
      throw Test_Error("Test missing variable " + key);
×
991
   }
992
}
259,629✔
993

994
std::vector<std::vector<uint8_t>> VarMap::get_req_bin_list(const std::string& key) const {
12✔
995
   const auto var = get_req_str(key);
12✔
996

997
   std::vector<std::vector<uint8_t>> bin_list;
12✔
998

999
   for(auto&& part : Botan::split_on(var, ',')) {
62✔
1000
      try {
50✔
1001
         bin_list.push_back(Botan::hex_decode(part));
100✔
1002
      } catch(std::exception& e) {
×
1003
         std::ostringstream oss;
×
1004
         oss << "Bad input '" << part << "'"
×
1005
             << " in binary list key " << key << " - " << e.what();
×
1006
         throw Test_Error(oss.str());
×
1007
      }
×
1008
   }
12✔
1009

1010
   return bin_list;
12✔
1011
}
12✔
1012

1013
std::vector<uint8_t> VarMap::get_req_bin(const std::string& key) const {
163,711✔
1014
   const auto var = get_req_str(key);
163,711✔
1015

1016
   try {
163,711✔
1017
      if(var.starts_with("0x")) {
163,711✔
1018
         if(var.size() % 2 == 0) {
×
1019
            return Botan::hex_decode(var.substr(2));
×
1020
         } else {
1021
            std::string z = var;
×
1022
            std::swap(z[0], z[1]);  // swap 0x to x0 then remove x
×
1023
            return Botan::hex_decode(z.substr(1));
×
1024
         }
×
1025
      } else {
1026
         return Botan::hex_decode(var);
163,711✔
1027
      }
1028
   } catch(std::exception& e) {
×
1029
      std::ostringstream oss;
×
1030
      oss << "Bad input '" << var << "'"
×
1031
          << " for key " << key << " - " << e.what();
×
1032
      throw Test_Error(oss.str());
×
1033
   }
×
1034
}
163,711✔
1035

1036
std::string VarMap::get_opt_str(const std::string& key, const std::string& def_value) const {
14,378✔
1037
   if(auto v = get_var(key)) {
14,378✔
1038
      return *v;
57✔
1039
   } else {
1040
      return def_value;
28,699✔
1041
   }
14,378✔
1042
}
1043

1044
bool VarMap::get_req_bool(const std::string& key) const {
39✔
1045
   const auto var = get_req_str(key);
39✔
1046

1047
   if(var == "true") {
39✔
1048
      return true;
1049
   } else if(var == "false") {
23✔
1050
      return false;
1051
   } else {
1052
      throw Test_Error("Invalid boolean for key '" + key + "' value '" + var + "'");
×
1053
   }
1054
}
39✔
1055

1056
size_t VarMap::get_req_sz(const std::string& key) const {
4,547✔
1057
   return Botan::to_u32bit(get_req_str(key));
4,547✔
1058
}
1059

1060
uint8_t VarMap::get_req_u8(const std::string& key) const {
17✔
1061
   const size_t s = this->get_req_sz(key);
17✔
1062
   if(s > 256) {
17✔
1063
      throw Test_Error("Invalid " + key + " expected uint8_t got " + std::to_string(s));
×
1064
   }
1065
   return static_cast<uint8_t>(s);
17✔
1066
}
1067

1068
uint32_t VarMap::get_req_u32(const std::string& key) const {
14✔
1069
   return static_cast<uint32_t>(get_req_sz(key));
14✔
1070
}
1071

1072
uint64_t VarMap::get_req_u64(const std::string& key) const {
17✔
1073
   const auto var = get_req_str(key);
17✔
1074
   try {
17✔
1075
      return std::stoull(var);
17✔
1076
   } catch(std::exception&) {
×
1077
      throw Test_Error("Invalid u64 value '" + var + "'");
×
1078
   }
×
1079
}
17✔
1080

1081
size_t VarMap::get_opt_sz(const std::string& key, const size_t def_value) const {
31,379✔
1082
   if(auto v = get_var(key)) {
31,379✔
1083
      return Botan::to_u32bit(*v);
12,244✔
1084
   } else {
1085
      return def_value;
1086
   }
31,379✔
1087
}
1088

1089
uint64_t VarMap::get_opt_u64(const std::string& key, const uint64_t def_value) const {
3,538✔
1090
   if(auto v = get_var(key)) {
3,538✔
1091
      try {
641✔
1092
         return std::stoull(*v);
3,538✔
1093
      } catch(std::exception&) {
×
1094
         throw Test_Error("Invalid u64 value '" + *v + "'");
×
1095
      }
×
1096
   } else {
1097
      return def_value;
1098
   }
3,538✔
1099
}
1100

1101
std::vector<uint8_t> VarMap::get_opt_bin(const std::string& key) const {
45,868✔
1102
   if(auto v = get_var(key)) {
45,868✔
1103
      try {
16,134✔
1104
         return Botan::hex_decode(*v);
16,134✔
1105
      } catch(std::exception&) {
×
1106
         throw Test_Error("Test invalid hex input '" + *v + "'" + +" for key " + key);
×
1107
      }
×
1108
   } else {
1109
      return {};
29,734✔
1110
   };
45,868✔
1111
}
1112

1113
#if defined(BOTAN_HAS_BIGINT)
1114
Botan::BigInt VarMap::get_req_bn(const std::string& key) const {
38,502✔
1115
   const auto var = get_req_str(key);
38,502✔
1116

1117
   try {
38,502✔
1118
      return Botan::BigInt(var);
38,502✔
1119
   } catch(std::exception&) {
×
1120
      throw Test_Error("Test invalid bigint input '" + var + "' for key " + key);
×
1121
   }
×
1122
}
38,502✔
1123

1124
Botan::BigInt VarMap::get_opt_bn(const std::string& key, const Botan::BigInt& def_value) const {
80✔
1125
   if(auto v = get_var(key)) {
80✔
1126
      try {
56✔
1127
         return Botan::BigInt(*v);
56✔
1128
      } catch(std::exception&) {
×
1129
         throw Test_Error("Test invalid bigint input '" + *v + "' for key " + key);
×
1130
      }
×
1131
   } else {
1132
      return def_value;
24✔
1133
   }
80✔
1134
}
1135
#endif
1136

1137
class Text_Based_Test::Text_Based_Test_Data {
1138
   public:
1139
      Text_Based_Test_Data(const std::string& data_src,
181✔
1140
                           const std::string& required_keys_str,
1141
                           const std::string& optional_keys_str) :
181✔
1142
            m_data_src(data_src) {
362✔
1143
         if(required_keys_str.empty()) {
181✔
1144
            throw Test_Error("Invalid test spec");
×
1145
         }
1146

1147
         m_required_keys = Botan::split_on(required_keys_str, ',');
181✔
1148
         std::vector<std::string> optional_keys = Botan::split_on(optional_keys_str, ',');
181✔
1149

1150
         m_all_keys.insert(m_required_keys.begin(), m_required_keys.end());
181✔
1151
         m_all_keys.insert(optional_keys.begin(), optional_keys.end());
181✔
1152
         m_output_key = m_required_keys.at(m_required_keys.size() - 1);
181✔
1153
      }
181✔
1154

1155
      std::string get_next_line();
1156

1157
      const std::string& current_source_name() const { return m_cur_src_name; }
×
1158

1159
      bool known_key(const std::string& key) const;
1160

1161
      const std::vector<std::string>& required_keys() const { return m_required_keys; }
48,388✔
1162

1163
      const std::string& output_key() const { return m_output_key; }
156,235✔
1164

1165
      const std::vector<std::string>& cpu_flags() const { return m_cpu_flags; }
48,249✔
1166

1167
      void set_cpu_flags(std::vector<std::string> flags) { m_cpu_flags = std::move(flags); }
21✔
1168

1169
      const std::string& initial_data_src_name() const { return m_data_src; }
181✔
1170

1171
   private:
1172
      std::string m_data_src;
1173
      std::vector<std::string> m_required_keys;
1174
      std::unordered_set<std::string> m_all_keys;
1175
      std::string m_output_key;
1176

1177
      bool m_first = true;
1178
      std::unique_ptr<std::istream> m_cur;
1179
      std::string m_cur_src_name;
1180
      std::deque<std::string> m_srcs;
1181
      std::vector<std::string> m_cpu_flags;
1182
};
1183

1184
Text_Based_Test::Text_Based_Test(const std::string& data_src,
181✔
1185
                                 const std::string& required_keys,
1186
                                 const std::string& optional_keys) :
181✔
1187
      m_data(std::make_unique<Text_Based_Test_Data>(data_src, required_keys, optional_keys)) {}
181✔
1188

1189
Text_Based_Test::~Text_Based_Test() = default;
181✔
1190

1191
std::string Text_Based_Test::Text_Based_Test_Data::get_next_line() {
157,242✔
1192
   while(true) {
157,500✔
1193
      if(m_cur == nullptr || m_cur->good() == false) {
157,500✔
1194
         if(m_srcs.empty()) {
450✔
1195
            if(m_first) {
362✔
1196
               if(m_data_src.ends_with(".vec")) {
181✔
1197
                  m_srcs.push_back(Test::data_file(m_data_src));
338✔
1198
               } else {
1199
                  const auto fs = Test::files_in_data_dir(m_data_src);
12✔
1200
                  m_srcs.assign(fs.begin(), fs.end());
12✔
1201
                  if(m_srcs.empty()) {
12✔
1202
                     throw Test_Error("Error reading test data dir " + m_data_src);
×
1203
                  }
1204
               }
12✔
1205

1206
               m_first = false;
181✔
1207
            } else {
1208
               return "";  // done
181✔
1209
            }
1210
         }
1211

1212
         m_cur = std::make_unique<std::ifstream>(m_srcs[0]);
357✔
1213
         m_cur_src_name = m_srcs[0];
269✔
1214

1215
#if defined(BOTAN_HAS_CPUID)
1216
         // Reinit cpuid on new file if needed
1217
         if(m_cpu_flags.empty() == false) {
269✔
1218
            m_cpu_flags.clear();
19✔
1219
            Botan::CPUID::initialize();
19✔
1220
         }
1221
#endif
1222

1223
         if(!m_cur->good()) {
269✔
1224
            throw Test_Error("Could not open input file '" + m_cur_src_name);
×
1225
         }
1226

1227
         m_srcs.pop_front();
269✔
1228
      }
1229

1230
      while(m_cur->good()) {
227,897✔
1231
         std::string line;
227,639✔
1232
         std::getline(*m_cur, line);
227,639✔
1233

1234
         if(line.empty()) {
227,639✔
1235
            continue;
55,310✔
1236
         }
1237

1238
         if(line[0] == '#') {
172,329✔
1239
            if(line.starts_with("#test ")) {
15,289✔
1240
               return line;
21✔
1241
            } else {
1242
               continue;
15,268✔
1243
            }
1244
         }
1245

1246
         return line;
157,040✔
1247
      }
227,639✔
1248
   }
1249
}
1250

1251
bool Text_Based_Test::Text_Based_Test_Data::known_key(const std::string& key) const {
156,235✔
1252
   return m_all_keys.contains(key);
156,235✔
1253
}
1254

1255
namespace {
1256

1257
// strips leading and trailing but not internal whitespace
1258
std::string strip_ws(const std::string& in) {
312,470✔
1259
   const char* whitespace = " ";
312,470✔
1260

1261
   const auto first_c = in.find_first_not_of(whitespace);
312,470✔
1262
   if(first_c == std::string::npos) {
312,470✔
1263
      return "";
1,033✔
1264
   }
1265

1266
   const auto last_c = in.find_last_not_of(whitespace);
311,437✔
1267

1268
   return in.substr(first_c, last_c - first_c + 1);
311,437✔
1269
}
1270

1271
std::vector<std::string> parse_cpuid_bits(const std::vector<std::string>& tok) {
21✔
1272
   std::vector<std::string> bits;
21✔
1273

1274
#if defined(BOTAN_HAS_CPUID)
1275
   for(size_t i = 1; i < tok.size(); ++i) {
106✔
1276
      if(auto bit = Botan::CPUID::bit_from_string(tok[i])) {
85✔
1277
         bits.push_back(bit->to_string());
98✔
1278
      }
1279
   }
1280
#else
1281
   BOTAN_UNUSED(tok);
1282
#endif
1283

1284
   return bits;
21✔
1285
}
×
1286

1287
}  // namespace
1288

1289
bool Text_Based_Test::skip_this_test(const std::string& /*header*/, const VarMap& /*vars*/) {
32,609✔
1290
   return false;
32,609✔
1291
}
1292

1293
std::vector<Test::Result> Text_Based_Test::run() {
181✔
1294
   std::vector<Test::Result> results;
181✔
1295

1296
   std::string header;
181✔
1297
   std::string header_or_name = m_data->initial_data_src_name();
181✔
1298
   VarMap vars;
181✔
1299
   size_t test_cnt = 0;
181✔
1300

1301
   while(true) {
157,242✔
1302
      const std::string line = m_data->get_next_line();
157,242✔
1303
      if(line.empty()) {
157,242✔
1304
         // EOF
1305
         break;
1306
      }
1307

1308
      if(line.starts_with("#test ")) {
157,061✔
1309
         std::vector<std::string> pragma_tokens = Botan::split_on(line.substr(6), ' ');
21✔
1310

1311
         if(pragma_tokens.empty()) {
21✔
1312
            throw Test_Error("Empty pragma found in " + m_data->current_source_name());
×
1313
         }
1314

1315
         if(pragma_tokens[0] != "cpuid") {
21✔
1316
            throw Test_Error("Unknown test pragma '" + line + "' in " + m_data->current_source_name());
×
1317
         }
1318

1319
         if(!Test_Registry::instance().needs_serialization(this->test_name())) {
42✔
1320
            throw Test_Error(Botan::fmt("'{}' used cpuid control but is not serialized", this->test_name()));
×
1321
         }
1322

1323
         m_data->set_cpu_flags(parse_cpuid_bits(pragma_tokens));
42✔
1324

1325
         continue;
21✔
1326
      } else if(line[0] == '#') {
157,061✔
1327
         throw Test_Error("Unknown test pragma '" + line + "' in " + m_data->current_source_name());
×
1328
      }
1329

1330
      if(line[0] == '[' && line[line.size() - 1] == ']') {
157,040✔
1331
         header = line.substr(1, line.size() - 2);
805✔
1332
         header_or_name = header;
805✔
1333
         test_cnt = 0;
805✔
1334
         vars.clear();
805✔
1335
         continue;
805✔
1336
      }
1337

1338
      const std::string test_id = "test " + std::to_string(test_cnt);
312,470✔
1339

1340
      auto equal_i = line.find_first_of('=');
156,235✔
1341

1342
      if(equal_i == std::string::npos) {
156,235✔
1343
         results.push_back(Test::Result::Failure(header_or_name, "invalid input '" + line + "'"));
×
1344
         continue;
×
1345
      }
1346

1347
      const std::string key = strip_ws(std::string(line.begin(), line.begin() + equal_i - 1));
312,470✔
1348
      const std::string val = strip_ws(std::string(line.begin() + equal_i + 1, line.end()));
312,470✔
1349

1350
      if(!m_data->known_key(key)) {
156,235✔
1351
         auto r = Test::Result::Failure(header_or_name, Botan::fmt("{} failed unknown key {}", test_id, key));
×
1352
         results.push_back(r);
×
1353
      }
×
1354

1355
      vars.add(key, val);
156,235✔
1356

1357
      if(key == m_data->output_key()) {
156,235✔
1358
         try {
48,388✔
1359
            for(const auto& req_key : m_data->required_keys()) {
245,554✔
1360
               if(!vars.has_key(req_key)) {
197,166✔
1361
                  auto r =
×
1362
                     Test::Result::Failure(header_or_name, Botan::fmt("{} missing required key {}", test_id, req_key));
×
1363
                  results.push_back(r);
×
1364
               }
×
1365
            }
1366

1367
            if(skip_this_test(header, vars)) {
48,388✔
1368
               continue;
139✔
1369
            }
1370

1371
            ++test_cnt;
48,249✔
1372

1373
            const uint64_t start = Test::timestamp();
48,249✔
1374

1375
            Test::Result result = run_one_test(header, vars);
48,249✔
1376
#if defined(BOTAN_HAS_CPUID)
1377
            if(!m_data->cpu_flags().empty()) {
48,249✔
1378
               for(const auto& cpuid_str : m_data->cpu_flags()) {
30,621✔
1379
                  if(const auto bit = Botan::CPUID::Feature::from_string(cpuid_str)) {
21,677✔
1380
                     if(Botan::CPUID::has(*bit)) {
21,677✔
1381
                        Botan::CPUID::clear_cpuid_bit(*bit);
16,577✔
1382
                        // now re-run the test
1383
                        result.merge(run_one_test(header, vars));
16,577✔
1384
                     }
1385
                  }
1386
               }
1387
               Botan::CPUID::initialize();
8,944✔
1388
            }
1389
#endif
1390
            result.set_ns_consumed(Test::timestamp() - start);
48,249✔
1391

1392
            if(result.tests_failed() > 0) {
48,249✔
1393
               std::ostringstream oss;
×
1394
               oss << "Test # " << test_cnt << " ";
×
1395
               if(!header.empty()) {
×
1396
                  oss << header << " ";
×
1397
               }
1398
               oss << "failed ";
×
1399

1400
               for(const auto& k : m_data->required_keys()) {
×
1401
                  oss << k << "=" << vars.get_req_str(k) << " ";
×
1402
               }
1403

1404
               result.test_note(oss.str());
×
1405
            }
×
1406
            results.push_back(result);
48,249✔
1407
         } catch(std::exception& e) {
48,249✔
1408
            std::ostringstream oss;
×
1409
            oss << "Test # " << test_cnt << " ";
×
1410
            if(!header.empty()) {
×
1411
               oss << header << " ";
×
1412
            }
1413

1414
            for(const auto& k : m_data->required_keys()) {
×
1415
               oss << k << "=" << vars.get_req_str(k) << " ";
×
1416
            }
1417

1418
            oss << "failed with exception '" << e.what() << "'";
×
1419

1420
            results.push_back(Test::Result::Failure(header_or_name, oss.str()));
×
1421
         }
×
1422

1423
         if(clear_between_callbacks()) {
48,249✔
1424
            vars.clear();
189,572✔
1425
         }
1426
      }
1427
   }
157,339✔
1428

1429
   if(results.empty()) {
181✔
1430
      return results;
1431
   }
1432

1433
   try {
181✔
1434
      std::vector<Test::Result> final_tests = run_final_tests();
181✔
1435
      results.insert(results.end(), final_tests.begin(), final_tests.end());
181✔
1436
   } catch(std::exception& e) {
181✔
1437
      results.push_back(Test::Result::Failure(header_or_name, "run_final_tests exception " + std::string(e.what())));
×
1438
   }
×
1439

1440
   return results;
1441
}
181✔
1442

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