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

randombit / botan / 5230455705

10 Jun 2023 02:30PM UTC coverage: 91.715% (-0.03%) from 91.746%
5230455705

push

github

randombit
Merge GH #3584 Change clang-format AllowShortFunctionsOnASingleLine config from All to Inline

77182 of 84154 relevant lines covered (91.72%)

11975295.43 hits per line

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

79.45
/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,823✔
41
   if(who() != other.who()) {
104,823✔
42
      if(!ignore_test_name) {
48✔
43
         throw Test_Error("Merging tests from different sources");
×
44
      }
45

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

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

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

66
void Test::Result::end_timer() {
831✔
67
   if(m_started > 0) {
831✔
68
      m_ns_taken += Test::timestamp() - m_started;
1,492✔
69
      m_started = 0;
746✔
70
   }
71
}
831✔
72

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

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

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

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

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

118
   return result.test_success(test_name + " behaved as expected");
92,116✔
119
}
120

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

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

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

133
bool Test::Result::test_success(const std::string& note) {
2,985,567✔
134
   if(Test::options().log_success()) {
2,985,460✔
135
      test_note(note);
×
136
   }
137
   ++m_tests_passed;
2,985,567✔
138
   return true;
399,090✔
139
}
140

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

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

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

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

159
bool Test::Result::test_ne(const std::string& what,
1,938✔
160
                           const uint8_t produced[],
161
                           size_t produced_len,
162
                           const uint8_t expected[],
163
                           size_t expected_len) {
164
   if(produced_len == expected_len && Botan::same_mem(produced, expected, expected_len)) {
2,133✔
165
      return test_failure(who() + ": " + what + " produced matching");
4✔
166
   }
167
   return test_success();
3,872✔
168
}
169

170
bool Test::Result::test_eq(const char* producer,
169,042✔
171
                           const std::string& what,
172
                           const uint8_t produced[],
173
                           size_t produced_size,
174
                           const uint8_t expected[],
175
                           size_t expected_size) {
176
   if(produced_size == expected_size && Botan::same_mem(produced, expected, expected_size)) {
338,083✔
177
      return test_success();
338,082✔
178
   }
179

180
   std::ostringstream err;
1✔
181

182
   err << who();
1✔
183

184
   if(producer) {
1✔
185
      err << " producer '" << producer << "'";
×
186
   }
187

188
   err << " unexpected result for " << what;
1✔
189

190
   if(produced_size != expected_size) {
1✔
191
      err << " produced " << produced_size << " bytes expected " << expected_size;
1✔
192
   }
193

194
   std::vector<uint8_t> xor_diff(std::min(produced_size, expected_size));
2✔
195
   size_t bytes_different = 0;
1✔
196

197
   for(size_t i = 0; i != xor_diff.size(); ++i) {
4✔
198
      xor_diff[i] = produced[i] ^ expected[i];
3✔
199
      bytes_different += (xor_diff[i] > 0);
3✔
200
   }
201

202
   err << "\nProduced: " << Botan::hex_encode(produced, produced_size)
1✔
203
       << "\nExpected: " << Botan::hex_encode(expected, expected_size);
3✔
204

205
   if(bytes_different > 0) {
1✔
206
      err << "\nXOR Diff: " << Botan::hex_encode(xor_diff);
1✔
207
   }
208

209
   return test_failure(err.str());
2✔
210
}
1✔
211

212
bool Test::Result::test_is_nonempty(const std::string& what_is_it, const std::string& to_examine) {
29,013✔
213
   if(to_examine.empty()) {
29,013✔
214
      return test_failure(what_is_it + " was empty");
1✔
215
   }
216
   return test_success();
58,024✔
217
}
218

219
bool Test::Result::test_eq(const std::string& what, const std::string& produced, const std::string& expected) {
61,169✔
220
   return test_is_eq(what, produced, expected);
61,169✔
221
}
222

223
bool Test::Result::test_eq(const std::string& what, const char* produced, const char* expected) {
20✔
224
   return test_is_eq(what, std::string(produced), std::string(expected));
34✔
225
}
226

227
bool Test::Result::test_eq(const std::string& what, size_t produced, size_t expected) {
125,394✔
228
   return test_is_eq(what, produced, expected);
125,394✔
229
}
230

231
bool Test::Result::test_eq_sz(const std::string& what, size_t produced, size_t expected) {
665✔
232
   return test_is_eq(what, produced, expected);
665✔
233
}
234

235
bool Test::Result::test_eq(const std::string& what,
107✔
236
                           const Botan::OctetString& produced,
237
                           const Botan::OctetString& expected) {
238
   std::ostringstream out;
107✔
239
   out << m_who << " " << what;
107✔
240

241
   if(produced == expected) {
107✔
242
      out << " produced expected result " << produced.to_string();
214✔
243
      return test_success(out.str());
214✔
244
   } else {
245
      out << " produced unexpected result '" << produced.to_string() << "' expected '" << expected.to_string() << "'";
×
246
      return test_failure(out.str());
×
247
   }
248
}
107✔
249

250
bool Test::Result::test_lt(const std::string& what, size_t produced, size_t expected) {
4,928✔
251
   if(produced >= expected) {
4,928✔
252
      std::ostringstream err;
1✔
253
      err << m_who << " " << what;
1✔
254
      err << " unexpected result " << produced << " >= " << expected;
1✔
255
      return test_failure(err.str());
1✔
256
   }
1✔
257

258
   return test_success();
9,854✔
259
}
260

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

268
   return test_success();
2,035,418✔
269
}
270

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

280
   return test_success();
2,259,866✔
281
}
282

283
bool Test::Result::test_gt(const std::string& what, size_t produced, size_t expected) {
14,382✔
284
   if(produced <= expected) {
14,382✔
285
      std::ostringstream err;
×
286
      err << m_who;
×
287
      err << " " << what;
×
288
      err << " unexpected result " << produced << " <= " << expected;
×
289
      return test_failure(err.str());
×
290
   }
×
291

292
   return test_success();
28,764✔
293
}
294

295
bool Test::Result::test_ne(const std::string& what, const std::string& str1, const std::string& str2) {
25✔
296
   if(str1 != str2) {
25✔
297
      return test_success(str1 + " != " + str2);
63✔
298
   }
299

300
   return test_failure(who() + " " + what + " produced matching strings " + str1);
2✔
301
}
302

303
bool Test::Result::test_ne(const std::string& what, size_t produced, size_t expected) {
9✔
304
   if(produced != expected) {
9✔
305
      return test_success();
16✔
306
   }
307

308
   std::ostringstream err;
1✔
309
   err << who() << " " << what << " produced " << produced << " unexpected value";
1✔
310
   return test_failure(err.str());
1✔
311
}
1✔
312

313
#if defined(BOTAN_HAS_BIGINT)
314
bool Test::Result::test_eq(const std::string& what, const BigInt& produced, const BigInt& expected) {
10,523✔
315
   return test_is_eq(what, produced, expected);
10,523✔
316
}
317

318
bool Test::Result::test_ne(const std::string& what, const BigInt& produced, const BigInt& expected) {
96✔
319
   if(produced != expected) {
96✔
320
      return test_success();
190✔
321
   }
322

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

329
#if defined(BOTAN_HAS_EC_CURVE_GFP)
330
bool Test::Result::test_eq(const std::string& what, const Botan::EC_Point& a, const Botan::EC_Point& b) {
3,135✔
331
   //return test_is_eq(what, a, b);
332
   if(a == b) {
3,135✔
333
      return test_success();
6,270✔
334
   }
335

336
   std::ostringstream err;
×
337
   err << who() << " " << what << " a=(" << a.get_affine_x() << "," << a.get_affine_y() << ")"
×
338
       << " b=(" << b.get_affine_x() << "," << b.get_affine_y();
×
339
   return test_failure(err.str());
×
340
}
×
341
#endif
342

343
bool Test::Result::test_eq(const std::string& what, bool produced, bool expected) {
281,321✔
344
   return test_is_eq(what, produced, expected);
281,321✔
345
}
346

347
bool Test::Result::test_rc_init(const std::string& func, int rc) {
39✔
348
   if(rc == 0) {
39✔
349
      return test_success();
78✔
350
   } else {
351
      std::ostringstream msg;
×
352
      msg << m_who;
×
353
      msg << " " << func;
×
354

355
      // -40 is BOTAN_FFI_ERROR_NOT_IMPLEMENTED
356
      if(rc == -40) {
×
357
         msg << " returned not implemented";
×
358
      } else {
359
         msg << " unexpectedly failed with error code " << rc;
×
360
      }
361

362
      if(rc == -40) {
×
363
         this->test_note(msg.str());
×
364
      } else {
365
         this->test_failure(msg.str());
×
366
      }
367
      return false;
×
368
   }
×
369
}
370

371
bool Test::Result::test_rc(const std::string& func, int expected, int rc) {
231✔
372
   if(expected != rc) {
231✔
373
      std::ostringstream err;
1✔
374
      err << m_who;
1✔
375
      err << " call to " << func << " unexpectedly returned " << rc;
1✔
376
      err << " but expecting " << expected;
1✔
377
      return test_failure(err.str());
1✔
378
   }
1✔
379

380
   return test_success();
460✔
381
}
382

383
std::vector<std::string> Test::possible_providers(const std::string& /*unused*/) {
×
384
   return Test::provider_filter({"base"});
×
385
}
386

387
//static
388
std::string Test::format_time(uint64_t ns) {
978✔
389
   std::ostringstream o;
978✔
390

391
   if(ns > 1000000000) {
978✔
392
      o << std::setprecision(2) << std::fixed << ns / 1000000000.0 << " sec";
117✔
393
   } else {
394
      o << std::setprecision(2) << std::fixed << ns / 1000000.0 << " msec";
861✔
395
   }
396

397
   return o.str();
1,956✔
398
}
978✔
399

400
Test::Result::Result(std::string who, const std::vector<Result>& downstream_results) : Result(std::move(who)) {
20✔
401
   for(const auto& result : downstream_results) {
58✔
402
      merge(result, true /* ignore non-matching test names */);
48✔
403
   }
404
}
10✔
405

406
// TODO: this should move to `StdoutReporter`
407
std::string Test::Result::result_string() const {
1,684✔
408
   const bool verbose = Test::options().verbose();
1,684✔
409

410
   if(tests_run() == 0 && !verbose) {
1,684✔
411
      return "";
13✔
412
   }
413

414
   std::ostringstream report;
1,671✔
415

416
   report << who() << " ran ";
1,671✔
417

418
   if(tests_run() == 0) {
1,671✔
419
      report << "ZERO";
×
420
   } else {
421
      report << tests_run();
1,671✔
422
   }
423
   report << " tests";
1,671✔
424

425
   if(m_ns_taken > 0) {
1,671✔
426
      report << " in " << format_time(m_ns_taken);
1,954✔
427
   }
428

429
   if(tests_failed()) {
1,671✔
430
      report << " " << tests_failed() << " FAILED";
25✔
431
   } else {
432
      report << " all ok";
1,646✔
433
   }
434

435
   report << "\n";
1,671✔
436

437
   for(size_t i = 0; i != m_fail_log.size(); ++i) {
1,696✔
438
      report << "Failure " << (i + 1) << ": " << m_fail_log[i];
25✔
439
      if(m_where) {
25✔
440
         report << " (at " << m_where->path << ":" << m_where->line << ")";
×
441
      }
442
      report << "\n";
25✔
443
   }
444

445
   if(!m_fail_log.empty() || tests_run() == 0 || verbose) {
1,671✔
446
      for(size_t i = 0; i != m_log.size(); ++i) {
25✔
447
         report << "Note " << (i + 1) << ": " << m_log[i] << "\n";
×
448
      }
449
   }
450

451
   return report.str();
1,671✔
452
}
1,671✔
453

454
namespace {
455

456
class Test_Registry {
457
   public:
458
      static Test_Registry& instance() {
946✔
459
         static Test_Registry registry;
947✔
460
         return registry;
946✔
461
      }
462

463
      void register_test(std::string category,
315✔
464
                         std::string name,
465
                         bool smoke_test,
466
                         bool needs_serialization,
467
                         std::function<std::unique_ptr<Test>()> maker_fn) {
468
         if(m_tests.contains(name)) {
315✔
469
            throw Test_Error("Duplicate registration of test '" + name + "'");
×
470
         }
471

472
         if(m_tests.contains(category)) {
315✔
473
            throw Test_Error("'" + category + "' cannot be used as category, test exists");
×
474
         }
475

476
         if(m_categories.contains(name)) {
315✔
477
            throw Test_Error("'" + name + "' cannot be used as test name, category exists");
×
478
         }
479

480
         if(smoke_test) {
315✔
481
            m_smoke_tests.push_back(name);
10✔
482
         }
483

484
         if(needs_serialization) {
315✔
485
            m_mutexed_tests.push_back(name);
17✔
486
         }
487

488
         m_tests.emplace(name, std::move(maker_fn));
315✔
489
         m_categories.emplace(std::move(category), std::move(name));
315✔
490
      }
315✔
491

492
      std::unique_ptr<Test> get_test(const std::string& test_name) const {
315✔
493
         auto i = m_tests.find(test_name);
315✔
494
         if(i != m_tests.end()) {
315✔
495
            return i->second();
315✔
496
         }
497
         return nullptr;
×
498
      }
499

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

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

504
      std::vector<std::string> filter_registered_tests(const std::vector<std::string>& requested,
1✔
505
                                                       const std::set<std::string>& to_be_skipped) {
506
         std::vector<std::string> result;
1✔
507

508
         // TODO: this is O(n^2), but we have a relatively small number of tests.
509
         auto insert_if_not_exists_and_not_skipped = [&](const std::string& test_name) {
316✔
510
            if(!Botan::value_exists(result, test_name) && to_be_skipped.find(test_name) == to_be_skipped.end()) {
315✔
511
               result.push_back(test_name);
305✔
512
            }
513
         };
316✔
514

515
         if(requested.empty()) {
1✔
516
            /*
517
            If nothing was requested on the command line, run everything. First
518
            run the "essentials" to smoke test, then everything else in
519
            alphabetical order.
520
            */
521
            result = m_smoke_tests;
1✔
522
            for(const auto& [test_name, _] : m_tests) {
316✔
523
               insert_if_not_exists_and_not_skipped(test_name);
315✔
524
            }
525
         } else {
526
            for(const auto& r : requested) {
×
527
               if(m_tests.find(r) != m_tests.end()) {
×
528
                  insert_if_not_exists_and_not_skipped(r);
×
529
               } else if(auto elems = m_categories.equal_range(r); elems.first != m_categories.end()) {
×
530
                  for(; elems.first != elems.second; ++elems.first) {
×
531
                     insert_if_not_exists_and_not_skipped(elems.first->second);
×
532
                  }
533
               } else {
534
                  throw Botan_Tests::Test_Error("Unknown test suite or category: " + r);
×
535
               }
536
            }
537
         }
538

539
         return result;
1✔
540
      }
×
541

542
      bool needs_serialization(const std::string& test_name) const {
315✔
543
         return Botan::value_exists(m_mutexed_tests, test_name);
315✔
544
      }
545

546
   private:
547
      Test_Registry() = default;
1✔
548

549
   private:
550
      std::map<std::string, std::function<std::unique_ptr<Test>()>> m_tests;
551
      std::multimap<std::string, std::string> m_categories;
552
      std::vector<std::string> m_smoke_tests;
553
      std::vector<std::string> m_mutexed_tests;
554
};
555

556
}  // namespace
557

558
// static Test:: functions
559

560
//static
561
void Test::register_test(std::string category,
315✔
562
                         std::string name,
563
                         bool smoke_test,
564
                         bool needs_serialization,
565
                         std::function<std::unique_ptr<Test>()> maker_fn) {
566
   Test_Registry::instance().register_test(
945✔
567
      std::move(category), std::move(name), smoke_test, needs_serialization, std::move(maker_fn));
945✔
568
}
315✔
569

570
//static
571
uint64_t Test::timestamp() {
88,779✔
572
   auto now = std::chrono::high_resolution_clock::now().time_since_epoch();
88,779✔
573
   return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
88,775✔
574
}
575

576
//static
577
std::vector<Test::Result> Test::flatten_result_lists(std::vector<std::vector<Test::Result>> result_lists) {
4✔
578
   std::vector<Test::Result> results;
4✔
579
   for(auto& result_list : result_lists) {
24✔
580
      for(auto& result : result_list) {
65✔
581
         results.emplace_back(std::move(result));
45✔
582
      }
583
   }
584
   return results;
4✔
585
}
×
586

587
//static
588
std::set<std::string> Test::registered_tests() {
×
589
   return Test_Registry::instance().registered_tests();
×
590
}
591

592
//static
593
std::set<std::string> Test::registered_test_categories() {
×
594
   return Test_Registry::instance().registered_test_categories();
×
595
}
596

597
//static
598
std::unique_ptr<Test> Test::get_test(const std::string& test_name) {
315✔
599
   return Test_Registry::instance().get_test(test_name);
315✔
600
}
601

602
//static
603
bool Test::test_needs_serialization(const std::string& test_name) {
315✔
604
   return Test_Registry::instance().needs_serialization(test_name);
315✔
605
}
606

607
//static
608
std::vector<std::string> Test::filter_registered_tests(const std::vector<std::string>& requested,
1✔
609
                                                       const std::set<std::string>& to_be_skipped) {
610
   return Test_Registry::instance().filter_registered_tests(requested, to_be_skipped);
1✔
611
}
612

613
//static
614
std::string Test::temp_file_name(const std::string& basename) {
21✔
615
   // TODO add a --tmp-dir option to the tests to specify where these files go
616

617
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
618

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

622
   int fd = ::mkstemp(&mkstemp_basename[0]);
21✔
623

624
   // error
625
   if(fd < 0) {
21✔
626
      return "";
×
627
   }
628

629
   ::close(fd);
21✔
630

631
   return mkstemp_basename;
42✔
632
#else
633
   // For now just create the temp in the current working directory
634
   return basename;
635
#endif
636
}
21✔
637

638
bool Test::copy_file(const std::string& from, const std::string& to) {
1✔
639
#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(__cpp_lib_filesystem)
640
   std::error_code ec;  // don't throw, just return false on error
1✔
641
   return std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing, ec);
1✔
642
#else
643
   // TODO: implement fallbacks to POSIX or WIN32
644
   // ... but then again: it's 2023 and we're using C++20 :o)
645
   BOTAN_UNUSED(from, to);
646
   throw Botan::No_Filesystem_Access();
647
#endif
648
}
649

650
std::string Test::read_data_file(const std::string& path) {
25✔
651
   const std::string fsname = Test::data_file(path);
25✔
652
   std::ifstream file(fsname.c_str());
25✔
653
   if(!file.good()) {
25✔
654
      throw Test_Error("Error reading from " + fsname);
×
655
   }
656

657
   return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
25✔
658
}
50✔
659

660
std::vector<uint8_t> Test::read_binary_data_file(const std::string& path) {
34✔
661
   const std::string fsname = Test::data_file(path);
34✔
662
   std::ifstream file(fsname.c_str(), std::ios::binary);
34✔
663
   if(!file.good()) {
34✔
664
      throw Test_Error("Error reading from " + fsname);
×
665
   }
666

667
   std::vector<uint8_t> contents;
34✔
668

669
   while(file.good()) {
74✔
670
      std::vector<uint8_t> buf(4096);
40✔
671
      file.read(reinterpret_cast<char*>(buf.data()), buf.size());
40✔
672
      const size_t got = static_cast<size_t>(file.gcount());
40✔
673

674
      if(got == 0 && file.eof()) {
40✔
675
         break;
676
      }
677

678
      contents.insert(contents.end(), buf.data(), buf.data() + got);
40✔
679
   }
40✔
680

681
   return contents;
68✔
682
}
68✔
683

684
// static member variables of Test
685

686
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
687
Test_Options Test::m_opts;
688
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
689
std::shared_ptr<Botan::RandomNumberGenerator> Test::m_test_rng;
690

691
//static
692
void Test::set_test_options(const Test_Options& opts) {
1✔
693
   m_opts = opts;
1✔
694
}
1✔
695

696
//static
697
void Test::set_test_rng(std::shared_ptr<Botan::RandomNumberGenerator> rng) {
1✔
698
   m_test_rng = std::move(rng);
1✔
699
}
1✔
700

701
//static
702
std::string Test::data_file(const std::string& what) {
251✔
703
   return Test::data_dir() + "/" + what;
502✔
704
}
705

706
//static
707
std::string Test::data_file_as_temporary_copy(const std::string& what) {
1✔
708
   auto tmp_basename = what;
1✔
709
   std::replace(tmp_basename.begin(), tmp_basename.end(), '/', '_');
1✔
710
   auto temp_file = temp_file_name("tmp-" + tmp_basename);
1✔
711
   if(temp_file.empty()) {
1✔
712
      return "";
×
713
   }
714
   if(!Test::copy_file(data_file(what), temp_file)) {
2✔
715
      return "";
×
716
   }
717
   return temp_file;
2✔
718
}
2✔
719

720
//static
721
std::vector<std::string> Test::provider_filter(const std::vector<std::string>& in) {
41,865✔
722
   if(m_opts.provider().empty()) {
41,865✔
723
      return in;
41,865✔
724
   }
725
   for(auto&& provider : in) {
×
726
      if(provider == m_opts.provider()) {
×
727
         return std::vector<std::string>{provider};
×
728
      }
729
   }
730
   return std::vector<std::string>{};
41,865✔
731
}
732

733
//static
734
Botan::RandomNumberGenerator& Test::rng() {
540,456✔
735
   if(!m_test_rng) {
540,456✔
736
      throw Test_Error("Test requires RNG but no RNG set with Test::set_test_rng");
×
737
   }
738
   return *m_test_rng;
540,456✔
739
}
740

741
//static
742
std::shared_ptr<Botan::RandomNumberGenerator> Test::rng_as_shared() {
107✔
743
   if(!m_test_rng) {
107✔
744
      throw Test_Error("Test requires RNG but no RNG set with Test::set_test_rng");
×
745
   }
746
   return m_test_rng;
107✔
747
}
748

749
std::string Test::random_password() {
84✔
750
   const size_t len = 1 + Test::rng().next_byte() % 32;
84✔
751
   return Botan::hex_encode(Test::rng().random_vec(len));
168✔
752
}
753

754
std::vector<std::vector<uint8_t>> VarMap::get_req_bin_list(const std::string& key) const {
12✔
755
   auto i = m_vars.find(key);
12✔
756
   if(i == m_vars.end()) {
12✔
757
      throw Test_Error("Test missing variable " + key);
×
758
   }
759

760
   std::vector<std::vector<uint8_t>> bin_list;
12✔
761

762
   for(auto&& part : Botan::split_on(i->second, ',')) {
62✔
763
      try {
50✔
764
         bin_list.push_back(Botan::hex_decode(part));
100✔
765
      } catch(std::exception& e) {
×
766
         std::ostringstream oss;
×
767
         oss << "Bad input '" << part << "'"
×
768
             << " in binary list key " << key << " - " << e.what();
×
769
         throw Test_Error(oss.str());
×
770
      }
×
771
   }
12✔
772

773
   return bin_list;
12✔
774
}
×
775

776
std::vector<uint8_t> VarMap::get_req_bin(const std::string& key) const {
130,278✔
777
   auto i = m_vars.find(key);
130,278✔
778
   if(i == m_vars.end()) {
130,278✔
779
      throw Test_Error("Test missing variable " + key);
×
780
   }
781

782
   try {
130,278✔
783
      return Botan::hex_decode(i->second);
130,278✔
784
   } catch(std::exception& e) {
×
785
      std::ostringstream oss;
×
786
      oss << "Bad input '" << i->second << "'"
×
787
          << " for key " << key << " - " << e.what();
×
788
      throw Test_Error(oss.str());
×
789
   }
×
790
}
791

792
std::string VarMap::get_opt_str(const std::string& key, const std::string& def_value) const {
13,538✔
793
   auto i = m_vars.find(key);
13,538✔
794
   if(i == m_vars.end()) {
13,538✔
795
      return def_value;
13,487✔
796
   }
797
   return i->second;
51✔
798
}
799

800
bool VarMap::get_req_bool(const std::string& key) const {
19✔
801
   auto i = m_vars.find(key);
19✔
802
   if(i == m_vars.end()) {
19✔
803
      throw Test_Error("Test missing variable " + key);
×
804
   }
805

806
   if(i->second == "true") {
19✔
807
      return true;
808
   } else if(i->second == "false") {
11✔
809
      return false;
810
   } else {
811
      throw Test_Error("Invalid boolean for key '" + key + "' value '" + i->second + "'");
×
812
   }
813
}
814

815
size_t VarMap::get_req_sz(const std::string& key) const {
4,368✔
816
   auto i = m_vars.find(key);
4,368✔
817
   if(i == m_vars.end()) {
4,368✔
818
      throw Test_Error("Test missing variable " + key);
×
819
   }
820
   return Botan::to_u32bit(i->second);
4,368✔
821
}
822

823
uint8_t VarMap::get_req_u8(const std::string& key) const {
17✔
824
   const size_t s = this->get_req_sz(key);
17✔
825
   if(s > 256) {
17✔
826
      throw Test_Error("Invalid " + key + " expected uint8_t got " + std::to_string(s));
×
827
   }
828
   return static_cast<uint8_t>(s);
17✔
829
}
830

831
uint32_t VarMap::get_req_u32(const std::string& key) const {
3✔
832
   return static_cast<uint32_t>(get_req_sz(key));
3✔
833
}
834

835
uint64_t VarMap::get_req_u64(const std::string& key) const {
13✔
836
   auto i = m_vars.find(key);
13✔
837
   if(i == m_vars.end()) {
13✔
838
      throw Test_Error("Test missing variable " + key);
×
839
   }
840
   try {
13✔
841
      return std::stoull(i->second);
13✔
842
   } catch(std::exception&) {
×
843
      throw Test_Error("Invalid u64 value '" + i->second + "'");
×
844
   }
×
845
}
846

847
size_t VarMap::get_opt_sz(const std::string& key, const size_t def_value) const {
26,922✔
848
   auto i = m_vars.find(key);
26,922✔
849
   if(i == m_vars.end()) {
26,922✔
850
      return def_value;
851
   }
852
   return Botan::to_u32bit(i->second);
11,934✔
853
}
854

855
uint64_t VarMap::get_opt_u64(const std::string& key, const uint64_t def_value) const {
3,538✔
856
   auto i = m_vars.find(key);
3,538✔
857
   if(i == m_vars.end()) {
3,538✔
858
      return def_value;
859
   }
860
   try {
641✔
861
      return std::stoull(i->second);
3,538✔
862
   } catch(std::exception&) {
×
863
      throw Test_Error("Invalid u64 value '" + i->second + "'");
×
864
   }
×
865
}
866

867
std::vector<uint8_t> VarMap::get_opt_bin(const std::string& key) const {
36,283✔
868
   auto i = m_vars.find(key);
36,283✔
869
   if(i == m_vars.end()) {
36,283✔
870
      return std::vector<uint8_t>();
36,283✔
871
   }
872

873
   try {
11,134✔
874
      return Botan::hex_decode(i->second);
11,134✔
875
   } catch(std::exception&) {
×
876
      throw Test_Error("Test invalid hex input '" + i->second + "'" + +" for key " + key);
×
877
   }
×
878
}
879

880
std::string VarMap::get_req_str(const std::string& key) const {
39,499✔
881
   auto i = m_vars.find(key);
39,499✔
882
   if(i == m_vars.end()) {
39,499✔
883
      throw Test_Error("Test missing variable " + key);
×
884
   }
885
   return i->second;
39,499✔
886
}
887

888
#if defined(BOTAN_HAS_BIGINT)
889
Botan::BigInt VarMap::get_req_bn(const std::string& key) const {
36,602✔
890
   auto i = m_vars.find(key);
36,602✔
891
   if(i == m_vars.end()) {
36,602✔
892
      throw Test_Error("Test missing variable " + key);
×
893
   }
894

895
   try {
36,602✔
896
      return Botan::BigInt(i->second);
36,602✔
897
   } catch(std::exception&) {
×
898
      throw Test_Error("Test invalid bigint input '" + i->second + "' for key " + key);
×
899
   }
×
900
}
901

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

904
{
905
   auto i = m_vars.find(key);
80✔
906
   if(i == m_vars.end()) {
80✔
907
      return def_value;
104✔
908
   }
909

910
   try {
56✔
911
      return Botan::BigInt(i->second);
56✔
912
   } catch(std::exception&) {
×
913
      throw Test_Error("Test invalid bigint input '" + i->second + "' for key " + key);
×
914
   }
×
915
}
916
#endif
917

918
Text_Based_Test::Text_Based_Test(const std::string& data_src,
147✔
919
                                 const std::string& required_keys_str,
920
                                 const std::string& optional_keys_str) :
147✔
921
      m_data_src(data_src) {
441✔
922
   if(required_keys_str.empty()) {
147✔
923
      throw Test_Error("Invalid test spec");
×
924
   }
925

926
   std::vector<std::string> required_keys = Botan::split_on(required_keys_str, ',');
147✔
927
   std::vector<std::string> optional_keys = Botan::split_on(optional_keys_str, ',');
147✔
928

929
   m_required_keys.insert(required_keys.begin(), required_keys.end());
147✔
930
   m_optional_keys.insert(optional_keys.begin(), optional_keys.end());
147✔
931
   m_output_key = required_keys.at(required_keys.size() - 1);
147✔
932
}
147✔
933

934
std::string Text_Based_Test::get_next_line() {
140,348✔
935
   while(true) {
140,567✔
936
      if(m_cur == nullptr || m_cur->good() == false) {
140,567✔
937
         if(m_srcs.empty()) {
374✔
938
            if(m_first) {
294✔
939
               const std::string full_path = Test::data_dir() + "/" + m_data_src;
147✔
940
               if(full_path.find(".vec") != std::string::npos) {
147✔
941
                  m_srcs.push_back(full_path);
136✔
942
               } else {
943
                  const auto fs = Botan::get_files_recursive(full_path);
11✔
944
                  m_srcs.assign(fs.begin(), fs.end());
11✔
945
                  if(m_srcs.empty()) {
11✔
946
                     throw Test_Error("Error reading test data dir " + full_path);
×
947
                  }
948
               }
11✔
949

950
               m_first = false;
147✔
951
            } else {
147✔
952
               return "";  // done
147✔
953
            }
954
         }
955

956
         m_cur = std::make_unique<std::ifstream>(m_srcs[0]);
307✔
957
         m_cur_src_name = m_srcs[0];
227✔
958

959
         // Reinit cpuid on new file if needed
960
         if(m_cpu_flags.empty() == false) {
227✔
961
            m_cpu_flags.clear();
12✔
962
            Botan::CPUID::initialize();
12✔
963
         }
964

965
         if(!m_cur->good()) {
227✔
966
            throw Test_Error("Could not open input file '" + m_cur_src_name);
×
967
         }
968

969
         m_srcs.pop_front();
227✔
970
      }
971

972
      while(m_cur->good()) {
204,348✔
973
         std::string line;
204,129✔
974
         std::getline(*m_cur, line);
204,129✔
975

976
         if(line.empty()) {
204,129✔
977
            continue;
50,461✔
978
         }
979

980
         if(line[0] == '#') {
153,668✔
981
            if(line.compare(0, 6, "#test ") == 0) {
13,483✔
982
               return line;
16✔
983
            } else {
984
               continue;
13,467✔
985
            }
986
         }
987

988
         return line;
280,386✔
989
      }
204,129✔
990
   }
991
}
992

993
namespace {
994

995
// strips leading and trailing but not internal whitespace
996
std::string strip_ws(const std::string& in) {
279,190✔
997
   const char* whitespace = " ";
279,190✔
998

999
   const auto first_c = in.find_first_not_of(whitespace);
279,190✔
1000
   if(first_c == std::string::npos) {
279,190✔
1001
      return "";
943✔
1002
   }
1003

1004
   const auto last_c = in.find_last_not_of(whitespace);
278,247✔
1005

1006
   return in.substr(first_c, last_c - first_c + 1);
278,247✔
1007
}
1008

1009
std::vector<uint64_t> parse_cpuid_bits(const std::vector<std::string>& tok) {
16✔
1010
   std::vector<uint64_t> bits;
16✔
1011
   for(size_t i = 1; i < tok.size(); ++i) {
55✔
1012
      const std::vector<Botan::CPUID::CPUID_bits> more = Botan::CPUID::bit_from_string(tok[i]);
39✔
1013
      bits.insert(bits.end(), more.begin(), more.end());
39✔
1014
   }
39✔
1015

1016
   return bits;
16✔
1017
}
×
1018

1019
}  // namespace
1020

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

1025
std::vector<Test::Result> Text_Based_Test::run() {
147✔
1026
   std::vector<Test::Result> results;
147✔
1027

1028
   std::string header, header_or_name = m_data_src;
147✔
1029
   VarMap vars;
147✔
1030
   size_t test_cnt = 0;
147✔
1031

1032
   while(true) {
140,348✔
1033
      const std::string line = get_next_line();
140,348✔
1034
      if(line.empty())  // EOF
140,348✔
1035
      {
1036
         break;
1037
      }
1038

1039
      if(line.compare(0, 6, "#test ") == 0) {
140,201✔
1040
         std::vector<std::string> pragma_tokens = Botan::split_on(line.substr(6), ' ');
16✔
1041

1042
         if(pragma_tokens.empty()) {
16✔
1043
            throw Test_Error("Empty pragma found in " + m_cur_src_name);
×
1044
         }
1045

1046
         if(pragma_tokens[0] != "cpuid") {
16✔
1047
            throw Test_Error("Unknown test pragma '" + line + "' in " + m_cur_src_name);
×
1048
         }
1049

1050
         m_cpu_flags = parse_cpuid_bits(pragma_tokens);
16✔
1051

1052
         continue;
16✔
1053
      } else if(line[0] == '#') {
140,201✔
1054
         throw Test_Error("Unknown test pragma '" + line + "' in " + m_cur_src_name);
×
1055
      }
1056

1057
      if(line[0] == '[' && line[line.size() - 1] == ']') {
140,185✔
1058
         header = line.substr(1, line.size() - 2);
590✔
1059
         header_or_name = header;
590✔
1060
         test_cnt = 0;
590✔
1061
         vars.clear();
590✔
1062
         continue;
590✔
1063
      }
1064

1065
      const std::string test_id = "test " + std::to_string(test_cnt);
139,595✔
1066

1067
      auto equal_i = line.find_first_of('=');
139,595✔
1068

1069
      if(equal_i == std::string::npos) {
139,595✔
1070
         results.push_back(Test::Result::Failure(header_or_name, "invalid input '" + line + "'"));
×
1071
         continue;
×
1072
      }
1073

1074
      std::string key = strip_ws(std::string(line.begin(), line.begin() + equal_i - 1));
139,595✔
1075
      std::string val = strip_ws(std::string(line.begin() + equal_i + 1, line.end()));
139,595✔
1076

1077
      if(!m_required_keys.contains(key) && !m_optional_keys.contains(key)) {
139,595✔
1078
         results.push_back(Test::Result::Failure(header_or_name, test_id + " failed unknown key " + key));
×
1079
      }
1080

1081
      vars.add(key, val);
139,595✔
1082

1083
      if(key == m_output_key) {
139,595✔
1084
         try {
43,641✔
1085
            for(auto& req_key : m_required_keys) {
224,720✔
1086
               if(!vars.has_key(req_key)) {
362,158✔
1087
                  results.push_back(
×
1088
                     Test::Result::Failure(header_or_name, test_id + " missing required key " + req_key));
×
1089
               }
1090
            }
1091

1092
            if(skip_this_test(header, vars)) {
43,641✔
1093
               continue;
×
1094
            }
1095

1096
            ++test_cnt;
43,641✔
1097

1098
            uint64_t start = Test::timestamp();
43,641✔
1099

1100
            Test::Result result = run_one_test(header, vars);
43,641✔
1101
            if(!m_cpu_flags.empty()) {
43,641✔
1102
               for(const auto& cpuid_u64 : m_cpu_flags) {
17,911✔
1103
                  Botan::CPUID::CPUID_bits cpuid_bit = static_cast<Botan::CPUID::CPUID_bits>(cpuid_u64);
12,320✔
1104
                  if(Botan::CPUID::has_cpuid_bit(cpuid_bit)) {
12,320✔
1105
                     Botan::CPUID::clear_cpuid_bit(cpuid_bit);
9,672✔
1106
                     // now re-run the test
1107
                     result.merge(run_one_test(header, vars));
9,672✔
1108
                  }
1109
               }
1110
               Botan::CPUID::initialize();
5,591✔
1111
            }
1112
            result.set_ns_consumed(Test::timestamp() - start);
43,641✔
1113

1114
            if(result.tests_failed()) {
43,641✔
1115
               std::ostringstream oss;
×
1116
               oss << "Test # " << test_cnt << " ";
×
1117
               if(!header.empty()) {
×
1118
                  oss << header << " ";
×
1119
               }
1120
               oss << "failed ";
×
1121

1122
               for(const auto& k : m_required_keys) {
×
1123
                  oss << k << "=" << vars.get_req_str(k) << " ";
×
1124
               }
1125

1126
               result.test_note(oss.str());
×
1127
            }
×
1128
            results.push_back(result);
43,641✔
1129
         } catch(std::exception& e) {
43,641✔
1130
            std::ostringstream oss;
×
1131
            oss << "Test # " << test_cnt << " ";
×
1132
            if(!header.empty()) {
×
1133
               oss << header << " ";
×
1134
            }
1135

1136
            for(const auto& k : m_required_keys) {
×
1137
               oss << k << "=" << vars.get_req_str(k) << " ";
×
1138
            }
1139

1140
            oss << "failed with exception '" << e.what() << "'";
×
1141

1142
            results.push_back(Test::Result::Failure(header_or_name, oss.str()));
×
1143
         }
×
1144

1145
         if(clear_between_callbacks()) {
43,641✔
1146
            vars.clear();
168,829✔
1147
         }
1148
      }
1149
   }
260,862✔
1150

1151
   if(results.empty()) {
147✔
1152
      return results;
1153
   }
1154

1155
   try {
147✔
1156
      std::vector<Test::Result> final_tests = run_final_tests();
147✔
1157
      results.insert(results.end(), final_tests.begin(), final_tests.end());
147✔
1158
   } catch(std::exception& e) {
147✔
1159
      results.push_back(Test::Result::Failure(header_or_name, "run_final_tests exception " + std::string(e.what())));
×
1160
   }
×
1161

1162
   m_first = true;
147✔
1163

1164
   return results;
147✔
1165
}
286✔
1166

1167
std::map<std::string, std::string> Test_Options::report_properties() const {
1✔
1168
   std::map<std::string, std::string> result;
1✔
1169

1170
   for(const auto& prop : m_report_properties) {
3✔
1171
      const auto colon = prop.find(':');
2✔
1172
      // props without a colon separator or without a name are not allowed
1173
      if(colon == std::string::npos || colon == 0) {
2✔
1174
         throw Test_Error("--report-properties should be of the form <key>:<value>,<key>:<value>,...");
×
1175
      }
1176

1177
      result.insert_or_assign(prop.substr(0, colon), prop.substr(colon + 1, prop.size() - colon - 1));
4✔
1178
   }
1179

1180
   return result;
1✔
1181
}
×
1182

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