06 - atomic update
This commit is contained in:
1 parent
6d63457737
commit
2bdacaf37a
21 files changed
+1302
-365
No files matched your search
@@ -0,0 +1,148 @@
|
||||
#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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user