test: test_bitcoin: allow -testdatadir=<datadir>

Specifying this argument overrides the path location for test_bitcoin;
it becomes <datadir>/test_common_Bitcoin Core/<testname>/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 <mfurszy@protonmail.com>
This commit is contained in:
Larry Ruane
2022-11-23 17:02:23 -07:00
parent eefe4bacdd
commit d27e2d87b9
8 changed files with 121 additions and 14 deletions

View File

@@ -49,6 +49,7 @@
#include <txmempool.h>
#include <util/chaintype.h>
#include <util/check.h>
#include <util/fs_helpers.h>
#include <util/rbf.h>
#include <util/strencodings.h>
#include <util/string.h>
@@ -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<random_string>)", 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<const char*>& 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();
}

View File

@@ -32,6 +32,9 @@ extern const std::function<void(const std::string&)> G_TEST_LOG_FUN;
/** Retrieve the command line arguments. */
extern const std::function<std::vector<const char*>()> G_TEST_COMMAND_LINE_ARGUMENTS;
/** Retrieve the unit test name. */
extern const std::function<std::string()> G_TEST_GET_FULL_NAME;
// Enable BOOST_CHECK_EQUAL for enum class types
namespace std {
template <typename T>
@@ -53,7 +56,9 @@ struct BasicTestingSetup {
explicit BasicTestingSetup(const ChainType chainType = ChainType::MAIN, const std::vector<const char*>& 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;
};