Files
ota-reference-design/examples/simulations/interrupted-installation/tests.cpp
T
2026-10-20 12:27:04 -04:00

149 lines
6.7 KiB
C++

#include "simulation.hpp"
#include <iostream>
#include <stdexcept>
namespace model = ota::installation;
namespace {
void expect(bool condition, const char *message) {
if (!condition) throw std::runtime_error(message);
}
template<class Operation>
void expect_rejected(Operation operation) {
try {
operation();
} catch (const std::logic_error &) {
return;
}
throw std::runtime_error("Unsafe operation accepted");
}
void cut_points() {
for (int cut = 0; cut <= model::installation_steps; ++cut) {
model::Storage storage;
{
model::Installer installer(storage);
for (int step = 0; step < cut; ++step) model::perform_step(installer, step);
}
model::Installer restarted(storage);
const auto action = restarted.recover();
expect(storage.a_contents == "release-1", "A changed during installation");
if (cut < 3) {
expect(action == model::RecoveryAction::RestartPreparation, "Incomplete B accepted");
expect(storage.boot == model::BootRecord::ASelected, "Incomplete B selected");
expect_rejected([&] { restarted.request_trial(); });
// Exercise the documented restart path, not only its decision.
for (int step = 0; step < model::installation_steps; ++step) model::perform_step(restarted, step);
} else if (cut < 6) {
expect(action == model::RecoveryAction::ReadyToActivate, "Complete B not reverified");
expect(storage.boot == model::BootRecord::ASelected, "Recovery activated B without policy");
restarted.request_trial();
restarted.save_requested();
} else {
expect(action == model::RecoveryAction::PreserveTrial, "Saved trial lost");
expect(storage.progress == model::Progress::TrialRequested, "Old progress not repaired");
}
expect(storage.boot == model::BootRecord::BTrial, "Continuation did not reach trial");
expect(storage.b_contents == model::expected_contents(), "Selected B is incomplete");
expect(storage.a_contents == "release-1", "Continuation changed A");
storage.attempts_left = 1;// Boot control has already consumed an attempt.
model::Installer second_restart(storage);
expect(second_restart.recover() == model::RecoveryAction::PreserveTrial, "Repeated recovery lost trial");
expect(storage.attempts_left == 1, "Recovery renewed trial attempts");
expect_rejected([&] { second_restart.begin(); });
expect_rejected([&] { second_restart.write_partial(); });
expect_rejected([&] { second_restart.finish_write(); });
expect_rejected([&] { second_restart.request_trial(); });
}
}
void activation_guards() {
model::Storage storage;
model::Installer installer(storage);
expect_rejected([&] { installer.finish_write(); });
expect_rejected([&] { installer.save_requested(); });
installer.begin();
installer.finish_write();
expect_rejected([&] { installer.save_prepared(); });
installer.verify();
expect_rejected([&] { installer.request_trial(); });
installer.save_prepared();
model::Installer restarted(storage);
expect_rejected([&] { restarted.request_trial(); });
expect(restarted.recover() == model::RecoveryAction::ReadyToActivate, "Fresh verification failed");
restarted.request_trial();
}
void bad_contents_and_identity() {
for (bool wrong_release: {false, true}) {
model::Storage storage;
{
model::Installer installer(storage);
for (int step = 0; step < 5; ++step) model::perform_step(installer, step);
}
if (wrong_release) storage.release = "another-release";
else
storage.b_contents = "damaged";
model::Installer restarted(storage);
expect(restarted.recover() == model::RecoveryAction::RestartPreparation, "Stale prepared record trusted");
try {
restarted.verify();
throw std::logic_error("Bad release passed verification");
} catch (const std::runtime_error &) {
// Expected content or identity failure; logic_error escapes.
}
expect_rejected([&] { restarted.request_trial(); });
expect(storage.boot == model::BootRecord::ASelected, "Bad release selected");
}
}
void interrupted_boot_write() {
for (const auto result: {model::WriteResult::OldRecord, model::WriteResult::NewRecord, model::WriteResult::InvalidRecord}) {
model::Storage storage;
{
model::Installer installer(storage);
for (int step = 0; step < 5; ++step) model::perform_step(installer, step);
bool failed = false;
try {
installer.request_trial(result);
} catch (const std::runtime_error &) {
failed = true;
}
expect(failed == (result != model::WriteResult::NewRecord), "Wrong request result");
}
model::Installer restarted(storage);
const auto action = restarted.recover();
if (result == model::WriteResult::OldRecord) {
expect(action == model::RecoveryAction::ReadyToActivate, "Old boot record not retained");
expect(storage.attempts_left == 0, "Old record partly changed");
} else if (result == model::WriteResult::NewRecord) {
expect(action == model::RecoveryAction::PreserveTrial, "New boot record not retained");
expect(storage.attempts_left == 2, "New trial limit lost");
} else {
expect(action == model::RecoveryAction::SeparateRecovery, "Invalid boot state accepted");
expect(storage.progress == model::Progress::Prepared, "Invalid boot state changed progress");
expect_rejected([&] { restarted.begin(); });
expect_rejected([&] { restarted.request_trial(); });
}
expect(storage.a_contents == "release-1", "Boot record failure changed A");
}
}
}// namespace
int main() {
try {
cut_points();
activation_guards();
bad_contents_and_identity();
interrupted_boot_write();
std::cout << "All installation recovery checks passed\n";
} catch (const std::exception &error) {
std::cerr << "Test failed: " << error.what() << '\n';
return 1;
}
}