Skip to content

Commit 5f134f5

Browse files
authored
Allow DynamicData source to be on base types (#4482)
1 parent 0b65aa2 commit 5f134f5

File tree

10 files changed

+318
-73
lines changed

10 files changed

+318
-73
lines changed

src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs

+41-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -19,11 +19,11 @@ public IEnumerable<object[]> GetData(Type? _dynamicDataDeclaringType, DynamicDat
1919
{
2020
case DynamicDataSourceType.AutoDetect:
2121
#pragma warning disable IDE0045 // Convert to conditional expression - it becomes less readable.
22-
if (PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredProperty(_dynamicDataDeclaringType, _dynamicDataSourceName) is { } dynamicDataPropertyInfo)
22+
if (GetPropertyConsideringInheritance(_dynamicDataDeclaringType, _dynamicDataSourceName) is { } dynamicDataPropertyInfo)
2323
{
2424
obj = GetDataFromProperty(dynamicDataPropertyInfo);
2525
}
26-
else if (PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredMethod(_dynamicDataDeclaringType, _dynamicDataSourceName) is { } dynamicDataMethodInfo)
26+
else if (GetMethodConsideringInheritance(_dynamicDataDeclaringType, _dynamicDataSourceName) is { } dynamicDataMethodInfo)
2727
{
2828
obj = GetDataFromMethod(dynamicDataMethodInfo);
2929
}
@@ -35,14 +35,14 @@ public IEnumerable<object[]> GetData(Type? _dynamicDataDeclaringType, DynamicDat
3535

3636
break;
3737
case DynamicDataSourceType.Property:
38-
PropertyInfo property = PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredProperty(_dynamicDataDeclaringType, _dynamicDataSourceName)
38+
PropertyInfo property = GetPropertyConsideringInheritance(_dynamicDataDeclaringType, _dynamicDataSourceName)
3939
?? throw new ArgumentNullException($"{DynamicDataSourceType.Property} {_dynamicDataSourceName}");
4040

4141
obj = GetDataFromProperty(property);
4242
break;
4343

4444
case DynamicDataSourceType.Method:
45-
MethodInfo method = PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredMethod(_dynamicDataDeclaringType, _dynamicDataSourceName)
45+
MethodInfo method = GetMethodConsideringInheritance(_dynamicDataDeclaringType, _dynamicDataSourceName)
4646
?? throw new ArgumentNullException($"{DynamicDataSourceType.Method} {_dynamicDataSourceName}");
4747

4848
obj = GetDataFromMethod(method);
@@ -251,4 +251,40 @@ private static bool IsTupleOrValueTuple(Type type, out int tupleSize)
251251
return false;
252252
}
253253
#endif
254+
255+
private static PropertyInfo? GetPropertyConsideringInheritance(Type type, string propertyName)
256+
{
257+
// NOTE: Don't use GetRuntimeProperty. It considers inheritance only for instance properties.
258+
Type? currentType = type;
259+
while (currentType is not null)
260+
{
261+
PropertyInfo? property = PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredProperty(currentType, propertyName);
262+
if (property is not null)
263+
{
264+
return property;
265+
}
266+
267+
currentType = currentType.BaseType;
268+
}
269+
270+
return null;
271+
}
272+
273+
private static MethodInfo? GetMethodConsideringInheritance(Type type, string methodName)
274+
{
275+
// NOTE: Don't use GetRuntimeMethod. It considers inheritance only for instance methods.
276+
Type? currentType = type;
277+
while (currentType is not null)
278+
{
279+
MethodInfo? method = PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredMethod(currentType, methodName);
280+
if (method is not null)
281+
{
282+
return method;
283+
}
284+
285+
currentType = currentType.BaseType;
286+
}
287+
288+
return null;
289+
}
254290
}

src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ private TestClassInfo CreateClassInfo(Type classType, TestMethod testMethod)
370370
{
371371
try
372372
{
373-
PropertyInfo? testContextProperty = PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeProperty(classType, TestContextPropertyName);
373+
PropertyInfo? testContextProperty = PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeProperty(classType, TestContextPropertyName, includeNonPublic: false);
374374
if (testContextProperty == null)
375375
{
376376
// that's okay may be the property was not defined
@@ -810,7 +810,8 @@ private MethodInfo GetMethodInfoForTestMethod(TestMethod testMethod, TestClassIn
810810
else if (methodBase != null)
811811
{
812812
Type[] parameters = methodBase.GetParameters().Select(i => i.ParameterType).ToArray();
813-
testMethodInfo = PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethod(methodBase.DeclaringType!, methodBase.Name, parameters);
813+
// TODO: Should we pass true for includeNonPublic?
814+
testMethodInfo = PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethod(methodBase.DeclaringType!, methodBase.Name, parameters, includeNonPublic: false);
814815
}
815816

816817
return testMethodInfo is null

src/Adapter/MSTest.TestAdapter/SourceGeneration/SourceGeneratedReflectionOperations.cs

+15-4
Original file line numberDiff line numberDiff line change
@@ -61,22 +61,33 @@ public PropertyInfo[] GetDeclaredProperties(Type type)
6161
=> ReflectionDataProvider.TypeProperties[type];
6262

6363
public PropertyInfo? GetDeclaredProperty(Type type, string propertyName)
64-
=> GetRuntimeProperty(type, propertyName);
64+
=> GetRuntimeProperty(type, propertyName, includeNonPublic: true);
6565

6666
public Type[] GetDefinedTypes(Assembly assembly)
6767
=> ReflectionDataProvider.Types;
6868

6969
public MethodInfo[] GetRuntimeMethods(Type type)
7070
=> ReflectionDataProvider.TypeMethods[type];
7171

72-
public MethodInfo? GetRuntimeMethod(Type declaringType, string methodName, Type[] parameters) => throw new NotImplementedException();
72+
public MethodInfo? GetRuntimeMethod(Type declaringType, string methodName, Type[] parameters, bool includeNonPublic)
73+
{
74+
IEnumerable<MethodInfo> runtimeMethods = GetRuntimeMethods(declaringType)
75+
.Where(
76+
m => m.Name == methodName &&
77+
m.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameters) &&
78+
(includeNonPublic || m.IsPublic));
79+
return runtimeMethods.SingleOrDefault();
80+
}
7381

74-
public PropertyInfo? GetRuntimeProperty(Type classType, string propertyName)
82+
public PropertyInfo? GetRuntimeProperty(Type classType, string propertyName, bool includeNonPublic)
7583
{
7684
Dictionary<string, PropertyInfo> type = ReflectionDataProvider.TypePropertiesByName[classType];
7785

7886
// We as asking for TestContext here, it may not be there.
79-
return type.TryGetValue(propertyName, out PropertyInfo? propertyInfo) ? propertyInfo : null;
87+
PropertyInfo? property = type.TryGetValue(propertyName, out PropertyInfo? propertyInfo) ? propertyInfo : null;
88+
return !includeNonPublic && (property?.GetMethod?.IsPublic == true || property?.SetMethod?.IsPublic == true)
89+
? null
90+
: property;
8091
}
8192

8293
public Type? GetType(string typeName)

src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations2.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ internal interface IReflectionOperations2 : IReflectionOperations
1919

2020
MethodInfo[] GetRuntimeMethods(Type type);
2121

22-
MethodInfo? GetRuntimeMethod(Type declaringType, string methodName, Type[] parameters);
22+
MethodInfo? GetRuntimeMethod(Type declaringType, string methodName, Type[] parameters, bool includeNonPublic);
2323

24-
PropertyInfo? GetRuntimeProperty(Type classType, string propertyName);
24+
PropertyInfo? GetRuntimeProperty(Type classType, string propertyName, bool includeNonPublic);
2525

2626
Type? GetType(string typeName);
2727

src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations2.cs

+9-5
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,15 @@ public Type[] GetDefinedTypes(Assembly assembly)
4545
public MethodInfo[] GetRuntimeMethods(Type type)
4646
=> type.GetMethods(Everything);
4747

48-
public MethodInfo? GetRuntimeMethod(Type declaringType, string methodName, Type[] parameters)
49-
=> declaringType.GetRuntimeMethod(methodName, parameters);
50-
51-
public PropertyInfo? GetRuntimeProperty(Type classType, string testContextPropertyName)
52-
=> classType.GetProperty(testContextPropertyName);
48+
public MethodInfo? GetRuntimeMethod(Type declaringType, string methodName, Type[] parameters, bool includeNonPublic)
49+
=> includeNonPublic
50+
? declaringType.GetMethod(methodName, Everything, null, parameters, null)
51+
: declaringType.GetMethod(methodName, parameters);
52+
53+
public PropertyInfo? GetRuntimeProperty(Type classType, string testContextPropertyName, bool includeNonPublic)
54+
=> includeNonPublic
55+
? classType.GetProperty(testContextPropertyName, Everything)
56+
: classType.GetProperty(testContextPropertyName);
5357

5458
public Type? GetType(string typeName)
5559
=> Type.GetType(typeName);

src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs

+51-8
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,48 @@ private static void AnalyzeAttribute(SymbolAnalysisContext context, AttributeDat
138138
AnalyzeDisplayNameSource(context, attributeData, attributeSyntax, methodSymbol, methodInfoTypeSymbol);
139139
}
140140

141+
private static (ISymbol? Member, bool AreTooMany) TryGetMember(INamedTypeSymbol declaringType, string memberName)
142+
{
143+
INamedTypeSymbol? currentType = declaringType;
144+
while (currentType is not null)
145+
{
146+
(ISymbol? Member, bool AreTooMany) result = TryGetMemberCore(currentType, memberName);
147+
if (result.Member is not null || result.AreTooMany)
148+
{
149+
return result;
150+
}
151+
152+
// Only continue to look at base types if the member is not found on the current type and we are not hit by "too many methods" rule.
153+
currentType = currentType.BaseType;
154+
}
155+
156+
return (null, false);
157+
158+
static (ISymbol? Member, bool AreTooMany) TryGetMemberCore(INamedTypeSymbol declaringType, string memberName)
159+
{
160+
// If we cannot find the member on the given type, report a diagnostic.
161+
if (declaringType.GetMembers(memberName) is { Length: 0 } potentialMembers)
162+
{
163+
return (null, false);
164+
}
165+
166+
ISymbol? potentialProperty = potentialMembers.FirstOrDefault(m => m.Kind == SymbolKind.Property);
167+
if (potentialProperty is not null)
168+
{
169+
return (potentialProperty, false);
170+
}
171+
172+
IEnumerable<ISymbol> candidateMethods = potentialMembers.Where(m => m.Kind == SymbolKind.Method);
173+
if (candidateMethods.Count() > 1)
174+
{
175+
// If there are multiple methods with the same name, report a diagnostic. This is not a supported scenario.
176+
return (null, true);
177+
}
178+
179+
return (candidateMethods.FirstOrDefault() ?? potentialMembers[0], false);
180+
}
181+
}
182+
141183
private static void AnalyzeDataSource(SymbolAnalysisContext context, AttributeData attributeData, SyntaxNode attributeSyntax,
142184
IMethodSymbol methodSymbol, INamedTypeSymbol dynamicDataSourceTypeSymbol, INamedTypeSymbol ienumerableTypeSymbol)
143185
{
@@ -173,22 +215,23 @@ private static void AnalyzeDataSource(SymbolAnalysisContext context, AttributeDa
173215
return;
174216
}
175217

176-
// If we cannot find the member on the given type, report a diagnostic.
177-
if (declaringType.GetMembers(memberName) is { Length: 0 } potentialMembers)
218+
(ISymbol? member, bool areTooMany) = TryGetMember(declaringType, memberName);
219+
220+
if (areTooMany)
178221
{
179-
context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberNotFoundRule, declaringType.Name, memberName));
222+
// If there are multiple methods with the same name and all are parameterless, report a diagnostic. This is not a supported scenario.
223+
// Note: This is likely to happen only when they differ in arity (for example, one is non-generic and the other is generic).
224+
context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(FoundTooManyMembersRule, declaringType.Name, memberName));
180225
return;
181226
}
182227

183-
// If there are multiple members with the same name, report a diagnostic. This is not a supported scenario.
184-
if (potentialMembers.Length > 1)
228+
if (member is null)
185229
{
186-
context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(FoundTooManyMembersRule, declaringType.Name, memberName));
230+
// If we cannot find the member on the given type, report a diagnostic.
231+
context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberNotFoundRule, declaringType.Name, memberName));
187232
return;
188233
}
189234

190-
ISymbol member = potentialMembers[0];
191-
192235
switch (member.Kind)
193236
{
194237
case SymbolKind.Property:

test/IntegrationTests/MSTest.IntegrationTests/Parameterized tests/DynamicDataTests.cs

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System.Collections.Immutable;
@@ -25,14 +25,22 @@ public void ExecuteDynamicDataTests()
2525
VerifyE2E.TestsPassed(
2626
testResults,
2727
"DynamicDataTest_SourceProperty (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)",
28+
"DynamicDataTest_SourcePropertyFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)",
29+
"DynamicDataTest_SourcePropertyShadowingBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)",
2830
"DynamicDataTest_SourcePropertyAuto (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)",
31+
"DynamicDataTest_SourcePropertyAutoFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)",
32+
"DynamicDataTest_SourcePropertyAutoShadowingBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)",
2933
"Custom DynamicDataTestMethod DynamicDataTest_SourcePropertyOtherType_CustomDisplayName with 2 parameters",
3034
"Custom DynamicDataTestMethod DynamicDataTest_SourceMethodOtherType_CustomDisplayName with 2 parameters",
3135
"UserDynamicDataTestMethod DynamicDataTest_SourcePropertyOtherType_CustomDisplayNameOtherType with 2 parameters",
3236
"Custom DynamicDataTestMethod DynamicDataTest_SourceMethod_CustomDisplayName with 2 parameters",
3337
"UserDynamicDataTestMethod DynamicDataTest_SourceMethodOtherType_CustomDisplayNameOtherType with 2 parameters",
3438
"DynamicDataTest_SourceMethod (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)",
39+
"DynamicDataTest_SourceMethodFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)",
40+
"DynamicDataTest_SourceMethodShadowingBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)",
3541
"DynamicDataTest_SourceMethodAuto (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)",
42+
"DynamicDataTest_SourceMethodAutoFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)",
43+
"DynamicDataTest_SourceMethodAutoShadowingBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)",
3644
"UserDynamicDataTestMethod DynamicDataTest_SourcePropertyOtherType_CustomDisplayNameOtherType with 2 parameters",
3745
"StackOverflowException_Example (DataSourceTestProject.DynamicDataTests+ExampleTestCase)",
3846
"Custom DynamicDataTestMethod DynamicDataTest_SourceProperty_CustomDisplayName with 2 parameters",
@@ -48,11 +56,19 @@ public void ExecuteDynamicDataTests()
4856
"UserDynamicDataTestMethod DynamicDataTest_SourceProperty_CustomDisplayNameOtherType with 2 parameters",
4957
"UserDynamicDataTestMethod DynamicDataTest_SourceProperty_CustomDisplayNameOtherType with 2 parameters",
5058
"DynamicDataTest_SourceMethod (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)",
59+
"DynamicDataTest_SourceMethodFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)",
60+
"DynamicDataTest_SourceMethodShadowingBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)",
5161
"DynamicDataTest_SourceMethodAuto (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)",
62+
"DynamicDataTest_SourceMethodAutoFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)",
63+
"DynamicDataTest_SourceMethodAutoShadowingBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)",
5264
"Custom DynamicDataTestMethod DynamicDataTest_SourcePropertyOtherType_CustomDisplayName with 2 parameters",
5365
"UserDynamicDataTestMethod DynamicDataTest_SourceMethodOtherType_CustomDisplayNameOtherType with 2 parameters",
5466
"DynamicDataTest_SourceProperty (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)",
67+
"DynamicDataTest_SourcePropertyFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)",
68+
"DynamicDataTest_SourcePropertyShadowingBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)",
5569
"DynamicDataTest_SourcePropertyAuto (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)",
70+
"DynamicDataTest_SourcePropertyAutoFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)",
71+
"DynamicDataTest_SourcePropertyAutoShadowingBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)",
5672
"DynamicDataTest_SourcePropertyOtherType (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)",
5773
"Custom DynamicDataTestMethod DynamicDataTest_SourceMethod_CustomDisplayName with 2 parameters",
5874
"MethodWithOverload (\"1\",1)",

0 commit comments

Comments
 (0)