winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }