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

randombit / botan / 12869303872

20 Jan 2025 01:40PM UTC coverage: 91.213% (+0.01%) from 91.202%
12869303872

push

github

web-flow
Merge pull request #4569 from randombit/jack/mod-inv-distinguish-cases

When computing modular inverses distingush which case we are in

93546 of 102558 relevant lines covered (91.21%)

11542300.02 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 <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_LEGACY_EC_POINT)
25
   #include <botan/ec_point.h>
26
#endif
27

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

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

40
namespace Botan_Tests {
41

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

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

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

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

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

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

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

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

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

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

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

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

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

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

135
bool Test::Result::test_success(const std::string& note) {
3,237,726✔
136
   if(Test::options().log_success()) {
3,237,595✔
137
      test_note(note);
×
138
   }
139
   ++m_tests_passed;
3,237,726✔
140
   return true;
465,992✔
141
}
142

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

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

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

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

161
namespace {
162

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

167
}  // namespace
168

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

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

190
   std::ostringstream err;
1✔
191

192
   err << who();
1✔
193

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

268
   return test_success();
11,224✔
269
}
270

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

278
   return test_success();
2,043,254✔
279
}
280

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

290
   return test_success();
2,277,400✔
291
}
292

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

302
   return test_success();
28,906✔
303
}
304

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

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

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

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

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

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

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

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

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

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

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

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

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

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

390
   return test_success();
668✔
391
}
392

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

398
Botan::RandomNumberGenerator& Test::rng() const {
280,220✔
399
   if(!m_test_rng) {
280,220✔
400
      m_test_rng = Test::new_rng(m_test_name);
140✔
401
   }
402

403
   return *m_test_rng;
280,220✔
404
}
405

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

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

414
   if(ns > 1000000000) {
1,393✔
415
      o << std::setprecision(2) << std::fixed << ns / 1000000000.0 << " sec";
160✔
416
   } else {
417
      o << std::setprecision(2) << std::fixed << ns / 1000000.0 << " msec";
1,233✔
418
   }
419

420
   return o.str();
2,786✔
421
}
1,393✔
422

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

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

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

437
   std::ostringstream report;
2,410✔
438

439
   report << who() << " ran ";
2,410✔
440

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

448
   if(m_ns_taken > 0) {
2,410✔
449
      report << " in " << format_time(m_ns_taken);
2,784✔
450
   }
451

452
   if(tests_failed()) {
2,410✔
453
      report << " " << tests_failed() << " FAILED";
25✔
454
   } else {
455
      report << " all ok";
2,385✔
456
   }
457

458
   report << "\n";
2,410✔
459

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

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

474
   return report.str();
2,410✔
475
}
2,410✔
476

477
namespace {
478

479
class Test_Registry {
480
   public:
481
      static Test_Registry& instance() {
1,202✔
482
         static Test_Registry registry;
1,203✔
483
         return registry;
1,202✔
484
      }
485

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

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

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

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

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

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

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

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

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

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

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

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

574
         return result;
1✔
575
      }
×
576

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

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

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

591
}  // namespace
592

593
// static Test:: functions
594

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

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

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

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

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

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

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

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

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

651
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
652

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

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

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

663
   ::close(fd);
21✔
664

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

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

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

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

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

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

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

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

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

715
   return contents;
68✔
716
}
34✔
717

718
// static member variables of Test
719

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

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

730
namespace {
731

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

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

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

744
      bool is_seeded() const override { return true; }
350,242✔
745

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

751
         for(auto& byte : output) {
40,692,552✔
752
            byte = mix();
30,655,879✔
753
         }
754
      }
10,036,673✔
755

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

759
         for(char c : seed) {
7,530✔
760
            this->mix(static_cast<uint8_t>(c));
7,279✔
761
         }
762
         for(char c : test_name) {
4,477✔
763
            this->mix(static_cast<uint8_t>(c));
4,226✔
764
         }
765
      }
251✔
766

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

775
      uint64_t m_x;
776
};
777

778
}  // namespace
779

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

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

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

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

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

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

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

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

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

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

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

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

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

869
   return bin_list;
12✔
870
}
×
871

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1077
      while(m_cur->good()) {
225,022✔
1078
         std::string line;
224,773✔
1079
         std::getline(*m_cur, line);
224,773✔
1080

1081
         if(line.empty()) {
224,773✔
1082
            continue;
54,815✔
1083
         }
1084

1085
         if(line[0] == '#') {
169,958✔
1086
            if(line.starts_with("#test ")) {
15,100✔
1087
               return line;
16✔
1088
            } else {
1089
               continue;
15,084✔
1090
            }
1091
         }
1092

1093
         return line;
154,858✔
1094
      }
224,773✔
1095
   }
1096
}
1097

1098
namespace {
1099

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

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

1109
   const auto last_c = in.find_last_not_of(whitespace);
307,185✔
1110

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

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

1121
   return bits;
16✔
1122
}
×
1123

1124
}  // namespace
1125

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

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

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

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

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

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

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

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

1159
         m_cpu_flags = parse_cpuid_bits(pragma_tokens);
16✔
1160

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

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

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

1176
      auto equal_i = line.find_first_of('=');
154,093✔
1177

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

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

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

1191
      vars.add(key, val);
154,093✔
1192

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

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

1207
            ++test_cnt;
47,748✔
1208

1209
            uint64_t start = Test::timestamp();
47,748✔
1210

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

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

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

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

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

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

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

1256
         if(clear_between_callbacks()) {
47,748✔
1257
            vars.clear();
33,015✔
1258
         }
1259
      }
1260
   }
155,150✔
1261

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

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

1273
   m_first = true;
175✔
1274

1275
   return results;
175✔
1276
}
175✔
1277

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

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

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

1291
   return result;
1✔
1292
}
×
1293

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