diff --git a/dev/ApplicationData/M.W.S.ApplicationData.cpp b/dev/ApplicationData/M.W.S.ApplicationData.cpp index fb379d6a25..10eeeadac6 100644 --- a/dev/ApplicationData/M.W.S.ApplicationData.cpp +++ b/dev/ApplicationData/M.W.S.ApplicationData.cpp @@ -20,7 +20,7 @@ #include // 61684930: [2.0 servicing] WindowsAppSDK: Microsoft.Windows.Storage.ApplicationData.GetForUnpackaged() -#define WINAPPSDK_CHANGEID_61684930 61684930, WinAppSDK_2_1_5 +#define WINAPPSDK_CHANGEID_61684930 61684930 static_assert(static_cast(winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Local) == static_cast(winrt::Windows::Storage::ApplicationDataLocality::Local)); static_assert(static_cast(winrt::Microsoft::Windows::Storage::ApplicationDataLocality::LocalCache) == static_cast(winrt::Windows::Storage::ApplicationDataLocality::LocalCache)); diff --git a/dev/MRTCore/mrt/Microsoft.Windows.ApplicationModel.Resources/src/Helper.cpp b/dev/MRTCore/mrt/Microsoft.Windows.ApplicationModel.Resources/src/Helper.cpp index 6b79268695..37ede14e27 100644 --- a/dev/MRTCore/mrt/Microsoft.Windows.ApplicationModel.Resources/src/Helper.cpp +++ b/dev/MRTCore/mrt/Microsoft.Windows.ApplicationModel.Resources/src/Helper.cpp @@ -7,7 +7,7 @@ #include // Bug 62382643: Fix sparse-packaged apps unable to discover module-specific PRI files -#define WINAPPSDK_CHANGEID_62382643 62382643, WinAppSDK_2_1_5 +#define WINAPPSDK_CHANGEID_62382643 62382643 bool IsResourceNotFound(HRESULT hr) { diff --git a/dev/RuntimeCompatibilityOptions/RuntimeCompatibilityOptions.cpp b/dev/RuntimeCompatibilityOptions/RuntimeCompatibilityOptions.cpp index 65310ff775..7005935266 100644 --- a/dev/RuntimeCompatibilityOptions/RuntimeCompatibilityOptions.cpp +++ b/dev/RuntimeCompatibilityOptions/RuntimeCompatibilityOptions.cpp @@ -6,6 +6,193 @@ #include "Microsoft.Windows.ApplicationModel.WindowsAppRuntime.RuntimeCompatibilityOptions.g.cpp" #include "AssemblyInfo.h" #include +#include + +namespace +{ + // Per-release groups of WinAppSDK contained change IDs. Each group's + // releaseVersion is the patch release in which the contained change first + // shipped. RuntimeCompatibilityOptions::Apply() walks the active catalog at + // startup and pre-prunes (adds to the disabled list) any group whose + // releaseVersion is greater than the runtime's effective patch level. + // + // Release engineering: at branch cut, add a new release group to + // s_catalogGroupsProd below for any new WINAPPSDK_CHANGE_* IDs that landed + // in os.2020 FrameworkUdk Containment.h since the previous branch. + // + // Servicing engineers do NOT edit this file. They only add the + // #define WINAPPSDK_CHANGE_FOO id macro in their component source. + // Release engineering performs the catalog roll-up. + // + // The WinAppSDK_Security pseudo-version is reserved for security fixes - + // entries in a security group are catalog-tracked but are never + // auto-disabled by Apply's pre-prune walk because their releaseVersion (0) + // is never greater than any effective patch level. + // + // WinAppSDKReleaseVersion is defined locally here (rather than alongside + // FrameworkUdk's Containment.h enum) to break the per-release maintenance + // coupling between FrUdk and Foundation. It is named WinAppSDKReleaseVersion + // rather than WinAppSDKPatchVersion to avoid colliding with the legacy + // unscoped enum type name still exposed by older Containment.h headers + // (renamed there to WinAppSDKPatchVersionDeprecated in newer FrUdk + // revisions). The scoped enum keeps all release-version constants + // (WinAppSDK_Latest, WinAppSDK_Security, and per-release WinAppSDK_M_m_p + // values added by release engineering as the catalog grows) confined to + // this translation unit; no other component should reference them. + // WinAppSDKReleaseVersionFromValues mirrors the historical + // WinAppSDKPatchVersionFromValues encoding (major*1000000 + minor*1000 + patch) + // so the WindowsAppRuntimeVersion -> WinAppSDKReleaseVersion conversion + // is consistent with the encoding release engineering uses when adding + // new per-release enumerators below. + constexpr UINT32 WinAppSDKReleaseVersionFromValues(UINT32 major, UINT32 minor, UINT32 patch) + { + return (major * 1000000) + (minor * 1000) + patch; + } + + enum class WinAppSDKReleaseVersion : UINT32 + { + WinAppSDK_2_1_0 = WinAppSDKReleaseVersionFromValues(2, 1, 0), + WinAppSDK_2_1_5 = WinAppSDKReleaseVersionFromValues(2, 1, 5), + WinAppSDK_2_3_0 = WinAppSDKReleaseVersionFromValues(2, 3, 0), + WinAppSDK_Latest = 999999999, + WinAppSDK_Security = 0, + }; + + struct CatalogGroup + { + const UINT32* changes; + UINT32 count; + WinAppSDKReleaseVersion releaseVersion; + }; + + template + constexpr CatalogGroup MakeGroup(const UINT32(&arr)[N], WinAppSDKReleaseVersion v) + { + return { arr, static_cast(N), v }; + } + + // Test-only catalog. Activated by ContainmentTestInitialize(true). Holds + // two groups whose releaseVersion straddles the runtime's effective + // patch level under the default WinAppSDKRuntimeConfiguration (Apply + // resolves an unspecified patch level to WinAppSDKReleaseVersion::WinAppSDK_Latest). + // Together they exercise both branches of Apply's pre-prune walk and + // span the changeId value range so that std::sort (Apply) and + // std::binary_search (FrUdk worker) are probed at both ends: + // + // * s_enabled_changes_test is pinned at WinAppSDK_Latest - 1, so + // its releaseVersion is NOT greater than the effective patch level. + // Apply prunes the group OUT of disabledChanges; these IDs remain + // ENABLED at runtime. {1, 2, 3} = small-end IDs that must not + // appear in the final sorted disabledChanges set. + // + // * s_disabled_changes_test is pinned at WinAppSDK_Latest + 1, so + // its releaseVersion IS greater than the effective patch level. + // Apply preserves the group IN disabledChanges; these IDs are + // DISABLED at runtime. {0, 99999999} = smallest representable ID + // and a high-value sentinel; they must appear in the final sorted + // disabledChanges set. + // + // Unit tests call IsChangeEnabled() (the 1-argument form) so the + // catalog pre-prune is the only mechanism that disables changes. + // Production callsites must not reference 0, 1, 2, 3, or 99999999. + constexpr UINT32 s_enabled_changes_test[] = { 1, 2, 3 }; + constexpr UINT32 s_disabled_changes_test[] = { 0, 99999999 }; + constexpr CatalogGroup s_catalogGroupsTest[] = { + MakeGroup(s_enabled_changes_test, + static_cast(static_cast(WinAppSDKReleaseVersion::WinAppSDK_Latest) - 1)), + MakeGroup(s_disabled_changes_test, + static_cast(static_cast(WinAppSDKReleaseVersion::WinAppSDK_Latest) + 1)), + }; + + // Production catalog for release/2.0-stable. Each per-release group + // collects the contained change IDs that first shipped in that patch + // release. Source of truth: os.2020 FrameworkUdk Containment.h + // (WINAPPSDK_CHANGEID_* macros) and WindowsAppSDK component callsites + // that #define WINAPPSDK_CHANGEID_* macros inline. + constexpr UINT32 s_changes_2_1_0[] = { + 61760863, // os.2020: weak ref in implicit animation completion callback + 62040515, // os.2020: see Containment.h for description + 62147120, // os.2020: UniformGridLayout::GetItemsPerLine floor at 1u + }; + constexpr UINT32 s_changes_2_1_5[] = { + 61684930, // WindowsAppSDK: ApplicationData.GetForUnpackaged() + 61854090, // os.2020: PopulateContactInFrame pen-overrides-touch crash + 62134997, // os.2020: lifted DesktopChildSiteBridge smooth resize + 62276145, // os.2020: ThemeSettings crash on shutdown off-thread + 62382643, // WindowsAppSDK: MRT sparse-packaged module-specific PRI discovery + }; + constexpr UINT32 s_changes_2_3_0[] = { + 62393567, // CPopupRoot_ReplayPointerUpdateCrash + 62406051, // XamlIslandRoot_UiaTeardownReentrancyCrash + 62434240, // ModernCollectionBasePanel_StaleViewportAnchorRecycle + 62434371, // Popup_RemoveChildIdempotentWhenClosing + 62434372, // UIAWindow_PauseNewDispatchOnDisconnectProviders + 62434373, // CascadingMenuHelper_DelayOpenTimerTickAfterUnload + 62471884, // AcrylicBrush_InitializePropertyInMarkup + 62542953, // Xaml_ReserveVectorSpace + 62639407, // XamlUICommand_LightweightBindings + 62644600, // Xaml_DefaultStyleOptimizations + 62645877, // Xaml_ThemeResourceUseResourceKeys + 62646974, // Xaml_FasterResourceDictionary + 62659855, // Xaml_CachedTypeChecks + 62673714, // Xaml_FasterXbfIntLoading + 62676756, // Xaml_ActivationFactoryFastPaths + 62676766, // XamlUICommand_SkipEmptyKeyboardAcceleratorBinding + 62676773, // Xaml_ReduceThemeResourceLookupHashing + 62688041, // RefreshRateInfo_RemovePowerNotification + 62698635, // ApplicationData_GetForUnpackaged_LocalSettings + 62724527, // CustomMetadataTypes_InvalidateUseAfterFree + 62724733, // CPopup_OpenSetFocusReentrancy + 62724906, // MediaPlayerPresenter_DeviceLostCrash + 62725196, // HWBuildGlyphRunTextures_DeviceGuardScope + 62725364, // FocusRectManager_FindFocusRectHostCrash + 62726703, // Xaml_EagerDeviceRemovedEventRegistration + 62738389, // XamlSettings_OptionalChangesApi + 62755661, // UniversalBGTask_RunCrash + 62759377, // Xaml_OptimizedCommandBarInitialization + 62771256, // Flyout_SidePlacementFlipUpAlignmentFix + 62774942, // LanguageModel_StructuredJsonOutput + 62776273, // Xaml_OptimizedCommandBarFlyoutUpdateCommands + 62779870, // XamlInit_GetMuxVersionCompileTime + 62784965, // Xaml_IconNoGridOptimization + 62786355, // UniformGridLayoutFlowLayout_OnItemsChangedNullLayoutStateCrash + 62789076, // Xaml_DeferStylePropertySetters + 62818033, // Xaml_LoadDeferredResourceOptimization + 62833838, // Xaml_DeferContextFlyoutInit + 62847209, // Xaml_DelayApplyStylesAfterCreationComplete + 62849414, // Xaml_ReserveTrackerCollectionSpace + 62865780, // WindowsML_ExecutionProviderEnumerationUpdate + 62917618, // CompositionEngine_API + 62927180, // WindowsServices_GetKeyboardModifiersStateRace + 62934326, // VideoSuperResolution_HardwareDetection + }; + constexpr CatalogGroup s_catalogGroupsProd[] = { + MakeGroup(s_changes_2_1_0, WinAppSDKReleaseVersion::WinAppSDK_2_1_0), + MakeGroup(s_changes_2_1_5, WinAppSDKReleaseVersion::WinAppSDK_2_1_5), + MakeGroup(s_changes_2_3_0, WinAppSDKReleaseVersion::WinAppSDK_2_3_0), + }; + constexpr size_t s_catalogGroupsProdCount{ std::size(s_catalogGroupsProd) }; + + // Active catalog. ContainmentTestInitialize(bool) swaps these pointers + // between the production and test catalogs. Apply() reads them at runtime. + const CatalogGroup* s_catalogGroup{ s_catalogGroupsProd }; + size_t s_catalogGroupCount{ s_catalogGroupsProdCount }; +} + +STDAPI ContainmentTestInitialize(bool enableTest) noexcept try +{ + if (enableTest) + { + s_catalogGroup = s_catalogGroupsTest; + s_catalogGroupCount = std::size(s_catalogGroupsTest); + } + else + { + s_catalogGroup = s_catalogGroupsProd; + s_catalogGroupCount = s_catalogGroupsProdCount; + } + return S_OK; +} CATCH_RETURN(); namespace winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::implementation { @@ -28,19 +215,46 @@ namespace winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::implem } } - // Apply the patch level which applies to the major.minor version, if any. - if (m_patchLevel1.Major == WINDOWSAPPSDK_RELEASE_MAJOR && m_patchLevel1.Minor == WINDOWSAPPSDK_RELEASE_MINOR) + // Resolve the effective patch level for catalog pre-prune. This is owned + // entirely by WinAppSDK now - when the 2.0 branch bumps to a FrUdk that + // exposes only WinAppSDKPatchVersionDeprecated, FrameworkUdk's + // Containment_GetChangeEnabled worker will no longer compare against any + // per-change patch version. The WASDK side is already prepared. + // A default-initialized WindowsAppRuntimeVersion is {0,0,0}; treat that as + // "not set" rather than as a request to pin to 0.0.0. (In production builds + // WINDOWSAPPSDK_RELEASE_MAJOR is > 0 so this case never arises; in dev builds + // the macro is 0/0 and the unset sentinel would otherwise spuriously match.) + WinAppSDKReleaseVersion runtimePatchLevel{ WinAppSDKReleaseVersion::WinAppSDK_Latest }; + if (m_patchLevel1.Major == WINDOWSAPPSDK_RELEASE_MAJOR && m_patchLevel1.Minor == WINDOWSAPPSDK_RELEASE_MINOR + && (m_patchLevel1.Major != 0 || m_patchLevel1.Minor != 0 || m_patchLevel1.Patch != 0)) { // Apply patch level 1 - config.patchVersion = (WinAppSDKPatchVersion)WinAppSDKPatchVersionFromValues(m_patchLevel1.Major, m_patchLevel1.Minor, m_patchLevel1.Patch); + runtimePatchLevel = static_cast( + WinAppSDKReleaseVersionFromValues(m_patchLevel1.Major, m_patchLevel1.Minor, m_patchLevel1.Patch)); } - else if (m_patchLevel2.Major == WINDOWSAPPSDK_RELEASE_MAJOR && m_patchLevel2.Minor == WINDOWSAPPSDK_RELEASE_MINOR) + else if (m_patchLevel2.Major == WINDOWSAPPSDK_RELEASE_MAJOR && m_patchLevel2.Minor == WINDOWSAPPSDK_RELEASE_MINOR + && (m_patchLevel2.Major != 0 || m_patchLevel2.Minor != 0 || m_patchLevel2.Patch != 0)) { // Apply patch level 2 - config.patchVersion = (WinAppSDKPatchVersion)WinAppSDKPatchVersionFromValues(m_patchLevel2.Major, m_patchLevel2.Minor, m_patchLevel2.Patch); + runtimePatchLevel = static_cast( + WinAppSDKReleaseVersionFromValues(m_patchLevel2.Major, m_patchLevel2.Minor, m_patchLevel2.Patch)); } - // Add the set of disabled changes + // Mirror the resolved patch level into the ABI-locked + // WinAppSDKRuntimeConfiguration::patchVersion field as an opaque UINT32 + // cast. Containment_GetChangeEnabled does not read it for change + // filtering, but Containment_SetConfiguration uses it as part of the + // identity check that detects attempts to apply a divergent + // configuration twice from the same process. + config.patchVersion = static_cast(static_cast(runtimePatchLevel)); + + // Build the disabled-changes list from two sources: + // (1) The app's explicit DisabledChanges list (unchanged behavior). + // (2) Catalog pre-pruning: every change whose release is newer than the + // effective patch level is treated as disabled, so callsites see + // pre-change behavior on patch-pinned runtimes. + // Duplicates between (1) and (2) are harmless: the FrUdk worker treats + // disabledChanges as a sorted set and uses set-membership semantics. std::vector disabledChanges; for (auto changeId : m_disabledChanges) { @@ -48,6 +262,23 @@ namespace winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::implem // UINT32 is used internally for the changeId, so cast from the enum's Int32 to that. disabledChanges.push_back(static_cast(changeId)); } + + for (size_t g = 0; g < s_catalogGroupCount; ++g) + { + auto const& group = s_catalogGroup[g]; + if (group.releaseVersion > runtimePatchLevel) + { + for (UINT32 i = 0; i < group.count; ++i) + { + disabledChanges.push_back(group.changes[i]); + } + } + } + + // FrameworkUdk's Containment_GetChangeEnabled assumes disabledChanges is + // sorted ascending so it can use std::binary_search for O(log N) lookups. + std::sort(disabledChanges.begin(), disabledChanges.end()); + config.disabledChanges = disabledChanges.data(); config.disabledChangesCount = static_cast(disabledChanges.size()); diff --git a/dev/WindowsAppRuntime_DLL/WindowsAppRuntime.def b/dev/WindowsAppRuntime_DLL/WindowsAppRuntime.def index c487bfd388..9ad36f293f 100644 --- a/dev/WindowsAppRuntime_DLL/WindowsAppRuntime.def +++ b/dev/WindowsAppRuntime_DLL/WindowsAppRuntime.def @@ -28,4 +28,6 @@ EXPORTS WindowsAppRuntime_VersionInfo_MSIX_Main_PackageFamilyName_Get WindowsAppRuntime_VersionInfo_TestInitialize + ContainmentTestInitialize + WindowsAppRuntime_EnsureIsLoaded diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 9f211c3190..30d270d697 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -27,25 +27,25 @@ --> - + https://dev.azure.com/microsoft/LiftedIXP/_git/DCPP - bcefb53ef31cde2d4d7e97760db1970335047aed + 3fe865e9c77580e74858a5064b1f079a8cd3b8b5 https://dev.azure.com/microsoft/ProjectReunion/_git/WindowsAppSDKClosed 4c1df0de1a0025847b63017d0eefd6dc89d685f3 - + https://dev.azure.com/microsoft/LiftedIXP/_git/DCPP - bcefb53ef31cde2d4d7e97760db1970335047aed + 3fe865e9c77580e74858a5064b1f079a8cd3b8b5 https://dev.azure.com/microsoft/ProjectReunion/_git/WindowsAppSDKAggregator 6f349c49ec4dfe60d424d12b3ab1091c06a1428f - + https://dev.azure.com/microsoft/LiftedIXP/_git/DCPP - bcefb53ef31cde2d4d7e97760db1970335047aed + 3fe865e9c77580e74858a5064b1f079a8cd3b8b5 diff --git a/test/Compatibility/CompatibilityTests/CompatibilityTests.cpp b/test/Compatibility/CompatibilityTests/CompatibilityTests.cpp index bcddf0b729..0459e51f4e 100644 --- a/test/Compatibility/CompatibilityTests/CompatibilityTests.cpp +++ b/test/Compatibility/CompatibilityTests/CompatibilityTests.cpp @@ -7,6 +7,21 @@ #include #include +// Signature of the test-only DLL export from Microsoft.WindowsAppRuntime.dll. +// Switches the WinAppSDK Containment catalog used by RuntimeCompatibilityOptions::Apply() +// between the production catalog (default) and a test-only catalog whose two groups +// straddle WinAppSDK_Latest: an "enabled" group at WinAppSDK_Latest - 1 holding +// {1, 2, 3} that Apply prunes out of disabledChanges, and a "disabled" group at +// WinAppSDK_Latest + 1 holding {0, 99999999} that Apply preserves in disabledChanges. +// +// The export is resolved via GetModuleHandleW/GetProcAddress (not a static import) +// because RuntimeCompatibilityOptions::Apply runs in the framework-package copy of +// Microsoft.WindowsAppRuntime.dll loaded by the bootstrap. The catalog globals live +// in that same module instance, so the test must call ContainmentTestInitialize on +// that DLL handle - a static import would resolve to a test-folder copy with its +// own (production) catalog and the catalog swap would have no observable effect. +using PFN_ContainmentTestInitialize = HRESULT(STDAPICALLTYPE*)(bool) noexcept; + namespace TB = ::Test::Bootstrap; namespace TP = ::Test::Packages; @@ -14,6 +29,31 @@ namespace WAR = winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime; namespace Test::CompatibilityTests { + // Resolve ContainmentTestInitialize from the bootstrap-loaded framework copy + // of Microsoft.WindowsAppRuntime.dll. Must be called AFTER SetupBootstrap so + // the DLL is present in the process module list. + // + // NOTE: the wrapper is named CallContainmentTestInitialize (not + // ContainmentTestInitialize) so MSVC's linker does not pull in the + // Microsoft.WindowsAppRuntime.lib import stub for the same name -- doing so + // would introduce a static dependency on Microsoft.WindowsAppRuntime.dll, + // load a second copy in the test folder, and break the catalog swap. + static HRESULT CallContainmentTestInitialize(bool enableTest) noexcept + { + HMODULE const hmod{ ::GetModuleHandleW(L"Microsoft.WindowsAppRuntime.dll") }; + if (hmod == nullptr) + { + return HRESULT_FROM_WIN32(::GetLastError()); + } + auto const pfn{ reinterpret_cast( + ::GetProcAddress(hmod, "ContainmentTestInitialize")) }; + if (pfn == nullptr) + { + return HRESULT_FROM_WIN32(::GetLastError()); + } + return pfn(enableTest); + } + class CompatibilityTests { public: @@ -42,12 +82,24 @@ namespace Test::CompatibilityTests // The test method setup and execution is on a different thread than the class setup. // Initialize the framework for the test thread. ::Test::Bootstrap::SetupBootstrap(); + + // Swap in the test-only catalog so the catalog pre-prune tests + // observe the synthetic groups that straddle WinAppSDK_Latest. + // Must be done after SetupBootstrap so the framework copy of + // Microsoft.WindowsAppRuntime.dll is loaded and reachable via + // GetModuleHandleW; that copy is the one that hosts + // RuntimeCompatibilityOptions::Apply and owns the catalog globals. + VERIFY_ARE_EQUAL(S_OK, CallContainmentTestInitialize(true)); return true; } TEST_METHOD_CLEANUP(MethodUninit) { VERIFY_IS_TRUE(TP::IsPackageRegistered_WindowsAppRuntimeFramework()); + // Restore the production catalog before tearing down the bootstrap. + // IsolationLevel="Method" already guarantees a fresh process per test, + // but resetting keeps the contract clean. + VERIFY_ARE_EQUAL(S_OK, CallContainmentTestInitialize(false)); ::Test::Bootstrap::CleanupBootstrap(); return true; } @@ -131,12 +183,64 @@ namespace Test::CompatibilityTests WEX::Logging::Log::Comment(WEX::Common::String(L"RuntimeCompatibilityOptions with DisabledChanges applied.")); // Verify that the specified DisabledChanges are disabled. - VERIFY_IS_FALSE((WinAppSdk::Containment::IsChangeEnabled<12345, WinAppSDK_Security>())); - VERIFY_IS_FALSE((WinAppSdk::Containment::IsChangeEnabled<23456, WinAppSDK_Security>())); - VERIFY_IS_FALSE((WinAppSdk::Containment::IsChangeEnabled<34567, WinAppSDK_Security>())); + VERIFY_IS_FALSE((WinAppSdk::Containment::IsChangeEnabled<12345>())); + VERIFY_IS_FALSE((WinAppSdk::Containment::IsChangeEnabled<23456>())); + VERIFY_IS_FALSE((WinAppSdk::Containment::IsChangeEnabled<34567>())); // A different value not in DisabledChanges should remain enabled. - VERIFY_IS_TRUE((WinAppSdk::Containment::IsChangeEnabled<99999, WinAppSDK_Security>())); + VERIFY_IS_TRUE((WinAppSdk::Containment::IsChangeEnabled<99999>())); + } + + TEST_METHOD(VerifyCatalogPrePrunesByPatchLevel) + { + // The test catalog has two groups whose releaseVersion straddles + // the runtime's effective patch level (WinAppSDK_Latest by + // default): + // * s_enabled_changes_test at WinAppSDK_Latest - 1: the + // pre-prune walk SKIPS this group, so {1, 2, 3} remain + // enabled. + // * s_disabled_changes_test at WinAppSDK_Latest + 1: the + // pre-prune walk COPIES this group into disabledChanges, + // so {0, 99999999} are disabled. + // Together the two groups span the value range and exercise + // both branches of the catalog walk, both ends of std::sort, + // and both ends of the FrUdk worker's std::binary_search. + winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::RuntimeCompatibilityOptions options; + options.Apply(); + + // s_enabled_changes_test: catalog group released BEFORE the + // effective patch level. Apply prunes it out; IDs remain enabled. + VERIFY_IS_TRUE((WinAppSdk::Containment::IsChangeEnabled<1>())); + VERIFY_IS_TRUE((WinAppSdk::Containment::IsChangeEnabled<2>())); + VERIFY_IS_TRUE((WinAppSdk::Containment::IsChangeEnabled<3>())); + + // s_disabled_changes_test: catalog group released AFTER the + // effective patch level. Apply preserves it; IDs are disabled. + VERIFY_IS_FALSE((WinAppSdk::Containment::IsChangeEnabled<0>())); // smallest representable ID + VERIFY_IS_FALSE((WinAppSdk::Containment::IsChangeEnabled<99999999>())); // high-value sentinel + + // An ID not in the catalog and not explicitly disabled stays enabled. + VERIFY_IS_TRUE((WinAppSdk::Containment::IsChangeEnabled<99999>())); + } + + TEST_METHOD(VerifyCatalogAndExplicitDisabledChangesCombine) + { + // Catalog pre-prune contributes {0, 99999999} via + // s_disabled_changes_test (the catalog group released AFTER the + // effective patch level), and the app's explicit DisabledChanges + // also names 99999999 plus 55555. Duplicates between the two + // sources are harmless: the FrUdk worker treats disabledChanges + // as a sorted set and uses set-membership semantics, so the + // result is the union, not a tally. + winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::RuntimeCompatibilityOptions options; + options.DisabledChanges().Append((WAR::RuntimeCompatibilityChange)99999999); + options.DisabledChanges().Append((WAR::RuntimeCompatibilityChange)55555); + options.Apply(); + + VERIFY_IS_FALSE((WinAppSdk::Containment::IsChangeEnabled<0>())); // catalog only + VERIFY_IS_FALSE((WinAppSdk::Containment::IsChangeEnabled<99999999>())); // catalog + explicit + VERIFY_IS_FALSE((WinAppSdk::Containment::IsChangeEnabled<55555>())); // explicit only + VERIFY_IS_TRUE((WinAppSdk::Containment::IsChangeEnabled<99999>())); // neither } }; } diff --git a/test/Compatibility/CompatibilityTests/CompatibilityTests.vcxproj b/test/Compatibility/CompatibilityTests/CompatibilityTests.vcxproj index 8edeb3908c..805e71ab5b 100644 --- a/test/Compatibility/CompatibilityTests/CompatibilityTests.vcxproj +++ b/test/Compatibility/CompatibilityTests/CompatibilityTests.vcxproj @@ -79,8 +79,18 @@ Windows - onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;wex.common.lib;wex.logger.lib;te.common.lib;%(AdditionalDependencies) + onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;wex.common.lib;wex.logger.lib;te.common.lib;delayimp.lib;%(AdditionalDependencies) $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL + + Microsoft.WindowsAppRuntime.dll;%(DelayLoadDLLs)