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

randombit / botan / 22017538080

14 Feb 2026 12:43PM UTC coverage: 90.069% (+0.004%) from 90.065%
22017538080

Pull #5328

github

web-flow
Merge 164f4ff86 into 5f60b6bbe
Pull Request #5328: Change Test::Result integer and bool predicates to be specifically named

102249 of 113523 relevant lines covered (90.07%)

11529820.5 hits per line

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

79.59
/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;
419✔
59

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

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

83
   m_timestamp = std::min(m_timestamp, other.m_timestamp);
126,761✔
84
   m_ns_taken += other.m_ns_taken;
126,761✔
85
   m_tests_passed += other.m_tests_passed;
126,761✔
86
   m_fail_log.insert(m_fail_log.end(), other.m_fail_log.begin(), other.m_fail_log.end());
126,761✔
87
   m_log.insert(m_log.end(), other.m_log.begin(), other.m_log.end());
126,761✔
88
}
126,761✔
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(!confirm(what, expr, expected)) {
329✔
131
      throw Test_Aborted(Botan::fmt("Test aborted, because required condition was not met: {}", what));
×
132
   }
133
}
329✔
134

135
bool Test::Result::ThrowExpectations::check(std::string_view test_name, Test::Result& result) {
67,783✔
136
   m_consumed = true;
67,783✔
137

138
   try {
67,783✔
139
      m_fn();
67,783✔
140
      if(!m_expect_success) {
1,646✔
141
         return result.test_failure(Botan::fmt("{} failed to throw expected exception", test_name));
2✔
142
      }
143
   } catch(const std::exception& ex) {
66,137✔
144
      if(m_expect_success) {
66,135✔
145
         return result.test_failure(Botan::fmt("{} threw unexpected exception: {}", test_name, ex.what()));
1✔
146
      }
147
      if(m_expected_exception_check_fn && !m_expected_exception_check_fn(std::current_exception())) {
190,230✔
148
         return result.test_failure(Botan::fmt("{} threw unexpected exception: {}", test_name, ex.what()));
2✔
149
      }
150
      if(m_expected_message.has_value() && m_expected_message.value() != ex.what()) {
66,132✔
151
         return result.test_failure(Botan::fmt("{} threw exception with unexpected message (expected {} got {})",
1✔
152
                                               test_name,
153
                                               m_expected_message.value(),
1✔
154
                                               ex.what()));
2✔
155
      }
156
   } catch(...) {
66,137✔
157
      if(m_expect_success || m_expected_exception_check_fn || m_expected_message.has_value()) {
2✔
158
         return result.test_failure(Botan::fmt("{} threw unexpected unknown exception", test_name));
1✔
159
      }
160
   }
2✔
161

162
   return result.test_success();
67,776✔
163
}
164

165
bool Test::Result::test_throws(std::string_view what, const std::function<void()>& fn) {
3,916✔
166
   return ThrowExpectations(fn).check(what, *this);
7,832✔
167
}
168

169
bool Test::Result::test_throws(std::string_view what, std::string_view expected, const std::function<void()>& fn) {
174✔
170
   return ThrowExpectations(fn).expect_message(expected).check(what, *this);
348✔
171
}
172

173
bool Test::Result::test_no_throw(std::string_view what, const std::function<void()>& fn) {
1,645✔
174
   return ThrowExpectations(fn).expect_success().check(what, *this);
3,290✔
175
}
176

177
bool Test::Result::test_success(std::string_view note) {
3,620,248✔
178
   if(Test::options().log_success()) {
3,620,223✔
179
      test_note(note);
×
180
   }
181
   ++m_tests_passed;
3,620,248✔
182
   return true;
397,232✔
183
}
184

185
bool Test::Result::test_failure(std::string_view what, std::string_view error) {
2✔
186
   return test_failure(Botan::fmt("{} {} with error {}", who(), what, error));
2✔
187
}
188

189
void Test::Result::test_failure(std::string_view what, const uint8_t buf[], size_t buf_len) {
×
190
   return test_failure(what, {buf, buf_len});
×
191
}
192

193
void Test::Result::test_failure(std::string_view what, std::span<const uint8_t> context) {
1✔
194
   test_failure(Botan::fmt("{} {} with value {}", who(), what, Botan::hex_encode(context)));
2✔
195
}
1✔
196

197
bool Test::Result::test_failure(std::string_view err) {
25✔
198
   m_fail_log.push_back(std::string(err));
25✔
199

200
   if(Test::options().abort_on_first_fail() && m_who != "Failing Test") {
25✔
201
      std::abort();
×
202
   }
203
   return false;
25✔
204
}
205

206
namespace {
207

208
bool same_contents(const uint8_t x[], const uint8_t y[], size_t len) {
292,413✔
209
   return (len == 0) ? true : std::memcmp(x, y, len) == 0;
290,982✔
210
}
211

212
}  // namespace
213

214
bool Test::Result::test_ne(std::string_view what,
3,406✔
215
                           const uint8_t produced[],
216
                           size_t produced_len,
217
                           const uint8_t expected[],
218
                           size_t expected_len) {
219
   if(produced_len == expected_len && same_contents(produced, expected, expected_len)) {
3,406✔
220
      return test_failure(Botan::fmt("{} {} produced matching bytes", who(), what));
2✔
221
   }
222
   return test_success();
3,404✔
223
}
224

225
bool Test::Result::test_eq(std::string_view what, std::span<const uint8_t> produced, const char* expected_hex) {
471✔
226
   const std::vector<uint8_t> expected = Botan::hex_decode(expected_hex);
471✔
227
   return test_eq(nullptr, what, produced.data(), produced.size(), expected.data(), expected.size());
471✔
228
}
471✔
229

230
bool Test::Result::test_eq(const char* producer,
290,963✔
231
                           std::string_view what,
232
                           const uint8_t produced[],
233
                           size_t produced_size,
234
                           const uint8_t expected[],
235
                           size_t expected_size) {
236
   if(produced_size == expected_size && same_contents(produced, expected, expected_size)) {
290,963✔
237
      return test_success();
290,962✔
238
   }
239

240
   std::ostringstream err;
1✔
241

242
   err << who();
1✔
243

244
   if(producer != nullptr) {
1✔
245
      err << " producer '" << producer << "'";
×
246
   }
247

248
   err << " unexpected result for " << what;
1✔
249

250
   if(produced_size != expected_size) {
1✔
251
      err << " produced " << produced_size << " bytes expected " << expected_size;
1✔
252
   }
253

254
   std::vector<uint8_t> xor_diff(std::min(produced_size, expected_size));
2✔
255
   size_t bytes_different = 0;
1✔
256

257
   for(size_t i = 0; i != xor_diff.size(); ++i) {
4✔
258
      xor_diff[i] = produced[i] ^ expected[i];
3✔
259
      if(xor_diff[i] > 0) {
3✔
260
         bytes_different++;
3✔
261
      }
262
   }
263

264
   err << "\nProduced: " << Botan::hex_encode(produced, produced_size)
1✔
265
       << "\nExpected: " << Botan::hex_encode(expected, expected_size);
3✔
266

267
   if(bytes_different > 0) {
1✔
268
      err << "\nXOR Diff: " << Botan::hex_encode(xor_diff);
1✔
269
   }
270

271
   return test_failure(err.str());
1✔
272
}
1✔
273

274
bool Test::Result::test_is_nonempty(std::string_view what_is_it, std::string_view to_examine) {
38,829✔
275
   if(to_examine.empty()) {
38,829✔
276
      return test_failure(what_is_it, "was empty");
1✔
277
   }
278
   return test_success();
38,828✔
279
}
280

281
bool Test::Result::test_eq(std::string_view what, std::string_view produced, std::string_view expected) {
76,783✔
282
   return test_is_eq(what, produced, expected);
76,783✔
283
}
284

285
bool Test::Result::test_eq(std::string_view what, const char* produced, const char* expected) {
30✔
286
   return test_is_eq(what, std::string(produced), std::string(expected));
30✔
287
}
288

289
bool Test::Result::test_u8_eq(uint8_t produced, uint8_t expected) {
214✔
290
   return test_sz_eq("comparison", produced, expected);
214✔
291
}
292

293
bool Test::Result::test_u8_eq(std::string_view what, uint8_t produced, uint8_t expected) {
46,561✔
294
   return test_sz_eq(what, produced, expected);
46,561✔
295
}
296

297
bool Test::Result::test_u16_eq(uint16_t produced, uint16_t expected) {
36✔
298
   return test_sz_eq("comparison", produced, expected);
36✔
299
}
300

301
bool Test::Result::test_u16_eq(std::string_view what, uint16_t produced, uint16_t expected) {
65,560✔
302
   return test_sz_eq(what, produced, expected);
65,560✔
303
}
304

305
bool Test::Result::test_u32_eq(uint32_t produced, uint32_t expected) {
12✔
306
   return test_sz_eq("comparison", produced, expected);
12✔
307
}
308

309
bool Test::Result::test_u32_eq(std::string_view what, uint32_t produced, uint32_t expected) {
4,127✔
310
   return test_sz_eq(what, produced, expected);
4,127✔
311
}
312

313
bool Test::Result::test_u64_eq(uint64_t produced, uint64_t expected) {
8✔
314
   return test_u64_eq("comparison", produced, expected);
8✔
315
}
316

317
bool Test::Result::test_u64_eq(std::string_view what, uint64_t produced, uint64_t expected) {
1,238✔
318
   if(produced == expected) {
1,238✔
319
      return test_success();
1,238✔
320
   } else {
321
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} == {}", who(), what, produced, expected));
×
322
   }
323
}
324

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

333
bool Test::Result::test_sz_ne(std::string_view what, size_t produced, size_t expected) {
119✔
334
   if(produced != expected) {
119✔
335
      return test_success();
118✔
336
   } else {
337
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} != {}", who(), what, produced, expected));
1✔
338
   }
339
}
340

341
bool Test::Result::test_sz_lt(std::string_view what, size_t produced, size_t expected) {
5,639✔
342
   if(produced < expected) {
5,639✔
343
      return test_success();
5,638✔
344
   } else {
345
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} < {}", who(), what, produced, expected));
1✔
346
   }
347
}
348

349
bool Test::Result::test_sz_lte(std::string_view what, size_t produced, size_t expected) {
1,021,636✔
350
   if(produced <= expected) {
1,021,636✔
351
      return test_success();
1,021,635✔
352
   } else {
353
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} <= {}", who(), what, produced, expected));
1✔
354
   }
355
}
356

357
bool Test::Result::test_sz_gt(std::string_view what, size_t produced, size_t expected) {
22,373✔
358
   if(produced > expected) {
22,373✔
359
      return test_success();
22,373✔
360
   } else {
361
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} > {}", who(), what, produced, expected));
×
362
   }
363
}
364

365
bool Test::Result::test_sz_gte(std::string_view what, size_t produced, size_t expected) {
1,142,430✔
366
   if(produced >= expected) {
1,142,430✔
367
      return test_success();
1,142,429✔
368
   } else {
369
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} >= {}", who(), what, produced, expected));
1✔
370
   }
371
}
372

373
bool Test::Result::test_ne(std::string_view what, std::string_view str1, std::string_view str2) {
26✔
374
   if(str1 != str2) {
26✔
375
      return test_success(Botan::fmt("{} != {}", str1, str2));
25✔
376
   } else {
377
      return test_failure(Botan::fmt("{} {} unexpectedly produced matching strings {}", who(), what, str1));
1✔
378
   }
379
}
380

381
#if defined(BOTAN_HAS_BIGINT)
382
bool Test::Result::test_eq(std::string_view what, const BigInt& produced, const BigInt& expected) {
252,644✔
383
   return test_is_eq(what, produced, expected);
252,644✔
384
}
385

386
bool Test::Result::test_ne(std::string_view what, const BigInt& produced, const BigInt& expected) {
97✔
387
   if(produced != expected) {
97✔
388
      return test_success();
96✔
389
   }
390

391
   std::ostringstream err;
1✔
392
   err << who() << " " << what << " produced " << produced << " prohibited value";
1✔
393
   return test_failure(err.str());
1✔
394
}
1✔
395
#endif
396

397
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
398
bool Test::Result::test_eq(std::string_view what, const Botan::EC_Point& a, const Botan::EC_Point& b) {
3,248✔
399
   //return test_is_eq(what, a, b);
400
   if(a == b) {
3,248✔
401
      return test_success();
3,248✔
402
   }
403

404
   std::ostringstream err;
×
405
   err << who() << " " << what << " a=(" << a.get_affine_x() << "," << a.get_affine_y() << ")"
×
406
       << " b=(" << b.get_affine_x() << "," << b.get_affine_y();
×
407
   return test_failure(err.str());
×
408
}
×
409
#endif
410

411
bool Test::Result::test_bool_eq(std::string_view what, bool produced, bool expected) {
378,472✔
412
   if(produced == expected) {
378,472✔
413
      return test_success();
378,472✔
414
   } else {
415
      if(expected == true) {
×
416
         return test_failure(Botan::fmt("Assertion failure in {}, {} was unexpectedly false", who(), what));
×
417
      } else {
418
         return test_failure(Botan::fmt("Assertion failure in {}, {} was unexpectedly true", who(), what));
×
419
      }
420
   }
421
}
422

423
bool Test::Result::test_is_true(std::string_view what, bool produced) {
74,923✔
424
   return test_bool_eq(what, produced, true);
74,923✔
425
}
426

427
bool Test::Result::test_is_false(std::string_view what, bool produced) {
125,665✔
428
   return test_bool_eq(what, produced, false);
125,665✔
429
}
430

431
bool Test::Result::test_rc_ok(std::string_view func, int rc) {
2,810✔
432
   if(rc != 0) {
2,810✔
433
      std::ostringstream err;
1✔
434
      err << m_who << " " << func << " unexpectedly failed with error code " << rc;
1✔
435
      return test_failure(err.str());
1✔
436
   }
1✔
437

438
   return test_success();
2,809✔
439
}
440

441
bool Test::Result::test_rc_fail(std::string_view func, std::string_view why, int rc) {
26✔
442
   if(rc == 0) {
26✔
443
      std::ostringstream err;
1✔
444
      err << m_who << " call to " << func << " unexpectedly succeeded expecting failure because " << why;
1✔
445
      return test_failure(err.str());
1✔
446
   }
1✔
447

448
   return test_success();
25✔
449
}
450

451
bool Test::Result::test_rc_init(std::string_view func, int rc) {
117✔
452
   if(rc == 0) {
117✔
453
      return test_success();
117✔
454
   } else {
455
      std::ostringstream msg;
×
456
      msg << m_who;
×
457
      msg << " " << func;
×
458

459
      // -40 is BOTAN_FFI_ERROR_NOT_IMPLEMENTED
460
      if(rc == -40) {
×
461
         msg << " returned not implemented";
×
462
      } else {
463
         msg << " unexpectedly failed with error code " << rc;
×
464
      }
465

466
      if(rc == -40) {
×
467
         this->test_note(msg.str());
×
468
      } else {
469
         this->test_failure(msg.str());
×
470
      }
471
      return false;
×
472
   }
×
473
}
474

475
bool Test::Result::test_rc(std::string_view func, int expected, int rc) {
416✔
476
   if(expected != rc) {
416✔
477
      std::ostringstream err;
1✔
478
      err << m_who;
1✔
479
      err << " call to " << func << " unexpectedly returned " << rc;
1✔
480
      err << " but expecting " << expected;
1✔
481
      return test_failure(err.str());
1✔
482
   }
1✔
483

484
   return test_success();
415✔
485
}
486

487
void Test::initialize(std::string test_name, CodeLocation location) {
419✔
488
   m_test_name = std::move(test_name);
419✔
489
   m_registration_location = location;
419✔
490
}
419✔
491

492
Botan::RandomNumberGenerator& Test::rng() const {
461,532✔
493
   if(!m_test_rng) {
461,532✔
494
      m_test_rng = Test::new_rng(m_test_name);
145✔
495
   }
496

497
   return *m_test_rng;
461,532✔
498
}
499

500
std::optional<std::string> Test::supported_ec_group_name(std::vector<std::string> preferred_groups) {
151✔
501
#if defined(BOTAN_HAS_ECC_GROUP)
502
   if(preferred_groups.empty()) {
151✔
503
      preferred_groups = {
149✔
504
         "secp256r1",
505
         "brainpool256r1",
506
         "secp384r1",
507
         "brainpool384r1",
508
         "secp521r1",
509
         "brainpool512r1",
510
      };
1,192✔
511
   }
512

513
   for(const auto& group : preferred_groups) {
151✔
514
      if(Botan::EC_Group::supports_named_group(group)) {
151✔
515
         return group;
151✔
516
      }
517
   }
518
#else
519
   BOTAN_UNUSED(preferred_groups);
520
#endif
521

522
   return std::nullopt;
×
523
}
298✔
524

525
std::vector<uint8_t> Test::mutate_vec(const std::vector<uint8_t>& v,
96,742✔
526
                                      Botan::RandomNumberGenerator& rng,
527
                                      bool maybe_resize,
528
                                      size_t min_offset) {
529
   std::vector<uint8_t> r = v;
96,742✔
530

531
   if(maybe_resize && (r.empty() || rng.next_byte() < 32)) {
110,318✔
532
      // TODO: occasionally truncate, insert at random index
533
      const size_t add = 1 + (rng.next_byte() % 16);
2,112✔
534
      r.resize(r.size() + add);
2,112✔
535
      rng.randomize(&r[r.size() - add], add);
2,112✔
536
   }
537

538
   if(r.size() > min_offset) {
96,742✔
539
      const size_t offset = std::max<size_t>(min_offset, rng.next_byte() % r.size());
95,204✔
540
      const uint8_t perturb = rng.next_nonzero_byte();
95,204✔
541
      r[offset] ^= perturb;
95,204✔
542
   }
543

544
   return r;
96,742✔
545
}
×
546

547
std::vector<std::string> Test::possible_providers(const std::string& /*alg*/) {
×
548
   return Test::provider_filter({"base"});
×
549
}
550

551
//static
552
std::string Test::format_time(uint64_t nanoseconds) {
1,470✔
553
   std::ostringstream o;
1,470✔
554

555
   if(nanoseconds > 1000000000) {
1,470✔
556
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000000.0 << " sec";
153✔
557
   } else {
558
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000.0 << " msec";
1,317✔
559
   }
560

561
   return o.str();
2,940✔
562
}
1,470✔
563

564
// TODO: this should move to `StdoutReporter`
565
std::string Test::Result::result_string() const {
2,541✔
566
   const bool verbose = Test::options().verbose();
2,541✔
567

568
   if(tests_run() == 0 && !verbose) {
2,541✔
569
      return "";
20✔
570
   }
571

572
   std::ostringstream report;
2,521✔
573

574
   report << who() << " ran ";
2,521✔
575

576
   if(tests_run() == 0) {
2,521✔
577
      report << "ZERO";
×
578
   } else {
579
      report << tests_run();
2,521✔
580
   }
581
   report << " tests";
2,521✔
582

583
   if(m_ns_taken > 0) {
2,521✔
584
      report << " in " << format_time(m_ns_taken);
2,938✔
585
   }
586

587
   if(tests_failed() > 0) {
2,521✔
588
      report << " " << tests_failed() << " FAILED";
25✔
589
   } else {
590
      report << " all ok";
2,496✔
591
   }
592

593
   report << "\n";
2,521✔
594

595
   for(size_t i = 0; i != m_fail_log.size(); ++i) {
2,546✔
596
      report << "Failure " << (i + 1) << ": " << m_fail_log[i];
25✔
597
      if(m_where) {
25✔
598
         report << " (at " << m_where->path << ":" << m_where->line << ")";
×
599
      }
600
      report << "\n";
25✔
601
   }
602

603
   if(!m_fail_log.empty() || tests_run() == 0 || verbose) {
2,521✔
604
      for(size_t i = 0; i != m_log.size(); ++i) {
25✔
605
         report << "Note " << (i + 1) << ": " << m_log[i] << "\n";
×
606
      }
607
   }
608

609
   return report.str();
2,521✔
610
}
2,521✔
611

612
namespace {
613

614
class Test_Registry {
615
   public:
616
      static Test_Registry& instance() {
1,279✔
617
         static Test_Registry registry;
1,280✔
618
         return registry;
1,279✔
619
      }
620

621
      void register_test(const std::string& category,
419✔
622
                         const std::string& name,
623
                         bool smoke_test,
624
                         bool needs_serialization,
625
                         std::function<std::unique_ptr<Test>()> maker_fn) {
626
         if(m_tests.contains(name)) {
419✔
627
            throw Test_Error("Duplicate registration of test '" + name + "'");
×
628
         }
629

630
         if(m_tests.contains(category)) {
419✔
631
            throw Test_Error("'" + category + "' cannot be used as category, test exists");
×
632
         }
633

634
         if(m_categories.contains(name)) {
419✔
635
            throw Test_Error("'" + name + "' cannot be used as test name, category exists");
×
636
         }
637

638
         if(smoke_test) {
419✔
639
            m_smoke_tests.push_back(name);
10✔
640
         }
641

642
         if(needs_serialization) {
419✔
643
            m_mutexed_tests.push_back(name);
21✔
644
         }
645

646
         m_tests.emplace(name, std::move(maker_fn));
419✔
647
         m_categories.emplace(category, name);
419✔
648
      }
419✔
649

650
      std::unique_ptr<Test> get_test(const std::string& test_name) const {
419✔
651
         auto i = m_tests.find(test_name);
419✔
652
         if(i != m_tests.end()) {
419✔
653
            return i->second();
419✔
654
         }
655
         return nullptr;
×
656
      }
657

658
      std::vector<std::string> registered_tests() const {
×
659
         std::vector<std::string> s;
×
660
         s.reserve(m_tests.size());
×
661
         for(auto&& i : m_tests) {
×
662
            s.push_back(i.first);
×
663
         }
664
         return s;
×
665
      }
×
666

667
      std::vector<std::string> registered_test_categories() const {
×
668
         std::set<std::string> s;
×
669
         for(auto&& i : m_categories) {
×
670
            s.insert(i.first);
×
671
         }
672
         return std::vector<std::string>(s.begin(), s.end());
×
673
      }
×
674

675
      std::vector<std::string> filter_registered_tests(const std::vector<std::string>& requested,
1✔
676
                                                       const std::vector<std::string>& to_be_skipped) {
677
         std::vector<std::string> result;
1✔
678

679
         std::set<std::string> to_be_skipped_set(to_be_skipped.begin(), to_be_skipped.end());
1✔
680
         // TODO: this is O(n^2), but we have a relatively small number of tests.
681
         auto insert_if_not_exists_and_not_skipped = [&](const std::string& test_name) {
420✔
682
            if(!Botan::value_exists(result, test_name) && !to_be_skipped_set.contains(test_name)) {
838✔
683
               result.push_back(test_name);
409✔
684
            }
685
         };
420✔
686

687
         if(requested.empty()) {
1✔
688
            /*
689
            If nothing was requested on the command line, run everything. First
690
            run the "essentials" to smoke test, then everything else in
691
            alphabetical order.
692
            */
693
            result = m_smoke_tests;
1✔
694
            for(const auto& [test_name, _] : m_tests) {
420✔
695
               insert_if_not_exists_and_not_skipped(test_name);
419✔
696
            }
697
         } else {
698
            for(const auto& r : requested) {
×
699
               if(m_tests.contains(r)) {
×
700
                  insert_if_not_exists_and_not_skipped(r);
×
701
               } else if(auto elems = m_categories.equal_range(r); elems.first != m_categories.end()) {
×
702
                  for(; elems.first != elems.second; ++elems.first) {
×
703
                     insert_if_not_exists_and_not_skipped(elems.first->second);
×
704
                  }
705
               } else {
706
                  throw Test_Error("Unknown test suite or category: " + r);
×
707
               }
708
            }
709
         }
710

711
         return result;
1✔
712
      }
1✔
713

714
      bool needs_serialization(const std::string& test_name) const {
440✔
715
         return Botan::value_exists(m_mutexed_tests, test_name);
21✔
716
      }
717

718
   private:
719
      Test_Registry() = default;
1✔
720

721
   private:
722
      std::map<std::string, std::function<std::unique_ptr<Test>()>> m_tests;
723
      std::multimap<std::string, std::string> m_categories;
724
      std::vector<std::string> m_smoke_tests;
725
      std::vector<std::string> m_mutexed_tests;
726
};
727

728
}  // namespace
729

730
// static Test:: functions
731

732
//static
733
void Test::register_test(const std::string& category,
419✔
734
                         const std::string& name,
735
                         bool smoke_test,
736
                         bool needs_serialization,
737
                         std::function<std::unique_ptr<Test>()> maker_fn) {
738
   Test_Registry::instance().register_test(category, name, smoke_test, needs_serialization, std::move(maker_fn));
838✔
739
}
419✔
740

741
//static
742
uint64_t Test::timestamp() {
180,013✔
743
   auto now = std::chrono::system_clock::now().time_since_epoch();
180,013✔
744
   return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
2,383✔
745
}
746

747
//static
748
std::vector<Test::Result> Test::flatten_result_lists(std::vector<std::vector<Test::Result>> result_lists) {
4✔
749
   std::vector<Test::Result> results;
4✔
750
   for(auto& result_list : result_lists) {
26✔
751
      for(auto& result : result_list) {
71✔
752
         results.emplace_back(std::move(result));
49✔
753
      }
754
   }
755
   return results;
4✔
756
}
×
757

758
//static
759
std::vector<std::string> Test::registered_tests() {
×
760
   return Test_Registry::instance().registered_tests();
×
761
}
762

763
//static
764
std::vector<std::string> Test::registered_test_categories() {
×
765
   return Test_Registry::instance().registered_test_categories();
×
766
}
767

768
//static
769
std::unique_ptr<Test> Test::get_test(const std::string& test_name) {
419✔
770
   return Test_Registry::instance().get_test(test_name);
419✔
771
}
772

773
//static
774
bool Test::test_needs_serialization(const std::string& test_name) {
419✔
775
   return Test_Registry::instance().needs_serialization(test_name);
419✔
776
}
777

778
//static
779
std::vector<std::string> Test::filter_registered_tests(const std::vector<std::string>& requested,
1✔
780
                                                       const std::vector<std::string>& to_be_skipped) {
781
   return Test_Registry::instance().filter_registered_tests(requested, to_be_skipped);
1✔
782
}
783

784
//static
785
std::string Test::temp_file_name(const std::string& basename) {
21✔
786
   // TODO add a --tmp-dir option to the tests to specify where these files go
787

788
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
789

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

793
   const int fd = ::mkstemp(mkstemp_basename.data());
21✔
794

795
   // error
796
   if(fd < 0) {
21✔
797
      return "";
×
798
   }
799

800
   ::close(fd);
21✔
801

802
   return mkstemp_basename;
21✔
803
#else
804
   // For now just create the temp in the current working directory
805
   return basename;
806
#endif
807
}
21✔
808

809
bool Test::copy_file(const std::string& from, const std::string& to) {
1✔
810
#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(__cpp_lib_filesystem)
811
   std::error_code ec;  // don't throw, just return false on error
1✔
812
   return std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing, ec);
1✔
813
#else
814
   // TODO: implement fallbacks to POSIX or WIN32
815
   // ... but then again: it's 2023 and we're using C++20 :o)
816
   BOTAN_UNUSED(from, to);
817
   throw Botan::No_Filesystem_Access();
818
#endif
819
}
820

821
std::string Test::read_data_file(const std::string& path) {
38✔
822
   const std::string fsname = Test::data_file(path);
38✔
823
   std::ifstream file(fsname.c_str());
38✔
824
   if(!file.good()) {
38✔
825
      throw Test_Error("Error reading from " + fsname);
×
826
   }
827

828
   return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
76✔
829
}
38✔
830

831
std::vector<uint8_t> Test::read_binary_data_file(const std::string& path) {
34✔
832
   const std::string fsname = Test::data_file(path);
34✔
833
   std::ifstream file(fsname.c_str(), std::ios::binary);
34✔
834
   if(!file.good()) {
34✔
835
      throw Test_Error("Error reading from " + fsname);
×
836
   }
837

838
   std::vector<uint8_t> contents;
34✔
839

840
   while(file.good()) {
74✔
841
      std::vector<uint8_t> buf(4096);
40✔
842
      file.read(reinterpret_cast<char*>(buf.data()), buf.size());
40✔
843
      const size_t got = static_cast<size_t>(file.gcount());
40✔
844

845
      if(got == 0 && file.eof()) {
40✔
846
         break;
847
      }
848

849
      contents.insert(contents.end(), buf.data(), buf.data() + got);
40✔
850
   }
40✔
851

852
   return contents;
68✔
853
}
34✔
854

855
// static member variables of Test
856

857
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
858
Test_Options Test::m_opts;
859
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
860
std::string Test::m_test_rng_seed;
861

862
//static
863
void Test::set_test_options(const Test_Options& opts) {
1✔
864
   m_opts = opts;
1✔
865
}
1✔
866

867
namespace {
868

869
/*
870
* This is a fast, simple, deterministic PRNG that's used for running
871
* the tests. It is not intended to be cryptographically secure.
872
*/
873
class Testsuite_RNG final : public Botan::RandomNumberGenerator {
8✔
874
   public:
875
      std::string name() const override { return "Testsuite_RNG"; }
×
876

877
      void clear() override { m_x = 0; }
×
878

879
      bool accepts_input() const override { return true; }
×
880

881
      bool is_seeded() const override { return true; }
276,562✔
882

883
      void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> input) override {
10,216,179✔
884
         for(const auto byte : input) {
10,216,179✔
885
            mix(byte);
×
886
         }
887

888
         for(auto& byte : output) {
73,032,610✔
889
            byte = mix();
62,816,431✔
890
         }
891
      }
10,216,179✔
892

893
      Testsuite_RNG(std::string_view seed, std::string_view test_name) : m_x(0) {
276✔
894
         for(const char c : seed) {
8,280✔
895
            this->mix(static_cast<uint8_t>(c));
8,004✔
896
         }
897
         for(const char c : test_name) {
5,578✔
898
            this->mix(static_cast<uint8_t>(c));
5,302✔
899
         }
900
      }
276✔
901

902
   private:
903
      uint8_t mix(uint8_t input = 0) {
62,829,737✔
904
         m_x ^= input;
62,829,737✔
905
         m_x *= 0xF2E16957;
62,829,737✔
906
         m_x += 0xE50B590F;
62,829,737✔
907
         return static_cast<uint8_t>(m_x >> 27);
62,829,737✔
908
      }
909

910
      uint64_t m_x;
911
};
912

913
}  // namespace
914

915
//static
916
void Test::set_test_rng_seed(std::span<const uint8_t> seed, size_t epoch) {
1✔
917
   m_test_rng_seed = Botan::fmt("seed={} epoch={}", Botan::hex_encode(seed), epoch);
1✔
918
}
1✔
919

920
//static
921
std::unique_ptr<Botan::RandomNumberGenerator> Test::new_rng(std::string_view test_name) {
268✔
922
   return std::make_unique<Testsuite_RNG>(m_test_rng_seed, test_name);
268✔
923
}
924

925
//static
926
std::shared_ptr<Botan::RandomNumberGenerator> Test::new_shared_rng(std::string_view test_name) {
8✔
927
   return std::make_shared<Testsuite_RNG>(m_test_rng_seed, test_name);
8✔
928
}
929

930
//static
931
std::string Test::data_file(const std::string& file) {
773✔
932
   return options().data_dir() + "/" + file;
1,546✔
933
}
934

935
//static
936
std::string Test::data_dir(const std::string& subdir) {
1✔
937
   return options().data_dir() + "/" + subdir;
2✔
938
}
939

940
//static
941
std::vector<std::string> Test::files_in_data_dir(const std::string& subdir) {
393✔
942
   auto fs = Botan::get_files_recursive(options().data_dir() + "/" + subdir);
1,179✔
943
   if(fs.empty()) {
393✔
944
      throw Test_Error("Test::files_in_data_dir encountered empty subdir " + subdir);
×
945
   }
946
   return fs;
393✔
947
}
×
948

949
//static
950
std::string Test::data_file_as_temporary_copy(const std::string& what) {
1✔
951
   auto tmp_basename = what;
1✔
952
   std::replace(tmp_basename.begin(), tmp_basename.end(), '/', '_');
1✔
953
   auto temp_file = temp_file_name("tmp-" + tmp_basename);
1✔
954
   if(temp_file.empty()) {
1✔
955
      return "";
×
956
   }
957
   if(!Test::copy_file(data_file(what), temp_file)) {
1✔
958
      return "";
×
959
   }
960
   return temp_file;
1✔
961
}
1✔
962

963
//static
964
std::vector<std::string> Test::provider_filter(const std::vector<std::string>& providers) {
49,022✔
965
   if(m_opts.provider().empty()) {
49,022✔
966
      return providers;
49,022✔
967
   }
968
   for(auto&& provider : providers) {
×
969
      if(provider == m_opts.provider()) {
×
970
         return std::vector<std::string>{provider};
×
971
      }
972
   }
973
   return std::vector<std::string>{};
×
974
}
×
975

976
std::string Test::random_password(Botan::RandomNumberGenerator& rng) {
222✔
977
   const size_t len = 1 + rng.next_byte() % 32;
222✔
978
   return Botan::hex_encode(rng.random_vec(len));
444✔
979
}
980

981
size_t Test::random_index(Botan::RandomNumberGenerator& rng, size_t max) {
8,062✔
982
   return Botan::load_be(rng.random_array<8>()) % max;
8,062✔
983
}
984

985
void VarMap::clear() {
34,281✔
986
   m_vars.clear();
×
987
}
×
988

989
namespace {
990

991
bool varmap_pair_lt(const std::pair<std::string, std::string>& kv, const std::string& k) {
1,713,618✔
992
   return kv.first < k;
1,713,618✔
993
}
994

995
}  // namespace
996

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

1000
   if(i != m_vars.end() && i->first == key) {
156,235✔
1001
      i->second = value;
44,405✔
1002
   } else {
1003
      m_vars.emplace(i, key, value);
111,830✔
1004
   }
1005
}
156,235✔
1006

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

1010
   if(i != m_vars.end() && i->first == key) {
568,446✔
1011
      return i->second;
498,591✔
1012
   } else {
1013
      return {};
69,855✔
1014
   }
1015
}
1016

1017
bool VarMap::has_key(const std::string& key) const {
213,574✔
1018
   return get_var(key).has_value();
213,574✔
1019
}
1020

1021
std::string VarMap::get_req_str(const std::string& key) const {
259,629✔
1022
   auto var = get_var(key);
259,629✔
1023
   if(var) {
259,629✔
1024
      return *var;
519,258✔
1025
   } else {
1026
      throw Test_Error("Test missing variable " + key);
×
1027
   }
1028
}
259,629✔
1029

1030
std::vector<std::vector<uint8_t>> VarMap::get_req_bin_list(const std::string& key) const {
12✔
1031
   const auto var = get_req_str(key);
12✔
1032

1033
   std::vector<std::vector<uint8_t>> bin_list;
12✔
1034

1035
   for(auto&& part : Botan::split_on(var, ',')) {
62✔
1036
      try {
50✔
1037
         bin_list.push_back(Botan::hex_decode(part));
100✔
1038
      } catch(std::exception& e) {
×
1039
         std::ostringstream oss;
×
1040
         oss << "Bad input '" << part << "'"
×
1041
             << " in binary list key " << key << " - " << e.what();
×
1042
         throw Test_Error(oss.str());
×
1043
      }
×
1044
   }
12✔
1045

1046
   return bin_list;
12✔
1047
}
12✔
1048

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

1052
   try {
163,711✔
1053
      if(var.starts_with("0x")) {
163,711✔
1054
         if(var.size() % 2 == 0) {
×
1055
            return Botan::hex_decode(var.substr(2));
×
1056
         } else {
1057
            std::string z = var;
×
1058
            std::swap(z[0], z[1]);  // swap 0x to x0 then remove x
×
1059
            return Botan::hex_decode(z.substr(1));
×
1060
         }
×
1061
      } else {
1062
         return Botan::hex_decode(var);
163,711✔
1063
      }
1064
   } catch(std::exception& e) {
×
1065
      std::ostringstream oss;
×
1066
      oss << "Bad input '" << var << "'"
×
1067
          << " for key " << key << " - " << e.what();
×
1068
      throw Test_Error(oss.str());
×
1069
   }
×
1070
}
163,711✔
1071

1072
std::string VarMap::get_opt_str(const std::string& key, const std::string& def_value) const {
14,378✔
1073
   if(auto v = get_var(key)) {
14,378✔
1074
      return *v;
57✔
1075
   } else {
1076
      return def_value;
28,699✔
1077
   }
14,378✔
1078
}
1079

1080
bool VarMap::get_req_bool(const std::string& key) const {
39✔
1081
   const auto var = get_req_str(key);
39✔
1082

1083
   if(var == "true") {
39✔
1084
      return true;
1085
   } else if(var == "false") {
23✔
1086
      return false;
1087
   } else {
1088
      throw Test_Error("Invalid boolean for key '" + key + "' value '" + var + "'");
×
1089
   }
1090
}
39✔
1091

1092
size_t VarMap::get_req_sz(const std::string& key) const {
4,547✔
1093
   return Botan::to_u32bit(get_req_str(key));
4,547✔
1094
}
1095

1096
uint8_t VarMap::get_req_u8(const std::string& key) const {
17✔
1097
   const size_t s = this->get_req_sz(key);
17✔
1098
   if(s > 256) {
17✔
1099
      throw Test_Error("Invalid " + key + " expected uint8_t got " + std::to_string(s));
×
1100
   }
1101
   return static_cast<uint8_t>(s);
17✔
1102
}
1103

1104
uint32_t VarMap::get_req_u32(const std::string& key) const {
14✔
1105
   return static_cast<uint32_t>(get_req_sz(key));
14✔
1106
}
1107

1108
uint64_t VarMap::get_req_u64(const std::string& key) const {
17✔
1109
   const auto var = get_req_str(key);
17✔
1110
   try {
17✔
1111
      return std::stoull(var);
17✔
1112
   } catch(std::exception&) {
×
1113
      throw Test_Error("Invalid u64 value '" + var + "'");
×
1114
   }
×
1115
}
17✔
1116

1117
size_t VarMap::get_opt_sz(const std::string& key, const size_t def_value) const {
31,379✔
1118
   if(auto v = get_var(key)) {
31,379✔
1119
      return Botan::to_u32bit(*v);
12,244✔
1120
   } else {
1121
      return def_value;
1122
   }
31,379✔
1123
}
1124

1125
uint64_t VarMap::get_opt_u64(const std::string& key, const uint64_t def_value) const {
3,538✔
1126
   if(auto v = get_var(key)) {
3,538✔
1127
      try {
641✔
1128
         return std::stoull(*v);
3,538✔
1129
      } catch(std::exception&) {
×
1130
         throw Test_Error("Invalid u64 value '" + *v + "'");
×
1131
      }
×
1132
   } else {
1133
      return def_value;
1134
   }
3,538✔
1135
}
×
1136

1137
std::vector<uint8_t> VarMap::get_opt_bin(const std::string& key) const {
45,868✔
1138
   if(auto v = get_var(key)) {
45,868✔
1139
      try {
16,134✔
1140
         return Botan::hex_decode(*v);
16,134✔
1141
      } catch(std::exception&) {
×
1142
         throw Test_Error("Test invalid hex input '" + *v + "'" + +" for key " + key);
×
1143
      }
×
1144
   } else {
1145
      return {};
29,734✔
1146
   };
45,868✔
1147
}
×
1148

1149
#if defined(BOTAN_HAS_BIGINT)
1150
Botan::BigInt VarMap::get_req_bn(const std::string& key) const {
38,502✔
1151
   const auto var = get_req_str(key);
38,502✔
1152

1153
   try {
38,502✔
1154
      return Botan::BigInt(var);
38,502✔
1155
   } catch(std::exception&) {
×
1156
      throw Test_Error("Test invalid bigint input '" + var + "' for key " + key);
×
1157
   }
×
1158
}
38,502✔
1159

1160
Botan::BigInt VarMap::get_opt_bn(const std::string& key, const Botan::BigInt& def_value) const {
80✔
1161
   if(auto v = get_var(key)) {
80✔
1162
      try {
56✔
1163
         return Botan::BigInt(*v);
56✔
1164
      } catch(std::exception&) {
×
1165
         throw Test_Error("Test invalid bigint input '" + *v + "' for key " + key);
×
1166
      }
×
1167
   } else {
1168
      return def_value;
24✔
1169
   }
80✔
1170
}
×
1171
#endif
1172

1173
class Text_Based_Test::Text_Based_Test_Data {
1174
   public:
1175
      Text_Based_Test_Data(const std::string& data_src,
181✔
1176
                           const std::string& required_keys_str,
1177
                           const std::string& optional_keys_str) :
181✔
1178
            m_data_src(data_src) {
362✔
1179
         if(required_keys_str.empty()) {
181✔
1180
            throw Test_Error("Invalid test spec");
×
1181
         }
1182

1183
         m_required_keys = Botan::split_on(required_keys_str, ',');
181✔
1184
         std::vector<std::string> optional_keys = Botan::split_on(optional_keys_str, ',');
181✔
1185

1186
         m_all_keys.insert(m_required_keys.begin(), m_required_keys.end());
181✔
1187
         m_all_keys.insert(optional_keys.begin(), optional_keys.end());
181✔
1188
         m_output_key = m_required_keys.at(m_required_keys.size() - 1);
181✔
1189
      }
181✔
1190

1191
      std::string get_next_line();
1192

1193
      const std::string& current_source_name() const { return m_cur_src_name; }
×
1194

1195
      bool known_key(const std::string& key) const;
1196

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

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

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

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

1205
      const std::string& initial_data_src_name() const { return m_data_src; }
181✔
1206

1207
   private:
1208
      std::string m_data_src;
1209
      std::vector<std::string> m_required_keys;
1210
      std::unordered_set<std::string> m_all_keys;
1211
      std::string m_output_key;
1212

1213
      bool m_first = true;
1214
      std::unique_ptr<std::istream> m_cur;
1215
      std::string m_cur_src_name;
1216
      std::deque<std::string> m_srcs;
1217
      std::vector<std::string> m_cpu_flags;
1218
};
1219

1220
Text_Based_Test::Text_Based_Test(const std::string& data_src,
181✔
1221
                                 const std::string& required_keys,
1222
                                 const std::string& optional_keys) :
181✔
1223
      m_data(std::make_unique<Text_Based_Test_Data>(data_src, required_keys, optional_keys)) {}
181✔
1224

1225
Text_Based_Test::~Text_Based_Test() = default;
181✔
1226

1227
std::string Text_Based_Test::Text_Based_Test_Data::get_next_line() {
157,242✔
1228
   while(true) {
157,500✔
1229
      if(m_cur == nullptr || m_cur->good() == false) {
157,500✔
1230
         if(m_srcs.empty()) {
450✔
1231
            if(m_first) {
362✔
1232
               if(m_data_src.ends_with(".vec")) {
181✔
1233
                  m_srcs.push_back(Test::data_file(m_data_src));
338✔
1234
               } else {
1235
                  const auto fs = Test::files_in_data_dir(m_data_src);
12✔
1236
                  m_srcs.assign(fs.begin(), fs.end());
12✔
1237
                  if(m_srcs.empty()) {
12✔
1238
                     throw Test_Error("Error reading test data dir " + m_data_src);
×
1239
                  }
1240
               }
12✔
1241

1242
               m_first = false;
181✔
1243
            } else {
1244
               return "";  // done
181✔
1245
            }
1246
         }
1247

1248
         m_cur = std::make_unique<std::ifstream>(m_srcs[0]);
357✔
1249
         m_cur_src_name = m_srcs[0];
269✔
1250

1251
#if defined(BOTAN_HAS_CPUID)
1252
         // Reinit cpuid on new file if needed
1253
         if(m_cpu_flags.empty() == false) {
269✔
1254
            m_cpu_flags.clear();
19✔
1255
            Botan::CPUID::initialize();
19✔
1256
         }
1257
#endif
1258

1259
         if(!m_cur->good()) {
269✔
1260
            throw Test_Error("Could not open input file '" + m_cur_src_name);
×
1261
         }
1262

1263
         m_srcs.pop_front();
269✔
1264
      }
1265

1266
      while(m_cur->good()) {
227,897✔
1267
         std::string line;
227,639✔
1268
         std::getline(*m_cur, line);
227,639✔
1269

1270
         if(line.empty()) {
227,639✔
1271
            continue;
55,310✔
1272
         }
1273

1274
         if(line[0] == '#') {
172,329✔
1275
            if(line.starts_with("#test ")) {
15,289✔
1276
               return line;
21✔
1277
            } else {
1278
               continue;
15,268✔
1279
            }
1280
         }
1281

1282
         return line;
157,040✔
1283
      }
227,639✔
1284
   }
1285
}
1286

1287
bool Text_Based_Test::Text_Based_Test_Data::known_key(const std::string& key) const {
156,235✔
1288
   return m_all_keys.contains(key);
156,235✔
1289
}
1290

1291
namespace {
1292

1293
// strips leading and trailing but not internal whitespace
1294
std::string strip_ws(const std::string& in) {
312,470✔
1295
   const char* whitespace = " ";
312,470✔
1296

1297
   const auto first_c = in.find_first_not_of(whitespace);
312,470✔
1298
   if(first_c == std::string::npos) {
312,470✔
1299
      return "";
1,033✔
1300
   }
1301

1302
   const auto last_c = in.find_last_not_of(whitespace);
311,437✔
1303

1304
   return in.substr(first_c, last_c - first_c + 1);
311,437✔
1305
}
1306

1307
std::vector<std::string> parse_cpuid_bits(const std::vector<std::string>& tok) {
21✔
1308
   std::vector<std::string> bits;
21✔
1309

1310
#if defined(BOTAN_HAS_CPUID)
1311
   for(size_t i = 1; i < tok.size(); ++i) {
106✔
1312
      if(auto bit = Botan::CPUID::bit_from_string(tok[i])) {
85✔
1313
         bits.push_back(bit->to_string());
98✔
1314
      }
1315
   }
1316
#else
1317
   BOTAN_UNUSED(tok);
1318
#endif
1319

1320
   return bits;
21✔
1321
}
×
1322

1323
}  // namespace
1324

1325
bool Text_Based_Test::skip_this_test(const std::string& /*header*/, const VarMap& /*vars*/) {
32,609✔
1326
   return false;
32,609✔
1327
}
1328

1329
std::vector<Test::Result> Text_Based_Test::run() {
181✔
1330
   std::vector<Test::Result> results;
181✔
1331

1332
   std::string header;
181✔
1333
   std::string header_or_name = m_data->initial_data_src_name();
181✔
1334
   VarMap vars;
181✔
1335
   size_t test_cnt = 0;
181✔
1336

1337
   while(true) {
157,242✔
1338
      const std::string line = m_data->get_next_line();
157,242✔
1339
      if(line.empty()) {
157,242✔
1340
         // EOF
1341
         break;
1342
      }
1343

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

1347
         if(pragma_tokens.empty()) {
21✔
1348
            throw Test_Error("Empty pragma found in " + m_data->current_source_name());
×
1349
         }
1350

1351
         if(pragma_tokens[0] != "cpuid") {
21✔
1352
            throw Test_Error("Unknown test pragma '" + line + "' in " + m_data->current_source_name());
×
1353
         }
1354

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

1359
         m_data->set_cpu_flags(parse_cpuid_bits(pragma_tokens));
42✔
1360

1361
         continue;
21✔
1362
      } else if(line[0] == '#') {
157,061✔
1363
         throw Test_Error("Unknown test pragma '" + line + "' in " + m_data->current_source_name());
×
1364
      }
1365

1366
      if(line[0] == '[' && line[line.size() - 1] == ']') {
157,040✔
1367
         header = line.substr(1, line.size() - 2);
805✔
1368
         header_or_name = header;
805✔
1369
         test_cnt = 0;
805✔
1370
         vars.clear();
805✔
1371
         continue;
805✔
1372
      }
1373

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

1376
      auto equal_i = line.find_first_of('=');
156,235✔
1377

1378
      if(equal_i == std::string::npos) {
156,235✔
1379
         results.push_back(Test::Result::Failure(header_or_name, "invalid input '" + line + "'"));
×
1380
         continue;
×
1381
      }
1382

1383
      const std::string key = strip_ws(std::string(line.begin(), line.begin() + equal_i - 1));
312,470✔
1384
      const std::string val = strip_ws(std::string(line.begin() + equal_i + 1, line.end()));
312,470✔
1385

1386
      if(!m_data->known_key(key)) {
156,235✔
1387
         auto r = Test::Result::Failure(header_or_name, Botan::fmt("{} failed unknown key {}", test_id, key));
×
1388
         results.push_back(r);
×
1389
      }
×
1390

1391
      vars.add(key, val);
156,235✔
1392

1393
      if(key == m_data->output_key()) {
156,235✔
1394
         try {
48,388✔
1395
            for(const auto& req_key : m_data->required_keys()) {
245,554✔
1396
               if(!vars.has_key(req_key)) {
197,166✔
1397
                  auto r =
×
1398
                     Test::Result::Failure(header_or_name, Botan::fmt("{} missing required key {}", test_id, req_key));
×
1399
                  results.push_back(r);
×
1400
               }
×
1401
            }
1402

1403
            if(skip_this_test(header, vars)) {
48,388✔
1404
               continue;
139✔
1405
            }
1406

1407
            ++test_cnt;
48,249✔
1408

1409
            const uint64_t start = Test::timestamp();
48,249✔
1410

1411
            Test::Result result = run_one_test(header, vars);
48,249✔
1412
#if defined(BOTAN_HAS_CPUID)
1413
            if(!m_data->cpu_flags().empty()) {
48,249✔
1414
               for(const auto& cpuid_str : m_data->cpu_flags()) {
30,621✔
1415
                  if(const auto bit = Botan::CPUID::Feature::from_string(cpuid_str)) {
21,677✔
1416
                     if(Botan::CPUID::has(*bit)) {
21,677✔
1417
                        Botan::CPUID::clear_cpuid_bit(*bit);
16,577✔
1418
                        // now re-run the test
1419
                        result.merge(run_one_test(header, vars));
16,577✔
1420
                     }
1421
                  }
1422
               }
1423
               Botan::CPUID::initialize();
8,944✔
1424
            }
1425
#endif
1426
            result.set_ns_consumed(Test::timestamp() - start);
48,249✔
1427

1428
            if(result.tests_failed() > 0) {
48,249✔
1429
               std::ostringstream oss;
×
1430
               oss << "Test # " << test_cnt << " ";
×
1431
               if(!header.empty()) {
×
1432
                  oss << header << " ";
×
1433
               }
1434
               oss << "failed ";
×
1435

1436
               for(const auto& k : m_data->required_keys()) {
×
1437
                  oss << k << "=" << vars.get_req_str(k) << " ";
×
1438
               }
1439

1440
               result.test_note(oss.str());
×
1441
            }
×
1442
            results.push_back(result);
48,249✔
1443
         } catch(std::exception& e) {
48,249✔
1444
            std::ostringstream oss;
×
1445
            oss << "Test # " << test_cnt << " ";
×
1446
            if(!header.empty()) {
×
1447
               oss << header << " ";
×
1448
            }
1449

1450
            for(const auto& k : m_data->required_keys()) {
×
1451
               oss << k << "=" << vars.get_req_str(k) << " ";
×
1452
            }
1453

1454
            oss << "failed with exception '" << e.what() << "'";
×
1455

1456
            results.push_back(Test::Result::Failure(header_or_name, oss.str()));
×
1457
         }
×
1458

1459
         if(clear_between_callbacks()) {
48,249✔
1460
            vars.clear();
189,572✔
1461
         }
1462
      }
1463
   }
157,339✔
1464

1465
   if(results.empty()) {
181✔
1466
      return results;
1467
   }
1468

1469
   try {
181✔
1470
      std::vector<Test::Result> final_tests = run_final_tests();
181✔
1471
      results.insert(results.end(), final_tests.begin(), final_tests.end());
181✔
1472
   } catch(std::exception& e) {
181✔
1473
      results.push_back(Test::Result::Failure(header_or_name, "run_final_tests exception " + std::string(e.what())));
×
1474
   }
×
1475

1476
   return results;
1477
}
181✔
1478

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