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

randombit / botan / 22301318151

23 Feb 2026 10:02AM UTC coverage: 90.335% (-0.002%) from 90.337%
22301318151

Pull #5382

github

web-flow
Merge 928d15ffa into 53329c11e
Pull Request #5382: Generalize XMSS_Index_Registry and use it for LMS as well

103017 of 114039 relevant lines covered (90.33%)

11716532.65 hits per line

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

80.85
/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_Error::Test_Error(std::string_view what) : std::runtime_error(std::string(what)) {}
2✔
57

58
Test::Test() = default;
421✔
59

60
Test::~Test() = default;
567✔
61

62
Test::Result::Result(std::string_view who) : m_who(who), m_timestamp(Test::timestamp()) {}
81,366✔
63

64
Test::Result::Result(std::string_view who, const std::vector<Result>& downstream_results) : Result(who) {
14✔
65
   for(const auto& result : downstream_results) {
82✔
66
      merge(result, true /* ignore non-matching test names */);
68✔
67
   }
68
}
14✔
69

70
void Test::Result::merge(const Result& other, bool ignore_test_name) {
126,946✔
71
   if(who() != other.who()) {
126,946✔
72
      if(!ignore_test_name) {
73✔
73
         throw Test_Error("Merging tests from different sources");
×
74
      }
75

76
      // When deliberately merging results with different names, the code location is
77
      // likely inconsistent and must be discarded.
78
      m_where.reset();
73✔
79
   } else {
80
      m_where = other.m_where;
126,873✔
81
   }
82

83
   m_timestamp = std::min(m_timestamp, other.m_timestamp);
126,946✔
84
   m_ns_taken += other.m_ns_taken;
126,946✔
85
   m_tests_passed += other.m_tests_passed;
126,946✔
86
   m_fail_log.insert(m_fail_log.end(), other.m_fail_log.begin(), other.m_fail_log.end());
126,946✔
87
   m_log.insert(m_log.end(), other.m_log.begin(), other.m_log.end());
126,946✔
88
}
126,946✔
89

90
void Test::Result::start_timer() {
1,192✔
91
   if(m_started == 0) {
1,192✔
92
      m_started = Test::timestamp();
2,384✔
93
   }
94
}
1,192✔
95

96
void Test::Result::end_timer() {
1,304✔
97
   if(m_started > 0) {
1,304✔
98
      m_ns_taken += Test::timestamp() - m_started;
2,382✔
99
      m_started = 0;
1,191✔
100
   }
101
}
1,304✔
102

103
void Test::Result::test_note(std::string_view note, std::span<const uint8_t> context) {
12✔
104
   return test_note(note, Botan::hex_encode(context));
24✔
105
}
106

107
void Test::Result::test_note(std::string_view note, std::string_view context) {
2,147✔
108
   m_log.emplace_back(Botan::fmt("{} {}: {}", who(), note, context));
2,147✔
109
}
2,147✔
110

111
void Test::Result::test_note(std::string_view note) {
620✔
112
   m_log.emplace_back(Botan::fmt("{} {}", who(), note));
620✔
113
}
620✔
114

115
void Test::Result::note_missing(std::string_view whatever_sv) {
275✔
116
   static std::set<std::string> s_already_seen;
275✔
117

118
   const std::string whatever(whatever_sv);
275✔
119
   if(!s_already_seen.contains(whatever)) {
275✔
120
      test_note(Botan::fmt("Skipping tests due to missing {}", whatever));
5✔
121
      s_already_seen.insert(whatever);
275✔
122
   }
123
}
275✔
124

125
void Test::Result::require(std::string_view what, bool expr) {
322✔
126
   if(!test_is_true(what, expr)) {
322✔
127
      throw Test_Aborted(Botan::fmt("Test aborted, because required condition was not met: {}", what));
×
128
   }
129
}
322✔
130

131
Test::Result::ThrowExpectations::~ThrowExpectations() {
67,783✔
132
   BOTAN_ASSERT_NOMSG(m_consumed);
67,783✔
133
}
130,020✔
134

135
void Test::Result::ThrowExpectations::assert_that_success_is_not_expected() const {
62,237✔
136
   BOTAN_ASSERT_NOMSG(!m_expect_success);
62,048✔
137
}
62,048✔
138

139
Test::Result::ThrowExpectations& Test::Result::ThrowExpectations::expect_success() {
1,645✔
140
   BOTAN_ASSERT_NOMSG(!m_expected_message && !m_expected_exception_check_fn);
1,645✔
141
   m_expect_success = true;
1,645✔
142
   return *this;
1,645✔
143
}
144

145
Test::Result::ThrowExpectations& Test::Result::ThrowExpectations::expect_message(std::string_view message) {
189✔
146
   assert_that_success_is_not_expected();
189✔
147
   m_expected_message = message;
189✔
148
   return *this;
189✔
149
}
150

151
bool Test::Result::ThrowExpectations::check(std::string_view test_name, Test::Result& result) {
67,783✔
152
   m_consumed = true;
67,783✔
153

154
   try {
67,783✔
155
      m_fn();
67,783✔
156
      if(!m_expect_success) {
1,646✔
157
         return result.test_failure(Botan::fmt("{} failed to throw expected exception", test_name));
2✔
158
      }
159
   } catch(const std::exception& ex) {
66,137✔
160
      if(m_expect_success) {
66,135✔
161
         return result.test_failure(Botan::fmt("{} threw unexpected exception: {}", test_name, ex.what()));
1✔
162
      }
163
      if(m_expected_exception_check_fn && !m_expected_exception_check_fn(std::current_exception())) {
190,230✔
164
         return result.test_failure(Botan::fmt("{} threw unexpected exception: {}", test_name, ex.what()));
2✔
165
      }
166
      if(m_expected_message.has_value() && m_expected_message.value() != ex.what()) {
66,132✔
167
         return result.test_failure(Botan::fmt("{} threw exception with unexpected message (expected {} got {})",
1✔
168
                                               test_name,
169
                                               m_expected_message.value(),
1✔
170
                                               ex.what()));
2✔
171
      }
172
   } catch(...) {
66,137✔
173
      if(m_expect_success || m_expected_exception_check_fn || m_expected_message.has_value()) {
2✔
174
         return result.test_failure(Botan::fmt("{} threw unexpected unknown exception", test_name));
1✔
175
      }
176
   }
2✔
177

178
   return result.test_success();
67,776✔
179
}
180

181
bool Test::Result::test_throws(std::string_view what, std::function<void()> fn) {
3,916✔
182
   return ThrowExpectations(std::move(fn)).check(what, *this);
11,748✔
183
}
184

185
bool Test::Result::test_throws(std::string_view what, std::string_view expected, std::function<void()> fn) {
174✔
186
   return ThrowExpectations(std::move(fn)).expect_message(expected).check(what, *this);
522✔
187
}
188

189
bool Test::Result::test_no_throw(std::string_view what, std::function<void()> fn) {
1,645✔
190
   return ThrowExpectations(std::move(fn)).expect_success().check(what, *this);
4,935✔
191
}
192

193
bool Test::Result::test_success(std::string_view note) {
3,621,537✔
194
   if(Test::options().log_success()) {
3,621,521✔
195
      test_note(note);
×
196
   }
197
   ++m_tests_passed;
3,621,537✔
198
   return true;
67,776✔
199
}
200

201
bool Test::Result::test_failure(std::string_view what, std::string_view error) {
2✔
202
   return test_failure(Botan::fmt("{} {} with error {}", who(), what, error));
2✔
203
}
204

205
void Test::Result::test_failure(std::string_view what, const uint8_t buf[], size_t buf_len) {
×
206
   return test_failure(what, {buf, buf_len});
×
207
}
208

209
void Test::Result::test_failure(std::string_view what, std::span<const uint8_t> context) {
1✔
210
   test_failure(Botan::fmt("{} {} with value {}", who(), what, Botan::hex_encode(context)));
2✔
211
}
1✔
212

213
bool Test::Result::test_failure(const char* err) {
×
214
   return test_failure(std::string_view(err));
×
215
}
216

217
bool Test::Result::test_failure(std::string_view err) {
1✔
218
   return test_failure(std::string(err));
1✔
219
}
220

221
bool Test::Result::test_failure(std::string err) {
24✔
222
   m_fail_log.push_back(std::move(err));
24✔
223

224
   if(Test::options().abort_on_first_fail() && m_who != "Failing Test") {
24✔
225
      std::abort();
×
226
   }
227
   return false;
24✔
228
}
229

230
namespace {
231

232
bool same_contents(std::span<const uint8_t> x, std::span<const uint8_t> y) {
302,842✔
233
   if(x.size() != y.size()) {
1,417✔
234
      return false;
235
   }
236
   if(x.empty()) {
302,841✔
237
      return true;
238
   }
239

240
   return std::memcmp(x.data(), y.data(), x.size()) == 0;
301,409✔
241
}
242

243
}  // namespace
244

245
bool Test::Result::test_bin_ne(std::string_view what,
3,407✔
246
                               std::span<const uint8_t> produced,
247
                               std::span<const uint8_t> expected) {
248
   if(produced.size() == expected.size() && same_contents(produced, expected)) {
4,824✔
249
      return test_failure(Botan::fmt("{} {} produced matching bytes", who(), what));
1✔
250
   }
251
   return test_success();
3,406✔
252
}
253

254
bool Test::Result::test_bin_eq(std::string_view what,
556✔
255
                               std::span<const uint8_t> produced,
256
                               std::string_view expected_hex) {
257
   const std::vector<uint8_t> expected = Botan::hex_decode(expected_hex);
556✔
258
   return test_bin_eq(what, produced, expected);
556✔
259
}
556✔
260

261
bool Test::Result::test_bin_eq(std::string_view what,
301,425✔
262
                               std::span<const uint8_t> produced,
263
                               std::span<const uint8_t> expected) {
264
   if(same_contents(produced, expected)) {
601,417✔
265
      return test_success();
301,424✔
266
   }
267

268
   std::ostringstream err;
1✔
269

270
   err << who();
1✔
271

272
   err << " unexpected result for " << what;
1✔
273

274
   if(produced.size() != expected.size()) {
1✔
275
      err << " produced " << produced.size() << " bytes expected " << expected.size();
1✔
276
   }
277

278
   std::vector<uint8_t> xor_diff(std::min(produced.size(), expected.size()));
2✔
279
   size_t bytes_different = 0;
1✔
280

281
   for(size_t i = 0; i != xor_diff.size(); ++i) {
4✔
282
      xor_diff[i] = produced[i] ^ expected[i];
3✔
283
      if(xor_diff[i] > 0) {
3✔
284
         bytes_different++;
3✔
285
      }
286
   }
287

288
   err << "\nProduced: " << Botan::hex_encode(produced) << "\nExpected: " << Botan::hex_encode(expected);
1✔
289

290
   if(bytes_different > 0) {
1✔
291
      err << "\nXOR Diff: " << Botan::hex_encode(xor_diff);
1✔
292
   }
293

294
   return test_failure(err.str());
1✔
295
}
1✔
296

297
bool Test::Result::test_str_not_empty(std::string_view what, std::string_view produced) {
38,863✔
298
   if(produced.empty()) {
38,863✔
299
      return test_failure(what, "was empty");
1✔
300
   } else {
301
      return test_success();
38,862✔
302
   }
303
}
304

305
bool Test::Result::test_str_eq(std::string_view what, std::string_view produced, std::string_view expected) {
76,864✔
306
   if(produced == expected) {
76,864✔
307
      return test_success();
76,864✔
308
   } else {
309
      return test_failure(Botan::fmt("Assertion failure in {} {}: '{}' == '{}'", who(), what, produced, expected));
×
310
   }
311
}
312

313
bool Test::Result::test_str_ne(std::string_view what, std::string_view str1, std::string_view str2) {
17✔
314
   if(str1 != str2) {
17✔
315
      return test_success(Botan::fmt("{} != {}", str1, str2));
16✔
316
   } else {
317
      return test_failure(Botan::fmt("{} {} unexpectedly produced matching strings {}", who(), what, str1));
1✔
318
   }
319
}
320

321
bool Test::Result::test_i16_eq(std::string_view what, int16_t produced, int16_t expected) {
1,025✔
322
   return test_i32_eq(what, produced, expected);
1,025✔
323
}
324

325
bool Test::Result::test_i32_eq(std::string_view what, int32_t produced, int32_t expected) {
1,026✔
326
   if(produced == expected) {
1,026✔
327
      return test_success();
1,026✔
328
   } else {
329
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} == {}", who(), what, produced, expected));
×
330
   }
331
}
332

333
bool Test::Result::test_u8_eq(uint8_t produced, uint8_t expected) {
214✔
334
   return test_sz_eq("comparison", produced, expected);
214✔
335
}
336

337
bool Test::Result::test_u8_eq(std::string_view what, uint8_t produced, uint8_t expected) {
49,385✔
338
   return test_sz_eq(what, produced, expected);
49,385✔
339
}
340

341
bool Test::Result::test_u16_eq(uint16_t produced, uint16_t expected) {
40✔
342
   return test_sz_eq("comparison", produced, expected);
40✔
343
}
344

345
bool Test::Result::test_u16_eq(std::string_view what, uint16_t produced, uint16_t expected) {
66,792✔
346
   return test_sz_eq(what, produced, expected);
66,792✔
347
}
348

349
bool Test::Result::test_u32_eq(uint32_t produced, uint32_t expected) {
16✔
350
   return test_sz_eq("comparison", produced, expected);
16✔
351
}
352

353
bool Test::Result::test_u32_eq(std::string_view what, uint32_t produced, uint32_t expected) {
4,198✔
354
   return test_sz_eq(what, produced, expected);
4,198✔
355
}
356

357
bool Test::Result::test_u64_eq(uint64_t produced, uint64_t expected) {
13✔
358
   return test_u64_eq("comparison", produced, expected);
13✔
359
}
360

361
bool Test::Result::test_u64_eq(std::string_view what, uint64_t produced, uint64_t expected) {
1,549✔
362
   if(produced == expected) {
1,549✔
363
      return test_success();
1,549✔
364
   } else {
365
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} == {}", who(), what, produced, expected));
×
366
   }
367
}
368

369
bool Test::Result::test_u64_lt(std::string_view what, uint64_t produced, uint64_t expected) {
2✔
370
   if(produced < expected) {
2✔
371
      return test_success();
2✔
372
   } else {
373
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} < {}", who(), what, produced, expected));
×
374
   }
375
}
376

377
bool Test::Result::test_sz_eq(std::string_view what, size_t produced, size_t expected) {
197,640✔
378
   if(produced == expected) {
197,640✔
379
      return test_success();
197,639✔
380
   } else {
381
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} == {}", who(), what, produced, expected));
1✔
382
   }
383
}
384

385
bool Test::Result::test_sz_ne(std::string_view what, size_t produced, size_t expected) {
119✔
386
   if(produced != expected) {
119✔
387
      return test_success();
118✔
388
   } else {
389
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} != {}", who(), what, produced, expected));
1✔
390
   }
391
}
392

393
bool Test::Result::test_sz_lt(std::string_view what, size_t produced, size_t expected) {
5,639✔
394
   if(produced < expected) {
5,639✔
395
      return test_success();
5,638✔
396
   } else {
397
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} < {}", who(), what, produced, expected));
1✔
398
   }
399
}
400

401
bool Test::Result::test_sz_lte(std::string_view what, size_t produced, size_t expected) {
1,021,636✔
402
   if(produced <= expected) {
1,021,636✔
403
      return test_success();
1,021,635✔
404
   } else {
405
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} <= {}", who(), what, produced, expected));
1✔
406
   }
407
}
408

409
bool Test::Result::test_sz_gt(std::string_view what, size_t produced, size_t expected) {
29,981✔
410
   if(produced > expected) {
29,981✔
411
      return test_success();
29,981✔
412
   } else {
413
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} > {}", who(), what, produced, expected));
×
414
   }
415
}
416

417
bool Test::Result::test_sz_gte(std::string_view what, size_t produced, size_t expected) {
1,142,521✔
418
   if(produced >= expected) {
1,142,521✔
419
      return test_success();
1,142,520✔
420
   } else {
421
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} >= {}", who(), what, produced, expected));
1✔
422
   }
423
}
424

425
bool Test::Result::test_opt_u8_eq(std::string_view what,
1,215✔
426
                                  std::optional<uint8_t> produced,
427
                                  std::optional<uint8_t> expected) {
428
   if(produced.has_value() and !expected.has_value()) {
1,215✔
429
      return test_failure(Botan::fmt("Assertion {} produced value {} but nullopt was expected", what, *produced));
×
430
   } else if(!produced.has_value() && expected.has_value()) {
1,215✔
431
      return test_failure(Botan::fmt("Assertion {} produced nullopt but {} was expected", what, *expected));
×
432
   } else if(produced.has_value() && expected.has_value()) {
1,215✔
433
      return test_u8_eq(what, *produced, *expected);
10✔
434
   } else {
435
      return test_success();
1,205✔
436
   }
437
}
438

439
bool Test::Result::test_opt_u64_eq(std::string_view what,
9✔
440
                                   std::optional<uint64_t> produced,
441
                                   std::optional<uint64_t> expected) {
442
   if(produced.has_value() and !expected.has_value()) {
9✔
443
      return test_failure(Botan::fmt("Assertion {} produced value {} but nullopt was expected", what, *produced));
×
444
   } else if(!produced.has_value() && expected.has_value()) {
9✔
445
      return test_failure(Botan::fmt("Assertion {} produced nullopt but {} was expected", what, *expected));
×
446
   } else if(produced.has_value() && expected.has_value()) {
9✔
447
      return test_u64_eq(what, *produced, *expected);
9✔
448
   } else {
449
      return test_success();
×
450
   }
451
}
452

453
#if defined(BOTAN_HAS_BIGINT)
454
bool Test::Result::test_bn_eq(std::string_view what, const BigInt& produced, const BigInt& expected) {
252,642✔
455
   if(produced == expected) {
252,642✔
456
      return test_success();
252,641✔
457
   } else {
458
      std::ostringstream err;
1✔
459
      err << who() << " " << what << " produced " << produced << " != expected value " << expected;
1✔
460
      return test_failure(err.str());
1✔
461
   }
1✔
462
}
463

464
bool Test::Result::test_bn_ne(std::string_view what, const BigInt& produced, const BigInt& expected) {
97✔
465
   if(produced != expected) {
97✔
466
      return test_success();
96✔
467
   } else {
468
      std::ostringstream err;
1✔
469
      err << who() << " " << what << " produced " << produced << " prohibited value";
1✔
470
      return test_failure(err.str());
1✔
471
   }
1✔
472
}
473
#endif
474

475
bool Test::Result::test_bool_eq(std::string_view what, bool produced, bool expected) {
361,897✔
476
   if(produced == expected) {
361,897✔
477
      return test_success();
361,897✔
478
   } else {
479
      if(expected == true) {
×
480
         return test_failure(Botan::fmt("Assertion failure in {}, {} was unexpectedly false", who(), what));
×
481
      } else {
482
         return test_failure(Botan::fmt("Assertion failure in {}, {} was unexpectedly true", who(), what));
×
483
      }
484
   }
485
}
486

487
bool Test::Result::test_is_true(std::string_view what, bool produced) {
228,309✔
488
   return test_bool_eq(what, produced, true);
228,309✔
489
}
490

491
bool Test::Result::test_is_false(std::string_view what, bool produced) {
126,017✔
492
   return test_bool_eq(what, produced, false);
126,017✔
493
}
494

495
bool Test::Result::test_rc_ok(std::string_view func, int rc) {
2,815✔
496
   if(rc != 0) {
2,815✔
497
      std::ostringstream err;
1✔
498
      err << m_who << " " << func << " unexpectedly failed with error code " << rc;
1✔
499
      return test_failure(err.str());
1✔
500
   }
1✔
501

502
   return test_success();
2,814✔
503
}
504

505
bool Test::Result::test_rc_fail(std::string_view func, std::string_view why, int rc) {
26✔
506
   if(rc == 0) {
26✔
507
      std::ostringstream err;
1✔
508
      err << m_who << " call to " << func << " unexpectedly succeeded expecting failure because " << why;
1✔
509
      return test_failure(err.str());
1✔
510
   }
1✔
511

512
   return test_success();
25✔
513
}
514

515
bool Test::Result::test_rc_init(std::string_view func, int rc) {
117✔
516
   if(rc == 0) {
117✔
517
      return test_success();
117✔
518
   } else {
519
      std::ostringstream msg;
×
520
      msg << m_who;
×
521
      msg << " " << func;
×
522

523
      // -40 is BOTAN_FFI_ERROR_NOT_IMPLEMENTED
524
      if(rc == -40) {
×
525
         msg << " returned not implemented";
×
526
      } else {
527
         msg << " unexpectedly failed with error code " << rc;
×
528
      }
529

530
      if(rc == -40) {
×
531
         this->test_note(msg.str());
×
532
      } else {
533
         this->test_failure(msg.str());
×
534
      }
535
      return false;
×
536
   }
×
537
}
538

539
bool Test::Result::test_rc(std::string_view func, int rc, int expected) {
426✔
540
   if(expected != rc) {
426✔
541
      std::ostringstream err;
1✔
542
      err << m_who;
1✔
543
      err << " call to " << func << " unexpectedly returned " << rc;
1✔
544
      err << " but expecting " << expected;
1✔
545
      return test_failure(err.str());
1✔
546
   }
1✔
547

548
   return test_success();
425✔
549
}
550

551
void Test::initialize(std::string test_name, CodeLocation location) {
421✔
552
   m_test_name = std::move(test_name);
421✔
553
   m_registration_location = location;
421✔
554
}
421✔
555

556
Botan::RandomNumberGenerator& Test::rng() const {
458,624✔
557
   if(!m_test_rng) {
458,624✔
558
      m_test_rng = Test::new_rng(m_test_name);
146✔
559
   }
560

561
   return *m_test_rng;
458,624✔
562
}
563

564
std::optional<std::string> Test::supported_ec_group_name(std::vector<std::string> preferred_groups) {
151✔
565
#if defined(BOTAN_HAS_ECC_GROUP)
566
   if(preferred_groups.empty()) {
151✔
567
      preferred_groups = {
149✔
568
         "secp256r1",
569
         "brainpool256r1",
570
         "secp384r1",
571
         "brainpool384r1",
572
         "secp521r1",
573
         "brainpool512r1",
574
      };
1,192✔
575
   }
576

577
   for(const auto& group : preferred_groups) {
151✔
578
      if(Botan::EC_Group::supports_named_group(group)) {
151✔
579
         return group;
151✔
580
      }
581
   }
582
#else
583
   BOTAN_UNUSED(preferred_groups);
584
#endif
585

586
   return std::nullopt;
×
587
}
298✔
588

589
std::vector<uint8_t> Test::mutate_vec(const std::vector<uint8_t>& v,
96,742✔
590
                                      Botan::RandomNumberGenerator& rng,
591
                                      bool maybe_resize,
592
                                      size_t min_offset) {
593
   std::vector<uint8_t> r = v;
96,742✔
594

595
   if(maybe_resize && (r.empty() || rng.next_byte() < 32)) {
110,319✔
596
      // TODO: occasionally truncate, insert at random index
597
      const size_t add = 1 + (rng.next_byte() % 16);
2,171✔
598
      r.resize(r.size() + add);
2,171✔
599
      rng.randomize(&r[r.size() - add], add);
2,171✔
600
   }
601

602
   if(r.size() > min_offset) {
96,742✔
603
      const size_t offset = std::max<size_t>(min_offset, rng.next_byte() % r.size());
95,204✔
604
      const uint8_t perturb = rng.next_nonzero_byte();
95,204✔
605
      r[offset] ^= perturb;
95,204✔
606
   }
607

608
   return r;
96,742✔
609
}
×
610

611
std::vector<std::string> Test::possible_providers(const std::string& /*alg*/) {
×
612
   return Test::provider_filter({"base"});
×
613
}
614

615
//static
616
std::string Test::format_time(uint64_t nanoseconds) {
1,470✔
617
   std::ostringstream o;
1,470✔
618

619
   if(nanoseconds > 1000000000) {
1,470✔
620
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000000.0 << " sec";
158✔
621
   } else {
622
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000.0 << " msec";
1,312✔
623
   }
624

625
   return o.str();
2,940✔
626
}
1,470✔
627

628
// TODO: this should move to `StdoutReporter`
629
std::string Test::Result::result_string() const {
2,612✔
630
   const bool verbose = Test::options().verbose();
2,612✔
631

632
   if(tests_run() == 0 && !verbose) {
2,612✔
633
      return "";
20✔
634
   }
635

636
   std::ostringstream report;
2,592✔
637

638
   report << who() << " ran ";
2,592✔
639

640
   if(tests_run() == 0) {
2,592✔
641
      report << "ZERO";
×
642
   } else {
643
      report << tests_run();
2,592✔
644
   }
645
   report << " tests";
2,592✔
646

647
   if(m_ns_taken > 0) {
2,592✔
648
      report << " in " << format_time(m_ns_taken);
2,938✔
649
   }
650

651
   if(tests_failed() > 0) {
2,592✔
652
      report << " " << tests_failed() << " FAILED";
24✔
653
   } else {
654
      report << " all ok";
2,568✔
655
   }
656

657
   report << "\n";
2,592✔
658

659
   for(size_t i = 0; i != m_fail_log.size(); ++i) {
2,616✔
660
      report << "Failure " << (i + 1) << ": " << m_fail_log[i];
24✔
661
      if(m_where) {
24✔
662
         report << " (at " << m_where->path << ":" << m_where->line << ")";
×
663
      }
664
      report << "\n";
24✔
665
   }
666

667
   if(!m_fail_log.empty() || tests_run() == 0 || verbose) {
2,592✔
668
      for(size_t i = 0; i != m_log.size(); ++i) {
24✔
669
         report << "Note " << (i + 1) << ": " << m_log[i] << "\n";
×
670
      }
671
   }
672

673
   return report.str();
2,592✔
674
}
2,592✔
675

676
namespace {
677

678
class Test_Registry {
679
   public:
680
      static Test_Registry& instance() {
1,285✔
681
         static Test_Registry registry;
1,286✔
682
         return registry;
1,285✔
683
      }
684

685
      void register_test(const std::string& category,
421✔
686
                         const std::string& name,
687
                         bool smoke_test,
688
                         bool needs_serialization,
689
                         std::function<std::unique_ptr<Test>()> maker_fn) {
690
         if(m_tests.contains(name)) {
421✔
691
            throw Test_Error("Duplicate registration of test '" + name + "'");
×
692
         }
693

694
         if(m_tests.contains(category)) {
421✔
695
            throw Test_Error("'" + category + "' cannot be used as category, test exists");
×
696
         }
697

698
         if(m_categories.contains(name)) {
421✔
699
            throw Test_Error("'" + name + "' cannot be used as test name, category exists");
×
700
         }
701

702
         if(smoke_test) {
421✔
703
            m_smoke_tests.push_back(name);
10✔
704
         }
705

706
         if(needs_serialization) {
421✔
707
            m_mutexed_tests.push_back(name);
22✔
708
         }
709

710
         m_tests.emplace(name, std::move(maker_fn));
421✔
711
         m_categories.emplace(category, name);
421✔
712
      }
421✔
713

714
      std::unique_ptr<Test> get_test(const std::string& test_name) const {
421✔
715
         auto i = m_tests.find(test_name);
421✔
716
         if(i != m_tests.end()) {
421✔
717
            return i->second();
421✔
718
         }
719
         return nullptr;
×
720
      }
721

722
      std::vector<std::string> registered_tests() const {
×
723
         std::vector<std::string> s;
×
724
         s.reserve(m_tests.size());
×
725
         for(auto&& i : m_tests) {
×
726
            s.push_back(i.first);
×
727
         }
728
         return s;
×
729
      }
×
730

731
      std::vector<std::string> registered_test_categories() const {
×
732
         std::set<std::string> s;
×
733
         for(auto&& i : m_categories) {
×
734
            s.insert(i.first);
×
735
         }
736
         return std::vector<std::string>(s.begin(), s.end());
×
737
      }
×
738

739
      std::vector<std::string> filter_registered_tests(const std::vector<std::string>& requested,
1✔
740
                                                       const std::vector<std::string>& to_be_skipped) {
741
         std::vector<std::string> result;
1✔
742

743
         std::set<std::string> to_be_skipped_set(to_be_skipped.begin(), to_be_skipped.end());
1✔
744
         // TODO: this is O(n^2), but we have a relatively small number of tests.
745
         auto insert_if_not_exists_and_not_skipped = [&](const std::string& test_name) {
422✔
746
            if(!Botan::value_exists(result, test_name) && !to_be_skipped_set.contains(test_name)) {
842✔
747
               result.push_back(test_name);
411✔
748
            }
749
         };
422✔
750

751
         if(requested.empty()) {
1✔
752
            /*
753
            If nothing was requested on the command line, run everything. First
754
            run the "essentials" to smoke test, then everything else in
755
            alphabetical order.
756
            */
757
            result = m_smoke_tests;
1✔
758
            for(const auto& [test_name, _] : m_tests) {
422✔
759
               insert_if_not_exists_and_not_skipped(test_name);
421✔
760
            }
761
         } else {
762
            for(const auto& r : requested) {
×
763
               if(m_tests.contains(r)) {
×
764
                  insert_if_not_exists_and_not_skipped(r);
×
765
               } else if(auto elems = m_categories.equal_range(r); elems.first != m_categories.end()) {
×
766
                  for(; elems.first != elems.second; ++elems.first) {
×
767
                     insert_if_not_exists_and_not_skipped(elems.first->second);
×
768
                  }
769
               } else {
770
                  throw Test_Error("Unknown test suite or category: " + r);
×
771
               }
772
            }
773
         }
774

775
         return result;
1✔
776
      }
1✔
777

778
      bool needs_serialization(const std::string& test_name) const {
442✔
779
         return Botan::value_exists(m_mutexed_tests, test_name);
21✔
780
      }
781

782
   private:
783
      Test_Registry() = default;
1✔
784

785
   private:
786
      std::map<std::string, std::function<std::unique_ptr<Test>()>> m_tests;
787
      std::multimap<std::string, std::string> m_categories;
788
      std::vector<std::string> m_smoke_tests;
789
      std::vector<std::string> m_mutexed_tests;
790
};
791

792
}  // namespace
793

794
// static Test:: functions
795

796
//static
797
void Test::register_test(const std::string& category,
421✔
798
                         const std::string& name,
799
                         bool smoke_test,
800
                         bool needs_serialization,
801
                         std::function<std::unique_ptr<Test>()> maker_fn) {
802
   Test_Registry::instance().register_test(category, name, smoke_test, needs_serialization, std::move(maker_fn));
842✔
803
}
421✔
804

805
//static
806
uint64_t Test::timestamp() {
180,285✔
807
   auto now = std::chrono::system_clock::now().time_since_epoch();
180,285✔
808
   return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
2,383✔
809
}
810

811
//static
812
std::vector<Test::Result> Test::flatten_result_lists(std::vector<std::vector<Test::Result>> result_lists) {
4✔
813
   std::vector<Test::Result> results;
4✔
814
   for(auto& result_list : result_lists) {
26✔
815
      for(auto& result : result_list) {
71✔
816
         results.emplace_back(std::move(result));
49✔
817
      }
818
   }
819
   return results;
4✔
820
}
×
821

822
//static
823
std::vector<std::string> Test::registered_tests() {
×
824
   return Test_Registry::instance().registered_tests();
×
825
}
826

827
//static
828
std::vector<std::string> Test::registered_test_categories() {
×
829
   return Test_Registry::instance().registered_test_categories();
×
830
}
831

832
//static
833
std::unique_ptr<Test> Test::get_test(const std::string& test_name) {
421✔
834
   return Test_Registry::instance().get_test(test_name);
421✔
835
}
836

837
//static
838
bool Test::test_needs_serialization(const std::string& test_name) {
421✔
839
   return Test_Registry::instance().needs_serialization(test_name);
421✔
840
}
841

842
//static
843
std::vector<std::string> Test::filter_registered_tests(const std::vector<std::string>& requested,
1✔
844
                                                       const std::vector<std::string>& to_be_skipped) {
845
   return Test_Registry::instance().filter_registered_tests(requested, to_be_skipped);
1✔
846
}
847

848
//static
849
std::string Test::temp_file_name(const std::string& basename) {
21✔
850
   // TODO add a --tmp-dir option to the tests to specify where these files go
851

852
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
853

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

857
   const int fd = ::mkstemp(mkstemp_basename.data());
21✔
858

859
   // error
860
   if(fd < 0) {
21✔
861
      return "";
×
862
   }
863

864
   ::close(fd);
21✔
865

866
   return mkstemp_basename;
21✔
867
#else
868
   // For now just create the temp in the current working directory
869
   return basename;
870
#endif
871
}
21✔
872

873
bool Test::copy_file(const std::string& from, const std::string& to) {
1✔
874
#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(__cpp_lib_filesystem)
875
   std::error_code ec;  // don't throw, just return false on error
1✔
876
   return std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing, ec);
1✔
877
#else
878
   // TODO: implement fallbacks to POSIX or WIN32
879
   // ... but then again: it's 2023 and we're using C++20 :o)
880
   BOTAN_UNUSED(from, to);
881
   throw Botan::No_Filesystem_Access();
882
#endif
883
}
884

885
std::string Test::read_data_file(const std::string& path) {
38✔
886
   const std::string fsname = Test::data_file(path);
38✔
887
   std::ifstream file(fsname.c_str());
38✔
888
   if(!file.good()) {
38✔
889
      throw Test_Error("Error reading from " + fsname);
×
890
   }
891

892
   return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
76✔
893
}
38✔
894

895
std::vector<uint8_t> Test::read_binary_data_file(const std::string& path) {
34✔
896
   const std::string fsname = Test::data_file(path);
34✔
897
   std::ifstream file(fsname.c_str(), std::ios::binary);
34✔
898
   if(!file.good()) {
34✔
899
      throw Test_Error("Error reading from " + fsname);
×
900
   }
901

902
   std::vector<uint8_t> contents;
34✔
903

904
   while(file.good()) {
74✔
905
      std::vector<uint8_t> buf(4096);
40✔
906
      file.read(reinterpret_cast<char*>(buf.data()), buf.size());
40✔
907
      const size_t got = static_cast<size_t>(file.gcount());
40✔
908

909
      if(got == 0 && file.eof()) {
40✔
910
         break;
911
      }
912

913
      contents.insert(contents.end(), buf.data(), buf.data() + got);
40✔
914
   }
40✔
915

916
   return contents;
68✔
917
}
34✔
918

919
// static member variables of Test
920

921
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
922
Test_Options Test::m_opts;
923
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
924
std::string Test::m_test_rng_seed;
925

926
//static
927
void Test::set_test_options(const Test_Options& opts) {
1✔
928
   m_opts = opts;
1✔
929
}
1✔
930

931
namespace {
932

933
/*
934
* This is a fast, simple, deterministic PRNG that's used for running
935
* the tests. It is not intended to be cryptographically secure.
936
*/
937
class Testsuite_RNG final : public Botan::RandomNumberGenerator {
8✔
938
   public:
939
      std::string name() const override { return "Testsuite_RNG"; }
×
940

941
      void clear() override { m_x = 0; }
×
942

943
      bool accepts_input() const override { return true; }
×
944

945
      bool is_seeded() const override { return true; }
277,822✔
946

947
      void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> input) override {
10,244,049✔
948
         for(const auto byte : input) {
10,244,049✔
949
            mix(byte);
×
950
         }
951

952
         for(auto& byte : output) {
73,869,470✔
953
            byte = mix();
63,625,421✔
954
         }
955
      }
10,244,049✔
956

957
      Testsuite_RNG(std::string_view seed, std::string_view test_name) : m_x(0) {
791✔
958
         for(const char c : seed) {
23,730✔
959
            this->mix(static_cast<uint8_t>(c));
22,939✔
960
         }
961
         for(const char c : test_name) {
31,559✔
962
            this->mix(static_cast<uint8_t>(c));
30,768✔
963
         }
964
      }
791✔
965

966
   private:
967
      uint8_t mix(uint8_t input = 0) {
63,679,128✔
968
         m_x ^= input;
63,679,128✔
969
         m_x *= 0xF2E16957;
63,679,128✔
970
         m_x += 0xE50B590F;
63,679,128✔
971
         return static_cast<uint8_t>(m_x >> 27);
63,679,128✔
972
      }
973

974
      uint64_t m_x;
975
};
976

977
}  // namespace
978

979
//static
980
void Test::set_test_rng_seed(std::span<const uint8_t> seed, size_t epoch) {
1✔
981
   m_test_rng_seed = Botan::fmt("seed={} epoch={}", Botan::hex_encode(seed), epoch);
1✔
982
}
1✔
983

984
//static
985
std::unique_ptr<Botan::RandomNumberGenerator> Test::new_rng(std::string_view test_name) {
783✔
986
   return std::make_unique<Testsuite_RNG>(m_test_rng_seed, test_name);
783✔
987
}
988

989
//static
990
std::shared_ptr<Botan::RandomNumberGenerator> Test::new_shared_rng(std::string_view test_name) {
8✔
991
   return std::make_shared<Testsuite_RNG>(m_test_rng_seed, test_name);
8✔
992
}
993

994
//static
995
std::string Test::data_file(const std::string& file) {
773✔
996
   return options().data_dir() + "/" + file;
1,546✔
997
}
998

999
//static
1000
std::string Test::data_dir(const std::string& subdir) {
1✔
1001
   return options().data_dir() + "/" + subdir;
2✔
1002
}
1003

1004
//static
1005
std::vector<std::string> Test::files_in_data_dir(const std::string& subdir) {
393✔
1006
   auto fs = Botan::get_files_recursive(options().data_dir() + "/" + subdir);
1,179✔
1007
   if(fs.empty()) {
393✔
1008
      throw Test_Error("Test::files_in_data_dir encountered empty subdir " + subdir);
×
1009
   }
1010
   return fs;
393✔
1011
}
×
1012

1013
//static
1014
std::string Test::data_file_as_temporary_copy(const std::string& what) {
1✔
1015
   auto tmp_basename = what;
1✔
1016
   std::replace(tmp_basename.begin(), tmp_basename.end(), '/', '_');
1✔
1017
   auto temp_file = temp_file_name("tmp-" + tmp_basename);
1✔
1018
   if(temp_file.empty()) {
1✔
1019
      return "";
×
1020
   }
1021
   if(!Test::copy_file(data_file(what), temp_file)) {
1✔
1022
      return "";
×
1023
   }
1024
   return temp_file;
1✔
1025
}
1✔
1026

1027
//static
1028
std::vector<std::string> Test::provider_filter(const std::vector<std::string>& providers) {
49,047✔
1029
   if(m_opts.provider().empty()) {
49,047✔
1030
      return providers;
49,047✔
1031
   }
1032
   for(auto&& provider : providers) {
×
1033
      if(provider == m_opts.provider()) {
×
1034
         return std::vector<std::string>{provider};
×
1035
      }
1036
   }
1037
   return std::vector<std::string>{};
×
1038
}
×
1039

1040
std::string Test::random_password(Botan::RandomNumberGenerator& rng) {
222✔
1041
   const size_t len = 1 + rng.next_byte() % 32;
222✔
1042
   return Botan::hex_encode(rng.random_vec(len));
444✔
1043
}
1044

1045
size_t Test::random_index(Botan::RandomNumberGenerator& rng, size_t max) {
8,062✔
1046
   return Botan::load_be(rng.random_array<8>()) % max;
8,062✔
1047
}
1048

1049
void VarMap::clear() {
34,297✔
1050
   m_vars.clear();
×
1051
}
×
1052

1053
namespace {
1054

1055
bool varmap_pair_lt(const std::pair<std::string, std::string>& kv, std::string_view k) {
1,713,996✔
1056
   return kv.first < k;
1,713,996✔
1057
}
1058

1059
}  // namespace
1060

1061
bool VarMap::has_key(std::string_view key) const {
213,622✔
1062
   return get_opt_var(key).has_value();
213,622✔
1063
}
1064

1065
void VarMap::add(std::string_view key, std::string_view value) {
156,283✔
1066
   auto i = std::lower_bound(m_vars.begin(), m_vars.end(), key, varmap_pair_lt);
156,283✔
1067

1068
   if(i != m_vars.end() && i->first == key) {
163,439✔
1069
      i->second = value;
44,405✔
1070
   } else {
1071
      m_vars.emplace(i, key, value);
111,878✔
1072
   }
1073
}
156,283✔
1074

1075
std::optional<std::string> VarMap::get_opt_var(std::string_view key) const {
308,915✔
1076
   auto i = std::lower_bound(m_vars.begin(), m_vars.end(), key, varmap_pair_lt);
308,915✔
1077

1078
   if(i != m_vars.end() && i->first == key) {
310,716✔
1079
      return i->second;
239,010✔
1080
   } else {
1081
      return {};
69,905✔
1082
   }
1083
}
1084

1085
const std::string& VarMap::get_req_var(std::string_view key) const {
259,704✔
1086
   auto i = std::lower_bound(m_vars.begin(), m_vars.end(), key, varmap_pair_lt);
259,704✔
1087

1088
   if(i != m_vars.end() && i->first == key) {
259,704✔
1089
      return i->second;
259,704✔
1090
   } else {
1091
      throw Test_Error(Botan::fmt("Test missing variable '{}'", key));
×
1092
   }
1093
}
1094

1095
std::string VarMap::get_req_str(std::string_view key) const {
52,801✔
1096
   return std::string(get_req_var(key));
52,801✔
1097
}
1098

1099
std::vector<std::vector<uint8_t>> VarMap::get_req_bin_list(std::string_view key) const {
12✔
1100
   const auto& var = get_req_var(key);
12✔
1101

1102
   std::vector<std::vector<uint8_t>> bin_list;
12✔
1103

1104
   for(auto&& part : Botan::split_on(var, ',')) {
62✔
1105
      try {
50✔
1106
         bin_list.push_back(Botan::hex_decode(part));
100✔
1107
      } catch(std::exception& e) {
×
1108
         std::ostringstream oss;
×
1109
         oss << "Bad input '" << part << "'"
×
1110
             << " in binary list key " << key << " - " << e.what();
×
1111
         throw Test_Error(oss.str());
×
1112
      }
×
1113
   }
12✔
1114

1115
   return bin_list;
12✔
1116
}
×
1117

1118
std::vector<uint8_t> VarMap::get_req_bin(std::string_view key) const {
163,786✔
1119
   const auto& var = get_req_var(key);
163,786✔
1120

1121
   try {
163,786✔
1122
      return Botan::hex_decode(var);
163,786✔
1123
   } catch(std::exception& e) {
×
1124
      throw Test_Error(Botan::fmt("Bad hex input '{}' for key '{}' err '{}'", var, key, e.what()));
×
1125
   }
×
1126
}
1127

1128
std::string VarMap::get_opt_str(std::string_view key, std::string_view def_value) const {
14,378✔
1129
   if(auto v = get_opt_var(key)) {
14,378✔
1130
      return *v;
14,435✔
1131
   } else {
1132
      return std::string(def_value);
28,642✔
1133
   }
14,378✔
1134
}
1135

1136
bool VarMap::get_req_bool(std::string_view key) const {
39✔
1137
   const auto& var = get_req_var(key);
39✔
1138

1139
   if(var == "true") {
39✔
1140
      return true;
1141
   } else if(var == "false") {
23✔
1142
      return false;
1143
   } else {
1144
      throw Test_Error(Botan::fmt("Invalid boolean '{}' for key '{}'", var, key));
×
1145
   }
1146
}
1147

1148
size_t VarMap::get_req_sz(std::string_view key) const {
4,547✔
1149
   return Botan::to_u32bit(get_req_var(key));
4,547✔
1150
}
1151

1152
uint8_t VarMap::get_req_u8(std::string_view key) const {
17✔
1153
   const size_t s = this->get_req_sz(key);
17✔
1154
   if(s > 256) {
17✔
1155
      throw Test_Error(Botan::fmt("Invalid value for '{}' expected uint8_t but got '{}'", key, s));
×
1156
   }
1157
   return static_cast<uint8_t>(s);
17✔
1158
}
1159

1160
uint32_t VarMap::get_req_u32(std::string_view key) const {
14✔
1161
   return static_cast<uint32_t>(get_req_sz(key));
14✔
1162
}
1163

1164
uint64_t VarMap::get_req_u64(std::string_view key) const {
17✔
1165
   const auto& var = get_req_var(key);
17✔
1166
   try {
17✔
1167
      return std::stoull(var);
17✔
1168
   } catch(std::exception&) {
×
1169
      throw Test_Error("Invalid u64 value '" + var + "'");
×
1170
   }
×
1171
}
1172

1173
size_t VarMap::get_opt_sz(std::string_view key, const size_t def_value) const {
31,404✔
1174
   if(auto v = get_opt_var(key)) {
31,404✔
1175
      return Botan::to_u32bit(*v);
12,244✔
1176
   } else {
1177
      return def_value;
1178
   }
31,404✔
1179
}
1180

1181
uint64_t VarMap::get_opt_u64(std::string_view key, const uint64_t def_value) const {
3,538✔
1182
   if(auto v = get_opt_var(key)) {
3,538✔
1183
      try {
641✔
1184
         return std::stoull(*v);
3,538✔
1185
      } catch(std::exception&) {
×
1186
         throw Test_Error("Invalid u64 value '" + *v + "'");
×
1187
      }
×
1188
   } else {
1189
      return def_value;
1190
   }
3,538✔
1191
}
×
1192

1193
std::vector<uint8_t> VarMap::get_opt_bin(std::string_view key) const {
45,893✔
1194
   if(auto v = get_opt_var(key)) {
45,893✔
1195
      try {
16,134✔
1196
         return Botan::hex_decode(*v);
16,134✔
1197
      } catch(std::exception&) {
×
1198
         throw Test_Error(Botan::fmt("Invalid hex for key '{}' got '{}'", key, *v));
×
1199
      }
×
1200
   } else {
1201
      return {};
29,759✔
1202
   };
45,893✔
1203
}
1204

1205
#if defined(BOTAN_HAS_BIGINT)
1206
Botan::BigInt VarMap::get_req_bn(std::string_view key) const {
38,502✔
1207
   const auto& var = get_req_var(key);
38,502✔
1208

1209
   try {
38,502✔
1210
      return Botan::BigInt(var);
38,502✔
1211
   } catch(std::exception&) {
×
1212
      throw Test_Error(Botan::fmt("Invalid BigInt for key '{}' got '{}'", key, var));
×
1213
   }
×
1214
}
1215

1216
Botan::BigInt VarMap::get_opt_bn(std::string_view key, const Botan::BigInt& def_value) const {
80✔
1217
   if(auto v = get_opt_var(key)) {
80✔
1218
      try {
56✔
1219
         return Botan::BigInt(*v);
56✔
1220
      } catch(std::exception&) {
×
1221
         throw Test_Error(Botan::fmt("Invalid BigInt for key '{}' got '{}'", key, *v));
×
1222
      }
×
1223
   } else {
1224
      return def_value;
24✔
1225
   }
80✔
1226
}
1227
#endif
1228

1229
class Text_Based_Test::Text_Based_Test_Data {
1230
   public:
1231
      Text_Based_Test_Data(const std::string& data_src,
181✔
1232
                           const std::string& required_keys_str,
1233
                           const std::string& optional_keys_str) :
181✔
1234
            m_data_src(data_src) {
362✔
1235
         if(required_keys_str.empty()) {
181✔
1236
            throw Test_Error("Invalid test spec");
×
1237
         }
1238

1239
         m_required_keys = Botan::split_on(required_keys_str, ',');
181✔
1240
         std::vector<std::string> optional_keys = Botan::split_on(optional_keys_str, ',');
181✔
1241

1242
         m_all_keys.insert(m_required_keys.begin(), m_required_keys.end());
181✔
1243
         m_all_keys.insert(optional_keys.begin(), optional_keys.end());
181✔
1244
         m_output_key = m_required_keys.at(m_required_keys.size() - 1);
181✔
1245
      }
181✔
1246

1247
      std::string get_next_line();
1248

1249
      const std::string& current_source_name() const { return m_cur_src_name; }
×
1250

1251
      bool known_key(const std::string& key) const;
1252

1253
      const std::vector<std::string>& required_keys() const { return m_required_keys; }
48,404✔
1254

1255
      const std::string& output_key() const { return m_output_key; }
156,283✔
1256

1257
      const std::vector<std::string>& cpu_flags() const { return m_cpu_flags; }
48,265✔
1258

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

1261
      const std::string& initial_data_src_name() const { return m_data_src; }
181✔
1262

1263
   private:
1264
      std::string m_data_src;
1265
      std::vector<std::string> m_required_keys;
1266
      std::unordered_set<std::string> m_all_keys;
1267
      std::string m_output_key;
1268

1269
      bool m_first = true;
1270
      std::unique_ptr<std::istream> m_cur;
1271
      std::string m_cur_src_name;
1272
      std::deque<std::string> m_srcs;
1273
      std::vector<std::string> m_cpu_flags;
1274
};
1275

1276
Text_Based_Test::Text_Based_Test(const std::string& data_src,
181✔
1277
                                 const std::string& required_keys,
1278
                                 const std::string& optional_keys) :
181✔
1279
      m_data(std::make_unique<Text_Based_Test_Data>(data_src, required_keys, optional_keys)) {}
181✔
1280

1281
Text_Based_Test::~Text_Based_Test() = default;
181✔
1282

1283
std::string Text_Based_Test::Text_Based_Test_Data::get_next_line() {
157,290✔
1284
   while(true) {
157,548✔
1285
      if(m_cur == nullptr || m_cur->good() == false) {
157,548✔
1286
         if(m_srcs.empty()) {
450✔
1287
            if(m_first) {
362✔
1288
               if(m_data_src.ends_with(".vec")) {
181✔
1289
                  m_srcs.push_back(Test::data_file(m_data_src));
338✔
1290
               } else {
1291
                  const auto fs = Test::files_in_data_dir(m_data_src);
12✔
1292
                  m_srcs.assign(fs.begin(), fs.end());
12✔
1293
                  if(m_srcs.empty()) {
12✔
1294
                     throw Test_Error("Error reading test data dir " + m_data_src);
×
1295
                  }
1296
               }
12✔
1297

1298
               m_first = false;
181✔
1299
            } else {
1300
               return "";  // done
181✔
1301
            }
1302
         }
1303

1304
         m_cur = std::make_unique<std::ifstream>(m_srcs[0]);
357✔
1305
         m_cur_src_name = m_srcs[0];
269✔
1306

1307
#if defined(BOTAN_HAS_CPUID)
1308
         // Reinit cpuid on new file if needed
1309
         if(m_cpu_flags.empty() == false) {
269✔
1310
            m_cpu_flags.clear();
19✔
1311
            Botan::CPUID::initialize();
19✔
1312
         }
1313
#endif
1314

1315
         if(!m_cur->good()) {
269✔
1316
            throw Test_Error("Could not open input file '" + m_cur_src_name);
×
1317
         }
1318

1319
         m_srcs.pop_front();
269✔
1320
      }
1321

1322
      while(m_cur->good()) {
227,975✔
1323
         std::string line;
227,717✔
1324
         std::getline(*m_cur, line);
227,717✔
1325

1326
         if(line.empty()) {
227,717✔
1327
            continue;
55,323✔
1328
         }
1329

1330
         if(line[0] == '#') {
172,394✔
1331
            if(line.starts_with("#test ")) {
15,306✔
1332
               return line;
21✔
1333
            } else {
1334
               continue;
15,285✔
1335
            }
1336
         }
1337

1338
         return line;
157,088✔
1339
      }
227,717✔
1340
   }
1341
}
1342

1343
bool Text_Based_Test::Text_Based_Test_Data::known_key(const std::string& key) const {
156,283✔
1344
   return m_all_keys.contains(key);
156,283✔
1345
}
1346

1347
namespace {
1348

1349
// strips leading and trailing but not internal whitespace
1350
std::string strip_ws(const std::string& in) {
312,566✔
1351
   const char* whitespace = " ";
312,566✔
1352

1353
   const auto first_c = in.find_first_not_of(whitespace);
312,566✔
1354
   if(first_c == std::string::npos) {
312,566✔
1355
      return "";
1,034✔
1356
   }
1357

1358
   const auto last_c = in.find_last_not_of(whitespace);
311,532✔
1359

1360
   return in.substr(first_c, last_c - first_c + 1);
311,532✔
1361
}
1362

1363
std::vector<std::string> parse_cpuid_bits(const std::vector<std::string>& tok) {
21✔
1364
   std::vector<std::string> bits;
21✔
1365

1366
#if defined(BOTAN_HAS_CPUID)
1367
   for(size_t i = 1; i < tok.size(); ++i) {
106✔
1368
      if(auto bit = Botan::CPUID::bit_from_string(tok[i])) {
85✔
1369
         bits.push_back(bit->to_string());
98✔
1370
      }
1371
   }
1372
#else
1373
   BOTAN_UNUSED(tok);
1374
#endif
1375

1376
   return bits;
21✔
1377
}
×
1378

1379
}  // namespace
1380

1381
bool Text_Based_Test::skip_this_test(const std::string& /*header*/, const VarMap& /*vars*/) {
32,625✔
1382
   return false;
32,625✔
1383
}
1384

1385
std::vector<Test::Result> Text_Based_Test::run() {
181✔
1386
   std::vector<Test::Result> results;
181✔
1387

1388
   std::string header;
181✔
1389
   std::string header_or_name = m_data->initial_data_src_name();
181✔
1390
   VarMap vars;
181✔
1391
   size_t test_cnt = 0;
181✔
1392

1393
   while(true) {
157,290✔
1394
      const std::string line = m_data->get_next_line();
157,290✔
1395
      if(line.empty()) {
157,290✔
1396
         // EOF
1397
         break;
1398
      }
1399

1400
      if(line.starts_with("#test ")) {
157,109✔
1401
         std::vector<std::string> pragma_tokens = Botan::split_on(line.substr(6), ' ');
21✔
1402

1403
         if(pragma_tokens.empty()) {
21✔
1404
            throw Test_Error("Empty pragma found in " + m_data->current_source_name());
×
1405
         }
1406

1407
         if(pragma_tokens[0] != "cpuid") {
21✔
1408
            throw Test_Error("Unknown test pragma '" + line + "' in " + m_data->current_source_name());
×
1409
         }
1410

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

1415
         m_data->set_cpu_flags(parse_cpuid_bits(pragma_tokens));
42✔
1416

1417
         continue;
21✔
1418
      } else if(line[0] == '#') {
157,109✔
1419
         throw Test_Error("Unknown test pragma '" + line + "' in " + m_data->current_source_name());
×
1420
      }
1421

1422
      if(line[0] == '[' && line[line.size() - 1] == ']') {
157,088✔
1423
         header = line.substr(1, line.size() - 2);
805✔
1424
         header_or_name = header;
805✔
1425
         test_cnt = 0;
805✔
1426
         vars.clear();
805✔
1427
         continue;
805✔
1428
      }
1429

1430
      const std::string test_id = "test " + std::to_string(test_cnt);
312,566✔
1431

1432
      auto equal_i = line.find_first_of('=');
156,283✔
1433

1434
      if(equal_i == std::string::npos) {
156,283✔
1435
         results.push_back(Test::Result::Failure(header_or_name, "invalid input '" + line + "'"));
×
1436
         continue;
×
1437
      }
1438

1439
      const std::string key = strip_ws(std::string(line.begin(), line.begin() + equal_i - 1));
312,566✔
1440
      const std::string val = strip_ws(std::string(line.begin() + equal_i + 1, line.end()));
312,566✔
1441

1442
      if(!m_data->known_key(key)) {
156,283✔
1443
         auto r = Test::Result::Failure(header_or_name, Botan::fmt("{} failed unknown key {}", test_id, key));
×
1444
         results.push_back(r);
×
1445
      }
×
1446

1447
      vars.add(key, val);
156,283✔
1448

1449
      if(key == m_data->output_key()) {
156,283✔
1450
         try {
48,404✔
1451
            for(const auto& req_key : m_data->required_keys()) {
245,618✔
1452
               if(!vars.has_key(req_key)) {
197,214✔
1453
                  auto r =
×
1454
                     Test::Result::Failure(header_or_name, Botan::fmt("{} missing required key {}", test_id, req_key));
×
1455
                  results.push_back(r);
×
1456
               }
×
1457
            }
1458

1459
            if(skip_this_test(header, vars)) {
48,404✔
1460
               continue;
139✔
1461
            }
1462

1463
            ++test_cnt;
48,265✔
1464

1465
            const uint64_t start = Test::timestamp();
48,265✔
1466

1467
            Test::Result result = run_one_test(header, vars);
48,265✔
1468
#if defined(BOTAN_HAS_CPUID)
1469
            if(!m_data->cpu_flags().empty()) {
48,265✔
1470
               for(const auto& cpuid_str : m_data->cpu_flags()) {
30,651✔
1471
                  if(const auto bit = Botan::CPUID::Feature::from_string(cpuid_str)) {
21,698✔
1472
                     if(Botan::CPUID::has(*bit)) {
21,698✔
1473
                        Botan::CPUID::clear_cpuid_bit(*bit);
16,586✔
1474
                        // now re-run the test
1475
                        result.merge(run_one_test(header, vars));
16,586✔
1476
                     }
1477
                  }
1478
               }
1479
               Botan::CPUID::initialize();
8,953✔
1480
            }
1481
#endif
1482
            result.set_ns_consumed(Test::timestamp() - start);
48,265✔
1483

1484
            if(result.tests_failed() > 0) {
48,265✔
1485
               std::ostringstream oss;
×
1486
               oss << "Test # " << test_cnt << " ";
×
1487
               if(!header.empty()) {
×
1488
                  oss << header << " ";
×
1489
               }
1490
               oss << "failed ";
×
1491

1492
               for(const auto& k : m_data->required_keys()) {
×
1493
                  oss << k << "=" << vars.get_req_str(k) << " ";
×
1494
               }
1495

1496
               result.test_note(oss.str());
×
1497
            }
×
1498
            results.push_back(result);
48,265✔
1499
         } catch(std::exception& e) {
48,265✔
1500
            std::ostringstream oss;
×
1501
            oss << "Test # " << test_cnt << " ";
×
1502
            if(!header.empty()) {
×
1503
               oss << header << " ";
×
1504
            }
1505

1506
            for(const auto& k : m_data->required_keys()) {
×
1507
               oss << k << "=" << vars.get_req_str(k) << " ";
×
1508
            }
1509

1510
            oss << "failed with exception '" << e.what() << "'";
×
1511

1512
            results.push_back(Test::Result::Failure(header_or_name, oss.str()));
×
1513
         }
×
1514

1515
         if(clear_between_callbacks()) {
48,265✔
1516
            vars.clear();
189,636✔
1517
         }
1518
      }
1519
   }
157,387✔
1520

1521
   if(results.empty()) {
181✔
1522
      return results;
1523
   }
1524

1525
   try {
181✔
1526
      std::vector<Test::Result> final_tests = run_final_tests();
181✔
1527
      results.insert(results.end(), final_tests.begin(), final_tests.end());
181✔
1528
   } catch(std::exception& e) {
181✔
1529
      results.push_back(Test::Result::Failure(header_or_name, "run_final_tests exception " + std::string(e.what())));
×
1530
   }
×
1531

1532
   return results;
1533
}
181✔
1534

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