#include "simulation.hpp" #include #include namespace model = ota::installation; namespace { void expect(bool condition, const char *message) { if (!condition) throw std::runtime_error(message); } template 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; } }