Skip to content

Commit 5ebb406

Browse files
committed
Merge bitcoin/bitcoin#26564: test: test_bitcoin: allow -testdatadir=<datadir>
d27e2d8 test: test_bitcoin: allow -testdatadir=<datadir> (Larry Ruane) Pull request description: This backward-compatible change would help with code review, testing, and debugging. When `test_bitcoin` runs, it creates a working or data directory within `/tmp/test_common_Bitcoin\ Core/`, named as a long random (hex) string. This small patch does three things: - If the (new) argument `-testdatadir=<datadir>` is given, use `<datadir>/test_temp/<test-name>/datadir` as the working directory - When the test starts, remove `<datadir>/test_temp/<test-name>/datadir` if it exists from an earlier run (currently, it's presumed not to exist due to the long random string) - Don't delete the working directory at the end of the test if a custom data directory is being used Example usage, which will remove, create, use `/somewhere/test_temp/getarg_tests/boolarg`, and leave it afterward: ``` $ test_bitcoin --run_test=getarg_tests/boolarg -- -testdatadir=/somewhere Running 1 test case... Test directory (will not be deleted): "/somewhere/test_temp/getarg_tests/boolarg/datadir" *** No errors detected $ ls -l /somewhere/test_temp/getarg_tests/boolarg/datadir total 8 drwxrwxr-x 2 larry larry 4096 Feb 22 10:28 blocks -rw-rw-r-- 1 larry larry 1273 Feb 22 10:28 debug.log ``` (A relative pathname also works.) This change affects only `test_bitcoin`; it could also be applied to `test_bitcoin-qt` but that's slightly more involved so I'm skipping that for now. The rationale for this change is that, when running the test using the debugger, it's often useful to watch `debug.log` as the test runs and inspect some of the other files (I've looked at the generated `blknnnn.dat` files for example). Currently, that requires figuring out where the test's working directory is since it changes on every test run. Tests can be run with `-printtoconsole=1` to show debug logging to the terminal, but it's nice to keep `debug.log` continuously open in an editor, for example. Even if not using a debugger, it's sometimes helpful to see `debug.log` and other artifacts after the test completes. Similar functionality is already possible with the functional tests using the `--tmpdir=` and `--nocleanup` arguments. ACKs for top commit: davidgumberg: ACK bitcoin/bitcoin@d27e2d8 tdb3: re-ACK for d27e2d8 achow101: ACK d27e2d8 cbergqvist: ACK d27e2d8! (Already did some testing with `fs::remove()` to make sure it was compatible with the `util::Lock/UnlockDirectory` implementation). marcofleon: ACK d27e2d8. I ran all the tests with my previous open file limit and no errors were detected. Also ran some individual tests with no, relative, and absolute paths and everything looks good. furszy: ACK d27e2d8 Tree-SHA512: a8f535f34a48b6699cb440f97f5562ec643f3bfba4ea685768980b871fc8b6e1135f70fc05dbe19aa2c8bacb1ddeaff212d63473605a7422ff76332b3a6b1f68
2 parents 4cc99df + d27e2d8 commit 5ebb406

File tree

8 files changed

+121
-14
lines changed

8 files changed

+121
-14
lines changed

src/bench/bench.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ const std::function<void(const std::string&)> G_TEST_LOG_FUN{};
2323

2424
const std::function<std::vector<const char*>()> G_TEST_COMMAND_LINE_ARGUMENTS{};
2525

26+
const std::function<std::string()> G_TEST_GET_FULL_NAME{};
27+
2628
namespace {
2729

2830
void GenerateTemplateResults(const std::vector<ankerl::nanobench::Result>& benchmarkResults, const fs::path& file, const char* tpl)

src/qt/main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ extern const std::function<std::string(const char*)> G_TRANSLATION_FUN = [](cons
1919
};
2020
UrlDecodeFn* const URL_DECODE = urlDecode;
2121

22+
const std::function<std::string()> G_TEST_GET_FULL_NAME{};
23+
2224
MAIN_FUNCTION
2325
{
2426
return GuiMain(argc, argv);

src/qt/test/test_main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ const std::function<void(const std::string&)> G_TEST_LOG_FUN{};
5050

5151
const std::function<std::vector<const char*>()> G_TEST_COMMAND_LINE_ARGUMENTS{};
5252

53+
const std::function<std::string()> G_TEST_GET_FULL_NAME{};
54+
5355
// This is all you need to run all the tests
5456
int main(int argc, char* argv[])
5557
{

src/test/README.md

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,61 @@ test_bitcoin --log_level=all --run_test=getarg_tests
4242
```
4343

4444
`log_level` controls the verbosity of the test framework, which logs when a
45-
test case is entered, for example. `test_bitcoin` also accepts the command
46-
line arguments accepted by `bitcoind`. Use `--` to separate both types of
47-
arguments:
45+
test case is entered, for example.
46+
47+
`test_bitcoin` also accepts some of the command line arguments accepted by
48+
`bitcoind`. Use `--` to separate these sets of arguments:
4849

4950
```bash
5051
test_bitcoin --log_level=all --run_test=getarg_tests -- -printtoconsole=1
5152
```
5253

53-
The `-printtoconsole=1` after the two dashes redirects the debug log, which
54-
would normally go to a file in the test datadir
55-
(`BasicTestingSetup::m_path_root`), to the standard terminal output.
54+
The `-printtoconsole=1` after the two dashes sends debug logging, which
55+
normally goes only to `debug.log` within the data directory, also to the
56+
standard terminal output.
5657

5758
... or to run just the doubledash test:
5859

5960
```bash
6061
test_bitcoin --run_test=getarg_tests/doubledash
6162
```
6263

63-
Run `test_bitcoin --help` for the full list.
64+
`test_bitcoin` creates a temporary working (data) directory with a randomly
65+
generated pathname within `test_common_Bitcoin Core/`, which in turn is within
66+
the system's temporary directory (see
67+
[`temp_directory_path`](https://en.cppreference.com/w/cpp/filesystem/temp_directory_path)).
68+
This data directory looks like a simplified form of the standard `bitcoind` data
69+
directory. Its content will vary depending on the test, but it will always
70+
have a `debug.log` file, for example.
71+
72+
The location of the temporary data directory can be specified with the
73+
`-testdatadir` option. This can make debugging easier. The directory
74+
path used is the argument path appended with
75+
`/test_common_Bitcoin Core/<test-name>/datadir`.
76+
The directory path is created if necessary.
77+
Specifying this argument also causes the data directory
78+
not to be removed after the last test. This is useful for looking at
79+
what the test wrote to `debug.log` after it completes, for example.
80+
(The directory is removed at the start of the next test run,
81+
so no leftover state is used.)
82+
83+
```bash
84+
$ test_bitcoin --run_test=getarg_tests/doubledash -- -testdatadir=/somewhere/mydatadir
85+
Test directory (will not be deleted): "/somewhere/mydatadir/test_common_Bitcoin Core/getarg_tests/doubledash/datadir
86+
Running 1 test case...
87+
88+
*** No errors detected
89+
$ ls -l '/somewhere/mydatadir/test_common_Bitcoin Core/getarg_tests/doubledash/datadir'
90+
total 8
91+
drwxrwxr-x 2 admin admin 4096 Nov 27 22:45 blocks
92+
-rw-rw-r-- 1 admin admin 1003 Nov 27 22:45 debug.log
93+
```
94+
95+
If you run an entire test suite, such as `--run_test=getarg_tests`, or all the test suites
96+
(by not specifying `--run_test`), a separate directory
97+
will be created for each individual test.
98+
99+
Run `test_bitcoin --help` for the full list of tests.
64100
65101
### Adding test cases
66102

src/test/fuzz/fuzz.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ __AFL_FUZZ_INIT();
3535

3636
const std::function<void(const std::string&)> G_TEST_LOG_FUN{};
3737

38+
const std::function<std::string()> G_TEST_GET_FULL_NAME{};
39+
3840
/**
3941
* A copy of the command line arguments that start with `--`.
4042
* First `LLVMFuzzerInitialize()` is called, which saves the arguments to `g_args`.

src/test/main.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,10 @@ const std::function<std::vector<const char*>()> G_TEST_COMMAND_LINE_ARGUMENTS =
3939
}
4040
return args;
4141
};
42+
43+
/**
44+
* Retrieve the boost unit test name.
45+
*/
46+
const std::function<std::string()> G_TEST_GET_FULL_NAME = []() {
47+
return boost::unit_test::framework::current_test_case().full_name();
48+
};

src/test/util/setup_common.cpp

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
#include <txmempool.h>
5353
#include <util/chaintype.h>
5454
#include <util/check.h>
55+
#include <util/fs_helpers.h>
5556
#include <util/rbf.h>
5657
#include <util/strencodings.h>
5758
#include <util/string.h>
@@ -100,9 +101,22 @@ struct NetworkSetup
100101
};
101102
static NetworkSetup g_networksetup_instance;
102103

104+
/** Register test-only arguments */
105+
static void SetupUnitTestArgs(ArgsManager& argsman)
106+
{
107+
argsman.AddArg("-testdatadir", strprintf("Custom data directory (default: %s<random_string>)", fs::PathToString(fs::temp_directory_path() / "test_common_" PACKAGE_NAME / "")),
108+
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
109+
}
110+
111+
/** Test setup failure */
112+
static void ExitFailure(std::string_view str_err)
113+
{
114+
std::cerr << str_err << std::endl;
115+
exit(EXIT_FAILURE);
116+
}
117+
103118
BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vector<const char*>& extra_args)
104-
: m_path_root{fs::temp_directory_path() / "test_common_" PACKAGE_NAME / g_insecure_rand_ctx_temp_path.rand256().ToString()},
105-
m_args{}
119+
: m_args{}
106120
{
107121
m_node.shutdown = &m_interrupt;
108122
m_node.args = &gArgs;
@@ -123,18 +137,49 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vecto
123137
arguments = Cat(arguments, G_TEST_COMMAND_LINE_ARGUMENTS());
124138
}
125139
util::ThreadRename("test");
126-
fs::create_directories(m_path_root);
127-
m_args.ForceSetArg("-datadir", fs::PathToString(m_path_root));
128-
gArgs.ForceSetArg("-datadir", fs::PathToString(m_path_root));
129140
gArgs.ClearPathCache();
130141
{
131142
SetupServerArgs(*m_node.args);
143+
SetupUnitTestArgs(*m_node.args);
132144
std::string error;
133145
if (!m_node.args->ParseParameters(arguments.size(), arguments.data(), error)) {
134146
m_node.args->ClearArgs();
135147
throw std::runtime_error{error};
136148
}
137149
}
150+
151+
if (!m_node.args->IsArgSet("-testdatadir")) {
152+
// By default, the data directory has a random name
153+
const auto rand_str{g_insecure_rand_ctx_temp_path.rand256().ToString()};
154+
m_path_root = fs::temp_directory_path() / "test_common_" PACKAGE_NAME / rand_str;
155+
TryCreateDirectories(m_path_root);
156+
} else {
157+
// Custom data directory
158+
m_has_custom_datadir = true;
159+
fs::path root_dir{m_node.args->GetPathArg("-testdatadir")};
160+
if (root_dir.empty()) ExitFailure("-testdatadir argument is empty, please specify a path");
161+
162+
root_dir = fs::absolute(root_dir);
163+
const std::string test_path{G_TEST_GET_FULL_NAME ? G_TEST_GET_FULL_NAME() : ""};
164+
m_path_lock = root_dir / "test_common_" PACKAGE_NAME / fs::PathFromString(test_path);
165+
m_path_root = m_path_lock / "datadir";
166+
167+
// Try to obtain the lock; if unsuccessful don't disturb the existing test.
168+
TryCreateDirectories(m_path_lock);
169+
if (util::LockDirectory(m_path_lock, ".lock", /*probe_only=*/false) != util::LockResult::Success) {
170+
ExitFailure("Cannot obtain a lock on test data lock directory " + fs::PathToString(m_path_lock) + '\n' + "The test executable is probably already running.");
171+
}
172+
173+
// Always start with a fresh data directory; this doesn't delete the .lock file located one level above.
174+
fs::remove_all(m_path_root);
175+
if (!TryCreateDirectories(m_path_root)) ExitFailure("Cannot create test data directory");
176+
177+
// Print the test directory name if custom.
178+
std::cout << "Test directory (will not be deleted): " << m_path_root << std::endl;
179+
}
180+
m_args.ForceSetArg("-datadir", fs::PathToString(m_path_root));
181+
gArgs.ForceSetArg("-datadir", fs::PathToString(m_path_root));
182+
138183
SelectParams(chainType);
139184
SeedInsecureRand();
140185
if (G_TEST_LOG_FUN) LogInstance().PushBackCallback(G_TEST_LOG_FUN);
@@ -162,7 +207,13 @@ BasicTestingSetup::~BasicTestingSetup()
162207
m_node.kernel.reset();
163208
SetMockTime(0s); // Reset mocktime for following tests
164209
LogInstance().DisconnectTestLogger();
165-
fs::remove_all(m_path_root);
210+
if (m_has_custom_datadir) {
211+
// Only remove the lock file, preserve the data directory.
212+
UnlockDirectory(m_path_lock, ".lock");
213+
fs::remove(m_path_lock / ".lock");
214+
} else {
215+
fs::remove_all(m_path_root);
216+
}
166217
gArgs.ClearArgs();
167218
}
168219

src/test/util/setup_common.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ extern const std::function<void(const std::string&)> G_TEST_LOG_FUN;
3232
/** Retrieve the command line arguments. */
3333
extern const std::function<std::vector<const char*>()> G_TEST_COMMAND_LINE_ARGUMENTS;
3434

35+
/** Retrieve the unit test name. */
36+
extern const std::function<std::string()> G_TEST_GET_FULL_NAME;
37+
3538
// Enable BOOST_CHECK_EQUAL for enum class types
3639
namespace std {
3740
template <typename T>
@@ -53,7 +56,9 @@ struct BasicTestingSetup {
5356
explicit BasicTestingSetup(const ChainType chainType = ChainType::MAIN, const std::vector<const char*>& extra_args = {});
5457
~BasicTestingSetup();
5558

56-
const fs::path m_path_root;
59+
fs::path m_path_root;
60+
fs::path m_path_lock;
61+
bool m_has_custom_datadir{false};
5762
ArgsManager m_args;
5863
};
5964

0 commit comments

Comments
 (0)