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 4437bdac9b..4fca769971 100644
--- a/src/Adapter/MSTest.TestAdapter/Discovery/AssemblyEnumerator.cs
+++ b/src/Adapter/MSTest.TestAdapter/Discovery/AssemblyEnumerator.cs
@@ -188,14 +188,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(string assemblyFileName, string? runSettingsXml, Type type,
@@ -214,9 +215,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)
@@ -256,6 +256,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();
@@ -268,11 +280,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
{
@@ -304,9 +316,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
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 5e93dd113c..cfe6880287 100644
--- a/test/UnitTests/MSTestAdapter.UnitTests/Discovery/AssemblyEnumeratorTests.cs
+++ b/test/UnitTests/MSTestAdapter.UnitTests/Discovery/AssemblyEnumeratorTests.cs
@@ -411,13 +411,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 fff1a8e654..74fabf1e32 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;
@@ -277,9 +277,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);
@@ -295,7 +295,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);
@@ -310,7 +310,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);
@@ -393,7 +393,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);
@@ -405,7 +405,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);
@@ -417,7 +417,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);
@@ -429,7 +429,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);
@@ -441,7 +441,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);
@@ -509,7 +509,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);
@@ -525,7 +525,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);
@@ -541,7 +541,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);
@@ -564,6 +564,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,
@@ -571,6 +572,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 8bb243efa4..f132911a30 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