diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs
index 84630c264a..9a0bd1396e 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs
@@ -7,6 +7,7 @@
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting.Internal;
@@ -21,18 +22,20 @@ internal class AssemblyEnumerator : MarshalByRefObject
///
/// Helper for reflection API's.
///
- private static readonly ReflectHelper ReflectHelper = ReflectHelper.Instance;
+ private readonly ReflectionOperations _reflectionOperations;
///
/// Type cache.
///
- private readonly TypeCache _typeCache = new(ReflectHelper);
+ private readonly TypeCache _typeCache;
///
/// Initializes a new instance of the class.
///
public AssemblyEnumerator()
{
+ _reflectionOperations = (ReflectionOperations)PlatformServiceProvider.Instance.ReflectionOperations;
+ _typeCache = new TypeCache(_reflectionOperations);
}
///
@@ -40,10 +43,11 @@ public AssemblyEnumerator()
///
/// The settings for the session.
/// Use this constructor when creating this object in a new app domain so the settings for this app domain are set.
- public AssemblyEnumerator(MSTestSettings settings) =>
+ public AssemblyEnumerator(MSTestSettings settings)
+ : this()
// Populate the settings into the domain(Desktop workflow) performing discovery.
// This would just be resetting the settings to itself in non desktop workflows.
- MSTestSettings.PopulateSettings(settings);
+ => MSTestSettings.PopulateSettings(settings);
///
/// Returns object to be used for controlling lifetime, null means infinite lifetime.
@@ -66,21 +70,26 @@ internal AssemblyEnumerationResult EnumerateAssembly(string assemblyFileName)
{
List warnings = [];
DebugEx.Assert(!StringEx.IsNullOrWhiteSpace(assemblyFileName), "Invalid assembly file name.");
- var tests = new List();
+ List tests = [];
Assembly assembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly(assemblyFileName);
Type[] types = GetTypes(assembly);
- bool discoverInternals = ReflectHelper.GetDiscoverInternalsAttribute(assembly) != null;
-
- TestDataSourceUnfoldingStrategy dataSourcesUnfoldingStrategy = ReflectHelper.GetTestDataSourceOptions(assembly)?.UnfoldingStrategy switch
+ bool discoverInternals = _reflectionOperations.GetCustomAttributes(assembly, typeof(DiscoverInternalsAttribute))
+ .OfType()
+ .Any();
+
+ TestDataSourceOptionsAttribute? assemblyUnfoldingStrategyAttribute = _reflectionOperations.GetCustomAttributes(assembly, typeof(TestDataSourceOptionsAttribute))
+ .OfType()
+ .FirstOrDefault();
+ TestDataSourceUnfoldingStrategy dataSourcesUnfoldingStrategy = assemblyUnfoldingStrategyAttribute?.UnfoldingStrategy switch
{
// When strategy is auto we want to unfold
TestDataSourceUnfoldingStrategy.Auto => TestDataSourceUnfoldingStrategy.Unfold,
// When strategy is set, let's use it
{ } value => value,
// When the attribute is not set, let's look at the legacy attribute
- null => ReflectHelper.GetTestDataSourceDiscoveryOption(assembly) switch
+ null => _reflectionOperations.GetCustomAttributes(assembly, typeof(TestDataSourceDiscoveryAttribute)).OfType().FirstOrDefault()?.DiscoveryOption switch
{
TestDataSourceDiscoveryOption.DuringExecution => TestDataSourceUnfoldingStrategy.Fold,
_ => TestDataSourceUnfoldingStrategy.Unfold,
@@ -142,10 +151,10 @@ internal static Type[] GetTypes(Assembly assembly)
/// a TypeEnumerator instance.
internal virtual TypeEnumerator GetTypeEnumerator(Type type, string assemblyFileName, bool discoverInternals)
{
- var typeValidator = new TypeValidator(ReflectHelper, discoverInternals);
- var testMethodValidator = new TestMethodValidator(ReflectHelper, discoverInternals);
+ var typeValidator = new TypeValidator(_reflectionOperations, discoverInternals);
+ var testMethodValidator = new TestMethodValidator(_reflectionOperations, discoverInternals);
- return new TypeEnumerator(type, assemblyFileName, ReflectHelper, typeValidator, testMethodValidator);
+ return new TypeEnumerator(type, assemblyFileName, _reflectionOperations, typeValidator, testMethodValidator);
}
private List DiscoverTestsInType(
@@ -156,7 +165,7 @@ private List DiscoverTestsInType(
TestDataSourceUnfoldingStrategy dataSourcesUnfoldingStrategy)
{
string? typeFullName = null;
- var tests = new List();
+ List tests = [];
try
{
@@ -221,7 +230,7 @@ private static bool TryUnfoldITestDataSources(UnitTestElement test, DiscoveryTes
// We don't have a special method to filter attributes that are not derived from Attribute, so we take all
// attributes and filter them. We don't have to care if there is one, because this method is only entered when
// there is at least one (we determine this in TypeEnumerator.GetTestFromMethod.
- IEnumerable testDataSources = ReflectHelper.Instance.GetAttributes(testMethodInfo.MethodInfo).OfType();
+ IEnumerable testDataSources = ((ReflectionOperations)PlatformServiceProvider.Instance.ReflectionOperations).GetAttributes(testMethodInfo.MethodInfo).OfType();
// We need to use a temporary list to avoid adding tests to the main list if we fail to expand any data source.
List tempListOfTests = [];
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TestMethodValidator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TestMethodValidator.cs
index 7d8e5160ac..4c0c91f19b 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TestMethodValidator.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TestMethodValidator.cs
@@ -2,7 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
-using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
+using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery;
@@ -13,18 +13,18 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery;
[SuppressMessage("Performance", "CA1852: Seal internal types", Justification = "Overrides required for testability")]
internal class TestMethodValidator
{
- private readonly ReflectHelper _reflectHelper;
+ private readonly IReflectionOperations _reflectionOperation;
private readonly bool _discoverInternals;
///
/// Initializes a new instance of the class.
///
- /// An instance to reflection helper for type information.
+ /// An instance to reflection helper for type information.
/// True to discover methods which are declared internal in addition to methods
/// which are declared public.
- internal TestMethodValidator(ReflectHelper reflectHelper, bool discoverInternals)
+ internal TestMethodValidator(IReflectionOperations reflectionOperation, bool discoverInternals)
{
- _reflectHelper = reflectHelper;
+ _reflectionOperation = reflectionOperation;
_discoverInternals = discoverInternals;
}
@@ -44,7 +44,7 @@ internal virtual bool IsValidTestMethod(MethodInfo testMethodInfo, Type type, IC
// but the difference is quite small, and we don't expect a huge amount of non-test methods in the assembly.
//
// Also skip all methods coming from object, because they cannot be tests.
- if (testMethodInfo.DeclaringType == typeof(object) || !_reflectHelper.IsAttributeDefined(testMethodInfo))
+ if (testMethodInfo.DeclaringType == typeof(object) || !_reflectionOperation.IsAttributeDefined(testMethodInfo))
{
return false;
}
@@ -55,7 +55,7 @@ internal virtual bool IsValidTestMethod(MethodInfo testMethodInfo, Type type, IC
// Todo: Decide whether parameter count matters.
bool isValidTestMethod = isAccessible &&
testMethodInfo is { IsAbstract: false, IsStatic: false } &&
- testMethodInfo.IsValidReturnType(_reflectHelper);
+ testMethodInfo.IsValidReturnType(_reflectionOperation);
if (!isValidTestMethod)
{
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs
index 77d9c4afab..e7a225ac70 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs
@@ -1,9 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers;
+using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery;
@@ -18,21 +18,21 @@ internal class TypeEnumerator
private readonly string _assemblyFilePath;
private readonly TypeValidator _typeValidator;
private readonly TestMethodValidator _testMethodValidator;
- private readonly ReflectHelper _reflectHelper;
+ private readonly IReflectionOperations _reflectionOperation;
///
/// Initializes a new instance of the class.
///
/// The reflected type.
/// The name of the assembly being reflected.
- /// An instance to reflection helper for type information.
+ /// An instance to reflection helper for type information.
/// The validator for test classes.
/// The validator for test methods.
- internal TypeEnumerator(Type type, string assemblyFilePath, ReflectHelper reflectHelper, TypeValidator typeValidator, TestMethodValidator testMethodValidator)
+ internal TypeEnumerator(Type type, string assemblyFilePath, IReflectionOperations reflectionOperation, TypeValidator typeValidator, TestMethodValidator testMethodValidator)
{
_type = type;
_assemblyFilePath = assemblyFilePath;
- _reflectHelper = reflectHelper;
+ _reflectionOperation = reflectionOperation;
_typeValidator = typeValidator;
_testMethodValidator = testMethodValidator;
}
@@ -69,7 +69,7 @@ internal List GetTests(List warnings)
// if we rely on analyzers to identify all invalid methods on build, we can change this to fit the current settings.
foreach (MethodInfo method in PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethods(_type))
{
- bool isMethodDeclaredInTestTypeAssembly = _reflectHelper.IsMethodDeclaredInSameAssemblyAsType(method, _type);
+ bool isMethodDeclaredInTestTypeAssembly = _reflectionOperation.IsMethodDeclaredInSameAssemblyAsType(method, _type);
bool enableMethodsFromOtherAssemblies = MSTestSettings.CurrentSettings.EnableBaseClassTestMethodsFromOtherAssemblies;
if (!isMethodDeclaredInTestTypeAssembly && !enableMethodsFromOtherAssemblies)
@@ -136,19 +136,20 @@ internal UnitTestElement GetTestFromMethod(MethodInfo method, ICollection(method)
+ || _reflectionOperation.IsAttributeDefined(_type),
+ Priority = _reflectionOperation.GetFirstAttributeOrDefault(method)?.Priority,
#if !WINDOWS_UWP && !WIN_UI
DeploymentItems = PlatformServiceProvider.Instance.TestDeployment.GetDeploymentItems(method, _type, warnings),
#endif
- Traits = [.. _reflectHelper.GetTestPropertiesAsTraits(method)],
+ Traits = [.. _reflectionOperation.GetTestPropertiesAsTraits(method)],
};
- Attribute[] attributes = _reflectHelper.GetCustomAttributesCached(method);
+ Attribute[] attributes = _reflectionOperation.GetCustomAttributesCached(method);
TestMethodAttribute? testMethodAttribute = null;
- // Backward looping for backcompat. This used to be calls to _reflectHelper.GetFirstAttributeOrDefault
+ // Backward looping for backcompat. This used to be calls to _reflectionOperation.GetFirstAttributeOrDefault
// So, to make sure the first attribute always wins, we loop from end to start.
for (int i = attributes.Length - 1; i >= 0; i--)
{
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeValidator.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeValidator.cs
index b4b346e649..bc694216bc 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeValidator.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeValidator.cs
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
+using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery;
@@ -15,27 +15,27 @@ internal class TypeValidator
// Setting this to a string representation instead of a typeof(TestContext).FullName
// since the later would require a load of the Test Framework extension assembly at this point.
private const string TestContextFullName = "Microsoft.VisualStudio.TestTools.UnitTesting.TestContext";
- private readonly ReflectHelper _reflectHelper;
+ private readonly IReflectionOperations _reflectionOperation;
private readonly bool _discoverInternals;
///
/// Initializes a new instance of the class.
///
- /// An instance to reflection helper for type information.
- internal TypeValidator(ReflectHelper reflectHelper)
- : this(reflectHelper, false)
+ /// An instance to reflection helper for type information.
+ internal TypeValidator(IReflectionOperations reflectionOperation)
+ : this(reflectionOperation, false)
{
}
///
/// Initializes a new instance of the class.
///
- /// An instance to reflection helper for type information.
+ /// An instance to reflection helper for type information.
/// True to discover test classes which are declared internal in
/// addition to test classes which are declared public.
- internal TypeValidator(ReflectHelper reflectHelper, bool discoverInternals)
+ internal TypeValidator(IReflectionOperations reflectionOperation, bool discoverInternals)
{
- _reflectHelper = reflectHelper;
+ _reflectionOperation = reflectionOperation;
_discoverInternals = discoverInternals;
}
@@ -52,7 +52,7 @@ internal virtual bool IsValidTestClass(Type type, List warnings)
// gives us a better performance.
// It would be possible to use non-caching reflection here if we knew that we are only doing discovery that won't be followed by run,
// but the difference is quite small, and we don't expect a huge amount of non-test classes in the assembly.
- if (!type.IsClass || !_reflectHelper.IsAttributeDefined(type))
+ if (!type.IsClass || !_reflectionOperation.IsAttributeDefined(type))
{
return false;
}
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs
index d2430c222f..5f5ed56515 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblySettingsProvider.cs
@@ -3,8 +3,8 @@
using System.Security;
-using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
@@ -34,7 +34,10 @@ internal TestAssemblySettings GetSettings(string source)
// Load the source.
Assembly testAssembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly(source);
- ParallelizeAttribute? parallelizeAttribute = ReflectHelper.GetParallelizeAttribute(testAssembly);
+ IReflectionOperations reflectionOperations = PlatformServiceProvider.Instance.ReflectionOperations;
+ ParallelizeAttribute? parallelizeAttribute = reflectionOperations.GetCustomAttributes(testAssembly, typeof(ParallelizeAttribute))
+ .OfType()
+ .FirstOrDefault();
if (parallelizeAttribute != null)
{
@@ -47,7 +50,7 @@ internal TestAssemblySettings GetSettings(string source)
}
}
- testAssemblySettings.CanParallelizeAssembly = !ReflectHelper.IsDoNotParallelizeSet(testAssembly);
+ testAssemblySettings.CanParallelizeAssembly = reflectionOperations.GetCustomAttributes(testAssembly, typeof(DoNotParallelizeAttribute)).Length == 0;
return testAssemblySettings;
}
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs
index 1eb518e160..1229edb879 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs
@@ -109,7 +109,7 @@ internal TestMethodInfo(
///
/// An array of the attributes.
public Attribute[]? GetAllAttributes()
- => [.. ReflectHelper.Instance.GetAttributes(MethodInfo)];
+ => [.. PlatformServiceProvider.Instance.ReflectionOperations.GetAttributes(MethodInfo)];
///
/// Gets all attributes of the test method.
@@ -118,7 +118,7 @@ internal TestMethodInfo(
/// An array of the attributes.
public TAttributeType[] GetAttributes()
where TAttributeType : Attribute
- => [.. ReflectHelper.Instance.GetAttributes(MethodInfo)];
+ => [.. PlatformServiceProvider.Instance.ReflectionOperations.GetAttributes(MethodInfo)];
///
/// Execute test method. Capture failures, handle async and return result.
@@ -253,7 +253,7 @@ public virtual async Task InvokeAsync(object?[]? arguments)
private TimeoutInfo GetTestTimeout()
{
DebugEx.Assert(MethodInfo != null, "TestMethod should be non-null");
- TimeoutAttribute? timeoutAttribute = ReflectHelper.Instance.GetFirstAttributeOrDefault(MethodInfo);
+ TimeoutAttribute? timeoutAttribute = PlatformServiceProvider.Instance.ReflectionOperations.GetFirstAttributeOrDefault(MethodInfo);
if (timeoutAttribute is null)
{
return TimeoutInfo.FromTestTimeoutSettings();
@@ -276,7 +276,7 @@ private TestMethodAttribute GetTestMethodAttribute()
{
// Get the derived TestMethod attribute from reflection.
// It should be non-null as it was already validated by IsValidTestMethod.
- TestMethodAttribute testMethodAttribute = ReflectHelper.Instance.GetSingleAttributeOrDefault(MethodInfo)!;
+ TestMethodAttribute testMethodAttribute = PlatformServiceProvider.Instance.ReflectionOperations.GetSingleAttributeOrDefault(MethodInfo)!;
// Get the derived TestMethod attribute from Extended TestClass Attribute
// If the extended TestClass Attribute doesn't have extended TestMethod attribute then base class returns back the original testMethod Attribute
@@ -292,7 +292,7 @@ private TestMethodAttribute GetTestMethodAttribute()
///
private RetryBaseAttribute? GetRetryAttribute()
{
- IEnumerable attributes = ReflectHelper.Instance.GetAttributes(MethodInfo);
+ IEnumerable attributes = PlatformServiceProvider.Instance.ReflectionOperations.GetAttributes(MethodInfo);
using IEnumerator enumerator = attributes.GetEnumerator();
if (!enumerator.MoveNext())
{
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs
index 0d6cb2e9fc..0fd2d8ad98 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
-
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
@@ -200,7 +199,7 @@ private async Task TryExecuteDataSourceBasedTestsAsync(List re
private async Task TryExecuteFoldedDataDrivenTestsAsync(List results)
{
bool hasTestDataSource = false;
- foreach (Attribute attribute in ReflectHelper.Instance.GetCustomAttributesCached(_testMethodInfo.MethodInfo))
+ foreach (Attribute attribute in PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributesCached(_testMethodInfo.MethodInfo))
{
if (attribute is not UTF.ITestDataSource testDataSource)
{
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs
index c13cdab187..dc0a0b192d 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs
@@ -9,6 +9,7 @@
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
@@ -21,7 +22,7 @@ internal sealed class TypeCache : MarshalByRefObject
///
/// Helper for reflection API's.
///
- private readonly ReflectHelper _reflectionHelper;
+ private readonly IReflectionOperations _reflectionOperations;
///
/// Assembly info cache.
@@ -39,15 +40,15 @@ internal sealed class TypeCache : MarshalByRefObject
/// Initializes a new instance of the class.
///
internal TypeCache()
- : this(ReflectHelper.Instance)
+ : this(PlatformServiceProvider.Instance.ReflectionOperations)
{
}
///
/// Initializes a new instance of the class.
///
- /// An instance to the object.
- internal TypeCache(ReflectHelper reflectionHelper) => _reflectionHelper = reflectionHelper;
+ /// An instance to the object.
+ internal TypeCache(IReflectionOperations reflectionOperations) => _reflectionOperations = reflectionOperations;
///
/// Gets Class Info cache which has cleanup methods to execute.
@@ -251,7 +252,7 @@ private TestClassInfo CreateClassInfo(Type classType)
TestAssemblyInfo assemblyInfo = GetAssemblyInfo(classType.Assembly);
- TestClassAttribute? testClassAttribute = ReflectHelper.Instance.GetSingleAttributeOrDefault(classType);
+ TestClassAttribute? testClassAttribute = PlatformServiceProvider.Instance.ReflectionOperations.GetSingleAttributeOrDefault(classType);
DebugEx.Assert(testClassAttribute is not null, "testClassAttribute is null");
var classInfo = new TestClassInfo(classType, constructor, isParameterLessConstructor, testClassAttribute, assemblyInfo);
@@ -303,7 +304,7 @@ private TestClassInfo CreateClassInfo(Type classType)
private TimeoutInfo? TryGetTimeoutInfo(MethodInfo methodInfo, FixtureKind fixtureKind)
{
- TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetFirstAttributeOrDefault(methodInfo);
+ TimeoutAttribute? timeoutAttribute = _reflectionOperations.GetFirstAttributeOrDefault(methodInfo);
if (timeoutAttribute != null)
{
if (!timeoutAttribute.HasCorrectTimeout)
@@ -340,7 +341,7 @@ private TestAssemblyInfo GetAssemblyInfo(Assembly assembly)
try
{
// Only examine classes which are TestClass or derives from TestClass attribute
- if (!@this._reflectionHelper.IsAttributeDefined(t))
+ if (!@this._reflectionOperations.IsAttributeDefined(t))
{
continue;
}
@@ -373,8 +374,8 @@ private TestAssemblyInfo GetAssemblyInfo(Assembly assembly)
assemblyInfo.AssemblyCleanupMethodTimeoutMilliseconds = @this.TryGetTimeoutInfo(methodInfo, FixtureKind.AssemblyCleanup);
}
- bool isGlobalTestInitialize = @this._reflectionHelper.IsAttributeDefined(methodInfo);
- bool isGlobalTestCleanup = @this._reflectionHelper.IsAttributeDefined(methodInfo);
+ bool isGlobalTestInitialize = @this._reflectionOperations.IsAttributeDefined(methodInfo);
+ bool isGlobalTestCleanup = @this._reflectionOperations.IsAttributeDefined(methodInfo);
if (isGlobalTestInitialize || isGlobalTestCleanup)
{
@@ -384,7 +385,7 @@ private TestAssemblyInfo GetAssemblyInfo(Assembly assembly)
// We want to avoid loading types early as much as we can.
bool isValid = methodInfo is { IsSpecialName: false, IsPublic: true, IsStatic: true, IsGenericMethod: false, DeclaringType.IsGenericType: false, DeclaringType.IsPublic: true } &&
methodInfo.GetParameters() is { } parameters && parameters.Length == 1 && parameters[0].ParameterType == typeof(TestContext) &&
- methodInfo.IsValidReturnType();
+ methodInfo.IsValidReturnType(@this._reflectionOperations);
if (isValid && isGlobalTestInitialize)
{
@@ -416,12 +417,12 @@ private bool IsAssemblyOrClassInitializeMethod(MethodInfo
// {
// return false;
// }
- if (!_reflectionHelper.IsAttributeDefined(methodInfo))
+ if (!_reflectionOperations.IsAttributeDefined(methodInfo))
{
return false;
}
- if (!methodInfo.HasCorrectClassOrAssemblyInitializeSignature())
+ if (!methodInfo.HasCorrectClassOrAssemblyInitializeSignature(_reflectionOperations))
{
string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ClassOrAssemblyInitializeMethodHasWrongSignature, methodInfo.DeclaringType!.FullName, methodInfo.Name);
throw new TypeInspectionException(message);
@@ -444,12 +445,12 @@ private bool IsAssemblyOrClassCleanupMethod(MethodInfo method
// {
// return false;
// }
- if (!_reflectionHelper.IsAttributeDefined(methodInfo))
+ if (!_reflectionOperations.IsAttributeDefined(methodInfo))
{
return false;
}
- if (!methodInfo.HasCorrectClassOrAssemblyCleanupSignature())
+ if (!methodInfo.HasCorrectClassOrAssemblyCleanupSignature(_reflectionOperations))
{
string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ClassOrAssemblyCleanupMethodHasWrongSignature, methodInfo.DeclaringType!.FullName, methodInfo.Name);
throw new TypeInspectionException(message);
@@ -512,7 +513,7 @@ private void UpdateInfoIfClassInitializeOrCleanupMethod(
if (isBase)
{
- if (_reflectionHelper.GetFirstAttributeOrDefault(methodInfo)?
+ if (_reflectionOperations.GetFirstAttributeOrDefault(methodInfo)?
.InheritanceBehavior == InheritanceBehavior.BeforeEachDerivedClass)
{
initAndCleanupMethods[0] = methodInfo;
@@ -534,7 +535,7 @@ private void UpdateInfoIfClassInitializeOrCleanupMethod(
if (isBase)
{
- if (_reflectionHelper.GetFirstAttributeOrDefault(methodInfo)?
+ if (_reflectionOperations.GetFirstAttributeOrDefault(methodInfo)?
.InheritanceBehavior == InheritanceBehavior.BeforeEachDerivedClass)
{
initAndCleanupMethods[1] = methodInfo;
@@ -561,12 +562,12 @@ private void UpdateInfoIfTestInitializeOrCleanupMethod(
bool isBase,
HashSet? instanceMethods)
{
- bool hasTestInitialize = _reflectionHelper.IsAttributeDefined(methodInfo);
- bool hasTestCleanup = _reflectionHelper.IsAttributeDefined(methodInfo);
+ bool hasTestInitialize = _reflectionOperations.IsAttributeDefined(methodInfo);
+ bool hasTestCleanup = _reflectionOperations.IsAttributeDefined(methodInfo);
if (!hasTestCleanup && !hasTestInitialize)
{
- if (instanceMethods is not null && methodInfo.HasCorrectTestInitializeOrCleanupSignature())
+ if (instanceMethods is not null && methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionOperations))
{
instanceMethods.Add(methodInfo.Name);
}
@@ -574,7 +575,7 @@ private void UpdateInfoIfTestInitializeOrCleanupMethod(
return;
}
- if (!methodInfo.HasCorrectTestInitializeOrCleanupSignature())
+ if (!methodInfo.HasCorrectTestInitializeOrCleanupSignature(_reflectionOperations))
{
string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_TestInitializeAndCleanupMethodHasWrongSignature, methodInfo.DeclaringType!.FullName, methodInfo.Name);
throw new TypeInspectionException(message);
@@ -639,6 +640,15 @@ private TestMethodInfo ResolveTestMethodInfo(TestMethod testMethod, TestClassInf
MethodInfo methodInfo = GetMethodInfoForTestMethod(testMethod, testClassInfo);
+ // Populate the test context with test properties from [TestProperty] attributes.
+ foreach (Trait trait in _reflectionOperations.GetTestPropertiesAsTraits(methodInfo))
+ {
+ if (!testContext.TryGetPropertyValue(trait.Name, out _))
+ {
+ testContext.AddProperty(trait.Name, trait.Value);
+ }
+ }
+
return new TestMethodInfo(methodInfo, testClassInfo, testContext);
}
@@ -694,7 +704,7 @@ private MethodInfo GetMethodInfoForTestMethod(TestMethod testMethod, TestClassIn
}
return testMethodInfo is null
- || !testMethodInfo.HasCorrectTestMethodSignature(true, discoverInternals)
+ || !testMethodInfo.HasCorrectTestMethodSignature(true, PlatformServiceProvider.Instance.ReflectionOperations, discoverInternals)
? null
: testMethodInfo;
}
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs
index b36aefb130..32acf7a3d2 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs
@@ -32,7 +32,7 @@ internal sealed class UnitTestRunner : MarshalByRefObject
/// Specifies adapter settings that need to be instantiated in the domain running these tests.
/// The tests to run.
public UnitTestRunner(MSTestSettings settings, UnitTestElement[] testsToRun)
- : this(settings, testsToRun, ReflectHelper.Instance)
+ : this(settings, testsToRun, PlatformServiceProvider.Instance.ReflectionOperations)
{
}
@@ -41,8 +41,8 @@ public UnitTestRunner(MSTestSettings settings, UnitTestElement[] testsToRun)
///
/// Specifies adapter settings.
/// The tests to run.
- /// The reflect Helper.
- internal UnitTestRunner(MSTestSettings settings, UnitTestElement[] testsToRun, ReflectHelper reflectHelper)
+ /// The reflect Helper.
+ internal UnitTestRunner(MSTestSettings settings, UnitTestElement[] testsToRun, IReflectionOperations reflectionOperation)
{
// Populate the settings into the domain(Desktop workflow) performing discovery.
// This would just be resetting the settings to itself in non desktop workflows.
@@ -61,7 +61,7 @@ internal UnitTestRunner(MSTestSettings settings, UnitTestElement[] testsToRun, R
}
PlatformServiceProvider.Instance.TestRunCancellationToken ??= new TestRunCancellationToken();
- _typeCache = new TypeCache(reflectHelper);
+ _typeCache = new TypeCache(reflectionOperation);
_classCleanupManager = new ClassCleanupManager(testsToRun);
}
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs
index a93726f59f..82de589de0 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs
@@ -4,6 +4,7 @@
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions;
+using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
@@ -14,8 +15,9 @@ internal static class MethodInfoExtensions
/// Verifies that the class initialize has the correct signature.
///
/// The method to verify.
+ /// The reflection service to use.
/// True if the method has the right Assembly/Class initialize signature.
- internal static bool HasCorrectClassOrAssemblyInitializeSignature(this MethodInfo method)
+ internal static bool HasCorrectClassOrAssemblyInitializeSignature(this MethodInfo method, IReflectionOperations reflectionOperation)
{
DebugEx.Assert(method != null, "method should not be null.");
@@ -25,22 +27,23 @@ internal static bool HasCorrectClassOrAssemblyInitializeSignature(this MethodInf
method is { IsStatic: true, IsPublic: true } &&
(parameters.Length == 1) &&
parameters[0].ParameterType == typeof(TestContext) &&
- method.IsValidReturnType();
+ method.IsValidReturnType(reflectionOperation);
}
///
/// Verifies that the class cleanup has the correct signature.
///
/// The method to verify.
+ /// The reflection service to use.
/// True if the method has the right Assembly/Class cleanup signature.
- internal static bool HasCorrectClassOrAssemblyCleanupSignature(this MethodInfo method)
+ internal static bool HasCorrectClassOrAssemblyCleanupSignature(this MethodInfo method, IReflectionOperations reflectionOperation)
{
DebugEx.Assert(method != null, "method should not be null.");
return
method is { IsStatic: true, IsPublic: true } &&
HasCorrectClassOrAssemblyCleanupParameters(method) &&
- method.IsValidReturnType();
+ method.IsValidReturnType(reflectionOperation);
}
private static bool HasCorrectClassOrAssemblyCleanupParameters(MethodInfo method)
@@ -53,15 +56,16 @@ private static bool HasCorrectClassOrAssemblyCleanupParameters(MethodInfo method
/// Verifies that the test Initialize/cleanup has the correct signature.
///
/// The method to verify.
+ /// The reflection service to use.
/// True if the method has the right test init/cleanup signature.
- internal static bool HasCorrectTestInitializeOrCleanupSignature(this MethodInfo method)
+ internal static bool HasCorrectTestInitializeOrCleanupSignature(this MethodInfo method, IReflectionOperations reflectionOperation)
{
DebugEx.Assert(method != null, "method should not be null.");
return
method is { IsStatic: false, IsPublic: true } &&
(method.GetParameters().Length == 0) &&
- method.IsValidReturnType();
+ method.IsValidReturnType(reflectionOperation);
}
///
@@ -69,10 +73,11 @@ internal static bool HasCorrectTestInitializeOrCleanupSignature(this MethodInfo
///
/// The method to verify.
/// Indicates whether parameter length is to be ignored.
+ /// The reflection service to use.
/// True if internal test classes and test methods should be discovered in
/// addition to public test classes and methods.
/// True if the method has the right test method signature.
- internal static bool HasCorrectTestMethodSignature(this MethodInfo method, bool ignoreParameterLength, bool discoverInternals = false)
+ internal static bool HasCorrectTestMethodSignature(this MethodInfo method, bool ignoreParameterLength, IReflectionOperations reflectionOperation, bool discoverInternals = false)
{
DebugEx.Assert(method != null, "method should not be null.");
@@ -80,18 +85,18 @@ internal static bool HasCorrectTestMethodSignature(this MethodInfo method, bool
method is { IsAbstract: false, IsStatic: false } &&
(method.IsPublic || (discoverInternals && method.IsAssembly)) &&
(method.GetParameters().Length == 0 || ignoreParameterLength) &&
- method.IsValidReturnType(); // Match return type Task for async methods only. Else return type void.
+ method.IsValidReturnType(reflectionOperation); // Match return type Task for async methods only. Else return type void.
}
///
/// Check is return type is void for non async and Task for async methods.
///
/// The method to verify.
- /// The reflection service to use.
+ /// The reflection service to use.
/// True if the method has a void/task return type..
- internal static bool IsValidReturnType(this MethodInfo method, ReflectHelper? reflectHelper = null)
- => ReflectHelper.MatchReturnType(method, typeof(Task))
- || (ReflectHelper.MatchReturnType(method, typeof(void)) && method.GetAsyncTypeName(reflectHelper) == null)
+ internal static bool IsValidReturnType(this MethodInfo method, IReflectionOperations reflectionOperation)
+ => method.ReturnType.Equals(typeof(Task))
+ || (method.ReturnType.Equals(typeof(void)) && method.GetAsyncTypeName(reflectionOperation) == null)
// Keep this the last check, as it avoids loading System.Threading.Tasks.Extensions unnecessarily.
|| method.IsValueTask();
@@ -101,18 +106,18 @@ internal static bool IsValidReturnType(this MethodInfo method, ReflectHelper? re
// Even when invokeResult is null or Task.
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool IsValueTask(this MethodInfo method)
- => ReflectHelper.MatchReturnType(method, typeof(ValueTask));
+ => method.ReturnType.Equals(typeof(ValueTask));
///
/// For async methods compiler generates different type and method.
/// Gets the compiler generated type name for given async test method.
///
/// The method to verify.
- /// The reflection service to use.
+ /// The reflection service to use.
/// Compiler generated type name for given async test method..
- internal static string? GetAsyncTypeName(this MethodInfo method, ReflectHelper? reflectHelper = null)
+ internal static string? GetAsyncTypeName(this MethodInfo method, IReflectionOperations reflectionOperation)
{
- AsyncStateMachineAttribute? asyncStateMachineAttribute = (reflectHelper ?? ReflectHelper.Instance).GetFirstAttributeOrDefault(method);
+ AsyncStateMachineAttribute? asyncStateMachineAttribute = reflectionOperation.GetFirstAttributeOrDefault(method);
return asyncStateMachineAttribute?.StateMachineType?.FullName;
}
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs
index a2bbcbe98e..b27e0bba2c 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs
@@ -9,7 +9,7 @@ internal static class AttributeExtensions
{
public static bool IsIgnored(this ICustomAttributeProvider type, out string? ignoreMessage)
{
- IEnumerable attributes = ReflectHelper.Instance.GetAttributes(type);
+ IEnumerable attributes = PlatformServiceProvider.Instance.ReflectionOperations.GetAttributes(type);
IEnumerable> groups = attributes.GroupBy(attr => attr.GroupName);
foreach (IGrouping? group in groups)
{
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectHelper.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectHelper.cs
deleted file mode 100644
index b1a43a1e41..0000000000
--- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectHelper.cs
+++ /dev/null
@@ -1,395 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System.Security;
-
-using Microsoft.VisualStudio.TestPlatform.ObjectModel;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
-
-[SuppressMessage("Performance", "CA1852: Seal internal types", Justification = "Overrides required for mocking")]
-internal class ReflectHelper : MarshalByRefObject
-{
-#pragma warning disable RS0030 // Do not use banned APIs
- private static readonly Lazy InstanceValue = new(() => new());
-#pragma warning restore RS0030 // Do not use banned APIs
-
- // PERF: This was moved from Dictionary> to Concurrent
- // storing an array allows us to store multiple attributes of the same type if we find them. It also has lower memory footprint, and is faster
- // when we are going through the whole collection. Giving us overall better perf.
- private readonly ConcurrentDictionary _attributeCache = [];
-
- public static ReflectHelper Instance => InstanceValue.Value;
-
- ///
- /// Checks to see if a member or type is decorated with the given attribute, or an attribute that derives from it. e.g. [MyTestClass] from [TestClass] will match if you look for [TestClass]. The inherit parameter does not impact this checking.
- ///
- /// Attribute to search for.
- /// Member to inspect for attributes.
- /// True if the attribute of the specified type is defined on this member or a class.
- public virtual /* for testing */ bool IsAttributeDefined(MemberInfo memberInfo)
- where TAttribute : Attribute
- {
- Ensure.NotNull(memberInfo);
-
- // Get all attributes on the member.
- Attribute[] attributes = GetCustomAttributesCached(memberInfo);
-
- // Try to find the attribute that is derived from baseAttrType.
- foreach (Attribute attribute in attributes)
- {
- DebugEx.Assert(attribute != null, $"{nameof(ReflectHelper)}.{nameof(GetCustomAttributesCached)}: internal error: wrong value in the attributes dictionary.");
-
- if (attribute is TAttribute)
- {
- return true;
- }
- }
-
- return false;
- }
-
- ///
- /// Returns object to be used for controlling lifetime, null means infinite lifetime.
- ///
- ///
- /// The .
- ///
- [SecurityCritical]
-#if NET5_0_OR_GREATER
- [Obsolete]
-#endif
- public override object InitializeLifetimeService() => null!;
-
- ///
- /// Gets first attribute that matches the type.
- /// Use this together with attribute that does not allow multiple and is sealed. In such case there cannot be more attributes, and this will avoid the cost of
- /// checking for more than one attribute.
- ///
- /// Type of the attribute to find.
- /// The type, assembly or method.
- /// The attribute that is found or null.
- public virtual /* for tests, for moq */ TAttribute? GetFirstAttributeOrDefault(ICustomAttributeProvider attributeProvider)
- where TAttribute : Attribute
- {
- // If the attribute is not sealed, then it can allow multiple, even if AllowMultiple is false.
- // This happens when a derived type is also applied along with the base type.
- // Or, if the derived type modifies the attribute usage to allow multiple.
- // So we want to ensure this is only called for sealed attributes.
- DebugEx.Assert(typeof(TAttribute).IsSealed, $"Expected '{typeof(TAttribute)}' to be sealed, but was not.");
-
- Attribute[] cachedAttributes = GetCustomAttributesCached(attributeProvider);
-
- foreach (Attribute cachedAttribute in cachedAttributes)
- {
- if (cachedAttribute is TAttribute cachedAttributeAsTAttribute)
- {
- return cachedAttributeAsTAttribute;
- }
- }
-
- return null;
- }
-
- ///
- /// Gets first attribute that matches the type or is derived from it.
- /// Use this together with attribute that does not allow multiple. In such case there cannot be more attributes, and this will avoid the cost of
- /// checking for more than one attribute.
- ///
- /// Type of the attribute to find.
- /// The type, assembly or method.
- /// The attribute that is found or null.
- /// Throws when multiple attributes are found (the attribute must allow multiple).
- public virtual /* for tests, for moq */ TAttribute? GetSingleAttributeOrDefault(ICustomAttributeProvider attributeProvider)
- where TAttribute : Attribute
- {
- Attribute[] cachedAttributes = GetCustomAttributesCached(attributeProvider);
-
- TAttribute? foundAttribute = null;
- foreach (Attribute cachedAttribute in cachedAttributes)
- {
- if (cachedAttribute is TAttribute cachedAttributeAsTAttribute)
- {
- if (foundAttribute is not null)
- {
- throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resource.DuplicateAttributeError, typeof(TAttribute)));
- }
-
- foundAttribute = cachedAttributeAsTAttribute;
- }
- }
-
- return foundAttribute;
- }
-
- ///
- /// Match return type of method.
- ///
- /// The method to inspect.
- /// The return type to match.
- /// True if there is a match.
- internal static bool MatchReturnType(MethodInfo method, Type returnType)
- {
- Ensure.NotNull(method);
- Ensure.NotNull(returnType);
- return method.ReturnType.Equals(returnType);
- }
-
- ///
- /// Returns true when the method is declared in the assembly where the type is declared.
- ///
- /// The method to check for.
- /// The type declared in the assembly to check.
- /// True if the method is declared in the assembly where the type is declared.
- internal virtual bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Type type)
- => method.DeclaringType!.Assembly.Equals(type.Assembly); // TODO: Investigate if we rely on NRE
-
- ///
- /// Get categories applied to the test method.
- ///
- /// The member to inspect.
- /// The reflected type that owns .
- /// Categories defined.
- internal virtual /* for tests, we are mocking this */ string[] GetTestCategories(MemberInfo categoryAttributeProvider, Type owningType)
- {
- IEnumerable methodCategories = GetAttributes(categoryAttributeProvider);
- IEnumerable typeCategories = GetAttributes(owningType);
- IEnumerable assemblyCategories = GetAttributes(owningType.Assembly);
-
- return [.. methodCategories.Concat(typeCategories).Concat(assemblyCategories).SelectMany(c => c.TestCategories)];
- }
-
- ///
- /// Gets the parallelization level set on an assembly.
- ///
- /// The test assembly.
- /// The parallelization level if set. -1 otherwise.
- internal static ParallelizeAttribute? GetParallelizeAttribute(Assembly assembly)
- => PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes(assembly, typeof(ParallelizeAttribute))
- .OfType()
- .FirstOrDefault();
-
- ///
- /// Gets discover internals assembly level attribute.
- ///
- /// The test assembly.
- internal static DiscoverInternalsAttribute? GetDiscoverInternalsAttribute(Assembly assembly)
- => PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes(assembly, typeof(DiscoverInternalsAttribute))
- .OfType()
- .FirstOrDefault();
-
- ///
- /// Gets TestDataSourceDiscovery assembly level attribute.
- ///
- /// The test assembly.
- internal static TestDataSourceDiscoveryOption? GetTestDataSourceDiscoveryOption(Assembly assembly)
- => PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes(assembly, typeof(TestDataSourceDiscoveryAttribute))
- .OfType()
- .FirstOrDefault()?.DiscoveryOption;
-
- ///
- /// Gets TestDataSourceOptions assembly level attribute.
- ///
- /// The test assembly.
- /// The TestDataSourceOptionsAttribute if set. Null otherwise.
- internal static TestDataSourceOptionsAttribute? GetTestDataSourceOptions(Assembly assembly)
- => PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes(assembly, typeof(TestDataSourceOptionsAttribute))
- .OfType()
- .FirstOrDefault();
-
- ///
- /// Get the parallelization behavior for a test method.
- ///
- /// Test method.
- /// The type that owns .
- /// True if test method should not run in parallel.
- internal bool IsDoNotParallelizeSet(MemberInfo testMethod, Type owningType)
- => IsAttributeDefined(testMethod)
- || IsAttributeDefined(owningType);
-
- ///
- /// Get the parallelization behavior for a test assembly.
- ///
- /// The test assembly.
- /// True if test assembly should not run in parallel.
- internal static bool IsDoNotParallelizeSet(Assembly assembly)
- => PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes(assembly, typeof(DoNotParallelizeAttribute))
- .Length != 0;
-
- ///
- /// Priority if any set for test method. Will return priority if attribute is applied to TestMethod
- /// else null.
- ///
- /// The member to inspect.
- /// Priority value if defined. Null otherwise.
- internal virtual int? GetPriority(MemberInfo priorityAttributeProvider) =>
- GetFirstAttributeOrDefault(priorityAttributeProvider)?.Priority;
-
- ///
- /// KeyValue pairs that are provided by TestPropertyAttributes of the given test method.
- ///
- /// The member to inspect.
- /// List of traits.
- internal Trait[] GetTestPropertiesAsTraits(MethodInfo testPropertyProvider)
- {
- Attribute[] attributesFromMethod = GetCustomAttributesCached(testPropertyProvider);
- Attribute[] attributesFromClass = testPropertyProvider.ReflectedType is { } testClass ? GetCustomAttributesCached(testClass) : [];
- int countTestPropertyAttribute = 0;
- foreach (Attribute attribute in attributesFromMethod)
- {
- if (attribute is TestPropertyAttribute)
- {
- countTestPropertyAttribute++;
- }
- }
-
- foreach (Attribute attribute in attributesFromClass)
- {
- if (attribute is TestPropertyAttribute)
- {
- countTestPropertyAttribute++;
- }
- }
-
- if (countTestPropertyAttribute == 0)
- {
- // This is the common case that we optimize for. This method used to be an iterator (uses yield return) which is allocating unnecessarily in common cases.
- return [];
- }
-
- var traits = new Trait[countTestPropertyAttribute];
- int index = 0;
- foreach (Attribute attribute in attributesFromMethod)
- {
- if (attribute is TestPropertyAttribute testProperty)
- {
- traits[index++] = new Trait(testProperty.Name, testProperty.Value);
- }
- }
-
- foreach (Attribute attribute in attributesFromClass)
- {
- if (attribute is TestPropertyAttribute testProperty)
- {
- traits[index++] = new Trait(testProperty.Name, testProperty.Value);
- }
- }
-
- return traits;
- }
-
- ///
- /// Get attribute defined on a method which is of given type of subtype of given type.
- ///
- /// The attribute type.
- /// The member to inspect.
- /// An instance of the attribute.
- internal virtual /* for tests, for moq */ IEnumerable GetAttributes(ICustomAttributeProvider attributeProvider)
- where TAttributeType : Attribute
- {
- Attribute[] attributes = GetCustomAttributesCached(attributeProvider);
-
- // Try to find the attribute that is derived from baseAttrType.
- foreach (Attribute attribute in attributes)
- {
- DebugEx.Assert(attribute != null, "ReflectHelper.DefinesAttributeDerivedFrom: internal error: wrong value in the attributes dictionary.");
-
- if (attribute is TAttributeType attributeAsAttributeType)
- {
- yield return attributeAsAttributeType;
- }
- }
- }
-
- ///
- /// Get attribute defined on a method which is of given type of subtype of given type.
- ///
- /// The attribute type.
- /// The type of state to be passed to Action.
- /// The member to inspect.
- /// The action to perform.
- /// The state to pass to action.
- internal void PerformActionOnAttribute(ICustomAttributeProvider attributeProvider, Action action, TState? state)
- where TAttributeType : Attribute
- {
- Attribute[] attributes = GetCustomAttributesCached(attributeProvider);
- foreach (Attribute attribute in attributes)
- {
- DebugEx.Assert(attribute != null, "ReflectHelper.DefinesAttributeDerivedFrom: internal error: wrong value in the attributes dictionary.");
-
- if (attribute is TAttributeType attributeAsAttributeType)
- {
- action(attributeAsAttributeType, state);
- }
- }
- }
-
- ///
- /// Gets and caches the attributes for the given type, or method.
- ///
- /// The member to inspect.
- /// attributes defined.
- internal Attribute[] GetCustomAttributesCached(ICustomAttributeProvider attributeProvider)
- {
- // If the information is cached, then use it otherwise populate the cache using
- // the reflection APIs.
- return _attributeCache.GetOrAdd(attributeProvider, GetAttributes);
-
- // We are avoiding func allocation here.
- static Attribute[] GetAttributes(ICustomAttributeProvider attributeProvider)
- {
- // Populate the cache
- try
- {
- object[]? attributes = NotCachedReflectionAccessor.GetCustomAttributesNotCached(attributeProvider);
- return attributes is null ? [] : attributes as Attribute[] ?? [.. attributes.Cast()];
- }
- catch (Exception ex)
- {
- // Get the exception description
- string description;
- try
- {
- // Can throw if the Message or StackTrace properties throw exceptions
- description = ex.ToString();
- }
- catch (Exception ex2)
- {
- description = string.Format(CultureInfo.CurrentCulture, Resource.ExceptionOccuredWhileGettingTheExceptionDescription, ex.GetType().FullName, ex2.GetType().FullName); // ex.GetType().FullName +
- }
-
- if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsWarningEnabled)
- {
- PlatformServiceProvider.Instance.AdapterTraceLogger.Warning(Resource.FailedToGetCustomAttribute, attributeProvider.GetType().FullName!, description);
- }
-
- return [];
- }
- }
- }
-
- ///
- /// Reflection helper that is accessing Reflection directly, and won't cache the results.
- ///
- internal static class NotCachedReflectionAccessor
- {
- ///
- /// Get custom attributes on a member without cache. Be CAREFUL where you use this, repeatedly accessing reflection without caching the results degrades the performance.
- ///
- /// Member for which attributes needs to be retrieved.
- /// All attributes of give type on member.
- public static object[]? GetCustomAttributesNotCached(ICustomAttributeProvider attributeProvider)
- {
- object[] attributesArray = attributeProvider is MemberInfo memberInfo
- ? PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes(memberInfo)
- : PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes((Assembly)attributeProvider, typeof(Attribute));
-
- return attributesArray; // TODO: Investigate if we rely on NRE
- }
- }
-
- internal /* for tests */ void ClearCache()
- // Tests manipulate the platform reflection provider, and we end up caching different attributes than the class / method actually has.
- => _attributeCache.Clear();
-}
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectionHelper.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectionHelper.cs
new file mode 100644
index 0000000000..7d2f960814
--- /dev/null
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectionHelper.cs
@@ -0,0 +1,84 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers;
+
+///
+/// Helper methods for extracting test metadata from reflection operations.
+///
+internal static class ReflectionHelper
+{
+ ///
+ /// Get categories applied to the test method.
+ ///
+ /// The reflection operations to use.
+ /// The member to inspect.
+ /// The reflected type that owns .
+ /// Categories defined.
+ public static string[] GetTestCategories(this IReflectionOperations reflectionOperations, MemberInfo categoryAttributeProvider, Type owningType)
+ {
+ IEnumerable methodCategories = reflectionOperations.GetAttributes(categoryAttributeProvider);
+ IEnumerable typeCategories = reflectionOperations.GetAttributes(owningType);
+ IEnumerable assemblyCategories = reflectionOperations.GetAttributes(owningType.Assembly);
+
+ return [.. methodCategories.Concat(typeCategories).Concat(assemblyCategories).SelectMany(c => c.TestCategories)];
+ }
+
+ ///
+ /// KeyValue pairs that are provided by TestPropertyAttributes of the given test method.
+ ///
+ /// The reflection operations to use.
+ /// The member to inspect.
+ /// List of traits.
+ public static Trait[] GetTestPropertiesAsTraits(this IReflectionOperations reflectionOperations, MethodInfo testPropertyProvider)
+ {
+ Attribute[] attributesFromMethod = reflectionOperations.GetCustomAttributesCached(testPropertyProvider);
+ Attribute[] attributesFromClass = testPropertyProvider.ReflectedType is { } testClass ? reflectionOperations.GetCustomAttributesCached(testClass) : [];
+ int countTestPropertyAttribute = 0;
+ foreach (Attribute attribute in attributesFromMethod)
+ {
+ if (attribute is TestPropertyAttribute)
+ {
+ countTestPropertyAttribute++;
+ }
+ }
+
+ foreach (Attribute attribute in attributesFromClass)
+ {
+ if (attribute is TestPropertyAttribute)
+ {
+ countTestPropertyAttribute++;
+ }
+ }
+
+ if (countTestPropertyAttribute == 0)
+ {
+ // This is the common case that we optimize for. This method used to be an iterator (uses yield return) which is allocating unnecessarily in common cases.
+ return [];
+ }
+
+ var traits = new Trait[countTestPropertyAttribute];
+ int index = 0;
+ foreach (Attribute attribute in attributesFromMethod)
+ {
+ if (attribute is TestPropertyAttribute testProperty)
+ {
+ traits[index++] = new Trait(testProperty.Name, testProperty.Value);
+ }
+ }
+
+ foreach (Attribute attribute in attributesFromClass)
+ {
+ if (attribute is TestPropertyAttribute testProperty)
+ {
+ traits[index++] = new Trait(testProperty.Name, testProperty.Value);
+ }
+ }
+
+ return traits;
+ }
+}
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs
index b154b531cc..ca23e62048 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.cs
@@ -43,4 +43,60 @@ internal interface IReflectionOperations
Type? GetType(Assembly assembly, string typeName);
object? CreateInstance(Type type, object?[] parameters);
+
+ ///
+ /// Checks to see if a member or type is decorated with the given attribute, or an attribute that derives from it.
+ ///
+ /// Attribute to search for.
+ /// Member to inspect for attributes.
+ /// True if the attribute of the specified type is defined on this member or a class.
+ bool IsAttributeDefined(MemberInfo memberInfo)
+ where TAttribute : Attribute;
+
+ ///
+ /// Gets first attribute that matches the type.
+ /// Use this together with attribute that does not allow multiple and is sealed. In such case there cannot be more attributes, and this will avoid the cost of
+ /// checking for more than one attribute.
+ ///
+ /// Type of the attribute to find.
+ /// The type, assembly or method.
+ /// The attribute that is found or null.
+ TAttribute? GetFirstAttributeOrDefault(ICustomAttributeProvider attributeProvider)
+ where TAttribute : Attribute;
+
+ ///
+ /// Gets first attribute that matches the type or is derived from it.
+ /// Use this together with attribute that does not allow multiple. In such case there cannot be more attributes, and this will avoid the cost of
+ /// checking for more than one attribute.
+ ///
+ /// Type of the attribute to find.
+ /// The type, assembly or method.
+ /// The attribute that is found or null.
+ /// Throws when multiple attributes are found (the attribute must allow multiple).
+ TAttribute? GetSingleAttributeOrDefault(ICustomAttributeProvider attributeProvider)
+ where TAttribute : Attribute;
+
+ ///
+ /// Get attribute defined on a member which is of given type of subtype of given type.
+ ///
+ /// The attribute type.
+ /// The member to inspect.
+ /// An instance of the attribute.
+ IEnumerable GetAttributes(ICustomAttributeProvider attributeProvider)
+ where TAttributeType : Attribute;
+
+ ///
+ /// Gets and caches the attributes for the given type, or method.
+ ///
+ /// The member to inspect.
+ /// Attributes defined.
+ Attribute[] GetCustomAttributesCached(ICustomAttributeProvider attributeProvider);
+
+ ///
+ /// Checks whether the declaring type of the method is declared in the same assembly as the given type.
+ ///
+ /// The method to check.
+ /// The type whose assembly to compare against.
+ /// True if the method's declaring type is in the same assembly as .
+ bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Type type);
}
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs
index df7e49c9b2..a884e1888c 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs
@@ -1,21 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+using System.Security;
+
+using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
-#if NETFRAMEWORK
-using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
-#endif
+using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
///
/// This service is responsible for platform specific reflection operations.
///
-internal sealed class ReflectionOperations : IReflectionOperations
+internal sealed class ReflectionOperations : MarshalByRefObject, IReflectionOperations
{
private const BindingFlags DeclaredOnlyLookup = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
private const BindingFlags Everything = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
+ // PERF: This was moved from Dictionary> to Concurrent
+ // storing an array allows us to store multiple attributes of the same type if we find them. It also has lower memory footprint, and is faster
+ // when we are going through the whole collection. Giving us overall better perf.
+ private readonly ConcurrentDictionary _attributeCache = [];
+
///
/// Gets all the custom attributes adorned on a member.
///
@@ -23,20 +29,15 @@ internal sealed class ReflectionOperations : IReflectionOperations
/// The list of attributes on the member. Empty list if none found.
[return: NotNullIfNotNull(nameof(memberInfo))]
public object[]? GetCustomAttributes(MemberInfo memberInfo)
-#if NETFRAMEWORK
- => [.. ReflectionOperationsNetFrameworkAttributeHelpers.GetCustomAttributes(memberInfo)];
-#else
{
object[] attributes = memberInfo.GetCustomAttributes(typeof(Attribute), inherit: true);
- // Ensures that when the return of this method is used here:
- // https://github.com/microsoft/testfx/blob/e101a9d48773cc935c7b536d25d378d9a3211fee/src/Adapter/MSTest.TestAdapter/Helpers/ReflectHelper.cs#L461
+ // Ensures that when the return of this method is used with GetCustomAttributesCached
// then we are already Attribute[] to avoid LINQ Cast and extra array allocation.
// This assert is solely for performance. Nothing "functional" will go wrong if the assert failed.
Debug.Assert(attributes is Attribute[], $"Expected Attribute[], found '{attributes.GetType()}'.");
return attributes;
}
-#endif
///
/// Gets all the custom attributes of a given type on an assembly.
@@ -44,12 +45,8 @@ internal sealed class ReflectionOperations : IReflectionOperations
/// The assembly.
/// The attribute type.
/// The list of attributes of the given type on the member. Empty list if none found.
- public object[] GetCustomAttributes(Assembly assembly, Type type) =>
-#if NETFRAMEWORK
- ReflectionOperationsNetFrameworkAttributeHelpers.GetCustomAttributes(assembly, type).ToArray();
-#else
- assembly.GetCustomAttributes(type, inherit: true);
-#endif
+ public object[] GetCustomAttributes(Assembly assembly, Type type)
+ => assembly.GetCustomAttributes(type, inherit: true);
#pragma warning disable IL2070 // this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to 'target method'.
#pragma warning disable IL2026 // Members attributed with RequiresUnreferencedCode may break when trimming
@@ -92,4 +89,201 @@ public MethodInfo[] GetRuntimeMethods(Type type)
#pragma warning restore IL2026 // Members attributed with RequiresUnreferencedCode may break when trimming
#pragma warning restore IL2067 // 'target parameter' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to 'target method'.
#pragma warning restore IL2057 // Unrecognized value passed to the typeName parameter of 'System.Type.GetType(String)'
+
+ ///
+ /// Checks to see if a member or type is decorated with the given attribute, or an attribute that derives from it. e.g. [MyTestClass] from [TestClass] will match if you look for [TestClass]. The inherit parameter does not impact this checking.
+ ///
+ /// Attribute to search for.
+ /// Member to inspect for attributes.
+ /// True if the attribute of the specified type is defined on this member or a class.
+ public bool IsAttributeDefined(MemberInfo memberInfo)
+ where TAttribute : Attribute
+ {
+ Ensure.NotNull(memberInfo);
+
+ // Get all attributes on the member.
+ Attribute[] attributes = GetCustomAttributesCached(memberInfo);
+
+ // Try to find the attribute that is derived from baseAttrType.
+ foreach (Attribute attribute in attributes)
+ {
+ DebugEx.Assert(attribute != null, $"{nameof(ReflectionOperations)}.{nameof(GetCustomAttributesCached)}: internal error: wrong value in the attributes dictionary.");
+
+ if (attribute is TAttribute)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Returns object to be used for controlling lifetime, null means infinite lifetime.
+ ///
+ ///
+ /// The .
+ ///
+ [SecurityCritical]
+#if NET5_0_OR_GREATER
+ [Obsolete]
+#endif
+ public override object InitializeLifetimeService() => null!;
+
+ ///
+ /// Gets first attribute that matches the type.
+ /// Use this together with attribute that does not allow multiple and is sealed. In such case there cannot be more attributes, and this will avoid the cost of
+ /// checking for more than one attribute.
+ ///
+ /// Type of the attribute to find.
+ /// The type, assembly or method.
+ /// The attribute that is found or null.
+ public TAttribute? GetFirstAttributeOrDefault(ICustomAttributeProvider attributeProvider)
+ where TAttribute : Attribute
+ {
+ // If the attribute is not sealed, then it can allow multiple, even if AllowMultiple is false.
+ // This happens when a derived type is also applied along with the base type.
+ // Or, if the derived type modifies the attribute usage to allow multiple.
+ // So we want to ensure this is only called for sealed attributes.
+ DebugEx.Assert(typeof(TAttribute).IsSealed, $"Expected '{typeof(TAttribute)}' to be sealed, but was not.");
+
+ Attribute[] cachedAttributes = GetCustomAttributesCached(attributeProvider);
+
+ foreach (Attribute cachedAttribute in cachedAttributes)
+ {
+ if (cachedAttribute is TAttribute cachedAttributeAsTAttribute)
+ {
+ return cachedAttributeAsTAttribute;
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Gets first attribute that matches the type or is derived from it.
+ /// Use this together with attribute that does not allow multiple. In such case there cannot be more attributes, and this will avoid the cost of
+ /// checking for more than one attribute.
+ ///
+ /// Type of the attribute to find.
+ /// The type, assembly or method.
+ /// The attribute that is found or null.
+ /// Throws when multiple attributes are found (the attribute must allow multiple).
+ public TAttribute? GetSingleAttributeOrDefault(ICustomAttributeProvider attributeProvider)
+ where TAttribute : Attribute
+ {
+ Attribute[] cachedAttributes = GetCustomAttributesCached(attributeProvider);
+
+ TAttribute? foundAttribute = null;
+ foreach (Attribute cachedAttribute in cachedAttributes)
+ {
+ if (cachedAttribute is TAttribute cachedAttributeAsTAttribute)
+ {
+ if (foundAttribute is not null)
+ {
+ throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resource.DuplicateAttributeError, typeof(TAttribute)));
+ }
+
+ foundAttribute = cachedAttributeAsTAttribute;
+ }
+ }
+
+ return foundAttribute;
+ }
+
+ ///
+ /// Get attribute defined on a method which is of given type of subtype of given type.
+ ///
+ /// The attribute type.
+ /// The member to inspect.
+ /// An instance of the attribute.
+ public IEnumerable GetAttributes(ICustomAttributeProvider attributeProvider)
+ where TAttributeType : Attribute
+ {
+ Attribute[] attributes = GetCustomAttributesCached(attributeProvider);
+
+ // Try to find the attribute that is derived from baseAttrType.
+ foreach (Attribute attribute in attributes)
+ {
+ DebugEx.Assert(attribute != null, "ReflectionOperations.DefinesAttributeDerivedFrom: internal error: wrong value in the attributes dictionary.");
+
+ if (attribute is TAttributeType attributeAsAttributeType)
+ {
+ yield return attributeAsAttributeType;
+ }
+ }
+ }
+
+ ///
+ /// Gets and caches the attributes for the given type, or method.
+ ///
+ /// The member to inspect.
+ /// attributes defined.
+ public Attribute[] GetCustomAttributesCached(ICustomAttributeProvider attributeProvider)
+ {
+ // If the information is cached, then use it otherwise populate the cache using
+ // the reflection APIs.
+ return _attributeCache.GetOrAdd(attributeProvider, GetAttributes);
+
+ // We are avoiding func allocation here.
+ static Attribute[] GetAttributes(ICustomAttributeProvider attributeProvider)
+ {
+ // Populate the cache
+ try
+ {
+ object[]? attributes = NotCachedReflectionAccessor.GetCustomAttributesNotCached(attributeProvider);
+ return attributes is null ? [] : attributes as Attribute[] ?? [.. attributes.Cast()];
+ }
+ catch (Exception ex)
+ {
+ // Get the exception description
+ string description;
+ try
+ {
+ // Can throw if the Message or StackTrace properties throw exceptions
+ description = ex.ToString();
+ }
+ catch (Exception ex2)
+ {
+ description = string.Format(CultureInfo.CurrentCulture, Resource.ExceptionOccuredWhileGettingTheExceptionDescription, ex.GetType().FullName, ex2.GetType().FullName); // ex.GetType().FullName +
+ }
+
+ if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsWarningEnabled)
+ {
+ PlatformServiceProvider.Instance.AdapterTraceLogger.Warning(Resource.FailedToGetCustomAttribute, attributeProvider.GetType().FullName!, description);
+ }
+
+ return [];
+ }
+ }
+ }
+
+ ///
+ /// Reflection helper that is accessing Reflection directly, and won't cache the results.
+ ///
+ internal static class NotCachedReflectionAccessor
+ {
+ ///
+ /// Get custom attributes on a member without cache. Be CAREFUL where you use this, repeatedly accessing reflection without caching the results degrades the performance.
+ ///
+ /// Member for which attributes needs to be retrieved.
+ /// All attributes of give type on member.
+ public static object[]? GetCustomAttributesNotCached(ICustomAttributeProvider attributeProvider)
+ {
+ IReflectionOperations reflectionOperations = PlatformServiceProvider.Instance.ReflectionOperations;
+ object[] attributesArray = attributeProvider is MemberInfo memberInfo
+ ? reflectionOperations.GetCustomAttributes(memberInfo)
+ : reflectionOperations.GetCustomAttributes((Assembly)attributeProvider, typeof(Attribute));
+
+ return attributesArray; // TODO: Investigate if we rely on NRE
+ }
+ }
+
+ ///
+ public bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Type type)
+ => method.DeclaringType!.Assembly.Equals(type.Assembly);
+
+ internal /* for tests */ void ClearCache()
+ // Tests manipulate the platform reflection provider, and we end up caching different attributes than the class / method actually has.
+ => _attributeCache.Clear();
}
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs
index 2fc480dc1e..6e2a76abb0 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs
@@ -4,7 +4,6 @@
#if !WINDOWS_UWP && !WIN_UI
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;
-using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
@@ -35,7 +34,7 @@ internal sealed class TestDeployment : ITestDeployment
/// Initializes a new instance of the class.
///
public TestDeployment()
- : this(new DeploymentItemUtility(ReflectHelper.Instance), new DeploymentUtility(), new FileUtility())
+ : this(new DeploymentItemUtility((ReflectionOperations)PlatformServiceProvider.Instance.ReflectionOperations), new DeploymentUtility(), new FileUtility())
{
}
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs
index 432db7470e..3dfac24c2d 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs
@@ -3,8 +3,8 @@
#if !WINDOWS_UWP && !WIN_UI
-using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment;
+using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -15,7 +15,7 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Uti
///
internal sealed class DeploymentItemUtility
{
- private readonly ReflectHelper _reflectHelper;
+ private readonly IReflectionOperations _reflectionOperation;
///
/// A cache for class level deployment items.
@@ -25,10 +25,10 @@ internal sealed class DeploymentItemUtility
///
/// Initializes a new instance of the class.
///
- /// The reflect helper.
- internal DeploymentItemUtility(ReflectHelper reflectHelper)
+ /// The reflect helper.
+ internal DeploymentItemUtility(IReflectionOperations reflectionOperation)
{
- _reflectHelper = reflectHelper;
+ _reflectionOperation = reflectionOperation;
_classLevelDeploymentItems = [];
}
@@ -42,7 +42,7 @@ internal IList GetClassLevelDeploymentItems(Type type, ICollecti
{
if (!_classLevelDeploymentItems.TryGetValue(type, out IList? value))
{
- IEnumerable deploymentItemAttributes = _reflectHelper.GetAttributes(type);
+ IEnumerable deploymentItemAttributes = _reflectionOperation.GetAttributes(type);
value = GetDeploymentItems(deploymentItemAttributes, warnings);
_classLevelDeploymentItems[type] = value;
}
@@ -59,7 +59,7 @@ internal IList GetClassLevelDeploymentItems(Type type, ICollecti
internal KeyValuePair[]? GetDeploymentItems(MethodInfo method, IEnumerable classLevelDeploymentItems,
ICollection warnings)
{
- List testLevelDeploymentItems = GetDeploymentItems(_reflectHelper.GetAttributes(method), warnings);
+ List testLevelDeploymentItems = GetDeploymentItems(_reflectionOperation.GetAttributes(method), warnings);
return ToKeyValuePairs(Concat(testLevelDeploymentItems, classLevelDeploymentItems));
}
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs
index 2f407266ac..68f908f5a7 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs
@@ -4,7 +4,6 @@
#if !WINDOWS_UWP && !WIN_UI
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;
-using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions;
@@ -26,7 +25,7 @@ internal abstract class DeploymentUtilityBase
protected const string DeploymentFolderPrefix = "Deploy";
public DeploymentUtilityBase()
- : this(new DeploymentItemUtility(ReflectHelper.Instance), new AssemblyUtility(), new FileUtility())
+ : this(new DeploymentItemUtility((ReflectionOperations)PlatformServiceProvider.Instance.ReflectionOperations), new AssemblyUtility(), new FileUtility())
{
}
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/ReflectionOperationsNetFrameworkAttributeHelpers.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/ReflectionOperationsNetFrameworkAttributeHelpers.cs
deleted file mode 100644
index 7dfd9fbf17..0000000000
--- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/ReflectionOperationsNetFrameworkAttributeHelpers.cs
+++ /dev/null
@@ -1,272 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-#if NETFRAMEWORK
-
-namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
-
-///
-/// This class is only intended to be used by ReflectionOperations on .NET Framework.
-/// TODO: Investigate why we need complicated logic under .NET Framework, and whether it
-/// can be simplified to have unified simple logic for .NET Core and .NET Framework.
-///
-internal static class ReflectionOperationsNetFrameworkAttributeHelpers
-{
- ///
- /// Gets all the custom attributes adorned on a member.
- ///
- /// The member.
- /// The list of attributes on the member. Empty list if none found.
- internal static IReadOnlyList