diff --git a/src/mender-update/daemon/state_machine.hpp b/src/mender-update/daemon/state_machine.hpp index 6a47e447f..8c14fb0bb 100644 --- a/src/mender-update/daemon/state_machine.hpp +++ b/src/mender-update/daemon/state_machine.hpp @@ -48,6 +48,9 @@ class StateMachine { // Mainly for tests. void StopAfterDeployment(); +#ifndef NDEBUG + void StopAfterDeployments(int number); +#endif private: Context &ctx_; @@ -198,6 +201,16 @@ class StateMachine { artifact_script_path, rootfs_script_path, script_executor::OnError::Ignore), + sync_error_download_( + loop, + script_executor::State::Sync, + script_executor::Action::Error, + script_timeout, + retry_interval, + retry_timeout, + artifact_script_path, + rootfs_script_path, + script_executor::OnError::Ignore), download_enter_( loop, script_executor::State::Download, @@ -458,6 +471,7 @@ class StateMachine { StateScriptState sync_leave_; StateScriptState sync_leave_download_; StateScriptState sync_error_; + StateScriptState sync_error_download_; SaveStateScriptState download_enter_; StateScriptState download_leave_; diff --git a/src/mender-update/daemon/state_machine/state_machine.cpp b/src/mender-update/daemon/state_machine/state_machine.cpp index 4fbd87e74..4623fba64 100644 --- a/src/mender-update/daemon/state_machine/state_machine.cpp +++ b/src/mender-update/daemon/state_machine/state_machine.cpp @@ -108,7 +108,10 @@ StateMachine::StateMachine(Context &ctx, events::EventLoop &event_loop) : main_states_.AddTransition(ss.sync_leave_, se::Failure, ss.sync_error_, tf::Immediate); main_states_.AddTransition(ss.sync_leave_download_, se::Success, send_download_status_state_, tf::Immediate); - main_states_.AddTransition(ss.sync_leave_download_, se::Failure, ss.sync_error_, tf::Immediate); + main_states_.AddTransition(ss.sync_leave_download_, se::Failure, ss.sync_error_download_, tf::Immediate); + + main_states_.AddTransition(ss.sync_error_download_, se::Success, end_of_deployment_state_, tf::Immediate); + main_states_.AddTransition(ss.sync_error_download_, se::Failure, end_of_deployment_state_, tf::Immediate); // Cannot fail due to FailureMode::Ignore. main_states_.AddTransition(send_download_status_state_, se::Success, ss.download_enter_, tf::Immediate); @@ -454,6 +457,19 @@ void StateMachine::StopAfterDeployment() { sm::TransitionFlag::Immediate); } +#ifndef NDEBUG +void StateMachine::StopAfterDeployments(int number) { + exit_state_.ExitAfter(number); + main_states_.AddTransition( + end_of_deployment_state_, StateEvent::Success, exit_state_, sm::TransitionFlag::Immediate); + main_states_.AddTransition( + exit_state_, + StateEvent::Success, + state_scripts_.idle_enter_, + sm::TransitionFlag::Immediate); +} +#endif + } // namespace daemon } // namespace update } // namespace mender diff --git a/src/mender-update/daemon/states.cpp b/src/mender-update/daemon/states.cpp index be22c7cc7..a6d84db7a 100644 --- a/src/mender-update/daemon/states.cpp +++ b/src/mender-update/daemon/states.cpp @@ -953,7 +953,15 @@ ExitState::ExitState(events::EventLoop &event_loop) : } void ExitState::OnEnter(Context &ctx, sm::EventPoster &poster) { +#ifndef NDEBUG + if (--iterations_left_ <= 0) { + event_loop_.Stop(); + } else { + poster.PostEvent(StateEvent::Success); + } +#else event_loop_.Stop(); +#endif } namespace deployment_tracking { diff --git a/src/mender-update/daemon/states.hpp b/src/mender-update/daemon/states.hpp index 61ee21d29..e3befc9e6 100644 --- a/src/mender-update/daemon/states.hpp +++ b/src/mender-update/daemon/states.hpp @@ -264,8 +264,18 @@ class ExitState : virtual public StateType { error::Error exit_error; +#ifndef NDEBUG + void ExitAfter(int number) { + iterations_left_ = number; + } +#endif + private: events::EventLoop &event_loop_; + +#ifndef NDEBUG + int iterations_left_ {1}; +#endif }; diff --git a/tests/src/mender-update/daemon/state_test.cpp b/tests/src/mender-update/daemon/state_test.cpp index b645414cf..bd7e01d28 100644 --- a/tests/src/mender-update/daemon/state_test.cpp +++ b/tests/src/mender-update/daemon/state_test.cpp @@ -104,6 +104,7 @@ struct StateTransitionsTestCase { bool generate_idle_sync_scripts {false}; // Set it to the string that the database should contain. string update_control_string; + int stop_after_n_deployments {1}; }; @@ -367,6 +368,65 @@ vector idle_and_sync_test_cases { .error_states = {"Sync_Leave_00"}, .generate_idle_sync_scripts = true, }, + + StateTransitionsTestCase { + .case_name = "Sync_Leave_Error_multiple_deployments", + .state_chain = + { + "Idle_Enter_00", + "Idle_Leave_00", + "Sync_Enter_00", // <- Only fails the first time, during inventory + // Start of inventory. + "Sync_Error_00", + // End of inventory. + "Idle_Enter_00", + "Idle_Leave_00", + "Sync_Enter_00", + // Start of deployment. + "Sync_Leave_00", // <- Only fails the second time, during deployment + "Sync_Error_00", + // End of deployment. + "Idle_Enter_00", + "Idle_Leave_00", + // Start of inventory. + "Sync_Enter_00", + "Sync_Leave_00", + // End of inventory. + "Idle_Enter_00", + "Idle_Leave_00", + // Start of deployment. + "Sync_Enter_00", + "Sync_Leave_00", + "Download_Enter_00", + "ProvidePayloadFileSizes", + "Download", + "Download_Leave_00", + "ArtifactInstall_Enter_00", + "ArtifactInstall", + "ArtifactInstall_Leave_00", + "ArtifactReboot_Enter_00", + "ArtifactReboot", + "ArtifactVerifyReboot", + "ArtifactReboot_Leave_00", + "ArtifactCommit_Enter_00", + "ArtifactCommit", + "ArtifactCommit_Leave_00", + "Cleanup", + // End of deployment. + }, + .status_log = + { + "downloading", + "installing", + "rebooting", + "installing", + "success", + }, + .install_outcome = InstallOutcome::SuccessfulInstall, + .error_states = {"Sync_Enter_00", "Sync_Leave_00"}, + .generate_idle_sync_scripts = true, + .stop_after_n_deployments = 2, + }, }; vector GenerateStateTransitionsTestCases() { @@ -3588,6 +3648,7 @@ void StateTransitionsTestSubProcess( // exit handlers which should not be invoked while these objects are still alive. { conf::MenderConfig config {}; + config.update_poll_interval_seconds = 1; config.module_timeout_seconds = 2; config.paths.SetDataStore(tmpdir); config.paths.SetArtScriptsPath(path::Join(tmpdir, "artifact-scripts")); @@ -3648,7 +3709,13 @@ void StateTransitionsTestSubProcess( test.GetParam().fail_status_report_status, test.GetParam().fail_status_aborted); - state_machine.StopAfterDeployment(); + if (test.GetParam().stop_after_n_deployments > 1) { +#ifndef NDEBUG + state_machine.StopAfterDeployments(test.GetParam().stop_after_n_deployments); +#endif + } else { + state_machine.StopAfterDeployment(); + } err = state_machine.Run(); ASSERT_IN_DEATH_TEST(err == error::NoError) << err.String(); } @@ -3686,6 +3753,12 @@ TEST_P(StateDeathTest, StateTransitionsTest) { // information. GTEST_FLAG_SET(death_test_style, "fast"); +#ifdef NDEBUG + if (GetParam().stop_after_n_deployments > 1) { + GTEST_SKIP() << "Stopping after N deployments requires debug mode"; + } +#endif + { ofstream f(path::Join(tmpdir.Path(), "device_type")); f << "device_type=test-type\n";