diff --git a/src/Adapter/MSTest.TestAdapter/Execution/TestAssemblyInfo.cs b/src/Adapter/MSTest.TestAdapter/Execution/TestAssemblyInfo.cs
index 8da4e5c3de..c25bbc9535 100644
--- a/src/Adapter/MSTest.TestAdapter/Execution/TestAssemblyInfo.cs
+++ b/src/Adapter/MSTest.TestAdapter/Execution/TestAssemblyInfo.cs
@@ -51,6 +51,8 @@ internal set
}
}
+ internal AssemblyInitializeAttribute? AssemblyInitializeAttribute { get; set; }
+
///
/// Gets or sets the AssemblyInitializeMethod timeout.
///
@@ -80,6 +82,8 @@ internal set
}
}
+ internal AssemblyCleanupAttribute? AssemblyCleanupAttribute { get; set; }
+
///
/// Gets a value indicating whether AssemblyInitialize has been executed.
///
@@ -143,7 +147,7 @@ public void RunAssemblyInitialize(TestContext testContext)
try
{
AssemblyInitializationException = FixtureMethodRunner.RunWithTimeoutAndCancellation(
- () => AssemblyInitializeMethod.InvokeAsSynchronousTask(null, testContext),
+ () => AssemblyInitializeAttribute!.ExecuteAsync(new AssemblyInitializeExecutionContext(() => AssemblyInitializeMethod.InvokeAsync(null, testContext))).GetAwaiter().GetResult(),
testContext.CancellationTokenSource,
AssemblyInitializeMethodTimeoutMilliseconds,
AssemblyInitializeMethod,
@@ -216,7 +220,7 @@ public void RunAssemblyInitialize(TestContext testContext)
try
{
AssemblyCleanupException = FixtureMethodRunner.RunWithTimeoutAndCancellation(
- () => AssemblyCleanupMethod.InvokeAsSynchronousTask(null),
+ () => AssemblyCleanupAttribute!.ExecuteAsync(new AssemblyCleanupExecutionContext(() => AssemblyCleanupMethod.InvokeAsync(null))).GetAwaiter().GetResult(),
new CancellationTokenSource(),
AssemblyCleanupMethodTimeoutMilliseconds,
AssemblyCleanupMethod,
@@ -276,11 +280,11 @@ internal void ExecuteAssemblyCleanup(TestContext testContext)
{
if (AssemblyCleanupMethod.GetParameters().Length == 0)
{
- AssemblyCleanupMethod.InvokeAsSynchronousTask(null);
+ AssemblyCleanupAttribute!.ExecuteAsync(new AssemblyCleanupExecutionContext(() => AssemblyCleanupMethod.InvokeAsync(null))).GetAwaiter().GetResult();
}
else
{
- AssemblyCleanupMethod.InvokeAsSynchronousTask(null, testContext);
+ AssemblyCleanupAttribute!.ExecuteAsync(new AssemblyCleanupExecutionContext(() => AssemblyCleanupMethod.InvokeAsync(null, testContext))).GetAwaiter().GetResult();
}
},
testContext.CancellationTokenSource,
diff --git a/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs b/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs
index 2b13c8384d..558acf81e0 100644
--- a/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs
+++ b/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs
@@ -416,14 +416,16 @@ private TestAssemblyInfo GetAssemblyInfo(Type type)
// Enumerate through all methods and identify the Assembly Init and cleanup methods.
foreach (MethodInfo methodInfo in PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredMethods(t))
{
- if (IsAssemblyOrClassInitializeMethod(methodInfo))
+ if (GetAssemblyOrClassInitializeMethod(methodInfo) is { } assemblyInitializeAttribute)
{
assemblyInfo.AssemblyInitializeMethod = methodInfo;
+ assemblyInfo.AssemblyInitializeAttribute = assemblyInitializeAttribute;
assemblyInfo.AssemblyInitializeMethodTimeoutMilliseconds = TryGetTimeoutInfo(methodInfo, FixtureKind.AssemblyInitialize);
}
- else if (IsAssemblyOrClassCleanupMethod(methodInfo))
+ else if (GetAssemblyOrClassCleanupMethod(methodInfo) is { } assemblyCleanupAttribute)
{
assemblyInfo.AssemblyCleanupMethod = methodInfo;
+ assemblyInfo.AssemblyCleanupAttribute = assemblyCleanupAttribute;
assemblyInfo.AssemblyCleanupMethodTimeoutMilliseconds = TryGetTimeoutInfo(methodInfo, FixtureKind.AssemblyCleanup);
}
}
@@ -440,7 +442,7 @@ private TestAssemblyInfo GetAssemblyInfo(Type type)
/// The initialization attribute type.
/// The method info.
/// True if its an initialization method.
- private bool IsAssemblyOrClassInitializeMethod(MethodInfo methodInfo)
+ private TInitializeAttribute? GetAssemblyOrClassInitializeMethod(MethodInfo methodInfo)
where TInitializeAttribute : Attribute
{
// TODO: this would be inconsistent with the codebase, but potential perf gain, issue: https://github.com/microsoft/testfx/issues/2999
@@ -448,9 +450,20 @@ private bool IsAssemblyOrClassInitializeMethod(MethodInfo
// {
// return false;
// }
- if (!_reflectionHelper.IsNonDerivedAttributeDefined(methodInfo, false))
+ IEnumerable attributes = _reflectionHelper.GetDerivedAttributes(methodInfo, inherit: false);
+ using IEnumerator enumerator = attributes.GetEnumerator();
+ if (!enumerator.MoveNext())
{
- return false;
+ // No attribute found.
+ return null;
+ }
+
+ TInitializeAttribute attribute = enumerator.Current;
+ if (enumerator.MoveNext())
+ {
+ // More than one attribute found.
+ string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_MultipleAttributesOnTestMethod, methodInfo.DeclaringType!.FullName, methodInfo.Name);
+ throw new TypeInspectionException(message);
}
if (!methodInfo.HasCorrectClassOrAssemblyInitializeSignature())
@@ -459,7 +472,7 @@ private bool IsAssemblyOrClassInitializeMethod(MethodInfo
throw new TypeInspectionException(message);
}
- return true;
+ return attribute;
}
///
@@ -468,7 +481,7 @@ private bool IsAssemblyOrClassInitializeMethod(MethodInfo
/// The cleanup attribute type.
/// The method info.
/// True if its a cleanup method.
- private bool IsAssemblyOrClassCleanupMethod(MethodInfo methodInfo)
+ private TCleanupAttribute? GetAssemblyOrClassCleanupMethod(MethodInfo methodInfo)
where TCleanupAttribute : Attribute
{
// TODO: this would be inconsistent with the codebase, but potential perf gain, issue: https://github.com/microsoft/testfx/issues/2999
@@ -476,9 +489,20 @@ private bool IsAssemblyOrClassCleanupMethod(MethodInfo method
// {
// return false;
// }
- if (!_reflectionHelper.IsNonDerivedAttributeDefined(methodInfo, false))
+ IEnumerable attributes = _reflectionHelper.GetDerivedAttributes(methodInfo, inherit: false);
+ using IEnumerator enumerator = attributes.GetEnumerator();
+ if (!enumerator.MoveNext())
{
- return false;
+ // No attribute found.
+ return null;
+ }
+
+ TCleanupAttribute attribute = enumerator.Current;
+ if (enumerator.MoveNext())
+ {
+ // More than one attribute found.
+ string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_MultipleAttributesOnTestMethod, methodInfo.DeclaringType!.FullName, methodInfo.Name);
+ throw new TypeInspectionException(message);
}
if (!methodInfo.HasCorrectClassOrAssemblyCleanupSignature())
@@ -487,7 +511,7 @@ private bool IsAssemblyOrClassCleanupMethod(MethodInfo method
throw new TypeInspectionException(message);
}
- return true;
+ return attribute;
}
#endregion
@@ -546,10 +570,10 @@ private void UpdateInfoIfClassInitializeOrCleanupMethod(
bool isBase,
ref MethodInfo?[] initAndCleanupMethods)
{
- bool isInitializeMethod = IsAssemblyOrClassInitializeMethod(methodInfo);
- bool isCleanupMethod = IsAssemblyOrClassCleanupMethod(methodInfo);
+ ClassInitializeAttribute? classInitializeAttribute = GetAssemblyOrClassInitializeMethod(methodInfo);
+ ClassCleanupAttribute? classCleanupAttribute = GetAssemblyOrClassCleanupMethod(methodInfo);
- if (isInitializeMethod)
+ if (classInitializeAttribute is not null)
{
if (TryGetTimeoutInfo(methodInfo, FixtureKind.ClassInitialize) is { } timeoutInfo)
{
@@ -571,7 +595,7 @@ private void UpdateInfoIfClassInitializeOrCleanupMethod(
}
}
- if (isCleanupMethod)
+ if (classCleanupAttribute is not null)
{
if (TryGetTimeoutInfo(methodInfo, FixtureKind.ClassCleanup) is { } timeoutInfo)
{
diff --git a/src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs b/src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs
index d220cd9470..d269644a41 100644
--- a/src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs
+++ b/src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs
@@ -107,7 +107,7 @@ internal static bool IsValidReturnType(this MethodInfo method)
}
///
- /// Invoke a as a synchronous .
+ /// Invoke a as an asynchronous .
///
///
/// instance.
@@ -118,7 +118,7 @@ internal static bool IsValidReturnType(this MethodInfo method)
///
/// Arguments for the methodInfo invoke.
///
- internal static void InvokeAsSynchronousTask(this MethodInfo methodInfo, object? classInstance, params object?[]? arguments)
+ internal static async Task InvokeAsync(this MethodInfo methodInfo, object? classInstance, params object?[]? arguments)
{
ParameterInfo[]? methodParameters = methodInfo.GetParameters();
@@ -189,14 +189,29 @@ internal static void InvokeAsSynchronousTask(this MethodInfo methodInfo, object?
// If methodInfo is an async method, wait for returned task
if (invokeResult is Task task)
{
- task.GetAwaiter().GetResult();
+ await task;
}
else if (invokeResult is ValueTask valueTask)
{
- valueTask.GetAwaiter().GetResult();
+ await valueTask;
}
}
+ ///
+ /// Invoke a as a synchronous .
+ ///
+ ///
+ /// instance.
+ ///
+ ///
+ /// Instance of the on which methodInfo is invoked.
+ ///
+ ///
+ /// Arguments for the methodInfo invoke.
+ ///
+ internal static void InvokeAsSynchronousTask(this MethodInfo methodInfo, object? classInstance, params object?[]? arguments)
+ => InvokeAsync(methodInfo, classInstance, arguments).GetAwaiter().GetResult();
+
// Scenarios to test:
//
// [DataRow(null, "Hello")]
diff --git a/src/TestFramework/TestFramework/Attributes/Lifecycle/Cleanup/AssemblyCleanupAttribute.cs b/src/TestFramework/TestFramework/Attributes/Lifecycle/Cleanup/AssemblyCleanupAttribute.cs
index 5d37d858f7..79419ef975 100644
--- a/src/TestFramework/TestFramework/Attributes/Lifecycle/Cleanup/AssemblyCleanupAttribute.cs
+++ b/src/TestFramework/TestFramework/Attributes/Lifecycle/Cleanup/AssemblyCleanupAttribute.cs
@@ -3,8 +3,20 @@
namespace Microsoft.VisualStudio.TestTools.UnitTesting;
+public readonly struct AssemblyCleanupExecutionContext
+{
+ internal AssemblyCleanupExecutionContext(Func assemblyCleanupExecutorGetter)
+ => AssemblyCleanupExecutorGetter = assemblyCleanupExecutorGetter;
+
+ public Func AssemblyCleanupExecutorGetter { get; }
+}
+
///
/// The assembly cleanup attribute.
///
[AttributeUsage(AttributeTargets.Method)]
-public sealed class AssemblyCleanupAttribute : Attribute;
+public class AssemblyCleanupAttribute : Attribute
+{
+ public virtual async Task ExecuteAsync(AssemblyCleanupExecutionContext assemblyCleanupContext)
+ => await assemblyCleanupContext.AssemblyCleanupExecutorGetter();
+}
diff --git a/src/TestFramework/TestFramework/Attributes/Lifecycle/Initialization/AssemblyInitializeAttribute.cs b/src/TestFramework/TestFramework/Attributes/Lifecycle/Initialization/AssemblyInitializeAttribute.cs
index c45d0124e5..abc86b27b8 100644
--- a/src/TestFramework/TestFramework/Attributes/Lifecycle/Initialization/AssemblyInitializeAttribute.cs
+++ b/src/TestFramework/TestFramework/Attributes/Lifecycle/Initialization/AssemblyInitializeAttribute.cs
@@ -3,8 +3,20 @@
namespace Microsoft.VisualStudio.TestTools.UnitTesting;
+public readonly struct AssemblyInitializeExecutionContext
+{
+ internal AssemblyInitializeExecutionContext(Func assemblyInitializeExecutorGetter)
+ => AssemblyInitializeExecutorGetter = assemblyInitializeExecutorGetter;
+
+ public Func AssemblyInitializeExecutorGetter { get; }
+}
+
///
/// The assembly initialize attribute.
///
[AttributeUsage(AttributeTargets.Method)]
-public sealed class AssemblyInitializeAttribute : Attribute;
+public class AssemblyInitializeAttribute : Attribute
+{
+ public virtual async Task ExecuteAsync(AssemblyInitializeExecutionContext assemblyInitializeContext)
+ => await assemblyInitializeContext.AssemblyInitializeExecutorGetter();
+}