149 lines
6.7 KiB
C++
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;
|
|
}
|
|
}
|