ResumeFlow.cpp (12140B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "WorkflowCommon.h" 5 #include "TestHooks.h" 6 #include <Commands/InstallCommand.h> 7 #include <Commands/ResumeCommand.h> 8 #include <AppInstallerRuntime.h> 9 #include <AppInstallerStrings.h> 10 #include <AppInstallerVersions.h> 11 #include <CheckpointManager.h> 12 #include <Workflows/ShellExecuteInstallerHandler.h> 13 14 using namespace std::string_literals; 15 using namespace AppInstaller::CLI; 16 using namespace AppInstaller::Repository::Microsoft; 17 using namespace AppInstaller::Settings; 18 using namespace AppInstaller::Runtime; 19 using namespace TestCommon; 20 using namespace AppInstaller::Checkpoints; 21 22 constexpr std::string_view s_AutomaticCheckpoint = "automatic"sv; 23 constexpr std::string_view s_CheckpointsFileName = "checkpoints.db"sv; 24 25 TEST_CASE("ResumeFlow_IndexNotFound", "[Resume]") 26 { 27 std::string tempGuidString = "{ec3a098c-a815-4d52-8866-946c03093a37}"; 28 29 std::ostringstream resumeOutput; 30 TestContext context{ resumeOutput, std::cin }; 31 auto previousThreadGlobals = context.SetForCurrentThread(); 32 context.Args.AddArg(Execution::Args::Type::ResumeId, tempGuidString); 33 34 ResumeCommand resume({}); 35 context.SetExecutingCommand(&resume); 36 resume.Execute(context); 37 INFO(resumeOutput.str()); 38 39 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_RESUME_ID_NOT_FOUND); 40 auto expectedMessage = Resource::String::ResumeIdNotFoundError(AppInstaller::Utility::LocIndString(tempGuidString)); 41 REQUIRE(resumeOutput.str().find(Resource::LocString(expectedMessage).get()) != std::string::npos); 42 } 43 44 TEST_CASE("ResumeFlow_InvalidClientVersion", "[Resume]") 45 { 46 TestCommon::TempDirectory tempCheckpointRecordDirectory("TempCheckpointRecordDirectory", true); 47 48 const auto& tempCheckpointRecordDirectoryPath = tempCheckpointRecordDirectory.GetPath(); 49 TestHook_SetPathOverride(PathName::CheckpointsLocation, tempCheckpointRecordDirectoryPath); 50 51 // Create temp guid and populate with invalid client version. 52 std::string tempGuidString = "{615339e9-3ac5-4e86-a5ab-c246657aca25}"; 53 auto tempRecordPath = tempCheckpointRecordDirectoryPath / tempGuidString / s_CheckpointsFileName; 54 std::string_view invalidClientVersion = "1.2.3.4"sv; 55 56 INFO("Using temporary file named: " << tempRecordPath); 57 58 { 59 // Manually set invalid client version 60 std::filesystem::create_directories(tempRecordPath.parent_path()); 61 std::shared_ptr<CheckpointDatabase> checkpointRecord = CheckpointDatabase::CreateNew(tempRecordPath.u8string()); 62 CheckpointDatabase::IdType checkpointId = checkpointRecord->AddCheckpoint(s_AutomaticCheckpoint); 63 checkpointRecord->SetDataValue(checkpointId, AutomaticCheckpointData::ClientVersion, {}, { "1.2.3.4" }); 64 } 65 66 std::ostringstream resumeOutput; 67 TestContext context{ resumeOutput, std::cin }; 68 auto previousThreadGlobals = context.SetForCurrentThread(); 69 context.Args.AddArg(Execution::Args::Type::ResumeId, tempGuidString); 70 71 ResumeCommand resume({}); 72 context.SetExecutingCommand(&resume); 73 resume.Execute(context); 74 INFO(resumeOutput.str()); 75 76 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_CLIENT_VERSION_MISMATCH); 77 auto expectedMessage = Resource::String::ClientVersionMismatchError(AppInstaller::Utility::LocIndString(invalidClientVersion)); 78 REQUIRE(resumeOutput.str().find(Resource::LocString(expectedMessage).get()) != std::string::npos); 79 } 80 81 TEST_CASE("ResumeFlow_EmptyIndex", "[Resume]") 82 { 83 TestCommon::TempDirectory tempCheckpointRecordDirectory("TempCheckpointRecordDirectory", true); 84 85 const auto& tempCheckpointRecordDirectoryPath = tempCheckpointRecordDirectory.GetPath(); 86 TestHook_SetPathOverride(PathName::CheckpointsLocation, tempCheckpointRecordDirectoryPath); 87 88 std::string tempGuidString = "{43ca664c-3eae-4f73-99ee-18cf83912c02}"; 89 auto tempRecordPath = tempCheckpointRecordDirectoryPath / tempGuidString / s_CheckpointsFileName; 90 91 INFO("Using temporary file named: " << tempRecordPath); 92 93 { 94 std::filesystem::create_directories(tempRecordPath.parent_path()); 95 CheckpointDatabase::CreateNew(tempRecordPath.u8string()); 96 } 97 98 std::ostringstream resumeOutput; 99 TestContext context{ resumeOutput, std::cin }; 100 auto previousThreadGlobals = context.SetForCurrentThread(); 101 context.Args.AddArg(Execution::Args::Type::ResumeId, tempGuidString); 102 103 ResumeCommand resume({}); 104 context.SetExecutingCommand(&resume); 105 resume.Execute(context); 106 INFO(resumeOutput.str()); 107 108 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INVALID_RESUME_STATE); 109 REQUIRE(resumeOutput.str().find(Resource::LocString(Resource::String::ResumeStateDataNotFoundError).get()) != std::string::npos); 110 } 111 112 TEST_CASE("ResumeFlow_InstallSuccess", "[Resume]") 113 { 114 TestCommon::TempDirectory tempCheckpointRecordDirectory("TempCheckpointRecordDirectory", false); 115 116 const auto& tempCheckpointRecordDirectoryPath = tempCheckpointRecordDirectory.GetPath(); 117 TestHook_SetPathOverride(PathName::CheckpointsLocation, tempCheckpointRecordDirectoryPath); 118 119 TestCommon::TestUserSettings testSettings; 120 testSettings.Set<Setting::EFResume>(true); 121 122 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 123 124 { 125 std::ostringstream installOutput; 126 TestContext context{ installOutput, std::cin }; 127 auto previousThreadGlobals = context.SetForCurrentThread(); 128 OverrideForShellExecute(context); 129 130 const auto& testManifestPath = TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string(); 131 context.Args.AddArg(Execution::Args::Type::Manifest, testManifestPath); 132 133 InstallCommand install({}); 134 context.SetExecutingCommand(&install); 135 install.Execute(context); 136 INFO(installOutput.str()); 137 } 138 139 // Verify Installer is called and parameters are passed in. 140 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 141 std::ifstream installResultFile(installResultPath.GetPath()); 142 REQUIRE(installResultFile.is_open()); 143 std::string installResultStr; 144 std::getline(installResultFile, installResultStr); 145 REQUIRE(installResultStr.find("/custom") != std::string::npos); 146 REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); 147 148 // The checkpoint index should not exist if the context succeeded. 149 std::vector<std::filesystem::path> checkpointFiles; 150 for (const auto& entry : std::filesystem::directory_iterator(tempCheckpointRecordDirectoryPath)) 151 { 152 checkpointFiles.emplace_back(entry.path()); 153 } 154 155 REQUIRE(checkpointFiles.size() == 0); 156 } 157 158 TEST_CASE("ResumeFlow_InstallFailure", "[Resume]") 159 { 160 TestCommon::TempDirectory tempCheckpointRecordDirectory("TempCheckpointRecordDirectory", false); 161 162 const auto& tempCheckpointRecordDirectoryPath = tempCheckpointRecordDirectory.GetPath(); 163 TestHook_SetPathOverride(PathName::CheckpointsLocation, tempCheckpointRecordDirectoryPath); 164 165 TestCommon::TestUserSettings testSettings; 166 testSettings.Set<Setting::EFResume>(true); 167 168 { 169 std::ostringstream installOutput; 170 TestContext context{ installOutput, std::cin }; 171 auto previousThreadGlobals = context.SetForCurrentThread(); 172 173 const auto& testManifestPath = TestDataFile("InstallFlowTest_UnsupportedArguments.yaml").GetPath().u8string(); 174 context.Args.AddArg(Execution::Args::Type::Manifest, testManifestPath); 175 context.Args.AddArg(Execution::Args::Type::InstallLocation, "installLocation"sv); 176 177 InstallCommand install({}); 178 context.SetExecutingCommand(&install); 179 install.Execute(context); 180 INFO(installOutput.str()); 181 182 // Verify unsupported arguments error message is shown 183 REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT); 184 } 185 186 // Checkpoint file should be cleaned up if the hr is not reboot related. 187 REQUIRE(std::filesystem::is_empty(tempCheckpointRecordDirectoryPath)); 188 } 189 190 191 TEST_CASE("ResumeFlow_WriteToRunOnceRegistry", "[Reboot][Resume][windowsFeature]") 192 { 193 if (!AppInstaller::Runtime::IsRunningAsAdmin()) 194 { 195 WARN("Test requires admin privilege. Skipped."); 196 return; 197 } 198 199 TestCommon::TempDirectory tempCheckpointRecordDirectory("TempCheckpointRecordDirectory", false); 200 201 const auto& tempCheckpointRecordDirectoryPath = tempCheckpointRecordDirectory.GetPath(); 202 TestHook_SetPathOverride(PathName::CheckpointsLocation, tempCheckpointRecordDirectoryPath); 203 204 TestCommon::TestUserSettings testSettings; 205 testSettings.Set<Setting::EFResume>(true); 206 207 std::ostringstream installOutput; 208 TestContext context{ installOutput, std::cin }; 209 auto previousThreadGlobals = context.SetForCurrentThread(); 210 OverrideOpenDependencySource(context); 211 OverrideRegisterStartupAfterReboot(context); 212 213 // Override with reboot required HRESULT. 214 auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(ERROR_SUCCESS); 215 auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(ERROR_SUCCESS_REBOOT_REQUIRED); 216 TestHook::SetRegisterForRestartResult_Override registerForRestartResultOverride(false); 217 TestHook::SetInitiateRebootResult_Override initiateRebootResultOverride(false); 218 219 const auto& testManifestPath = TestDataFile("InstallFlowTest_WindowsFeatures.yaml").GetPath().u8string(); 220 context.Args.AddArg(Execution::Args::Type::Manifest, testManifestPath); 221 context.Args.AddArg(Execution::Args::Type::AllowReboot); 222 223 InstallCommand install({}); 224 install.Execute(context); 225 INFO(installOutput.str()); 226 227 REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL); 228 } 229 230 TEST_CASE("ResumeFlow_ResumeLimitExceeded", "[Resume]") 231 { 232 if (!AppInstaller::Runtime::IsRunningAsAdmin()) 233 { 234 WARN("Test requires admin privilege. Skipped."); 235 return; 236 } 237 238 TestCommon::TempDirectory tempCheckpointsLocationDir("TempCheckpointsLocationDir", true); 239 240 const auto& tempCheckpointRecordDirectoryPath = tempCheckpointsLocationDir.GetPath(); 241 TestHook_SetPathOverride(PathName::CheckpointsLocation, tempCheckpointRecordDirectoryPath); 242 243 TestCommon::TestUserSettings testSettings; 244 testSettings.Set<Setting::EFResume>(true); 245 testSettings.Set<Setting::MaxResumes>(1); 246 247 std::filesystem::path testResumeId = L"testResumeId"; 248 std::filesystem::path tempResumeDir = tempCheckpointRecordDirectoryPath / testResumeId; 249 std::filesystem::path tempCheckpointDatabasePath = tempResumeDir / L"checkpoints.db"; 250 std::filesystem::create_directory(tempResumeDir); 251 252 { 253 std::shared_ptr<CheckpointDatabase> database = CheckpointDatabase::CreateNew(tempCheckpointDatabasePath.u8string(), {1, 0}); 254 std::string_view testCheckpointName = "testCheckpoint"sv; 255 256 CheckpointDatabase::IdType checkpointId = database->AddCheckpoint(testCheckpointName); 257 database->SetDataValue(checkpointId, AutomaticCheckpointData::Command, {}, { "install" }); 258 database->SetDataValue(checkpointId, AutomaticCheckpointData::ClientVersion, {}, { GetClientVersion()}); 259 database->SetDataValue(checkpointId, AutomaticCheckpointData::ResumeCount, {}, {"1"}); 260 } 261 262 { 263 std::ostringstream resumeOutput; 264 TestContext resumeContext{ resumeOutput, std::cin }; 265 auto previousThreadGlobals = resumeContext.SetForCurrentThread(); 266 resumeContext.Args.AddArg(Execution::Args::Type::ResumeId, testResumeId.u8string()); 267 268 ResumeCommand resume({}); 269 resume.Execute(resumeContext); 270 INFO(resumeOutput.str()); 271 REQUIRE(resumeContext.IsTerminated()); 272 REQUIRE(resumeContext.GetTerminationHR() == APPINSTALLER_CLI_ERROR_RESUME_LIMIT_EXCEEDED); 273 274 REQUIRE(resumeOutput.str().find(Resource::LocString(Resource::String::ResumeLimitExceeded('1')).get()) != std::string::npos); 275 REQUIRE(resumeOutput.str().find("winget resume -g " + testResumeId.u8string() + " --ignore-resume-limit") != std::string::npos); 276 } 277 }