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

randombit / botan / 13212092742

08 Feb 2025 12:33AM UTC coverage: 91.658% (-0.004%) from 91.662%
13212092742

push

github

web-flow
Merge pull request #4642 from randombit/jack/target-info-header

Add internal target_info.h header

94839 of 103471 relevant lines covered (91.66%)

11295178.12 hits per line

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

77.42
/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/internal/cpuid.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 <fstream>
18
#include <iomanip>
19
#include <sstream>
20

21
#if defined(BOTAN_HAS_BIGINT)
22
   #include <botan/bigint.h>
23
#endif
24

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

29
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
30
   #include <stdlib.h>
31
   #include <unistd.h>
32
#endif
33

34
#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
35
   #include <version>
36
   #if defined(__cpp_lib_filesystem)
37
      #include <filesystem>
38
   #endif
39
#endif
40

41
namespace Botan_Tests {
42

43
void Test::Result::merge(const Result& other, bool ignore_test_name) {
117,159✔
44
   if(who() != other.who()) {
117,159✔
45
      if(!ignore_test_name) {
73✔
46
         throw Test_Error("Merging tests from different sources");
×
47
      }
48

49
      // When deliberately merging results with different names, the code location is
50
      // likely inconsistent and must be discarded.
51
      m_where.reset();
73✔
52
   } else {
53
      m_where = other.m_where;
117,086✔
54
   }
55

56
   m_timestamp = std::min(m_timestamp, other.m_timestamp);
117,159✔
57
   m_ns_taken += other.m_ns_taken;
117,159✔
58
   m_tests_passed += other.m_tests_passed;
117,159✔
59
   m_fail_log.insert(m_fail_log.end(), other.m_fail_log.begin(), other.m_fail_log.end());
117,159✔
60
   m_log.insert(m_log.end(), other.m_log.begin(), other.m_log.end());
117,159✔
61
}
117,159✔
62

63
void Test::Result::start_timer() {
984✔
64
   if(m_started == 0) {
984✔
65
      m_started = Test::timestamp();
1,968✔
66
   }
67
}
984✔
68

69
void Test::Result::end_timer() {
1,096✔
70
   if(m_started > 0) {
1,096✔
71
      m_ns_taken += Test::timestamp() - m_started;
1,966✔
72
      m_started = 0;
983✔
73
   }
74
}
1,096✔
75

76
void Test::Result::test_note(const std::string& note, const char* extra) {
2,753✔
77
   if(!note.empty()) {
2,753✔
78
      std::ostringstream out;
2,753✔
79
      out << who() << " " << note;
2,753✔
80
      if(extra) {
2,753✔
81
         out << ": " << extra;
12✔
82
      }
83
      m_log.push_back(out.str());
2,753✔
84
   }
2,753✔
85
}
2,753✔
86

87
void Test::Result::note_missing(const std::string& whatever) {
275✔
88
   static std::set<std::string> s_already_seen;
275✔
89

90
   if(!s_already_seen.contains(whatever)) {
275✔
91
      test_note("Skipping tests due to missing " + whatever);
5✔
92
      s_already_seen.insert(whatever);
5✔
93
   }
94
}
275✔
95

96
bool Test::Result::ThrowExpectations::check(const std::string& test_name, Test::Result& result) {
47,939✔
97
   m_consumed = true;
47,939✔
98

99
   try {
47,939✔
100
      m_fn();
47,939✔
101
      if(!m_expect_success) {
1,645✔
102
         return result.test_failure(test_name + " failed to throw expected exception");
2✔
103
      }
104
   } catch(const std::exception& ex) {
46,294✔
105
      if(m_expect_success) {
46,292✔
106
         return result.test_failure(test_name + " threw unexpected exception: " + ex.what());
2✔
107
      }
108
      if(m_expected_exception_type.has_value() && m_expected_exception_type.value() != typeid(ex)) {
46,291✔
109
         return result.test_failure(test_name + " threw unexpected exception: " + ex.what());
4✔
110
      }
111
      if(m_expected_message.has_value() && m_expected_message.value() != ex.what()) {
46,289✔
112
         return result.test_failure(test_name + " threw exception with unexpected message (expected: '" +
3✔
113
                                    m_expected_message.value() + "', got: '" + ex.what() + "')");
6✔
114
      }
115
   } catch(...) {
46,294✔
116
      if(m_expect_success || m_expected_exception_type.has_value() || m_expected_message.has_value()) {
2✔
117
         return result.test_failure(test_name + " threw unexpected unknown exception");
1✔
118
      }
119
   }
2✔
120

121
   return result.test_success(test_name + " behaved as expected");
95,864✔
122
}
123

124
bool Test::Result::test_throws(const std::string& what, const std::function<void()>& fn) {
45,912✔
125
   return ThrowExpectations(fn).check(what, *this);
91,824✔
126
}
127

128
bool Test::Result::test_throws(const std::string& what, const std::string& expected, const std::function<void()>& fn) {
201✔
129
   return ThrowExpectations(fn).expect_message(expected).check(what, *this);
402✔
130
}
131

132
bool Test::Result::test_no_throw(const std::string& what, const std::function<void()>& fn) {
1,644✔
133
   return ThrowExpectations(fn).expect_success().check(what, *this);
3,288✔
134
}
135

136
bool Test::Result::test_success(const std::string& note) {
3,311,170✔
137
   if(Test::options().log_success()) {
3,311,039✔
138
      test_note(note);
×
139
   }
140
   ++m_tests_passed;
3,311,170✔
141
   return true;
538,693✔
142
}
143

144
bool Test::Result::test_failure(const std::string& what, const std::string& error) {
1✔
145
   return test_failure(who() + " " + what + " with error " + error);
4✔
146
}
147

148
void Test::Result::test_failure(const std::string& what, const uint8_t buf[], size_t buf_len) {
1✔
149
   test_failure(who() + ": " + what + " buf len " + std::to_string(buf_len) + " value " +
5✔
150
                Botan::hex_encode(buf, buf_len));
1✔
151
}
1✔
152

153
bool Test::Result::test_failure(const std::string& err) {
25✔
154
   m_fail_log.push_back(err);
25✔
155

156
   if(Test::options().abort_on_first_fail() && m_who != "Failing Test") {
25✔
157
      std::abort();
×
158
   }
159
   return false;
25✔
160
}
161

162
namespace {
163

164
bool same_contents(const uint8_t x[], const uint8_t y[], size_t len) {
263,211✔
165
   return (len == 0) ? true : std::memcmp(x, y, len) == 0;
261,862✔
166
}
167

168
}  // namespace
169

170
bool Test::Result::test_ne(const std::string& what,
3,408✔
171
                           const uint8_t produced[],
172
                           size_t produced_len,
173
                           const uint8_t expected[],
174
                           size_t expected_len) {
175
   if(produced_len == expected_len && same_contents(produced, expected, expected_len)) {
3,408✔
176
      return test_failure(who() + ": " + what + " produced matching");
6✔
177
   }
178
   return test_success();
6,812✔
179
}
180

181
bool Test::Result::test_eq(const char* producer,
261,780✔
182
                           const std::string& what,
183
                           const uint8_t produced[],
184
                           size_t produced_size,
185
                           const uint8_t expected[],
186
                           size_t expected_size) {
187
   if(produced_size == expected_size && same_contents(produced, expected, expected_size)) {
261,780✔
188
      return test_success();
523,558✔
189
   }
190

191
   std::ostringstream err;
1✔
192

193
   err << who();
1✔
194

195
   if(producer) {
1✔
196
      err << " producer '" << producer << "'";
×
197
   }
198

199
   err << " unexpected result for " << what;
1✔
200

201
   if(produced_size != expected_size) {
1✔
202
      err << " produced " << produced_size << " bytes expected " << expected_size;
1✔
203
   }
204

205
   std::vector<uint8_t> xor_diff(std::min(produced_size, expected_size));
2✔
206
   size_t bytes_different = 0;
1✔
207

208
   for(size_t i = 0; i != xor_diff.size(); ++i) {
4✔
209
      xor_diff[i] = produced[i] ^ expected[i];
3✔
210
      bytes_different += (xor_diff[i] > 0);
3✔
211
   }
212

213
   err << "\nProduced: " << Botan::hex_encode(produced, produced_size)
1✔
214
       << "\nExpected: " << Botan::hex_encode(expected, expected_size);
3✔
215

216
   if(bytes_different > 0) {
1✔
217
      err << "\nXOR Diff: " << Botan::hex_encode(xor_diff);
1✔
218
   }
219

220
   return test_failure(err.str());
1✔
221
}
1✔
222

223
bool Test::Result::test_is_nonempty(const std::string& what_is_it, const std::string& to_examine) {
32,459✔
224
   if(to_examine.empty()) {
32,459✔
225
      return test_failure(what_is_it + " was empty");
1✔
226
   }
227
   return test_success();
64,916✔
228
}
229

230
bool Test::Result::test_eq(const std::string& what, const std::string& produced, const std::string& expected) {
69,076✔
231
   return test_is_eq(what, produced, expected);
69,076✔
232
}
233

234
bool Test::Result::test_eq(const std::string& what, const char* produced, const char* expected) {
21✔
235
   return test_is_eq(what, std::string(produced), std::string(expected));
21✔
236
}
237

238
bool Test::Result::test_eq(const std::string& what, size_t produced, size_t expected) {
135,560✔
239
   return test_is_eq(what, produced, expected);
135,560✔
240
}
241

242
bool Test::Result::test_eq_sz(const std::string& what, size_t produced, size_t expected) {
45,786✔
243
   return test_is_eq(what, produced, expected);
45,786✔
244
}
245

246
bool Test::Result::test_eq(const std::string& what,
107✔
247
                           const Botan::OctetString& produced,
248
                           const Botan::OctetString& expected) {
249
   std::ostringstream out;
107✔
250
   out << m_who << " " << what;
107✔
251

252
   if(produced == expected) {
107✔
253
      out << " produced expected result " << produced.to_string();
214✔
254
      return test_success(out.str());
107✔
255
   } else {
256
      out << " produced unexpected result '" << produced.to_string() << "' expected '" << expected.to_string() << "'";
×
257
      return test_failure(out.str());
×
258
   }
259
}
107✔
260

261
bool Test::Result::test_lt(const std::string& what, size_t produced, size_t expected) {
5,640✔
262
   if(produced >= expected) {
5,640✔
263
      std::ostringstream err;
1✔
264
      err << m_who << " " << what;
1✔
265
      err << " unexpected result " << produced << " >= " << expected;
1✔
266
      return test_failure(err.str());
1✔
267
   }
1✔
268

269
   return test_success();
11,278✔
270
}
271

272
bool Test::Result::test_lte(const std::string& what, size_t produced, size_t expected) {
1,021,636✔
273
   if(produced > expected) {
1,021,636✔
274
      std::ostringstream err;
1✔
275
      err << m_who << " " << what << " unexpected result " << produced << " > " << expected;
1✔
276
      return test_failure(err.str());
1✔
277
   }
1✔
278

279
   return test_success();
2,043,270✔
280
}
281

282
bool Test::Result::test_gte(const std::string& what, size_t produced, size_t expected) {
1,138,764✔
283
   if(produced < expected) {
1,138,764✔
284
      std::ostringstream err;
1✔
285
      err << m_who;
1✔
286
      err << " " << what;
1✔
287
      err << " unexpected result " << produced << " < " << expected;
1✔
288
      return test_failure(err.str());
1✔
289
   }
1✔
290

291
   return test_success();
2,277,526✔
292
}
293

294
bool Test::Result::test_gt(const std::string& what, size_t produced, size_t expected) {
14,473✔
295
   if(produced <= expected) {
14,473✔
296
      std::ostringstream err;
×
297
      err << m_who;
×
298
      err << " " << what;
×
299
      err << " unexpected result " << produced << " <= " << expected;
×
300
      return test_failure(err.str());
×
301
   }
×
302

303
   return test_success();
28,946✔
304
}
305

306
bool Test::Result::test_ne(const std::string& what, const std::string& str1, const std::string& str2) {
25✔
307
   if(str1 != str2) {
25✔
308
      return test_success(str1 + " != " + str2);
48✔
309
   }
310

311
   return test_failure(who() + " " + what + " produced matching strings " + str1);
4✔
312
}
313

314
bool Test::Result::test_ne(const std::string& what, size_t produced, size_t expected) {
118✔
315
   if(produced != expected) {
118✔
316
      return test_success();
234✔
317
   }
318

319
   std::ostringstream err;
1✔
320
   err << who() << " " << what << " produced " << produced << " unexpected value";
1✔
321
   return test_failure(err.str());
1✔
322
}
1✔
323

324
#if defined(BOTAN_HAS_BIGINT)
325
bool Test::Result::test_eq(const std::string& what, const BigInt& produced, const BigInt& expected) {
82,705✔
326
   return test_is_eq(what, produced, expected);
82,705✔
327
}
328

329
bool Test::Result::test_ne(const std::string& what, const BigInt& produced, const BigInt& expected) {
97✔
330
   if(produced != expected) {
97✔
331
      return test_success();
192✔
332
   }
333

334
   std::ostringstream err;
1✔
335
   err << who() << " " << what << " produced " << produced << " prohibited value";
1✔
336
   return test_failure(err.str());
1✔
337
}
1✔
338
#endif
339

340
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
341
bool Test::Result::test_eq(const std::string& what, const Botan::EC_Point& a, const Botan::EC_Point& b) {
3,248✔
342
   //return test_is_eq(what, a, b);
343
   if(a == b) {
3,248✔
344
      return test_success();
6,496✔
345
   }
346

347
   std::ostringstream err;
×
348
   err << who() << " " << what << " a=(" << a.get_affine_x() << "," << a.get_affine_y() << ")"
×
349
       << " b=(" << b.get_affine_x() << "," << b.get_affine_y();
×
350
   return test_failure(err.str());
×
351
}
×
352
#endif
353

354
bool Test::Result::test_eq(const std::string& what, bool produced, bool expected) {
338,960✔
355
   return test_is_eq(what, produced, expected);
338,960✔
356
}
357

358
bool Test::Result::test_rc_init(const std::string& func, int rc) {
108✔
359
   if(rc == 0) {
108✔
360
      return test_success();
216✔
361
   } else {
362
      std::ostringstream msg;
×
363
      msg << m_who;
×
364
      msg << " " << func;
×
365

366
      // -40 is BOTAN_FFI_ERROR_NOT_IMPLEMENTED
367
      if(rc == -40) {
×
368
         msg << " returned not implemented";
×
369
      } else {
370
         msg << " unexpectedly failed with error code " << rc;
×
371
      }
372

373
      if(rc == -40) {
×
374
         this->test_note(msg.str());
×
375
      } else {
376
         this->test_failure(msg.str());
×
377
      }
378
      return false;
×
379
   }
×
380
}
381

382
bool Test::Result::test_rc(const std::string& func, int expected, int rc) {
335✔
383
   if(expected != rc) {
335✔
384
      std::ostringstream err;
1✔
385
      err << m_who;
1✔
386
      err << " call to " << func << " unexpectedly returned " << rc;
1✔
387
      err << " but expecting " << expected;
1✔
388
      return test_failure(err.str());
1✔
389
   }
1✔
390

391
   return test_success();
668✔
392
}
393

394
void Test::initialize(std::string test_name, CodeLocation location) {
397✔
395
   m_test_name = std::move(test_name);
397✔
396
   m_registration_location = std::move(location);
397✔
397
}
397✔
398

399
Botan::RandomNumberGenerator& Test::rng() const {
346,466✔
400
   if(!m_test_rng) {
346,466✔
401
      m_test_rng = Test::new_rng(m_test_name);
142✔
402
   }
403

404
   return *m_test_rng;
346,466✔
405
}
406

407
std::vector<std::string> Test::possible_providers(const std::string& /*unused*/) {
×
408
   return Test::provider_filter({"base"});
×
409
}
410

411
//static
412
std::string Test::format_time(uint64_t ns) {
1,406✔
413
   std::ostringstream o;
1,406✔
414

415
   if(ns > 1000000000) {
1,406✔
416
      o << std::setprecision(2) << std::fixed << ns / 1000000000.0 << " sec";
156✔
417
   } else {
418
      o << std::setprecision(2) << std::fixed << ns / 1000000.0 << " msec";
1,250✔
419
   }
420

421
   return o.str();
2,812✔
422
}
1,406✔
423

424
Test::Result::Result(std::string who, const std::vector<Result>& downstream_results) : Result(std::move(who)) {
14✔
425
   for(const auto& result : downstream_results) {
82✔
426
      merge(result, true /* ignore non-matching test names */);
68✔
427
   }
428
}
14✔
429

430
// TODO: this should move to `StdoutReporter`
431
std::string Test::Result::result_string() const {
2,453✔
432
   const bool verbose = Test::options().verbose();
2,453✔
433

434
   if(tests_run() == 0 && !verbose) {
2,453✔
435
      return "";
20✔
436
   }
437

438
   std::ostringstream report;
2,433✔
439

440
   report << who() << " ran ";
2,433✔
441

442
   if(tests_run() == 0) {
2,433✔
443
      report << "ZERO";
×
444
   } else {
445
      report << tests_run();
2,433✔
446
   }
447
   report << " tests";
2,433✔
448

449
   if(m_ns_taken > 0) {
2,433✔
450
      report << " in " << format_time(m_ns_taken);
2,810✔
451
   }
452

453
   if(tests_failed()) {
2,433✔
454
      report << " " << tests_failed() << " FAILED";
25✔
455
   } else {
456
      report << " all ok";
2,408✔
457
   }
458

459
   report << "\n";
2,433✔
460

461
   for(size_t i = 0; i != m_fail_log.size(); ++i) {
2,458✔
462
      report << "Failure " << (i + 1) << ": " << m_fail_log[i];
25✔
463
      if(m_where) {
25✔
464
         report << " (at " << m_where->path << ":" << m_where->line << ")";
×
465
      }
466
      report << "\n";
25✔
467
   }
468

469
   if(!m_fail_log.empty() || tests_run() == 0 || verbose) {
2,433✔
470
      for(size_t i = 0; i != m_log.size(); ++i) {
25✔
471
         report << "Note " << (i + 1) << ": " << m_log[i] << "\n";
×
472
      }
473
   }
474

475
   return report.str();
2,433✔
476
}
2,433✔
477

478
namespace {
479

480
class Test_Registry {
481
   public:
482
      static Test_Registry& instance() {
1,208✔
483
         static Test_Registry registry;
1,209✔
484
         return registry;
1,208✔
485
      }
486

487
      void register_test(const std::string& category,
397✔
488
                         const std::string& name,
489
                         bool smoke_test,
490
                         bool needs_serialization,
491
                         std::function<std::unique_ptr<Test>()> maker_fn) {
492
         if(m_tests.contains(name)) {
397✔
493
            throw Test_Error("Duplicate registration of test '" + name + "'");
×
494
         }
495

496
         if(m_tests.contains(category)) {
397✔
497
            throw Test_Error("'" + category + "' cannot be used as category, test exists");
×
498
         }
499

500
         if(m_categories.contains(name)) {
397✔
501
            throw Test_Error("'" + name + "' cannot be used as test name, category exists");
×
502
         }
503

504
         if(smoke_test) {
397✔
505
            m_smoke_tests.push_back(name);
10✔
506
         }
507

508
         if(needs_serialization) {
397✔
509
            m_mutexed_tests.push_back(name);
21✔
510
         }
511

512
         m_tests.emplace(name, std::move(maker_fn));
397✔
513
         m_categories.emplace(category, name);
397✔
514
      }
397✔
515

516
      std::unique_ptr<Test> get_test(const std::string& test_name) const {
397✔
517
         auto i = m_tests.find(test_name);
397✔
518
         if(i != m_tests.end()) {
397✔
519
            return i->second();
397✔
520
         }
521
         return nullptr;
×
522
      }
523

524
      std::set<std::string> registered_tests() const {
×
525
         std::set<std::string> s;
×
526
         for(auto&& i : m_tests) {
×
527
            s.insert(i.first);
×
528
         }
529
         return s;
×
530
      }
×
531

532
      std::set<std::string> registered_test_categories() const {
×
533
         std::set<std::string> s;
×
534
         for(auto&& i : m_categories) {
×
535
            s.insert(i.first);
×
536
         }
537
         return s;
×
538
      }
×
539

540
      std::vector<std::string> filter_registered_tests(const std::vector<std::string>& requested,
1✔
541
                                                       const std::set<std::string>& to_be_skipped) {
542
         std::vector<std::string> result;
1✔
543

544
         // TODO: this is O(n^2), but we have a relatively small number of tests.
545
         auto insert_if_not_exists_and_not_skipped = [&](const std::string& test_name) {
398✔
546
            if(!Botan::value_exists(result, test_name) && to_be_skipped.find(test_name) == to_be_skipped.end()) {
397✔
547
               result.push_back(test_name);
387✔
548
            }
549
         };
398✔
550

551
         if(requested.empty()) {
1✔
552
            /*
553
            If nothing was requested on the command line, run everything. First
554
            run the "essentials" to smoke test, then everything else in
555
            alphabetical order.
556
            */
557
            result = m_smoke_tests;
1✔
558
            for(const auto& [test_name, _] : m_tests) {
398✔
559
               insert_if_not_exists_and_not_skipped(test_name);
397✔
560
            }
561
         } else {
562
            for(const auto& r : requested) {
×
563
               if(m_tests.find(r) != m_tests.end()) {
×
564
                  insert_if_not_exists_and_not_skipped(r);
×
565
               } else if(auto elems = m_categories.equal_range(r); elems.first != m_categories.end()) {
×
566
                  for(; elems.first != elems.second; ++elems.first) {
×
567
                     insert_if_not_exists_and_not_skipped(elems.first->second);
×
568
                  }
569
               } else {
570
                  throw Test_Error("Unknown test suite or category: " + r);
×
571
               }
572
            }
573
         }
574

575
         return result;
1✔
576
      }
×
577

578
      bool needs_serialization(const std::string& test_name) const {
413✔
579
         return Botan::value_exists(m_mutexed_tests, test_name);
16✔
580
      }
581

582
   private:
583
      Test_Registry() = default;
1✔
584

585
   private:
586
      std::map<std::string, std::function<std::unique_ptr<Test>()>> m_tests;
587
      std::multimap<std::string, std::string> m_categories;
588
      std::vector<std::string> m_smoke_tests;
589
      std::vector<std::string> m_mutexed_tests;
590
};
591

592
}  // namespace
593

594
// static Test:: functions
595

596
//static
597
void Test::register_test(const std::string& category,
397✔
598
                         const std::string& name,
599
                         bool smoke_test,
600
                         bool needs_serialization,
601
                         std::function<std::unique_ptr<Test>()> maker_fn) {
602
   Test_Registry::instance().register_test(category, name, smoke_test, needs_serialization, std::move(maker_fn));
794✔
603
}
397✔
604

605
//static
606
uint64_t Test::timestamp() {
97,515✔
607
   auto now = std::chrono::high_resolution_clock::now().time_since_epoch();
97,515✔
608
   return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
1,967✔
609
}
610

611
//static
612
std::vector<Test::Result> Test::flatten_result_lists(std::vector<std::vector<Test::Result>> result_lists) {
4✔
613
   std::vector<Test::Result> results;
4✔
614
   for(auto& result_list : result_lists) {
26✔
615
      for(auto& result : result_list) {
71✔
616
         results.emplace_back(std::move(result));
49✔
617
      }
618
   }
619
   return results;
4✔
620
}
×
621

622
//static
623
std::set<std::string> Test::registered_tests() {
×
624
   return Test_Registry::instance().registered_tests();
×
625
}
626

627
//static
628
std::set<std::string> Test::registered_test_categories() {
×
629
   return Test_Registry::instance().registered_test_categories();
×
630
}
631

632
//static
633
std::unique_ptr<Test> Test::get_test(const std::string& test_name) {
397✔
634
   return Test_Registry::instance().get_test(test_name);
397✔
635
}
636

637
//static
638
bool Test::test_needs_serialization(const std::string& test_name) {
397✔
639
   return Test_Registry::instance().needs_serialization(test_name);
397✔
640
}
641

642
//static
643
std::vector<std::string> Test::filter_registered_tests(const std::vector<std::string>& requested,
1✔
644
                                                       const std::set<std::string>& to_be_skipped) {
645
   return Test_Registry::instance().filter_registered_tests(requested, to_be_skipped);
1✔
646
}
647

648
//static
649
std::string Test::temp_file_name(const std::string& basename) {
21✔
650
   // TODO add a --tmp-dir option to the tests to specify where these files go
651

652
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
653

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

657
   int fd = ::mkstemp(&mkstemp_basename[0]);
21✔
658

659
   // error
660
   if(fd < 0) {
21✔
661
      return "";
×
662
   }
663

664
   ::close(fd);
21✔
665

666
   return mkstemp_basename;
21✔
667
#else
668
   // For now just create the temp in the current working directory
669
   return basename;
670
#endif
671
}
21✔
672

673
bool Test::copy_file(const std::string& from, const std::string& to) {
1✔
674
#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(__cpp_lib_filesystem)
675
   std::error_code ec;  // don't throw, just return false on error
1✔
676
   return std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing, ec);
1✔
677
#else
678
   // TODO: implement fallbacks to POSIX or WIN32
679
   // ... but then again: it's 2023 and we're using C++20 :o)
680
   BOTAN_UNUSED(from, to);
681
   throw Botan::No_Filesystem_Access();
682
#endif
683
}
684

685
std::string Test::read_data_file(const std::string& path) {
37✔
686
   const std::string fsname = Test::data_file(path);
37✔
687
   std::ifstream file(fsname.c_str());
37✔
688
   if(!file.good()) {
37✔
689
      throw Test_Error("Error reading from " + fsname);
×
690
   }
691

692
   return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
74✔
693
}
37✔
694

695
std::vector<uint8_t> Test::read_binary_data_file(const std::string& path) {
34✔
696
   const std::string fsname = Test::data_file(path);
34✔
697
   std::ifstream file(fsname.c_str(), std::ios::binary);
34✔
698
   if(!file.good()) {
34✔
699
      throw Test_Error("Error reading from " + fsname);
×
700
   }
701

702
   std::vector<uint8_t> contents;
34✔
703

704
   while(file.good()) {
74✔
705
      std::vector<uint8_t> buf(4096);
40✔
706
      file.read(reinterpret_cast<char*>(buf.data()), buf.size());
40✔
707
      const size_t got = static_cast<size_t>(file.gcount());
40✔
708

709
      if(got == 0 && file.eof()) {
40✔
710
         break;
711
      }
712

713
      contents.insert(contents.end(), buf.data(), buf.data() + got);
40✔
714
   }
40✔
715

716
   return contents;
68✔
717
}
34✔
718

719
// static member variables of Test
720

721
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
722
Test_Options Test::m_opts;
723
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
724
std::string Test::m_test_rng_seed;
725

726
//static
727
void Test::set_test_options(const Test_Options& opts) {
1✔
728
   m_opts = opts;
1✔
729
}
1✔
730

731
namespace {
732

733
/*
734
* This is a fast, simple, deterministic PRNG that's used for running
735
* the tests. It is not intended to be cryptographically secure.
736
*/
737
class Testsuite_RNG final : public Botan::RandomNumberGenerator {
8✔
738
   public:
739
      std::string name() const override { return "Testsuite_RNG"; }
×
740

741
      void clear() override { m_x = 0; }
×
742

743
      bool accepts_input() const override { return true; }
×
744

745
      bool is_seeded() const override { return true; }
350,529✔
746

747
      void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> input) override {
10,110,328✔
748
         for(const auto byte : input) {
10,110,328✔
749
            mix(byte);
×
750
         }
751

752
         for(auto& byte : output) {
43,454,739✔
753
            byte = mix();
33,344,411✔
754
         }
755
      }
10,110,328✔
756

757
      Testsuite_RNG(std::string_view seed, std::string_view test_name) {
253✔
758
         m_x = 0;
253✔
759

760
         for(char c : seed) {
7,590✔
761
            this->mix(static_cast<uint8_t>(c));
7,337✔
762
         }
763
         for(char c : test_name) {
4,512✔
764
            this->mix(static_cast<uint8_t>(c));
4,259✔
765
         }
766
      }
253✔
767

768
   private:
769
      uint8_t mix(uint8_t input = 0) {
33,356,007✔
770
         m_x ^= input;
33,356,007✔
771
         m_x *= 0xF2E16957;
33,356,007✔
772
         m_x += 0xE50B590F;
33,356,007✔
773
         return static_cast<uint8_t>(m_x >> 27);
33,356,007✔
774
      }
775

776
      uint64_t m_x;
777
};
778

779
}  // namespace
780

781
//static
782
void Test::set_test_rng_seed(std::span<const uint8_t> seed, size_t epoch) {
1✔
783
   m_test_rng_seed = Botan::fmt("seed={} epoch={}", Botan::hex_encode(seed), epoch);
1✔
784
}
1✔
785

786
//static
787
std::unique_ptr<Botan::RandomNumberGenerator> Test::new_rng(std::string_view test_name) {
245✔
788
   return std::make_unique<Testsuite_RNG>(m_test_rng_seed, test_name);
245✔
789
}
790

791
//static
792
std::shared_ptr<Botan::RandomNumberGenerator> Test::new_shared_rng(std::string_view test_name) {
8✔
793
   return std::make_shared<Testsuite_RNG>(m_test_rng_seed, test_name);
8✔
794
}
795

796
//static
797
std::string Test::data_file(const std::string& file) {
618✔
798
   return options().data_dir() + "/" + file;
1,236✔
799
}
800

801
//static
802
std::string Test::data_dir(const std::string& subdir) {
1✔
803
   return options().data_dir() + "/" + subdir;
2✔
804
}
805

806
//static
807
std::vector<std::string> Test::files_in_data_dir(const std::string& subdir) {
263✔
808
   auto fs = Botan::get_files_recursive(options().data_dir() + "/" + subdir);
789✔
809
   if(fs.empty()) {
263✔
810
      throw Test_Error("Test::files_in_data_dir encountered empty subdir " + subdir);
×
811
   }
812
   return fs;
263✔
813
}
×
814

815
//static
816
std::string Test::data_file_as_temporary_copy(const std::string& what) {
1✔
817
   auto tmp_basename = what;
1✔
818
   std::replace(tmp_basename.begin(), tmp_basename.end(), '/', '_');
1✔
819
   auto temp_file = temp_file_name("tmp-" + tmp_basename);
1✔
820
   if(temp_file.empty()) {
1✔
821
      return "";
×
822
   }
823
   if(!Test::copy_file(data_file(what), temp_file)) {
1✔
824
      return "";
×
825
   }
826
   return temp_file;
1✔
827
}
1✔
828

829
//static
830
std::vector<std::string> Test::provider_filter(const std::vector<std::string>& in) {
46,597✔
831
   if(m_opts.provider().empty()) {
46,597✔
832
      return in;
46,597✔
833
   }
834
   for(auto&& provider : in) {
×
835
      if(provider == m_opts.provider()) {
×
836
         return std::vector<std::string>{provider};
×
837
      }
838
   }
839
   return std::vector<std::string>{};
×
840
}
×
841

842
std::string Test::random_password(Botan::RandomNumberGenerator& rng) {
222✔
843
   const size_t len = 1 + rng.next_byte() % 32;
222✔
844
   return Botan::hex_encode(rng.random_vec(len));
444✔
845
}
846

847
size_t Test::random_index(Botan::RandomNumberGenerator& rng, size_t max) {
8,062✔
848
   return Botan::load_be(rng.random_array<8>()) % max;
8,062✔
849
}
850

851
std::vector<std::vector<uint8_t>> VarMap::get_req_bin_list(const std::string& key) const {
12✔
852
   auto i = m_vars.find(key);
12✔
853
   if(i == m_vars.end()) {
12✔
854
      throw Test_Error("Test missing variable " + key);
×
855
   }
856

857
   std::vector<std::vector<uint8_t>> bin_list;
12✔
858

859
   for(auto&& part : Botan::split_on(i->second, ',')) {
62✔
860
      try {
50✔
861
         bin_list.push_back(Botan::hex_decode(part));
100✔
862
      } catch(std::exception& e) {
×
863
         std::ostringstream oss;
×
864
         oss << "Bad input '" << part << "'"
×
865
             << " in binary list key " << key << " - " << e.what();
×
866
         throw Test_Error(oss.str());
×
867
      }
×
868
   }
12✔
869

870
   return bin_list;
12✔
871
}
×
872

873
std::vector<uint8_t> VarMap::get_req_bin(const std::string& key) const {
151,133✔
874
   auto i = m_vars.find(key);
151,133✔
875
   if(i == m_vars.end()) {
151,133✔
876
      throw Test_Error("Test missing variable " + key);
×
877
   }
878

879
   try {
151,133✔
880
      if(i->second.starts_with("0x")) {
151,133✔
881
         if(i->second.size() % 2 == 0) {
×
882
            return Botan::hex_decode(i->second.substr(2));
×
883
         } else {
884
            std::string z = i->second;
×
885
            std::swap(z[0], z[1]);  // swap 0x to x0 then remove x
×
886
            return Botan::hex_decode(z.substr(1));
×
887
         }
×
888
      } else {
889
         return Botan::hex_decode(i->second);
151,133✔
890
      }
891
   } catch(std::exception& e) {
×
892
      std::ostringstream oss;
×
893
      oss << "Bad input '" << i->second << "'"
×
894
          << " for key " << key << " - " << e.what();
×
895
      throw Test_Error(oss.str());
×
896
   }
×
897
}
×
898

899
std::string VarMap::get_opt_str(const std::string& key, const std::string& def_value) const {
14,372✔
900
   auto i = m_vars.find(key);
14,372✔
901
   if(i == m_vars.end()) {
14,372✔
902
      return def_value;
14,318✔
903
   }
904
   return i->second;
54✔
905
}
906

907
bool VarMap::get_req_bool(const std::string& key) const {
19✔
908
   auto i = m_vars.find(key);
19✔
909
   if(i == m_vars.end()) {
19✔
910
      throw Test_Error("Test missing variable " + key);
×
911
   }
912

913
   if(i->second == "true") {
19✔
914
      return true;
915
   } else if(i->second == "false") {
11✔
916
      return false;
917
   } else {
918
      throw Test_Error("Invalid boolean for key '" + key + "' value '" + i->second + "'");
×
919
   }
920
}
921

922
size_t VarMap::get_req_sz(const std::string& key) const {
4,513✔
923
   auto i = m_vars.find(key);
4,513✔
924
   if(i == m_vars.end()) {
4,513✔
925
      throw Test_Error("Test missing variable " + key);
×
926
   }
927
   return Botan::to_u32bit(i->second);
4,513✔
928
}
929

930
uint8_t VarMap::get_req_u8(const std::string& key) const {
17✔
931
   const size_t s = this->get_req_sz(key);
17✔
932
   if(s > 256) {
17✔
933
      throw Test_Error("Invalid " + key + " expected uint8_t got " + std::to_string(s));
×
934
   }
935
   return static_cast<uint8_t>(s);
17✔
936
}
937

938
uint32_t VarMap::get_req_u32(const std::string& key) const {
14✔
939
   return static_cast<uint32_t>(get_req_sz(key));
14✔
940
}
941

942
uint64_t VarMap::get_req_u64(const std::string& key) const {
17✔
943
   auto i = m_vars.find(key);
17✔
944
   if(i == m_vars.end()) {
17✔
945
      throw Test_Error("Test missing variable " + key);
×
946
   }
947
   try {
17✔
948
      return std::stoull(i->second);
17✔
949
   } catch(std::exception&) {
×
950
      throw Test_Error("Invalid u64 value '" + i->second + "'");
×
951
   }
×
952
}
953

954
size_t VarMap::get_opt_sz(const std::string& key, const size_t def_value) const {
30,347✔
955
   auto i = m_vars.find(key);
30,347✔
956
   if(i == m_vars.end()) {
30,347✔
957
      return def_value;
958
   }
959
   return Botan::to_u32bit(i->second);
12,244✔
960
}
961

962
uint64_t VarMap::get_opt_u64(const std::string& key, const uint64_t def_value) const {
3,538✔
963
   auto i = m_vars.find(key);
3,538✔
964
   if(i == m_vars.end()) {
3,538✔
965
      return def_value;
966
   }
967
   try {
641✔
968
      return std::stoull(i->second);
3,538✔
969
   } catch(std::exception&) {
×
970
      throw Test_Error("Invalid u64 value '" + i->second + "'");
×
971
   }
×
972
}
973

974
std::vector<uint8_t> VarMap::get_opt_bin(const std::string& key) const {
40,013✔
975
   auto i = m_vars.find(key);
40,013✔
976
   if(i == m_vars.end()) {
40,013✔
977
      return std::vector<uint8_t>();
28,335✔
978
   }
979

980
   try {
11,678✔
981
      return Botan::hex_decode(i->second);
11,678✔
982
   } catch(std::exception&) {
×
983
      throw Test_Error("Test invalid hex input '" + i->second + "'" + +" for key " + key);
×
984
   }
×
985
}
986

987
std::string VarMap::get_req_str(const std::string& key) const {
52,583✔
988
   auto i = m_vars.find(key);
52,583✔
989
   if(i == m_vars.end()) {
52,583✔
990
      throw Test_Error("Test missing variable " + key);
×
991
   }
992
   return i->second;
52,583✔
993
}
994

995
#if defined(BOTAN_HAS_BIGINT)
996
Botan::BigInt VarMap::get_req_bn(const std::string& key) const {
38,482✔
997
   auto i = m_vars.find(key);
38,482✔
998
   if(i == m_vars.end()) {
38,482✔
999
      throw Test_Error("Test missing variable " + key);
×
1000
   }
1001

1002
   try {
38,482✔
1003
      return Botan::BigInt(i->second);
38,482✔
1004
   } catch(std::exception&) {
×
1005
      throw Test_Error("Test invalid bigint input '" + i->second + "' for key " + key);
×
1006
   }
×
1007
}
1008

1009
Botan::BigInt VarMap::get_opt_bn(const std::string& key, const Botan::BigInt& def_value) const
80✔
1010

1011
{
1012
   auto i = m_vars.find(key);
80✔
1013
   if(i == m_vars.end()) {
80✔
1014
      return def_value;
24✔
1015
   }
1016

1017
   try {
56✔
1018
      return Botan::BigInt(i->second);
56✔
1019
   } catch(std::exception&) {
×
1020
      throw Test_Error("Test invalid bigint input '" + i->second + "' for key " + key);
×
1021
   }
×
1022
}
1023
#endif
1024

1025
Text_Based_Test::Text_Based_Test(const std::string& data_src,
175✔
1026
                                 const std::string& required_keys_str,
1027
                                 const std::string& optional_keys_str) :
175✔
1028
      m_data_src(data_src) {
525✔
1029
   if(required_keys_str.empty()) {
175✔
1030
      throw Test_Error("Invalid test spec");
×
1031
   }
1032

1033
   std::vector<std::string> required_keys = Botan::split_on(required_keys_str, ',');
175✔
1034
   std::vector<std::string> optional_keys = Botan::split_on(optional_keys_str, ',');
175✔
1035

1036
   m_required_keys.insert(required_keys.begin(), required_keys.end());
175✔
1037
   m_optional_keys.insert(optional_keys.begin(), optional_keys.end());
175✔
1038
   m_output_key = required_keys.at(required_keys.size() - 1);
175✔
1039
}
175✔
1040

1041
std::string Text_Based_Test::get_next_line() {
155,134✔
1042
   while(true) {
155,383✔
1043
      if(m_cur == nullptr || m_cur->good() == false) {
155,383✔
1044
         if(m_srcs.empty()) {
435✔
1045
            if(m_first) {
350✔
1046
               if(m_data_src.ends_with(".vec")) {
175✔
1047
                  m_srcs.push_back(Test::data_file(m_data_src));
326✔
1048
               } else {
1049
                  const auto fs = Test::files_in_data_dir(m_data_src);
12✔
1050
                  m_srcs.assign(fs.begin(), fs.end());
12✔
1051
                  if(m_srcs.empty()) {
12✔
1052
                     throw Test_Error("Error reading test data dir " + m_data_src);
×
1053
                  }
1054
               }
12✔
1055

1056
               m_first = false;
175✔
1057
            } else {
1058
               return "";  // done
175✔
1059
            }
1060
         }
1061

1062
         m_cur = std::make_unique<std::ifstream>(m_srcs[0]);
345✔
1063
         m_cur_src_name = m_srcs[0];
260✔
1064

1065
         // Reinit cpuid on new file if needed
1066
         if(m_cpu_flags.empty() == false) {
260✔
1067
            m_cpu_flags.clear();
13✔
1068
            Botan::CPUID::initialize();
13✔
1069
         }
1070

1071
         if(!m_cur->good()) {
260✔
1072
            throw Test_Error("Could not open input file '" + m_cur_src_name);
×
1073
         }
1074

1075
         m_srcs.pop_front();
260✔
1076
      }
1077

1078
      while(m_cur->good()) {
225,144✔
1079
         std::string line;
224,895✔
1080
         std::getline(*m_cur, line);
224,895✔
1081

1082
         if(line.empty()) {
224,895✔
1083
            continue;
54,840✔
1084
         }
1085

1086
         if(line[0] == '#') {
170,055✔
1087
            if(line.starts_with("#test ")) {
15,112✔
1088
               return line;
16✔
1089
            } else {
1090
               continue;
15,096✔
1091
            }
1092
         }
1093

1094
         return line;
154,943✔
1095
      }
224,895✔
1096
   }
1097
}
1098

1099
namespace {
1100

1101
// strips leading and trailing but not internal whitespace
1102
std::string strip_ws(const std::string& in) {
308,344✔
1103
   const char* whitespace = " ";
308,344✔
1104

1105
   const auto first_c = in.find_first_not_of(whitespace);
308,344✔
1106
   if(first_c == std::string::npos) {
308,344✔
1107
      return "";
1,005✔
1108
   }
1109

1110
   const auto last_c = in.find_last_not_of(whitespace);
307,339✔
1111

1112
   return in.substr(first_c, last_c - first_c + 1);
307,339✔
1113
}
1114

1115
std::vector<uint64_t> parse_cpuid_bits(const std::vector<std::string>& tok) {
16✔
1116
   std::vector<uint64_t> bits;
16✔
1117
   for(size_t i = 1; i < tok.size(); ++i) {
58✔
1118
      const std::vector<Botan::CPUID::CPUID_bits> more = Botan::CPUID::bit_from_string(tok[i]);
42✔
1119
      bits.insert(bits.end(), more.begin(), more.end());
42✔
1120
   }
42✔
1121

1122
   return bits;
16✔
1123
}
×
1124

1125
}  // namespace
1126

1127
bool Text_Based_Test::skip_this_test(const std::string& /*header*/, const VarMap& /*vars*/) {
32,175✔
1128
   return false;
32,175✔
1129
}
1130

1131
std::vector<Test::Result> Text_Based_Test::run() {
175✔
1132
   std::vector<Test::Result> results;
175✔
1133

1134
   std::string header, header_or_name = m_data_src;
175✔
1135
   VarMap vars;
175✔
1136
   size_t test_cnt = 0;
175✔
1137

1138
   while(true) {
155,134✔
1139
      const std::string line = get_next_line();
155,134✔
1140
      if(line.empty())  // EOF
155,134✔
1141
      {
1142
         break;
1143
      }
1144

1145
      if(line.starts_with("#test ")) {
154,959✔
1146
         std::vector<std::string> pragma_tokens = Botan::split_on(line.substr(6), ' ');
16✔
1147

1148
         if(pragma_tokens.empty()) {
16✔
1149
            throw Test_Error("Empty pragma found in " + m_cur_src_name);
×
1150
         }
1151

1152
         if(pragma_tokens[0] != "cpuid") {
16✔
1153
            throw Test_Error("Unknown test pragma '" + line + "' in " + m_cur_src_name);
×
1154
         }
1155

1156
         if(!Test_Registry::instance().needs_serialization(this->test_name())) {
16✔
1157
            throw Test_Error(Botan::fmt("'{}' used cpuid control but is not serialized", this->test_name()));
×
1158
         }
1159

1160
         m_cpu_flags = parse_cpuid_bits(pragma_tokens);
16✔
1161

1162
         continue;
16✔
1163
      } else if(line[0] == '#') {
154,959✔
1164
         throw Test_Error("Unknown test pragma '" + line + "' in " + m_cur_src_name);
×
1165
      }
1166

1167
      if(line[0] == '[' && line[line.size() - 1] == ']') {
154,943✔
1168
         header = line.substr(1, line.size() - 2);
771✔
1169
         header_or_name = header;
771✔
1170
         test_cnt = 0;
771✔
1171
         vars.clear();
771✔
1172
         continue;
771✔
1173
      }
1174

1175
      const std::string test_id = "test " + std::to_string(test_cnt);
308,344✔
1176

1177
      auto equal_i = line.find_first_of('=');
154,172✔
1178

1179
      if(equal_i == std::string::npos) {
154,172✔
1180
         results.push_back(Test::Result::Failure(header_or_name, "invalid input '" + line + "'"));
×
1181
         continue;
×
1182
      }
1183

1184
      std::string key = strip_ws(std::string(line.begin(), line.begin() + equal_i - 1));
308,344✔
1185
      std::string val = strip_ws(std::string(line.begin() + equal_i + 1, line.end()));
308,344✔
1186

1187
      if(!m_required_keys.contains(key) && !m_optional_keys.contains(key)) {
154,172✔
1188
         auto r = Test::Result::Failure(header_or_name, Botan::fmt("{} failed unknown key {}", test_id, key));
×
1189
         results.push_back(r);
×
1190
      }
×
1191

1192
      vars.add(key, val);
154,172✔
1193

1194
      if(key == m_output_key) {
154,172✔
1195
         try {
47,909✔
1196
            for(auto& req_key : m_required_keys) {
243,641✔
1197
               if(!vars.has_key(req_key)) {
391,464✔
1198
                  auto r =
×
1199
                     Test::Result::Failure(header_or_name, Botan::fmt("{} missing required key {}", test_id, req_key));
×
1200
                  results.push_back(r);
×
1201
               }
×
1202
            }
1203

1204
            if(skip_this_test(header, vars)) {
47,909✔
1205
               continue;
138✔
1206
            }
1207

1208
            ++test_cnt;
47,771✔
1209

1210
            uint64_t start = Test::timestamp();
47,771✔
1211

1212
            Test::Result result = run_one_test(header, vars);
47,771✔
1213
            if(!m_cpu_flags.empty()) {
47,771✔
1214
               for(const auto& cpuid_u64 : m_cpu_flags) {
19,314✔
1215
                  Botan::CPUID::CPUID_bits cpuid_bit = static_cast<Botan::CPUID::CPUID_bits>(cpuid_u64);
13,699✔
1216
                  if(Botan::CPUID::has_cpuid_bit(cpuid_bit)) {
13,699✔
1217
                     Botan::CPUID::clear_cpuid_bit(cpuid_bit);
12,524✔
1218
                     // now re-run the test
1219
                     result.merge(run_one_test(header, vars));
12,524✔
1220
                  }
1221
               }
1222
               Botan::CPUID::initialize();
5,615✔
1223
            }
1224
            result.set_ns_consumed(Test::timestamp() - start);
47,771✔
1225

1226
            if(result.tests_failed()) {
47,771✔
1227
               std::ostringstream oss;
×
1228
               oss << "Test # " << test_cnt << " ";
×
1229
               if(!header.empty()) {
×
1230
                  oss << header << " ";
×
1231
               }
1232
               oss << "failed ";
×
1233

1234
               for(const auto& k : m_required_keys) {
×
1235
                  oss << k << "=" << vars.get_req_str(k) << " ";
×
1236
               }
1237

1238
               result.test_note(oss.str());
×
1239
            }
×
1240
            results.push_back(result);
47,771✔
1241
         } catch(std::exception& e) {
47,771✔
1242
            std::ostringstream oss;
×
1243
            oss << "Test # " << test_cnt << " ";
×
1244
            if(!header.empty()) {
×
1245
               oss << header << " ";
×
1246
            }
1247

1248
            for(const auto& k : m_required_keys) {
×
1249
               oss << k << "=" << vars.get_req_str(k) << " ";
×
1250
            }
1251

1252
            oss << "failed with exception '" << e.what() << "'";
×
1253

1254
            results.push_back(Test::Result::Failure(header_or_name, oss.str()));
×
1255
         }
×
1256

1257
         if(clear_between_callbacks()) {
47,771✔
1258
            vars.clear();
33,030✔
1259
         }
1260
      }
1261
   }
155,235✔
1262

1263
   if(results.empty()) {
175✔
1264
      return results;
1265
   }
1266

1267
   try {
175✔
1268
      std::vector<Test::Result> final_tests = run_final_tests();
175✔
1269
      results.insert(results.end(), final_tests.begin(), final_tests.end());
175✔
1270
   } catch(std::exception& e) {
175✔
1271
      results.push_back(Test::Result::Failure(header_or_name, "run_final_tests exception " + std::string(e.what())));
×
1272
   }
×
1273

1274
   m_first = true;
175✔
1275

1276
   return results;
175✔
1277
}
175✔
1278

1279
std::map<std::string, std::string> Test_Options::report_properties() const {
1✔
1280
   std::map<std::string, std::string> result;
1✔
1281

1282
   for(const auto& prop : m_report_properties) {
3✔
1283
      const auto colon = prop.find(':');
2✔
1284
      // props without a colon separator or without a name are not allowed
1285
      if(colon == std::string::npos || colon == 0) {
2✔
1286
         throw Test_Error("--report-properties should be of the form <key>:<value>,<key>:<value>,...");
×
1287
      }
1288

1289
      result.insert_or_assign(prop.substr(0, colon), prop.substr(colon + 1, prop.size() - colon - 1));
4✔
1290
   }
1291

1292
   return result;
1✔
1293
}
×
1294

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