diff --git a/src/Adapter/MSTest.TestAdapter/BannedSymbols.txt b/src/Adapter/MSTest.TestAdapter/BannedSymbols.txt new file mode 100644 index 0000000000..91178cbc64 --- /dev/null +++ b/src/Adapter/MSTest.TestAdapter/BannedSymbols.txt @@ -0,0 +1 @@ +M:Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers.ReflectHelper.#ctor; This is allowed only for tests. diff --git a/src/Adapter/MSTest.TestAdapter/Discovery/AssemblyEnumerator.cs b/src/Adapter/MSTest.TestAdapter/Discovery/AssemblyEnumerator.cs index 080d2aceff..43d03cdf6e 100644 --- a/src/Adapter/MSTest.TestAdapter/Discovery/AssemblyEnumerator.cs +++ b/src/Adapter/MSTest.TestAdapter/Discovery/AssemblyEnumerator.cs @@ -189,14 +189,15 @@ internal static string GetLoadExceptionDetails(ReflectionTypeLoadException ex) /// The reflected assembly name. /// True to discover test classes which are declared internal in /// addition to test classes which are declared public. + /// to use when generating tests. /// to use when generating TestId. /// a TypeEnumerator instance. - internal virtual TypeEnumerator GetTypeEnumerator(Type type, string assemblyFileName, bool discoverInternals, TestIdGenerationStrategy testIdGenerationStrategy) + internal virtual TypeEnumerator GetTypeEnumerator(Type type, string assemblyFileName, bool discoverInternals, TestDataSourceDiscoveryOption discoveryOption, TestIdGenerationStrategy testIdGenerationStrategy) { var typeValidator = new TypeValidator(ReflectHelper, discoverInternals); var testMethodValidator = new TestMethodValidator(ReflectHelper, discoverInternals); - return new TypeEnumerator(type, assemblyFileName, ReflectHelper, typeValidator, testMethodValidator, testIdGenerationStrategy); + return new TypeEnumerator(type, assemblyFileName, ReflectHelper, typeValidator, testMethodValidator, discoveryOption, testIdGenerationStrategy); } private List DiscoverTestsInType( @@ -220,9 +221,8 @@ internal virtual TypeEnumerator GetTypeEnumerator(Type type, string assemblyFile try { typeFullName = type.FullName; - TypeEnumerator testTypeEnumerator = GetTypeEnumerator(type, assemblyFileName, discoverInternals, testIdGenerationStrategy); + TypeEnumerator testTypeEnumerator = GetTypeEnumerator(type, assemblyFileName, discoverInternals, discoveryOption, testIdGenerationStrategy); ICollection? unitTestCases = testTypeEnumerator.Enumerate(out ICollection warningsFromTypeEnumerator); - warningMessages.AddRange(warningsFromTypeEnumerator); if (unitTestCases != null) @@ -262,6 +262,18 @@ private bool DynamicDataAttached(IDictionary sourceLevelParamet return false; } + if (test.TestMethod.DataType == DynamicDataType.None) + { + return false; + } + + // PERF: For perf we started setting DataType in TypeEnumerator, so when it is None we will not reach this line. + // But if we do run this code, we still reset it to None, because the code that determines if this is data drive test expects the value to be None + // and only sets it when needed. + // + // If you remove this line and acceptance tests still pass you are okay. + test.TestMethod.DataType = DynamicDataType.None; + // NOTE: From this place we don't have any path that would let the user write a message on the TestContext and we don't do // anything with what would be printed anyway so we can simply use a simple StringWriter. using var writer = new StringWriter(); @@ -274,11 +286,11 @@ private bool DynamicDataAttached(IDictionary sourceLevelParamet private static bool TryProcessTestDataSourceTests(UnitTestElement test, TestMethodInfo testMethodInfo, List tests) { MethodInfo methodInfo = testMethodInfo.MethodInfo; - IEnumerable? testDataSources = ReflectHelper.GetAttributes(methodInfo, false)?.OfType(); - if (testDataSources == null || !testDataSources.Any()) - { - return false; - } + + // 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.GetDerivedAttributes(methodInfo, inherit: false).OfType(); try { @@ -310,9 +322,11 @@ private static bool TryProcessTestDataSourceTests(UnitTestElement test, TestMeth throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, FrameworkMessages.DynamicDataIEnumerableEmpty, "GetData", dataSource.GetType().Name)); } } - catch (Exception ex) when (ex is ArgumentException && MSTestSettings.CurrentSettings.ConsiderEmptyDataSourceAsInconclusive) + catch (ArgumentException) when (MSTestSettings.CurrentSettings.ConsiderEmptyDataSourceAsInconclusive) { UnitTestElement discoveredTest = test.Clone(); + // Make the test not data driven, because it had no data. + discoveredTest.TestMethod.DataType = DynamicDataType.None; discoveredTest.DisplayName = dataSource.GetDisplayName(methodInfo, null) ?? discoveredTest.DisplayName; tests.Add(discoveredTest); continue; diff --git a/src/Adapter/MSTest.TestAdapter/Discovery/TestMethodValidator.cs b/src/Adapter/MSTest.TestAdapter/Discovery/TestMethodValidator.cs index c9330b5768..861d108ab4 100644 --- a/src/Adapter/MSTest.TestAdapter/Discovery/TestMethodValidator.cs +++ b/src/Adapter/MSTest.TestAdapter/Discovery/TestMethodValidator.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Globalization; @@ -48,8 +48,14 @@ internal TestMethodValidator(ReflectHelper reflectHelper, bool discoverInternals /// Return true if a method is a valid test method. internal virtual bool IsValidTestMethod(MethodInfo testMethodInfo, Type type, ICollection warnings) { - if (!_reflectHelper.IsAttributeDefined(testMethodInfo, false) - && !_reflectHelper.HasAttributeDerivedFrom(testMethodInfo, false)) + // PERF: We are doing caching reflection here, meaning we will cache every method info in the + // assembly, this is because when we discover and run we will repeatedly inspect all the methods in the assembly, and this + // 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 methods in the assembly. + // + // Also skip all methods coming from object, because they cannot be tests. + if (testMethodInfo.DeclaringType == typeof(object) || !_reflectHelper.IsDerivedAttributeDefined(testMethodInfo, inherit: false)) { return false; } diff --git a/src/Adapter/MSTest.TestAdapter/Discovery/TypeEnumerator.cs b/src/Adapter/MSTest.TestAdapter/Discovery/TypeEnumerator.cs index 5f7fce2535..e0fab8b22e 100644 --- a/src/Adapter/MSTest.TestAdapter/Discovery/TypeEnumerator.cs +++ b/src/Adapter/MSTest.TestAdapter/Discovery/TypeEnumerator.cs @@ -22,6 +22,7 @@ internal class TypeEnumerator private readonly TypeValidator _typeValidator; private readonly TestMethodValidator _testMethodValidator; private readonly TestIdGenerationStrategy _testIdGenerationStrategy; + private readonly TestDataSourceDiscoveryOption _discoveryOption; private readonly ReflectHelper _reflectHelper; /// @@ -33,7 +34,7 @@ internal class TypeEnumerator /// The validator for test classes. /// The validator for test methods. /// to use when generating TestId. - internal TypeEnumerator(Type type, string assemblyFilePath, ReflectHelper reflectHelper, TypeValidator typeValidator, TestMethodValidator testMethodValidator, TestIdGenerationStrategy testIdGenerationStrategy) + internal TypeEnumerator(Type type, string assemblyFilePath, ReflectHelper reflectHelper, TypeValidator typeValidator, TestMethodValidator testMethodValidator, TestDataSourceDiscoveryOption discoveryOption, TestIdGenerationStrategy testIdGenerationStrategy) { _type = type; _assemblyFilePath = assemblyFilePath; @@ -41,6 +42,7 @@ internal TypeEnumerator(Type type, string assemblyFilePath, ReflectHelper reflec _typeValidator = typeValidator; _testMethodValidator = testMethodValidator; _testIdGenerationStrategy = testIdGenerationStrategy; + _discoveryOption = discoveryOption; } /// @@ -73,6 +75,8 @@ internal Collection GetTests(ICollection warnings) var tests = new Collection(); // Test class is already valid. Verify methods. + // PERF: GetRuntimeMethods is used here to get all methods, including non-public, and static methods. + // 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 _type.GetRuntimeMethods()) { bool isMethodDeclaredInTestTypeAssembly = _reflectHelper.IsMethodDeclaredInSameAssemblyAsType(method, _type); @@ -147,14 +151,30 @@ internal UnitTestElement GetTestFromMethod(MethodInfo method, bool isDeclaredInT method.DeclaringType.Assembly); } + // PERF: When discovery option is set to DuringDiscovery, we will expand data on tests to one test case + // per data item. This will happen in AssemblyEnumerator. But AssemblyEnumerator does not have direct access to + // the method info or method attributes, so it would create a TestMethodInfo to see if the test is data driven. + // Creating TestMethodInfo is expensive and should be done only for a test that we know is data driven. + // + // So to optimize this we check if we have some data source attribute. Because here we have access to all attributes + // and we store that info in DataType. AssemblyEnumerator will pick this up and will get the real test data in the expensive way + // or it will skip over getting the data cheaply, when DataType = DynamicDataType.None. + // + // This needs to be done only when DuringDiscovery is set, because otherwise we would populate the DataType, but we would not populate + // and execution would not try to re-populate the data, because DataType is already set to data driven, so it would just throw error about empty data. + if (_discoveryOption == TestDataSourceDiscoveryOption.DuringDiscovery) + { + testMethod.DataType = GetDynamicDataType(method); + } + var testElement = new UnitTestElement(testMethod) { // Get compiler generated type name for async test method (either void returning or task returning). AsyncTypeName = method.GetAsyncTypeName(), - TestCategory = _reflectHelper.GetCategories(method, _type), + TestCategory = _reflectHelper.GetTestCategories(method, _type), DoNotParallelize = _reflectHelper.IsDoNotParallelizeSet(method, _type), Priority = _reflectHelper.GetPriority(method), - Ignored = _reflectHelper.IsAttributeDefined(method, false), + Ignored = _reflectHelper.IsNonDerivedAttributeDefined(method, inherit: false), DeploymentItems = PlatformServiceProvider.Instance.TestDeployment.GetDeploymentItems(method, _type, warnings), }; @@ -174,31 +194,49 @@ internal UnitTestElement GetTestFromMethod(MethodInfo method, bool isDeclaredInT testElement.Traits = traits.ToArray(); - if (_reflectHelper.GetCustomAttribute(method) is CssIterationAttribute cssIteration) + if (_reflectHelper.GetFirstDerivedAttributeOrDefault(method, inherit: true) is CssIterationAttribute cssIteration) { testElement.CssIteration = cssIteration.CssIteration; } - if (_reflectHelper.GetCustomAttribute(method) is CssProjectStructureAttribute cssProjectStructure) + if (_reflectHelper.GetFirstDerivedAttributeOrDefault(method, inherit: true) is CssProjectStructureAttribute cssProjectStructure) { testElement.CssProjectStructure = cssProjectStructure.CssProjectStructure; } - if (_reflectHelper.GetCustomAttribute(method) is DescriptionAttribute descriptionAttribute) + if (_reflectHelper.GetFirstDerivedAttributeOrDefault(method, inherit: true) is DescriptionAttribute descriptionAttribute) { testElement.Description = descriptionAttribute.Description; } - WorkItemAttribute[] workItemAttributes = _reflectHelper.GetCustomAttributes(method); + WorkItemAttribute[] workItemAttributes = _reflectHelper.GetDerivedAttributes(method, inherit: true).ToArray(); if (workItemAttributes.Length != 0) { testElement.WorkItemIds = workItemAttributes.Select(x => x.Id.ToString(CultureInfo.InvariantCulture)).ToArray(); } // get DisplayName from TestMethodAttribute (or any inherited attribute) - TestMethodAttribute? testMethodAttribute = _reflectHelper.GetCustomAttribute(method); + TestMethodAttribute? testMethodAttribute = _reflectHelper.GetFirstDerivedAttributeOrDefault(method, inherit: true); testElement.DisplayName = testMethodAttribute?.DisplayName ?? method.Name; return testElement; } + + private DynamicDataType GetDynamicDataType(MethodInfo method) + { + foreach (Attribute attribute in _reflectHelper.GetDerivedAttributes(method, inherit: true)) + { + if (AttributeComparer.IsDerived(attribute)) + { + return DynamicDataType.ITestDataSource; + } + + if (AttributeComparer.IsDerived(attribute)) + { + return DynamicDataType.DataSourceAttribute; + } + } + + return DynamicDataType.None; + } } diff --git a/src/Adapter/MSTest.TestAdapter/Discovery/TypeValidator.cs b/src/Adapter/MSTest.TestAdapter/Discovery/TypeValidator.cs index f9b91f21cf..edea385520 100644 --- a/src/Adapter/MSTest.TestAdapter/Discovery/TypeValidator.cs +++ b/src/Adapter/MSTest.TestAdapter/Discovery/TypeValidator.cs @@ -51,9 +51,12 @@ internal virtual bool IsValidTestClass(Type type, ICollection warnings) { TypeInfo typeInfo = type.GetTypeInfo(); - if (!typeInfo.IsClass - || (!_reflectHelper.IsAttributeDefined(typeInfo, false) - && !_reflectHelper.HasAttributeDerivedFrom(typeInfo, false))) + // PERF: We are doing caching reflection here, meaning we will cache every class info in the + // assembly, this is because when we discover and run we will repeatedly inspect all the types in the assembly, and this + // 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 (!typeInfo.IsClass || !_reflectHelper.IsDerivedAttributeDefined(typeInfo, inherit: false)) { return false; } diff --git a/src/Adapter/MSTest.TestAdapter/Execution/TestMethodInfo.cs b/src/Adapter/MSTest.TestAdapter/Execution/TestMethodInfo.cs index c4ea17a3de..cd014db000 100644 --- a/src/Adapter/MSTest.TestAdapter/Execution/TestMethodInfo.cs +++ b/src/Adapter/MSTest.TestAdapter/Execution/TestMethodInfo.cs @@ -83,12 +83,11 @@ public class TestMethodInfo : ITestMethod /// internal TestMethodOptions TestMethodOptions { get; } - public Attribute[]? GetAllAttributes(bool inherit) => ReflectHelper.GetCustomAttributes(TestMethod, inherit) as Attribute[]; + public Attribute[]? GetAllAttributes(bool inherit) => ReflectHelper.Instance.GetDerivedAttributes(TestMethod, inherit).ToArray(); public TAttributeType[] GetAttributes(bool inherit) where TAttributeType : Attribute - => ReflectHelper.GetAttributes(TestMethod, inherit) - ?? []; + => ReflectHelper.Instance.GetDerivedAttributes(TestMethod, inherit).ToArray(); /// /// Execute test method. Capture failures, handle async and return result. diff --git a/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs b/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs index afac3031be..5798158cf2 100644 --- a/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs +++ b/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs @@ -282,7 +282,7 @@ private TestClassInfo CreateClassInfo(Type classType, TestMethod testMethod) TestAssemblyInfo assemblyInfo = GetAssemblyInfo(classType); - TestClassAttribute? testClassAttribute = ReflectHelper.GetDerivedAttribute(classType, false); + TestClassAttribute? testClassAttribute = ReflectHelper.Instance.GetFirstDerivedAttributeOrDefault(classType, inherit: false); DebugEx.Assert(testClassAttribute is not null, "testClassAttribute is null"); var classInfo = new TestClassInfo(classType, constructor, testContextProperty, testClassAttribute, assemblyInfo); @@ -298,15 +298,14 @@ private TestClassInfo CreateClassInfo(Type classType, TestMethod testMethod) foreach (MethodInfo methodInfo in classType.GetTypeInfo().DeclaredMethods) { - // Update test initialize/cleanup method UpdateInfoIfTestInitializeOrCleanupMethod(classInfo, methodInfo, false, instanceMethods); - // Update class initialize/cleanup method UpdateInfoIfClassInitializeOrCleanupMethod(classInfo, methodInfo, false, ref initAndCleanupMethods); } Type? baseType = classType.BaseType; - while (baseType != null) + // PERF: Don't inspect object, no test methods or setups can be defined on it. + while (baseType != null && baseType != typeof(object)) { foreach (MethodInfo methodInfo in baseType.GetTypeInfo().DeclaredMethods) { @@ -394,8 +393,7 @@ private TestAssemblyInfo GetAssemblyInfo(Type type) { // Only examine classes which are TestClass or derives from TestClass attribute TypeInfo typeInfo = t.GetTypeInfo(); - if (!_reflectionHelper.IsAttributeDefined(typeInfo, inherit: true) && - !_reflectionHelper.HasAttributeDerivedFrom(typeInfo, true)) + if (!_reflectionHelper.IsDerivedAttributeDefined(typeInfo, inherit: true)) { continue; } @@ -418,16 +416,15 @@ private TestAssemblyInfo GetAssemblyInfo(Type type) { assemblyInfo.AssemblyInitializeMethod = methodInfo; - if (_reflectionHelper.IsAttributeDefined(methodInfo, false)) + TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetFirstNonDerivedAttributeOrDefault(methodInfo, inherit: false); + if (timeoutAttribute != null) { - if (!methodInfo.HasCorrectTimeout()) + if (!methodInfo.HasCorrectTimeout(timeoutAttribute)) { string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorInvalidTimeout, methodInfo.DeclaringType!.FullName, methodInfo.Name); throw new TypeInspectionException(message); } - TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetAttribute(methodInfo); - DebugEx.Assert(timeoutAttribute != null, "TimeoutAttribute cannot be null"); assemblyInfo.AssemblyInitializeMethodTimeoutMilliseconds = timeoutAttribute.Timeout; } else if (MSTestSettings.CurrentSettings.AssemblyInitializeTimeout > 0) @@ -438,16 +435,15 @@ private TestAssemblyInfo GetAssemblyInfo(Type type) else if (IsAssemblyOrClassCleanupMethod(methodInfo)) { assemblyInfo.AssemblyCleanupMethod = methodInfo; - if (_reflectionHelper.IsAttributeDefined(methodInfo, false)) + TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetFirstNonDerivedAttributeOrDefault(methodInfo, inherit: false); + if (timeoutAttribute != null) { - if (!methodInfo.HasCorrectTimeout()) + if (!methodInfo.HasCorrectTimeout(timeoutAttribute)) { string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorInvalidTimeout, methodInfo.DeclaringType!.FullName, methodInfo.Name); throw new TypeInspectionException(message); } - TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetAttribute(methodInfo); - DebugEx.Assert(timeoutAttribute != null, "TimeoutAttribute cannot be null"); assemblyInfo.AssemblyCleanupMethodTimeoutMilliseconds = timeoutAttribute.Timeout; } else if (MSTestSettings.CurrentSettings.AssemblyCleanupTimeout > 0) @@ -472,7 +468,12 @@ private TestAssemblyInfo GetAssemblyInfo(Type type) private bool IsAssemblyOrClassInitializeMethod(MethodInfo methodInfo) where TInitializeAttribute : Attribute { - if (!_reflectionHelper.IsAttributeDefined(methodInfo, false)) + // TODO: this would be inconsistent with the codebase, but potential perf gain, issue: https://github.com/microsoft/testfx/issues/2999 + // if (!methodInfo.IsStatic) + // { + // return false; + // } + if (!_reflectionHelper.IsNonDerivedAttributeDefined(methodInfo, false)) { return false; } @@ -495,7 +496,12 @@ private bool IsAssemblyOrClassInitializeMethod(MethodInfo private bool IsAssemblyOrClassCleanupMethod(MethodInfo methodInfo) where TCleanupAttribute : Attribute { - if (!_reflectionHelper.IsAttributeDefined(methodInfo, false)) + // TODO: this would be inconsistent with the codebase, but potential perf gain, issue: https://github.com/microsoft/testfx/issues/2999 + // if (!methodInfo.IsStatic) + // { + // return false; + // } + if (!_reflectionHelper.IsNonDerivedAttributeDefined(methodInfo, false)) { return false; } @@ -549,16 +555,15 @@ private bool IsAssemblyOrClassCleanupMethod(MethodInfo method if (isInitializeMethod) { - if (_reflectionHelper.IsAttributeDefined(methodInfo, false)) + TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetFirstNonDerivedAttributeOrDefault(methodInfo, inherit: false); + if (timeoutAttribute != null) { - if (!methodInfo.HasCorrectTimeout()) + if (!methodInfo.HasCorrectTimeout(timeoutAttribute)) { string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorInvalidTimeout, methodInfo.DeclaringType!.FullName, methodInfo.Name); throw new TypeInspectionException(message); } - TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetAttribute(methodInfo); - DebugEx.Assert(timeoutAttribute != null, "TimeoutAttribute cannot be null"); classInfo.ClassInitializeMethodTimeoutMilliseconds.Add(methodInfo, timeoutAttribute.Timeout); } else if (MSTestSettings.CurrentSettings.ClassInitializeTimeout > 0) @@ -568,7 +573,7 @@ private bool IsAssemblyOrClassCleanupMethod(MethodInfo method if (isBase) { - if (_reflectionHelper.GetCustomAttribute(methodInfo)! + if (_reflectionHelper.GetFirstDerivedAttributeOrDefault(methodInfo, inherit: true)? .InheritanceBehavior == InheritanceBehavior.BeforeEachDerivedClass) { initAndCleanupMethods[0] = methodInfo; @@ -583,16 +588,15 @@ private bool IsAssemblyOrClassCleanupMethod(MethodInfo method if (isCleanupMethod) { - if (_reflectionHelper.IsAttributeDefined(methodInfo, false)) + TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetFirstNonDerivedAttributeOrDefault(methodInfo, inherit: false); + if (timeoutAttribute != null) { - if (!methodInfo.HasCorrectTimeout()) + if (!methodInfo.HasCorrectTimeout(timeoutAttribute)) { string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorInvalidTimeout, methodInfo.DeclaringType!.FullName, methodInfo.Name); throw new TypeInspectionException(message); } - TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetAttribute(methodInfo); - DebugEx.Assert(timeoutAttribute != null, "TimeoutAttribute cannot be null"); classInfo.ClassCleanupMethodTimeoutMilliseconds.Add(methodInfo, timeoutAttribute.Timeout); } else if (MSTestSettings.CurrentSettings.ClassCleanupTimeout > 0) @@ -602,7 +606,7 @@ private bool IsAssemblyOrClassCleanupMethod(MethodInfo method if (isBase) { - if (_reflectionHelper.GetCustomAttribute(methodInfo)! + if (_reflectionHelper.GetFirstDerivedAttributeOrDefault(methodInfo, inherit: true)? .InheritanceBehavior == InheritanceBehavior.BeforeEachDerivedClass) { initAndCleanupMethods[1] = methodInfo; @@ -629,8 +633,8 @@ private bool IsAssemblyOrClassCleanupMethod(MethodInfo method bool isBase, Dictionary instanceMethods) { - bool hasTestInitialize = _reflectionHelper.IsAttributeDefined(methodInfo, inherit: false); - bool hasTestCleanup = _reflectionHelper.IsAttributeDefined(methodInfo, inherit: false); + bool hasTestInitialize = _reflectionHelper.IsNonDerivedAttributeDefined(methodInfo, inherit: false); + bool hasTestCleanup = _reflectionHelper.IsNonDerivedAttributeDefined(methodInfo, inherit: false); if (!hasTestCleanup && !hasTestInitialize) { @@ -650,16 +654,15 @@ private bool IsAssemblyOrClassCleanupMethod(MethodInfo method if (hasTestInitialize) { - if (_reflectionHelper.IsAttributeDefined(methodInfo, false)) + TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetFirstNonDerivedAttributeOrDefault(methodInfo, inherit: false); + if (timeoutAttribute != null) { - if (!methodInfo.HasCorrectTimeout()) + if (!methodInfo.HasCorrectTimeout(timeoutAttribute)) { string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorInvalidTimeout, methodInfo.DeclaringType!.FullName, methodInfo.Name); throw new TypeInspectionException(message); } - TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetAttribute(methodInfo); - DebugEx.Assert(timeoutAttribute != null, "TimeoutAttribute cannot be null"); classInfo.TestInitializeMethodTimeoutMilliseconds.Add(methodInfo, timeoutAttribute.Timeout); } else if (MSTestSettings.CurrentSettings.TestInitializeTimeout > 0) @@ -682,16 +685,15 @@ private bool IsAssemblyOrClassCleanupMethod(MethodInfo method if (hasTestCleanup) { - if (_reflectionHelper.IsAttributeDefined(methodInfo, false)) + TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetFirstNonDerivedAttributeOrDefault(methodInfo, inherit: false); + if (timeoutAttribute != null) { - if (!methodInfo.HasCorrectTimeout()) + if (!methodInfo.HasCorrectTimeout(timeoutAttribute)) { string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorInvalidTimeout, methodInfo.DeclaringType!.FullName, methodInfo.Name); throw new TypeInspectionException(message); } - TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetAttribute(methodInfo); - DebugEx.Assert(timeoutAttribute != null, "TimeoutAttribute cannot be null"); classInfo.TestCleanupMethodTimeoutMilliseconds.Add(methodInfo, timeoutAttribute.Timeout); } else if (MSTestSettings.CurrentSettings.TestCleanupTimeout > 0) @@ -762,7 +764,7 @@ private bool IsAssemblyOrClassCleanupMethod(MethodInfo method private TestMethodAttribute? GetTestMethodAttribute(MethodInfo methodInfo, TestClassInfo testClassInfo) { // Get the derived TestMethod attribute from reflection - TestMethodAttribute? testMethodAttribute = _reflectionHelper.GetDerivedAttribute(methodInfo, false); + TestMethodAttribute? testMethodAttribute = _reflectionHelper.GetFirstDerivedAttributeOrDefault(methodInfo, inherit: false); // 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 @@ -858,12 +860,12 @@ private MethodInfo GetMethodInfoForTestMethod(TestMethod testMethod, TestClassIn private int GetTestTimeout(MethodInfo methodInfo, TestMethod testMethod) { DebugEx.Assert(methodInfo != null, "TestMethod should be non-null"); - TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetAttribute(methodInfo); + TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetFirstNonDerivedAttributeOrDefault(methodInfo, inherit: false); int globalTimeout = MSTestSettings.CurrentSettings.TestTimeout; if (timeoutAttribute != null) { - if (!methodInfo.HasCorrectTimeout()) + if (!methodInfo.HasCorrectTimeout(timeoutAttribute)) { string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorInvalidTimeout, testMethod.FullClassName, testMethod.Name); throw new TypeInspectionException(message); diff --git a/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs b/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs index b0f7d945b8..5fbe5701e8 100644 --- a/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs +++ b/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Concurrent; @@ -254,9 +254,9 @@ private void RunRequiredCleanups(ITestContext testContext, TestMethodInfo? testM string? ignoreMessage = null; bool isIgnoreAttributeOnClass = - _reflectHelper.IsAttributeDefined(testMethodInfo.Parent.ClassType, false); + _reflectHelper.IsNonDerivedAttributeDefined(testMethodInfo.Parent.ClassType, false); bool isIgnoreAttributeOnMethod = - _reflectHelper.IsAttributeDefined(testMethodInfo.TestMethod, false); + _reflectHelper.IsNonDerivedAttributeDefined(testMethodInfo.TestMethod, false); if (isIgnoreAttributeOnClass) { @@ -291,7 +291,7 @@ private class ClassCleanupManager IEnumerable testsToRun, ClassCleanupBehavior? lifecycleFromMsTest, ClassCleanupBehavior lifecycleFromAssembly, - ReflectHelper? reflectHelper = null) + ReflectHelper reflectHelper) { _remainingTestsByClass = new(testsToRun.GroupBy(t => t.TestMethod.FullClassName) @@ -300,7 +300,7 @@ private class ClassCleanupManager g => new HashSet(g.Select(t => t.TestMethod.UniqueName)))); _lifecycleFromMsTest = lifecycleFromMsTest; _lifecycleFromAssembly = lifecycleFromAssembly; - _reflectHelper = reflectHelper ?? new ReflectHelper(); + _reflectHelper = reflectHelper; } public void MarkTestComplete(TestMethodInfo testMethodInfo, TestMethod testMethod, out bool shouldRunEndOfClassCleanup, diff --git a/src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs b/src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs index 83f7c46211..beb22b57f8 100644 --- a/src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs +++ b/src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs @@ -84,20 +84,16 @@ internal static bool HasCorrectTestMethodSignature(this MethodInfo method, bool /// Checks whether test method has correct Timeout attribute. /// /// The method to verify. + /// The timeout attribute when we already have it. /// True if the method has the right test timeout signature. - internal static bool HasCorrectTimeout(this MethodInfo method) + internal static bool HasCorrectTimeout(this MethodInfo method, TimeoutAttribute? timeoutAttribute = null) { DebugEx.Assert(method != null, "method should not be null."); - // There should be one and only one TimeoutAttribute. - TimeoutAttribute[] attributes = ReflectHelper.GetCustomAttributes(method, false); - if (attributes.Length != 1) - { - return false; - } + // TODO: redesign this, probably change this to GetTimeout? so we don't have to do this weird dance? + timeoutAttribute ??= ReflectHelper.Instance.GetFirstNonDerivedAttributeOrDefault(method, inherit: false); - // Timeout cannot be less than 0. - return !(attributes[0].Timeout < 0); + return timeoutAttribute?.Timeout > 0; } /// @@ -120,7 +116,7 @@ internal static bool IsValidReturnType(this MethodInfo method) /// Compiler generated type name for given async test method.. internal static string? GetAsyncTypeName(this MethodInfo method) { - AsyncStateMachineAttribute? asyncStateMachineAttribute = ReflectHelper.GetCustomAttributes(method, false).FirstOrDefault(); + AsyncStateMachineAttribute? asyncStateMachineAttribute = ReflectHelper.Instance.GetFirstNonDerivedAttributeOrDefault(method, inherit: false); return asyncStateMachineAttribute?.StateMachineType?.FullName; } diff --git a/src/Adapter/MSTest.TestAdapter/Helpers/AttributeComparer.cs b/src/Adapter/MSTest.TestAdapter/Helpers/AttributeComparer.cs new file mode 100644 index 0000000000..5736b8295b --- /dev/null +++ b/src/Adapter/MSTest.TestAdapter/Helpers/AttributeComparer.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; + +internal sealed class AttributeComparer +{ + public static bool IsNonDerived(Attribute attribute) => + attribute is TAttribute; + + public static bool IsDerived(Attribute attribute) + { + Type attributeType = attribute.GetType(); + + // IsSubclassOf returns false when the types are equal. + if (attributeType == typeof(TAttribute)) + { + return true; + } + + // IsAssignableFrom also does this internally, but later falls to check generic + // and we don't need that. + if (!typeof(TAttribute).IsInterface) + { + // This returns false when TAttribute is interface (like ITestDataSource). + return attributeType.IsSubclassOf(typeof(TAttribute)); + } + + return typeof(TAttribute).IsAssignableFrom(attributeType); + } +} diff --git a/src/Adapter/MSTest.TestAdapter/Helpers/INotCachedReflectionAccessor.cs b/src/Adapter/MSTest.TestAdapter/Helpers/INotCachedReflectionAccessor.cs new file mode 100644 index 0000000000..12604078f3 --- /dev/null +++ b/src/Adapter/MSTest.TestAdapter/Helpers/INotCachedReflectionAccessor.cs @@ -0,0 +1,14 @@ +// 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.Reflection; + +namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; + +/// +/// This interface is for unit tests tests so they can easily replace the implementation of accessing the attributes. +/// +internal interface INotCachedReflectionAccessor +{ + object[]? GetCustomAttributesNotCached(ICustomAttributeProvider attributeProvider, bool inherit); +} diff --git a/src/Adapter/MSTest.TestAdapter/Helpers/ReflectHelper.cs b/src/Adapter/MSTest.TestAdapter/Helpers/ReflectHelper.cs index f7222bcc23..80664866aa 100644 --- a/src/Adapter/MSTest.TestAdapter/Helpers/ReflectHelper.cs +++ b/src/Adapter/MSTest.TestAdapter/Helpers/ReflectHelper.cs @@ -1,7 +1,6 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reflection; using System.Security; @@ -15,23 +14,36 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; internal class ReflectHelper : MarshalByRefObject { - private static readonly Lazy InstanceValue = new(() => new ReflectHelper()); + private static readonly Lazy InstanceValue = new(() => new ReflectHelper(new NotCachedReflectionAccessor())); - /// - /// Contains the memberInfo Vs the name/type of the attributes defined on that member. (FYI: - MemberInfo denotes properties, fields, methods, events). - /// - private readonly Dictionary> _attributeCache = []; + // PERF: This was moved from Dictionary> to Dictionary + // this 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 Dictionary _inheritedAttributeCache = []; + private readonly Dictionary _nonInheritedAttributeCache = []; + + internal /* for tests only */ ReflectHelper(INotCachedReflectionAccessor notCachedAttributeHelper) => + NotCachedAttributes = notCachedAttributeHelper ?? new NotCachedReflectionAccessor(); + + internal /* for tests only, because of Moq */ ReflectHelper() + : this(new NotCachedReflectionAccessor()) + { + } + + private readonly AttributeComparer _attributeComparer = new(); public static ReflectHelper Instance => InstanceValue.Value; + public INotCachedReflectionAccessor NotCachedAttributes { get; } + /// - /// Checks to see if the parameter memberInfo contains the parameter attribute or not. + /// Checks to see if a member or type is decorated with the given attribute. The type is checked exactly. If attribute is derived (inherits from) a class, e.g. [MyTestClass] from [TestClass] it won't match if you look for [TestClass]. The inherit parameter does not impact this checking. /// - /// Attribute to search for. + /// Attribute to search for by fully qualified name. /// Member/Type to test. - /// Look through inheritance or not. - /// True if the attribute of the specified type is defined. - public virtual bool IsAttributeDefined(MemberInfo memberInfo, bool inherit) + /// Inspect inheritance chain of the member or class. E.g. if parent class has this attribute defined. + /// True if the attribute of the specified type is defined on this member or a class. + public virtual bool IsNonDerivedAttributeDefined(MemberInfo memberInfo, bool inherit) where TAttribute : Attribute { if (memberInfo == null) @@ -40,70 +52,49 @@ public virtual bool IsAttributeDefined(MemberInfo memberInfo, bool i } // Get attributes defined on the member from the cache. - Dictionary? attributes = GetAttributes(memberInfo, inherit); - string requiredAttributeQualifiedName = typeof(TAttribute).AssemblyQualifiedName!; - if (attributes == null) + Attribute[] attributes = GetCustomAttributesCached(memberInfo, inherit); + + foreach (Attribute attribute in attributes) { - // If we could not obtain all attributes from cache, just get the one we need. - TAttribute[] specificAttributes = GetCustomAttributes(memberInfo, inherit); - return specificAttributes.Any(a => string.Equals(a.GetType().AssemblyQualifiedName, requiredAttributeQualifiedName, StringComparison.Ordinal)); + if (AttributeComparer.IsNonDerived(attribute)) + { + return true; + } } - return attributes.ContainsKey(requiredAttributeQualifiedName); + return false; } /// - /// Checks to see if the parameter memberInfo contains the parameter attribute or not. + /// Checks to see if a member or type is decorated with the given attribute. The type is checked exactly. If attribute is derived (inherits from) a class, e.g. [MyTestClass] from [TestClass] it won't match if you look for [TestClass]. The inherit parameter does not impact this checking. /// - /// Attribute to search for. - /// Member/Type to test. - /// Look through inheritance or not. - /// True if the specified attribute is defined on the type. - public virtual bool IsAttributeDefined(Type type, bool inherit) + /// Attribute to search for by fully qualified name. + /// Type to test. + /// Inspect inheritance chain of the member or class. E.g. if parent class has this attribute defined. + /// True if the attribute of the specified type is defined on this member or a class. + public virtual bool IsNonDerivedAttributeDefined(Type type, bool inherit) where TAttribute : Attribute - => IsAttributeDefined((MemberInfo)type.GetTypeInfo(), inherit); + => IsNonDerivedAttributeDefined((MemberInfo)type, inherit); /// - /// Checks to see if the parameter memberInfo contains the parameter attribute or not. + /// 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. - /// Type info to test. - /// Look through inheritance or not. - /// True if the specified attribute is defined on the type. - public virtual bool IsAttributeDefined(TypeInfo typeInfo, bool inherit) - where TAttribute : Attribute - => IsAttributeDefined((MemberInfo)typeInfo, inherit); - - /// - /// Returns true when specified class/member has attribute derived from specific attribute. - /// - /// The base attribute type. - /// The type. - /// Should look at inheritance tree. - /// An object derived from Attribute that corresponds to the instance of found attribute. - public virtual bool HasAttributeDerivedFrom(Type type, bool inherit) - where TAttribute : Attribute - => HasAttributeDerivedFrom((MemberInfo)type.GetTypeInfo(), inherit); - - /// - /// Returns true when specified class/member has attribute derived from specific attribute. - /// - /// The base attribute type. - /// The type info. - /// Should look at inheritance tree. - /// An object derived from Attribute that corresponds to the instance of found attribute. - public virtual bool HasAttributeDerivedFrom(TypeInfo typeInfo, bool inherit) + /// Type to test. + /// Inspect inheritance chain of the member or class. E.g. if parent class has this attribute defined. + /// True if the attribute of the specified type is defined on this member or a class. + public virtual bool IsDerivedAttributeDefined(Type type, bool inherit) where TAttribute : Attribute - => HasAttributeDerivedFrom((MemberInfo)typeInfo, inherit); + => IsDerivedAttributeDefined((MemberInfo)type, inherit); /// - /// Returns true when specified class/member has attribute derived from specific attribute. + /// 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. /// - /// The base attribute type. - /// The member info. - /// Should look at inheritance tree. - /// An object derived from Attribute that corresponds to the instance of found attribute. - public bool HasAttributeDerivedFrom(MemberInfo memberInfo, bool inherit) + /// Attribute to search for. + /// Member to inspect for attributes. + /// Inspect inheritance chain of the member or class. E.g. if parent class has this attribute defined. + /// True if the attribute of the specified type is defined on this member or a class. + public virtual /* for testing */ bool IsDerivedAttributeDefined(MemberInfo memberInfo, bool inherit) where TAttribute : Attribute { if (memberInfo == null) @@ -112,21 +103,14 @@ public bool HasAttributeDerivedFrom(MemberInfo memberInfo, bool inhe } // Get all attributes on the member. - Dictionary? attributes = GetAttributes(memberInfo, inherit); - if (attributes == null) - { - PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning($"{nameof(ReflectHelper)}.{nameof(GetAttributes)}: {Resource.FailedFetchAttributeCache}"); - - return IsAttributeDefined(memberInfo, inherit); - } + Attribute[] attributes = GetCustomAttributesCached(memberInfo, inherit); // Try to find the attribute that is derived from baseAttrType. - foreach (object attribute in attributes.Values) + foreach (Attribute attribute in attributes) { - DebugEx.Assert(attribute != null, $"{nameof(ReflectHelper)}.{nameof(GetAttributes)}: internal error: wrong value in the attributes dictionary."); + DebugEx.Assert(attribute != null, $"{nameof(ReflectHelper)}.{nameof(GetCustomAttributesCached)}: internal error: wrong value in the attributes dictionary."); - Type attributeType = attribute.GetType(); - if (attributeType.IsSubclassOf(typeof(TAttribute))) + if (AttributeComparer.IsDerived(attribute)) { return true; } @@ -149,10 +133,10 @@ public bool HasAttributeDerivedFrom(MemberInfo memberInfo, bool inhe DebugEx.Assert(methodInfo != null, "MethodInfo should be non-null"); // Get the expected exception attribute - ExpectedExceptionBaseAttribute[]? expectedExceptions; + ExpectedExceptionBaseAttribute? expectedException; try { - expectedExceptions = GetCustomAttributes(methodInfo, true); + expectedException = GetFirstDerivedAttributeOrDefault(methodInfo, inherit: true); } catch (Exception ex) { @@ -167,27 +151,7 @@ public bool HasAttributeDerivedFrom(MemberInfo memberInfo, bool inhe throw new TypeInspectionException(errorMessage); } - if (expectedExceptions == null || expectedExceptions.Length == 0) - { - return null; - } - - // Verify that there is only one attribute (multiple attributes derived from - // ExpectedExceptionBaseAttribute are not allowed on a test method) - if (expectedExceptions.Length > 1) - { - string errorMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_MultipleExpectedExceptionsOnTestMethod, - testMethod.FullClassName, - testMethod.Name); - throw new TypeInspectionException(errorMessage); - } - - // Set the expected exception attribute to use for this test - ExpectedExceptionBaseAttribute expectedException = expectedExceptions[0]; - - return expectedException; + return expectedException ?? null; } /// @@ -202,89 +166,70 @@ public bool HasAttributeDerivedFrom(MemberInfo memberInfo, bool inhe #endif public override object InitializeLifetimeService() => null!; - internal static TAttribute[]? GetAttributes(MethodBase methodBase, bool inherit) - where TAttribute : Attribute - { - TAttribute[]? attributeArray = GetCustomAttributes(methodBase, inherit); - return attributeArray.Length == 0 - ? null - : attributeArray; - } - /// - /// Match return type of method. + /// Gets first attribute that matches the type (but is not 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. /// - /// The method to inspect. - /// The return type to match. - /// True if there is a match. - internal static bool MatchReturnType(MethodInfo method, Type returnType) => method == null - ? throw new ArgumentNullException(nameof(method)) - : returnType == null ? throw new ArgumentNullException(nameof(returnType)) : method.ReturnType.Equals(returnType); - - /// - /// Get custom attributes on a member for both normal and reflection only load. - /// - /// Type of attribute to retrieve. - /// Member for which attributes needs to be retrieved. - /// If inherited type of attribute. - /// All attributes of give type on member. - [return: NotNullIfNotNull(nameof(memberInfo))] - internal static TAttribute[]? GetCustomAttributes(MemberInfo? memberInfo, bool inherit) - where TAttribute : Attribute + /// Type of the attribute to find. + /// The type, assembly or method. + /// If we should inspect parents of this type. + /// The attribute that is found or null. + /// Throws when multiple attributes are found (the attribute must allow multiple). + public TAttribute? GetFirstNonDerivedAttributeOrDefault(ICustomAttributeProvider attributeProvider, bool inherit) + where TAttribute : Attribute { - if (memberInfo == null) + Attribute[] cachedAttributes = GetCustomAttributesCached(attributeProvider, inherit); + + foreach (Attribute cachedAttribute in cachedAttributes) { - return null; + if (AttributeComparer.IsNonDerived(cachedAttribute)) + { + return (TAttribute)cachedAttribute; + } } - object[] attributesArray = PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes( - memberInfo, - typeof(TAttribute), - inherit); - - return attributesArray.OfType().ToArray(); + return null; } /// - /// Get custom attributes on a member for both normal and reflection only load. + /// 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. /// - /// Member for which attributes needs to be retrieved. - /// If inherited type of attribute. - /// All attributes of give type on member. - [return: NotNullIfNotNull(nameof(memberInfo))] - internal static object[]? GetCustomAttributes(MemberInfo memberInfo, bool inherit) + /// Type of the attribute to find. + /// The type, assembly or method. + /// If we should inspect parents of this type. + /// 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? GetFirstDerivedAttributeOrDefault(ICustomAttributeProvider attributeProvider, bool inherit) + where TAttribute : Attribute { - if (memberInfo == null) + Attribute[] cachedAttributes = GetCustomAttributesCached(attributeProvider, inherit); + + foreach (Attribute cachedAttribute in cachedAttributes) { - return null; + if (AttributeComparer.IsDerived(cachedAttribute)) + { + return (TAttribute)cachedAttribute; + } } - object[] attributesArray = PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes( - memberInfo, - inherit); - - return attributesArray.ToArray(); + return null; } /// - /// Returns the first attribute of the specified type or null if no attribute - /// of the specified type is set on the method. + /// Match return type of method. /// - /// The type of attribute to return. - /// The method on which the attribute is defined. - /// The attribute or null if none exists. - internal TAttribute? GetAttribute(MethodInfo method) - where TAttribute : Attribute - { - if (IsAttributeDefined(method, false)) - { - TAttribute[] attributes = GetCustomAttributes(method, false); - DebugEx.Assert(attributes.Length == 1, "Should only be one attribute."); - return attributes[0]; - } - - return null; - } + /// The method to inspect. + /// The return type to match. + /// True if there is a match. + internal static bool MatchReturnType(MethodInfo method, Type returnType) => + method == null + ? throw new ArgumentNullException(nameof(method)) + : returnType == null + ? throw new ArgumentNullException(nameof(returnType)) + : method.ReturnType.Equals(returnType); /// /// Returns true when the method is declared in the assembly where the type is declared. @@ -301,20 +246,13 @@ internal virtual bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Ty /// The member to inspect. /// The reflected type that owns . /// Categories defined. - internal virtual string[] GetCategories(MemberInfo categoryAttributeProvider, Type owningType) + internal virtual /* for tests, we are mocking this */ string[] GetTestCategories(MemberInfo categoryAttributeProvider, Type owningType) { - IEnumerable categories = GetCustomAttributesRecursively(categoryAttributeProvider, owningType); - List testCategories = []; - - if (categories != null) - { - foreach (TestCategoryBaseAttribute category in categories.Cast()) - { - testCategories.AddRange(category.TestCategories); - } - } + IEnumerable? methodCategories = GetDerivedAttributes(categoryAttributeProvider, inherit: true); + IEnumerable? typeCategories = GetDerivedAttributes(owningType, inherit: true); + IEnumerable? assemblyCategories = GetDerivedAttributes(owningType.Assembly, inherit: true); - return testCategories.ToArray(); + return methodCategories.Concat(typeCategories).Concat(assemblyCategories).SelectMany(c => c.TestCategories).ToArray(); } /// @@ -334,8 +272,8 @@ internal virtual string[] GetCategories(MemberInfo categoryAttributeProvider, Ty /// The type that owns . /// True if test method should not run in parallel. internal bool IsDoNotParallelizeSet(MemberInfo testMethod, Type owningType) - => GetCustomAttributes(testMethod).Length != 0 - || GetCustomAttributes(owningType.GetTypeInfo()).Length != 0; + => IsDerivedAttributeDefined(testMethod, inherit: true) + || IsDerivedAttributeDefined(owningType, inherit: true); /// /// Get the parallelization behavior for a test assembly. @@ -356,68 +294,6 @@ internal static bool IsDoNotParallelizeSet(Assembly assembly) .OfType() .FirstOrDefault(); - /// - /// Gets custom attributes at the class and assembly for a method. - /// - /// Method Info or Member Info or a Type. - /// The type that owns . - /// The categories of the specified type on the method. - internal IEnumerable GetCustomAttributesRecursively(MemberInfo attributeProvider, Type owningType) - { - TestCategoryBaseAttribute[]? categories = GetCustomAttributes(attributeProvider); - if (categories != null) - { - categories = categories.Concat(GetCustomAttributes(owningType.GetTypeInfo())).ToArray(); - } - - if (categories != null) - { - categories = categories.Concat(GetCustomAttributeForAssembly(owningType.GetTypeInfo())).ToArray(); - } - - return categories ?? Enumerable.Empty(); - } - - /// - /// Gets the custom attributes on the assembly of a member info - /// NOTE: having it as separate virtual method, so that we can extend it for testing. - /// - /// The attribute type to find. - /// The member to inspect. - /// Custom attributes defined. - internal virtual TAttribute[] GetCustomAttributeForAssembly(MemberInfo memberInfo) - where TAttribute : Attribute - => PlatformServiceProvider.Instance.ReflectionOperations - .GetCustomAttributes(memberInfo.Module.Assembly, typeof(TAttribute)) - .OfType() - .ToArray(); - - /// - /// Gets the custom attributes of the provided type on a memberInfo. - /// - /// The attribute type. - /// The member to reflect on. - /// Attributes defined. - internal virtual TAttribute[] GetCustomAttributes(MemberInfo attributeProvider) - where TAttribute : Attribute - => GetCustomAttributes(attributeProvider, true); - - /// - /// Gets the first custom attribute of the provided type on a memberInfo. - /// - /// The attribute type. - /// The member to reflect on. - /// Attribute defined. - internal virtual TAttribute? GetCustomAttribute(MemberInfo attributeProvider) - where TAttribute : Attribute - { - TAttribute[] attribute = GetCustomAttributes(attributeProvider, true); - - return attribute.Length != 1 - ? null - : attribute[0]; - } - /// /// KeyValue pairs that are provided by TestOwnerAttribute of the given test method. /// @@ -447,14 +323,8 @@ internal virtual TAttribute[] GetCustomAttributes(MemberInfo attribu /// /// The member to inspect. /// Priority value if defined. Null otherwise. - internal virtual int? GetPriority(MemberInfo priorityAttributeProvider) - { - PriorityAttribute[] priorityAttribute = GetCustomAttributes(priorityAttributeProvider, true); - - return priorityAttribute.Length != 1 - ? null - : priorityAttribute[0].Priority; - } + internal virtual int? GetPriority(MemberInfo priorityAttributeProvider) => + GetFirstDerivedAttributeOrDefault(priorityAttributeProvider, inherit: true)?.Priority; /// /// Priority if any set for test method. Will return priority if attribute is applied to TestMethod @@ -462,14 +332,8 @@ internal virtual TAttribute[] GetCustomAttributes(MemberInfo attribu /// /// The member to inspect. /// Priority value if defined. Null otherwise. - internal virtual string? GetIgnoreMessage(MemberInfo ignoreAttributeProvider) - { - IgnoreAttribute[]? ignoreAttribute = GetCustomAttributes(ignoreAttributeProvider, true); - - return ignoreAttribute.Length == 0 - ? null - : ignoreAttribute[0].IgnoreMessage; - } + internal virtual string? GetIgnoreMessage(MemberInfo ignoreAttributeProvider) => + GetFirstDerivedAttributeOrDefault(ignoreAttributeProvider, inherit: true)?.IgnoreMessage; /// /// Gets the class cleanup lifecycle for the class, if set. @@ -478,6 +342,7 @@ internal virtual TAttribute[] GetCustomAttributes(MemberInfo attribu /// Returns if provided, otherwise null. internal virtual ClassCleanupBehavior? GetClassCleanupBehavior(TestClassInfo classInfo) { + // TODO: not discovery related but seems expensive and unnecessary, because we do inheritance lookup, and to put the method into the stack we've already did this lookup before? if (!classInfo.HasExecutableCleanupMethod) { return null; @@ -486,9 +351,9 @@ internal virtual TAttribute[] GetCustomAttributes(MemberInfo attribu var cleanupBehaviors = new HashSet( classInfo.BaseClassCleanupMethodsStack - .Select(x => x.GetCustomAttribute(true)?.CleanupBehavior)) + .Select(x => GetFirstDerivedAttributeOrDefault(x, inherit: true)?.CleanupBehavior)) { - classInfo.ClassCleanupMethod?.GetCustomAttribute(true)?.CleanupBehavior, + classInfo.ClassCleanupMethod == null ? null : GetFirstDerivedAttributeOrDefault(classInfo.ClassCleanupMethod, inherit: true)?.CleanupBehavior, }; return cleanupBehaviors.Contains(ClassCleanupBehavior.EndOfClass) @@ -503,7 +368,7 @@ internal virtual TAttribute[] GetCustomAttributes(MemberInfo attribu /// List of traits. internal virtual IEnumerable GetTestPropertiesAsTraits(MemberInfo testPropertyProvider) { - TestPropertyAttribute[] testPropertyAttributes = GetCustomAttributes(testPropertyProvider, true); + IEnumerable testPropertyAttributes = GetDerivedAttributes(testPropertyProvider, inherit: true); foreach (TestPropertyAttribute testProperty in testPropertyAttributes) { @@ -516,62 +381,24 @@ internal virtual IEnumerable GetTestPropertiesAsTraits(MemberInfo testPro /// Get attribute defined on a method which is of given type of subtype of given type. /// /// The attribute type. - /// The member to inspect. - /// Look at inheritance chain. - /// An instance of the attribute. - internal TAttributeType? GetDerivedAttribute(MemberInfo memberInfo, bool inherit) - where TAttributeType : Attribute - { - Dictionary? attributes = GetAttributes(memberInfo, inherit); - if (attributes == null) - { - return null; - } - - // Try to find the attribute that is derived from baseAttrType. - foreach (object attribute in attributes.Values) - { - DebugEx.Assert(attribute != null, "ReflectHelper.DefinesAttributeDerivedFrom: internal error: wrong value in the attributes dictionary."); - - Type attributeType = attribute.GetType(); - if (attributeType.Equals(typeof(TAttributeType)) || attributeType.IsSubclassOf(typeof(TAttributeType))) - { - return attribute as TAttributeType; - } - } - - return null; - } - - /// - /// Get attribute defined on a method which is of given type of subtype of given type. - /// - /// The attribute type. - /// The type to inspect. + /// The member to inspect. /// Look at inheritance chain. /// An instance of the attribute. - internal static TAttributeType? GetDerivedAttribute(Type type, bool inherit) + internal virtual /* for tests, for moq */ IEnumerable GetDerivedAttributes(ICustomAttributeProvider attributeProvider, bool inherit) where TAttributeType : Attribute { - object[] attributes = GetCustomAttributes(type.GetTypeInfo(), inherit); - if (attributes == null) - { - return null; - } + Attribute[] attributes = GetCustomAttributesCached(attributeProvider, inherit); // Try to find the attribute that is derived from baseAttrType. - foreach (object attribute in attributes) + foreach (Attribute attribute in attributes) { DebugEx.Assert(attribute != null, "ReflectHelper.DefinesAttributeDerivedFrom: internal error: wrong value in the attributes dictionary."); - Type attributeType = attribute.GetType(); - if (attributeType.Equals(typeof(TAttributeType)) || attributeType.IsSubclassOf(typeof(TAttributeType))) + if (AttributeComparer.IsDerived(attribute)) { - return attribute as TAttributeType; + yield return (TAttributeType)attribute; } } - - return null; } /// @@ -579,39 +406,49 @@ internal virtual IEnumerable GetTestPropertiesAsTraits(MemberInfo testPro /// /// The member to inspect. /// owner if attribute is applied to TestMethod, else null. - private static string? GetOwner(MemberInfo ownerAttributeProvider) + private string? GetOwner(MemberInfo ownerAttributeProvider) { - OwnerAttribute[] ownerAttribute = GetCustomAttributes(ownerAttributeProvider, true); + OwnerAttribute? ownerAttribute = GetFirstDerivedAttributeOrDefault(ownerAttributeProvider, inherit: true); - return ownerAttribute.Length != 1 - ? null - : ownerAttribute[0].Owner; + return ownerAttribute?.Owner; } /// - /// Get the Attributes (TypeName/TypeObject) for a given member. + /// Gets and caches the attributes for the given type, or method. /// - /// The member to inspect. + /// The member to inspect. /// Look at inheritance chain. /// attributes defined. - private Dictionary? GetAttributes(MemberInfo memberInfo, bool inherit) + private Attribute[] GetCustomAttributesCached(ICustomAttributeProvider attributeProvider, bool inherit) { + if (inherit) + { + lock (_inheritedAttributeCache) + { + return GetOrAddAttributes(_inheritedAttributeCache, attributeProvider, inherit: true); + } + } + else + { + lock (_nonInheritedAttributeCache) + { + return GetOrAddAttributes(_nonInheritedAttributeCache, attributeProvider, inherit: false); + } + } + // If the information is cached, then use it otherwise populate the cache using // the reflection APIs. - lock (_attributeCache) + Attribute[] GetOrAddAttributes(Dictionary cache, ICustomAttributeProvider attributeProvider, bool inherit) { - if (_attributeCache.TryGetValue(memberInfo, out Dictionary? attributes)) + if (cache.TryGetValue(attributeProvider, out Attribute[]? attributes)) { return attributes; } // Populate the cache - attributes = []; - - object[]? customAttributesArray; try { - customAttributesArray = GetCustomAttributes(memberInfo, inherit); + attributes = NotCachedAttributes.GetCustomAttributesNotCached(attributeProvider, inherit)?.Cast().ToArray() ?? Array.Empty(); } catch (Exception ex) { @@ -627,25 +464,58 @@ internal virtual IEnumerable GetTestPropertiesAsTraits(MemberInfo testPro description = string.Format(CultureInfo.CurrentCulture, Resource.ExceptionOccuredWhileGettingTheExceptionDescription, ex.GetType().FullName, ex2.GetType().FullName); // ex.GetType().FullName + } - PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning(Resource.FailedToGetCustomAttribute, memberInfo.GetType().FullName!, description); + PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning(Resource.FailedToGetCustomAttribute, attributeProvider.GetType().FullName!, description); - // Since we cannot check by attribute names, do it in reflection way. - // Note 1: this will not work for different version of assembly but it is better than nothing. - // Note 2: we cannot cache this because we don't know if there are other attributes defined. - return null; + return Array.Empty(); } - DebugEx.Assert(customAttributesArray != null, "attributes should not be null."); + DebugEx.Assert(attributes != null, "attributes should not be null."); + + cache.Add(attributeProvider, attributes); + + return attributes; + } + } + + internal IEnumerable? GetNonDerivedAttributes(MethodInfo methodInfo, bool inherit) + where TAttribute : Attribute + { + Attribute[] cachedAttributes = GetCustomAttributesCached(methodInfo, inherit); - foreach (object customAttribute in customAttributesArray) + foreach (Attribute cachedAttribute in cachedAttributes) + { + if (AttributeComparer.IsNonDerived(cachedAttribute)) { - Type attributeType = customAttribute.GetType(); - attributes[attributeType.AssemblyQualifiedName!] = customAttribute; + yield return (TAttribute)cachedAttribute; } + } + } - _attributeCache.Add(memberInfo, attributes); + /// + /// Reflection helper that is accessing Reflection directly, and won't cache the results. + /// + internal class NotCachedReflectionAccessor : INotCachedReflectionAccessor + { + /// + /// Get custom attributes on a member without cache. + /// + /// Member for which attributes needs to be retrieved. + /// If inherited type of attribute. + /// All attributes of give type on member. + public object[]? GetCustomAttributesNotCached(ICustomAttributeProvider attributeProvider, bool inherit) + { + object[] attributesArray = attributeProvider is MemberInfo memberInfo + ? PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes(memberInfo, inherit) + : PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes((Assembly)attributeProvider, typeof(Attribute)); - return attributes; + 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. + _inheritedAttributeCache.Clear(); + _nonInheritedAttributeCache.Clear(); + } } diff --git a/src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj b/src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj index 7c69546912..067bd24771 100644 --- a/src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj +++ b/src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj @@ -1,4 +1,4 @@ - + @@ -54,6 +54,7 @@ + diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/FileOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/FileOperations.cs index 65100a103c..46275bb72f 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/FileOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/FileOperations.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Collections.Concurrent; using System.Reflection; #if WIN_UI @@ -19,6 +20,8 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; /// public class FileOperations : IFileOperations { + private readonly ConcurrentDictionary _assemblyCache = new(); + #if WIN_UI private readonly bool _isPackaged; @@ -45,11 +48,19 @@ public Assembly LoadAssembly(string assemblyName, bool isReflectionOnly) } #endif string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(assemblyName); - return Assembly.Load(new AssemblyName(fileNameWithoutExtension)); + Assembly assembly = _assemblyCache.GetOrAdd(fileNameWithoutExtension, fileNameWithoutExtension => Assembly.Load(new AssemblyName(fileNameWithoutExtension))); + + return assembly; #elif NETFRAMEWORK -#pragma warning disable IDE0022 // Use expression body for method - return isReflectionOnly ? Assembly.ReflectionOnlyLoadFrom(assemblyName) : Assembly.LoadFrom(assemblyName); -#pragma warning restore IDE0022 // Use expression body for method + if (isReflectionOnly) + { + return Assembly.ReflectionOnlyLoadFrom(assemblyName); + } + else + { + Assembly assembly = _assemblyCache.GetOrAdd(assemblyName, Assembly.LoadFrom); + return assembly; + } #endif } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs index 6882b1a48e..b3408b01b6 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. #if !WINDOWS_UWP @@ -19,6 +19,7 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Uti /// internal class DeploymentItemUtility { + // REVIEW: it would be better if this was a ReflectionHelper, because helper is able to cache. But we don't have reflection helper here, because this is platform services dll. private readonly ReflectionUtility _reflectionUtility; /// diff --git a/src/TestFramework/TestFramework/Attributes/TestMethod/ExpectedExceptionAttribute.cs b/src/TestFramework/TestFramework/Attributes/TestMethod/ExpectedExceptionAttribute.cs index ab5cfca150..1b2aff0347 100644 --- a/src/TestFramework/TestFramework/Attributes/TestMethod/ExpectedExceptionAttribute.cs +++ b/src/TestFramework/TestFramework/Attributes/TestMethod/ExpectedExceptionAttribute.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Globalization; diff --git a/test/UnitTests/MSTestAdapter.UnitTests/Discovery/AssemblyEnumeratorTests.cs b/test/UnitTests/MSTestAdapter.UnitTests/Discovery/AssemblyEnumeratorTests.cs index 2b49fb179d..024b6fe98a 100644 --- a/test/UnitTests/MSTestAdapter.UnitTests/Discovery/AssemblyEnumeratorTests.cs +++ b/test/UnitTests/MSTestAdapter.UnitTests/Discovery/AssemblyEnumeratorTests.cs @@ -413,13 +413,14 @@ internal TestableAssemblyEnumerator() reflectHelper.Object, typeValidator.Object, testMethodValidator.Object, + TestDataSourceDiscoveryOption.DuringExecution, TestIdGenerationStrategy.FullyQualified); } internal Mock MockTypeEnumerator { get; set; } internal override TypeEnumerator GetTypeEnumerator(Type type, string assemblyFileName, bool discoverInternals, - TestIdGenerationStrategy testIdGenerationStrategy) + TestDataSourceDiscoveryOption discoveryOption, TestIdGenerationStrategy testIdGenerationStrategy) => MockTypeEnumerator.Object; } diff --git a/test/UnitTests/MSTestAdapter.UnitTests/Discovery/TestMethodValidatorTests.cs b/test/UnitTests/MSTestAdapter.UnitTests/Discovery/TestMethodValidatorTests.cs index dafafa633f..e7c3a59f05 100644 --- a/test/UnitTests/MSTestAdapter.UnitTests/Discovery/TestMethodValidatorTests.cs +++ b/test/UnitTests/MSTestAdapter.UnitTests/Discovery/TestMethodValidatorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Diagnostics.CodeAnalysis; @@ -39,7 +39,7 @@ public TestMethodValidatorTests() public void IsValidTestMethodShouldReturnFalseForMethodsWithoutATestMethodAttributeOrItsDerivedAttributes() { _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(It.IsAny(), false)).Returns(false); + rh => rh.IsDerivedAttributeDefined(It.IsAny(), false)).Returns(false); Verify(!_testMethodValidator.IsValidTestMethod(_mockMethodInfo.Object, _type, _warnings)); } @@ -56,6 +56,7 @@ public void IsValidTestMethodShouldReturnFalseForGenericTestMethodDefinitions() public void IsValidTestMethodShouldReportWarningsForGenericTestMethodDefinitions() { SetupTestMethod(); + _mockMethodInfo.Setup(mi => mi.IsGenericMethodDefinition).Returns(true); _mockMethodInfo.Setup(mi => mi.DeclaringType.FullName).Returns("DummyTestClass"); _mockMethodInfo.Setup(mi => mi.Name).Returns("DummyTestMethod"); @@ -183,7 +184,7 @@ public void WhenDiscoveryOfInternalsIsEnabledIsValidTestMethodShouldReturnFalseF #endregion private void SetupTestMethod() => _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(It.IsAny(), false)).Returns(true); + rh => rh.IsDerivedAttributeDefined(It.IsAny(), false)).Returns(true); } #region Dummy types diff --git a/test/UnitTests/MSTestAdapter.UnitTests/Discovery/TypeEnumeratorTests.cs b/test/UnitTests/MSTestAdapter.UnitTests/Discovery/TypeEnumeratorTests.cs index f8cc381bd5..542b58375e 100644 --- a/test/UnitTests/MSTestAdapter.UnitTests/Discovery/TypeEnumeratorTests.cs +++ b/test/UnitTests/MSTestAdapter.UnitTests/Discovery/TypeEnumeratorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Reflection; @@ -281,9 +281,9 @@ public void GetTestFromMethodShouldSetIgnoredPropertyToFalseIfNotSetOnTestClassA // Setup mocks _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(typeof(DummyTestClass), false)).Returns(false); + rh => rh.IsNonDerivedAttributeDefined(typeof(DummyTestClass), false)).Returns(false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(methodInfo, false)).Returns(false); + rh => rh.IsNonDerivedAttributeDefined(methodInfo, false)).Returns(false); MSTest.TestAdapter.ObjectModel.UnitTestElement testElement = typeEnumerator.GetTestFromMethod(methodInfo, true, _warnings); @@ -299,7 +299,7 @@ public void GetTestFromMethodShouldSetTestCategory() string[] testCategories = ["foo", "bar"]; // Setup mocks - _mockReflectHelper.Setup(rh => rh.GetCategories(methodInfo, typeof(DummyTestClass))).Returns(testCategories); + _mockReflectHelper.Setup(rh => rh.GetTestCategories(methodInfo, typeof(DummyTestClass))).Returns(testCategories); MSTest.TestAdapter.ObjectModel.UnitTestElement testElement = typeEnumerator.GetTestFromMethod(methodInfo, true, _warnings); @@ -314,7 +314,7 @@ public void GetTestFromMethodShouldSetDoNotParallelize() MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("MethodWithVoidReturnType"); // Setup mocks - _mockReflectHelper.Setup(rh => rh.GetCustomAttributes(It.IsAny())).Returns([new DoNotParallelizeAttribute()]); + _mockReflectHelper.Setup(rh => rh.IsDerivedAttributeDefined(It.IsAny(), true)).Returns(true); MSTest.TestAdapter.ObjectModel.UnitTestElement testElement = typeEnumerator.GetTestFromMethod(methodInfo, true, _warnings); @@ -397,7 +397,7 @@ public void GetTestFromMethodShouldSetDescription() SetupTestClassAndTestMethods(isValidTestClass: true, isValidTestMethod: true, isMethodFromSameAssembly: true); TypeEnumerator typeEnumerator = GetTypeEnumeratorInstance(typeof(DummyTestClass), "DummyAssemblyName"); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("MethodWithVoidReturnType"); - _mockReflectHelper.Setup(rh => rh.GetCustomAttribute(methodInfo)).Returns(new DescriptionAttribute("Dummy description")); + _mockReflectHelper.Setup(rh => rh.GetFirstDerivedAttributeOrDefault(methodInfo, true)).Returns(new DescriptionAttribute("Dummy description")); MSTest.TestAdapter.ObjectModel.UnitTestElement testElement = typeEnumerator.GetTestFromMethod(methodInfo, true, _warnings); @@ -409,7 +409,7 @@ public void GetTestFromMethodShouldSetWorkItemIds() SetupTestClassAndTestMethods(isValidTestClass: true, isValidTestMethod: true, isMethodFromSameAssembly: true); TypeEnumerator typeEnumerator = GetTypeEnumeratorInstance(typeof(DummyTestClass), "DummyAssemblyName"); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("MethodWithVoidReturnType"); - _mockReflectHelper.Setup(rh => rh.GetCustomAttributes(methodInfo)).Returns([new(123), new(345)]); + _mockReflectHelper.Setup(rh => rh.GetDerivedAttributes(methodInfo, true)).Returns([new(123), new(345)]); MSTest.TestAdapter.ObjectModel.UnitTestElement testElement = typeEnumerator.GetTestFromMethod(methodInfo, true, _warnings); @@ -421,7 +421,7 @@ public void GetTestFromMethodShouldSetWorkItemIdsToNullIfNotAny() SetupTestClassAndTestMethods(isValidTestClass: true, isValidTestMethod: true, isMethodFromSameAssembly: true); TypeEnumerator typeEnumerator = GetTypeEnumeratorInstance(typeof(DummyTestClass), "DummyAssemblyName"); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("MethodWithVoidReturnType"); - _mockReflectHelper.Setup(rh => rh.GetCustomAttributes(methodInfo)).Returns([]); + _mockReflectHelper.Setup(rh => rh.GetDerivedAttributes(methodInfo, true)).Returns([]); MSTest.TestAdapter.ObjectModel.UnitTestElement testElement = typeEnumerator.GetTestFromMethod(methodInfo, true, _warnings); @@ -433,7 +433,7 @@ public void GetTestFromMethodShouldSetCssIteration() SetupTestClassAndTestMethods(isValidTestClass: true, isValidTestMethod: true, isMethodFromSameAssembly: true); TypeEnumerator typeEnumerator = GetTypeEnumeratorInstance(typeof(DummyTestClass), "DummyAssemblyName"); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("MethodWithVoidReturnType"); - _mockReflectHelper.Setup(rh => rh.GetCustomAttribute(methodInfo)).Returns(new CssIterationAttribute("234")); + _mockReflectHelper.Setup(rh => rh.GetFirstDerivedAttributeOrDefault(methodInfo, true)).Returns(new CssIterationAttribute("234")); MSTest.TestAdapter.ObjectModel.UnitTestElement testElement = typeEnumerator.GetTestFromMethod(methodInfo, true, _warnings); @@ -445,7 +445,7 @@ public void GetTestFromMethodShouldSetCssProjectStructure() SetupTestClassAndTestMethods(isValidTestClass: true, isValidTestMethod: true, isMethodFromSameAssembly: true); TypeEnumerator typeEnumerator = GetTypeEnumeratorInstance(typeof(DummyTestClass), "DummyAssemblyName"); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("MethodWithVoidReturnType"); - _mockReflectHelper.Setup(rh => rh.GetCustomAttribute(methodInfo)).Returns(new CssProjectStructureAttribute("ProjectStructure123")); + _mockReflectHelper.Setup(rh => rh.GetFirstDerivedAttributeOrDefault(methodInfo, true)).Returns(new CssProjectStructureAttribute("ProjectStructure123")); MSTest.TestAdapter.ObjectModel.UnitTestElement testElement = typeEnumerator.GetTestFromMethod(methodInfo, true, _warnings); @@ -513,7 +513,7 @@ public void GetTestFromMethodShouldSetDisplayNameToTestMethodNameIfDisplayNameIs // Setup mocks to behave like we have [TestMethod] attribute on the method _mockReflectHelper.Setup( - rh => rh.GetCustomAttribute(It.IsAny())).Returns(new TestMethodAttribute()); + rh => rh.GetFirstDerivedAttributeOrDefault(It.IsAny(), false)).Returns(new TestMethodAttribute()); MSTest.TestAdapter.ObjectModel.UnitTestElement testElement = typeEnumerator.GetTestFromMethod(methodInfo, true, _warnings); @@ -529,7 +529,7 @@ public void GetTestFromMethodShouldSetDisplayNameFromTestMethodAttribute() // Setup mocks to behave like we have [TestMethod("Test method display name.")] attribute on the method _mockReflectHelper.Setup( - rh => rh.GetCustomAttribute(methodInfo)).Returns(new TestMethodAttribute("Test method display name.")); + rh => rh.GetFirstDerivedAttributeOrDefault(methodInfo, true)).Returns(new TestMethodAttribute("Test method display name.")); MSTest.TestAdapter.ObjectModel.UnitTestElement testElement = typeEnumerator.GetTestFromMethod(methodInfo, true, _warnings); @@ -545,7 +545,7 @@ public void GetTestFromMethodShouldSetDisplayNameFromDataTestMethodAttribute() // Setup mocks to behave like we have [DataTestMethod("Test method display name.")] attribute on the method _mockReflectHelper.Setup( - rh => rh.GetCustomAttribute(methodInfo)).Returns(new DataTestMethodAttribute("Test method display name.")); + rh => rh.GetFirstDerivedAttributeOrDefault(methodInfo, true)).Returns(new DataTestMethodAttribute("Test method display name.")); MSTest.TestAdapter.ObjectModel.UnitTestElement testElement = typeEnumerator.GetTestFromMethod(methodInfo, true, _warnings); @@ -568,6 +568,7 @@ private void SetupTestClassAndTestMethods(bool isValidTestClass, bool isValidTes } private TypeEnumerator GetTypeEnumeratorInstance(Type type, string assemblyName, + TestDataSourceDiscoveryOption discoveryOption = TestDataSourceDiscoveryOption.DuringExecution, TestIdGenerationStrategy idGenerationStrategy = TestIdGenerationStrategy.FullyQualified) => new( type, @@ -575,6 +576,7 @@ private void SetupTestClassAndTestMethods(bool isValidTestClass, bool isValidTes _mockReflectHelper.Object, _mockTypeValidator.Object, _mockTestMethodValidator.Object, + discoveryOption, idGenerationStrategy); #endregion diff --git a/test/UnitTests/MSTestAdapter.UnitTests/Discovery/TypeValidatorTests.cs b/test/UnitTests/MSTestAdapter.UnitTests/Discovery/TypeValidatorTests.cs index 86c27e760e..f9b1a109ca 100644 --- a/test/UnitTests/MSTestAdapter.UnitTests/Discovery/TypeValidatorTests.cs +++ b/test/UnitTests/MSTestAdapter.UnitTests/Discovery/TypeValidatorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Globalization; @@ -44,15 +44,15 @@ public TypeValidatorTests() public void IsValidTestClassShouldReturnFalseForClassesNotHavingTestClassAttributeOrDerivedAttributeTypes() { - _mockReflectHelper.Setup(rh => rh.IsAttributeDefined(It.IsAny(), false)).Returns(false); + _mockReflectHelper.Setup(rh => rh.IsNonDerivedAttributeDefined(It.IsAny(), false)).Returns(false); Verify(!_typeValidator.IsValidTestClass(typeof(TypeValidatorTests), _warnings)); } public void IsValidTestClassShouldReturnTrueForClassesMarkedByAnAttributeDerivedFromTestClass() { - _mockReflectHelper.Setup(rh => rh.IsAttributeDefined(It.IsAny(), false)).Returns(false); + _mockReflectHelper.Setup(rh => rh.IsNonDerivedAttributeDefined(It.IsAny(), false)).Returns(false); _mockReflectHelper.Setup( - rh => rh.HasAttributeDerivedFrom(It.IsAny(), false)).Returns(true); + rh => rh.IsDerivedAttributeDefined(It.IsAny(), false)).Returns(true); Verify(_typeValidator.IsValidTestClass(typeof(TypeValidatorTests), _warnings)); } @@ -403,7 +403,7 @@ private static Type[] GetAllTestTypes() #region private methods - private void SetupTestClass() => _mockReflectHelper.Setup(rh => rh.IsAttributeDefined(It.IsAny(), false)).Returns(true); + private void SetupTestClass() => _mockReflectHelper.Setup(rh => rh.IsDerivedAttributeDefined(It.IsAny(), false)).Returns(true); #endregion } diff --git a/test/UnitTests/MSTestAdapter.UnitTests/Execution/TestMethodRunnerTests.cs b/test/UnitTests/MSTestAdapter.UnitTests/Execution/TestMethodRunnerTests.cs index 31e659ac41..8c47711976 100644 --- a/test/UnitTests/MSTestAdapter.UnitTests/Execution/TestMethodRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.UnitTests/Execution/TestMethodRunnerTests.cs @@ -7,6 +7,7 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; @@ -89,6 +90,8 @@ public TestMethodRunnerTests() _testablePlatformServiceProvider = new TestablePlatformServiceProvider(); _testablePlatformServiceProvider.SetupMockReflectionOperations(); PlatformServiceProvider.Instance = _testablePlatformServiceProvider; + + ReflectHelper.Instance.ClearCache(); } protected override void Dispose(bool disposing) @@ -306,7 +309,7 @@ public void RunTestMethodShouldRunDataDrivenTestsWhenDataIsProvidedUsingDataSour TestDataSource testDataSource = new(); // Setup mocks - _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(_methodInfo, It.IsAny(), It.IsAny())).Returns(attributes); + _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(_methodInfo, It.IsAny())).Returns(attributes); _testablePlatformServiceProvider.MockTestDataSource.Setup(tds => tds.GetData(testMethodInfo, _testContextImplementation)).Returns(new object[] { 1, 2, 3 }); UnitTestResult[] results = testMethodRunner.RunTestMethod(); @@ -352,7 +355,7 @@ public void RunTestMethodShouldSetDataRowIndexForDataDrivenTestsWhenDataIsProvid var attributes = new Attribute[] { dataSourceAttribute }; // Setup mocks - _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(_methodInfo, It.IsAny(), It.IsAny())).Returns(attributes); + _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(_methodInfo, It.IsAny())).Returns(attributes); _testablePlatformServiceProvider.MockTestDataSource.Setup(tds => tds.GetData(testMethodInfo, _testContextImplementation)).Returns(new object[] { 1, 2, 3 }); UnitTestResult[] results = testMethodRunner.RunTestMethod(); @@ -363,7 +366,7 @@ public void RunTestMethodShouldSetDataRowIndexForDataDrivenTestsWhenDataIsProvid Verify(results[2].DatarowIndex == 2); } - public void RunTestMethodShoudlRunOnlyDataSourceTestsWhenBothDataSourceAndDataRowAreProvided() + public void RunTestMethodShouldRunOnlyDataSourceTestsWhenBothDataSourceAndDataRowAreProvided() { var testMethodInfo = new TestableTestmethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => new UTF.TestResult()); var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation, false); @@ -378,7 +381,7 @@ public void RunTestMethodShoudlRunOnlyDataSourceTestsWhenBothDataSourceAndDataRo var attributes = new Attribute[] { dataSourceAttribute, dataRowAttribute }; // Setup mocks - _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(_methodInfo, It.IsAny(), It.IsAny())).Returns(attributes); + _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(_methodInfo, It.IsAny())).Returns(attributes); _testablePlatformServiceProvider.MockTestDataSource.Setup(tds => tds.GetData(testMethodInfo, _testContextImplementation)).Returns(new object[] { 1, 2, 3 }); UnitTestResult[] results = testMethodRunner.RunTestMethod(); @@ -405,7 +408,7 @@ public void RunTestMethodShouldFillInDisplayNameWithDataRowDisplayNameIfProvided var attributes = new Attribute[] { dataRowAttribute }; // Setup mocks - _testablePlatformServiceProvider.MockReflectionOperations.Setup(ro => ro.GetCustomAttributes(_methodInfo, It.IsAny(), It.IsAny())).Returns(attributes); + _testablePlatformServiceProvider.MockReflectionOperations.Setup(ro => ro.GetCustomAttributes(_methodInfo, It.IsAny())).Returns(attributes); UnitTestResult[] results = testMethodRunner.RunTestMethod(); @@ -428,7 +431,7 @@ public void RunTestMethodShouldFillInDisplayNameWithDataRowArgumentsIfNoDisplayN var attributes = new Attribute[] { dataRowAttribute }; // Setup mocks - _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(_methodInfo, It.IsAny(), It.IsAny())).Returns(attributes); + _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(_methodInfo, It.IsAny())).Returns(attributes); UnitTestResult[] results = testMethodRunner.RunTestMethod(); @@ -454,7 +457,7 @@ public void RunTestMethodShouldSetResultFilesIfPresentForDataDrivenTests() var attributes = new Attribute[] { dataRowAttribute1, dataRowAttribute2 }; // Setup mocks - _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(_methodInfo, It.IsAny(), It.IsAny())).Returns(attributes); + _testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(_methodInfo, It.IsAny())).Returns(attributes); UnitTestResult[] results = testMethodRunner.RunTestMethod(); Verify(results[0].ResultFiles.ToList().Contains("C:\\temp.txt")); diff --git a/test/UnitTests/MSTestAdapter.UnitTests/Execution/TypeCacheTests.cs b/test/UnitTests/MSTestAdapter.UnitTests/Execution/TypeCacheTests.cs index 7e8bc42f6c..9737d3f08b 100644 --- a/test/UnitTests/MSTestAdapter.UnitTests/Execution/TypeCacheTests.cs +++ b/test/UnitTests/MSTestAdapter.UnitTests/Execution/TypeCacheTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Globalization; @@ -10,6 +10,7 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -38,6 +39,8 @@ public TypeCacheTests() _testablePlatformServiceProvider = new TestablePlatformServiceProvider(); PlatformServiceProvider.Instance = _testablePlatformServiceProvider; + ReflectHelper.Instance.ClearCache(); + SetupMocks(); } @@ -177,7 +180,7 @@ public void GetTestMethodInfoShouldSetTestContextIfPresent() var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type, true)).Returns(true); TestMethodInfo testMethodInfo = _typeCache.GetTestMethodInfo( testMethod, @@ -195,7 +198,7 @@ public void GetTestMethodInfoShouldSetTestContextToNullIfNotPresent() var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type, true)).Returns(true); TestMethodInfo testMethodInfo = _typeCache.GetTestMethodInfo( testMethod, @@ -215,7 +218,7 @@ public void GetTestMethodInfoShouldAddAssemblyInfoToTheCache() var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type, true)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -232,10 +235,10 @@ public void GetTestMethodInfoShouldNotThrowIfWeFailToDiscoverTypeFromAnAssembly( var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(It.IsAny(), true)).Throws(new Exception()); + rh => rh.IsNonDerivedAttributeDefined(It.IsAny(), true)).Throws(new Exception()); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(typeof(DummyTestClassWithTestMethods), true)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(typeof(DummyTestClassWithTestMethods), true)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -251,10 +254,10 @@ public void GetTestMethodInfoShouldCacheAssemblyInitializeAttribute() var testMethod = new TestMethod("TestInit", type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetTypeInfo(), true)).Returns(true); + rh => rh.IsDerivedAttributeDefined(type.GetTypeInfo(), true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyInit"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyInit"), false)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -271,10 +274,10 @@ public void GetTestMethodInfoShouldCacheAssemblyCleanupAttribute() var testMethod = new TestMethod("TestCleanup", type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetTypeInfo(), true)).Returns(true); + rh => rh.IsDerivedAttributeDefined(type.GetTypeInfo(), true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -291,12 +294,12 @@ public void GetTestMethodInfoShouldCacheAssemblyInitAndCleanupAttribute() var testMethod = new TestMethod("TestInitOrCleanup", type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetTypeInfo(), true)).Returns(true); + rh => rh.IsDerivedAttributeDefined(type.GetTypeInfo(), true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyInit"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyInit"), false)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -314,10 +317,10 @@ public void GetTestMethodInfoShouldThrowIfAssemblyInitHasIncorrectSignature() var testMethod = new TestMethod("M", type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetTypeInfo(), true)).Returns(true); + rh => rh.IsDerivedAttributeDefined(type.GetTypeInfo(), true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyInit"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyInit"), false)).Returns(true); void A() => _typeCache.GetTestMethodInfo( @@ -346,10 +349,10 @@ public void GetTestMethodInfoShouldThrowIfAssemblyCleanupHasIncorrectSignature() var testMethod = new TestMethod("M", type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetTypeInfo(), true)).Returns(true); + rh => rh.IsDerivedAttributeDefined(type, true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); void A() => _typeCache.GetTestMethodInfo( @@ -379,7 +382,7 @@ public void GetTestMethodInfoShouldCacheAssemblyInfoInstanceAndReuseTheCache() var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetTypeInfo(), true)).Returns(true); + rh => rh.IsDerivedAttributeDefined(type.GetTypeInfo(), true)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -391,7 +394,7 @@ public void GetTestMethodInfoShouldCacheAssemblyInfoInstanceAndReuseTheCache() new TestContextImplementation(testMethod, new ThreadSafeStringWriter(null, "test"), new Dictionary()), false); - _mockReflectHelper.Verify(rh => rh.IsAttributeDefined(type.GetTypeInfo(), true), Times.Once); + _mockReflectHelper.Verify(rh => rh.IsDerivedAttributeDefined(type.GetTypeInfo(), true), Times.Once); Verify(_typeCache.AssemblyInfoCache.Count == 1); } @@ -406,7 +409,7 @@ public void GetTestMethodInfoShouldAddClassInfoToTheCache() var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type, true)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -424,10 +427,10 @@ public void GetTestMethodInfoShouldCacheClassInitializeAttribute() var testMethod = new TestMethod("TestInit", type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type, true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyInit"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyInit"), false)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -447,16 +450,16 @@ public void GetTestMethodInfoShouldCacheBaseClassInitializeAttributes() var testMethod = new TestMethod("TestMethod", type.FullName, "A", false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsDerivedAttributeDefined(type, false)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(baseType.GetMethod("AssemblyInit"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(baseType.GetMethod("AssemblyInit"), false)).Returns(true); _mockReflectHelper.Setup( - rh => rh.GetCustomAttribute(baseType.GetMethod("AssemblyInit"))) + rh => rh.GetFirstDerivedAttributeOrDefault(baseType.GetMethod("AssemblyInit"), true)) .Returns(new UTF.ClassInitializeAttribute(UTF.InheritanceBehavior.BeforeEachDerivedClass)); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("ClassInit"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("ClassInit"), false)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -475,10 +478,10 @@ public void GetTestMethodInfoShouldCacheClassCleanupAttribute() var testMethod = new TestMethod("TestCleanup", type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type, true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -497,11 +500,11 @@ public void GetTestMethodInfoShouldCacheBaseClassCleanupAttributes() var testMethod = new TestMethod("TestMethod", type.FullName, "A", false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsDerivedAttributeDefined(type, true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(baseType.GetMethod("AssemblyCleanup"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(baseType.GetMethod("AssemblyCleanup"), false)).Returns(true); _mockReflectHelper.Setup( - rh => rh.GetCustomAttribute(baseType.GetMethod("AssemblyCleanup"))) + rh => rh.GetFirstDerivedAttributeOrDefault(baseType.GetMethod("AssemblyCleanup"), true)) .Returns(new UTF.ClassCleanupAttribute(UTF.InheritanceBehavior.BeforeEachDerivedClass)); _typeCache.GetTestMethodInfo( @@ -521,11 +524,11 @@ public void GetTestMethodInfoShouldCacheClassInitAndCleanupAttribute() var testMethod = new TestMethod("TestInitOrCleanup", type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type, true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyInit"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyInit"), false)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -547,23 +550,23 @@ public void GetTestMethodInfoShouldCacheBaseClassInitAndCleanupAttributes() MethodInfo baseCleanupMethod = baseType.GetMethod("ClassCleanup"); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsDerivedAttributeDefined(type, true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(baseInitializeMethod, false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(baseInitializeMethod, false)).Returns(true); _mockReflectHelper.Setup( - rh => rh.GetCustomAttribute(baseInitializeMethod)) + rh => rh.GetFirstDerivedAttributeOrDefault(baseInitializeMethod, true)) .Returns(new UTF.ClassInitializeAttribute(UTF.InheritanceBehavior.BeforeEachDerivedClass)); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(baseCleanupMethod, false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(baseCleanupMethod, false)).Returns(true); _mockReflectHelper.Setup( - rh => rh.GetCustomAttribute(baseCleanupMethod)) + rh => rh.GetFirstDerivedAttributeOrDefault(baseCleanupMethod, true)) .Returns(new UTF.ClassCleanupAttribute(UTF.InheritanceBehavior.BeforeEachDerivedClass)); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyInit"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyInit"), false)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -591,35 +594,35 @@ public void GetTestMethodInfoShouldCacheParentAndGrandparentClassInitAndCleanupA MethodInfo parentCleanupMethod = parentType.GetMethod("ChildClassCleanup"); _mockReflectHelper - .Setup(rh => rh.IsAttributeDefined(type, true)) + .Setup(rh => rh.IsNonDerivedAttributeDefined(type, true)) .Returns(true); // Setup grandparent class init/cleanup methods _mockReflectHelper - .Setup(rh => rh.IsAttributeDefined(grandparentInitMethod, false)) + .Setup(rh => rh.IsNonDerivedAttributeDefined(grandparentInitMethod, false)) .Returns(true); _mockReflectHelper - .Setup(rh => rh.GetCustomAttribute(grandparentInitMethod)) + .Setup(rh => rh.GetFirstDerivedAttributeOrDefault(grandparentInitMethod, true)) .Returns(new UTF.ClassInitializeAttribute(UTF.InheritanceBehavior.BeforeEachDerivedClass)); _mockReflectHelper - .Setup(rh => rh.IsAttributeDefined(grandparentCleanupMethod, false)) + .Setup(rh => rh.IsNonDerivedAttributeDefined(grandparentCleanupMethod, false)) .Returns(true); _mockReflectHelper - .Setup(rh => rh.GetCustomAttribute(grandparentCleanupMethod)) + .Setup(rh => rh.GetFirstDerivedAttributeOrDefault(grandparentCleanupMethod, true)) .Returns(new UTF.ClassCleanupAttribute(UTF.InheritanceBehavior.BeforeEachDerivedClass)); // Setup parent class init/cleanup methods _mockReflectHelper - .Setup(rh => rh.IsAttributeDefined(parentInitMethod, false)) + .Setup(rh => rh.IsNonDerivedAttributeDefined(parentInitMethod, false)) .Returns(true); _mockReflectHelper - .Setup(rh => rh.GetCustomAttribute(parentInitMethod)) + .Setup(rh => rh.GetFirstDerivedAttributeOrDefault(parentInitMethod, true)) .Returns(new UTF.ClassInitializeAttribute(UTF.InheritanceBehavior.BeforeEachDerivedClass)); _mockReflectHelper - .Setup(rh => rh.IsAttributeDefined(parentCleanupMethod, false)) + .Setup(rh => rh.IsNonDerivedAttributeDefined(parentCleanupMethod, false)) .Returns(true); _mockReflectHelper - .Setup(rh => rh.GetCustomAttribute(parentCleanupMethod)) + .Setup(rh => rh.GetFirstDerivedAttributeOrDefault(parentCleanupMethod, true)) .Returns(new UTF.ClassCleanupAttribute(UTF.InheritanceBehavior.BeforeEachDerivedClass)); var testMethod = new TestMethod("TestMethod", type.FullName, "A", isAsync: false); @@ -650,10 +653,10 @@ public void GetTestMethodInfoShouldThrowIfClassInitHasIncorrectSignature() var testMethod = new TestMethod("M", type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsDerivedAttributeDefined(type, true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyInit"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyInit"), false)).Returns(true); void A() => _typeCache.GetTestMethodInfo( @@ -682,10 +685,10 @@ public void GetTestMethodInfoShouldThrowIfClassCleanupHasIncorrectSignature() var testMethod = new TestMethod("M", type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsDerivedAttributeDefined(type, true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); void A() => _typeCache.GetTestMethodInfo( @@ -714,10 +717,10 @@ public void GetTestMethodInfoShouldCacheTestInitializeAttribute() var testMethod = new TestMethod("TestInit", type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type, true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("TestInit"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("TestInit"), false)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -734,10 +737,10 @@ public void GetTestMethodInfoShouldCacheTestCleanupAttribute() var testMethod = new TestMethod("TestCleanup", type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type, true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("TestCleanup"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("TestCleanup"), false)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -754,10 +757,10 @@ public void GetTestMethodInfoShouldThrowIfTestInitOrCleanupHasIncorrectSignature var testMethod = new TestMethod("M", type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsDerivedAttributeDefined(type, true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("TestInit"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("TestInit"), false)).Returns(true); void A() => _typeCache.GetTestMethodInfo( @@ -788,10 +791,10 @@ public void GetTestMethodInfoShouldCacheTestInitializeAttributeDefinedInBaseClas var testMethod = new TestMethod("TestMethod", type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type, true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(baseType.GetMethod("TestInit"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(baseType.GetMethod("TestInit"), false)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -809,10 +812,10 @@ public void GetTestMethodInfoShouldCacheTestCleanupAttributeDefinedInBaseClass() var testMethod = new TestMethod("TestMethod", type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type, true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(baseType.GetMethod("TestCleanup"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(baseType.GetMethod("TestCleanup"), false)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -830,7 +833,7 @@ public void GetTestMethodInfoShouldCacheClassInfoInstanceAndReuseFromCache() var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type, true)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -882,6 +885,7 @@ public void GetTestMethodInfoShouldReturnTestMethodInfo() MethodInfo methodInfo = type.GetMethod("TestMethod"); var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); + _mockReflectHelper.Setup(rh => rh.GetFirstDerivedAttributeOrDefault(It.IsAny(), false)).CallBase(); TestMethodInfo testMethodInfo = _typeCache.GetTestMethodInfo( testMethod, new TestContextImplementation(testMethod, new ThreadSafeStringWriter(null, "test"), new Dictionary()), @@ -899,9 +903,9 @@ public void GetTestMethodInfoShouldReturnTestMethodInfoWithTimeout() MethodInfo methodInfo = type.GetMethod("TestMethodWithTimeout"); var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); - _mockReflectHelper.Setup(rh => rh.IsAttributeDefined(methodInfo, false)) + _mockReflectHelper.Setup(rh => rh.IsNonDerivedAttributeDefined(methodInfo, false)) .Returns(true); - + _mockReflectHelper.Setup(rh => rh.GetFirstDerivedAttributeOrDefault(It.IsAny(), false)).CallBase(); TestMethodInfo testMethodInfo = _typeCache.GetTestMethodInfo( testMethod, new TestContextImplementation(testMethod, new ThreadSafeStringWriter(null, "test"), new Dictionary()), @@ -919,7 +923,7 @@ public void GetTestMethodInfoShouldThrowWhenTimeoutIsIncorrect() MethodInfo methodInfo = type.GetMethod("TestMethodWithIncorrectTimeout"); var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); - _mockReflectHelper.Setup(rh => rh.IsAttributeDefined(methodInfo, false)) + _mockReflectHelper.Setup(rh => rh.IsNonDerivedAttributeDefined(methodInfo, false)) .Returns(true); void A() => _typeCache.GetTestMethodInfo( @@ -984,7 +988,7 @@ public void GetTestMethodInfoWhenTimeoutAttributeSetShouldReturnTimeoutBasedOnAt MethodInfo methodInfo = type.GetMethod("TestMethodWithTimeout"); var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); - _mockReflectHelper.Setup(rh => rh.IsAttributeDefined(methodInfo, false)) + _mockReflectHelper.Setup(rh => rh.IsNonDerivedAttributeDefined(methodInfo, false)) .Returns(true); TestMethodInfo testMethodInfo = _typeCache.GetTestMethodInfo( @@ -1026,6 +1030,7 @@ public void GetTestMethodInfoShouldReturnTestMethodInfoForMethodsAdornedWithADer MethodInfo methodInfo = type.GetMethod("TestMethodWithDerivedTestMethodAttribute"); var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); + _mockReflectHelper.Setup(rh => rh.GetFirstDerivedAttributeOrDefault(It.IsAny(), false)).CallBase(); TestMethodInfo testMethodInfo = _typeCache.GetTestMethodInfo( testMethod, new TestContextImplementation(testMethod, new ThreadSafeStringWriter(null, "test"), new Dictionary()), @@ -1144,6 +1149,7 @@ public void GetTestMethodInfoShouldReturnTestMethodInfoForDerivedTestClasses() MethodInfo methodInfo = type.GetRuntimeMethod("DummyTestMethod", []); var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); + _mockReflectHelper.Setup(rh => rh.GetFirstDerivedAttributeOrDefault(It.IsAny(), false)).CallBase(); TestMethodInfo testMethodInfo = _typeCache.GetTestMethodInfo( testMethod, new TestContextImplementation(testMethod, new ThreadSafeStringWriter(null, "test"), new Dictionary()), @@ -1161,6 +1167,7 @@ public void GetTestMethodInfoShouldReturnTestMethodInfoForDerivedClassMethodOver MethodInfo methodInfo = type.GetRuntimeMethod("OverloadedTestMethod", []); var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); + _mockReflectHelper.Setup(rh => rh.GetFirstDerivedAttributeOrDefault(It.IsAny(), false)).CallBase(); TestMethodInfo testMethodInfo = _typeCache.GetTestMethodInfo( testMethod, new TestContextImplementation(testMethod, new ThreadSafeStringWriter(null, "test"), new Dictionary()), @@ -1182,6 +1189,7 @@ public void GetTestMethodInfoShouldReturnTestMethodInfoForDeclaringTypeMethodOve DeclaringClassFullName = baseType.FullName, }; + _mockReflectHelper.Setup(rh => rh.GetFirstDerivedAttributeOrDefault(It.IsAny(), false)).CallBase(); TestMethodInfo testMethodInfo = _typeCache.GetTestMethodInfo( testMethod, new TestContextImplementation(testMethod, new ThreadSafeStringWriter(null, "test"), new Dictionary()), @@ -1215,10 +1223,10 @@ public void ClassInfoListWithExecutableCleanupMethodsShouldReturnEmptyListWhenCl var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type, true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("TestCleanup"), false)).Returns(false); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("TestCleanup"), false)).Returns(false); _typeCache.GetTestMethodInfo( testMethod, @@ -1237,10 +1245,10 @@ public void ClassInfoListWithExecutableCleanupMethodsShouldReturnClassInfosWithE var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type, true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -1271,10 +1279,10 @@ public void AssemblyInfoListWithExecutableCleanupMethodsShouldReturnEmptyListWhe var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type, true)).Returns(true); + rh => rh.IsDerivedAttributeDefined(type, true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(false); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(false); _typeCache.GetTestMethodInfo( testMethod, @@ -1293,10 +1301,10 @@ public void AssemblyInfoListWithExecutableCleanupMethodsShouldReturnAssemblyInfo var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetTypeInfo(), true)).Returns(true); + rh => rh.IsDerivedAttributeDefined(type.GetTypeInfo(), true)).Returns(true); _mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyCleanup"), false)).Returns(true); _typeCache.GetTestMethodInfo( testMethod, @@ -1320,7 +1328,7 @@ public void ResolveExpectedExceptionHelperShouldReturnExpectedExceptionAttribute var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); UTF.ExpectedExceptionAttribute expectedException = new(typeof(DivideByZeroException)); - _mockReflectHelper.Setup(rh => rh.IsAttributeDefined(methodInfo, false)) + _mockReflectHelper.Setup(rh => rh.IsNonDerivedAttributeDefined(methodInfo, false)) .Returns(true); _mockReflectHelper.Setup(rh => rh.ResolveExpectedExceptionHelper(methodInfo, testMethod)).Returns(expectedException); @@ -1338,7 +1346,7 @@ public void ResolveExpectedExceptionHelperShouldReturnNullIfExpectedExceptionAtt MethodInfo methodInfo = type.GetMethod("TestMethod"); var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); - _mockReflectHelper.Setup(rh => rh.IsAttributeDefined(methodInfo, false)) + _mockReflectHelper.Setup(rh => rh.IsNonDerivedAttributeDefined(methodInfo, false)) .Returns(true); TestMethodInfo testMethodInfo = _typeCache.GetTestMethodInfo( @@ -1357,7 +1365,7 @@ public void ResolveExpectedExceptionHelperShouldThrowIfMultipleExpectedException MethodInfo methodInfo = type.GetMethod("TestMethodWithMultipleExpectedException"); var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); - _mockReflectHelper.Setup(rh => rh.IsAttributeDefined(methodInfo, false)) + _mockReflectHelper.Setup(rh => rh.IsNonDerivedAttributeDefined(methodInfo, false)) .Returns(true); try diff --git a/test/UnitTests/MSTestAdapter.UnitTests/Execution/UnitTestRunnerTests.cs b/test/UnitTests/MSTestAdapter.UnitTests/Execution/UnitTestRunnerTests.cs index 4288ce7236..6953eafcec 100644 --- a/test/UnitTests/MSTestAdapter.UnitTests/Execution/UnitTestRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.UnitTests/Execution/UnitTestRunnerTests.cs @@ -306,7 +306,7 @@ public void RunSingleTestShouldCallAssemblyInitializeAndClassInitializeMethodsIn _testablePlatformServiceProvider.MockFileOperations.Setup(fo => fo.LoadAssembly("A", It.IsAny())) .Returns(Assembly.GetExecutingAssembly()); mockReflectHelper.Setup( - rh => rh.IsAttributeDefined(type.GetMethod("AssemblyInitialize"), It.IsAny())) + rh => rh.IsNonDerivedAttributeDefined(type.GetMethod("AssemblyInitialize"), It.IsAny())) .Returns(true); int validator = 1; diff --git a/test/UnitTests/MSTestAdapter.UnitTests/Helpers/ReflectHelperTests.cs b/test/UnitTests/MSTestAdapter.UnitTests/Helpers/ReflectHelperTests.cs index f7d3293d5f..d857ea3aeb 100644 --- a/test/UnitTests/MSTestAdapter.UnitTests/Helpers/ReflectHelperTests.cs +++ b/test/UnitTests/MSTestAdapter.UnitTests/Helpers/ReflectHelperTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Reflection; @@ -49,7 +49,7 @@ public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtClassLevel() _reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), [new UTF.TestCategoryAttribute("ClassLevel")], MemberTypes.TypeInfo); string[] expected = ["ClassLevel"]; - string[] actual = _reflectHelper.GetCategories(_method.Object, typeof(ReflectHelperTests)).ToArray(); + string[] actual = _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests)).ToArray(); Verify(expected.SequenceEqual(actual)); } @@ -64,7 +64,7 @@ public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtAllLevels() _reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), [new UTF.TestCategoryAttribute("ClassLevel")], MemberTypes.TypeInfo); _reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), [new UTF.TestCategoryAttribute("MethodLevel")], MemberTypes.Method); - string[] actual = _reflectHelper.GetCategories(_method.Object, typeof(ReflectHelperTests)).ToArray(); + string[] actual = _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests)).ToArray(); string[] expected = ["MethodLevel", "ClassLevel", "AsmLevel1", "AsmLevel2", "AsmLevel3"]; Verify(expected.SequenceEqual(actual)); @@ -82,7 +82,7 @@ public void GetTestCategoryAttributeShouldConcatCustomAttributeOfSameType() _reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), [new UTF.TestCategoryAttribute("MethodLevel1")], MemberTypes.Method); _reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), [new UTF.TestCategoryAttribute("MethodLevel2")], MemberTypes.Method); - string[] actual = _reflectHelper.GetCategories(_method.Object, typeof(ReflectHelperTests)).ToArray(); + string[] actual = _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests)).ToArray(); string[] expected = ["MethodLevel1", "MethodLevel2", "ClassLevel1", "ClassLevel2", "AsmLevel1", "AsmLevel2"]; Verify(expected.SequenceEqual(actual)); @@ -97,7 +97,7 @@ public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtAssemblyLevel() string[] expected = ["AsmLevel"]; - string[] actual = _reflectHelper.GetCategories(_method.Object, typeof(ReflectHelperTests)).ToArray(); + string[] actual = _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests)).ToArray(); Verify(expected.SequenceEqual(actual)); } @@ -110,7 +110,7 @@ public void GetTestCategoryAttributeShouldIncludeMultipleTestCategoriesAtClassLe _reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), [new UTF.TestCategoryAttribute("ClassLevel"), new UTF.TestCategoryAttribute("ClassLevel1")], MemberTypes.TypeInfo); string[] expected = ["ClassLevel", "ClassLevel1"]; - string[] actual = _reflectHelper.GetCategories(_method.Object, typeof(ReflectHelperTests)).ToArray(); + string[] actual = _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests)).ToArray(); Verify(expected.SequenceEqual(actual)); } @@ -123,7 +123,7 @@ public void GetTestCategoryAttributeShouldIncludeMultipleTestCategoriesAtAssembl _reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), [new UTF.TestCategoryAttribute("AsmLevel"), new UTF.TestCategoryAttribute("AsmLevel1")], MemberTypes.All); string[] expected = ["AsmLevel", "AsmLevel1"]; - string[] actual = _reflectHelper.GetCategories(_method.Object, typeof(ReflectHelperTests)).ToArray(); + string[] actual = _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests)).ToArray(); Verify(expected.SequenceEqual(actual)); } @@ -135,7 +135,7 @@ public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtMethodLevel() _reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), [new UTF.TestCategoryAttribute("MethodLevel")], MemberTypes.Method); string[] expected = ["MethodLevel"]; - string[] actual = _reflectHelper.GetCategories(_method.Object, typeof(ReflectHelperTests)).ToArray(); + string[] actual = _reflectHelper.GetTestCategories(_method.Object, typeof(ReflectHelperTests)).ToArray(); Verify(expected.SequenceEqual(actual)); } @@ -150,7 +150,7 @@ public void IsAttributeDefinedShouldReturnTrueIfSpecifiedAttributeIsDefinedOnAMe Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object, true)). Returns(attributes); - Verify(rh.IsAttributeDefined(mockMemberInfo.Object, true)); + Verify(rh.IsNonDerivedAttributeDefined(mockMemberInfo.Object, true)); } public void IsAttributeDefinedShouldReturnFalseIfSpecifiedAttributeIsNotDefinedOnAMember() @@ -163,7 +163,7 @@ public void IsAttributeDefinedShouldReturnFalseIfSpecifiedAttributeIsNotDefinedO Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object, true)). Returns(attributes); - Verify(!rh.IsAttributeDefined(mockMemberInfo.Object, true)); + Verify(!rh.IsNonDerivedAttributeDefined(mockMemberInfo.Object, true)); } public void IsAttributeDefinedShouldReturnFromCache() @@ -180,33 +180,16 @@ public void IsAttributeDefinedShouldReturnFromCache() Setup(ro => ro.GetCustomAttributes(memberInfo, true)). Returns(attributes); - Verify(rh.IsAttributeDefined(memberInfo, true)); + Verify(rh.IsNonDerivedAttributeDefined(memberInfo, true)); // Validate that reflection APIs are not called again. - Verify(rh.IsAttributeDefined(memberInfo, true)); + Verify(rh.IsNonDerivedAttributeDefined(memberInfo, true)); _testablePlatformServiceProvider.MockReflectionOperations.Verify(ro => ro.GetCustomAttributes(memberInfo, true), Times.Once); // Also validate that reflection APIs for an individual type is not called since the cache gives us what we need already. _testablePlatformServiceProvider.MockReflectionOperations.Verify(ro => ro.GetCustomAttributes(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } - public void IsAttributeDefinedShouldReturnTrueQueryingASpecificAttributesExistenceEvenIfGettingAllAttributesFail() - { - var rh = new ReflectHelper(); - var mockMemberInfo = new Mock(); - var attributes = new Attribute[] { new UTF.TestMethodAttribute() }; - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object, true)). - Returns((object[])null); - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object, typeof(UTF.TestMethodAttribute), true)). - Returns(attributes); - - Verify(rh.IsAttributeDefined(mockMemberInfo.Object, true)); - } - public void HasAttributeDerivedFromShouldReturnTrueIfSpecifiedAttributeIsDefinedOnAMember() { var rh = new ReflectHelper(); @@ -217,7 +200,7 @@ public void HasAttributeDerivedFromShouldReturnTrueIfSpecifiedAttributeIsDefined Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object, true)). Returns(attributes); - Verify(rh.HasAttributeDerivedFrom(mockMemberInfo.Object, true)); + Verify(rh.IsDerivedAttributeDefined(mockMemberInfo.Object, true)); } public void HasAttributeDerivedFromShouldReturnFalseIfSpecifiedAttributeIsNotDefinedOnAMember() @@ -230,7 +213,7 @@ public void HasAttributeDerivedFromShouldReturnFalseIfSpecifiedAttributeIsNotDef Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object, true)). Returns(attributes); - Verify(!rh.IsAttributeDefined(mockMemberInfo.Object, true)); + Verify(!rh.IsNonDerivedAttributeDefined(mockMemberInfo.Object, true)); } public void HasAttributeDerivedFromShouldReturnFromCache() @@ -247,10 +230,10 @@ public void HasAttributeDerivedFromShouldReturnFromCache() Setup(ro => ro.GetCustomAttributes(memberInfo, true)). Returns(attributes); - Verify(rh.HasAttributeDerivedFrom(memberInfo, true)); + Verify(rh.IsDerivedAttributeDefined(memberInfo, true)); // Validate that reflection APIs are not called again. - Verify(rh.HasAttributeDerivedFrom(memberInfo, true)); + Verify(rh.IsDerivedAttributeDefined(memberInfo, true)); _testablePlatformServiceProvider.MockReflectionOperations.Verify(ro => ro.GetCustomAttributes(memberInfo, true), Times.Once); // Also validate that reflection APIs for an individual type is not called since the cache gives us what we need already. @@ -271,24 +254,7 @@ public void HasAttributeDerivedFromShouldReturnFalseQueryingProvidedAttributesEx Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object, typeof(UTF.TestMethodAttribute), true)). Returns(attributes); - Verify(!rh.IsAttributeDefined(mockMemberInfo.Object, true)); - } - - public void HasAttributeDerivedFromShouldReturnTrueQueryingProvidedAttributesExistenceIfGettingAllAttributesFail() - { - var rh = new ReflectHelper(); - var mockMemberInfo = new Mock(); - var attributes = new Attribute[] { new TestableExtendedTestMethod() }; - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object, true)). - Returns((object[])null); - - _testablePlatformServiceProvider.MockReflectionOperations. - Setup(ro => ro.GetCustomAttributes(mockMemberInfo.Object, typeof(TestableExtendedTestMethod), true)). - Returns(attributes); - - Verify(rh.IsAttributeDefined(mockMemberInfo.Object, true)); + Verify(!rh.IsNonDerivedAttributeDefined(mockMemberInfo.Object, true)); } } diff --git a/test/UnitTests/MSTestAdapter.UnitTests/TestableImplementations/TestableReflectHelper.cs b/test/UnitTests/MSTestAdapter.UnitTests/TestableImplementations/TestableReflectHelper.cs index 834c80fa43..5679d5692d 100644 --- a/test/UnitTests/MSTestAdapter.UnitTests/TestableImplementations/TestableReflectHelper.cs +++ b/test/UnitTests/MSTestAdapter.UnitTests/TestableImplementations/TestableReflectHelper.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Reflection; @@ -12,43 +12,55 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableIm /// internal sealed class TestableReflectHelper : ReflectHelper { - /// - /// A dictionary to hold mock custom attributes. The int represents a hash code of - /// the Type of custom attribute and the level its applied at : - /// MemberTypes.All for assembly level - /// MemberTypes.TypeInfo for class level - /// MemberTypes.Method for method level. - /// - private readonly Dictionary _customAttributes; - public TestableReflectHelper() + : base(new TestableReflectionAccessor()) { - _customAttributes = []; } public void SetCustomAttribute(Type type, Attribute[] values, MemberTypes memberTypes) { - int hashCode = type.FullName.GetHashCode() + memberTypes.GetHashCode(); - _customAttributes[hashCode] = _customAttributes.TryGetValue(hashCode, out Attribute[] value) - ? value.Concat(values).ToArray() - : values; + var attributeProvider = (TestableReflectionAccessor)NotCachedAttributes; + attributeProvider.AddData(type, values, memberTypes); } +} - internal override TAttribute[] GetCustomAttributeForAssembly(MemberInfo memberInfo) +internal class TestableReflectionAccessor : INotCachedReflectionAccessor +{ + /// + /// 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 = new(); + + public object[] GetCustomAttributesNotCached(ICustomAttributeProvider attributeProvider, bool inherit) { - int hashCode = MemberTypes.All.GetHashCode() + typeof(TAttribute).FullName.GetHashCode(); + 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 _customAttributes.TryGetValue(hashCode, out Attribute[] value) - ? value.OfType().ToArray() - : []; + return foundAttributes.ToArray(); } - internal override TAttribute[] GetCustomAttributes(MemberInfo memberInfo) + internal void AddData(Type type, Attribute[] values, MemberTypes memberTypes) { - int hashCode = memberInfo.MemberType.GetHashCode() + typeof(TAttribute).FullName.GetHashCode(); - - return _customAttributes.TryGetValue(hashCode, out Attribute[] value) - ? value.OfType().ToArray() - : []; + foreach (Attribute attribute in values) + { + _data.Add((type, attribute, memberTypes)); + } } }