winget-cli

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

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 }