diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs index 84630c264a..9a0bd1396e 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs @@ -7,6 +7,7 @@ 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 +22,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 +43,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. @@ -66,21 +70,26 @@ 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); Type[] types = GetTypes(assembly); - bool discoverInternals = ReflectHelper.GetDiscoverInternalsAttribute(assembly) != null; - - TestDataSourceUnfoldingStrategy dataSourcesUnfoldingStrategy = ReflectHelper.GetTestDataSourceOptions(assembly)?.UnfoldingStrategy switch + bool discoverInternals = _reflectionOperations.GetCustomAttributes(assembly, typeof(DiscoverInternalsAttribute)) + .OfType() + .Any(); + + TestDataSourceOptionsAttribute? assemblyUnfoldingStrategyAttribute = _reflectionOperations.GetCustomAttributes(assembly, typeof(TestDataSourceOptionsAttribute)) + .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 => ReflectHelper.GetTestDataSourceDiscoveryOption(assembly) switch + null => _reflectionOperations.GetCustomAttributes(assembly, typeof(TestDataSourceDiscoveryAttribute)).OfType().FirstOrDefault()?.DiscoveryOption switch { TestDataSourceDiscoveryOption.DuringExecution => TestDataSourceUnfoldingStrategy.Fold, _ => TestDataSourceUnfoldingStrategy.Unfold, @@ -142,10 +151,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( @@ -156,7 +165,7 @@ private List DiscoverTestsInType( TestDataSourceUnfoldingStrategy dataSourcesUnfoldingStrategy) { string? typeFullName = null; - var tests = new List(); + List tests = []; try { @@ -221,7 +230,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..e7a225ac70 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,20 @@ internal UnitTestElement GetTestFromMethod(MethodInfo method, ICollection(method) + || _reflectionOperation.IsAttributeDefined(_type), + Priority = _reflectionOperation.GetFirstAttributeOrDefault(method)?.Priority, #if !WINDOWS_UWP && !WIN_UI DeploymentItems = PlatformServiceProvider.Instance.TestDeployment.GetDeploymentItems(method, _type, warnings), #endif - Traits = [.. _reflectHelper.GetTestPropertiesAsTraits(method)], + Traits = [.. _reflectionOperation.GetTestPropertiesAsTraits(method)], }; - Attribute[] attributes = _reflectHelper.GetCustomAttributesCached(method); + Attribute[] attributes = _reflectionOperation.GetCustomAttributesCached(method); TestMethodAttribute? testMethodAttribute = null; - // Backward looping for backcompat. This used to be calls to _reflectHelper.GetFirstAttributeOrDefault + // Backward looping for backcompat. This used to be calls to _reflectionOperation.GetFirstAttributeOrDefault // So, to make sure the first attribute always wins, we loop from end to start. for (int i = attributes.Length - 1; i >= 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..5f5ed56515 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs @@ -3,8 +3,8 @@ using System.Security; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; @@ -34,7 +34,10 @@ internal TestAssemblySettings GetSettings(string source) // Load the source. Assembly testAssembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly(source); - ParallelizeAttribute? parallelizeAttribute = ReflectHelper.GetParallelizeAttribute(testAssembly); + IReflectionOperations reflectionOperations = PlatformServiceProvider.Instance.ReflectionOperations; + ParallelizeAttribute? parallelizeAttribute = reflectionOperations.GetCustomAttributes(testAssembly, typeof(ParallelizeAttribute)) + .OfType() + .FirstOrDefault(); if (parallelizeAttribute != null) { @@ -47,7 +50,7 @@ internal TestAssemblySettings GetSettings(string source) } } - testAssemblySettings.CanParallelizeAssembly = !ReflectHelper.IsDoNotParallelizeSet(testAssembly); + testAssemblySettings.CanParallelizeAssembly = reflectionOperations.GetCustomAttributes(testAssembly, typeof(DoNotParallelizeAttribute)).Length == 0; return testAssemblySettings; } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs index 1eb518e160..1229edb879 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs @@ -109,7 +109,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. @@ -118,7 +118,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. @@ -253,7 +253,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(); @@ -276,7 +276,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 @@ -292,7 +292,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..0fd2d8ad98 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions; - using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; @@ -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 c13cdab187..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; @@ -21,7 +22,7 @@ internal sealed class TypeCache : MarshalByRefObject /// /// Helper for reflection API's. /// - private readonly ReflectHelper _reflectionHelper; + private readonly IReflectionOperations _reflectionOperations; /// /// Assembly info cache. @@ -39,15 +40,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 reflectionOperations) => _reflectionOperations = reflectionOperations; /// /// Gets Class Info cache which has cleanup methods to execute. @@ -251,7 +252,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); @@ -303,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) @@ -340,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; } @@ -373,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) { @@ -384,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(); + methodInfo.IsValidReturnType(@this._reflectionOperations); if (isValid && isGlobalTestInitialize) { @@ -416,12 +417,12 @@ private bool IsAssemblyOrClassInitializeMethod(MethodInfo // { // return false; // } - if (!_reflectionHelper.IsAttributeDefined(methodInfo)) + if (!_reflectionOperations.IsAttributeDefined(methodInfo)) { return false; } - if (!methodInfo.HasCorrectClassOrAssemblyInitializeSignature()) + if (!methodInfo.HasCorrectClassOrAssemblyInitializeSignature(_reflectionOperations)) { string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ClassOrAssemblyInitializeMethodHasWrongSignature, methodInfo.DeclaringType!.FullName, methodInfo.Name); throw new TypeInspectionException(message); @@ -444,12 +445,12 @@ private bool IsAssemblyOrClassCleanupMethod(MethodInfo method // { // return false; // } - if (!_reflectionHelper.IsAttributeDefined(methodInfo)) + if (!_reflectionOperations.IsAttributeDefined(methodInfo)) { return false; } - if (!methodInfo.HasCorrectClassOrAssemblyCleanupSignature()) + if (!methodInfo.HasCorrectClassOrAssemblyCleanupSignature(_reflectionOperations)) { string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ClassOrAssemblyCleanupMethodHasWrongSignature, methodInfo.DeclaringType!.FullName, methodInfo.Name); throw new TypeInspectionException(message); @@ -512,7 +513,7 @@ private void UpdateInfoIfClassInitializeOrCleanupMethod( if (isBase) { - if (_reflectionHelper.GetFirstAttributeOrDefault(methodInfo)? + if (_reflectionOperations.GetFirstAttributeOrDefault(methodInfo)? .InheritanceBehavior == InheritanceBehavior.BeforeEachDerivedClass) { initAndCleanupMethods[0] = methodInfo; @@ -534,7 +535,7 @@ private void UpdateInfoIfClassInitializeOrCleanupMethod( if (isBase) { - if (_reflectionHelper.GetFirstAttributeOrDefault(methodInfo)? + if (_reflectionOperations.GetFirstAttributeOrDefault(methodInfo)? .InheritanceBehavior == InheritanceBehavior.BeforeEachDerivedClass) { initAndCleanupMethods[1] = methodInfo; @@ -561,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()) + if (instanceMethods is not null && methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionOperations)) { instanceMethods.Add(methodInfo.Name); } @@ -574,7 +575,7 @@ private void UpdateInfoIfTestInitializeOrCleanupMethod( return; } - if (!methodInfo.HasCorrectTestInitializeOrCleanupSignature()) + if (!methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionOperations)) { string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_TestInitializeAndCleanupMethodHasWrongSignature, methodInfo.DeclaringType!.FullName, methodInfo.Name); throw new TypeInspectionException(message); @@ -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); } @@ -694,7 +704,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 b36aefb130..32acf7a3d2 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. @@ -61,7 +61,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..82de589de0 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; 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; 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) + => 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(); @@ -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)); + => method.ReturnType.Equals(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..b27e0bba2c 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs @@ -9,7 +9,7 @@ internal static class AttributeExtensions { public static bool IsIgnored(this ICustomAttributeProvider type, out string? ignoreMessage) { - IEnumerable attributes = ReflectHelper.Instance.GetAttributes(type); + IEnumerable attributes = PlatformServiceProvider.Instance.ReflectionOperations.GetAttributes(type); IEnumerable> groups = attributes.GroupBy(attr => attr.GroupName); foreach (IGrouping? group in groups) { diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/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/Helpers/ReflectionHelper.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectionHelper.cs new file mode 100644 index 0000000000..7d2f960814 --- /dev/null +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectionHelper.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; + +/// +/// Helper methods for extracting test metadata from reflection operations. +/// +internal static class ReflectionHelper +{ + /// + /// Get categories applied to the test method. + /// + /// The reflection operations to use. + /// The member to inspect. + /// The reflected type that owns . + /// Categories defined. + public static string[] GetTestCategories(this IReflectionOperations reflectionOperations, MemberInfo categoryAttributeProvider, Type owningType) + { + IEnumerable methodCategories = reflectionOperations.GetAttributes(categoryAttributeProvider); + IEnumerable typeCategories = reflectionOperations.GetAttributes(owningType); + IEnumerable assemblyCategories = reflectionOperations.GetAttributes(owningType.Assembly); + + return [.. methodCategories.Concat(typeCategories).Concat(assemblyCategories).SelectMany(c => c.TestCategories)]; + } + + /// + /// KeyValue pairs that are provided by TestPropertyAttributes of the given test method. + /// + /// The reflection operations to use. + /// The member to inspect. + /// List of traits. + public static Trait[] GetTestPropertiesAsTraits(this IReflectionOperations reflectionOperations, MethodInfo testPropertyProvider) + { + Attribute[] attributesFromMethod = reflectionOperations.GetCustomAttributesCached(testPropertyProvider); + Attribute[] attributesFromClass = testPropertyProvider.ReflectedType is { } testClass ? reflectionOperations.GetCustomAttributesCached(testClass) : []; + int countTestPropertyAttribute = 0; + foreach (Attribute attribute in attributesFromMethod) + { + if (attribute is TestPropertyAttribute) + { + countTestPropertyAttribute++; + } + } + + foreach (Attribute attribute in attributesFromClass) + { + if (attribute is TestPropertyAttribute) + { + countTestPropertyAttribute++; + } + } + + if (countTestPropertyAttribute == 0) + { + // This is the common case that we optimize for. This method used to be an iterator (uses yield return) which is allocating unnecessarily in common cases. + return []; + } + + var traits = new Trait[countTestPropertyAttribute]; + int index = 0; + foreach (Attribute attribute in attributesFromMethod) + { + if (attribute is TestPropertyAttribute testProperty) + { + traits[index++] = new Trait(testProperty.Name, testProperty.Value); + } + } + + foreach (Attribute attribute in attributesFromClass) + { + if (attribute is TestPropertyAttribute testProperty) + { + traits[index++] = new Trait(testProperty.Name, testProperty.Value); + } + } + + return traits; + } +} diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs index b154b531cc..ca23e62048 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs @@ -43,4 +43,60 @@ internal interface IReflectionOperations Type? GetType(Assembly assembly, string typeName); object? CreateInstance(Type type, object?[] parameters); + + /// + /// Checks to see if a member or type is decorated with the given attribute, or an attribute that derives from it. + /// + /// Attribute to search for. + /// Member to inspect for attributes. + /// True if the attribute of the specified type is defined on this member or a class. + bool IsAttributeDefined(MemberInfo memberInfo) + where TAttribute : Attribute; + + /// + /// Gets first attribute that matches the type. + /// Use this together with attribute that does not allow multiple and is sealed. In such case there cannot be more attributes, and this will avoid the cost of + /// checking for more than one attribute. + /// + /// Type of the attribute to find. + /// The type, assembly or method. + /// The attribute that is found or null. + TAttribute? GetFirstAttributeOrDefault(ICustomAttributeProvider attributeProvider) + where TAttribute : Attribute; + + /// + /// Gets first attribute that matches the type or is derived from it. + /// Use this together with attribute that does not allow multiple. In such case there cannot be more attributes, and this will avoid the cost of + /// checking for more than one attribute. + /// + /// Type of the attribute to find. + /// The type, assembly or method. + /// The attribute that is found or null. + /// Throws when multiple attributes are found (the attribute must allow multiple). + TAttribute? GetSingleAttributeOrDefault(ICustomAttributeProvider attributeProvider) + where TAttribute : Attribute; + + /// + /// Get attribute defined on a member which is of given type of subtype of given type. + /// + /// The attribute type. + /// The member to inspect. + /// An instance of the attribute. + IEnumerable GetAttributes(ICustomAttributeProvider attributeProvider) + where TAttributeType : Attribute; + + /// + /// Gets and caches the attributes for the given type, or method. + /// + /// The member to inspect. + /// Attributes defined. + Attribute[] GetCustomAttributesCached(ICustomAttributeProvider attributeProvider); + + /// + /// Checks whether the declaring type of the method is declared in the same assembly as the given type. + /// + /// The method to check. + /// The type whose assembly to compare against. + /// True if the method's declaring type is in the same assembly as . + bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Type type); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs index df7e49c9b2..a884e1888c 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs @@ -1,21 +1,27 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Security; + +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -#if NETFRAMEWORK -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; -#endif +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; /// /// This service is responsible for platform specific reflection operations. /// -internal sealed class ReflectionOperations : IReflectionOperations +internal sealed class ReflectionOperations : MarshalByRefObject, IReflectionOperations { private const BindingFlags DeclaredOnlyLookup = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly; private const BindingFlags Everything = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; + // PERF: This was moved from Dictionary> to Concurrent + // storing an array allows us to store multiple attributes of the same type if we find them. It also has lower memory footprint, and is faster + // when we are going through the whole collection. Giving us overall better perf. + private readonly ConcurrentDictionary _attributeCache = []; + /// /// Gets all the custom attributes adorned on a member. /// @@ -23,20 +29,15 @@ internal sealed class ReflectionOperations : IReflectionOperations /// The list of attributes on the member. Empty list if none found. [return: NotNullIfNotNull(nameof(memberInfo))] public object[]? GetCustomAttributes(MemberInfo memberInfo) -#if NETFRAMEWORK - => [.. ReflectionOperationsNetFrameworkAttributeHelpers.GetCustomAttributes(memberInfo)]; -#else { object[] attributes = memberInfo.GetCustomAttributes(typeof(Attribute), inherit: true); - // Ensures that when the return of this method is used here: - // https://github.com/microsoft/testfx/blob/e101a9d48773cc935c7b536d25d378d9a3211fee/src/Adapter/MSTest.TestAdapter/Helpers/ReflectHelper.cs#L461 + // Ensures that when the return of this method is used with GetCustomAttributesCached // then we are already Attribute[] to avoid LINQ Cast and extra array allocation. // This assert is solely for performance. Nothing "functional" will go wrong if the assert failed. Debug.Assert(attributes is Attribute[], $"Expected Attribute[], found '{attributes.GetType()}'."); return attributes; } -#endif /// /// Gets all the custom attributes of a given type on an assembly. @@ -44,12 +45,8 @@ internal sealed class ReflectionOperations : IReflectionOperations /// The assembly. /// The attribute type. /// The list of attributes of the given type on the member. Empty list if none found. - public object[] GetCustomAttributes(Assembly assembly, Type type) => -#if NETFRAMEWORK - ReflectionOperationsNetFrameworkAttributeHelpers.GetCustomAttributes(assembly, type).ToArray(); -#else - assembly.GetCustomAttributes(type, inherit: true); -#endif + public object[] GetCustomAttributes(Assembly assembly, Type type) + => assembly.GetCustomAttributes(type, inherit: true); #pragma warning disable IL2070 // this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to 'target method'. #pragma warning disable IL2026 // Members attributed with RequiresUnreferencedCode may break when trimming @@ -92,4 +89,201 @@ public MethodInfo[] GetRuntimeMethods(Type type) #pragma warning restore IL2026 // Members attributed with RequiresUnreferencedCode may break when trimming #pragma warning restore IL2067 // 'target parameter' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to 'target method'. #pragma warning restore IL2057 // Unrecognized value passed to the typeName parameter of 'System.Type.GetType(String)' + + /// + /// Checks to see if a member or type is decorated with the given attribute, or an attribute that derives from it. e.g. [MyTestClass] from [TestClass] will match if you look for [TestClass]. The inherit parameter does not impact this checking. + /// + /// Attribute to search for. + /// Member to inspect for attributes. + /// True if the attribute of the specified type is defined on this member or a class. + public bool IsAttributeDefined(MemberInfo memberInfo) + where TAttribute : Attribute + { + 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; + } + + /// + /// Get attribute defined on a method which is of given type of subtype of given type. + /// + /// The attribute type. + /// The member to inspect. + /// An instance of the attribute. + public IEnumerable GetAttributes(ICustomAttributeProvider attributeProvider) + where TAttributeType : Attribute + { + Attribute[] attributes = GetCustomAttributesCached(attributeProvider); + + // Try to find the attribute that is derived from baseAttrType. + foreach (Attribute attribute in attributes) + { + DebugEx.Assert(attribute != null, "ReflectionOperations.DefinesAttributeDerivedFrom: internal error: wrong value in the attributes dictionary."); + + if (attribute is TAttributeType attributeAsAttributeType) + { + yield return attributeAsAttributeType; + } + } + } + + /// + /// Gets and caches the attributes for the given type, or method. + /// + /// The member to inspect. + /// attributes defined. + public Attribute[] GetCustomAttributesCached(ICustomAttributeProvider attributeProvider) + { + // If the information is cached, then use it otherwise populate the cache using + // the reflection APIs. + return _attributeCache.GetOrAdd(attributeProvider, GetAttributes); + + // We are avoiding func allocation here. + static Attribute[] GetAttributes(ICustomAttributeProvider attributeProvider) + { + // Populate the cache + try + { + object[]? attributes = NotCachedReflectionAccessor.GetCustomAttributesNotCached(attributeProvider); + return attributes is null ? [] : attributes as Attribute[] ?? [.. attributes.Cast()]; + } + catch (Exception ex) + { + // Get the exception description + string description; + try + { + // Can throw if the Message or StackTrace properties throw exceptions + description = ex.ToString(); + } + catch (Exception ex2) + { + description = string.Format(CultureInfo.CurrentCulture, Resource.ExceptionOccuredWhileGettingTheExceptionDescription, ex.GetType().FullName, ex2.GetType().FullName); // ex.GetType().FullName + + } + + if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsWarningEnabled) + { + PlatformServiceProvider.Instance.AdapterTraceLogger.Warning(Resource.FailedToGetCustomAttribute, attributeProvider.GetType().FullName!, description); + } + + return []; + } + } + } + + /// + /// Reflection helper that is accessing Reflection directly, and won't cache the results. + /// + internal static class NotCachedReflectionAccessor + { + /// + /// Get custom attributes on a member without cache. Be CAREFUL where you use this, repeatedly accessing reflection without caching the results degrades the performance. + /// + /// Member for which attributes needs to be retrieved. + /// All attributes of give type on member. + public static object[]? GetCustomAttributesNotCached(ICustomAttributeProvider attributeProvider) + { + IReflectionOperations reflectionOperations = PlatformServiceProvider.Instance.ReflectionOperations; + object[] attributesArray = attributeProvider is MemberInfo memberInfo + ? reflectionOperations.GetCustomAttributes(memberInfo) + : reflectionOperations.GetCustomAttributes((Assembly)attributeProvider, typeof(Attribute)); + + return attributesArray; // TODO: Investigate if we rely on NRE + } + } + + /// + public bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Type type) + => method.DeclaringType!.Assembly.Equals(type.Assembly); + + internal /* for tests */ void ClearCache() + // Tests manipulate the platform reflection provider, and we end up caching different attributes than the class / method actually has. + => _attributeCache.Clear(); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs index 2fc480dc1e..6e2a76abb0 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs @@ -4,7 +4,6 @@ #if !WINDOWS_UWP && !WIN_UI using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; @@ -35,7 +34,7 @@ internal sealed class TestDeployment : ITestDeployment /// Initializes a new instance of the class. /// public TestDeployment() - : this(new DeploymentItemUtility(ReflectHelper.Instance), new DeploymentUtility(), new FileUtility()) + : this(new DeploymentItemUtility((ReflectionOperations)PlatformServiceProvider.Instance.ReflectionOperations), new DeploymentUtility(), new FileUtility()) { } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs index 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/src/Adapter/MSTestAdapter.PlatformServices/Utilities/ReflectionOperationsNetFrameworkAttributeHelpers.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/ReflectionOperationsNetFrameworkAttributeHelpers.cs deleted file mode 100644 index 7dfd9fbf17..0000000000 --- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/ReflectionOperationsNetFrameworkAttributeHelpers.cs +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -#if NETFRAMEWORK - -namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; - -/// -/// This class is only intended to be used by ReflectionOperations on .NET Framework. -/// TODO: Investigate why we need complicated logic under .NET Framework, and whether it -/// can be simplified to have unified simple logic for .NET Core and .NET Framework. -/// -internal static class ReflectionOperationsNetFrameworkAttributeHelpers -{ - /// - /// Gets all the custom attributes adorned on a member. - /// - /// The member. - /// The list of attributes on the member. Empty list if none found. - internal static IReadOnlyList GetCustomAttributes(MemberInfo memberInfo) - => GetCustomAttributesCore(memberInfo, type: null); - - /// - /// Get custom attributes on a member for both normal and reflection only load. - /// - /// Member for which attributes needs to be retrieved. - /// Type of attribute to retrieve. - /// All attributes of give type on member. -#pragma warning disable CA1859 // Use concrete types when possible for improved performance - private static IReadOnlyList GetCustomAttributesCore(MemberInfo memberInfo, Type? type) -#pragma warning restore CA1859 - { - bool shouldGetAllAttributes = type == null; - - if (!IsReflectionOnlyLoad(memberInfo)) - { - return shouldGetAllAttributes ? memberInfo.GetCustomAttributes(inherit: true) : memberInfo.GetCustomAttributes(type, inherit: true); - } - else - { - List nonUniqueAttributes = []; - Dictionary uniqueAttributes = []; - - int inheritanceThreshold = 10; - int inheritanceLevel = 0; - - if (memberInfo.MemberType == MemberTypes.TypeInfo) - { - // This code is based on the code for fetching CustomAttributes in System.Reflection.CustomAttribute(RuntimeType type, RuntimeType caType, bool inherit) - var tempTypeInfo = memberInfo as TypeInfo; - - do - { - IList attributes = CustomAttributeData.GetCustomAttributes(tempTypeInfo); - AddNewAttributes( - attributes, - shouldGetAllAttributes, - type!, - uniqueAttributes, - nonUniqueAttributes); - tempTypeInfo = tempTypeInfo!.BaseType?.GetTypeInfo(); - inheritanceLevel++; - } - while (tempTypeInfo != null && tempTypeInfo != typeof(object).GetTypeInfo() - && inheritanceLevel < inheritanceThreshold); - } - else if (memberInfo.MemberType == MemberTypes.Method) - { - // This code is based on the code for fetching CustomAttributes in System.Reflection.CustomAttribute(RuntimeMethodInfo method, RuntimeType caType, bool inherit). - var tempMethodInfo = memberInfo as MethodInfo; - - do - { - IList attributes = CustomAttributeData.GetCustomAttributes(tempMethodInfo); - AddNewAttributes( - attributes, - shouldGetAllAttributes, - type!, - uniqueAttributes, - nonUniqueAttributes); - MethodInfo? baseDefinition = tempMethodInfo!.GetBaseDefinition(); - - if (baseDefinition != null - && string.Equals( - string.Concat(tempMethodInfo.DeclaringType.FullName, tempMethodInfo.Name), - string.Concat(baseDefinition.DeclaringType.FullName, baseDefinition.Name), StringComparison.Ordinal)) - { - break; - } - - tempMethodInfo = baseDefinition; - inheritanceLevel++; - } - while (tempMethodInfo != null && inheritanceLevel < inheritanceThreshold); - } - else - { - // Ideally we should not be reaching here. We only query for attributes on types/methods currently. - // Return the attributes that CustomAttributeData returns in this cases not considering inheritance. - IList firstLevelAttributes = - CustomAttributeData.GetCustomAttributes(memberInfo); - AddNewAttributes(firstLevelAttributes, shouldGetAllAttributes, type!, uniqueAttributes, nonUniqueAttributes); - } - - nonUniqueAttributes.AddRange(uniqueAttributes.Values); - return nonUniqueAttributes; - } - } - - internal static List GetCustomAttributes(Assembly assembly, Type type) - { - if (!assembly.ReflectionOnly) - { - return [.. assembly.GetCustomAttributes(type)]; - } - - List customAttributes = [.. CustomAttributeData.GetCustomAttributes(assembly)]; - - List attributesArray = []; - - foreach (CustomAttributeData attribute in customAttributes) - { - if (!IsTypeInheriting(attribute.Constructor.DeclaringType, type) - && !attribute.Constructor.DeclaringType.AssemblyQualifiedName.Equals( - type.AssemblyQualifiedName, StringComparison.Ordinal)) - { - continue; - } - - Attribute? attributeInstance = CreateAttributeInstance(attribute); - if (attributeInstance != null) - { - attributesArray.Add(attributeInstance); - } - } - - return attributesArray; - } - - /// - /// Create instance of the attribute for reflection only load. - /// - /// The attribute data. - /// An attribute. - private static Attribute? CreateAttributeInstance(CustomAttributeData attributeData) - { - object? attribute = null; - try - { - // Create instance of attribute. For some case, constructor param is returned as ReadOnlyCollection - // instead of array. So convert it to array else constructor invoke will fail. - var attributeType = Type.GetType(attributeData.Constructor.DeclaringType.AssemblyQualifiedName); - - List constructorParameters = []; - List constructorArguments = []; - foreach (CustomAttributeTypedArgument parameter in attributeData.ConstructorArguments) - { - var parameterType = Type.GetType(parameter.ArgumentType.AssemblyQualifiedName); - constructorParameters.Add(parameterType); - if (!parameterType.IsArray - || parameter.Value is not IEnumerable enumerable) - { - constructorArguments.Add(parameter.Value); - continue; - } - - ArrayList list = []; - foreach (object? item in enumerable) - { - if (item is CustomAttributeTypedArgument argument) - { - list.Add(argument.Value); - } - else - { - list.Add(item); - } - } - - constructorArguments.Add(list.ToArray(parameterType.GetElementType())); - } - - ConstructorInfo constructor = attributeType.GetConstructor([.. constructorParameters]); - attribute = constructor.Invoke([.. constructorArguments]); - - foreach (CustomAttributeNamedArgument namedArgument in attributeData.NamedArguments) - { - attributeType.GetProperty(namedArgument.MemberInfo.Name).SetValue(attribute, namedArgument.TypedValue.Value, null); - } - } - - // If not able to create instance of attribute ignore attribute. (May happen for custom user defined attributes). - catch (BadImageFormatException) - { - } - catch (FileLoadException) - { - } - catch (TypeLoadException) - { - } - - return attribute as Attribute; - } - - private static void AddNewAttributes( - IList customAttributes, - bool shouldGetAllAttributes, - Type type, - Dictionary uniqueAttributes, - List nonUniqueAttributes) - { - foreach (CustomAttributeData attribute in customAttributes) - { - if (!shouldGetAllAttributes - && !IsTypeInheriting(attribute.Constructor.DeclaringType, type) - && !attribute.Constructor.DeclaringType.AssemblyQualifiedName.Equals( - type.AssemblyQualifiedName, StringComparison.Ordinal)) - { - continue; - } - - Attribute? attributeInstance = CreateAttributeInstance(attribute); - if (attributeInstance == null) - { - continue; - } - - Type attributeType = attributeInstance.GetType(); - IReadOnlyList attributeUsageAttributes = GetCustomAttributesCore( - attributeType, - typeof(AttributeUsageAttribute)); - if (attributeUsageAttributes.Count > 0 - && attributeUsageAttributes[0] is AttributeUsageAttribute { AllowMultiple: false }) - { - if (!uniqueAttributes.ContainsKey(attributeType.FullName)) - { - uniqueAttributes.Add(attributeType.FullName, attributeInstance); - } - } - else - { - nonUniqueAttributes.Add(attributeInstance); - } - } - } - - /// - /// Check whether the member is loaded in a reflection only context. - /// - /// The member Info. - /// True if the member is loaded in a reflection only context. - private static bool IsReflectionOnlyLoad(MemberInfo? memberInfo) - => memberInfo != null && memberInfo.Module.Assembly.ReflectionOnly; - - private static bool IsTypeInheriting(Type? type1, Type type2) - { - while (type1 != null) - { - if (type1.AssemblyQualifiedName.Equals(type2.AssemblyQualifiedName, StringComparison.Ordinal)) - { - return true; - } - - type1 = type1.BaseType; - } - - return false; - } -} - -#endif diff --git a/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs b/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs index 1fc4f89cd2..7f7ba9c924 100644 --- a/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs +++ b/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/ReflectionUtilityTests.cs @@ -4,7 +4,6 @@ using AwesomeAssertions; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; using SampleFrameworkExtensions; @@ -12,9 +11,13 @@ namespace PlatformServices.Desktop.ComponentTests; +/// +/// Integration tests for ReflectionOperations which provides platform-specific reflection operations. +/// public class ReflectionUtilityTests : TestContainer { private readonly Assembly _testAsset; + private readonly ReflectionOperations _reflectionOperations = new(); public ReflectionUtilityTests() { @@ -30,62 +33,86 @@ public ReflectionUtilityTests() #endif currentAssemblyDirectory.Name /* TFM (e.g. net462) */, "TestProjectForDiscovery.dll"); - _testAsset = Assembly.ReflectionOnlyLoadFrom(testAssetPath); - - // This is needed for System assemblies. - AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ReflectionOnlyOnResolve; + _testAsset = Assembly.LoadFrom(testAssetPath); } public void GetCustomAttributesShouldReturnAllAttributes() { MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestBaseClass").GetMethod("DummyVTestMethod1"); - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(methodInfo); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); string[] expectedAttributes = ["TestCategory : base", "Owner : base"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().BeEquivalentTo(expectedAttributes); } public void GetCustomAttributesShouldReturnAllAttributesWithBaseInheritance() { MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass").GetMethod("DummyVTestMethod1"); - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(methodInfo); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo); attributes.Should().NotBeNull(); attributes.Should().HaveCount(3); // Notice that the Owner on the base method does not show up since it can only be defined once. string[] expectedAttributes = ["TestCategory : derived", "TestCategory : base", "Owner : derived"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().BeEquivalentTo(expectedAttributes); } public void GetCustomAttributesOnTypeShouldReturnAllAttributes() { Type type = _testAsset.GetType("TestProjectForDiscovery.AttributeTestBaseClass"); - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(type); + object[]? attributes = _reflectionOperations.GetCustomAttributes(type); attributes.Should().NotBeNull(); attributes.Should().HaveCount(1); string[] expectedAttributes = ["TestCategory : ba"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritance() { Type type = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass"); - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(type); + object[]? attributes = _reflectionOperations.GetCustomAttributes(type); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); string[] expectedAttributes = ["TestCategory : a", "TestCategory : ba"]; + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); + } + + public void GetSpecificCustomAttributesShouldReturnAllAttributes() + { + MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestBaseClass").GetMethod("DummyVTestMethod1"); + + TestCategoryAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); + + attributes.Should().NotBeNull(); + attributes.Should().HaveCount(1); + + string[] expectedAttributes = ["TestCategory : base"]; + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + } + + public void GetSpecificCustomAttributesShouldReturnAllAttributesWithBaseInheritance() + { + MethodInfo methodInfo = + _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass").GetMethod("DummyVTestMethod1"); + + TestCategoryAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); + + attributes.Should().NotBeNull(); + attributes.Should().HaveCount(2); + + string[] expectedAttributes = ["TestCategory : derived", "TestCategory : base"]; GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } @@ -93,33 +120,78 @@ public void GetCustomAttributesShouldReturnAllAttributesIncludingUserDefinedAttr { MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClassWithCustomAttributes").GetMethod("DummyVTestMethod1"); - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(methodInfo); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo); attributes.Should().NotBeNull(); attributes.Should().HaveCount(3); string[] expectedAttributes = ["Duration : superfast", "TestCategory : base", "Owner : base"]; + GetAttributeValuePairs(attributes!).Should().BeEquivalentTo(expectedAttributes); + } + + public void GetSpecificCustomAttributesShouldReturnAllAttributesIncludingUserDefinedAttributes() + { + MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClassWithCustomAttributes").GetMethod("DummyVTestMethod1"); + + TestPropertyAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); + + attributes.Should().NotBeNull(); + attributes.Should().HaveCount(2); + + string[] expectedAttributes = ["Duration : superfast", "Owner : base"]; GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } - public void GetSpecificCustomAttributesOnAssemblyShouldReturnAllAttributes() + public void GetSpecificCustomAttributesShouldReturnArrayAttributesAsWell() { - Assembly asm = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass").Assembly; + MethodInfo methodInfo = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClassWithCustomAttributes").GetMethod("DummyTestMethod2"); + + CategoryArrayAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); + + attributes.Should().NotBeNull(); + attributes.Should().HaveCount(1); + + string[] expectedAttributes = ["CategoryAttribute : foo,foo2"]; + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + } + + public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributes() + { + Type type = _testAsset.GetType("TestProjectForDiscovery.AttributeTestBaseClass"); + + TestCategoryAttribute[] attributes = _reflectionOperations.GetAttributes(type).ToArray(); + + attributes.Should().NotBeNull(); + attributes.Should().HaveCount(1); + + string[] expectedAttributes = ["TestCategory : ba"]; + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + } + + public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritance() + { + Type type = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass"); - object[] attributes = new ReflectionOperations().GetCustomAttributes(asm, typeof(TestCategoryAttribute)); + TestCategoryAttribute[] attributes = _reflectionOperations.GetAttributes(type).ToArray(); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); - string[] expectedAttributes = ["TestCategory : a1", "TestCategory : a2"]; + string[] expectedAttributes = ["TestCategory : a", "TestCategory : ba"]; GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } - private static Assembly ReflectionOnlyOnResolve(object sender, ResolveEventArgs args) + public void GetSpecificCustomAttributesOnAssemblyShouldReturnAllAttributes() { - string assemblyNameToLoad = AppDomain.CurrentDomain.ApplyPolicy(args.Name); + Assembly asm = _testAsset.GetType("TestProjectForDiscovery.AttributeTestClass").Assembly; + + object[] attributes = _reflectionOperations.GetCustomAttributes(asm, typeof(TestCategoryAttribute)); + + attributes.Should().NotBeNull(); + attributes.Should().HaveCount(2); - return Assembly.ReflectionOnlyLoad(assemblyNameToLoad); + string[] expectedAttributes = ["TestCategory : a1", "TestCategory : a2"]; + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } private static string[] GetAttributeValuePairs(IEnumerable attributes) diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs index 8439fb293b..653b5fc5d5 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs @@ -7,8 +7,9 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; @@ -322,13 +323,14 @@ internal sealed class TestableAssemblyEnumerator : AssemblyEnumerator { internal TestableAssemblyEnumerator() { - var reflectHelper = new Mock(); - var typeValidator = new Mock(reflectHelper.Object); - var testMethodValidator = new Mock(reflectHelper.Object, false); + var mockReflectionOperations = new Mock(); + IReflectionOperations wrappedReflectionOperations = MockableReflectionOperations.Create(mockReflectionOperations); + var typeValidator = new Mock(wrappedReflectionOperations); + var testMethodValidator = new Mock(wrappedReflectionOperations, false); MockTypeEnumerator = new Mock( typeof(DummyTestClass), "DummyAssembly", - reflectHelper.Object, + wrappedReflectionOperations, typeValidator.Object, testMethodValidator.Object); } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/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..8663f38b6c 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,8 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Discovery; public partial class TypeEnumeratorTests : TestContainer { - private readonly Mock _mockReflectHelper; + private readonly Mock _mockReflectionOperations; + private readonly IReflectionOperations _wrappedReflectionOperations; private readonly Mock _mockTestMethodValidator; private readonly Mock _mockTypeValidator; private readonly TestablePlatformServiceProvider _testablePlatformServiceProvider; @@ -28,13 +30,13 @@ public partial class TypeEnumeratorTests : TestContainer public TypeEnumeratorTests() { - _mockReflectHelper = new Mock - { - CallBase = true, - }; + _mockReflectionOperations = new Mock(); + _mockReflectionOperations.Setup(ro => ro.GetCustomAttributes(It.IsAny())) + .Returns(mi => mi.GetCustomAttributes(true)); + _wrappedReflectionOperations = MockableReflectionOperations.Create(_mockReflectionOperations); - _mockTypeValidator = new Mock(MockBehavior.Default, _mockReflectHelper.Object); - _mockTestMethodValidator = new Mock(MockBehavior.Default, _mockReflectHelper.Object, false); + _mockTypeValidator = new Mock(MockBehavior.Default, _wrappedReflectionOperations); + _mockTestMethodValidator = new Mock(MockBehavior.Default, _wrappedReflectionOperations, false); _warnings = []; _mockMessageLogger = new Mock(); @@ -492,15 +494,15 @@ private void SetupTestClassAndTestMethods(bool isValidTestClass, bool isValidTes .Returns(isValidTestClass); _mockTestMethodValidator.Setup( tmv => tmv.IsValidTestMethod(It.IsAny(), It.IsAny(), It.IsAny>())).Returns(isValidTestMethod); - _mockReflectHelper.Setup( - rh => rh.IsMethodDeclaredInSameAssemblyAsType(It.IsAny(), It.IsAny())).Returns(isMethodFromSameAssembly); + _mockReflectionOperations.Setup(ro => ro.IsMethodDeclaredInSameAssemblyAsType(It.IsAny(), It.IsAny())) + .Returns(isMethodFromSameAssembly); } private TypeEnumerator GetTypeEnumeratorInstance(Type type, string assemblyName) => new( type, assemblyName, - _mockReflectHelper.Object, + _wrappedReflectionOperations, _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..ef30abd9d5 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/ClassCleanupManagerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/ClassCleanupManagerTests.cs @@ -4,8 +4,8 @@ using AwesomeAssertions; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Moq; @@ -17,7 +17,6 @@ public class ClassCleanupManagerTests : TestContainer { public void AssemblyCleanupRunsAfterAllTestsFinishEvenIfWeScheduleTheSameTestMultipleTime() { - ReflectHelper reflectHelper = Mock.Of(); MethodInfo classCleanupMethodInfo = typeof(FakeTestClass).GetMethod(nameof(FakeTestClass.FakeClassCleanupMethod), BindingFlags.Instance | BindingFlags.NonPublic)!; // Full class name must agree between unitTestElement.TestMethod.FullClassName and testMethod.FullClassName; string fullClassName = typeof(FakeTestClass).FullName!; diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs index 8c601187e1..20c4a65f39 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs @@ -5,7 +5,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; @@ -58,8 +57,6 @@ public TestMethodRunnerTests() _testablePlatformServiceProvider = new TestablePlatformServiceProvider(); _testablePlatformServiceProvider.SetupMockReflectionOperations(); PlatformServiceProvider.Instance = _testablePlatformServiceProvider; - - ReflectHelper.Instance.ClearCache(); } private static TestClassInfo GetTestClassInfo() @@ -220,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]); @@ -255,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); @@ -282,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); @@ -311,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); @@ -335,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); @@ -361,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/TestPropertyAttributeTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs index b3579dd42c..d16bd026b0 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs @@ -4,8 +4,10 @@ using AwesomeAssertions; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; using Moq; @@ -16,13 +18,18 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Execution; public class TestPropertyAttributeTests : TestContainer { + private readonly TypeCache _typeCache; + private readonly ReflectionOperations _reflectionOperations; + public TestPropertyAttributeTests() { + _reflectionOperations = new ReflectionOperations(); + _typeCache = new TypeCache(_reflectionOperations); var testablePlatformServiceProvider = new TestablePlatformServiceProvider(); testablePlatformServiceProvider.MockFileOperations.Setup(x => x.LoadAssembly(It.IsAny())).Returns(GetType().Assembly); PlatformServiceProvider.Instance = testablePlatformServiceProvider; - ReflectHelper.Instance.ClearCache(); + _reflectionOperations.ClearCache(); } protected override void Dispose(bool disposing) @@ -35,11 +42,35 @@ protected override void Dispose(bool disposing) } } + private static TestContextImplementation CreateTestContextImplementationForMethod(TestMethod testMethod) + => new(testMethod, null, new Dictionary(), null, null); + + private static TestMethod CreateTestMethod(string methodName, string className, string assemblyName, string? displayName) + => new(className, methodName, null, methodName, className, assemblyName, displayName, null); + #region GetTestMethodInfo tests public void GetTestMethodInfoShouldAddPropertiesFromContainingClassCorrectly() { - TestPlatform.ObjectModel.Trait[] traits = [.. ReflectHelper.Instance.GetTestPropertiesAsTraits(typeof(DummyTestClassBase).GetMethod(nameof(DummyTestClassBase.VirtualTestMethodInBaseAndDerived))!)]; + string className = typeof(DummyTestClassBase).FullName!; + TestMethod testMethod = CreateTestMethod(nameof(DummyTestClassBase.VirtualTestMethodInBaseAndDerived), className, typeof(DummyTestClassBase).Assembly.GetName().Name!, displayName: null); + + TestContextImplementation testContext = CreateTestContextImplementationForMethod(testMethod); + + _ = _typeCache.GetTestMethodInfo( + testMethod, + testContext); + + testContext.TryGetPropertyValue("TestMethodKeyFromBase", out object? value1).Should().BeTrue(); + value1.Should().Be("TestMethodValueFromBase"); + + testContext.TryGetPropertyValue("DummyTestClassBaseKey1", out object? value2).Should().BeTrue(); + value2.Should().Be("DummyTestClassBaseValue1"); + + testContext.TryGetPropertyValue("DummyTestClassBaseKey2", out object? value3).Should().BeTrue(); + value3.Should().Be("DummyTestClassBaseValue2"); + + TestPlatform.ObjectModel.Trait[] traits = [.. _reflectionOperations.GetTestPropertiesAsTraits(typeof(DummyTestClassBase).GetMethod(nameof(DummyTestClassBase.VirtualTestMethodInBaseAndDerived))!)]; traits.Length.Should().Be(3); traits[0].Name.Should().Be("TestMethodKeyFromBase"); traits[0].Value.Should().Be("TestMethodValueFromBase"); @@ -51,7 +82,34 @@ public void GetTestMethodInfoShouldAddPropertiesFromContainingClassCorrectly() public void GetTestMethodInfoShouldAddPropertiesFromContainingClassAndBaseClassesAndOverriddenMethodsCorrectly_OverriddenIsTestMethod() { - TestPlatform.ObjectModel.Trait[] traits = [.. ReflectHelper.Instance.GetTestPropertiesAsTraits(typeof(DummyTestClassDerived).GetMethod(nameof(DummyTestClassDerived.VirtualTestMethodInBaseAndDerived))!)]; + string className = typeof(DummyTestClassDerived).FullName!; + TestMethod testMethod = CreateTestMethod(nameof(DummyTestClassDerived.VirtualTestMethodInBaseAndDerived), className, typeof(DummyTestClassBase).Assembly.GetName().Name!, displayName: null); + + TestContextImplementation testContext = CreateTestContextImplementationForMethod(testMethod); + + _ = _typeCache.GetTestMethodInfo( + testMethod, + testContext); + + testContext.TryGetPropertyValue("DerivedMethod1Key", out object? value1).Should().BeTrue(); + value1.Should().Be("DerivedMethod1Value"); + + testContext.TryGetPropertyValue("TestMethodKeyFromBase", out object? value2).Should().BeTrue(); + value2.Should().Be("TestMethodValueFromBase"); + + testContext.TryGetPropertyValue("DummyTestClassDerivedKey1", out object? value3).Should().BeTrue(); + value3.Should().Be("DummyTestClassValue1"); + + testContext.TryGetPropertyValue("DummyTestClassDerivedKey2", out object? value4).Should().BeTrue(); + value4.Should().Be("DummyTestClassValue2"); + + testContext.TryGetPropertyValue("DummyTestClassBaseKey1", out object? value5).Should().BeTrue(); + value5.Should().Be("DummyTestClassBaseValue1"); + + testContext.TryGetPropertyValue("DummyTestClassBaseKey2", out object? value6).Should().BeTrue(); + value6.Should().Be("DummyTestClassBaseValue2"); + + TestPlatform.ObjectModel.Trait[] traits = [.. _reflectionOperations.GetTestPropertiesAsTraits(typeof(DummyTestClassDerived).GetMethod(nameof(DummyTestClassDerived.VirtualTestMethodInBaseAndDerived))!)]; traits.Length.Should().Be(6); traits[0].Name.Should().Be("DerivedMethod1Key"); traits[0].Value.Should().Be("DerivedMethod1Value"); @@ -69,7 +127,34 @@ public void GetTestMethodInfoShouldAddPropertiesFromContainingClassAndBaseClasse public void GetTestMethodInfoShouldAddPropertiesFromContainingClassAndBaseClassesAndOverriddenMethodsCorrectly_OverriddenIsNotTestMethod() { - TestPlatform.ObjectModel.Trait[] traits = [.. ReflectHelper.Instance.GetTestPropertiesAsTraits(typeof(DummyTestClassDerived).GetMethod(nameof(DummyTestClassDerived.VirtualTestMethodInDerivedButNotTestMethodInBase))!)]; + string className = typeof(DummyTestClassDerived).FullName!; + TestMethod testMethod = CreateTestMethod(nameof(DummyTestClassDerived.VirtualTestMethodInDerivedButNotTestMethodInBase), className, typeof(DummyTestClassBase).Assembly.GetName().Name!, displayName: null); + + TestContextImplementation testContext = CreateTestContextImplementationForMethod(testMethod); + + _ = _typeCache.GetTestMethodInfo( + testMethod, + testContext); + + testContext.TryGetPropertyValue("DerivedMethod2Key", out object? value1).Should().BeTrue(); + value1.Should().Be("DerivedMethod2Value"); + + testContext.TryGetPropertyValue("NonTestMethodKeyFromBase", out object? value2).Should().BeTrue(); + value2.Should().Be("NonTestMethodValueFromBase"); + + testContext.TryGetPropertyValue("DummyTestClassDerivedKey1", out object? value3).Should().BeTrue(); + value3.Should().Be("DummyTestClassValue1"); + + testContext.TryGetPropertyValue("DummyTestClassDerivedKey2", out object? value4).Should().BeTrue(); + value4.Should().Be("DummyTestClassValue2"); + + testContext.TryGetPropertyValue("DummyTestClassBaseKey1", out object? value5).Should().BeTrue(); + value5.Should().Be("DummyTestClassBaseValue1"); + + testContext.TryGetPropertyValue("DummyTestClassBaseKey2", out object? value6).Should().BeTrue(); + value6.Should().Be("DummyTestClassBaseValue2"); + + TestPlatform.ObjectModel.Trait[] traits = [.. _reflectionOperations.GetTestPropertiesAsTraits(typeof(DummyTestClassDerived).GetMethod(nameof(DummyTestClassDerived.VirtualTestMethodInDerivedButNotTestMethodInBase))!)]; traits.Length.Should().Be(6); traits[0].Name.Should().Be("DerivedMethod2Key"); traits[0].Value.Should().Be("DerivedMethod2Value"); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTests.cs index bce06c2d3e..8c40928541 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTests.cs @@ -5,9 +5,9 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; @@ -23,22 +23,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 +152,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 +169,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 +188,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 +204,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 +222,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 +241,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 +260,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 +282,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 +311,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 +341,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 +352,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 +366,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 +383,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 +405,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 +431,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 +452,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 +474,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 +499,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 +541,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 +597,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 +626,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 +655,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 +674,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 +693,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 +723,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 +743,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 +763,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 +810,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 +828,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 +850,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 +877,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 +943,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 +987,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)); @@ -998,13 +1000,51 @@ public void GetTestMethodInfoShouldReturnTestMethodInfoForMethodsAdornedWithADer testMethodInfo.Executor.Should().BeOfType(); } + public void GetTestMethodInfoShouldSetTestContextWithCustomProperty() + { + // 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("TestMethodWithCustomProperty")!; + TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); + + TestContextImplementation testContext = CreateTestContextImplementationForMethod(testMethod); + + typeCache.GetTestMethodInfo(testMethod, testContext); + KeyValuePair customProperty = testContext.Properties.FirstOrDefault(p => p.Key.Equals("WhoAmI", StringComparison.Ordinal)); + + customProperty.Should().NotBeNull(); + (customProperty.Value as string).Should().Be("Me"); + } + + public void GetTestMethodInfoShouldNotAddDuplicateTestPropertiesToTestContext() + { + // 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("TestMethodWithDuplicateCustomPropertyNames")!; + TestMethod testMethod = CreateTestMethod(methodInfo.Name, type.FullName!, "A", displayName: null); + TestContextImplementation testContext = CreateTestContextImplementationForMethod(testMethod); + + TestMethodInfo? testMethodInfo = typeCache.GetTestMethodInfo(testMethod, testContext); + + testMethodInfo.Should().NotBeNull(); + + // Verify that the first value gets set. + testContext.Properties.TryGetValue("WhoAmI", out object? value).Should().BeTrue(); + value.Should().Be("Me"); + } + public void GetTestMethodInfoShouldReturnTestMethodInfoForDerivedTestClasses() { Type type = typeof(DerivedTestClass); 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)); @@ -1021,7 +1061,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)); @@ -1039,7 +1080,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)); @@ -1071,10 +1113,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( @@ -1092,10 +1134,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( @@ -1125,10 +1167,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( @@ -1146,10 +1188,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( @@ -1232,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/Execution/UnitTestRunnerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/UnitTestRunnerTests.cs index ffab723596..6ecaf4cbd7 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/UnitTestRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/UnitTestRunnerTests.cs @@ -5,8 +5,9 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; @@ -278,16 +279,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); @@ -349,7 +350,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..3777514177 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Extensions/MethodInfoExtensionsTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Extensions/MethodInfoExtensionsTests.cs @@ -3,9 +3,12 @@ using AwesomeAssertions; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using TestFramework.ForTestingMSTest; @@ -16,60 +19,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 +84,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 +136,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 +188,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 +268,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 +302,13 @@ public void IsVoidOrTaskReturnTypeShouldReturnTrueForAsyncNonTaskMethods() public void GetAsyncTypeNameShouldReturnNullForVoidMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicMethod")!; - methodInfo.GetAsyncTypeName().Should().BeNull(); + methodInfo.GetAsyncTypeName(_reflectionOperations).Should().BeNull(); } public void GetAsyncTypeNameShouldReturnStateMachineTypeNameForAsyncMethods() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("PublicAsyncVoidMethod")!; - methodInfo.GetAsyncTypeName()!.Should().StartWith("Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Extensions.MethodInfoExtensionsTests+DummyTestClass+"); + methodInfo.GetAsyncTypeName(_reflectionOperations)!.Should().StartWith("Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Extensions.MethodInfoExtensionsTests+DummyTestClass+"); } #endregion diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Helpers/ReflectHelperTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Helpers/ReflectHelperTests.cs deleted file mode 100644 index 89979ac6df..0000000000 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Helpers/ReflectHelperTests.cs +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using AwesomeAssertions; - -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; - -using Moq; - -using TestFramework.ForTestingMSTest; - -namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests; - -public class ReflectHelperTests : TestContainer -{ - private readonly ReflectHelper _reflectHelper; - private readonly AttributeMockingHelper _attributeMockingHelper; - private readonly Mock _method; - private readonly TestablePlatformServiceProvider _testablePlatformServiceProvider; - - public ReflectHelperTests() - { - _reflectHelper = new(); - _method = new Mock(); - _method.Setup(x => x.MemberType).Returns(MemberTypes.Method); - - _testablePlatformServiceProvider = new TestablePlatformServiceProvider(); - _testablePlatformServiceProvider.SetupMockReflectionOperations(); - _attributeMockingHelper = new(_testablePlatformServiceProvider.MockReflectionOperations); - - PlatformServiceProvider.Instance = _testablePlatformServiceProvider; - } - - protected override void Dispose(bool disposing) - { - if (!IsDisposed) - { - base.Dispose(disposing); - PlatformServiceProvider.Instance = null; - } - } - - /// - /// Testing test category attribute adorned at class level. - /// - public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtClassLevel() - { - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel")], MemberTypes.TypeInfo); - - string[] expected = ["ClassLevel"]; - string[] actual = [.. _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests))]; - - expected.SequenceEqual(actual).Should().BeTrue(); - } - - /// - /// Testing test category attributes adorned at class, assembly and method level are getting collected. - /// - public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtAllLevels() - { - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel1"), new TestCategoryAttribute("AsmLevel2")], MemberTypes.All); - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel3")], MemberTypes.All); - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel")], MemberTypes.TypeInfo); - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("MethodLevel")], MemberTypes.Method); - - string[] actual = [.. _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests))]; - string[] expected = ["MethodLevel", "ClassLevel", "AsmLevel1", "AsmLevel2", "AsmLevel3"]; - - expected.SequenceEqual(actual).Should().BeTrue(); - } - - /// - /// Testing test category attributes adorned at class, assembly and method level are getting collected. - /// - public void GetTestCategoryAttributeShouldConcatCustomAttributeOfSameType() - { - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel1")], MemberTypes.All); - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel2")], MemberTypes.All); - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel1")], MemberTypes.TypeInfo); - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel2")], MemberTypes.TypeInfo); - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("MethodLevel1")], MemberTypes.Method); - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("MethodLevel2")], MemberTypes.Method); - - string[] actual = [.. _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests))]; - string[] expected = ["MethodLevel1", "MethodLevel2", "ClassLevel1", "ClassLevel2", "AsmLevel1", "AsmLevel2"]; - - expected.SequenceEqual(actual).Should().BeTrue(); - } - - /// - /// Testing test category attributes adorned at assembly level. - /// - public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtAssemblyLevel() - { - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel")], MemberTypes.All); - - string[] expected = ["AsmLevel"]; - - string[] actual = [.. _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests))]; - - expected.SequenceEqual(actual).Should().BeTrue(); - } - - /// - /// Testing multiple test category attribute adorned at class level. - /// - public void GetTestCategoryAttributeShouldIncludeMultipleTestCategoriesAtClassLevel() - { - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel"), new TestCategoryAttribute("ClassLevel1")], MemberTypes.TypeInfo); - - string[] expected = ["ClassLevel", "ClassLevel1"]; - string[] actual = [.. _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests))]; - - expected.SequenceEqual(actual).Should().BeTrue(); - } - - /// - /// Testing multiple test category attributes adorned at assembly level. - /// - public void GetTestCategoryAttributeShouldIncludeMultipleTestCategoriesAtAssemblyLevel() - { - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel"), new TestCategoryAttribute("AsmLevel1")], MemberTypes.All); - - string[] expected = ["AsmLevel", "AsmLevel1"]; - string[] actual = [.. _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests))]; - expected.SequenceEqual(actual).Should().BeTrue(); - } - - /// - /// Testing test category attributes adorned at method level - regression. - /// - public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtMethodLevel() - { - _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("MethodLevel")], MemberTypes.Method); - - string[] expected = ["MethodLevel"]; - string[] actual = [.. _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests))]; - - expected.SequenceEqual(actual).Should().BeTrue(); - } - - public void IsAttributeDefinedShouldReturnTrueIfSpecifiedAttributeIsDefinedOnAMember() - { - var rh = new ReflectHelper(); - var mockMemberInfo = new Mock(); - var attributes = new Attribute[] { new TestMethodAttribute() }; - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). - Returns(attributes); - - rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeTrue(); - } - - public void IsAttributeDefinedShouldReturnFalseIfSpecifiedAttributeIsNotDefinedOnAMember() - { - var rh = new ReflectHelper(); - var mockMemberInfo = new Mock(); - var attributes = new Attribute[] { new TestClassAttribute() }; - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). - Returns(attributes); - - rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeFalse(); - } - - public void IsAttributeDefinedShouldReturnFromCache() - { - var rh = new ReflectHelper(); - - // Not using mocks here because for some reason a dictionary match of the mock is not returning true in the product code. - MethodInfo memberInfo = typeof(ReflectHelperTests).GetMethod("IsAttributeDefinedShouldReturnFromCache")!; - - // new Mock(); - var attributes = new Attribute[] { new TestMethodAttribute() }; - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(memberInfo)). - Returns(attributes); - - rh.IsAttributeDefined(memberInfo).Should().BeTrue(); - - // Validate that reflection APIs are not called again. - rh.IsAttributeDefined(memberInfo).Should().BeTrue(); - _testablePlatformServiceProvider.MockReflectionOperations.Verify(ro => ro.GetCustomAttributes(memberInfo), Times.Once); - } - - public void HasAttributeDerivedFromShouldReturnTrueIfSpecifiedAttributeIsDefinedOnAMember() - { - var rh = new ReflectHelper(); - var mockMemberInfo = new Mock(); - var attributes = new Attribute[] { new TestableExtendedTestMethod() }; - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). - Returns(attributes); - - rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeTrue(); - } - - public void HasAttributeDerivedFromShouldReturnFalseIfSpecifiedAttributeIsNotDefinedOnAMember() - { - var rh = new ReflectHelper(); - var mockMemberInfo = new Mock(); - var attributes = new Attribute[] { new TestableExtendedTestMethod() }; - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). - Returns(attributes); - - rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeFalse(); - } - - public void HasAttributeDerivedFromShouldReturnFromCache() - { - var rh = new ReflectHelper(); - - // Not using mocks here because for some reason a dictionary match of the mock is not returning true in the product code. - MethodInfo memberInfo = typeof(ReflectHelperTests).GetMethod("HasAttributeDerivedFromShouldReturnFromCache")!; - - // new Mock(); - var attributes = new Attribute[] { new TestableExtendedTestMethod() }; - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(memberInfo)). - Returns(attributes); - - rh.IsAttributeDefined(memberInfo).Should().BeTrue(); - - // Validate that reflection APIs are not called again. - rh.IsAttributeDefined(memberInfo).Should().BeTrue(); - _testablePlatformServiceProvider.MockReflectionOperations.Verify(ro => ro.GetCustomAttributes(memberInfo), Times.Once); - } - - public void HasAttributeDerivedFromShouldReturnFalseQueryingProvidedAttributesExistenceIfGettingAllAttributesFail() - { - var rh = new ReflectHelper(); - var mockMemberInfo = new Mock(); - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). - Returns((object[])null!); - - rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeFalse(); - } - - internal class AttributeMockingHelper - { - public AttributeMockingHelper(Mock mockReflectionOperations) => _mockReflectionOperations = mockReflectionOperations; - - /// - /// A collection to hold mock custom attributes. - /// MemberTypes.All for assembly level - /// MemberTypes.TypeInfo for class level - /// MemberTypes.Method for method level. - /// - private readonly List<(Type Type, Attribute Attribute, MemberTypes MemberType)> _data = []; - private readonly Mock _mockReflectionOperations; - - public void SetCustomAttribute(Type type, Attribute[] values, MemberTypes memberTypes) - { - foreach (Attribute attribute in values) - { - _data.Add((type, attribute, memberTypes)); - } - - _mockReflectionOperations.Setup(r => r.GetCustomAttributes(It.IsAny())) - .Returns(GetCustomAttributesNotCached); - _mockReflectionOperations.Setup(r => r.GetCustomAttributes(It.IsAny(), It.IsAny())) - .Returns((assembly, _) => GetCustomAttributesNotCached(assembly)); - } - - public object[] GetCustomAttributesNotCached(ICustomAttributeProvider attributeProvider) - { - var foundAttributes = new List(); - foreach ((Type Type, Attribute Attribute, MemberTypes MemberType) attributeData in _data) - { - if (attributeProvider is MethodInfo && (attributeData.MemberType == MemberTypes.Method)) - { - foundAttributes.Add(attributeData.Attribute); - } - else if (attributeProvider is TypeInfo && (attributeData.MemberType == MemberTypes.TypeInfo)) - { - foundAttributes.Add(attributeData.Attribute); - } - else if (attributeProvider is Assembly && attributeData.MemberType == MemberTypes.All) - { - foundAttributes.Add(attributeData.Attribute); - } - } - - return foundAttributes.ToArray(); - } - } -} - -#region Dummy Implementations - -public class TestableExtendedTestMethod : TestMethodAttribute; - -#endregion diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopReflectionOperationsTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopReflectionOperationsTests.cs index b92337a744..c06086479a 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopReflectionOperationsTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopReflectionOperationsTests.cs @@ -38,10 +38,12 @@ public void GetCustomAttributesOnTypeShouldReturnAllAttributes() object[] attributes = _reflectionOperations.GetCustomAttributes(type); attributes.Should().NotBeNull(); - attributes.Length.Should().Be(1); + // Filter to only our test attributes (excludes compiler-generated attributes like NullableContextAttribute) + List testAttributes = ReflectionUtilityTests.GetAttributeValuePairs(attributes); + testAttributes.Count.Should().Be(1); string[] expectedAttributes = ["DummyA : ba"]; - expectedAttributes.SequenceEqual(ReflectionUtilityTests.GetAttributeValuePairs(attributes)).Should().BeTrue(); + expectedAttributes.SequenceEqual(testAttributes).Should().BeTrue(); } public void GetSpecificCustomAttributesOnAssemblyShouldReturnAllAttributes() diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs index ddd508f5c6..fd2706ae2c 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs @@ -4,9 +4,9 @@ #if NETFRAMEWORK using AwesomeAssertions; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; @@ -24,6 +24,7 @@ public class DesktopTestDeploymentTests : TestContainer private const string DefaultDeploymentItemPath = @"c:\temp"; private const string DefaultDeploymentItemOutputDirectory = "out"; + private readonly Mock _mockReflectionOperations; private readonly Mock _mockFileUtility; #pragma warning disable IDE0052 // Remove unread private members @@ -32,6 +33,7 @@ public class DesktopTestDeploymentTests : TestContainer public DesktopTestDeploymentTests() { + _mockReflectionOperations = new Mock(); _mockFileUtility = new Mock(); _warnings = []; @@ -116,6 +118,21 @@ public void DeployShouldCreateDeploymentDirectories() #region private methods +#pragma warning disable IDE0051 // Remove unused private members + private void SetupDeploymentItems(ICustomAttributeProvider attributeProvider, KeyValuePair[] deploymentItems) +#pragma warning restore IDE0051 // Remove unused private members + { + var deploymentItemAttributes = new List(); + + foreach (KeyValuePair deploymentItem in deploymentItems) + { + deploymentItemAttributes.Add(new DeploymentItemAttribute(deploymentItem.Key, deploymentItem.Value)); + } + + _mockReflectionOperations.Setup( + ru => ru.GetAttributes(attributeProvider)).Returns(deploymentItemAttributes); + } + private TestCase GetTestCase(string source) { var testCase = new TestCase("A.C.M", new Uri("executor://testExecutor"), source); @@ -148,7 +165,7 @@ private TestDeployment CreateAndSetupDeploymentRelatedUtilities(out TestRunDirec _mockFileUtility.Setup(fu => fu.GetNextIterationDirectoryName(It.IsAny(), It.IsAny())) .Returns(testRunDirectories.RootDeploymentDirectory); - var deploymentItemUtility = new DeploymentItemUtility(new ReflectHelper()); + var deploymentItemUtility = new DeploymentItemUtility(_mockReflectionOperations.Object); return new TestDeployment( deploymentItemUtility, diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs index be9a7893b4..2ae240e2bb 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs @@ -3,7 +3,13 @@ using AwesomeAssertions; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; + +using Moq; using TestFramework.ForTestingMSTest; @@ -12,8 +18,33 @@ namespace MSTestAdapter.PlatformServices.UnitTests.Services; public class ReflectionOperationsTests : TestContainer { private readonly ReflectionOperations _reflectionOperations; + private readonly AttributeMockingHelper _attributeMockingHelper; + private readonly Mock _method; + private readonly TestablePlatformServiceProvider _testablePlatformServiceProvider; + + public ReflectionOperationsTests() + { + _reflectionOperations = new ReflectionOperations(); + _method = new Mock(); + _method.Setup(x => x.MemberType).Returns(MemberTypes.Method); + + _testablePlatformServiceProvider = new TestablePlatformServiceProvider(); + _testablePlatformServiceProvider.SetupMockReflectionOperations(); + _attributeMockingHelper = new(_testablePlatformServiceProvider.MockReflectionOperations); + + PlatformServiceProvider.Instance = _testablePlatformServiceProvider; + } + + protected override void Dispose(bool disposing) + { + if (!IsDisposed) + { + base.Dispose(disposing); + PlatformServiceProvider.Instance = null; + } + } - public ReflectionOperationsTests() => _reflectionOperations = new ReflectionOperations(); + #region GetCustomAttributes Tests public void GetCustomAttributesShouldReturnAllAttributes() { @@ -84,6 +115,223 @@ public void GetSpecificCustomAttributesOnAssemblyShouldReturnAllAttributes() GetAttributeValuePairs(attributes).SequenceEqual(expectedAttributes).Should().BeTrue(); } + #endregion + + #region GetTestCategories Tests + + /// + /// Testing test category attribute adorned at class level. + /// + public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtClassLevel() + { + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel")], MemberTypes.TypeInfo); + + string[] expected = ["ClassLevel"]; + string[] actual = [.. _reflectionOperations.GetTestCategories(_method.Object, typeof(ReflectionOperationsTests))]; + + expected.SequenceEqual(actual).Should().BeTrue(); + } + + /// + /// Testing test category attributes adorned at class, assembly and method level are getting collected. + /// + public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtAllLevels() + { + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel1"), new TestCategoryAttribute("AsmLevel2")], MemberTypes.All); + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel3")], MemberTypes.All); + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel")], MemberTypes.TypeInfo); + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("MethodLevel")], MemberTypes.Method); + + string[] actual = [.. _reflectionOperations.GetTestCategories(_method.Object, typeof(ReflectionOperationsTests))]; + string[] expected = ["MethodLevel", "ClassLevel", "AsmLevel1", "AsmLevel2", "AsmLevel3"]; + + expected.SequenceEqual(actual).Should().BeTrue(); + } + + /// + /// Testing test category attributes adorned at class, assembly and method level are getting collected. + /// + public void GetTestCategoryAttributeShouldConcatCustomAttributeOfSameType() + { + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel1")], MemberTypes.All); + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel2")], MemberTypes.All); + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel1")], MemberTypes.TypeInfo); + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel2")], MemberTypes.TypeInfo); + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("MethodLevel1")], MemberTypes.Method); + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("MethodLevel2")], MemberTypes.Method); + + string[] actual = [.. _reflectionOperations.GetTestCategories(_method.Object, typeof(ReflectionOperationsTests))]; + string[] expected = ["MethodLevel1", "MethodLevel2", "ClassLevel1", "ClassLevel2", "AsmLevel1", "AsmLevel2"]; + + expected.SequenceEqual(actual).Should().BeTrue(); + } + + /// + /// Testing test category attributes adorned at assembly level. + /// + public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtAssemblyLevel() + { + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel")], MemberTypes.All); + + string[] expected = ["AsmLevel"]; + + string[] actual = [.. _reflectionOperations.GetTestCategories(_method.Object, typeof(ReflectionOperationsTests))]; + + expected.SequenceEqual(actual).Should().BeTrue(); + } + + /// + /// Testing multiple test category attribute adorned at class level. + /// + public void GetTestCategoryAttributeShouldIncludeMultipleTestCategoriesAtClassLevel() + { + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("ClassLevel"), new TestCategoryAttribute("ClassLevel1")], MemberTypes.TypeInfo); + + string[] expected = ["ClassLevel", "ClassLevel1"]; + string[] actual = [.. _reflectionOperations.GetTestCategories(_method.Object, typeof(ReflectionOperationsTests))]; + + expected.SequenceEqual(actual).Should().BeTrue(); + } + + /// + /// Testing multiple test category attributes adorned at assembly level. + /// + public void GetTestCategoryAttributeShouldIncludeMultipleTestCategoriesAtAssemblyLevel() + { + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("AsmLevel"), new TestCategoryAttribute("AsmLevel1")], MemberTypes.All); + + string[] expected = ["AsmLevel", "AsmLevel1"]; + string[] actual = [.. _reflectionOperations.GetTestCategories(_method.Object, typeof(ReflectionOperationsTests))]; + expected.SequenceEqual(actual).Should().BeTrue(); + } + + /// + /// Testing test category attributes adorned at method level - regression. + /// + public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtMethodLevel() + { + _attributeMockingHelper.SetCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("MethodLevel")], MemberTypes.Method); + + string[] expected = ["MethodLevel"]; + string[] actual = [.. _reflectionOperations.GetTestCategories(_method.Object, typeof(ReflectionOperationsTests))]; + + expected.SequenceEqual(actual).Should().BeTrue(); + } + + #endregion + + #region IsAttributeDefined Tests + + public void IsAttributeDefinedShouldReturnTrueIfSpecifiedAttributeIsDefinedOnAMember() + { + var rh = new ReflectionOperations(); + var mockMemberInfo = new Mock(); + var attributes = new Attribute[] { new TestMethodAttribute() }; + + _testablePlatformServiceProvider.MockReflectionOperations. + Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). + Returns(attributes); + + rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeTrue(); + } + + public void IsAttributeDefinedShouldReturnFalseIfSpecifiedAttributeIsNotDefinedOnAMember() + { + var rh = new ReflectionOperations(); + var mockMemberInfo = new Mock(); + var attributes = new Attribute[] { new TestClassAttribute() }; + + _testablePlatformServiceProvider.MockReflectionOperations. + Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). + Returns(attributes); + + rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeFalse(); + } + + public void IsAttributeDefinedShouldReturnFromCache() + { + var rh = new ReflectionOperations(); + + // Not using mocks here because for some reason a dictionary match of the mock is not returning true in the product code. + MethodInfo memberInfo = typeof(ReflectionOperationsTests).GetMethod("IsAttributeDefinedShouldReturnFromCache")!; + + // new Mock(); + var attributes = new Attribute[] { new TestMethodAttribute() }; + + _testablePlatformServiceProvider.MockReflectionOperations. + Setup(ro => ro.GetCustomAttributes(memberInfo)). + Returns(attributes); + + rh.IsAttributeDefined(memberInfo).Should().BeTrue(); + + // Validate that reflection APIs are not called again. + rh.IsAttributeDefined(memberInfo).Should().BeTrue(); + _testablePlatformServiceProvider.MockReflectionOperations.Verify(ro => ro.GetCustomAttributes(memberInfo), Times.Once); + } + + public void HasAttributeDerivedFromShouldReturnTrueIfSpecifiedAttributeIsDefinedOnAMember() + { + var rh = new ReflectionOperations(); + var mockMemberInfo = new Mock(); + var attributes = new Attribute[] { new TestableExtendedTestMethod() }; + + _testablePlatformServiceProvider.MockReflectionOperations. + Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). + Returns(attributes); + + rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeTrue(); + } + + public void HasAttributeDerivedFromShouldReturnFalseIfSpecifiedAttributeIsNotDefinedOnAMember() + { + var rh = new ReflectionOperations(); + var mockMemberInfo = new Mock(); + var attributes = new Attribute[] { new TestableExtendedTestMethod() }; + + _testablePlatformServiceProvider.MockReflectionOperations. + Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). + Returns(attributes); + + rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeFalse(); + } + + public void HasAttributeDerivedFromShouldReturnFromCache() + { + var rh = new ReflectionOperations(); + + // Not using mocks here because for some reason a dictionary match of the mock is not returning true in the product code. + MethodInfo memberInfo = typeof(ReflectionOperationsTests).GetMethod("HasAttributeDerivedFromShouldReturnFromCache")!; + + // new Mock(); + var attributes = new Attribute[] { new TestableExtendedTestMethod() }; + + _testablePlatformServiceProvider.MockReflectionOperations. + Setup(ro => ro.GetCustomAttributes(memberInfo)). + Returns(attributes); + + rh.IsAttributeDefined(memberInfo).Should().BeTrue(); + + // Validate that reflection APIs are not called again. + rh.IsAttributeDefined(memberInfo).Should().BeTrue(); + _testablePlatformServiceProvider.MockReflectionOperations.Verify(ro => ro.GetCustomAttributes(memberInfo), Times.Once); + } + + public void HasAttributeDerivedFromShouldReturnFalseQueryingProvidedAttributesExistenceIfGettingAllAttributesFail() + { + var rh = new ReflectionOperations(); + var mockMemberInfo = new Mock(); + + _testablePlatformServiceProvider.MockReflectionOperations. + Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object)). + Returns((object[])null!); + + rh.IsAttributeDefined(mockMemberInfo.Object).Should().BeFalse(); + } + + #endregion + + #region Helpers + private static string[] GetAttributeValuePairs(object[] attributes) { var attribValuePairs = new List(); @@ -102,6 +350,59 @@ private static string[] GetAttributeValuePairs(object[] attributes) return [.. attribValuePairs]; } + internal class AttributeMockingHelper + { + public AttributeMockingHelper(Mock mockReflectionOperations) => _mockReflectionOperations = mockReflectionOperations; + + /// + /// A collection to hold mock custom attributes. + /// MemberTypes.All for assembly level + /// MemberTypes.TypeInfo for class level + /// MemberTypes.Method for method level. + /// + private readonly List<(Type Type, Attribute Attribute, MemberTypes MemberType)> _data = []; + private readonly Mock _mockReflectionOperations; + + public void SetCustomAttribute(Type type, Attribute[] values, MemberTypes memberTypes) + { + foreach (Attribute attribute in values) + { + _data.Add((type, attribute, memberTypes)); + } + + _mockReflectionOperations.Setup(r => r.GetCustomAttributes(It.IsAny())) + .Returns(GetCustomAttributesNotCached); + _mockReflectionOperations.Setup(r => r.GetCustomAttributes(It.IsAny(), It.IsAny())) + .Returns((assembly, _) => GetCustomAttributesNotCached(assembly)); + } + + public object[] GetCustomAttributesNotCached(ICustomAttributeProvider attributeProvider) + { + var foundAttributes = new List(); + foreach ((Type Type, Attribute Attribute, MemberTypes MemberType) attributeData in _data) + { + if (attributeProvider is MethodInfo && (attributeData.MemberType == MemberTypes.Method)) + { + foundAttributes.Add(attributeData.Attribute); + } + else if (attributeProvider is TypeInfo && (attributeData.MemberType == MemberTypes.TypeInfo)) + { + foundAttributes.Add(attributeData.Attribute); + } + else if (attributeProvider is Assembly && attributeData.MemberType == MemberTypes.All) + { + foundAttributes.Add(attributeData.Attribute); + } + } + + return foundAttributes.ToArray(); + } + } + + #endregion + + #region Dummy Test Classes + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] public class DummyAAttribute : Attribute { @@ -146,7 +447,13 @@ public void DummyTestMethod2() { } } + + #endregion } -#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName +#region Dummy Implementations + +public class TestableExtendedTestMethod : TestMethodAttribute; + +#endregion diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs index 4ac9f1a50c..9169fb6262 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs @@ -4,9 +4,9 @@ #if !WINDOWS_UWP && !WIN_UI using AwesomeAssertions; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; @@ -25,7 +25,7 @@ public class TestDeploymentTests : TestContainer private const string DefaultDeploymentItemPath = @"c:\temp"; private const string DefaultDeploymentItemOutputDirectory = "out"; - private readonly Mock _mockReflectHelper; + private readonly Mock _mockReflectionOperations; private readonly Mock _mockFileUtility; #pragma warning disable IDE0044 // Add readonly modifier @@ -34,7 +34,7 @@ public class TestDeploymentTests : TestContainer public TestDeploymentTests() { - _mockReflectHelper = new Mock(); + _mockReflectionOperations = new Mock(); _mockFileUtility = new Mock(); _warnings = []; @@ -53,7 +53,7 @@ public void GetDeploymentItemsReturnsNullWhenNoDeploymentItems() public void GetDeploymentItemsReturnsDeploymentItems() { // Arrange. - var testDeployment = new TestDeployment(new DeploymentItemUtility(_mockReflectHelper.Object), null!, null!); + var testDeployment = new TestDeployment(new DeploymentItemUtility(_mockReflectionOperations.Object), null!, null!); // setup mocks KeyValuePair[] methodLevelDeploymentItems = @@ -182,7 +182,7 @@ public void DeployShouldReturnFalseWhenDeploymentEnabledSetToFalseButHasDeployme testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, kvpArray); var testDeployment = new TestDeployment( - new DeploymentItemUtility(_mockReflectHelper.Object), + new DeploymentItemUtility(_mockReflectionOperations.Object), new DeploymentUtility(), _mockFileUtility.Object); @@ -205,7 +205,7 @@ public void DeployShouldReturnFalseWhenDeploymentEnabledSetToFalseAndHasNoDeploy var testCase = new TestCase("A.C.M", new Uri("executor://testExecutor"), "path/to/asm.dll"); testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, null); var testDeployment = new TestDeployment( - new DeploymentItemUtility(_mockReflectHelper.Object), + new DeploymentItemUtility(_mockReflectionOperations.Object), new DeploymentUtility(), _mockFileUtility.Object); @@ -228,7 +228,7 @@ public void DeployShouldReturnFalseWhenDeploymentEnabledSetToTrueButHasNoDeploym var testCase = new TestCase("A.C.M", new Uri("executor://testExecutor"), "path/to/asm.dll"); testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, null); var testDeployment = new TestDeployment( - new DeploymentItemUtility(_mockReflectHelper.Object), + new DeploymentItemUtility(_mockReflectionOperations.Object), new DeploymentUtility(), _mockFileUtility.Object); @@ -258,7 +258,7 @@ internal void DeployShouldReturnTrueWhenDeploymentEnabledSetToTrueAndHasDeployme ]; testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, kvpArray); var testDeployment = new TestDeployment( - new DeploymentItemUtility(_mockReflectHelper.Object), + new DeploymentItemUtility(_mockReflectionOperations.Object), new DeploymentUtility(), _mockFileUtility.Object); @@ -362,7 +362,7 @@ public void GetDeploymentInformationShouldReturnRunDirectoryInformationIfSourceI #region private methods - private void SetupDeploymentItems(MemberInfo memberInfo, KeyValuePair[] deploymentItems) + private void SetupDeploymentItems(ICustomAttributeProvider attributeProvider, KeyValuePair[] deploymentItems) { var deploymentItemAttributes = new List(); @@ -371,9 +371,8 @@ private void SetupDeploymentItems(MemberInfo memberInfo, KeyValuePair ru.GetAttributes(memberInfo)) - .Returns(deploymentItemAttributes.ToArray()); + _mockReflectionOperations.Setup( + ru => ru.GetAttributes(attributeProvider)).Returns(deploymentItemAttributes); } private static TestCase GetTestCase(string source) @@ -417,7 +416,7 @@ private TestDeployment CreateAndSetupDeploymentRelatedUtilities(out TestRunDirec _mockFileUtility.Setup(fu => fu.GetNextIterationDirectoryName(It.IsAny(), It.IsAny())) .Returns(testRunDirectories.RootDeploymentDirectory); - var deploymentItemUtility = new DeploymentItemUtility(_mockReflectHelper.Object); + var deploymentItemUtility = new DeploymentItemUtility(_mockReflectionOperations.Object); return new TestDeployment( deploymentItemUtility, diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/MockableReflectionOperations.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/MockableReflectionOperations.cs new file mode 100644 index 0000000000..0247387d3a --- /dev/null +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/MockableReflectionOperations.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; + +using Moq; + +namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; + +/// +/// An implementation that delegates non-generic interface methods to a Moq +/// mock, and implements the higher-level generic methods by filtering the mock's +/// results. +/// This bridges the gap where Moq cannot set up generic methods with type constraints. +/// Tests should set up GetCustomAttributes(MemberInfo) or +/// GetCustomAttributes(Assembly, Type) on the mock, and the generic methods will filter those results. +/// +internal sealed class MockableReflectionOperations(Mock mock) : IReflectionOperations +{ + /// + /// Creates a new from a mock. + /// + public static MockableReflectionOperations Create(Mock mock) + => new(mock); + + // Pre-existing interface methods → delegate to mock + [return: NotNullIfNotNull(nameof(memberInfo))] + public object[]? GetCustomAttributes(MemberInfo memberInfo) => mock.Object.GetCustomAttributes(memberInfo); + + public object[] GetCustomAttributes(Assembly assembly, Type type) => mock.Object.GetCustomAttributes(assembly, type); + + public ConstructorInfo[] GetDeclaredConstructors(Type classType) => mock.Object.GetDeclaredConstructors(classType); + + public MethodInfo[] GetDeclaredMethods(Type classType) => mock.Object.GetDeclaredMethods(classType); + + public PropertyInfo[] GetDeclaredProperties(Type type) => mock.Object.GetDeclaredProperties(type); + + public Type[] GetDefinedTypes(Assembly assembly) => mock.Object.GetDefinedTypes(assembly); + + public MethodInfo[] GetRuntimeMethods(Type type) => mock.Object.GetRuntimeMethods(type); + + public MethodInfo? GetRuntimeMethod(Type declaringType, string methodName, Type[] parameters, bool includeNonPublic) + => mock.Object.GetRuntimeMethod(declaringType, methodName, parameters, includeNonPublic); + + public PropertyInfo? GetRuntimeProperty(Type classType, string propertyName, bool includeNonPublic) + => mock.Object.GetRuntimeProperty(classType, propertyName, includeNonPublic); + + public Type? GetType(string typeName) => mock.Object.GetType(typeName); + + public Type? GetType(Assembly assembly, string typeName) => mock.Object.GetType(assembly, typeName); + + public object? CreateInstance(Type type, object?[] parameters) => mock.Object.CreateInstance(type, parameters); + + // Higher-level generic methods → filter results from mock's GetCustomAttributes + public bool IsAttributeDefined(MemberInfo memberInfo) + where TAttribute : Attribute + => GetCustomAttributesCached(memberInfo).OfType().Any(); + + public TAttribute? GetFirstAttributeOrDefault(ICustomAttributeProvider attributeProvider) + where TAttribute : Attribute + => GetCustomAttributesCached(attributeProvider).OfType().FirstOrDefault(); + + public TAttribute? GetSingleAttributeOrDefault(ICustomAttributeProvider attributeProvider) + where TAttribute : Attribute + => GetCustomAttributesCached(attributeProvider).OfType().SingleOrDefault(); + + public IEnumerable GetAttributes(ICustomAttributeProvider attributeProvider) + where TAttributeType : Attribute + => GetCustomAttributesCached(attributeProvider).OfType(); + + public Attribute[] GetCustomAttributesCached(ICustomAttributeProvider attributeProvider) + => attributeProvider switch + { + MemberInfo memberInfo => mock.Object.GetCustomAttributes(memberInfo)?.OfType().ToArray() ?? [], + Assembly assembly => mock.Object.GetCustomAttributes(assembly, typeof(Attribute)).OfType().ToArray(), + _ => attributeProvider.GetCustomAttributes(true).OfType().ToArray(), + }; + + public bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Type type) + => mock.Object.IsMethodDeclaredInSameAssemblyAsType(method, type); +} diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.cs index 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.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs index bedfa7f4d6..c1a11d0c2b 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs @@ -4,8 +4,9 @@ #if !WINDOWS_UWP && !WIN_UI using AwesomeAssertions; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -25,7 +26,7 @@ public class DeploymentItemUtilityTests : TestContainer TestPropertyAttributes.Hidden, typeof(TestCase)); - private readonly Mock _mockReflectHelper; + private readonly Mock _mockReflectionOperations; private readonly DeploymentItemUtility _deploymentItemUtility; private readonly ICollection _warnings; @@ -34,8 +35,8 @@ public class DeploymentItemUtilityTests : TestContainer public DeploymentItemUtilityTests() { - _mockReflectHelper = new Mock(); - _deploymentItemUtility = new DeploymentItemUtility(_mockReflectHelper.Object); + _mockReflectionOperations = new Mock(); + _deploymentItemUtility = new DeploymentItemUtility(_mockReflectionOperations.Object); _warnings = []; } @@ -43,7 +44,7 @@ public DeploymentItemUtilityTests() public void GetClassLevelDeploymentItemsShouldReturnEmptyListWhenNoDeploymentItems() { - _mockReflectHelper.Setup(x => x.GetAttributes(typeof(DeploymentItemUtilityTests))) + _mockReflectionOperations.Setup(x => x.GetAttributes(typeof(DeploymentItemUtilityTests))) .Returns([]); IList deploymentItems = _deploymentItemUtility.GetClassLevelDeploymentItems(typeof(DeploymentItemUtilityTests), _warnings); @@ -164,7 +165,7 @@ public void GetClassLevelDeploymentItemsShouldReportWarningsForInvalidDeployment public void GetDeploymentItemsShouldReturnNullOnNoDeploymentItems() { MethodInfo method = typeof(DeploymentItemUtilityTests).GetMethod("GetDeploymentItemsShouldReturnNullOnNoDeploymentItems")!; - _mockReflectHelper.Setup(x => x.GetAttributes(method)) + _mockReflectionOperations.Setup(x => x.GetAttributes(method)) .Returns([]); _deploymentItemUtility.GetDeploymentItems(method, null!, _warnings).Should().BeNull(); @@ -209,7 +210,7 @@ public void GetDeploymentItemsShouldReturnClassLevelDeploymentItemsOnly() }; MethodInfo method = typeof(DeploymentItemUtilityTests).GetMethod("GetDeploymentItemsShouldReturnNullOnNoDeploymentItems")!; - _mockReflectHelper.Setup(x => x.GetAttributes(method)) + _mockReflectionOperations.Setup(x => x.GetAttributes(method)) .Returns([]); // Act. @@ -414,7 +415,7 @@ public void HasDeployItemsShouldReturnTrueWhenDeploymentItemsArePresent() #region private methods - private void SetupDeploymentItems(MemberInfo memberInfo, KeyValuePair[] deploymentItems) + private void SetupDeploymentItems(ICustomAttributeProvider attributeProvider, KeyValuePair[] deploymentItems) { var deploymentItemAttributes = new List(); @@ -423,9 +424,8 @@ private void SetupDeploymentItems(MemberInfo memberInfo, KeyValuePair ru.GetAttributes(memberInfo)) - .Returns(deploymentItemAttributes.ToArray()); + _mockReflectionOperations.Setup( + ru => ru.GetAttributes(attributeProvider)).Returns(deploymentItemAttributes); } #endregion diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentUtilityTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentUtilityTests.cs index 14543a254b..c66fcba062 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentUtilityTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentUtilityTests.cs @@ -4,8 +4,9 @@ #if !WINDOWS_UWP && !WIN_UI using AwesomeAssertions; -using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -25,6 +26,7 @@ public class DeploymentUtilityTests : TestContainer private const string DefaultDeploymentItemPath = @"c:\temp"; private const string DefaultDeploymentItemOutputDirectory = "out"; + private readonly Mock _mockReflectionOperations; private readonly Mock _mockFileUtility; private readonly Mock _mockAssemblyUtility; private readonly Mock _mockRunContext; @@ -40,12 +42,13 @@ public class DeploymentUtilityTests : TestContainer public DeploymentUtilityTests() { + _mockReflectionOperations = new Mock(); _mockFileUtility = new Mock(); _mockAssemblyUtility = new Mock(); _warnings = []; _deploymentUtility = new DeploymentUtility( - new DeploymentItemUtility(new ReflectHelper()), + new DeploymentItemUtility(_mockReflectionOperations.Object), _mockAssemblyUtility.Object, _mockFileUtility.Object); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/ReflectionUtilityTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/ReflectionUtilityTests.cs index 5b331a5fc0..56510822c7 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/ReflectionUtilityTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/ReflectionUtilityTests.cs @@ -4,20 +4,24 @@ using AwesomeAssertions; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; using TestFramework.ForTestingMSTest; namespace MSTestAdapter.PlatformServices.UnitTests.Utilities; +/// +/// Tests for ReflectionOperations which provides platform-specific reflection operations. +/// public class ReflectionUtilityTests : TestContainer { + private readonly ReflectionOperations _reflectionOperations = new(); + #if NETFRAMEWORK public void GetSpecificCustomAttributesOnAssemblyShouldReturnAllAttributes() { Assembly asm = typeof(DummyTestClass).Assembly; - object[] attributes = new ReflectionOperations().GetCustomAttributes(asm, typeof(DummyAAttribute)); + object[] attributes = _reflectionOperations.GetCustomAttributes(asm, typeof(DummyAAttribute)); attributes.Should().NotBeNull(); attributes.Length.Should().Be(2); @@ -31,34 +35,90 @@ public void GetCustomAttributesShouldReturnAllAttributes() { MethodInfo methodInfo = typeof(DummyBaseTestClass).GetMethod("DummyVTestMethod1")!; - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(methodInfo); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); string[] expectedAttributes = ["DummyA : base", "DummySingleA : base"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetCustomAttributesShouldReturnAllAttributesWithBaseInheritance() { MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("DummyVTestMethod1")!; - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(methodInfo); + object[]? attributes = _reflectionOperations.GetCustomAttributes(methodInfo); attributes.Should().NotBeNull(); attributes.Should().HaveCount(3); // Notice that the DummySingleA on the base method does not show up since it can only be defined once. string[] expectedAttributes = ["DummyA : derived", "DummySingleA : derived", "DummyA : base"]; - GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + GetAttributeValuePairs(attributes!).Should().Equal(expectedAttributes); } public void GetCustomAttributesOnTypeShouldReturnAllAttributes() { Type type = typeof(DummyBaseTestClass); - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(type); + object[]? attributes = _reflectionOperations.GetCustomAttributes(type); + + attributes.Should().NotBeNull(); + // Filter to only our test attributes (excludes compiler-generated attributes like NullableContextAttribute) + List testAttributes = GetAttributeValuePairs(attributes!); + testAttributes.Should().HaveCount(1); + + string[] expectedAttributes = ["DummyA : ba"]; + testAttributes.Should().Equal(expectedAttributes); + } + + public void GetCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritance() + { + Type type = typeof(DummyTestClass); + + object[]? attributes = _reflectionOperations.GetCustomAttributes(type); + + attributes.Should().NotBeNull(); + // Filter to only our test attributes (excludes compiler-generated attributes like NullableContextAttribute) + List testAttributes = GetAttributeValuePairs(attributes!); + testAttributes.Should().HaveCount(2); + + string[] expectedAttributes = ["DummyA : a", "DummyA : ba"]; + testAttributes.Should().Equal(expectedAttributes); + } + + public void GetSpecificCustomAttributesShouldReturnAllAttributes() + { + MethodInfo methodInfo = typeof(DummyBaseTestClass).GetMethod("DummyVTestMethod1")!; + + DummyAAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); + + attributes.Should().NotBeNull(); + attributes.Should().HaveCount(1); + + string[] expectedAttributes = ["DummyA : base"]; + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + } + + public void GetSpecificCustomAttributesShouldReturnAllAttributesWithBaseInheritance() + { + MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("DummyVTestMethod1")!; + + DummyAAttribute[] attributes = _reflectionOperations.GetAttributes(methodInfo).ToArray(); + + attributes.Should().NotBeNull(); + attributes.Should().HaveCount(2); + + string[] expectedAttributes = ["DummyA : derived", "DummyA : base"]; + GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); + } + + public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributes() + { + Type type = typeof(DummyBaseTestClass); + + DummyAAttribute[] attributes = _reflectionOperations.GetAttributes(type).ToArray(); attributes.Should().NotBeNull(); attributes.Should().HaveCount(1); @@ -67,11 +127,11 @@ public void GetCustomAttributesOnTypeShouldReturnAllAttributes() GetAttributeValuePairs(attributes).Should().Equal(expectedAttributes); } - public void GetCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritance() + public void GetSpecificCustomAttributesOnTypeShouldReturnAllAttributesWithBaseInheritance() { Type type = typeof(DummyTestClass); - IReadOnlyList attributes = new ReflectionOperations().GetCustomAttributes(type); + DummyAAttribute[] attributes = _reflectionOperations.GetAttributes(type).ToArray(); attributes.Should().NotBeNull(); attributes.Should().HaveCount(2); diff --git a/test/UnitTests/MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj b/test/UnitTests/MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj index 6ca3cab48e..3d69cc0145 100644 --- a/test/UnitTests/MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj +++ b/test/UnitTests/MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj @@ -17,6 +17,7 @@ +