Some checks failed
Fuzz Smoke / libFuzzer smoke (push) Has been cancelled
* chore(warnings): default-initialize aggregate members left out of brace-init
GCC -Wmissing-field-initializers fired about 1300 times where tests and
src build structs with a short positional brace list. Give the omitted
members an empty default member initializer. Value-initialization from
{} is what aggregate init already did for them, so behavior is
unchanged.
Signed-off-by: trvon <me@trevon.dev>
* chore(warnings): remove or annotate unused parameters, functions and variables
- init_command, s3_signer test, request_handler_lifetime test: comment out
the unused parameter name.
- video_content_handler: path is only read under YAMS_HAVE_FFPROBE.
- vector_memory_scaling_bench: main() takes no arguments.
- daemon_status_render: delete unused neutralText helper.
- DaemonMetrics: mallocStackLoggingNoCompactEnabled is only called in the
__APPLE__ block, so define it there.
- SocketServer: einval_streak only matters in the __APPLE__ EINVAL path.
- onnx_plugin_harness test, vector_backend_engine_compare bench,
work_coordinator test: mark platform-conditional or busy-loop
variables [[maybe_unused]].
Signed-off-by: trvon <git@trevon.dev>
* chore(warnings): fix sign-compare, range-loop, nodiscard and dangling-ref in tests
- search_admission_controller: count started workers in std::uint32_t so
the comparison with the uint32 cap is same-signed.
- p2p_manager: iterate the invalid-host list by const reference.
- integrity_verifier: discard the nodiscard results the tests do not
inspect with static_cast<void>.
- post_ingest_queue: findNode/findEdge take std::string_view so no
temporary std::string is created for the call (GCC
-Wdangling-reference false positive on the returned reference).
- topology_ablation_bench: the doc comment ended a line with a backslash,
which continues the comment (-Wcomment); show the env export on its own
line.
Signed-off-by: trvon <git@trevon.dev>
* chore(warnings): drop redundant std::move of a const captured json
The coroutine lambda is not mutable, so the captured optional<json> is
const and std::move(*progressToken) already selected the copy assignment.
Drop the std::move so the code states what actually happens.
Signed-off-by: trvon <git@trevon.dev>
* chore(warnings): mark __int128 use in db_integrity_stamp as an extension
-Wpedantic flags __int128. Use a __extension__ typedef, the GCC/Clang
idiom for an intentional extension, instead of the bare keyword.
Signed-off-by: trvon <git@trevon.dev>
* chore(warnings): remove always-true unsigned >= 0 comparisons
GCC -Wtype-limits flagged 51 test assertions of the form
CHECK(unsignedValue >= 0) that can never fail, plus a dead
`totalCount.value() < 0` clause in the graph listByType handler (the
count is std::size_t). In the tests keep the evaluation as a
static_cast<void> so the accessors are still exercised; in the handler
drop the dead clause and keep the range check.
Signed-off-by: trvon <me@trevon.dev>
---------
Signed-off-by: trvon <me@trevon.dev>
Signed-off-by: trvon <git@trevon.dev>
361 lines
13 KiB
C++
361 lines
13 KiB
C++
#include <catch2/catch_test_macros.hpp>
|
|
|
|
#include "../../common/test_helpers_catch2.h"
|
|
#include <yams/crypto/hasher.h>
|
|
#include <yams/integrity/verifier.h>
|
|
#include <yams/storage/reference_counter.h>
|
|
#include <yams/storage/storage_engine.h>
|
|
|
|
#include <chrono>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <span>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using namespace yams;
|
|
using namespace yams::integrity;
|
|
using namespace yams::storage;
|
|
|
|
namespace {
|
|
|
|
struct IntegrityVerifierFixture {
|
|
yams::test::TempDirGuard tempDirGuard{"yams_integrity_verifier_"};
|
|
std::filesystem::path testDir{tempDirGuard.path()};
|
|
std::unique_ptr<StorageEngine> storageEngine;
|
|
std::unique_ptr<ReferenceCounter> refCounter;
|
|
std::unique_ptr<IntegrityVerifier> verifier;
|
|
std::unique_ptr<crypto::IContentHasher> hasher;
|
|
std::vector<std::byte> testDataBytes;
|
|
std::string testDataString;
|
|
std::string testHash;
|
|
|
|
IntegrityVerifierFixture() {
|
|
StorageConfig config{
|
|
.basePath = testDir, .shardDepth = 2, .mutexPoolSize = 128, .enableCompression = false};
|
|
storageEngine = std::make_unique<StorageEngine>(std::move(config));
|
|
|
|
ReferenceCounter::Config refConfig;
|
|
refConfig.databasePath = testDir / "refs.db";
|
|
refCounter = std::make_unique<ReferenceCounter>(refConfig);
|
|
|
|
VerificationConfig verifyConfig;
|
|
verifyConfig.maxConcurrentVerifications = 2;
|
|
verifyConfig.blocksPerSecond = 100;
|
|
verifyConfig.enableAutoRepair = false;
|
|
|
|
verifier = std::make_unique<IntegrityVerifier>(*storageEngine, *refCounter, verifyConfig);
|
|
|
|
testDataBytes = yams::test::TestVectors::getHelloWorldData();
|
|
testDataString = "Hello World";
|
|
|
|
hasher = crypto::createSHA256Hasher();
|
|
testHash = hasher->hash(testDataBytes);
|
|
}
|
|
|
|
~IntegrityVerifierFixture() {
|
|
if (verifier && verifier->isRunning()) {
|
|
static_cast<void>(verifier->stopBackgroundVerification());
|
|
}
|
|
verifier.reset();
|
|
refCounter.reset();
|
|
storageEngine.reset();
|
|
}
|
|
|
|
IntegrityVerifierFixture(const IntegrityVerifierFixture&) = delete;
|
|
IntegrityVerifierFixture& operator=(const IntegrityVerifierFixture&) = delete;
|
|
|
|
std::string storeBlock(const std::vector<std::byte>& data) {
|
|
auto dataHash = hasher->hash(data);
|
|
auto storeResult =
|
|
storageEngine->store(dataHash, std::span<const std::byte>(data.data(), data.size()));
|
|
REQUIRE(storeResult.has_value());
|
|
|
|
auto refResult = refCounter->increment(dataHash, data.size());
|
|
REQUIRE(refResult.has_value());
|
|
return dataHash;
|
|
}
|
|
|
|
void storeTestBlock() { storeBlock(testDataBytes); }
|
|
|
|
void corruptTestBlock() {
|
|
// Mirror StorageEngine::getObjectPath sharding (shardDepth=2).
|
|
auto blockPath = testDir / "objects" / testHash.substr(0, 2) / testHash.substr(2);
|
|
if (std::filesystem::exists(blockPath)) {
|
|
std::ofstream file(blockPath, std::ios::binary | std::ios::out);
|
|
if (file.is_open()) {
|
|
std::string corruptData = "CORRUPTED DATA";
|
|
file.write(corruptData.data(),
|
|
yams::test::checked_streamsize(corruptData.size(),
|
|
"integrity corrupt fixture"));
|
|
file.close();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "ConstructorAndDestructor", "[integrity][verifier]") {
|
|
CHECK_FALSE(verifier->isRunning());
|
|
CHECK_FALSE(verifier->isPaused());
|
|
|
|
const auto& stats = verifier->getStatistics();
|
|
CHECK(stats.blocksVerifiedTotal.load() == 0);
|
|
CHECK(stats.verificationErrorsTotal.load() == 0);
|
|
}
|
|
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "VerifyValidBlock", "[integrity][verifier]") {
|
|
storeTestBlock();
|
|
|
|
auto result = verifier->verifyBlock(testHash);
|
|
|
|
CHECK(result.status == VerificationStatus::Passed);
|
|
CHECK(result.blockHash == testHash);
|
|
CHECK(result.blockSize == testDataBytes.size());
|
|
CHECK(result.errorDetails.empty());
|
|
CHECK(result.isSuccess());
|
|
|
|
const auto& stats = verifier->getStatistics();
|
|
CHECK(stats.blocksVerifiedTotal.load() == 1);
|
|
CHECK(stats.verificationErrorsTotal.load() == 0);
|
|
CHECK(stats.bytesVerifiedTotal.load() == testDataBytes.size());
|
|
}
|
|
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "VerifyMissingBlock", "[integrity][verifier]") {
|
|
std::string missingHash = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
|
|
auto result = verifier->verifyBlock(missingHash);
|
|
|
|
CHECK(result.status == VerificationStatus::Missing);
|
|
CHECK(result.blockHash == missingHash);
|
|
CHECK(result.blockSize == 0);
|
|
CHECK_FALSE(result.errorDetails.empty());
|
|
CHECK_FALSE(result.isSuccess());
|
|
|
|
const auto& stats = verifier->getStatistics();
|
|
CHECK(stats.blocksVerifiedTotal.load() == 0);
|
|
CHECK(stats.verificationErrorsTotal.load() == 1);
|
|
}
|
|
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "VerifyCorruptedBlock", "[integrity][verifier]") {
|
|
storeTestBlock();
|
|
corruptTestBlock();
|
|
|
|
auto result = verifier->verifyBlock(testHash);
|
|
|
|
CHECK(result.status == VerificationStatus::Corrupted);
|
|
CHECK(result.blockHash == testHash);
|
|
CHECK(result.blockSize > 0);
|
|
CHECK_FALSE(result.errorDetails.empty());
|
|
CHECK_FALSE(result.isSuccess());
|
|
|
|
const auto& stats = verifier->getStatistics();
|
|
CHECK(stats.blocksVerifiedTotal.load() == 0);
|
|
CHECK(stats.verificationErrorsTotal.load() == 1);
|
|
}
|
|
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "VerifyMultipleBlocks", "[integrity][verifier]") {
|
|
std::vector<std::vector<std::byte>> testBlocks = {yams::test::TestVectors::getABCData(),
|
|
yams::test::TestVectors::getHelloWorldData()};
|
|
|
|
std::vector<std::string> hashes;
|
|
hashes.reserve(testBlocks.size());
|
|
for (const auto& data : testBlocks) {
|
|
hashes.push_back(storeBlock(data));
|
|
}
|
|
|
|
auto results = verifier->verifyBlocks(hashes);
|
|
|
|
REQUIRE(results.size() == testBlocks.size());
|
|
|
|
for (size_t i = 0; i < results.size(); ++i) {
|
|
CHECK(results[i].status == VerificationStatus::Passed);
|
|
CHECK(results[i].blockHash == hashes[i]);
|
|
CHECK(results[i].isSuccess());
|
|
}
|
|
|
|
const auto& stats = verifier->getStatistics();
|
|
CHECK(stats.blocksVerifiedTotal.load() == testBlocks.size());
|
|
CHECK(stats.verificationErrorsTotal.load() == 0);
|
|
}
|
|
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "BackgroundVerificationLifecycle",
|
|
"[integrity][verifier]") {
|
|
CHECK_FALSE(verifier->isRunning());
|
|
|
|
auto startResult = verifier->startBackgroundVerification();
|
|
CHECK(startResult.has_value());
|
|
CHECK(verifier->isRunning());
|
|
CHECK_FALSE(verifier->isPaused());
|
|
|
|
auto startAgainResult = verifier->startBackgroundVerification();
|
|
CHECK_FALSE(startAgainResult.has_value());
|
|
CHECK(startAgainResult.error() == ErrorCode::OperationInProgress);
|
|
|
|
auto stopResult = verifier->stopBackgroundVerification();
|
|
CHECK(stopResult.has_value());
|
|
CHECK_FALSE(verifier->isRunning());
|
|
|
|
auto stopAgainResult = verifier->stopBackgroundVerification();
|
|
CHECK(stopAgainResult.has_value());
|
|
}
|
|
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "PauseResumeVerification", "[integrity][verifier]") {
|
|
auto startResult = verifier->startBackgroundVerification();
|
|
CHECK(startResult.has_value());
|
|
CHECK(verifier->isRunning());
|
|
CHECK_FALSE(verifier->isPaused());
|
|
|
|
auto pauseResult = verifier->pauseVerification();
|
|
CHECK(pauseResult.has_value());
|
|
CHECK(verifier->isRunning());
|
|
CHECK(verifier->isPaused());
|
|
|
|
auto resumeResult = verifier->resumeVerification();
|
|
CHECK(resumeResult.has_value());
|
|
CHECK(verifier->isRunning());
|
|
CHECK_FALSE(verifier->isPaused());
|
|
|
|
static_cast<void>(verifier->stopBackgroundVerification());
|
|
|
|
auto pauseFailResult = verifier->pauseVerification();
|
|
CHECK_FALSE(pauseFailResult.has_value());
|
|
CHECK(pauseFailResult.error() == ErrorCode::InvalidOperation);
|
|
|
|
auto resumeFailResult = verifier->resumeVerification();
|
|
CHECK_FALSE(resumeFailResult.has_value());
|
|
CHECK(resumeFailResult.error() == ErrorCode::InvalidOperation);
|
|
}
|
|
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "StatisticsAndReporting", "[integrity][verifier]") {
|
|
storeTestBlock();
|
|
|
|
const auto& initialStats = verifier->getStatistics();
|
|
CHECK(initialStats.blocksVerifiedTotal.load() == 0);
|
|
|
|
static_cast<void>(verifier->verifyBlock(testHash));
|
|
|
|
const auto& stats = verifier->getStatistics();
|
|
CHECK(stats.blocksVerifiedTotal.load() == 1);
|
|
CHECK(stats.totalVerificationTime.count() >= 0);
|
|
CHECK(stats.getVerificationRate() >= 0.0);
|
|
|
|
auto report = verifier->generateReport(std::chrono::hours{1});
|
|
CHECK(report.blocksVerified == 1);
|
|
CHECK(report.blocksPassed == 1);
|
|
CHECK(report.blocksFailed == 0);
|
|
CHECK(report.totalBytes > 0);
|
|
CHECK(report.getSuccessRate() > 0.99);
|
|
CHECK(report.getErrorRate() < 0.01);
|
|
|
|
verifier->resetStatistics();
|
|
|
|
const auto& resetStats = verifier->getStatistics();
|
|
CHECK(resetStats.blocksVerifiedTotal.load() == 0);
|
|
CHECK(resetStats.verificationErrorsTotal.load() == 0);
|
|
}
|
|
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "RecentFailures", "[integrity][verifier]") {
|
|
storeTestBlock();
|
|
corruptTestBlock();
|
|
|
|
static_cast<void>(verifier->verifyBlock(testHash));
|
|
|
|
auto failures = verifier->getRecentFailures(10);
|
|
REQUIRE(failures.size() == 1);
|
|
CHECK(failures[0].status == VerificationStatus::Corrupted);
|
|
CHECK(failures[0].blockHash == testHash);
|
|
CHECK_FALSE(failures[0].isSuccess());
|
|
}
|
|
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "ConfigurationUpdate", "[integrity][verifier]") {
|
|
VerificationConfig newConfig;
|
|
newConfig.maxConcurrentVerifications = 8;
|
|
newConfig.blocksPerSecond = 500;
|
|
newConfig.enableAutoRepair = true;
|
|
|
|
CHECK_NOTHROW(verifier->updateConfig(newConfig));
|
|
}
|
|
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "CallbackFunctionality", "[integrity][verifier]") {
|
|
storeTestBlock();
|
|
|
|
bool progressCallbackCalled = false;
|
|
VerificationResult capturedResult;
|
|
|
|
verifier->setProgressCallback([&](const VerificationResult& result) {
|
|
progressCallbackCalled = true;
|
|
capturedResult = result;
|
|
});
|
|
|
|
bool alertCallbackCalled = false;
|
|
verifier->setAlertCallback(
|
|
[&](const IntegrityReport& /*report*/) { alertCallbackCalled = true; });
|
|
|
|
static_cast<void>(verifier->verifyBlock(testHash));
|
|
|
|
CHECK(progressCallbackCalled);
|
|
CHECK(capturedResult.blockHash == testHash);
|
|
CHECK(capturedResult.status == VerificationStatus::Passed);
|
|
(void)alertCallbackCalled;
|
|
}
|
|
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "SchedulingStrategyChange", "[integrity][verifier]") {
|
|
CHECK_NOTHROW(verifier->setSchedulingStrategy(SchedulingStrategy::ByAge));
|
|
CHECK_NOTHROW(verifier->setSchedulingStrategy(SchedulingStrategy::BySize));
|
|
CHECK_NOTHROW(verifier->setSchedulingStrategy(SchedulingStrategy::ByFailures));
|
|
CHECK_NOTHROW(verifier->setSchedulingStrategy(SchedulingStrategy::ByAccess));
|
|
CHECK_NOTHROW(verifier->setSchedulingStrategy(SchedulingStrategy::Balanced));
|
|
}
|
|
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "VerifyAllOperation", "[integrity][verifier]") {
|
|
storeTestBlock();
|
|
|
|
auto report = verifier->verifyAll();
|
|
|
|
CHECK(report.generatedAt.time_since_epoch().count() >= 0);
|
|
CHECK(report.duration.count() >= 0);
|
|
}
|
|
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "VerifyEmptyHash", "[integrity][verifier]") {
|
|
auto result = verifier->verifyBlock("");
|
|
|
|
CHECK(result.status != VerificationStatus::Passed);
|
|
CHECK_FALSE(result.isSuccess());
|
|
}
|
|
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "VerifyInvalidHash", "[integrity][verifier]") {
|
|
auto result = verifier->verifyBlock("invalid_hash");
|
|
|
|
CHECK(result.status != VerificationStatus::Passed);
|
|
CHECK_FALSE(result.isSuccess());
|
|
}
|
|
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "MoveConstructorAndAssignment",
|
|
"[integrity][verifier]") {
|
|
VerificationConfig config;
|
|
auto verifier1 = std::make_unique<IntegrityVerifier>(*storageEngine, *refCounter, config);
|
|
|
|
auto verifier2 = std::move(*verifier1);
|
|
|
|
IntegrityVerifier verifier3(*storageEngine, *refCounter, config);
|
|
verifier3 = std::move(verifier2);
|
|
|
|
CHECK_FALSE(verifier3.isRunning());
|
|
}
|
|
|
|
// Regression: the pool used to set its stop flag without holding the queue mutex, so a worker
|
|
// between the wait predicate check and blocking could miss the shutdown notify and join()
|
|
// hung forever (seen as sporadic integrity_catch2 timeouts under parallel load). Churn
|
|
// construction/destruction so a reintroduced lost wakeup shows up as a hang/timeout here.
|
|
TEST_CASE_METHOD(IntegrityVerifierFixture, "Verifier shutdown does not lose the stop wakeup",
|
|
"[integrity][verifier][shutdown]") {
|
|
VerificationConfig config;
|
|
config.maxConcurrentVerifications = 8;
|
|
for (int i = 0; i < 400; ++i) {
|
|
IntegrityVerifier churn(*storageEngine, *refCounter, config);
|
|
CHECK_FALSE(churn.isRunning());
|
|
}
|
|
}
|