File: src\VisualStudio\IntegrationTest\Harness\XUnitShared\Threading\IdeFactDiscoverer.cs
Web Access
Project: src\src\VisualStudio\IntegrationTest\Harness\XUnit\Microsoft.VisualStudio.Extensibility.Testing.Xunit.csproj (Microsoft.VisualStudio.Extensibility.Testing.Xunit)
// 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.
 
namespace Xunit.Threading
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using Xunit.Abstractions;
    using Xunit.Harness;
    using Xunit.Sdk;
 
    public class IdeFactDiscoverer : IXunitTestCaseDiscoverer
    {
        private readonly IMessageSink _diagnosticMessageSink;
 
        public IdeFactDiscoverer(IMessageSink diagnosticMessageSink)
        {
            _diagnosticMessageSink = diagnosticMessageSink;
        }
 
        public IEnumerable<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute)
        {
            if (testMethod is null)
            {
                throw new ArgumentNullException(nameof(testMethod));
            }
 
            if (!testMethod.Method.GetParameters().Any())
            {
                if (!testMethod.Method.IsGenericMethodDefinition)
                {
                    foreach (var supportedInstance in GetSupportedInstances(testMethod, factAttribute))
                    {
                        yield return new IdeTestCase(_diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, supportedInstance);
                        if (IdeInstanceTestCase.TryCreateNewInstanceForFramework(discoveryOptions, _diagnosticMessageSink, supportedInstance) is { } instanceTestCase)
                        {
                            yield return instanceTestCase;
                        }
                    }
                }
                else
                {
                    yield return new ExecutionErrorTestCase(_diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, "[IdeFact] methods are not allowed to be generic.");
                }
            }
            else
            {
                yield return new ExecutionErrorTestCase(_diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, "[IdeFact] methods are not allowed to have parameters. Did you mean to use [IdeTheory]?");
            }
        }
 
        internal static ITestMethod CreateVisualStudioTestMethod()
        {
            var testAssembly = new TestAssembly(new ReflectionAssemblyInfo(typeof(Instances).Assembly));
            var testCollection = new TestCollection(testAssembly, collectionDefinition: null, nameof(Instances));
            var testClass = new TestClass(testCollection, new ReflectionTypeInfo(typeof(Instances)));
            var testMethod = testClass.Class.GetMethods(false).Single(method => method.Name == nameof(Instances.VisualStudio));
            return new TestMethod(testClass, testMethod);
        }
 
        internal static IEnumerable<VisualStudioInstanceKey> GetSupportedInstances(ITestMethod testMethod, IAttributeInfo factAttribute)
        {
            var rootSuffix = GetRootSuffix(testMethod, factAttribute);
            var maxAttempts = GetMaxAttempts(testMethod, factAttribute);
            var environmentVariables = GetEnvironmentVariables(testMethod, factAttribute);
            return GetSupportedVersions(factAttribute, GetSettingsAttributes(testMethod).ToArray())
                .Select(version => new VisualStudioInstanceKey(version, rootSuffix, maxAttempts, environmentVariables));
        }
 
        private static string GetRootSuffix(ITestMethod testMethod, IAttributeInfo factAttribute)
        {
            return GetRootSuffix(factAttribute, GetSettingsAttributes(testMethod).ToArray());
        }
 
        private static int GetMaxAttempts(ITestMethod testMethod, IAttributeInfo factAttribute)
        {
            return GetMaxAttempts(factAttribute, GetSettingsAttributes(testMethod).ToArray());
        }
 
        private static string[] GetEnvironmentVariables(ITestMethod testMethod, IAttributeInfo factAttribute)
        {
            return GetEnvironmentVariables(factAttribute, GetSettingsAttributes(testMethod).ToArray());
        }
 
        private static IEnumerable<IAttributeInfo> GetSettingsAttributes(ITestMethod testMethod)
        {
            foreach (var attributeData in testMethod.Method.GetCustomAttributes(typeof(IdeSettingsAttribute)))
            {
                yield return attributeData;
            }
 
            foreach (var attributeData in testMethod.TestClass.Class.GetCustomAttributes(typeof(IdeSettingsAttribute)))
            {
                yield return attributeData;
            }
        }
 
        private static IEnumerable<VisualStudioVersion> GetSupportedVersions(IAttributeInfo factAttribute, IAttributeInfo[] settingsAttributes)
        {
            var minVersion = GetNamedArgument(
                factAttribute,
                settingsAttributes,
                nameof(IIdeSettingsAttribute.MinVersion),
                static value => value is not VisualStudioVersion.Unspecified,
                defaultValue: VisualStudioVersion.VS2012);
 
            var maxVersion = GetNamedArgument(
                factAttribute,
                settingsAttributes,
                nameof(IIdeSettingsAttribute.MaxVersion),
                static value => value is not VisualStudioVersion.Unspecified,
                defaultValue: VisualStudioVersion.VS18);
 
            for (var version = minVersion; version <= maxVersion; version++)
            {
#if MERGED_PIA
                if (version >= VisualStudioVersion.VS2012 && version < VisualStudioVersion.VS2022)
                {
                    continue;
                }
#else
                if (version >= VisualStudioVersion.VS2022)
                {
                    continue;
                }
#endif
 
                yield return version;
            }
        }
 
        private static string GetRootSuffix(IAttributeInfo factAttribute, IAttributeInfo[] settingsAttributes)
        {
            return GetNamedArgument(
                factAttribute,
                settingsAttributes,
                nameof(IIdeSettingsAttribute.RootSuffix),
                static value => value is not null,
                defaultValue: "Exp");
        }
 
        private static int GetMaxAttempts(IAttributeInfo factAttribute, IAttributeInfo[] settingsAttributes)
        {
            return GetNamedArgument(
                factAttribute,
                settingsAttributes,
                nameof(IIdeSettingsAttribute.MaxAttempts),
                static value => value > 0,
                defaultValue: 1);
        }
 
        private static string[] GetEnvironmentVariables(IAttributeInfo factAttribute, IAttributeInfo[] settingsAttributes)
        {
            return GetNamedArgument(
                factAttribute,
                settingsAttributes,
                nameof(IIdeSettingsAttribute.EnvironmentVariables),
                static value => value != null,
                (inherited, current) => MergeEnvironmentVariables(inherited, current),
                defaultValue: new string[0]);
        }
 
        private static string[] MergeEnvironmentVariables(string[] inherited, string[] current)
        {
            if (inherited.Length == 0)
            {
                return current;
            }
            else if (current.Length == 0)
            {
                return inherited;
            }
 
            var set = new HashSet<string>(KeyOnlyComparerIgnoreCase.Instance);
            foreach (var value in current)
            {
                set.Add(value);
            }
 
            foreach (var value in inherited)
            {
                set.Add(value);
            }
 
            return set.ToArray();
        }
 
        private static TValue GetNamedArgument<TValue>(IAttributeInfo factAttribute, IAttributeInfo[] settingsAttributes, string argumentName, Func<TValue, bool> isValidValue, TValue defaultValue)
        {
            return GetNamedArgument(
                factAttribute,
                settingsAttributes,
                argumentName,
                isValidValue,
                merge: null,
                defaultValue);
        }
 
        private static TValue GetNamedArgument<TValue>(IAttributeInfo factAttribute, IAttributeInfo[] settingsAttributes, string argumentName, Func<TValue, bool> isValidValue, Func<TValue, TValue, TValue>? merge, TValue defaultValue)
        {
            StrongBox<TValue>? result = null;
            if (TryGetNamedArgument(factAttribute, argumentName, isValidValue, out var value))
            {
                if (merge is null)
                {
                    return value;
                }
 
                result = new StrongBox<TValue>(value);
            }
 
            foreach (var attribute in settingsAttributes)
            {
                if (TryGetNamedArgument(attribute, argumentName, isValidValue, out value))
                {
                    if (merge is null)
                    {
                        return value;
                    }
                    else if (result is null)
                    {
                        result = new StrongBox<TValue>(value);
                    }
                    else
                    {
                        result.Value = merge(value, result.Value);
                    }
 
                    return value;
                }
            }
 
            if (result is not null)
            {
                return result.Value;
            }
 
            return defaultValue;
 
            static bool TryGetNamedArgument(IAttributeInfo attribute, string argumentName, Func<TValue, bool> isValidValue, out TValue value)
            {
                value = attribute.GetNamedArgument<TValue>(argumentName);
                return isValidValue(value);
            }
        }
 
        private class KeyOnlyComparerIgnoreCase : IEqualityComparer<string?>
        {
            public static readonly KeyOnlyComparerIgnoreCase Instance = new KeyOnlyComparerIgnoreCase();
 
            private KeyOnlyComparerIgnoreCase()
            {
            }
 
            public bool Equals(string? x, string? y)
            {
                if (x is null)
                {
                    return y is null;
                }
                else if (y is null)
                {
                    return false;
                }
 
                return StringComparer.OrdinalIgnoreCase.Equals(GetKey(x), GetKey(y));
            }
 
            public int GetHashCode(string? obj)
            {
                if (obj is null)
                {
                    return 0;
                }
 
                return StringComparer.OrdinalIgnoreCase.GetHashCode(GetKey(obj));
            }
 
            private static string GetKey(string s)
            {
                var keyEnd = s.IndexOf('=');
                if (keyEnd < 0)
                {
                    return s;
                }
 
                return s.Substring(0, keyEnd);
            }
        }
    }
}