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

randombit / botan / 21244058871

22 Jan 2026 07:50AM UTC coverage: 90.074% (-0.001%) from 90.075%
21244058871

push

github

web-flow
Merge pull request #5254 from Rohde-Schwarz/chore/tls_does_not_depend_on_secp

Avoid hard dependency on `pcurves_secp*` in the TLS module

102104 of 113356 relevant lines covered (90.07%)

11498532.38 hits per line

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

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

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

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

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

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

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

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

53
namespace Botan_Tests {
54

55
Test::Test() = default;
418✔
56

57
Test::~Test() = default;
563✔
58

59
Test::Result::Result(std::string who) : m_who(std::move(who)), m_timestamp(Test::timestamp()) {}
80,571✔
60

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

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

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

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

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

94
void Test::Result::test_note(const std::string& note, const char* extra) {
2,767✔
95
   if(!note.empty()) {
2,767✔
96
      std::ostringstream out;
2,767✔
97
      out << who() << " " << note;
2,767✔
98
      if(extra != nullptr) {
2,767✔
99
         out << ": " << extra;
12✔
100
      }
101
      m_log.push_back(out.str());
2,767✔
102
   }
2,767✔
103
}
2,767✔
104

105
void Test::Result::note_missing(const std::string& whatever) {
275✔
106
   static std::set<std::string> s_already_seen;
275✔
107

108
   if(!s_already_seen.contains(whatever)) {
275✔
109
      test_note("Skipping tests due to missing " + whatever);
5✔
110
      s_already_seen.insert(whatever);
5✔
111
   }
112
}
275✔
113

114
bool Test::Result::ThrowExpectations::check(const std::string& test_name, Test::Result& result) {
67,674✔
115
   m_consumed = true;
67,674✔
116

117
   try {
67,674✔
118
      m_fn();
67,674✔
119
      if(!m_expect_success) {
1,646✔
120
         return result.test_failure(test_name + " failed to throw expected exception");
2✔
121
      }
122
   } catch(const std::exception& ex) {
66,028✔
123
      if(m_expect_success) {
66,026✔
124
         return result.test_failure(test_name + " threw unexpected exception: " + ex.what());
2✔
125
      }
126
      if(m_expected_exception_check_fn && !m_expected_exception_check_fn(std::current_exception())) {
189,897✔
127
         return result.test_failure(test_name + " threw unexpected exception: " + ex.what());
4✔
128
      }
129
      if(m_expected_message.has_value() && m_expected_message.value() != ex.what()) {
66,023✔
130
         return result.test_failure(test_name + " threw exception with unexpected message (expected: '" +
3✔
131
                                    m_expected_message.value() + "', got: '" + ex.what() + "')");
6✔
132
      }
133
   } catch(...) {
66,028✔
134
      if(m_expect_success || m_expected_exception_check_fn || m_expected_message.has_value()) {
2✔
135
         return result.test_failure(test_name + " threw unexpected unknown exception");
1✔
136
      }
137
   }
2✔
138

139
   return result.test_success(test_name + " behaved as expected");
135,334✔
140
}
141

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

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

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

154
bool Test::Result::test_success(const std::string& note) {
3,610,690✔
155
   if(Test::options().log_success()) {
3,610,558✔
156
      test_note(note);
×
157
   }
158
   ++m_tests_passed;
3,610,690✔
159
   return true;
771,439✔
160
}
161

162
bool Test::Result::test_failure(const std::string& what, const std::string& error) {
1✔
163
   return test_failure(who() + " " + what + " with error " + error);
4✔
164
}
165

166
void Test::Result::test_failure(const std::string& what, const uint8_t buf[], size_t buf_len) {
1✔
167
   test_failure(who() + ": " + what + " buf len " + std::to_string(buf_len) + " value " +
5✔
168
                Botan::hex_encode(buf, buf_len));
1✔
169
}
1✔
170

171
bool Test::Result::test_failure(const std::string& err) {
25✔
172
   m_fail_log.push_back(err);
25✔
173

174
   if(Test::options().abort_on_first_fail() && m_who != "Failing Test") {
25✔
175
      std::abort();
×
176
   }
177
   return false;
25✔
178
}
179

180
namespace {
181

182
bool same_contents(const uint8_t x[], const uint8_t y[], size_t len) {
289,135✔
183
   return (len == 0) ? true : std::memcmp(x, y, len) == 0;
287,704✔
184
}
185

186
}  // namespace
187

188
bool Test::Result::test_ne(const std::string& what,
3,407✔
189
                           const uint8_t produced[],
190
                           size_t produced_len,
191
                           const uint8_t expected[],
192
                           size_t expected_len) {
193
   if(produced_len == expected_len && same_contents(produced, expected, expected_len)) {
3,407✔
194
      return test_failure(who() + ": " + what + " produced matching");
6✔
195
   }
196
   return test_success();
6,810✔
197
}
198

199
bool Test::Result::test_eq(const char* producer,
287,714✔
200
                           const std::string& what,
201
                           const uint8_t produced[],
202
                           size_t produced_size,
203
                           const uint8_t expected[],
204
                           size_t expected_size) {
205
   if(produced_size == expected_size && same_contents(produced, expected, expected_size)) {
287,714✔
206
      return test_success();
575,426✔
207
   }
208

209
   std::ostringstream err;
1✔
210

211
   err << who();
1✔
212

213
   if(producer != nullptr) {
1✔
214
      err << " producer '" << producer << "'";
×
215
   }
216

217
   err << " unexpected result for " << what;
1✔
218

219
   if(produced_size != expected_size) {
1✔
220
      err << " produced " << produced_size << " bytes expected " << expected_size;
1✔
221
   }
222

223
   std::vector<uint8_t> xor_diff(std::min(produced_size, expected_size));
2✔
224
   size_t bytes_different = 0;
1✔
225

226
   for(size_t i = 0; i != xor_diff.size(); ++i) {
4✔
227
      xor_diff[i] = produced[i] ^ expected[i];
3✔
228
      if(xor_diff[i] > 0) {
3✔
229
         bytes_different++;
3✔
230
      }
231
   }
232

233
   err << "\nProduced: " << Botan::hex_encode(produced, produced_size)
1✔
234
       << "\nExpected: " << Botan::hex_encode(expected, expected_size);
3✔
235

236
   if(bytes_different > 0) {
1✔
237
      err << "\nXOR Diff: " << Botan::hex_encode(xor_diff);
1✔
238
   }
239

240
   return test_failure(err.str());
1✔
241
}
1✔
242

243
bool Test::Result::test_is_nonempty(const std::string& what_is_it, const std::string& to_examine) {
38,301✔
244
   if(to_examine.empty()) {
38,301✔
245
      return test_failure(what_is_it + " was empty");
1✔
246
   }
247
   return test_success();
76,600✔
248
}
249

250
bool Test::Result::test_eq(const std::string& what, const std::string& produced, const std::string& expected) {
75,751✔
251
   return test_is_eq(what, produced, expected);
75,751✔
252
}
253

254
bool Test::Result::test_eq(const std::string& what, const char* produced, const char* expected) {
29✔
255
   return test_is_eq(what, std::string(produced), std::string(expected));
29✔
256
}
257

258
bool Test::Result::test_eq(const std::string& what, size_t produced, size_t expected) {
146,673✔
259
   return test_is_eq(what, produced, expected);
146,673✔
260
}
261

262
bool Test::Result::test_eq_sz(const std::string& what, size_t produced, size_t expected) {
45,789✔
263
   return test_is_eq(what, produced, expected);
45,789✔
264
}
265

266
bool Test::Result::test_eq(const std::string& what,
107✔
267
                           const Botan::OctetString& produced,
268
                           const Botan::OctetString& expected) {
269
   std::ostringstream out;
107✔
270
   out << m_who << " " << what;
107✔
271

272
   if(produced == expected) {
107✔
273
      out << " produced expected result " << produced.to_string();
214✔
274
      return test_success(out.str());
107✔
275
   } else {
276
      out << " produced unexpected result '" << produced.to_string() << "' expected '" << expected.to_string() << "'";
×
277
      return test_failure(out.str());
×
278
   }
279
}
107✔
280

281
bool Test::Result::test_lt(const std::string& what, size_t produced, size_t expected) {
5,639✔
282
   if(produced >= expected) {
5,639✔
283
      std::ostringstream err;
1✔
284
      err << m_who << " " << what;
1✔
285
      err << " unexpected result " << produced << " >= " << expected;
1✔
286
      return test_failure(err.str());
1✔
287
   }
1✔
288

289
   return test_success();
11,276✔
290
}
291

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

299
   return test_success();
2,043,270✔
300
}
301

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

311
   return test_success();
2,284,858✔
312
}
313

314
bool Test::Result::test_gt(const std::string& what, size_t produced, size_t expected) {
22,341✔
315
   if(produced <= expected) {
22,341✔
316
      std::ostringstream err;
×
317
      err << m_who;
×
318
      err << " " << what;
×
319
      err << " unexpected result " << produced << " <= " << expected;
×
320
      return test_failure(err.str());
×
321
   }
×
322

323
   return test_success();
44,682✔
324
}
325

326
bool Test::Result::test_ne(const std::string& what, const std::string& str1, const std::string& str2) {
26✔
327
   if(str1 != str2) {
26✔
328
      return test_success(str1 + " != " + str2);
50✔
329
   }
330

331
   return test_failure(who() + " " + what + " produced matching strings " + str1);
4✔
332
}
333

334
bool Test::Result::test_ne(const std::string& what, size_t produced, size_t expected) {
119✔
335
   if(produced != expected) {
119✔
336
      return test_success();
236✔
337
   }
338

339
   std::ostringstream err;
1✔
340
   err << who() << " " << what << " produced " << produced << " unexpected value";
1✔
341
   return test_failure(err.str());
1✔
342
}
1✔
343

344
#if defined(BOTAN_HAS_BIGINT)
345
bool Test::Result::test_eq(const std::string& what, const BigInt& produced, const BigInt& expected) {
252,643✔
346
   return test_is_eq(what, produced, expected);
252,643✔
347
}
348

349
bool Test::Result::test_ne(const std::string& what, const BigInt& produced, const BigInt& expected) {
97✔
350
   if(produced != expected) {
97✔
351
      return test_success();
192✔
352
   }
353

354
   std::ostringstream err;
1✔
355
   err << who() << " " << what << " produced " << produced << " prohibited value";
1✔
356
   return test_failure(err.str());
1✔
357
}
1✔
358
#endif
359

360
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
361
bool Test::Result::test_eq(const std::string& what, const Botan::EC_Point& a, const Botan::EC_Point& b) {
3,248✔
362
   //return test_is_eq(what, a, b);
363
   if(a == b) {
3,248✔
364
      return test_success();
6,496✔
365
   }
366

367
   std::ostringstream err;
×
368
   err << who() << " " << what << " a=(" << a.get_affine_x() << "," << a.get_affine_y() << ")"
×
369
       << " b=(" << b.get_affine_x() << "," << b.get_affine_y();
×
370
   return test_failure(err.str());
×
371
}
×
372
#endif
373

374
bool Test::Result::test_eq(const std::string& what, bool produced, bool expected) {
375,350✔
375
   return test_is_eq(what, produced, expected);
375,350✔
376
}
377

378
bool Test::Result::test_rc_init(const std::string& func, int rc) {
117✔
379
   if(rc == 0) {
117✔
380
      return test_success();
234✔
381
   } else {
382
      std::ostringstream msg;
×
383
      msg << m_who;
×
384
      msg << " " << func;
×
385

386
      // -40 is BOTAN_FFI_ERROR_NOT_IMPLEMENTED
387
      if(rc == -40) {
×
388
         msg << " returned not implemented";
×
389
      } else {
390
         msg << " unexpectedly failed with error code " << rc;
×
391
      }
392

393
      if(rc == -40) {
×
394
         this->test_note(msg.str());
×
395
      } else {
396
         this->test_failure(msg.str());
×
397
      }
398
      return false;
×
399
   }
×
400
}
401

402
bool Test::Result::test_rc(const std::string& func, int expected, int rc) {
416✔
403
   if(expected != rc) {
416✔
404
      std::ostringstream err;
1✔
405
      err << m_who;
1✔
406
      err << " call to " << func << " unexpectedly returned " << rc;
1✔
407
      err << " but expecting " << expected;
1✔
408
      return test_failure(err.str());
1✔
409
   }
1✔
410

411
   return test_success();
830✔
412
}
413

414
void Test::initialize(std::string test_name, CodeLocation location) {
418✔
415
   m_test_name = std::move(test_name);
418✔
416
   m_registration_location = location;
418✔
417
}
418✔
418

419
Botan::RandomNumberGenerator& Test::rng() const {
462,034✔
420
   if(!m_test_rng) {
462,034✔
421
      m_test_rng = Test::new_rng(m_test_name);
145✔
422
   }
423

424
   return *m_test_rng;
462,034✔
425
}
426

427
std::optional<std::string> Test::supported_ec_group_name(std::vector<std::string> preferred_groups) {
151✔
428
#if defined(BOTAN_HAS_ECC_GROUP)
429
   if(preferred_groups.empty()) {
151✔
430
      preferred_groups = {
149✔
431
         "secp256r1",
432
         "brainpool256r1",
433
         "secp384r1",
434
         "brainpool384r1",
435
         "secp521r1",
436
         "brainpool512r1",
437
      };
1,192✔
438
   }
439

440
   for(const auto& group : preferred_groups) {
151✔
441
      if(Botan::EC_Group::supports_named_group(group)) {
151✔
442
         return group;
151✔
443
      }
444
   }
445
#else
446
   BOTAN_UNUSED(preferred_groups);
447
#endif
448

449
   return std::nullopt;
×
450
}
298✔
451

452
std::vector<uint8_t> Test::mutate_vec(const std::vector<uint8_t>& v,
96,686✔
453
                                      Botan::RandomNumberGenerator& rng,
454
                                      bool maybe_resize,
455
                                      size_t min_offset) {
456
   std::vector<uint8_t> r = v;
96,686✔
457

458
   if(maybe_resize && (r.empty() || rng.next_byte() < 32)) {
110,248✔
459
      // TODO: occasionally truncate, insert at random index
460
      const size_t add = 1 + (rng.next_byte() % 16);
2,142✔
461
      r.resize(r.size() + add);
2,142✔
462
      rng.randomize(&r[r.size() - add], add);
2,142✔
463
   }
464

465
   if(r.size() > min_offset) {
96,686✔
466
      const size_t offset = std::max<size_t>(min_offset, rng.next_byte() % r.size());
95,148✔
467
      const uint8_t perturb = rng.next_nonzero_byte();
95,148✔
468
      r[offset] ^= perturb;
95,148✔
469
   }
470

471
   return r;
96,686✔
472
}
×
473

474
std::vector<std::string> Test::possible_providers(const std::string& /*alg*/) {
×
475
   return Test::provider_filter({"base"});
×
476
}
477

478
//static
479
std::string Test::format_time(uint64_t nanoseconds) {
1,469✔
480
   std::ostringstream o;
1,469✔
481

482
   if(nanoseconds > 1000000000) {
1,469✔
483
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000000.0 << " sec";
155✔
484
   } else {
485
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000.0 << " msec";
1,314✔
486
   }
487

488
   return o.str();
2,938✔
489
}
1,469✔
490

491
Test::Result::Result(std::string who, const std::vector<Result>& downstream_results) : Result(std::move(who)) {
14✔
492
   for(const auto& result : downstream_results) {
82✔
493
      merge(result, true /* ignore non-matching test names */);
68✔
494
   }
495
}
14✔
496

497
// TODO: this should move to `StdoutReporter`
498
std::string Test::Result::result_string() const {
2,540✔
499
   const bool verbose = Test::options().verbose();
2,540✔
500

501
   if(tests_run() == 0 && !verbose) {
2,540✔
502
      return "";
20✔
503
   }
504

505
   std::ostringstream report;
2,520✔
506

507
   report << who() << " ran ";
2,520✔
508

509
   if(tests_run() == 0) {
2,520✔
510
      report << "ZERO";
×
511
   } else {
512
      report << tests_run();
2,520✔
513
   }
514
   report << " tests";
2,520✔
515

516
   if(m_ns_taken > 0) {
2,520✔
517
      report << " in " << format_time(m_ns_taken);
2,936✔
518
   }
519

520
   if(tests_failed() > 0) {
2,520✔
521
      report << " " << tests_failed() << " FAILED";
25✔
522
   } else {
523
      report << " all ok";
2,495✔
524
   }
525

526
   report << "\n";
2,520✔
527

528
   for(size_t i = 0; i != m_fail_log.size(); ++i) {
2,545✔
529
      report << "Failure " << (i + 1) << ": " << m_fail_log[i];
25✔
530
      if(m_where) {
25✔
531
         report << " (at " << m_where->path << ":" << m_where->line << ")";
×
532
      }
533
      report << "\n";
25✔
534
   }
535

536
   if(!m_fail_log.empty() || tests_run() == 0 || verbose) {
2,520✔
537
      for(size_t i = 0; i != m_log.size(); ++i) {
25✔
538
         report << "Note " << (i + 1) << ": " << m_log[i] << "\n";
×
539
      }
540
   }
541

542
   return report.str();
2,520✔
543
}
2,520✔
544

545
namespace {
546

547
class Test_Registry {
548
   public:
549
      static Test_Registry& instance() {
1,276✔
550
         static Test_Registry registry;
1,277✔
551
         return registry;
1,276✔
552
      }
553

554
      void register_test(const std::string& category,
418✔
555
                         const std::string& name,
556
                         bool smoke_test,
557
                         bool needs_serialization,
558
                         std::function<std::unique_ptr<Test>()> maker_fn) {
559
         if(m_tests.contains(name)) {
418✔
560
            throw Test_Error("Duplicate registration of test '" + name + "'");
×
561
         }
562

563
         if(m_tests.contains(category)) {
418✔
564
            throw Test_Error("'" + category + "' cannot be used as category, test exists");
×
565
         }
566

567
         if(m_categories.contains(name)) {
418✔
568
            throw Test_Error("'" + name + "' cannot be used as test name, category exists");
×
569
         }
570

571
         if(smoke_test) {
418✔
572
            m_smoke_tests.push_back(name);
10✔
573
         }
574

575
         if(needs_serialization) {
418✔
576
            m_mutexed_tests.push_back(name);
21✔
577
         }
578

579
         m_tests.emplace(name, std::move(maker_fn));
418✔
580
         m_categories.emplace(category, name);
418✔
581
      }
418✔
582

583
      std::unique_ptr<Test> get_test(const std::string& test_name) const {
418✔
584
         auto i = m_tests.find(test_name);
418✔
585
         if(i != m_tests.end()) {
418✔
586
            return i->second();
418✔
587
         }
588
         return nullptr;
×
589
      }
590

591
      std::vector<std::string> registered_tests() const {
×
592
         std::vector<std::string> s;
×
593
         s.reserve(m_tests.size());
×
594
         for(auto&& i : m_tests) {
×
595
            s.push_back(i.first);
×
596
         }
597
         return s;
×
598
      }
×
599

600
      std::vector<std::string> registered_test_categories() const {
×
601
         std::vector<std::string> s;
×
602
         s.reserve(m_categories.size());
×
603
         for(auto&& i : m_categories) {
×
604
            s.push_back(i.first);
×
605
         }
606
         return s;
×
607
      }
×
608

609
      std::vector<std::string> filter_registered_tests(const std::vector<std::string>& requested,
1✔
610
                                                       const std::vector<std::string>& to_be_skipped) {
611
         std::vector<std::string> result;
1✔
612

613
         std::set<std::string> to_be_skipped_set(to_be_skipped.begin(), to_be_skipped.end());
1✔
614
         // TODO: this is O(n^2), but we have a relatively small number of tests.
615
         auto insert_if_not_exists_and_not_skipped = [&](const std::string& test_name) {
419✔
616
            if(!Botan::value_exists(result, test_name) && !to_be_skipped_set.contains(test_name)) {
418✔
617
               result.push_back(test_name);
408✔
618
            }
619
         };
419✔
620

621
         if(requested.empty()) {
1✔
622
            /*
623
            If nothing was requested on the command line, run everything. First
624
            run the "essentials" to smoke test, then everything else in
625
            alphabetical order.
626
            */
627
            result = m_smoke_tests;
1✔
628
            for(const auto& [test_name, _] : m_tests) {
419✔
629
               insert_if_not_exists_and_not_skipped(test_name);
418✔
630
            }
631
         } else {
632
            for(const auto& r : requested) {
×
633
               if(m_tests.contains(r)) {
×
634
                  insert_if_not_exists_and_not_skipped(r);
×
635
               } else if(auto elems = m_categories.equal_range(r); elems.first != m_categories.end()) {
×
636
                  for(; elems.first != elems.second; ++elems.first) {
×
637
                     insert_if_not_exists_and_not_skipped(elems.first->second);
×
638
                  }
639
               } else {
640
                  throw Test_Error("Unknown test suite or category: " + r);
×
641
               }
642
            }
643
         }
644

645
         return result;
1✔
646
      }
1✔
647

648
      bool needs_serialization(const std::string& test_name) const {
439✔
649
         return Botan::value_exists(m_mutexed_tests, test_name);
21✔
650
      }
651

652
   private:
653
      Test_Registry() = default;
1✔
654

655
   private:
656
      std::map<std::string, std::function<std::unique_ptr<Test>()>> m_tests;
657
      std::multimap<std::string, std::string> m_categories;
658
      std::vector<std::string> m_smoke_tests;
659
      std::vector<std::string> m_mutexed_tests;
660
};
661

662
}  // namespace
663

664
// static Test:: functions
665

666
//static
667
void Test::register_test(const std::string& category,
418✔
668
                         const std::string& name,
669
                         bool smoke_test,
670
                         bool needs_serialization,
671
                         std::function<std::unique_ptr<Test>()> maker_fn) {
672
   Test_Registry::instance().register_test(category, name, smoke_test, needs_serialization, std::move(maker_fn));
836✔
673
}
418✔
674

675
//static
676
uint64_t Test::timestamp() {
179,164✔
677
   auto now = std::chrono::system_clock::now().time_since_epoch();
179,164✔
678
   return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
2,383✔
679
}
680

681
//static
682
std::vector<Test::Result> Test::flatten_result_lists(std::vector<std::vector<Test::Result>> result_lists) {
4✔
683
   std::vector<Test::Result> results;
4✔
684
   for(auto& result_list : result_lists) {
26✔
685
      for(auto& result : result_list) {
71✔
686
         results.emplace_back(std::move(result));
49✔
687
      }
688
   }
689
   return results;
4✔
690
}
×
691

692
//static
693
std::vector<std::string> Test::registered_tests() {
×
694
   return Test_Registry::instance().registered_tests();
×
695
}
696

697
//static
698
std::vector<std::string> Test::registered_test_categories() {
×
699
   return Test_Registry::instance().registered_test_categories();
×
700
}
701

702
//static
703
std::unique_ptr<Test> Test::get_test(const std::string& test_name) {
418✔
704
   return Test_Registry::instance().get_test(test_name);
418✔
705
}
706

707
//static
708
bool Test::test_needs_serialization(const std::string& test_name) {
418✔
709
   return Test_Registry::instance().needs_serialization(test_name);
418✔
710
}
711

712
//static
713
std::vector<std::string> Test::filter_registered_tests(const std::vector<std::string>& requested,
1✔
714
                                                       const std::vector<std::string>& to_be_skipped) {
715
   return Test_Registry::instance().filter_registered_tests(requested, to_be_skipped);
1✔
716
}
717

718
//static
719
std::string Test::temp_file_name(const std::string& basename) {
21✔
720
   // TODO add a --tmp-dir option to the tests to specify where these files go
721

722
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
723

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

727
   const int fd = ::mkstemp(mkstemp_basename.data());
21✔
728

729
   // error
730
   if(fd < 0) {
21✔
731
      return "";
×
732
   }
733

734
   ::close(fd);
21✔
735

736
   return mkstemp_basename;
21✔
737
#else
738
   // For now just create the temp in the current working directory
739
   return basename;
740
#endif
741
}
21✔
742

743
bool Test::copy_file(const std::string& from, const std::string& to) {
1✔
744
#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(__cpp_lib_filesystem)
745
   std::error_code ec;  // don't throw, just return false on error
1✔
746
   return std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing, ec);
1✔
747
#else
748
   // TODO: implement fallbacks to POSIX or WIN32
749
   // ... but then again: it's 2023 and we're using C++20 :o)
750
   BOTAN_UNUSED(from, to);
751
   throw Botan::No_Filesystem_Access();
752
#endif
753
}
754

755
std::string Test::read_data_file(const std::string& path) {
38✔
756
   const std::string fsname = Test::data_file(path);
38✔
757
   std::ifstream file(fsname.c_str());
38✔
758
   if(!file.good()) {
38✔
759
      throw Test_Error("Error reading from " + fsname);
×
760
   }
761

762
   return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
76✔
763
}
38✔
764

765
std::vector<uint8_t> Test::read_binary_data_file(const std::string& path) {
34✔
766
   const std::string fsname = Test::data_file(path);
34✔
767
   std::ifstream file(fsname.c_str(), std::ios::binary);
34✔
768
   if(!file.good()) {
34✔
769
      throw Test_Error("Error reading from " + fsname);
×
770
   }
771

772
   std::vector<uint8_t> contents;
34✔
773

774
   while(file.good()) {
74✔
775
      std::vector<uint8_t> buf(4096);
40✔
776
      file.read(reinterpret_cast<char*>(buf.data()), buf.size());
40✔
777
      const size_t got = static_cast<size_t>(file.gcount());
40✔
778

779
      if(got == 0 && file.eof()) {
40✔
780
         break;
781
      }
782

783
      contents.insert(contents.end(), buf.data(), buf.data() + got);
40✔
784
   }
40✔
785

786
   return contents;
68✔
787
}
34✔
788

789
// static member variables of Test
790

791
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
792
Test_Options Test::m_opts;
793
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
794
std::string Test::m_test_rng_seed;
795

796
//static
797
void Test::set_test_options(const Test_Options& opts) {
1✔
798
   m_opts = opts;
1✔
799
}
1✔
800

801
namespace {
802

803
/*
804
* This is a fast, simple, deterministic PRNG that's used for running
805
* the tests. It is not intended to be cryptographically secure.
806
*/
807
class Testsuite_RNG final : public Botan::RandomNumberGenerator {
8✔
808
   public:
809
      std::string name() const override { return "Testsuite_RNG"; }
×
810

811
      void clear() override { m_x = 0; }
×
812

813
      bool accepts_input() const override { return true; }
×
814

815
      bool is_seeded() const override { return true; }
277,336✔
816

817
      void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> input) override {
10,231,116✔
818
         for(const auto byte : input) {
10,231,116✔
819
            mix(byte);
×
820
         }
821

822
         for(auto& byte : output) {
73,442,979✔
823
            byte = mix();
63,211,863✔
824
         }
825
      }
10,231,116✔
826

827
      Testsuite_RNG(std::string_view seed, std::string_view test_name) : m_x(0) {
276✔
828
         for(const char c : seed) {
8,280✔
829
            this->mix(static_cast<uint8_t>(c));
8,004✔
830
         }
831
         for(const char c : test_name) {
5,578✔
832
            this->mix(static_cast<uint8_t>(c));
5,302✔
833
         }
834
      }
276✔
835

836
   private:
837
      uint8_t mix(uint8_t input = 0) {
63,225,169✔
838
         m_x ^= input;
63,225,169✔
839
         m_x *= 0xF2E16957;
63,225,169✔
840
         m_x += 0xE50B590F;
63,225,169✔
841
         return static_cast<uint8_t>(m_x >> 27);
63,225,169✔
842
      }
843

844
      uint64_t m_x;
845
};
846

847
}  // namespace
848

849
//static
850
void Test::set_test_rng_seed(std::span<const uint8_t> seed, size_t epoch) {
1✔
851
   m_test_rng_seed = Botan::fmt("seed={} epoch={}", Botan::hex_encode(seed), epoch);
1✔
852
}
1✔
853

854
//static
855
std::unique_ptr<Botan::RandomNumberGenerator> Test::new_rng(std::string_view test_name) {
268✔
856
   return std::make_unique<Testsuite_RNG>(m_test_rng_seed, test_name);
268✔
857
}
858

859
//static
860
std::shared_ptr<Botan::RandomNumberGenerator> Test::new_shared_rng(std::string_view test_name) {
8✔
861
   return std::make_shared<Testsuite_RNG>(m_test_rng_seed, test_name);
8✔
862
}
863

864
//static
865
std::string Test::data_file(const std::string& file) {
772✔
866
   return options().data_dir() + "/" + file;
1,544✔
867
}
868

869
//static
870
std::string Test::data_dir(const std::string& subdir) {
1✔
871
   return options().data_dir() + "/" + subdir;
2✔
872
}
873

874
//static
875
std::vector<std::string> Test::files_in_data_dir(const std::string& subdir) {
393✔
876
   auto fs = Botan::get_files_recursive(options().data_dir() + "/" + subdir);
1,179✔
877
   if(fs.empty()) {
393✔
878
      throw Test_Error("Test::files_in_data_dir encountered empty subdir " + subdir);
×
879
   }
880
   return fs;
393✔
881
}
×
882

883
//static
884
std::string Test::data_file_as_temporary_copy(const std::string& what) {
1✔
885
   auto tmp_basename = what;
1✔
886
   std::replace(tmp_basename.begin(), tmp_basename.end(), '/', '_');
1✔
887
   auto temp_file = temp_file_name("tmp-" + tmp_basename);
1✔
888
   if(temp_file.empty()) {
1✔
889
      return "";
×
890
   }
891
   if(!Test::copy_file(data_file(what), temp_file)) {
1✔
892
      return "";
×
893
   }
894
   return temp_file;
1✔
895
}
1✔
896

897
//static
898
std::vector<std::string> Test::provider_filter(const std::vector<std::string>& providers) {
48,510✔
899
   if(m_opts.provider().empty()) {
48,510✔
900
      return providers;
48,510✔
901
   }
902
   for(auto&& provider : providers) {
×
903
      if(provider == m_opts.provider()) {
×
904
         return std::vector<std::string>{provider};
×
905
      }
906
   }
907
   return std::vector<std::string>{};
×
908
}
×
909

910
std::string Test::random_password(Botan::RandomNumberGenerator& rng) {
222✔
911
   const size_t len = 1 + rng.next_byte() % 32;
222✔
912
   return Botan::hex_encode(rng.random_vec(len));
444✔
913
}
914

915
size_t Test::random_index(Botan::RandomNumberGenerator& rng, size_t max) {
8,062✔
916
   return Botan::load_be(rng.random_array<8>()) % max;
8,062✔
917
}
918

919
void VarMap::clear() {
34,133✔
920
   m_vars.clear();
×
921
}
×
922

923
namespace {
924

925
bool varmap_pair_lt(const std::pair<std::string, std::string>& kv, const std::string& k) {
1,706,667✔
926
   return kv.first < k;
1,706,667✔
927
}
928

929
}  // namespace
930

931
void VarMap::add(const std::string& key, const std::string& value) {
155,679✔
932
   auto i = std::lower_bound(m_vars.begin(), m_vars.end(), key, varmap_pair_lt);
155,679✔
933

934
   if(i != m_vars.end() && i->first == key) {
155,679✔
935
      i->second = value;
44,405✔
936
   } else {
937
      m_vars.emplace(i, key, value);
111,274✔
938
   }
939
}
155,679✔
940

941
std::optional<std::string> VarMap::get_var(const std::string& key) const {
565,900✔
942
   auto i = std::lower_bound(m_vars.begin(), m_vars.end(), key, varmap_pair_lt);
565,900✔
943

944
   if(i != m_vars.end() && i->first == key) {
565,900✔
945
      return i->second;
496,045✔
946
   } else {
947
      return {};
69,855✔
948
   }
949
}
950

951
bool VarMap::has_key(const std::string& key) const {
213,150✔
952
   return get_var(key).has_value();
213,150✔
953
}
954

955
std::string VarMap::get_req_str(const std::string& key) const {
258,035✔
956
   auto var = get_var(key);
258,035✔
957
   if(var) {
258,035✔
958
      return *var;
516,070✔
959
   } else {
960
      throw Test_Error("Test missing variable " + key);
×
961
   }
962
}
258,035✔
963

964
std::vector<std::vector<uint8_t>> VarMap::get_req_bin_list(const std::string& key) const {
12✔
965
   const auto var = get_req_str(key);
12✔
966

967
   std::vector<std::vector<uint8_t>> bin_list;
12✔
968

969
   for(auto&& part : Botan::split_on(var, ',')) {
62✔
970
      try {
50✔
971
         bin_list.push_back(Botan::hex_decode(part));
100✔
972
      } catch(std::exception& e) {
×
973
         std::ostringstream oss;
×
974
         oss << "Bad input '" << part << "'"
×
975
             << " in binary list key " << key << " - " << e.what();
×
976
         throw Test_Error(oss.str());
×
977
      }
×
978
   }
12✔
979

980
   return bin_list;
12✔
981
}
12✔
982

983
std::vector<uint8_t> VarMap::get_req_bin(const std::string& key) const {
162,134✔
984
   const auto var = get_req_str(key);
162,134✔
985

986
   try {
162,134✔
987
      if(var.starts_with("0x")) {
162,134✔
988
         if(var.size() % 2 == 0) {
×
989
            return Botan::hex_decode(var.substr(2));
×
990
         } else {
991
            std::string z = var;
×
992
            std::swap(z[0], z[1]);  // swap 0x to x0 then remove x
×
993
            return Botan::hex_decode(z.substr(1));
×
994
         }
×
995
      } else {
996
         return Botan::hex_decode(var);
162,134✔
997
      }
998
   } catch(std::exception& e) {
×
999
      std::ostringstream oss;
×
1000
      oss << "Bad input '" << var << "'"
×
1001
          << " for key " << key << " - " << e.what();
×
1002
      throw Test_Error(oss.str());
×
1003
   }
×
1004
}
162,134✔
1005

1006
std::string VarMap::get_opt_str(const std::string& key, const std::string& def_value) const {
14,378✔
1007
   if(auto v = get_var(key)) {
14,378✔
1008
      return *v;
57✔
1009
   } else {
1010
      return def_value;
28,699✔
1011
   }
14,378✔
1012
}
1013

1014
bool VarMap::get_req_bool(const std::string& key) const {
39✔
1015
   const auto var = get_req_str(key);
39✔
1016

1017
   if(var == "true") {
39✔
1018
      return true;
1019
   } else if(var == "false") {
23✔
1020
      return false;
1021
   } else {
1022
      throw Test_Error("Invalid boolean for key '" + key + "' value '" + var + "'");
×
1023
   }
1024
}
39✔
1025

1026
size_t VarMap::get_req_sz(const std::string& key) const {
4,547✔
1027
   return Botan::to_u32bit(get_req_str(key));
4,547✔
1028
}
1029

1030
uint8_t VarMap::get_req_u8(const std::string& key) const {
17✔
1031
   const size_t s = this->get_req_sz(key);
17✔
1032
   if(s > 256) {
17✔
1033
      throw Test_Error("Invalid " + key + " expected uint8_t got " + std::to_string(s));
×
1034
   }
1035
   return static_cast<uint8_t>(s);
17✔
1036
}
1037

1038
uint32_t VarMap::get_req_u32(const std::string& key) const {
14✔
1039
   return static_cast<uint32_t>(get_req_sz(key));
14✔
1040
}
1041

1042
uint64_t VarMap::get_req_u64(const std::string& key) const {
17✔
1043
   const auto var = get_req_str(key);
17✔
1044
   try {
17✔
1045
      return std::stoull(var);
17✔
1046
   } catch(std::exception&) {
×
1047
      throw Test_Error("Invalid u64 value '" + var + "'");
×
1048
   }
×
1049
}
17✔
1050

1051
size_t VarMap::get_opt_sz(const std::string& key, const size_t def_value) const {
31,379✔
1052
   if(auto v = get_var(key)) {
31,379✔
1053
      return Botan::to_u32bit(*v);
12,244✔
1054
   } else {
1055
      return def_value;
1056
   }
31,379✔
1057
}
1058

1059
uint64_t VarMap::get_opt_u64(const std::string& key, const uint64_t def_value) const {
3,538✔
1060
   if(auto v = get_var(key)) {
3,538✔
1061
      try {
641✔
1062
         return std::stoull(*v);
3,538✔
1063
      } catch(std::exception&) {
×
1064
         throw Test_Error("Invalid u64 value '" + *v + "'");
×
1065
      }
×
1066
   } else {
1067
      return def_value;
1068
   }
3,538✔
1069
}
1070

1071
std::vector<uint8_t> VarMap::get_opt_bin(const std::string& key) const {
45,340✔
1072
   if(auto v = get_var(key)) {
45,340✔
1073
      try {
15,606✔
1074
         return Botan::hex_decode(*v);
15,606✔
1075
      } catch(std::exception&) {
×
1076
         throw Test_Error("Test invalid hex input '" + *v + "'" + +" for key " + key);
×
1077
      }
×
1078
   } else {
1079
      return {};
29,734✔
1080
   };
45,340✔
1081
}
1082

1083
#if defined(BOTAN_HAS_BIGINT)
1084
Botan::BigInt VarMap::get_req_bn(const std::string& key) const {
38,502✔
1085
   const auto var = get_req_str(key);
38,502✔
1086

1087
   try {
38,502✔
1088
      return Botan::BigInt(var);
38,502✔
1089
   } catch(std::exception&) {
×
1090
      throw Test_Error("Test invalid bigint input '" + var + "' for key " + key);
×
1091
   }
×
1092
}
38,502✔
1093

1094
Botan::BigInt VarMap::get_opt_bn(const std::string& key, const Botan::BigInt& def_value) const {
80✔
1095
   if(auto v = get_var(key)) {
80✔
1096
      try {
56✔
1097
         return Botan::BigInt(*v);
56✔
1098
      } catch(std::exception&) {
×
1099
         throw Test_Error("Test invalid bigint input '" + *v + "' for key " + key);
×
1100
      }
×
1101
   } else {
1102
      return def_value;
24✔
1103
   }
80✔
1104
}
1105
#endif
1106

1107
class Text_Based_Test::Text_Based_Test_Data {
1108
   public:
1109
      Text_Based_Test_Data(const std::string& data_src,
180✔
1110
                           const std::string& required_keys_str,
1111
                           const std::string& optional_keys_str) :
180✔
1112
            m_data_src(data_src) {
360✔
1113
         if(required_keys_str.empty()) {
180✔
1114
            throw Test_Error("Invalid test spec");
×
1115
         }
1116

1117
         m_required_keys = Botan::split_on(required_keys_str, ',');
180✔
1118
         std::vector<std::string> optional_keys = Botan::split_on(optional_keys_str, ',');
180✔
1119

1120
         m_all_keys.insert(m_required_keys.begin(), m_required_keys.end());
180✔
1121
         m_all_keys.insert(optional_keys.begin(), optional_keys.end());
180✔
1122
         m_output_key = m_required_keys.at(m_required_keys.size() - 1);
180✔
1123
      }
180✔
1124

1125
      std::string get_next_line();
1126

1127
      const std::string& current_source_name() const { return m_cur_src_name; }
×
1128

1129
      bool known_key(const std::string& key) const;
1130

1131
      const std::vector<std::string>& required_keys() const { return m_required_keys; }
48,241✔
1132

1133
      const std::string& output_key() const { return m_output_key; }
155,679✔
1134

1135
      const std::vector<std::string>& cpu_flags() const { return m_cpu_flags; }
48,102✔
1136

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

1139
      const std::string& initial_data_src_name() const { return m_data_src; }
180✔
1140

1141
   private:
1142
      std::string m_data_src;
1143
      std::vector<std::string> m_required_keys;
1144
      std::unordered_set<std::string> m_all_keys;
1145
      std::string m_output_key;
1146

1147
      bool m_first = true;
1148
      std::unique_ptr<std::istream> m_cur;
1149
      std::string m_cur_src_name;
1150
      std::deque<std::string> m_srcs;
1151
      std::vector<std::string> m_cpu_flags;
1152
};
1153

1154
Text_Based_Test::Text_Based_Test(const std::string& data_src,
180✔
1155
                                 const std::string& required_keys,
1156
                                 const std::string& optional_keys) :
180✔
1157
      m_data(std::make_unique<Text_Based_Test_Data>(data_src, required_keys, optional_keys)) {}
180✔
1158

1159
Text_Based_Test::~Text_Based_Test() = default;
180✔
1160

1161
std::string Text_Based_Test::Text_Based_Test_Data::get_next_line() {
156,684✔
1162
   while(true) {
156,941✔
1163
      if(m_cur == nullptr || m_cur->good() == false) {
156,941✔
1164
         if(m_srcs.empty()) {
448✔
1165
            if(m_first) {
360✔
1166
               if(m_data_src.ends_with(".vec")) {
180✔
1167
                  m_srcs.push_back(Test::data_file(m_data_src));
336✔
1168
               } else {
1169
                  const auto fs = Test::files_in_data_dir(m_data_src);
12✔
1170
                  m_srcs.assign(fs.begin(), fs.end());
12✔
1171
                  if(m_srcs.empty()) {
12✔
1172
                     throw Test_Error("Error reading test data dir " + m_data_src);
×
1173
                  }
1174
               }
12✔
1175

1176
               m_first = false;
180✔
1177
            } else {
1178
               return "";  // done
180✔
1179
            }
1180
         }
1181

1182
         m_cur = std::make_unique<std::ifstream>(m_srcs[0]);
356✔
1183
         m_cur_src_name = m_srcs[0];
268✔
1184

1185
#if defined(BOTAN_HAS_CPUID)
1186
         // Reinit cpuid on new file if needed
1187
         if(m_cpu_flags.empty() == false) {
268✔
1188
            m_cpu_flags.clear();
19✔
1189
            Botan::CPUID::initialize();
19✔
1190
         }
1191
#endif
1192

1193
         if(!m_cur->good()) {
268✔
1194
            throw Test_Error("Could not open input file '" + m_cur_src_name);
×
1195
         }
1196

1197
         m_srcs.pop_front();
268✔
1198
      }
1199

1200
      while(m_cur->good()) {
227,174✔
1201
         std::string line;
226,917✔
1202
         std::getline(*m_cur, line);
226,917✔
1203

1204
         if(line.empty()) {
226,917✔
1205
            continue;
55,157✔
1206
         }
1207

1208
         if(line[0] == '#') {
171,760✔
1209
            if(line.starts_with("#test ")) {
15,277✔
1210
               return line;
21✔
1211
            } else {
1212
               continue;
15,256✔
1213
            }
1214
         }
1215

1216
         return line;
156,483✔
1217
      }
226,917✔
1218
   }
1219
}
1220

1221
bool Text_Based_Test::Text_Based_Test_Data::known_key(const std::string& key) const {
155,679✔
1222
   return m_all_keys.contains(key);
155,679✔
1223
}
1224

1225
namespace {
1226

1227
// strips leading and trailing but not internal whitespace
1228
std::string strip_ws(const std::string& in) {
311,358✔
1229
   const char* whitespace = " ";
311,358✔
1230

1231
   const auto first_c = in.find_first_not_of(whitespace);
311,358✔
1232
   if(first_c == std::string::npos) {
311,358✔
1233
      return "";
1,033✔
1234
   }
1235

1236
   const auto last_c = in.find_last_not_of(whitespace);
310,325✔
1237

1238
   return in.substr(first_c, last_c - first_c + 1);
310,325✔
1239
}
1240

1241
std::vector<std::string> parse_cpuid_bits(const std::vector<std::string>& tok) {
21✔
1242
   std::vector<std::string> bits;
21✔
1243

1244
#if defined(BOTAN_HAS_CPUID)
1245
   for(size_t i = 1; i < tok.size(); ++i) {
106✔
1246
      if(auto bit = Botan::CPUID::bit_from_string(tok[i])) {
85✔
1247
         bits.push_back(bit->to_string());
98✔
1248
      }
1249
   }
1250
#else
1251
   BOTAN_UNUSED(tok);
1252
#endif
1253

1254
   return bits;
21✔
1255
}
×
1256

1257
}  // namespace
1258

1259
bool Text_Based_Test::skip_this_test(const std::string& /*header*/, const VarMap& /*vars*/) {
32,462✔
1260
   return false;
32,462✔
1261
}
1262

1263
std::vector<Test::Result> Text_Based_Test::run() {
180✔
1264
   std::vector<Test::Result> results;
180✔
1265

1266
   std::string header;
180✔
1267
   std::string header_or_name = m_data->initial_data_src_name();
180✔
1268
   VarMap vars;
180✔
1269
   size_t test_cnt = 0;
180✔
1270

1271
   while(true) {
156,684✔
1272
      const std::string line = m_data->get_next_line();
156,684✔
1273
      if(line.empty()) {
156,684✔
1274
         // EOF
1275
         break;
1276
      }
1277

1278
      if(line.starts_with("#test ")) {
156,504✔
1279
         std::vector<std::string> pragma_tokens = Botan::split_on(line.substr(6), ' ');
21✔
1280

1281
         if(pragma_tokens.empty()) {
21✔
1282
            throw Test_Error("Empty pragma found in " + m_data->current_source_name());
×
1283
         }
1284

1285
         if(pragma_tokens[0] != "cpuid") {
21✔
1286
            throw Test_Error("Unknown test pragma '" + line + "' in " + m_data->current_source_name());
×
1287
         }
1288

1289
         if(!Test_Registry::instance().needs_serialization(this->test_name())) {
21✔
1290
            throw Test_Error(Botan::fmt("'{}' used cpuid control but is not serialized", this->test_name()));
×
1291
         }
1292

1293
         m_data->set_cpu_flags(parse_cpuid_bits(pragma_tokens));
42✔
1294

1295
         continue;
21✔
1296
      } else if(line[0] == '#') {
156,504✔
1297
         throw Test_Error("Unknown test pragma '" + line + "' in " + m_data->current_source_name());
×
1298
      }
1299

1300
      if(line[0] == '[' && line[line.size() - 1] == ']') {
156,483✔
1301
         header = line.substr(1, line.size() - 2);
804✔
1302
         header_or_name = header;
804✔
1303
         test_cnt = 0;
804✔
1304
         vars.clear();
804✔
1305
         continue;
804✔
1306
      }
1307

1308
      const std::string test_id = "test " + std::to_string(test_cnt);
311,358✔
1309

1310
      auto equal_i = line.find_first_of('=');
155,679✔
1311

1312
      if(equal_i == std::string::npos) {
155,679✔
1313
         results.push_back(Test::Result::Failure(header_or_name, "invalid input '" + line + "'"));
×
1314
         continue;
×
1315
      }
1316

1317
      const std::string key = strip_ws(std::string(line.begin(), line.begin() + equal_i - 1));
311,358✔
1318
      const std::string val = strip_ws(std::string(line.begin() + equal_i + 1, line.end()));
311,358✔
1319

1320
      if(!m_data->known_key(key)) {
155,679✔
1321
         auto r = Test::Result::Failure(header_or_name, Botan::fmt("{} failed unknown key {}", test_id, key));
×
1322
         results.push_back(r);
×
1323
      }
×
1324

1325
      vars.add(key, val);
155,679✔
1326

1327
      if(key == m_data->output_key()) {
155,679✔
1328
         try {
48,241✔
1329
            for(const auto& req_key : m_data->required_keys()) {
244,983✔
1330
               if(!vars.has_key(req_key)) {
196,742✔
1331
                  auto r =
×
1332
                     Test::Result::Failure(header_or_name, Botan::fmt("{} missing required key {}", test_id, req_key));
×
1333
                  results.push_back(r);
×
1334
               }
×
1335
            }
1336

1337
            if(skip_this_test(header, vars)) {
48,241✔
1338
               continue;
139✔
1339
            }
1340

1341
            ++test_cnt;
48,102✔
1342

1343
            const uint64_t start = Test::timestamp();
48,102✔
1344

1345
            Test::Result result = run_one_test(header, vars);
48,102✔
1346
#if defined(BOTAN_HAS_CPUID)
1347
            if(!m_data->cpu_flags().empty()) {
48,102✔
1348
               for(const auto& cpuid_str : m_data->cpu_flags()) {
30,101✔
1349
                  if(const auto bit = Botan::CPUID::Feature::from_string(cpuid_str)) {
21,287✔
1350
                     if(Botan::CPUID::has(*bit)) {
21,287✔
1351
                        Botan::CPUID::clear_cpuid_bit(*bit);
16,187✔
1352
                        // now re-run the test
1353
                        result.merge(run_one_test(header, vars));
16,187✔
1354
                     }
1355
                  }
1356
               }
1357
               Botan::CPUID::initialize();
8,814✔
1358
            }
1359
#endif
1360
            result.set_ns_consumed(Test::timestamp() - start);
48,102✔
1361

1362
            if(result.tests_failed() > 0) {
48,102✔
1363
               std::ostringstream oss;
×
1364
               oss << "Test # " << test_cnt << " ";
×
1365
               if(!header.empty()) {
×
1366
                  oss << header << " ";
×
1367
               }
1368
               oss << "failed ";
×
1369

1370
               for(const auto& k : m_data->required_keys()) {
×
1371
                  oss << k << "=" << vars.get_req_str(k) << " ";
×
1372
               }
1373

1374
               result.test_note(oss.str());
×
1375
            }
×
1376
            results.push_back(result);
48,102✔
1377
         } catch(std::exception& e) {
48,102✔
1378
            std::ostringstream oss;
×
1379
            oss << "Test # " << test_cnt << " ";
×
1380
            if(!header.empty()) {
×
1381
               oss << header << " ";
×
1382
            }
1383

1384
            for(const auto& k : m_data->required_keys()) {
×
1385
               oss << k << "=" << vars.get_req_str(k) << " ";
×
1386
            }
1387

1388
            oss << "failed with exception '" << e.what() << "'";
×
1389

1390
            results.push_back(Test::Result::Failure(header_or_name, oss.str()));
×
1391
         }
×
1392

1393
         if(clear_between_callbacks()) {
48,102✔
1394
            vars.clear();
188,869✔
1395
         }
1396
      }
1397
   }
156,782✔
1398

1399
   if(results.empty()) {
180✔
1400
      return results;
1401
   }
1402

1403
   try {
180✔
1404
      std::vector<Test::Result> final_tests = run_final_tests();
180✔
1405
      results.insert(results.end(), final_tests.begin(), final_tests.end());
180✔
1406
   } catch(std::exception& e) {
180✔
1407
      results.push_back(Test::Result::Failure(header_or_name, "run_final_tests exception " + std::string(e.what())));
×
1408
   }
×
1409

1410
   return results;
1411
}
180✔
1412

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