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

randombit / botan / 11844561993

14 Nov 2024 07:58PM UTC coverage: 91.178% (+0.1%) from 91.072%
11844561993

Pull #4435

github

web-flow
Merge 81dcb29da into e430f157a
Pull Request #4435: Test duration values ​​are now presented in seconds with six digits of precision. Tests without time measurements have been edited.

91856 of 100744 relevant lines covered (91.18%)

9311006.71 hits per line

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

78.28
/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/parsing.h>
14
#include <botan/internal/stl_util.h>
15
#include <fstream>
16
#include <iomanip>
17
#include <sstream>
18

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

23
#if defined(BOTAN_HAS_EC_CURVE_GFP)
24
   #include <botan/ec_point.h>
25
#endif
26

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

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

39
namespace Botan_Tests {
40

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

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

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

61
void Test::Result::start_timer() {
19,795✔
62
   if(m_started == 0) {
19,795✔
63
      m_started = Test::timestamp();
39,562✔
64
   }
65
}
19,795✔
66

67
void Test::Result::end_timer() {
19,870✔
68
   if(m_started > 0) {
19,870✔
69
      m_ns_taken += Test::timestamp() - m_started;
39,544✔
70
      m_started = 0;
19,772✔
71
   }
72
}
19,870✔
73

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

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

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

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

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

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

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

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

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

134
bool Test::Result::test_success(const std::string& note) {
3,147,903✔
135
   if(Test::options().log_success()) {
3,147,772✔
136
      test_note(note);
×
137
   }
138
   ++m_tests_passed;
3,147,903✔
139
   return true;
447,082✔
140
}
141

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

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

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

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

160
namespace {
161

162
bool same_contents(const uint8_t x[], const uint8_t y[], size_t len) {
198,289✔
163
   return (len == 0) ? true : std::memcmp(x, y, len) == 0;
196,952✔
164
}
165

166
}  // namespace
167

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

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

189
   std::ostringstream err;
1✔
190

191
   err << who();
1✔
192

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

267
   return test_success();
11,188✔
268
}
269

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

277
   return test_success();
2,035,566✔
278
}
279

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

289
   return test_success();
2,277,356✔
290
}
291

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

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

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

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

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

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

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

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

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

338
#if defined(BOTAN_HAS_EC_CURVE_GFP)
339
bool Test::Result::test_eq(const std::string& what, const Botan::EC_Point& a, const Botan::EC_Point& b) {
5,382✔
340
   //return test_is_eq(what, a, b);
341
   if(a == b) {
5,382✔
342
      return test_success();
10,764✔
343
   }
344

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

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

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

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

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

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

389
   return test_success();
604✔
390
}
391

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

397
Botan::RandomNumberGenerator& Test::rng() const {
263,232✔
398
   if(!m_test_rng) {
263,232✔
399
      m_test_rng = Test::new_rng(m_test_name);
132✔
400
   }
401

402
   return *m_test_rng;
263,232✔
403
}
404

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

409
//static
410
std::string Test::format_time(uint64_t ns) {
1,894✔
411
   // Use a stringstream to convert nanoseconds to seconds. ns in a second is 1'000'000'000
412
   std::ostringstream o;
1,894✔
413
   o << std::fixed << std::setprecision(6) << static_cast<long double>(ns) / 1'000'000'000 << " sec";
1,894✔
414
   return o.str();
3,788✔
415
}
1,894✔
416

417
Test::Result::Result(std::string who, const std::vector<Result>& downstream_results) : Result(std::move(who)) {
14✔
418
   for(const auto& result : downstream_results) {
82✔
419
      merge(result, true /* ignore non-matching test names */);
68✔
420
   }
421
}
14✔
422

423
// TODO: this should move to `StdoutReporter`
424
std::string Test::Result::result_string() const {
2,185✔
425
   const bool verbose = Test::options().verbose();
2,185✔
426

427
   if(tests_run() == 0 && !verbose) {
2,185✔
428
      return "";
22✔
429
   }
430

431
   std::ostringstream report;
2,163✔
432

433
   report << who() << " ran ";
2,163✔
434

435
   if(tests_run() == 0) {
2,163✔
436
      report << "ZERO";
×
437
   } else {
438
      report << tests_run();
2,163✔
439
   }
440
   report << " tests";
2,163✔
441

442
   if(m_ns_taken > 0) {
2,163✔
443
      report << " in " << format_time(m_ns_taken);
3,786✔
444
   }
445

446
   if(tests_failed()) {
2,163✔
447
      report << " " << tests_failed() << " FAILED";
25✔
448
   } else {
449
      report << " all ok";
2,138✔
450
   }
451

452
   report << "\n";
2,163✔
453

454
   for(size_t i = 0; i != m_fail_log.size(); ++i) {
2,188✔
455
      report << "Failure " << (i + 1) << ": " << m_fail_log[i];
25✔
456
      if(m_where) {
25✔
457
         report << " (at " << m_where->path << ":" << m_where->line << ")";
×
458
      }
459
      report << "\n";
25✔
460
   }
461

462
   if(!m_fail_log.empty() || tests_run() == 0 || verbose) {
2,163✔
463
      for(size_t i = 0; i != m_log.size(); ++i) {
25✔
464
         report << "Note " << (i + 1) << ": " << m_log[i] << "\n";
×
465
      }
466
   }
467

468
   return report.str();
2,163✔
469
}
2,163✔
470

471
namespace {
472

473
class Test_Registry {
474
   public:
475
      static Test_Registry& instance() {
1,169✔
476
         static Test_Registry registry;
1,170✔
477
         return registry;
1,169✔
478
      }
479

480
      void register_test(const std::string& category,
384✔
481
                         const std::string& name,
482
                         bool smoke_test,
483
                         bool needs_serialization,
484
                         std::function<std::unique_ptr<Test>()> maker_fn) {
485
         if(m_tests.contains(name)) {
384✔
486
            throw Test_Error("Duplicate registration of test '" + name + "'");
×
487
         }
488

489
         if(m_tests.contains(category)) {
384✔
490
            throw Test_Error("'" + category + "' cannot be used as category, test exists");
×
491
         }
492

493
         if(m_categories.contains(name)) {
384✔
494
            throw Test_Error("'" + name + "' cannot be used as test name, category exists");
×
495
         }
496

497
         if(smoke_test) {
384✔
498
            m_smoke_tests.push_back(name);
10✔
499
         }
500

501
         if(needs_serialization) {
384✔
502
            m_mutexed_tests.push_back(name);
21✔
503
         }
504

505
         m_tests.emplace(name, std::move(maker_fn));
384✔
506
         m_categories.emplace(category, name);
384✔
507
      }
384✔
508

509
      std::unique_ptr<Test> get_test(const std::string& test_name) const {
384✔
510
         auto i = m_tests.find(test_name);
384✔
511
         if(i != m_tests.end()) {
384✔
512
            return i->second();
384✔
513
         }
514
         return nullptr;
×
515
      }
516

517
      std::set<std::string> registered_tests() const {
×
518
         std::set<std::string> s;
×
519
         for(auto&& i : m_tests) {
×
520
            s.insert(i.first);
×
521
         }
522
         return s;
×
523
      }
×
524

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

533
      std::vector<std::string> filter_registered_tests(const std::vector<std::string>& requested,
1✔
534
                                                       const std::set<std::string>& to_be_skipped) {
535
         std::vector<std::string> result;
1✔
536

537
         // TODO: this is O(n^2), but we have a relatively small number of tests.
538
         auto insert_if_not_exists_and_not_skipped = [&](const std::string& test_name) {
385✔
539
            if(!Botan::value_exists(result, test_name) && to_be_skipped.find(test_name) == to_be_skipped.end()) {
384✔
540
               result.push_back(test_name);
374✔
541
            }
542
         };
385✔
543

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

568
         return result;
1✔
569
      }
×
570

571
      bool needs_serialization(const std::string& test_name) const {
400✔
572
         return Botan::value_exists(m_mutexed_tests, test_name);
16✔
573
      }
574

575
   private:
576
      Test_Registry() = default;
1✔
577

578
   private:
579
      std::map<std::string, std::function<std::unique_ptr<Test>()>> m_tests;
580
      std::multimap<std::string, std::string> m_categories;
581
      std::vector<std::string> m_smoke_tests;
582
      std::vector<std::string> m_mutexed_tests;
583
};
584

585
}  // namespace
586

587
// static Test:: functions
588

589
//static
590
void Test::register_test(const std::string& category,
384✔
591
                         const std::string& name,
592
                         bool smoke_test,
593
                         bool needs_serialization,
594
                         std::function<std::unique_ptr<Test>()> maker_fn) {
595
   Test_Registry::instance().register_test(category, name, smoke_test, needs_serialization, std::move(maker_fn));
768✔
596
}
384✔
597

598
//static
599
uint64_t Test::timestamp() {
133,565✔
600
   auto now = std::chrono::high_resolution_clock::now().time_since_epoch();
133,565✔
601
   return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
39,553✔
602
}
603

604
//static
605
std::vector<Test::Result> Test::flatten_result_lists(std::vector<std::vector<Test::Result>> result_lists) {
4✔
606
   std::vector<Test::Result> results;
4✔
607
   for(auto& result_list : result_lists) {
26✔
608
      for(auto& result : result_list) {
71✔
609
         results.emplace_back(std::move(result));
49✔
610
      }
611
   }
612
   return results;
4✔
613
}
×
614

615
//static
616
std::set<std::string> Test::registered_tests() {
×
617
   return Test_Registry::instance().registered_tests();
×
618
}
619

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

625
//static
626
std::unique_ptr<Test> Test::get_test(const std::string& test_name) {
384✔
627
   return Test_Registry::instance().get_test(test_name);
384✔
628
}
629

630
//static
631
bool Test::test_needs_serialization(const std::string& test_name) {
384✔
632
   return Test_Registry::instance().needs_serialization(test_name);
384✔
633
}
634

635
//static
636
std::vector<std::string> Test::filter_registered_tests(const std::vector<std::string>& requested,
1✔
637
                                                       const std::set<std::string>& to_be_skipped) {
638
   return Test_Registry::instance().filter_registered_tests(requested, to_be_skipped);
1✔
639
}
640

641
//static
642
std::string Test::temp_file_name(const std::string& basename) {
21✔
643
   // TODO add a --tmp-dir option to the tests to specify where these files go
644

645
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
646

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

650
   int fd = ::mkstemp(&mkstemp_basename[0]);
21✔
651

652
   // error
653
   if(fd < 0) {
21✔
654
      return "";
×
655
   }
656

657
   ::close(fd);
21✔
658

659
   return mkstemp_basename;
21✔
660
#else
661
   // For now just create the temp in the current working directory
662
   return basename;
663
#endif
664
}
21✔
665

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

678
std::string Test::read_data_file(const std::string& path) {
37✔
679
   const std::string fsname = Test::data_file(path);
37✔
680
   std::ifstream file(fsname.c_str());
37✔
681
   if(!file.good()) {
37✔
682
      throw Test_Error("Error reading from " + fsname);
×
683
   }
684

685
   return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
74✔
686
}
37✔
687

688
std::vector<uint8_t> Test::read_binary_data_file(const std::string& path) {
34✔
689
   const std::string fsname = Test::data_file(path);
34✔
690
   std::ifstream file(fsname.c_str(), std::ios::binary);
34✔
691
   if(!file.good()) {
34✔
692
      throw Test_Error("Error reading from " + fsname);
×
693
   }
694

695
   std::vector<uint8_t> contents;
34✔
696

697
   while(file.good()) {
74✔
698
      std::vector<uint8_t> buf(4096);
40✔
699
      file.read(reinterpret_cast<char*>(buf.data()), buf.size());
40✔
700
      const size_t got = static_cast<size_t>(file.gcount());
40✔
701

702
      if(got == 0 && file.eof()) {
40✔
703
         break;
704
      }
705

706
      contents.insert(contents.end(), buf.data(), buf.data() + got);
40✔
707
   }
40✔
708

709
   return contents;
68✔
710
}
34✔
711

712
// static member variables of Test
713

714
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
715
Test_Options Test::m_opts;
716
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
717
std::string Test::m_test_rng_seed;
718

719
//static
720
void Test::set_test_options(const Test_Options& opts) {
1✔
721
   m_opts = opts;
1✔
722
}
1✔
723

724
namespace {
725

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

734
      void clear() override { m_x = 0; }
×
735

736
      bool accepts_input() const override { return true; }
×
737

738
      bool is_seeded() const override { return true; }
117,849✔
739

740
      void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> input) override {
1,519,893✔
741
         for(const auto byte : input) {
1,519,893✔
742
            mix(byte);
×
743
         }
744

745
         for(auto& byte : output) {
14,924,212✔
746
            byte = mix();
13,404,319✔
747
         }
748
      }
1,519,893✔
749

750
      Testsuite_RNG(std::string_view seed, std::string_view test_name) {
242✔
751
         m_x = 0;
242✔
752

753
         for(char c : seed) {
7,260✔
754
            this->mix(static_cast<uint8_t>(c));
7,018✔
755
         }
756
         for(char c : test_name) {
4,347✔
757
            this->mix(static_cast<uint8_t>(c));
4,105✔
758
         }
759
      }
242✔
760

761
   private:
762
      uint8_t mix(uint8_t input = 0) {
13,415,442✔
763
         m_x ^= input;
13,415,442✔
764
         m_x *= 0xF2E16957;
13,415,442✔
765
         m_x += 0xE50B590F;
13,415,442✔
766
         return static_cast<uint8_t>(m_x >> 27);
13,415,442✔
767
      }
768

769
      uint64_t m_x;
770
};
771

772
}  // namespace
773

774
//static
775
void Test::set_test_rng_seed(std::span<const uint8_t> seed, size_t epoch) {
1✔
776
   m_test_rng_seed = Botan::fmt("seed={} epoch={}", Botan::hex_encode(seed), epoch);
1✔
777
}
1✔
778

779
//static
780
std::unique_ptr<Botan::RandomNumberGenerator> Test::new_rng(std::string_view test_name) {
234✔
781
   return std::make_unique<Testsuite_RNG>(m_test_rng_seed, test_name);
234✔
782
}
783

784
//static
785
std::shared_ptr<Botan::RandomNumberGenerator> Test::new_shared_rng(std::string_view test_name) {
8✔
786
   return std::make_shared<Testsuite_RNG>(m_test_rng_seed, test_name);
8✔
787
}
788

789
//static
790
std::string Test::data_file(const std::string& file) {
615✔
791
   return options().data_dir() + "/" + file;
1,230✔
792
}
793

794
//static
795
std::string Test::data_dir(const std::string& subdir) {
1✔
796
   return options().data_dir() + "/" + subdir;
2✔
797
}
798

799
//static
800
std::vector<std::string> Test::files_in_data_dir(const std::string& subdir) {
263✔
801
   auto fs = Botan::get_files_recursive(options().data_dir() + "/" + subdir);
789✔
802
   if(fs.empty()) {
263✔
803
      throw Test_Error("Test::files_in_data_dir encountered empty subdir " + subdir);
×
804
   }
805
   return fs;
263✔
806
}
×
807

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

822
//static
823
std::vector<std::string> Test::provider_filter(const std::vector<std::string>& in) {
46,017✔
824
   if(m_opts.provider().empty()) {
46,017✔
825
      return in;
46,017✔
826
   }
827
   for(auto&& provider : in) {
×
828
      if(provider == m_opts.provider()) {
×
829
         return std::vector<std::string>{provider};
×
830
      }
831
   }
832
   return std::vector<std::string>{};
×
833
}
×
834

835
std::string Test::random_password(Botan::RandomNumberGenerator& rng) {
218✔
836
   const size_t len = 1 + rng.next_byte() % 32;
218✔
837
   return Botan::hex_encode(rng.random_vec(len));
436✔
838
}
839

840
std::vector<std::vector<uint8_t>> VarMap::get_req_bin_list(const std::string& key) const {
12✔
841
   auto i = m_vars.find(key);
12✔
842
   if(i == m_vars.end()) {
12✔
843
      throw Test_Error("Test missing variable " + key);
×
844
   }
845

846
   std::vector<std::vector<uint8_t>> bin_list;
12✔
847

848
   for(auto&& part : Botan::split_on(i->second, ',')) {
62✔
849
      try {
50✔
850
         bin_list.push_back(Botan::hex_decode(part));
100✔
851
      } catch(std::exception& e) {
×
852
         std::ostringstream oss;
×
853
         oss << "Bad input '" << part << "'"
×
854
             << " in binary list key " << key << " - " << e.what();
×
855
         throw Test_Error(oss.str());
×
856
      }
×
857
   }
12✔
858

859
   return bin_list;
12✔
860
}
×
861

862
std::vector<uint8_t> VarMap::get_req_bin(const std::string& key) const {
148,603✔
863
   auto i = m_vars.find(key);
148,603✔
864
   if(i == m_vars.end()) {
148,603✔
865
      throw Test_Error("Test missing variable " + key);
×
866
   }
867

868
   try {
148,603✔
869
      if(i->second.starts_with("0x")) {
148,603✔
870
         if(i->second.size() % 2 == 0) {
156✔
871
            return Botan::hex_decode(i->second.substr(2));
308✔
872
         } else {
873
            std::string z = i->second;
2✔
874
            std::swap(z[0], z[1]);  // swap 0x to x0 then remove x
2✔
875
            return Botan::hex_decode(z.substr(1));
4✔
876
         }
2✔
877
      } else {
878
         return Botan::hex_decode(i->second);
148,447✔
879
      }
880
   } catch(std::exception& e) {
×
881
      std::ostringstream oss;
×
882
      oss << "Bad input '" << i->second << "'"
×
883
          << " for key " << key << " - " << e.what();
×
884
      throw Test_Error(oss.str());
×
885
   }
×
886
}
2✔
887

888
std::string VarMap::get_opt_str(const std::string& key, const std::string& def_value) const {
14,143✔
889
   auto i = m_vars.find(key);
14,143✔
890
   if(i == m_vars.end()) {
14,143✔
891
      return def_value;
14,089✔
892
   }
893
   return i->second;
54✔
894
}
895

896
bool VarMap::get_req_bool(const std::string& key) const {
19✔
897
   auto i = m_vars.find(key);
19✔
898
   if(i == m_vars.end()) {
19✔
899
      throw Test_Error("Test missing variable " + key);
×
900
   }
901

902
   if(i->second == "true") {
19✔
903
      return true;
904
   } else if(i->second == "false") {
11✔
905
      return false;
906
   } else {
907
      throw Test_Error("Invalid boolean for key '" + key + "' value '" + i->second + "'");
×
908
   }
909
}
910

911
size_t VarMap::get_req_sz(const std::string& key) const {
4,513✔
912
   auto i = m_vars.find(key);
4,513✔
913
   if(i == m_vars.end()) {
4,513✔
914
      throw Test_Error("Test missing variable " + key);
×
915
   }
916
   return Botan::to_u32bit(i->second);
4,513✔
917
}
918

919
uint8_t VarMap::get_req_u8(const std::string& key) const {
17✔
920
   const size_t s = this->get_req_sz(key);
17✔
921
   if(s > 256) {
17✔
922
      throw Test_Error("Invalid " + key + " expected uint8_t got " + std::to_string(s));
×
923
   }
924
   return static_cast<uint8_t>(s);
17✔
925
}
926

927
uint32_t VarMap::get_req_u32(const std::string& key) const {
14✔
928
   return static_cast<uint32_t>(get_req_sz(key));
14✔
929
}
930

931
uint64_t VarMap::get_req_u64(const std::string& key) const {
17✔
932
   auto i = m_vars.find(key);
17✔
933
   if(i == m_vars.end()) {
17✔
934
      throw Test_Error("Test missing variable " + key);
×
935
   }
936
   try {
17✔
937
      return std::stoull(i->second);
17✔
938
   } catch(std::exception&) {
×
939
      throw Test_Error("Invalid u64 value '" + i->second + "'");
×
940
   }
×
941
}
942

943
size_t VarMap::get_opt_sz(const std::string& key, const size_t def_value) const {
30,045✔
944
   auto i = m_vars.find(key);
30,045✔
945
   if(i == m_vars.end()) {
30,045✔
946
      return def_value;
947
   }
948
   return Botan::to_u32bit(i->second);
12,023✔
949
}
950

951
uint64_t VarMap::get_opt_u64(const std::string& key, const uint64_t def_value) const {
3,538✔
952
   auto i = m_vars.find(key);
3,538✔
953
   if(i == m_vars.end()) {
3,538✔
954
      return def_value;
955
   }
956
   try {
641✔
957
      return std::stoull(i->second);
3,538✔
958
   } catch(std::exception&) {
×
959
      throw Test_Error("Invalid u64 value '" + i->second + "'");
×
960
   }
×
961
}
962

963
std::vector<uint8_t> VarMap::get_opt_bin(const std::string& key) const {
39,635✔
964
   auto i = m_vars.find(key);
39,635✔
965
   if(i == m_vars.end()) {
39,635✔
966
      return std::vector<uint8_t>();
28,323✔
967
   }
968

969
   try {
11,312✔
970
      return Botan::hex_decode(i->second);
11,312✔
971
   } catch(std::exception&) {
×
972
      throw Test_Error("Test invalid hex input '" + i->second + "'" + +" for key " + key);
×
973
   }
×
974
}
975

976
std::string VarMap::get_req_str(const std::string& key) const {
39,959✔
977
   auto i = m_vars.find(key);
39,959✔
978
   if(i == m_vars.end()) {
39,959✔
979
      throw Test_Error("Test missing variable " + key);
×
980
   }
981
   return i->second;
39,959✔
982
}
983

984
#if defined(BOTAN_HAS_BIGINT)
985
Botan::BigInt VarMap::get_req_bn(const std::string& key) const {
36,287✔
986
   auto i = m_vars.find(key);
36,287✔
987
   if(i == m_vars.end()) {
36,287✔
988
      throw Test_Error("Test missing variable " + key);
×
989
   }
990

991
   try {
36,287✔
992
      return Botan::BigInt(i->second);
36,287✔
993
   } catch(std::exception&) {
×
994
      throw Test_Error("Test invalid bigint input '" + i->second + "' for key " + key);
×
995
   }
×
996
}
997

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

1000
{
1001
   auto i = m_vars.find(key);
80✔
1002
   if(i == m_vars.end()) {
80✔
1003
      return def_value;
24✔
1004
   }
1005

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

1014
Text_Based_Test::Text_Based_Test(const std::string& data_src,
172✔
1015
                                 const std::string& required_keys_str,
1016
                                 const std::string& optional_keys_str) :
172✔
1017
      m_data_src(data_src) {
516✔
1018
   if(required_keys_str.empty()) {
172✔
1019
      throw Test_Error("Invalid test spec");
×
1020
   }
1021

1022
   std::vector<std::string> required_keys = Botan::split_on(required_keys_str, ',');
172✔
1023
   std::vector<std::string> optional_keys = Botan::split_on(optional_keys_str, ',');
172✔
1024

1025
   m_required_keys.insert(required_keys.begin(), required_keys.end());
172✔
1026
   m_optional_keys.insert(optional_keys.begin(), optional_keys.end());
172✔
1027
   m_output_key = required_keys.at(required_keys.size() - 1);
172✔
1028
}
172✔
1029

1030
std::string Text_Based_Test::get_next_line() {
150,009✔
1031
   while(true) {
150,257✔
1032
      if(m_cur == nullptr || m_cur->good() == false) {
150,257✔
1033
         if(m_srcs.empty()) {
429✔
1034
            if(m_first) {
344✔
1035
               if(m_data_src.ends_with(".vec")) {
172✔
1036
                  m_srcs.push_back(Test::data_file(m_data_src));
320✔
1037
               } else {
1038
                  const auto fs = Test::files_in_data_dir(m_data_src);
12✔
1039
                  m_srcs.assign(fs.begin(), fs.end());
12✔
1040
                  if(m_srcs.empty()) {
12✔
1041
                     throw Test_Error("Error reading test data dir " + m_data_src);
×
1042
                  }
1043
               }
12✔
1044

1045
               m_first = false;
172✔
1046
            } else {
1047
               return "";  // done
172✔
1048
            }
1049
         }
1050

1051
         m_cur = std::make_unique<std::ifstream>(m_srcs[0]);
342✔
1052
         m_cur_src_name = m_srcs[0];
257✔
1053

1054
         // Reinit cpuid on new file if needed
1055
         if(m_cpu_flags.empty() == false) {
257✔
1056
            m_cpu_flags.clear();
13✔
1057
            Botan::CPUID::initialize();
13✔
1058
         }
1059

1060
         if(!m_cur->good()) {
257✔
1061
            throw Test_Error("Could not open input file '" + m_cur_src_name);
×
1062
         }
1063

1064
         m_srcs.pop_front();
257✔
1065
      }
1066

1067
      while(m_cur->good()) {
218,609✔
1068
         std::string line;
218,361✔
1069
         std::getline(*m_cur, line);
218,361✔
1070

1071
         if(line.empty()) {
218,361✔
1072
            continue;
54,051✔
1073
         }
1074

1075
         if(line[0] == '#') {
164,310✔
1076
            if(line.compare(0, 6, "#test ") == 0) {
14,489✔
1077
               return line;
16✔
1078
            } else {
1079
               continue;
14,473✔
1080
            }
1081
         }
1082

1083
         return line;
149,821✔
1084
      }
218,361✔
1085
   }
1086
}
1087

1088
namespace {
1089

1090
// strips leading and trailing but not internal whitespace
1091
std::string strip_ws(const std::string& in) {
298,242✔
1092
   const char* whitespace = " ";
298,242✔
1093

1094
   const auto first_c = in.find_first_not_of(whitespace);
298,242✔
1095
   if(first_c == std::string::npos) {
298,242✔
1096
      return "";
1,001✔
1097
   }
1098

1099
   const auto last_c = in.find_last_not_of(whitespace);
297,241✔
1100

1101
   return in.substr(first_c, last_c - first_c + 1);
297,241✔
1102
}
1103

1104
std::vector<uint64_t> parse_cpuid_bits(const std::vector<std::string>& tok) {
16✔
1105
   std::vector<uint64_t> bits;
16✔
1106
   for(size_t i = 1; i < tok.size(); ++i) {
58✔
1107
      const std::vector<Botan::CPUID::CPUID_bits> more = Botan::CPUID::bit_from_string(tok[i]);
42✔
1108
      bits.insert(bits.end(), more.begin(), more.end());
42✔
1109
   }
42✔
1110

1111
   return bits;
16✔
1112
}
×
1113

1114
}  // namespace
1115

1116
bool Text_Based_Test::skip_this_test(const std::string& /*header*/, const VarMap& /*vars*/) {
46,171✔
1117
   return false;
46,171✔
1118
}
1119

1120
std::vector<Test::Result> Text_Based_Test::run() {
172✔
1121
   std::vector<Test::Result> results;
172✔
1122

1123
   std::string header, header_or_name = m_data_src;
172✔
1124
   VarMap vars;
172✔
1125
   size_t test_cnt = 0;
172✔
1126

1127
   while(true) {
150,009✔
1128
      const std::string line = get_next_line();
150,009✔
1129
      if(line.empty())  // EOF
150,009✔
1130
      {
1131
         break;
1132
      }
1133

1134
      if(line.compare(0, 6, "#test ") == 0) {
149,837✔
1135
         std::vector<std::string> pragma_tokens = Botan::split_on(line.substr(6), ' ');
16✔
1136

1137
         if(pragma_tokens.empty()) {
16✔
1138
            throw Test_Error("Empty pragma found in " + m_cur_src_name);
×
1139
         }
1140

1141
         if(pragma_tokens[0] != "cpuid") {
16✔
1142
            throw Test_Error("Unknown test pragma '" + line + "' in " + m_cur_src_name);
×
1143
         }
1144

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

1149
         m_cpu_flags = parse_cpuid_bits(pragma_tokens);
16✔
1150

1151
         continue;
16✔
1152
      } else if(line[0] == '#') {
149,837✔
1153
         throw Test_Error("Unknown test pragma '" + line + "' in " + m_cur_src_name);
×
1154
      }
1155

1156
      if(line[0] == '[' && line[line.size() - 1] == ']') {
149,821✔
1157
         header = line.substr(1, line.size() - 2);
700✔
1158
         header_or_name = header;
700✔
1159
         test_cnt = 0;
700✔
1160
         vars.clear();
700✔
1161
         continue;
700✔
1162
      }
1163

1164
      const std::string test_id = "test " + std::to_string(test_cnt);
298,242✔
1165

1166
      auto equal_i = line.find_first_of('=');
149,121✔
1167

1168
      if(equal_i == std::string::npos) {
149,121✔
1169
         results.push_back(Test::Result::Failure(header_or_name, "invalid input '" + line + "'"));
×
1170
         continue;
×
1171
      }
1172

1173
      std::string key = strip_ws(std::string(line.begin(), line.begin() + equal_i - 1));
298,242✔
1174
      std::string val = strip_ws(std::string(line.begin() + equal_i + 1, line.end()));
298,242✔
1175

1176
      if(!m_required_keys.contains(key) && !m_optional_keys.contains(key)) {
149,121✔
1177
         auto r = Test::Result::Failure(header_or_name, Botan::fmt("{} failed unknown key {}", test_id, key));
×
1178
         results.push_back(r);
×
1179
      }
×
1180

1181
      vars.add(key, val);
149,121✔
1182

1183
      if(key == m_output_key) {
149,121✔
1184
         try {
47,142✔
1185
            for(auto& req_key : m_required_keys) {
237,756✔
1186
               if(!vars.has_key(req_key)) {
381,228✔
1187
                  auto r =
×
1188
                     Test::Result::Failure(header_or_name, Botan::fmt("{} missing required key {}", test_id, req_key));
×
1189
                  results.push_back(r);
×
1190
               }
×
1191
            }
1192

1193
            if(skip_this_test(header, vars)) {
47,142✔
1194
               continue;
138✔
1195
            }
1196

1197
            ++test_cnt;
47,004✔
1198

1199
            uint64_t start = Test::timestamp();
47,004✔
1200

1201
            Test::Result result = run_one_test(header, vars);
47,004✔
1202
            if(!m_cpu_flags.empty()) {
47,004✔
1203
               for(const auto& cpuid_u64 : m_cpu_flags) {
19,298✔
1204
                  Botan::CPUID::CPUID_bits cpuid_bit = static_cast<Botan::CPUID::CPUID_bits>(cpuid_u64);
13,687✔
1205
                  if(Botan::CPUID::has_cpuid_bit(cpuid_bit)) {
13,687✔
1206
                     Botan::CPUID::clear_cpuid_bit(cpuid_bit);
12,512✔
1207
                     // now re-run the test
1208
                     result.merge(run_one_test(header, vars));
12,512✔
1209
                  }
1210
               }
1211
               Botan::CPUID::initialize();
5,611✔
1212
            }
1213
            result.set_ns_consumed(Test::timestamp() - start);
47,004✔
1214

1215
            if(result.tests_failed()) {
47,004✔
1216
               std::ostringstream oss;
×
1217
               oss << "Test # " << test_cnt << " ";
×
1218
               if(!header.empty()) {
×
1219
                  oss << header << " ";
×
1220
               }
1221
               oss << "failed ";
×
1222

1223
               for(const auto& k : m_required_keys) {
×
1224
                  oss << k << "=" << vars.get_req_str(k) << " ";
×
1225
               }
1226

1227
               result.test_note(oss.str());
×
1228
            }
×
1229
            results.push_back(result);
47,004✔
1230
         } catch(std::exception& e) {
47,004✔
1231
            std::ostringstream oss;
×
1232
            oss << "Test # " << test_cnt << " ";
×
1233
            if(!header.empty()) {
×
1234
               oss << header << " ";
×
1235
            }
1236

1237
            for(const auto& k : m_required_keys) {
×
1238
               oss << k << "=" << vars.get_req_str(k) << " ";
×
1239
            }
1240

1241
            oss << "failed with exception '" << e.what() << "'";
×
1242

1243
            results.push_back(Test::Result::Failure(header_or_name, oss.str()));
×
1244
         }
×
1245

1246
         if(clear_between_callbacks()) {
47,004✔
1247
            vars.clear();
32,492✔
1248
         }
1249
      }
1250
   }
150,113✔
1251

1252
   if(results.empty()) {
172✔
1253
      return results;
1254
   }
1255

1256
   try {
172✔
1257
      std::vector<Test::Result> final_tests = run_final_tests();
172✔
1258
      results.insert(results.end(), final_tests.begin(), final_tests.end());
172✔
1259
   } catch(std::exception& e) {
172✔
1260
      results.push_back(Test::Result::Failure(header_or_name, "run_final_tests exception " + std::string(e.what())));
×
1261
   }
×
1262

1263
   m_first = true;
172✔
1264

1265
   return results;
172✔
1266
}
172✔
1267

1268
std::map<std::string, std::string> Test_Options::report_properties() const {
1✔
1269
   std::map<std::string, std::string> result;
1✔
1270

1271
   for(const auto& prop : m_report_properties) {
3✔
1272
      const auto colon = prop.find(':');
2✔
1273
      // props without a colon separator or without a name are not allowed
1274
      if(colon == std::string::npos || colon == 0) {
2✔
1275
         throw Test_Error("--report-properties should be of the form <key>:<value>,<key>:<value>,...");
×
1276
      }
1277

1278
      result.insert_or_assign(prop.substr(0, colon), prop.substr(colon + 1, prop.size() - colon - 1));
4✔
1279
   }
1280

1281
   return result;
1✔
1282
}
×
1283

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

© 2025 Coveralls, Inc