diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs index d2430c222f..5f5ed56515 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs @@ -3,8 +3,8 @@ using System.Security; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; @@ -34,7 +34,10 @@ internal TestAssemblySettings GetSettings(string source) // Load the source. Assembly testAssembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly(source); - ParallelizeAttribute? parallelizeAttribute = ReflectHelper.GetParallelizeAttribute(testAssembly); + IReflectionOperations reflectionOperations = PlatformServiceProvider.Instance.ReflectionOperations; + ParallelizeAttribute? parallelizeAttribute = reflectionOperations.GetCustomAttributes(testAssembly, typeof(ParallelizeAttribute)) + .OfType() + .FirstOrDefault(); if (parallelizeAttribute != null) { @@ -47,7 +50,7 @@ internal TestAssemblySettings GetSettings(string source) } } - testAssemblySettings.CanParallelizeAssembly = !ReflectHelper.IsDoNotParallelizeSet(testAssembly); + testAssemblySettings.CanParallelizeAssembly = reflectionOperations.GetCustomAttributes(testAssembly, typeof(DoNotParallelizeAttribute)).Length == 0; return testAssemblySettings; } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs index 1b8756af00..d8535cf217 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs @@ -102,7 +102,7 @@ internal TestMethodInfo(MethodInfo testMethod, TestClassInfo parent) /// /// An array of the attributes. public Attribute[]? GetAllAttributes() - => [.. ReflectHelper.Instance.GetAttributes(MethodInfo)]; + => [.. PlatformServiceProvider.Instance.ReflectionOperations.GetAttributes(MethodInfo)]; /// /// Gets all attributes of the test method. @@ -111,7 +111,7 @@ internal TestMethodInfo(MethodInfo testMethod, TestClassInfo parent) /// An array of the attributes. public TAttributeType[] GetAttributes() where TAttributeType : Attribute - => [.. ReflectHelper.Instance.GetAttributes(MethodInfo)]; + => [.. PlatformServiceProvider.Instance.ReflectionOperations.GetAttributes(MethodInfo)]; /// /// Execute test method. Capture failures, handle async and return result. @@ -246,7 +246,7 @@ public virtual async Task InvokeAsync(object?[]? arguments) private TimeoutInfo GetTestTimeout() { DebugEx.Assert(MethodInfo != null, "TestMethod should be non-null"); - TimeoutAttribute? timeoutAttribute = ReflectHelper.Instance.GetFirstAttributeOrDefault(MethodInfo); + TimeoutAttribute? timeoutAttribute = PlatformServiceProvider.Instance.ReflectionOperations.GetFirstAttributeOrDefault(MethodInfo); if (timeoutAttribute is null) { return TimeoutInfo.FromTestTimeoutSettings(); @@ -269,7 +269,7 @@ private TestMethodAttribute GetTestMethodAttribute() { // Get the derived TestMethod attribute from reflection. // It should be non-null as it was already validated by IsValidTestMethod. - TestMethodAttribute testMethodAttribute = ReflectHelper.Instance.GetSingleAttributeOrDefault(MethodInfo)!; + TestMethodAttribute testMethodAttribute = PlatformServiceProvider.Instance.ReflectionOperations.GetSingleAttributeOrDefault(MethodInfo)!; // Get the derived TestMethod attribute from Extended TestClass Attribute // If the extended TestClass Attribute doesn't have extended TestMethod attribute then base class returns back the original testMethod Attribute @@ -285,7 +285,7 @@ private TestMethodAttribute GetTestMethodAttribute() /// private RetryBaseAttribute? GetRetryAttribute() { - IEnumerable attributes = ReflectHelper.Instance.GetAttributes(MethodInfo); + IEnumerable attributes = PlatformServiceProvider.Instance.ReflectionOperations.GetAttributes(MethodInfo); using IEnumerator enumerator = attributes.GetEnumerator(); if (!enumerator.MoveNext()) { diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs index 3f0332fa95..8915a45f14 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions; - using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; @@ -191,7 +190,7 @@ private async Task TryExecuteDataSourceBasedTestsAsync(List re private async Task TryExecuteFoldedDataDrivenTestsAsync(List results) { bool hasTestDataSource = false; - foreach (Attribute attribute in ReflectHelper.Instance.GetCustomAttributesCached(_testMethodInfo.MethodInfo)) + foreach (Attribute attribute in PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributesCached(_testMethodInfo.MethodInfo)) { if (attribute is not UTF.ITestDataSource testDataSource) { diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs index a2bbcbe98e..b27e0bba2c 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs @@ -9,7 +9,7 @@ internal static class AttributeExtensions { public static bool IsIgnored(this ICustomAttributeProvider type, out string? ignoreMessage) { - IEnumerable attributes = ReflectHelper.Instance.GetAttributes(type); + IEnumerable attributes = PlatformServiceProvider.Instance.ReflectionOperations.GetAttributes(type); IEnumerable> groups = attributes.GroupBy(attr => attr.GroupName); foreach (IGrouping? group in groups) { diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectionHelper.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectionHelper.cs new file mode 100644 index 0000000000..7d2f960814 --- /dev/null +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectionHelper.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; + +/// +/// Helper methods for extracting test metadata from reflection operations. +/// +internal static class ReflectionHelper +{ + /// + /// Get categories applied to the test method. + /// + /// The reflection operations to use. + /// The member to inspect. + /// The reflected type that owns . + /// Categories defined. + public static string[] GetTestCategories(this IReflectionOperations reflectionOperations, MemberInfo categoryAttributeProvider, Type owningType) + { + IEnumerable methodCategories = reflectionOperations.GetAttributes(categoryAttributeProvider); + IEnumerable typeCategories = reflectionOperations.GetAttributes(owningType); + IEnumerable assemblyCategories = reflectionOperations.GetAttributes(owningType.Assembly); + + return [.. methodCategories.Concat(typeCategories).Concat(assemblyCategories).SelectMany(c => c.TestCategories)]; + } + + /// + /// KeyValue pairs that are provided by TestPropertyAttributes of the given test method. + /// + /// The reflection operations to use. + /// The member to inspect. + /// List of traits. + public static Trait[] GetTestPropertiesAsTraits(this IReflectionOperations reflectionOperations, MethodInfo testPropertyProvider) + { + Attribute[] attributesFromMethod = reflectionOperations.GetCustomAttributesCached(testPropertyProvider); + Attribute[] attributesFromClass = testPropertyProvider.ReflectedType is { } testClass ? reflectionOperations.GetCustomAttributesCached(testClass) : []; + int countTestPropertyAttribute = 0; + foreach (Attribute attribute in attributesFromMethod) + { + if (attribute is TestPropertyAttribute) + { + countTestPropertyAttribute++; + } + } + + foreach (Attribute attribute in attributesFromClass) + { + if (attribute is TestPropertyAttribute) + { + countTestPropertyAttribute++; + } + } + + if (countTestPropertyAttribute == 0) + { + // This is the common case that we optimize for. This method used to be an iterator (uses yield return) which is allocating unnecessarily in common cases. + return []; + } + + var traits = new Trait[countTestPropertyAttribute]; + int index = 0; + foreach (Attribute attribute in attributesFromMethod) + { + if (attribute is TestPropertyAttribute testProperty) + { + traits[index++] = new Trait(testProperty.Name, testProperty.Value); + } + } + + foreach (Attribute attribute in attributesFromClass) + { + if (attribute is TestPropertyAttribute testProperty) + { + traits[index++] = new Trait(testProperty.Name, testProperty.Value); + } + } + + return traits; + } +} diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs index b154b531cc..ca23e62048 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs @@ -43,4 +43,60 @@ internal interface IReflectionOperations Type? GetType(Assembly assembly, string typeName); object? CreateInstance(Type type, object?[] parameters); + + /// + /// Checks to see if a member or type is decorated with the given attribute, or an attribute that derives from it. + /// + /// Attribute to search for. + /// Member to inspect for attributes. + /// True if the attribute of the specified type is defined on this member or a class. + bool IsAttributeDefined(MemberInfo memberInfo) + where TAttribute : Attribute; + + /// + /// Gets first attribute that matches the type. + /// Use this together with attribute that does not allow multiple and is sealed. In such case there cannot be more attributes, and this will avoid the cost of + /// checking for more than one attribute. + /// + /// Type of the attribute to find. + /// The type, assembly or method. + /// The attribute that is found or null. + TAttribute? GetFirstAttributeOrDefault(ICustomAttributeProvider attributeProvider) + where TAttribute : Attribute; + + /// + /// Gets first attribute that matches the type or is derived from it. + /// Use this together with attribute that does not allow multiple. In such case there cannot be more attributes, and this will avoid the cost of + /// checking for more than one attribute. + /// + /// Type of the attribute to find. + /// The type, assembly or method. + /// The attribute that is found or null. + /// Throws when multiple attributes are found (the attribute must allow multiple). + TAttribute? GetSingleAttributeOrDefault(ICustomAttributeProvider attributeProvider) + where TAttribute : Attribute; + + /// + /// Get attribute defined on a member which is of given type of subtype of given type. + /// + /// The attribute type. + /// The member to inspect. + /// An instance of the attribute. + IEnumerable GetAttributes(ICustomAttributeProvider attributeProvider) + where TAttributeType : Attribute; + + /// + /// Gets and caches the attributes for the given type, or method. + /// + /// The member to inspect. + /// Attributes defined. + Attribute[] GetCustomAttributesCached(ICustomAttributeProvider attributeProvider); + + /// + /// Checks whether the declaring type of the method is declared in the same assembly as the given type. + /// + /// The method to check. + /// The type whose assembly to compare against. + /// True if the method's declaring type is in the same assembly as . + bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Type type); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs index df7e49c9b2..89ff808115 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs @@ -1,21 +1,27 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Security; + +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -#if NETFRAMEWORK -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; -#endif +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; /// /// This service is responsible for platform specific reflection operations. /// -internal sealed class ReflectionOperations : IReflectionOperations +internal sealed class ReflectionOperations : MarshalByRefObject, IReflectionOperations { private const BindingFlags DeclaredOnlyLookup = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly; private const BindingFlags Everything = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; + // PERF: This was moved from Dictionary> to Concurrent + // storing an array allows us to store multiple attributes of the same type if we find them. It also has lower memory footprint, and is faster + // when we are going through the whole collection. Giving us overall better perf. + private readonly ConcurrentDictionary _attributeCache = []; + /// /// Gets all the custom attributes adorned on a member. /// @@ -23,20 +29,15 @@ internal sealed class ReflectionOperations : IReflectionOperations /// The list of attributes on the member. Empty list if none found. [return: NotNullIfNotNull(nameof(memberInfo))] public object[]? GetCustomAttributes(MemberInfo memberInfo) -#if NETFRAMEWORK - => [.. ReflectionOperationsNetFrameworkAttributeHelpers.GetCustomAttributes(memberInfo)]; -#else { object[] attributes = memberInfo.GetCustomAttributes(typeof(Attribute), inherit: true); - // Ensures that when the return of this method is used here: - // https://github.com/microsoft/testfx/blob/e101a9d48773cc935c7b536d25d378d9a3211fee/src/Adapter/MSTest.TestAdapter/Helpers/ReflectHelper.cs#L461 + // Ensures that when the return of this method is used with GetCustomAttributesCached // then we are already Attribute[] to avoid LINQ Cast and extra array allocation. // This assert is solely for performance. Nothing "functional" will go wrong if the assert failed. Debug.Assert(attributes is Attribute[], $"Expected Attribute[], found '{attributes.GetType()}'."); return attributes; } -#endif /// /// Gets all the custom attributes of a given type on an assembly. @@ -44,12 +45,8 @@ internal sealed class ReflectionOperations : IReflectionOperations /// The assembly. /// The attribute type. /// The list of attributes of the given type on the member. Empty list if none found. - public object[] GetCustomAttributes(Assembly assembly, Type type) => -#if NETFRAMEWORK - ReflectionOperationsNetFrameworkAttributeHelpers.GetCustomAttributes(assembly, type).ToArray(); -#else - assembly.GetCustomAttributes(type, inherit: true); -#endif + public object[] GetCustomAttributes(Assembly assembly, Type type) + => assembly.GetCustomAttributes(type, inherit: true); #pragma warning disable IL2070 // this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to 'target method'. #pragma warning disable IL2026 // Members attributed with RequiresUnreferencedCode may break when trimming @@ -92,4 +89,201 @@ public MethodInfo[] GetRuntimeMethods(Type type) #pragma warning restore IL2026 // Members attributed with RequiresUnreferencedCode may break when trimming #pragma warning restore IL2067 // 'target parameter' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to 'target method'. #pragma warning restore IL2057 // Unrecognized value passed to the typeName parameter of 'System.Type.GetType(String)' + + /// + /// Checks to see if a member or type is decorated with the given attribute, or an attribute that derives from it. e.g. [MyTestClass] from [TestClass] will match if you look for [TestClass]. The inherit parameter does not impact this checking. + /// + /// Attribute to search for. + /// Member to inspect for attributes. + /// True if the attribute of the specified type is defined on this member or a class. + public bool IsAttributeDefined(MemberInfo memberInfo) + where TAttribute : Attribute + { + ArgumentNullException.ThrowIfNull(memberInfo); + + // Get all attributes on the member. + Attribute[] attributes = GetCustomAttributesCached(memberInfo); + + // Try to find the attribute that is derived from baseAttrType. + foreach (Attribute attribute in attributes) + { + DebugEx.Assert(attribute != null, $"{nameof(ReflectionOperations)}.{nameof(GetCustomAttributesCached)}: internal error: wrong value in the attributes dictionary."); + + if (attribute is TAttribute) + { + return true; + } + } + + return false; + } + + /// + /// Returns object to be used for controlling lifetime, null means infinite lifetime. + /// + /// + /// The . + /// + [SecurityCritical] +#if NET5_0_OR_GREATER + [Obsolete] +#endif + public override object InitializeLifetimeService() => null!; + + /// + /// Gets first attribute that matches the type. + /// Use this together with attribute that does not allow multiple and is sealed. In such case there cannot be more attributes, and this will avoid the cost of + /// checking for more than one attribute. + /// + /// Type of the attribute to find. + /// The type, assembly or method. + /// The attribute that is found or null. + public TAttribute? GetFirstAttributeOrDefault(ICustomAttributeProvider attributeProvider) + where TAttribute : Attribute + { + // If the attribute is not sealed, then it can allow multiple, even if AllowMultiple is false. + // This happens when a derived type is also applied along with the base type. + // Or, if the derived type modifies the attribute usage to allow multiple. + // So we want to ensure this is only called for sealed attributes. + DebugEx.Assert(typeof(TAttribute).IsSealed, $"Expected '{typeof(TAttribute)}' to be sealed, but was not."); + + Attribute[] cachedAttributes = GetCustomAttributesCached(attributeProvider); + + foreach (Attribute cachedAttribute in cachedAttributes) + { + if (cachedAttribute is TAttribute cachedAttributeAsTAttribute) + { + return cachedAttributeAsTAttribute; + } + } + + return null; + } + + /// + /// Gets first attribute that matches the type or is derived from it. + /// Use this together with attribute that does not allow multiple. In such case there cannot be more attributes, and this will avoid the cost of + /// checking for more than one attribute. + /// + /// Type of the attribute to find. + /// The type, assembly or method. + /// The attribute that is found or null. + /// Throws when multiple attributes are found (the attribute must allow multiple). + public TAttribute? GetSingleAttributeOrDefault(ICustomAttributeProvider attributeProvider) + where TAttribute : Attribute + { + Attribute[] cachedAttributes = GetCustomAttributesCached(attributeProvider); + + TAttribute? foundAttribute = null; + foreach (Attribute cachedAttribute in cachedAttributes) + { + if (cachedAttribute is TAttribute cachedAttributeAsTAttribute) + { + if (foundAttribute is not null) + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resource.DuplicateAttributeError, typeof(TAttribute))); + } + + foundAttribute = cachedAttributeAsTAttribute; + } + } + + return foundAttribute; + } + + /// + /// Get attribute defined on a method which is of given type of subtype of given type. + /// + /// The attribute type. + /// The member to inspect. + /// An instance of the attribute. + public IEnumerable GetAttributes(ICustomAttributeProvider attributeProvider) + where TAttributeType : Attribute + { + Attribute[] attributes = GetCustomAttributesCached(attributeProvider); + + // Try to find the attribute that is derived from baseAttrType. + foreach (Attribute attribute in attributes) + { + DebugEx.Assert(attribute != null, "ReflectionOperations.DefinesAttributeDerivedFrom: internal error: wrong value in the attributes dictionary."); + + if (attribute is TAttributeType attributeAsAttributeType) + { + yield return attributeAsAttributeType; + } + } + } + + /// + /// Gets and caches the attributes for the given type, or method. + /// + /// The member to inspect. + /// attributes defined. + public Attribute[] GetCustomAttributesCached(ICustomAttributeProvider attributeProvider) + { + // If the information is cached, then use it otherwise populate the cache using + // the reflection APIs. + return _attributeCache.GetOrAdd(attributeProvider, GetAttributes); + + // We are avoiding func allocation here. + static Attribute[] GetAttributes(ICustomAttributeProvider attributeProvider) + { + // Populate the cache + try + { + object[]? attributes = NotCachedReflectionAccessor.GetCustomAttributesNotCached(attributeProvider); + return attributes is null ? [] : attributes as Attribute[] ?? [.. attributes.Cast()]; + } + catch (Exception ex) + { + // Get the exception description + string description; + try + { + // Can throw if the Message or StackTrace properties throw exceptions + description = ex.ToString(); + } + catch (Exception ex2) + { + description = string.Format(CultureInfo.CurrentCulture, Resource.ExceptionOccuredWhileGettingTheExceptionDescription, ex.GetType().FullName, ex2.GetType().FullName); // ex.GetType().FullName + + } + + if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsWarningEnabled) + { + PlatformServiceProvider.Instance.AdapterTraceLogger.Warning(Resource.FailedToGetCustomAttribute, attributeProvider.GetType().FullName!, description); + } + + return []; + } + } + } + + /// + /// Reflection helper that is accessing Reflection directly, and won't cache the results. + /// + internal static class NotCachedReflectionAccessor + { + /// + /// Get custom attributes on a member without cache. Be CAREFUL where you use this, repeatedly accessing reflection without caching the results degrades the performance. + /// + /// Member for which attributes needs to be retrieved. + /// All attributes of give type on member. + public static object[]? GetCustomAttributesNotCached(ICustomAttributeProvider attributeProvider) + { + IReflectionOperations reflectionOperations = PlatformServiceProvider.Instance.ReflectionOperations; + object[] attributesArray = attributeProvider is MemberInfo memberInfo + ? reflectionOperations.GetCustomAttributes(memberInfo) + : reflectionOperations.GetCustomAttributes((Assembly)attributeProvider, typeof(Attribute)); + + return attributesArray; // TODO: Investigate if we rely on NRE + } + } + + /// + public bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Type type) + => method.DeclaringType!.Assembly.Equals(type.Assembly); + + internal /* for tests */ void ClearCache() + // Tests manipulate the platform reflection provider, and we end up caching different attributes than the class / method actually has. + => _attributeCache.Clear(); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs index 2fc480dc1e..6e2a76abb0 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs @@ -4,7 +4,6 @@ #if !WINDOWS_UWP && !WIN_UI using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; @@ -35,7 +34,7 @@ internal sealed class TestDeployment : ITestDeployment /// Initializes a new instance of the class. /// public TestDeployment() - : this(new DeploymentItemUtility(ReflectHelper.Instance), new DeploymentUtility(), new FileUtility()) + : this(new DeploymentItemUtility((ReflectionOperations)PlatformServiceProvider.Instance.ReflectionOperations), new DeploymentUtility(), new FileUtility()) { } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs index 294c2366cf..e6ead6244c 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs @@ -3,8 +3,8 @@ #if !WINDOWS_UWP && !WIN_UI -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -15,7 +15,7 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Uti /// internal sealed class DeploymentItemUtility { - private readonly ReflectHelper _reflectHelper; + private readonly IReflectionOperations _reflectionOperation; /// /// A cache for class level deployment items. @@ -25,10 +25,10 @@ internal sealed class DeploymentItemUtility /// /// Initializes a new instance of the class. /// - /// The reflect helper. - internal DeploymentItemUtility(ReflectHelper reflectHelper) + /// The reflect helper. + internal DeploymentItemUtility(IReflectionOperations reflectionOperation) { - _reflectHelper = reflectHelper; + _reflectionOperation = reflectionOperation; _classLevelDeploymentItems = []; } @@ -42,7 +42,7 @@ internal IList GetClassLevelDeploymentItems(Type type, ICollecti { if (!_classLevelDeploymentItems.TryGetValue(type, out IList? value)) { - IEnumerable deploymentItemAttributes = _reflectHelper.GetAttributes(type); + IEnumerable deploymentItemAttributes = _reflectionOperation.GetAttributes(type); value = GetDeploymentItems(deploymentItemAttributes, warnings); _classLevelDeploymentItems[type] = value; } @@ -59,7 +59,7 @@ internal IList GetClassLevelDeploymentItems(Type type, ICollecti internal KeyValuePair[]? GetDeploymentItems(MethodInfo method, IEnumerable classLevelDeploymentItems, ICollection warnings) { - List testLevelDeploymentItems = GetDeploymentItems(_reflectHelper.GetAttributes(method), warnings); + List testLevelDeploymentItems = GetDeploymentItems(_reflectionOperation.GetAttributes(method), warnings); return ToKeyValuePairs(Concat(testLevelDeploymentItems, classLevelDeploymentItems)); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs index c9c93d3fd6..da892aa22c 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs @@ -4,7 +4,6 @@ #if !WINDOWS_UWP && !WIN_UI using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions; @@ -26,7 +25,7 @@ internal abstract class DeploymentUtilityBase protected const string DeploymentFolderPrefix = "Deploy"; public DeploymentUtilityBase() - : this(new DeploymentItemUtility(ReflectHelper.Instance), new AssemblyUtility(), new FileUtility()) + : this(new DeploymentItemUtility((ReflectionOperations)PlatformServiceProvider.Instance.ReflectionOperations), new AssemblyUtility(), new FileUtility()) { } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/ReflectionOperationsNetFrameworkAttributeHelpers.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/ReflectionOperationsNetFrameworkAttributeHelpers.cs deleted file mode 100644 index 7dfd9fbf17..0000000000 --- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/ReflectionOperationsNetFrameworkAttributeHelpers.cs +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -#if NETFRAMEWORK - -namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; - -/// -/// This class is only intended to be used by ReflectionOperations on .NET Framework. -/// TODO: Investigate why we need complicated logic under .NET Framework, and whether it -/// can be simplified to have unified simple logic for .NET Core and .NET Framework. -/// -internal static class ReflectionOperationsNetFrameworkAttributeHelpers -{ - /// - /// Gets all the custom attributes adorned on a member. - /// - /// The member. - /// The list of attributes on the member. Empty list if none found. - internal static IReadOnlyList GetCustomAttributes(MemberInfo memberInfo) - => GetCustomAttributesCore(memberInfo, type: null); - - /// - /// Get custom attributes on a member for both normal and reflection only load. - /// - /// Member for which attributes needs to be retrieved. - /// Type of attribute to retrieve. - /// All attributes of give type on member. -#pragma warning disable CA1859 // Use concrete types when possible for improved performance - private static IReadOnlyList GetCustomAttributesCore(MemberInfo memberInfo, Type? type) -#pragma warning restore CA1859 - { - bool shouldGetAllAttributes = type == null; - - if (!IsReflectionOnlyLoad(memberInfo)) - { - return shouldGetAllAttributes ? memberInfo.GetCustomAttributes(inherit: true) : memberInfo.GetCustomAttributes(type, inherit: true); - } - else - { - List nonUniqueAttributes = []; - Dictionary uniqueAttributes = []; - - int inheritanceThreshold = 10; - int inheritanceLevel = 0; - - if (memberInfo.MemberType == MemberTypes.TypeInfo) - { - // This code is based on the code for fetching CustomAttributes in System.Reflection.CustomAttribute(RuntimeType type, RuntimeType caType, bool inherit) - var tempTypeInfo = memberInfo as TypeInfo; - - do - { - IList attributes = CustomAttributeData.GetCustomAttributes(tempTypeInfo); - AddNewAttributes( - attributes, - shouldGetAllAttributes, - type!, - uniqueAttributes, - nonUniqueAttributes); - tempTypeInfo = tempTypeInfo!.BaseType?.GetTypeInfo(); - inheritanceLevel++; - } - while (tempTypeInfo != null && tempTypeInfo != typeof(object).GetTypeInfo() - && inheritanceLevel < inheritanceThreshold); - } - else if (memberInfo.MemberType == MemberTypes.Method) - { - // This code is based on the code for fetching CustomAttributes in System.Reflection.CustomAttribute(RuntimeMethodInfo method, RuntimeType caType, bool inherit). - var tempMethodInfo = memberInfo as MethodInfo; - - do - { - IList attributes = CustomAttributeData.GetCustomAttributes(tempMethodInfo); - AddNewAttributes( - attributes, - shouldGetAllAttributes, - type!, - uniqueAttributes, - nonUniqueAttributes); - MethodInfo? baseDefinition = tempMethodInfo!.GetBaseDefinition(); - - if (baseDefinition != null - && string.Equals( - string.Concat(tempMethodInfo.DeclaringType.FullName, tempMethodInfo.Name), - string.Concat(baseDefinition.DeclaringType.FullName, baseDefinition.Name), StringComparison.Ordinal)) - { - break; - } - - tempMethodInfo = baseDefinition; - inheritanceLevel++; - } - while (tempMethodInfo != null && inheritanceLevel < inheritanceThreshold); - } - else - { - // Ideally we should not be reaching here. We only query for attributes on types/methods currently. - // Return the attributes that CustomAttributeData returns in this cases not considering inheritance. - IList firstLevelAttributes = - CustomAttributeData.GetCustomAttributes(memberInfo); - AddNewAttributes(firstLevelAttributes, shouldGetAllAttributes, type!, uniqueAttributes, nonUniqueAttributes); - } - - nonUniqueAttributes.AddRange(uniqueAttributes.Values); - return nonUniqueAttributes; - } - } - - internal static List GetCustomAttributes(Assembly assembly, Type type) - { - if (!assembly.ReflectionOnly) - { - return [.. assembly.GetCustomAttributes(type)]; - } - - List customAttributes = [.. CustomAttributeData.GetCustomAttributes(assembly)]; - - List attributesArray = []; - - foreach (CustomAttributeData attribute in customAttributes) - { - if (!IsTypeInheriting(attribute.Constructor.DeclaringType, type) - && !attribute.Constructor.DeclaringType.AssemblyQualifiedName.Equals( - type.AssemblyQualifiedName, StringComparison.Ordinal)) - { - continue; - } - - Attribute? attributeInstance = CreateAttributeInstance(attribute); - if (attributeInstance != null) - { - attributesArray.Add(attributeInstance); - } - } - - return attributesArray; - } - - /// - /// Create instance of the attribute for reflection only load. - /// - /// The attribute data. - /// An attribute. - private static Attribute? CreateAttributeInstance(CustomAttributeData attributeData) - { - object? attribute = null; - try - { - // Create instance of attribute. For some case, constructor param is returned as ReadOnlyCollection - // instead of array. So convert it to array else constructor invoke will fail. - var attributeType = Type.GetType(attributeData.Constructor.DeclaringType.AssemblyQualifiedName); - - List constructorParameters = []; - List constructorArguments = []; - foreach (CustomAttributeTypedArgument parameter in attributeData.ConstructorArguments) - { - var parameterType = Type.GetType(parameter.ArgumentType.AssemblyQualifiedName); - constructorParameters.Add(parameterType); - if (!parameterType.IsArray - || parameter.Value is not IEnumerable enumerable) - { - constructorArguments.Add(parameter.Value); - continue; - } - - ArrayList list = []; - foreach (object? item in enumerable) - { - if (item is CustomAttributeTypedArgument argument) - { - list.Add(argument.Value); - } - else - { - list.Add(item); - } - } - - constructorArguments.Add(list.ToArray(parameterType.GetElementType())); - } - - ConstructorInfo constructor = attributeType.GetConstructor([.. constructorParameters]); - attribute = constructor.Invoke([.. constructorArguments]); - - foreach (CustomAttributeNamedArgument namedArgument in attributeData.NamedArguments) - { - attributeType.GetProperty(namedArgument.MemberInfo.Name).SetValue(attribute, namedArgument.TypedValue.Value, null); - } - } - - // If not able to create instance of attribute ignore attribute. (May happen for custom user defined attributes). - catch (BadImageFormatException) - { - } - catch (FileLoadException) - { - } - catch (TypeLoadException) - { - } - - return attribute as Attribute; - } - - private static void AddNewAttributes( - IList customAttributes, - bool shouldGetAllAttributes, - Type type, - Dictionary uniqueAttributes, - List nonUniqueAttributes) - { - foreach (CustomAttributeData attribute in customAttributes) - { - if (!shouldGetAllAttributes - && !IsTypeInheriting(attribute.Constructor.DeclaringType, type) - && !attribute.Constructor.DeclaringType.AssemblyQualifiedName.Equals( - type.AssemblyQualifiedName, StringComparison.Ordinal)) - { - continue; - } - - Attribute? attributeInstance = CreateAttributeInstance(attribute); - if (attributeInstance == null) - { - continue; - } - - Type attributeType = attributeInstance.GetType(); - IReadOnlyList attributeUsageAttributes = GetCustomAttributesCore( - attributeType, - typeof(AttributeUsageAttribute)); - if (attributeUsageAttributes.Count > 0 - && attributeUsageAttributes[0] is AttributeUsageAttribute { AllowMultiple: false }) - { - if (!uniqueAttributes.ContainsKey(attributeType.FullName)) - { - uniqueAttributes.Add(attributeType.FullName, attributeInstance); - } - } - else - { - nonUniqueAttributes.Add(attributeInstance); - } - } - } - - /// - /// Check whether the member is loaded in a reflection only context. - /// - /// The member Info. - /// True if the member is loaded in a reflection only context. - private static bool IsReflectionOnlyLoad(MemberInfo? memberInfo) - => memberInfo != null && memberInfo.Module.Assembly.ReflectionOnly; - - private static bool IsTypeInheriting(Type? type1, Type type2) - { - while (type1 != null) - { - if (type1.AssemblyQualifiedName.Equals(type2.AssemblyQualifiedName, StringComparison.Ordinal)) - { - return true; - } - - type1 = type1.BaseType; - } - - return false; - } -} - -#endif diff --git a/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs b/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs index 1fc4f89cd2..7f7ba9c924 100644 --- a/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs +++ b/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs @@ -4,7 +4,6 @@ using AwesomeAssertions; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; using SampleFrameworkExtensions; @@ -12,9 +11,13 @@ namespace PlatformServices.Desktop.ComponentTests; +/// +/// Integration tests for ReflectionOperations which provides platform-specific reflection operations. +/// public class ReflectionUtilityTests : TestContainer { private readonly Assembly _testAsset; + private readonly ReflectionOperations _reflectionOperations = new(); public ReflectionUtilityTests() { @@ -30,62 +33,86 @@ public ReflectionUtilityTests() #endif currentAssemblyDirectory.Name /* TFM (e.g. net462) */, "TestProjectForDiscovery.dll"); - _testAsset = Assembly.ReflectionOnlyLoadFrom(testAssetPath); - - // This is needed for System assemblies. - AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ReflectionOnlyOnResolve; + _testAsset = Assembly.LoadFrom(testAssetPath); } public void GetCustomAttributesShouldReturnAllAttributes() { MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestBaseClass").GetMethod("DummyVTestMethod1"); - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(methodInfo); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); string[] expectedAttributes = ["TestCategory : base", "Owner : base"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().BeEquivalentTo(expectedAttributes); } public void GetCustomAttributesShouldReturnAllAttributesWithBaseInheritance() { MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass").GetMethod("DummyVTestMethod1"); - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(methodInfo); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo); attributes.Should().NotBeNull(); attributes.Should().HaveCount(3); // Notice that the Owner on the base method does not show up since it can only be defined once. string[] expectedAttributes = ["TestCategory : derived", "TestCategory : base", "Owner : derived"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().BeEquivalentTo(expectedAttributes); } public void GetCustomAttributesOnTypeShouldReturnAllAttributes() { Type type = _testAsset.GetType("TestProjectForDiscovery.AttributeTestBaseClass"); - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(type); + object[]? attributes = _reflectionOperations.GetCustomAttributes(type); attributes.Should().NotBeNull(); attributes.Should().HaveCount(1); string[] expectedAttributes = ["TestCategory : ba"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritance() { Type type = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass"); - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(type); + object[]? attributes = _reflectionOperations.GetCustomAttributes(type); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); string[] expectedAttributes = ["TestCategory : a", "TestCategory : ba"]; + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); + } + + public void GetSpecificCustomAttributesShouldReturnAllAttributes() + { + MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestBaseClass").GetMethod("DummyVTestMethod1"); + + TestCategoryAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); + + attributes.Should().NotBeNull(); + attributes.Should().HaveCount(1); + + string[] expectedAttributes = ["TestCategory : base"]; + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + } + + public void GetSpecificCustomAttributesShouldReturnAllAttributesWithBaseInheritance() + { + MethodInfo methodInfo = + _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass").GetMethod("DummyVTestMethod1"); + + TestCategoryAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); + + attributes.Should().NotBeNull(); + attributes.Should().HaveCount(2); + + string[] expectedAttributes = ["TestCategory : derived", "TestCategory : base"]; GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } @@ -93,33 +120,78 @@ public void GetCustomAttributesShouldReturnAllAttributesIncludingUserDefinedAttr { MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClassWithCustomAttributes").GetMethod("DummyVTestMethod1"); - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(methodInfo); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo); attributes.Should().NotBeNull(); attributes.Should().HaveCount(3); string[] expectedAttributes = ["Duration : superfast", "TestCategory : base", "Owner : base"]; + GetAttributeValuePairs(attributes!).Should().BeEquivalentTo(expectedAttributes); + } + + public void GetSpecificCustomAttributesShouldReturnAllAttributesIncludingUserDefinedAttributes() + { + MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClassWithCustomAttributes").GetMethod("DummyVTestMethod1"); + + TestPropertyAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); + + attributes.Should().NotBeNull(); + attributes.Should().HaveCount(2); + + string[] expectedAttributes = ["Duration : superfast", "Owner : base"]; GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } - public void GetSpecificCustomAttributesOnAssemblyShouldReturnAllAttributes() + public void GetSpecificCustomAttributesShouldReturnArrayAttributesAsWell() { - Assembly asm = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass").Assembly; + MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClassWithCustomAttributes").GetMethod("DummyTestMethod2"); + + CategoryArrayAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); + + attributes.Should().NotBeNull(); + attributes.Should().HaveCount(1); + + string[] expectedAttributes = ["CategoryAttribute : foo,foo2"]; + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + } + + public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributes() + { + Type type = _testAsset.GetType("TestProjectForDiscovery.AttributeTestBaseClass"); + + TestCategoryAttribute[] attributes = _reflectionOperations.GetAttributes(type).ToArray(); + + attributes.Should().NotBeNull(); + attributes.Should().HaveCount(1); + + string[] expectedAttributes = ["TestCategory : ba"]; + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + } + + public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritance() + { + Type type = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass"); - object[] attributes = new ReflectionOperations().GetCustomAttributes(asm, typeof(TestCategoryAttribute)); + TestCategoryAttribute[] attributes = _reflectionOperations.GetAttributes(type).ToArray(); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); - string[] expectedAttributes = ["TestCategory : a1", "TestCategory : a2"]; + string[] expectedAttributes = ["TestCategory : a", "TestCategory : ba"]; GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } - private static Assembly ReflectionOnlyOnResolve(object sender, ResolveEventArgs args) + public void GetSpecificCustomAttributesOnAssemblyShouldReturnAllAttributes() { - string assemblyNameToLoad = AppDomain.CurrentDomain.ApplyPolicy(args.Name); + Assembly asm = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass").Assembly; + + object[] attributes = _reflectionOperations.GetCustomAttributes(asm, typeof(TestCategoryAttribute)); + + attributes.Should().NotBeNull(); + attributes.Should().HaveCount(2); - return Assembly.ReflectionOnlyLoad(assemblyNameToLoad); + string[] expectedAttributes = ["TestCategory : a1", "TestCategory : a2"]; + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } private static string[] GetAttributeValuePairs(IEnumerable attributes) diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs index 2766c1f9da..d962325fb1 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs @@ -7,8 +7,9 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; @@ -322,13 +323,14 @@ internal sealed class TestableAssemblyEnumerator : AssemblyEnumerator { internal TestableAssemblyEnumerator() { - var reflectHelper = new Mock(); - var typeValidator = new Mock(reflectHelper.Object); - var testMethodValidator = new Mock(reflectHelper.Object, false); + var mockReflectionOperations = new Mock(); + IReflectionOperations wrappedReflectionOperations = MockableReflectionOperations.Create(mockReflectionOperations); + var typeValidator = new Mock(wrappedReflectionOperations); + var testMethodValidator = new Mock(wrappedReflectionOperations, false); MockTypeEnumerator = new Mock( typeof(DummyTestClass), "DummyAssembly", - reflectHelper.Object, + wrappedReflectionOperations, typeValidator.Object, testMethodValidator.Object); } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeValidatorTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeValidatorTests.cs index 34e7b0db56..35ed7372bf 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeValidatorTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeValidatorTests.cs @@ -4,7 +4,8 @@ using AwesomeAssertions; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using Moq; @@ -18,7 +19,7 @@ public class TypeValidatorTests : TestContainer #region private variables private readonly TypeValidator _typeValidator; - private readonly Mock _mockReflectHelper; + private readonly Mock _mockReflectionOperations; private readonly List _warnings; #endregion @@ -27,8 +28,8 @@ public class TypeValidatorTests : TestContainer public TypeValidatorTests() { - _mockReflectHelper = new Mock(); - _typeValidator = new TypeValidator(_mockReflectHelper.Object); + _mockReflectionOperations = new Mock(); + _typeValidator = new TypeValidator(_mockReflectionOperations.Object); _warnings = []; } @@ -40,14 +41,14 @@ public TypeValidatorTests() public void IsValidTestClassShouldReturnFalseForClassesNotHavingTestClassAttributeOrDerivedAttributeTypes() { - _mockReflectHelper.Setup(rh => rh.IsAttributeDefined(It.IsAny())).Returns(false); + _mockReflectionOperations.Setup(rh => rh.IsAttributeDefined(It.IsAny())).Returns(false); _typeValidator.IsValidTestClass(typeof(TypeValidatorTests), _warnings).Should().BeFalse(); } public void IsValidTestClassShouldReturnTrueForClassesMarkedByAnAttributeDerivedFromTestClass() { - _mockReflectHelper.Setup(rh => rh.IsAttributeDefined(It.IsAny())).Returns(false); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup(rh => rh.IsAttributeDefined(It.IsAny())).Returns(false); + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(It.IsAny())).Returns(true); _typeValidator.IsValidTestClass(typeof(TypeValidatorTests), _warnings).Should().BeTrue(); } @@ -102,7 +103,7 @@ public void IsValidTestClassShouldReturnTrueForNestedPublicTestClasses() public void WhenInternalDiscoveryIsEnabledIsValidTestClassShouldReturnTrueForInternalTestClasses() { - var typeValidator = new TypeValidator(_mockReflectHelper.Object, true); + var typeValidator = new TypeValidator(_mockReflectionOperations.Object, true); SetupTestClass(); typeValidator.IsValidTestClass(typeof(InternalTestClass), _warnings).Should().BeTrue(); @@ -110,7 +111,7 @@ public void WhenInternalDiscoveryIsEnabledIsValidTestClassShouldReturnTrueForInt public void WhenInternalDiscoveryIsEnabledIsValidTestClassShouldNotReportWarningForInternalTestClasses() { - var typeValidator = new TypeValidator(_mockReflectHelper.Object, true); + var typeValidator = new TypeValidator(_mockReflectionOperations.Object, true); SetupTestClass(); typeValidator.IsValidTestClass(typeof(InternalTestClass), _warnings); @@ -119,7 +120,7 @@ public void WhenInternalDiscoveryIsEnabledIsValidTestClassShouldNotReportWarning public void WhenInternalDiscoveryIsEnabledIsValidTestClassShouldReturnTrueForNestedInternalTestClasses() { - var typeValidator = new TypeValidator(_mockReflectHelper.Object, true); + var typeValidator = new TypeValidator(_mockReflectionOperations.Object, true); SetupTestClass(); typeValidator.IsValidTestClass(typeof(OuterClass.NestedInternalClass), _warnings).Should().BeTrue(); @@ -127,7 +128,7 @@ public void WhenInternalDiscoveryIsEnabledIsValidTestClassShouldReturnTrueForNes public void WhenInternalDiscoveryIsEnabledIsValidTestClassShouldReturnFalseForPrivateTestClasses() { - var typeValidator = new TypeValidator(_mockReflectHelper.Object, true); + var typeValidator = new TypeValidator(_mockReflectionOperations.Object, true); Type nestedPrivateClassType = Assembly.GetExecutingAssembly().GetTypes().First(t => t.Name == "NestedPrivateClass"); @@ -137,7 +138,7 @@ public void WhenInternalDiscoveryIsEnabledIsValidTestClassShouldReturnFalseForPr public void WhenInternalDiscoveryIsEnabledIsValidTestClassShouldReturnFalseForInaccessibleTestClasses() { - var typeValidator = new TypeValidator(_mockReflectHelper.Object, true); + var typeValidator = new TypeValidator(_mockReflectionOperations.Object, true); Type inaccessibleClassType = Assembly.GetExecutingAssembly().GetTypes().First(t => t.Name == "InaccessiblePublicClass"); @@ -147,7 +148,7 @@ public void WhenInternalDiscoveryIsEnabledIsValidTestClassShouldReturnFalseForIn public void WhenInternalDiscoveryIsEnabledIsValidTestClassShouldNotReportWarningsForNestedInternalTestClasses() { - var typeValidator = new TypeValidator(_mockReflectHelper.Object, true); + var typeValidator = new TypeValidator(_mockReflectionOperations.Object, true); SetupTestClass(); typeValidator.IsValidTestClass(typeof(OuterClass.NestedInternalClass), _warnings); @@ -392,7 +393,7 @@ private static Type[] GetAllTestTypes() #region private methods - private void SetupTestClass() => _mockReflectHelper.Setup(rh => rh.IsAttributeDefined(It.IsAny())).Returns(true); + private void SetupTestClass() => _mockReflectionOperations.Setup(rh => rh.IsAttributeDefined(It.IsAny())).Returns(true); #endregion } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/ClassCleanupManagerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/ClassCleanupManagerTests.cs index 3d2211b3fe..ef30abd9d5 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/ClassCleanupManagerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/ClassCleanupManagerTests.cs @@ -4,8 +4,8 @@ using AwesomeAssertions; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Moq; @@ -17,7 +17,6 @@ public class ClassCleanupManagerTests : TestContainer { public void AssemblyCleanupRunsAfterAllTestsFinishEvenIfWeScheduleTheSameTestMultipleTime() { - ReflectHelper reflectHelper = Mock.Of(); MethodInfo classCleanupMethodInfo = typeof(FakeTestClass).GetMethod(nameof(FakeTestClass.FakeClassCleanupMethod), BindingFlags.Instance | BindingFlags.NonPublic)!; // Full class name must agree between unitTestElement.TestMethod.FullClassName and testMethod.FullClassName; string fullClassName = typeof(FakeTestClass).FullName!; diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs index 257043dc55..072a31daf3 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs @@ -5,7 +5,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; @@ -56,8 +55,6 @@ public TestMethodRunnerTests() _testablePlatformServiceProvider = new TestablePlatformServiceProvider(); _testablePlatformServiceProvider.SetupMockReflectionOperations(); PlatformServiceProvider.Instance = _testablePlatformServiceProvider; - - ReflectHelper.Instance.ClearCache(); } private static TestClassInfo GetTestClassInfo() @@ -218,6 +215,7 @@ public async Task RunTestMethodShouldRunDataDrivenTestsWhenDataIsProvidedUsingDa // Setup mocks _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(_methodInfo)).Returns(attributes); + _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetAttributes(_methodInfo)).Returns([dataSourceAttribute]); var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => new TestResult { Outcome = UnitTestOutcome.Passed }); _testablePlatformServiceProvider.MockTestDataSource.Setup(tds => tds.GetData(testMethodInfo, _testContextImplementation)).Returns([1, 2, 3]); @@ -253,6 +251,7 @@ public async Task RunTestMethodShouldSetDisplayNameForDataDrivenTestsWhenDataIsP // Setup mocks _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(methodInfo)).Returns(attributes); + _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetAttributes(methodInfo)).Returns(attributes.OfType()); var testMethodInfo = new TestableTestMethodInfo(methodInfo, _testClassInfo, _testMethodOptions, () => new TestResult()); var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation); @@ -280,6 +279,7 @@ public async Task RunTestMethodShouldRunOnlyDataSourceTestsWhenBothDataSourceAnd // Setup mocks _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(_methodInfo)).Returns(attributes); + _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetAttributes(_methodInfo)).Returns([dataSourceAttribute]); var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => new TestResult()); var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation); @@ -309,6 +309,7 @@ public async Task RunTestMethodShouldFillInDisplayNameWithDataRowDisplayNameIfPr // Setup mocks _testablePlatformServiceProvider.MockReflectionOperations.Setup(ro => ro.GetCustomAttributes(_methodInfo)).Returns(attributes); + _testablePlatformServiceProvider.MockReflectionOperations.Setup(ro => ro.GetCustomAttributesCached(_methodInfo)).Returns(attributes); var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => testResult); var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation); @@ -333,6 +334,7 @@ public async Task RunTestMethodShouldFillInDisplayNameWithDataRowArgumentsIfNoDi // Setup mocks _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(_methodInfo)).Returns(attributes); + _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributesCached(_methodInfo)).Returns(attributes); var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => testResult); var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation); @@ -359,6 +361,7 @@ public async Task RunTestMethodShouldSetResultFilesIfPresentForDataDrivenTests() // Setup mocks _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(_methodInfo)).Returns(attributes); + _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributesCached(_methodInfo)).Returns(attributes); var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => testResult); var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs index b3579dd42c..d16bd026b0 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs @@ -4,8 +4,10 @@ using AwesomeAssertions; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; using Moq; @@ -16,13 +18,18 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Execution; public class TestPropertyAttributeTests : TestContainer { + private readonly TypeCache _typeCache; + private readonly ReflectionOperations _reflectionOperations; + public TestPropertyAttributeTests() { + _reflectionOperations = new ReflectionOperations(); + _typeCache = new TypeCache(_reflectionOperations); var testablePlatformServiceProvider = new TestablePlatformServiceProvider(); testablePlatformServiceProvider.MockFileOperations.Setup(x => x.LoadAssembly(It.IsAny())).Returns(GetType().Assembly); PlatformServiceProvider.Instance = testablePlatformServiceProvider; - ReflectHelper.Instance.ClearCache(); + _reflectionOperations.ClearCache(); } protected override void Dispose(bool disposing) @@ -35,11 +42,35 @@ protected override void Dispose(bool disposing) } } + private static TestContextImplementation CreateTestContextImplementationForMethod(TestMethod testMethod) + => new(testMethod, null, new Dictionary(), null, null); + + private static TestMethod CreateTestMethod(string methodName, string className, string assemblyName, string? displayName) + => new(className, methodName, null, methodName, className, assemblyName, displayName, null); + #region GetTestMethodInfo tests public void GetTestMethodInfoShouldAddPropertiesFromContainingClassCorrectly() { - TestPlatform.ObjectModel.Trait[] traits = [.. ReflectHelper.Instance.GetTestPropertiesAsTraits(typeof(DummyTestClassBase).GetMethod(nameof(DummyTestClassBase.VirtualTestMethodInBaseAndDerived))!)]; + string className = typeof(DummyTestClassBase).FullName!; + TestMethod testMethod = CreateTestMethod(nameof(DummyTestClassBase.VirtualTestMethodInBaseAndDerived), className, typeof(DummyTestClassBase).Assembly.GetName().Name!, displayName: null); + + TestContextImplementation testContext = CreateTestContextImplementationForMethod(testMethod); + + _ = _typeCache.GetTestMethodInfo( + testMethod, + testContext); + + testContext.TryGetPropertyValue("TestMethodKeyFromBase", out object? value1).Should().BeTrue(); + value1.Should().Be("TestMethodValueFromBase"); + + testContext.TryGetPropertyValue("DummyTestClassBaseKey1", out object? value2).Should().BeTrue(); + value2.Should().Be("DummyTestClassBaseValue1"); + + testContext.TryGetPropertyValue("DummyTestClassBaseKey2", out object? value3).Should().BeTrue(); + value3.Should().Be("DummyTestClassBaseValue2"); + + TestPlatform.ObjectModel.Trait[] traits = [.. _reflectionOperations.GetTestPropertiesAsTraits(typeof(DummyTestClassBase).GetMethod(nameof(DummyTestClassBase.VirtualTestMethodInBaseAndDerived))!)]; traits.Length.Should().Be(3); traits[0].Name.Should().Be("TestMethodKeyFromBase"); traits[0].Value.Should().Be("TestMethodValueFromBase"); @@ -51,7 +82,34 @@ public void GetTestMethodInfoShouldAddPropertiesFromContainingClassCorrectly() public void GetTestMethodInfoShouldAddPropertiesFromContainingClassAndBaseClassesAndOverriddenMethodsCorrectly_OverriddenIsTestMethod() { - TestPlatform.ObjectModel.Trait[] traits = [.. ReflectHelper.Instance.GetTestPropertiesAsTraits(typeof(DummyTestClassDerived).GetMethod(nameof(DummyTestClassDerived.VirtualTestMethodInBaseAndDerived))!)]; + string className = typeof(DummyTestClassDerived).FullName!; + TestMethod testMethod = CreateTestMethod(nameof(DummyTestClassDerived.VirtualTestMethodInBaseAndDerived), className, typeof(DummyTestClassBase).Assembly.GetName().Name!, displayName: null); + + TestContextImplementation testContext = CreateTestContextImplementationForMethod(testMethod); + + _ = _typeCache.GetTestMethodInfo( + testMethod, + testContext); + + testContext.TryGetPropertyValue("DerivedMethod1Key", out object? value1).Should().BeTrue(); + value1.Should().Be("DerivedMethod1Value"); + + testContext.TryGetPropertyValue("TestMethodKeyFromBase", out object? value2).Should().BeTrue(); + value2.Should().Be("TestMethodValueFromBase"); + + testContext.TryGetPropertyValue("DummyTestClassDerivedKey1", out object? value3).Should().BeTrue(); + value3.Should().Be("DummyTestClassValue1"); + + testContext.TryGetPropertyValue("DummyTestClassDerivedKey2", out object? value4).Should().BeTrue(); + value4.Should().Be("DummyTestClassValue2"); + + testContext.TryGetPropertyValue("DummyTestClassBaseKey1", out object? value5).Should().BeTrue(); + value5.Should().Be("DummyTestClassBaseValue1"); + + testContext.TryGetPropertyValue("DummyTestClassBaseKey2", out object? value6).Should().BeTrue(); + value6.Should().Be("DummyTestClassBaseValue2"); + + TestPlatform.ObjectModel.Trait[] traits = [.. _reflectionOperations.GetTestPropertiesAsTraits(typeof(DummyTestClassDerived).GetMethod(nameof(DummyTestClassDerived.VirtualTestMethodInBaseAndDerived))!)]; traits.Length.Should().Be(6); traits[0].Name.Should().Be("DerivedMethod1Key"); traits[0].Value.Should().Be("DerivedMethod1Value"); @@ -69,7 +127,34 @@ public void GetTestMethodInfoShouldAddPropertiesFromContainingClassAndBaseClasse public void GetTestMethodInfoShouldAddPropertiesFromContainingClassAndBaseClassesAndOverriddenMethodsCorrectly_OverriddenIsNotTestMethod() { - TestPlatform.ObjectModel.Trait[] traits = [.. ReflectHelper.Instance.GetTestPropertiesAsTraits(typeof(DummyTestClassDerived).GetMethod(nameof(DummyTestClassDerived.VirtualTestMethodInDerivedButNotTestMethodInBase))!)]; + string className = typeof(DummyTestClassDerived).FullName!; + TestMethod testMethod = CreateTestMethod(nameof(DummyTestClassDerived.VirtualTestMethodInDerivedButNotTestMethodInBase), className, typeof(DummyTestClassBase).Assembly.GetName().Name!, displayName: null); + + TestContextImplementation testContext = CreateTestContextImplementationForMethod(testMethod); + + _ = _typeCache.GetTestMethodInfo( + testMethod, + testContext); + + testContext.TryGetPropertyValue("DerivedMethod2Key", out object? value1).Should().BeTrue(); + value1.Should().Be("DerivedMethod2Value"); + + testContext.TryGetPropertyValue("NonTestMethodKeyFromBase", out object? value2).Should().BeTrue(); + value2.Should().Be("NonTestMethodValueFromBase"); + + testContext.TryGetPropertyValue("DummyTestClassDerivedKey1", out object? value3).Should().BeTrue(); + value3.Should().Be("DummyTestClassValue1"); + + testContext.TryGetPropertyValue("DummyTestClassDerivedKey2", out object? value4).Should().BeTrue(); + value4.Should().Be("DummyTestClassValue2"); + + testContext.TryGetPropertyValue("DummyTestClassBaseKey1", out object? value5).Should().BeTrue(); + value5.Should().Be("DummyTestClassBaseValue1"); + + testContext.TryGetPropertyValue("DummyTestClassBaseKey2", out object? value6).Should().BeTrue(); + value6.Should().Be("DummyTestClassBaseValue2"); + + TestPlatform.ObjectModel.Trait[] traits = [.. _reflectionOperations.GetTestPropertiesAsTraits(typeof(DummyTestClassDerived).GetMethod(nameof(DummyTestClassDerived.VirtualTestMethodInDerivedButNotTestMethodInBase))!)]; traits.Length.Should().Be(6); traits[0].Name.Should().Be("DerivedMethod2Key"); traits[0].Value.Should().Be("DerivedMethod2Value"); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Extensions/MethodInfoExtensionsTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Extensions/MethodInfoExtensionsTests.cs index 3044af962c..534abe3687 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Extensions/MethodInfoExtensionsTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Extensions/MethodInfoExtensionsTests.cs @@ -3,9 +3,12 @@ using AwesomeAssertions; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using TestFramework.ForTestingMSTest; @@ -14,60 +17,62 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Extensions public class MethodInfoExtensionsTests : TestContainer { + private readonly IReflectionOperations _reflectionOperations = PlatformServiceProvider.Instance.ReflectionOperations; + #region HasCorrectClassOrAssemblyInitializeSignature tests public void HasCorrectClassOrAssemblyInitializeSignatureShouldReturnFalseForNonStaticMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicMethod")!; - methodInfo.HasCorrectClassOrAssemblyInitializeSignature().Should().BeFalse(); + methodInfo.HasCorrectClassOrAssemblyInitializeSignature(_reflectionOperations).Should().BeFalse(); } public void HasCorrectClassOrAssemblyInitializeSignatureShouldReturnFalseForNonPublicMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("InternalStaticMethod", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)!; - methodInfo.HasCorrectClassOrAssemblyInitializeSignature().Should().BeFalse(); + methodInfo.HasCorrectClassOrAssemblyInitializeSignature(_reflectionOperations).Should().BeFalse(); } public void HasCorrectClassOrAssemblyInitializeSignatureShouldReturnFalseForMethodsNotHavingOneParameter() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicStaticMethod")!; - methodInfo.HasCorrectClassOrAssemblyInitializeSignature().Should().BeFalse(); + methodInfo.HasCorrectClassOrAssemblyInitializeSignature(_reflectionOperations).Should().BeFalse(); } public void HasCorrectClassOrAssemblyInitializeSignatureShouldReturnFalseForMethodsNotTestContextParameter() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicStaticMethodWithInt")!; - methodInfo.HasCorrectClassOrAssemblyInitializeSignature().Should().BeFalse(); + methodInfo.HasCorrectClassOrAssemblyInitializeSignature(_reflectionOperations).Should().BeFalse(); } public void HasCorrectClassOrAssemblyInitializeSignatureShouldReturnFalseForMethodsNotHavingVoidOrAsyncReturnType() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicStaticMethodWithTCReturningInt")!; - methodInfo.HasCorrectClassOrAssemblyInitializeSignature().Should().BeFalse(); + methodInfo.HasCorrectClassOrAssemblyInitializeSignature(_reflectionOperations).Should().BeFalse(); } public void HasCorrectClassOrAssemblyInitializeSignatureShouldReturnTrueForTestMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicStaticMethodWithTC")!; - methodInfo.HasCorrectClassOrAssemblyInitializeSignature().Should().BeTrue(); + methodInfo.HasCorrectClassOrAssemblyInitializeSignature(_reflectionOperations).Should().BeTrue(); } public void HasCorrectClassOrAssemblyInitializeSignatureShouldReturnTrueForAsyncTestMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicStaticAsyncTaskMethodWithTC")!; - methodInfo.HasCorrectClassOrAssemblyInitializeSignature().Should().BeTrue(); + methodInfo.HasCorrectClassOrAssemblyInitializeSignature(_reflectionOperations).Should().BeTrue(); } public void HasCorrectClassOrAssemblyInitializeSignatureShouldReturnTrueForTestMethodsWithoutAsync() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicStaticNonAsyncTaskMethodWithTC")!; - methodInfo.HasCorrectClassOrAssemblyInitializeSignature().Should().BeTrue(); + methodInfo.HasCorrectClassOrAssemblyInitializeSignature(_reflectionOperations).Should().BeTrue(); } public void HasCorrectClassOrAssemblyInitializeSignatureShouldReturnFalseForAsyncTestMethodsWithNonTaskReturnTypes() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicStaticAsyncVoidMethodWithTC")!; - methodInfo.HasCorrectClassOrAssemblyInitializeSignature().Should().BeFalse(); + methodInfo.HasCorrectClassOrAssemblyInitializeSignature(_reflectionOperations).Should().BeFalse(); } #endregion @@ -77,49 +82,49 @@ public void HasCorrectClassOrAssemblyInitializeSignatureShouldReturnFalseForAsyn public void HasCorrectClassOrAssemblyCleanupSignatureShouldReturnFalseForNonStaticMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicMethod")!; - methodInfo.HasCorrectClassOrAssemblyCleanupSignature().Should().BeFalse(); + methodInfo.HasCorrectClassOrAssemblyCleanupSignature(_reflectionOperations).Should().BeFalse(); } public void HasCorrectClassOrAssemblyCleanupSignatureShouldReturnFalseForNonPublicMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("InternalStaticMethod", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)!; - methodInfo.HasCorrectClassOrAssemblyCleanupSignature().Should().BeFalse(); + methodInfo.HasCorrectClassOrAssemblyCleanupSignature(_reflectionOperations).Should().BeFalse(); } public void HasCorrectClassOrAssemblyCleanupSignatureShouldReturnFalseForMethodsHavingParameters() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicStaticMethodWithInt")!; - methodInfo.HasCorrectClassOrAssemblyCleanupSignature().Should().BeFalse(); + methodInfo.HasCorrectClassOrAssemblyCleanupSignature(_reflectionOperations).Should().BeFalse(); } public void HasCorrectClassOrAssemblyCleanupSignatureShouldReturnFalseForMethodsNotHavingVoidOrAsyncReturnType() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicStaticMethodReturningInt")!; - methodInfo.HasCorrectClassOrAssemblyCleanupSignature().Should().BeFalse(); + methodInfo.HasCorrectClassOrAssemblyCleanupSignature(_reflectionOperations).Should().BeFalse(); } public void HasCorrectClassOrAssemblyCleanupSignatureShouldReturnTrueForTestMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicStaticMethod")!; - methodInfo.HasCorrectClassOrAssemblyCleanupSignature().Should().BeTrue(); + methodInfo.HasCorrectClassOrAssemblyCleanupSignature(_reflectionOperations).Should().BeTrue(); } public void HasCorrectClassOrAssemblyCleanupSignatureShouldReturnTrueForAsyncTestMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicStaticAsyncTaskMethod")!; - methodInfo.HasCorrectClassOrAssemblyCleanupSignature().Should().BeTrue(); + methodInfo.HasCorrectClassOrAssemblyCleanupSignature(_reflectionOperations).Should().BeTrue(); } public void HasCorrectClassOrAssemblyCleanupSignatureShouldReturnTrueForTestMethodsWithoutAsync() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicStaticNonAsyncTaskMethod")!; - methodInfo.HasCorrectClassOrAssemblyCleanupSignature().Should().BeTrue(); + methodInfo.HasCorrectClassOrAssemblyCleanupSignature(_reflectionOperations).Should().BeTrue(); } public void HasCorrectClassOrAssemblyCleanupSignatureShouldReturnFalseForAsyncTestMethodsWithNonTaskReturnTypes() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicStaticAsyncVoidMethod")!; - methodInfo.HasCorrectClassOrAssemblyCleanupSignature().Should().BeFalse(); + methodInfo.HasCorrectClassOrAssemblyCleanupSignature(_reflectionOperations).Should().BeFalse(); } #endregion @@ -129,49 +134,49 @@ public void HasCorrectClassOrAssemblyCleanupSignatureShouldReturnFalseForAsyncTe public void HasCorrectTestInitializeOrCleanupSignatureShouldReturnFalseForStaticMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicStaticMethod")!; - methodInfo.HasCorrectTestInitializeOrCleanupSignature().Should().BeFalse(); + methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionOperations).Should().BeFalse(); } public void HasCorrectTestInitializeOrCleanupSignatureShouldReturnFalseForNonPublicMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("InternalMethod", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!; - methodInfo.HasCorrectTestInitializeOrCleanupSignature().Should().BeFalse(); + methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionOperations).Should().BeFalse(); } public void HasCorrectTestInitializeOrCleanupSignatureShouldReturnFalseForMethodsHavingParameters() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicMethodWithInt")!; - methodInfo.HasCorrectTestInitializeOrCleanupSignature().Should().BeFalse(); + methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionOperations).Should().BeFalse(); } public void HasCorrectTestInitializeOrCleanupSignatureShouldReturnFalseForMethodsNotHavingVoidOrAsyncReturnType() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicMethodReturningInt")!; - methodInfo.HasCorrectTestInitializeOrCleanupSignature().Should().BeFalse(); + methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionOperations).Should().BeFalse(); } public void HasCorrectTestInitializeOrCleanupSignatureShouldReturnTrueForTestMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicMethod")!; - methodInfo.HasCorrectTestInitializeOrCleanupSignature().Should().BeTrue(); + methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionOperations).Should().BeTrue(); } public void HasCorrectTestInitializeOrCleanupSignatureShouldReturnTrueForAsyncTestMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicAsyncTaskMethod")!; - methodInfo.HasCorrectTestInitializeOrCleanupSignature().Should().BeTrue(); + methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionOperations).Should().BeTrue(); } public void HasCorrectTestInitializeOrCleanupSignatureShouldReturnTrueForTestMethodsWithoutAsync() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicNonAsyncTaskMethod")!; - methodInfo.HasCorrectTestInitializeOrCleanupSignature().Should().BeTrue(); + methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionOperations).Should().BeTrue(); } public void HasCorrectTestInitializeOrCleanupSignatureShouldReturnFalseForAsyncTestMethodsWithNonTaskReturnTypes() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicAsyncVoidMethod")!; - methodInfo.HasCorrectTestInitializeOrCleanupSignature().Should().BeFalse(); + methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionOperations).Should().BeFalse(); } #endregion @@ -181,61 +186,61 @@ public void HasCorrectTestInitializeOrCleanupSignatureShouldReturnFalseForAsyncT public void HasCorrectTestMethodSignatureShouldReturnFalseForAbstractMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicAbstractMethod")!; - methodInfo.HasCorrectTestMethodSignature(false).Should().BeFalse(); + methodInfo.HasCorrectTestMethodSignature(false, _reflectionOperations).Should().BeFalse(); } public void HasCorrectTestMethodSignatureShouldReturnFalseForStaticMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicStaticMethod")!; - methodInfo.HasCorrectTestMethodSignature(false).Should().BeFalse(); + methodInfo.HasCorrectTestMethodSignature(false, _reflectionOperations).Should().BeFalse(); } public void HasCorrectTestMethodSignatureShouldReturnFalseForGenericMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicGenericMethod")!; - methodInfo.HasCorrectTestMethodSignature(false).Should().BeFalse(); + methodInfo.HasCorrectTestMethodSignature(false, _reflectionOperations).Should().BeFalse(); } public void HasCorrectTestMethodSignatureShouldReturnFalseForNonPublicMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("InternalMethod", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!; - methodInfo.HasCorrectTestMethodSignature(false).Should().BeFalse(); + methodInfo.HasCorrectTestMethodSignature(false, _reflectionOperations).Should().BeFalse(); } public void HasCorrectTestMethodSignatureShouldReturnFalseForMethodsHavingParameters() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicMethodWithInt")!; - methodInfo.HasCorrectTestMethodSignature(false).Should().BeFalse(); + methodInfo.HasCorrectTestMethodSignature(false, _reflectionOperations).Should().BeFalse(); } public void HasCorrectTestMethodSignatureShouldReturnTrueForMethodsWithParametersWhenParameterCountIsIgnored() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicMethodWithInt")!; - methodInfo.HasCorrectTestMethodSignature(true).Should().BeTrue(); + methodInfo.HasCorrectTestMethodSignature(true, _reflectionOperations).Should().BeTrue(); } public void HasCorrectTestMethodSignatureShouldReturnTrueForTestMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicMethod")!; - methodInfo.HasCorrectTestMethodSignature(false).Should().BeTrue(); + methodInfo.HasCorrectTestMethodSignature(false, _reflectionOperations).Should().BeTrue(); } public void HasCorrectTestMethodSignatureShouldReturnTrueForAsyncTestMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicAsyncTaskMethod")!; - methodInfo.HasCorrectTestMethodSignature(false).Should().BeTrue(); + methodInfo.HasCorrectTestMethodSignature(false, _reflectionOperations).Should().BeTrue(); } public void HasCorrectTestMethodSignatureShouldReturnTrueForTaskTestMethodsWithoutAsync() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicNonAsyncTaskMethod")!; - methodInfo.HasCorrectTestMethodSignature(false).Should().BeTrue(); + methodInfo.HasCorrectTestMethodSignature(false, _reflectionOperations).Should().BeTrue(); } public void HasCorrectTestMethodSignatureShouldReturnFalseForAsyncTestMethodsWithNonTaskReturnTypes() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicAsyncVoidMethod")!; - methodInfo.HasCorrectTestMethodSignature(false).Should().BeFalse(); + methodInfo.HasCorrectTestMethodSignature(false, _reflectionOperations).Should().BeFalse(); } #endregion @@ -261,31 +266,31 @@ public void HasCorrectTimeoutShouldReturnTrueForMethodsWithTimeoutAttribute() public void IsVoidOrTaskReturnTypeShouldReturnTrueForVoidMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicMethod")!; - methodInfo.IsValidReturnType().Should().BeTrue(); + methodInfo.IsValidReturnType(_reflectionOperations).Should().BeTrue(); } public void IsVoidOrTaskReturnTypeShouldReturnTrueForAsyncTaskMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicAsyncTaskMethod")!; - methodInfo.IsValidReturnType().Should().BeTrue(); + methodInfo.IsValidReturnType(_reflectionOperations).Should().BeTrue(); } public void IsVoidOrTaskReturnTypeShouldReturnTrueForTaskMethodsWithoutAsync() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicNonAsyncTaskMethod")!; - methodInfo.IsValidReturnType().Should().BeTrue(); + methodInfo.IsValidReturnType(_reflectionOperations).Should().BeTrue(); } public void IsVoidOrTaskReturnTypeShouldReturnFalseForNonVoidMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicMethodReturningInt")!; - methodInfo.IsValidReturnType().Should().BeFalse(); + methodInfo.IsValidReturnType(_reflectionOperations).Should().BeFalse(); } public void IsVoidOrTaskReturnTypeShouldReturnTrueForAsyncNonTaskMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicAsyncVoidMethod")!; - methodInfo.IsValidReturnType().Should().BeFalse(); + methodInfo.IsValidReturnType(_reflectionOperations).Should().BeFalse(); } #endregion @@ -295,13 +300,13 @@ public void IsVoidOrTaskReturnTypeShouldReturnTrueForAsyncNonTaskMethods() public void GetAsyncTypeNameShouldReturnNullForVoidMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicMethod")!; - methodInfo.GetAsyncTypeName().Should().BeNull(); + methodInfo.GetAsyncTypeName(_reflectionOperations).Should().BeNull(); } public void GetAsyncTypeNameShouldReturnStateMachineTypeNameForAsyncMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicAsyncVoidMethod")!; - methodInfo.GetAsyncTypeName()!.Should().StartWith("Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Extensions.MethodInfoExtensionsTests+DummyTestClass+"); + methodInfo.GetAsyncTypeName(_reflectionOperations)!.Should().StartWith("Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Extensions.MethodInfoExtensionsTests+DummyTestClass+"); } #endregion diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Helpers/ReflectHelperTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Helpers/ReflectHelperTests.cs deleted file mode 100644 index 89979ac6df..0000000000 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Helpers/ReflectHelperTests.cs +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using AwesomeAssertions; - -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; - -using Moq; - -using TestFramework.ForTestingMSTest; - -namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests; - -public class ReflectHelperTests : TestContainer -{ - private readonly ReflectHelper _reflectHelper; - private readonly AttributeMockingHelper _attributeMockingHelper; - private readonly Mock _method; - private readonly TestablePlatformServiceProvider _testablePlatformServiceProvider; - - public ReflectHelperTests() - { - _reflectHelper = new(); - _method = new Mock(); - _method.Setup(x => x.MemberType).Returns(MemberTypes.Method); - - _testablePlatformServiceProvider = new TestablePlatformServiceProvider(); - _testablePlatformServiceProvider.SetupMockReflectionOperations(); - _attributeMockingHelper = new(_testablePlatformServiceProvider.MockReflectionOperations); - - PlatformServiceProvider.Instance = _testablePlatformServiceProvider; - } - - protected override void Dispose(bool disposing) - { - if (!IsDisposed) - { - base.Dispose(disposing); - PlatformServiceProvider.Instance = null; - } - } - - /// - /// Testing test category attribute adorned at class level. - /// - public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtClassLevel() - { - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel")], MemberTypes.TypeInfo); - - string[] expected = ["ClassLevel"]; - string[] actual = [.. _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests))]; - - expected.SequenceEqual(actual).Should().BeTrue(); - } - - /// - /// Testing test category attributes adorned at class, assembly and method level are getting collected. - /// - public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtAllLevels() - { - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel1"), new TestCategoryAttribute("AsmLevel2")], MemberTypes.All); - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel3")], MemberTypes.All); - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel")], MemberTypes.TypeInfo); - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("MethodLevel")], MemberTypes.Method); - - string[] actual = [.. _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests))]; - string[] expected = ["MethodLevel", "ClassLevel", "AsmLevel1", "AsmLevel2", "AsmLevel3"]; - - expected.SequenceEqual(actual).Should().BeTrue(); - } - - /// - /// Testing test category attributes adorned at class, assembly and method level are getting collected. - /// - public void GetTestCategoryAttributeShouldConcatCustomAttributeOfSameType() - { - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel1")], MemberTypes.All); - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel2")], MemberTypes.All); - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel1")], MemberTypes.TypeInfo); - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel2")], MemberTypes.TypeInfo); - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("MethodLevel1")], MemberTypes.Method); - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("MethodLevel2")], MemberTypes.Method); - - string[] actual = [.. _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests))]; - string[] expected = ["MethodLevel1", "MethodLevel2", "ClassLevel1", "ClassLevel2", "AsmLevel1", "AsmLevel2"]; - - expected.SequenceEqual(actual).Should().BeTrue(); - } - - /// - /// Testing test category attributes adorned at assembly level. - /// - public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtAssemblyLevel() - { - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel")], MemberTypes.All); - - string[] expected = ["AsmLevel"]; - - string[] actual = [.. _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests))]; - - expected.SequenceEqual(actual).Should().BeTrue(); - } - - /// - /// Testing multiple test category attribute adorned at class level. - /// - public void GetTestCategoryAttributeShouldIncludeMultipleTestCategoriesAtClassLevel() - { - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel"), new TestCategoryAttribute("ClassLevel1")], MemberTypes.TypeInfo); - - string[] expected = ["ClassLevel", "ClassLevel1"]; - string[] actual = [.. _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests))]; - - expected.SequenceEqual(actual).Should().BeTrue(); - } - - /// - /// Testing multiple test category attributes adorned at assembly level. - /// - public void GetTestCategoryAttributeShouldIncludeMultipleTestCategoriesAtAssemblyLevel() - { - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel"), new TestCategoryAttribute("AsmLevel1")], MemberTypes.All); - - string[] expected = ["AsmLevel", "AsmLevel1"]; - string[] actual = [.. _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests))]; - expected.SequenceEqual(actual).Should().BeTrue(); - } - - /// - /// Testing test category attributes adorned at method level - regression. - /// - public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtMethodLevel() - { - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("MethodLevel")], MemberTypes.Method); - - string[] expected = ["MethodLevel"]; - string[] actual = [.. _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests))]; - - expected.SequenceEqual(actual).Should().BeTrue(); - } - - public void IsAttributeDefinedShouldReturnTrueIfSpecifiedAttributeIsDefinedOnAMember() - { - var rh = new ReflectHelper(); - var mockMemberInfo = new Mock(); - var attributes = new Attribute[] { new TestMethodAttribute() }; - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). - Returns(attributes); - - rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeTrue(); - } - - public void IsAttributeDefinedShouldReturnFalseIfSpecifiedAttributeIsNotDefinedOnAMember() - { - var rh = new ReflectHelper(); - var mockMemberInfo = new Mock(); - var attributes = new Attribute[] { new TestClassAttribute() }; - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). - Returns(attributes); - - rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeFalse(); - } - - public void IsAttributeDefinedShouldReturnFromCache() - { - var rh = new ReflectHelper(); - - // Not using mocks here because for some reason a dictionary match of the mock is not returning true in the product code. - MethodInfo memberInfo = typeof(ReflectHelperTests).GetMethod("IsAttributeDefinedShouldReturnFromCache")!; - - // new Mock(); - var attributes = new Attribute[] { new TestMethodAttribute() }; - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(memberInfo)). - Returns(attributes); - - rh.IsAttributeDefined(memberInfo).Should().BeTrue(); - - // Validate that reflection APIs are not called again. - rh.IsAttributeDefined(memberInfo).Should().BeTrue(); - _testablePlatformServiceProvider.MockReflectionOperations.Verify(ro => ro.GetCustomAttributes(memberInfo), Times.Once); - } - - public void HasAttributeDerivedFromShouldReturnTrueIfSpecifiedAttributeIsDefinedOnAMember() - { - var rh = new ReflectHelper(); - var mockMemberInfo = new Mock(); - var attributes = new Attribute[] { new TestableExtendedTestMethod() }; - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). - Returns(attributes); - - rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeTrue(); - } - - public void HasAttributeDerivedFromShouldReturnFalseIfSpecifiedAttributeIsNotDefinedOnAMember() - { - var rh = new ReflectHelper(); - var mockMemberInfo = new Mock(); - var attributes = new Attribute[] { new TestableExtendedTestMethod() }; - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). - Returns(attributes); - - rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeFalse(); - } - - public void HasAttributeDerivedFromShouldReturnFromCache() - { - var rh = new ReflectHelper(); - - // Not using mocks here because for some reason a dictionary match of the mock is not returning true in the product code. - MethodInfo memberInfo = typeof(ReflectHelperTests).GetMethod("HasAttributeDerivedFromShouldReturnFromCache")!; - - // new Mock(); - var attributes = new Attribute[] { new TestableExtendedTestMethod() }; - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(memberInfo)). - Returns(attributes); - - rh.IsAttributeDefined(memberInfo).Should().BeTrue(); - - // Validate that reflection APIs are not called again. - rh.IsAttributeDefined(memberInfo).Should().BeTrue(); - _testablePlatformServiceProvider.MockReflectionOperations.Verify(ro => ro.GetCustomAttributes(memberInfo), Times.Once); - } - - public void HasAttributeDerivedFromShouldReturnFalseQueryingProvidedAttributesExistenceIfGettingAllAttributesFail() - { - var rh = new ReflectHelper(); - var mockMemberInfo = new Mock(); - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). - Returns((object[])null!); - - rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeFalse(); - } - - internal class AttributeMockingHelper - { - public AttributeMockingHelper(Mock mockReflectionOperations) => _mockReflectionOperations = mockReflectionOperations; - - /// - /// A collection to hold mock custom attributes. - /// MemberTypes.All for assembly level - /// MemberTypes.TypeInfo for class level - /// MemberTypes.Method for method level. - /// - private readonly List<(Type Type, Attribute Attribute, MemberTypes MemberType)> _data = []; - private readonly Mock _mockReflectionOperations; - - public void SetCustomAttribute(Type type, Attribute[] values, MemberTypes memberTypes) - { - foreach (Attribute attribute in values) - { - _data.Add((type, attribute, memberTypes)); - } - - _mockReflectionOperations.Setup(r => r.GetCustomAttributes(It.IsAny())) - .Returns(GetCustomAttributesNotCached); - _mockReflectionOperations.Setup(r => r.GetCustomAttributes(It.IsAny(), It.IsAny())) - .Returns((assembly, _) => GetCustomAttributesNotCached(assembly)); - } - - public object[] GetCustomAttributesNotCached(ICustomAttributeProvider attributeProvider) - { - var foundAttributes = new List(); - foreach ((Type Type, Attribute Attribute, MemberTypes MemberType) attributeData in _data) - { - if (attributeProvider is MethodInfo && (attributeData.MemberType == MemberTypes.Method)) - { - foundAttributes.Add(attributeData.Attribute); - } - else if (attributeProvider is TypeInfo && (attributeData.MemberType == MemberTypes.TypeInfo)) - { - foundAttributes.Add(attributeData.Attribute); - } - else if (attributeProvider is Assembly && attributeData.MemberType == MemberTypes.All) - { - foundAttributes.Add(attributeData.Attribute); - } - } - - return foundAttributes.ToArray(); - } - } -} - -#region Dummy Implementations - -public class TestableExtendedTestMethod : TestMethodAttribute; - -#endregion diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopReflectionOperationsTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopReflectionOperationsTests.cs index b92337a744..c06086479a 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopReflectionOperationsTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopReflectionOperationsTests.cs @@ -38,10 +38,12 @@ public void GetCustomAttributesOnTypeShouldReturnAllAttributes() object[] attributes = _reflectionOperations.GetCustomAttributes(type); attributes.Should().NotBeNull(); - attributes.Length.Should().Be(1); + // Filter to only our test attributes (excludes compiler-generated attributes like NullableContextAttribute) + List testAttributes = ReflectionUtilityTests.GetAttributeValuePairs(attributes); + testAttributes.Count.Should().Be(1); string[] expectedAttributes = ["DummyA : ba"]; - expectedAttributes.SequenceEqual(ReflectionUtilityTests.GetAttributeValuePairs(attributes)).Should().BeTrue(); + expectedAttributes.SequenceEqual(testAttributes).Should().BeTrue(); } public void GetSpecificCustomAttributesOnAssemblyShouldReturnAllAttributes() diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs index ddd508f5c6..fd2706ae2c 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs @@ -4,9 +4,9 @@ #if NETFRAMEWORK using AwesomeAssertions; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; @@ -24,6 +24,7 @@ public class DesktopTestDeploymentTests : TestContainer private const string DefaultDeploymentItemPath = @"c:\temp"; private const string DefaultDeploymentItemOutputDirectory = "out"; + private readonly Mock _mockReflectionOperations; private readonly Mock _mockFileUtility; #pragma warning disable IDE0052 // Remove unread private members @@ -32,6 +33,7 @@ public class DesktopTestDeploymentTests : TestContainer public DesktopTestDeploymentTests() { + _mockReflectionOperations = new Mock(); _mockFileUtility = new Mock(); _warnings = []; @@ -116,6 +118,21 @@ public void DeployShouldCreateDeploymentDirectories() #region private methods +#pragma warning disable IDE0051 // Remove unused private members + private void SetupDeploymentItems(ICustomAttributeProvider attributeProvider, KeyValuePair[] deploymentItems) +#pragma warning restore IDE0051 // Remove unused private members + { + var deploymentItemAttributes = new List(); + + foreach (KeyValuePair deploymentItem in deploymentItems) + { + deploymentItemAttributes.Add(new DeploymentItemAttribute(deploymentItem.Key, deploymentItem.Value)); + } + + _mockReflectionOperations.Setup( + ru => ru.GetAttributes(attributeProvider)).Returns(deploymentItemAttributes); + } + private TestCase GetTestCase(string source) { var testCase = new TestCase("A.C.M", new Uri("executor://testExecutor"), source); @@ -148,7 +165,7 @@ private TestDeployment CreateAndSetupDeploymentRelatedUtilities(out TestRunDirec _mockFileUtility.Setup(fu => fu.GetNextIterationDirectoryName(It.IsAny(), It.IsAny())) .Returns(testRunDirectories.RootDeploymentDirectory); - var deploymentItemUtility = new DeploymentItemUtility(new ReflectHelper()); + var deploymentItemUtility = new DeploymentItemUtility(_mockReflectionOperations.Object); return new TestDeployment( deploymentItemUtility, diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs index be9a7893b4..2ae240e2bb 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs @@ -3,7 +3,13 @@ using AwesomeAssertions; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; + +using Moq; using TestFramework.ForTestingMSTest; @@ -12,8 +18,33 @@ namespace MSTestAdapter.PlatformServices.UnitTests.Services; public class ReflectionOperationsTests : TestContainer { private readonly ReflectionOperations _reflectionOperations; + private readonly AttributeMockingHelper _attributeMockingHelper; + private readonly Mock _method; + private readonly TestablePlatformServiceProvider _testablePlatformServiceProvider; + + public ReflectionOperationsTests() + { + _reflectionOperations = new ReflectionOperations(); + _method = new Mock(); + _method.Setup(x => x.MemberType).Returns(MemberTypes.Method); + + _testablePlatformServiceProvider = new TestablePlatformServiceProvider(); + _testablePlatformServiceProvider.SetupMockReflectionOperations(); + _attributeMockingHelper = new(_testablePlatformServiceProvider.MockReflectionOperations); + + PlatformServiceProvider.Instance = _testablePlatformServiceProvider; + } + + protected override void Dispose(bool disposing) + { + if (!IsDisposed) + { + base.Dispose(disposing); + PlatformServiceProvider.Instance = null; + } + } - public ReflectionOperationsTests() => _reflectionOperations = new ReflectionOperations(); + #region GetCustomAttributes Tests public void GetCustomAttributesShouldReturnAllAttributes() { @@ -84,6 +115,223 @@ public void GetSpecificCustomAttributesOnAssemblyShouldReturnAllAttributes() GetAttributeValuePairs(attributes).SequenceEqual(expectedAttributes).Should().BeTrue(); } + #endregion + + #region GetTestCategories Tests + + /// + /// Testing test category attribute adorned at class level. + /// + public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtClassLevel() + { + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel")], MemberTypes.TypeInfo); + + string[] expected = ["ClassLevel"]; + string[] actual = [.. _reflectionOperations.GetTestCategories(_method.Object, typeof(ReflectionOperationsTests))]; + + expected.SequenceEqual(actual).Should().BeTrue(); + } + + /// + /// Testing test category attributes adorned at class, assembly and method level are getting collected. + /// + public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtAllLevels() + { + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel1"), new TestCategoryAttribute("AsmLevel2")], MemberTypes.All); + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel3")], MemberTypes.All); + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel")], MemberTypes.TypeInfo); + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("MethodLevel")], MemberTypes.Method); + + string[] actual = [.. _reflectionOperations.GetTestCategories(_method.Object, typeof(ReflectionOperationsTests))]; + string[] expected = ["MethodLevel", "ClassLevel", "AsmLevel1", "AsmLevel2", "AsmLevel3"]; + + expected.SequenceEqual(actual).Should().BeTrue(); + } + + /// + /// Testing test category attributes adorned at class, assembly and method level are getting collected. + /// + public void GetTestCategoryAttributeShouldConcatCustomAttributeOfSameType() + { + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel1")], MemberTypes.All); + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel2")], MemberTypes.All); + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel1")], MemberTypes.TypeInfo); + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel2")], MemberTypes.TypeInfo); + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("MethodLevel1")], MemberTypes.Method); + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("MethodLevel2")], MemberTypes.Method); + + string[] actual = [.. _reflectionOperations.GetTestCategories(_method.Object, typeof(ReflectionOperationsTests))]; + string[] expected = ["MethodLevel1", "MethodLevel2", "ClassLevel1", "ClassLevel2", "AsmLevel1", "AsmLevel2"]; + + expected.SequenceEqual(actual).Should().BeTrue(); + } + + /// + /// Testing test category attributes adorned at assembly level. + /// + public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtAssemblyLevel() + { + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel")], MemberTypes.All); + + string[] expected = ["AsmLevel"]; + + string[] actual = [.. _reflectionOperations.GetTestCategories(_method.Object, typeof(ReflectionOperationsTests))]; + + expected.SequenceEqual(actual).Should().BeTrue(); + } + + /// + /// Testing multiple test category attribute adorned at class level. + /// + public void GetTestCategoryAttributeShouldIncludeMultipleTestCategoriesAtClassLevel() + { + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel"), new TestCategoryAttribute("ClassLevel1")], MemberTypes.TypeInfo); + + string[] expected = ["ClassLevel", "ClassLevel1"]; + string[] actual = [.. _reflectionOperations.GetTestCategories(_method.Object, typeof(ReflectionOperationsTests))]; + + expected.SequenceEqual(actual).Should().BeTrue(); + } + + /// + /// Testing multiple test category attributes adorned at assembly level. + /// + public void GetTestCategoryAttributeShouldIncludeMultipleTestCategoriesAtAssemblyLevel() + { + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel"), new TestCategoryAttribute("AsmLevel1")], MemberTypes.All); + + string[] expected = ["AsmLevel", "AsmLevel1"]; + string[] actual = [.. _reflectionOperations.GetTestCategories(_method.Object, typeof(ReflectionOperationsTests))]; + expected.SequenceEqual(actual).Should().BeTrue(); + } + + /// + /// Testing test category attributes adorned at method level - regression. + /// + public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtMethodLevel() + { + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("MethodLevel")], MemberTypes.Method); + + string[] expected = ["MethodLevel"]; + string[] actual = [.. _reflectionOperations.GetTestCategories(_method.Object, typeof(ReflectionOperationsTests))]; + + expected.SequenceEqual(actual).Should().BeTrue(); + } + + #endregion + + #region IsAttributeDefined Tests + + public void IsAttributeDefinedShouldReturnTrueIfSpecifiedAttributeIsDefinedOnAMember() + { + var rh = new ReflectionOperations(); + var mockMemberInfo = new Mock(); + var attributes = new Attribute[] { new TestMethodAttribute() }; + + _testablePlatformServiceProvider.MockReflectionOperations. + Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). + Returns(attributes); + + rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeTrue(); + } + + public void IsAttributeDefinedShouldReturnFalseIfSpecifiedAttributeIsNotDefinedOnAMember() + { + var rh = new ReflectionOperations(); + var mockMemberInfo = new Mock(); + var attributes = new Attribute[] { new TestClassAttribute() }; + + _testablePlatformServiceProvider.MockReflectionOperations. + Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). + Returns(attributes); + + rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeFalse(); + } + + public void IsAttributeDefinedShouldReturnFromCache() + { + var rh = new ReflectionOperations(); + + // Not using mocks here because for some reason a dictionary match of the mock is not returning true in the product code. + MethodInfo memberInfo = typeof(ReflectionOperationsTests).GetMethod("IsAttributeDefinedShouldReturnFromCache")!; + + // new Mock(); + var attributes = new Attribute[] { new TestMethodAttribute() }; + + _testablePlatformServiceProvider.MockReflectionOperations. + Setup(ro => ro.GetCustomAttributes(memberInfo)). + Returns(attributes); + + rh.IsAttributeDefined(memberInfo).Should().BeTrue(); + + // Validate that reflection APIs are not called again. + rh.IsAttributeDefined(memberInfo).Should().BeTrue(); + _testablePlatformServiceProvider.MockReflectionOperations.Verify(ro => ro.GetCustomAttributes(memberInfo), Times.Once); + } + + public void HasAttributeDerivedFromShouldReturnTrueIfSpecifiedAttributeIsDefinedOnAMember() + { + var rh = new ReflectionOperations(); + var mockMemberInfo = new Mock(); + var attributes = new Attribute[] { new TestableExtendedTestMethod() }; + + _testablePlatformServiceProvider.MockReflectionOperations. + Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). + Returns(attributes); + + rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeTrue(); + } + + public void HasAttributeDerivedFromShouldReturnFalseIfSpecifiedAttributeIsNotDefinedOnAMember() + { + var rh = new ReflectionOperations(); + var mockMemberInfo = new Mock(); + var attributes = new Attribute[] { new TestableExtendedTestMethod() }; + + _testablePlatformServiceProvider.MockReflectionOperations. + Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). + Returns(attributes); + + rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeFalse(); + } + + public void HasAttributeDerivedFromShouldReturnFromCache() + { + var rh = new ReflectionOperations(); + + // Not using mocks here because for some reason a dictionary match of the mock is not returning true in the product code. + MethodInfo memberInfo = typeof(ReflectionOperationsTests).GetMethod("HasAttributeDerivedFromShouldReturnFromCache")!; + + // new Mock(); + var attributes = new Attribute[] { new TestableExtendedTestMethod() }; + + _testablePlatformServiceProvider.MockReflectionOperations. + Setup(ro => ro.GetCustomAttributes(memberInfo)). + Returns(attributes); + + rh.IsAttributeDefined(memberInfo).Should().BeTrue(); + + // Validate that reflection APIs are not called again. + rh.IsAttributeDefined(memberInfo).Should().BeTrue(); + _testablePlatformServiceProvider.MockReflectionOperations.Verify(ro => ro.GetCustomAttributes(memberInfo), Times.Once); + } + + public void HasAttributeDerivedFromShouldReturnFalseQueryingProvidedAttributesExistenceIfGettingAllAttributesFail() + { + var rh = new ReflectionOperations(); + var mockMemberInfo = new Mock(); + + _testablePlatformServiceProvider.MockReflectionOperations. + Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). + Returns((object[])null!); + + rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeFalse(); + } + + #endregion + + #region Helpers + private static string[] GetAttributeValuePairs(object[] attributes) { var attribValuePairs = new List(); @@ -102,6 +350,59 @@ private static string[] GetAttributeValuePairs(object[] attributes) return [.. attribValuePairs]; } + internal class AttributeMockingHelper + { + public AttributeMockingHelper(Mock mockReflectionOperations) => _mockReflectionOperations = mockReflectionOperations; + + /// + /// A collection to hold mock custom attributes. + /// MemberTypes.All for assembly level + /// MemberTypes.TypeInfo for class level + /// MemberTypes.Method for method level. + /// + private readonly List<(Type Type, Attribute Attribute, MemberTypes MemberType)> _data = []; + private readonly Mock _mockReflectionOperations; + + public void SetCustomAttribute(Type type, Attribute[] values, MemberTypes memberTypes) + { + foreach (Attribute attribute in values) + { + _data.Add((type, attribute, memberTypes)); + } + + _mockReflectionOperations.Setup(r => r.GetCustomAttributes(It.IsAny())) + .Returns(GetCustomAttributesNotCached); + _mockReflectionOperations.Setup(r => r.GetCustomAttributes(It.IsAny(), It.IsAny())) + .Returns((assembly, _) => GetCustomAttributesNotCached(assembly)); + } + + public object[] GetCustomAttributesNotCached(ICustomAttributeProvider attributeProvider) + { + var foundAttributes = new List(); + foreach ((Type Type, Attribute Attribute, MemberTypes MemberType) attributeData in _data) + { + if (attributeProvider is MethodInfo && (attributeData.MemberType == MemberTypes.Method)) + { + foundAttributes.Add(attributeData.Attribute); + } + else if (attributeProvider is TypeInfo && (attributeData.MemberType == MemberTypes.TypeInfo)) + { + foundAttributes.Add(attributeData.Attribute); + } + else if (attributeProvider is Assembly && attributeData.MemberType == MemberTypes.All) + { + foundAttributes.Add(attributeData.Attribute); + } + } + + return foundAttributes.ToArray(); + } + } + + #endregion + + #region Dummy Test Classes + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] public class DummyAAttribute : Attribute { @@ -146,7 +447,13 @@ public void DummyTestMethod2() { } } + + #endregion } -#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName +#region Dummy Implementations + +public class TestableExtendedTestMethod : TestMethodAttribute; + +#endregion diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs index 4ac9f1a50c..9169fb6262 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs @@ -4,9 +4,9 @@ #if !WINDOWS_UWP && !WIN_UI using AwesomeAssertions; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; @@ -25,7 +25,7 @@ public class TestDeploymentTests : TestContainer private const string DefaultDeploymentItemPath = @"c:\temp"; private const string DefaultDeploymentItemOutputDirectory = "out"; - private readonly Mock _mockReflectHelper; + private readonly Mock _mockReflectionOperations; private readonly Mock _mockFileUtility; #pragma warning disable IDE0044 // Add readonly modifier @@ -34,7 +34,7 @@ public class TestDeploymentTests : TestContainer public TestDeploymentTests() { - _mockReflectHelper = new Mock(); + _mockReflectionOperations = new Mock(); _mockFileUtility = new Mock(); _warnings = []; @@ -53,7 +53,7 @@ public void GetDeploymentItemsReturnsNullWhenNoDeploymentItems() public void GetDeploymentItemsReturnsDeploymentItems() { // Arrange. - var testDeployment = new TestDeployment(new DeploymentItemUtility(_mockReflectHelper.Object), null!, null!); + var testDeployment = new TestDeployment(new DeploymentItemUtility(_mockReflectionOperations.Object), null!, null!); // setup mocks KeyValuePair[] methodLevelDeploymentItems = @@ -182,7 +182,7 @@ public void DeployShouldReturnFalseWhenDeploymentEnabledSetToFalseButHasDeployme testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, kvpArray); var testDeployment = new TestDeployment( - new DeploymentItemUtility(_mockReflectHelper.Object), + new DeploymentItemUtility(_mockReflectionOperations.Object), new DeploymentUtility(), _mockFileUtility.Object); @@ -205,7 +205,7 @@ public void DeployShouldReturnFalseWhenDeploymentEnabledSetToFalseAndHasNoDeploy var testCase = new TestCase("A.C.M", new Uri("executor://testExecutor"), "path/to/asm.dll"); testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, null); var testDeployment = new TestDeployment( - new DeploymentItemUtility(_mockReflectHelper.Object), + new DeploymentItemUtility(_mockReflectionOperations.Object), new DeploymentUtility(), _mockFileUtility.Object); @@ -228,7 +228,7 @@ public void DeployShouldReturnFalseWhenDeploymentEnabledSetToTrueButHasNoDeploym var testCase = new TestCase("A.C.M", new Uri("executor://testExecutor"), "path/to/asm.dll"); testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, null); var testDeployment = new TestDeployment( - new DeploymentItemUtility(_mockReflectHelper.Object), + new DeploymentItemUtility(_mockReflectionOperations.Object), new DeploymentUtility(), _mockFileUtility.Object); @@ -258,7 +258,7 @@ internal void DeployShouldReturnTrueWhenDeploymentEnabledSetToTrueAndHasDeployme ]; testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, kvpArray); var testDeployment = new TestDeployment( - new DeploymentItemUtility(_mockReflectHelper.Object), + new DeploymentItemUtility(_mockReflectionOperations.Object), new DeploymentUtility(), _mockFileUtility.Object); @@ -362,7 +362,7 @@ public void GetDeploymentInformationShouldReturnRunDirectoryInformationIfSourceI #region private methods - private void SetupDeploymentItems(MemberInfo memberInfo, KeyValuePair[] deploymentItems) + private void SetupDeploymentItems(ICustomAttributeProvider attributeProvider, KeyValuePair[] deploymentItems) { var deploymentItemAttributes = new List(); @@ -371,9 +371,8 @@ private void SetupDeploymentItems(MemberInfo memberInfo, KeyValuePair ru.GetAttributes(memberInfo)) - .Returns(deploymentItemAttributes.ToArray()); + _mockReflectionOperations.Setup( + ru => ru.GetAttributes(attributeProvider)).Returns(deploymentItemAttributes); } private static TestCase GetTestCase(string source) @@ -417,7 +416,7 @@ private TestDeployment CreateAndSetupDeploymentRelatedUtilities(out TestRunDirec _mockFileUtility.Setup(fu => fu.GetNextIterationDirectoryName(It.IsAny(), It.IsAny())) .Returns(testRunDirectories.RootDeploymentDirectory); - var deploymentItemUtility = new DeploymentItemUtility(_mockReflectHelper.Object); + var deploymentItemUtility = new DeploymentItemUtility(_mockReflectionOperations.Object); return new TestDeployment( deploymentItemUtility, diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/MockableReflectionOperations.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/MockableReflectionOperations.cs new file mode 100644 index 0000000000..0247387d3a --- /dev/null +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/MockableReflectionOperations.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; + +using Moq; + +namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; + +/// +/// An implementation that delegates non-generic interface methods to a Moq +/// mock, and implements the higher-level generic methods by filtering the mock's +/// results. +/// This bridges the gap where Moq cannot set up generic methods with type constraints. +/// Tests should set up GetCustomAttributes(MemberInfo) or +/// GetCustomAttributes(Assembly, Type) on the mock, and the generic methods will filter those results. +/// +internal sealed class MockableReflectionOperations(Mock mock) : IReflectionOperations +{ + /// + /// Creates a new from a mock. + /// + public static MockableReflectionOperations Create(Mock mock) + => new(mock); + + // Pre-existing interface methods → delegate to mock + [return: NotNullIfNotNull(nameof(memberInfo))] + public object[]? GetCustomAttributes(MemberInfo memberInfo) => mock.Object.GetCustomAttributes(memberInfo); + + public object[] GetCustomAttributes(Assembly assembly, Type type) => mock.Object.GetCustomAttributes(assembly, type); + + public ConstructorInfo[] GetDeclaredConstructors(Type classType) => mock.Object.GetDeclaredConstructors(classType); + + public MethodInfo[] GetDeclaredMethods(Type classType) => mock.Object.GetDeclaredMethods(classType); + + public PropertyInfo[] GetDeclaredProperties(Type type) => mock.Object.GetDeclaredProperties(type); + + public Type[] GetDefinedTypes(Assembly assembly) => mock.Object.GetDefinedTypes(assembly); + + public MethodInfo[] GetRuntimeMethods(Type type) => mock.Object.GetRuntimeMethods(type); + + public MethodInfo? GetRuntimeMethod(Type declaringType, string methodName, Type[] parameters, bool includeNonPublic) + => mock.Object.GetRuntimeMethod(declaringType, methodName, parameters, includeNonPublic); + + public PropertyInfo? GetRuntimeProperty(Type classType, string propertyName, bool includeNonPublic) + => mock.Object.GetRuntimeProperty(classType, propertyName, includeNonPublic); + + public Type? GetType(string typeName) => mock.Object.GetType(typeName); + + public Type? GetType(Assembly assembly, string typeName) => mock.Object.GetType(assembly, typeName); + + public object? CreateInstance(Type type, object?[] parameters) => mock.Object.CreateInstance(type, parameters); + + // Higher-level generic methods → filter results from mock's GetCustomAttributes + public bool IsAttributeDefined(MemberInfo memberInfo) + where TAttribute : Attribute + => GetCustomAttributesCached(memberInfo).OfType().Any(); + + public TAttribute? GetFirstAttributeOrDefault(ICustomAttributeProvider attributeProvider) + where TAttribute : Attribute + => GetCustomAttributesCached(attributeProvider).OfType().FirstOrDefault(); + + public TAttribute? GetSingleAttributeOrDefault(ICustomAttributeProvider attributeProvider) + where TAttribute : Attribute + => GetCustomAttributesCached(attributeProvider).OfType().SingleOrDefault(); + + public IEnumerable GetAttributes(ICustomAttributeProvider attributeProvider) + where TAttributeType : Attribute + => GetCustomAttributesCached(attributeProvider).OfType(); + + public Attribute[] GetCustomAttributesCached(ICustomAttributeProvider attributeProvider) + => attributeProvider switch + { + MemberInfo memberInfo => mock.Object.GetCustomAttributes(memberInfo)?.OfType().ToArray() ?? [], + Assembly assembly => mock.Object.GetCustomAttributes(assembly, typeof(Attribute)).OfType().ToArray(), + _ => attributeProvider.GetCustomAttributes(true).OfType().ToArray(), + }; + + public bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Type type) + => mock.Object.IsMethodDeclaredInSameAssemblyAsType(method, type); +} diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.cs index fa61772872..925f215bd2 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.cs @@ -35,6 +35,8 @@ internal class TestablePlatformServiceProvider : IPlatformServiceProvider public Mock MockReflectionOperations { get; set; } = null!; + private MockableReflectionOperations? _reflectionOperationsWrapper; + #endregion public IFileOperations FileOperations => MockFileOperations.Object; @@ -54,7 +56,7 @@ internal class TestablePlatformServiceProvider : IPlatformServiceProvider public IReflectionOperations ReflectionOperations { get => MockReflectionOperations != null - ? MockReflectionOperations.Object + ? (_reflectionOperationsWrapper ?? MockReflectionOperations.Object) : field ??= new ReflectionOperations(); private set; } @@ -74,5 +76,15 @@ public ITestContext GetTestContext(ITestMethod? testMethod, string? testClassFul public ITestSourceHost CreateTestSourceHost(string source, TestPlatform.ObjectModel.Adapter.IRunSettings? runSettings) => MockTestSourceHost.Object; - public void SetupMockReflectionOperations() => MockReflectionOperations = new Mock(); + public void SetupMockReflectionOperations() + { + MockReflectionOperations = new Mock(); + _reflectionOperationsWrapper = MockableReflectionOperations.Create(MockReflectionOperations); + } + + public void SetupMockReflectionOperations(Mock mock) + { + MockReflectionOperations = mock; + _reflectionOperationsWrapper = MockableReflectionOperations.Create(mock); + } } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs index bedfa7f4d6..c1a11d0c2b 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs @@ -4,8 +4,9 @@ #if !WINDOWS_UWP && !WIN_UI using AwesomeAssertions; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -25,7 +26,7 @@ public class DeploymentItemUtilityTests : TestContainer TestPropertyAttributes.Hidden, typeof(TestCase)); - private readonly Mock _mockReflectHelper; + private readonly Mock _mockReflectionOperations; private readonly DeploymentItemUtility _deploymentItemUtility; private readonly ICollection _warnings; @@ -34,8 +35,8 @@ public class DeploymentItemUtilityTests : TestContainer public DeploymentItemUtilityTests() { - _mockReflectHelper = new Mock(); - _deploymentItemUtility = new DeploymentItemUtility(_mockReflectHelper.Object); + _mockReflectionOperations = new Mock(); + _deploymentItemUtility = new DeploymentItemUtility(_mockReflectionOperations.Object); _warnings = []; } @@ -43,7 +44,7 @@ public DeploymentItemUtilityTests() public void GetClassLevelDeploymentItemsShouldReturnEmptyListWhenNoDeploymentItems() { - _mockReflectHelper.Setup(x => x.GetAttributes(typeof(DeploymentItemUtilityTests))) + _mockReflectionOperations.Setup(x => x.GetAttributes(typeof(DeploymentItemUtilityTests))) .Returns([]); IList deploymentItems = _deploymentItemUtility.GetClassLevelDeploymentItems(typeof(DeploymentItemUtilityTests), _warnings); @@ -164,7 +165,7 @@ public void GetClassLevelDeploymentItemsShouldReportWarningsForInvalidDeployment public void GetDeploymentItemsShouldReturnNullOnNoDeploymentItems() { MethodInfo method = typeof(DeploymentItemUtilityTests).GetMethod("GetDeploymentItemsShouldReturnNullOnNoDeploymentItems")!; - _mockReflectHelper.Setup(x => x.GetAttributes(method)) + _mockReflectionOperations.Setup(x => x.GetAttributes(method)) .Returns([]); _deploymentItemUtility.GetDeploymentItems(method, null!, _warnings).Should().BeNull(); @@ -209,7 +210,7 @@ public void GetDeploymentItemsShouldReturnClassLevelDeploymentItemsOnly() }; MethodInfo method = typeof(DeploymentItemUtilityTests).GetMethod("GetDeploymentItemsShouldReturnNullOnNoDeploymentItems")!; - _mockReflectHelper.Setup(x => x.GetAttributes(method)) + _mockReflectionOperations.Setup(x => x.GetAttributes(method)) .Returns([]); // Act. @@ -414,7 +415,7 @@ public void HasDeployItemsShouldReturnTrueWhenDeploymentItemsArePresent() #region private methods - private void SetupDeploymentItems(MemberInfo memberInfo, KeyValuePair[] deploymentItems) + private void SetupDeploymentItems(ICustomAttributeProvider attributeProvider, KeyValuePair[] deploymentItems) { var deploymentItemAttributes = new List(); @@ -423,9 +424,8 @@ private void SetupDeploymentItems(MemberInfo memberInfo, KeyValuePair ru.GetAttributes(memberInfo)) - .Returns(deploymentItemAttributes.ToArray()); + _mockReflectionOperations.Setup( + ru => ru.GetAttributes(attributeProvider)).Returns(deploymentItemAttributes); } #endregion diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentUtilityTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentUtilityTests.cs index 14543a254b..c66fcba062 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentUtilityTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentUtilityTests.cs @@ -4,8 +4,9 @@ #if !WINDOWS_UWP && !WIN_UI using AwesomeAssertions; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -25,6 +26,7 @@ public class DeploymentUtilityTests : TestContainer private const string DefaultDeploymentItemPath = @"c:\temp"; private const string DefaultDeploymentItemOutputDirectory = "out"; + private readonly Mock _mockReflectionOperations; private readonly Mock _mockFileUtility; private readonly Mock _mockAssemblyUtility; private readonly Mock _mockRunContext; @@ -40,12 +42,13 @@ public class DeploymentUtilityTests : TestContainer public DeploymentUtilityTests() { + _mockReflectionOperations = new Mock(); _mockFileUtility = new Mock(); _mockAssemblyUtility = new Mock(); _warnings = []; _deploymentUtility = new DeploymentUtility( - new DeploymentItemUtility(new ReflectHelper()), + new DeploymentItemUtility(_mockReflectionOperations.Object), _mockAssemblyUtility.Object, _mockFileUtility.Object); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/ReflectionUtilityTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/ReflectionUtilityTests.cs index 5b331a5fc0..56510822c7 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/ReflectionUtilityTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/ReflectionUtilityTests.cs @@ -4,20 +4,24 @@ using AwesomeAssertions; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; using TestFramework.ForTestingMSTest; namespace MSTestAdapter.PlatformServices.UnitTests.Utilities; +/// +/// Tests for ReflectionOperations which provides platform-specific reflection operations. +/// public class ReflectionUtilityTests : TestContainer { + private readonly ReflectionOperations _reflectionOperations = new(); + #if NETFRAMEWORK public void GetSpecificCustomAttributesOnAssemblyShouldReturnAllAttributes() { Assembly asm = typeof(DummyTestClass).Assembly; - object[] attributes = new ReflectionOperations().GetCustomAttributes(asm, typeof(DummyAAttribute)); + object[] attributes = _reflectionOperations.GetCustomAttributes(asm, typeof(DummyAAttribute)); attributes.Should().NotBeNull(); attributes.Length.Should().Be(2); @@ -31,34 +35,90 @@ public void GetCustomAttributesShouldReturnAllAttributes() { MethodInfo methodInfo = typeof(DummyBaseTestClass).GetMethod("DummyVTestMethod1")!; - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(methodInfo); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); string[] expectedAttributes = ["DummyA : base", "DummySingleA : base"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetCustomAttributesShouldReturnAllAttributesWithBaseInheritance() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("DummyVTestMethod1")!; - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(methodInfo); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo); attributes.Should().NotBeNull(); attributes.Should().HaveCount(3); // Notice that the DummySingleA on the base method does not show up since it can only be defined once. string[] expectedAttributes = ["DummyA : derived", "DummySingleA : derived", "DummyA : base"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetCustomAttributesOnTypeShouldReturnAllAttributes() { Type type = typeof(DummyBaseTestClass); - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(type); + object[]? attributes = _reflectionOperations.GetCustomAttributes(type); + + attributes.Should().NotBeNull(); + // Filter to only our test attributes (excludes compiler-generated attributes like NullableContextAttribute) + List testAttributes = GetAttributeValuePairs(attributes!); + testAttributes.Should().HaveCount(1); + + string[] expectedAttributes = ["DummyA : ba"]; + testAttributes.Should().Equal(expectedAttributes); + } + + public void GetCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritance() + { + Type type = typeof(DummyTestClass); + + object[]? attributes = _reflectionOperations.GetCustomAttributes(type); + + attributes.Should().NotBeNull(); + // Filter to only our test attributes (excludes compiler-generated attributes like NullableContextAttribute) + List testAttributes = GetAttributeValuePairs(attributes!); + testAttributes.Should().HaveCount(2); + + string[] expectedAttributes = ["DummyA : a", "DummyA : ba"]; + testAttributes.Should().Equal(expectedAttributes); + } + + public void GetSpecificCustomAttributesShouldReturnAllAttributes() + { + MethodInfo methodInfo = typeof(DummyBaseTestClass).GetMethod("DummyVTestMethod1")!; + + DummyAAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); + + attributes.Should().NotBeNull(); + attributes.Should().HaveCount(1); + + string[] expectedAttributes = ["DummyA : base"]; + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + } + + public void GetSpecificCustomAttributesShouldReturnAllAttributesWithBaseInheritance() + { + MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("DummyVTestMethod1")!; + + DummyAAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); + + attributes.Should().NotBeNull(); + attributes.Should().HaveCount(2); + + string[] expectedAttributes = ["DummyA : derived", "DummyA : base"]; + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + } + + public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributes() + { + Type type = typeof(DummyBaseTestClass); + + DummyAAttribute[] attributes = _reflectionOperations.GetAttributes(type).ToArray(); attributes.Should().NotBeNull(); attributes.Should().HaveCount(1); @@ -67,11 +127,11 @@ public void GetCustomAttributesOnTypeShouldReturnAllAttributes() GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } - public void GetCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritance() + public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritance() { Type type = typeof(DummyTestClass); - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(type); + DummyAAttribute[] attributes = _reflectionOperations.GetAttributes(type).ToArray(); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); diff --git a/test/UnitTests/MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj b/test/UnitTests/MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj index 4cd17ec7e7..b04c4a168e 100644 --- a/test/UnitTests/MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj +++ b/test/UnitTests/MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj @@ -17,6 +17,7 @@ +