winget-cli

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

InstallDependenciesFlow.cpp (13891B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "WorkflowCommon.h"
      5 #include "DependenciesTestSource.h"
      6 #include <Commands/InstallCommand.h>
      7 #include <Commands/COMCommand.h>
      8 #include <Workflows/DependenciesFlow.h>
      9 #include <Workflows/DownloadFlow.h>
     10 #include <Workflows/InstallFlow.h>
     11 #include <Workflows/ShellExecuteInstallerHandler.h>
     12 
     13 using namespace TestCommon;
     14 using namespace AppInstaller::CLI;
     15 using namespace AppInstaller::CLI::Workflow;
     16 using namespace AppInstaller::Repository;
     17 
     18 void OverrideOpenSourceForDependencies(TestContext& context)
     19 {
     20     context.Override({ "OpenSource", [](TestContext& context)
     21     {
     22         context.Add<Execution::Data::Source>(Source{ std::make_shared<DependenciesTestSource>() });
     23     } });
     24 
     25     context.Override({ Workflow::OpenDependencySource, [](TestContext& context)
     26     {
     27         context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() });
     28     } });
     29 }
     30 
     31 void OverrideForProcessMultiplePackages(TestContext& context)
     32 {
     33     context.Override({ ProcessMultiplePackages(
     34         Resource::String::PackageRequiresDependencies,
     35         APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES,
     36         ProcessMultiplePackages::Flags::SkipPackageAgreements | ProcessMultiplePackages::Flags::IgnoreDependencies), [](TestContext&)
     37     {
     38 
     39     } });
     40 }
     41 
     42 void OverrideShellExecute(TestContext& context, std::vector<std::string>& installationOrder)
     43 {
     44     context.Override({ ShellExecuteInstallImpl, [&installationOrder](TestContext& c)
     45         {
     46             installationOrder.push_back(c.Get<Execution::Data::Manifest>().Id);
     47             c.Add<Execution::Data::OperationReturnCode>(0);
     48         } });
     49 }
     50 
     51 TEST_CASE("DependencyGraph_SkipInstalled", "[InstallFlow][workflow][dependencyGraph][dependencies]")
     52 {
     53     TestCommon::TempFile installResultPath("TestExeInstalled.txt");
     54 
     55     std::ostringstream installOutput;
     56     TestContext context{ installOutput, std::cin };
     57     auto previousThreadGlobals = context.SetForCurrentThread();
     58 
     59     Manifest manifest = CreateFakeManifestWithDependencies("DependenciesInstalled");
     60     OverrideOpenDependencySource(context);
     61 
     62     context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() });
     63     context.Add<Execution::Data::Manifest>(manifest);
     64     context.Add<Execution::Data::Installer>(manifest.Installers[0]);
     65 
     66     context << CreateDependencySubContexts(Resource::String::PackageRequiresDependencies);
     67 
     68     auto& dependencyPackages = context.Get<Execution::Data::PackageSubContexts>();
     69     REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos);
     70     REQUIRE(dependencyPackages.size() == 0);
     71 }
     72 
     73 TEST_CASE("DependencyGraph_validMinVersions", "[InstallFlow][workflow][dependencyGraph][dependencies]")
     74 {
     75     TestCommon::TempFile installResultPath("TestExeInstalled.txt");
     76 
     77     std::ostringstream installOutput;
     78     TestContext context{ installOutput, std::cin };
     79     auto previousThreadGlobals = context.SetForCurrentThread();
     80     Manifest manifest = CreateFakeManifestWithDependencies("DependenciesValidMinVersions");
     81     OverrideOpenDependencySource(context);
     82 
     83     context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() });
     84     context.Add<Execution::Data::Manifest>(manifest);
     85     context.Add<Execution::Data::Installer>(manifest.Installers[0]);
     86 
     87     context << CreateDependencySubContexts(Resource::String::PackageRequiresDependencies);
     88 
     89     auto& dependencyPackages = context.Get<Execution::Data::PackageSubContexts>();
     90 
     91     REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos);
     92     REQUIRE(dependencyPackages.size() == 1);
     93     REQUIRE(dependencyPackages.at(0)->Get<Execution::Data::Manifest>().Id == "minVersion");
     94     // minVersion 1.5 is available but this requires 1.0 so that version is installed
     95     REQUIRE(dependencyPackages.at(0)->Get<Execution::Data::Manifest>().Version == "1.0");
     96 }
     97 
     98 TEST_CASE("DependencyGraph_PathNoLoop", "[InstallFlow][workflow][dependencyGraph][dependencies]", )
     99 {
    100     std::ostringstream installOutput;
    101     TestContext context{ installOutput, std::cin };
    102     auto previousThreadGlobals = context.SetForCurrentThread();
    103     Manifest manifest = CreateFakeManifestWithDependencies("PathBetweenBranchesButNoLoop");
    104     OverrideOpenDependencySource(context);
    105 
    106     context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() });
    107     context.Add<Execution::Data::Manifest>(manifest);
    108     context.Add<Execution::Data::Installer>(manifest.Installers[0]);
    109 
    110     context << CreateDependencySubContexts(Resource::String::PackageRequiresDependencies);
    111 
    112     auto& dependencyPackages = context.Get<Execution::Data::PackageSubContexts>();
    113 
    114     REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos);
    115 
    116     // Verify installers are called in order
    117     REQUIRE(dependencyPackages.size() == 4);
    118     REQUIRE(dependencyPackages.at(0)->Get<Execution::Data::Manifest>().Id == "B");
    119     REQUIRE(dependencyPackages.at(1)->Get<Execution::Data::Manifest>().Id == "C");
    120     REQUIRE(dependencyPackages.at(2)->Get<Execution::Data::Manifest>().Id == "G");
    121     REQUIRE(dependencyPackages.at(3)->Get<Execution::Data::Manifest>().Id == "H");
    122 }
    123 
    124 TEST_CASE("DependencyGraph_StackOrderIsOk", "[InstallFlow][workflow][dependencyGraph][dependencies]")
    125 {
    126     std::vector<Dependency> installationOrder;
    127 
    128     std::ostringstream installOutput;
    129     TestContext context{ installOutput, std::cin };
    130     auto previousThreadGlobals = context.SetForCurrentThread();
    131     OverrideOpenSourceForDependencies(context);
    132     OverrideForShellExecute(context, installationOrder);
    133 
    134     context.Args.AddArg(Execution::Args::Type::Query, "StackOrderIsOk"sv);
    135 
    136     InstallCommand install({});
    137     install.Execute(context);
    138     INFO(installOutput.str());
    139 
    140     REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos);
    141 
    142     // Verify installers are called in order
    143     REQUIRE(installationOrder.size() == 3);
    144     REQUIRE(installationOrder.at(0).Id() == "B");
    145     REQUIRE(installationOrder.at(1).Id() == "C");
    146     REQUIRE(installationOrder.at(2).Id() == "StackOrderIsOk");
    147 }
    148 
    149 TEST_CASE("DependencyGraph_MultipleDependenciesFromManifest", "[InstallFlow][workflow][dependencyGraph][dependencies]")
    150 {
    151     std::vector<Dependency> installationOrder;
    152 
    153     std::ostringstream installOutput;
    154     TestContext context{ installOutput, std::cin };
    155     auto previousThreadGlobals = context.SetForCurrentThread();
    156     OverrideOpenSourceForDependencies(context);
    157     OverrideForShellExecute(context, installationOrder);
    158     OverrideEnableWindowsFeaturesDependencies(context);
    159 
    160     context.Args.AddArg(Execution::Args::Type::Query, "MultipleDependenciesFromManifest"sv);
    161 
    162     InstallCommand install({});
    163     install.Execute(context);
    164     INFO(installOutput.str());
    165 
    166     REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos);
    167 
    168     // Verify installers are called in order
    169     REQUIRE(installationOrder.size() == 3);
    170     REQUIRE(installationOrder.at(0).Id() == "Dependency1");
    171     REQUIRE(installationOrder.at(1).Id() == "Dependency2");
    172     REQUIRE(installationOrder.at(2).Id() == "AppInstallerCliTest.TestExeInstaller.MultipleDependencies");
    173 }
    174 
    175 TEST_CASE("InstallerWithoutDependencies_RootDependenciesAreUsed", "[dependencies]")
    176 {
    177     std::ostringstream installOutput;
    178     TestContext context{ installOutput, std::cin };
    179     auto previousThreadGlobals = context.SetForCurrentThread();
    180     OverrideForShellExecute(context);
    181     OverrideOpenDependencySource(context);
    182     OverrideEnableWindowsFeaturesDependencies(context);
    183 
    184     context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_DependenciesOnRoot.yaml").GetPath().u8string());
    185 
    186     InstallCommand install({});
    187     install.Execute(context);
    188     INFO(installOutput.str());
    189 
    190     // Verify root dependencies are shown
    191     REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos);
    192     REQUIRE(installOutput.str().find("PreviewIISOnRoot") != std::string::npos);
    193 }
    194 
    195 TEST_CASE("InstallerWithDependencies_SkipDependencies", "[dependencies]")
    196 {
    197     std::ostringstream installOutput;
    198     TestContext context{ installOutput, std::cin };
    199     auto previousThreadGlobals = context.SetForCurrentThread();
    200     OverrideForShellExecute(context);
    201 
    202     context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_Dependencies.yaml").GetPath().u8string());
    203     context.Args.AddArg(Execution::Args::Type::SkipDependencies);
    204 
    205     InstallCommand install({});
    206     install.Execute(context);
    207     INFO(installOutput.str());
    208 
    209     REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesSkippedMessage).get()) != std::string::npos);
    210     REQUIRE_FALSE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos);
    211     REQUIRE_FALSE(installOutput.str().find("PreviewIIS") != std::string::npos);
    212 }
    213 
    214 TEST_CASE("InstallerWithDependencies_IgnoreDependenciesSetting", "[dependencies]")
    215 {
    216     std::ostringstream installOutput;
    217     TestContext context{ installOutput, std::cin };
    218     auto previousThreadGlobals = context.SetForCurrentThread();
    219     OverrideForShellExecute(context);
    220 
    221     context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_Dependencies.yaml").GetPath().u8string());
    222 
    223     TestUserSettings settings;
    224     settings.Set<AppInstaller::Settings::Setting::InstallSkipDependencies>({ true });
    225 
    226     InstallCommand install({});
    227     install.Execute(context);
    228     INFO(installOutput.str());
    229 
    230     REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesSkippedMessage).get()) != std::string::npos);
    231     REQUIRE_FALSE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos);
    232     REQUIRE_FALSE(installOutput.str().find("PreviewIIS") != std::string::npos);
    233 }
    234 
    235 TEST_CASE("DependenciesMultideclaration_InstallerDependenciesPreference", "[dependencies]")
    236 {
    237     std::ostringstream installOutput;
    238     TestContext context{ installOutput, std::cin };
    239     auto previousThreadGlobals = context.SetForCurrentThread();
    240     OverrideForShellExecute(context);
    241     OverrideOpenDependencySource(context);
    242     OverrideEnableWindowsFeaturesDependencies(context);
    243 
    244     context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_DependenciesMultideclaration.yaml").GetPath().u8string());
    245 
    246     InstallCommand install({});
    247     install.Execute(context);
    248     INFO(installOutput.str());
    249 
    250     // Verify installer dependencies are shown
    251     REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos);
    252     REQUIRE(installOutput.str().find("PreviewIIS") != std::string::npos);
    253     // and root dependencies are not
    254     REQUIRE(installOutput.str().find("PreviewIISOnRoot") == std::string::npos);
    255 }
    256 
    257 TEST_CASE("InstallFlow_Dependencies", "[InstallFlow][workflow][dependencies]")
    258 {
    259     std::ostringstream installOutput;
    260     TestContext context{ installOutput, std::cin };
    261     auto previousThreadGlobals = context.SetForCurrentThread();
    262     OverrideForShellExecute(context);
    263     OverrideOpenDependencySource(context);
    264     OverrideEnableWindowsFeaturesDependencies(context);
    265 
    266     context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_Dependencies.yaml").GetPath().u8string());
    267 
    268     InstallCommand install({});
    269     install.Execute(context);
    270     INFO(installOutput.str());
    271 
    272     // Verify all types of dependencies are printed
    273     REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos);
    274     REQUIRE(installOutput.str().find("PreviewIIS") != std::string::npos);
    275 }
    276 
    277 TEST_CASE("InstallFlow_Dependencies_COM", "[InstallFlow][workflow][dependencies]")
    278 {
    279     std::vector<std::string> installationOrder;
    280 
    281     std::ostringstream installOutput;
    282     TestContext context{ installOutput, std::cin };
    283     auto previousThreadGlobals = context.SetForCurrentThread();
    284     OverrideForShellExecute(context);
    285     OverrideShellExecute(context, installationOrder);
    286     OverrideOpenDependencySource(context);
    287     OverrideEnableWindowsFeaturesDependencies(context);
    288     context.Override({ ReverifyInstallerHash, [](TestContext&) {} });
    289 
    290     context.Add<Execution::Data::Manifest>(YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_MultipleDependencies.yaml")));
    291 
    292     COMDownloadCommand download({});
    293     download.Execute(context);
    294 
    295     REQUIRE(installationOrder.size() == 0);
    296 
    297     COMInstallCommand install({});
    298     REQUIRE_NOTHROW(install.Execute(context));
    299 
    300     REQUIRE(context.GetTerminationHR() == S_OK);
    301 
    302     // Verify installers are called in order
    303     REQUIRE(installationOrder.size() == 3);
    304     REQUIRE(installationOrder.at(0) == "Dependency1");
    305     REQUIRE(installationOrder.at(1) == "Dependency2");
    306     REQUIRE(installationOrder.at(2) == "AppInstallerCliTest.TestExeInstaller.MultipleDependencies");
    307 }
    308 
    309 // TODO:
    310 // add dependencies for installer tests to DependenciesTestSource (or a new one)
    311 // add tests for min version dependency solving
    312 // add tests that check for correct installation of dependencies (not only the order)