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

randombit / botan / 5111374265

29 May 2023 11:19AM UTC coverage: 92.227% (+0.5%) from 91.723%
5111374265

push

github

randombit
Next release will be 3.1.0. Update release notes

75588 of 81959 relevant lines covered (92.23%)

11886470.91 hits per line

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

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

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

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

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

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

38
namespace Botan_Tests {
39

40
void Test::Result::merge(const Result& other, bool ignore_test_name) {
104,789✔
41
   if(who() != other.who()) {
104,789✔
42
      if(!ignore_test_name)
48✔
43
         throw Test_Error("Merging tests from different sources");
×
44

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

52
   m_timestamp = std::min(m_timestamp, other.m_timestamp);
104,789✔
53
   m_ns_taken += other.m_ns_taken;
104,789✔
54
   m_tests_passed += other.m_tests_passed;
104,789✔
55
   m_fail_log.insert(m_fail_log.end(), other.m_fail_log.begin(), other.m_fail_log.end());
104,789✔
56
   m_log.insert(m_log.end(), other.m_log.begin(), other.m_log.end());
104,789✔
57
}
104,789✔
58

59
void Test::Result::start_timer() {
744✔
60
   if(m_started == 0) {
744✔
61
      m_started = Test::timestamp();
744✔
62
   }
63
}
744✔
64

65
void Test::Result::end_timer() {
828✔
66
   if(m_started > 0) {
828✔
67
      m_ns_taken += Test::timestamp() - m_started;
1,486✔
68
      m_started = 0;
743✔
69
   }
70
}
828✔
71

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

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

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

92
bool Test::Result::ThrowExpectations::check(const std::string& test_name, Test::Result& result) {
46,055✔
93
   m_consumed = true;
46,055✔
94

95
   try {
46,055✔
96
      m_fn();
46,055✔
97
      if(!m_expect_success)
62✔
98
         return result.test_failure(test_name + " failed to throw expected exception");
4✔
99
   } catch(const std::exception& ex) {
45,993✔
100
      if(m_expect_success)
45,991✔
101
         return result.test_failure(test_name + " threw unexpected exception: " + ex.what());
2✔
102
      if(m_expected_exception_type.has_value() && m_expected_exception_type.value() != typeid(ex))
45,990✔
103
         return result.test_failure(test_name + " threw unexpected exception: " + ex.what());
4✔
104
      if(m_expected_message.has_value() && m_expected_message.value() != ex.what())
45,988✔
105
         return result.test_failure(test_name + " threw exception with unexpected message (expected: '" +
3✔
106
                                    m_expected_message.value() + "', got: '" + ex.what() + "')");
3✔
107
   } catch(...) {
45,993✔
108
      if(m_expect_success || m_expected_exception_type.has_value() || m_expected_message.has_value())
2✔
109
         return result.test_failure(test_name + " threw unexpected unknown exception");
1✔
110
   }
2✔
111

112
   return result.test_success(test_name + " behaved as expected");
92,096✔
113
}
114

115
bool Test::Result::test_throws(const std::string& what, const std::function<void()>& fn) {
45,797✔
116
   return ThrowExpectations(fn).check(what, *this);
91,594✔
117
}
118

119
bool Test::Result::test_throws(const std::string& what, const std::string& expected, const std::function<void()>& fn) {
154✔
120
   return ThrowExpectations(fn).expect_message(expected).check(what, *this);
308✔
121
}
122

123
bool Test::Result::test_no_throw(const std::string& what, const std::function<void()>& fn) {
61✔
124
   return ThrowExpectations(fn).expect_success().check(what, *this);
122✔
125
}
126

127
bool Test::Result::test_success(const std::string& note) {
2,985,319✔
128
   if(Test::options().log_success()) {
2,985,212✔
129
      test_note(note);
×
130
   }
131
   ++m_tests_passed;
2,985,319✔
132
   return true;
398,933✔
133
}
134

135
bool Test::Result::test_failure(const std::string& what, const std::string& error) {
1✔
136
   return test_failure(who() + " " + what + " with error " + error);
2✔
137
}
138

139
void Test::Result::test_failure(const std::string& what, const uint8_t buf[], size_t buf_len) {
1✔
140
   test_failure(who() + ": " + what + " buf len " + std::to_string(buf_len) + " value " +
2✔
141
                Botan::hex_encode(buf, buf_len));
1✔
142
}
1✔
143

144
bool Test::Result::test_failure(const std::string& err) {
25✔
145
   m_fail_log.push_back(err);
25✔
146

147
   if(Test::options().abort_on_first_fail() && m_who != "Failing Test") {
25✔
148
      std::abort();
×
149
   }
150
   return false;
25✔
151
}
152

153
bool Test::Result::test_ne(const std::string& what,
1,940✔
154
                           const uint8_t produced[],
155
                           size_t produced_len,
156
                           const uint8_t expected[],
157
                           size_t expected_len) {
158
   if(produced_len == expected_len && Botan::same_mem(produced, expected, expected_len)) {
2,130✔
159
      return test_failure(who() + ": " + what + " produced matching");
4✔
160
   }
161
   return test_success();
3,876✔
162
}
163

164
bool Test::Result::test_eq(const char* producer,
169,020✔
165
                           const std::string& what,
166
                           const uint8_t produced[],
167
                           size_t produced_size,
168
                           const uint8_t expected[],
169
                           size_t expected_size) {
170
   if(produced_size == expected_size && Botan::same_mem(produced, expected, expected_size)) {
338,039✔
171
      return test_success();
338,038✔
172
   }
173

174
   std::ostringstream err;
1✔
175

176
   err << who();
1✔
177

178
   if(producer) {
1✔
179
      err << " producer '" << producer << "'";
×
180
   }
181

182
   err << " unexpected result for " << what;
1✔
183

184
   if(produced_size != expected_size) {
1✔
185
      err << " produced " << produced_size << " bytes expected " << expected_size;
1✔
186
   }
187

188
   std::vector<uint8_t> xor_diff(std::min(produced_size, expected_size));
2✔
189
   size_t bytes_different = 0;
1✔
190

191
   for(size_t i = 0; i != xor_diff.size(); ++i) {
4✔
192
      xor_diff[i] = produced[i] ^ expected[i];
3✔
193
      bytes_different += (xor_diff[i] > 0);
3✔
194
   }
195

196
   err << "\nProduced: " << Botan::hex_encode(produced, produced_size)
1✔
197
       << "\nExpected: " << Botan::hex_encode(expected, expected_size);
3✔
198

199
   if(bytes_different > 0) {
1✔
200
      err << "\nXOR Diff: " << Botan::hex_encode(xor_diff);
1✔
201
   }
202

203
   return test_failure(err.str());
2✔
204
}
1✔
205

206
bool Test::Result::test_is_nonempty(const std::string& what_is_it, const std::string& to_examine) {
29,010✔
207
   if(to_examine.empty()) {
29,010✔
208
      return test_failure(what_is_it + " was empty");
1✔
209
   }
210
   return test_success();
58,018✔
211
}
212

213
bool Test::Result::test_eq(const std::string& what, const std::string& produced, const std::string& expected) {
61,162✔
214
   return test_is_eq(what, produced, expected);
61,162✔
215
}
216

217
bool Test::Result::test_eq(const std::string& what, const char* produced, const char* expected) {
20✔
218
   return test_is_eq(what, std::string(produced), std::string(expected));
34✔
219
}
220

221
bool Test::Result::test_eq(const std::string& what, size_t produced, size_t expected) {
125,386✔
222
   return test_is_eq(what, produced, expected);
125,386✔
223
}
224

225
bool Test::Result::test_eq_sz(const std::string& what, size_t produced, size_t expected) {
665✔
226
   return test_is_eq(what, produced, expected);
665✔
227
}
228

229
bool Test::Result::test_eq(const std::string& what,
107✔
230
                           const Botan::OctetString& produced,
231
                           const Botan::OctetString& expected) {
232
   std::ostringstream out;
107✔
233
   out << m_who << " " << what;
107✔
234

235
   if(produced == expected) {
107✔
236
      out << " produced expected result " << produced.to_string();
214✔
237
      return test_success(out.str());
214✔
238
   } else {
239
      out << " produced unexpected result '" << produced.to_string() << "' expected '" << expected.to_string() << "'";
×
240
      return test_failure(out.str());
×
241
   }
242
}
107✔
243

244
bool Test::Result::test_lt(const std::string& what, size_t produced, size_t expected) {
4,928✔
245
   if(produced >= expected) {
4,928✔
246
      std::ostringstream err;
1✔
247
      err << m_who << " " << what;
1✔
248
      err << " unexpected result " << produced << " >= " << expected;
1✔
249
      return test_failure(err.str());
1✔
250
   }
1✔
251

252
   return test_success();
9,854✔
253
}
254

255
bool Test::Result::test_lte(const std::string& what, size_t produced, size_t expected) {
1,017,710✔
256
   if(produced > expected) {
1,017,710✔
257
      std::ostringstream err;
1✔
258
      err << m_who << " " << what << " unexpected result " << produced << " > " << expected;
1✔
259
      return test_failure(err.str());
1✔
260
   }
1✔
261

262
   return test_success();
2,035,418✔
263
}
264

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

274
   return test_success();
2,259,848✔
275
}
276

277
bool Test::Result::test_gt(const std::string& what, size_t produced, size_t expected) {
14,382✔
278
   if(produced <= expected) {
14,382✔
279
      std::ostringstream err;
×
280
      err << m_who;
×
281
      err << " " << what;
×
282
      err << " unexpected result " << produced << " <= " << expected;
×
283
      return test_failure(err.str());
×
284
   }
×
285

286
   return test_success();
28,764✔
287
}
288

289
bool Test::Result::test_ne(const std::string& what, const std::string& str1, const std::string& str2) {
25✔
290
   if(str1 != str2) {
25✔
291
      return test_success(str1 + " != " + str2);
63✔
292
   }
293

294
   return test_failure(who() + " " + what + " produced matching strings " + str1);
2✔
295
}
296

297
bool Test::Result::test_ne(const std::string& what, size_t produced, size_t expected) {
9✔
298
   if(produced != expected) {
9✔
299
      return test_success();
16✔
300
   }
301

302
   std::ostringstream err;
1✔
303
   err << who() << " " << what << " produced " << produced << " unexpected value";
1✔
304
   return test_failure(err.str());
1✔
305
}
1✔
306

307
#if defined(BOTAN_HAS_BIGINT)
308
bool Test::Result::test_eq(const std::string& what, const BigInt& produced, const BigInt& expected) {
10,523✔
309
   return test_is_eq(what, produced, expected);
10,523✔
310
}
311

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

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

323
#if defined(BOTAN_HAS_EC_CURVE_GFP)
324
bool Test::Result::test_eq(const std::string& what, const Botan::EC_Point& a, const Botan::EC_Point& b) {
3,135✔
325
   //return test_is_eq(what, a, b);
326
   if(a == b) {
3,135✔
327
      return test_success();
6,270✔
328
   }
329

330
   std::ostringstream err;
×
331
   err << who() << " " << what << " a=(" << a.get_affine_x() << "," << a.get_affine_y() << ")"
×
332
       << " b=(" << b.get_affine_x() << "," << b.get_affine_y();
×
333
   return test_failure(err.str());
×
334
}
×
335
#endif
336

337
bool Test::Result::test_eq(const std::string& what, bool produced, bool expected) {
281,181✔
338
   return test_is_eq(what, produced, expected);
281,181✔
339
}
340

341
bool Test::Result::test_rc_init(const std::string& func, int rc) {
36✔
342
   if(rc == 0) {
36✔
343
      return test_success();
72✔
344
   } else {
345
      std::ostringstream msg;
×
346
      msg << m_who;
×
347
      msg << " " << func;
×
348

349
      // -40 is BOTAN_FFI_ERROR_NOT_IMPLEMENTED
350
      if(rc == -40)
×
351
         msg << " returned not implemented";
×
352
      else
353
         msg << " unexpectedly failed with error code " << rc;
×
354

355
      if(rc == -40)
×
356
         this->test_note(msg.str());
×
357
      else
358
         this->test_failure(msg.str());
×
359
      return false;
×
360
   }
×
361
}
362

363
bool Test::Result::test_rc(const std::string& func, int expected, int rc) {
231✔
364
   if(expected != rc) {
231✔
365
      std::ostringstream err;
1✔
366
      err << m_who;
1✔
367
      err << " call to " << func << " unexpectedly returned " << rc;
1✔
368
      err << " but expecting " << expected;
1✔
369
      return test_failure(err.str());
1✔
370
   }
1✔
371

372
   return test_success();
460✔
373
}
374

375
std::vector<std::string> Test::possible_providers(const std::string& /*unused*/) {
×
376
   return Test::provider_filter({"base"});
×
377
}
378

379
//static
380
std::string Test::format_time(uint64_t ns) {
975✔
381
   std::ostringstream o;
975✔
382

383
   if(ns > 1000000000) {
975✔
384
      o << std::setprecision(2) << std::fixed << ns / 1000000000.0 << " sec";
119✔
385
   } else {
386
      o << std::setprecision(2) << std::fixed << ns / 1000000.0 << " msec";
856✔
387
   }
388

389
   return o.str();
1,950✔
390
}
975✔
391

392
Test::Result::Result(std::string who, const std::vector<Result>& downstream_results) : Result(std::move(who)) {
20✔
393
   for(const auto& result : downstream_results)
58✔
394
      merge(result, true /* ignore non-matching test names */);
48✔
395
}
10✔
396

397
// TODO: this should move to `StdoutReporter`
398
std::string Test::Result::result_string() const {
1,668✔
399
   const bool verbose = Test::options().verbose();
1,668✔
400

401
   if(tests_run() == 0 && !verbose) {
1,668✔
402
      return "";
13✔
403
   }
404

405
   std::ostringstream report;
1,655✔
406

407
   report << who() << " ran ";
1,655✔
408

409
   if(tests_run() == 0) {
1,655✔
410
      report << "ZERO";
×
411
   } else {
412
      report << tests_run();
1,655✔
413
   }
414
   report << " tests";
1,655✔
415

416
   if(m_ns_taken > 0) {
1,655✔
417
      report << " in " << format_time(m_ns_taken);
1,948✔
418
   }
419

420
   if(tests_failed()) {
1,655✔
421
      report << " " << tests_failed() << " FAILED";
25✔
422
   } else {
423
      report << " all ok";
1,630✔
424
   }
425

426
   report << "\n";
1,655✔
427

428
   for(size_t i = 0; i != m_fail_log.size(); ++i) {
1,680✔
429
      report << "Failure " << (i + 1) << ": " << m_fail_log[i];
25✔
430
      if(m_where) {
25✔
431
         report << " (at " << m_where->path << ":" << m_where->line << ")";
×
432
      }
433
      report << "\n";
25✔
434
   }
435

436
   if(!m_fail_log.empty() || tests_run() == 0 || verbose) {
1,655✔
437
      for(size_t i = 0; i != m_log.size(); ++i) {
25✔
438
         report << "Note " << (i + 1) << ": " << m_log[i] << "\n";
×
439
      }
440
   }
441

442
   return report.str();
1,655✔
443
}
1,655✔
444

445
namespace {
446

447
class Test_Registry {
448
   public:
449
      static Test_Registry& instance() {
934✔
450
         static Test_Registry registry;
935✔
451
         return registry;
934✔
452
      }
453

454
      void register_test(std::string category,
311✔
455
                         std::string name,
456
                         bool smoke_test,
457
                         bool needs_serialization,
458
                         std::function<std::unique_ptr<Test>()> maker_fn) {
459
         if(m_tests.contains(name))
311✔
460
            throw Test_Error("Duplicate registration of test '" + name + "'");
×
461

462
         if(m_tests.contains(category))
311✔
463
            throw Test_Error("'" + category + "' cannot be used as category, test exists");
×
464

465
         if(m_categories.contains(name))
311✔
466
            throw Test_Error("'" + name + "' cannot be used as test name, category exists");
×
467

468
         if(smoke_test)
311✔
469
            m_smoke_tests.push_back(name);
10✔
470

471
         if(needs_serialization)
311✔
472
            m_mutexed_tests.push_back(name);
17✔
473

474
         m_tests.emplace(name, std::move(maker_fn));
311✔
475
         m_categories.emplace(std::move(category), std::move(name));
311✔
476
      }
311✔
477

478
      std::unique_ptr<Test> get_test(const std::string& test_name) const {
311✔
479
         auto i = m_tests.find(test_name);
311✔
480
         if(i != m_tests.end()) {
311✔
481
            return i->second();
311✔
482
         }
483
         return nullptr;
×
484
      }
485

486
      std::set<std::string> registered_tests() const { return Botan::map_keys_as_set(m_tests); }
×
487

488
      std::set<std::string> registered_test_categories() const { return Botan::map_keys_as_set(m_categories); }
×
489

490
      std::vector<std::string> filter_registered_tests(const std::vector<std::string>& requested,
1✔
491
                                                       const std::set<std::string>& to_be_skipped) {
492
         std::vector<std::string> result;
1✔
493

494
         // TODO: this is O(n^2), but we have a relatively small number of tests.
495
         auto insert_if_not_exists_and_not_skipped = [&](const std::string& test_name) {
312✔
496
            if(!Botan::value_exists(result, test_name) && to_be_skipped.find(test_name) == to_be_skipped.end())
311✔
497
               result.push_back(test_name);
301✔
498
         };
312✔
499

500
         if(requested.empty()) {
1✔
501
            /*
502
            If nothing was requested on the command line, run everything. First
503
            run the "essentials" to smoke test, then everything else in
504
            alphabetical order.
505
            */
506
            result = m_smoke_tests;
1✔
507
            for(const auto& [test_name, _] : m_tests) {
312✔
508
               insert_if_not_exists_and_not_skipped(test_name);
311✔
509
            }
510
         } else {
511
            for(const auto& r : requested) {
×
512
               if(m_tests.find(r) != m_tests.end()) {
×
513
                  insert_if_not_exists_and_not_skipped(r);
×
514
               } else if(auto elems = m_categories.equal_range(r); elems.first != m_categories.end()) {
×
515
                  for(; elems.first != elems.second; ++elems.first) {
×
516
                     insert_if_not_exists_and_not_skipped(elems.first->second);
×
517
                  }
518
               } else {
519
                  throw Botan_Tests::Test_Error("Unknown test suite or category: " + r);
×
520
               }
521
            }
522
         }
523

524
         return result;
1✔
525
      }
×
526

527
      bool needs_serialization(const std::string& test_name) const {
311✔
528
         return Botan::value_exists(m_mutexed_tests, test_name);
311✔
529
      }
530

531
   private:
532
      Test_Registry() = default;
1✔
533

534
   private:
535
      std::map<std::string, std::function<std::unique_ptr<Test>()>> m_tests;
536
      std::multimap<std::string, std::string> m_categories;
537
      std::vector<std::string> m_smoke_tests;
538
      std::vector<std::string> m_mutexed_tests;
539
};
540

541
}  // namespace
542

543
// static Test:: functions
544

545
//static
546
void Test::register_test(std::string category,
311✔
547
                         std::string name,
548
                         bool smoke_test,
549
                         bool needs_serialization,
550
                         std::function<std::unique_ptr<Test>()> maker_fn) {
551
   Test_Registry::instance().register_test(
933✔
552
      std::move(category), std::move(name), smoke_test, needs_serialization, std::move(maker_fn));
933✔
553
}
311✔
554

555
//static
556
uint64_t Test::timestamp() {
88,773✔
557
   auto now = std::chrono::high_resolution_clock::now().time_since_epoch();
88,773✔
558
   return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
88,769✔
559
}
560

561
//static
562
std::vector<Test::Result> Test::flatten_result_lists(std::vector<std::vector<Test::Result>> result_lists) {
4✔
563
   std::vector<Test::Result> results;
4✔
564
   for(auto& result_list : result_lists) {
24✔
565
      for(auto& result : result_list) {
65✔
566
         results.emplace_back(std::move(result));
45✔
567
      }
568
   }
569
   return results;
4✔
570
}
×
571

572
//static
573
std::set<std::string> Test::registered_tests() { return Test_Registry::instance().registered_tests(); }
×
574

575
//static
576
std::set<std::string> Test::registered_test_categories() {
×
577
   return Test_Registry::instance().registered_test_categories();
×
578
}
579

580
//static
581
std::unique_ptr<Test> Test::get_test(const std::string& test_name) {
311✔
582
   return Test_Registry::instance().get_test(test_name);
311✔
583
}
584

585
//static
586
bool Test::test_needs_serialization(const std::string& test_name) {
311✔
587
   return Test_Registry::instance().needs_serialization(test_name);
311✔
588
}
589

590
//static
591
std::vector<std::string> Test::filter_registered_tests(const std::vector<std::string>& requested,
1✔
592
                                                       const std::set<std::string>& to_be_skipped) {
593
   return Test_Registry::instance().filter_registered_tests(requested, to_be_skipped);
1✔
594
}
595

596
//static
597
std::string Test::temp_file_name(const std::string& basename) {
21✔
598
   // TODO add a --tmp-dir option to the tests to specify where these files go
599

600
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
601

602
   // POSIX only calls for 6 'X' chars but OpenBSD allows arbitrary amount
603
   std::string mkstemp_basename = "/tmp/" + basename + ".XXXXXXXXXX";
21✔
604

605
   int fd = ::mkstemp(&mkstemp_basename[0]);
21✔
606

607
   // error
608
   if(fd < 0) {
21✔
609
      return "";
×
610
   }
611

612
   ::close(fd);
21✔
613

614
   return mkstemp_basename;
42✔
615
#else
616
   // For now just create the temp in the current working directory
617
   return basename;
618
#endif
619
}
21✔
620

621
bool Test::copy_file(const std::string& from, const std::string& to) {
1✔
622
#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(__cpp_lib_filesystem)
623
   std::error_code ec;  // don't throw, just return false on error
1✔
624
   return std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing, ec);
1✔
625
#else
626
   // TODO: implement fallbacks to POSIX or WIN32
627
   // ... but then again: it's 2023 and we're using C++20 :o)
628
   BOTAN_UNUSED(from, to);
629
   throw Botan::No_Filesystem_Access();
630
#endif
631
}
632

633
std::string Test::read_data_file(const std::string& path) {
24✔
634
   const std::string fsname = Test::data_file(path);
24✔
635
   std::ifstream file(fsname.c_str());
24✔
636
   if(!file.good()) {
24✔
637
      throw Test_Error("Error reading from " + fsname);
×
638
   }
639

640
   return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
24✔
641
}
48✔
642

643
std::vector<uint8_t> Test::read_binary_data_file(const std::string& path) {
33✔
644
   const std::string fsname = Test::data_file(path);
33✔
645
   std::ifstream file(fsname.c_str(), std::ios::binary);
33✔
646
   if(!file.good()) {
33✔
647
      throw Test_Error("Error reading from " + fsname);
×
648
   }
649

650
   std::vector<uint8_t> contents;
33✔
651

652
   while(file.good()) {
71✔
653
      std::vector<uint8_t> buf(4096);
38✔
654
      file.read(reinterpret_cast<char*>(buf.data()), buf.size());
38✔
655
      const size_t got = static_cast<size_t>(file.gcount());
38✔
656

657
      if(got == 0 && file.eof()) {
38✔
658
         break;
659
      }
660

661
      contents.insert(contents.end(), buf.data(), buf.data() + got);
38✔
662
   }
38✔
663

664
   return contents;
66✔
665
}
66✔
666

667
// static member variables of Test
668

669
Test_Options Test::m_opts;
670
std::shared_ptr<Botan::RandomNumberGenerator> Test::m_test_rng;
671

672
//static
673
void Test::set_test_options(const Test_Options& opts) { m_opts = opts; }
1✔
674

675
//static
676
void Test::set_test_rng(std::shared_ptr<Botan::RandomNumberGenerator> rng) { m_test_rng = std::move(rng); }
1✔
677

678
//static
679
std::string Test::data_file(const std::string& what) { return Test::data_dir() + "/" + what; }
498✔
680

681
//static
682
std::string Test::data_file_as_temporary_copy(const std::string& what) {
1✔
683
   auto tmp_basename = what;
1✔
684
   std::replace(tmp_basename.begin(), tmp_basename.end(), '/', '_');
1✔
685
   auto temp_file = temp_file_name("tmp-" + tmp_basename);
1✔
686
   if(temp_file.empty()) {
1✔
687
      return "";
×
688
   }
689
   if(!Test::copy_file(data_file(what), temp_file)) {
2✔
690
      return "";
×
691
   }
692
   return temp_file;
2✔
693
}
2✔
694

695
//static
696
std::vector<std::string> Test::provider_filter(const std::vector<std::string>& in) {
41,862✔
697
   if(m_opts.provider().empty()) {
41,862✔
698
      return in;
41,862✔
699
   }
700
   for(auto&& provider : in) {
×
701
      if(provider == m_opts.provider()) {
×
702
         return std::vector<std::string>{provider};
×
703
      }
704
   }
705
   return std::vector<std::string>{};
41,862✔
706
}
707

708
//static
709
Botan::RandomNumberGenerator& Test::rng() {
540,749✔
710
   if(!m_test_rng) {
540,749✔
711
      throw Test_Error("Test requires RNG but no RNG set with Test::set_test_rng");
×
712
   }
713
   return *m_test_rng;
540,749✔
714
}
715

716
//static
717
std::shared_ptr<Botan::RandomNumberGenerator> Test::rng_as_shared() {
107✔
718
   if(!m_test_rng) {
107✔
719
      throw Test_Error("Test requires RNG but no RNG set with Test::set_test_rng");
×
720
   }
721
   return m_test_rng;
107✔
722
}
723

724
std::string Test::random_password() {
84✔
725
   const size_t len = 1 + Test::rng().next_byte() % 32;
84✔
726
   return Botan::hex_encode(Test::rng().random_vec(len));
168✔
727
}
728

729
std::vector<std::vector<uint8_t>> VarMap::get_req_bin_list(const std::string& key) const {
12✔
730
   auto i = m_vars.find(key);
12✔
731
   if(i == m_vars.end()) {
12✔
732
      throw Test_Error("Test missing variable " + key);
×
733
   }
734

735
   std::vector<std::vector<uint8_t>> bin_list;
12✔
736

737
   for(auto&& part : Botan::split_on(i->second, ',')) {
62✔
738
      try {
50✔
739
         bin_list.push_back(Botan::hex_decode(part));
100✔
740
      } catch(std::exception& e) {
×
741
         std::ostringstream oss;
×
742
         oss << "Bad input '" << part << "'"
×
743
             << " in binary list key " << key << " - " << e.what();
×
744
         throw Test_Error(oss.str());
×
745
      }
×
746
   }
12✔
747

748
   return bin_list;
12✔
749
}
×
750

751
std::vector<uint8_t> VarMap::get_req_bin(const std::string& key) const {
130,271✔
752
   auto i = m_vars.find(key);
130,271✔
753
   if(i == m_vars.end()) {
130,271✔
754
      throw Test_Error("Test missing variable " + key);
×
755
   }
756

757
   try {
130,271✔
758
      return Botan::hex_decode(i->second);
130,271✔
759
   } catch(std::exception& e) {
×
760
      std::ostringstream oss;
×
761
      oss << "Bad input '" << i->second << "'"
×
762
          << " for key " << key << " - " << e.what();
×
763
      throw Test_Error(oss.str());
×
764
   }
×
765
}
766

767
std::string VarMap::get_opt_str(const std::string& key, const std::string& def_value) const {
13,538✔
768
   auto i = m_vars.find(key);
13,538✔
769
   if(i == m_vars.end()) {
13,538✔
770
      return def_value;
13,487✔
771
   }
772
   return i->second;
51✔
773
}
774

775
bool VarMap::get_req_bool(const std::string& key) const {
19✔
776
   auto i = m_vars.find(key);
19✔
777
   if(i == m_vars.end()) {
19✔
778
      throw Test_Error("Test missing variable " + key);
×
779
   }
780

781
   if(i->second == "true") {
19✔
782
      return true;
783
   } else if(i->second == "false") {
11✔
784
      return false;
785
   } else {
786
      throw Test_Error("Invalid boolean for key '" + key + "' value '" + i->second + "'");
×
787
   }
788
}
789

790
size_t VarMap::get_req_sz(const std::string& key) const {
4,368✔
791
   auto i = m_vars.find(key);
4,368✔
792
   if(i == m_vars.end()) {
4,368✔
793
      throw Test_Error("Test missing variable " + key);
×
794
   }
795
   return Botan::to_u32bit(i->second);
4,368✔
796
}
797

798
uint8_t VarMap::get_req_u8(const std::string& key) const {
17✔
799
   const size_t s = this->get_req_sz(key);
17✔
800
   if(s > 256) {
17✔
801
      throw Test_Error("Invalid " + key + " expected uint8_t got " + std::to_string(s));
×
802
   }
803
   return static_cast<uint8_t>(s);
17✔
804
}
805

806
uint32_t VarMap::get_req_u32(const std::string& key) const { return static_cast<uint32_t>(get_req_sz(key)); }
3✔
807

808
uint64_t VarMap::get_req_u64(const std::string& key) const {
13✔
809
   auto i = m_vars.find(key);
13✔
810
   if(i == m_vars.end()) {
13✔
811
      throw Test_Error("Test missing variable " + key);
×
812
   }
813
   try {
13✔
814
      return std::stoull(i->second);
13✔
815
   } catch(std::exception&) { throw Test_Error("Invalid u64 value '" + i->second + "'"); }
×
816
}
817

818
size_t VarMap::get_opt_sz(const std::string& key, const size_t def_value) const {
26,919✔
819
   auto i = m_vars.find(key);
26,919✔
820
   if(i == m_vars.end()) {
26,919✔
821
      return def_value;
822
   }
823
   return Botan::to_u32bit(i->second);
11,934✔
824
}
825

826
uint64_t VarMap::get_opt_u64(const std::string& key, const uint64_t def_value) const {
3,538✔
827
   auto i = m_vars.find(key);
3,538✔
828
   if(i == m_vars.end()) {
3,538✔
829
      return def_value;
830
   }
831
   try {
641✔
832
      return std::stoull(i->second);
3,538✔
833
   } catch(std::exception&) { throw Test_Error("Invalid u64 value '" + i->second + "'"); }
×
834
}
835

836
std::vector<uint8_t> VarMap::get_opt_bin(const std::string& key) const {
36,280✔
837
   auto i = m_vars.find(key);
36,280✔
838
   if(i == m_vars.end()) {
36,280✔
839
      return std::vector<uint8_t>();
36,280✔
840
   }
841

842
   try {
11,134✔
843
      return Botan::hex_decode(i->second);
11,134✔
844
   } catch(std::exception&) { throw Test_Error("Test invalid hex input '" + i->second + "'" + +" for key " + key); }
×
845
}
846

847
std::string VarMap::get_req_str(const std::string& key) const {
39,499✔
848
   auto i = m_vars.find(key);
39,499✔
849
   if(i == m_vars.end()) {
39,499✔
850
      throw Test_Error("Test missing variable " + key);
×
851
   }
852
   return i->second;
39,499✔
853
}
854

855
#if defined(BOTAN_HAS_BIGINT)
856
Botan::BigInt VarMap::get_req_bn(const std::string& key) const {
36,602✔
857
   auto i = m_vars.find(key);
36,602✔
858
   if(i == m_vars.end()) {
36,602✔
859
      throw Test_Error("Test missing variable " + key);
×
860
   }
861

862
   try {
36,602✔
863
      return Botan::BigInt(i->second);
36,602✔
864
   } catch(std::exception&) { throw Test_Error("Test invalid bigint input '" + i->second + "' for key " + key); }
×
865
}
866

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

869
{
870
   auto i = m_vars.find(key);
80✔
871
   if(i == m_vars.end()) {
80✔
872
      return def_value;
104✔
873
   }
874

875
   try {
56✔
876
      return Botan::BigInt(i->second);
56✔
877
   } catch(std::exception&) { throw Test_Error("Test invalid bigint input '" + i->second + "' for key " + key); }
×
878
}
879
#endif
880

881
Text_Based_Test::Text_Based_Test(const std::string& data_src,
147✔
882
                                 const std::string& required_keys_str,
883
                                 const std::string& optional_keys_str) :
147✔
884
      m_data_src(data_src) {
441✔
885
   if(required_keys_str.empty()) {
147✔
886
      throw Test_Error("Invalid test spec");
×
887
   }
888

889
   std::vector<std::string> required_keys = Botan::split_on(required_keys_str, ',');
147✔
890
   std::vector<std::string> optional_keys = Botan::split_on(optional_keys_str, ',');
147✔
891

892
   m_required_keys.insert(required_keys.begin(), required_keys.end());
147✔
893
   m_optional_keys.insert(optional_keys.begin(), optional_keys.end());
147✔
894
   m_output_key = required_keys.at(required_keys.size() - 1);
147✔
895
}
147✔
896

897
std::string Text_Based_Test::get_next_line() {
140,347✔
898
   while(true) {
140,566✔
899
      if(m_cur == nullptr || m_cur->good() == false) {
140,566✔
900
         if(m_srcs.empty()) {
374✔
901
            if(m_first) {
294✔
902
               const std::string full_path = Test::data_dir() + "/" + m_data_src;
147✔
903
               if(full_path.find(".vec") != std::string::npos) {
147✔
904
                  m_srcs.push_back(full_path);
136✔
905
               } else {
906
                  const auto fs = Botan::get_files_recursive(full_path);
11✔
907
                  m_srcs.assign(fs.begin(), fs.end());
11✔
908
                  if(m_srcs.empty()) {
11✔
909
                     throw Test_Error("Error reading test data dir " + full_path);
×
910
                  }
911
               }
11✔
912

913
               m_first = false;
147✔
914
            } else {
147✔
915
               return "";  // done
147✔
916
            }
917
         }
918

919
         m_cur = std::make_unique<std::ifstream>(m_srcs[0]);
307✔
920
         m_cur_src_name = m_srcs[0];
227✔
921

922
         // Reinit cpuid on new file if needed
923
         if(m_cpu_flags.empty() == false) {
227✔
924
            m_cpu_flags.clear();
12✔
925
            Botan::CPUID::initialize();
12✔
926
         }
927

928
         if(!m_cur->good()) {
227✔
929
            throw Test_Error("Could not open input file '" + m_cur_src_name);
×
930
         }
931

932
         m_srcs.pop_front();
227✔
933
      }
934

935
      while(m_cur->good()) {
204,347✔
936
         std::string line;
204,128✔
937
         std::getline(*m_cur, line);
204,128✔
938

939
         if(line.empty()) {
204,128✔
940
            continue;
50,461✔
941
         }
942

943
         if(line[0] == '#') {
153,667✔
944
            if(line.compare(0, 6, "#test ") == 0) {
13,483✔
945
               return line;
16✔
946
            } else {
947
               continue;
13,467✔
948
            }
949
         }
950

951
         return line;
280,384✔
952
      }
204,128✔
953
   }
954
}
955

956
namespace {
957

958
// strips leading and trailing but not internal whitespace
959
std::string strip_ws(const std::string& in) {
279,188✔
960
   const char* whitespace = " ";
279,188✔
961

962
   const auto first_c = in.find_first_not_of(whitespace);
279,188✔
963
   if(first_c == std::string::npos) {
279,188✔
964
      return "";
943✔
965
   }
966

967
   const auto last_c = in.find_last_not_of(whitespace);
278,245✔
968

969
   return in.substr(first_c, last_c - first_c + 1);
278,245✔
970
}
971

972
std::vector<uint64_t> parse_cpuid_bits(const std::vector<std::string>& tok) {
16✔
973
   std::vector<uint64_t> bits;
16✔
974
   for(size_t i = 1; i < tok.size(); ++i) {
55✔
975
      const std::vector<Botan::CPUID::CPUID_bits> more = Botan::CPUID::bit_from_string(tok[i]);
39✔
976
      bits.insert(bits.end(), more.begin(), more.end());
39✔
977
   }
39✔
978

979
   return bits;
16✔
980
}
×
981

982
}  // namespace
983

984
bool Text_Based_Test::skip_this_test(const std::string& /*header*/, const VarMap& /*vars*/) { return false; }
43,600✔
985

986
std::vector<Test::Result> Text_Based_Test::run() {
147✔
987
   std::vector<Test::Result> results;
147✔
988

989
   std::string header, header_or_name = m_data_src;
147✔
990
   VarMap vars;
147✔
991
   size_t test_cnt = 0;
147✔
992

993
   while(true) {
140,347✔
994
      const std::string line = get_next_line();
140,347✔
995
      if(line.empty())  // EOF
140,347✔
996
      {
997
         break;
998
      }
999

1000
      if(line.compare(0, 6, "#test ") == 0) {
140,200✔
1001
         std::vector<std::string> pragma_tokens = Botan::split_on(line.substr(6), ' ');
16✔
1002

1003
         if(pragma_tokens.empty()) {
16✔
1004
            throw Test_Error("Empty pragma found in " + m_cur_src_name);
×
1005
         }
1006

1007
         if(pragma_tokens[0] != "cpuid") {
16✔
1008
            throw Test_Error("Unknown test pragma '" + line + "' in " + m_cur_src_name);
×
1009
         }
1010

1011
         m_cpu_flags = parse_cpuid_bits(pragma_tokens);
16✔
1012

1013
         continue;
16✔
1014
      } else if(line[0] == '#') {
140,200✔
1015
         throw Test_Error("Unknown test pragma '" + line + "' in " + m_cur_src_name);
×
1016
      }
1017

1018
      if(line[0] == '[' && line[line.size() - 1] == ']') {
140,184✔
1019
         header = line.substr(1, line.size() - 2);
590✔
1020
         header_or_name = header;
590✔
1021
         test_cnt = 0;
590✔
1022
         vars.clear();
590✔
1023
         continue;
590✔
1024
      }
1025

1026
      const std::string test_id = "test " + std::to_string(test_cnt);
139,594✔
1027

1028
      auto equal_i = line.find_first_of('=');
139,594✔
1029

1030
      if(equal_i == std::string::npos) {
139,594✔
1031
         results.push_back(Test::Result::Failure(header_or_name, "invalid input '" + line + "'"));
×
1032
         continue;
×
1033
      }
1034

1035
      std::string key = strip_ws(std::string(line.begin(), line.begin() + equal_i - 1));
139,594✔
1036
      std::string val = strip_ws(std::string(line.begin() + equal_i + 1, line.end()));
139,594✔
1037

1038
      if(!m_required_keys.contains(key) && !m_optional_keys.contains(key))
139,594✔
1039
         results.push_back(Test::Result::Failure(header_or_name, test_id + " failed unknown key " + key));
×
1040

1041
      vars.add(key, val);
139,594✔
1042

1043
      if(key == m_output_key) {
139,594✔
1044
         try {
43,641✔
1045
            for(auto& req_key : m_required_keys) {
224,719✔
1046
               if(!vars.has_key(req_key))
362,156✔
1047
                  results.push_back(
×
1048
                     Test::Result::Failure(header_or_name, test_id + " missing required key " + req_key));
×
1049
            }
1050

1051
            if(skip_this_test(header, vars))
43,641✔
1052
               continue;
×
1053

1054
            ++test_cnt;
43,641✔
1055

1056
            uint64_t start = Test::timestamp();
43,641✔
1057

1058
            Test::Result result = run_one_test(header, vars);
43,641✔
1059
            if(!m_cpu_flags.empty()) {
43,641✔
1060
               for(const auto& cpuid_u64 : m_cpu_flags) {
17,907✔
1061
                  Botan::CPUID::CPUID_bits cpuid_bit = static_cast<Botan::CPUID::CPUID_bits>(cpuid_u64);
12,317✔
1062
                  if(Botan::CPUID::has_cpuid_bit(cpuid_bit)) {
12,317✔
1063
                     Botan::CPUID::clear_cpuid_bit(cpuid_bit);
9,670✔
1064
                     // now re-run the test
1065
                     result.merge(run_one_test(header, vars));
9,670✔
1066
                  }
1067
               }
1068
               Botan::CPUID::initialize();
5,590✔
1069
            }
1070
            result.set_ns_consumed(Test::timestamp() - start);
43,641✔
1071

1072
            if(result.tests_failed()) {
43,641✔
1073
               std::ostringstream oss;
×
1074
               oss << "Test # " << test_cnt << " ";
×
1075
               if(!header.empty())
×
1076
                  oss << header << " ";
×
1077
               oss << "failed ";
×
1078

1079
               for(const auto& k : m_required_keys)
×
1080
                  oss << k << "=" << vars.get_req_str(k) << " ";
×
1081

1082
               result.test_note(oss.str());
×
1083
            }
×
1084
            results.push_back(result);
43,641✔
1085
         } catch(std::exception& e) {
43,641✔
1086
            std::ostringstream oss;
×
1087
            oss << "Test # " << test_cnt << " ";
×
1088
            if(!header.empty())
×
1089
               oss << header << " ";
×
1090

1091
            for(const auto& k : m_required_keys)
×
1092
               oss << k << "=" << vars.get_req_str(k) << " ";
×
1093

1094
            oss << "failed with exception '" << e.what() << "'";
×
1095

1096
            results.push_back(Test::Result::Failure(header_or_name, oss.str()));
×
1097
         }
×
1098

1099
         if(clear_between_callbacks()) {
43,641✔
1100
            vars.clear();
168,828✔
1101
         }
1102
      }
1103
   }
260,860✔
1104

1105
   if(results.empty()) {
147✔
1106
      return results;
1107
   }
1108

1109
   try {
147✔
1110
      std::vector<Test::Result> final_tests = run_final_tests();
147✔
1111
      results.insert(results.end(), final_tests.begin(), final_tests.end());
147✔
1112
   } catch(std::exception& e) {
147✔
1113
      results.push_back(Test::Result::Failure(header_or_name, "run_final_tests exception " + std::string(e.what())));
×
1114
   }
×
1115

1116
   m_first = true;
147✔
1117

1118
   return results;
147✔
1119
}
286✔
1120

1121
std::map<std::string, std::string> Test_Options::report_properties() const {
1✔
1122
   std::map<std::string, std::string> result;
1✔
1123

1124
   for(const auto& prop : m_report_properties) {
3✔
1125
      const auto colon = prop.find(':');
2✔
1126
      // props without a colon separator or without a name are not allowed
1127
      if(colon == std::string::npos || colon == 0) {
2✔
1128
         throw Test_Error("--report-properties should be of the form <key>:<value>,<key>:<value>,...");
×
1129
      }
1130

1131
      result.insert_or_assign(prop.substr(0, colon), prop.substr(colon + 1, prop.size() - colon - 1));
4✔
1132
   }
1133

1134
   return result;
1✔
1135
}
×
1136

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