Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions src/Common/tests/TestUtilities/XUnit/UseCultureAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// 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
{
/// <summary>
/// Apply this attribute to your test method to replace the <see cref="Thread.CurrentThread" /> <see cref="CultureInfo.CurrentCulture" /> and
/// <see cref="CultureInfo.CurrentUICulture" /> with another culture.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class UseCultureAttribute : BeforeAfterTestAttribute
{
private readonly Lazy<CultureInfo> _culture;
private readonly Lazy<CultureInfo> _uiCulture;
private CultureInfo _originalCulture;
private CultureInfo _originalUICulture;
private bool _updateUnmanagedUiThreadCulture;

/// <summary>
/// Replaces the culture and UI culture of the current thread with <paramref name="culture" />.
/// </summary>
/// <param name="culture">The name of the culture to set for both <see cref="Culture" /> and <see cref="UICulture" />.</param>
public UseCultureAttribute(string culture)
: this(culture, culture)
{
}

/// <summary>
/// Replaces the culture and UI culture of the current thread with <paramref name="culture" /> and <paramref name="uiCulture" />.
/// </summary>
/// <param name="culture">The name of the culture.</param>
/// <param name="uiCulture">The name of the UI culture.</param>
public UseCultureAttribute(string culture, string uiCulture)
{
_culture = new Lazy<CultureInfo>(() => new(culture, useUserOverride: false));
_uiCulture = new Lazy<CultureInfo>(() => new(uiCulture, useUserOverride: false));
}

/// <summary>
/// Gets the culture.
/// </summary>
public CultureInfo Culture => _culture.Value;

/// <summary>
/// Indicates whether the native thread UI culture should also be set.
/// </summary>
public bool SetUnmanagedUiThreadCulture { get; set; }

/// <summary>
/// Gets the UI culture.
/// </summary>
public CultureInfo UICulture => _uiCulture.Value;

/// <summary>
/// Stores the current <see cref="Thread.CurrentPrincipal" />
/// <see cref="CultureInfo.CurrentCulture" /> and <see cref="CultureInfo.CurrentUICulture" />
/// and replaces them with the new cultures defined in the constructor.
/// </summary>
/// <param name="methodUnderTest">The method under test</param>
public override unsafe void Before(MethodInfo methodUnderTest)
{
_originalCulture = Thread.CurrentThread.CurrentCulture;
_originalUICulture = Thread.CurrentThread.CurrentUICulture;

CultureInfo.DefaultThreadCurrentCulture = Culture;
Thread.CurrentThread.CurrentCulture = Culture;
Thread.CurrentThread.CurrentUICulture = UICulture;

_updateUnmanagedUiThreadCulture = !_originalUICulture.Equals(UICulture);
if (SetUnmanagedUiThreadCulture && _updateUnmanagedUiThreadCulture)
{
SetNativeUiThreadCulture(UICulture);
}

CultureInfo.CurrentCulture.ClearCachedData();
CultureInfo.CurrentUICulture.ClearCachedData();
}

/// <summary>
/// Restores the original <see cref="CultureInfo.CurrentCulture" /> and
/// <see cref="CultureInfo.CurrentUICulture" /> to <see cref="Thread.CurrentPrincipal" />
/// </summary>
/// <param name="methodUnderTest">The method under test</param>
public override void After(MethodInfo methodUnderTest)
{
Thread.CurrentThread.CurrentCulture = _originalCulture;
Thread.CurrentThread.CurrentUICulture = _originalUICulture;

if (SetUnmanagedUiThreadCulture && _updateUnmanagedUiThreadCulture)
{
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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Apply this attribute to your test method to replace the <see cref="Thread.CurrentThread" /> <see cref="CultureInfo.CurrentCulture" /> and
/// <see cref="CultureInfo.CurrentUICulture" /> with another culture.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class UseDefaultXunitCultureAttribute : UseCultureAttribute
{
public const string DefaultXunitCultureAttribute = "en-US";

/// <summary>
/// Replaces the culture and UI culture of the current thread with the <see cref="DefaultXunitCultureAttribute"/>.
/// </summary>
public UseDefaultXunitCultureAttribute()
: base(DefaultXunitCultureAttribute, DefaultXunitCultureAttribute)
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace System.Windows.Forms.UITests
{
[UseDefaultXunitCulture]
public abstract class ControlTestBase : IAsyncLifetime, IDisposable
{
private const int SPIF_SENDCHANGE = 0x0002;
Expand All @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ public void MdiControlStrip_Ctor_VerifyMenuItemsHaveImages()
}
}

[UseDefaultXunitCulture(SetUnmanagedUiThreadCulture = true)]
[WinFormsTheory]
[InlineData(RightToLeft.No)]
[InlineData(RightToLeft.Yes)]
Expand Down Expand Up @@ -237,6 +238,7 @@ public void MdiControlStrip_MaximizedChildWindow_NextSibling_ReturnsControlBoxBu
Assert.True(menuStrip.IsHandleCreated);
}

[UseDefaultXunitCulture(SetUnmanagedUiThreadCulture = true)]
[WinFormsTheory]
[InlineData(RightToLeft.No)]
[InlineData(RightToLeft.Yes)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

namespace System.Windows.Forms.Tests
{
[UseDefaultXunitCulture]
public class MonthCalendar_CalendarCellAccessibleObjectTests : IClassFixture<ThreadExceptionFixture>
{
[WinFormsFact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace System.Windows.Forms.Tests
using Point = System.Drawing.Point;
using Size = System.Drawing.Size;

[UseDefaultXunitCulture]
public class MonthCalendarTests : IClassFixture<ThreadExceptionFixture>
{
[WinFormsFact]
Expand Down