diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 020afbeebe..e8d7a2fc29 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -221,6 +221,7 @@ IIS ILogger IManifest impl +inapplicabilities Inet inheritdoc inno diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 5851cace03..09629e5437 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -338,6 +338,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(UpdateNotApplicable); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnology); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnologyInNewerVersions); WINGET_DEFINE_RESOURCE_STRINGID(Usage); WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandReportDependencies); diff --git a/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp b/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp index d1540a0a70..271e33cf13 100644 --- a/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp +++ b/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp @@ -74,8 +74,6 @@ namespace AppInstaller::CLI::Workflow return DependencyNodeProcessorResult::Error; } - std::optional installer; - IPackageVersion::Metadata installationMetadata; if (m_nodePackageInstalledVersion) { @@ -83,7 +81,7 @@ namespace AppInstaller::CLI::Workflow } ManifestComparator manifestComparator(m_context, installationMetadata); - installer = manifestComparator.GetPreferredInstaller(m_nodeManifest); + auto [installer, inapplicabilities] = manifestComparator.GetPreferredInstaller(m_nodeManifest); if (!installer.has_value()) { diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp b/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp index 4deaaa054e..213599b9f8 100644 --- a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp +++ b/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp @@ -26,9 +26,14 @@ namespace AppInstaller::CLI::Workflow { OSVersionFilter() : details::FilterField("OS Version") {} - bool IsApplicable(const Manifest::ManifestInstaller& installer) override + InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override { - return installer.MinOSVersion.empty() || Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version(installer.MinOSVersion)); + if (installer.MinOSVersion.empty() || Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version(installer.MinOSVersion))) + { + return InapplicabilityFlags::None; + } + + return InapplicabilityFlags::OSVersion; } std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override @@ -97,9 +102,14 @@ namespace AppInstaller::CLI::Workflow return std::make_unique(); } - bool IsApplicable(const Manifest::ManifestInstaller& installer) override + InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override { - return CheckAllowedArchitecture(installer.Arch) != Utility::InapplicableArchitecture; + if (CheckAllowedArchitecture(installer.Arch) == Utility::InapplicableArchitecture) + { + return InapplicabilityFlags::MachineArchitecture; + } + + return InapplicabilityFlags::None; } std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override @@ -182,9 +192,14 @@ namespace AppInstaller::CLI::Workflow return {}; } - bool IsApplicable(const Manifest::ManifestInstaller& installer) override + InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override { - return Manifest::IsInstallerTypeCompatible(installer.InstallerType, m_installedType); + if (Manifest::IsInstallerTypeCompatible(installer.InstallerType, m_installedType)) + { + return InapplicabilityFlags::None; + } + + return InapplicabilityFlags::InstalledType; } std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override @@ -224,10 +239,15 @@ namespace AppInstaller::CLI::Workflow return {}; } - bool IsApplicable(const Manifest::ManifestInstaller& installer) override + InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override { // We have to assume the unknown scope will match our required scope, or the entire catalog would stop working for upgrade. - return installer.Scope == Manifest::ScopeEnum::Unknown || installer.Scope == m_requirement; + if (installer.Scope == Manifest::ScopeEnum::Unknown || installer.Scope == m_requirement) + { + return InapplicabilityFlags::None; + } + + return InapplicabilityFlags::InstalledScope; } std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override @@ -275,9 +295,14 @@ namespace AppInstaller::CLI::Workflow } } - bool IsApplicable(const Manifest::ManifestInstaller& installer) override + InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override { - return m_requirement == Manifest::ScopeEnum::Unknown || installer.Scope == m_requirement; + if (m_requirement == Manifest::ScopeEnum::Unknown || installer.Scope == m_requirement) + { + return InapplicabilityFlags::None; + } + + return InapplicabilityFlags::Scope; } std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override @@ -328,10 +353,16 @@ namespace AppInstaller::CLI::Workflow return {}; } - bool IsApplicable(const Manifest::ManifestInstaller& installer) override + InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override { // We have to assume an unknown installer locale will match our installed locale, or the entire catalog would stop working for upgrade. - return installer.Locale.empty() || Locale::GetDistanceOfLanguage(m_installedLocale, installer.Locale) >= Locale::MinimumDistanceScoreAsCompatibleMatch; + if (installer.Locale.empty() || + Locale::GetDistanceOfLanguage(m_installedLocale, installer.Locale) >= Locale::MinimumDistanceScoreAsCompatibleMatch) + { + return InapplicabilityFlags::None; + } + + return InapplicabilityFlags::InstalledLocale; } std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override @@ -397,22 +428,22 @@ namespace AppInstaller::CLI::Workflow } } - bool IsApplicable(const Manifest::ManifestInstaller& installer) override + InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override { if (m_requirement.empty()) { - return true; + return InapplicabilityFlags::None; } for (auto const& requiredLocale : m_requirement) { if (Locale::GetDistanceOfLanguage(requiredLocale, installer.Locale) >= Locale::MinimumDistanceScoreAsPerfectMatch) { - return true; + return InapplicabilityFlags::None; } } - return false; + return InapplicabilityFlags::Locale; } std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override @@ -502,24 +533,33 @@ namespace AppInstaller::CLI::Workflow AddComparator(MachineArchitectureComparator::Create(context, installationMetadata)); } - std::optional ManifestComparator::GetPreferredInstaller(const Manifest::Manifest& manifest) + InstallerAndInapplicabilities ManifestComparator::GetPreferredInstaller(const Manifest::Manifest& manifest) { AICLI_LOG(CLI, Info, << "Starting installer selection."); const Manifest::ManifestInstaller* result = nullptr; + std::vector inapplicabilitiesInstallers; for (const auto& installer : manifest.Installers) { - if (IsApplicable(installer) && (!result || IsFirstBetter(installer, *result))) + auto inapplicabilityInstaller = IsApplicable(installer); + if (inapplicabilityInstaller == InapplicabilityFlags::None) { - AICLI_LOG(CLI, Verbose, << "Installer " << installer << " is current best choice"); - result = &installer; + if (!result || IsFirstBetter(installer, *result)) + { + AICLI_LOG(CLI, Verbose, << "Installer " << installer << " is current best choice"); + result = &installer; + } + } + else + { + inapplicabilitiesInstallers.push_back(inapplicabilityInstaller); } } if (!result) { - return {}; + return { {}, std::move(inapplicabilitiesInstallers) }; } Logging::Telemetry().LogSelectedInstaller( @@ -529,22 +569,24 @@ namespace AppInstaller::CLI::Workflow Manifest::ScopeToString(result->Scope), result->Locale); - return *result; + return { *result, std::move(inapplicabilitiesInstallers) }; } - // TODO: Implement a mechanism for better error messaging for no applicable installer scenario - bool ManifestComparator::IsApplicable(const Manifest::ManifestInstaller& installer) + InapplicabilityFlags ManifestComparator::IsApplicable(const Manifest::ManifestInstaller& installer) { + InapplicabilityFlags inapplicabilityResult = InapplicabilityFlags::None; + for (const auto& filter : m_filters) { - if (!filter->IsApplicable(installer)) + auto inapplicability = filter->IsApplicable(installer); + if (inapplicability != InapplicabilityFlags::None) { AICLI_LOG(CLI, Info, << "Installer " << installer << " not applicable: " << filter->ExplainInapplicable(installer)); - return false; + WI_SetAllFlags(inapplicabilityResult, inapplicability); } } - return true; + return inapplicabilityResult; } bool ManifestComparator::IsFirstBetter( diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.h b/src/AppInstallerCLICore/Workflows/ManifestComparator.h index 6b04479bac..d08d5c988b 100644 --- a/src/AppInstallerCLICore/Workflows/ManifestComparator.h +++ b/src/AppInstallerCLICore/Workflows/ManifestComparator.h @@ -13,6 +13,21 @@ namespace AppInstaller::CLI::Workflow { + // Flags to indicate why an installer was not applicable + enum class InapplicabilityFlags : int + { + None = 0x0, + OSVersion = 0x1, + InstalledScope = 0x2, + InstalledType = 0x4, + InstalledLocale = 0x8, + Locale = 0x10, + Scope = 0x20, + MachineArchitecture = 0x40, + }; + + DEFINE_ENUM_FLAG_OPERATORS(InapplicabilityFlags); + namespace details { // An interface for defining new filters based on user inputs. @@ -25,7 +40,7 @@ namespace AppInstaller::CLI::Workflow std::string_view Name() const { return m_name; } // Determines if the installer is applicable based on this field alone. - virtual bool IsApplicable(const Manifest::ManifestInstaller& installer) = 0; + virtual InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) = 0; // Explains why the filter regarded this installer as inapplicable. // Will only be called when IsApplicable returns false. @@ -47,16 +62,22 @@ namespace AppInstaller::CLI::Workflow }; } + struct InstallerAndInapplicabilities + { + std::optional installer; + std::vector inapplicabilitiesInstaller; + }; + // Class in charge of comparing manifest entries struct ManifestComparator { ManifestComparator(const Execution::Context& context, const Repository::IPackageVersion::Metadata& installationMetadata); // Gets the best installer from the manifest, if at least one is applicable. - std::optional GetPreferredInstaller(const Manifest::Manifest& manifest); + InstallerAndInapplicabilities GetPreferredInstaller(const Manifest::Manifest& manifest); // Determines if an installer is applicable. - bool IsApplicable(const Manifest::ManifestInstaller& installer); + InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer); // Determines if the first installer is a better choice. bool IsFirstBetter( diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index 4197e7f298..f1b18fe5da 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -42,6 +42,7 @@ namespace AppInstaller::CLI::Workflow Utility::Version installedVersion = Utility::Version(installedPackage->GetProperty(PackageVersionProperty::Version)); ManifestComparator manifestComparator(context, installedPackage->GetMetadata()); bool updateFound = false; + bool installedTypeInapplicable = false; // The version keys should have already been sorted by version const auto& versionKeys = package->GetAvailableVersionKeys(); @@ -54,9 +55,16 @@ namespace AppInstaller::CLI::Workflow auto manifest = packageVersion->GetManifest(); // Check applicable Installer - auto installer = manifestComparator.GetPreferredInstaller(manifest); + auto [installer, inapplicabilities] = manifestComparator.GetPreferredInstaller(manifest); if (!installer.has_value()) { + // If there is at least one installer whose only reason is InstalledType. + auto onlyInstalledType = std::find(inapplicabilities.begin(), inapplicabilities.end(), InapplicabilityFlags::InstalledType); + if (onlyInstalledType != inapplicabilities.end()) + { + installedTypeInapplicable = true; + } + continue; } @@ -80,8 +88,16 @@ namespace AppInstaller::CLI::Workflow { if (m_reportUpdateNotFound) { - context.Reporter.Info() << Resource::String::UpdateNotApplicable << std::endl; + if (installedTypeInapplicable) + { + context.Reporter.Info() << Resource::String::UpgradeDifferentInstallTechnologyInNewerVersions << std::endl; + } + else + { + context.Reporter.Info() << Resource::String::UpdateNotApplicable << std::endl; + } } + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); } } diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 97dc60e6ef..8b26adb99e 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -971,7 +971,19 @@ namespace AppInstaller::CLI::Workflow context.Add({ Utility::ConvertToArchitectureEnum(std::string(context.Args.GetArg(Execution::Args::Type::InstallArchitecture))) }); } ManifestComparator manifestComparator(context, installationMetadata); - context.Add(manifestComparator.GetPreferredInstaller(context.Get())); + auto [installer, inapplicabilities] = manifestComparator.GetPreferredInstaller(context.Get()); + + if (!installer.has_value()) + { + auto onlyInstalledType = std::find(inapplicabilities.begin(), inapplicabilities.end(), InapplicabilityFlags::InstalledType); + if (onlyInstalledType != inapplicabilities.end()) + { + context.Reporter.Info() << Resource::String::UpgradeDifferentInstallTechnology << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); + } + } + + context.Add(installer); } void EnsureRunningAsAdmin(Execution::Context& context) diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 2339e09376..6b2916f82c 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1237,6 +1237,12 @@ Please specify one of them using the `--source` option to proceed. The requested number of results must be between 1 and 1000. + + A newer version was found, but the install technology is different from the current version installed. Please uninstall the package and install the newer version. + + + The install technology of the newer version specified is different from the current version installed. Please uninstall the package and install the newer version. + Windows Package Manager (Preview) The product name plus an indicator that this is a pre-release version. diff --git a/src/AppInstallerCLITests/ManifestComparator.cpp b/src/AppInstallerCLITests/ManifestComparator.cpp index d5e1037bd6..9a41b98a34 100644 --- a/src/AppInstallerCLITests/ManifestComparator.cpp +++ b/src/AppInstallerCLITests/ManifestComparator.cpp @@ -48,15 +48,26 @@ void RequireInstaller(const std::optional& actual, const Mani REQUIRE(actual->Locale == expected.Locale); } +void RequireInapplicabilities(const std::vector& inapplicabilities, const std::vector& expected) +{ + REQUIRE(inapplicabilities.size() == expected.size()); + + for (std::size_t i = 0; i < inapplicabilities.size(); i++) + { + REQUIRE(inapplicabilities[i] == expected[i]); + } +} + TEST_CASE("ManifestComparator_OSFilter_Low", "[manifest_comparator]") { Manifest manifest; AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Exe, ScopeEnum::Unknown, "10.0.99999.0"); ManifestComparator mc(ManifestComparatorTestContext{}, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(!result); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::OSVersion }); } TEST_CASE("ManifestComparator_OSFilter_High", "[manifest_comparator]") @@ -65,9 +76,10 @@ TEST_CASE("ManifestComparator_OSFilter_High", "[manifest_comparator]") ManifestInstaller expected = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Exe, ScopeEnum::Unknown, "10.0.0.0"); ManifestComparator mc(ManifestComparatorTestContext{}, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, expected); + REQUIRE(inapplicabilities.size() == 0); } TEST_CASE("ManifestComparator_InstalledScopeFilter_Uknown", "[manifest_comparator]") @@ -78,10 +90,11 @@ TEST_CASE("ManifestComparator_InstalledScopeFilter_Uknown", "[manifest_comparato SECTION("Nothing Installed") { ManifestComparator mc(ManifestComparatorTestContext{}, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); // Only because it is first RequireInstaller(result, unknown); + REQUIRE(inapplicabilities.size() == 0); } SECTION("User Installed") { @@ -89,9 +102,10 @@ TEST_CASE("ManifestComparator_InstalledScopeFilter_Uknown", "[manifest_comparato metadata[PackageVersionMetadata::InstalledScope] = ScopeToString(ScopeEnum::User); ManifestComparator mc(ManifestComparatorTestContext{}, metadata); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, unknown); + REQUIRE(inapplicabilities.size() == 0); } SECTION("Machine Installed") { @@ -99,9 +113,10 @@ TEST_CASE("ManifestComparator_InstalledScopeFilter_Uknown", "[manifest_comparato metadata[PackageVersionMetadata::InstalledScope] = ScopeToString(ScopeEnum::Machine); ManifestComparator mc(ManifestComparatorTestContext{}, metadata); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, unknown); + REQUIRE(inapplicabilities.size() == 0); } } @@ -114,10 +129,11 @@ TEST_CASE("ManifestComparator_InstalledScopeFilter", "[manifest_comparator]") SECTION("Nothing Installed") { ManifestComparator mc(ManifestComparatorTestContext{}, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); // Only because it is first RequireInstaller(result, user); + REQUIRE(inapplicabilities.size() == 0); } SECTION("User Installed") { @@ -125,9 +141,10 @@ TEST_CASE("ManifestComparator_InstalledScopeFilter", "[manifest_comparator]") metadata[PackageVersionMetadata::InstalledScope] = ScopeToString(ScopeEnum::User); ManifestComparator mc(ManifestComparatorTestContext{}, metadata); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, user); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledScope }); } SECTION("Machine Installed") { @@ -135,9 +152,10 @@ TEST_CASE("ManifestComparator_InstalledScopeFilter", "[manifest_comparator]") metadata[PackageVersionMetadata::InstalledScope] = ScopeToString(ScopeEnum::Machine); ManifestComparator mc(ManifestComparatorTestContext{}, metadata); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, machine); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledScope }); } } @@ -150,10 +168,11 @@ TEST_CASE("ManifestComparator_InstalledTypeFilter", "[manifest_comparator]") SECTION("Nothing Installed") { ManifestComparator mc(ManifestComparatorTestContext{}, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); // Only because it is first RequireInstaller(result, msi); + REQUIRE(inapplicabilities.size() == 0); } SECTION("MSI Installed") { @@ -161,9 +180,10 @@ TEST_CASE("ManifestComparator_InstalledTypeFilter", "[manifest_comparator]") metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Msi); ManifestComparator mc(ManifestComparatorTestContext{}, metadata); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, msi); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledType }); } SECTION("MSIX Installed") { @@ -171,9 +191,10 @@ TEST_CASE("ManifestComparator_InstalledTypeFilter", "[manifest_comparator]") metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Msix); ManifestComparator mc(ManifestComparatorTestContext{}, metadata); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, msix); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledType }); } } @@ -186,10 +207,11 @@ TEST_CASE("ManifestComparator_InstalledTypeCompare", "[manifest_comparator]") SECTION("Nothing Installed") { ManifestComparator mc(ManifestComparatorTestContext{}, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); // Only because it is first RequireInstaller(result, burn); + REQUIRE(inapplicabilities.size() == 0); } SECTION("Exe Installed") { @@ -197,9 +219,10 @@ TEST_CASE("ManifestComparator_InstalledTypeCompare", "[manifest_comparator]") metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Exe); ManifestComparator mc(ManifestComparatorTestContext{}, metadata); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, exe); + REQUIRE(inapplicabilities.size() == 0); } SECTION("Inno Installed") { @@ -207,9 +230,10 @@ TEST_CASE("ManifestComparator_InstalledTypeCompare", "[manifest_comparator]") metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Inno); ManifestComparator mc(ManifestComparatorTestContext{}, metadata); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, burn); + REQUIRE(inapplicabilities.size() == 0); } } @@ -222,10 +246,11 @@ TEST_CASE("ManifestComparator_ScopeFilter", "[manifest_comparator]") SECTION("Nothing Required") { ManifestComparator mc(ManifestComparatorTestContext{}, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); // Only because it is first RequireInstaller(result, user); + REQUIRE(inapplicabilities.size() == 0); } SECTION("User Required") { @@ -233,9 +258,10 @@ TEST_CASE("ManifestComparator_ScopeFilter", "[manifest_comparator]") context.Args.AddArg(Args::Type::InstallScope, ScopeToString(ScopeEnum::User)); ManifestComparator mc(context, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, user); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Scope }); } SECTION("Machine Required") { @@ -243,9 +269,10 @@ TEST_CASE("ManifestComparator_ScopeFilter", "[manifest_comparator]") context.Args.AddArg(Args::Type::InstallScope, ScopeToString(ScopeEnum::Machine)); ManifestComparator mc(context, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, machine); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Scope }); } } @@ -258,10 +285,11 @@ TEST_CASE("ManifestComparator_ScopeCompare", "[manifest_comparator]") SECTION("No Preference") { ManifestComparator mc(ManifestComparatorTestContext{}, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); // The default preference is user RequireInstaller(result, user); + REQUIRE(inapplicabilities.size() == 0); } SECTION("User Preference") { @@ -269,9 +297,10 @@ TEST_CASE("ManifestComparator_ScopeCompare", "[manifest_comparator]") settings.Set(ScopePreference::User); ManifestComparator mc(ManifestComparatorTestContext{}, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, user); + REQUIRE(inapplicabilities.size() == 0); } SECTION("Machine Preference") { @@ -279,9 +308,10 @@ TEST_CASE("ManifestComparator_ScopeCompare", "[manifest_comparator]") settings.Set(ScopePreference::Machine); ManifestComparator mc(ManifestComparatorTestContext{}, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, machine); + REQUIRE(inapplicabilities.size() == 0); } } @@ -297,10 +327,11 @@ TEST_CASE("ManifestComparator_InstalledLocaleComparator_Uknown", "[manifest_comp settings.Set({ "en-US" }); ManifestComparator mc(ManifestComparatorTestContext{}, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); // Only because it is first RequireInstaller(result, enGB); + REQUIRE(inapplicabilities.size() == 0); } SECTION("en-US Installed") { @@ -308,9 +339,10 @@ TEST_CASE("ManifestComparator_InstalledLocaleComparator_Uknown", "[manifest_comp metadata[PackageVersionMetadata::InstalledLocale] = "en-US"; ManifestComparator mc(ManifestComparatorTestContext{}, metadata); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, enGB); + REQUIRE(inapplicabilities.size() == 0); } SECTION("zh-CN Installed") { @@ -318,9 +350,10 @@ TEST_CASE("ManifestComparator_InstalledLocaleComparator_Uknown", "[manifest_comp metadata[PackageVersionMetadata::InstalledLocale] = "zh-CN"; ManifestComparator mc(ManifestComparatorTestContext{}, metadata); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, unknown); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledLocale }); } } @@ -336,10 +369,11 @@ TEST_CASE("ManifestComparator_InstalledLocaleComparator", "[manifest_comparator] settings.Set({ "en-US" }); ManifestComparator mc(ManifestComparatorTestContext{}, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); // Only because it is first RequireInstaller(result, enGB); + REQUIRE(inapplicabilities.size() == 0); } SECTION("en-US Installed") { @@ -347,9 +381,10 @@ TEST_CASE("ManifestComparator_InstalledLocaleComparator", "[manifest_comparator] metadata[PackageVersionMetadata::InstalledLocale] = "en-US"; ManifestComparator mc(ManifestComparatorTestContext{}, metadata); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, enGB); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledLocale }); } SECTION("zh-CN Installed") { @@ -357,9 +392,10 @@ TEST_CASE("ManifestComparator_InstalledLocaleComparator", "[manifest_comparator] metadata[PackageVersionMetadata::InstalledLocale] = "zh-CN"; ManifestComparator mc(ManifestComparatorTestContext{}, metadata); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(!result); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledLocale, InapplicabilityFlags::InstalledLocale }); } } @@ -376,9 +412,10 @@ TEST_CASE("ManifestComparator_LocaleComparator", "[manifest_comparator]") context.Args.AddArg(Args::Type::Locale, "en-GB"s); ManifestComparator mc(context, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, enGB); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Locale , InapplicabilityFlags::Locale }); } SECTION("zh-CN Required") { @@ -386,9 +423,10 @@ TEST_CASE("ManifestComparator_LocaleComparator", "[manifest_comparator]") context.Args.AddArg(Args::Type::Locale, "zh-CN"s); ManifestComparator mc(context, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(!result); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Locale , InapplicabilityFlags::Locale, InapplicabilityFlags::Locale }); } SECTION("en-US Preference") { @@ -396,9 +434,10 @@ TEST_CASE("ManifestComparator_LocaleComparator", "[manifest_comparator]") settings.Set({ "en-US" }); ManifestComparator mc(ManifestComparatorTestContext{}, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, enGB); + REQUIRE(inapplicabilities.size() == 0); } SECTION("zh-CN Preference") { @@ -406,9 +445,10 @@ TEST_CASE("ManifestComparator_LocaleComparator", "[manifest_comparator]") settings.Set({ "zh-CN" }); ManifestComparator mc(ManifestComparatorTestContext{}, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, unknown); + REQUIRE(inapplicabilities.size() == 0); } } @@ -421,9 +461,10 @@ TEST_CASE("ManifestComparator_AllowedArchitecture_x64_only", "[manifest_comparat context.Add({ Architecture::X64 }); ManifestComparator mc(context, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(!result); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::MachineArchitecture }); } TEST_CASE("ManifestComparator_AllowedArchitecture", "[manifest_comparator]") @@ -440,9 +481,10 @@ TEST_CASE("ManifestComparator_AllowedArchitecture", "[manifest_comparator]") context.Add({ Architecture::X86, Architecture::X64 }); ManifestComparator mc(context, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, x86); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::MachineArchitecture, InapplicabilityFlags::MachineArchitecture }); } SECTION("Unknown") { @@ -450,10 +492,11 @@ TEST_CASE("ManifestComparator_AllowedArchitecture", "[manifest_comparator]") context.Add({ Architecture::Unknown }); ManifestComparator mc(context, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(result); REQUIRE(result->Arch == GetApplicableArchitectures()[0]); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::MachineArchitecture, InapplicabilityFlags::MachineArchitecture }); } SECTION("x86 and Unknown") { @@ -461,8 +504,31 @@ TEST_CASE("ManifestComparator_AllowedArchitecture", "[manifest_comparator]") context.Add({ Architecture::X86, Architecture::Unknown }); ManifestComparator mc(context, {}); - auto result = mc.GetPreferredInstaller(manifest); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, x86); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::MachineArchitecture, InapplicabilityFlags::MachineArchitecture }); } } + +TEST_CASE("ManifestComparator_Inapplicabilities", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller installer = AddInstaller(manifest, Architecture::Arm64, InstallerTypeEnum::Exe, ScopeEnum::User, "10.0.99999.0", "es-MX"); + + ManifestComparatorTestContext context; + context.Add({ Architecture::X86 }); + context.Args.AddArg(Args::Type::InstallScope, ScopeToString(ScopeEnum::Machine)); + context.Args.AddArg(Args::Type::Locale, "en-GB"s); + + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Msix); + + ManifestComparator mc(context, metadata); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + REQUIRE(!result); + RequireInapplicabilities( + inapplicabilities, + { InapplicabilityFlags::OSVersion | InapplicabilityFlags::InstalledType | InapplicabilityFlags::Locale | InapplicabilityFlags::Scope | InapplicabilityFlags::MachineArchitecture }); +} diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index 4779e918ad..a42c1fab2d 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -1434,7 +1434,27 @@ TEST_CASE("UpdateFlow_UpdateExeInstallerTypeNotApplicable", "[UpdateFlow][workfl // Verify Installer is not called. REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpdateNotApplicable).get()) != std::string::npos); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeDifferentInstallTechnologyInNewerVersions).get()) != std::string::npos); + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); +} + +TEST_CASE("UpdateFlow_UpdateExeInstallerTypeNotApplicableSpecificVersion", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + OverrideForCompositeInstalledSource(context); + context.Args.AddArg(Execution::Args::Type::Query, "TestExeInstallerWithIncompatibleInstallerType"sv); + context.Args.AddArg(Execution::Args::Type::Version, "2.0.0.0"sv); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installer is not called. + REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeDifferentInstallTechnology).get()) != std::string::npos); REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); }