cmvr-es/cmvr-es/manager/safety_manager/tests/command_ledger_test.cpp

123 lines
4.1 KiB
C++

#include "manager/safety_manager/include/command_ledger.h"
#include <atomic>
#include <chrono>
#include <thread>
#include <vector>
#include <gtest/gtest.h>
namespace cmvr::safety {
namespace {
CommandKey key(const std::string& id)
{
return {"anonymous", id};
}
CommandOutcome completedOutcome()
{
CommandOutcome outcome;
outcome.lifecycle = CommandLifecycle::Completed;
outcome.reason = SafetyReason::None;
outcome.serialized_response = "done";
return outcome;
}
TEST(CommandLedgerTest, SameIdJoinsAndDifferentPayloadConflicts)
{
CommandLedger ledger;
const auto first = ledger.reserve(key("command-1"), "payload-a");
ASSERT_EQ(first.status, CommandReservationStatus::AcceptedNew);
EXPECT_EQ(
ledger.reserve(key("command-1"), "payload-a").status,
CommandReservationStatus::JoinedInFlight);
EXPECT_EQ(
ledger.reserve(key("command-1"), "payload-b").status,
CommandReservationStatus::CommandIdConflict);
ASSERT_TRUE(ledger.complete(first.ticket, completedOutcome()));
const auto cached = ledger.reserve(key("command-1"), "payload-a");
ASSERT_EQ(cached.status, CommandReservationStatus::CachedResult);
ASSERT_TRUE(cached.cached_outcome.has_value());
EXPECT_EQ(cached.cached_outcome->serialized_response, "done");
}
TEST(CommandLedgerTest, ConcurrentCallersNeverCreateASecondReservation)
{
CommandLedger ledger;
std::atomic<int> accepted{0};
std::vector<std::thread> workers;
for (int index = 0; index < 16; ++index) {
workers.emplace_back([&] {
const auto result = ledger.reserve(key("shared"), "payload");
if (result.status == CommandReservationStatus::AcceptedNew) {
accepted.fetch_add(1);
}
});
}
for (auto& worker : workers) {
worker.join();
}
EXPECT_EQ(accepted.load(), 1);
EXPECT_EQ(ledger.acceptedIdCount(), 1U);
}
TEST(CommandLedgerTest, OutcomeUnknownIsCachedAndNeverReservedAgain)
{
CommandLedger ledger;
const auto first = ledger.reserve(key("uncertain"), "payload");
ASSERT_EQ(first.status, CommandReservationStatus::AcceptedNew);
CommandOutcome outcome;
outcome.lifecycle = CommandLifecycle::OutcomeUnknown;
outcome.reason = SafetyReason::OutcomeUnknown;
outcome.hardware_submission_possible = true;
ASSERT_TRUE(ledger.complete(first.ticket, outcome));
const auto retry = ledger.reserve(key("uncertain"), "payload");
ASSERT_EQ(retry.status, CommandReservationStatus::CachedResult);
ASSERT_TRUE(retry.cached_outcome.has_value());
EXPECT_EQ(
retry.cached_outcome->lifecycle, CommandLifecycle::OutcomeUnknown);
EXPECT_TRUE(retry.cached_outcome->hardware_submission_possible);
}
TEST(CommandLedgerTest, EvictedResultLeavesAnExactTombstone)
{
CommandLedger ledger({1, 3});
auto first = ledger.reserve(key("first"), "payload-1");
ASSERT_TRUE(ledger.complete(first.ticket, completedOutcome()));
auto second = ledger.reserve(key("second"), "payload-2");
ASSERT_TRUE(ledger.complete(second.ticket, completedOutcome()));
EXPECT_EQ(
ledger.reserve(key("first"), "payload-1").status,
CommandReservationStatus::ResultEvicted);
EXPECT_EQ(
ledger.reserve(key("first"), "different").status,
CommandReservationStatus::CommandIdConflict);
auto third = ledger.reserve(key("third"), "payload-3");
ASSERT_EQ(third.status, CommandReservationStatus::AcceptedNew);
EXPECT_EQ(
ledger.reserve(key("fourth"), "payload-4").status,
CommandReservationStatus::LedgerExhausted);
}
TEST(CommandLedgerTest, WaitTimesOutWithoutChangingExecution)
{
CommandLedger ledger;
const auto reservation = ledger.reserve(key("slow"), "payload");
ASSERT_EQ(reservation.status, CommandReservationStatus::AcceptedNew);
EXPECT_FALSE(ledger.wait(
reservation.ticket,
SafetyClock::now() + std::chrono::milliseconds(5)).has_value());
EXPECT_EQ(
ledger.reserve(key("slow"), "payload").status,
CommandReservationStatus::JoinedInFlight);
}
} // namespace
} // namespace cmvr::safety