From bce4b9c16d784c3aa9daa7391ec3a7f41cada14d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Wed, 4 Feb 2026 17:02:31 +0100 Subject: [PATCH 01/10] Unify reflection helpers --- .../Services/ReflectionOperations.cs | 265 +++++++++++++++- .../Services/TestDeployment.cs | 3 +- .../Utilities/DeploymentItemUtility.cs | 20 +- .../Utilities/DeploymentUtilityBase.cs | 3 +- .../Utilities/ReflectionUtility.cs | 282 ------------------ .../ReflectionUtilityTests.cs | 53 ++-- .../DesktopReflectionOperationsTests.cs | 6 +- .../Services/DesktopTestDeploymentTests.cs | 16 +- .../Services/TestDeploymentTests.cs | 26 +- .../Utilities/DeploymentItemUtilityTests.cs | 22 +- .../Utilities/DeploymentUtilityTests.cs | 7 +- .../Utilities/ReflectionUtilityTests.cs | 51 ++-- 12 files changed, 368 insertions(+), 386 deletions(-) delete mode 100644 src/Adapter/MSTestAdapter.PlatformServices/Utilities/ReflectionUtility.cs diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs index 6f0a33458f..a71b99faa4 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs @@ -2,9 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -#if NETFRAMEWORK -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; -#endif namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; @@ -24,7 +21,7 @@ internal sealed class ReflectionOperations : IReflectionOperations [return: NotNullIfNotNull(nameof(memberInfo))] public object[]? GetCustomAttributes(MemberInfo memberInfo) #if NETFRAMEWORK - => [.. ReflectionUtility.GetCustomAttributes(memberInfo)]; + => [.. GetCustomAttributesImpl(memberInfo)]; #else { object[] attributes = memberInfo.GetCustomAttributes(typeof(Attribute), inherit: true); @@ -47,7 +44,7 @@ internal sealed class ReflectionOperations : IReflectionOperations [return: NotNullIfNotNull(nameof(memberInfo))] public object[]? GetCustomAttributes(MemberInfo memberInfo, Type type) => #if NETFRAMEWORK - [.. ReflectionUtility.GetCustomAttributesCore(memberInfo, type)]; + [.. GetCustomAttributesCoreImpl(memberInfo, type)]; #else memberInfo.GetCustomAttributes(type, inherit: true); #endif @@ -60,11 +57,267 @@ internal sealed class ReflectionOperations : IReflectionOperations /// 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 - ReflectionUtility.GetCustomAttributes(assembly, type).ToArray(); + GetCustomAttributesFromAssembly(assembly, type).ToArray(); #else assembly.GetCustomAttributes(type, inherit: true); #endif +#if NETFRAMEWORK + /// + /// Gets all the custom attributes adorned on a member. + /// + /// The member. + /// The list of attributes on the member. Empty list if none found. + private static IReadOnlyList GetCustomAttributesImpl(MemberInfo memberInfo) + => GetCustomAttributesCoreImpl(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 GetCustomAttributesCoreImpl(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); + } + + 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; + } + + private static List GetCustomAttributesFromAssembly(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 = GetCustomAttributesCoreImpl( + 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 + #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 #pragma warning disable IL2067 // 'target parameter' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to 'target method'. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs index 74314239ff..2fc480dc1e 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs @@ -4,6 +4,7 @@ #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; @@ -34,7 +35,7 @@ internal sealed class TestDeployment : ITestDeployment /// Initializes a new instance of the class. /// public TestDeployment() - : this(new DeploymentItemUtility(new ReflectionUtility()), new DeploymentUtility(), new FileUtility()) + : this(new DeploymentItemUtility(ReflectHelper.Instance), new DeploymentUtility(), new FileUtility()) { } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs index da40a93100..432db7470e 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs @@ -3,6 +3,7 @@ #if !WINDOWS_UWP && !WIN_UI +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -14,8 +15,7 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Uti /// internal sealed class DeploymentItemUtility { - // REVIEW: it would be better if this was a ReflectionHelper, because helper is able to cache. But we don't have reflection helper here, because this is platform services dll. - private readonly ReflectionUtility _reflectionUtility; + private readonly ReflectHelper _reflectHelper; /// /// 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(ReflectionUtility reflectionUtility) + /// The reflect helper. + internal DeploymentItemUtility(ReflectHelper reflectHelper) { - _reflectionUtility = reflectionUtility; + _reflectHelper = reflectHelper; _classLevelDeploymentItems = []; } @@ -42,9 +42,7 @@ internal IList GetClassLevelDeploymentItems(Type type, ICollecti { if (!_classLevelDeploymentItems.TryGetValue(type, out IList? value)) { - IReadOnlyList deploymentItemAttributes = _reflectionUtility.GetCustomAttributes( - type, - typeof(DeploymentItemAttribute)); + IEnumerable deploymentItemAttributes = _reflectHelper.GetAttributes(type); value = GetDeploymentItems(deploymentItemAttributes, warnings); _classLevelDeploymentItems[type] = value; } @@ -61,7 +59,7 @@ internal IList GetClassLevelDeploymentItems(Type type, ICollecti internal KeyValuePair[]? GetDeploymentItems(MethodInfo method, IEnumerable classLevelDeploymentItems, ICollection warnings) { - List testLevelDeploymentItems = GetDeploymentItems(_reflectionUtility.GetCustomAttributes(method, typeof(DeploymentItemAttribute)), warnings); + List testLevelDeploymentItems = GetDeploymentItems(_reflectHelper.GetAttributes(method), warnings); return ToKeyValuePairs(Concat(testLevelDeploymentItems, classLevelDeploymentItems)); } @@ -174,11 +172,11 @@ private static bool IsInvalidPath(string path) return false; } - private static List GetDeploymentItems(IEnumerable deploymentItemAttributes, ICollection warnings) + private static List GetDeploymentItems(IEnumerable deploymentItemAttributes, ICollection warnings) { var deploymentItems = new List(); - foreach (DeploymentItemAttribute deploymentItemAttribute in deploymentItemAttributes.Cast()) + foreach (DeploymentItemAttribute deploymentItemAttribute in deploymentItemAttributes) { if (IsValidDeploymentItem(deploymentItemAttribute.Path, deploymentItemAttribute.OutputDirectory, out string? warning)) { diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs index eae48a32ee..2f407266ac 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs @@ -4,6 +4,7 @@ #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; @@ -25,7 +26,7 @@ internal abstract class DeploymentUtilityBase protected const string DeploymentFolderPrefix = "Deploy"; public DeploymentUtilityBase() - : this(new DeploymentItemUtility(new ReflectionUtility()), new AssemblyUtility(), new FileUtility()) + : this(new DeploymentItemUtility(ReflectHelper.Instance), new AssemblyUtility(), new FileUtility()) { } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/ReflectionUtility.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/ReflectionUtility.cs deleted file mode 100644 index fd3a04028c..0000000000 --- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/ReflectionUtility.cs +++ /dev/null @@ -1,282 +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 !WINDOWS_UWP - -namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; - -/// -/// Utility for reflection API's. -/// -[SuppressMessage("Performance", "CA1852: Seal internal types", Justification = "Overrides required for testability")] -internal class ReflectionUtility -{ - internal virtual IReadOnlyList GetCustomAttributes(MemberInfo memberInfo, Type? type) - => GetCustomAttributesCore(memberInfo, type); - - /// - /// 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 - internal static IReadOnlyList GetCustomAttributesCore(MemberInfo memberInfo, Type? type) -#pragma warning restore CA1859 - { -#if NETFRAMEWORK - 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; - } -#else - return type == null - ? memberInfo.GetCustomAttributes(inherit: true) - : memberInfo.GetCustomAttributes(type, inherit: true); -#endif - } - -#if NETFRAMEWORK - 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 -} - -#endif diff --git a/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs b/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs index e8202aedbc..91fedeedce 100644 --- a/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs +++ b/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs @@ -3,7 +3,7 @@ using AwesomeAssertions; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using SampleFrameworkExtensions; @@ -11,9 +11,14 @@ namespace PlatformServices.Desktop.ComponentTests; +/// +/// Integration tests for ReflectionOperations which provides platform-specific reflection operations, +/// including the special handling of reflection-only loaded assemblies. +/// public class ReflectionUtilityTests : TestContainer { private readonly Assembly _testAsset; + private readonly ReflectionOperations _reflectionOperations = new(); public ReflectionUtilityTests() { @@ -39,66 +44,66 @@ public void GetCustomAttributesShouldReturnAllAttributes() { MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestBaseClass").GetMethod("DummyVTestMethod1"); - IReadOnlyList attributes = ReflectionUtility.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().Equal(expectedAttributes); } public void GetCustomAttributesShouldReturnAllAttributesWithBaseInheritance() { MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass").GetMethod("DummyVTestMethod1"); - IReadOnlyList attributes = ReflectionUtility.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().Equal(expectedAttributes); } public void GetCustomAttributesOnTypeShouldReturnAllAttributes() { Type type = _testAsset.GetType("TestProjectForDiscovery.AttributeTestBaseClass"); - IReadOnlyList attributes = ReflectionUtility.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 = ReflectionUtility.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); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesShouldReturnAllAttributes() { MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestBaseClass").GetMethod("DummyVTestMethod1"); - IReadOnlyList attributes = ReflectionUtility.GetCustomAttributesCore(methodInfo, typeof(TestCategoryAttribute)); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo, typeof(TestCategoryAttribute)); attributes.Should().NotBeNull(); attributes.Should().HaveCount(1); string[] expectedAttributes = ["TestCategory : base"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesShouldReturnAllAttributesWithBaseInheritance() @@ -106,85 +111,85 @@ public void GetSpecificCustomAttributesShouldReturnAllAttributesWithBaseInherita MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass").GetMethod("DummyVTestMethod1"); - IReadOnlyList attributes = ReflectionUtility.GetCustomAttributesCore(methodInfo, typeof(TestCategoryAttribute)); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo, typeof(TestCategoryAttribute)); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); string[] expectedAttributes = ["TestCategory : derived", "TestCategory : base"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetCustomAttributesShouldReturnAllAttributesIncludingUserDefinedAttributes() { MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClassWithCustomAttributes").GetMethod("DummyVTestMethod1"); - IReadOnlyList attributes = ReflectionUtility.GetCustomAttributesCore(methodInfo, null); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo); attributes.Should().NotBeNull(); attributes.Should().HaveCount(3); string[] expectedAttributes = ["Duration : superfast", "TestCategory : base", "Owner : base"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesShouldReturnAllAttributesIncludingUserDefinedAttributes() { MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClassWithCustomAttributes").GetMethod("DummyVTestMethod1"); - IReadOnlyList attributes = ReflectionUtility.GetCustomAttributesCore(methodInfo, typeof(TestPropertyAttribute)); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo, typeof(TestPropertyAttribute)); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); string[] expectedAttributes = ["Duration : superfast", "Owner : base"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesShouldReturnArrayAttributesAsWell() { MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClassWithCustomAttributes").GetMethod("DummyTestMethod2"); - IReadOnlyList attributes = ReflectionUtility.GetCustomAttributesCore(methodInfo, typeof(CategoryArrayAttribute)); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo, typeof(CategoryArrayAttribute)); attributes.Should().NotBeNull(); attributes.Should().HaveCount(1); string[] expectedAttributes = ["CategoryAttribute : foo,foo2"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributes() { Type type = _testAsset.GetType("TestProjectForDiscovery.AttributeTestBaseClass"); - IReadOnlyList attributes = ReflectionUtility.GetCustomAttributesCore(type, typeof(TestCategoryAttribute)); + object[]? attributes = _reflectionOperations.GetCustomAttributes(type, typeof(TestCategoryAttribute)); attributes.Should().NotBeNull(); attributes.Should().HaveCount(1); string[] expectedAttributes = ["TestCategory : ba"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritance() { Type type = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass"); - IReadOnlyList attributes = ReflectionUtility.GetCustomAttributesCore(type, typeof(TestCategoryAttribute)); + object[]? attributes = _reflectionOperations.GetCustomAttributes(type, typeof(TestCategoryAttribute)); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); string[] expectedAttributes = ["TestCategory : a", "TestCategory : ba"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesOnAssemblyShouldReturnAllAttributes() { Assembly asm = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass").Assembly; - List attributes = ReflectionUtility.GetCustomAttributes(asm, typeof(TestCategoryAttribute)); + object[] attributes = _reflectionOperations.GetCustomAttributes(asm, typeof(TestCategoryAttribute)); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); 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 46075ccf5b..81e99154b4 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs @@ -4,6 +4,7 @@ #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.Utilities; @@ -23,7 +24,7 @@ public class DesktopTestDeploymentTests : TestContainer private const string DefaultDeploymentItemPath = @"c:\temp"; private const string DefaultDeploymentItemOutputDirectory = "out"; - private readonly Mock _mockReflectionUtility; + private readonly Mock _mockReflectHelper; private readonly Mock _mockFileUtility; #pragma warning disable IDE0052 // Remove unread private members @@ -32,7 +33,7 @@ public class DesktopTestDeploymentTests : TestContainer public DesktopTestDeploymentTests() { - _mockReflectionUtility = new Mock(); + _mockReflectHelper = new Mock(); _mockFileUtility = new Mock(); _warnings = []; @@ -118,7 +119,7 @@ public void DeployShouldCreateDeploymentDirectories() #region private methods #pragma warning disable IDE0051 // Remove unused private members - private void SetupDeploymentItems(MemberInfo memberInfo, KeyValuePair[] deploymentItems) + private void SetupDeploymentItems(ICustomAttributeProvider attributeProvider, KeyValuePair[] deploymentItems) #pragma warning restore IDE0051 // Remove unused private members { var deploymentItemAttributes = new List(); @@ -128,11 +129,8 @@ private void SetupDeploymentItems(MemberInfo memberInfo, KeyValuePair - ru.GetCustomAttributes( - memberInfo, - typeof(DeploymentItemAttribute))).Returns(deploymentItemAttributes.ToArray()); + _mockReflectHelper.Setup( + ru => ru.GetAttributes(attributeProvider)).Returns(deploymentItemAttributes); } private TestCase GetTestCase(string source) @@ -167,7 +165,7 @@ private TestDeployment CreateAndSetupDeploymentRelatedUtilities(out TestRunDirec _mockFileUtility.Setup(fu => fu.GetNextIterationDirectoryName(It.IsAny(), It.IsAny())) .Returns(testRunDirectories.RootDeploymentDirectory); - var deploymentItemUtility = new DeploymentItemUtility(_mockReflectionUtility.Object); + var deploymentItemUtility = new DeploymentItemUtility(_mockReflectHelper.Object); return new TestDeployment( deploymentItemUtility, diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs index bac0185bd2..1e18309439 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs @@ -4,6 +4,7 @@ #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.Utilities; @@ -24,7 +25,7 @@ public class TestDeploymentTests : TestContainer private const string DefaultDeploymentItemPath = @"c:\temp"; private const string DefaultDeploymentItemOutputDirectory = "out"; - private readonly Mock _mockReflectionUtility; + private readonly Mock _mockReflectHelper; private readonly Mock _mockFileUtility; #pragma warning disable IDE0044 // Add readonly modifier @@ -33,7 +34,7 @@ public class TestDeploymentTests : TestContainer public TestDeploymentTests() { - _mockReflectionUtility = new Mock(); + _mockReflectHelper = new Mock(); _mockFileUtility = new Mock(); _warnings = []; @@ -52,7 +53,7 @@ public void GetDeploymentItemsReturnsNullWhenNoDeploymentItems() public void GetDeploymentItemsReturnsDeploymentItems() { // Arrange. - var testDeployment = new TestDeployment(new DeploymentItemUtility(_mockReflectionUtility.Object), null!, null!); + var testDeployment = new TestDeployment(new DeploymentItemUtility(_mockReflectHelper.Object), null!, null!); // setup mocks KeyValuePair[] methodLevelDeploymentItems = @@ -181,7 +182,7 @@ public void DeployShouldReturnFalseWhenDeploymentEnabledSetToFalseButHasDeployme testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, kvpArray); var testDeployment = new TestDeployment( - new DeploymentItemUtility(_mockReflectionUtility.Object), + new DeploymentItemUtility(_mockReflectHelper.Object), new DeploymentUtility(), _mockFileUtility.Object); @@ -204,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(_mockReflectionUtility.Object), + new DeploymentItemUtility(_mockReflectHelper.Object), new DeploymentUtility(), _mockFileUtility.Object); @@ -227,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(_mockReflectionUtility.Object), + new DeploymentItemUtility(_mockReflectHelper.Object), new DeploymentUtility(), _mockFileUtility.Object); @@ -257,7 +258,7 @@ internal void DeployShouldReturnTrueWhenDeploymentEnabledSetToTrueAndHasDeployme ]; testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, kvpArray); var testDeployment = new TestDeployment( - new DeploymentItemUtility(_mockReflectionUtility.Object), + new DeploymentItemUtility(_mockReflectHelper.Object), new DeploymentUtility(), _mockFileUtility.Object); @@ -361,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(); @@ -370,11 +371,8 @@ private void SetupDeploymentItems(MemberInfo memberInfo, KeyValuePair - ru.GetCustomAttributes( - memberInfo, - typeof(DeploymentItemAttribute))).Returns(deploymentItemAttributes.ToArray()); + _mockReflectHelper.Setup( + ru => ru.GetAttributes(attributeProvider)).Returns(deploymentItemAttributes); } private static TestCase GetTestCase(string source) @@ -418,7 +416,7 @@ private TestDeployment CreateAndSetupDeploymentRelatedUtilities(out TestRunDirec _mockFileUtility.Setup(fu => fu.GetNextIterationDirectoryName(It.IsAny(), It.IsAny())) .Returns(testRunDirectories.RootDeploymentDirectory); - var deploymentItemUtility = new DeploymentItemUtility(_mockReflectionUtility.Object); + var deploymentItemUtility = new DeploymentItemUtility(_mockReflectHelper.Object); return new TestDeployment( deploymentItemUtility, diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs index 9f6cca7428..81a73eecdc 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs @@ -4,6 +4,7 @@ #if !WINDOWS_UWP && !WIN_UI using AwesomeAssertions; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; @@ -24,7 +25,7 @@ public class DeploymentItemUtilityTests : TestContainer TestPropertyAttributes.Hidden, typeof(TestCase)); - private readonly Mock _mockReflectionUtility; + private readonly Mock _mockReflectHelper; private readonly DeploymentItemUtility _deploymentItemUtility; private readonly ICollection _warnings; @@ -33,8 +34,8 @@ public class DeploymentItemUtilityTests : TestContainer public DeploymentItemUtilityTests() { - _mockReflectionUtility = new Mock(); - _deploymentItemUtility = new DeploymentItemUtility(_mockReflectionUtility.Object); + _mockReflectHelper = new Mock(); + _deploymentItemUtility = new DeploymentItemUtility(_mockReflectHelper.Object); _warnings = []; } @@ -42,7 +43,7 @@ public DeploymentItemUtilityTests() public void GetClassLevelDeploymentItemsShouldReturnEmptyListWhenNoDeploymentItems() { - _mockReflectionUtility.Setup(x => x.GetCustomAttributes(typeof(DeploymentItemUtilityTests), typeof(DeploymentItemAttribute))) + _mockReflectHelper.Setup(x => x.GetAttributes(typeof(DeploymentItemUtilityTests))) .Returns([]); IList deploymentItems = _deploymentItemUtility.GetClassLevelDeploymentItems(typeof(DeploymentItemUtilityTests), _warnings); @@ -163,7 +164,7 @@ public void GetClassLevelDeploymentItemsShouldReportWarningsForInvalidDeployment public void GetDeploymentItemsShouldReturnNullOnNoDeploymentItems() { MethodInfo method = typeof(DeploymentItemUtilityTests).GetMethod("GetDeploymentItemsShouldReturnNullOnNoDeploymentItems")!; - _mockReflectionUtility.Setup(x => x.GetCustomAttributes(method, typeof(DeploymentItemAttribute))) + _mockReflectHelper.Setup(x => x.GetAttributes(method)) .Returns([]); _deploymentItemUtility.GetDeploymentItems(method, null!, _warnings).Should().BeNull(); @@ -208,7 +209,7 @@ public void GetDeploymentItemsShouldReturnClassLevelDeploymentItemsOnly() }; MethodInfo method = typeof(DeploymentItemUtilityTests).GetMethod("GetDeploymentItemsShouldReturnNullOnNoDeploymentItems")!; - _mockReflectionUtility.Setup(x => x.GetCustomAttributes(method, typeof(DeploymentItemAttribute))) + _mockReflectHelper.Setup(x => x.GetAttributes(method)) .Returns([]); // Act. @@ -413,7 +414,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(); @@ -422,11 +423,8 @@ private void SetupDeploymentItems(MemberInfo memberInfo, KeyValuePair - ru.GetCustomAttributes( - memberInfo, - typeof(DeploymentItemAttribute))).Returns(deploymentItemAttributes.ToArray()); + _mockReflectHelper.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 e5b4e16018..78b566b86f 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentUtilityTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentUtilityTests.cs @@ -4,6 +4,7 @@ #if !WINDOWS_UWP && !WIN_UI using AwesomeAssertions; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; @@ -24,7 +25,7 @@ public class DeploymentUtilityTests : TestContainer private const string DefaultDeploymentItemPath = @"c:\temp"; private const string DefaultDeploymentItemOutputDirectory = "out"; - private readonly Mock _mockReflectionUtility; + private readonly Mock _mockReflectHelper; private readonly Mock _mockFileUtility; private readonly Mock _mockAssemblyUtility; private readonly Mock _mockRunContext; @@ -40,13 +41,13 @@ public class DeploymentUtilityTests : TestContainer public DeploymentUtilityTests() { - _mockReflectionUtility = new Mock(); + _mockReflectHelper = new Mock(); _mockFileUtility = new Mock(); _mockAssemblyUtility = new Mock(); _warnings = []; _deploymentUtility = new DeploymentUtility( - new DeploymentItemUtility(_mockReflectionUtility.Object), + new DeploymentItemUtility(_mockReflectHelper.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 0741a12d64..8f3b572557 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/ReflectionUtilityTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/ReflectionUtilityTests.cs @@ -3,23 +3,28 @@ using AwesomeAssertions; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; 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; - List attributes = ReflectionUtility.GetCustomAttributes(asm, typeof(DummyAAttribute)); + object[] attributes = _reflectionOperations.GetCustomAttributes(asm, typeof(DummyAAttribute)); attributes.Should().NotBeNull(); - attributes.Count.Should().Be(2); + attributes.Length.Should().Be(2); string[] expectedAttributes = ["DummyA : a1", "DummyA : a2"]; expectedAttributes.SequenceEqual(GetAttributeValuePairs(attributes)).Should().BeTrue(); @@ -30,105 +35,109 @@ public void GetCustomAttributesShouldReturnAllAttributes() { MethodInfo methodInfo = typeof(DummyBaseTestClass).GetMethod("DummyVTestMethod1")!; - IReadOnlyList attributes = ReflectionUtility.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 = ReflectionUtility.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 = ReflectionUtility.GetCustomAttributes(type); + object[]? attributes = _reflectionOperations.GetCustomAttributes(type); attributes.Should().NotBeNull(); - attributes.Should().HaveCount(1); + // Filter to only our test attributes (excludes compiler-generated attributes like NullableContextAttribute) + List testAttributes = GetAttributeValuePairs(attributes!); + testAttributes.Should().HaveCount(1); string[] expectedAttributes = ["DummyA : ba"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + testAttributes.Should().Equal(expectedAttributes); } public void GetCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritance() { Type type = typeof(DummyTestClass); - IReadOnlyList attributes = ReflectionUtility.GetCustomAttributes(type); + object[]? attributes = _reflectionOperations.GetCustomAttributes(type); attributes.Should().NotBeNull(); - attributes.Should().HaveCount(2); + // 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"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + testAttributes.Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesShouldReturnAllAttributes() { MethodInfo methodInfo = typeof(DummyBaseTestClass).GetMethod("DummyVTestMethod1")!; - IReadOnlyList attributes = ReflectionUtility.GetCustomAttributesCore(methodInfo, typeof(DummyAAttribute)); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo, typeof(DummyAAttribute)); attributes.Should().NotBeNull(); attributes.Should().HaveCount(1); string[] expectedAttributes = ["DummyA : base"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesShouldReturnAllAttributesWithBaseInheritance() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("DummyVTestMethod1")!; - IReadOnlyList attributes = ReflectionUtility.GetCustomAttributesCore(methodInfo, typeof(DummyAAttribute)); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo, typeof(DummyAAttribute)); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); string[] expectedAttributes = ["DummyA : derived", "DummyA : base"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributes() { Type type = typeof(DummyBaseTestClass); - IReadOnlyList attributes = ReflectionUtility.GetCustomAttributesCore(type, typeof(DummyAAttribute)); + object[]? attributes = _reflectionOperations.GetCustomAttributes(type, typeof(DummyAAttribute)); attributes.Should().NotBeNull(); attributes.Should().HaveCount(1); string[] expectedAttributes = ["DummyA : ba"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritance() { Type type = typeof(DummyTestClass); - IReadOnlyList attributes = ReflectionUtility.GetCustomAttributesCore(type, typeof(DummyAAttribute)); + object[]? attributes = _reflectionOperations.GetCustomAttributes(type, typeof(DummyAAttribute)); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); string[] expectedAttributes = ["DummyA : a", "DummyA : ba"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } internal static List GetAttributeValuePairs(IEnumerable attributes) From 3a29b63dcd49226dcf6f2a963d4229e931e84a72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Fri, 6 Feb 2026 17:03:42 +0100 Subject: [PATCH 02/10] Simplify further --- .../Services/ReflectionOperations.cs | 276 +----------------- .../ReflectionUtilityTests.cs | 21 +- 2 files changed, 9 insertions(+), 288 deletions(-) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs index a71b99faa4..9cb917f0bc 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs @@ -20,9 +20,6 @@ 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 - => [.. GetCustomAttributesImpl(memberInfo)]; -#else { object[] attributes = memberInfo.GetCustomAttributes(typeof(Attribute), inherit: true); @@ -33,7 +30,6 @@ internal sealed class ReflectionOperations : IReflectionOperations Debug.Assert(attributes is Attribute[], $"Expected Attribute[], found '{attributes.GetType()}'."); return attributes; } -#endif /// /// Gets all the custom attributes of a given type adorned on a member. @@ -42,12 +38,8 @@ internal sealed class ReflectionOperations : IReflectionOperations /// The attribute type. /// The list of attributes on the member. Empty list if none found. [return: NotNullIfNotNull(nameof(memberInfo))] - public object[]? GetCustomAttributes(MemberInfo memberInfo, Type type) => -#if NETFRAMEWORK - [.. GetCustomAttributesCoreImpl(memberInfo, type)]; -#else - memberInfo.GetCustomAttributes(type, inherit: true); -#endif + public object[]? GetCustomAttributes(MemberInfo memberInfo, Type type) + => memberInfo.GetCustomAttributes(type, inherit: true); /// /// Gets all the custom attributes of a given type on an assembly. @@ -55,268 +47,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 - GetCustomAttributesFromAssembly(assembly, type).ToArray(); -#else - assembly.GetCustomAttributes(type, inherit: true); -#endif - -#if NETFRAMEWORK - /// - /// Gets all the custom attributes adorned on a member. - /// - /// The member. - /// The list of attributes on the member. Empty list if none found. - private static IReadOnlyList GetCustomAttributesImpl(MemberInfo memberInfo) - => GetCustomAttributesCoreImpl(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 GetCustomAttributesCoreImpl(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); - } - - 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; - } - - private static List GetCustomAttributesFromAssembly(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 = GetCustomAttributesCoreImpl( - 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 + 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 diff --git a/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs b/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs index 91fedeedce..aac9d4adfd 100644 --- a/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs +++ b/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs @@ -12,8 +12,7 @@ namespace PlatformServices.Desktop.ComponentTests; /// -/// Integration tests for ReflectionOperations which provides platform-specific reflection operations, -/// including the special handling of reflection-only loaded assemblies. +/// Integration tests for ReflectionOperations which provides platform-specific reflection operations. /// public class ReflectionUtilityTests : TestContainer { @@ -34,10 +33,7 @@ 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() @@ -50,7 +46,7 @@ public void GetCustomAttributesShouldReturnAllAttributes() attributes.Should().HaveCount(2); string[] expectedAttributes = ["TestCategory : base", "Owner : base"]; - GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().BeEquivalentTo(expectedAttributes); } public void GetCustomAttributesShouldReturnAllAttributesWithBaseInheritance() @@ -64,7 +60,7 @@ public void GetCustomAttributesShouldReturnAllAttributesWithBaseInheritance() // 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() @@ -130,7 +126,7 @@ public void GetCustomAttributesShouldReturnAllAttributesIncludingUserDefinedAttr attributes.Should().HaveCount(3); string[] expectedAttributes = ["Duration : superfast", "TestCategory : base", "Owner : base"]; - GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().BeEquivalentTo(expectedAttributes); } public void GetSpecificCustomAttributesShouldReturnAllAttributesIncludingUserDefinedAttributes() @@ -198,13 +194,6 @@ public void GetSpecificCustomAttributesOnAssemblyShouldReturnAllAttributes() GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } - private static Assembly ReflectionOnlyOnResolve(object sender, ResolveEventArgs args) - { - string assemblyNameToLoad = AppDomain.CurrentDomain.ApplyPolicy(args.Name); - - return Assembly.ReflectionOnlyLoad(assemblyNameToLoad); - } - private static string[] GetAttributeValuePairs(IEnumerable attributes) { var attributeValuePairs = new List(); From ed701973b9d026665e66795f1166959d0482c379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Fri, 6 Feb 2026 17:17:57 +0100 Subject: [PATCH 03/10] Apply Youssef changes --- .../Interfaces/IReflectionOperations.cs | 9 ---- .../Services/ReflectionOperations.cs | 10 ---- .../Execution/TestMethodRunnerTests.cs | 11 ---- .../Helpers/ReflectHelperTests.cs | 11 ---- .../Services/ReflectionOperationsTests.cs | 52 ------------------- 5 files changed, 93 deletions(-) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs index bc1c3e7f6e..b154b531cc 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs @@ -16,15 +16,6 @@ internal interface IReflectionOperations [return: NotNullIfNotNull(nameof(memberInfo))] object[]? GetCustomAttributes(MemberInfo memberInfo); - /// - /// Gets all the custom attributes of a given type adorned on a member. - /// - /// The member info. - /// The attribute type. - /// The list of attributes on the member. Empty list if none found. - [return: NotNullIfNotNull(nameof(memberInfo))] - object[]? GetCustomAttributes(MemberInfo memberInfo, Type type); - /// /// Gets all the custom attributes of a given type on an assembly. /// diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs index 9cb917f0bc..494f845b5b 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs @@ -31,16 +31,6 @@ internal sealed class ReflectionOperations : IReflectionOperations return attributes; } - /// - /// Gets all the custom attributes of a given type adorned on a member. - /// - /// The member info. - /// The attribute type. - /// The list of attributes on the member. Empty list if none found. - [return: NotNullIfNotNull(nameof(memberInfo))] - public object[]? GetCustomAttributes(MemberInfo memberInfo, Type type) - => memberInfo.GetCustomAttributes(type, inherit: true); - /// /// Gets all the custom attributes of a given type on an assembly. /// diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs index 6960d52cd7..8c601187e1 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs @@ -244,17 +244,6 @@ public async Task RunTestMethodShouldRunDataDrivenTestsWhenDataIsProvidedUsingDa var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => testResult); var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation); - int dummyIntData = 2; - string dummyStringData = "DummyString"; - DataRowAttribute dataRowAttribute = new( - dummyIntData, - dummyStringData); - - var attributes = new Attribute[] { dataRowAttribute }; - - // Setup mocks - _testablePlatformServiceProvider.MockReflectionOperations.Setup(ro => ro.GetCustomAttributes(_methodInfo, It.IsAny())).Returns(attributes); - TestResult[] results = await testMethodRunner.RunTestMethodAsync(); results[0].Outcome.Should().Be(UTF.UnitTestOutcome.Inconclusive); } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Helpers/ReflectHelperTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Helpers/ReflectHelperTests.cs index f77e4c6301..89979ac6df 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Helpers/ReflectHelperTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Helpers/ReflectHelperTests.cs @@ -187,9 +187,6 @@ public void IsAttributeDefinedShouldReturnFromCache() // Validate that reflection APIs are not called again. rh.IsAttributeDefined(memberInfo).Should().BeTrue(); _testablePlatformServiceProvider.MockReflectionOperations.Verify(ro => ro.GetCustomAttributes(memberInfo), Times.Once); - - // Also validate that reflection APIs for an individual type is not called since the cache gives us what we need already. - _testablePlatformServiceProvider.MockReflectionOperations.Verify(ro => ro.GetCustomAttributes(It.IsAny(), It.IsAny()), Times.Never); } public void HasAttributeDerivedFromShouldReturnTrueIfSpecifiedAttributeIsDefinedOnAMember() @@ -237,25 +234,17 @@ public void HasAttributeDerivedFromShouldReturnFromCache() // Validate that reflection APIs are not called again. rh.IsAttributeDefined(memberInfo).Should().BeTrue(); _testablePlatformServiceProvider.MockReflectionOperations.Verify(ro => ro.GetCustomAttributes(memberInfo), Times.Once); - - // Also validate that reflection APIs for an individual type is not called since the cache gives us what we need already. - _testablePlatformServiceProvider.MockReflectionOperations.Verify(ro => ro.GetCustomAttributes(It.IsAny(), It.IsAny()), Times.Never); } public void HasAttributeDerivedFromShouldReturnFalseQueryingProvidedAttributesExistenceIfGettingAllAttributesFail() { var rh = new ReflectHelper(); var mockMemberInfo = new Mock(); - var attributes = new Attribute[] { new TestableExtendedTestMethod() }; _testablePlatformServiceProvider.MockReflectionOperations. Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). Returns((object[])null!); - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object, typeof(TestMethodAttribute))). - Returns(attributes); - rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeFalse(); } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs index 6eeec3094a..be9a7893b4 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs @@ -71,58 +71,6 @@ public void GetCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritanc GetAttributeValuePairs(attributes).SequenceEqual(expectedAttributes).Should().BeTrue(); } - public void GetSpecificCustomAttributesShouldReturnAllAttributes() - { - MethodInfo methodInfo = typeof(DummyBaseTestClass).GetMethod("DummyVTestMethod1")!; - - object[] attributes = _reflectionOperations.GetCustomAttributes(methodInfo, typeof(DummyAAttribute)); - - attributes.Should().NotBeNull(); - attributes.Length.Should().Be(1); - - string[] expectedAttributes = ["DummyA : base"]; - GetAttributeValuePairs(attributes).SequenceEqual(expectedAttributes).Should().BeTrue(); - } - - public void GetSpecificCustomAttributesShouldReturnAllAttributesWithBaseInheritance() - { - MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("DummyVTestMethod1")!; - - object[] attributes = _reflectionOperations.GetCustomAttributes(methodInfo, typeof(DummyAAttribute)); - - attributes.Should().NotBeNull(); - attributes.Length.Should().Be(2); - - string[] expectedAttributes = ["DummyA : derived", "DummyA : base"]; - GetAttributeValuePairs(attributes).SequenceEqual(expectedAttributes).Should().BeTrue(); - } - - public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributes() - { - Type type = typeof(DummyBaseTestClass); - - object[] attributes = _reflectionOperations.GetCustomAttributes(type, typeof(DummyAAttribute)); - - attributes.Should().NotBeNull(); - attributes.Length.Should().Be(1); - - string[] expectedAttributes = ["DummyA : ba"]; - GetAttributeValuePairs(attributes).SequenceEqual(expectedAttributes).Should().BeTrue(); - } - - public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritance() - { - Type type = typeof(DummyTestClass); - - object[] attributes = _reflectionOperations.GetCustomAttributes(type, typeof(DummyAAttribute)); - - attributes.Should().NotBeNull(); - attributes.Length.Should().Be(2); - - string[] expectedAttributes = ["DummyA : a", "DummyA : ba"]; - GetAttributeValuePairs(attributes).SequenceEqual(expectedAttributes).Should().BeTrue(); - } - public void GetSpecificCustomAttributesOnAssemblyShouldReturnAllAttributes() { Assembly asm = typeof(DummyTestClass).Assembly; From 2ca249e7ec518db0a404b6b8bf693991b8ce1bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Fri, 6 Feb 2026 19:10:33 +0100 Subject: [PATCH 04/10] More updates --- .../Discovery/AssemblyEnumerator.cs | 27 +- .../Discovery/TestMethodValidator.cs | 14 +- .../Discovery/TypeEnumerator.cs | 24 +- .../Discovery/TypeValidator.cs | 18 +- .../Execution/TestAssemblySettingsProvider.cs | 7 +- .../Execution/TestMethodInfo.cs | 11 +- .../Execution/TestMethodRunner.cs | 3 +- .../Execution/TypeCache.cs | 23 +- .../Execution/UnitTestRunner.cs | 8 +- .../Extensions/MethodInfoExtensions.cs | 39 +- .../Helpers/AttributeHelpers.cs | 3 +- .../Helpers/ReflectHelper.cs | 395 ------------------ .../Interfaces/IReflectionOperations.cs | 89 ++++ .../Services/ReflectionOperations.cs | 387 ++++++++++++++++- .../Services/TestDeployment.cs | 3 +- .../Utilities/DeploymentItemUtility.cs | 14 +- .../Utilities/DeploymentUtilityBase.cs | 3 +- .../Discovery/AssemblyEnumeratorTests.cs | 12 +- .../Discovery/TestMethodValidatorTests.cs | 23 +- .../Discovery/TypeEnumeratorTests.cs | 21 +- .../Discovery/TypeValidatorTests.cs | 29 +- .../Execution/ClassCleanupManagerTests.cs | 6 +- .../Execution/TestMethodRunnerTests.cs | 4 +- .../Execution/TestPropertyAttributeTests.cs | 14 +- .../Execution/TypeCacheTests.cs | 224 +++++----- .../Execution/UnitTestRunnerTests.cs | 11 +- .../Extensions/MethodInfoExtensionsTests.cs | 88 ++-- .../Helpers/ReflectHelperTests.cs | 305 -------------- .../Services/DesktopTestDeploymentTests.cs | 11 +- .../Services/ReflectionOperationsTests.cs | 310 +++++++++++++- .../Services/TestDeploymentTests.cs | 21 +- .../Utilities/DeploymentItemUtilityTests.cs | 17 +- .../Utilities/DeploymentUtilityTests.cs | 9 +- 33 files changed, 1137 insertions(+), 1036 deletions(-) delete mode 100644 src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectHelper.cs delete mode 100644 test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Helpers/ReflectHelperTests.cs diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs index 84630c264a..1e7d2401bd 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs @@ -5,8 +5,8 @@ using System.Security; 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.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting.Internal; @@ -21,18 +21,20 @@ internal class AssemblyEnumerator : MarshalByRefObject /// /// Helper for reflection API's. /// - private static readonly ReflectHelper ReflectHelper = ReflectHelper.Instance; + private readonly ReflectionOperations _reflectionOperations; /// /// Type cache. /// - private readonly TypeCache _typeCache = new(ReflectHelper); + private readonly TypeCache _typeCache; /// /// Initializes a new instance of the class. /// public AssemblyEnumerator() { + _reflectionOperations = (ReflectionOperations)PlatformServiceProvider.Instance.ReflectionOperations; + _typeCache = new TypeCache(_reflectionOperations); } /// @@ -40,10 +42,11 @@ public AssemblyEnumerator() /// /// The settings for the session. /// Use this constructor when creating this object in a new app domain so the settings for this app domain are set. - public AssemblyEnumerator(MSTestSettings settings) => + public AssemblyEnumerator(MSTestSettings settings) + : this() // Populate the settings into the domain(Desktop workflow) performing discovery. // This would just be resetting the settings to itself in non desktop workflows. - MSTestSettings.PopulateSettings(settings); + => MSTestSettings.PopulateSettings(settings); /// /// Returns object to be used for controlling lifetime, null means infinite lifetime. @@ -71,16 +74,16 @@ internal AssemblyEnumerationResult EnumerateAssembly(string assemblyFileName) Assembly assembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly(assemblyFileName); Type[] types = GetTypes(assembly); - bool discoverInternals = ReflectHelper.GetDiscoverInternalsAttribute(assembly) != null; + bool discoverInternals = _reflectionOperations.GetDiscoverInternalsAttribute(assembly) != null; - TestDataSourceUnfoldingStrategy dataSourcesUnfoldingStrategy = ReflectHelper.GetTestDataSourceOptions(assembly)?.UnfoldingStrategy switch + TestDataSourceUnfoldingStrategy dataSourcesUnfoldingStrategy = _reflectionOperations.GetTestDataSourceOptions(assembly)?.UnfoldingStrategy switch { // When strategy is auto we want to unfold TestDataSourceUnfoldingStrategy.Auto => TestDataSourceUnfoldingStrategy.Unfold, // When strategy is set, let's use it { } value => value, // When the attribute is not set, let's look at the legacy attribute - null => ReflectHelper.GetTestDataSourceDiscoveryOption(assembly) switch + null => _reflectionOperations.GetTestDataSourceDiscoveryOption(assembly) switch { TestDataSourceDiscoveryOption.DuringExecution => TestDataSourceUnfoldingStrategy.Fold, _ => TestDataSourceUnfoldingStrategy.Unfold, @@ -142,10 +145,10 @@ internal static Type[] GetTypes(Assembly assembly) /// a TypeEnumerator instance. internal virtual TypeEnumerator GetTypeEnumerator(Type type, string assemblyFileName, bool discoverInternals) { - var typeValidator = new TypeValidator(ReflectHelper, discoverInternals); - var testMethodValidator = new TestMethodValidator(ReflectHelper, discoverInternals); + var typeValidator = new TypeValidator(_reflectionOperations, discoverInternals); + var testMethodValidator = new TestMethodValidator(_reflectionOperations, discoverInternals); - return new TypeEnumerator(type, assemblyFileName, ReflectHelper, typeValidator, testMethodValidator); + return new TypeEnumerator(type, assemblyFileName, _reflectionOperations, typeValidator, testMethodValidator); } private List DiscoverTestsInType( @@ -221,7 +224,7 @@ private static bool TryUnfoldITestDataSources(UnitTestElement test, DiscoveryTes // We don't have a special method to filter attributes that are not derived from Attribute, so we take all // attributes and filter them. We don't have to care if there is one, because this method is only entered when // there is at least one (we determine this in TypeEnumerator.GetTestFromMethod. - IEnumerable testDataSources = ReflectHelper.Instance.GetAttributes(testMethodInfo.MethodInfo).OfType(); + IEnumerable testDataSources = ((ReflectionOperations)PlatformServiceProvider.Instance.ReflectionOperations).GetAttributes(testMethodInfo.MethodInfo).OfType(); // We need to use a temporary list to avoid adding tests to the main list if we fail to expand any data source. List tempListOfTests = []; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TestMethodValidator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TestMethodValidator.cs index 7d8e5160ac..4c0c91f19b 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TestMethodValidator.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TestMethodValidator.cs @@ -2,7 +2,7 @@ // 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.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery; @@ -13,18 +13,18 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery; [SuppressMessage("Performance", "CA1852: Seal internal types", Justification = "Overrides required for testability")] internal class TestMethodValidator { - private readonly ReflectHelper _reflectHelper; + private readonly IReflectionOperations _reflectionOperation; private readonly bool _discoverInternals; /// /// Initializes a new instance of the class. /// - /// An instance to reflection helper for type information. + /// An instance to reflection helper for type information. /// True to discover methods which are declared internal in addition to methods /// which are declared public. - internal TestMethodValidator(ReflectHelper reflectHelper, bool discoverInternals) + internal TestMethodValidator(IReflectionOperations reflectionOperation, bool discoverInternals) { - _reflectHelper = reflectHelper; + _reflectionOperation = reflectionOperation; _discoverInternals = discoverInternals; } @@ -44,7 +44,7 @@ internal virtual bool IsValidTestMethod(MethodInfo testMethodInfo, Type type, IC // but the difference is quite small, and we don't expect a huge amount of non-test methods in the assembly. // // Also skip all methods coming from object, because they cannot be tests. - if (testMethodInfo.DeclaringType == typeof(object) || !_reflectHelper.IsAttributeDefined(testMethodInfo)) + if (testMethodInfo.DeclaringType == typeof(object) || !_reflectionOperation.IsAttributeDefined(testMethodInfo)) { return false; } @@ -55,7 +55,7 @@ internal virtual bool IsValidTestMethod(MethodInfo testMethodInfo, Type type, IC // Todo: Decide whether parameter count matters. bool isValidTestMethod = isAccessible && testMethodInfo is { IsAbstract: false, IsStatic: false } && - testMethodInfo.IsValidReturnType(_reflectHelper); + testMethodInfo.IsValidReturnType(_reflectionOperation); if (!isValidTestMethod) { diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs index 77d9c4afab..741eecc70a 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs @@ -1,9 +1,9 @@ // 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.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery; @@ -18,21 +18,21 @@ internal class TypeEnumerator private readonly string _assemblyFilePath; private readonly TypeValidator _typeValidator; private readonly TestMethodValidator _testMethodValidator; - private readonly ReflectHelper _reflectHelper; + private readonly IReflectionOperations _reflectionOperation; /// /// Initializes a new instance of the class. /// /// The reflected type. /// The name of the assembly being reflected. - /// An instance to reflection helper for type information. + /// An instance to reflection helper for type information. /// The validator for test classes. /// The validator for test methods. - internal TypeEnumerator(Type type, string assemblyFilePath, ReflectHelper reflectHelper, TypeValidator typeValidator, TestMethodValidator testMethodValidator) + internal TypeEnumerator(Type type, string assemblyFilePath, IReflectionOperations reflectionOperation, TypeValidator typeValidator, TestMethodValidator testMethodValidator) { _type = type; _assemblyFilePath = assemblyFilePath; - _reflectHelper = reflectHelper; + _reflectionOperation = reflectionOperation; _typeValidator = typeValidator; _testMethodValidator = testMethodValidator; } @@ -69,7 +69,7 @@ internal List GetTests(List warnings) // if we rely on analyzers to identify all invalid methods on build, we can change this to fit the current settings. foreach (MethodInfo method in PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethods(_type)) { - bool isMethodDeclaredInTestTypeAssembly = _reflectHelper.IsMethodDeclaredInSameAssemblyAsType(method, _type); + bool isMethodDeclaredInTestTypeAssembly = _reflectionOperation.IsMethodDeclaredInSameAssemblyAsType(method, _type); bool enableMethodsFromOtherAssemblies = MSTestSettings.CurrentSettings.EnableBaseClassTestMethodsFromOtherAssemblies; if (!isMethodDeclaredInTestTypeAssembly && !enableMethodsFromOtherAssemblies) @@ -136,19 +136,19 @@ internal UnitTestElement GetTestFromMethod(MethodInfo method, ICollection= 0; i--) { diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeValidator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeValidator.cs index b4b346e649..bc694216bc 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeValidator.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeValidator.cs @@ -1,7 +1,7 @@ // 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.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery; @@ -15,27 +15,27 @@ internal class TypeValidator // Setting this to a string representation instead of a typeof(TestContext).FullName // since the later would require a load of the Test Framework extension assembly at this point. private const string TestContextFullName = "Microsoft.VisualStudio.TestTools.UnitTesting.TestContext"; - private readonly ReflectHelper _reflectHelper; + private readonly IReflectionOperations _reflectionOperation; private readonly bool _discoverInternals; /// /// Initializes a new instance of the class. /// - /// An instance to reflection helper for type information. - internal TypeValidator(ReflectHelper reflectHelper) - : this(reflectHelper, false) + /// An instance to reflection helper for type information. + internal TypeValidator(IReflectionOperations reflectionOperation) + : this(reflectionOperation, false) { } /// /// Initializes a new instance of the class. /// - /// An instance to reflection helper for type information. + /// An instance to reflection helper for type information. /// True to discover test classes which are declared internal in /// addition to test classes which are declared public. - internal TypeValidator(ReflectHelper reflectHelper, bool discoverInternals) + internal TypeValidator(IReflectionOperations reflectionOperation, bool discoverInternals) { - _reflectHelper = reflectHelper; + _reflectionOperation = reflectionOperation; _discoverInternals = discoverInternals; } @@ -52,7 +52,7 @@ internal virtual bool IsValidTestClass(Type type, List warnings) // gives us a better performance. // It would be possible to use non-caching reflection here if we knew that we are only doing discovery that won't be followed by run, // but the difference is quite small, and we don't expect a huge amount of non-test classes in the assembly. - if (!type.IsClass || !_reflectHelper.IsAttributeDefined(type)) + if (!type.IsClass || !_reflectionOperation.IsAttributeDefined(type)) { return false; } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs index d2430c222f..494f06bd2e 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; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; @@ -34,7 +34,8 @@ internal TestAssemblySettings GetSettings(string source) // Load the source. Assembly testAssembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly(source); - ParallelizeAttribute? parallelizeAttribute = ReflectHelper.GetParallelizeAttribute(testAssembly); + var reflectionOperations = (ReflectionOperations)PlatformServiceProvider.Instance.ReflectionOperations; + ParallelizeAttribute? parallelizeAttribute = reflectionOperations.GetParallelizeAttribute(testAssembly); if (parallelizeAttribute != null) { @@ -47,7 +48,7 @@ internal TestAssemblySettings GetSettings(string source) } } - testAssemblySettings.CanParallelizeAssembly = !ReflectHelper.IsDoNotParallelizeSet(testAssembly); + testAssemblySettings.CanParallelizeAssembly = !reflectionOperations.IsDoNotParallelizeSet(testAssembly); return testAssemblySettings; } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs index 576d947dd1..49756ce5cf 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs @@ -6,7 +6,6 @@ #endif 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; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Execution; @@ -119,7 +118,7 @@ internal TestMethodInfo( /// /// An array of the attributes. public Attribute[]? GetAllAttributes() - => [.. ReflectHelper.Instance.GetAttributes(MethodInfo)]; + => [.. PlatformServiceProvider.Instance.ReflectionOperations.GetAttributes(MethodInfo)]; /// /// Gets all attributes of the test method. @@ -128,7 +127,7 @@ internal TestMethodInfo( /// 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. @@ -263,7 +262,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(); @@ -286,7 +285,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 @@ -302,7 +301,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 0d6cb2e9fc..2f2a15d0f1 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs @@ -3,7 +3,6 @@ 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; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions; @@ -200,7 +199,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/Execution/TypeCache.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs index 3909001054..adc50484ee 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs @@ -7,6 +7,7 @@ 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; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -26,7 +27,7 @@ internal sealed class TypeCache : MarshalByRefObject /// /// Helper for reflection API's. /// - private readonly ReflectHelper _reflectionHelper; + private readonly IReflectionOperations _reflectionHelper; /// /// Assembly info cache. @@ -44,15 +45,15 @@ internal sealed class TypeCache : MarshalByRefObject /// Initializes a new instance of the class. /// internal TypeCache() - : this(ReflectHelper.Instance) + : this(PlatformServiceProvider.Instance.ReflectionOperations) { } /// /// Initializes a new instance of the class. /// - /// An instance to the object. - internal TypeCache(ReflectHelper reflectionHelper) => _reflectionHelper = reflectionHelper; + /// An instance to the object. + internal TypeCache(IReflectionOperations reflectionHelper) => _reflectionHelper = reflectionHelper; /// /// Gets Class Info cache which has cleanup methods to execute. @@ -256,7 +257,7 @@ private TestClassInfo CreateClassInfo(Type classType) TestAssemblyInfo assemblyInfo = GetAssemblyInfo(classType.Assembly); - TestClassAttribute? testClassAttribute = ReflectHelper.Instance.GetSingleAttributeOrDefault(classType); + TestClassAttribute? testClassAttribute = PlatformServiceProvider.Instance.ReflectionOperations.GetSingleAttributeOrDefault(classType); DebugEx.Assert(testClassAttribute is not null, "testClassAttribute is null"); var classInfo = new TestClassInfo(classType, constructor, isParameterLessConstructor, testClassAttribute, assemblyInfo); @@ -389,7 +390,7 @@ private TestAssemblyInfo GetAssemblyInfo(Assembly assembly) // We want to avoid loading types early as much as we can. bool isValid = methodInfo is { IsSpecialName: false, IsPublic: true, IsStatic: true, IsGenericMethod: false, DeclaringType.IsGenericType: false, DeclaringType.IsPublic: true } && methodInfo.GetParameters() is { } parameters && parameters.Length == 1 && parameters[0].ParameterType == typeof(TestContext) && - methodInfo.IsValidReturnType(); + methodInfo.IsValidReturnType(@this._reflectionHelper); if (isValid && isGlobalTestInitialize) { @@ -426,7 +427,7 @@ private bool IsAssemblyOrClassInitializeMethod(MethodInfo return false; } - if (!methodInfo.HasCorrectClassOrAssemblyInitializeSignature()) + if (!methodInfo.HasCorrectClassOrAssemblyInitializeSignature(_reflectionHelper)) { string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ClassOrAssemblyInitializeMethodHasWrongSignature, methodInfo.DeclaringType!.FullName, methodInfo.Name); throw new TypeInspectionException(message); @@ -454,7 +455,7 @@ private bool IsAssemblyOrClassCleanupMethod(MethodInfo method return false; } - if (!methodInfo.HasCorrectClassOrAssemblyCleanupSignature()) + if (!methodInfo.HasCorrectClassOrAssemblyCleanupSignature(_reflectionHelper)) { string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ClassOrAssemblyCleanupMethodHasWrongSignature, methodInfo.DeclaringType!.FullName, methodInfo.Name); throw new TypeInspectionException(message); @@ -571,7 +572,7 @@ private void UpdateInfoIfTestInitializeOrCleanupMethod( if (!hasTestCleanup && !hasTestInitialize) { - if (instanceMethods is not null && methodInfo.HasCorrectTestInitializeOrCleanupSignature()) + if (instanceMethods is not null && methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionHelper)) { instanceMethods.Add(methodInfo.Name); } @@ -579,7 +580,7 @@ private void UpdateInfoIfTestInitializeOrCleanupMethod( return; } - if (!methodInfo.HasCorrectTestInitializeOrCleanupSignature()) + if (!methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionHelper)) { string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_TestInitializeAndCleanupMethodHasWrongSignature, methodInfo.DeclaringType!.FullName, methodInfo.Name); throw new TypeInspectionException(message); @@ -715,7 +716,7 @@ private MethodInfo GetMethodInfoForTestMethod(TestMethod testMethod, TestClassIn } return testMethodInfo is null - || !testMethodInfo.HasCorrectTestMethodSignature(true, discoverInternals) + || !testMethodInfo.HasCorrectTestMethodSignature(true, PlatformServiceProvider.Instance.ReflectionOperations, discoverInternals) ? null : testMethodInfo; } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs index 935332d497..cb9cd46cad 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs @@ -32,7 +32,7 @@ internal sealed class UnitTestRunner : MarshalByRefObject /// Specifies adapter settings that need to be instantiated in the domain running these tests. /// The tests to run. public UnitTestRunner(MSTestSettings settings, UnitTestElement[] testsToRun) - : this(settings, testsToRun, ReflectHelper.Instance) + : this(settings, testsToRun, PlatformServiceProvider.Instance.ReflectionOperations) { } @@ -41,8 +41,8 @@ public UnitTestRunner(MSTestSettings settings, UnitTestElement[] testsToRun) /// /// Specifies adapter settings. /// The tests to run. - /// The reflect Helper. - internal UnitTestRunner(MSTestSettings settings, UnitTestElement[] testsToRun, ReflectHelper reflectHelper) + /// The reflect Helper. + internal UnitTestRunner(MSTestSettings settings, UnitTestElement[] testsToRun, IReflectionOperations reflectionOperation) { // Populate the settings into the domain(Desktop workflow) performing discovery. // This would just be resetting the settings to itself in non desktop workflows. @@ -57,7 +57,7 @@ internal UnitTestRunner(MSTestSettings settings, UnitTestElement[] testsToRun, R } PlatformServiceProvider.Instance.TestRunCancellationToken ??= new TestRunCancellationToken(); - _typeCache = new TypeCache(reflectHelper); + _typeCache = new TypeCache(reflectionOperation); _classCleanupManager = new ClassCleanupManager(testsToRun); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs index a93726f59f..6e2dcc91f6 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs @@ -1,9 +1,10 @@ // 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.MSTest.TestAdapter.Helpers; 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.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions; @@ -14,8 +15,9 @@ internal static class MethodInfoExtensions /// Verifies that the class initialize has the correct signature. /// /// The method to verify. + /// The reflection service to use. /// True if the method has the right Assembly/Class initialize signature. - internal static bool HasCorrectClassOrAssemblyInitializeSignature(this MethodInfo method) + internal static bool HasCorrectClassOrAssemblyInitializeSignature(this MethodInfo method, IReflectionOperations reflectionOperation) { DebugEx.Assert(method != null, "method should not be null."); @@ -25,22 +27,23 @@ internal static bool HasCorrectClassOrAssemblyInitializeSignature(this MethodInf method is { IsStatic: true, IsPublic: true } && (parameters.Length == 1) && parameters[0].ParameterType == typeof(TestContext) && - method.IsValidReturnType(); + method.IsValidReturnType(reflectionOperation); } /// /// Verifies that the class cleanup has the correct signature. /// /// The method to verify. + /// The reflection service to use. /// True if the method has the right Assembly/Class cleanup signature. - internal static bool HasCorrectClassOrAssemblyCleanupSignature(this MethodInfo method) + internal static bool HasCorrectClassOrAssemblyCleanupSignature(this MethodInfo method, IReflectionOperations reflectionOperation) { DebugEx.Assert(method != null, "method should not be null."); return method is { IsStatic: true, IsPublic: true } && HasCorrectClassOrAssemblyCleanupParameters(method) && - method.IsValidReturnType(); + method.IsValidReturnType(reflectionOperation); } private static bool HasCorrectClassOrAssemblyCleanupParameters(MethodInfo method) @@ -53,15 +56,16 @@ private static bool HasCorrectClassOrAssemblyCleanupParameters(MethodInfo method /// Verifies that the test Initialize/cleanup has the correct signature. /// /// The method to verify. + /// The reflection service to use. /// True if the method has the right test init/cleanup signature. - internal static bool HasCorrectTestInitializeOrCleanupSignature(this MethodInfo method) + internal static bool HasCorrectTestInitializeOrCleanupSignature(this MethodInfo method, IReflectionOperations reflectionOperation) { DebugEx.Assert(method != null, "method should not be null."); return method is { IsStatic: false, IsPublic: true } && (method.GetParameters().Length == 0) && - method.IsValidReturnType(); + method.IsValidReturnType(reflectionOperation); } /// @@ -69,10 +73,11 @@ internal static bool HasCorrectTestInitializeOrCleanupSignature(this MethodInfo /// /// The method to verify. /// Indicates whether parameter length is to be ignored. + /// The reflection service to use. /// True if internal test classes and test methods should be discovered in /// addition to public test classes and methods. /// True if the method has the right test method signature. - internal static bool HasCorrectTestMethodSignature(this MethodInfo method, bool ignoreParameterLength, bool discoverInternals = false) + internal static bool HasCorrectTestMethodSignature(this MethodInfo method, bool ignoreParameterLength, IReflectionOperations reflectionOperation, bool discoverInternals = false) { DebugEx.Assert(method != null, "method should not be null."); @@ -80,18 +85,18 @@ internal static bool HasCorrectTestMethodSignature(this MethodInfo method, bool method is { IsAbstract: false, IsStatic: false } && (method.IsPublic || (discoverInternals && method.IsAssembly)) && (method.GetParameters().Length == 0 || ignoreParameterLength) && - method.IsValidReturnType(); // Match return type Task for async methods only. Else return type void. + method.IsValidReturnType(reflectionOperation); // Match return type Task for async methods only. Else return type void. } /// /// Check is return type is void for non async and Task for async methods. /// /// The method to verify. - /// The reflection service to use. + /// The reflection service to use. /// True if the method has a void/task return type.. - internal static bool IsValidReturnType(this MethodInfo method, ReflectHelper? reflectHelper = null) - => ReflectHelper.MatchReturnType(method, typeof(Task)) - || (ReflectHelper.MatchReturnType(method, typeof(void)) && method.GetAsyncTypeName(reflectHelper) == null) + internal static bool IsValidReturnType(this MethodInfo method, IReflectionOperations reflectionOperation) + => ReflectionOperations.MatchReturnType(method, typeof(Task)) + || (ReflectionOperations.MatchReturnType(method, typeof(void)) && method.GetAsyncTypeName(reflectionOperation) == null) // Keep this the last check, as it avoids loading System.Threading.Tasks.Extensions unnecessarily. || method.IsValueTask(); @@ -101,18 +106,18 @@ internal static bool IsValidReturnType(this MethodInfo method, ReflectHelper? re // Even when invokeResult is null or Task. [MethodImpl(MethodImplOptions.NoInlining)] private static bool IsValueTask(this MethodInfo method) - => ReflectHelper.MatchReturnType(method, typeof(ValueTask)); + => ReflectionOperations.MatchReturnType(method, typeof(ValueTask)); /// /// For async methods compiler generates different type and method. /// Gets the compiler generated type name for given async test method. /// /// The method to verify. - /// The reflection service to use. + /// The reflection service to use. /// Compiler generated type name for given async test method.. - internal static string? GetAsyncTypeName(this MethodInfo method, ReflectHelper? reflectHelper = null) + internal static string? GetAsyncTypeName(this MethodInfo method, IReflectionOperations reflectionOperation) { - AsyncStateMachineAttribute? asyncStateMachineAttribute = (reflectHelper ?? ReflectHelper.Instance).GetFirstAttributeOrDefault(method); + AsyncStateMachineAttribute? asyncStateMachineAttribute = reflectionOperation.GetFirstAttributeOrDefault(method); return asyncStateMachineAttribute?.StateMachineType?.FullName; } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs index a2bbcbe98e..7e8aebfd76 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs @@ -1,6 +1,7 @@ // 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; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; @@ -9,7 +10,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/ReflectHelper.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectHelper.cs deleted file mode 100644 index b1a43a1e41..0000000000 --- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectHelper.cs +++ /dev/null @@ -1,395 +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 System.Security; - -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; - -[SuppressMessage("Performance", "CA1852: Seal internal types", Justification = "Overrides required for mocking")] -internal class ReflectHelper : MarshalByRefObject -{ -#pragma warning disable RS0030 // Do not use banned APIs - private static readonly Lazy InstanceValue = new(() => new()); -#pragma warning restore RS0030 // Do not use banned APIs - - // 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 = []; - - public static ReflectHelper Instance => InstanceValue.Value; - - /// - /// 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 virtual /* for testing */ bool IsAttributeDefined(MemberInfo memberInfo) - where TAttribute : Attribute - { - Ensure.NotNull(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(ReflectHelper)}.{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 virtual /* for tests, for moq */ 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 virtual /* for tests, for moq */ 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; - } - - /// - /// Match return type of method. - /// - /// The method to inspect. - /// The return type to match. - /// True if there is a match. - internal static bool MatchReturnType(MethodInfo method, Type returnType) - { - Ensure.NotNull(method); - Ensure.NotNull(returnType); - return method.ReturnType.Equals(returnType); - } - - /// - /// Returns true when the method is declared in the assembly where the type is declared. - /// - /// The method to check for. - /// The type declared in the assembly to check. - /// True if the method is declared in the assembly where the type is declared. - internal virtual bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Type type) - => method.DeclaringType!.Assembly.Equals(type.Assembly); // TODO: Investigate if we rely on NRE - - /// - /// Get categories applied to the test method. - /// - /// The member to inspect. - /// The reflected type that owns . - /// Categories defined. - internal virtual /* for tests, we are mocking this */ string[] GetTestCategories(MemberInfo categoryAttributeProvider, Type owningType) - { - IEnumerable methodCategories = GetAttributes(categoryAttributeProvider); - IEnumerable typeCategories = GetAttributes(owningType); - IEnumerable assemblyCategories = GetAttributes(owningType.Assembly); - - return [.. methodCategories.Concat(typeCategories).Concat(assemblyCategories).SelectMany(c => c.TestCategories)]; - } - - /// - /// Gets the parallelization level set on an assembly. - /// - /// The test assembly. - /// The parallelization level if set. -1 otherwise. - internal static ParallelizeAttribute? GetParallelizeAttribute(Assembly assembly) - => PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes(assembly, typeof(ParallelizeAttribute)) - .OfType() - .FirstOrDefault(); - - /// - /// Gets discover internals assembly level attribute. - /// - /// The test assembly. - internal static DiscoverInternalsAttribute? GetDiscoverInternalsAttribute(Assembly assembly) - => PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes(assembly, typeof(DiscoverInternalsAttribute)) - .OfType() - .FirstOrDefault(); - - /// - /// Gets TestDataSourceDiscovery assembly level attribute. - /// - /// The test assembly. - internal static TestDataSourceDiscoveryOption? GetTestDataSourceDiscoveryOption(Assembly assembly) - => PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes(assembly, typeof(TestDataSourceDiscoveryAttribute)) - .OfType() - .FirstOrDefault()?.DiscoveryOption; - - /// - /// Gets TestDataSourceOptions assembly level attribute. - /// - /// The test assembly. - /// The TestDataSourceOptionsAttribute if set. Null otherwise. - internal static TestDataSourceOptionsAttribute? GetTestDataSourceOptions(Assembly assembly) - => PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes(assembly, typeof(TestDataSourceOptionsAttribute)) - .OfType() - .FirstOrDefault(); - - /// - /// Get the parallelization behavior for a test method. - /// - /// Test method. - /// The type that owns . - /// True if test method should not run in parallel. - internal bool IsDoNotParallelizeSet(MemberInfo testMethod, Type owningType) - => IsAttributeDefined(testMethod) - || IsAttributeDefined(owningType); - - /// - /// Get the parallelization behavior for a test assembly. - /// - /// The test assembly. - /// True if test assembly should not run in parallel. - internal static bool IsDoNotParallelizeSet(Assembly assembly) - => PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes(assembly, typeof(DoNotParallelizeAttribute)) - .Length != 0; - - /// - /// Priority if any set for test method. Will return priority if attribute is applied to TestMethod - /// else null. - /// - /// The member to inspect. - /// Priority value if defined. Null otherwise. - internal virtual int? GetPriority(MemberInfo priorityAttributeProvider) => - GetFirstAttributeOrDefault(priorityAttributeProvider)?.Priority; - - /// - /// KeyValue pairs that are provided by TestPropertyAttributes of the given test method. - /// - /// The member to inspect. - /// List of traits. - internal Trait[] GetTestPropertiesAsTraits(MethodInfo testPropertyProvider) - { - Attribute[] attributesFromMethod = GetCustomAttributesCached(testPropertyProvider); - Attribute[] attributesFromClass = testPropertyProvider.ReflectedType is { } testClass ? 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; - } - - /// - /// 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. - internal virtual /* for tests, for moq */ 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, "ReflectHelper.DefinesAttributeDerivedFrom: internal error: wrong value in the attributes dictionary."); - - if (attribute is TAttributeType attributeAsAttributeType) - { - yield return attributeAsAttributeType; - } - } - } - - /// - /// Get attribute defined on a method which is of given type of subtype of given type. - /// - /// The attribute type. - /// The type of state to be passed to Action. - /// The member to inspect. - /// The action to perform. - /// The state to pass to action. - internal void PerformActionOnAttribute(ICustomAttributeProvider attributeProvider, Action action, TState? state) - where TAttributeType : Attribute - { - Attribute[] attributes = GetCustomAttributesCached(attributeProvider); - foreach (Attribute attribute in attributes) - { - DebugEx.Assert(attribute != null, "ReflectHelper.DefinesAttributeDerivedFrom: internal error: wrong value in the attributes dictionary."); - - if (attribute is TAttributeType attributeAsAttributeType) - { - action(attributeAsAttributeType, state); - } - } - } - - /// - /// Gets and caches the attributes for the given type, or method. - /// - /// The member to inspect. - /// attributes defined. - internal 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) - { - object[] attributesArray = attributeProvider is MemberInfo memberInfo - ? PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes(memberInfo) - : PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes((Assembly)attributeProvider, typeof(Attribute)); - - return attributesArray; // TODO: Investigate if we rely on NRE - } - } - - 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/Interfaces/IReflectionOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs index b154b531cc..159e9f15f4 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs @@ -1,6 +1,8 @@ // 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.ObjectModel; + namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; /// @@ -43,4 +45,91 @@ 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); + + /// + /// Returns true when the method is declared in the assembly where the type is declared. + /// + /// The method to check for. + /// The type declared in the assembly to check. + /// True if the method is declared in the assembly where the type is declared. + bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Type type); + + /// + /// Get categories applied to the test method. + /// + /// The member to inspect. + /// The reflected type that owns . + /// Categories defined. + string[] GetTestCategories(MemberInfo categoryAttributeProvider, Type owningType); + + /// + /// Get the parallelization behavior for a test method. + /// + /// Test method. + /// The type that owns . + /// True if test method should not run in parallel. + bool IsDoNotParallelizeSet(MemberInfo testMethod, Type owningType); + + /// + /// Priority if any set for test method. Will return priority if attribute is applied to TestMethod + /// else null. + /// + /// The member to inspect. + /// Priority value if defined. Null otherwise. + int? GetPriority(MemberInfo priorityAttributeProvider); + + /// + /// KeyValue pairs that are provided by TestPropertyAttributes of the given test method. + /// + /// The member to inspect. + /// List of traits. + Trait[] GetTestPropertiesAsTraits(MethodInfo testPropertyProvider); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs index 494f845b5b..b285c17fd6 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs @@ -1,18 +1,28 @@ // 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; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +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,8 +33,7 @@ internal sealed class ReflectionOperations : IReflectionOperations { 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()}'."); @@ -81,4 +90,376 @@ 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 + { + Ensure.NotNull(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; + } + + /// + /// Match return type of method. + /// + /// The method to inspect. + /// The return type to match. + /// True if there is a match. + internal static bool MatchReturnType(MethodInfo method, Type returnType) + { + Ensure.NotNull(method); + Ensure.NotNull(returnType); + return method.ReturnType.Equals(returnType); + } + + /// + /// Gets the parallelization level set on an assembly. + /// + /// The test assembly. + /// The parallelization level if set. -1 otherwise. + internal ParallelizeAttribute? GetParallelizeAttribute(Assembly assembly) + => GetCustomAttributes(assembly, typeof(ParallelizeAttribute)) + .OfType() + .FirstOrDefault(); + + /// + /// Gets discover internals assembly level attribute. + /// + /// The test assembly. + internal DiscoverInternalsAttribute? GetDiscoverInternalsAttribute(Assembly assembly) + => GetCustomAttributes(assembly, typeof(DiscoverInternalsAttribute)) + .OfType() + .FirstOrDefault(); + + /// + /// Gets TestDataSourceDiscovery assembly level attribute. + /// + /// The test assembly. + internal TestDataSourceDiscoveryOption? GetTestDataSourceDiscoveryOption(Assembly assembly) + => GetCustomAttributes(assembly, typeof(TestDataSourceDiscoveryAttribute)) + .OfType() + .FirstOrDefault()?.DiscoveryOption; + + /// + /// Gets TestDataSourceOptions assembly level attribute. + /// + /// The test assembly. + /// The TestDataSourceOptionsAttribute if set. Null otherwise. + internal TestDataSourceOptionsAttribute? GetTestDataSourceOptions(Assembly assembly) + => GetCustomAttributes(assembly, typeof(TestDataSourceOptionsAttribute)) + .OfType() + .FirstOrDefault(); + + /// + /// Get the parallelization behavior for a test assembly. + /// + /// The test assembly. + /// True if test assembly should not run in parallel. + internal bool IsDoNotParallelizeSet(Assembly assembly) + => GetCustomAttributes(assembly, typeof(DoNotParallelizeAttribute)) + .Length != 0; + + /// + /// Returns true when the method is declared in the assembly where the type is declared. + /// + /// The method to check for. + /// The type declared in the assembly to check. + /// True if the method is declared in the assembly where the type is declared. + public bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Type type) + => method.DeclaringType!.Assembly.Equals(type.Assembly); // TODO: Investigate if we rely on NRE + + /// + /// Get categories applied to the test method. + /// + /// The member to inspect. + /// The reflected type that owns . + /// Categories defined. + public string[] GetTestCategories(MemberInfo categoryAttributeProvider, Type owningType) + { + IEnumerable methodCategories = GetAttributes(categoryAttributeProvider); + IEnumerable typeCategories = GetAttributes(owningType); + IEnumerable assemblyCategories = GetAttributes(owningType.Assembly); + + return [.. methodCategories.Concat(typeCategories).Concat(assemblyCategories).SelectMany(c => c.TestCategories)]; + } + + /// + /// Get the parallelization behavior for a test method. + /// + /// Test method. + /// The type that owns . + /// True if test method should not run in parallel. + public bool IsDoNotParallelizeSet(MemberInfo testMethod, Type owningType) + => IsAttributeDefined(testMethod) + || IsAttributeDefined(owningType); + + /// + /// Priority if any set for test method. Will return priority if attribute is applied to TestMethod + /// else null. + /// + /// The member to inspect. + /// Priority value if defined. Null otherwise. + public int? GetPriority(MemberInfo priorityAttributeProvider) => + GetFirstAttributeOrDefault(priorityAttributeProvider)?.Priority; + + /// + /// KeyValue pairs that are provided by TestPropertyAttributes of the given test method. + /// + /// The member to inspect. + /// List of traits. + public Trait[] GetTestPropertiesAsTraits(MethodInfo testPropertyProvider) + { + Attribute[] attributesFromMethod = GetCustomAttributesCached(testPropertyProvider); + Attribute[] attributesFromClass = testPropertyProvider.ReflectedType is { } testClass ? 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; + } + + /// + /// 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; + } + } + } + + /// + /// Get attribute defined on a method which is of given type of subtype of given type. + /// + /// The attribute type. + /// The type of state to be passed to Action. + /// The member to inspect. + /// The action to perform. + /// The state to pass to action. + internal void PerformActionOnAttribute(ICustomAttributeProvider attributeProvider, Action action, TState? state) + where TAttributeType : Attribute + { + Attribute[] attributes = GetCustomAttributesCached(attributeProvider); + foreach (Attribute attribute in attributes) + { + DebugEx.Assert(attribute != null, "ReflectionOperations.DefinesAttributeDerivedFrom: internal error: wrong value in the attributes dictionary."); + + if (attribute is TAttributeType attributeAsAttributeType) + { + action(attributeAsAttributeType, state); + } + } + } + + /// + /// 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 + } + } + + 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 432db7470e..3dfac24c2d 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 2f407266ac..68f908f5a7 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/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs index 8439fb293b..1c4953e6ae 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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.Collections.ObjectModel; @@ -7,7 +7,7 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; @@ -322,13 +322,13 @@ 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 reflectionOperations = new Mock(); + var typeValidator = new Mock(reflectionOperations.Object); + var testMethodValidator = new Mock(reflectionOperations.Object, false); MockTypeEnumerator = new Mock( typeof(DummyTestClass), "DummyAssembly", - reflectHelper.Object, + reflectionOperations.Object, typeValidator.Object, testMethodValidator.Object); } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TestMethodValidatorTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TestMethodValidatorTests.cs index defee3b8ab..063282beff 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TestMethodValidatorTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TestMethodValidatorTests.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 Moq; @@ -15,7 +16,7 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Discovery; public class TestMethodValidatorTests : TestContainer { private readonly TestMethodValidator _testMethodValidator; - private readonly Mock _mockReflectHelper; + private readonly Mock _mockReflectionOperations; private readonly List _warnings; private readonly Mock _mockMethodInfo; @@ -23,8 +24,8 @@ public class TestMethodValidatorTests : TestContainer public TestMethodValidatorTests() { - _mockReflectHelper = new Mock(); - _testMethodValidator = new TestMethodValidator(_mockReflectHelper.Object, discoverInternals: false); + _mockReflectionOperations = new Mock(); + _testMethodValidator = new TestMethodValidator(_mockReflectionOperations.Object, discoverInternals: false); _warnings = []; _mockMethodInfo = new Mock(); @@ -33,7 +34,7 @@ public TestMethodValidatorTests() public void IsValidTestMethodShouldReturnFalseForMethodsWithoutATestMethodAttributeOrItsDerivedAttributes() { - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(It.IsAny())).Returns(false); _testMethodValidator.IsValidTestMethod(_mockMethodInfo.Object, _type, _warnings).Should().BeFalse(); } @@ -56,7 +57,7 @@ public void IsValidTestMethodShouldNotReportWarningsForGenericTestMethodDefiniti { SetupTestMethod(); - _mockReflectHelper + _mockReflectionOperations .Setup(x => x.GetFirstAttributeOrDefault(_mockMethodInfo.Object)) .Returns(default(AsyncStateMachineAttribute?)); @@ -114,8 +115,8 @@ public void IsValidTestMethodShouldReturnFalseForAsyncMethodsWithNonTaskReturnTy MethodInfo methodInfo = typeof(DummyTestClass).GetMethod( "AsyncMethodWithVoidReturnType", BindingFlags.Instance | BindingFlags.Public)!; - _mockReflectHelper.Setup(_mockReflectHelper => _mockReflectHelper.GetFirstAttributeOrDefault(methodInfo)) - .CallBase(); + _mockReflectionOperations.Setup(_mockReflectionOperations => _mockReflectionOperations.GetFirstAttributeOrDefault(methodInfo)) + .Returns((ICustomAttributeProvider p) => ((MemberInfo)p).GetCustomAttribute(inherit: true)); _testMethodValidator.IsValidTestMethod(methodInfo, typeof(DummyTestClass), _warnings).Should().BeFalse(); } @@ -169,7 +170,7 @@ public void WhenDiscoveryOfInternalsIsEnabledIsValidTestMethodShouldReturnTrueFo "InternalTestMethod", BindingFlags.Instance | BindingFlags.NonPublic)!; - var testMethodValidator = new TestMethodValidator(_mockReflectHelper.Object, true); + var testMethodValidator = new TestMethodValidator(_mockReflectionOperations.Object, true); testMethodValidator.IsValidTestMethod(methodInfo, typeof(DummyTestClass), _warnings).Should().BeTrue(); } @@ -181,7 +182,7 @@ public void WhenDiscoveryOfInternalsIsEnabledIsValidTestMethodShouldReturnFalseF "PrivateTestMethod", BindingFlags.Instance | BindingFlags.NonPublic)!; - var testMethodValidator = new TestMethodValidator(_mockReflectHelper.Object, true); + var testMethodValidator = new TestMethodValidator(_mockReflectionOperations.Object, true); testMethodValidator.IsValidTestMethod(methodInfo, typeof(DummyTestClass), _warnings).Should().BeFalse(); } @@ -189,7 +190,7 @@ public void WhenDiscoveryOfInternalsIsEnabledIsValidTestMethodShouldReturnFalseF #endregion private void SetupTestMethod() - => _mockReflectHelper.Setup( + => _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(It.IsAny())).Returns(true); } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs index 5c520962db..4b24f84e55 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs @@ -5,7 +5,8 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; 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.UnitTests.TestableImplementations; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; @@ -18,7 +19,7 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Discovery; public partial class TypeEnumeratorTests : TestContainer { - private readonly Mock _mockReflectHelper; + private readonly Mock _mockReflectionOperations; private readonly Mock _mockTestMethodValidator; private readonly Mock _mockTypeValidator; private readonly TestablePlatformServiceProvider _testablePlatformServiceProvider; @@ -28,13 +29,13 @@ public partial class TypeEnumeratorTests : TestContainer public TypeEnumeratorTests() { - _mockReflectHelper = new Mock - { - CallBase = true, - }; + _mockReflectionOperations = new Mock(); + _mockReflectionOperations.Setup(r => r.GetTestCategories(It.IsAny(), It.IsAny())).Returns(Array.Empty()); + _mockReflectionOperations.Setup(r => r.GetTestPropertiesAsTraits(It.IsAny())).Returns(Array.Empty()); + _mockReflectionOperations.Setup(r => r.GetCustomAttributesCached(It.IsAny())).Returns(Array.Empty()); - _mockTypeValidator = new Mock(MockBehavior.Default, _mockReflectHelper.Object); - _mockTestMethodValidator = new Mock(MockBehavior.Default, _mockReflectHelper.Object, false); + _mockTypeValidator = new Mock(MockBehavior.Default, _mockReflectionOperations.Object); + _mockTestMethodValidator = new Mock(MockBehavior.Default, _mockReflectionOperations.Object, false); _warnings = []; _mockMessageLogger = new Mock(); @@ -492,7 +493,7 @@ private void SetupTestClassAndTestMethods(bool isValidTestClass, bool isValidTes .Returns(isValidTestClass); _mockTestMethodValidator.Setup( tmv => tmv.IsValidTestMethod(It.IsAny(), It.IsAny(), It.IsAny>())).Returns(isValidTestMethod); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsMethodDeclaredInSameAssemblyAsType(It.IsAny(), It.IsAny())).Returns(isMethodFromSameAssembly); } @@ -500,7 +501,7 @@ private TypeEnumerator GetTypeEnumeratorInstance(Type type, string assemblyName) => new( type, assemblyName, - _mockReflectHelper.Object, + _mockReflectionOperations.Object, _mockTypeValidator.Object, _mockTestMethodValidator.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..a3ca44b5ed 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/ClassCleanupManagerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/ClassCleanupManagerTests.cs @@ -1,10 +1,10 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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.Execution; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Moq; @@ -17,7 +17,7 @@ public class ClassCleanupManagerTests : TestContainer { public void AssemblyCleanupRunsAfterAllTestsFinishEvenIfWeScheduleTheSameTestMultipleTime() { - ReflectHelper reflectHelper = Mock.Of(); + ReflectionOperations reflectionOperations = 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 8c601187e1..155cfa9282 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs @@ -5,7 +5,7 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; @@ -58,8 +58,6 @@ public TestMethodRunnerTests() _testablePlatformServiceProvider = new TestablePlatformServiceProvider(); _testablePlatformServiceProvider.SetupMockReflectionOperations(); PlatformServiceProvider.Instance = _testablePlatformServiceProvider; - - ReflectHelper.Instance.ClearCache(); } private static TestClassInfo GetTestClassInfo() diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs index a3540c0937..da7fa4727b 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs @@ -5,7 +5,7 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; @@ -19,15 +19,17 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Execution; public class TestPropertyAttributeTests : TestContainer { private readonly TypeCache _typeCache; + private readonly ReflectionOperations _reflectionOperations; public TestPropertyAttributeTests() { - _typeCache = new TypeCache(new ReflectHelper()); + _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) @@ -68,7 +70,7 @@ public void GetTestMethodInfoShouldAddPropertiesFromContainingClassCorrectly() testContext.TryGetPropertyValue("DummyTestClassBaseKey2", out object? value3).Should().BeTrue(); value3.Should().Be("DummyTestClassBaseValue2"); - TestPlatform.ObjectModel.Trait[] traits = [.. ReflectHelper.Instance.GetTestPropertiesAsTraits(typeof(DummyTestClassBase).GetMethod(nameof(DummyTestClassBase.VirtualTestMethodInBaseAndDerived))!)]; + 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"); @@ -107,7 +109,7 @@ public void GetTestMethodInfoShouldAddPropertiesFromContainingClassAndBaseClasse testContext.TryGetPropertyValue("DummyTestClassBaseKey2", out object? value6).Should().BeTrue(); value6.Should().Be("DummyTestClassBaseValue2"); - TestPlatform.ObjectModel.Trait[] traits = [.. ReflectHelper.Instance.GetTestPropertiesAsTraits(typeof(DummyTestClassDerived).GetMethod(nameof(DummyTestClassDerived.VirtualTestMethodInBaseAndDerived))!)]; + 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"); @@ -152,7 +154,7 @@ public void GetTestMethodInfoShouldAddPropertiesFromContainingClassAndBaseClasse testContext.TryGetPropertyValue("DummyTestClassBaseKey2", out object? value6).Should().BeTrue(); value6.Should().Be("DummyTestClassBaseValue2"); - TestPlatform.ObjectModel.Trait[] traits = [.. ReflectHelper.Instance.GetTestPropertiesAsTraits(typeof(DummyTestClassDerived).GetMethod(nameof(DummyTestClassDerived.VirtualTestMethodInDerivedButNotTestMethodInBase))!)]; + 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/Execution/TypeCacheTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTests.cs index c46daae8e5..6e23b594ba 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTests.cs @@ -5,9 +5,10 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; 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.UnitTests.TestableImplementations; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; @@ -23,22 +24,20 @@ public class TypeCacheTests : TestContainer { private readonly TypeCache _typeCache; - private readonly Mock _mockReflectHelper; + private readonly Mock _mockReflectionOperations; private readonly Mock _mockMessageLogger; private readonly TestablePlatformServiceProvider _testablePlatformServiceProvider; public TypeCacheTests() { - _mockReflectHelper = new Mock(); - _typeCache = new TypeCache(_mockReflectHelper.Object); + _mockReflectionOperations = new Mock(); + _typeCache = new TypeCache(_mockReflectionOperations.Object); _mockMessageLogger = new Mock(); _testablePlatformServiceProvider = new TestablePlatformServiceProvider(); PlatformServiceProvider.Instance = _testablePlatformServiceProvider; - ReflectHelper.Instance.ClearCache(); - SetupMocks(); } @@ -154,7 +153,7 @@ public void GetTestMethodInfoShouldSetTestContextIfPresent() MethodInfo methodInfo = type.GetMethod("TestMethod")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); TestMethodInfo? testMethodInfo = _typeCache.GetTestMethodInfo( @@ -171,7 +170,7 @@ public void GetTestMethodInfoShouldSetTestContextToNullIfNotPresent() MethodInfo methodInfo = type.GetMethod("TestInit")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); TestMethodInfo? testMethodInfo = _typeCache.GetTestMethodInfo( @@ -190,7 +189,7 @@ public void GetTestMethodInfoShouldAddAssemblyInfoToTheCache() MethodInfo methodInfo = type.GetMethod("TestMethod")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); _typeCache.GetTestMethodInfo( @@ -206,10 +205,10 @@ public void GetTestMethodInfoShouldNotThrowIfWeFailToDiscoverTypeFromAnAssembly( MethodInfo methodInfo = type.GetMethod("TestMethod")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(It.IsAny())).Throws(new Exception()); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(typeof(DummyTestClassWithTestMethods))).Returns(true); _typeCache.GetTestMethodInfo( @@ -224,10 +223,10 @@ public void GetTestMethodInfoShouldCacheAssemblyInitializeAttribute() Type type = typeof(DummyTestClassWithInitializeMethods); TestMethod testMethod = CreateTestMethod("TestInit", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyInit")!)).Returns(true); _typeCache.GetTestMethodInfo( @@ -243,10 +242,10 @@ public void GetTestMethodInfoShouldCacheAssemblyCleanupAttribute() Type type = typeof(DummyTestClassWithCleanupMethods); TestMethod testMethod = CreateTestMethod("TestCleanup", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup")!)).Returns(true); _typeCache.GetTestMethodInfo( @@ -262,12 +261,12 @@ public void GetTestMethodInfoShouldCacheAssemblyInitAndCleanupAttribute() Type type = typeof(DummyTestClassWithInitAndCleanupMethods); TestMethod testMethod = CreateTestMethod("TestInitOrCleanup", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyInit")!)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup")!)).Returns(true); _typeCache.GetTestMethodInfo( @@ -284,10 +283,10 @@ public void GetTestMethodInfoShouldThrowIfAssemblyInitHasIncorrectSignature() Type type = typeof(DummyTestClassWithIncorrectInitializeMethods); TestMethod testMethod = CreateTestMethod("M", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyInit")!)).Returns(true); void A() => @@ -313,10 +312,10 @@ public void GetTestMethodInfoShouldThrowIfAssemblyCleanupHasIncorrectSignature() Type type = typeof(DummyTestClassWithIncorrectCleanupMethods); TestMethod testMethod = CreateTestMethod("M", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup")!)).Returns(true); void A() => @@ -343,7 +342,7 @@ public void GetTestMethodInfoShouldCacheAssemblyInfoInstanceAndReuseTheCache() MethodInfo methodInfo = type.GetMethod("TestMethod")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); _typeCache.GetTestMethodInfo( @@ -354,7 +353,7 @@ public void GetTestMethodInfoShouldCacheAssemblyInfoInstanceAndReuseTheCache() testMethod, CreateTestContextImplementationForMethod(testMethod)); - _mockReflectHelper.Verify(rh => rh.IsAttributeDefined(type), Times.Once); + _mockReflectionOperations.Verify(rh => rh.IsAttributeDefined(type), Times.Once); _typeCache.AssemblyInfoCache.Should().HaveCount(1); } @@ -368,7 +367,7 @@ public void GetTestMethodInfoShouldAddClassInfoToTheCache() MethodInfo methodInfo = type.GetMethod("TestMethod")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); _typeCache.GetTestMethodInfo( @@ -385,10 +384,10 @@ public void GetTestMethodInfoShouldCacheClassInitializeAttribute() Type type = typeof(DummyTestClassWithInitializeMethods); TestMethod testMethod = CreateTestMethod("TestInit", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyInit")!)).Returns(true); _typeCache.GetTestMethodInfo( @@ -407,16 +406,16 @@ public void GetTestMethodInfoShouldCacheBaseClassInitializeAttributes() TestMethod testMethod = CreateTestMethod("TestMethod", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(baseType.GetMethod("AssemblyInit")!)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.GetFirstAttributeOrDefault(baseType.GetMethod("AssemblyInit")!)) .Returns(new ClassInitializeAttribute(InheritanceBehavior.BeforeEachDerivedClass)); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("ClassInit")!)).Returns(true); _typeCache.GetTestMethodInfo( @@ -433,10 +432,10 @@ public void GetTestMethodInfoShouldCacheClassCleanupAttribute() Type type = typeof(DummyTestClassWithCleanupMethods); TestMethod testMethod = CreateTestMethod("TestCleanup", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup")!)).Returns(true); _typeCache.GetTestMethodInfo( @@ -454,11 +453,11 @@ public void GetTestMethodInfoShouldCacheBaseClassCleanupAttributes() Type baseType = typeof(DummyTestClassWithCleanupMethods); TestMethod testMethod = CreateTestMethod("TestMethod", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(baseType.GetMethod("AssemblyCleanup")!)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.GetFirstAttributeOrDefault(baseType.GetMethod("AssemblyCleanup")!)) .Returns(new ClassCleanupAttribute(InheritanceBehavior.BeforeEachDerivedClass)); @@ -476,11 +475,11 @@ public void GetTestMethodInfoShouldCacheClassInitAndCleanupAttribute() Type type = typeof(DummyTestClassWithInitAndCleanupMethods); TestMethod testMethod = CreateTestMethod("TestInitOrCleanup", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyInit")!)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup")!)).Returns(true); _typeCache.GetTestMethodInfo( @@ -501,23 +500,23 @@ public void GetTestMethodInfoShouldCacheBaseClassInitAndCleanupAttributes() MethodInfo baseInitializeMethod = baseType.GetMethod("ClassInit")!; MethodInfo baseCleanupMethod = baseType.GetMethod("ClassCleanup")!; - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(baseInitializeMethod)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.GetFirstAttributeOrDefault(baseInitializeMethod)) .Returns(new ClassInitializeAttribute(InheritanceBehavior.BeforeEachDerivedClass)); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(baseCleanupMethod)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.GetFirstAttributeOrDefault(baseCleanupMethod)) .Returns(new ClassCleanupAttribute(InheritanceBehavior.BeforeEachDerivedClass)); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyInit")!)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup")!)).Returns(true); _typeCache.GetTestMethodInfo( @@ -543,35 +542,35 @@ public void GetTestMethodInfoShouldCacheParentAndGrandparentClassInitAndCleanupA MethodInfo parentInitMethod = parentType.GetMethod("ChildClassInit")!; MethodInfo parentCleanupMethod = parentType.GetMethod("ChildClassCleanup")!; - _mockReflectHelper + _mockReflectionOperations .Setup(rh => rh.IsAttributeDefined(type)) .Returns(true); // Setup grandparent class init/cleanup methods - _mockReflectHelper + _mockReflectionOperations .Setup(rh => rh.IsAttributeDefined(grandparentInitMethod)) .Returns(true); - _mockReflectHelper + _mockReflectionOperations .Setup(rh => rh.GetFirstAttributeOrDefault(grandparentInitMethod)) .Returns(new ClassInitializeAttribute(InheritanceBehavior.BeforeEachDerivedClass)); - _mockReflectHelper + _mockReflectionOperations .Setup(rh => rh.IsAttributeDefined(grandparentCleanupMethod)) .Returns(true); - _mockReflectHelper + _mockReflectionOperations .Setup(rh => rh.GetFirstAttributeOrDefault(grandparentCleanupMethod)) .Returns(new ClassCleanupAttribute(InheritanceBehavior.BeforeEachDerivedClass)); // Setup parent class init/cleanup methods - _mockReflectHelper + _mockReflectionOperations .Setup(rh => rh.IsAttributeDefined(parentInitMethod)) .Returns(true); - _mockReflectHelper + _mockReflectionOperations .Setup(rh => rh.GetFirstAttributeOrDefault(parentInitMethod)) .Returns(new ClassInitializeAttribute(InheritanceBehavior.BeforeEachDerivedClass)); - _mockReflectHelper + _mockReflectionOperations .Setup(rh => rh.IsAttributeDefined(parentCleanupMethod)) .Returns(true); - _mockReflectHelper + _mockReflectionOperations .Setup(rh => rh.GetFirstAttributeOrDefault(parentCleanupMethod)) .Returns(new ClassCleanupAttribute(InheritanceBehavior.BeforeEachDerivedClass)); @@ -599,10 +598,10 @@ public void GetTestMethodInfoShouldThrowIfClassInitHasIncorrectSignature() Type type = typeof(DummyTestClassWithIncorrectInitializeMethods); TestMethod testMethod = CreateTestMethod("M", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyInit")!)).Returns(true); void A() => @@ -628,10 +627,10 @@ public void GetTestMethodInfoShouldThrowIfClassCleanupHasIncorrectSignature() Type type = typeof(DummyTestClassWithIncorrectCleanupMethods); TestMethod testMethod = CreateTestMethod("M", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup")!)).Returns(true); void A() => @@ -657,10 +656,10 @@ public void GetTestMethodInfoShouldCacheTestInitializeAttribute() Type type = typeof(DummyTestClassWithInitializeMethods); TestMethod testMethod = CreateTestMethod("TestInit", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("TestInit")!)).Returns(true); _typeCache.GetTestMethodInfo( @@ -676,10 +675,10 @@ public void GetTestMethodInfoShouldCacheTestCleanupAttribute() Type type = typeof(DummyTestClassWithCleanupMethods); TestMethod testMethod = CreateTestMethod("TestCleanup", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("TestCleanup")!)).Returns(true); _typeCache.GetTestMethodInfo( @@ -695,10 +694,10 @@ public void GetTestMethodInfoShouldThrowIfTestInitOrCleanupHasIncorrectSignature Type type = typeof(DummyTestClassWithIncorrectInitializeMethods); TestMethod testMethod = CreateTestMethod("M", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("TestInit")!)).Returns(true); Action action = () => @@ -725,10 +724,10 @@ public void GetTestMethodInfoShouldCacheTestInitializeAttributeDefinedInBaseClas Type baseType = typeof(DummyTestClassWithInitializeMethods); TestMethod testMethod = CreateTestMethod("TestMethod", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(baseType.GetMethod("TestInit")!)).Returns(true); _typeCache.GetTestMethodInfo( @@ -745,10 +744,10 @@ public void GetTestMethodInfoShouldCacheTestCleanupAttributeDefinedInBaseClass() Type baseType = typeof(DummyTestClassWithCleanupMethods); TestMethod testMethod = CreateTestMethod("TestMethod", type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(baseType.GetMethod("TestCleanup")!)).Returns(true); _typeCache.GetTestMethodInfo( @@ -765,7 +764,7 @@ public void GetTestMethodInfoShouldCacheClassInfoInstanceAndReuseFromCache() MethodInfo methodInfo = type.GetMethod("TestMethod")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); _typeCache.GetTestMethodInfo( @@ -812,7 +811,8 @@ public void GetTestMethodInfoShouldReturnTestMethodInfo() MethodInfo methodInfo = type.GetMethod("TestMethod")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup(rh => rh.GetSingleAttributeOrDefault(It.IsAny())).CallBase(); + _mockReflectionOperations.Setup(rh => rh.GetSingleAttributeOrDefault(It.IsAny())) + .Returns((ICustomAttributeProvider p) => ((MemberInfo)p).GetCustomAttribute(inherit: true)); TestMethodInfo? testMethodInfo = _typeCache.GetTestMethodInfo( testMethod, CreateTestContextImplementationForMethod(testMethod)); @@ -829,10 +829,12 @@ public void GetTestMethodInfoShouldReturnTestMethodInfoWithTimeout() MethodInfo methodInfo = type.GetMethod("TestMethodWithTimeout")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup(rh => rh.IsAttributeDefined(methodInfo)) + _mockReflectionOperations.Setup(rh => rh.IsAttributeDefined(methodInfo)) .Returns(true); - _mockReflectHelper.Setup(rh => rh.GetSingleAttributeOrDefault(It.IsAny())).CallBase(); - _mockReflectHelper.Setup(rh => rh.GetFirstAttributeOrDefault(methodInfo)).CallBase(); + _mockReflectionOperations.Setup(rh => rh.GetSingleAttributeOrDefault(It.IsAny())) + .Returns((ICustomAttributeProvider p) => ((MemberInfo)p).GetCustomAttribute(inherit: true)); + _mockReflectionOperations.Setup(rh => rh.GetFirstAttributeOrDefault(methodInfo)) + .Returns((ICustomAttributeProvider p) => ((MemberInfo)p).GetCustomAttribute(inherit: true)); TestMethodInfo? testMethodInfo = _typeCache.GetTestMethodInfo( testMethod, CreateTestContextImplementationForMethod(testMethod)); @@ -849,10 +851,10 @@ public void GetTestMethodInfoShouldThrowWhenTimeoutIsNegative() MethodInfo methodInfo = type.GetMethod("TestMethodWithNegativeTimeout")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup(rh => rh.IsAttributeDefined(methodInfo)) + _mockReflectionOperations.Setup(rh => rh.IsAttributeDefined(methodInfo)) .Returns(true); - _mockReflectHelper.Setup(ReflectHelper => ReflectHelper.GetFirstAttributeOrDefault(methodInfo)) - .CallBase(); + _mockReflectionOperations.Setup(ro => ro.GetFirstAttributeOrDefault(methodInfo)) + .Returns((ICustomAttributeProvider p) => ((MemberInfo)p).GetCustomAttribute(inherit: true)); void A() => _typeCache.GetTestMethodInfo( testMethod, @@ -876,10 +878,10 @@ public void GetTestMethodInfoShouldThrowWhenTimeoutIsZero() MethodInfo methodInfo = type.GetMethod("TestMethodWithTimeoutOfZero")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup(rh => rh.IsAttributeDefined(methodInfo)) + _mockReflectionOperations.Setup(rh => rh.IsAttributeDefined(methodInfo)) .Returns(true); - _mockReflectHelper.Setup(ReflectHelper => ReflectHelper.GetFirstAttributeOrDefault(methodInfo)) - .CallBase(); + _mockReflectionOperations.Setup(ro => ro.GetFirstAttributeOrDefault(methodInfo)) + .Returns((ICustomAttributeProvider p) => ((MemberInfo)p).GetCustomAttribute(inherit: true)); void A() => _typeCache.GetTestMethodInfo( testMethod, @@ -942,10 +944,10 @@ public void GetTestMethodInfoWhenTimeoutAttributeSetShouldReturnTimeoutBasedOnAt MethodInfo methodInfo = type.GetMethod("TestMethodWithTimeout")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup(rh => rh.IsAttributeDefined(methodInfo)) + _mockReflectionOperations.Setup(rh => rh.IsAttributeDefined(methodInfo)) .Returns(true); - _mockReflectHelper.Setup(ReflectHelper => ReflectHelper.GetFirstAttributeOrDefault(methodInfo)) - .CallBase(); + _mockReflectionOperations.Setup(ro => ro.GetFirstAttributeOrDefault(methodInfo)) + .Returns((ICustomAttributeProvider p) => ((MemberInfo)p).GetCustomAttribute(inherit: true)); TestMethodInfo? testMethodInfo = _typeCache.GetTestMethodInfo( testMethod, @@ -986,7 +988,8 @@ public void GetTestMethodInfoShouldReturnTestMethodInfoForMethodsAdornedWithADer MethodInfo methodInfo = type.GetMethod("TestMethodWithDerivedTestMethodAttribute")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup(rh => rh.GetSingleAttributeOrDefault(It.IsAny())).CallBase(); + _mockReflectionOperations.Setup(rh => rh.GetSingleAttributeOrDefault(It.IsAny())) + .Returns((ICustomAttributeProvider p) => ((MemberInfo)p).GetCustomAttribute(inherit: true)); TestMethodInfo? testMethodInfo = _typeCache.GetTestMethodInfo( testMethod, CreateTestContextImplementationForMethod(testMethod)); @@ -1000,9 +1003,9 @@ public void GetTestMethodInfoShouldReturnTestMethodInfoForMethodsAdornedWithADer public void GetTestMethodInfoShouldSetTestContextWithCustomProperty() { - // Not using _typeCache here which uses a mocked ReflectHelper which doesn't work well with this test. + // Not using _typeCache here which uses a mocked ReflectionOperations which doesn't work well with this test. // Setting up the mock feels unnecessary when the original production implementation can work just fine. - var typeCache = new TypeCache(new ReflectHelper()); + var typeCache = new TypeCache(new ReflectionOperations()); Type type = typeof(DummyTestClassWithTestMethods); MethodInfo methodInfo = type.GetMethod("TestMethodWithCustomProperty")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); @@ -1018,9 +1021,9 @@ public void GetTestMethodInfoShouldSetTestContextWithCustomProperty() public void GetTestMethodInfoShouldReportWarningIfCustomPropertyHasSameNameAsPredefinedProperties() { - // Not using _typeCache here which uses a mocked ReflectHelper which doesn't work well with this test. + // Not using _typeCache here which uses a mocked ReflectionOperations which doesn't work well with this test. // Setting up the mock feels unnecessary when the original production implementation can work just fine. - var typeCache = new TypeCache(new ReflectHelper()); + var typeCache = new TypeCache(new ReflectionOperations()); Type type = typeof(DummyTestClassWithTestMethods); MethodInfo methodInfo = type.GetMethod("TestMethodWithTestCategoryAsCustomProperty")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); @@ -1041,7 +1044,7 @@ public void GetTestMethodInfoShouldReportWarningIfCustomPropertyHasSameNameAsPre public void GetTestMethodInfoShouldReportWarningIfCustomOwnerPropertyIsDefined() { // Test that [TestProperty("Owner", "value")] is still blocked - var typeCache = new TypeCache(new ReflectHelper()); + var typeCache = new TypeCache(new ReflectionOperations()); Type type = typeof(DummyTestClassWithTestMethods); MethodInfo methodInfo = type.GetMethod("TestMethodWithOwnerAsCustomProperty")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); @@ -1062,7 +1065,7 @@ public void GetTestMethodInfoShouldReportWarningIfCustomOwnerPropertyIsDefined() public void GetTestMethodInfoShouldReportWarningIfCustomPriorityPropertyIsDefined() { // Test that [TestProperty("Priority", "value")] is still blocked - var typeCache = new TypeCache(new ReflectHelper()); + var typeCache = new TypeCache(new ReflectionOperations()); Type type = typeof(DummyTestClassWithTestMethods); MethodInfo methodInfo = type.GetMethod("TestMethodWithPriorityAsCustomProperty")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); @@ -1083,7 +1086,7 @@ public void GetTestMethodInfoShouldReportWarningIfCustomPriorityPropertyIsDefine public void GetTestMethodInfoShouldAllowActualOwnerAttribute() { // Test that the actual OwnerAttribute is allowed - var typeCache = new TypeCache(new ReflectHelper()); + var typeCache = new TypeCache(new ReflectionOperations()); Type type = typeof(DummyTestClassWithTestMethods); MethodInfo methodInfo = type.GetMethod("TestMethodWithActualOwnerAttribute")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); @@ -1102,7 +1105,7 @@ public void GetTestMethodInfoShouldAllowActualOwnerAttribute() public void GetTestMethodInfoShouldAllowActualPriorityAttribute() { // Test that the actual PriorityAttribute is allowed - var typeCache = new TypeCache(new ReflectHelper()); + var typeCache = new TypeCache(new ReflectionOperations()); Type type = typeof(DummyTestClassWithTestMethods); MethodInfo methodInfo = type.GetMethod("TestMethodWithActualPriorityAttribute")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); @@ -1120,9 +1123,9 @@ public void GetTestMethodInfoShouldAllowActualPriorityAttribute() public void GetTestMethodInfoShouldReportWarningIfCustomPropertyNameIsEmpty() { - // Not using _typeCache here which uses a mocked ReflectHelper which doesn't work well with this test. + // Not using _typeCache here which uses a mocked ReflectionOperations which doesn't work well with this test. // Setting up the mock feels unnecessary when the original production implementation can work just fine. - var typeCache = new TypeCache(new ReflectHelper()); + var typeCache = new TypeCache(new ReflectionOperations()); Type type = typeof(DummyTestClassWithTestMethods); MethodInfo methodInfo = type.GetMethod("TestMethodWithEmptyCustomPropertyName")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); @@ -1141,9 +1144,9 @@ public void GetTestMethodInfoShouldReportWarningIfCustomPropertyNameIsEmpty() public void GetTestMethodInfoShouldReportWarningIfCustomPropertyNameIsNull() { - // Not using _typeCache here which uses a mocked ReflectHelper which doesn't work well with this test. + // Not using _typeCache here which uses a mocked ReflectionOperations which doesn't work well with this test. // Setting up the mock feels unnecessary when the original production implementation can work just fine. - var typeCache = new TypeCache(new ReflectHelper()); + var typeCache = new TypeCache(new ReflectionOperations()); Type type = typeof(DummyTestClassWithTestMethods); MethodInfo methodInfo = type.GetMethod("TestMethodWithNullCustomPropertyName")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); @@ -1162,9 +1165,9 @@ public void GetTestMethodInfoShouldReportWarningIfCustomPropertyNameIsNull() public void GetTestMethodInfoShouldNotAddDuplicateTestPropertiesToTestContext() { - // Not using _typeCache here which uses a mocked ReflectHelper which doesn't work well with this test. + // Not using _typeCache here which uses a mocked ReflectionOperations which doesn't work well with this test. // Setting up the mock feels unnecessary when the original production implementation can work just fine. - var typeCache = new TypeCache(new ReflectHelper()); + var typeCache = new TypeCache(new ReflectionOperations()); Type type = typeof(DummyTestClassWithTestMethods); MethodInfo methodInfo = type.GetMethod("TestMethodWithDuplicateCustomPropertyNames")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); @@ -1185,7 +1188,8 @@ public void GetTestMethodInfoShouldReturnTestMethodInfoForDerivedTestClasses() MethodInfo methodInfo = type.GetRuntimeMethod("DummyTestMethod", [])!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup(rh => rh.GetSingleAttributeOrDefault(It.IsAny())).CallBase(); + _mockReflectionOperations.Setup(rh => rh.GetSingleAttributeOrDefault(It.IsAny())) + .Returns((ICustomAttributeProvider p) => ((MemberInfo)p).GetCustomAttribute(inherit: true)); TestMethodInfo? testMethodInfo = _typeCache.GetTestMethodInfo( testMethod, CreateTestContextImplementationForMethod(testMethod)); @@ -1202,7 +1206,8 @@ public void GetTestMethodInfoShouldReturnTestMethodInfoForDerivedClassMethodOver MethodInfo methodInfo = type.GetRuntimeMethod("OverloadedTestMethod", [])!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup(rh => rh.GetSingleAttributeOrDefault(It.IsAny())).CallBase(); + _mockReflectionOperations.Setup(rh => rh.GetSingleAttributeOrDefault(It.IsAny())) + .Returns((ICustomAttributeProvider p) => ((MemberInfo)p).GetCustomAttribute(inherit: true)); TestMethodInfo? testMethodInfo = _typeCache.GetTestMethodInfo( testMethod, CreateTestContextImplementationForMethod(testMethod)); @@ -1220,7 +1225,8 @@ public void GetTestMethodInfoShouldReturnTestMethodInfoForDeclaringTypeMethodOve MethodInfo methodInfo = baseType.GetRuntimeMethod("OverloadedTestMethod", [])!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, baseType.FullName!, "A", displayName: null); - _mockReflectHelper.Setup(rh => rh.GetSingleAttributeOrDefault(It.IsAny())).CallBase(); + _mockReflectionOperations.Setup(rh => rh.GetSingleAttributeOrDefault(It.IsAny())) + .Returns((ICustomAttributeProvider p) => ((MemberInfo)p).GetCustomAttribute(inherit: true)); TestMethodInfo? testMethodInfo = _typeCache.GetTestMethodInfo( testMethod, CreateTestContextImplementationForMethod(testMethod)); @@ -1252,10 +1258,10 @@ public void ClassInfoListWithExecutableCleanupMethodsShouldReturnEmptyListWhenCl MethodInfo methodInfo = type.GetMethod("TestCleanup")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("TestCleanup")!)).Returns(false); _typeCache.GetTestMethodInfo( @@ -1273,10 +1279,10 @@ public void ClassInfoListWithExecutableCleanupMethodsShouldReturnClassInfosWithE MethodInfo methodInfo = type.GetMethod("TestCleanup")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup")!)).Returns(true); _typeCache.GetTestMethodInfo( @@ -1306,10 +1312,10 @@ public void AssemblyInfoListWithExecutableCleanupMethodsShouldReturnEmptyListWhe MethodInfo methodInfo = type.GetMethod("TestCleanup")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup")!)).Returns(false); _typeCache.GetTestMethodInfo( @@ -1327,10 +1333,10 @@ public void AssemblyInfoListWithExecutableCleanupMethodsShouldReturnAssemblyInfo MethodInfo methodInfo = type.GetMethod("TestCleanup")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type)).Returns(true); - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup")!)).Returns(true); _typeCache.GetTestMethodInfo( diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/UnitTestRunnerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/UnitTestRunnerTests.cs index e4accd97a6..c758676796 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/UnitTestRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/UnitTestRunnerTests.cs @@ -5,7 +5,8 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; @@ -302,16 +303,16 @@ public async Task RunSingleTestShouldSetTestsAsInProgressInTestContext() public async Task RunSingleTestShouldCallAssemblyInitializeAndClassInitializeMethodsInOrder() { - var mockReflectHelper = new Mock(); + var mockReflectionOperations = new Mock(); Type type = typeof(DummyTestClassWithInitializeMethods); MethodInfo methodInfo = type.GetMethod("TestMethod")!; TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - var unitTestRunner = new UnitTestRunner(new MSTestSettings(), [new UnitTestElement(testMethod)], mockReflectHelper.Object); + var unitTestRunner = new UnitTestRunner(new MSTestSettings(), [new UnitTestElement(testMethod)], mockReflectionOperations.Object); _testablePlatformServiceProvider.MockFileOperations.Setup(fo => fo.LoadAssembly("A")) .Returns(Assembly.GetExecutingAssembly()); - mockReflectHelper.Setup( + mockReflectionOperations.Setup( rh => rh.IsAttributeDefined(type.GetMethod("AssemblyInitialize")!)) .Returns(true); @@ -373,7 +374,7 @@ private class DummyTestClassWithInitializeMethods public static Action ClassInitializeMethodBody { get; set; } = null!; - // The reflectHelper instance would set the AssemblyInitialize attribute here before running any tests. + // The reflectionOperations instance would set the AssemblyInitialize attribute here before running any tests. // Setting an attribute causes conflicts with other tests. public static void AssemblyInitialize(TestContext tc) => AssemblyInitializeMethodBody.Invoke(); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Extensions/MethodInfoExtensionsTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Extensions/MethodInfoExtensionsTests.cs index 7da3168397..aec4511156 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Extensions/MethodInfoExtensionsTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Extensions/MethodInfoExtensionsTests.cs @@ -5,7 +5,9 @@ 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; @@ -16,60 +18,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 @@ -79,49 +83,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 @@ -131,49 +135,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 @@ -183,61 +187,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 @@ -263,31 +267,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 @@ -297,13 +301,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/DesktopTestDeploymentTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs index 81e99154b4..8fbe0e3af3 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs @@ -4,9 +4,10 @@ #if NETFRAMEWORK using AwesomeAssertions; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; 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,7 +25,7 @@ public class DesktopTestDeploymentTests : 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 IDE0052 // Remove unread private members @@ -33,7 +34,7 @@ public class DesktopTestDeploymentTests : TestContainer public DesktopTestDeploymentTests() { - _mockReflectHelper = new Mock(); + _mockReflectionOperations = new Mock(); _mockFileUtility = new Mock(); _warnings = []; @@ -129,7 +130,7 @@ private void SetupDeploymentItems(ICustomAttributeProvider attributeProvider, Ke deploymentItemAttributes.Add(new DeploymentItemAttribute(deploymentItem.Key, deploymentItem.Value)); } - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( ru => ru.GetAttributes(attributeProvider)).Returns(deploymentItemAttributes); } @@ -165,7 +166,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/Services/ReflectionOperationsTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs index be9a7893b4..a8ddc62b25 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs @@ -3,7 +3,12 @@ using AwesomeAssertions; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; + +using Moq; using TestFramework.ForTestingMSTest; @@ -12,8 +17,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 +114,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 +349,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 +446,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 1e18309439..74c159dc45 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs @@ -4,9 +4,10 @@ #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; 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 +26,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 +35,7 @@ public class TestDeploymentTests : TestContainer public TestDeploymentTests() { - _mockReflectHelper = new Mock(); + _mockReflectionOperations = new Mock(); _mockFileUtility = new Mock(); _warnings = []; @@ -53,7 +54,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 +183,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 +206,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 +229,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 +259,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); @@ -371,7 +372,7 @@ private void SetupDeploymentItems(ICustomAttributeProvider attributeProvider, Ke deploymentItemAttributes.Add(new DeploymentItemAttribute(deploymentItem.Key, deploymentItem.Value)); } - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( ru => ru.GetAttributes(attributeProvider)).Returns(deploymentItemAttributes); } @@ -416,7 +417,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/Utilities/DeploymentItemUtilityTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs index 81a73eecdc..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. @@ -423,7 +424,7 @@ private void SetupDeploymentItems(ICustomAttributeProvider attributeProvider, Ke deploymentItemAttributes.Add(new DeploymentItemAttribute(deploymentItem.Key, deploymentItem.Value)); } - _mockReflectHelper.Setup( + _mockReflectionOperations.Setup( ru => ru.GetAttributes(attributeProvider)).Returns(deploymentItemAttributes); } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentUtilityTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentUtilityTests.cs index 78b566b86f..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,7 +26,7 @@ public class DeploymentUtilityTests : TestContainer private const string DefaultDeploymentItemPath = @"c:\temp"; private const string DefaultDeploymentItemOutputDirectory = "out"; - private readonly Mock _mockReflectHelper; + private readonly Mock _mockReflectionOperations; private readonly Mock _mockFileUtility; private readonly Mock _mockAssemblyUtility; private readonly Mock _mockRunContext; @@ -41,13 +42,13 @@ public class DeploymentUtilityTests : TestContainer public DeploymentUtilityTests() { - _mockReflectHelper = new Mock(); + _mockReflectionOperations = new Mock(); _mockFileUtility = new Mock(); _mockAssemblyUtility = new Mock(); _warnings = []; _deploymentUtility = new DeploymentUtility( - new DeploymentItemUtility(_mockReflectHelper.Object), + new DeploymentItemUtility(_mockReflectionOperations.Object), _mockAssemblyUtility.Object, _mockFileUtility.Object); From c4375645a2ffbad9293932b4d2099265bbec819c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Fri, 6 Feb 2026 19:35:24 +0100 Subject: [PATCH 05/10] Massive refactoring --- .../Discovery/AssemblyEnumerator.cs | 13 +- .../Discovery/TypeEnumerator.cs | 7 +- .../Execution/TestAssemblySettingsProvider.cs | 6 +- .../Execution/TypeCache.cs | 36 ++-- .../Extensions/MethodInfoExtensions.cs | 6 +- .../Helpers/ReflectionHelper.cs | 84 ++++++++ .../Interfaces/IReflectionOperations.cs | 41 ---- .../Services/ReflectionOperations.cs | 180 ------------------ .../Discovery/TypeEnumeratorTests.cs | 2 - .../Execution/TestPropertyAttributeTests.cs | 1 + .../Services/ReflectionOperationsTests.cs | 1 + 11 files changed, 124 insertions(+), 253 deletions(-) create mode 100644 src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectionHelper.cs diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs index 1e7d2401bd..88340743ba 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs @@ -74,16 +74,21 @@ internal AssemblyEnumerationResult EnumerateAssembly(string assemblyFileName) Assembly assembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly(assemblyFileName); Type[] types = GetTypes(assembly); - bool discoverInternals = _reflectionOperations.GetDiscoverInternalsAttribute(assembly) != null; - - TestDataSourceUnfoldingStrategy dataSourcesUnfoldingStrategy = _reflectionOperations.GetTestDataSourceOptions(assembly)?.UnfoldingStrategy switch + bool discoverInternals = _reflectionOperations.GetCustomAttributes(assembly, typeof(DiscoverInternalsAttribute)) + .OfType() + .Any(); + + var assemblyUnfoldingStrategyAttribute = _reflectionOperations.GetCustomAttributes(assembly, typeof(TestDataSourceUnfoldingStrategyAttribute)) + .OfType() + .FirstOrDefault(); + TestDataSourceUnfoldingStrategy dataSourcesUnfoldingStrategy = assemblyUnfoldingStrategyAttribute?.UnfoldingStrategy switch { // When strategy is auto we want to unfold TestDataSourceUnfoldingStrategy.Auto => TestDataSourceUnfoldingStrategy.Unfold, // When strategy is set, let's use it { } value => value, // When the attribute is not set, let's look at the legacy attribute - null => _reflectionOperations.GetTestDataSourceDiscoveryOption(assembly) switch + null => _reflectionOperations.GetCustomAttributes(assembly, typeof(TestDataSourceDiscoveryAttribute)).OfType().FirstOrDefault()?.DiscoveryOption switch { TestDataSourceDiscoveryOption.DuringExecution => TestDataSourceUnfoldingStrategy.Fold, _ => TestDataSourceUnfoldingStrategy.Unfold, diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs index 741eecc70a..e56f4d6994 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs @@ -69,7 +69,7 @@ internal List GetTests(List warnings) // if we rely on analyzers to identify all invalid methods on build, we can change this to fit the current settings. foreach (MethodInfo method in PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethods(_type)) { - bool isMethodDeclaredInTestTypeAssembly = _reflectionOperation.IsMethodDeclaredInSameAssemblyAsType(method, _type); + bool isMethodDeclaredInTestTypeAssembly = method.DeclaringType!.Assembly.Equals(_type.Assembly); // TODO: Investigate if we rely on NRE; bool enableMethodsFromOtherAssemblies = MSTestSettings.CurrentSettings.EnableBaseClassTestMethodsFromOtherAssemblies; if (!isMethodDeclaredInTestTypeAssembly && !enableMethodsFromOtherAssemblies) @@ -137,8 +137,9 @@ internal UnitTestElement GetTestFromMethod(MethodInfo method, ICollection(testMethod) + || IsAttributeDefined(_type), + Priority = _reflectionOperation.GetFirstAttributeOrDefault(method)?.Priority, #if !WINDOWS_UWP && !WIN_UI DeploymentItems = PlatformServiceProvider.Instance.TestDeployment.GetDeploymentItems(method, _type, warnings), #endif diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs index 494f06bd2e..6e7526ac4f 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs @@ -35,7 +35,9 @@ internal TestAssemblySettings GetSettings(string source) Assembly testAssembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly(source); var reflectionOperations = (ReflectionOperations)PlatformServiceProvider.Instance.ReflectionOperations; - ParallelizeAttribute? parallelizeAttribute = reflectionOperations.GetParallelizeAttribute(testAssembly); + ParallelizeAttribute? parallelizeAttribute = reflectionOperations.GetCustomAttributes(testAssembly, typeof(ParallelizeAttribute)) + .OfType() + .FirstOrDefault(); if (parallelizeAttribute != null) { @@ -48,7 +50,7 @@ internal TestAssemblySettings GetSettings(string source) } } - testAssemblySettings.CanParallelizeAssembly = !reflectionOperations.IsDoNotParallelizeSet(testAssembly); + testAssemblySettings.CanParallelizeAssembly = !reflectionOperations.GetCustomAttributes(testAssembly, typeof(DoNotParallelizeAttribute)).Length != 0; return testAssemblySettings; } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs index ca60d7c0b1..050b191345 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs @@ -22,7 +22,7 @@ internal sealed class TypeCache : MarshalByRefObject /// /// Helper for reflection API's. /// - private readonly IReflectionOperations _reflectionHelper; + private readonly IReflectionOperations _reflectionOperations; /// /// Assembly info cache. @@ -47,8 +47,8 @@ internal TypeCache() /// /// Initializes a new instance of the class. /// - /// An instance to the object. - internal TypeCache(IReflectionOperations reflectionHelper) => _reflectionHelper = reflectionHelper; + /// An instance to the object. + internal TypeCache(IReflectionOperations reflectionOperations) => _reflectionOperations = reflectionOperations; /// /// Gets Class Info cache which has cleanup methods to execute. @@ -304,7 +304,7 @@ private TestClassInfo CreateClassInfo(Type classType) private TimeoutInfo? TryGetTimeoutInfo(MethodInfo methodInfo, FixtureKind fixtureKind) { - TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetFirstAttributeOrDefault(methodInfo); + TimeoutAttribute? timeoutAttribute = _reflectionOperations.GetFirstAttributeOrDefault(methodInfo); if (timeoutAttribute != null) { if (!timeoutAttribute.HasCorrectTimeout) @@ -341,7 +341,7 @@ private TestAssemblyInfo GetAssemblyInfo(Assembly assembly) try { // Only examine classes which are TestClass or derives from TestClass attribute - if (!@this._reflectionHelper.IsAttributeDefined(t)) + if (!@this._reflectionOperations.IsAttributeDefined(t)) { continue; } @@ -374,8 +374,8 @@ private TestAssemblyInfo GetAssemblyInfo(Assembly assembly) assemblyInfo.AssemblyCleanupMethodTimeoutMilliseconds = @this.TryGetTimeoutInfo(methodInfo, FixtureKind.AssemblyCleanup); } - bool isGlobalTestInitialize = @this._reflectionHelper.IsAttributeDefined(methodInfo); - bool isGlobalTestCleanup = @this._reflectionHelper.IsAttributeDefined(methodInfo); + bool isGlobalTestInitialize = @this._reflectionOperations.IsAttributeDefined(methodInfo); + bool isGlobalTestCleanup = @this._reflectionOperations.IsAttributeDefined(methodInfo); if (isGlobalTestInitialize || isGlobalTestCleanup) { @@ -385,7 +385,7 @@ private TestAssemblyInfo GetAssemblyInfo(Assembly assembly) // We want to avoid loading types early as much as we can. bool isValid = methodInfo is { IsSpecialName: false, IsPublic: true, IsStatic: true, IsGenericMethod: false, DeclaringType.IsGenericType: false, DeclaringType.IsPublic: true } && methodInfo.GetParameters() is { } parameters && parameters.Length == 1 && parameters[0].ParameterType == typeof(TestContext) && - methodInfo.IsValidReturnType(@this._reflectionHelper); + methodInfo.IsValidReturnType(@this._reflectionOperations); if (isValid && isGlobalTestInitialize) { @@ -417,12 +417,12 @@ private bool IsAssemblyOrClassInitializeMethod(MethodInfo // { // return false; // } - if (!_reflectionHelper.IsAttributeDefined(methodInfo)) + if (!_reflectionOperations.IsAttributeDefined(methodInfo)) { return false; } - if (!methodInfo.HasCorrectClassOrAssemblyInitializeSignature(_reflectionHelper)) + if (!methodInfo.HasCorrectClassOrAssemblyInitializeSignature(_reflectionOperations)) { string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ClassOrAssemblyInitializeMethodHasWrongSignature, methodInfo.DeclaringType!.FullName, methodInfo.Name); throw new TypeInspectionException(message); @@ -445,12 +445,12 @@ private bool IsAssemblyOrClassCleanupMethod(MethodInfo method // { // return false; // } - if (!_reflectionHelper.IsAttributeDefined(methodInfo)) + if (!_reflectionOperations.IsAttributeDefined(methodInfo)) { return false; } - if (!methodInfo.HasCorrectClassOrAssemblyCleanupSignature(_reflectionHelper)) + if (!methodInfo.HasCorrectClassOrAssemblyCleanupSignature(_reflectionOperations)) { string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ClassOrAssemblyCleanupMethodHasWrongSignature, methodInfo.DeclaringType!.FullName, methodInfo.Name); throw new TypeInspectionException(message); @@ -513,7 +513,7 @@ private void UpdateInfoIfClassInitializeOrCleanupMethod( if (isBase) { - if (_reflectionHelper.GetFirstAttributeOrDefault(methodInfo)? + if (_reflectionOperations.GetFirstAttributeOrDefault(methodInfo)? .InheritanceBehavior == InheritanceBehavior.BeforeEachDerivedClass) { initAndCleanupMethods[0] = methodInfo; @@ -535,7 +535,7 @@ private void UpdateInfoIfClassInitializeOrCleanupMethod( if (isBase) { - if (_reflectionHelper.GetFirstAttributeOrDefault(methodInfo)? + if (_reflectionOperations.GetFirstAttributeOrDefault(methodInfo)? .InheritanceBehavior == InheritanceBehavior.BeforeEachDerivedClass) { initAndCleanupMethods[1] = methodInfo; @@ -562,12 +562,12 @@ private void UpdateInfoIfTestInitializeOrCleanupMethod( bool isBase, HashSet? instanceMethods) { - bool hasTestInitialize = _reflectionHelper.IsAttributeDefined(methodInfo); - bool hasTestCleanup = _reflectionHelper.IsAttributeDefined(methodInfo); + bool hasTestInitialize = _reflectionOperations.IsAttributeDefined(methodInfo); + bool hasTestCleanup = _reflectionOperations.IsAttributeDefined(methodInfo); if (!hasTestCleanup && !hasTestInitialize) { - if (instanceMethods is not null && methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionHelper)) + if (instanceMethods is not null && methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionOperations)) { instanceMethods.Add(methodInfo.Name); } @@ -575,7 +575,7 @@ private void UpdateInfoIfTestInitializeOrCleanupMethod( return; } - if (!methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionHelper)) + if (!methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionOperations)) { string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_TestInitializeAndCleanupMethodHasWrongSignature, methodInfo.DeclaringType!.FullName, methodInfo.Name); throw new TypeInspectionException(message); diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs index 6e2dcc91f6..2ddc14b761 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs @@ -95,8 +95,8 @@ internal static bool HasCorrectTestMethodSignature(this MethodInfo method, bool /// The reflection service to use. /// True if the method has a void/task return type.. internal static bool IsValidReturnType(this MethodInfo method, IReflectionOperations reflectionOperation) - => ReflectionOperations.MatchReturnType(method, typeof(Task)) - || (ReflectionOperations.MatchReturnType(method, typeof(void)) && method.GetAsyncTypeName(reflectionOperation) == null) + => method.ReturnType.Equals(typeof(Task)) + || (method.ReturnType.Equals(typeof(void)) && method.GetAsyncTypeName(reflectionOperation) == null) // Keep this the last check, as it avoids loading System.Threading.Tasks.Extensions unnecessarily. || method.IsValueTask(); @@ -106,7 +106,7 @@ internal static bool IsValidReturnType(this MethodInfo method, IReflectionOperat // Even when invokeResult is null or Task. [MethodImpl(MethodImplOptions.NoInlining)] private static bool IsValueTask(this MethodInfo method) - => ReflectionOperations.MatchReturnType(method, typeof(ValueTask)); + => method.ReturnType.Equals(typeof(ValueTask)); /// /// For async methods compiler generates different type and method. 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 159e9f15f4..dd22ab136f 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs @@ -1,8 +1,6 @@ // 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.ObjectModel; - namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; /// @@ -93,43 +91,4 @@ IEnumerable GetAttributes(ICustomAttributeProvid /// The member to inspect. /// Attributes defined. Attribute[] GetCustomAttributesCached(ICustomAttributeProvider attributeProvider); - - /// - /// Returns true when the method is declared in the assembly where the type is declared. - /// - /// The method to check for. - /// The type declared in the assembly to check. - /// True if the method is declared in the assembly where the type is declared. - bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Type type); - - /// - /// Get categories applied to the test method. - /// - /// The member to inspect. - /// The reflected type that owns . - /// Categories defined. - string[] GetTestCategories(MemberInfo categoryAttributeProvider, Type owningType); - - /// - /// Get the parallelization behavior for a test method. - /// - /// Test method. - /// The type that owns . - /// True if test method should not run in parallel. - bool IsDoNotParallelizeSet(MemberInfo testMethod, Type owningType); - - /// - /// Priority if any set for test method. Will return priority if attribute is applied to TestMethod - /// else null. - /// - /// The member to inspect. - /// Priority value if defined. Null otherwise. - int? GetPriority(MemberInfo priorityAttributeProvider); - - /// - /// KeyValue pairs that are provided by TestPropertyAttributes of the given test method. - /// - /// The member to inspect. - /// List of traits. - Trait[] GetTestPropertiesAsTraits(MethodInfo testPropertyProvider); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs index b285c17fd6..de79b3c9c2 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs @@ -5,7 +5,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; @@ -192,162 +191,6 @@ public bool IsAttributeDefined(MemberInfo memberInfo) return foundAttribute; } - /// - /// Match return type of method. - /// - /// The method to inspect. - /// The return type to match. - /// True if there is a match. - internal static bool MatchReturnType(MethodInfo method, Type returnType) - { - Ensure.NotNull(method); - Ensure.NotNull(returnType); - return method.ReturnType.Equals(returnType); - } - - /// - /// Gets the parallelization level set on an assembly. - /// - /// The test assembly. - /// The parallelization level if set. -1 otherwise. - internal ParallelizeAttribute? GetParallelizeAttribute(Assembly assembly) - => GetCustomAttributes(assembly, typeof(ParallelizeAttribute)) - .OfType() - .FirstOrDefault(); - - /// - /// Gets discover internals assembly level attribute. - /// - /// The test assembly. - internal DiscoverInternalsAttribute? GetDiscoverInternalsAttribute(Assembly assembly) - => GetCustomAttributes(assembly, typeof(DiscoverInternalsAttribute)) - .OfType() - .FirstOrDefault(); - - /// - /// Gets TestDataSourceDiscovery assembly level attribute. - /// - /// The test assembly. - internal TestDataSourceDiscoveryOption? GetTestDataSourceDiscoveryOption(Assembly assembly) - => GetCustomAttributes(assembly, typeof(TestDataSourceDiscoveryAttribute)) - .OfType() - .FirstOrDefault()?.DiscoveryOption; - - /// - /// Gets TestDataSourceOptions assembly level attribute. - /// - /// The test assembly. - /// The TestDataSourceOptionsAttribute if set. Null otherwise. - internal TestDataSourceOptionsAttribute? GetTestDataSourceOptions(Assembly assembly) - => GetCustomAttributes(assembly, typeof(TestDataSourceOptionsAttribute)) - .OfType() - .FirstOrDefault(); - - /// - /// Get the parallelization behavior for a test assembly. - /// - /// The test assembly. - /// True if test assembly should not run in parallel. - internal bool IsDoNotParallelizeSet(Assembly assembly) - => GetCustomAttributes(assembly, typeof(DoNotParallelizeAttribute)) - .Length != 0; - - /// - /// Returns true when the method is declared in the assembly where the type is declared. - /// - /// The method to check for. - /// The type declared in the assembly to check. - /// True if the method is declared in the assembly where the type is declared. - public bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Type type) - => method.DeclaringType!.Assembly.Equals(type.Assembly); // TODO: Investigate if we rely on NRE - - /// - /// Get categories applied to the test method. - /// - /// The member to inspect. - /// The reflected type that owns . - /// Categories defined. - public string[] GetTestCategories(MemberInfo categoryAttributeProvider, Type owningType) - { - IEnumerable methodCategories = GetAttributes(categoryAttributeProvider); - IEnumerable typeCategories = GetAttributes(owningType); - IEnumerable assemblyCategories = GetAttributes(owningType.Assembly); - - return [.. methodCategories.Concat(typeCategories).Concat(assemblyCategories).SelectMany(c => c.TestCategories)]; - } - - /// - /// Get the parallelization behavior for a test method. - /// - /// Test method. - /// The type that owns . - /// True if test method should not run in parallel. - public bool IsDoNotParallelizeSet(MemberInfo testMethod, Type owningType) - => IsAttributeDefined(testMethod) - || IsAttributeDefined(owningType); - - /// - /// Priority if any set for test method. Will return priority if attribute is applied to TestMethod - /// else null. - /// - /// The member to inspect. - /// Priority value if defined. Null otherwise. - public int? GetPriority(MemberInfo priorityAttributeProvider) => - GetFirstAttributeOrDefault(priorityAttributeProvider)?.Priority; - - /// - /// KeyValue pairs that are provided by TestPropertyAttributes of the given test method. - /// - /// The member to inspect. - /// List of traits. - public Trait[] GetTestPropertiesAsTraits(MethodInfo testPropertyProvider) - { - Attribute[] attributesFromMethod = GetCustomAttributesCached(testPropertyProvider); - Attribute[] attributesFromClass = testPropertyProvider.ReflectedType is { } testClass ? 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; - } - /// /// Get attribute defined on a method which is of given type of subtype of given type. /// @@ -371,29 +214,6 @@ public IEnumerable GetAttributes(ICustomAttribut } } - /// - /// Get attribute defined on a method which is of given type of subtype of given type. - /// - /// The attribute type. - /// The type of state to be passed to Action. - /// The member to inspect. - /// The action to perform. - /// The state to pass to action. - internal void PerformActionOnAttribute(ICustomAttributeProvider attributeProvider, Action action, TState? state) - where TAttributeType : Attribute - { - Attribute[] attributes = GetCustomAttributesCached(attributeProvider); - foreach (Attribute attribute in attributes) - { - DebugEx.Assert(attribute != null, "ReflectionOperations.DefinesAttributeDerivedFrom: internal error: wrong value in the attributes dictionary."); - - if (attribute is TAttributeType attributeAsAttributeType) - { - action(attributeAsAttributeType, state); - } - } - } - /// /// Gets and caches the attributes for the given type, or method. /// diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs index 4b24f84e55..0dd8cc175a 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs @@ -30,8 +30,6 @@ public partial class TypeEnumeratorTests : TestContainer public TypeEnumeratorTests() { _mockReflectionOperations = new Mock(); - _mockReflectionOperations.Setup(r => r.GetTestCategories(It.IsAny(), It.IsAny())).Returns(Array.Empty()); - _mockReflectionOperations.Setup(r => r.GetTestPropertiesAsTraits(It.IsAny())).Returns(Array.Empty()); _mockReflectionOperations.Setup(r => r.GetCustomAttributesCached(It.IsAny())).Returns(Array.Empty()); _mockTypeValidator = new Mock(MockBehavior.Default, _mockReflectionOperations.Object); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs index 6d4a897784..51ad86c49c 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs @@ -6,6 +6,7 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs index a8ddc62b25..2ae240e2bb 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs @@ -5,6 +5,7 @@ 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; From 5568ca8b6aa4e3b08c9a0f46366b6b627e4bbdb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Fri, 6 Feb 2026 20:38:37 +0100 Subject: [PATCH 06/10] Fix glitches --- .../Discovery/AssemblyEnumerator.cs | 4 ++-- .../Discovery/TypeEnumerator.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs index 88340743ba..0f9473d0f5 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs @@ -78,8 +78,8 @@ internal AssemblyEnumerationResult EnumerateAssembly(string assemblyFileName) .OfType() .Any(); - var assemblyUnfoldingStrategyAttribute = _reflectionOperations.GetCustomAttributes(assembly, typeof(TestDataSourceUnfoldingStrategyAttribute)) - .OfType() + var assemblyUnfoldingStrategyAttribute = _reflectionOperations.GetCustomAttributes(assembly, typeof(TestDataSourceOptionsAttribute)) + .OfType() .FirstOrDefault(); TestDataSourceUnfoldingStrategy dataSourcesUnfoldingStrategy = assemblyUnfoldingStrategyAttribute?.UnfoldingStrategy switch { diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs index e56f4d6994..366505a56f 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs @@ -137,7 +137,7 @@ internal UnitTestElement GetTestFromMethod(MethodInfo method, ICollection(testMethod) + DoNotParallelize = _reflectionOperation.IsAttributeDefined(method) || IsAttributeDefined(_type), Priority = _reflectionOperation.GetFirstAttributeOrDefault(method)?.Priority, #if !WINDOWS_UWP && !WIN_UI From 23e4303052a18deaab65734cc4845981e5424ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Sat, 7 Feb 2026 16:32:02 +0100 Subject: [PATCH 07/10] Fixes --- .../Discovery/AssemblyEnumerator.cs | 1 + .../MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs | 2 +- .../Execution/TestAssemblySettingsProvider.cs | 2 +- .../MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs | 1 + .../Execution/TestMethodRunner.cs | 2 +- .../Extensions/MethodInfoExtensions.cs | 1 + 6 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs index 0f9473d0f5..207dacd188 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs @@ -6,6 +6,7 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting.Internal; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs index 366505a56f..5356f9a956 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs @@ -138,7 +138,7 @@ internal UnitTestElement GetTestFromMethod(MethodInfo method, ICollection(method) - || IsAttributeDefined(_type), + || _reflectionOperation.IsAttributeDefined(_type), Priority = _reflectionOperation.GetFirstAttributeOrDefault(method)?.Priority, #if !WINDOWS_UWP && !WIN_UI DeploymentItems = PlatformServiceProvider.Instance.TestDeployment.GetDeploymentItems(method, _type, warnings), diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs index 6e7526ac4f..ae38b089c7 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs @@ -50,7 +50,7 @@ internal TestAssemblySettings GetSettings(string source) } } - testAssemblySettings.CanParallelizeAssembly = !reflectionOperations.GetCustomAttributes(testAssembly, typeof(DoNotParallelizeAttribute)).Length != 0; + 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 c964d626ed..1229edb879 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs @@ -6,6 +6,7 @@ #endif 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; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Execution; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs index 2f2a15d0f1..0fd2d8ad98 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs @@ -2,7 +2,7 @@ // 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; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs index 2ddc14b761..36606a8a86 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; From 875ae8d5326b87d927fcb8af7dedec8073e29b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Sat, 7 Feb 2026 17:14:33 +0100 Subject: [PATCH 08/10] Even more --- .../Discovery/AssemblyEnumerator.cs | 8 ++++---- .../MSTestAdapter.PlatformServices/Execution/TypeCache.cs | 1 - .../Extensions/MethodInfoExtensions.cs | 3 +-- .../Helpers/AttributeHelpers.cs | 1 - 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs index 207dacd188..9a0bd1396e 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs @@ -5,8 +5,8 @@ using System.Security; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting.Internal; @@ -70,7 +70,7 @@ internal AssemblyEnumerationResult EnumerateAssembly(string assemblyFileName) { List warnings = []; DebugEx.Assert(!StringEx.IsNullOrWhiteSpace(assemblyFileName), "Invalid assembly file name."); - var tests = new List(); + List tests = []; Assembly assembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly(assemblyFileName); @@ -79,7 +79,7 @@ internal AssemblyEnumerationResult EnumerateAssembly(string assemblyFileName) .OfType() .Any(); - var assemblyUnfoldingStrategyAttribute = _reflectionOperations.GetCustomAttributes(assembly, typeof(TestDataSourceOptionsAttribute)) + TestDataSourceOptionsAttribute? assemblyUnfoldingStrategyAttribute = _reflectionOperations.GetCustomAttributes(assembly, typeof(TestDataSourceOptionsAttribute)) .OfType() .FirstOrDefault(); TestDataSourceUnfoldingStrategy dataSourcesUnfoldingStrategy = assemblyUnfoldingStrategyAttribute?.UnfoldingStrategy switch @@ -165,7 +165,7 @@ private List DiscoverTestsInType( TestDataSourceUnfoldingStrategy dataSourcesUnfoldingStrategy) { string? typeFullName = null; - var tests = new List(); + List tests = []; try { diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs index 050b191345..5dd02a9660 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs @@ -7,7 +7,6 @@ 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; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs index 36606a8a86..82de589de0 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs @@ -1,9 +1,8 @@ // 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.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs index 7e8aebfd76..b27e0bba2c 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs @@ -1,7 +1,6 @@ // 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; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; From 68b754d0c6cfbf452956353689e06d112e3b00bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Sat, 7 Feb 2026 21:10:17 +0100 Subject: [PATCH 09/10] Again --- .../ReflectionUtilityTests.cs | 24 +-- .../Discovery/AssemblyEnumeratorTests.cs | 4 +- .../Discovery/TypeEnumeratorTests.cs | 2 - .../Execution/ClassCleanupManagerTests.cs | 4 +- .../Execution/TestMethodRunnerTests.cs | 1 - .../Execution/TestPropertyAttributeTests.cs | 8 +- .../Execution/TypeCacheTests.cs | 145 ------------------ .../Execution/UnitTestRunnerTests.cs | 2 +- .../Extensions/MethodInfoExtensionsTests.cs | 1 + .../Services/DesktopTestDeploymentTests.cs | 1 - .../Services/TestDeploymentTests.cs | 1 - .../Utilities/ReflectionUtilityTests.cs | 16 +- 12 files changed, 33 insertions(+), 176 deletions(-) diff --git a/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs b/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs index aac9d4adfd..7f7ba9c924 100644 --- a/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs +++ b/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs @@ -93,13 +93,13 @@ public void GetSpecificCustomAttributesShouldReturnAllAttributes() { MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestBaseClass").GetMethod("DummyVTestMethod1"); - object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo, typeof(TestCategoryAttribute)); + TestCategoryAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); attributes.Should().NotBeNull(); attributes.Should().HaveCount(1); string[] expectedAttributes = ["TestCategory : base"]; - GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesShouldReturnAllAttributesWithBaseInheritance() @@ -107,13 +107,13 @@ public void GetSpecificCustomAttributesShouldReturnAllAttributesWithBaseInherita MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass").GetMethod("DummyVTestMethod1"); - object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo, typeof(TestCategoryAttribute)); + TestCategoryAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); string[] expectedAttributes = ["TestCategory : derived", "TestCategory : base"]; - GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } public void GetCustomAttributesShouldReturnAllAttributesIncludingUserDefinedAttributes() @@ -133,52 +133,52 @@ public void GetSpecificCustomAttributesShouldReturnAllAttributesIncludingUserDef { MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClassWithCustomAttributes").GetMethod("DummyVTestMethod1"); - object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo, typeof(TestPropertyAttribute)); + TestPropertyAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); string[] expectedAttributes = ["Duration : superfast", "Owner : base"]; - GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesShouldReturnArrayAttributesAsWell() { MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClassWithCustomAttributes").GetMethod("DummyTestMethod2"); - object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo, typeof(CategoryArrayAttribute)); + CategoryArrayAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); attributes.Should().NotBeNull(); attributes.Should().HaveCount(1); string[] expectedAttributes = ["CategoryAttribute : foo,foo2"]; - GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributes() { Type type = _testAsset.GetType("TestProjectForDiscovery.AttributeTestBaseClass"); - object[]? attributes = _reflectionOperations.GetCustomAttributes(type, typeof(TestCategoryAttribute)); + TestCategoryAttribute[] attributes = _reflectionOperations.GetAttributes(type).ToArray(); attributes.Should().NotBeNull(); attributes.Should().HaveCount(1); string[] expectedAttributes = ["TestCategory : ba"]; - GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritance() { Type type = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass"); - object[]? attributes = _reflectionOperations.GetCustomAttributes(type, typeof(TestCategoryAttribute)); + TestCategoryAttribute[] attributes = _reflectionOperations.GetAttributes(type).ToArray(); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); string[] expectedAttributes = ["TestCategory : a", "TestCategory : ba"]; - GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesOnAssemblyShouldReturnAllAttributes() diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs index 1c4953e6ae..fcfc0a6792 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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.Collections.ObjectModel; @@ -7,8 +7,8 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs index 0dd8cc175a..d0a05988bd 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs @@ -491,8 +491,6 @@ private void SetupTestClassAndTestMethods(bool isValidTestClass, bool isValidTes .Returns(isValidTestClass); _mockTestMethodValidator.Setup( tmv => tmv.IsValidTestMethod(It.IsAny(), It.IsAny(), It.IsAny>())).Returns(isValidTestMethod); - _mockReflectionOperations.Setup( - rh => rh.IsMethodDeclaredInSameAssemblyAsType(It.IsAny(), It.IsAny())).Returns(isMethodFromSameAssembly); } private TypeEnumerator GetTypeEnumeratorInstance(Type type, string assemblyName) diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/ClassCleanupManagerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/ClassCleanupManagerTests.cs index a3ca44b5ed..a08c96980d 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/ClassCleanupManagerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/ClassCleanupManagerTests.cs @@ -1,11 +1,11 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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.Execution; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Moq; diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs index 155cfa9282..b077c8ef1a 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.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs index 51ad86c49c..d16bd026b0 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs @@ -5,9 +5,9 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; 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.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; using Moq; @@ -42,6 +42,12 @@ 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() diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTests.cs index 5a0c990e75..aa71cfadee 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTests.cs @@ -5,7 +5,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; @@ -1019,150 +1018,6 @@ public void GetTestMethodInfoShouldSetTestContextWithCustomProperty() (customProperty.Value as string).Should().Be("Me"); } - public void GetTestMethodInfoShouldReportWarningIfCustomPropertyHasSameNameAsPredefinedProperties() - { - // Not using _typeCache here which uses a mocked ReflectionOperations which doesn't work well with this test. - // Setting up the mock feels unnecessary when the original production implementation can work just fine. - var typeCache = new TypeCache(new ReflectionOperations()); - Type type = typeof(DummyTestClassWithTestMethods); - MethodInfo methodInfo = type.GetMethod("TestMethodWithTestCategoryAsCustomProperty")!; - TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - TestContextImplementation testContext = CreateTestContextImplementationForMethod(testMethod); - - TestMethodInfo? testMethodInfo = typeCache.GetTestMethodInfo(testMethod, testContext); - - testMethodInfo.Should().NotBeNull(); - string expectedMessage = string.Format( - CultureInfo.InvariantCulture, - "UTA023: {0}: Cannot define predefined property {2} on method {1}.", - methodInfo.DeclaringType!.FullName!, - methodInfo.Name, - "TestCategory"); - testMethodInfo.NotRunnableReason.Should().Be(expectedMessage); - } - - public void GetTestMethodInfoShouldReportWarningIfCustomOwnerPropertyIsDefined() - { - // Test that [TestProperty("Owner", "value")] is still blocked - var typeCache = new TypeCache(new ReflectionOperations()); - Type type = typeof(DummyTestClassWithTestMethods); - MethodInfo methodInfo = type.GetMethod("TestMethodWithOwnerAsCustomProperty")!; - TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - TestContextImplementation testContext = CreateTestContextImplementationForMethod(testMethod); - - TestMethodInfo? testMethodInfo = typeCache.GetTestMethodInfo(testMethod, testContext); - - testMethodInfo.Should().NotBeNull(); - string expectedMessage = string.Format( - CultureInfo.InvariantCulture, - "UTA023: {0}: Cannot define predefined property {2} on method {1}.", - methodInfo.DeclaringType!.FullName!, - methodInfo.Name, - "Owner"); - testMethodInfo.NotRunnableReason.Should().Be(expectedMessage); - } - - public void GetTestMethodInfoShouldReportWarningIfCustomPriorityPropertyIsDefined() - { - // Test that [TestProperty("Priority", "value")] is still blocked - var typeCache = new TypeCache(new ReflectionOperations()); - Type type = typeof(DummyTestClassWithTestMethods); - MethodInfo methodInfo = type.GetMethod("TestMethodWithPriorityAsCustomProperty")!; - TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - TestContextImplementation testContext = CreateTestContextImplementationForMethod(testMethod); - - TestMethodInfo? testMethodInfo = typeCache.GetTestMethodInfo(testMethod, testContext); - - testMethodInfo.Should().NotBeNull(); - string expectedMessage = string.Format( - CultureInfo.InvariantCulture, - "UTA023: {0}: Cannot define predefined property {2} on method {1}.", - methodInfo.DeclaringType!.FullName!, - methodInfo.Name, - "Priority"); - testMethodInfo.NotRunnableReason.Should().Be(expectedMessage); - } - - public void GetTestMethodInfoShouldAllowActualOwnerAttribute() - { - // Test that the actual OwnerAttribute is allowed - var typeCache = new TypeCache(new ReflectionOperations()); - Type type = typeof(DummyTestClassWithTestMethods); - MethodInfo methodInfo = type.GetMethod("TestMethodWithActualOwnerAttribute")!; - TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - TestContextImplementation testContext = CreateTestContextImplementationForMethod(testMethod); - - TestMethodInfo? testMethodInfo = typeCache.GetTestMethodInfo(testMethod, testContext); - - testMethodInfo.Should().NotBeNull(); - // Owner should be allowed - no NotRunnableReason should be set - string.IsNullOrEmpty(testMethodInfo.NotRunnableReason).Should().BeTrue(); - // The Owner property should be added to the test context - testContext.TryGetPropertyValue("Owner", out object? ownerValue).Should().BeTrue(); - ownerValue?.ToString().Should().Be("TestOwner"); - } - - public void GetTestMethodInfoShouldAllowActualPriorityAttribute() - { - // Test that the actual PriorityAttribute is allowed - var typeCache = new TypeCache(new ReflectionOperations()); - Type type = typeof(DummyTestClassWithTestMethods); - MethodInfo methodInfo = type.GetMethod("TestMethodWithActualPriorityAttribute")!; - TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - TestContextImplementation testContext = CreateTestContextImplementationForMethod(testMethod); - - TestMethodInfo? testMethodInfo = typeCache.GetTestMethodInfo(testMethod, testContext); - - testMethodInfo.Should().NotBeNull(); - // Priority should be allowed - no NotRunnableReason should be set - string.IsNullOrEmpty(testMethodInfo.NotRunnableReason).Should().BeTrue(); - // The Priority property should be added to the test context - testContext.TryGetPropertyValue("Priority", out object? priorityValue).Should().BeTrue(); - priorityValue?.ToString().Should().Be("1"); - } - - public void GetTestMethodInfoShouldReportWarningIfCustomPropertyNameIsEmpty() - { - // Not using _typeCache here which uses a mocked ReflectionOperations which doesn't work well with this test. - // Setting up the mock feels unnecessary when the original production implementation can work just fine. - var typeCache = new TypeCache(new ReflectionOperations()); - Type type = typeof(DummyTestClassWithTestMethods); - MethodInfo methodInfo = type.GetMethod("TestMethodWithEmptyCustomPropertyName")!; - TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - TestContextImplementation testContext = CreateTestContextImplementationForMethod(testMethod); - - TestMethodInfo? testMethodInfo = typeCache.GetTestMethodInfo(testMethod, testContext); - - testMethodInfo.Should().NotBeNull(); - string expectedMessage = string.Format( - CultureInfo.InvariantCulture, - "UTA021: {0}: Null or empty custom property defined on method {1}. The custom property must have a valid name.", - methodInfo.DeclaringType!.FullName!, - methodInfo.Name); - testMethodInfo.NotRunnableReason.Should().Be(expectedMessage); - } - - public void GetTestMethodInfoShouldReportWarningIfCustomPropertyNameIsNull() - { - // Not using _typeCache here which uses a mocked ReflectionOperations which doesn't work well with this test. - // Setting up the mock feels unnecessary when the original production implementation can work just fine. - var typeCache = new TypeCache(new ReflectionOperations()); - Type type = typeof(DummyTestClassWithTestMethods); - MethodInfo methodInfo = type.GetMethod("TestMethodWithNullCustomPropertyName")!; - TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); - TestContextImplementation testContext = CreateTestContextImplementationForMethod(testMethod); - - TestMethodInfo? testMethodInfo = typeCache.GetTestMethodInfo(testMethod, testContext); - - testMethodInfo.Should().NotBeNull(); - string expectedMessage = string.Format( - CultureInfo.InvariantCulture, - "UTA021: {0}: Null or empty custom property defined on method {1}. The custom property must have a valid name.", - methodInfo.DeclaringType!.FullName!, - methodInfo.Name); - testMethodInfo.NotRunnableReason.Should().Be(expectedMessage); - } - public void GetTestMethodInfoShouldNotAddDuplicateTestPropertiesToTestContext() { // Not using _typeCache here which uses a mocked ReflectionOperations which doesn't work well with this test. diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/UnitTestRunnerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/UnitTestRunnerTests.cs index a174c7b7ca..6ecaf4cbd7 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/UnitTestRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/UnitTestRunnerTests.cs @@ -5,9 +5,9 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; 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.Interface; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Extensions/MethodInfoExtensionsTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Extensions/MethodInfoExtensionsTests.cs index aec4511156..3777514177 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Extensions/MethodInfoExtensionsTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Extensions/MethodInfoExtensionsTests.cs @@ -3,6 +3,7 @@ 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; diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs index 8fbe0e3af3..fd2706ae2c 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs @@ -4,7 +4,6 @@ #if NETFRAMEWORK using AwesomeAssertions; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs index 74c159dc45..9169fb6262 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs @@ -4,7 +4,6 @@ #if !WINDOWS_UWP && !WIN_UI using AwesomeAssertions; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/ReflectionUtilityTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/ReflectionUtilityTests.cs index 8f3b572557..56510822c7 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/ReflectionUtilityTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/ReflectionUtilityTests.cs @@ -92,52 +92,52 @@ public void GetSpecificCustomAttributesShouldReturnAllAttributes() { MethodInfo methodInfo = typeof(DummyBaseTestClass).GetMethod("DummyVTestMethod1")!; - object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo, typeof(DummyAAttribute)); + DummyAAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); attributes.Should().NotBeNull(); attributes.Should().HaveCount(1); string[] expectedAttributes = ["DummyA : base"]; - GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesShouldReturnAllAttributesWithBaseInheritance() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("DummyVTestMethod1")!; - object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo, typeof(DummyAAttribute)); + DummyAAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); string[] expectedAttributes = ["DummyA : derived", "DummyA : base"]; - GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributes() { Type type = typeof(DummyBaseTestClass); - object[]? attributes = _reflectionOperations.GetCustomAttributes(type, typeof(DummyAAttribute)); + DummyAAttribute[] attributes = _reflectionOperations.GetAttributes(type).ToArray(); attributes.Should().NotBeNull(); attributes.Should().HaveCount(1); string[] expectedAttributes = ["DummyA : ba"]; - GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritance() { Type type = typeof(DummyTestClass); - object[]? attributes = _reflectionOperations.GetCustomAttributes(type, typeof(DummyAAttribute)); + DummyAAttribute[] attributes = _reflectionOperations.GetAttributes(type).ToArray(); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); string[] expectedAttributes = ["DummyA : a", "DummyA : ba"]; - GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } internal static List GetAttributeValuePairs(IEnumerable attributes) From d57fedf33025886e56388a36daa0e555ac40346d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Mon, 9 Feb 2026 23:01:46 +0100 Subject: [PATCH 10/10] more --- .../Discovery/TypeEnumerator.cs | 2 +- .../Execution/TestAssemblySettingsProvider.cs | 4 +- .../Execution/TypeCache.cs | 10 +++ .../Interfaces/IReflectionOperations.cs | 8 ++ .../Services/ReflectionOperations.cs | 4 + .../Discovery/AssemblyEnumeratorTests.cs | 10 ++- .../Discovery/TypeEnumeratorTests.cs | 13 ++- .../Execution/ClassCleanupManagerTests.cs | 1 - .../Execution/TestMethodRunnerTests.cs | 6 ++ .../Execution/TypeCacheTests.cs | 6 ++ .../MockableReflectionOperations.cs | 81 +++++++++++++++++++ .../TestablePlatformServiceProvider.cs | 16 +++- .../MSTestAdapter.UnitTests.csproj | 1 + 13 files changed, 148 insertions(+), 14 deletions(-) create mode 100644 test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/MockableReflectionOperations.cs diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs index 5356f9a956..e7a225ac70 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs @@ -69,7 +69,7 @@ internal List GetTests(List warnings) // if we rely on analyzers to identify all invalid methods on build, we can change this to fit the current settings. foreach (MethodInfo method in PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethods(_type)) { - bool isMethodDeclaredInTestTypeAssembly = method.DeclaringType!.Assembly.Equals(_type.Assembly); // TODO: Investigate if we rely on NRE; + bool isMethodDeclaredInTestTypeAssembly = _reflectionOperation.IsMethodDeclaredInSameAssemblyAsType(method, _type); bool enableMethodsFromOtherAssemblies = MSTestSettings.CurrentSettings.EnableBaseClassTestMethodsFromOtherAssemblies; if (!isMethodDeclaredInTestTypeAssembly && !enableMethodsFromOtherAssemblies) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs index ae38b089c7..5f5ed56515 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs @@ -4,7 +4,7 @@ using System.Security; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; @@ -34,7 +34,7 @@ internal TestAssemblySettings GetSettings(string source) // Load the source. Assembly testAssembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly(source); - var reflectionOperations = (ReflectionOperations)PlatformServiceProvider.Instance.ReflectionOperations; + IReflectionOperations reflectionOperations = PlatformServiceProvider.Instance.ReflectionOperations; ParallelizeAttribute? parallelizeAttribute = reflectionOperations.GetCustomAttributes(testAssembly, typeof(ParallelizeAttribute)) .OfType() .FirstOrDefault(); diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs index 5dd02a9660..dc0a0b192d 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs @@ -9,6 +9,7 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; @@ -639,6 +640,15 @@ private TestMethodInfo ResolveTestMethodInfo(TestMethod testMethod, TestClassInf MethodInfo methodInfo = GetMethodInfoForTestMethod(testMethod, testClassInfo); + // Populate the test context with test properties from [TestProperty] attributes. + foreach (Trait trait in _reflectionOperations.GetTestPropertiesAsTraits(methodInfo)) + { + if (!testContext.TryGetPropertyValue(trait.Name, out _)) + { + testContext.AddProperty(trait.Name, trait.Value); + } + } + return new TestMethodInfo(methodInfo, testClassInfo, testContext); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs index dd22ab136f..ca23e62048 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs @@ -91,4 +91,12 @@ IEnumerable GetAttributes(ICustomAttributeProvid /// 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 de79b3c9c2..a884e1888c 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs @@ -279,6 +279,10 @@ internal static class NotCachedReflectionAccessor } } + /// + 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/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs index fcfc0a6792..653b5fc5d5 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs @@ -9,6 +9,7 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery; 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 reflectionOperations = new Mock(); - var typeValidator = new Mock(reflectionOperations.Object); - var testMethodValidator = new Mock(reflectionOperations.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", - reflectionOperations.Object, + wrappedReflectionOperations, typeValidator.Object, testMethodValidator.Object); } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs index d0a05988bd..8663f38b6c 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs @@ -20,6 +20,7 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Discovery; public partial class TypeEnumeratorTests : TestContainer { private readonly Mock _mockReflectionOperations; + private readonly IReflectionOperations _wrappedReflectionOperations; private readonly Mock _mockTestMethodValidator; private readonly Mock _mockTypeValidator; private readonly TestablePlatformServiceProvider _testablePlatformServiceProvider; @@ -30,10 +31,12 @@ public partial class TypeEnumeratorTests : TestContainer public TypeEnumeratorTests() { _mockReflectionOperations = new Mock(); - _mockReflectionOperations.Setup(r => r.GetCustomAttributesCached(It.IsAny())).Returns(Array.Empty()); + _mockReflectionOperations.Setup(ro => ro.GetCustomAttributes(It.IsAny())) + .Returns(mi => mi.GetCustomAttributes(true)); + _wrappedReflectionOperations = MockableReflectionOperations.Create(_mockReflectionOperations); - _mockTypeValidator = new Mock(MockBehavior.Default, _mockReflectionOperations.Object); - _mockTestMethodValidator = new Mock(MockBehavior.Default, _mockReflectionOperations.Object, false); + _mockTypeValidator = new Mock(MockBehavior.Default, _wrappedReflectionOperations); + _mockTestMethodValidator = new Mock(MockBehavior.Default, _wrappedReflectionOperations, false); _warnings = []; _mockMessageLogger = new Mock(); @@ -491,13 +494,15 @@ private void SetupTestClassAndTestMethods(bool isValidTestClass, bool isValidTes .Returns(isValidTestClass); _mockTestMethodValidator.Setup( tmv => tmv.IsValidTestMethod(It.IsAny(), It.IsAny(), It.IsAny>())).Returns(isValidTestMethod); + _mockReflectionOperations.Setup(ro => ro.IsMethodDeclaredInSameAssemblyAsType(It.IsAny(), It.IsAny())) + .Returns(isMethodFromSameAssembly); } private TypeEnumerator GetTypeEnumeratorInstance(Type type, string assemblyName) => new( type, assemblyName, - _mockReflectionOperations.Object, + _wrappedReflectionOperations, _mockTypeValidator.Object, _mockTestMethodValidator.Object); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/ClassCleanupManagerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/ClassCleanupManagerTests.cs index a08c96980d..ef30abd9d5 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/ClassCleanupManagerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/ClassCleanupManagerTests.cs @@ -17,7 +17,6 @@ public class ClassCleanupManagerTests : TestContainer { public void AssemblyCleanupRunsAfterAllTestsFinishEvenIfWeScheduleTheSameTestMultipleTime() { - ReflectionOperations reflectionOperations = 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 b077c8ef1a..20c4a65f39 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs @@ -217,6 +217,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 = UTF.UnitTestOutcome.Passed }); _testablePlatformServiceProvider.MockTestDataSource.Setup(tds => tds.GetData(testMethodInfo, _testContextImplementation)).Returns([1, 2, 3]); @@ -252,6 +253,7 @@ public async Task RunTestMethodShouldSetDataRowIndexForDataDrivenTestsWhenDataIs // 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); @@ -279,6 +281,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); @@ -308,6 +311,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); @@ -332,6 +336,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); @@ -358,6 +363,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/TypeCacheTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTests.cs index aa71cfadee..8c40928541 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTests.cs @@ -1274,6 +1274,12 @@ public void TestMethodWithNullCustomPropertyName() { } + [TestMethod] + [TestProperty("WhoAmI", "Me")] + public void TestMethodWithCustomProperty() + { + } + [TestMethod] [TestProperty("WhoAmI", "Me")] [TestProperty("WhoAmI", "Me2")] 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 8556128948..355ce51468 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, TestPlatform.ObjectModel.Adapter.IFrameworkHandle? frameworkHandle) => 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.UnitTests/MSTestAdapter.UnitTests.csproj b/test/UnitTests/MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj index 6ca3cab48e..3d69cc0145 100644 --- a/test/UnitTests/MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj +++ b/test/UnitTests/MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj @@ -17,6 +17,7 @@ +