File: XUnit\UseCultureAttribute.cs
Web Access
Project: src\src\Common\tests\TestUtilities\System.Private.Windows.Core.TestUtilities.csproj (System.Private.Windows.Core.TestUtilities)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
// 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;
 
    // These will be set to a value that is never null in the Before method
    // which will always run before it is used in the After method.
    private CultureInfo _originalCulture = null!;
    private CultureInfo _originalUICulture = null!;
 
    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);
    }
}