From d27e2d87b95b7982c05b4c88e463cc9626ab9f0a Mon Sep 17 00:00:00 2001 From: Larry Ruane Date: Wed, 23 Nov 2022 17:02:23 -0700 Subject: [PATCH] test: test_bitcoin: allow -testdatadir= Specifying this argument overrides the path location for test_bitcoin; it becomes /test_common_Bitcoin Core//datadir. Also, this directory isn't removed after the test completes. This can make it easier for developers to study the results of a test (see the state of the data directory after the test runs), and also (for example) have an editor open on debug.log to monitor it across multiple test runs instead of having to re-open a different pathname each time. Example usage (note the "--" is needed): test_bitcoin --run_test=getarg_tests/boolarg -- \ -testdatadir=/somewhere/mydatadir This will create (if necessary) and use the data directory: /somewhere/mydatadir/test_common_Bitcoin Core/getarg_tests/boolarg/datadir Co-authored-by: furszy --- src/bench/bench.cpp | 2 ++ src/qt/main.cpp | 2 ++ src/qt/test/test_main.cpp | 2 ++ src/test/README.md | 50 +++++++++++++++++++++++---- src/test/fuzz/fuzz.cpp | 2 ++ src/test/main.cpp | 7 ++++ src/test/util/setup_common.cpp | 63 ++++++++++++++++++++++++++++++---- src/test/util/setup_common.h | 7 +++- 8 files changed, 121 insertions(+), 14 deletions(-) diff --git a/src/bench/bench.cpp b/src/bench/bench.cpp index 84b66bc4b22..a13a693ad77 100644 --- a/src/bench/bench.cpp +++ b/src/bench/bench.cpp @@ -23,6 +23,8 @@ const std::function G_TEST_LOG_FUN{}; const std::function()> G_TEST_COMMAND_LINE_ARGUMENTS{}; +const std::function G_TEST_GET_FULL_NAME{}; + namespace { void GenerateTemplateResults(const std::vector& benchmarkResults, const fs::path& file, const char* tpl) diff --git a/src/qt/main.cpp b/src/qt/main.cpp index c84dd78b44e..ded057dbfaf 100644 --- a/src/qt/main.cpp +++ b/src/qt/main.cpp @@ -19,6 +19,8 @@ extern const std::function G_TRANSLATION_FUN = [](cons }; UrlDecodeFn* const URL_DECODE = urlDecode; +const std::function G_TEST_GET_FULL_NAME{}; + MAIN_FUNCTION { return GuiMain(argc, argv); diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index e45fc1ced84..8c09e9f5401 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -48,6 +48,8 @@ const std::function G_TEST_LOG_FUN{}; const std::function()> G_TEST_COMMAND_LINE_ARGUMENTS{}; +const std::function G_TEST_GET_FULL_NAME{}; + // This is all you need to run all the tests int main(int argc, char* argv[]) { diff --git a/src/test/README.md b/src/test/README.md index 90d0e7102d0..4c152b0d526 100644 --- a/src/test/README.md +++ b/src/test/README.md @@ -41,17 +41,18 @@ test_bitcoin --log_level=all --run_test=getarg_tests ``` `log_level` controls the verbosity of the test framework, which logs when a -test case is entered, for example. `test_bitcoin` also accepts the command -line arguments accepted by `bitcoind`. Use `--` to separate both types of -arguments: +test case is entered, for example. + +`test_bitcoin` also accepts some of the command line arguments accepted by +`bitcoind`. Use `--` to separate these sets of arguments: ```bash test_bitcoin --log_level=all --run_test=getarg_tests -- -printtoconsole=1 ``` -The `-printtoconsole=1` after the two dashes redirects the debug log, which -would normally go to a file in the test datadir -(`BasicTestingSetup::m_path_root`), to the standard terminal output. +The `-printtoconsole=1` after the two dashes sends debug logging, which +normally goes only to `debug.log` within the data directory, also to the +standard terminal output. ... or to run just the doubledash test: @@ -59,7 +60,42 @@ would normally go to a file in the test datadir test_bitcoin --run_test=getarg_tests/doubledash ``` -Run `test_bitcoin --help` for the full list. +`test_bitcoin` creates a temporary working (data) directory with a randomly +generated pathname within `test_common_Bitcoin Core/`, which in turn is within +the system's temporary directory (see +[`temp_directory_path`](https://en.cppreference.com/w/cpp/filesystem/temp_directory_path)). +This data directory looks like a simplified form of the standard `bitcoind` data +directory. Its content will vary depending on the test, but it will always +have a `debug.log` file, for example. + +The location of the temporary data directory can be specified with the +`-testdatadir` option. This can make debugging easier. The directory +path used is the argument path appended with +`/test_common_Bitcoin Core//datadir`. +The directory path is created if necessary. +Specifying this argument also causes the data directory +not to be removed after the last test. This is useful for looking at +what the test wrote to `debug.log` after it completes, for example. +(The directory is removed at the start of the next test run, +so no leftover state is used.) + +```bash +$ test_bitcoin --run_test=getarg_tests/doubledash -- -testdatadir=/somewhere/mydatadir +Test directory (will not be deleted): "/somewhere/mydatadir/test_common_Bitcoin Core/getarg_tests/doubledash/datadir +Running 1 test case... + +*** No errors detected +$ ls -l '/somewhere/mydatadir/test_common_Bitcoin Core/getarg_tests/doubledash/datadir' +total 8 +drwxrwxr-x 2 admin admin 4096 Nov 27 22:45 blocks +-rw-rw-r-- 1 admin admin 1003 Nov 27 22:45 debug.log +``` + +If you run an entire test suite, such as `--run_test=getarg_tests`, or all the test suites +(by not specifying `--run_test`), a separate directory +will be created for each individual test. + +Run `test_bitcoin --help` for the full list of tests. ### Adding test cases diff --git a/src/test/fuzz/fuzz.cpp b/src/test/fuzz/fuzz.cpp index 5245b4607bf..0507aa251b1 100644 --- a/src/test/fuzz/fuzz.cpp +++ b/src/test/fuzz/fuzz.cpp @@ -35,6 +35,8 @@ __AFL_FUZZ_INIT(); const std::function G_TEST_LOG_FUN{}; +const std::function G_TEST_GET_FULL_NAME{}; + /** * A copy of the command line arguments that start with `--`. * First `LLVMFuzzerInitialize()` is called, which saves the arguments to `g_args`. diff --git a/src/test/main.cpp b/src/test/main.cpp index 0809f83c93f..67740ece930 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -39,3 +39,10 @@ const std::function()> G_TEST_COMMAND_LINE_ARGUMENTS = } return args; }; + +/** + * Retrieve the boost unit test name. + */ +const std::function G_TEST_GET_FULL_NAME = []() { + return boost::unit_test::framework::current_test_case().full_name(); +}; diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 8789e861969..86a33423537 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -97,9 +98,22 @@ struct NetworkSetup }; static NetworkSetup g_networksetup_instance; +/** Register test-only arguments */ +static void SetupUnitTestArgs(ArgsManager& argsman) +{ + argsman.AddArg("-testdatadir", strprintf("Custom data directory (default: %s)", fs::PathToString(fs::temp_directory_path() / "test_common_" PACKAGE_NAME / "")), + ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); +} + +/** Test setup failure */ +static void ExitFailure(std::string_view str_err) +{ + std::cerr << str_err << std::endl; + exit(EXIT_FAILURE); +} + BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vector& extra_args) - : m_path_root{fs::temp_directory_path() / "test_common_" PACKAGE_NAME / g_insecure_rand_ctx_temp_path.rand256().ToString()}, - m_args{} + : m_args{} { m_node.shutdown = &m_interrupt; m_node.args = &gArgs; @@ -120,18 +134,49 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vecto arguments = Cat(arguments, G_TEST_COMMAND_LINE_ARGUMENTS()); } util::ThreadRename("test"); - fs::create_directories(m_path_root); - m_args.ForceSetArg("-datadir", fs::PathToString(m_path_root)); - gArgs.ForceSetArg("-datadir", fs::PathToString(m_path_root)); gArgs.ClearPathCache(); { SetupServerArgs(*m_node.args); + SetupUnitTestArgs(*m_node.args); std::string error; if (!m_node.args->ParseParameters(arguments.size(), arguments.data(), error)) { m_node.args->ClearArgs(); throw std::runtime_error{error}; } } + + if (!m_node.args->IsArgSet("-testdatadir")) { + // By default, the data directory has a random name + const auto rand_str{g_insecure_rand_ctx_temp_path.rand256().ToString()}; + m_path_root = fs::temp_directory_path() / "test_common_" PACKAGE_NAME / rand_str; + TryCreateDirectories(m_path_root); + } else { + // Custom data directory + m_has_custom_datadir = true; + fs::path root_dir{m_node.args->GetPathArg("-testdatadir")}; + if (root_dir.empty()) ExitFailure("-testdatadir argument is empty, please specify a path"); + + root_dir = fs::absolute(root_dir); + const std::string test_path{G_TEST_GET_FULL_NAME ? G_TEST_GET_FULL_NAME() : ""}; + m_path_lock = root_dir / "test_common_" PACKAGE_NAME / fs::PathFromString(test_path); + m_path_root = m_path_lock / "datadir"; + + // Try to obtain the lock; if unsuccessful don't disturb the existing test. + TryCreateDirectories(m_path_lock); + if (util::LockDirectory(m_path_lock, ".lock", /*probe_only=*/false) != util::LockResult::Success) { + ExitFailure("Cannot obtain a lock on test data lock directory " + fs::PathToString(m_path_lock) + '\n' + "The test executable is probably already running."); + } + + // Always start with a fresh data directory; this doesn't delete the .lock file located one level above. + fs::remove_all(m_path_root); + if (!TryCreateDirectories(m_path_root)) ExitFailure("Cannot create test data directory"); + + // Print the test directory name if custom. + std::cout << "Test directory (will not be deleted): " << m_path_root << std::endl; + } + m_args.ForceSetArg("-datadir", fs::PathToString(m_path_root)); + gArgs.ForceSetArg("-datadir", fs::PathToString(m_path_root)); + SelectParams(chainType); SeedInsecureRand(); if (G_TEST_LOG_FUN) LogInstance().PushBackCallback(G_TEST_LOG_FUN); @@ -159,7 +204,13 @@ BasicTestingSetup::~BasicTestingSetup() m_node.kernel.reset(); SetMockTime(0s); // Reset mocktime for following tests LogInstance().DisconnectTestLogger(); - fs::remove_all(m_path_root); + if (m_has_custom_datadir) { + // Only remove the lock file, preserve the data directory. + UnlockDirectory(m_path_lock, ".lock"); + fs::remove(m_path_lock / ".lock"); + } else { + fs::remove_all(m_path_root); + } gArgs.ClearArgs(); } diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 9ff4c372a5c..8ccf9b571c8 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -32,6 +32,9 @@ extern const std::function G_TEST_LOG_FUN; /** Retrieve the command line arguments. */ extern const std::function()> G_TEST_COMMAND_LINE_ARGUMENTS; +/** Retrieve the unit test name. */ +extern const std::function G_TEST_GET_FULL_NAME; + // Enable BOOST_CHECK_EQUAL for enum class types namespace std { template @@ -53,7 +56,9 @@ struct BasicTestingSetup { explicit BasicTestingSetup(const ChainType chainType = ChainType::MAIN, const std::vector& extra_args = {}); ~BasicTestingSetup(); - const fs::path m_path_root; + fs::path m_path_root; + fs::path m_path_lock; + bool m_has_custom_datadir{false}; ArgsManager m_args; };