-
Notifications
You must be signed in to change notification settings - Fork 256
/
MethodInfoExtensions.cs
166 lines (144 loc) · 6.8 KB
/
MethodInfoExtensions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// 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;
using System.Runtime.CompilerServices;
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
internal static class MethodInfoExtensions
{
/// <summary>
/// Verifies that the class initialize has the correct signature.
/// </summary>
/// <param name="method">The method to verify.</param>
/// <returns>True if the method has the right Assembly/Class initialize signature.</returns>
internal static bool HasCorrectClassOrAssemblyInitializeSignature(this MethodInfo method)
{
DebugEx.Assert(method != null, "method should not be null.");
ParameterInfo[] parameters = method.GetParameters();
return
method.IsStatic &&
method.IsPublic &&
(parameters.Length == 1) &&
parameters[0].ParameterType == typeof(TestContext) &&
method.IsValidReturnType();
}
/// <summary>
/// Verifies that the class cleanup has the correct signature.
/// </summary>
/// <param name="method">The method to verify.</param>
/// <returns>True if the method has the right Assembly/Class cleanup signature.</returns>
internal static bool HasCorrectClassOrAssemblyCleanupSignature(this MethodInfo method)
{
DebugEx.Assert(method != null, "method should not be null.");
return
method.IsStatic &&
method.IsPublic &&
(method.GetParameters().Length == 0) &&
method.IsValidReturnType();
}
/// <summary>
/// Verifies that the test Initialize/cleanup has the correct signature.
/// </summary>
/// <param name="method">The method to verify.</param>
/// <returns>True if the method has the right test init/cleanup signature.</returns>
internal static bool HasCorrectTestInitializeOrCleanupSignature(this MethodInfo method)
{
DebugEx.Assert(method != null, "method should not be null.");
return
!method.IsStatic &&
method.IsPublic &&
(method.GetParameters().Length == 0) &&
method.IsValidReturnType();
}
/// <summary>
/// Verifies that the test method has the correct signature.
/// </summary>
/// <param name="method">The method to verify.</param>
/// <param name="ignoreParameterLength">Indicates whether parameter length is to be ignored.</param>
/// <param name="discoverInternals">True if internal test classes and test methods should be discovered in
/// addition to public test classes and methods.</param>
/// <returns>True if the method has the right test method signature.</returns>
internal static bool HasCorrectTestMethodSignature(this MethodInfo method, bool ignoreParameterLength, bool discoverInternals = false)
{
DebugEx.Assert(method != null, "method should not be null.");
return
!method.IsAbstract &&
!method.IsStatic &&
!method.IsGenericMethod &&
(method.IsPublic || (discoverInternals && method.IsAssembly)) &&
(method.GetParameters().Length == 0 || ignoreParameterLength) &&
method.IsValidReturnType(); // Match return type Task for async methods only. Else return type void.
}
/// <summary>
/// Checks whether test method has correct Timeout attribute.
/// </summary>
/// <param name="method">The method to verify.</param>
/// <returns>True if the method has the right test timeout signature.</returns>
internal static bool HasCorrectTimeout(this MethodInfo method)
{
DebugEx.Assert(method != null, "method should not be null.");
// There should be one and only one TimeoutAttribute.
var attributes = ReflectHelper.GetCustomAttributes<TimeoutAttribute>(method, false);
if (attributes?.Length != 1)
{
return false;
}
// Timeout cannot be less than 0.
return !(attributes[0]?.Timeout < 0);
}
/// <summary>
/// Check is return type is void for non async and Task for async methods.
/// </summary>
/// <param name="method">The method to verify.</param>
/// <returns>True if the method has a void/task return type..</returns>
internal static bool IsValidReturnType(this MethodInfo method)
{
return ReflectHelper.MatchReturnType(method, typeof(Task))
#if NETCOREAPP
|| ReflectHelper.MatchReturnType(method, typeof(ValueTask))
#endif
|| (ReflectHelper.MatchReturnType(method, typeof(void)) && method.GetAsyncTypeName() == null);
}
/// <summary>
/// For async methods compiler generates different type and method.
/// Gets the compiler generated type name for given async test method.
/// </summary>
/// <param name="method">The method to verify.</param>
/// <returns>Compiler generated type name for given async test method..</returns>
internal static string? GetAsyncTypeName(this MethodInfo method)
{
var asyncStateMachineAttribute = ReflectHelper.GetCustomAttributes<AsyncStateMachineAttribute>(method, false).FirstOrDefault();
return asyncStateMachineAttribute?.StateMachineType?.FullName;
}
/// <summary>
/// Invoke a <see cref="MethodInfo"/> as a synchronous <see cref="Task"/>.
/// </summary>
/// <param name="methodInfo">
/// <see cref="MethodInfo"/> instance.
/// </param>
/// <param name="classInstance">
/// Instance of the on which methodInfo is invoked.
/// </param>
/// <param name="parameters">
/// Arguments for the methodInfo invoke.
/// </param>
internal static void InvokeAsSynchronousTask(this MethodInfo methodInfo, object? classInstance, params object?[]? parameters)
{
var methodParameters = methodInfo.GetParameters();
// check if test method expected parameter values but no test data was provided,
// throw error with appropriate message.
if (methodParameters != null && methodParameters.Length > 0 && parameters == null)
{
throw new TestFailedException(ObjectModel.UnitTestOutcome.Error, Resource.UTA_TestMethodExpectedParameters);
}
Task? task = parameters is not null
&& methodParameters?.Length == 1
&& methodParameters[0].ParameterType == typeof(object[])
? methodInfo.Invoke(classInstance, new[] { parameters }) as Task
: methodInfo.Invoke(classInstance, parameters) as Task;
// If methodInfo is an Async method, wait for returned task
task?.GetAwaiter().GetResult();
}
}