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

randombit / botan / 22037645935

15 Feb 2026 02:49PM UTC coverage: 90.059% (-1.6%) from 91.706%
22037645935

push

github

web-flow
Merge pull request #5340 from randombit/jack/test-h-misc-cmp

Further cleanups of test predicates

102371 of 113671 relevant lines covered (90.06%)

11413670.75 hits per line

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

80.15
/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;
420✔
59

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

62
Test::Result::Result(std::string_view who) : m_who(who), m_timestamp(Test::timestamp()) {}
81,199✔
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,834✔
71
   if(who() != other.who()) {
126,834✔
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,761✔
81
   }
82

83
   m_timestamp = std::min(m_timestamp, other.m_timestamp);
126,834✔
84
   m_ns_taken += other.m_ns_taken;
126,834✔
85
   m_tests_passed += other.m_tests_passed;
126,834✔
86
   m_fail_log.insert(m_fail_log.end(), other.m_fail_log.begin(), other.m_fail_log.end());
126,834✔
87
   m_log.insert(m_log.end(), other.m_log.begin(), other.m_log.end());
126,834✔
88
}
126,834✔
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
   const std::string hex = Botan::hex_encode(context);
12✔
105
   return test_note(note, hex.c_str());
12✔
106
}
12✔
107

108
void Test::Result::test_note(std::string_view note, const char* extra) {
2,767✔
109
   if(!note.empty()) {
2,767✔
110
      std::ostringstream out;
2,767✔
111
      out << who() << " " << note;
2,767✔
112
      if(extra != nullptr) {
2,767✔
113
         out << ": " << extra;
12✔
114
      }
115
      m_log.push_back(out.str());
2,767✔
116
   }
2,767✔
117
}
2,767✔
118

119
void Test::Result::note_missing(std::string_view whatever_sv) {
275✔
120
   static std::set<std::string> s_already_seen;
275✔
121

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

129
void Test::Result::require(std::string_view what, bool expr, bool expected) {
329✔
130
   if(!test_bool_eq(what, expr, expected)) {
329✔
131
      throw Test_Aborted(Botan::fmt("Test aborted, because required condition was not met: {}", what));
×
132
   }
133
}
329✔
134

135
Test::Result::ThrowExpectations::~ThrowExpectations() {
67,783✔
136
   BOTAN_ASSERT_NOMSG(m_consumed);
67,783✔
137
}
130,020✔
138

139
void Test::Result::ThrowExpectations::assert_that_success_is_not_expected() const {
62,237✔
140
   BOTAN_ASSERT_NOMSG(!m_expect_success);
62,048✔
141
}
62,048✔
142

143
Test::Result::ThrowExpectations& Test::Result::ThrowExpectations::expect_success() {
1,645✔
144
   BOTAN_ASSERT_NOMSG(!m_expected_message && !m_expected_exception_check_fn);
1,645✔
145
   m_expect_success = true;
1,645✔
146
   return *this;
1,645✔
147
}
148

149
Test::Result::ThrowExpectations& Test::Result::ThrowExpectations::expect_message(std::string_view message) {
189✔
150
   assert_that_success_is_not_expected();
189✔
151
   m_expected_message = message;
189✔
152
   return *this;
189✔
153
}
154

155
bool Test::Result::ThrowExpectations::check(std::string_view test_name, Test::Result& result) {
67,783✔
156
   m_consumed = true;
67,783✔
157

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

182
   return result.test_success();
67,776✔
183
}
184

185
bool Test::Result::test_throws(std::string_view what, std::function<void()> fn) {
3,916✔
186
   return ThrowExpectations(std::move(fn)).check(what, *this);
11,748✔
187
}
188

189
bool Test::Result::test_throws(std::string_view what, std::string_view expected, std::function<void()> fn) {
174✔
190
   return ThrowExpectations(std::move(fn)).expect_message(expected).check(what, *this);
522✔
191
}
192

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

197
bool Test::Result::test_success(std::string_view note) {
3,620,879✔
198
   if(Test::options().log_success()) {
3,620,863✔
199
      test_note(note);
×
200
   }
201
   ++m_tests_passed;
3,620,879✔
202
   return true;
144,651✔
203
}
204

205
bool Test::Result::test_failure(std::string_view what, std::string_view error) {
2✔
206
   return test_failure(Botan::fmt("{} {} with error {}", who(), what, error));
2✔
207
}
208

209
void Test::Result::test_failure(std::string_view what, const uint8_t buf[], size_t buf_len) {
×
210
   return test_failure(what, {buf, buf_len});
×
211
}
212

213
void Test::Result::test_failure(std::string_view what, std::span<const uint8_t> context) {
1✔
214
   test_failure(Botan::fmt("{} {} with value {}", who(), what, Botan::hex_encode(context)));
2✔
215
}
1✔
216

217
bool Test::Result::test_failure(const char* err) {
×
218
   return test_failure(std::string_view(err));
×
219
}
220

221
bool Test::Result::test_failure(std::string_view err) {
1✔
222
   return test_failure(std::string(err));
1✔
223
}
224

225
bool Test::Result::test_failure(std::string err) {
25✔
226
   m_fail_log.push_back(std::move(err));
25✔
227

228
   if(Test::options().abort_on_first_fail() && m_who != "Failing Test") {
25✔
229
      std::abort();
×
230
   }
231
   return false;
25✔
232
}
233

234
namespace {
235

236
bool same_contents(const uint8_t x[], const uint8_t y[], size_t len) {
295,640✔
237
   return (len == 0) ? true : std::memcmp(x, y, len) == 0;
294,209✔
238
}
239

240
}  // namespace
241

242
bool Test::Result::test_ne(std::string_view what,
3,407✔
243
                           const uint8_t produced[],
244
                           size_t produced_len,
245
                           const uint8_t expected[],
246
                           size_t expected_len) {
247
   if(produced_len == expected_len && same_contents(produced, expected, expected_len)) {
3,407✔
248
      return test_failure(Botan::fmt("{} {} produced matching bytes", who(), what));
2✔
249
   }
250
   return test_success();
3,405✔
251
}
252

253
bool Test::Result::test_eq(std::string_view what, std::span<const uint8_t> produced, const char* expected_hex) {
471✔
254
   const std::vector<uint8_t> expected = Botan::hex_decode(expected_hex);
471✔
255
   return test_eq(nullptr, what, produced.data(), produced.size(), expected.data(), expected.size());
471✔
256
}
471✔
257

258
bool Test::Result::test_eq(const char* producer,
294,210✔
259
                           std::string_view what,
260
                           const uint8_t produced[],
261
                           size_t produced_size,
262
                           const uint8_t expected[],
263
                           size_t expected_size) {
264
   if(produced_size == expected_size && same_contents(produced, expected, expected_size)) {
294,210✔
265
      return test_success();
294,209✔
266
   }
267

268
   std::ostringstream err;
1✔
269

270
   err << who();
1✔
271

272
   if(producer != nullptr) {
1✔
273
      err << " producer '" << producer << "'";
×
274
   }
275

276
   err << " unexpected result for " << what;
1✔
277

278
   if(produced_size != expected_size) {
1✔
279
      err << " produced " << produced_size << " bytes expected " << expected_size;
1✔
280
   }
281

282
   std::vector<uint8_t> xor_diff(std::min(produced_size, expected_size));
2✔
283
   size_t bytes_different = 0;
1✔
284

285
   for(size_t i = 0; i != xor_diff.size(); ++i) {
4✔
286
      xor_diff[i] = produced[i] ^ expected[i];
3✔
287
      if(xor_diff[i] > 0) {
3✔
288
         bytes_different++;
3✔
289
      }
290
   }
291

292
   err << "\nProduced: " << Botan::hex_encode(produced, produced_size)
1✔
293
       << "\nExpected: " << Botan::hex_encode(expected, expected_size);
3✔
294

295
   if(bytes_different > 0) {
1✔
296
      err << "\nXOR Diff: " << Botan::hex_encode(xor_diff);
1✔
297
   }
298

299
   return test_failure(err.str());
1✔
300
}
1✔
301

302
bool Test::Result::test_str_not_empty(std::string_view what, std::string_view produced) {
38,863✔
303
   if(produced.empty()) {
38,863✔
304
      return test_failure(what, "was empty");
1✔
305
   } else {
306
      return test_success();
38,862✔
307
   }
308
}
309

310
bool Test::Result::test_str_eq(std::string_view what, std::string_view produced, std::string_view expected) {
76,875✔
311
   return test_is_eq(what, produced, expected);
76,875✔
312
}
313

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

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

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

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

338
bool Test::Result::test_u8_eq(std::string_view what, uint8_t produced, uint8_t expected) {
46,585✔
339
   return test_sz_eq(what, produced, expected);
46,585✔
340
}
341

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

346
bool Test::Result::test_u16_eq(std::string_view what, uint16_t produced, uint16_t expected) {
65,580✔
347
   return test_sz_eq(what, produced, expected);
65,580✔
348
}
349

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

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

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

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

370
bool Test::Result::test_sz_eq(std::string_view what, size_t produced, size_t expected) {
193,574✔
371
   if(produced == expected) {
193,574✔
372
      return test_success();
193,573✔
373
   } else {
374
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} == {}", who(), what, produced, expected));
1✔
375
   }
376
}
377

378
bool Test::Result::test_sz_ne(std::string_view what, size_t produced, size_t expected) {
119✔
379
   if(produced != expected) {
119✔
380
      return test_success();
118✔
381
   } else {
382
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} != {}", who(), what, produced, expected));
1✔
383
   }
384
}
385

386
bool Test::Result::test_sz_lt(std::string_view what, size_t produced, size_t expected) {
5,639✔
387
   if(produced < expected) {
5,639✔
388
      return test_success();
5,638✔
389
   } else {
390
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} < {}", who(), what, produced, expected));
1✔
391
   }
392
}
393

394
bool Test::Result::test_sz_lte(std::string_view what, size_t produced, size_t expected) {
1,021,636✔
395
   if(produced <= expected) {
1,021,636✔
396
      return test_success();
1,021,635✔
397
   } else {
398
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} <= {}", who(), what, produced, expected));
1✔
399
   }
400
}
401

402
bool Test::Result::test_sz_gt(std::string_view what, size_t produced, size_t expected) {
29,981✔
403
   if(produced > expected) {
29,981✔
404
      return test_success();
29,981✔
405
   } else {
406
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} > {}", who(), what, produced, expected));
×
407
   }
408
}
409

410
bool Test::Result::test_sz_gte(std::string_view what, size_t produced, size_t expected) {
1,142,521✔
411
   if(produced >= expected) {
1,142,521✔
412
      return test_success();
1,142,520✔
413
   } else {
414
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} >= {}", who(), what, produced, expected));
1✔
415
   }
416
}
417

418
bool Test::Result::test_opt_u8_eq(std::string_view what, std::optional<uint8_t> a, std::optional<uint8_t> b) {
1,215✔
419
   if(a.has_value() != b.has_value()) {
1,215✔
420
      std::ostringstream err;
×
421
      err << m_who << " " << what << " only one of a/b was nullopt";
×
422
      return test_failure(err.str());
×
423
   } else if(a.has_value() && b.has_value()) {
1,215✔
424
      return test_u8_eq(what, a.value(), b.value());
10✔
425
   } else {
426
      // both nullopt
427
      return test_success();
1,205✔
428
   }
429
}
430

431
#if defined(BOTAN_HAS_BIGINT)
432
bool Test::Result::test_bn_eq(std::string_view what, const BigInt& produced, const BigInt& expected) {
252,647✔
433
   if(produced == expected) {
252,647✔
434
      return test_success();
252,646✔
435
   } else {
436
      std::ostringstream err;
1✔
437
      err << who() << " " << what << " produced " << produced << " != expected value " << expected;
1✔
438
      return test_failure(err.str());
1✔
439
   }
1✔
440
}
441

442
bool Test::Result::test_bn_ne(std::string_view what, const BigInt& produced, const BigInt& expected) {
97✔
443
   if(produced != expected) {
97✔
444
      return test_success();
96✔
445
   } else {
446
      std::ostringstream err;
1✔
447
      err << who() << " " << what << " produced " << produced << " prohibited value";
1✔
448
      return test_failure(err.str());
1✔
449
   }
1✔
450
}
451
#endif
452

453
bool Test::Result::test_bool_eq(std::string_view what, bool produced, bool expected) {
370,534✔
454
   if(produced == expected) {
370,534✔
455
      return test_success();
370,534✔
456
   } else {
457
      if(expected == true) {
×
458
         return test_failure(Botan::fmt("Assertion failure in {}, {} was unexpectedly false", who(), what));
×
459
      } else {
460
         return test_failure(Botan::fmt("Assertion failure in {}, {} was unexpectedly true", who(), what));
×
461
      }
462
   }
463
}
464

465
bool Test::Result::test_is_true(std::string_view what, bool produced) {
236,629✔
466
   return test_bool_eq(what, produced, true);
236,629✔
467
}
468

469
bool Test::Result::test_is_false(std::string_view what, bool produced) {
126,005✔
470
   return test_bool_eq(what, produced, false);
126,005✔
471
}
472

473
bool Test::Result::test_rc_ok(std::string_view func, int rc) {
2,815✔
474
   if(rc != 0) {
2,815✔
475
      std::ostringstream err;
1✔
476
      err << m_who << " " << func << " unexpectedly failed with error code " << rc;
1✔
477
      return test_failure(err.str());
1✔
478
   }
1✔
479

480
   return test_success();
2,814✔
481
}
482

483
bool Test::Result::test_rc_fail(std::string_view func, std::string_view why, int rc) {
26✔
484
   if(rc == 0) {
26✔
485
      std::ostringstream err;
1✔
486
      err << m_who << " call to " << func << " unexpectedly succeeded expecting failure because " << why;
1✔
487
      return test_failure(err.str());
1✔
488
   }
1✔
489

490
   return test_success();
25✔
491
}
492

493
bool Test::Result::test_rc_init(std::string_view func, int rc) {
117✔
494
   if(rc == 0) {
117✔
495
      return test_success();
117✔
496
   } else {
497
      std::ostringstream msg;
×
498
      msg << m_who;
×
499
      msg << " " << func;
×
500

501
      // -40 is BOTAN_FFI_ERROR_NOT_IMPLEMENTED
502
      if(rc == -40) {
×
503
         msg << " returned not implemented";
×
504
      } else {
505
         msg << " unexpectedly failed with error code " << rc;
×
506
      }
507

508
      if(rc == -40) {
×
509
         this->test_note(msg.str());
×
510
      } else {
511
         this->test_failure(msg.str());
×
512
      }
513
      return false;
×
514
   }
×
515
}
516

517
bool Test::Result::test_rc(std::string_view func, int rc, int expected) {
421✔
518
   if(expected != rc) {
421✔
519
      std::ostringstream err;
1✔
520
      err << m_who;
1✔
521
      err << " call to " << func << " unexpectedly returned " << rc;
1✔
522
      err << " but expecting " << expected;
1✔
523
      return test_failure(err.str());
1✔
524
   }
1✔
525

526
   return test_success();
420✔
527
}
528

529
void Test::initialize(std::string test_name, CodeLocation location) {
420✔
530
   m_test_name = std::move(test_name);
420✔
531
   m_registration_location = location;
420✔
532
}
420✔
533

534
Botan::RandomNumberGenerator& Test::rng() const {
462,245✔
535
   if(!m_test_rng) {
462,245✔
536
      m_test_rng = Test::new_rng(m_test_name);
146✔
537
   }
538

539
   return *m_test_rng;
462,245✔
540
}
541

542
std::optional<std::string> Test::supported_ec_group_name(std::vector<std::string> preferred_groups) {
151✔
543
#if defined(BOTAN_HAS_ECC_GROUP)
544
   if(preferred_groups.empty()) {
151✔
545
      preferred_groups = {
149✔
546
         "secp256r1",
547
         "brainpool256r1",
548
         "secp384r1",
549
         "brainpool384r1",
550
         "secp521r1",
551
         "brainpool512r1",
552
      };
1,192✔
553
   }
554

555
   for(const auto& group : preferred_groups) {
151✔
556
      if(Botan::EC_Group::supports_named_group(group)) {
151✔
557
         return group;
151✔
558
      }
559
   }
560
#else
561
   BOTAN_UNUSED(preferred_groups);
562
#endif
563

564
   return std::nullopt;
×
565
}
298✔
566

567
std::vector<uint8_t> Test::mutate_vec(const std::vector<uint8_t>& v,
96,742✔
568
                                      Botan::RandomNumberGenerator& rng,
569
                                      bool maybe_resize,
570
                                      size_t min_offset) {
571
   std::vector<uint8_t> r = v;
96,742✔
572

573
   if(maybe_resize && (r.empty() || rng.next_byte() < 32)) {
110,323✔
574
      // TODO: occasionally truncate, insert at random index
575
      const size_t add = 1 + (rng.next_byte() % 16);
2,167✔
576
      r.resize(r.size() + add);
2,167✔
577
      rng.randomize(&r[r.size() - add], add);
2,167✔
578
   }
579

580
   if(r.size() > min_offset) {
96,742✔
581
      const size_t offset = std::max<size_t>(min_offset, rng.next_byte() % r.size());
95,204✔
582
      const uint8_t perturb = rng.next_nonzero_byte();
95,204✔
583
      r[offset] ^= perturb;
95,204✔
584
   }
585

586
   return r;
96,742✔
587
}
×
588

589
std::vector<std::string> Test::possible_providers(const std::string& /*alg*/) {
×
590
   return Test::provider_filter({"base"});
×
591
}
592

593
//static
594
std::string Test::format_time(uint64_t nanoseconds) {
1,470✔
595
   std::ostringstream o;
1,470✔
596

597
   if(nanoseconds > 1000000000) {
1,470✔
598
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000000.0 << " sec";
153✔
599
   } else {
600
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000.0 << " msec";
1,317✔
601
   }
602

603
   return o.str();
2,940✔
604
}
1,470✔
605

606
// TODO: this should move to `StdoutReporter`
607
std::string Test::Result::result_string() const {
2,557✔
608
   const bool verbose = Test::options().verbose();
2,557✔
609

610
   if(tests_run() == 0 && !verbose) {
2,557✔
611
      return "";
20✔
612
   }
613

614
   std::ostringstream report;
2,537✔
615

616
   report << who() << " ran ";
2,537✔
617

618
   if(tests_run() == 0) {
2,537✔
619
      report << "ZERO";
×
620
   } else {
621
      report << tests_run();
2,537✔
622
   }
623
   report << " tests";
2,537✔
624

625
   if(m_ns_taken > 0) {
2,537✔
626
      report << " in " << format_time(m_ns_taken);
2,938✔
627
   }
628

629
   if(tests_failed() > 0) {
2,537✔
630
      report << " " << tests_failed() << " FAILED";
25✔
631
   } else {
632
      report << " all ok";
2,512✔
633
   }
634

635
   report << "\n";
2,537✔
636

637
   for(size_t i = 0; i != m_fail_log.size(); ++i) {
2,562✔
638
      report << "Failure " << (i + 1) << ": " << m_fail_log[i];
25✔
639
      if(m_where) {
25✔
640
         report << " (at " << m_where->path << ":" << m_where->line << ")";
×
641
      }
642
      report << "\n";
25✔
643
   }
644

645
   if(!m_fail_log.empty() || tests_run() == 0 || verbose) {
2,537✔
646
      for(size_t i = 0; i != m_log.size(); ++i) {
25✔
647
         report << "Note " << (i + 1) << ": " << m_log[i] << "\n";
×
648
      }
649
   }
650

651
   return report.str();
2,537✔
652
}
2,537✔
653

654
namespace {
655

656
class Test_Registry {
657
   public:
658
      static Test_Registry& instance() {
1,282✔
659
         static Test_Registry registry;
1,283✔
660
         return registry;
1,282✔
661
      }
662

663
      void register_test(const std::string& category,
420✔
664
                         const std::string& name,
665
                         bool smoke_test,
666
                         bool needs_serialization,
667
                         std::function<std::unique_ptr<Test>()> maker_fn) {
668
         if(m_tests.contains(name)) {
420✔
669
            throw Test_Error("Duplicate registration of test '" + name + "'");
×
670
         }
671

672
         if(m_tests.contains(category)) {
420✔
673
            throw Test_Error("'" + category + "' cannot be used as category, test exists");
×
674
         }
675

676
         if(m_categories.contains(name)) {
420✔
677
            throw Test_Error("'" + name + "' cannot be used as test name, category exists");
×
678
         }
679

680
         if(smoke_test) {
420✔
681
            m_smoke_tests.push_back(name);
10✔
682
         }
683

684
         if(needs_serialization) {
420✔
685
            m_mutexed_tests.push_back(name);
21✔
686
         }
687

688
         m_tests.emplace(name, std::move(maker_fn));
420✔
689
         m_categories.emplace(category, name);
420✔
690
      }
420✔
691

692
      std::unique_ptr<Test> get_test(const std::string& test_name) const {
420✔
693
         auto i = m_tests.find(test_name);
420✔
694
         if(i != m_tests.end()) {
420✔
695
            return i->second();
420✔
696
         }
697
         return nullptr;
×
698
      }
699

700
      std::vector<std::string> registered_tests() const {
×
701
         std::vector<std::string> s;
×
702
         s.reserve(m_tests.size());
×
703
         for(auto&& i : m_tests) {
×
704
            s.push_back(i.first);
×
705
         }
706
         return s;
×
707
      }
×
708

709
      std::vector<std::string> registered_test_categories() const {
×
710
         std::set<std::string> s;
×
711
         for(auto&& i : m_categories) {
×
712
            s.insert(i.first);
×
713
         }
714
         return std::vector<std::string>(s.begin(), s.end());
×
715
      }
×
716

717
      std::vector<std::string> filter_registered_tests(const std::vector<std::string>& requested,
1✔
718
                                                       const std::vector<std::string>& to_be_skipped) {
719
         std::vector<std::string> result;
1✔
720

721
         std::set<std::string> to_be_skipped_set(to_be_skipped.begin(), to_be_skipped.end());
1✔
722
         // TODO: this is O(n^2), but we have a relatively small number of tests.
723
         auto insert_if_not_exists_and_not_skipped = [&](const std::string& test_name) {
421✔
724
            if(!Botan::value_exists(result, test_name) && !to_be_skipped_set.contains(test_name)) {
840✔
725
               result.push_back(test_name);
410✔
726
            }
727
         };
421✔
728

729
         if(requested.empty()) {
1✔
730
            /*
731
            If nothing was requested on the command line, run everything. First
732
            run the "essentials" to smoke test, then everything else in
733
            alphabetical order.
734
            */
735
            result = m_smoke_tests;
1✔
736
            for(const auto& [test_name, _] : m_tests) {
421✔
737
               insert_if_not_exists_and_not_skipped(test_name);
420✔
738
            }
739
         } else {
740
            for(const auto& r : requested) {
×
741
               if(m_tests.contains(r)) {
×
742
                  insert_if_not_exists_and_not_skipped(r);
×
743
               } else if(auto elems = m_categories.equal_range(r); elems.first != m_categories.end()) {
×
744
                  for(; elems.first != elems.second; ++elems.first) {
×
745
                     insert_if_not_exists_and_not_skipped(elems.first->second);
×
746
                  }
747
               } else {
748
                  throw Test_Error("Unknown test suite or category: " + r);
×
749
               }
750
            }
751
         }
752

753
         return result;
1✔
754
      }
1✔
755

756
      bool needs_serialization(const std::string& test_name) const {
441✔
757
         return Botan::value_exists(m_mutexed_tests, test_name);
21✔
758
      }
759

760
   private:
761
      Test_Registry() = default;
1✔
762

763
   private:
764
      std::map<std::string, std::function<std::unique_ptr<Test>()>> m_tests;
765
      std::multimap<std::string, std::string> m_categories;
766
      std::vector<std::string> m_smoke_tests;
767
      std::vector<std::string> m_mutexed_tests;
768
};
769

770
}  // namespace
771

772
// static Test:: functions
773

774
//static
775
void Test::register_test(const std::string& category,
420✔
776
                         const std::string& name,
777
                         bool smoke_test,
778
                         bool needs_serialization,
779
                         std::function<std::unique_ptr<Test>()> maker_fn) {
780
   Test_Registry::instance().register_test(category, name, smoke_test, needs_serialization, std::move(maker_fn));
840✔
781
}
420✔
782

783
//static
784
uint64_t Test::timestamp() {
180,118✔
785
   auto now = std::chrono::system_clock::now().time_since_epoch();
180,118✔
786
   return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
2,383✔
787
}
788

789
//static
790
std::vector<Test::Result> Test::flatten_result_lists(std::vector<std::vector<Test::Result>> result_lists) {
4✔
791
   std::vector<Test::Result> results;
4✔
792
   for(auto& result_list : result_lists) {
26✔
793
      for(auto& result : result_list) {
71✔
794
         results.emplace_back(std::move(result));
49✔
795
      }
796
   }
797
   return results;
4✔
798
}
×
799

800
//static
801
std::vector<std::string> Test::registered_tests() {
×
802
   return Test_Registry::instance().registered_tests();
×
803
}
804

805
//static
806
std::vector<std::string> Test::registered_test_categories() {
×
807
   return Test_Registry::instance().registered_test_categories();
×
808
}
809

810
//static
811
std::unique_ptr<Test> Test::get_test(const std::string& test_name) {
420✔
812
   return Test_Registry::instance().get_test(test_name);
420✔
813
}
814

815
//static
816
bool Test::test_needs_serialization(const std::string& test_name) {
420✔
817
   return Test_Registry::instance().needs_serialization(test_name);
420✔
818
}
819

820
//static
821
std::vector<std::string> Test::filter_registered_tests(const std::vector<std::string>& requested,
1✔
822
                                                       const std::vector<std::string>& to_be_skipped) {
823
   return Test_Registry::instance().filter_registered_tests(requested, to_be_skipped);
1✔
824
}
825

826
//static
827
std::string Test::temp_file_name(const std::string& basename) {
21✔
828
   // TODO add a --tmp-dir option to the tests to specify where these files go
829

830
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
831

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

835
   const int fd = ::mkstemp(mkstemp_basename.data());
21✔
836

837
   // error
838
   if(fd < 0) {
21✔
839
      return "";
×
840
   }
841

842
   ::close(fd);
21✔
843

844
   return mkstemp_basename;
21✔
845
#else
846
   // For now just create the temp in the current working directory
847
   return basename;
848
#endif
849
}
21✔
850

851
bool Test::copy_file(const std::string& from, const std::string& to) {
1✔
852
#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(__cpp_lib_filesystem)
853
   std::error_code ec;  // don't throw, just return false on error
1✔
854
   return std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing, ec);
1✔
855
#else
856
   // TODO: implement fallbacks to POSIX or WIN32
857
   // ... but then again: it's 2023 and we're using C++20 :o)
858
   BOTAN_UNUSED(from, to);
859
   throw Botan::No_Filesystem_Access();
860
#endif
861
}
862

863
std::string Test::read_data_file(const std::string& path) {
38✔
864
   const std::string fsname = Test::data_file(path);
38✔
865
   std::ifstream file(fsname.c_str());
38✔
866
   if(!file.good()) {
38✔
867
      throw Test_Error("Error reading from " + fsname);
×
868
   }
869

870
   return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
76✔
871
}
38✔
872

873
std::vector<uint8_t> Test::read_binary_data_file(const std::string& path) {
34✔
874
   const std::string fsname = Test::data_file(path);
34✔
875
   std::ifstream file(fsname.c_str(), std::ios::binary);
34✔
876
   if(!file.good()) {
34✔
877
      throw Test_Error("Error reading from " + fsname);
×
878
   }
879

880
   std::vector<uint8_t> contents;
34✔
881

882
   while(file.good()) {
74✔
883
      std::vector<uint8_t> buf(4096);
40✔
884
      file.read(reinterpret_cast<char*>(buf.data()), buf.size());
40✔
885
      const size_t got = static_cast<size_t>(file.gcount());
40✔
886

887
      if(got == 0 && file.eof()) {
40✔
888
         break;
889
      }
890

891
      contents.insert(contents.end(), buf.data(), buf.data() + got);
40✔
892
   }
40✔
893

894
   return contents;
68✔
895
}
34✔
896

897
// static member variables of Test
898

899
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
900
Test_Options Test::m_opts;
901
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
902
std::string Test::m_test_rng_seed;
903

904
//static
905
void Test::set_test_options(const Test_Options& opts) {
1✔
906
   m_opts = opts;
1✔
907
}
1✔
908

909
namespace {
910

911
/*
912
* This is a fast, simple, deterministic PRNG that's used for running
913
* the tests. It is not intended to be cryptographically secure.
914
*/
915
class Testsuite_RNG final : public Botan::RandomNumberGenerator {
8✔
916
   public:
917
      std::string name() const override { return "Testsuite_RNG"; }
×
918

919
      void clear() override { m_x = 0; }
×
920

921
      bool accepts_input() const override { return true; }
×
922

923
      bool is_seeded() const override { return true; }
277,286✔
924

925
      void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> input) override {
10,244,430✔
926
         for(const auto byte : input) {
10,244,430✔
927
            mix(byte);
×
928
         }
929

930
         for(auto& byte : output) {
73,493,311✔
931
            byte = mix();
63,248,881✔
932
         }
933
      }
10,244,430✔
934

935
      Testsuite_RNG(std::string_view seed, std::string_view test_name) : m_x(0) {
277✔
936
         for(const char c : seed) {
8,310✔
937
            this->mix(static_cast<uint8_t>(c));
8,033✔
938
         }
939
         for(const char c : test_name) {
5,587✔
940
            this->mix(static_cast<uint8_t>(c));
5,310✔
941
         }
942
      }
277✔
943

944
   private:
945
      uint8_t mix(uint8_t input = 0) {
63,262,224✔
946
         m_x ^= input;
63,262,224✔
947
         m_x *= 0xF2E16957;
63,262,224✔
948
         m_x += 0xE50B590F;
63,262,224✔
949
         return static_cast<uint8_t>(m_x >> 27);
63,262,224✔
950
      }
951

952
      uint64_t m_x;
953
};
954

955
}  // namespace
956

957
//static
958
void Test::set_test_rng_seed(std::span<const uint8_t> seed, size_t epoch) {
1✔
959
   m_test_rng_seed = Botan::fmt("seed={} epoch={}", Botan::hex_encode(seed), epoch);
1✔
960
}
1✔
961

962
//static
963
std::unique_ptr<Botan::RandomNumberGenerator> Test::new_rng(std::string_view test_name) {
269✔
964
   return std::make_unique<Testsuite_RNG>(m_test_rng_seed, test_name);
269✔
965
}
966

967
//static
968
std::shared_ptr<Botan::RandomNumberGenerator> Test::new_shared_rng(std::string_view test_name) {
8✔
969
   return std::make_shared<Testsuite_RNG>(m_test_rng_seed, test_name);
8✔
970
}
971

972
//static
973
std::string Test::data_file(const std::string& file) {
773✔
974
   return options().data_dir() + "/" + file;
1,546✔
975
}
976

977
//static
978
std::string Test::data_dir(const std::string& subdir) {
1✔
979
   return options().data_dir() + "/" + subdir;
2✔
980
}
981

982
//static
983
std::vector<std::string> Test::files_in_data_dir(const std::string& subdir) {
393✔
984
   auto fs = Botan::get_files_recursive(options().data_dir() + "/" + subdir);
1,179✔
985
   if(fs.empty()) {
393✔
986
      throw Test_Error("Test::files_in_data_dir encountered empty subdir " + subdir);
×
987
   }
988
   return fs;
393✔
989
}
×
990

991
//static
992
std::string Test::data_file_as_temporary_copy(const std::string& what) {
1✔
993
   auto tmp_basename = what;
1✔
994
   std::replace(tmp_basename.begin(), tmp_basename.end(), '/', '_');
1✔
995
   auto temp_file = temp_file_name("tmp-" + tmp_basename);
1✔
996
   if(temp_file.empty()) {
1✔
997
      return "";
×
998
   }
999
   if(!Test::copy_file(data_file(what), temp_file)) {
1✔
1000
      return "";
×
1001
   }
1002
   return temp_file;
1✔
1003
}
1✔
1004

1005
//static
1006
std::vector<std::string> Test::provider_filter(const std::vector<std::string>& providers) {
49,047✔
1007
   if(m_opts.provider().empty()) {
49,047✔
1008
      return providers;
49,047✔
1009
   }
1010
   for(auto&& provider : providers) {
×
1011
      if(provider == m_opts.provider()) {
×
1012
         return std::vector<std::string>{provider};
×
1013
      }
1014
   }
1015
   return std::vector<std::string>{};
×
1016
}
×
1017

1018
std::string Test::random_password(Botan::RandomNumberGenerator& rng) {
222✔
1019
   const size_t len = 1 + rng.next_byte() % 32;
222✔
1020
   return Botan::hex_encode(rng.random_vec(len));
444✔
1021
}
1022

1023
size_t Test::random_index(Botan::RandomNumberGenerator& rng, size_t max) {
8,062✔
1024
   return Botan::load_be(rng.random_array<8>()) % max;
8,062✔
1025
}
1026

1027
void VarMap::clear() {
34,297✔
1028
   m_vars.clear();
×
1029
}
×
1030

1031
namespace {
1032

1033
bool varmap_pair_lt(const std::pair<std::string, std::string>& kv, const std::string& k) {
1,713,996✔
1034
   return kv.first < k;
1,713,996✔
1035
}
1036

1037
}  // namespace
1038

1039
void VarMap::add(const std::string& key, const std::string& value) {
156,283✔
1040
   auto i = std::lower_bound(m_vars.begin(), m_vars.end(), key, varmap_pair_lt);
156,283✔
1041

1042
   if(i != m_vars.end() && i->first == key) {
156,283✔
1043
      i->second = value;
44,405✔
1044
   } else {
1045
      m_vars.emplace(i, key, value);
111,878✔
1046
   }
1047
}
156,283✔
1048

1049
std::optional<std::string> VarMap::get_var(const std::string& key) const {
568,619✔
1050
   auto i = std::lower_bound(m_vars.begin(), m_vars.end(), key, varmap_pair_lt);
568,619✔
1051

1052
   if(i != m_vars.end() && i->first == key) {
568,619✔
1053
      return i->second;
498,714✔
1054
   } else {
1055
      return {};
69,905✔
1056
   }
1057
}
1058

1059
bool VarMap::has_key(const std::string& key) const {
213,622✔
1060
   return get_var(key).has_value();
213,622✔
1061
}
1062

1063
std::string VarMap::get_req_str(const std::string& key) const {
259,704✔
1064
   auto var = get_var(key);
259,704✔
1065
   if(var) {
259,704✔
1066
      return *var;
519,408✔
1067
   } else {
1068
      throw Test_Error("Test missing variable " + key);
×
1069
   }
1070
}
259,704✔
1071

1072
std::vector<std::vector<uint8_t>> VarMap::get_req_bin_list(const std::string& key) const {
12✔
1073
   const auto var = get_req_str(key);
12✔
1074

1075
   std::vector<std::vector<uint8_t>> bin_list;
12✔
1076

1077
   for(auto&& part : Botan::split_on(var, ',')) {
62✔
1078
      try {
50✔
1079
         bin_list.push_back(Botan::hex_decode(part));
100✔
1080
      } catch(std::exception& e) {
×
1081
         std::ostringstream oss;
×
1082
         oss << "Bad input '" << part << "'"
×
1083
             << " in binary list key " << key << " - " << e.what();
×
1084
         throw Test_Error(oss.str());
×
1085
      }
×
1086
   }
12✔
1087

1088
   return bin_list;
12✔
1089
}
12✔
1090

1091
std::vector<uint8_t> VarMap::get_req_bin(const std::string& key) const {
163,786✔
1092
   const auto var = get_req_str(key);
163,786✔
1093

1094
   try {
163,786✔
1095
      if(var.starts_with("0x")) {
163,786✔
1096
         if(var.size() % 2 == 0) {
×
1097
            return Botan::hex_decode(var.substr(2));
×
1098
         } else {
1099
            std::string z = var;
×
1100
            std::swap(z[0], z[1]);  // swap 0x to x0 then remove x
×
1101
            return Botan::hex_decode(z.substr(1));
×
1102
         }
×
1103
      } else {
1104
         return Botan::hex_decode(var);
163,786✔
1105
      }
1106
   } catch(std::exception& e) {
×
1107
      std::ostringstream oss;
×
1108
      oss << "Bad input '" << var << "'"
×
1109
          << " for key " << key << " - " << e.what();
×
1110
      throw Test_Error(oss.str());
×
1111
   }
×
1112
}
163,786✔
1113

1114
std::string VarMap::get_opt_str(const std::string& key, const std::string& def_value) const {
14,378✔
1115
   if(auto v = get_var(key)) {
14,378✔
1116
      return *v;
57✔
1117
   } else {
1118
      return def_value;
28,699✔
1119
   }
14,378✔
1120
}
1121

1122
bool VarMap::get_req_bool(const std::string& key) const {
39✔
1123
   const auto var = get_req_str(key);
39✔
1124

1125
   if(var == "true") {
39✔
1126
      return true;
1127
   } else if(var == "false") {
23✔
1128
      return false;
1129
   } else {
1130
      throw Test_Error("Invalid boolean for key '" + key + "' value '" + var + "'");
×
1131
   }
1132
}
39✔
1133

1134
size_t VarMap::get_req_sz(const std::string& key) const {
4,547✔
1135
   return Botan::to_u32bit(get_req_str(key));
4,547✔
1136
}
1137

1138
uint8_t VarMap::get_req_u8(const std::string& key) const {
17✔
1139
   const size_t s = this->get_req_sz(key);
17✔
1140
   if(s > 256) {
17✔
1141
      throw Test_Error("Invalid " + key + " expected uint8_t got " + std::to_string(s));
×
1142
   }
1143
   return static_cast<uint8_t>(s);
17✔
1144
}
1145

1146
uint32_t VarMap::get_req_u32(const std::string& key) const {
14✔
1147
   return static_cast<uint32_t>(get_req_sz(key));
14✔
1148
}
1149

1150
uint64_t VarMap::get_req_u64(const std::string& key) const {
17✔
1151
   const auto var = get_req_str(key);
17✔
1152
   try {
17✔
1153
      return std::stoull(var);
17✔
1154
   } catch(std::exception&) {
×
1155
      throw Test_Error("Invalid u64 value '" + var + "'");
×
1156
   }
×
1157
}
17✔
1158

1159
size_t VarMap::get_opt_sz(const std::string& key, const size_t def_value) const {
31,404✔
1160
   if(auto v = get_var(key)) {
31,404✔
1161
      return Botan::to_u32bit(*v);
12,244✔
1162
   } else {
1163
      return def_value;
1164
   }
31,404✔
1165
}
1166

1167
uint64_t VarMap::get_opt_u64(const std::string& key, const uint64_t def_value) const {
3,538✔
1168
   if(auto v = get_var(key)) {
3,538✔
1169
      try {
641✔
1170
         return std::stoull(*v);
3,538✔
1171
      } catch(std::exception&) {
×
1172
         throw Test_Error("Invalid u64 value '" + *v + "'");
×
1173
      }
×
1174
   } else {
1175
      return def_value;
1176
   }
3,538✔
1177
}
×
1178

1179
std::vector<uint8_t> VarMap::get_opt_bin(const std::string& key) const {
45,893✔
1180
   if(auto v = get_var(key)) {
45,893✔
1181
      try {
16,134✔
1182
         return Botan::hex_decode(*v);
16,134✔
1183
      } catch(std::exception&) {
×
1184
         throw Test_Error("Test invalid hex input '" + *v + "'" + +" for key " + key);
×
1185
      }
×
1186
   } else {
1187
      return {};
29,759✔
1188
   };
45,893✔
1189
}
×
1190

1191
#if defined(BOTAN_HAS_BIGINT)
1192
Botan::BigInt VarMap::get_req_bn(const std::string& key) const {
38,502✔
1193
   const auto var = get_req_str(key);
38,502✔
1194

1195
   try {
38,502✔
1196
      return Botan::BigInt(var);
38,502✔
1197
   } catch(std::exception&) {
×
1198
      throw Test_Error("Test invalid bigint input '" + var + "' for key " + key);
×
1199
   }
×
1200
}
38,502✔
1201

1202
Botan::BigInt VarMap::get_opt_bn(const std::string& key, const Botan::BigInt& def_value) const {
80✔
1203
   if(auto v = get_var(key)) {
80✔
1204
      try {
56✔
1205
         return Botan::BigInt(*v);
56✔
1206
      } catch(std::exception&) {
×
1207
         throw Test_Error("Test invalid bigint input '" + *v + "' for key " + key);
×
1208
      }
×
1209
   } else {
1210
      return def_value;
24✔
1211
   }
80✔
1212
}
×
1213
#endif
1214

1215
class Text_Based_Test::Text_Based_Test_Data {
1216
   public:
1217
      Text_Based_Test_Data(const std::string& data_src,
181✔
1218
                           const std::string& required_keys_str,
1219
                           const std::string& optional_keys_str) :
181✔
1220
            m_data_src(data_src) {
362✔
1221
         if(required_keys_str.empty()) {
181✔
1222
            throw Test_Error("Invalid test spec");
×
1223
         }
1224

1225
         m_required_keys = Botan::split_on(required_keys_str, ',');
181✔
1226
         std::vector<std::string> optional_keys = Botan::split_on(optional_keys_str, ',');
181✔
1227

1228
         m_all_keys.insert(m_required_keys.begin(), m_required_keys.end());
181✔
1229
         m_all_keys.insert(optional_keys.begin(), optional_keys.end());
181✔
1230
         m_output_key = m_required_keys.at(m_required_keys.size() - 1);
181✔
1231
      }
181✔
1232

1233
      std::string get_next_line();
1234

1235
      const std::string& current_source_name() const { return m_cur_src_name; }
×
1236

1237
      bool known_key(const std::string& key) const;
1238

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

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

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

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

1247
      const std::string& initial_data_src_name() const { return m_data_src; }
181✔
1248

1249
   private:
1250
      std::string m_data_src;
1251
      std::vector<std::string> m_required_keys;
1252
      std::unordered_set<std::string> m_all_keys;
1253
      std::string m_output_key;
1254

1255
      bool m_first = true;
1256
      std::unique_ptr<std::istream> m_cur;
1257
      std::string m_cur_src_name;
1258
      std::deque<std::string> m_srcs;
1259
      std::vector<std::string> m_cpu_flags;
1260
};
1261

1262
Text_Based_Test::Text_Based_Test(const std::string& data_src,
181✔
1263
                                 const std::string& required_keys,
1264
                                 const std::string& optional_keys) :
181✔
1265
      m_data(std::make_unique<Text_Based_Test_Data>(data_src, required_keys, optional_keys)) {}
181✔
1266

1267
Text_Based_Test::~Text_Based_Test() = default;
181✔
1268

1269
std::string Text_Based_Test::Text_Based_Test_Data::get_next_line() {
157,290✔
1270
   while(true) {
157,548✔
1271
      if(m_cur == nullptr || m_cur->good() == false) {
157,548✔
1272
         if(m_srcs.empty()) {
450✔
1273
            if(m_first) {
362✔
1274
               if(m_data_src.ends_with(".vec")) {
181✔
1275
                  m_srcs.push_back(Test::data_file(m_data_src));
338✔
1276
               } else {
1277
                  const auto fs = Test::files_in_data_dir(m_data_src);
12✔
1278
                  m_srcs.assign(fs.begin(), fs.end());
12✔
1279
                  if(m_srcs.empty()) {
12✔
1280
                     throw Test_Error("Error reading test data dir " + m_data_src);
×
1281
                  }
1282
               }
12✔
1283

1284
               m_first = false;
181✔
1285
            } else {
1286
               return "";  // done
181✔
1287
            }
1288
         }
1289

1290
         m_cur = std::make_unique<std::ifstream>(m_srcs[0]);
357✔
1291
         m_cur_src_name = m_srcs[0];
269✔
1292

1293
#if defined(BOTAN_HAS_CPUID)
1294
         // Reinit cpuid on new file if needed
1295
         if(m_cpu_flags.empty() == false) {
269✔
1296
            m_cpu_flags.clear();
19✔
1297
            Botan::CPUID::initialize();
19✔
1298
         }
1299
#endif
1300

1301
         if(!m_cur->good()) {
269✔
1302
            throw Test_Error("Could not open input file '" + m_cur_src_name);
×
1303
         }
1304

1305
         m_srcs.pop_front();
269✔
1306
      }
1307

1308
      while(m_cur->good()) {
227,971✔
1309
         std::string line;
227,713✔
1310
         std::getline(*m_cur, line);
227,713✔
1311

1312
         if(line.empty()) {
227,713✔
1313
            continue;
55,323✔
1314
         }
1315

1316
         if(line[0] == '#') {
172,390✔
1317
            if(line.starts_with("#test ")) {
15,302✔
1318
               return line;
21✔
1319
            } else {
1320
               continue;
15,281✔
1321
            }
1322
         }
1323

1324
         return line;
157,088✔
1325
      }
227,713✔
1326
   }
1327
}
1328

1329
bool Text_Based_Test::Text_Based_Test_Data::known_key(const std::string& key) const {
156,283✔
1330
   return m_all_keys.contains(key);
156,283✔
1331
}
1332

1333
namespace {
1334

1335
// strips leading and trailing but not internal whitespace
1336
std::string strip_ws(const std::string& in) {
312,566✔
1337
   const char* whitespace = " ";
312,566✔
1338

1339
   const auto first_c = in.find_first_not_of(whitespace);
312,566✔
1340
   if(first_c == std::string::npos) {
312,566✔
1341
      return "";
1,034✔
1342
   }
1343

1344
   const auto last_c = in.find_last_not_of(whitespace);
311,532✔
1345

1346
   return in.substr(first_c, last_c - first_c + 1);
311,532✔
1347
}
1348

1349
std::vector<std::string> parse_cpuid_bits(const std::vector<std::string>& tok) {
21✔
1350
   std::vector<std::string> bits;
21✔
1351

1352
#if defined(BOTAN_HAS_CPUID)
1353
   for(size_t i = 1; i < tok.size(); ++i) {
106✔
1354
      if(auto bit = Botan::CPUID::bit_from_string(tok[i])) {
85✔
1355
         bits.push_back(bit->to_string());
98✔
1356
      }
1357
   }
1358
#else
1359
   BOTAN_UNUSED(tok);
1360
#endif
1361

1362
   return bits;
21✔
1363
}
×
1364

1365
}  // namespace
1366

1367
bool Text_Based_Test::skip_this_test(const std::string& /*header*/, const VarMap& /*vars*/) {
32,625✔
1368
   return false;
32,625✔
1369
}
1370

1371
std::vector<Test::Result> Text_Based_Test::run() {
181✔
1372
   std::vector<Test::Result> results;
181✔
1373

1374
   std::string header;
181✔
1375
   std::string header_or_name = m_data->initial_data_src_name();
181✔
1376
   VarMap vars;
181✔
1377
   size_t test_cnt = 0;
181✔
1378

1379
   while(true) {
157,290✔
1380
      const std::string line = m_data->get_next_line();
157,290✔
1381
      if(line.empty()) {
157,290✔
1382
         // EOF
1383
         break;
1384
      }
1385

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

1389
         if(pragma_tokens.empty()) {
21✔
1390
            throw Test_Error("Empty pragma found in " + m_data->current_source_name());
×
1391
         }
1392

1393
         if(pragma_tokens[0] != "cpuid") {
21✔
1394
            throw Test_Error("Unknown test pragma '" + line + "' in " + m_data->current_source_name());
×
1395
         }
1396

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

1401
         m_data->set_cpu_flags(parse_cpuid_bits(pragma_tokens));
42✔
1402

1403
         continue;
21✔
1404
      } else if(line[0] == '#') {
157,109✔
1405
         throw Test_Error("Unknown test pragma '" + line + "' in " + m_data->current_source_name());
×
1406
      }
1407

1408
      if(line[0] == '[' && line[line.size() - 1] == ']') {
157,088✔
1409
         header = line.substr(1, line.size() - 2);
805✔
1410
         header_or_name = header;
805✔
1411
         test_cnt = 0;
805✔
1412
         vars.clear();
805✔
1413
         continue;
805✔
1414
      }
1415

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

1418
      auto equal_i = line.find_first_of('=');
156,283✔
1419

1420
      if(equal_i == std::string::npos) {
156,283✔
1421
         results.push_back(Test::Result::Failure(header_or_name, "invalid input '" + line + "'"));
×
1422
         continue;
×
1423
      }
1424

1425
      const std::string key = strip_ws(std::string(line.begin(), line.begin() + equal_i - 1));
312,566✔
1426
      const std::string val = strip_ws(std::string(line.begin() + equal_i + 1, line.end()));
312,566✔
1427

1428
      if(!m_data->known_key(key)) {
156,283✔
1429
         auto r = Test::Result::Failure(header_or_name, Botan::fmt("{} failed unknown key {}", test_id, key));
×
1430
         results.push_back(r);
×
1431
      }
×
1432

1433
      vars.add(key, val);
156,283✔
1434

1435
      if(key == m_data->output_key()) {
156,283✔
1436
         try {
48,404✔
1437
            for(const auto& req_key : m_data->required_keys()) {
245,618✔
1438
               if(!vars.has_key(req_key)) {
197,214✔
1439
                  auto r =
×
1440
                     Test::Result::Failure(header_or_name, Botan::fmt("{} missing required key {}", test_id, req_key));
×
1441
                  results.push_back(r);
×
1442
               }
×
1443
            }
1444

1445
            if(skip_this_test(header, vars)) {
48,404✔
1446
               continue;
139✔
1447
            }
1448

1449
            ++test_cnt;
48,265✔
1450

1451
            const uint64_t start = Test::timestamp();
48,265✔
1452

1453
            Test::Result result = run_one_test(header, vars);
48,265✔
1454
#if defined(BOTAN_HAS_CPUID)
1455
            if(!m_data->cpu_flags().empty()) {
48,265✔
1456
               for(const auto& cpuid_str : m_data->cpu_flags()) {
30,651✔
1457
                  if(const auto bit = Botan::CPUID::Feature::from_string(cpuid_str)) {
21,698✔
1458
                     if(Botan::CPUID::has(*bit)) {
21,698✔
1459
                        Botan::CPUID::clear_cpuid_bit(*bit);
16,586✔
1460
                        // now re-run the test
1461
                        result.merge(run_one_test(header, vars));
16,586✔
1462
                     }
1463
                  }
1464
               }
1465
               Botan::CPUID::initialize();
8,953✔
1466
            }
1467
#endif
1468
            result.set_ns_consumed(Test::timestamp() - start);
48,265✔
1469

1470
            if(result.tests_failed() > 0) {
48,265✔
1471
               std::ostringstream oss;
×
1472
               oss << "Test # " << test_cnt << " ";
×
1473
               if(!header.empty()) {
×
1474
                  oss << header << " ";
×
1475
               }
1476
               oss << "failed ";
×
1477

1478
               for(const auto& k : m_data->required_keys()) {
×
1479
                  oss << k << "=" << vars.get_req_str(k) << " ";
×
1480
               }
1481

1482
               result.test_note(oss.str());
×
1483
            }
×
1484
            results.push_back(result);
48,265✔
1485
         } catch(std::exception& e) {
48,265✔
1486
            std::ostringstream oss;
×
1487
            oss << "Test # " << test_cnt << " ";
×
1488
            if(!header.empty()) {
×
1489
               oss << header << " ";
×
1490
            }
1491

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

1496
            oss << "failed with exception '" << e.what() << "'";
×
1497

1498
            results.push_back(Test::Result::Failure(header_or_name, oss.str()));
×
1499
         }
×
1500

1501
         if(clear_between_callbacks()) {
48,265✔
1502
            vars.clear();
189,636✔
1503
         }
1504
      }
1505
   }
157,387✔
1506

1507
   if(results.empty()) {
181✔
1508
      return results;
1509
   }
1510

1511
   try {
181✔
1512
      std::vector<Test::Result> final_tests = run_final_tests();
181✔
1513
      results.insert(results.end(), final_tests.begin(), final_tests.end());
181✔
1514
   } catch(std::exception& e) {
181✔
1515
      results.push_back(Test::Result::Failure(header_or_name, "run_final_tests exception " + std::string(e.what())));
×
1516
   }
×
1517

1518
   return results;
1519
}
181✔
1520

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