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

randombit / botan / 28002582618

22 Jun 2026 07:30PM UTC coverage: 89.368% (+0.007%) from 89.361%
28002582618

push

github

web-flow
Merge pull request #5685 from randombit/jack/support-1950-timepoint

Extend calendar_point/ASN1_Time to support timepoint conversions for 1950-1970

111874 of 125184 relevant lines covered (89.37%)

11150060.87 hits per line

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

80.22
/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;
460✔
59

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

62
Test::Result::Result(std::string_view who) : m_who(who), m_timestamp(Test::timestamp()) {}
83,685✔
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) {
128,747✔
71
   if(who() != other.who()) {
128,747✔
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;
128,674✔
81
   }
82

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

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

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

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

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

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

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

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

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

131
Test::Result::ThrowExpectations::~ThrowExpectations() {
119,446✔
132
   BOTAN_ASSERT_NOMSG(m_consumed);
119,446✔
133
}
232,482✔
134

135
void Test::Result::ThrowExpectations::assert_that_success_is_not_expected() const {
113,036✔
136
   BOTAN_ASSERT_NOMSG(!m_expect_success);
112,817✔
137
}
112,817✔
138

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

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

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

154
   try {
119,446✔
155
      m_fn();
119,446✔
156
      if(!m_expect_success) {
1,655✔
157
         return result.test_failure(Botan::fmt("{} failed to throw expected exception", test_name));
2✔
158
      }
159
   } catch(const std::exception& ex) {
117,791✔
160
      if(m_expect_success) {
117,789✔
161
         return result.test_failure(Botan::fmt("{} threw unexpected exception: {}", test_name, ex.what()));
1✔
162
      }
163
      if(m_expected_exception_check_fn && !m_expected_exception_check_fn(std::current_exception())) {
343,422✔
164
         return result.test_failure(Botan::fmt("{} threw unexpected exception: {}", test_name, ex.what()));
2✔
165
      }
166
      if(m_expected_message.has_value()) {
117,786✔
167
         const std::string ex_msg = std::string(ex.what());
216✔
168

169
         // TODO(C++23) std::string::contains
170
         if(ex_msg.find(m_expected_message.value()) == std::string::npos) {
216✔
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_msg));
175
         }
176
      }
216✔
177
   } catch(...) {
117,791✔
178
      if(m_expect_success || m_expected_exception_check_fn || m_expected_message.has_value()) {
2✔
179
         return result.test_failure(Botan::fmt("{} threw unexpected unknown exception", test_name));
1✔
180
      }
181
   }
2✔
182

183
   return result.test_success();
119,439✔
184
}
185

186
bool Test::Result::test_throws(std::string_view what, std::function<void()> fn) {
4,771✔
187
   return ThrowExpectations(std::move(fn)).check(what, *this);
14,313✔
188
}
189

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

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

198
bool Test::Result::test_success(std::string_view note) {
3,933,019✔
199
   if(Test::options().log_success()) {
3,933,001✔
200
      test_note(note);
×
201
   }
202
   ++m_tests_passed;
3,933,019✔
203
   return true;
119,439✔
204
}
205

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

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

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

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

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

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

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

235
namespace {
236

237
bool same_contents(std::span<const uint8_t> x, std::span<const uint8_t> y) {
349,857✔
238
   if(x.size() != y.size()) {
1,451✔
239
      return false;
240
   }
241
   if(x.empty()) {
349,856✔
242
      return true;
243
   }
244

245
   return std::memcmp(x.data(), y.data(), x.size()) == 0;
348,062✔
246
}
247

248
}  // namespace
249

250
bool Test::Result::test_bin_ne(std::string_view what,
3,365✔
251
                               std::span<const uint8_t> produced,
252
                               std::span<const uint8_t> expected) {
253
   if(produced.size() == expected.size() && same_contents(produced, expected)) {
4,816✔
254
      return test_failure(Botan::fmt("{} {} produced matching bytes", who(), what));
1✔
255
   }
256
   return test_success();
3,364✔
257
}
258

259
bool Test::Result::test_bin_eq(std::string_view what,
617✔
260
                               std::span<const uint8_t> produced,
261
                               std::string_view expected_hex) {
262
   const std::vector<uint8_t> expected = Botan::hex_decode(expected_hex);
617✔
263
   return test_bin_eq(what, produced, expected);
617✔
264
}
617✔
265

266
bool Test::Result::test_bin_eq(std::string_view what,
348,406✔
267
                               std::span<const uint8_t> produced,
268
                               std::span<const uint8_t> expected) {
269
   if(same_contents(produced, expected)) {
695,017✔
270
      return test_success();
348,405✔
271
   }
272

273
   std::ostringstream err;
1✔
274

275
   err << who();
1✔
276

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

279
   if(produced.size() != expected.size()) {
1✔
280
      err << " produced " << produced.size() << " bytes expected " << expected.size();
1✔
281
   }
282

283
   std::vector<uint8_t> xor_diff(std::min(produced.size(), expected.size()));
2✔
284
   size_t bytes_different = 0;
1✔
285

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

293
   err << "\nProduced: " << Botan::hex_encode(produced) << "\nExpected: " << Botan::hex_encode(expected);
1✔
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) {
39,448✔
303
   if(produced.empty()) {
39,448✔
304
      return test_failure(what, "was empty");
1✔
305
   } else {
306
      return test_success();
39,447✔
307
   }
308
}
309

310
bool Test::Result::test_str_eq(std::string_view what, std::string_view produced, std::string_view expected) {
78,479✔
311
   if(produced == expected) {
78,479✔
312
      return test_success();
78,479✔
313
   } else {
314
      return test_failure(Botan::fmt("Assertion failure in {} {}: '{}' == '{}'", who(), what, produced, expected));
×
315
   }
316
}
317

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

326
bool Test::Result::test_str_contains(std::string_view what,
2✔
327
                                     std::string_view produced,
328
                                     std::string_view expected_substr) {
329
   if(produced.find(expected_substr) == std::string::npos) {
2✔
330
      return test_failure(Botan::fmt(
×
331
         "{} {} produced '{}' which unexpectedly does not contain '{}'", who(), what, produced, expected_substr));
×
332
   } else {
333
      return test_success(Botan::fmt("{} {} '{}' contains '{}'", who(), what, produced, expected_substr));
2✔
334
   }
335
}
336

337
bool Test::Result::test_i16_eq(std::string_view what, int16_t produced, int16_t expected) {
1,025✔
338
   return test_i32_eq(what, produced, expected);
1,025✔
339
}
340

341
bool Test::Result::test_i32_eq(std::string_view what, int32_t produced, int32_t expected) {
1,026✔
342
   if(produced == expected) {
1,026✔
343
      return test_success();
1,026✔
344
   } else {
345
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} == {}", who(), what, produced, expected));
×
346
   }
347
}
348

349
bool Test::Result::test_u8_eq(uint8_t produced, uint8_t expected) {
214✔
350
   return test_sz_eq("comparison", produced, expected);
214✔
351
}
352

353
bool Test::Result::test_u8_eq(std::string_view what, uint8_t produced, uint8_t expected) {
162,763✔
354
   return test_sz_eq(what, produced, expected);
162,763✔
355
}
356

357
bool Test::Result::test_u16_eq(uint16_t produced, uint16_t expected) {
40✔
358
   return test_sz_eq("comparison", produced, expected);
40✔
359
}
360

361
bool Test::Result::test_u16_eq(std::string_view what, uint16_t produced, uint16_t expected) {
66,790✔
362
   return test_sz_eq(what, produced, expected);
66,790✔
363
}
364

365
bool Test::Result::test_u32_eq(uint32_t produced, uint32_t expected) {
16✔
366
   return test_sz_eq("comparison", produced, expected);
16✔
367
}
368

369
bool Test::Result::test_u32_eq(std::string_view what, uint32_t produced, uint32_t expected) {
4,279✔
370
   return test_sz_eq(what, produced, expected);
4,279✔
371
}
372

373
bool Test::Result::test_u64_eq(uint64_t produced, uint64_t expected) {
13✔
374
   return test_u64_eq("comparison", produced, expected);
13✔
375
}
376

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

385
bool Test::Result::test_u64_lt(std::string_view what, uint64_t produced, uint64_t expected) {
2✔
386
   if(produced < expected) {
2✔
387
      return test_success();
2✔
388
   } else {
389
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} < {}", who(), what, produced, expected));
×
390
   }
391
}
392

393
bool Test::Result::test_sz_eq(std::string_view what, size_t produced, size_t expected) {
311,362✔
394
   if(produced == expected) {
311,362✔
395
      return test_success();
311,361✔
396
   } else {
397
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} == {}", who(), what, produced, expected));
1✔
398
   }
399
}
400

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

409
bool Test::Result::test_sz_lt(std::string_view what, size_t produced, size_t expected) {
5,639✔
410
   if(produced < expected) {
5,639✔
411
      return test_success();
5,638✔
412
   } else {
413
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} < {}", who(), what, produced, expected));
1✔
414
   }
415
}
416

417
bool Test::Result::test_sz_lte(std::string_view what, size_t produced, size_t expected) {
1,021,636✔
418
   if(produced <= expected) {
1,021,636✔
419
      return test_success();
1,021,635✔
420
   } else {
421
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} <= {}", who(), what, produced, expected));
1✔
422
   }
423
}
424

425
bool Test::Result::test_sz_gt(std::string_view what, size_t produced, size_t expected) {
30,149✔
426
   if(produced > expected) {
30,149✔
427
      return test_success();
30,149✔
428
   } else {
429
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} > {}", who(), what, produced, expected));
×
430
   }
431
}
432

433
bool Test::Result::test_sz_gte(std::string_view what, size_t produced, size_t expected) {
1,141,359✔
434
   if(produced >= expected) {
1,141,359✔
435
      return test_success();
1,141,358✔
436
   } else {
437
      return test_failure(Botan::fmt("Assertion failure in {} {}: {} >= {}", who(), what, produced, expected));
1✔
438
   }
439
}
440

441
bool Test::Result::test_opt_u8_eq(std::string_view what,
1,215✔
442
                                  std::optional<uint8_t> produced,
443
                                  std::optional<uint8_t> expected) {
444
   if(produced.has_value() and !expected.has_value()) {
1,215✔
445
      return test_failure(Botan::fmt("Assertion {} produced value {} but nullopt was expected", what, *produced));
×
446
   } else if(!produced.has_value() && expected.has_value()) {
1,215✔
447
      return test_failure(Botan::fmt("Assertion {} produced nullopt but {} was expected", what, *expected));
×
448
   } else if(produced.has_value() && expected.has_value()) {
1,215✔
449
      return test_u8_eq(what, *produced, *expected);
10✔
450
   } else {
451
      return test_success();
1,205✔
452
   }
453
}
454

455
bool Test::Result::test_opt_u16_eq(std::string_view what,
15✔
456
                                   std::optional<uint16_t> produced,
457
                                   std::optional<uint16_t> expected) {
458
   if(produced.has_value() and !expected.has_value()) {
15✔
459
      return test_failure(Botan::fmt("Assertion {} produced value {} but nullopt was expected", what, *produced));
×
460
   } else if(!produced.has_value() && expected.has_value()) {
15✔
461
      return test_failure(Botan::fmt("Assertion {} produced nullopt but {} was expected", what, *expected));
×
462
   } else if(produced.has_value() && expected.has_value()) {
15✔
463
      return test_u16_eq(what, *produced, *expected);
7✔
464
   } else {
465
      return test_success();
8✔
466
   }
467
}
468

469
bool Test::Result::test_opt_u32_eq(std::string_view what,
×
470
                                   std::optional<uint32_t> produced,
471
                                   std::optional<uint32_t> expected) {
472
   if(produced.has_value() and !expected.has_value()) {
×
473
      return test_failure(Botan::fmt("Assertion {} produced value {} but nullopt was expected", what, *produced));
×
474
   } else if(!produced.has_value() && expected.has_value()) {
×
475
      return test_failure(Botan::fmt("Assertion {} produced nullopt but {} was expected", what, *expected));
×
476
   } else if(produced.has_value() && expected.has_value()) {
×
477
      return test_u32_eq(what, *produced, *expected);
×
478
   } else {
479
      return test_success();
×
480
   }
481
}
482

483
bool Test::Result::test_opt_u64_eq(std::string_view what,
23✔
484
                                   std::optional<uint64_t> produced,
485
                                   std::optional<uint64_t> expected) {
486
   if(produced.has_value() and !expected.has_value()) {
23✔
487
      return test_failure(Botan::fmt("Assertion {} produced value {} but nullopt was expected", what, *produced));
×
488
   } else if(!produced.has_value() && expected.has_value()) {
23✔
489
      return test_failure(Botan::fmt("Assertion {} produced nullopt but {} was expected", what, *expected));
×
490
   } else if(produced.has_value() && expected.has_value()) {
23✔
491
      return test_u64_eq(what, *produced, *expected);
23✔
492
   } else {
493
      return test_success();
×
494
   }
495
}
496

497
bool Test::Result::test_opt_str_eq(std::string_view what,
13✔
498
                                   std::optional<std::string> produced,
499
                                   std::optional<std::string> expected) {
500
   if(produced.has_value() and !expected.has_value()) {
13✔
501
      return test_failure(Botan::fmt("Assertion {} produced value {} but nullopt was expected", what, *produced));
×
502
   } else if(!produced.has_value() && expected.has_value()) {
13✔
503
      return test_failure(Botan::fmt("Assertion {} produced nullopt but {} was expected", what, *expected));
×
504
   } else if(produced.has_value() && expected.has_value()) {
13✔
505
      return test_str_eq(what, *produced, *expected);
13✔
506
   } else {
507
      return test_success();
×
508
   }
509
}
510

511
#if defined(BOTAN_HAS_BIGINT)
512
bool Test::Result::test_bn_eq(std::string_view what, const BigInt& produced, const BigInt& expected) {
252,699✔
513
   if(produced == expected) {
252,699✔
514
      return test_success();
252,698✔
515
   } else {
516
      std::ostringstream err;
1✔
517
      err << who() << " " << what << " produced " << produced << " != expected value " << expected;
1✔
518
      return test_failure(err.str());
1✔
519
   }
1✔
520
}
521

522
bool Test::Result::test_bn_ne(std::string_view what, const BigInt& produced, const BigInt& expected) {
97✔
523
   if(produced != expected) {
97✔
524
      return test_success();
96✔
525
   } else {
526
      std::ostringstream err;
1✔
527
      err << who() << " " << what << " produced " << produced << " prohibited value";
1✔
528
      return test_failure(err.str());
1✔
529
   }
1✔
530
}
531
#endif
532

533
bool Test::Result::test_bool_eq(std::string_view what, bool produced, bool expected) {
460,709✔
534
   if(produced == expected) {
460,709✔
535
      return test_success();
460,709✔
536
   } else {
537
      if(expected == true) {
×
538
         return test_failure(Botan::fmt("Assertion failure in {}, {} was unexpectedly false", who(), what));
×
539
      } else {
540
         return test_failure(Botan::fmt("Assertion failure in {}, {} was unexpectedly true", who(), what));
×
541
      }
542
   }
543
}
544

545
bool Test::Result::test_is_true(std::string_view what, bool produced) {
316,010✔
546
   return test_bool_eq(what, produced, true);
316,010✔
547
}
548

549
bool Test::Result::test_is_false(std::string_view what, bool produced) {
136,983✔
550
   return test_bool_eq(what, produced, false);
136,983✔
551
}
552

553
bool Test::Result::test_rc_ok(std::string_view func, int rc) {
2,912✔
554
   if(rc != 0) {
2,912✔
555
      std::ostringstream err;
1✔
556
      err << m_who << " " << func << " unexpectedly failed with error code " << rc;
1✔
557
      return test_failure(err.str());
1✔
558
   }
1✔
559

560
   return test_success();
2,911✔
561
}
562

563
bool Test::Result::test_rc_fail(std::string_view func, std::string_view why, int rc) {
26✔
564
   if(rc == 0) {
26✔
565
      std::ostringstream err;
1✔
566
      err << m_who << " call to " << func << " unexpectedly succeeded expecting failure because " << why;
1✔
567
      return test_failure(err.str());
1✔
568
   }
1✔
569

570
   return test_success();
25✔
571
}
572

573
bool Test::Result::test_rc_init(std::string_view func, int rc) {
121✔
574
   if(rc == 0) {
121✔
575
      return test_success();
121✔
576
   } else {
577
      std::ostringstream msg;
×
578
      msg << m_who;
×
579
      msg << " " << func;
×
580

581
      // -40 is BOTAN_FFI_ERROR_NOT_IMPLEMENTED
582
      if(rc == -40) {
×
583
         msg << " returned not implemented";
×
584
      } else {
585
         msg << " unexpectedly failed with error code " << rc;
×
586
      }
587

588
      if(rc == -40) {
×
589
         this->test_note(msg.str());
×
590
      } else {
591
         this->test_failure(msg.str());
×
592
      }
593
      return false;
×
594
   }
×
595
}
596

597
bool Test::Result::test_rc(std::string_view func, int rc, int expected) {
450✔
598
   if(expected != rc) {
450✔
599
      std::ostringstream err;
1✔
600
      err << m_who;
1✔
601
      err << " call to " << func << " unexpectedly returned " << rc;
1✔
602
      err << " but expecting " << expected;
1✔
603
      return test_failure(err.str());
1✔
604
   }
1✔
605

606
   return test_success();
449✔
607
}
608

609
void Test::initialize(std::string test_name, CodeLocation location) {
460✔
610
   m_test_name = std::move(test_name);
460✔
611
   m_registration_location = location;
460✔
612
}
460✔
613

614
Botan::RandomNumberGenerator& Test::rng() const {
503,813✔
615
   if(!m_test_rng) {
503,813✔
616
      m_test_rng = Test::new_rng(m_test_name);
149✔
617
   }
618

619
   return *m_test_rng;
503,813✔
620
}
621

622
std::optional<std::string> Test::supported_ec_group_name(std::vector<std::string> preferred_groups) {
151✔
623
#if defined(BOTAN_HAS_ECC_GROUP)
624
   if(preferred_groups.empty()) {
151✔
625
      preferred_groups = {
149✔
626
         "secp256r1",
627
         "brainpool256r1",
628
         "secp384r1",
629
         "brainpool384r1",
630
         "secp521r1",
631
         "brainpool512r1",
632
      };
1,192✔
633
   }
634

635
   for(const auto& group : preferred_groups) {
151✔
636
      if(Botan::EC_Group::supports_named_group(group)) {
151✔
637
         return group;
151✔
638
      }
639
   }
640
#else
641
   BOTAN_UNUSED(preferred_groups);
642
#endif
643

644
   return std::nullopt;
×
645
}
298✔
646

647
std::vector<uint8_t> Test::mutate_vec(const std::vector<uint8_t>& v,
97,316✔
648
                                      Botan::RandomNumberGenerator& rng,
649
                                      bool maybe_resize,
650
                                      size_t min_offset) {
651
   std::vector<uint8_t> r = v;
97,316✔
652

653
   if(maybe_resize && (r.empty() || rng.next_byte() < 32)) {
110,951✔
654
      // TODO: occasionally truncate, insert at random index
655
      const size_t add = 1 + (rng.next_byte() % 16);
2,149✔
656
      r.resize(r.size() + add);
2,149✔
657
      rng.randomize(&r[r.size() - add], add);
2,149✔
658
   }
659

660
   if(r.size() > min_offset) {
97,316✔
661
      const size_t offset = std::max<size_t>(min_offset, rng.next_byte() % r.size());
95,778✔
662
      const uint8_t perturb = rng.next_nonzero_byte();
95,778✔
663
      r[offset] ^= perturb;
95,778✔
664
   }
665

666
   return r;
97,316✔
667
}
×
668

669
std::vector<std::string> Test::possible_providers(const std::string& /*alg*/) {
×
670
   return Test::provider_filter({"base"});
×
671
}
672

673
//static
674
std::string Test::format_time(uint64_t nanoseconds) {
1,507✔
675
   std::ostringstream o;
1,507✔
676

677
   if(nanoseconds > 1000000000) {
1,507✔
678
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000000.0 << " sec";
156✔
679
   } else {
680
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000.0 << " msec";
1,351✔
681
   }
682

683
   return o.str();
3,014✔
684
}
1,507✔
685

686
// TODO: this should move to `StdoutReporter`
687
std::string Test::Result::result_string() const {
3,071✔
688
   const bool verbose = Test::options().verbose();
3,071✔
689

690
   if(tests_run() == 0 && !verbose) {
3,071✔
691
      return "";
26✔
692
   }
693

694
   std::ostringstream report;
3,045✔
695

696
   report << who() << " ran ";
3,045✔
697

698
   if(tests_run() == 0) {
3,045✔
699
      report << "ZERO";
×
700
   } else {
701
      report << tests_run();
3,045✔
702
   }
703
   report << " tests";
3,045✔
704

705
   if(m_ns_taken > 0) {
3,045✔
706
      report << " in " << format_time(m_ns_taken);
3,012✔
707
   }
708

709
   if(tests_failed() > 0) {
3,045✔
710
      report << " " << tests_failed() << " FAILED";
24✔
711
   } else {
712
      report << " all ok";
3,021✔
713
   }
714

715
   report << "\n";
3,045✔
716

717
   for(size_t i = 0; i != m_fail_log.size(); ++i) {
3,069✔
718
      report << "Failure " << (i + 1) << ": " << m_fail_log[i];
24✔
719
      if(m_where) {
24✔
720
         report << " (at " << m_where->path << ":" << m_where->line << ")";
×
721
      }
722
      report << "\n";
24✔
723
   }
724

725
   if(!m_fail_log.empty() || tests_run() == 0 || verbose) {
3,045✔
726
      for(size_t i = 0; i != m_log.size(); ++i) {
24✔
727
         report << "Note " << (i + 1) << ": " << m_log[i] << "\n";
×
728
      }
729
   }
730

731
   return report.str();
3,045✔
732
}
3,045✔
733

734
namespace {
735

736
class Test_Registry {
737
   public:
738
      static Test_Registry& instance() {
1,408✔
739
         static Test_Registry registry;
1,409✔
740
         return registry;
1,408✔
741
      }
742

743
      void register_test(const std::string& category,
460✔
744
                         const std::string& name,
745
                         bool smoke_test,
746
                         bool needs_serialization,
747
                         std::function<std::unique_ptr<Test>()> maker_fn) {
748
         if(m_tests.contains(name)) {
460✔
749
            throw Test_Error("Duplicate registration of test '" + name + "'");
×
750
         }
751

752
         if(m_tests.contains(category)) {
460✔
753
            throw Test_Error("'" + category + "' cannot be used as category, test exists");
×
754
         }
755

756
         if(m_categories.contains(name)) {
460✔
757
            throw Test_Error("'" + name + "' cannot be used as test name, category exists");
×
758
         }
759

760
         if(smoke_test) {
460✔
761
            m_smoke_tests.push_back(name);
10✔
762
         }
763

764
         if(needs_serialization) {
460✔
765
            m_mutexed_tests.push_back(name);
22✔
766
         }
767

768
         m_tests.emplace(name, std::move(maker_fn));
460✔
769
         m_categories.emplace(category, name);
460✔
770
      }
460✔
771

772
      std::unique_ptr<Test> get_test(const std::string& test_name) const {
460✔
773
         auto i = m_tests.find(test_name);
460✔
774
         if(i != m_tests.end()) {
460✔
775
            return i->second();
460✔
776
         }
777
         return nullptr;
×
778
      }
779

780
      std::vector<std::string> registered_tests() const {
×
781
         std::vector<std::string> s;
×
782
         s.reserve(m_tests.size());
×
783
         for(auto&& i : m_tests) {
×
784
            s.push_back(i.first);
×
785
         }
786
         return s;
×
787
      }
×
788

789
      std::vector<std::string> registered_test_categories() const {
×
790
         std::set<std::string> s;
×
791
         for(auto&& i : m_categories) {
×
792
            s.insert(i.first);
×
793
         }
794
         return std::vector<std::string>(s.begin(), s.end());
×
795
      }
×
796

797
      std::vector<std::string> filter_registered_tests(const std::vector<std::string>& requested,
1✔
798
                                                       const std::vector<std::string>& to_be_skipped) {
799
         std::vector<std::string> result;
1✔
800

801
         std::set<std::string> to_be_skipped_set(to_be_skipped.begin(), to_be_skipped.end());
1✔
802
         // TODO: this is O(n^2), but we have a relatively small number of tests.
803
         auto insert_if_not_exists_and_not_skipped = [&](const std::string& test_name) {
461✔
804
            if(!Botan::value_exists(result, test_name) && !to_be_skipped_set.contains(test_name)) {
920✔
805
               result.push_back(test_name);
450✔
806
            }
807
         };
461✔
808

809
         if(requested.empty()) {
1✔
810
            /*
811
            If nothing was requested on the command line, run everything. First
812
            run the "essentials" to smoke test, then everything else in
813
            alphabetical order.
814
            */
815
            result = m_smoke_tests;
1✔
816
            for(const auto& [test_name, _] : m_tests) {
461✔
817
               insert_if_not_exists_and_not_skipped(test_name);
460✔
818
            }
819
         } else {
820
            for(const auto& r : requested) {
×
821
               if(m_tests.contains(r)) {
×
822
                  insert_if_not_exists_and_not_skipped(r);
×
823
               } else if(auto elems = m_categories.equal_range(r); elems.first != m_categories.end()) {
×
824
                  for(; elems.first != elems.second; ++elems.first) {
×
825
                     insert_if_not_exists_and_not_skipped(elems.first->second);
×
826
                  }
827
               } else {
828
                  throw Test_Error("Unknown test suite or category: " + r);
×
829
               }
830
            }
831
         }
832

833
         return result;
1✔
834
      }
1✔
835

836
      bool needs_serialization(const std::string& test_name) const {
487✔
837
         return Botan::value_exists(m_mutexed_tests, test_name);
27✔
838
      }
839

840
   private:
841
      Test_Registry() = default;
1✔
842

843
   private:
844
      std::map<std::string, std::function<std::unique_ptr<Test>()>> m_tests;
845
      std::multimap<std::string, std::string> m_categories;
846
      std::vector<std::string> m_smoke_tests;
847
      std::vector<std::string> m_mutexed_tests;
848
};
849

850
}  // namespace
851

852
// static Test:: functions
853

854
//static
855
void Test::register_test(const std::string& category,
460✔
856
                         const std::string& name,
857
                         bool smoke_test,
858
                         bool needs_serialization,
859
                         std::function<std::unique_ptr<Test>()> maker_fn) {
860
   Test_Registry::instance().register_test(category, name, smoke_test, needs_serialization, std::move(maker_fn));
920✔
861
}
460✔
862

863
//static
864
uint64_t Test::timestamp() {
182,564✔
865
   auto now = std::chrono::system_clock::now().time_since_epoch();
182,564✔
866
   return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
2,073✔
867
}
868

869
//static
870
std::vector<Test::Result> Test::flatten_result_lists(std::vector<std::vector<Test::Result>> result_lists) {
4✔
871
   std::vector<Test::Result> results;
4✔
872
   for(auto& result_list : result_lists) {
26✔
873
      for(auto& result : result_list) {
71✔
874
         results.emplace_back(std::move(result));
49✔
875
      }
876
   }
877
   return results;
4✔
878
}
×
879

880
//static
881
std::vector<std::string> Test::registered_tests() {
×
882
   return Test_Registry::instance().registered_tests();
×
883
}
884

885
//static
886
std::vector<std::string> Test::registered_test_categories() {
×
887
   return Test_Registry::instance().registered_test_categories();
×
888
}
889

890
//static
891
std::unique_ptr<Test> Test::get_test(const std::string& test_name) {
460✔
892
   return Test_Registry::instance().get_test(test_name);
460✔
893
}
894

895
//static
896
bool Test::test_needs_serialization(const std::string& test_name) {
460✔
897
   return Test_Registry::instance().needs_serialization(test_name);
460✔
898
}
899

900
//static
901
std::vector<std::string> Test::filter_registered_tests(const std::vector<std::string>& requested,
1✔
902
                                                       const std::vector<std::string>& to_be_skipped) {
903
   return Test_Registry::instance().filter_registered_tests(requested, to_be_skipped);
1✔
904
}
905

906
//static
907
std::string Test::temp_file_name(const std::string& basename) {
21✔
908
   // TODO add a --tmp-dir option to the tests to specify where these files go
909

910
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
911

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

915
   const int fd = ::mkstemp(mkstemp_basename.data());
21✔
916

917
   // error
918
   if(fd < 0) {
21✔
919
      return "";
×
920
   }
921

922
   ::close(fd);
21✔
923

924
   return mkstemp_basename;
21✔
925
#else
926
   // For now just create the temp in the current working directory
927
   return basename;
928
#endif
929
}
21✔
930

931
bool Test::copy_file(const std::string& from, const std::string& to) {
1✔
932
#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(__cpp_lib_filesystem)
933
   std::error_code ec;  // don't throw, just return false on error
1✔
934
   return std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing, ec);
1✔
935
#else
936
   // TODO: implement fallbacks to POSIX or WIN32
937
   // ... but then again: it's 2023 and we're using C++20 :o)
938
   BOTAN_UNUSED(from, to);
939
   throw Botan::No_Filesystem_Access();
940
#endif
941
}
942

943
std::string Test::read_data_file(const std::string& path) {
39✔
944
   const std::string fsname = Test::data_file(path);
39✔
945
   std::ifstream file(fsname.c_str());
39✔
946
   if(!file.good()) {
39✔
947
      throw Test_Error("Error reading from " + fsname);
×
948
   }
949

950
   return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
78✔
951
}
39✔
952

953
std::vector<uint8_t> Test::read_binary_data_file(const std::string& path) {
41✔
954
   const std::string fsname = Test::data_file(path);
41✔
955
   std::ifstream file(fsname.c_str(), std::ios::binary);
41✔
956
   if(!file.good()) {
41✔
957
      throw Test_Error("Error reading from " + fsname);
×
958
   }
959

960
   std::vector<uint8_t> contents;
41✔
961

962
   while(file.good()) {
89✔
963
      std::vector<uint8_t> buf(4096);
48✔
964
      file.read(reinterpret_cast<char*>(buf.data()), buf.size());
48✔
965
      const size_t got = static_cast<size_t>(file.gcount());
48✔
966

967
      if(got == 0 && file.eof()) {
48✔
968
         break;
969
      }
970

971
      contents.insert(contents.end(), buf.data(), buf.data() + got);
48✔
972
   }
48✔
973

974
   return contents;
82✔
975
}
41✔
976

977
// static member variables of Test
978

979
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
980
Test_Options Test::m_opts;
981
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
982
std::string Test::m_test_rng_seed;
983

984
//static
985
void Test::set_test_options(const Test_Options& opts) {
1✔
986
   m_opts = opts;
1✔
987
}
1✔
988

989
namespace {
990

991
/*
992
* This is a fast, simple, deterministic PRNG that's used for running
993
* the tests. It is not intended to be cryptographically secure.
994
*/
995
class Testsuite_RNG final : public Botan::RandomNumberGenerator {
9✔
996
   public:
997
      std::string name() const override { return "Testsuite_RNG"; }
×
998

999
      void clear() override { m_x = 0; }
×
1000

1001
      bool accepts_input() const override { return true; }
×
1002

1003
      bool is_seeded() const override { return true; }
278,699✔
1004

1005
      void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> input) override {
10,106,590✔
1006
         for(const auto byte : input) {
10,106,590✔
1007
            mix(byte);
×
1008
         }
1009

1010
         for(auto& byte : output) {
85,287,452✔
1011
            byte = mix();
75,180,862✔
1012
         }
1013
      }
10,106,590✔
1014

1015
      Testsuite_RNG(std::string_view seed, std::string_view test_name) : m_x(0) {
903✔
1016
         for(const char c : seed) {
27,090✔
1017
            this->mix(static_cast<uint8_t>(c));
26,187✔
1018
         }
1019
         for(const char c : test_name) {
37,501✔
1020
            this->mix(static_cast<uint8_t>(c));
36,598✔
1021
         }
1022
      }
903✔
1023

1024
   private:
1025
      uint8_t mix(uint8_t input = 0) {
75,243,647✔
1026
         m_x ^= input;
75,243,647✔
1027
         m_x *= 0xF2E16957;
75,243,647✔
1028
         m_x += 0xE50B590F;
75,243,647✔
1029
         return static_cast<uint8_t>(m_x >> 27);
75,243,647✔
1030
      }
1031

1032
      uint64_t m_x;
1033
};
1034

1035
}  // namespace
1036

1037
//static
1038
void Test::set_test_rng_seed(std::span<const uint8_t> seed, size_t epoch) {
1✔
1039
   m_test_rng_seed = Botan::fmt("seed={} epoch={}", Botan::hex_encode(seed), epoch);
1✔
1040
}
1✔
1041

1042
//static
1043
std::unique_ptr<Botan::RandomNumberGenerator> Test::new_rng(std::string_view test_name) {
894✔
1044
   return std::make_unique<Testsuite_RNG>(m_test_rng_seed, test_name);
894✔
1045
}
1046

1047
//static
1048
std::shared_ptr<Botan::RandomNumberGenerator> Test::new_shared_rng(std::string_view test_name) {
9✔
1049
   return std::make_shared<Testsuite_RNG>(m_test_rng_seed, test_name);
9✔
1050
}
1051

1052
//static
1053
std::string Test::data_file(const std::string& file) {
1,126✔
1054
   return options().data_dir() + "/" + file;
2,252✔
1055
}
1056

1057
//static
1058
std::string Test::data_file(std::string_view subdir, std::string_view filename) {
4✔
1059
   if(subdir.empty() || filename.empty()) {
4✔
1060
      throw Test_Error("Empty subdir or filename in Test::data_file");
×
1061
   }
1062
   return Botan::fmt("{}/{}/{}", options().data_dir(), subdir, filename);
4✔
1063
}
1064

1065
//static
1066
std::string Test::data_dir(const std::string& subdir) {
1✔
1067
   return options().data_dir() + "/" + subdir;
2✔
1068
}
1069

1070
//static
1071
std::vector<std::string> Test::files_in_data_dir(const std::string& subdir) {
394✔
1072
   auto fs = Botan::get_files_recursive(options().data_dir() + "/" + subdir);
1,182✔
1073
   if(fs.empty()) {
394✔
1074
      throw Test_Error("Test::files_in_data_dir encountered empty subdir " + subdir);
×
1075
   }
1076
   return fs;
394✔
1077
}
×
1078

1079
//static
1080
std::string Test::data_file_as_temporary_copy(const std::string& what) {
1✔
1081
   auto tmp_basename = what;
1✔
1082
   std::replace(tmp_basename.begin(), tmp_basename.end(), '/', '_');
1✔
1083
   auto temp_file = temp_file_name("tmp-" + tmp_basename);
1✔
1084
   if(temp_file.empty()) {
1✔
1085
      return "";
×
1086
   }
1087
   if(!Test::copy_file(data_file(what), temp_file)) {
1✔
1088
      return "";
×
1089
   }
1090
   return temp_file;
1✔
1091
}
1✔
1092

1093
//static
1094
std::vector<std::string> Test::provider_filter(const std::vector<std::string>& providers) {
49,595✔
1095
   if(m_opts.provider().empty()) {
49,595✔
1096
      return providers;
49,595✔
1097
   }
1098
   for(auto&& provider : providers) {
×
1099
      if(provider == m_opts.provider()) {
×
1100
         return std::vector<std::string>{provider};
×
1101
      }
1102
   }
1103
   return std::vector<std::string>{};
×
1104
}
×
1105

1106
std::string Test::random_password(Botan::RandomNumberGenerator& rng) {
222✔
1107
   const size_t len = 1 + rng.next_byte() % 32;
222✔
1108
   return Botan::hex_encode(rng.random_vec(len));
444✔
1109
}
1110

1111
size_t Test::random_index(Botan::RandomNumberGenerator& rng, size_t max) {
8,062✔
1112
   return Botan::load_be(rng.random_array<8>()) % max;
8,062✔
1113
}
1114

1115
void VarMap::clear() {
34,361✔
1116
   m_vars.clear();
×
1117
}
×
1118

1119
namespace {
1120

1121
bool varmap_pair_lt(const std::pair<std::string, std::string>& kv, std::string_view k) {
1,690,551✔
1122
   return kv.first < k;
1,690,551✔
1123
}
1124

1125
}  // namespace
1126

1127
bool VarMap::has_key(std::string_view key) const {
213,574✔
1128
   return get_opt_var(key).has_value();
213,574✔
1129
}
1130

1131
void VarMap::add(std::string_view key, std::string_view value) {
156,226✔
1132
   auto i = std::lower_bound(m_vars.begin(), m_vars.end(), key, varmap_pair_lt);
156,226✔
1133

1134
   if(i != m_vars.end() && i->first == key) {
163,445✔
1135
      i->second = value;
44,673✔
1136
   } else {
1137
      m_vars.emplace(i, key, value);
111,553✔
1138
   }
1139
}
156,226✔
1140

1141
std::optional<std::string> VarMap::get_opt_var(std::string_view key) const {
293,665✔
1142
   auto i = std::lower_bound(m_vars.begin(), m_vars.end(), key, varmap_pair_lt);
293,665✔
1143

1144
   if(i != m_vars.end() && i->first == key) {
295,549✔
1145
      return i->second;
241,112✔
1146
   } else {
1147
      return {};
52,553✔
1148
   }
1149
}
1150

1151
const std::string& VarMap::get_req_var(std::string_view key) const {
261,660✔
1152
   auto i = std::lower_bound(m_vars.begin(), m_vars.end(), key, varmap_pair_lt);
261,660✔
1153

1154
   if(i != m_vars.end() && i->first == key) {
261,660✔
1155
      return i->second;
261,660✔
1156
   } else {
1157
      throw Test_Error(Botan::fmt("Test missing variable '{}'", key));
×
1158
   }
1159
}
1160

1161
std::string VarMap::get_req_str(std::string_view key) const {
53,364✔
1162
   return std::string(get_req_var(key));
53,364✔
1163
}
1164

1165
std::vector<std::vector<uint8_t>> VarMap::get_req_bin_list(std::string_view key) const {
12✔
1166
   const auto& var = get_req_var(key);
12✔
1167

1168
   std::vector<std::vector<uint8_t>> bin_list;
12✔
1169

1170
   for(auto&& part : Botan::split_on(var, ',')) {
62✔
1171
      try {
50✔
1172
         bin_list.push_back(Botan::hex_decode(part));
100✔
1173
      } catch(std::exception& e) {
×
1174
         std::ostringstream oss;
×
1175
         oss << "Bad input '" << part << "'"
×
1176
             << " in binary list key " << key << " - " << e.what();
×
1177
         throw Test_Error(oss.str());
×
1178
      }
×
1179
   }
12✔
1180

1181
   return bin_list;
12✔
1182
}
×
1183

1184
std::vector<uint8_t> VarMap::get_req_bin(std::string_view key) const {
164,984✔
1185
   const auto& var = get_req_var(key);
164,984✔
1186

1187
   try {
164,984✔
1188
      return Botan::hex_decode(var);
164,984✔
1189
   } catch(std::exception& e) {
×
1190
      throw Test_Error(Botan::fmt("Bad hex input '{}' for key '{}' err '{}'", var, key, e.what()));
×
1191
   }
×
1192
}
1193

1194
std::string VarMap::get_opt_str(std::string_view key, std::string_view def_value) const {
14,468✔
1195
   if(auto v = get_opt_var(key)) {
14,468✔
1196
      return *v;
14,547✔
1197
   } else {
1198
      return std::string(def_value);
28,778✔
1199
   }
14,468✔
1200
}
1201

1202
bool VarMap::get_req_bool(std::string_view key) const {
74✔
1203
   const auto& var = get_req_var(key);
74✔
1204

1205
   if(var == "true") {
74✔
1206
      return true;
1207
   } else if(var == "false") {
34✔
1208
      return false;
1209
   } else {
1210
      throw Test_Error(Botan::fmt("Invalid boolean '{}' for key '{}'", var, key));
×
1211
   }
1212
}
1213

1214
size_t VarMap::get_req_sz(std::string_view key) const {
4,653✔
1215
   return Botan::to_u32bit(get_req_var(key));
4,653✔
1216
}
1217

1218
uint8_t VarMap::get_req_u8(std::string_view key) const {
17✔
1219
   const size_t s = this->get_req_sz(key);
17✔
1220
   if(s > 256) {
17✔
1221
      throw Test_Error(Botan::fmt("Invalid value for '{}' expected uint8_t but got '{}'", key, s));
×
1222
   }
1223
   return static_cast<uint8_t>(s);
17✔
1224
}
1225

1226
uint32_t VarMap::get_req_u32(std::string_view key) const {
14✔
1227
   return static_cast<uint32_t>(get_req_sz(key));
14✔
1228
}
1229

1230
uint64_t VarMap::get_req_u64(std::string_view key) const {
17✔
1231
   const auto& var = get_req_var(key);
17✔
1232
   if(const auto val = Botan::parse_u64(var)) {
17✔
1233
      return *val;
17✔
1234
   } else {
1235
      throw Test_Error(Botan::fmt("Invalid u64 value '{}' for key '{}'", var, key));
×
1236
   }
1237
}
1238

1239
size_t VarMap::get_opt_sz(std::string_view key, const size_t def_value) const {
13,884✔
1240
   if(auto v = get_opt_var(key)) {
13,884✔
1241
      return Botan::to_u32bit(*v);
12,261✔
1242
   } else {
1243
      return def_value;
1244
   }
13,884✔
1245
}
1246

1247
uint64_t VarMap::get_opt_u64(std::string_view key, const uint64_t def_value) const {
4,363✔
1248
   if(auto var = get_opt_var(key)) {
4,363✔
1249
      if(const auto val = Botan::parse_u64(*var)) {
1,053✔
1250
         return *val;
1,053✔
1251
      } else {
1252
         throw Test_Error(Botan::fmt("Invalid u64 value '{}' for key '{}'", *var, key));
×
1253
      }
1254
   } else {
1255
      return def_value;
1256
   }
4,363✔
1257
}
1258

1259
std::vector<uint8_t> VarMap::get_opt_bin(std::string_view key) const {
47,296✔
1260
   if(auto v = get_opt_var(key)) {
47,296✔
1261
      try {
17,835✔
1262
         return Botan::hex_decode(*v);
17,835✔
1263
      } catch(std::exception&) {
×
1264
         throw Test_Error(Botan::fmt("Invalid hex for key '{}' got '{}'", key, *v));
×
1265
      }
×
1266
   } else {
1267
      return {};
29,461✔
1268
   };
47,296✔
1269
}
1270

1271
#if defined(BOTAN_HAS_BIGINT)
1272
Botan::BigInt VarMap::get_req_bn(std::string_view key) const {
38,556✔
1273
   const auto& var = get_req_var(key);
38,556✔
1274

1275
   try {
38,556✔
1276
      return Botan::BigInt(var);
38,556✔
1277
   } catch(std::exception&) {
×
1278
      throw Test_Error(Botan::fmt("Invalid BigInt for key '{}' got '{}'", key, var));
×
1279
   }
×
1280
}
1281

1282
Botan::BigInt VarMap::get_opt_bn(std::string_view key, const Botan::BigInt& def_value) const {
80✔
1283
   if(auto v = get_opt_var(key)) {
80✔
1284
      try {
56✔
1285
         return Botan::BigInt(*v);
56✔
1286
      } catch(std::exception&) {
×
1287
         throw Test_Error(Botan::fmt("Invalid BigInt for key '{}' got '{}'", key, *v));
×
1288
      }
×
1289
   } else {
1290
      return def_value;
24✔
1291
   }
80✔
1292
}
1293
#endif
1294

1295
class Text_Based_Test::Text_Based_Test_Data {
1296
   public:
1297
      Text_Based_Test_Data(const std::string& data_src,
194✔
1298
                           const std::string& required_keys_str,
1299
                           const std::string& optional_keys_str) :
194✔
1300
            m_data_src(data_src) {
388✔
1301
         if(required_keys_str.empty()) {
194✔
1302
            throw Test_Error("Invalid test spec");
×
1303
         }
1304

1305
         m_required_keys = Botan::split_on(required_keys_str, ',');
194✔
1306
         std::vector<std::string> optional_keys = Botan::split_on(optional_keys_str, ',');
194✔
1307

1308
         m_all_keys.insert(m_required_keys.begin(), m_required_keys.end());
194✔
1309
         m_all_keys.insert(optional_keys.begin(), optional_keys.end());
194✔
1310
         m_output_key = m_required_keys.at(m_required_keys.size() - 1);
194✔
1311
      }
194✔
1312

1313
      std::string get_next_line();
1314

1315
      const std::string& current_source_name() const { return m_cur_src_name; }
×
1316

1317
      bool known_key(const std::string& key) const;
1318

1319
      const std::vector<std::string>& required_keys() const { return m_required_keys; }
48,541✔
1320

1321
      const std::string& output_key() const { return m_output_key; }
156,226✔
1322

1323
      const std::vector<std::string>& cpu_flags() const { return m_cpu_flags; }
48,400✔
1324

1325
      void set_cpu_flags(std::vector<std::string> flags) { m_cpu_flags = std::move(flags); }
27✔
1326

1327
      const std::string& initial_data_src_name() const { return m_data_src; }
194✔
1328

1329
   private:
1330
      std::string m_data_src;
1331
      std::vector<std::string> m_required_keys;
1332
      std::unordered_set<std::string> m_all_keys;
1333
      std::string m_output_key;
1334

1335
      bool m_first = true;
1336
      std::unique_ptr<std::istream> m_cur;
1337
      std::string m_cur_src_name;
1338
      std::deque<std::string> m_srcs;
1339
      std::vector<std::string> m_cpu_flags;
1340
};
1341

1342
Text_Based_Test::Text_Based_Test(const std::string& data_src,
194✔
1343
                                 const std::string& required_keys,
1344
                                 const std::string& optional_keys) :
194✔
1345
      m_data(std::make_unique<Text_Based_Test_Data>(data_src, required_keys, optional_keys)) {}
194✔
1346

1347
Text_Based_Test::~Text_Based_Test() = default;
194✔
1348

1349
std::string Text_Based_Test::Text_Based_Test_Data::get_next_line() {
157,285✔
1350
   while(true) {
157,559✔
1351
      if(m_cur == nullptr || m_cur->good() == false) {
157,559✔
1352
         if(m_srcs.empty()) {
478✔
1353
            if(m_first) {
388✔
1354
               if(m_data_src.ends_with(".vec")) {
194✔
1355
                  m_srcs.push_back(Test::data_file(m_data_src));
364✔
1356
               } else {
1357
                  const auto fs = Test::files_in_data_dir(m_data_src);
12✔
1358
                  m_srcs.assign(fs.begin(), fs.end());
12✔
1359
                  if(m_srcs.empty()) {
12✔
1360
                     throw Test_Error("Error reading test data dir " + m_data_src);
×
1361
                  }
1362
               }
12✔
1363

1364
               m_first = false;
194✔
1365
            } else {
1366
               return "";  // done
194✔
1367
            }
1368
         }
1369

1370
         m_cur = std::make_unique<std::ifstream>(m_srcs[0]);
374✔
1371
         m_cur_src_name = m_srcs[0];
284✔
1372

1373
#if defined(BOTAN_HAS_CPUID)
1374
         // Reinit cpuid on new file if needed
1375
         if(m_cpu_flags.empty() == false) {
284✔
1376
            m_cpu_flags.clear();
23✔
1377
            Botan::CPUID::initialize();
23✔
1378
         }
1379
#endif
1380

1381
         if(!m_cur->good()) {
284✔
1382
            throw Test_Error("Could not open input file '" + m_cur_src_name);
×
1383
         }
1384

1385
         m_srcs.pop_front();
284✔
1386
      }
1387

1388
      while(m_cur->good()) {
228,418✔
1389
         std::string line;
228,144✔
1390
         std::getline(*m_cur, line);
228,144✔
1391

1392
         if(line.empty()) {
228,144✔
1393
            continue;
55,404✔
1394
         }
1395

1396
         if(line[0] == '#') {
172,740✔
1397
            if(line.starts_with("#test ")) {
15,676✔
1398
               return line;
27✔
1399
            } else {
1400
               continue;
15,649✔
1401
            }
1402
         }
1403

1404
         return line;
157,064✔
1405
      }
228,144✔
1406
   }
1407
}
1408

1409
bool Text_Based_Test::Text_Based_Test_Data::known_key(const std::string& key) const {
156,226✔
1410
   return m_all_keys.contains(key);
156,226✔
1411
}
1412

1413
namespace {
1414

1415
// strips leading and trailing but not internal whitespace
1416
std::string strip_ws(const std::string& in) {
312,452✔
1417
   const char* whitespace = " ";
312,452✔
1418

1419
   const auto first_c = in.find_first_not_of(whitespace);
312,452✔
1420
   if(first_c == std::string::npos) {
312,452✔
1421
      return "";
1,084✔
1422
   }
1423

1424
   const auto last_c = in.find_last_not_of(whitespace);
311,368✔
1425

1426
   return in.substr(first_c, last_c - first_c + 1);
311,368✔
1427
}
1428

1429
std::vector<std::string> parse_cpuid_bits(const std::vector<std::string>& tok) {
27✔
1430
   std::vector<std::string> bits;
27✔
1431

1432
#if defined(BOTAN_HAS_CPUID)
1433
   for(size_t i = 1; i < tok.size(); ++i) {
129✔
1434
      if(auto bit = Botan::CPUID::bit_from_string(tok[i])) {
102✔
1435
         bits.push_back(bit->to_string());
124✔
1436
      }
1437
   }
1438
#else
1439
   BOTAN_UNUSED(tok);
1440
#endif
1441

1442
   return bits;
27✔
1443
}
×
1444

1445
}  // namespace
1446

1447
bool Text_Based_Test::skip_this_test(const std::string& /*header*/, const VarMap& /*vars*/) {
32,744✔
1448
   return false;
32,744✔
1449
}
1450

1451
std::vector<Test::Result> Text_Based_Test::run() {
194✔
1452
   std::vector<Test::Result> results;
194✔
1453

1454
   std::string header;
194✔
1455
   std::string header_or_name = m_data->initial_data_src_name();
194✔
1456
   VarMap vars;
194✔
1457
   size_t test_cnt = 0;
194✔
1458

1459
   while(true) {
157,285✔
1460
      const std::string line = m_data->get_next_line();
157,285✔
1461
      if(line.empty()) {
157,285✔
1462
         // EOF
1463
         break;
1464
      }
1465

1466
      if(line.starts_with("#test ")) {
157,091✔
1467
         std::vector<std::string> pragma_tokens = Botan::split_on(line.substr(6), ' ');
27✔
1468

1469
         if(pragma_tokens.empty()) {
27✔
1470
            throw Test_Error("Empty pragma found in " + m_data->current_source_name());
×
1471
         }
1472

1473
         if(pragma_tokens[0] != "cpuid") {
27✔
1474
            throw Test_Error("Unknown test pragma '" + line + "' in " + m_data->current_source_name());
×
1475
         }
1476

1477
         if(!Test_Registry::instance().needs_serialization(this->test_name())) {
54✔
1478
            throw Test_Error(Botan::fmt("'{}' used cpuid control but is not serialized", this->test_name()));
×
1479
         }
1480

1481
         m_data->set_cpu_flags(parse_cpuid_bits(pragma_tokens));
54✔
1482

1483
         continue;
27✔
1484
      } else if(line[0] == '#') {
157,091✔
1485
         throw Test_Error("Unknown test pragma '" + line + "' in " + m_data->current_source_name());
×
1486
      }
1487

1488
      if(line[0] == '[' && line[line.size() - 1] == ']') {
157,064✔
1489
         header = line.substr(1, line.size() - 2);
838✔
1490
         header_or_name = header;
838✔
1491
         test_cnt = 0;
838✔
1492
         vars.clear();
838✔
1493
         continue;
838✔
1494
      }
1495

1496
      const std::string test_id = "test " + std::to_string(test_cnt);
312,452✔
1497

1498
      auto equal_i = line.find_first_of('=');
156,226✔
1499

1500
      if(equal_i == std::string::npos) {
156,226✔
1501
         results.push_back(Test::Result::Failure(header_or_name, "invalid input '" + line + "'"));
×
1502
         continue;
×
1503
      }
1504

1505
      const std::string key = strip_ws(std::string(line.begin(), line.begin() + equal_i - 1));
312,452✔
1506
      const std::string val = strip_ws(std::string(line.begin() + equal_i + 1, line.end()));
312,452✔
1507

1508
      if(!m_data->known_key(key)) {
156,226✔
1509
         auto r = Test::Result::Failure(header_or_name, Botan::fmt("{} failed unknown key {}", test_id, key));
×
1510
         results.push_back(r);
×
1511
      }
×
1512

1513
      vars.add(key, val);
156,226✔
1514

1515
      if(key == m_data->output_key()) {
156,226✔
1516
         try {
48,541✔
1517
            for(const auto& req_key : m_data->required_keys()) {
245,689✔
1518
               if(!vars.has_key(req_key)) {
197,148✔
1519
                  auto r =
×
1520
                     Test::Result::Failure(header_or_name, Botan::fmt("{} missing required key {}", test_id, req_key));
×
1521
                  results.push_back(r);
×
1522
               }
×
1523
            }
1524

1525
            if(skip_this_test(header, vars)) {
48,541✔
1526
               continue;
141✔
1527
            }
1528

1529
            ++test_cnt;
48,400✔
1530

1531
            const uint64_t start = Test::timestamp();
48,400✔
1532

1533
            Test::Result result = run_one_test(header, vars);
48,400✔
1534
#if defined(BOTAN_HAS_CPUID)
1535
            if(!m_data->cpu_flags().empty()) {
48,400✔
1536
               for(const auto& cpuid_str : m_data->cpu_flags()) {
34,682✔
1537
                  if(const auto bit = Botan::CPUID::Feature::from_string(cpuid_str)) {
23,851✔
1538
                     if(Botan::CPUID::has(*bit)) {
23,851✔
1539
                        Botan::CPUID::clear_cpuid_bit(*bit);
17,531✔
1540
                        // now re-run the test
1541
                        result.merge(run_one_test(header, vars));
17,531✔
1542
                     }
1543
                  }
1544
               }
1545
               Botan::CPUID::initialize();
10,831✔
1546
            }
1547
#endif
1548
            result.set_ns_consumed(Test::timestamp() - start);
48,400✔
1549

1550
            if(result.tests_failed() > 0) {
48,400✔
1551
               std::ostringstream oss;
×
1552
               oss << "Test # " << test_cnt << " ";
×
1553
               if(!header.empty()) {
×
1554
                  oss << header << " ";
×
1555
               }
1556
               oss << "failed ";
×
1557

1558
               for(const auto& k : m_data->required_keys()) {
×
1559
                  oss << k << "=" << vars.get_req_str(k) << " ";
×
1560
               }
1561

1562
               result.test_note(oss.str());
×
1563
            }
×
1564
            results.push_back(result);
48,400✔
1565
         } catch(std::exception& e) {
48,400✔
1566
            std::ostringstream oss;
×
1567
            oss << "Test # " << test_cnt << " ";
×
1568
            if(!header.empty()) {
×
1569
               oss << header << " ";
×
1570
            }
1571

1572
            for(const auto& k : m_data->required_keys()) {
×
1573
               oss << k << "=" << vars.get_req_str(k) << " ";
×
1574
            }
1575

1576
            oss << "failed with exception '" << e.what() << "'";
×
1577

1578
            results.push_back(Test::Result::Failure(header_or_name, oss.str()));
×
1579
         }
×
1580

1581
         if(clear_between_callbacks()) {
48,400✔
1582
            vars.clear();
189,608✔
1583
         }
1584
      }
1585
   }
157,373✔
1586

1587
   if(results.empty()) {
194✔
1588
      return results;
1589
   }
1590

1591
   try {
194✔
1592
      std::vector<Test::Result> final_tests = run_final_tests();
194✔
1593
      results.insert(results.end(), final_tests.begin(), final_tests.end());
194✔
1594
   } catch(std::exception& e) {
194✔
1595
      results.push_back(Test::Result::Failure(header_or_name, "run_final_tests exception " + std::string(e.what())));
×
1596
   }
×
1597

1598
   return results;
1599
}
194✔
1600

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