File: ConditionalFactAttribute.cs
Web Access
Project: src\src\Razor\src\Shared\Microsoft.AspNetCore.Razor.Test.Common\Microsoft.AspNetCore.Razor.Test.Common.csproj (Microsoft.AspNetCore.Razor.Test.Common)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.AspNetCore.Razor.Utilities;
using Xunit;
 
namespace Microsoft.AspNetCore.Razor;
 
/// <summary>
///  A <see cref="FactAttribute"/> that only executes if each of the given conditions are met.
/// </summary>
/// <remarks>
///  Conditions can be provided using the <see cref="Is"/> type. For example, <see cref="Is.Windows"/> -or- 
///  <see cref="Is.Not.Linux"/>, <see cref="Is.Not.MacOS"/>.
/// </remarks>
public sealed class ConditionalFactAttribute : FactAttribute
{
    public ConditionalFactAttribute(params string[] conditions)
    {
        if (!Conditions.AllTrue(conditions))
        {
            base.Skip = Reason ?? Conditions.GetSkipReason(conditions);
        }
    }
 
    /// <summary>
    ///  This property exists to prevent users of <see cref="ConditionalFactAttribute"/>
    ///  from accidentally putting documentation in the <see cref="Skip"/> property instead of Reason.
    ///  Setting <see cref="Skip"/> would cause the test to be unconditionally skip.
    /// </summary>
    [Obsolete($"{nameof(ConditionalFactAttribute)} should always use {nameof(Reason)} or {nameof(AlwaysSkip)}", error: true)]
    public new string Skip
    {
        get { return base.Skip; }
        set { base.Skip = value; }
    }
 
    /// <summary>
    ///  Use to unconditionally skip a test.
    /// </summary>
    /// <remarks>
    ///  This is useful in the rare occasion when a conditional test needs to be skipped unconditionally.
    ///  Typically, this is for a short term reason, such as working on a bug fix.
    /// </remarks>
    public string AlwaysSkip
    {
        get { return base.Skip; }
        set { base.Skip = value; }
    }
 
    public string? Reason { get; set; }
}
 
/// <summary>
///  A <see cref="TheoryAttribute"/> that only executes if each of the given conditions are met.
/// </summary>
/// <remarks>
///  Conditions can be provided using the <see cref="Is"/> type. For example, <see cref="Is.Windows"/> -or- 
///  <see cref="Is.Not.Linux"/>, <see cref="Is.Not.MacOS"/>.
/// </remarks>
public sealed class ConditionalTheoryAttribute : TheoryAttribute
{
    public ConditionalTheoryAttribute(params string[] conditions)
    {
        if (!Conditions.AllTrue(conditions))
        {
            base.Skip = Reason ?? Conditions.GetSkipReason(conditions);
        }
    }
 
    /// <summary>
    ///  This property exists to prevent users of <see cref="ConditionalFactAttribute"/>
    ///  from accidentally putting documentation in the <see cref="Skip"/> property instead of Reason.
    ///  Setting <see cref="Skip"/> would cause the test to be unconditionally skip.
    /// </summary>
    [Obsolete($"{nameof(ConditionalFactAttribute)} should always use {nameof(Reason)} or {nameof(AlwaysSkip)}", error: true)]
    public new string Skip
    {
        get { return base.Skip; }
        set { base.Skip = value; }
    }
 
    /// <summary>
    ///  Use to unconditionally skip a test.
    /// </summary>
    /// <remarks>
    ///  This is useful in the rare occasion when a conditional test needs to be skipped unconditionally.
    ///  Typically, this is for a short term reason, such as working on a bug fix.
    /// </remarks>
    public string AlwaysSkip
    {
        get { return base.Skip; }
        set { base.Skip = value; }
    }
 
    public string? Reason { get; set; }
}
 
public static class Is
{
    /// <summary>
    ///  Only execute if the current operating system platform is Windows.
    /// </summary>
    public const string Windows = nameof(Windows);
 
    /// <summary>
    ///  Only execute if the current operating system platform is Linux.
    /// </summary>
    public const string Linux = nameof(Linux);
 
    /// <summary>
    ///  Only execute if the current operating system platform is MacOS.
    /// </summary>
    public const string MacOS = nameof(MacOS);
 
    /// <summary>
    ///  Only execute if the current operating system platform is FreeBSD.
    /// </summary>
    public const string FreeBSD = nameof(FreeBSD);
 
    /// <summary>
    ///  Only execute if the current operating system platform is Unix-based.
    /// </summary>
    public const string AnyUnix = nameof(AnyUnix);
 
    /// <summary>
    ///  Only execute if the current culture and UI culture are English-based.
    /// </summary>
    public const string EnglishLocale = nameof(EnglishLocale);
 
    public static class Not
    {
        /// <summary>
        ///  Only execute if the current operating system platform is not Windows.
        /// </summary>
        public const string Windows = $"!{nameof(Windows)}";
 
        /// <summary>
        ///  Only execute if the current operating system platform is Linux.
        /// </summary>
        public const string Linux = $"!{nameof(Linux)}";
 
        /// <summary>
        ///  Only execute if the current operating system platform is not MacOS.
        /// </summary>
        public const string MacOS = $"!{nameof(MacOS)}";
 
        /// <summary>
        ///  Only execute if the current operating system platform is not FreeBSD.
        /// </summary>
        public const string FreeBSD = $"!{nameof(FreeBSD)}";
 
        /// <summary>
        ///  Only execute if the current operating system platform is not Unix-based.
        /// </summary>
        public const string AnyUnix = $"!{nameof(AnyUnix)}";
 
        /// <summary>
        ///  Only execute if the current culture and UI culture are not English-based.
        /// </summary>
        public const string EnglishLocale = $"!{nameof(EnglishLocale)}";
 
    }
}
 
public static class Conditions
{
    private static readonly FrozenDictionary<string, Func<bool>> s_conditionMap = CreateConditionMap();
 
    private static FrozenDictionary<string, Func<bool>> CreateConditionMap()
    {
        var map = new Dictionary<string, Func<bool>>(StringComparer.OrdinalIgnoreCase);
 
        Add(Is.Windows, static () => PlatformInformation.IsWindows);
        Add(Is.Linux, static () => PlatformInformation.IsLinux);
        Add(Is.MacOS, static () => PlatformInformation.IsMacOS);
        Add(Is.FreeBSD, static () => PlatformInformation.IsFreeBSD);
        Add(Is.AnyUnix, static () => PlatformInformation.IsLinux ||
                                     PlatformInformation.IsMacOS ||
                                     PlatformInformation.IsFreeBSD);
        Add(Is.EnglishLocale, static () =>
        {
            if (string.IsNullOrEmpty(CultureInfo.CurrentCulture.Name))
            {
                return true;
            }
 
            return CultureInfo.CurrentUICulture.Name.StartsWith("en", StringComparison.OrdinalIgnoreCase)
                && CultureInfo.CurrentCulture.Name.StartsWith("en", StringComparison.OrdinalIgnoreCase);
        });
 
        return map.ToFrozenDictionary();
 
        void Add(string name, Func<bool> predicate)
        {
            map.Add(name, predicate);
 
            // Add negated condition
            map.Add($"!{name}", () => !predicate());
        }
    }
 
    public static bool AllTrue(string[] conditions)
    {
        foreach (var condition in conditions)
        {
            if (!s_conditionMap.TryGetValue(condition, out var predicate))
            {
                throw new NotSupportedException($"Encountered unexpected condition: {condition}");
            }
 
            if (!predicate())
            {
                return false;
            }
        }
 
        return true;
    }
 
    public static string GetSkipReason(string[] conditions)
        => $"The following conditions are not all true: {string.Join(", ", conditions)}";
}