7.3 KiB
Doctest Migration - Final Status
Overview
This document summarizes the migration of rippled unit tests from the Beast Unit Test framework to doctest. The migration follows a hybrid approach: standalone unit tests are migrated to doctest, while integration tests remain in the Beast framework.
Migration Complete ✅
Status: Successfully migrated 11 unit test files Result: 81 test cases, 17,397 assertions - ALL PASSING
What Was Migrated
Successfully Migrated to Doctest
Located in src/doctest/:
Basics Tests (5 files, 36 test cases, 1,365 assertions)
- Buffer_test.cpp
- Expected_test.cpp
- IOUAmount_test.cpp
- Number_test.cpp
- XRPAmount_test.cpp
Protocol Tests (7 files, 37 test cases, 16,020 assertions)
- ApiVersion_test.cpp
- BuildInfo_test.cpp
- STAccount_test.cpp
- STInteger_test.cpp
- STNumber_test.cpp
- SecretKey_test.cpp
- Seed_test.cpp
JSON Tests (1 file, 8 test cases, 12 assertions)
- Object_test.cpp
Kept in Beast Framework
Located in src/test/:
- All integration tests (app, rpc, consensus, core, csf, jtx modules)
- Tests requiring test infrastructure (Env, Config, Ledger setup)
- Multi-component interaction tests
Key Challenges & Solutions
1. Namespace Migration (ripple → xrpl)
Problem: Many types moved from ripple to xrpl namespace.
Solution: Add using declarations at global scope:
using xrpl::Buffer;
using xrpl::IOUAmount;
using xrpl::STUInt32;
2. Nested Namespaces
Problem: RPC namespace nested inside xrpl (not ripple).
Solution: Use full qualification or namespace alias:
// Option 1: Full qualification
xrpl::RPC::apiMinimumSupportedVersion
// Option 2: Namespace alias
namespace BuildInfo = xrpl::BuildInfo;
3. CHECK Macro Differences
Problem: Beast's BEAST_EXPECT returns a boolean; doctest's CHECK doesn't.
Solution: Replace conditional patterns:
// Before (Beast):
if (CHECK(parsed)) { /* use parsed */ }
// After (Doctest):
auto parsed = parseBase58<AccountID>(s);
REQUIRE(parsed); // Stops if fails
// use parsed
4. Exception Testing
Problem: Beast used try-catch blocks explicitly.
Solution: Use doctest macros:
// Before (Beast):
try {
auto _ = func();
BEAST_EXPECT(false);
} catch (std::runtime_error const& e) {
BEAST_EXPECT(e.what() == expected);
}
// After (Doctest):
CHECK_THROWS_AS(func(), std::runtime_error);
5. Test Organization
Problem: Beast used class methods for test organization.
Solution: Use TEST_CASE with SUBCASE:
TEST_CASE("STNumber_test") {
SUBCASE("Integer parsing") { /* tests */ }
SUBCASE("Decimal parsing") { /* tests */ }
SUBCASE("Error cases") { /* tests */ }
}
Migration Guidelines
When to Migrate to Doctest
✅ Good Candidates:
- Tests single class/function in isolation
- No dependencies on test/jtx or test/csf frameworks
- Pure logic/algorithm/data structure tests
- No Env, Config, or Ledger setup required
❌ Keep in Beast:
- Requires test/jtx utilities (Env, IOU, pay, etc.)
- Requires test/csf (consensus simulation)
- Multi-component integration tests
- End-to-end workflow tests
Migration Pattern
// 1. Include production headers first
#include <xrpl/protocol/STInteger.h>
#include <xrpl/protocol/LedgerFormats.h>
// 2. Include doctest
#include <doctest/doctest.h>
// 3. Add using declarations for xrpl types
using xrpl::STUInt32;
using xrpl::JsonOptions;
using xrpl::ltACCOUNT_ROOT;
// 4. Write tests in xrpl namespace (or ripple::test)
namespace xrpl {
TEST_CASE("Descriptive Test Name") {
SUBCASE("Specific scenario") {
// Setup
STUInt32 value(42);
// Test
CHECK(value.getValue() == 42);
CHECK(value.getSType() == STI_UINT32);
}
}
} // namespace xrpl
Doctest Best Practices Applied
All migrated tests follow official doctest best practices as documented in the doctest guidelines:
1. TEST_SUITE Organization
All test files are organized into suites for better filtering and organization:
TEST_SUITE_BEGIN("basics");
TEST_CASE("Buffer") { /* tests */ }
TEST_SUITE_END();
Benefits:
- Filter tests by suite:
./xrpl.test.protocol --test-suite=protocol - Better organization and documentation
- Clearer test structure
2. CHECK_FALSE for Readability
Replaced CHECK(!(expression)) with more readable CHECK_FALSE(expression):
// Before:
CHECK(!buffer.empty());
// After:
CHECK_FALSE(buffer.empty());
3. CAPTURE Macros in Loops
Added CAPTURE macros in loops for better failure diagnostics:
for (std::size_t i = 0; i < 16; ++i) {
CAPTURE(i); // Shows value of i when test fails
test(buffer, i);
}
Note: Files with many loops (Number, XRPAmount, SecretKey, Seed) have the essential TEST_SUITE organization. CAPTURE macros can be added incrementally for enhanced diagnostics.
4. REQUIRE for Critical Preconditions
Use REQUIRE when subsequent code depends on the assertion being true:
auto parsed = parseBase58<AccountID>(s);
REQUIRE(parsed); // Stops test if parsing fails
CHECK(toBase58(*parsed) == s); // Safe to dereference
Build & Run
Build
cd .build
# Build all doctest tests
cmake --build . --target xrpl.doctest.tests
# Build individual modules
cmake --build . --target xrpl.test.basics
cmake --build . --target xrpl.test.protocol
cmake --build . --target xrpl.test.json
Run
# Run all tests
./src/doctest/xrpl.test.basics
./src/doctest/xrpl.test.protocol
./src/doctest/xrpl.test.json
# Run with options
./src/doctest/xrpl.test.basics --list-test-cases
./src/doctest/xrpl.test.protocol --success
# Filter by test suite
./src/doctest/xrpl.test.basics --test-suite=basics
./src/doctest/xrpl.test.protocol --test-suite=protocol
./src/doctest/xrpl.test.json --test-suite=JsonObject
Benefits of Hybrid Approach
- ✅ Fast compilation: Doctest is header-only and very lightweight
- ✅ Simple unit tests: No framework overhead for simple tests
- ✅ Keep integration tests: Complex test infrastructure remains intact
- ✅ Both frameworks work: No conflicts between Beast and doctest
- ✅ Clear separation: Unit tests vs integration tests
Statistics
Before Migration
- 281 test files in Beast framework
- Mix of unit and integration tests
- All in
src/test/
After Migration
- 11 unit test files migrated to doctest (
src/doctest/) - ~270 integration test files remain in Beast (
src/test/) - Both frameworks coexist successfully
Future Work
Additional unit tests can be migrated using the established patterns:
- More protocol tests (Serializer, PublicKey, Quality, Issue, MultiApiJson, TER, SeqProxy)
- More basics tests (StringUtilities, base58, base_uint, join, KeyCache, TaggedCache, hardened_hash)
- Other standalone unit tests identified in the codebase
References
Last Updated: December 12, 2024 Status: Migration Complete & Production Ready