Dependencies.cpp (10742B)
1 #include "pch.h" 2 #include "TestCommon.h" 3 #include "TestSource.h" 4 #include "DependenciesTestSource.h" 5 #include <winget/DependenciesGraph.h> 6 #include <Workflows/DependencyNodeProcessor.h> 7 #include <AppInstallerErrors.h> 8 #include <AppInstallerRuntime.h> 9 #include <AppInstallerStrings.h> 10 #include <Workflows/DependenciesFlow.h> 11 #include <Workflows/WorkflowBase.h> 12 #include <winget/ManifestYamlParser.h> 13 #include <winget/PathVariable.h> 14 #include <winget/RepositorySource.h> 15 #include <Resources.h> 16 17 using namespace winrt::Windows::Foundation; 18 using namespace winrt::Windows::Management::Deployment; 19 using namespace TestCommon; 20 using namespace AppInstaller::CLI; 21 using namespace AppInstaller::CLI::Execution; 22 using namespace AppInstaller::CLI::Workflow; 23 using namespace AppInstaller::Logging; 24 using namespace AppInstaller::Manifest; 25 using namespace AppInstaller::Repository; 26 using namespace AppInstaller::Settings; 27 using namespace AppInstaller::Utility; 28 using namespace AppInstaller::Utility::literals; 29 30 TEST_CASE("DependencyGraph_BFirst", "[dependencyGraph][dependencies]") 31 { 32 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 33 std::vector<Dependency> installationOrder; 34 35 const auto& manifest = CreateFakeManifestWithDependencies("NeedsToInstallBFirst"); 36 const auto& installers = manifest.Installers; 37 const Dependency& rootAsDependency = Dependency(DependencyType::Package, manifest.Id); 38 DependencyList rootDependencies; 39 std::for_each(installers.begin(), installers.end(), [&](ManifestInstaller installer) { rootDependencies.Add(installer.Dependencies); }); 40 41 DependencyGraph graph(rootAsDependency, rootDependencies, [&](Dependency) 42 { 43 DependencyList dependencyList; 44 auto dependencyManifest = CreateFakeManifestWithDependencies(manifest.Id); 45 46 for (auto installer : dependencyManifest.Installers) 47 { 48 dependencyList.Add(installer.Dependencies); 49 } 50 51 return dependencyList; 52 }); 53 54 graph.BuildGraph(); 55 56 installationOrder = graph.GetInstallationOrder(); 57 58 REQUIRE(installationOrder.size() == 3); 59 REQUIRE(installationOrder.at(0).Id() == "C"); 60 REQUIRE(installationOrder.at(1).Id() == "B"); 61 REQUIRE(installationOrder.at(2).Id() == "NeedsToInstallBFirst"); 62 } 63 64 TEST_CASE("DependencyGraph_InStackNoLoop", "[dependencyGraph][dependencies]") 65 { 66 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 67 std::vector<Dependency> installationOrder; 68 69 const auto& manifest = CreateFakeManifestWithDependencies("DependencyAlreadyInStackButNoLoop"); 70 const auto& installers = manifest.Installers; 71 const Dependency& rootAsDependency = Dependency(DependencyType::Package, manifest.Id); 72 DependencyList rootDependencies; 73 std::for_each(installers.begin(), installers.end(), [&](ManifestInstaller installer) { rootDependencies.Add(installer.Dependencies); }); 74 75 DependencyGraph graph(rootAsDependency, rootDependencies, [&](Dependency) 76 { 77 DependencyList dependencyList; 78 auto dependencyManifest = CreateFakeManifestWithDependencies(manifest.Id); 79 80 for (auto installer : dependencyManifest.Installers) 81 { 82 dependencyList.Add(installer.Dependencies); 83 } 84 85 return dependencyList; 86 }); 87 88 graph.BuildGraph(); 89 90 installationOrder = graph.GetInstallationOrder(); 91 92 REQUIRE(installationOrder.size() == 3); 93 REQUIRE(installationOrder.at(0).Id() == "F"); 94 REQUIRE(installationOrder.at(1).Id() == "C"); 95 REQUIRE(installationOrder.at(2).Id() == "DependencyAlreadyInStackButNoLoop"); 96 } 97 98 TEST_CASE("DependencyGraph_EasyToSeeLoop", "[dependencyGraph][dependencies]") 99 { 100 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 101 std::vector<Dependency> installationOrder; 102 103 const auto& manifest = CreateFakeManifestWithDependencies("EasyToSeeLoop"); 104 const auto& installers = manifest.Installers; 105 const Dependency& rootAsDependency = Dependency(DependencyType::Package, manifest.Id); 106 DependencyList rootDependencies; 107 std::for_each(installers.begin(), installers.end(), [&](ManifestInstaller installer) { rootDependencies.Add(installer.Dependencies); }); 108 109 DependencyGraph graph(rootAsDependency, rootDependencies, [&](Dependency) { 110 DependencyList dependencyList; 111 auto dependencyManifest = CreateFakeManifestWithDependencies(manifest.Id); 112 113 for (auto installer : dependencyManifest.Installers) 114 { 115 dependencyList.Add(installer.Dependencies); 116 } 117 118 return dependencyList; 119 }); 120 121 graph.BuildGraph(); 122 123 installationOrder = graph.GetInstallationOrder(); 124 125 bool hasLoop = graph.HasLoop(); 126 127 REQUIRE(hasLoop); 128 129 REQUIRE(installationOrder.size() == 2); 130 REQUIRE(installationOrder.at(0).Id() == "D"); 131 REQUIRE(installationOrder.at(1).Id() == "EasyToSeeLoop"); 132 } 133 134 TEST_CASE("DependencyNodeProcessor_SkipInstalled", "[dependencies]") 135 { 136 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 137 138 std::ostringstream installOutput; 139 Context context{ installOutput, std::cin }; 140 141 Manifest manifest = CreateFakeManifestWithDependencies("installed1"); 142 143 context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() }); 144 DependencyNodeProcessor nodeProcessor(context); 145 146 Dependency rootAsDependency(DependencyType::Package, manifest.Id); 147 148 DependencyNodeProcessorResult result = nodeProcessor.EvaluateDependencies(rootAsDependency); 149 REQUIRE(result == DependencyNodeProcessorResult::Skipped); 150 } 151 152 TEST_CASE("DependencyNodeProcessor_NoInstallers", "[dependencies]") 153 { 154 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 155 156 std::ostringstream installOutput; 157 Context context { installOutput, std::cin }; 158 159 Manifest manifest = CreateFakeManifestWithDependencies("withoutInstallers"); 160 161 context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() }); 162 DependencyNodeProcessor nodeProcessor(context); 163 164 Dependency rootAsDependency(DependencyType::Package, manifest.Id); 165 166 DependencyNodeProcessorResult result = nodeProcessor.EvaluateDependencies(rootAsDependency); 167 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowNoInstallerFound("withoutInstallers"_liv))) != std::string::npos); 168 REQUIRE(result == DependencyNodeProcessorResult::Error); 169 } 170 171 TEST_CASE("DependencyNodeProcessor_StackOrderIsOk", "[dependencies]") 172 { 173 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 174 175 std::ostringstream installOutput; 176 Context context{ installOutput, std::cin }; 177 178 Manifest manifest = CreateFakeManifestWithDependencies("StackOrderIsOk"); 179 180 context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() }); 181 DependencyNodeProcessor nodeProcessor(context); 182 183 Dependency rootAsDependency(DependencyType::Package, manifest.Id); 184 185 DependencyNodeProcessorResult result = nodeProcessor.EvaluateDependencies(rootAsDependency); 186 auto dependencyList = nodeProcessor.GetDependencyList(); 187 REQUIRE(dependencyList.Size() == 1); 188 REQUIRE(dependencyList.HasDependency(Dependency(DependencyType::Package, "C"))); 189 REQUIRE(result == DependencyNodeProcessorResult::Success); 190 } 191 192 TEST_CASE("DependencyNodeProcessor_NoMatches", "[dependencies]") 193 { 194 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 195 196 std::ostringstream installOutput; 197 Context context{ installOutput, std::cin }; 198 199 Manifest manifest = CreateFakeManifestWithDependencies("NoMatches"); 200 201 context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() }); 202 DependencyNodeProcessor nodeProcessor(context); 203 204 Dependency rootAsDependency(DependencyType::Package, manifest.Id); 205 206 DependencyNodeProcessorResult result = nodeProcessor.EvaluateDependencies(rootAsDependency); 207 auto dependencyList = nodeProcessor.GetDependencyList(); 208 REQUIRE(dependencyList.Size() == 0); 209 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowNoMatches)) != std::string::npos); 210 REQUIRE(result == DependencyNodeProcessorResult::Error); 211 } 212 213 TEST_CASE("DependencyList_Add_MinVersion", "[dependencies]") 214 { 215 DependencyType type = DependencyType::Package; 216 std::string identifier = "Identifier"; 217 218 DependencyList list; 219 Dependency dependencyWithoutMinVersion{ type, identifier }; 220 Dependency dependencyWithLowerMinVersion{ type, identifier, "1.0" }; 221 Dependency dependencyWithHigherMinVersion{ type, identifier, "3.0" }; 222 223 Dependency dependencyToAdd{ type, identifier, "2.0" }; 224 225 SECTION("Existing dependency has no min version, added does") 226 { 227 list.Add(dependencyWithoutMinVersion); 228 list.Add(dependencyToAdd); 229 230 const Dependency* dependency = list.HasDependency(dependencyToAdd); 231 REQUIRE(dependency != nullptr); 232 REQUIRE(dependency->MinVersion.has_value()); 233 REQUIRE(dependency->MinVersion == dependencyToAdd.MinVersion); 234 } 235 SECTION("Existing dependency has lower min version") 236 { 237 list.Add(dependencyWithLowerMinVersion); 238 list.Add(dependencyToAdd); 239 240 const Dependency* dependency = list.HasDependency(dependencyToAdd); 241 REQUIRE(dependency != nullptr); 242 REQUIRE(dependency->MinVersion.has_value()); 243 REQUIRE(dependency->MinVersion == dependencyToAdd.MinVersion); 244 } 245 SECTION("Existing dependency has higher min version") 246 { 247 list.Add(dependencyWithHigherMinVersion); 248 list.Add(dependencyToAdd); 249 250 const Dependency* dependency = list.HasDependency(dependencyToAdd); 251 REQUIRE(dependency != nullptr); 252 REQUIRE(dependency->MinVersion.has_value()); 253 REQUIRE(dependency->MinVersion == dependencyWithHigherMinVersion.MinVersion); 254 } 255 SECTION("Existing dependency has no min version, neither does added") 256 { 257 list.Add(dependencyWithoutMinVersion); 258 list.Add(dependencyWithoutMinVersion); 259 260 const Dependency* dependency = list.HasDependency(dependencyToAdd); 261 REQUIRE(dependency != nullptr); 262 REQUIRE(!dependency->MinVersion.has_value()); 263 } 264 SECTION("Existing dependency has min version, added does not") 265 { 266 list.Add(dependencyWithHigherMinVersion); 267 list.Add(dependencyWithoutMinVersion); 268 269 const Dependency* dependency = list.HasDependency(dependencyToAdd); 270 REQUIRE(dependency != nullptr); 271 REQUIRE(dependency->MinVersion.has_value()); 272 REQUIRE(dependency->MinVersion == dependencyWithHigherMinVersion.MinVersion); 273 } 274 }