ARPChanges.cpp (17589B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "TestCommon.h" 5 #include "TestSource.h" 6 #include "TestHooks.h" 7 #include <Workflows/WorkflowBase.h> 8 #include <Workflows/InstallFlow.h> 9 #include <winget/Manifest.h> 10 #include <winget/ARPCorrelationAlgorithms.h> 11 #include <Microsoft/PredefinedInstalledSourceFactory.h> 12 #include <winget/PackageVersionSelection.h> 13 14 using namespace TestCommon; 15 using namespace AppInstaller; 16 using namespace AppInstaller::CLI; 17 using namespace AppInstaller::CLI::Execution; 18 using namespace AppInstaller::CLI::Workflow; 19 using namespace AppInstaller::Logging; 20 using namespace AppInstaller::Repository; 21 using namespace AppInstaller::Repository::Correlation; 22 23 struct TestTelemetry : public TelemetryTraceLogger 24 { 25 void LogSuccessfulInstallARPChange( 26 std::string_view sourceIdentifier, 27 std::string_view packageIdentifier, 28 std::string_view packageVersion, 29 std::string_view packageChannel, 30 size_t changesToARP, 31 size_t matchesInARP, 32 size_t countOfIntersectionOfChangesAndMatches, 33 std::string_view arpName, 34 std::string_view arpVersion, 35 std::string_view arpPublisher, 36 std::string_view arpLanguage) const noexcept override 37 { 38 WasLogSuccessfulInstallARPChangeCalled = true; 39 if (OnLogSuccessfulInstallARPChange) 40 { 41 OnLogSuccessfulInstallARPChange( 42 sourceIdentifier, packageIdentifier, packageVersion, packageChannel, 43 changesToARP, matchesInARP, countOfIntersectionOfChangesAndMatches, 44 arpName, arpVersion, arpPublisher, arpLanguage); 45 } 46 } 47 48 std::function<void( 49 std::string_view, std::string_view, std::string_view, std::string_view, 50 size_t, size_t, size_t, 51 std::string_view, std::string_view, std::string_view, std::string_view)> OnLogSuccessfulInstallARPChange; 52 53 mutable bool WasLogSuccessfulInstallARPChangeCalled = false; 54 }; 55 56 struct ARPTestContext : public Context 57 { 58 ARPTestContext(Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Exe) : 59 Context(OStream, IStream), SourceFactory([this](const SourceDetails&) { return Source; }) 60 { 61 // Put installer in to control whether arp change code cares to run 62 Manifest::ManifestInstaller installer; 63 installer.BaseInstallerType = installerType; 64 Add<Data::Installer>(std::move(installer)); 65 66 // Put in an empty manifest by default 67 Manifest::Manifest manifest; 68 manifest.Id = "Installing.Id"; 69 manifest.Version = "Installing.Version"; 70 manifest.Channel = "Installing.Channel"; 71 manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>("Installing.Name"); 72 Add<Data::Manifest>(std::move(manifest)); 73 74 // Set up logger to intercept event 75 Logger = std::make_shared<TestTelemetry>(); 76 TestHook_SetTelemetryOverride(Logger); 77 78 Logger->OnLogSuccessfulInstallARPChange = [this]( 79 std::string_view sourceIdentifier, 80 std::string_view packageIdentifier, 81 std::string_view packageVersion, 82 std::string_view packageChannel, 83 size_t changesToARP, 84 size_t matchesInARP, 85 size_t countOfIntersectionOfChangesAndMatches, 86 std::string_view arpName, 87 std::string_view arpVersion, 88 std::string_view arpPublisher, 89 std::string_view arpLanguage) 90 { 91 SourceIdentifier = sourceIdentifier; 92 PackageIdentifier = packageIdentifier; 93 PackageVersion = packageVersion; 94 PackageChannel = packageChannel; 95 ChangesToARP = changesToARP; 96 MatchesInARP = matchesInARP; 97 CountOfIntersectionOfChangesAndMatches = countOfIntersectionOfChangesAndMatches; 98 ARPName = arpName; 99 ARPVersion = arpVersion; 100 ARPPublisher = arpPublisher; 101 ARPLanguage = arpLanguage; 102 }; 103 104 // Inject our source 105 TestHook_SetSourceFactoryOverride(std::string{ Repository::Microsoft::PredefinedInstalledSourceFactory::Type() }, SourceFactory); 106 107 Source = std::make_shared<TestSource>(); 108 Source->SearchFunction = [&](const SearchRequest& request) 109 { 110 return request.IsForEverything() ? EverythingResult : MatchResult; 111 }; 112 113 // The package version is used to get the source identifier 114 Add<Data::PackageVersion>(TestPackageVersion::Make(Get<Data::Manifest>(), Source)); 115 116 // Populate everything result with a few items 117 AddEverythingResult("Id1", "Name1", "Publisher1", "1.0"); 118 AddEverythingResult("Id2", "Name2", "Publisher2", "2.0"); 119 } 120 121 ~ARPTestContext() 122 { 123 TestHook_ClearSourceFactoryOverrides(); 124 TestHook_SetTelemetryOverride({}); 125 } 126 127 void AddEverythingResult(std::string_view id, std::string_view name, std::string_view publisher, std::string_view version) 128 { 129 AddResult(EverythingResult, id, name, publisher, version); 130 } 131 132 void AddMatchResult(std::string_view id, std::string_view name, std::string_view publisher, std::string_view version) 133 { 134 AddResult(MatchResult, id, name, publisher, version); 135 } 136 137 void ExpectEvent(size_t arpChanges, size_t matches, size_t overlap, const std::shared_ptr<ICompositePackage>& arpEntry = nullptr) 138 { 139 REQUIRE(Logger->WasLogSuccessfulInstallARPChangeCalled); 140 141 const auto& manifest = Get<Data::Manifest>(); 142 143 REQUIRE(Source->GetIdentifier() == SourceIdentifier); 144 REQUIRE(manifest.Id == PackageIdentifier); 145 REQUIRE(manifest.Version == PackageVersion); 146 REQUIRE(manifest.Channel == PackageChannel); 147 REQUIRE(arpChanges == ChangesToARP); 148 REQUIRE(matches == MatchesInARP); 149 REQUIRE(overlap == CountOfIntersectionOfChangesAndMatches); 150 151 if (arpEntry) 152 { 153 auto version = GetInstalledVersion(arpEntry); 154 REQUIRE(version->GetProperty(PackageVersionProperty::Name) == ARPName); 155 REQUIRE(version->GetProperty(PackageVersionProperty::Version) == ARPVersion); 156 157 auto metadata = version->GetMetadata(); 158 REQUIRE(metadata[PackageVersionMetadata::Publisher] == ARPPublisher); 159 REQUIRE(metadata[PackageVersionMetadata::InstalledLocale] == ARPLanguage); 160 } 161 else 162 { 163 REQUIRE(ARPName.empty()); 164 REQUIRE(ARPVersion.empty()); 165 REQUIRE(ARPPublisher.empty()); 166 REQUIRE(ARPLanguage.empty()); 167 } 168 } 169 170 std::ostringstream OStream; 171 std::istringstream IStream; 172 std::shared_ptr<TestTelemetry> Logger; 173 TestSourceFactory SourceFactory; 174 std::shared_ptr<TestSource> Source; 175 SearchResult EverythingResult; 176 SearchResult MatchResult; 177 178 // EventData 179 std::string SourceIdentifier; 180 std::string PackageIdentifier; 181 std::string PackageVersion; 182 std::string PackageChannel; 183 size_t ChangesToARP; 184 size_t MatchesInARP; 185 size_t CountOfIntersectionOfChangesAndMatches; 186 std::string ARPName; 187 std::string ARPVersion; 188 std::string ARPPublisher; 189 std::string ARPLanguage; 190 191 private: 192 void AddResult(SearchResult& result, std::string_view id, std::string_view name, std::string_view publisher, std::string_view version) 193 { 194 PackageMatchFilter defaultFilter{ PackageMatchField::Id, MatchType::Exact }; 195 Manifest::Manifest manifest; 196 197 manifest.Id = id; 198 manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>(name); 199 manifest.DefaultLocalization.Add<Manifest::Localization::Publisher>(publisher); 200 manifest.Version = version; 201 manifest.Installers.push_back({}); 202 203 TestPackage::MetadataMap metadata; 204 metadata[PackageVersionMetadata::Publisher] = publisher; 205 206 result.Matches.emplace_back(TestCompositePackage::Make(manifest, std::move(metadata), std::vector<Manifest::Manifest>{}, Source), defaultFilter); 207 } 208 }; 209 210 // Override the correlation heuristic by an empty one to ensure that these tests 211 // consider only the exact matching. 212 struct TestHeuristicOverride 213 { 214 TestHeuristicOverride() 215 { 216 IARPMatchConfidenceAlgorithm::OverrideInstance(&m_algorithm); 217 } 218 219 ~TestHeuristicOverride() 220 { 221 IARPMatchConfidenceAlgorithm::ResetInstance(); 222 } 223 224 private: 225 EmptyMatchConfidenceAlgorithm m_algorithm; 226 }; 227 228 TEST_CASE("ARPChanges_MSIX_Ignored", "[ARPChanges][workflow]") 229 { 230 TestHeuristicOverride heuristicOverride; 231 ARPTestContext context(Manifest::InstallerTypeEnum::Msix); 232 233 context << SnapshotARPEntries; 234 235 REQUIRE(!context.Contains(Data::ARPCorrelationData)); 236 237 context << ReportARPChanges; 238 239 REQUIRE(!context.Logger->WasLogSuccessfulInstallARPChangeCalled); 240 } 241 242 TEST_CASE("ARPChanges_CheckSnapshot", "[ARPChanges][workflow]") 243 { 244 TestHeuristicOverride heuristicOverride; 245 ARPTestContext context; 246 247 context << SnapshotARPEntries; 248 249 REQUIRE(context.Contains(Data::ARPCorrelationData)); 250 251 auto snapshot = context.Get<Data::ARPCorrelationData>().GetPreInstallSnapshot(); 252 253 REQUIRE(context.EverythingResult.Matches.size() == snapshot.size()); 254 255 // Destructively match 256 for (const auto& match : context.EverythingResult.Matches) 257 { 258 bool found = false; 259 for (auto itr = snapshot.begin(); itr != snapshot.end(); ++itr) 260 { 261 if (match.Package->GetProperty(PackageProperty::Id) == std::get<0>(*itr)) 262 { 263 REQUIRE(GetInstalledVersion(match.Package)->GetProperty(PackageVersionProperty::Version) == std::get<1>(*itr)); 264 REQUIRE(GetInstalledVersion(match.Package)->GetProperty(PackageVersionProperty::Channel) == std::get<2>(*itr)); 265 266 snapshot.erase(itr); 267 found = true; 268 break; 269 } 270 } 271 REQUIRE(found); 272 } 273 274 REQUIRE(snapshot.empty()); 275 } 276 277 TEST_CASE("ARPChanges_NoChange_NoMatch", "[ARPChanges][workflow]") 278 { 279 TestHeuristicOverride heuristicOverride; 280 ARPTestContext context; 281 282 context << SnapshotARPEntries; 283 REQUIRE(context.Contains(Data::ARPCorrelationData)); 284 285 context << ReportARPChanges; 286 context.ExpectEvent(0, 0, 0); 287 } 288 289 TEST_CASE("ARPChanges_NoChange_SingleMatch", "[ARPChanges][workflow]") 290 { 291 TestHeuristicOverride heuristicOverride; 292 ARPTestContext context; 293 294 context << SnapshotARPEntries; 295 REQUIRE(context.Contains(Data::ARPCorrelationData)); 296 297 context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); 298 299 context << ReportARPChanges; 300 context.ExpectEvent(0, 1, 0, context.MatchResult.Matches[0].Package); 301 } 302 303 TEST_CASE("ARPChanges_NoChange_MultiMatch", "[ARPChanges][workflow]") 304 { 305 TestHeuristicOverride heuristicOverride; 306 ARPTestContext context; 307 308 context << SnapshotARPEntries; 309 REQUIRE(context.Contains(Data::ARPCorrelationData)); 310 311 context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); 312 context.AddMatchResult("MatchId2", "MatchName2", "MatchPublisher2", "MatchVersion2"); 313 314 context << ReportARPChanges; 315 context.ExpectEvent(0, 2, 0); 316 } 317 318 TEST_CASE("ARPChanges_SingleChange_NoMatch", "[ARPChanges][workflow]") 319 { 320 TestHeuristicOverride heuristicOverride; 321 ARPTestContext context; 322 323 context << SnapshotARPEntries; 324 REQUIRE(context.Contains(Data::ARPCorrelationData)); 325 326 context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); 327 328 context << ReportARPChanges; 329 context.ExpectEvent(1, 0, 0); 330 } 331 332 TEST_CASE("ARPChanges_SingleChange_SingleMatch", "[ARPChanges][workflow]") 333 { 334 TestHeuristicOverride heuristicOverride; 335 ARPTestContext context; 336 337 context << SnapshotARPEntries; 338 REQUIRE(context.Contains(Data::ARPCorrelationData)); 339 340 context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); 341 context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); 342 343 context << ReportARPChanges; 344 context.ExpectEvent(1, 1, 0, context.MatchResult.Matches.back().Package); 345 } 346 347 TEST_CASE("ARPChanges_SingleChange_MultiMatch", "[ARPChanges][workflow]") 348 { 349 TestHeuristicOverride heuristicOverride; 350 ARPTestContext context; 351 352 context << SnapshotARPEntries; 353 REQUIRE(context.Contains(Data::ARPCorrelationData)); 354 355 context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); 356 context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); 357 context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); 358 359 context << ReportARPChanges; 360 context.ExpectEvent(1, 2, 1, context.EverythingResult.Matches.back().Package); 361 } 362 363 TEST_CASE("ARPChanges_MultiChange_NoMatch", "[ARPChanges][workflow]") 364 { 365 TestHeuristicOverride heuristicOverride; 366 ARPTestContext context; 367 368 context << SnapshotARPEntries; 369 REQUIRE(context.Contains(Data::ARPCorrelationData)); 370 371 context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); 372 context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); 373 374 context << ReportARPChanges; 375 context.ExpectEvent(2, 0, 0); 376 } 377 378 TEST_CASE("ARPChanges_MultiChange_SingleMatch_NoOverlap", "[ARPChanges][workflow]") 379 { 380 TestHeuristicOverride heuristicOverride; 381 ARPTestContext context; 382 383 context << SnapshotARPEntries; 384 REQUIRE(context.Contains(Data::ARPCorrelationData)); 385 386 context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); 387 context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); 388 context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); 389 390 context << ReportARPChanges; 391 context.ExpectEvent(2, 1, 0, context.MatchResult.Matches.back().Package); 392 } 393 394 TEST_CASE("ARPChanges_MultiChange_SingleMatch_Overlap", "[ARPChanges][workflow]") 395 { 396 TestHeuristicOverride heuristicOverride; 397 ARPTestContext context; 398 399 context << SnapshotARPEntries; 400 REQUIRE(context.Contains(Data::ARPCorrelationData)); 401 402 context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); 403 context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); 404 context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); 405 406 context << ReportARPChanges; 407 context.ExpectEvent(2, 1, 1, context.MatchResult.Matches.back().Package); 408 } 409 410 TEST_CASE("ARPChanges_MultiChange_MultiMatch_NoOverlap", "[ARPChanges][workflow]") 411 { 412 TestHeuristicOverride heuristicOverride; 413 ARPTestContext context; 414 415 context << SnapshotARPEntries; 416 REQUIRE(context.Contains(Data::ARPCorrelationData)); 417 418 context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); 419 context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); 420 context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); 421 context.AddMatchResult("MatchId2", "MatchName2", "MatchPublisher2", "MatchVersion2"); 422 423 context << ReportARPChanges; 424 context.ExpectEvent(2, 2, 0); 425 } 426 427 TEST_CASE("ARPChanges_MultiChange_MultiMatch_SingleOverlap", "[ARPChanges][workflow]") 428 { 429 TestHeuristicOverride heuristicOverride; 430 ARPTestContext context; 431 432 context << SnapshotARPEntries; 433 REQUIRE(context.Contains(Data::ARPCorrelationData)); 434 435 context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); 436 context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); 437 context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); 438 context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); 439 440 context << ReportARPChanges; 441 context.ExpectEvent(2, 2, 1, context.MatchResult.Matches.back().Package); 442 } 443 444 TEST_CASE("ARPChanges_MultiChange_MultiMatch_MultiOverlap", "[ARPChanges][workflow]") 445 { 446 TestHeuristicOverride heuristicOverride; 447 ARPTestContext context; 448 449 context << SnapshotARPEntries; 450 REQUIRE(context.Contains(Data::ARPCorrelationData)); 451 452 context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); 453 context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); 454 context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); 455 context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); 456 457 context << ReportARPChanges; 458 context.ExpectEvent(2, 2, 2); 459 }