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)