211 lines
7.2 KiB
C++
211 lines
7.2 KiB
C++
#include "PersistenceWriter.h"
|
|
|
|
#include <condition_variable>
|
|
#include <chrono>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace
|
|
{
|
|
int gFailures = 0;
|
|
|
|
void Expect(bool condition, const char* message)
|
|
{
|
|
if (condition)
|
|
return;
|
|
|
|
std::cerr << "FAIL: " << message << "\n";
|
|
++gFailures;
|
|
}
|
|
|
|
PersistenceSnapshot MakeRuntimeSnapshot(const std::string& contents)
|
|
{
|
|
PersistenceSnapshot snapshot;
|
|
snapshot.targetKind = PersistenceTargetKind::RuntimeState;
|
|
snapshot.targetPath = std::filesystem::temp_directory_path() / "video-shader-persistence-writer-test.json";
|
|
snapshot.contents = contents;
|
|
snapshot.reason = "test";
|
|
snapshot.debounceKey = "runtime-state";
|
|
snapshot.debounceAllowed = true;
|
|
return snapshot;
|
|
}
|
|
|
|
void TestDebouncedRequestsCoalesceToNewestSnapshot()
|
|
{
|
|
std::mutex mutex;
|
|
std::vector<PersistenceSnapshot> writtenSnapshots;
|
|
PersistenceWriter writer(
|
|
std::chrono::milliseconds(1000),
|
|
[&](const PersistenceSnapshot& snapshot, std::string&) {
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
writtenSnapshots.push_back(snapshot);
|
|
return true;
|
|
});
|
|
|
|
std::string error;
|
|
Expect(writer.EnqueueSnapshot(MakeRuntimeSnapshot("first"), error), "first debounced snapshot enqueues");
|
|
Expect(writer.EnqueueSnapshot(MakeRuntimeSnapshot("second"), error), "second debounced snapshot enqueues");
|
|
|
|
PersistenceWriterMetrics metrics = writer.GetMetrics();
|
|
Expect(metrics.pendingCount == 1, "debounced snapshots share one pending slot");
|
|
Expect(metrics.enqueuedCount == 1, "first debounced snapshot counts as enqueue");
|
|
Expect(metrics.coalescedCount == 1, "second debounced snapshot counts as coalesced");
|
|
|
|
Expect(writer.StopAndFlush(std::chrono::seconds(1), error), "flush drains debounced snapshots");
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
Expect(writtenSnapshots.size() == 1, "flush writes one coalesced snapshot");
|
|
Expect(!writtenSnapshots.empty() && writtenSnapshots[0].contents == "second", "coalesced writer keeps newest snapshot");
|
|
}
|
|
|
|
metrics = writer.GetMetrics();
|
|
Expect(metrics.pendingCount == 0, "flush drains pending debounced snapshot");
|
|
Expect(metrics.writtenCount == 1, "flush records one successful write");
|
|
}
|
|
|
|
void TestImmediateRequestsAreNotCoalesced()
|
|
{
|
|
std::mutex mutex;
|
|
std::vector<PersistenceSnapshot> writtenSnapshots;
|
|
PersistenceWriter writer(
|
|
std::chrono::milliseconds(1000),
|
|
[&](const PersistenceSnapshot& snapshot, std::string&) {
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
writtenSnapshots.push_back(snapshot);
|
|
return true;
|
|
});
|
|
|
|
PersistenceSnapshot first = MakeRuntimeSnapshot("first");
|
|
first.debounceAllowed = false;
|
|
PersistenceSnapshot second = MakeRuntimeSnapshot("second");
|
|
second.debounceAllowed = false;
|
|
|
|
std::string error;
|
|
Expect(writer.EnqueueSnapshot(first, error), "first immediate snapshot enqueues");
|
|
Expect(writer.EnqueueSnapshot(second, error), "second immediate snapshot enqueues");
|
|
Expect(writer.StopAndFlush(std::chrono::seconds(1), error), "flush drains immediate snapshots");
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
Expect(writtenSnapshots.size() == 2, "immediate snapshots are written independently");
|
|
Expect(writtenSnapshots.size() == 2 && writtenSnapshots[0].contents == "first" && writtenSnapshots[1].contents == "second",
|
|
"immediate snapshots preserve order");
|
|
}
|
|
}
|
|
|
|
void TestWriteFailureReportsStructuredResult()
|
|
{
|
|
std::vector<PersistenceWriteResult> results;
|
|
PersistenceWriter writer(
|
|
std::chrono::milliseconds(1),
|
|
[](const PersistenceSnapshot&, std::string& error) {
|
|
error = "simulated failure";
|
|
return false;
|
|
});
|
|
writer.SetResultCallback([&results](const PersistenceWriteResult& result) {
|
|
results.push_back(result);
|
|
});
|
|
|
|
PersistenceSnapshot snapshot = MakeRuntimeSnapshot("payload");
|
|
snapshot.debounceAllowed = false;
|
|
snapshot.reason = "failure-test";
|
|
|
|
std::string error;
|
|
Expect(writer.EnqueueSnapshot(snapshot, error), "failing snapshot still enqueues");
|
|
Expect(writer.StopAndFlush(std::chrono::seconds(1), error), "flush reports failing snapshot result");
|
|
|
|
Expect(results.size() == 1, "writer reports one failure result");
|
|
Expect(!results.empty() && !results[0].succeeded, "writer result records failure");
|
|
Expect(!results.empty() && results[0].reason == "failure-test", "writer result preserves reason");
|
|
Expect(!results.empty() && results[0].errorMessage == "simulated failure", "writer result preserves error message");
|
|
Expect(!results.empty() && !results[0].newerRequestPending, "writer result reports no newer pending request");
|
|
Expect(writer.GetMetrics().failedCount == 1, "writer metrics count failed writes");
|
|
}
|
|
|
|
void TestShutdownFlushDrainsPendingSnapshotAndRejectsNewRequests()
|
|
{
|
|
std::mutex mutex;
|
|
std::vector<PersistenceSnapshot> writtenSnapshots;
|
|
PersistenceWriter writer(
|
|
std::chrono::milliseconds(1000),
|
|
[&](const PersistenceSnapshot& snapshot, std::string&) {
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
writtenSnapshots.push_back(snapshot);
|
|
return true;
|
|
});
|
|
|
|
std::string error;
|
|
Expect(writer.EnqueueSnapshot(MakeRuntimeSnapshot("pending"), error), "pending snapshot enqueues before shutdown");
|
|
Expect(writer.StopAndFlush(std::chrono::seconds(1), error), "bounded shutdown flush completes");
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
Expect(writtenSnapshots.size() == 1, "shutdown flush writes pending debounced snapshot");
|
|
Expect(!writtenSnapshots.empty() && writtenSnapshots[0].contents == "pending", "shutdown flush preserves pending snapshot contents");
|
|
}
|
|
|
|
Expect(!writer.EnqueueSnapshot(MakeRuntimeSnapshot("late"), error), "writer rejects requests after shutdown flush");
|
|
}
|
|
|
|
void TestShutdownFlushTimeoutCanBeRetried()
|
|
{
|
|
std::mutex mutex;
|
|
std::condition_variable condition;
|
|
bool sinkStarted = false;
|
|
bool releaseSink = false;
|
|
PersistenceWriter writer(
|
|
std::chrono::milliseconds(1),
|
|
[&](const PersistenceSnapshot&, std::string&) {
|
|
std::unique_lock<std::mutex> lock(mutex);
|
|
sinkStarted = true;
|
|
condition.notify_all();
|
|
condition.wait(lock, [&]() { return releaseSink; });
|
|
return true;
|
|
});
|
|
|
|
PersistenceSnapshot snapshot = MakeRuntimeSnapshot("slow");
|
|
snapshot.debounceAllowed = false;
|
|
|
|
std::string error;
|
|
Expect(writer.EnqueueSnapshot(snapshot, error), "slow snapshot enqueues");
|
|
{
|
|
std::unique_lock<std::mutex> lock(mutex);
|
|
Expect(condition.wait_for(lock, std::chrono::seconds(1), [&]() { return sinkStarted; }),
|
|
"slow sink starts before timeout test");
|
|
}
|
|
|
|
Expect(!writer.StopAndFlush(std::chrono::milliseconds(10), error), "bounded shutdown flush reports timeout");
|
|
Expect(error.find("Timed out") != std::string::npos, "shutdown timeout returns a useful error");
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
releaseSink = true;
|
|
}
|
|
condition.notify_all();
|
|
error.clear();
|
|
Expect(writer.StopAndFlush(std::chrono::seconds(1), error), "shutdown flush can complete after earlier timeout");
|
|
}
|
|
}
|
|
|
|
int main()
|
|
{
|
|
TestDebouncedRequestsCoalesceToNewestSnapshot();
|
|
TestImmediateRequestsAreNotCoalesced();
|
|
TestWriteFailureReportsStructuredResult();
|
|
TestShutdownFlushDrainsPendingSnapshotAndRejectsNewRequests();
|
|
TestShutdownFlushTimeoutCanBeRetried();
|
|
|
|
if (gFailures != 0)
|
|
{
|
|
std::cerr << gFailures << " persistence writer test(s) failed.\n";
|
|
return 1;
|
|
}
|
|
|
|
std::cout << "Persistence writer tests passed.\n";
|
|
return 0;
|
|
}
|