123 lines
4.1 KiB
C++
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
|