From ec381add74c096a918e524913364e077f6ed00db Mon Sep 17 00:00:00 2001 From: Igor Velikorossov Date: Fri, 8 Apr 2022 10:39:02 +1000 Subject: [PATCH 1/3] Run selected tests in en-US culture --- .../XUnit/UseCultureAttribute.cs | 136 ++++++++++++++++++ .../XUnit/UseDefaultXunitCultureAttribute.cs | 26 ++++ .../Infra/ControlTestBase.cs | 3 +- .../Windows/Forms/MdiControlStripTests.cs | 2 + ...endar.CalendarCellAccessibleObjectTests.cs | 1 + .../Windows/Forms/MonthCalendarTests.cs | 9 +- 6 files changed, 169 insertions(+), 8 deletions(-) create mode 100644 src/Common/tests/TestUtilities/XUnit/UseCultureAttribute.cs create mode 100644 src/Common/tests/TestUtilities/XUnit/UseDefaultXunitCultureAttribute.cs diff --git a/src/Common/tests/TestUtilities/XUnit/UseCultureAttribute.cs b/src/Common/tests/TestUtilities/XUnit/UseCultureAttribute.cs new file mode 100644 index 00000000000..2afd39f1fb3 --- /dev/null +++ b/src/Common/tests/TestUtilities/XUnit/UseCultureAttribute.cs @@ -0,0 +1,136 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// The original code was borrowed from https://github.com/xunit/samples.xunit/blob/93f87d5/UseCulture/UseCultureAttribute.cs +// Licensed under http://www.apache.org/licenses/LICENSE-2.0. + +using System.Globalization; +using System.Reflection; +using System.Runtime.InteropServices; +using Xunit.Sdk; + +namespace Xunit +{ + /// + /// Apply this attribute to your test method to replace the and + /// with another culture. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class UseCultureAttribute : BeforeAfterTestAttribute + { + private readonly Lazy _culture; + private readonly Lazy _uiCulture; + private CultureInfo _originalCulture; + private CultureInfo _originalUICulture; + + /// + /// Replaces the culture and UI culture of the current thread with . + /// + /// The name of the culture to set for both and . + public UseCultureAttribute(string culture) + : this(culture, culture) + { + } + + /// + /// Replaces the culture and UI culture of the current thread with and . + /// + /// The name of the culture. + /// The name of the UI culture. + public UseCultureAttribute(string culture, string uiCulture) + { + _culture = new Lazy(() => new(culture, useUserOverride: false)); + _uiCulture = new Lazy(() => new(uiCulture, useUserOverride: false)); + } + + /// + /// Gets the culture. + /// + public CultureInfo Culture => _culture.Value; + + /// + /// Indicates whether the native thread UI culture should also be set. + /// + public bool SetUnmanagedUiThreadCulture { get; set; } + + /// + /// Gets the UI culture. + /// + public CultureInfo UICulture => _uiCulture.Value; + + /// + /// Stores the current + /// and + /// and replaces them with the new cultures defined in the constructor. + /// + /// The method under test + public override unsafe void Before(MethodInfo methodUnderTest) + { + _originalCulture = Thread.CurrentThread.CurrentCulture; + _originalUICulture = Thread.CurrentThread.CurrentUICulture; + + CultureInfo.DefaultThreadCurrentCulture = + Thread.CurrentThread.CurrentCulture = Culture; + Thread.CurrentThread.CurrentUICulture = UICulture; + + if (SetUnmanagedUiThreadCulture) + { + SetNativeUiThreadCulture(UICulture); + } + + CultureInfo.CurrentCulture.ClearCachedData(); + CultureInfo.CurrentUICulture.ClearCachedData(); + } + + /// + /// Restores the original and + /// to + /// + /// The method under test + public override void After(MethodInfo methodUnderTest) + { + Thread.CurrentThread.CurrentCulture = _originalCulture; + Thread.CurrentThread.CurrentUICulture = _originalUICulture; + + if (SetUnmanagedUiThreadCulture) + { + SetNativeUiThreadCulture(_originalUICulture); + } + + CultureInfo.CurrentCulture.ClearCachedData(); + CultureInfo.CurrentUICulture.ClearCachedData(); + } + + // Thread.CurrentThread.CurrentUICulture only sets the UI culture for the managed resources. + private static unsafe void SetNativeUiThreadCulture(CultureInfo uiCulture) + { + uint pulNumLanguages = 0; + string lcid = uiCulture.LCID.ToString("X4"); + fixed (char* plcid = lcid) + { + if (Interop.SetThreadPreferredUILanguages(Interop.MUI_LANGUAGE_ID, plcid, &pulNumLanguages) == Interop.BOOL.FALSE) + { + throw new InvalidOperationException("Unable to set the desired UI language."); + } + } + } + + private static class Interop + { + internal const uint MUI_LANGUAGE_ID = 0x4; + + internal enum BOOL : int + { + FALSE = 0, + TRUE = 1, + } + + [DllImport("Kernel32.dll", ExactSpelling = true, CharSet = CharSet.Unicode)] + internal static extern unsafe BOOL SetThreadPreferredUILanguages( + uint dwFlags, + char* pwszLanguagesBuffer, + uint* pulNumLanguages); + } + } +} diff --git a/src/Common/tests/TestUtilities/XUnit/UseDefaultXunitCultureAttribute.cs b/src/Common/tests/TestUtilities/XUnit/UseDefaultXunitCultureAttribute.cs new file mode 100644 index 00000000000..e2b021149fd --- /dev/null +++ b/src/Common/tests/TestUtilities/XUnit/UseDefaultXunitCultureAttribute.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; + +namespace Xunit +{ + /// + /// Apply this attribute to your test method to replace the and + /// with another culture. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class UseDefaultXunitCultureAttribute : UseCultureAttribute + { + public const string DefaultXunitCultureAttribute = "en-US"; + + /// + /// Replaces the culture and UI culture of the current thread with the . + /// + public UseDefaultXunitCultureAttribute() + : base(DefaultXunitCultureAttribute, DefaultXunitCultureAttribute) + { + } + } +} diff --git a/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/Infra/ControlTestBase.cs b/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/Infra/ControlTestBase.cs index 615394b3344..83344045184 100644 --- a/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/Infra/ControlTestBase.cs +++ b/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/Infra/ControlTestBase.cs @@ -11,6 +11,7 @@ namespace System.Windows.Forms.UITests { + [UseDefaultXunitCulture] public abstract class ControlTestBase : IAsyncLifetime, IDisposable { private const int SPIF_SENDCHANGE = 0x0002; @@ -22,8 +23,6 @@ public abstract class ControlTestBase : IAsyncLifetime, IDisposable protected ControlTestBase(ITestOutputHelper testOutputHelper) { TestOutputHelper = testOutputHelper; - Thread.CurrentThread.CurrentCulture = - Thread.CurrentThread.CurrentUICulture = new("en-US"); Application.EnableVisualStyles(); diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/MdiControlStripTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/MdiControlStripTests.cs index 5c668217003..9f9e0c6808d 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/MdiControlStripTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/MdiControlStripTests.cs @@ -192,6 +192,7 @@ public void MdiControlStrip_Ctor_VerifyMenuItemsHaveImages() } } + [UseDefaultXunitCulture(SetUnmanagedUiThreadCulture = true)] [WinFormsTheory] [InlineData(RightToLeft.No)] [InlineData(RightToLeft.Yes)] @@ -237,6 +238,7 @@ public void MdiControlStrip_MaximizedChildWindow_NextSibling_ReturnsControlBoxBu Assert.True(menuStrip.IsHandleCreated); } + [UseDefaultXunitCulture(SetUnmanagedUiThreadCulture = true)] [WinFormsTheory] [InlineData(RightToLeft.No)] [InlineData(RightToLeft.Yes)] diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/MonthCalendar.CalendarCellAccessibleObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/MonthCalendar.CalendarCellAccessibleObjectTests.cs index ea71f56b750..bfce3fa5725 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/MonthCalendar.CalendarCellAccessibleObjectTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/MonthCalendar.CalendarCellAccessibleObjectTests.cs @@ -10,6 +10,7 @@ namespace System.Windows.Forms.Tests { + [UseDefaultXunitCulture] public class MonthCalendar_CalendarCellAccessibleObjectTests : IClassFixture { [WinFormsFact] diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/MonthCalendarTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/MonthCalendarTests.cs index 7a0405f8019..885a20c2835 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/MonthCalendarTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/MonthCalendarTests.cs @@ -15,6 +15,7 @@ namespace System.Windows.Forms.Tests using Point = System.Drawing.Point; using Size = System.Drawing.Size; + [UseDefaultXunitCulture] public class MonthCalendarTests : IClassFixture { [WinFormsFact] @@ -1134,6 +1135,7 @@ public void MonthCalendar_Handle_GetWithTrailingForeColor_Success() Assert.Equal(0x785634, User32.SendMessageW(control.Handle, (User32.WM)ComCtl32.MCM.GETCOLOR, (nint)ComCtl32.MCSC.TRAILINGTEXT)); } + [UseDefaultXunitCulture(SetUnmanagedUiThreadCulture = true)] [WinFormsFact] public void MonthCalendar_Handle_GetWithDefaultFirstDayOfWeek_Success() { @@ -1142,12 +1144,7 @@ public void MonthCalendar_Handle_GetWithDefaultFirstDayOfWeek_Success() FirstDayOfWeek = Day.Default }; Assert.NotEqual(IntPtr.Zero, control.Handle); - int expected = (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek + 6; - while (expected > 6) - { - expected -= 7; - } - + int expected = (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek; Assert.Equal(expected, User32.SendMessageW(control.Handle, (User32.WM)ComCtl32.MCM.GETFIRSTDAYOFWEEK)); } From cc298d368d69b52a00e71c3cd8404ae8bdc2e3af Mon Sep 17 00:00:00 2001 From: Igor Velikorossov Date: Fri, 8 Apr 2022 11:56:45 +1000 Subject: [PATCH 2/3] fixup! Run selected tests in en-US culture --- .../UnitTests/System/Windows/Forms/MonthCalendarTests.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/MonthCalendarTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/MonthCalendarTests.cs index 885a20c2835..2c8ecb63608 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/MonthCalendarTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/MonthCalendarTests.cs @@ -1135,7 +1135,6 @@ public void MonthCalendar_Handle_GetWithTrailingForeColor_Success() Assert.Equal(0x785634, User32.SendMessageW(control.Handle, (User32.WM)ComCtl32.MCM.GETCOLOR, (nint)ComCtl32.MCSC.TRAILINGTEXT)); } - [UseDefaultXunitCulture(SetUnmanagedUiThreadCulture = true)] [WinFormsFact] public void MonthCalendar_Handle_GetWithDefaultFirstDayOfWeek_Success() { @@ -1144,7 +1143,12 @@ public void MonthCalendar_Handle_GetWithDefaultFirstDayOfWeek_Success() FirstDayOfWeek = Day.Default }; Assert.NotEqual(IntPtr.Zero, control.Handle); - int expected = (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek; + int expected = (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek + 6; + while (expected > 6) + { + expected -= 7; + } + Assert.Equal(expected, User32.SendMessageW(control.Handle, (User32.WM)ComCtl32.MCM.GETFIRSTDAYOFWEEK)); } From d5b7870389b89b00938c7db8114390f49b86ca7b Mon Sep 17 00:00:00 2001 From: Igor Velikorossov Date: Tue, 26 Apr 2022 15:53:52 +1000 Subject: [PATCH 3/3] fixup! Run selected tests in en-US culture --- .../tests/TestUtilities/XUnit/UseCultureAttribute.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Common/tests/TestUtilities/XUnit/UseCultureAttribute.cs b/src/Common/tests/TestUtilities/XUnit/UseCultureAttribute.cs index 2afd39f1fb3..4c5d3a79b31 100644 --- a/src/Common/tests/TestUtilities/XUnit/UseCultureAttribute.cs +++ b/src/Common/tests/TestUtilities/XUnit/UseCultureAttribute.cs @@ -23,6 +23,7 @@ public class UseCultureAttribute : BeforeAfterTestAttribute private readonly Lazy _uiCulture; private CultureInfo _originalCulture; private CultureInfo _originalUICulture; + private bool _updateUnmanagedUiThreadCulture; /// /// Replaces the culture and UI culture of the current thread with . @@ -70,11 +71,12 @@ public override unsafe void Before(MethodInfo methodUnderTest) _originalCulture = Thread.CurrentThread.CurrentCulture; _originalUICulture = Thread.CurrentThread.CurrentUICulture; - CultureInfo.DefaultThreadCurrentCulture = - Thread.CurrentThread.CurrentCulture = Culture; + CultureInfo.DefaultThreadCurrentCulture = Culture; + Thread.CurrentThread.CurrentCulture = Culture; Thread.CurrentThread.CurrentUICulture = UICulture; - if (SetUnmanagedUiThreadCulture) + _updateUnmanagedUiThreadCulture = !_originalUICulture.Equals(UICulture); + if (SetUnmanagedUiThreadCulture && _updateUnmanagedUiThreadCulture) { SetNativeUiThreadCulture(UICulture); } @@ -93,7 +95,7 @@ public override void After(MethodInfo methodUnderTest) Thread.CurrentThread.CurrentCulture = _originalCulture; Thread.CurrentThread.CurrentUICulture = _originalUICulture; - if (SetUnmanagedUiThreadCulture) + if (SetUnmanagedUiThreadCulture && _updateUnmanagedUiThreadCulture) { SetNativeUiThreadCulture(_originalUICulture); }