Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions ci/test/00_setup_env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ export TEST_RUNNER_TIMEOUT_FACTOR=${TEST_RUNNER_TIMEOUT_FACTOR:-40}
export RUN_FUZZ_TESTS=${RUN_FUZZ_TESTS:-false}

# Randomize test order.
# See https://www.boost.org/doc/libs/1_71_0/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/random.html
export BOOST_TEST_RANDOM=${BOOST_TEST_RANDOM:-1}
export BITCOIN_TEST_RANDOM=${BITCOIN_TEST_RANDOM:-1}
# See man 7 debconf
export DEBIAN_FRONTEND=noninteractive
export CCACHE_MAXSIZE=${CCACHE_MAXSIZE:-2G}
Expand Down
2 changes: 1 addition & 1 deletion doc/developer-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ code.
naming style](#internal-interface-naming-style) for an exception to this
convention.

- Test suite naming convention: The Boost test suite in file
- Test suite naming convention: The test suite in file
`src/test/foo_tests.cpp` should be named `foo_tests`. Test suite names
must be unique.

Expand Down
37 changes: 18 additions & 19 deletions src/ipc/test/ipc_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
#include <kj/test.h>
#include <stdexcept>

#include <boost/test/unit_test.hpp>

#include <test/util/framework.hpp>
static_assert(ipc::capnp::messages::MAX_MONEY == MAX_MONEY);
static_assert(ipc::capnp::messages::MAX_DOUBLE == std::numeric_limits<double>::max());
static_assert(ipc::capnp::messages::DEFAULT_BLOCK_RESERVED_WEIGHT == DEFAULT_BLOCK_RESERVED_WEIGHT);
Expand All @@ -47,8 +46,8 @@ static std::string TempPath(std::string_view pattern)
std::string temp{fs::PathToString(fs::path{fs::temp_directory_path()} / fs::PathFromString(std::string{pattern}))};
temp.push_back('\0');
int fd{mkstemp(temp.data())};
BOOST_CHECK_GE(fd, 0);
BOOST_CHECK_EQUAL(close(fd), 0);
CHECK(fd >= 0);
CHECK(close(fd) == 0);
temp.resize(temp.size() - 1);
fs::remove(fs::PathFromString(temp));
return temp;
Expand Down Expand Up @@ -87,17 +86,17 @@ void IpcPipeTest()
std::unique_ptr<mp::ProxyClient<gen::FooInterface>> foo{foo_promise.get_future().get()};

// Test: make sure arguments were sent and return value is received
BOOST_CHECK_EQUAL(foo->add(1, 2), 3);
CHECK(foo->add(1, 2) == 3);

COutPoint txout1{Txid::FromUint256(uint256{100}), 200};
COutPoint txout2{foo->passOutPoint(txout1)};
BOOST_CHECK(txout1 == txout2);
CHECK((txout1 == txout2));

UniValue uni1{UniValue::VOBJ};
uni1.pushKV("i", 1);
uni1.pushKV("s", "two");
UniValue uni2{foo->passUniValue(uni1)};
BOOST_CHECK_EQUAL(uni1.write(), uni2.write());
CHECK(uni1.write() == uni2.write());

CMutableTransaction mtx;
mtx.version = 2;
Expand All @@ -106,15 +105,15 @@ void IpcPipeTest()
mtx.vout.emplace_back(COIN, CScript());
CTransactionRef tx1{MakeTransactionRef(mtx)};
CTransactionRef tx2{foo->passTransaction(tx1)};
BOOST_CHECK(*Assert(tx1) == *Assert(tx2));
CHECK((*Assert(tx1) == *Assert(tx2)));

std::vector<char> vec1{'H', 'e', 'l', 'l', 'o'};
std::vector<char> vec2{foo->passVectorChar(vec1)};
BOOST_CHECK_EQUAL(std::string_view(vec1.begin(), vec1.end()), std::string_view(vec2.begin(), vec2.end()));
CHECK(std::string_view(vec1.begin(), vec1.end()) == std::string_view(vec2.begin(), vec2.end()));

auto script1{CScript() << OP_11};
auto script2{foo->passScript(script1)};
BOOST_CHECK_EQUAL(HexStr(script1), HexStr(script2));
CHECK(HexStr(script1) == HexStr(script2));

// Test cleanup: disconnect and join thread
foo.reset();
Expand All @@ -125,7 +124,7 @@ void IpcPipeTest()
void IpcSocketPairTest()
{
int fds[2];
BOOST_CHECK_EQUAL(socketpair(AF_UNIX, SOCK_STREAM, 0, fds), 0);
CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0);
std::unique_ptr<interfaces::Init> init{std::make_unique<TestInit>()};
std::unique_ptr<ipc::Protocol> protocol{ipc::capnp::MakeCapnpProtocol()};
std::promise<void> promise;
Expand All @@ -135,10 +134,10 @@ void IpcSocketPairTest()
promise.get_future().wait();
std::unique_ptr<interfaces::Init> remote_init{protocol->connect(fds[1], "test-connect")};
std::unique_ptr<interfaces::Echo> remote_echo{remote_init->makeEcho()};
BOOST_CHECK_EQUAL(remote_echo->echo("echo test"), "echo test");
CHECK(remote_echo->echo("echo test") == "echo test");
remote_echo.reset();
remote_init->stop();
BOOST_CHECK(static_cast<TestInit*>(init.get())->stop_called.load());
CHECK(static_cast<TestInit*>(init.get())->stop_called.load());
remote_init.reset();
thread.join();
}
Expand All @@ -151,24 +150,24 @@ void IpcSocketTest(const fs::path& datadir)
std::unique_ptr<ipc::Process> process{ipc::MakeProcess()};

std::string invalid_bind{"invalid:"};
BOOST_CHECK_THROW(process->bind(datadir, "test_bitcoin", invalid_bind), std::invalid_argument);
BOOST_CHECK_THROW(process->connect(datadir, "test_bitcoin", invalid_bind), std::invalid_argument);
CHECK_THROWS_AS(process->bind(datadir, "test_bitcoin", invalid_bind), std::invalid_argument);
CHECK_THROWS_AS(process->connect(datadir, "test_bitcoin", invalid_bind), std::invalid_argument);

auto bind_and_listen{[&](const std::string& bind_address) {
std::string address{bind_address};
int serve_fd = process->bind(datadir, "test_bitcoin", address);
BOOST_CHECK_GE(serve_fd, 0);
BOOST_CHECK_EQUAL(address, bind_address);
CHECK(serve_fd >= 0);
CHECK(address == bind_address);
protocol->listen(serve_fd, "test-serve", *init);
}};

auto connect_and_test{[&](const std::string& connect_address) {
std::string address{connect_address};
int connect_fd{process->connect(datadir, "test_bitcoin", address)};
BOOST_CHECK_EQUAL(address, connect_address);
CHECK(address == connect_address);
std::unique_ptr<interfaces::Init> remote_init{protocol->connect(connect_fd, "test-connect")};
std::unique_ptr<interfaces::Echo> remote_echo{remote_init->makeEcho()};
BOOST_CHECK_EQUAL(remote_echo->echo("echo test"), "echo test");
CHECK(remote_echo->echo("echo test") == "echo test");
}};

// Need to specify explicit socket addresses outside the data directory, because the data
Expand Down
17 changes: 8 additions & 9 deletions src/ipc/test/ipc_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,28 @@

#include <test/util/common.h>
#include <test/util/setup_common.h>
#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(ipc_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(ipc_tests)
#include <test/util/framework.hpp>
TEST_SUITE_BEGIN(ipc_tests)
FIXTURE_TEST_CASE(ipc_tests, BasicTestingSetup)
{
IpcPipeTest();
IpcSocketPairTest();
IpcSocketTest(m_args.GetDataDirNet());
}

// Test address parsing.
BOOST_AUTO_TEST_CASE(parse_address_test)
FIXTURE_TEST_CASE(parse_address_test, BasicTestingSetup)
{
std::unique_ptr<ipc::Process> process{ipc::MakeProcess()};
fs::path datadir{"/var/empty/notexist"};
auto check_notexist{[](const std::system_error& e) { return e.code() == std::errc::no_such_file_or_directory; }};
auto check_address{[&](std::string address, std::string expect_address, std::string expect_error) {
if (expect_error.empty()) {
BOOST_CHECK_EXCEPTION(process->connect(datadir, "test_bitcoin", address), std::system_error, check_notexist);
CHECK_EXCEPTION(process->connect(datadir, "test_bitcoin", address), std::system_error, check_notexist);
} else {
BOOST_CHECK_EXCEPTION(process->connect(datadir, "test_bitcoin", address), std::invalid_argument, HasReason(expect_error));
CHECK_EXCEPTION(process->connect(datadir, "test_bitcoin", address), std::invalid_argument, HasReason(expect_error));
}
BOOST_CHECK_EQUAL(address, expect_address);
CHECK(address == expect_address);
}};
check_address("unix", "unix:/var/empty/notexist/test_bitcoin.sock", "");
check_address("unix:", "unix:/var/empty/notexist/test_bitcoin.sock", "");
Expand All @@ -40,4 +39,4 @@ BOOST_AUTO_TEST_CASE(parse_address_test)
check_address("invalid", "invalid", "Unrecognized address 'invalid'");
}

BOOST_AUTO_TEST_SUITE_END()
TEST_SUITE_END()
12 changes: 5 additions & 7 deletions src/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -160,22 +160,20 @@ target_link_libraries(test_bitcoin

add_subdirectory(${PROJECT_SOURCE_DIR}/src/ipc/test ipc)

function(add_boost_test source_file)
function(register_test_suite source_file)
if(NOT EXISTS ${source_file})
return()
endif()

file(READ "${source_file}" source_file_content)
string(REGEX
MATCHALL "(BOOST_FIXTURE_TEST_SUITE|BOOST_AUTO_TEST_SUITE)\\(([A-Za-z0-9_]+)"
MATCHALL "TEST_SUITE_BEGIN\\([A-Za-z_][A-Za-z0-9_]*"
test_suite_macro "${source_file_content}"
)
list(TRANSFORM test_suite_macro
REPLACE "(BOOST_FIXTURE_TEST_SUITE|BOOST_AUTO_TEST_SUITE)\\(" ""
)
list(TRANSFORM test_suite_macro REPLACE "^TEST_SUITE_BEGIN\\(" "")
foreach(test_suite_name IN LISTS test_suite_macro)
add_test(NAME ${test_suite_name}
COMMAND test_bitcoin --run_test=${test_suite_name} --catch_system_error=no --log_level=test_suite -- -printtoconsole=1
COMMAND test_bitcoin --run_test=${test_suite_name} -- -printtoconsole=1
)
set_property(TEST ${test_suite_name} PROPERTY
SKIP_REGULAR_EXPRESSION
Expand All @@ -194,7 +192,7 @@ function(add_all_test_targets)
if(result)
cmake_path(APPEND test_source_dir ${test_source} OUTPUT_VARIABLE test_source)
endif()
add_boost_test(${test_source})
register_test_suite(${test_source})
endforeach()
endfunction()

Expand Down
45 changes: 22 additions & 23 deletions src/test/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Unit tests

The sources in this directory are unit test cases. Boost includes a
unit testing framework, and since Bitcoin Core already uses Boost, it makes
sense to simply use this framework rather than require developers to
configure some other framework (we want as few impediments to creating
unit tests as possible).
The sources in this directory are unit test cases. Tests use a small
header-only framework declared in `src/test/util/framework.hpp`, which
provides registration macros (`TEST_CASE`, `FIXTURE_TEST_CASE`,
`TEST_SUITE_BEGIN`/`TEST_SUITE_END`) and assertion macros (`CHECK`,
`REQUIRE`, `CHECK_THROWS`, `CHECK_THROWS_AS`, `CHECK_EXCEPTION`,
`CHECK_EQUAL_RANGES`, `TEST_MESSAGE`, `WARN_MESSAGE`).

The build system is set up to compile an executable called `test_bitcoin`
that runs all of the unit tests. The main source file for the test library is found in
Expand All @@ -22,27 +23,26 @@ and tests weren't explicitly disabled.
The unit tests can be run with `ctest --test-dir build`, which includes unit
tests from subtrees.

Run `build/bin/test_bitcoin --list_content` for the full list of tests.
Run `build/bin/test_bitcoin --list` for the full list of tests.

To run the unit tests manually, launch `build/bin/test_bitcoin`. To recompile
after a test file was modified, run `cmake --build build` and then run the test again. If you
modify a non-test file, use `cmake --build build --target test_bitcoin` to recompile only what's needed
to run the unit tests.

To add more unit tests, add `BOOST_AUTO_TEST_CASE` functions to the existing
.cpp files in the `test/` directory or add new .cpp files that
implement new `BOOST_AUTO_TEST_SUITE` sections.
To add more unit tests, add `TEST_CASE` (or `FIXTURE_TEST_CASE`) functions to
the existing .cpp files in the `test/` directory, or add new .cpp files that
declare a new suite with `TEST_SUITE_BEGIN("<name>")`.

### Running individual tests

The `test_bitcoin` runner accepts command line arguments from the Boost
framework. To see the list of arguments that may be passed, run:
To see the list of arguments that may be passed, run:

```
build/bin/test_bitcoin --help
```

For example, to run only the tests in the `getarg_tests` file, with full logging:
For example, to run only the tests in the `getarg_tests` suite, with full logging:

```bash
build/bin/test_bitcoin --log_level=all --run_test=getarg_tests
Expand All @@ -54,13 +54,14 @@ or
build/bin/test_bitcoin -l all -t getarg_tests
```

or to run only the doubledash test in `getarg_tests`
or to run only the `doubledash` test in `getarg_tests`

```bash
build/bin/test_bitcoin --run_test=getarg_tests/doubledash
build/bin/test_bitcoin --run_test=getarg_tests::doubledash
```

The `--log_level=` (or `-l`) argument controls the verbosity of the test output.
Accepted values: `none`, `error` (default), `info`, `all`.

The `test_bitcoin` runner also accepts some of the command line arguments accepted by
`bitcoind`. Use `--` to separate these sets of arguments:
Expand Down Expand Up @@ -126,8 +127,10 @@ additionally use the `--output-on-failure` option to display logs of the failed
on failure. For running individual tests verbosely, refer to the section
[above](#running-individual-tests).

To write to logs from unit tests you need to use specific message methods
provided by Boost. The simplest is `BOOST_TEST_MESSAGE`.
To write a diagnostic message from a unit test, use `TEST_MESSAGE(...)`
(emitted at log level `info` or higher). `WARN_MESSAGE(cond, msg)` emits
a warning when `cond` is false without failing the test — use `CHECK` /
`REQUIRE` to fail.

For debugging you can launch the `test_bitcoin` executable with `gdb` or `lldb` and
start debugging, just like you would with any other program:
Expand All @@ -146,13 +149,9 @@ Another tool that can be used to resolve segmentation faults is
[valgrind](https://valgrind.org/).

If for whatever reason you want to produce a core dump file for this fault, you can do
that as well. By default, the boost test runner will intercept system errors and not
produce a core file. To bypass this, add `--catch_system_errors=no` to the
`test_bitcoin` arguments and ensure that your ulimits are set properly (e.g. `ulimit -c
unlimited`).

Running the tests and hitting a segmentation fault should now produce a file called `core`
(on Linux platforms, the file name will likely depend on the contents of
that as well. Ensure that your ulimits are set properly (e.g. `ulimit -c unlimited`),
then running the tests and hitting a segmentation fault should produce a file called
`core` (on Linux platforms, the file name will likely depend on the contents of
`/proc/sys/kernel/core_pattern`).

You can then explore the core dump using
Expand Down
Loading
Loading