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

randombit / botan / 16242902969

12 Jul 2025 11:06PM UTC coverage: 90.574% (+0.003%) from 90.571%
16242902969

push

github

web-flow
Merge pull request #4977 from randombit/jack/fix-some-clang-tidy-implicit-bool-conversion

Fix some clang-tidy readability-implict-bool-conversion warnings

99099 of 109412 relevant lines covered (90.57%)

12335877.7 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/filesystem.h>
11
#include <botan/internal/fmt.h>
12
#include <botan/internal/loadstor.h>
13
#include <botan/internal/parsing.h>
14
#include <botan/internal/stl_util.h>
15
#include <botan/internal/target_info.h>
16
#include <fstream>
17
#include <iomanip>
18
#include <sstream>
19

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

24
#if defined(BOTAN_HAS_CPUID)
25
   #include <botan/internal/cpuid.h>
26
#endif
27

28
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
29
   #include <botan/ec_point.h>
30
#endif
31

32
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
33
   #include <stdlib.h>
34
   #include <unistd.h>
35
#endif
36

37
#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
38
   #include <version>
39
   #if defined(__cpp_lib_filesystem)
40
      #include <filesystem>
41
   #endif
42
#endif
43

44
namespace Botan_Tests {
45

46
void Test::Result::merge(const Result& other, bool ignore_test_name) {
118,424✔
47
   if(who() != other.who()) {
118,424✔
48
      if(!ignore_test_name) {
73✔
49
         throw Test_Error("Merging tests from different sources");
×
50
      }
51

52
      // When deliberately merging results with different names, the code location is
53
      // likely inconsistent and must be discarded.
54
      m_where.reset();
73✔
55
   } else {
56
      m_where = other.m_where;
118,351✔
57
   }
58

59
   m_timestamp = std::min(m_timestamp, other.m_timestamp);
118,424✔
60
   m_ns_taken += other.m_ns_taken;
118,424✔
61
   m_tests_passed += other.m_tests_passed;
118,424✔
62
   m_fail_log.insert(m_fail_log.end(), other.m_fail_log.begin(), other.m_fail_log.end());
118,424✔
63
   m_log.insert(m_log.end(), other.m_log.begin(), other.m_log.end());
118,424✔
64
}
118,424✔
65

66
void Test::Result::start_timer() {
1,004✔
67
   if(m_started == 0) {
1,004✔
68
      m_started = Test::timestamp();
2,008✔
69
   }
70
}
1,004✔
71

72
void Test::Result::end_timer() {
1,116✔
73
   if(m_started > 0) {
1,116✔
74
      m_ns_taken += Test::timestamp() - m_started;
2,006✔
75
      m_started = 0;
1,003✔
76
   }
77
}
1,116✔
78

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

90
void Test::Result::note_missing(const std::string& whatever) {
275✔
91
   static std::set<std::string> s_already_seen;
275✔
92

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

99
bool Test::Result::ThrowExpectations::check(const std::string& test_name, Test::Result& result) {
47,999✔
100
   m_consumed = true;
47,999✔
101

102
   try {
47,999✔
103
      m_fn();
47,999✔
104
      if(!m_expect_success) {
1,645✔
105
         return result.test_failure(test_name + " failed to throw expected exception");
2✔
106
      }
107
   } catch(const std::exception& ex) {
46,354✔
108
      if(m_expect_success) {
46,352✔
109
         return result.test_failure(test_name + " threw unexpected exception: " + ex.what());
2✔
110
      }
111
      if(m_expected_exception_check_fn && !m_expected_exception_check_fn(std::current_exception())) {
130,875✔
112
         return result.test_failure(test_name + " threw unexpected exception: " + ex.what());
4✔
113
      }
114
      if(m_expected_message.has_value() && m_expected_message.value() != ex.what()) {
46,349✔
115
         return result.test_failure(test_name + " threw exception with unexpected message (expected: '" +
3✔
116
                                    m_expected_message.value() + "', got: '" + ex.what() + "')");
6✔
117
      }
118
   } catch(...) {
46,354✔
119
      if(m_expect_success || m_expected_exception_check_fn || m_expected_message.has_value()) {
2✔
120
         return result.test_failure(test_name + " threw unexpected unknown exception");
1✔
121
      }
122
   }
2✔
123

124
   return result.test_success(test_name + " behaved as expected");
95,984✔
125
}
126

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

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

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

139
bool Test::Result::test_success(const std::string& note) {
3,441,591✔
140
   if(Test::options().log_success()) {
3,441,460✔
141
      test_note(note);
×
142
   }
143
   ++m_tests_passed;
3,441,591✔
144
   return true;
650,471✔
145
}
146

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

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

156
bool Test::Result::test_failure(const std::string& err) {
25✔
157
   m_fail_log.push_back(err);
25✔
158

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

165
namespace {
166

167
bool same_contents(const uint8_t x[], const uint8_t y[], size_t len) {
271,646✔
168
   return (len == 0) ? true : std::memcmp(x, y, len) == 0;
270,297✔
169
}
170

171
}  // namespace
172

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

184
bool Test::Result::test_eq(const char* producer,
270,190✔
185
                           const std::string& what,
186
                           const uint8_t produced[],
187
                           size_t produced_size,
188
                           const uint8_t expected[],
189
                           size_t expected_size) {
190
   if(produced_size == expected_size && same_contents(produced, expected, expected_size)) {
270,190✔
191
      return test_success();
540,378✔
192
   }
193

194
   std::ostringstream err;
1✔
195

196
   err << who();
1✔
197

198
   if(producer != nullptr) {
1✔
199
      err << " producer '" << producer << "'";
×
200
   }
201

202
   err << " unexpected result for " << what;
1✔
203

204
   if(produced_size != expected_size) {
1✔
205
      err << " produced " << produced_size << " bytes expected " << expected_size;
1✔
206
   }
207

208
   std::vector<uint8_t> xor_diff(std::min(produced_size, expected_size));
2✔
209
   size_t bytes_different = 0;
1✔
210

211
   for(size_t i = 0; i != xor_diff.size(); ++i) {
4✔
212
      xor_diff[i] = produced[i] ^ expected[i];
3✔
213
      if(xor_diff[i] > 0) {
3✔
214
         bytes_different++;
3✔
215
      }
216
   }
217

218
   err << "\nProduced: " << Botan::hex_encode(produced, produced_size)
1✔
219
       << "\nExpected: " << Botan::hex_encode(expected, expected_size);
3✔
220

221
   if(bytes_different > 0) {
1✔
222
      err << "\nXOR Diff: " << Botan::hex_encode(xor_diff);
1✔
223
   }
224

225
   return test_failure(err.str());
1✔
226
}
1✔
227

228
bool Test::Result::test_is_nonempty(const std::string& what_is_it, const std::string& to_examine) {
33,715✔
229
   if(to_examine.empty()) {
33,715✔
230
      return test_failure(what_is_it + " was empty");
1✔
231
   }
232
   return test_success();
67,428✔
233
}
234

235
bool Test::Result::test_eq(const std::string& what, const std::string& produced, const std::string& expected) {
71,608✔
236
   return test_is_eq(what, produced, expected);
71,608✔
237
}
238

239
bool Test::Result::test_eq(const std::string& what, const char* produced, const char* expected) {
29✔
240
   return test_is_eq(what, std::string(produced), std::string(expected));
29✔
241
}
242

243
bool Test::Result::test_eq(const std::string& what, size_t produced, size_t expected) {
135,688✔
244
   return test_is_eq(what, produced, expected);
135,688✔
245
}
246

247
bool Test::Result::test_eq_sz(const std::string& what, size_t produced, size_t expected) {
45,786✔
248
   return test_is_eq(what, produced, expected);
45,786✔
249
}
250

251
bool Test::Result::test_eq(const std::string& what,
107✔
252
                           const Botan::OctetString& produced,
253
                           const Botan::OctetString& expected) {
254
   std::ostringstream out;
107✔
255
   out << m_who << " " << what;
107✔
256

257
   if(produced == expected) {
107✔
258
      out << " produced expected result " << produced.to_string();
214✔
259
      return test_success(out.str());
107✔
260
   } else {
261
      out << " produced unexpected result '" << produced.to_string() << "' expected '" << expected.to_string() << "'";
×
262
      return test_failure(out.str());
×
263
   }
264
}
107✔
265

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

274
   return test_success();
11,278✔
275
}
276

277
bool Test::Result::test_lte(const std::string& what, size_t produced, size_t expected) {
1,021,636✔
278
   if(produced > expected) {
1,021,636✔
279
      std::ostringstream err;
1✔
280
      err << m_who << " " << what << " unexpected result " << produced << " > " << expected;
1✔
281
      return test_failure(err.str());
1✔
282
   }
1✔
283

284
   return test_success();
2,043,270✔
285
}
286

287
bool Test::Result::test_gte(const std::string& what, size_t produced, size_t expected) {
1,142,072✔
288
   if(produced < expected) {
1,142,072✔
289
      std::ostringstream err;
1✔
290
      err << m_who;
1✔
291
      err << " " << what;
1✔
292
      err << " unexpected result " << produced << " < " << expected;
1✔
293
      return test_failure(err.str());
1✔
294
   }
1✔
295

296
   return test_success();
2,284,142✔
297
}
298

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

308
   return test_success();
28,946✔
309
}
310

311
bool Test::Result::test_ne(const std::string& what, const std::string& str1, const std::string& str2) {
25✔
312
   if(str1 != str2) {
25✔
313
      return test_success(str1 + " != " + str2);
48✔
314
   }
315

316
   return test_failure(who() + " " + what + " produced matching strings " + str1);
4✔
317
}
318

319
bool Test::Result::test_ne(const std::string& what, size_t produced, size_t expected) {
118✔
320
   if(produced != expected) {
118✔
321
      return test_success();
234✔
322
   }
323

324
   std::ostringstream err;
1✔
325
   err << who() << " " << what << " produced " << produced << " unexpected value";
1✔
326
   return test_failure(err.str());
1✔
327
}
1✔
328

329
#if defined(BOTAN_HAS_BIGINT)
330
bool Test::Result::test_eq(const std::string& what, const BigInt& produced, const BigInt& expected) {
184,685✔
331
   return test_is_eq(what, produced, expected);
184,685✔
332
}
333

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

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

345
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
346
bool Test::Result::test_eq(const std::string& what, const Botan::EC_Point& a, const Botan::EC_Point& b) {
3,248✔
347
   //return test_is_eq(what, a, b);
348
   if(a == b) {
3,248✔
349
      return test_success();
6,496✔
350
   }
351

352
   std::ostringstream err;
×
353
   err << who() << " " << what << " a=(" << a.get_affine_x() << "," << a.get_affine_y() << ")"
×
354
       << " b=(" << b.get_affine_x() << "," << b.get_affine_y();
×
355
   return test_failure(err.str());
×
356
}
×
357
#endif
358

359
bool Test::Result::test_eq(const std::string& what, bool produced, bool expected) {
346,158✔
360
   return test_is_eq(what, produced, expected);
346,158✔
361
}
362

363
bool Test::Result::test_rc_init(const std::string& func, int rc) {
110✔
364
   if(rc == 0) {
110✔
365
      return test_success();
220✔
366
   } else {
367
      std::ostringstream msg;
×
368
      msg << m_who;
×
369
      msg << " " << func;
×
370

371
      // -40 is BOTAN_FFI_ERROR_NOT_IMPLEMENTED
372
      if(rc == -40) {
×
373
         msg << " returned not implemented";
×
374
      } else {
375
         msg << " unexpectedly failed with error code " << rc;
×
376
      }
377

378
      if(rc == -40) {
×
379
         this->test_note(msg.str());
×
380
      } else {
381
         this->test_failure(msg.str());
×
382
      }
383
      return false;
×
384
   }
×
385
}
386

387
bool Test::Result::test_rc(const std::string& func, int expected, int rc) {
371✔
388
   if(expected != rc) {
371✔
389
      std::ostringstream err;
1✔
390
      err << m_who;
1✔
391
      err << " call to " << func << " unexpectedly returned " << rc;
1✔
392
      err << " but expecting " << expected;
1✔
393
      return test_failure(err.str());
1✔
394
   }
1✔
395

396
   return test_success();
740✔
397
}
398

399
void Test::initialize(std::string test_name, CodeLocation location) {
409✔
400
   m_test_name = std::move(test_name);
409✔
401
   m_registration_location = location;
409✔
402
}
409✔
403

404
Botan::RandomNumberGenerator& Test::rng() const {
452,576✔
405
   if(!m_test_rng) {
452,576✔
406
      m_test_rng = Test::new_rng(m_test_name);
144✔
407
   }
408

409
   return *m_test_rng;
452,576✔
410
}
411

412
std::vector<std::string> Test::possible_providers(const std::string& /*unused*/) {
×
413
   return Test::provider_filter({"base"});
×
414
}
415

416
//static
417
std::string Test::format_time(uint64_t nanoseconds) {
1,448✔
418
   std::ostringstream o;
1,448✔
419

420
   if(nanoseconds > 1000000000) {
1,448✔
421
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000000.0 << " sec";
151✔
422
   } else {
423
      o << std::setprecision(2) << std::fixed << nanoseconds / 1000000.0 << " msec";
1,297✔
424
   }
425

426
   return o.str();
2,896✔
427
}
1,448✔
428

429
Test::Result::Result(std::string who, const std::vector<Result>& downstream_results) : Result(std::move(who)) {
14✔
430
   for(const auto& result : downstream_results) {
82✔
431
      merge(result, true /* ignore non-matching test names */);
68✔
432
   }
433
}
14✔
434

435
// TODO: this should move to `StdoutReporter`
436
std::string Test::Result::result_string() const {
2,497✔
437
   const bool verbose = Test::options().verbose();
2,497✔
438

439
   if(tests_run() == 0 && !verbose) {
2,497✔
440
      return "";
20✔
441
   }
442

443
   std::ostringstream report;
2,477✔
444

445
   report << who() << " ran ";
2,477✔
446

447
   if(tests_run() == 0) {
2,477✔
448
      report << "ZERO";
×
449
   } else {
450
      report << tests_run();
2,477✔
451
   }
452
   report << " tests";
2,477✔
453

454
   if(m_ns_taken > 0) {
2,477✔
455
      report << " in " << format_time(m_ns_taken);
2,894✔
456
   }
457

458
   if(tests_failed()) {
2,477✔
459
      report << " " << tests_failed() << " FAILED";
25✔
460
   } else {
461
      report << " all ok";
2,452✔
462
   }
463

464
   report << "\n";
2,477✔
465

466
   for(size_t i = 0; i != m_fail_log.size(); ++i) {
2,502✔
467
      report << "Failure " << (i + 1) << ": " << m_fail_log[i];
25✔
468
      if(m_where) {
25✔
469
         report << " (at " << m_where->path << ":" << m_where->line << ")";
×
470
      }
471
      report << "\n";
25✔
472
   }
473

474
   if(!m_fail_log.empty() || tests_run() == 0 || verbose) {
2,477✔
475
      for(size_t i = 0; i != m_log.size(); ++i) {
25✔
476
         report << "Note " << (i + 1) << ": " << m_log[i] << "\n";
×
477
      }
478
   }
479

480
   return report.str();
2,477✔
481
}
2,477✔
482

483
namespace {
484

485
class Test_Registry {
486
   public:
487
      static Test_Registry& instance() {
1,245✔
488
         static Test_Registry registry;
1,246✔
489
         return registry;
1,245✔
490
      }
491

492
      void register_test(const std::string& category,
409✔
493
                         const std::string& name,
494
                         bool smoke_test,
495
                         bool needs_serialization,
496
                         std::function<std::unique_ptr<Test>()> maker_fn) {
497
         if(m_tests.contains(name)) {
409✔
498
            throw Test_Error("Duplicate registration of test '" + name + "'");
×
499
         }
500

501
         if(m_tests.contains(category)) {
409✔
502
            throw Test_Error("'" + category + "' cannot be used as category, test exists");
×
503
         }
504

505
         if(m_categories.contains(name)) {
409✔
506
            throw Test_Error("'" + name + "' cannot be used as test name, category exists");
×
507
         }
508

509
         if(smoke_test) {
409✔
510
            m_smoke_tests.push_back(name);
10✔
511
         }
512

513
         if(needs_serialization) {
409✔
514
            m_mutexed_tests.push_back(name);
21✔
515
         }
516

517
         m_tests.emplace(name, std::move(maker_fn));
409✔
518
         m_categories.emplace(category, name);
409✔
519
      }
409✔
520

521
      std::unique_ptr<Test> get_test(const std::string& test_name) const {
409✔
522
         auto i = m_tests.find(test_name);
409✔
523
         if(i != m_tests.end()) {
409✔
524
            return i->second();
409✔
525
         }
526
         return nullptr;
×
527
      }
528

529
      std::set<std::string> registered_tests() const {
×
530
         std::set<std::string> s;
×
531
         for(auto&& i : m_tests) {
×
532
            s.insert(i.first);
×
533
         }
534
         return s;
×
535
      }
×
536

537
      std::set<std::string> registered_test_categories() const {
×
538
         std::set<std::string> s;
×
539
         for(auto&& i : m_categories) {
×
540
            s.insert(i.first);
×
541
         }
542
         return s;
×
543
      }
×
544

545
      std::vector<std::string> filter_registered_tests(const std::vector<std::string>& requested,
1✔
546
                                                       const std::set<std::string>& to_be_skipped) {
547
         std::vector<std::string> result;
1✔
548

549
         // TODO: this is O(n^2), but we have a relatively small number of tests.
550
         auto insert_if_not_exists_and_not_skipped = [&](const std::string& test_name) {
410✔
551
            if(!Botan::value_exists(result, test_name) && !to_be_skipped.contains(test_name)) {
409✔
552
               result.push_back(test_name);
399✔
553
            }
554
         };
410✔
555

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

580
         return result;
1✔
581
      }
×
582

583
      bool needs_serialization(const std::string& test_name) const {
426✔
584
         return Botan::value_exists(m_mutexed_tests, test_name);
17✔
585
      }
586

587
   private:
588
      Test_Registry() = default;
1✔
589

590
   private:
591
      std::map<std::string, std::function<std::unique_ptr<Test>()>> m_tests;
592
      std::multimap<std::string, std::string> m_categories;
593
      std::vector<std::string> m_smoke_tests;
594
      std::vector<std::string> m_mutexed_tests;
595
};
596

597
}  // namespace
598

599
// static Test:: functions
600

601
//static
602
void Test::register_test(const std::string& category,
409✔
603
                         const std::string& name,
604
                         bool smoke_test,
605
                         bool needs_serialization,
606
                         std::function<std::unique_ptr<Test>()> maker_fn) {
607
   Test_Registry::instance().register_test(category, name, smoke_test, needs_serialization, std::move(maker_fn));
818✔
608
}
409✔
609

610
//static
611
uint64_t Test::timestamp() {
97,265✔
612
   auto now = std::chrono::high_resolution_clock::now().time_since_epoch();
97,265✔
613
   return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
2,007✔
614
}
615

616
//static
617
std::vector<Test::Result> Test::flatten_result_lists(std::vector<std::vector<Test::Result>> result_lists) {
4✔
618
   std::vector<Test::Result> results;
4✔
619
   for(auto& result_list : result_lists) {
26✔
620
      for(auto& result : result_list) {
71✔
621
         results.emplace_back(std::move(result));
49✔
622
      }
623
   }
624
   return results;
4✔
625
}
×
626

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

632
//static
633
std::set<std::string> Test::registered_test_categories() {
×
634
   return Test_Registry::instance().registered_test_categories();
×
635
}
636

637
//static
638
std::unique_ptr<Test> Test::get_test(const std::string& test_name) {
409✔
639
   return Test_Registry::instance().get_test(test_name);
409✔
640
}
641

642
//static
643
bool Test::test_needs_serialization(const std::string& test_name) {
409✔
644
   return Test_Registry::instance().needs_serialization(test_name);
409✔
645
}
646

647
//static
648
std::vector<std::string> Test::filter_registered_tests(const std::vector<std::string>& requested,
1✔
649
                                                       const std::set<std::string>& to_be_skipped) {
650
   return Test_Registry::instance().filter_registered_tests(requested, to_be_skipped);
1✔
651
}
652

653
//static
654
std::string Test::temp_file_name(const std::string& basename) {
21✔
655
   // TODO add a --tmp-dir option to the tests to specify where these files go
656

657
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
658

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

662
   int fd = ::mkstemp(mkstemp_basename.data());
21✔
663

664
   // error
665
   if(fd < 0) {
21✔
666
      return "";
×
667
   }
668

669
   ::close(fd);
21✔
670

671
   return mkstemp_basename;
21✔
672
#else
673
   // For now just create the temp in the current working directory
674
   return basename;
675
#endif
676
}
21✔
677

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

690
std::string Test::read_data_file(const std::string& path) {
38✔
691
   const std::string fsname = Test::data_file(path);
38✔
692
   std::ifstream file(fsname.c_str());
38✔
693
   if(!file.good()) {
38✔
694
      throw Test_Error("Error reading from " + fsname);
×
695
   }
696

697
   return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
76✔
698
}
38✔
699

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

707
   std::vector<uint8_t> contents;
34✔
708

709
   while(file.good()) {
74✔
710
      std::vector<uint8_t> buf(4096);
40✔
711
      file.read(reinterpret_cast<char*>(buf.data()), buf.size());
40✔
712
      const size_t got = static_cast<size_t>(file.gcount());
40✔
713

714
      if(got == 0 && file.eof()) {
40✔
715
         break;
716
      }
717

718
      contents.insert(contents.end(), buf.data(), buf.data() + got);
40✔
719
   }
40✔
720

721
   return contents;
68✔
722
}
34✔
723

724
// static member variables of Test
725

726
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
727
Test_Options Test::m_opts;
728
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
729
std::string Test::m_test_rng_seed;
730

731
//static
732
void Test::set_test_options(const Test_Options& opts) {
1✔
733
   m_opts = opts;
1✔
734
}
1✔
735

736
namespace {
737

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

746
      void clear() override { m_x = 0; }
×
747

748
      bool accepts_input() const override { return true; }
×
749

750
      bool is_seeded() const override { return true; }
274,886✔
751

752
      void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> input) override {
10,221,696✔
753
         for(const auto byte : input) {
10,221,696✔
754
            mix(byte);
×
755
         }
756

757
         for(auto& byte : output) {
72,486,109✔
758
            byte = mix();
62,264,413✔
759
         }
760
      }
10,221,696✔
761

762
      Testsuite_RNG(std::string_view seed, std::string_view test_name) {
265✔
763
         m_x = 0;
265✔
764

765
         for(char c : seed) {
7,950✔
766
            this->mix(static_cast<uint8_t>(c));
7,685✔
767
         }
768
         for(char c : test_name) {
5,070✔
769
            this->mix(static_cast<uint8_t>(c));
4,805✔
770
         }
771
      }
265✔
772

773
   private:
774
      uint8_t mix(uint8_t input = 0) {
62,276,903✔
775
         m_x ^= input;
62,276,903✔
776
         m_x *= 0xF2E16957;
62,276,903✔
777
         m_x += 0xE50B590F;
62,276,903✔
778
         return static_cast<uint8_t>(m_x >> 27);
62,276,903✔
779
      }
780

781
      uint64_t m_x;
782
};
783

784
}  // namespace
785

786
//static
787
void Test::set_test_rng_seed(std::span<const uint8_t> seed, size_t epoch) {
1✔
788
   m_test_rng_seed = Botan::fmt("seed={} epoch={}", Botan::hex_encode(seed), epoch);
1✔
789
}
1✔
790

791
//static
792
std::unique_ptr<Botan::RandomNumberGenerator> Test::new_rng(std::string_view test_name) {
257✔
793
   return std::make_unique<Testsuite_RNG>(m_test_rng_seed, test_name);
257✔
794
}
795

796
//static
797
std::shared_ptr<Botan::RandomNumberGenerator> Test::new_shared_rng(std::string_view test_name) {
8✔
798
   return std::make_shared<Testsuite_RNG>(m_test_rng_seed, test_name);
8✔
799
}
800

801
//static
802
std::string Test::data_file(const std::string& file) {
631✔
803
   return options().data_dir() + "/" + file;
1,262✔
804
}
805

806
//static
807
std::string Test::data_dir(const std::string& subdir) {
1✔
808
   return options().data_dir() + "/" + subdir;
2✔
809
}
810

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

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

834
//static
835
std::vector<std::string> Test::provider_filter(const std::vector<std::string>& providers) {
47,855✔
836
   if(m_opts.provider().empty()) {
47,855✔
837
      return providers;
47,855✔
838
   }
839
   for(auto&& provider : providers) {
×
840
      if(provider == m_opts.provider()) {
×
841
         return std::vector<std::string>{provider};
×
842
      }
843
   }
844
   return std::vector<std::string>{};
×
845
}
×
846

847
std::string Test::random_password(Botan::RandomNumberGenerator& rng) {
222✔
848
   const size_t len = 1 + rng.next_byte() % 32;
222✔
849
   return Botan::hex_encode(rng.random_vec(len));
444✔
850
}
851

852
size_t Test::random_index(Botan::RandomNumberGenerator& rng, size_t max) {
8,062✔
853
   return Botan::load_be(rng.random_array<8>()) % max;
8,062✔
854
}
855

856
std::vector<std::vector<uint8_t>> VarMap::get_req_bin_list(const std::string& key) const {
12✔
857
   auto i = m_vars.find(key);
12✔
858
   if(i == m_vars.end()) {
12✔
859
      throw Test_Error("Test missing variable " + key);
×
860
   }
861

862
   std::vector<std::vector<uint8_t>> bin_list;
12✔
863

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

875
   return bin_list;
12✔
876
}
×
877

878
std::vector<uint8_t> VarMap::get_req_bin(const std::string& key) const {
154,836✔
879
   auto i = m_vars.find(key);
154,836✔
880
   if(i == m_vars.end()) {
154,836✔
881
      throw Test_Error("Test missing variable " + key);
×
882
   }
883

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

904
std::string VarMap::get_opt_str(const std::string& key, const std::string& def_value) const {
14,376✔
905
   auto i = m_vars.find(key);
14,376✔
906
   if(i == m_vars.end()) {
14,376✔
907
      return def_value;
14,321✔
908
   }
909
   return i->second;
55✔
910
}
911

912
bool VarMap::get_req_bool(const std::string& key) const {
39✔
913
   auto i = m_vars.find(key);
39✔
914
   if(i == m_vars.end()) {
39✔
915
      throw Test_Error("Test missing variable " + key);
×
916
   }
917

918
   if(i->second == "true") {
39✔
919
      return true;
920
   } else if(i->second == "false") {
23✔
921
      return false;
922
   } else {
923
      throw Test_Error("Invalid boolean for key '" + key + "' value '" + i->second + "'");
×
924
   }
925
}
926

927
size_t VarMap::get_req_sz(const std::string& key) const {
4,547✔
928
   auto i = m_vars.find(key);
4,547✔
929
   if(i == m_vars.end()) {
4,547✔
930
      throw Test_Error("Test missing variable " + key);
×
931
   }
932
   return Botan::to_u32bit(i->second);
4,547✔
933
}
934

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

943
uint32_t VarMap::get_req_u32(const std::string& key) const {
14✔
944
   return static_cast<uint32_t>(get_req_sz(key));
14✔
945
}
946

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

959
size_t VarMap::get_opt_sz(const std::string& key, const size_t def_value) const {
31,379✔
960
   auto i = m_vars.find(key);
31,379✔
961
   if(i == m_vars.end()) {
31,379✔
962
      return def_value;
963
   }
964
   return Botan::to_u32bit(i->second);
12,244✔
965
}
966

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

979
std::vector<uint8_t> VarMap::get_opt_bin(const std::string& key) const {
41,050✔
980
   auto i = m_vars.find(key);
41,050✔
981
   if(i == m_vars.end()) {
41,050✔
982
      return std::vector<uint8_t>();
29,373✔
983
   }
984

985
   try {
11,677✔
986
      return Botan::hex_decode(i->second);
11,677✔
987
   } catch(std::exception&) {
×
988
      throw Test_Error("Test invalid hex input '" + i->second + "'" + +" for key " + key);
×
989
   }
×
990
}
991

992
std::string VarMap::get_req_str(const std::string& key) const {
52,783✔
993
   auto i = m_vars.find(key);
52,783✔
994
   if(i == m_vars.end()) {
52,783✔
995
      throw Test_Error("Test missing variable " + key);
×
996
   }
997
   return i->second;
52,783✔
998
}
999

1000
#if defined(BOTAN_HAS_BIGINT)
1001
Botan::BigInt VarMap::get_req_bn(const std::string& key) const {
38,484✔
1002
   auto i = m_vars.find(key);
38,484✔
1003
   if(i == m_vars.end()) {
38,484✔
1004
      throw Test_Error("Test missing variable " + key);
×
1005
   }
1006

1007
   try {
38,484✔
1008
      return Botan::BigInt(i->second);
38,484✔
1009
   } catch(std::exception&) {
×
1010
      throw Test_Error("Test invalid bigint input '" + i->second + "' for key " + key);
×
1011
   }
×
1012
}
1013

1014
Botan::BigInt VarMap::get_opt_bn(const std::string& key, const Botan::BigInt& def_value) const {
80✔
1015
   auto i = m_vars.find(key);
80✔
1016
   if(i == m_vars.end()) {
80✔
1017
      return def_value;
24✔
1018
   }
1019

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

1028
Text_Based_Test::Text_Based_Test(const std::string& data_src,
180✔
1029
                                 const std::string& required_keys_str,
1030
                                 const std::string& optional_keys_str) :
180✔
1031
      m_data_src(data_src) {
540✔
1032
   if(required_keys_str.empty()) {
180✔
1033
      throw Test_Error("Invalid test spec");
×
1034
   }
1035

1036
   std::vector<std::string> required_keys = Botan::split_on(required_keys_str, ',');
180✔
1037
   std::vector<std::string> optional_keys = Botan::split_on(optional_keys_str, ',');
180✔
1038

1039
   m_required_keys.insert(required_keys.begin(), required_keys.end());
180✔
1040
   m_optional_keys.insert(optional_keys.begin(), optional_keys.end());
180✔
1041
   m_output_key = required_keys.at(required_keys.size() - 1);
180✔
1042
}
180✔
1043

1044
std::string Text_Based_Test::get_next_line() {
154,946✔
1045
   while(true) {
155,200✔
1046
      if(m_cur == nullptr || m_cur->good() == false) {
155,200✔
1047
         if(m_srcs.empty()) {
445✔
1048
            if(m_first) {
360✔
1049
               if(m_data_src.ends_with(".vec")) {
180✔
1050
                  m_srcs.push_back(Test::data_file(m_data_src));
336✔
1051
               } else {
1052
                  const auto fs = Test::files_in_data_dir(m_data_src);
12✔
1053
                  m_srcs.assign(fs.begin(), fs.end());
12✔
1054
                  if(m_srcs.empty()) {
12✔
1055
                     throw Test_Error("Error reading test data dir " + m_data_src);
×
1056
                  }
1057
               }
12✔
1058

1059
               m_first = false;
180✔
1060
            } else {
1061
               return "";  // done
180✔
1062
            }
1063
         }
1064

1065
         m_cur = std::make_unique<std::ifstream>(m_srcs[0]);
350✔
1066
         m_cur_src_name = m_srcs[0];
265✔
1067

1068
#if defined(BOTAN_HAS_CPUID)
1069
         // Reinit cpuid on new file if needed
1070
         if(m_cpu_flags.empty() == false) {
265✔
1071
            m_cpu_flags.clear();
15✔
1072
            Botan::CPUID::initialize();
15✔
1073
         }
1074
#endif
1075

1076
         if(!m_cur->good()) {
265✔
1077
            throw Test_Error("Could not open input file '" + m_cur_src_name);
×
1078
         }
1079

1080
         m_srcs.pop_front();
265✔
1081
      }
1082

1083
      while(m_cur->good()) {
224,795✔
1084
         std::string line;
224,541✔
1085
         std::getline(*m_cur, line);
224,541✔
1086

1087
         if(line.empty()) {
224,541✔
1088
            continue;
54,668✔
1089
         }
1090

1091
         if(line[0] == '#') {
169,873✔
1092
            if(line.starts_with("#test ")) {
15,124✔
1093
               return line;
17✔
1094
            } else {
1095
               continue;
15,107✔
1096
            }
1097
         }
1098

1099
         return line;
154,749✔
1100
      }
224,541✔
1101
   }
1102
}
1103

1104
namespace {
1105

1106
// strips leading and trailing but not internal whitespace
1107
std::string strip_ws(const std::string& in) {
307,896✔
1108
   const char* whitespace = " ";
307,896✔
1109

1110
   const auto first_c = in.find_first_not_of(whitespace);
307,896✔
1111
   if(first_c == std::string::npos) {
307,896✔
1112
      return "";
1,008✔
1113
   }
1114

1115
   const auto last_c = in.find_last_not_of(whitespace);
306,888✔
1116

1117
   return in.substr(first_c, last_c - first_c + 1);
306,888✔
1118
}
1119

1120
std::vector<std::string> parse_cpuid_bits(const std::vector<std::string>& tok) {
17✔
1121
   std::vector<std::string> bits;
17✔
1122

1123
#if defined(BOTAN_HAS_CPUID)
1124
   for(size_t i = 1; i < tok.size(); ++i) {
82✔
1125
      if(auto bit = Botan::CPUID::bit_from_string(tok[i])) {
65✔
1126
         bits.push_back(bit->to_string());
78✔
1127
      }
1128
   }
1129
#else
1130
   BOTAN_UNUSED(tok);
1131
#endif
1132

1133
   return bits;
17✔
1134
}
×
1135

1136
}  // namespace
1137

1138
bool Text_Based_Test::skip_this_test(const std::string& /*header*/, const VarMap& /*vars*/) {
31,986✔
1139
   return false;
31,986✔
1140
}
1141

1142
std::vector<Test::Result> Text_Based_Test::run() {
180✔
1143
   std::vector<Test::Result> results;
180✔
1144

1145
   std::string header, header_or_name = m_data_src;
180✔
1146
   VarMap vars;
180✔
1147
   size_t test_cnt = 0;
180✔
1148

1149
   while(true) {
154,946✔
1150
      const std::string line = get_next_line();
154,946✔
1151
      if(line.empty())  // EOF
154,946✔
1152
      {
1153
         break;
1154
      }
1155

1156
      if(line.starts_with("#test ")) {
154,766✔
1157
         std::vector<std::string> pragma_tokens = Botan::split_on(line.substr(6), ' ');
17✔
1158

1159
         if(pragma_tokens.empty()) {
17✔
1160
            throw Test_Error("Empty pragma found in " + m_cur_src_name);
×
1161
         }
1162

1163
         if(pragma_tokens[0] != "cpuid") {
17✔
1164
            throw Test_Error("Unknown test pragma '" + line + "' in " + m_cur_src_name);
×
1165
         }
1166

1167
         if(!Test_Registry::instance().needs_serialization(this->test_name())) {
17✔
1168
            throw Test_Error(Botan::fmt("'{}' used cpuid control but is not serialized", this->test_name()));
×
1169
         }
1170

1171
         m_cpu_flags = parse_cpuid_bits(pragma_tokens);
17✔
1172

1173
         continue;
17✔
1174
      } else if(line[0] == '#') {
154,766✔
1175
         throw Test_Error("Unknown test pragma '" + line + "' in " + m_cur_src_name);
×
1176
      }
1177

1178
      if(line[0] == '[' && line[line.size() - 1] == ']') {
154,749✔
1179
         header = line.substr(1, line.size() - 2);
801✔
1180
         header_or_name = header;
801✔
1181
         test_cnt = 0;
801✔
1182
         vars.clear();
801✔
1183
         continue;
801✔
1184
      }
1185

1186
      const std::string test_id = "test " + std::to_string(test_cnt);
307,896✔
1187

1188
      auto equal_i = line.find_first_of('=');
153,948✔
1189

1190
      if(equal_i == std::string::npos) {
153,948✔
1191
         results.push_back(Test::Result::Failure(header_or_name, "invalid input '" + line + "'"));
×
1192
         continue;
×
1193
      }
1194

1195
      std::string key = strip_ws(std::string(line.begin(), line.begin() + equal_i - 1));
307,896✔
1196
      std::string val = strip_ws(std::string(line.begin() + equal_i + 1, line.end()));
307,896✔
1197

1198
      if(!m_required_keys.contains(key) && !m_optional_keys.contains(key)) {
153,948✔
1199
         auto r = Test::Result::Failure(header_or_name, Botan::fmt("{} failed unknown key {}", test_id, key));
×
1200
         results.push_back(r);
×
1201
      }
×
1202

1203
      vars.add(key, val);
153,948✔
1204

1205
      if(key == m_output_key) {
153,948✔
1206
         try {
47,765✔
1207
            for(const auto& req_key : m_required_keys) {
243,290✔
1208
               if(!vars.has_key(req_key)) {
391,050✔
1209
                  auto r =
×
1210
                     Test::Result::Failure(header_or_name, Botan::fmt("{} missing required key {}", test_id, req_key));
×
1211
                  results.push_back(r);
×
1212
               }
×
1213
            }
1214

1215
            if(skip_this_test(header, vars)) {
47,765✔
1216
               continue;
139✔
1217
            }
1218

1219
            ++test_cnt;
47,626✔
1220

1221
            uint64_t start = Test::timestamp();
47,626✔
1222

1223
            Test::Result result = run_one_test(header, vars);
47,626✔
1224
#if defined(BOTAN_HAS_CPUID)
1225
            if(!m_cpu_flags.empty()) {
47,626✔
1226
               for(const auto& cpuid_str : m_cpu_flags) {
23,221✔
1227
                  if(const auto bit = Botan::CPUID::Feature::from_string(cpuid_str)) {
16,559✔
1228
                     if(Botan::CPUID::has(*bit)) {
16,559✔
1229
                        Botan::CPUID::clear_cpuid_bit(*bit);
14,035✔
1230
                        // now re-run the test
1231
                        result.merge(run_one_test(header, vars));
14,035✔
1232
                     }
1233
                  }
1234
               }
1235
               Botan::CPUID::initialize();
6,662✔
1236
            }
1237
#endif
1238
            result.set_ns_consumed(Test::timestamp() - start);
47,626✔
1239

1240
            if(result.tests_failed() > 0) {
47,626✔
1241
               std::ostringstream oss;
×
1242
               oss << "Test # " << test_cnt << " ";
×
1243
               if(!header.empty()) {
×
1244
                  oss << header << " ";
×
1245
               }
1246
               oss << "failed ";
×
1247

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

1252
               result.test_note(oss.str());
×
1253
            }
×
1254
            results.push_back(result);
47,626✔
1255
         } catch(std::exception& e) {
47,626✔
1256
            std::ostringstream oss;
×
1257
            oss << "Test # " << test_cnt << " ";
×
1258
            if(!header.empty()) {
×
1259
               oss << header << " ";
×
1260
            }
1261

1262
            for(const auto& k : m_required_keys) {
×
1263
               oss << k << "=" << vars.get_req_str(k) << " ";
×
1264
            }
1265

1266
            oss << "failed with exception '" << e.what() << "'";
×
1267

1268
            results.push_back(Test::Result::Failure(header_or_name, oss.str()));
×
1269
         }
×
1270

1271
         if(clear_between_callbacks()) {
47,626✔
1272
            vars.clear();
32,854✔
1273
         }
1274
      }
1275
   }
155,044✔
1276

1277
   if(results.empty()) {
180✔
1278
      return results;
1279
   }
1280

1281
   try {
180✔
1282
      std::vector<Test::Result> final_tests = run_final_tests();
180✔
1283
      results.insert(results.end(), final_tests.begin(), final_tests.end());
180✔
1284
   } catch(std::exception& e) {
180✔
1285
      results.push_back(Test::Result::Failure(header_or_name, "run_final_tests exception " + std::string(e.what())));
×
1286
   }
×
1287

1288
   m_first = true;
180✔
1289

1290
   return results;
180✔
1291
}
180✔
1292

1293
std::map<std::string, std::string> Test_Options::report_properties() const {
1✔
1294
   std::map<std::string, std::string> result;
1✔
1295

1296
   for(const auto& prop : m_report_properties) {
3✔
1297
      const auto colon = prop.find(':');
2✔
1298
      // props without a colon separator or without a name are not allowed
1299
      if(colon == std::string::npos || colon == 0) {
2✔
1300
         throw Test_Error("--report-properties should be of the form <key>:<value>,<key>:<value>,...");
×
1301
      }
1302

1303
      result.insert_or_assign(prop.substr(0, colon), prop.substr(colon + 1, prop.size() - colon - 1));
4✔
1304
   }
1305

1306
   return result;
1✔
1307
}
×
1308

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