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

randombit / botan / 5356326050

23 Jun 2023 01:05PM UTC coverage: 91.728% (-0.008%) from 91.736%
5356326050

Pull #3595

github

web-flow
Merge a5b917599 into 92171c524
Pull Request #3595: Improve clang-tidy coverage

78163 of 85212 relevant lines covered (91.73%)

12690161.35 hits per line

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

78.96
/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) {
104,929✔
42
   if(who() != other.who()) {
104,929✔
43
      if(!ignore_test_name) {
48✔
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();
48✔
50
   } else {
51
      m_where = other.m_where;
104,881✔
52
   }
53

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

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

67
void Test::Result::end_timer() {
843✔
68
   if(m_started > 0) {
843✔
69
      m_ns_taken += Test::timestamp() - m_started;
1,516✔
70
      m_started = 0;
758✔
71
   }
72
}
843✔
73

74
void Test::Result::test_note(const std::string& note, const char* extra) {
2,677✔
75
   if(!note.empty()) {
2,677✔
76
      std::ostringstream out;
2,677✔
77
      out << who() << " " << note;
2,677✔
78
      if(extra) {
2,677✔
79
         out << ": " << extra;
12✔
80
      }
81
      m_log.push_back(out.str());
2,677✔
82
   }
2,677✔
83
}
2,677✔
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) {
46,065✔
95
   m_consumed = true;
46,065✔
96

97
   try {
46,065✔
98
      m_fn();
46,065✔
99
      if(!m_expect_success) {
62✔
100
         return result.test_failure(test_name + " failed to throw expected exception");
4✔
101
      }
102
   } catch(const std::exception& ex) {
46,003✔
103
      if(m_expect_success) {
46,001✔
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,000✔
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()) {
45,998✔
110
         return result.test_failure(test_name + " threw exception with unexpected message (expected: '" +
3✔
111
                                    m_expected_message.value() + "', got: '" + ex.what() + "')");
3✔
112
      }
113
   } catch(...) {
46,003✔
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");
92,116✔
120
}
121

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

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

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

134
bool Test::Result::test_success(const std::string& note) {
2,986,038✔
135
   if(Test::options().log_success()) {
2,985,931✔
136
      test_note(note);
×
137
   }
138
   ++m_tests_passed;
2,986,038✔
139
   return true;
399,347✔
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);
2✔
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 " +
2✔
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
bool Test::Result::test_ne(const std::string& what,
1,937✔
161
                           const uint8_t produced[],
162
                           size_t produced_len,
163
                           const uint8_t expected[],
164
                           size_t expected_len) {
165
   if(produced_len == expected_len && Botan::same_mem(produced, expected, expected_len)) {
2,131✔
166
      return test_failure(who() + ": " + what + " produced matching");
4✔
167
   }
168
   return test_success();
3,870✔
169
}
170

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

181
   std::ostringstream err;
1✔
182

183
   err << who();
1✔
184

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

259
   return test_success();
9,878✔
260
}
261

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

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

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

281
   return test_success();
2,259,890✔
282
}
283

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

381
   return test_success();
460✔
382
}
383

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

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

392
   if(ns > 1000000000) {
993✔
393
      o << std::setprecision(2) << std::fixed << ns / 1000000000.0 << " sec";
143✔
394
   } else {
395
      o << std::setprecision(2) << std::fixed << ns / 1000000.0 << " msec";
850✔
396
   }
397

398
   return o.str();
1,986✔
399
}
993✔
400

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

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

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

415
   std::ostringstream report;
1,691✔
416

417
   report << who() << " ran ";
1,691✔
418

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

426
   if(m_ns_taken > 0) {
1,691✔
427
      report << " in " << format_time(m_ns_taken);
1,984✔
428
   }
429

430
   if(tests_failed()) {
1,691✔
431
      report << " " << tests_failed() << " FAILED";
25✔
432
   } else {
433
      report << " all ok";
1,666✔
434
   }
435

436
   report << "\n";
1,691✔
437

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

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

452
   return report.str();
1,691✔
453
}
1,691✔
454

455
namespace {
456

457
class Test_Registry {
458
   public:
459
      static Test_Registry& instance() {
961✔
460
         static Test_Registry registry;
962✔
461
         return registry;
961✔
462
      }
463

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

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

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

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

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

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

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

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

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

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

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

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

540
         return result;
1✔
541
      }
×
542

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

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

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

557
}  // namespace
558

559
// static Test:: functions
560

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

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

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

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

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

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

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

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

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

618
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
619

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

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

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

630
   ::close(fd);
21✔
631

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

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

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

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

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

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

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

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

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

682
   return contents;
68✔
683
}
68✔
684

685
// static member variables of Test
686

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

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

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

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

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

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

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

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

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

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

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

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

774
   return bin_list;
12✔
775
}
×
776

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

957
         m_cur = std::make_unique<std::ifstream>(m_srcs[0]);
310✔
958
         m_cur_src_name = m_srcs[0];
230✔
959

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

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

970
         m_srcs.pop_front();
230✔
971
      }
972

973
      while(m_cur->good()) {
204,653✔
974
         std::string line;
204,431✔
975
         std::getline(*m_cur, line);
204,431✔
976

977
         if(line.empty()) {
204,431✔
978
            continue;
50,504✔
979
         }
980

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

989
         return line;
280,866✔
990
      }
204,431✔
991
   }
992
}
993

994
namespace {
995

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

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

1005
   const auto last_c = in.find_last_not_of(whitespace);
278,727✔
1006

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

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

1017
   return bits;
16✔
1018
}
×
1019

1020
}  // namespace
1021

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

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

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

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

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

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

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

1051
         m_cpu_flags = parse_cpuid_bits(pragma_tokens);
16✔
1052

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

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

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

1068
      auto equal_i = line.find_first_of('=');
139,835✔
1069

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

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

1078
      if(!m_required_keys.contains(key) && !m_optional_keys.contains(key)) {
139,835✔
1079
         auto r = Test::Result::Failure(header_or_name, Botan::fmt("{} failed unknown key {}", test_id, key));
×
1080
         results.push_back(r);
×
1081
      }
×
1082

1083
      vars.add(key, val);
139,835✔
1084

1085
      if(key == m_output_key) {
139,835✔
1086
         try {
43,677✔
1087
            for(auto& req_key : m_required_keys) {
224,996✔
1088
               if(!vars.has_key(req_key)) {
362,638✔
1089
                  auto r =
×
1090
                     Test::Result::Failure(header_or_name, Botan::fmt("{} missing required key {}", test_id, req_key));
×
1091
                  results.push_back(r);
×
1092
               }
×
1093
            }
1094

1095
            if(skip_this_test(header, vars)) {
43,677✔
1096
               continue;
×
1097
            }
1098

1099
            ++test_cnt;
43,677✔
1100

1101
            uint64_t start = Test::timestamp();
43,677✔
1102

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

1117
            if(result.tests_failed()) {
43,677✔
1118
               std::ostringstream oss;
×
1119
               oss << "Test # " << test_cnt << " ";
×
1120
               if(!header.empty()) {
×
1121
                  oss << header << " ";
×
1122
               }
1123
               oss << "failed ";
×
1124

1125
               for(const auto& k : m_required_keys) {
×
1126
                  oss << k << "=" << vars.get_req_str(k) << " ";
×
1127
               }
1128

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

1139
            for(const auto& k : m_required_keys) {
×
1140
               oss << k << "=" << vars.get_req_str(k) << " ";
×
1141
            }
1142

1143
            oss << "failed with exception '" << e.what() << "'";
×
1144

1145
            results.push_back(Test::Result::Failure(header_or_name, oss.str()));
×
1146
         }
×
1147

1148
         if(clear_between_callbacks()) {
43,677✔
1149
            vars.clear();
169,105✔
1150
         }
1151
      }
1152
   }
261,378✔
1153

1154
   if(results.empty()) {
150✔
1155
      return results;
1156
   }
1157

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

1165
   m_first = true;
150✔
1166

1167
   return results;
150✔
1168
}
292✔
1169

1170
std::map<std::string, std::string> Test_Options::report_properties() const {
1✔
1171
   std::map<std::string, std::string> result;
1✔
1172

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

1180
      result.insert_or_assign(prop.substr(0, colon), prop.substr(colon + 1, prop.size() - colon - 1));
4✔
1181
   }
1182

1183
   return result;
1✔
1184
}
×
1185

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