From e87b09eae5fb44e0513772d9467e36db6f538b9a Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Tue, 31 Dec 2024 07:37:10 +0100 Subject: [PATCH 1/8] Allow DynamicData source to be on base types --- .../DynamicDataOperations.cs | 8 +++---- .../DynamicDataShouldBeValidAnalyzer.cs | 21 ++++++++++++++----- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs b/src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs index d78223016c..4d5fb24459 100644 --- a/src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs +++ b/src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs @@ -19,11 +19,11 @@ public IEnumerable GetData(Type? _dynamicDataDeclaringType, DynamicDat { case DynamicDataSourceType.AutoDetect: #pragma warning disable IDE0045 // Convert to conditional expression - it becomes less readable. - if (PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredProperty(_dynamicDataDeclaringType, _dynamicDataSourceName) is { } dynamicDataPropertyInfo) + if (PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeProperty(_dynamicDataDeclaringType, _dynamicDataSourceName) is { } dynamicDataPropertyInfo) { obj = GetDataFromProperty(dynamicDataPropertyInfo); } - else if (PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredMethod(_dynamicDataDeclaringType, _dynamicDataSourceName) is { } dynamicDataMethodInfo) + else if (PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethod(_dynamicDataDeclaringType, _dynamicDataSourceName, parameters: []) is { } dynamicDataMethodInfo) { obj = GetDataFromMethod(dynamicDataMethodInfo); } @@ -35,14 +35,14 @@ public IEnumerable GetData(Type? _dynamicDataDeclaringType, DynamicDat break; case DynamicDataSourceType.Property: - PropertyInfo property = PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredProperty(_dynamicDataDeclaringType, _dynamicDataSourceName) + PropertyInfo property = PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeProperty(_dynamicDataDeclaringType, _dynamicDataSourceName) ?? throw new ArgumentNullException($"{DynamicDataSourceType.Property} {_dynamicDataSourceName}"); obj = GetDataFromProperty(property); break; case DynamicDataSourceType.Method: - MethodInfo method = PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredMethod(_dynamicDataDeclaringType, _dynamicDataSourceName) + MethodInfo method = PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethod(_dynamicDataDeclaringType, _dynamicDataSourceName, parameters: []) ?? throw new ArgumentNullException($"{DynamicDataSourceType.Method} {_dynamicDataSourceName}"); obj = GetDataFromMethod(method); diff --git a/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs index acc5aeab60..32820e1ec6 100644 --- a/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs +++ b/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs @@ -180,14 +180,25 @@ private static void AnalyzeDataSource(SymbolAnalysisContext context, AttributeDa return; } - // If there are multiple members with the same name, report a diagnostic. This is not a supported scenario. - if (potentialMembers.Length > 1) + ISymbol? potentialProperty = potentialMembers.FirstOrDefault(m => m.Kind == SymbolKind.Property); + ISymbol member; + if (potentialProperty is not null) { - context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(FoundTooManyMembersRule, declaringType.Name, memberName)); - return; + member = potentialProperty; } + else + { + IEnumerable candidateMethods = potentialMembers.OfType().Where(m => m.Parameters.Length == 0); + if (candidateMethods.Count() > 1) + { + // If there are multiple methods with the same name and all are parameterless, report a diagnostic. This is not a supported scenario. + // Note: This is likely to happen only when they differ in arity (for example, one is non-generic and the other is generic). + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(FoundTooManyMembersRule, declaringType.Name, memberName)); + return; + } - ISymbol member = potentialMembers[0]; + member = candidateMethods.FirstOrDefault() ?? potentialMembers[0]; + } switch (member.Kind) { From 04f13bd85f298e1a815fd8846432184562a2b910 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Tue, 31 Dec 2024 08:51:38 +0100 Subject: [PATCH 2/8] Allow GetRuntimeProperty/GetRuntimeMethod to return non-public --- .../DynamicDataOperations.cs | 8 ++++---- .../MSTest.TestAdapter/Execution/TypeCache.cs | 5 +++-- .../SourceGeneratedReflectionOperations.cs | 19 +++++++++++++++---- .../Interfaces/IReflectionOperations2.cs | 4 ++-- .../Services/ReflectionOperations2.cs | 14 +++++++++----- 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs b/src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs index 4d5fb24459..946c48d73f 100644 --- a/src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs +++ b/src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs @@ -19,11 +19,11 @@ public IEnumerable GetData(Type? _dynamicDataDeclaringType, DynamicDat { case DynamicDataSourceType.AutoDetect: #pragma warning disable IDE0045 // Convert to conditional expression - it becomes less readable. - if (PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeProperty(_dynamicDataDeclaringType, _dynamicDataSourceName) is { } dynamicDataPropertyInfo) + if (PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeProperty(_dynamicDataDeclaringType, _dynamicDataSourceName, includeNonPublic: true) is { } dynamicDataPropertyInfo) { obj = GetDataFromProperty(dynamicDataPropertyInfo); } - else if (PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethod(_dynamicDataDeclaringType, _dynamicDataSourceName, parameters: []) is { } dynamicDataMethodInfo) + else if (PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethod(_dynamicDataDeclaringType, _dynamicDataSourceName, parameters: [], includeNonPublic: true) is { } dynamicDataMethodInfo) { obj = GetDataFromMethod(dynamicDataMethodInfo); } @@ -35,14 +35,14 @@ public IEnumerable GetData(Type? _dynamicDataDeclaringType, DynamicDat break; case DynamicDataSourceType.Property: - PropertyInfo property = PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeProperty(_dynamicDataDeclaringType, _dynamicDataSourceName) + PropertyInfo property = PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeProperty(_dynamicDataDeclaringType, _dynamicDataSourceName, includeNonPublic: true) ?? throw new ArgumentNullException($"{DynamicDataSourceType.Property} {_dynamicDataSourceName}"); obj = GetDataFromProperty(property); break; case DynamicDataSourceType.Method: - MethodInfo method = PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethod(_dynamicDataDeclaringType, _dynamicDataSourceName, parameters: []) + MethodInfo method = PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethod(_dynamicDataDeclaringType, _dynamicDataSourceName, parameters: [], includeNonPublic: true) ?? throw new ArgumentNullException($"{DynamicDataSourceType.Method} {_dynamicDataSourceName}"); obj = GetDataFromMethod(method); diff --git a/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs b/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs index 5e76ac275a..66f0f1d5c5 100644 --- a/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs +++ b/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs @@ -370,7 +370,7 @@ private TestClassInfo CreateClassInfo(Type classType, TestMethod testMethod) { try { - PropertyInfo? testContextProperty = PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeProperty(classType, TestContextPropertyName); + PropertyInfo? testContextProperty = PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeProperty(classType, TestContextPropertyName, includeNonPublic: false); if (testContextProperty == null) { // that's okay may be the property was not defined @@ -810,7 +810,8 @@ private MethodInfo GetMethodInfoForTestMethod(TestMethod testMethod, TestClassIn else if (methodBase != null) { Type[] parameters = methodBase.GetParameters().Select(i => i.ParameterType).ToArray(); - testMethodInfo = PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethod(methodBase.DeclaringType!, methodBase.Name, parameters); + // TODO: Should we pass true for includeNonPublic? + testMethodInfo = PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethod(methodBase.DeclaringType!, methodBase.Name, parameters, includeNonPublic: false); } return testMethodInfo is null diff --git a/src/Adapter/MSTest.TestAdapter/SourceGeneration/SourceGeneratedReflectionOperations.cs b/src/Adapter/MSTest.TestAdapter/SourceGeneration/SourceGeneratedReflectionOperations.cs index 16bd0f9454..d66a4db69a 100644 --- a/src/Adapter/MSTest.TestAdapter/SourceGeneration/SourceGeneratedReflectionOperations.cs +++ b/src/Adapter/MSTest.TestAdapter/SourceGeneration/SourceGeneratedReflectionOperations.cs @@ -61,7 +61,7 @@ public PropertyInfo[] GetDeclaredProperties(Type type) => ReflectionDataProvider.TypeProperties[type]; public PropertyInfo? GetDeclaredProperty(Type type, string propertyName) - => GetRuntimeProperty(type, propertyName); + => GetRuntimeProperty(type, propertyName, includeNonPublic: true); public Type[] GetDefinedTypes(Assembly assembly) => ReflectionDataProvider.Types; @@ -69,14 +69,25 @@ public Type[] GetDefinedTypes(Assembly assembly) public MethodInfo[] GetRuntimeMethods(Type type) => ReflectionDataProvider.TypeMethods[type]; - public MethodInfo? GetRuntimeMethod(Type declaringType, string methodName, Type[] parameters) => throw new NotImplementedException(); + public MethodInfo? GetRuntimeMethod(Type declaringType, string methodName, Type[] parameters, bool includeNonPublic) + { + IEnumerable runtimeMethods = GetRuntimeMethods(declaringType) + .Where( + m => m.Name == methodName && + m.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameters) && + (includeNonPublic || m.IsPublic)); + return runtimeMethods.SingleOrDefault(); + } - public PropertyInfo? GetRuntimeProperty(Type classType, string propertyName) + public PropertyInfo? GetRuntimeProperty(Type classType, string propertyName, bool includeNonPublic) { Dictionary type = ReflectionDataProvider.TypePropertiesByName[classType]; // We as asking for TestContext here, it may not be there. - return type.TryGetValue(propertyName, out PropertyInfo? propertyInfo) ? propertyInfo : null; + PropertyInfo? property = type.TryGetValue(propertyName, out PropertyInfo? propertyInfo) ? propertyInfo : null; + return !includeNonPublic && (property?.GetMethod?.IsPublic == true || property?.SetMethod?.IsPublic == true) + ? null + : property; } public Type? GetType(string typeName) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations2.cs b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations2.cs index 0036e00cbb..f7fc969270 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations2.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations2.cs @@ -19,9 +19,9 @@ internal interface IReflectionOperations2 : IReflectionOperations MethodInfo[] GetRuntimeMethods(Type type); - MethodInfo? GetRuntimeMethod(Type declaringType, string methodName, Type[] parameters); + MethodInfo? GetRuntimeMethod(Type declaringType, string methodName, Type[] parameters, bool includeNonPublic); - PropertyInfo? GetRuntimeProperty(Type classType, string propertyName); + PropertyInfo? GetRuntimeProperty(Type classType, string propertyName, bool includeNonPublic); Type? GetType(string typeName); diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations2.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations2.cs index acce42de99..98d475b99b 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations2.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations2.cs @@ -45,11 +45,15 @@ public Type[] GetDefinedTypes(Assembly assembly) public MethodInfo[] GetRuntimeMethods(Type type) => type.GetMethods(Everything); - public MethodInfo? GetRuntimeMethod(Type declaringType, string methodName, Type[] parameters) - => declaringType.GetRuntimeMethod(methodName, parameters); - - public PropertyInfo? GetRuntimeProperty(Type classType, string testContextPropertyName) - => classType.GetProperty(testContextPropertyName); + public MethodInfo? GetRuntimeMethod(Type declaringType, string methodName, Type[] parameters, bool includeNonPublic) + => includeNonPublic + ? declaringType.GetMethod(methodName, Everything, null, parameters, null) + : declaringType.GetMethod(methodName, parameters); + + public PropertyInfo? GetRuntimeProperty(Type classType, string testContextPropertyName, bool includeNonPublic) + => includeNonPublic + ? classType.GetProperty(testContextPropertyName, Everything) + : classType.GetProperty(testContextPropertyName); public Type? GetType(string typeName) => Type.GetType(typeName); From 852c69f1141b77c5fd0e6c302d7ee26ed249df12 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Tue, 31 Dec 2024 09:45:01 +0100 Subject: [PATCH 3/8] Adjust analyzer test for the new behavior --- .../DynamicDataShouldBeValidAnalyzerTests.cs | 64 ++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs index d57bf15a77..238ec58742 100644 --- a/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs @@ -620,13 +620,13 @@ public void TestMethod6(object[] o) } public static IEnumerable GetData() => new List(); - public static IEnumerable GetData(int i) => new List(); + public static IEnumerable GetData() => new List(); } public class SomeClass { public static IEnumerable GetSomeData() => new List(); - public static IEnumerable GetSomeData(int i) => new List(); + public static IEnumerable GetSomeData() => new List(); } """; @@ -640,6 +640,66 @@ await VerifyCS.VerifyAnalyzerAsync( VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.FoundTooManyMembersRule).WithLocation(5).WithArguments("MyTestClass", "GetData")); } + [TestMethod] + public async Task WhenDataSourceMemberFoundMultipleTimesDifferentParameters_NoDiagnostic() + { + string code = """ + using System.Collections.Generic; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [DynamicData("GetData", DynamicDataSourceType.Method)] + [TestMethod] + public void TestMethod1(object[] o) + { + } + + [DynamicData("GetSomeData", typeof(SomeClass), DynamicDataSourceType.Method)] + [TestMethod] + public void TestMethod2(object[] o) + { + } + + [DynamicData(dynamicDataSourceType: DynamicDataSourceType.Method, dynamicDataSourceName: "GetData")] + [TestMethod] + public void TestMethod3(object[] o) + { + } + + [DynamicData(dynamicDataDeclaringType: typeof(SomeClass), dynamicDataSourceType: DynamicDataSourceType.Method, dynamicDataSourceName: "GetSomeData")] + [TestMethod] + public void TestMethod4(object[] o) + { + } + + [DynamicData("GetData", DynamicDataSourceType.AutoDetect)] + [TestMethod] + public void TestMethod5(object[] o) + { + } + + [DynamicData("GetData")] + [TestMethod] + public void TestMethod6(object[] o) + { + } + + public static IEnumerable GetData() => new List(); + public static IEnumerable GetData(int i) => new List(); + } + + public class SomeClass + { + public static IEnumerable GetSomeData() => new List(); + public static IEnumerable GetSomeData(int i) => new List(); + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + [TestMethod] public async Task WhenMemberKindIsMixedUp_Diagnostic() { From bd8ed31dc553e7fa92013bf8766c4eb230d13796 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Tue, 31 Dec 2024 10:44:42 +0100 Subject: [PATCH 4/8] Add tests --- .../Parameterized tests/DynamicDataTests.cs | 10 +- .../Parameterized tests/DynamicDataTests.cs | 8 ++ .../DynamicDataTests.cs | 119 +++++++++++------- 3 files changed, 88 insertions(+), 49 deletions(-) diff --git a/test/IntegrationTests/MSTest.IntegrationTests/Parameterized tests/DynamicDataTests.cs b/test/IntegrationTests/MSTest.IntegrationTests/Parameterized tests/DynamicDataTests.cs index 44dc75da83..e9788fe5b2 100644 --- a/test/IntegrationTests/MSTest.IntegrationTests/Parameterized tests/DynamicDataTests.cs +++ b/test/IntegrationTests/MSTest.IntegrationTests/Parameterized tests/DynamicDataTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Immutable; @@ -25,14 +25,18 @@ public void ExecuteDynamicDataTests() VerifyE2E.TestsPassed( testResults, "DynamicDataTest_SourceProperty (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourcePropertyFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyAuto (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourcePropertyAutoFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "Custom DynamicDataTestMethod DynamicDataTest_SourcePropertyOtherType_CustomDisplayName with 2 parameters", "Custom DynamicDataTestMethod DynamicDataTest_SourceMethodOtherType_CustomDisplayName with 2 parameters", "UserDynamicDataTestMethod DynamicDataTest_SourcePropertyOtherType_CustomDisplayNameOtherType with 2 parameters", "Custom DynamicDataTestMethod DynamicDataTest_SourceMethod_CustomDisplayName with 2 parameters", "UserDynamicDataTestMethod DynamicDataTest_SourceMethodOtherType_CustomDisplayNameOtherType with 2 parameters", "DynamicDataTest_SourceMethod (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourceMethodFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethodAuto (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourceMethodAutoFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "UserDynamicDataTestMethod DynamicDataTest_SourcePropertyOtherType_CustomDisplayNameOtherType with 2 parameters", "StackOverflowException_Example (DataSourceTestProject.DynamicDataTests+ExampleTestCase)", "Custom DynamicDataTestMethod DynamicDataTest_SourceProperty_CustomDisplayName with 2 parameters", @@ -48,11 +52,15 @@ public void ExecuteDynamicDataTests() "UserDynamicDataTestMethod DynamicDataTest_SourceProperty_CustomDisplayNameOtherType with 2 parameters", "UserDynamicDataTestMethod DynamicDataTest_SourceProperty_CustomDisplayNameOtherType with 2 parameters", "DynamicDataTest_SourceMethod (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourceMethodFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethodAuto (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourceMethodAutoFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "Custom DynamicDataTestMethod DynamicDataTest_SourcePropertyOtherType_CustomDisplayName with 2 parameters", "UserDynamicDataTestMethod DynamicDataTest_SourceMethodOtherType_CustomDisplayNameOtherType with 2 parameters", "DynamicDataTest_SourceProperty (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourcePropertyFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyAuto (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourcePropertyAutoFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyOtherType (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "Custom DynamicDataTestMethod DynamicDataTest_SourceMethod_CustomDisplayName with 2 parameters", "MethodWithOverload (\"1\",1)", diff --git a/test/IntegrationTests/MSTest.VstestConsoleWrapper.IntegrationTests/Parameterized tests/DynamicDataTests.cs b/test/IntegrationTests/MSTest.VstestConsoleWrapper.IntegrationTests/Parameterized tests/DynamicDataTests.cs index d04d431f5a..53858b2fcd 100644 --- a/test/IntegrationTests/MSTest.VstestConsoleWrapper.IntegrationTests/Parameterized tests/DynamicDataTests.cs +++ b/test/IntegrationTests/MSTest.VstestConsoleWrapper.IntegrationTests/Parameterized tests/DynamicDataTests.cs @@ -20,12 +20,20 @@ public void ExecuteDynamicDataTests() ValidatePassedTests( "DynamicDataTest_SourceMethod (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethod (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourceMethodFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourceMethodFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethodAuto (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethodAuto (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourceMethodAutoFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourceMethodAutoFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceProperty (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceProperty (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourcePropertyFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourcePropertyFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyAuto (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyAuto (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourcePropertyAutoFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourcePropertyAutoFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "Custom DynamicDataTestMethod DynamicDataTest_SourceMethod_CustomDisplayName with 2 parameters", "Custom DynamicDataTestMethod DynamicDataTest_SourceMethod_CustomDisplayName with 2 parameters", "Custom DynamicDataTestMethod DynamicDataTest_SourceProperty_CustomDisplayName with 2 parameters", diff --git a/test/IntegrationTests/TestAssets/DynamicDataTestProject/DynamicDataTests.cs b/test/IntegrationTests/TestAssets/DynamicDataTestProject/DynamicDataTests.cs index 88668e8b42..daf6346c1f 100644 --- a/test/IntegrationTests/TestAssets/DynamicDataTestProject/DynamicDataTests.cs +++ b/test/IntegrationTests/TestAssets/DynamicDataTestProject/DynamicDataTests.cs @@ -12,25 +12,93 @@ namespace DataSourceTestProject; +public abstract class DynamicDataTestsBase +{ + public static IEnumerable GetDataFromBase() + { + yield return + [ + "John;Doe", + new User() + { + FirstName = "John", + LastName = "Doe", + } + ]; + + yield return + [ + "Jane;Doe", + new User() + { + FirstName = "Jane", + LastName = "Doe", + } + ]; + } + + public static IEnumerable DataFromBase + { + get + { + yield return + [ + "John;Doe", + new User() + { + FirstName = "John", + LastName = "Doe", + } + ]; + + yield return + [ + "Jane;Doe", + new User() + { + FirstName = "Jane", + LastName = "Doe", + } + ]; + } + } +} + [TestClass] -public class DynamicDataTests +public class DynamicDataTests : DynamicDataTestsBase { [DataTestMethod] [DynamicData(nameof(GetParseUserData), DynamicDataSourceType.Method)] public void DynamicDataTest_SourceMethod(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); + [DataTestMethod] + [DynamicData(nameof(GetDataFromBase), DynamicDataSourceType.Method)] + public void DynamicDataTest_SourceMethodFromBase(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); + [DataTestMethod] [DynamicData(nameof(GetParseUserData))] public void DynamicDataTest_SourceMethodAuto(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); + [DataTestMethod] + [DynamicData(nameof(GetDataFromBase))] + public void DynamicDataTest_SourceMethodAutoFromBase(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); + [DataTestMethod] [DynamicData(nameof(ParseUserData), DynamicDataSourceType.Property)] public void DynamicDataTest_SourceProperty(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); + [DataTestMethod] + [DynamicData(nameof(DataFromBase), DynamicDataSourceType.Property)] + public void DynamicDataTest_SourcePropertyFromBase(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); + [DataTestMethod] [DynamicData(nameof(ParseUserData))] public void DynamicDataTest_SourcePropertyAuto(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); + [DataTestMethod] + [DynamicData(nameof(DataFromBase))] + public void DynamicDataTest_SourcePropertyAutoFromBase(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); + [DataTestMethod] [DynamicData(nameof(GetParseUserData), DynamicDataSourceType.Method, DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))] @@ -117,54 +185,9 @@ private static void ParseAndAssert(string userData, User expectedUser) Assert.AreEqual(user.LastName, expectedUser.LastName); } - public static IEnumerable GetParseUserData() - { - yield return - [ - "John;Doe", - new User() - { - FirstName = "John", - LastName = "Doe", - } - ]; + public static IEnumerable GetParseUserData() => GetDataFromBase(); - yield return - [ - "Jane;Doe", - new User() - { - FirstName = "Jane", - LastName = "Doe", - } - ]; - } - - public static IEnumerable ParseUserData - { - get - { - yield return - [ - "John;Doe", - new User() - { - FirstName = "John", - LastName = "Doe", - } - ]; - - yield return - [ - "Jane;Doe", - new User() - { - FirstName = "Jane", - LastName = "Doe", - } - ]; - } - } + public static IEnumerable ParseUserData => DataFromBase; public static string GetCustomDynamicDataDisplayName(MethodInfo methodInfo, object[] data) => $"Custom DynamicDataTestMethod {methodInfo.Name} with {data.Length} parameters"; From eca506c9c291d4490f554bd59d0f6e6381019296 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Tue, 31 Dec 2024 11:01:34 +0100 Subject: [PATCH 5/8] Shadowing test --- .../Parameterized tests/DynamicDataTests.cs | 8 +++++++ .../Parameterized tests/DynamicDataTests.cs | 8 +++++++ .../DynamicDataTests.cs | 24 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/test/IntegrationTests/MSTest.IntegrationTests/Parameterized tests/DynamicDataTests.cs b/test/IntegrationTests/MSTest.IntegrationTests/Parameterized tests/DynamicDataTests.cs index e9788fe5b2..06d74ed0c7 100644 --- a/test/IntegrationTests/MSTest.IntegrationTests/Parameterized tests/DynamicDataTests.cs +++ b/test/IntegrationTests/MSTest.IntegrationTests/Parameterized tests/DynamicDataTests.cs @@ -26,8 +26,10 @@ public void ExecuteDynamicDataTests() testResults, "DynamicDataTest_SourceProperty (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourcePropertyShadowingBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyAuto (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyAutoFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourcePropertyAutoShadowingBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "Custom DynamicDataTestMethod DynamicDataTest_SourcePropertyOtherType_CustomDisplayName with 2 parameters", "Custom DynamicDataTestMethod DynamicDataTest_SourceMethodOtherType_CustomDisplayName with 2 parameters", "UserDynamicDataTestMethod DynamicDataTest_SourcePropertyOtherType_CustomDisplayNameOtherType with 2 parameters", @@ -35,8 +37,10 @@ public void ExecuteDynamicDataTests() "UserDynamicDataTestMethod DynamicDataTest_SourceMethodOtherType_CustomDisplayNameOtherType with 2 parameters", "DynamicDataTest_SourceMethod (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethodFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourceMethodShadowingBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethodAuto (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethodAutoFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourceMethodAutoShadowingBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "UserDynamicDataTestMethod DynamicDataTest_SourcePropertyOtherType_CustomDisplayNameOtherType with 2 parameters", "StackOverflowException_Example (DataSourceTestProject.DynamicDataTests+ExampleTestCase)", "Custom DynamicDataTestMethod DynamicDataTest_SourceProperty_CustomDisplayName with 2 parameters", @@ -53,14 +57,18 @@ public void ExecuteDynamicDataTests() "UserDynamicDataTestMethod DynamicDataTest_SourceProperty_CustomDisplayNameOtherType with 2 parameters", "DynamicDataTest_SourceMethod (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethodFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourceMethodShadowingBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethodAuto (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethodAutoFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourceMethodAutoShadowingBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "Custom DynamicDataTestMethod DynamicDataTest_SourcePropertyOtherType_CustomDisplayName with 2 parameters", "UserDynamicDataTestMethod DynamicDataTest_SourceMethodOtherType_CustomDisplayNameOtherType with 2 parameters", "DynamicDataTest_SourceProperty (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourcePropertyShadowingBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyAuto (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyAutoFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourcePropertyAutoShadowingBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyOtherType (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "Custom DynamicDataTestMethod DynamicDataTest_SourceMethod_CustomDisplayName with 2 parameters", "MethodWithOverload (\"1\",1)", diff --git a/test/IntegrationTests/MSTest.VstestConsoleWrapper.IntegrationTests/Parameterized tests/DynamicDataTests.cs b/test/IntegrationTests/MSTest.VstestConsoleWrapper.IntegrationTests/Parameterized tests/DynamicDataTests.cs index 53858b2fcd..ab9083a04b 100644 --- a/test/IntegrationTests/MSTest.VstestConsoleWrapper.IntegrationTests/Parameterized tests/DynamicDataTests.cs +++ b/test/IntegrationTests/MSTest.VstestConsoleWrapper.IntegrationTests/Parameterized tests/DynamicDataTests.cs @@ -22,18 +22,26 @@ public void ExecuteDynamicDataTests() "DynamicDataTest_SourceMethod (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethodFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethodFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourceMethodShadowingBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourceMethodShadowingBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethodAuto (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethodAuto (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethodAutoFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceMethodAutoFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourceMethodAutoShadowingBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourceMethodAutoShadowingBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceProperty (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourceProperty (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourcePropertyShadowingBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourcePropertyShadowingBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyAuto (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyAuto (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyAutoFromBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", "DynamicDataTest_SourcePropertyAutoFromBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourcePropertyAutoShadowingBase (\"John;Doe\",LibProjectReferencedByDataSourceTest.User)", + "DynamicDataTest_SourcePropertyAutoShadowingBase (\"Jane;Doe\",LibProjectReferencedByDataSourceTest.User)", "Custom DynamicDataTestMethod DynamicDataTest_SourceMethod_CustomDisplayName with 2 parameters", "Custom DynamicDataTestMethod DynamicDataTest_SourceMethod_CustomDisplayName with 2 parameters", "Custom DynamicDataTestMethod DynamicDataTest_SourceProperty_CustomDisplayName with 2 parameters", diff --git a/test/IntegrationTests/TestAssets/DynamicDataTestProject/DynamicDataTests.cs b/test/IntegrationTests/TestAssets/DynamicDataTestProject/DynamicDataTests.cs index daf6346c1f..2bd7372e62 100644 --- a/test/IntegrationTests/TestAssets/DynamicDataTestProject/DynamicDataTests.cs +++ b/test/IntegrationTests/TestAssets/DynamicDataTestProject/DynamicDataTests.cs @@ -62,6 +62,10 @@ public static IEnumerable DataFromBase ]; } } + + public static IEnumerable DataShadowingBase => throw new NotImplementedException(); + + public static IEnumerable GetDataShadowingBase() => throw new NotImplementedException(); } [TestClass] @@ -75,6 +79,10 @@ public class DynamicDataTests : DynamicDataTestsBase [DynamicData(nameof(GetDataFromBase), DynamicDataSourceType.Method)] public void DynamicDataTest_SourceMethodFromBase(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); + [DataTestMethod] + [DynamicData(nameof(GetDataShadowingBase), DynamicDataSourceType.Method)] + public void DynamicDataTest_SourceMethodShadowingBase(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); + [DataTestMethod] [DynamicData(nameof(GetParseUserData))] public void DynamicDataTest_SourceMethodAuto(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); @@ -83,6 +91,10 @@ public class DynamicDataTests : DynamicDataTestsBase [DynamicData(nameof(GetDataFromBase))] public void DynamicDataTest_SourceMethodAutoFromBase(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); + [DataTestMethod] + [DynamicData(nameof(GetDataShadowingBase))] + public void DynamicDataTest_SourceMethodAutoShadowingBase(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); + [DataTestMethod] [DynamicData(nameof(ParseUserData), DynamicDataSourceType.Property)] public void DynamicDataTest_SourceProperty(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); @@ -91,6 +103,10 @@ public class DynamicDataTests : DynamicDataTestsBase [DynamicData(nameof(DataFromBase), DynamicDataSourceType.Property)] public void DynamicDataTest_SourcePropertyFromBase(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); + [DataTestMethod] + [DynamicData(nameof(DataShadowingBase), DynamicDataSourceType.Property)] + public void DynamicDataTest_SourcePropertyShadowingBase(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); + [DataTestMethod] [DynamicData(nameof(ParseUserData))] public void DynamicDataTest_SourcePropertyAuto(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); @@ -99,6 +115,10 @@ public class DynamicDataTests : DynamicDataTestsBase [DynamicData(nameof(DataFromBase))] public void DynamicDataTest_SourcePropertyAutoFromBase(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); + [DataTestMethod] + [DynamicData(nameof(DataShadowingBase))] + public void DynamicDataTest_SourcePropertyAutoShadowingBase(string userData, User expectedUser) => ParseAndAssert(userData, expectedUser); + [DataTestMethod] [DynamicData(nameof(GetParseUserData), DynamicDataSourceType.Method, DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))] @@ -185,10 +205,14 @@ private static void ParseAndAssert(string userData, User expectedUser) Assert.AreEqual(user.LastName, expectedUser.LastName); } + public static new IEnumerable GetDataShadowingBase() => GetDataFromBase(); + public static IEnumerable GetParseUserData() => GetDataFromBase(); public static IEnumerable ParseUserData => DataFromBase; + public static new IEnumerable DataShadowingBase => DataFromBase; + public static string GetCustomDynamicDataDisplayName(MethodInfo methodInfo, object[] data) => $"Custom DynamicDataTestMethod {methodInfo.Name} with {data.Length} parameters"; From 469ef2f48c39e16ee80f0ff860f447aa2b26e2db Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Tue, 31 Dec 2024 13:28:59 +0100 Subject: [PATCH 6/8] Analyzer progress --- .../DynamicDataShouldBeValidAnalyzer.cs | 73 ++++++++++++++----- .../DynamicDataShouldBeValidAnalyzerTests.cs | 71 ++++++++++++++++++ 2 files changed, 124 insertions(+), 20 deletions(-) diff --git a/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs index 32820e1ec6..28d0143e3b 100644 --- a/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs +++ b/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs @@ -138,6 +138,49 @@ private static void AnalyzeAttribute(SymbolAnalysisContext context, AttributeDat AnalyzeDisplayNameSource(context, attributeData, attributeSyntax, methodSymbol, methodInfoTypeSymbol); } + private static (ISymbol? Member, bool AreTooMany) TryGetMember(INamedTypeSymbol declaringType, string memberName) + { + INamedTypeSymbol? currentType = declaringType; + while (currentType is not null) + { + (ISymbol? Member, bool AreTooMany) result = TryGetMemberCore(currentType, memberName); + if (result.Member is not null || result.AreTooMany) + { + return result; + } + + // 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. + currentType = currentType.BaseType; + } + + return (null, false); + + static (ISymbol? Member, bool AreTooMany) TryGetMemberCore(INamedTypeSymbol declaringType, string memberName) + { + // If we cannot find the member on the given type, report a diagnostic. + if (declaringType.GetMembers(memberName) is { Length: 0 } potentialMembers) + { + return (null, false); + } + + ISymbol? potentialProperty = potentialMembers.FirstOrDefault(m => m.Kind == SymbolKind.Property); + if (potentialProperty is not null) + { + return (potentialProperty, false); + } + + IEnumerable candidateMethods = potentialMembers.OfType().Where(m => m.Parameters.Length == 0); + if (candidateMethods.Count() > 1) + { + // If there are multiple methods with the same name and all are parameterless, report a diagnostic. This is not a supported scenario. + // Note: This is likely to happen only when they differ in arity (for example, one is non-generic and the other is generic). + return (null, true); + } + + return (candidateMethods.FirstOrDefault() ?? potentialMembers[0], false); + } + } + private static void AnalyzeDataSource(SymbolAnalysisContext context, AttributeData attributeData, SyntaxNode attributeSyntax, IMethodSymbol methodSymbol, INamedTypeSymbol dynamicDataSourceTypeSymbol, INamedTypeSymbol ienumerableTypeSymbol) { @@ -173,31 +216,21 @@ private static void AnalyzeDataSource(SymbolAnalysisContext context, AttributeDa return; } - // If we cannot find the member on the given type, report a diagnostic. - if (declaringType.GetMembers(memberName) is { Length: 0 } potentialMembers) + (ISymbol? member, bool areTooMany) = TryGetMember(declaringType, memberName); + + if (areTooMany) { - context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberNotFoundRule, declaringType.Name, memberName)); + // If there are multiple methods with the same name and all are parameterless, report a diagnostic. This is not a supported scenario. + // Note: This is likely to happen only when they differ in arity (for example, one is non-generic and the other is generic). + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(FoundTooManyMembersRule, declaringType.Name, memberName)); return; } - ISymbol? potentialProperty = potentialMembers.FirstOrDefault(m => m.Kind == SymbolKind.Property); - ISymbol member; - if (potentialProperty is not null) - { - member = potentialProperty; - } - else + if (member is null) { - IEnumerable candidateMethods = potentialMembers.OfType().Where(m => m.Parameters.Length == 0); - if (candidateMethods.Count() > 1) - { - // If there are multiple methods with the same name and all are parameterless, report a diagnostic. This is not a supported scenario. - // Note: This is likely to happen only when they differ in arity (for example, one is non-generic and the other is generic). - context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(FoundTooManyMembersRule, declaringType.Name, memberName)); - return; - } - - member = candidateMethods.FirstOrDefault() ?? potentialMembers[0]; + // If we cannot find the member on the given type, report a diagnostic. + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberNotFoundRule, declaringType.Name, memberName)); + return; } switch (member.Kind) diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs index 238ec58742..29575bf0a7 100644 --- a/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs @@ -875,6 +875,77 @@ public void TestMethod2(object[] o) await VerifyCS.VerifyAnalyzerAsync(code); } + [TestMethod] + public async Task MemberIsShadowingBase_NoDiagnostic() + { + string code = """ + using System; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public abstract class MyTestClassBase + { + public static IEnumerable Data => new List(); + public static IEnumerable GetData() => new List(); + } + + [TestClass] + public class MyTestClass : MyTestClassBase + { + [DynamicData("Data")] + [TestMethod] + public void TestMethod1(object[] o) + { + } + + [DynamicData("GetData", DynamicDataSourceType.Method)] + [TestMethod] + public void TestMethod2(object[] o) + { + } + + public static IEnumerable Data => new List(); + public static IEnumerable GetData() => new List(); + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + + [TestMethod] + public async Task MemberIsFromBase_NoDiagnostic() + { + string code = """ + using System; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public abstract class MyTestClassBase + { + public static IEnumerable Data => new List(); + public static IEnumerable GetData() => new List(); + } + + [TestClass] + public class MyTestClass : MyTestClassBase + { + [DynamicData("Data")] + [TestMethod] + public void TestMethod1(object[] o) + { + } + + [DynamicData("GetData", DynamicDataSourceType.Method)] + [TestMethod] + public void TestMethod2(object[] o) + { + } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + [TestMethod] public async Task MethodHasParameters_Diagnostic() { From 1bef33e94ab9abc8c626e4db8bc29b4014d0c4b6 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Tue, 31 Dec 2024 15:29:51 +0100 Subject: [PATCH 7/8] Walk base types manually --- .../DynamicDataOperations.cs | 44 +++++++++++-- .../DynamicDataShouldBeValidAnalyzer.cs | 5 +- .../DynamicDataShouldBeValidAnalyzerTests.cs | 64 +------------------ 3 files changed, 44 insertions(+), 69 deletions(-) diff --git a/src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs b/src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs index 946c48d73f..553bbb241d 100644 --- a/src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs +++ b/src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs @@ -19,11 +19,11 @@ public IEnumerable GetData(Type? _dynamicDataDeclaringType, DynamicDat { case DynamicDataSourceType.AutoDetect: #pragma warning disable IDE0045 // Convert to conditional expression - it becomes less readable. - if (PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeProperty(_dynamicDataDeclaringType, _dynamicDataSourceName, includeNonPublic: true) is { } dynamicDataPropertyInfo) + if (GetPropertyConsideringInheritance(_dynamicDataDeclaringType, _dynamicDataSourceName) is { } dynamicDataPropertyInfo) { obj = GetDataFromProperty(dynamicDataPropertyInfo); } - else if (PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethod(_dynamicDataDeclaringType, _dynamicDataSourceName, parameters: [], includeNonPublic: true) is { } dynamicDataMethodInfo) + else if (GetMethodConsideringInheritance(_dynamicDataDeclaringType, _dynamicDataSourceName) is { } dynamicDataMethodInfo) { obj = GetDataFromMethod(dynamicDataMethodInfo); } @@ -35,14 +35,14 @@ public IEnumerable GetData(Type? _dynamicDataDeclaringType, DynamicDat break; case DynamicDataSourceType.Property: - PropertyInfo property = PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeProperty(_dynamicDataDeclaringType, _dynamicDataSourceName, includeNonPublic: true) + PropertyInfo property = GetPropertyConsideringInheritance(_dynamicDataDeclaringType, _dynamicDataSourceName) ?? throw new ArgumentNullException($"{DynamicDataSourceType.Property} {_dynamicDataSourceName}"); obj = GetDataFromProperty(property); break; case DynamicDataSourceType.Method: - MethodInfo method = PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethod(_dynamicDataDeclaringType, _dynamicDataSourceName, parameters: [], includeNonPublic: true) + MethodInfo method = GetMethodConsideringInheritance(_dynamicDataDeclaringType, _dynamicDataSourceName) ?? throw new ArgumentNullException($"{DynamicDataSourceType.Method} {_dynamicDataSourceName}"); obj = GetDataFromMethod(method); @@ -251,4 +251,40 @@ private static bool IsTupleOrValueTuple(Type type, out int tupleSize) return false; } #endif + + private static PropertyInfo? GetPropertyConsideringInheritance(Type type, string propertyName) + { + // NOTE: Don't use GetRuntimeProperty. It considers inheritance only for instance properties. + Type? currentType = type; + while (currentType is not null) + { + PropertyInfo? property = PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredProperty(type, propertyName); + if (property is not null) + { + return property; + } + + currentType = currentType.BaseType; + } + + return null; + } + + private static MethodInfo? GetMethodConsideringInheritance(Type type, string methodName) + { + // NOTE: Don't use GetRuntimeMethod. It considers inheritance only for instance methods. + Type? currentType = type; + while (currentType is not null) + { + MethodInfo? method = PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredMethod(type, methodName); + if (method is not null) + { + return method; + } + + currentType = currentType.BaseType; + } + + return null; + } } diff --git a/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs index 28d0143e3b..d43862282d 100644 --- a/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs +++ b/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs @@ -169,11 +169,10 @@ private static (ISymbol? Member, bool AreTooMany) TryGetMember(INamedTypeSymbol return (potentialProperty, false); } - IEnumerable candidateMethods = potentialMembers.OfType().Where(m => m.Parameters.Length == 0); + IEnumerable candidateMethods = potentialMembers.Where(m => m.Kind == SymbolKind.Method); if (candidateMethods.Count() > 1) { - // If there are multiple methods with the same name and all are parameterless, report a diagnostic. This is not a supported scenario. - // Note: This is likely to happen only when they differ in arity (for example, one is non-generic and the other is generic). + // If there are multiple methods with the same name, report a diagnostic. This is not a supported scenario. return (null, true); } diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs index 29575bf0a7..5cc4064392 100644 --- a/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs @@ -620,13 +620,13 @@ public void TestMethod6(object[] o) } public static IEnumerable GetData() => new List(); - public static IEnumerable GetData() => new List(); + public static IEnumerable GetData(int i) => new List(); } public class SomeClass { public static IEnumerable GetSomeData() => new List(); - public static IEnumerable GetSomeData() => new List(); + public static IEnumerable GetSomeData(int i) => new List(); } """; @@ -640,66 +640,6 @@ await VerifyCS.VerifyAnalyzerAsync( VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.FoundTooManyMembersRule).WithLocation(5).WithArguments("MyTestClass", "GetData")); } - [TestMethod] - public async Task WhenDataSourceMemberFoundMultipleTimesDifferentParameters_NoDiagnostic() - { - string code = """ - using System.Collections.Generic; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - [TestClass] - public class MyTestClass - { - [DynamicData("GetData", DynamicDataSourceType.Method)] - [TestMethod] - public void TestMethod1(object[] o) - { - } - - [DynamicData("GetSomeData", typeof(SomeClass), DynamicDataSourceType.Method)] - [TestMethod] - public void TestMethod2(object[] o) - { - } - - [DynamicData(dynamicDataSourceType: DynamicDataSourceType.Method, dynamicDataSourceName: "GetData")] - [TestMethod] - public void TestMethod3(object[] o) - { - } - - [DynamicData(dynamicDataDeclaringType: typeof(SomeClass), dynamicDataSourceType: DynamicDataSourceType.Method, dynamicDataSourceName: "GetSomeData")] - [TestMethod] - public void TestMethod4(object[] o) - { - } - - [DynamicData("GetData", DynamicDataSourceType.AutoDetect)] - [TestMethod] - public void TestMethod5(object[] o) - { - } - - [DynamicData("GetData")] - [TestMethod] - public void TestMethod6(object[] o) - { - } - - public static IEnumerable GetData() => new List(); - public static IEnumerable GetData(int i) => new List(); - } - - public class SomeClass - { - public static IEnumerable GetSomeData() => new List(); - public static IEnumerable GetSomeData(int i) => new List(); - } - """; - - await VerifyCS.VerifyAnalyzerAsync(code); - } - [TestMethod] public async Task WhenMemberKindIsMixedUp_Diagnostic() { From 8ab31ba337284c784cb746acec21855bd4d9a8e2 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 31 Dec 2024 17:43:58 +0100 Subject: [PATCH 8/8] Fix typo --- src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs b/src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs index 553bbb241d..954b9b435e 100644 --- a/src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs +++ b/src/Adapter/MSTest.TestAdapter/DynamicDataOperations.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -258,7 +258,7 @@ private static bool IsTupleOrValueTuple(Type type, out int tupleSize) Type? currentType = type; while (currentType is not null) { - PropertyInfo? property = PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredProperty(type, propertyName); + PropertyInfo? property = PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredProperty(currentType, propertyName); if (property is not null) { return property; @@ -276,7 +276,7 @@ private static bool IsTupleOrValueTuple(Type type, out int tupleSize) Type? currentType = type; while (currentType is not null) { - MethodInfo? method = PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredMethod(type, methodName); + MethodInfo? method = PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredMethod(currentType, methodName); if (method is not null) { return method;