test: add missing coverage for CDBWrapper::Read() errors

CDBWrapper::Read() errors have no test coverage or documentation.

Currently, the function can return false when deserialization fails
(indistinguishable from a missing key) and throw dbwrapper_error
on an internal database error.

This commit introduces tests that pin both behaviors so we can work
on improvements in the next commits without worrying about introducing
a behavior change.
This commit is contained in:
furszy
2026-03-25 23:31:29 -04:00
parent 1a1f584360
commit f78834fac9

View File

@@ -9,6 +9,7 @@
#include <uint256.h>
#include <util/string.h>
#include <fstream>
#include <memory>
#include <ranges>
@@ -186,6 +187,60 @@ BOOST_AUTO_TEST_CASE(dbwrapper_batch)
}
}
// Verify that Read() returns false (without throwing) when the stored value
// fails to be deserialized
BOOST_AUTO_TEST_CASE(dbwrapper_read_returns_false_on_deserialization_error)
{
for (const bool obfuscate : {false, true}) {
const fs::path path{m_args.GetDataDirBase() / (obfuscate ? "dbwrapper_deser_obf" : "dbwrapper_deser_noobf")};
CDBWrapper dbw({.path = path, .cache_bytes = 1 << 20, .wipe_data = true, .obfuscate = obfuscate});
constexpr uint8_t key{'X'};
// Write a single byte. uint256 requires 32 bytes, so reading this key
// as uint256 must trigger a deserialization error inside Read()
dbw.Write(key, uint8_t{0xFF});
BOOST_CHECK(dbw.Exists(key));
// Read() must catch the deserialization exception and return false,
// the same as if the key were absent
uint256 result;
BOOST_CHECK(!dbw.Read(key, result));
}
}
// Verify Read() throws dbwrapper_error due to an internal db error
BOOST_AUTO_TEST_CASE(dbwrapper_read_throws_on_db_error)
{
const fs::path path{m_args.GetDataDirBase() / "dbwrapper_db_error"};
constexpr uint8_t key{'Y'};
const auto make_db = [] (const fs::path& path, const bool force_compact) {
return CDBWrapper({.path = path, .cache_bytes = 1 << 20, .obfuscate = false,
.options = {.force_compact = force_compact}});
};
// Write a value and close the database
make_db(path, /*force_compact=*/false).Write(key, m_rng.rand256());
// Force compaction to ensure the data is written into the .ldb files
// rather than left in the WAL.
(void)make_db(path, /*force_compact=*/true);
// Corrupt every table so any subsequent Read() fails
for (const auto& entry : fs::directory_iterator(path)) {
if (entry.path().extension() == ".ldb") {
std::ofstream{entry.path(), std::ios::binary | std::ios::trunc}
.write("\xff", 1);
}
}
// Read() should detect the issue now and throw
const auto db{make_db(path, /*force_compact=*/false)};
uint256 result;
BOOST_CHECK_EXCEPTION(db.Read(key, result), dbwrapper_error, HasReason("Fatal LevelDB error"));
}
BOOST_AUTO_TEST_CASE(dbwrapper_iterator)
{
// Perform tests both obfuscated and non-obfuscated.