aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/lib/utils/os_utils.cpp30
-rw-r--r--src/lib/utils/os_utils.h9
-rw-r--r--src/lib/utils/thread_utils/info.txt3
-rw-r--r--src/lib/utils/thread_utils/thread_pool.cpp103
-rw-r--r--src/lib/utils/thread_utils/thread_pool.h81
-rwxr-xr-xsrc/scripts/ci_build.py7
-rw-r--r--src/tests/main.cpp12
-rw-r--r--src/tests/test_rng.cpp12
-rw-r--r--src/tests/test_runner.cpp131
-rw-r--r--src/tests/test_runner.h3
-rw-r--r--src/tests/test_thread_utils.cpp56
-rw-r--r--src/tests/tests.cpp28
-rw-r--r--src/tests/tests.h63
13 files changed, 454 insertions, 84 deletions
diff --git a/src/lib/utils/os_utils.cpp b/src/lib/utils/os_utils.cpp
index f64b85c18..71f4f12d4 100644
--- a/src/lib/utils/os_utils.cpp
+++ b/src/lib/utils/os_utils.cpp
@@ -263,20 +263,9 @@ size_t OS::get_memory_locking_limit()
* programs), but small enough that we should not cause problems
* even if many processes are mlocking on the same machine.
*/
- size_t mlock_requested = BOTAN_MLOCK_ALLOCATOR_MAX_LOCKED_KB;
+ const size_t user_req = read_env_variable_sz("BOTAN_MLOCK_POOL_SIZE", BOTAN_MLOCK_ALLOCATOR_MAX_LOCKED_KB);
- /*
- * Allow override via env variable
- */
- if(const char* env = read_env_variable("BOTAN_MLOCK_POOL_SIZE"))
- {
- try
- {
- const size_t user_req = std::stoul(env, nullptr);
- mlock_requested = std::min(user_req, mlock_requested);
- }
- catch(std::exception&) { /* ignore it */ }
- }
+ const size_t mlock_requested = std::min<size_t>(user_req, BOTAN_MLOCK_ALLOCATOR_MAX_LOCKED_KB);
if(mlock_requested > 0)
{
@@ -327,6 +316,21 @@ const char* OS::read_env_variable(const std::string& name)
return std::getenv(name.c_str());
}
+size_t OS::read_env_variable_sz(const std::string& name, size_t def)
+ {
+ if(const char* env = read_env_variable(name))
+ {
+ try
+ {
+ const size_t val = std::stoul(env, nullptr);
+ return val;
+ }
+ catch(std::exception&) { /* ignore it */ }
+ }
+
+ return def;
+ }
+
std::vector<void*> OS::allocate_locked_pages(size_t count)
{
std::vector<void*> result;
diff --git a/src/lib/utils/os_utils.h b/src/lib/utils/os_utils.h
index 37a8d3a9c..82f3aad04 100644
--- a/src/lib/utils/os_utils.h
+++ b/src/lib/utils/os_utils.h
@@ -90,6 +90,15 @@ size_t system_page_size();
const char* read_env_variable(const std::string& var_name);
/**
+* Read the value of an environment variable and convert it to an
+* integer. If not set or conversion fails, returns the default value.
+*
+* If the process seems to be running in a privileged state (such as setuid)
+* then always returns nullptr, similiar to glibc's secure_getenv.
+*/
+size_t read_env_variable_sz(const std::string& var_name, size_t def_value = 0);
+
+/**
* Request @count pages of RAM which are locked into memory using mlock,
* VirtualLock, or some similar OS specific API. Free it with free_locked_pages.
*
diff --git a/src/lib/utils/thread_utils/info.txt b/src/lib/utils/thread_utils/info.txt
index 826a9d734..80ce2d389 100644
--- a/src/lib/utils/thread_utils/info.txt
+++ b/src/lib/utils/thread_utils/info.txt
@@ -1,10 +1,11 @@
<defines>
-THREAD_UTILS -> 20180112
+THREAD_UTILS -> 20190122
</defines>
<header:internal>
barrier.h
semaphore.h
+thread_pool.h
</header:internal>
<os_features>
diff --git a/src/lib/utils/thread_utils/thread_pool.cpp b/src/lib/utils/thread_utils/thread_pool.cpp
new file mode 100644
index 000000000..4ccefe8dc
--- /dev/null
+++ b/src/lib/utils/thread_utils/thread_pool.cpp
@@ -0,0 +1,103 @@
+/*
+* (C) 2019 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include <botan/internal/thread_pool.h>
+#include <botan/internal/os_utils.h>
+#include <botan/exceptn.h>
+#include <thread>
+
+namespace Botan {
+
+//static
+Thread_Pool& Thread_Pool::global_instance()
+ {
+ static Thread_Pool g_thread_pool(OS::read_env_variable_sz("BOTAN_THREAD_POOL_SIZE"));
+ return g_thread_pool;
+ }
+
+Thread_Pool::Thread_Pool(size_t pool_size)
+ {
+ if(pool_size == 0)
+ {
+ pool_size = std::thread::hardware_concurrency();
+
+ /*
+ * For large machines don't create too many threads, unless
+ * explicitly asked to by the caller.
+ */
+ if(pool_size > 16)
+ pool_size = 16;
+ }
+
+ if(pool_size <= 1)
+ pool_size = 2;
+
+ m_shutdown = false;
+
+ for(size_t i = 0; i != pool_size; ++i)
+ {
+ m_workers.push_back(std::thread(&Thread_Pool::worker_thread, this));
+ }
+ }
+
+void Thread_Pool::shutdown()
+ {
+ {
+ std::unique_lock<std::mutex> lock(m_mutex);
+
+ if(m_shutdown == true)
+ return;
+
+ m_shutdown = true;
+
+ m_more_tasks.notify_all();
+ }
+
+ for(auto&& thread : m_workers)
+ {
+ thread.join();
+ }
+ m_workers.clear();
+ }
+
+void Thread_Pool::queue_thunk(std::function<void ()> fn)
+ {
+ std::unique_lock<std::mutex> lock(m_mutex);
+
+ if(m_shutdown)
+ throw Invalid_State("Cannot add work after thread pool has shut down");
+
+ m_tasks.push_back(fn);
+ m_more_tasks.notify_one();
+ }
+
+void Thread_Pool::worker_thread()
+ {
+ for(;;)
+ {
+ std::function<void()> task;
+
+ {
+ std::unique_lock<std::mutex> lock(m_mutex);
+ m_more_tasks.wait(lock, [this]{ return m_shutdown || !m_tasks.empty(); });
+
+ if(m_tasks.empty())
+ {
+ if(m_shutdown)
+ return;
+ else
+ continue;
+ }
+
+ task = m_tasks.front();
+ m_tasks.pop_front();
+ }
+
+ task();
+ }
+ }
+
+}
diff --git a/src/lib/utils/thread_utils/thread_pool.h b/src/lib/utils/thread_utils/thread_pool.h
new file mode 100644
index 000000000..d48975090
--- /dev/null
+++ b/src/lib/utils/thread_utils/thread_pool.h
@@ -0,0 +1,81 @@
+/*
+* (C) 2019 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#ifndef BOTAN_THREAD_POOL_H_
+#define BOTAN_THREAD_POOL_H_
+
+#include <botan/types.h>
+#include <functional>
+#include <deque>
+#include <vector>
+#include <memory>
+#include <utility>
+#include <type_traits>
+#include <mutex>
+#include <thread>
+#include <future>
+#include <condition_variable>
+
+namespace Botan {
+
+class BOTAN_TEST_API Thread_Pool
+ {
+ public:
+ /**
+ * Return an instance to a shared thread pool
+ */
+ static Thread_Pool& global_instance();
+
+ /**
+ * Initialize a thread pool with some number of threads
+ * @param pool_size number of threads in the pool, if 0
+ * then some default value is chosen
+ */
+ Thread_Pool(size_t pool_size = 0);
+
+ ~Thread_Pool() { shutdown(); }
+
+ void shutdown();
+
+ Thread_Pool(const Thread_Pool&) = delete;
+ Thread_Pool& operator=(const Thread_Pool&) = delete;
+
+ // Does this work?
+ Thread_Pool(Thread_Pool&&) = default;
+ Thread_Pool& operator=(Thread_Pool&&) = default;
+
+ /*
+ * Enqueue some work
+ */
+ void queue_thunk(std::function<void ()>);
+
+ template<class F, class... Args>
+ auto run(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type>
+ {
+ typedef typename std::result_of<F(Args...)>::type return_type;
+
+ auto future_work = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
+ auto task = std::make_shared<std::packaged_task<return_type ()>>(future_work);
+ auto future_result = task->get_future();
+ queue_thunk([task]() { (*task)(); });
+ return future_result;
+ }
+
+ private:
+ void worker_thread();
+
+ // Only touched in constructor and destructor
+ std::vector<std::thread> m_workers;
+
+ std::mutex m_mutex;
+ std::condition_variable m_more_tasks;
+ std::deque<std::function<void ()>> m_tasks;
+ bool m_shutdown;
+ };
+
+}
+
+#endif
diff --git a/src/scripts/ci_build.py b/src/scripts/ci_build.py
index 314ededa5..77ba19662 100755
--- a/src/scripts/ci_build.py
+++ b/src/scripts/ci_build.py
@@ -22,7 +22,7 @@ def get_concurrency():
try:
import multiprocessing
- return max(def_concurrency, multiprocessing.cpu_count())
+ return multiprocessing.cpu_count()
except ImportError:
return def_concurrency
@@ -48,6 +48,9 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache, ro
test_prefix = []
test_cmd = [os.path.join(root_dir, 'botan-test')]
+ if target in ['shared', 'static', 'sanitizer', 'gcc4.8', 'cross-i386', 'bsi', 'nist']:
+ test_cmd += ['--test-threads=%d' % (get_concurrency())]
+
fast_tests = ['block', 'aead', 'hash', 'stream', 'mac', 'modes', 'kdf',
'hmac_drbg', 'hmac_drbg_unit',
'tls', 'ffi',
@@ -146,7 +149,7 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache, ro
cc_bin = 'x86_64-w64-mingw32-g++'
flags += ['--cpu=x86_64', '--cc-abi-flags=-static',
'--ar-command=x86_64-w64-mingw32-ar', '--without-os-feature=threads']
- test_cmd = [os.path.join(root_dir, 'botan-test.exe')]
+ test_cmd = [os.path.join(root_dir, 'botan-test.exe')] + test_cmd[1:]
# No runtime prefix required for Wine
else:
# Build everything but restrict what is run
diff --git a/src/tests/main.cpp b/src/tests/main.cpp
index 995510ec7..3cf7526f0 100644
--- a/src/tests/main.cpp
+++ b/src/tests/main.cpp
@@ -61,7 +61,7 @@ int main(int argc, char* argv[])
const std::string arg_spec =
"botan-test --verbose --help --data-dir= --pkcs11-lib= --provider= "
"--log-success --abort-on-first-fail --no-avoid-undefined --skip-tests= "
- "--run-long-tests --run-online-tests --test-runs=1 --drbg-seed= "
+ "--test-threads=1 --run-long-tests --run-online-tests --test-runs=1 --drbg-seed= "
"*suites";
Botan_CLI::Argument_Parser parser(arg_spec);
@@ -74,6 +74,15 @@ int main(int argc, char* argv[])
return 0;
}
+#if defined(BOTAN_TARGET_OS_HAS_POSIX1) && defined(BOTAN_TARGET_OS_HAS_THREADS)
+ // The mlock pool becomes a major contention point when many
+ // threads are running.
+ if(parser.get_arg_sz("test-threads") != 1)
+ {
+ ::setenv("BOTAN_MLOCK_POOL_SIZE", "0", 1);
+ }
+#endif
+
const Botan_Tests::Test_Options opts(
parser.get_arg_list("suites"),
parser.get_arg_list("skip-tests"),
@@ -82,6 +91,7 @@ int main(int argc, char* argv[])
parser.get_arg("provider"),
parser.get_arg("drbg-seed"),
parser.get_arg_sz("test-runs"),
+ parser.get_arg_sz("test-threads"),
parser.flag_set("verbose"),
parser.flag_set("log-success"),
parser.flag_set("run-online-tests"),
diff --git a/src/tests/test_rng.cpp b/src/tests/test_rng.cpp
index a7510b650..e62f81623 100644
--- a/src/tests/test_rng.cpp
+++ b/src/tests/test_rng.cpp
@@ -60,9 +60,19 @@ class Stateful_RNG_Tests : public Test
results.push_back(test_broken_entropy_input());
results.push_back(test_check_nonce());
results.push_back(test_prediction_resistance());
- results.push_back(test_fork_safety());
results.push_back(test_randomize_with_ts_input());
results.push_back(test_security_level());
+
+ /*
+ * This test uses the library in both parent and child processes. But
+ * this causes a race with other threads, where if any other test thread
+ * is holding the mlock pool mutex, it is killed after the fork. Then,
+ * in the child, any attempt to allocate or free memory will cause a
+ * deadlock.
+ */
+ if(Test::options().test_threads() == 1)
+ results.push_back(test_fork_safety());
+
return results;
}
diff --git a/src/tests/test_runner.cpp b/src/tests/test_runner.cpp
index e346e9afa..86ceab1f8 100644
--- a/src/tests/test_runner.cpp
+++ b/src/tests/test_runner.cpp
@@ -12,6 +12,10 @@
#include <botan/loadstor.h>
#include <botan/cpuid.h>
+#if defined(BOTAN_HAS_THREAD_UTILS)
+ #include <botan/internal/thread_pool.h>
+#endif
+
namespace Botan_Tests {
Test_Runner::Test_Runner(std::ostream& out) : m_output(out) {}
@@ -196,7 +200,7 @@ int Test_Runner::run(const Test_Options& opts)
Botan_Tests::Test::set_test_rng(std::move(rng));
- const size_t failed = run_tests(req, i, opts.test_runs());
+ const size_t failed = run_tests(req, opts.test_threads(), i, opts.test_runs());
if(failed > 0)
return static_cast<int>(failed);
}
@@ -236,9 +240,68 @@ std::string report_out(const std::vector<Botan_Tests::Test::Result>& results,
return out.str();
}
+std::vector<Test::Result> run_a_test(const std::string& test_name)
+ {
+ std::vector<Test::Result> results;
+
+ try
+ {
+ if(test_name == "simd_32" && Botan::CPUID::has_simd_32() == false)
+ {
+ results.push_back(Test::Result::Note(test_name, "SIMD not available on this platform"));
+ }
+ else if(std::unique_ptr<Test> test = Test::get_test(test_name))
+ {
+ std::vector<Test::Result> test_results = test->run();
+ results.insert(results.end(), test_results.begin(), test_results.end());
+ }
+ else
+ {
+ results.push_back(Test::Result::Note(test_name, "Test missing or unavailable"));
+ }
+ }
+ catch(std::exception& e)
+ {
+ results.push_back(Test::Result::Failure(test_name, e.what()));
+ }
+ catch(...)
+ {
+ results.push_back(Test::Result::Failure(test_name, "unknown exception"));
+ }
+
+ return results;
+ }
+
+std::string test_summary(size_t test_run, size_t tot_test_runs, uint64_t total_ns,
+ size_t tests_ran, size_t tests_failed)
+ {
+ std::ostringstream oss;
+
+ if(test_run == 0 && tot_test_runs == 1)
+ oss << "Tests";
+ else
+ oss << "Test run " << (1+test_run) << "/" << tot_test_runs;
+
+ oss << " complete ran " << tests_ran << " tests in "
+ << Botan_Tests::Test::format_time(total_ns) << " ";
+
+ if(tests_failed > 0)
+ {
+ oss << tests_failed << " tests failed";
+ }
+ else if(tests_ran > 0)
+ {
+ oss << "all tests ok";
+ }
+
+ oss << "\n";
+ return oss.str();
+ }
+
}
size_t Test_Runner::run_tests(const std::vector<std::string>& tests_to_run,
+ size_t test_threads,
size_t test_run,
size_t tot_test_runs)
{
@@ -246,60 +309,48 @@ size_t Test_Runner::run_tests(const std::vector<std::string>& tests_to_run,
const uint64_t start_time = Botan_Tests::Test::timestamp();
- for(auto const& test_name : tests_to_run)
+ if(test_threads != 1)
{
- output() << test_name << ':' << std::endl;
+#if defined(BOTAN_HAS_THREAD_UTILS)
+ // If 0 then we let thread pool select the count
+ Botan::Thread_Pool pool(test_threads);
- std::vector<Test::Result> results;
+ std::vector<std::future<std::vector<Test::Result>>> m_fut_results;
- try
+ for(auto const& test_name : tests_to_run)
{
- if(test_name == "simd_32" && Botan::CPUID::has_simd_32() == false)
- {
- results.push_back(Test::Result::Note(test_name, "SIMD not available on this platform"));
- }
- else if(Test* test = Test::get_test(test_name))
- {
- std::vector<Test::Result> test_results = test->run();
- results.insert(results.end(), test_results.begin(), test_results.end());
- }
- else
- {
- results.push_back(Test::Result::Note(test_name, "Test missing or unavailable"));
- }
+ m_fut_results.push_back(pool.run(run_a_test, test_name));
}
- catch(std::exception& e)
- {
- results.push_back(Test::Result::Failure(test_name, e.what()));
- }
- catch(...)
+
+ for(size_t i = 0; i != m_fut_results.size(); ++i)
{
- results.push_back(Test::Result::Failure(test_name, "unknown exception"));
+ output() << tests_to_run[i] << ':' << std::endl;
+ const std::vector<Test::Result> results = m_fut_results[i].get();
+ output() << report_out(results, tests_failed, tests_ran) << std::flush;
}
- output() << report_out(results, tests_failed, tests_ran) << std::flush;
- }
-
- const uint64_t total_ns = Botan_Tests::Test::timestamp() - start_time;
+ pool.shutdown();
- if(test_run == 0 && tot_test_runs == 1)
- output() << "Tests";
- else
- output() << "Test run " << (1+test_run) << "/" << tot_test_runs;
+ const uint64_t total_ns = Botan_Tests::Test::timestamp() - start_time;
- output() << " complete ran " << tests_ran << " tests in "
- << Botan_Tests::Test::format_time(total_ns) << " ";
+ output() << test_summary(test_run, tot_test_runs, total_ns, tests_ran, tests_failed);
- if(tests_failed > 0)
- {
- output() << tests_failed << " tests failed";
+ return tests_failed;
+#else
+ output() << "Running tests in multiple threads not enabled in this build\n";
+#endif
}
- else if(tests_ran > 0)
+
+ for(auto const& test_name : tests_to_run)
{
- output() << "all tests ok";
+ output() << test_name << ':' << std::endl;
+ const std::vector<Test::Result> results = run_a_test(test_name);
+ output() << report_out(results, tests_failed, tests_ran) << std::flush;
}
- output() << std::endl;
+ const uint64_t total_ns = Botan_Tests::Test::timestamp() - start_time;
+
+ output() << test_summary(test_run, tot_test_runs, total_ns, tests_ran, tests_failed);
return tests_failed;
}
diff --git a/src/tests/test_runner.h b/src/tests/test_runner.h
index 0bde5cc4f..47d4f1363 100644
--- a/src/tests/test_runner.h
+++ b/src/tests/test_runner.h
@@ -26,8 +26,9 @@ class Test_Runner final
std::ostream& output() const { return m_output; }
size_t run_tests(const std::vector<std::string>& tests_to_run,
+ size_t test_threads,
size_t test_run,
- const size_t tot_test_runs);
+ size_t tot_test_runs);
std::ostream& m_output;
};
diff --git a/src/tests/test_thread_utils.cpp b/src/tests/test_thread_utils.cpp
new file mode 100644
index 000000000..2374df860
--- /dev/null
+++ b/src/tests/test_thread_utils.cpp
@@ -0,0 +1,56 @@
+/*
+* (C) 2019 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include "tests.h"
+
+#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_THREAD_UTILS)
+
+#include <botan/internal/thread_pool.h>
+#include <chrono>
+
+namespace Botan_Tests {
+
+// TODO test Barrier
+// TODO test Semaphore
+
+namespace {
+
+Test::Result thread_pool()
+ {
+ Test::Result result("Thread_Pool");
+
+ // Using lots of threads since here the works spend most of the time sleeping
+ Botan::Thread_Pool pool(16);
+
+ auto sleep_and_return = [](size_t x) -> size_t {
+ std::this_thread::sleep_for(std::chrono::milliseconds((x*97)%127));
+ return x;
+ };
+
+ std::vector<std::future<size_t>> futures;
+ for(size_t i = 0; i != 100; ++i)
+ {
+ auto fut = pool.run(sleep_and_return, i);
+ futures.push_back(std::move(fut));
+ }
+
+ for(size_t i = 0; i != futures.size(); ++i)
+ {
+ result.test_eq("Expected return value", futures[i].get(), i);
+ }
+
+ pool.shutdown();
+
+ return result;
+ }
+
+BOTAN_REGISTER_TEST_FN("thread_pool", thread_pool);
+
+}
+
+}
+
+#endif
diff --git a/src/tests/tests.cpp b/src/tests/tests.cpp
index 2300dfaea..6b44bccd9 100644
--- a/src/tests/tests.cpp
+++ b/src/tests/tests.cpp
@@ -29,18 +29,6 @@
namespace Botan_Tests {
-Test::Registration::Registration(const std::string& name, Test* test)
- {
- if(Test::global_registry().count(name) == 0)
- {
- Test::global_registry().insert(std::make_pair(name, std::unique_ptr<Test>(test)));
- }
- else
- {
- throw Test_Error("Duplicate registration of test '" + name + "'");
- }
- }
-
void Test::Result::merge(const Result& other)
{
if(who() != other.who())
@@ -484,9 +472,9 @@ std::string Test::Result::result_string() const
// static Test:: functions
//static
-std::map<std::string, std::unique_ptr<Test>>& Test::global_registry()
+std::map<std::string, std::function<Test* ()>>& Test::global_registry()
{
- static std::map<std::string, std::unique_ptr<Test>> g_test_registry;
+ static std::map<std::string, std::function<Test* ()>> g_test_registry;
return g_test_registry;
}
@@ -504,12 +492,12 @@ std::set<std::string> Test::registered_tests()
}
//static
-Test* Test::get_test(const std::string& test_name)
+std::unique_ptr<Test> Test::get_test(const std::string& test_name)
{
auto i = Test::global_registry().find(test_name);
if(i != Test::global_registry().end())
{
- return i->second.get();
+ return std::unique_ptr<Test>(i->second());
}
return nullptr;
}
@@ -596,6 +584,14 @@ void Test::set_test_options(const Test_Options& opts)
//static
void Test::set_test_rng(std::unique_ptr<Botan::RandomNumberGenerator> rng)
{
+#if defined(BOTAN_TARGET_OS_HAS_THREADS)
+ if(m_opts.test_threads() != 1)
+ {
+ m_test_rng.reset(new Botan::Serialized_RNG(rng.release()));
+ return;
+ }
+#endif
+
m_test_rng.reset(rng.release());
}
diff --git a/src/tests/tests.h b/src/tests/tests.h
index 93e972900..22651ff8c 100644
--- a/src/tests/tests.h
+++ b/src/tests/tests.h
@@ -60,6 +60,7 @@ class Test_Options
const std::string& provider,
const std::string& drbg_seed,
size_t test_runs,
+ size_t test_threads,
bool verbose,
bool log_success,
bool run_online_tests,
@@ -73,6 +74,7 @@ class Test_Options
m_provider(provider),
m_drbg_seed(drbg_seed),
m_test_runs(test_runs),
+ m_test_threads(test_threads),
m_verbose(verbose),
m_log_success(log_success),
m_run_online_tests(run_online_tests),
@@ -98,6 +100,8 @@ class Test_Options
size_t test_runs() const { return m_test_runs; }
+ size_t test_threads() const { return m_test_threads; }
+
bool log_success() const { return m_log_success; }
bool run_online_tests() const { return m_run_online_tests; }
@@ -126,6 +130,7 @@ class Test_Options
std::string m_provider;
std::string m_drbg_seed;
size_t m_test_runs;
+ size_t m_test_threads;
bool m_verbose;
bool m_log_success;
bool m_run_online_tests;
@@ -436,22 +441,30 @@ class Test
std::vector<std::string> m_log;
};
- class Registration final
- {
- public:
- Registration(const std::string& name, Test* test);
- };
-
virtual ~Test() = default;
virtual std::vector<Test::Result> run() = 0;
virtual std::vector<std::string> possible_providers(const std::string&);
- static std::map<std::string, std::unique_ptr<Test>>& global_registry();
+ template<typename Test_Class>
+ class Registration
+ {
+ public:
+ Registration(const std::string& name)
+ {
+ if(Test::global_registry().count(name) != 0)
+ throw Test_Error("Duplicate registration of test '" + name + "'");
+
+ auto maker = []() -> Test* { return new Test_Class; };
+ Test::global_registry().insert(std::make_pair(name, maker));
+ }
+ };
+
+ static std::map<std::string, std::function<Test* ()>>& global_registry();
static std::set<std::string> registered_tests();
- static Test* get_test(const std::string& test_name);
+ static std::unique_ptr<Test> get_test(const std::string& test_name);
static std::string data_file(const std::string& what);
@@ -515,7 +528,39 @@ class Test
* Register the test with the runner
*/
#define BOTAN_REGISTER_TEST(type, Test_Class) \
- Test::Registration reg_ ## Test_Class ## _tests(type, new Test_Class)
+ Test::Registration<Test_Class> reg_ ## Test_Class ## _tests(type)
+
+typedef Test::Result (*test_fn)();
+
+class FnTest : public Test
+ {
+ public:
+ FnTest(test_fn fn) : m_fn(fn) {}
+
+ std::vector<Test::Result> run() override
+ {
+ return {m_fn()};
+ }
+
+ private:
+ test_fn m_fn;
+ };
+
+class FnRegistration
+ {
+ public:
+ FnRegistration(const std::string& name, test_fn fn)
+ {
+ if(Test::global_registry().count(name) != 0)
+ throw Test_Error("Duplicate registration of test '" + name + "'");
+
+ auto maker = [=]() -> Test* { return new FnTest(fn); };
+ Test::global_registry().insert(std::make_pair(name, maker));
+ }
+ };
+
+#define BOTAN_REGISTER_TEST_FN(test_name, fn_name) \
+ FnRegistration reg_ ## fn_name(test_name, fn_name)
class VarMap
{