From cf164f8ca2f4f6e0fe65bc9e02749ca7935d925a Mon Sep 17 00:00:00 2001 From: Paul Spangler Date: Wed, 10 Oct 2018 22:52:01 -0500 Subject: [PATCH 1/5] Apply TestCategory from derived class on inherited test methods Enables sharing test methods across a class hierarchy while categorizing the inherited tests based on the derived class that's actually running them. --- .../Discovery/TypeEnumerator.cs | 4 +-- .../Helpers/ReflectHelper.cs | 26 ++++++++++--------- .../Discovery/TypeEnumeratorTests.cs | 2 +- .../Helpers/ReflectHelperTests.cs | 13 +++++----- .../TestableReflectHelper.cs | 2 +- 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/Adapter/MSTest.CoreAdapter/Discovery/TypeEnumerator.cs b/src/Adapter/MSTest.CoreAdapter/Discovery/TypeEnumerator.cs index 2e9260ef34..cbc90a9ff1 100644 --- a/src/Adapter/MSTest.CoreAdapter/Discovery/TypeEnumerator.cs +++ b/src/Adapter/MSTest.CoreAdapter/Discovery/TypeEnumerator.cs @@ -126,9 +126,9 @@ internal UnitTestElement GetTestFromMethod(MethodInfo method, bool isDeclaredInT var asyncTypeName = method.GetAsyncTypeName(); testElement.AsyncTypeName = asyncTypeName; - testElement.TestCategory = this.reflectHelper.GetCategories(method); + testElement.TestCategory = this.reflectHelper.GetCategories(method, this.type); - testElement.DoNotParallelize = this.reflectHelper.IsDoNotParallelizeSet(method); + testElement.DoNotParallelize = this.reflectHelper.IsDoNotParallelizeSet(method, this.type); var traits = this.reflectHelper.GetTestPropertiesAsTraits(method); diff --git a/src/Adapter/MSTest.CoreAdapter/Helpers/ReflectHelper.cs b/src/Adapter/MSTest.CoreAdapter/Helpers/ReflectHelper.cs index 94829b0270..2f19a74b08 100644 --- a/src/Adapter/MSTest.CoreAdapter/Helpers/ReflectHelper.cs +++ b/src/Adapter/MSTest.CoreAdapter/Helpers/ReflectHelper.cs @@ -313,10 +313,11 @@ internal virtual bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Ty /// Get categories applied to the test method /// /// The member to inspect. + /// The reflected type that owns . /// Categories defined. - internal virtual string[] GetCategories(MemberInfo categoryAttributeProvider) + internal virtual string[] GetCategories(MemberInfo categoryAttributeProvider, Type owningType) { - var categories = this.GetCustomAttributesRecursively(categoryAttributeProvider, typeof(TestCategoryBaseAttribute)); + var categories = this.GetCustomAttributesRecursively(categoryAttributeProvider, owningType, typeof(TestCategoryBaseAttribute)); List testCategories = new List(); if (categories != null) @@ -344,11 +345,12 @@ internal ParallelizeAttribute GetParallelizeAttribute(Assembly assembly) /// 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) + internal bool IsDoNotParallelizeSet(MemberInfo testMethod, Type owningType) { return this.GetCustomAttributes(testMethod, typeof(DoNotParallelizeAttribute)).Any() - || this.GetCustomAttributes(testMethod.DeclaringType.GetTypeInfo(), typeof(DoNotParallelizeAttribute)).Any(); + || this.GetCustomAttributes(owningType.GetTypeInfo(), typeof(DoNotParallelizeAttribute)).Any(); } /// @@ -365,19 +367,20 @@ internal bool IsDoNotParallelizeSet(Assembly assembly) /// Gets custom attributes at the class and assembly for a method. /// /// Method Info or Member Info or a Type + /// The type that owns . /// What type of CustomAttribute you need. For instance: TestCategory, Owner etc., /// The categories of the specified type on the method. - internal IEnumerable GetCustomAttributesRecursively(MemberInfo attributeProvider, Type type) + internal IEnumerable GetCustomAttributesRecursively(MemberInfo attributeProvider, Type owningType, Type type) { var categories = this.GetCustomAttributes(attributeProvider, typeof(TestCategoryBaseAttribute)); if (categories != null) { - categories = categories.Concat(this.GetCustomAttributes(attributeProvider.DeclaringType.GetTypeInfo(), typeof(TestCategoryBaseAttribute))).ToArray(); + categories = categories.Concat(this.GetCustomAttributes(owningType.GetTypeInfo(), typeof(TestCategoryBaseAttribute))).ToArray(); } if (categories != null) { - categories = categories.Concat(this.GetCustomAttributeForAssembly(attributeProvider, typeof(TestCategoryBaseAttribute))).ToArray(); + categories = categories.Concat(this.GetCustomAttributeForAssembly(owningType.GetTypeInfo().Assembly, typeof(TestCategoryBaseAttribute))).ToArray(); } if (categories != null) @@ -389,18 +392,17 @@ internal IEnumerable GetCustomAttributesRecursively(MemberInfo attribute } /// - /// Gets the custom attributes on the assembly of a member info + /// Gets the custom attributes on an assembly /// NOTE: having it as separate virtual method, so that we can extend it for testing. /// - /// The member to inspect. + /// The assembly to inspect. /// The attribute type to find. /// Custom attributes defined. - internal virtual Attribute[] GetCustomAttributeForAssembly(MemberInfo memberInfo, Type type) + internal virtual Attribute[] GetCustomAttributeForAssembly(Assembly assembly, Type type) { return PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes( - memberInfo.DeclaringType.GetTypeInfo().Assembly, - type).OfType().ToArray(); + assembly, type).OfType().ToArray(); } /// diff --git a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Discovery/TypeEnumeratorTests.cs b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Discovery/TypeEnumeratorTests.cs index bc06bfaecf..fed5311e8d 100644 --- a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Discovery/TypeEnumeratorTests.cs +++ b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Discovery/TypeEnumeratorTests.cs @@ -247,7 +247,7 @@ public void GetTestFromMethodShouldSetTestCategory() var testCategories = new string[] { "foo", "bar" }; // Setup mocks - this.mockReflectHelper.Setup(rh => rh.GetCategories(methodInfo)).Returns(testCategories); + this.mockReflectHelper.Setup(rh => rh.GetCategories(methodInfo, typeof(DummyTestClass))).Returns(testCategories); var testElement = typeEnumerator.GetTestFromMethod(methodInfo, true, this.warnings); diff --git a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Helpers/ReflectHelperTests.cs b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Helpers/ReflectHelperTests.cs index 884e2d6c6b..4397b59a51 100644 --- a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Helpers/ReflectHelperTests.cs +++ b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Helpers/ReflectHelperTests.cs @@ -36,7 +36,6 @@ public void IntializeTests() this.reflectHelper = new TestableReflectHelper(); this.method = new Mock(); this.method.Setup(x => x.MemberType).Returns(MemberTypes.Method); - this.method.Setup(x => x.DeclaringType).Returns(typeof(ReflectHelperTests)); this.testablePlatformServiceProvider = new TestablePlatformServiceProvider(); this.testablePlatformServiceProvider.SetupMockReflectionOperations(); @@ -58,7 +57,7 @@ public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtClassLevel() this.reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), new[] { new UTF.TestCategoryAttribute("ClassLevel") }, MemberTypes.TypeInfo); string[] expected = new[] { "ClassLevel" }; - var actual = this.reflectHelper.GetCategories(this.method.Object).ToArray(); + var actual = this.reflectHelper.GetCategories(this.method.Object, typeof(ReflectHelperTests)).ToArray(); CollectionAssert.AreEqual(expected, actual); } @@ -73,7 +72,7 @@ public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtAllLevels() this.reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), new[] { new UTF.TestCategoryAttribute("ClassLevel") }, MemberTypes.TypeInfo); this.reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), new[] { new UTF.TestCategoryAttribute("MethodLevel") }, MemberTypes.Method); - var actual = this.reflectHelper.GetCategories(this.method.Object).ToArray(); + var actual = this.reflectHelper.GetCategories(this.method.Object, typeof(ReflectHelperTests)).ToArray(); string[] expected = new[] { "MethodLevel", "ClassLevel", "AsmLevel" }; CollectionAssert.AreEqual(expected, actual); @@ -89,7 +88,7 @@ public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtAssemblyLevel() string[] expected = new[] { "AsmLevel" }; - var actual = this.reflectHelper.GetCategories(this.method.Object).ToArray(); + var actual = this.reflectHelper.GetCategories(this.method.Object, typeof(ReflectHelperTests)).ToArray(); CollectionAssert.AreEqual(expected, actual); } @@ -103,7 +102,7 @@ public void GetTestCategoryAttributeShouldIncludeMultipleTestCategoriesAtClassLe this.reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), new[] { new UTF.TestCategoryAttribute("ClassLevel"), new UTF.TestCategoryAttribute("ClassLevel1") }, MemberTypes.TypeInfo); string[] expected = new[] { "ClassLevel", "ClassLevel1" }; - var actual = this.reflectHelper.GetCategories(this.method.Object).ToArray(); + var actual = this.reflectHelper.GetCategories(this.method.Object, typeof(ReflectHelperTests)).ToArray(); CollectionAssert.AreEqual(expected, actual); } @@ -117,7 +116,7 @@ public void GetTestCategoryAttributeShouldIncludeMultipleTestCategoriesAtAssembl this.reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), new[] { new UTF.TestCategoryAttribute("AsmLevel"), new UTF.TestCategoryAttribute("AsmLevel1") }, MemberTypes.All); string[] expected = new[] { "AsmLevel", "AsmLevel1" }; - var actual = this.reflectHelper.GetCategories(this.method.Object).ToArray(); + var actual = this.reflectHelper.GetCategories(this.method.Object, typeof(ReflectHelperTests)).ToArray(); CollectionAssert.AreEqual(expected, actual); } @@ -130,7 +129,7 @@ public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtMethodLevel() this.reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), new[] { new UTF.TestCategoryAttribute("MethodLevel") }, MemberTypes.Method); string[] expected = new[] { "MethodLevel" }; - var actual = this.reflectHelper.GetCategories(this.method.Object).ToArray(); + var actual = this.reflectHelper.GetCategories(this.method.Object, typeof(ReflectHelperTests)).ToArray(); CollectionAssert.AreEqual(expected, actual); } diff --git a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/TestableImplementations/TestableReflectHelper.cs b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/TestableImplementations/TestableReflectHelper.cs index c09e9dba37..3da81a50ef 100644 --- a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/TestableImplementations/TestableReflectHelper.cs +++ b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/TestableImplementations/TestableReflectHelper.cs @@ -41,7 +41,7 @@ public void SetCustomAttribute(Type type, Attribute[] values, MemberTypes member } } - internal override Attribute[] GetCustomAttributeForAssembly(MemberInfo memberInfo, Type type) + internal override Attribute[] GetCustomAttributeForAssembly(Assembly assembly, Type type) { var hashcode = MemberTypes.All.GetHashCode() + type.FullName.GetHashCode(); From 160eb6f50de4190a091509800a0c90a0c33953db Mon Sep 17 00:00:00 2001 From: Paul Spangler Date: Fri, 30 Nov 2018 00:14:22 -0600 Subject: [PATCH 2/5] Preserve DeclaringClassFullName from discovery to execution --- src/Adapter/MSTest.CoreAdapter/Constants.cs | 3 ++ .../MSTest.CoreAdapter/Execution/TypeCache.cs | 22 ++++++-- .../Extensions/TestCaseExtensions.cs | 6 +++ .../ObjectModel/TestMethod.cs | 3 +- .../ObjectModel/UnitTestElement.cs | 6 +++ .../ObjectModel/ITestMethod.cs | 3 +- .../Execution/TypeCacheTests.cs | 51 +++++++++++++++++++ .../Extensions/TestCaseExtensionsTests.cs | 14 +++++ .../ObjectModel/UnitTestElementTests.cs | 14 +++++ 9 files changed, 117 insertions(+), 5 deletions(-) diff --git a/src/Adapter/MSTest.CoreAdapter/Constants.cs b/src/Adapter/MSTest.CoreAdapter/Constants.cs index 58bde36910..24a74c71c4 100644 --- a/src/Adapter/MSTest.CoreAdapter/Constants.cs +++ b/src/Adapter/MSTest.CoreAdapter/Constants.cs @@ -32,6 +32,8 @@ internal static class Constants internal static readonly TestProperty TestClassNameProperty = TestProperty.Register("MSTestDiscoverer.TestClassName", TestClassNameLabel, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase)); + internal static readonly TestProperty DeclaringClassNameProperty = TestProperty.Register("MSTestDiscoverer.DeclaringClassName", DeclaringClassNameLabel, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase)); + internal static readonly TestProperty AsyncTestProperty = TestProperty.Register("MSTestDiscoverer.IsAsync", IsAsyncLabel, typeof(bool), TestPropertyAttributes.Hidden, typeof(TestCase)); #pragma warning disable CS0618 // Type or member is obsolete @@ -90,6 +92,7 @@ internal static class Constants /// These Property names should not be localized. /// private const string TestClassNameLabel = "ClassName"; + private const string DeclaringClassNameLabel = "DeclaringClassName"; private const string IsAsyncLabel = "IsAsync"; private const string TestCategoryLabel = "TestCategory"; private const string PriorityLabel = "Priority"; diff --git a/src/Adapter/MSTest.CoreAdapter/Execution/TypeCache.cs b/src/Adapter/MSTest.CoreAdapter/Execution/TypeCache.cs index f2f659c2e3..4bd7e8785d 100644 --- a/src/Adapter/MSTest.CoreAdapter/Execution/TypeCache.cs +++ b/src/Adapter/MSTest.CoreAdapter/Execution/TypeCache.cs @@ -605,10 +605,26 @@ private TestMethodAttribute GetTestMethodAttribute(MethodInfo methodInfo, TestCl private MethodInfo GetMethodInfoForTestMethod(TestMethod testMethod, TestClassInfo testClassInfo) { var methodsInClass = testClassInfo.ClassType.GetRuntimeMethods().ToArray(); + MethodInfo testMethodInfo; - var testMethodInfo = - methodsInClass.Where(method => method.Name.Equals(testMethod.Name)) - .FirstOrDefault(method => method.HasCorrectTestMethodSignature(true)); + if (testMethod.DeclaringClassFullName != null) + { + // Only find methods that match the given declaring name. + testMethodInfo = + methodsInClass.Where(method => method.Name.Equals(testMethod.Name) + && method.DeclaringType.FullName.Equals(testMethod.DeclaringClassFullName) + && method.HasCorrectTestMethodSignature(true)).FirstOrDefault(); + } + else + { + // Either the declaring class is the same as the test class, or + // the declaring class information wasn't passed in the test case. + // Prioritize the former while maintaining previous behavior for the latter. + var className = testClassInfo.ClassType.FullName; + testMethodInfo = + methodsInClass.Where(method => method.Name.Equals(testMethod.Name) && method.HasCorrectTestMethodSignature(true)) + .OrderByDescending(method => method.DeclaringType.FullName.Equals(className)).FirstOrDefault(); + } // if correct method is not found, throw appropriate // exception about what is wrong. diff --git a/src/Adapter/MSTest.CoreAdapter/Extensions/TestCaseExtensions.cs b/src/Adapter/MSTest.CoreAdapter/Extensions/TestCaseExtensions.cs index 45a17a14d7..4b36457406 100644 --- a/src/Adapter/MSTest.CoreAdapter/Extensions/TestCaseExtensions.cs +++ b/src/Adapter/MSTest.CoreAdapter/Extensions/TestCaseExtensions.cs @@ -22,9 +22,15 @@ internal static UnitTestElement ToUnitTestElement(this TestCase testCase, string { var isAsync = (testCase.GetPropertyValue(Constants.AsyncTestProperty) as bool?) ?? false; var testClassName = testCase.GetPropertyValue(Constants.TestClassNameProperty) as string; + var declaringClassName = testCase.GetPropertyValue(Constants.DeclaringClassNameProperty) as string; TestMethod testMethod = new TestMethod(testCase.DisplayName, testClassName, source, isAsync); + if (declaringClassName != null && declaringClassName != testClassName) + { + testMethod.DeclaringClassFullName = declaringClassName; + } + UnitTestElement testElement = new UnitTestElement(testMethod) { IsAsync = isAsync, diff --git a/src/Adapter/MSTest.CoreAdapter/ObjectModel/TestMethod.cs b/src/Adapter/MSTest.CoreAdapter/ObjectModel/TestMethod.cs index 8692bd0b39..708e9b621f 100644 --- a/src/Adapter/MSTest.CoreAdapter/ObjectModel/TestMethod.cs +++ b/src/Adapter/MSTest.CoreAdapter/ObjectModel/TestMethod.cs @@ -74,7 +74,8 @@ public string DeclaringAssemblyName } /// - /// Gets or sets the declaring class full name. This will be used while getting navigation data. + /// Gets or sets the declaring class full name. + /// This will be used to resolve overloads and while getting navigation data. /// This will be null if FullClassName is same as DeclaringClassFullName. /// Reason to set to null in the above case is to minimise the transfer of data across appdomains and not have a perf hit. /// diff --git a/src/Adapter/MSTest.CoreAdapter/ObjectModel/UnitTestElement.cs b/src/Adapter/MSTest.CoreAdapter/ObjectModel/UnitTestElement.cs index 945fc8b57d..71df25f5ea 100644 --- a/src/Adapter/MSTest.CoreAdapter/ObjectModel/UnitTestElement.cs +++ b/src/Adapter/MSTest.CoreAdapter/ObjectModel/UnitTestElement.cs @@ -94,6 +94,12 @@ internal TestCase ToTestCase() testCase.SetPropertyValue(TestAdapter.Constants.TestClassNameProperty, this.TestMethod.FullClassName); + // Set declaring type if present so the correct method info can be retrieved + if (this.TestMethod.DeclaringClassFullName != null) + { + testCase.SetPropertyValue(TestAdapter.Constants.DeclaringClassNameProperty, this.TestMethod.DeclaringClassFullName); + } + // Many of the tests will not be async, so there is no point in sending extra data if (this.IsAsync) { diff --git a/src/Adapter/PlatformServices.Interface/ObjectModel/ITestMethod.cs b/src/Adapter/PlatformServices.Interface/ObjectModel/ITestMethod.cs index fd3abb6496..5d3abe84dd 100644 --- a/src/Adapter/PlatformServices.Interface/ObjectModel/ITestMethod.cs +++ b/src/Adapter/PlatformServices.Interface/ObjectModel/ITestMethod.cs @@ -19,7 +19,8 @@ public interface ITestMethod string FullClassName { get; } /// - /// Gets the declaring class full name. This will be used while getting navigation data. + /// Gets the declaring class full name. + /// This will be used for resolving overloads and while getting navigation data. /// string DeclaringClassFullName { get; } diff --git a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Execution/TypeCacheTests.cs b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Execution/TypeCacheTests.cs index db1c33b763..f14c20e450 100644 --- a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Execution/TypeCacheTests.cs +++ b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Execution/TypeCacheTests.cs @@ -1031,6 +1031,48 @@ public void GetTestMethodInfoShouldReturnTestMethodInfoForDerivedTestClasses() Assert.IsNotNull(testMethodInfo.TestMethodOptions.Executor); } + [TestMethodV1] + public void GetTestMethodInfoShouldReturnTestMethodInfoForDerivedClassMethodOverloadByDefault() + { + var type = typeof(DerivedTestClass); + var methodInfo = type.GetRuntimeMethod("OverloadedTestMethod", new Type[] { }); + var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); + + var testMethodInfo = this.typeCache.GetTestMethodInfo( + testMethod, + new TestContextImplementation(testMethod, null, new Dictionary()), + false); + + Assert.AreEqual(methodInfo, testMethodInfo.TestMethod); + Assert.AreEqual(0, testMethodInfo.TestMethodOptions.Timeout); + Assert.AreEqual(this.typeCache.ClassInfoCache.ToArray()[0], testMethodInfo.Parent); + Assert.IsNotNull(testMethodInfo.TestMethodOptions.Executor); + } + + [TestMethodV1] + public void GetTestMethodInfoShouldReturnTestMethodInfoForDeclaringTypeMethodOverload() + { + var baseType = typeof(BaseTestClass); + var type = typeof(DerivedTestClass); + var methodInfo = baseType.GetRuntimeMethod("OverloadedTestMethod", new Type[] { }); + var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false) + { + DeclaringClassFullName = baseType.FullName + }; + + var testMethodInfo = this.typeCache.GetTestMethodInfo( + testMethod, + new TestContextImplementation(testMethod, null, new Dictionary()), + false); + + // The two MethodInfo instances will have different ReflectedType properties, + // so cannot be compared directly. Use MethodHandle to verify it's the same. + Assert.AreEqual(methodInfo.MethodHandle, testMethodInfo.TestMethod.MethodHandle); + Assert.AreEqual(0, testMethodInfo.TestMethodOptions.Timeout); + Assert.AreEqual(this.typeCache.ClassInfoCache.ToArray()[0], testMethodInfo.Parent); + Assert.IsNotNull(testMethodInfo.TestMethodOptions.Executor); + } + #endregion #endregion @@ -1311,6 +1353,10 @@ public void TestMethodWithMultipleExpectedException() [DummyTestClass] internal class DerivedTestClass : BaseTestClass { + [UTF.TestMethod] + public new void OverloadedTestMethod() + { + } } internal class BaseTestClass @@ -1319,6 +1365,11 @@ internal class BaseTestClass public void DummyTestMethod() { } + + [UTF.TestMethod] + public void OverloadedTestMethod() + { + } } private class DummyTestClassWithNoDefaultConstructor diff --git a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Extensions/TestCaseExtensionsTests.cs b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Extensions/TestCaseExtensionsTests.cs index e827d03569..8c6ef2c73f 100644 --- a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Extensions/TestCaseExtensionsTests.cs +++ b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Extensions/TestCaseExtensionsTests.cs @@ -37,6 +37,7 @@ public void ToUnitTestElementShouldReturnUnitTestElementWithFieldsSet() Assert.AreEqual("DummyDisplayName", resultUnitTestElement.TestMethod.Name); Assert.AreEqual("DummyClassName", resultUnitTestElement.TestMethod.FullClassName); Assert.AreEqual(true, resultUnitTestElement.TestMethod.IsAsync); + Assert.IsNull(resultUnitTestElement.TestMethod.DeclaringClassFullName); } [TestMethod] @@ -52,5 +53,18 @@ public void ToUnitTestElementForTestCaseWithNoPropertiesShouldReturnUnitTestElem Assert.AreEqual(0, resultUnitTestElement.Priority); Assert.AreEqual(null, resultUnitTestElement.TestCategory); } + + [TestMethod] + public void ToUnitTestElementShouldAddDeclaringClassNameToTestElementWhenAvailable() + { + TestCase testCase = new TestCase("DummyClass.DummyMethod", new Uri("DummyUri", UriKind.Relative), Assembly.GetCallingAssembly().FullName); + testCase.SetPropertyValue(Constants.TestClassNameProperty, "DummyClassName"); + testCase.SetPropertyValue(Constants.DeclaringClassNameProperty, "DummyDeclaringClassName"); + + var resultUnitTestElement = testCase.ToUnitTestElement(testCase.Source); + + Assert.AreEqual("DummyClassName", resultUnitTestElement.TestMethod.FullClassName); + Assert.AreEqual("DummyDeclaringClassName", resultUnitTestElement.TestMethod.DeclaringClassFullName); + } } } diff --git a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/ObjectModel/UnitTestElementTests.cs b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/ObjectModel/UnitTestElementTests.cs index 7ccd716198..b831ee402d 100644 --- a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/ObjectModel/UnitTestElementTests.cs +++ b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/ObjectModel/UnitTestElementTests.cs @@ -84,6 +84,20 @@ public void ToTestCaseShouldSetTestClassNameProperty() Assert.AreEqual("C", testCase.GetPropertyValue(Constants.TestClassNameProperty)); } + [TestMethodV1] + public void ToTestCaseShouldSetDeclaringClassNameIfPresent() + { + this.testMethod.DeclaringClassFullName = null; + var testCase = this.unitTestElement.ToTestCase(); + + Assert.IsNull(testCase.GetPropertyValue(Constants.DeclaringClassNameProperty)); + + this.testMethod.DeclaringClassFullName = "DC"; + testCase = this.unitTestElement.ToTestCase(); + + Assert.AreEqual("DC", testCase.GetPropertyValue(Constants.DeclaringClassNameProperty)); + } + [TestMethodV1] public void ToTestCaseShouldSetIsAsyncProperty() { From d587fa86fe7a84a049b89afbd18f0330fd68bf7a Mon Sep 17 00:00:00 2001 From: Paul Spangler Date: Fri, 30 Nov 2018 11:27:46 -0600 Subject: [PATCH 3/5] Restore GetCustomAttributeForAssembly taking in a MemberInfo, but reference the member's Module instead of DeclaringType --- .../MSTest.CoreAdapter/Helpers/ReflectHelper.cs | 10 +++++----- .../TestableImplementations/TestableReflectHelper.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Adapter/MSTest.CoreAdapter/Helpers/ReflectHelper.cs b/src/Adapter/MSTest.CoreAdapter/Helpers/ReflectHelper.cs index 2f19a74b08..5ebaed6cde 100644 --- a/src/Adapter/MSTest.CoreAdapter/Helpers/ReflectHelper.cs +++ b/src/Adapter/MSTest.CoreAdapter/Helpers/ReflectHelper.cs @@ -380,7 +380,7 @@ internal IEnumerable GetCustomAttributesRecursively(MemberInfo attribute if (categories != null) { - categories = categories.Concat(this.GetCustomAttributeForAssembly(owningType.GetTypeInfo().Assembly, typeof(TestCategoryBaseAttribute))).ToArray(); + categories = categories.Concat(this.GetCustomAttributeForAssembly(owningType.GetTypeInfo(), typeof(TestCategoryBaseAttribute))).ToArray(); } if (categories != null) @@ -392,17 +392,17 @@ internal IEnumerable GetCustomAttributesRecursively(MemberInfo attribute } /// - /// Gets the custom attributes on an assembly + /// Gets the custom attributes on the assembly of a member info /// NOTE: having it as separate virtual method, so that we can extend it for testing. /// - /// The assembly to inspect. + /// The member to inspect. /// The attribute type to find. /// Custom attributes defined. - internal virtual Attribute[] GetCustomAttributeForAssembly(Assembly assembly, Type type) + internal virtual Attribute[] GetCustomAttributeForAssembly(MemberInfo memberInfo, Type type) { return PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes( - assembly, type).OfType().ToArray(); + memberInfo.Module.Assembly, type).OfType().ToArray(); } /// diff --git a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/TestableImplementations/TestableReflectHelper.cs b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/TestableImplementations/TestableReflectHelper.cs index 3da81a50ef..c09e9dba37 100644 --- a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/TestableImplementations/TestableReflectHelper.cs +++ b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/TestableImplementations/TestableReflectHelper.cs @@ -41,7 +41,7 @@ public void SetCustomAttribute(Type type, Attribute[] values, MemberTypes member } } - internal override Attribute[] GetCustomAttributeForAssembly(Assembly assembly, Type type) + internal override Attribute[] GetCustomAttributeForAssembly(MemberInfo memberInfo, Type type) { var hashcode = MemberTypes.All.GetHashCode() + type.FullName.GetHashCode(); From 67658d132cc807562c49b8986637f544a4c5e361 Mon Sep 17 00:00:00 2001 From: Paul Spangler Date: Wed, 12 Dec 2018 22:34:31 -0600 Subject: [PATCH 4/5] Filter out duplicate test methods Selects the one declared closest to the test class among all valid test methods with the same name. --- .../Discovery/TypeEnumerator.cs | 28 ++++- .../Discovery/TypeEnumeratorTests.cs | 105 ++++++++++++++++++ 2 files changed, 132 insertions(+), 1 deletion(-) diff --git a/src/Adapter/MSTest.CoreAdapter/Discovery/TypeEnumerator.cs b/src/Adapter/MSTest.CoreAdapter/Discovery/TypeEnumerator.cs index cbc90a9ff1..8e5946af84 100644 --- a/src/Adapter/MSTest.CoreAdapter/Discovery/TypeEnumerator.cs +++ b/src/Adapter/MSTest.CoreAdapter/Discovery/TypeEnumerator.cs @@ -70,6 +70,8 @@ internal virtual ICollection Enumerate(out ICollection /// List of Valid Tests. internal Collection GetTests(ICollection warnings) { + bool foundDuplicateTests = false; + var foundTests = new HashSet(); var tests = new Collection(); // Test class is already valid. Verify methods. @@ -85,11 +87,35 @@ internal Collection GetTests(ICollection warnings) if (this.testMethodValidator.IsValidTestMethod(method, this.type, warnings)) { + foundDuplicateTests = foundDuplicateTests || !foundTests.Add(method.Name); tests.Add(this.GetTestFromMethod(method, isMethodDeclaredInTestTypeAssembly, warnings)); } } - return tests; + if (!foundDuplicateTests) + { + return tests; + } + + // Remove duplicate test methods by taking the first one of each name + // that is declared closest to the test class in the hierarchy. + var inheritanceDepths = new Dictionary(); + var currentType = this.type; + int currentDepth = 0; + + while (currentType != null) + { + inheritanceDepths[currentType.FullName] = currentDepth; + ++currentDepth; + currentType = currentType.GetTypeInfo().BaseType; + } + + return new Collection( + tests.GroupBy( + t => t.TestMethod.Name, + (_, elements) => + elements.OrderBy(t => inheritanceDepths[t.TestMethod.DeclaringClassFullName ?? t.TestMethod.FullClassName]).First()) + .ToList()); } /// diff --git a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Discovery/TypeEnumeratorTests.cs b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Discovery/TypeEnumeratorTests.cs index fed5311e8d..95d1ccf5bd 100644 --- a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Discovery/TypeEnumeratorTests.cs +++ b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Discovery/TypeEnumeratorTests.cs @@ -183,6 +183,82 @@ public void GetTestsShouldNotReturnBaseTestMethodsFromAnotherAssemblyByConfigura "DummyDerivedFromRemoteTestClass inherits DummyRemoteBaseTestClass from different assembly. BestTestMethod from DummyRemoteBaseTestClass should not be discovered when RunSettings MSTestV2 specifies EnableBaseClassTestMethodsFromOtherAssemblies = false."); } + [TestMethod] + public void GetTestsShouldNotReturnHiddenTestMethods() + { + this.SetupTestClassAndTestMethods(isValidTestClass: true, isValidTestMethod: true, isMethodFromSameAssembly: true); + TypeEnumerator typeEnumerator = this.GetTypeEnumeratorInstance(typeof(DummyHidingTestClass), Assembly.GetExecutingAssembly().FullName); + + var tests = typeEnumerator.Enumerate(out this.warnings); + + Assert.IsNotNull(tests); + Assert.AreEqual( + 1, + tests.Count(t => t.TestMethod.Name == "BaseTestMethod"), + "DummyHidingTestClass declares BaseTestMethod directly so it should always be discovered."); + Assert.AreEqual( + 1, + tests.Count(t => t.TestMethod.Name == "DerivedTestMethod"), + "DummyHidingTestClass declares BaseTestMethod directly so it should always be discovered."); + Assert.IsFalse( + tests.Any(t => t.TestMethod.DeclaringClassFullName == typeof(DummyBaseTestClass).FullName), + "DummyHidingTestClass hides BaseTestMethod so declaring class should not be the base class"); + } + + [TestMethod] + public void GetTestsShouldReturnOverriddenTestMethods() + { + this.SetupTestClassAndTestMethods(isValidTestClass: true, isValidTestMethod: true, isMethodFromSameAssembly: true); + TypeEnumerator typeEnumerator = this.GetTypeEnumeratorInstance(typeof(DummyOverridingTestClass), Assembly.GetExecutingAssembly().FullName); + + var tests = typeEnumerator.Enumerate(out this.warnings); + + Assert.IsNotNull(tests); + Assert.AreEqual( + 1, + tests.Count(t => t.TestMethod.Name == "BaseTestMethod"), + "DummyOverridingTestClass inherits BaseTestMethod so it should be discovered."); + Assert.AreEqual( + 1, + tests.Count(t => t.TestMethod.Name == "DerivedTestMethod"), + "DummyOverridingTestClass overrides DerivedTestMethod directly so it should always be discovered."); + Assert.AreEqual( + typeof(DummyHidingTestClass).FullName, + tests.Single(t => t.TestMethod.Name == "BaseTestMethod").TestMethod.DeclaringClassFullName, + "DummyOverridingTestClass inherits BaseTestMethod from DummyHidingTestClass specifically."); + Assert.IsNull( + tests.Single(t => t.TestMethod.Name == "DerivedTestMethod").TestMethod.DeclaringClassFullName, + "DummyOverridingTestClass overrides DerivedTestMethod so is the declaring class."); + } + + [TestMethod] + public void GetTestsShouldNotReturnHiddenTestMethodsFromAnyLevel() + { + this.SetupTestClassAndTestMethods(isValidTestClass: true, isValidTestMethod: true, isMethodFromSameAssembly: true); + TypeEnumerator typeEnumerator = this.GetTypeEnumeratorInstance(typeof(DummySecondHidingTestClass), Assembly.GetExecutingAssembly().FullName); + + var tests = typeEnumerator.Enumerate(out this.warnings); + + Assert.IsNotNull(tests); + Assert.AreEqual( + 1, + tests.Count(t => t.TestMethod.Name == "BaseTestMethod"), + "DummySecondHidingTestClass hides BaseTestMethod so it should be discovered."); + Assert.AreEqual( + 1, + tests.Count(t => t.TestMethod.Name == "DerivedTestMethod"), + "DummySecondHidingTestClass hides DerivedTestMethod so it should be discovered."); + Assert.IsFalse( + tests.Any(t => t.TestMethod.DeclaringClassFullName == typeof(DummyBaseTestClass).FullName), + "DummySecondHidingTestClass hides all base test methods so declaring class should not be any base class"); + Assert.IsFalse( + tests.Any(t => t.TestMethod.DeclaringClassFullName == typeof(DummyHidingTestClass).FullName), + "DummySecondHidingTestClass hides all base test methods so declaring class should not be any base class"); + Assert.IsFalse( + tests.Any(t => t.TestMethod.DeclaringClassFullName == typeof(DummyOverridingTestClass).FullName), + "DummySecondHidingTestClass hides all base test methods so declaring class should not be any base class"); + } + #endregion #region GetTestFromMethod tests @@ -444,5 +520,34 @@ public void DerivedTestMethod() } } + public class DummyHidingTestClass : DummyBaseTestClass + { + public new virtual void BaseTestMethod() + { + } + + public virtual void DerivedTestMethod() + { + } + } + + public class DummyOverridingTestClass : DummyHidingTestClass + { + public override void DerivedTestMethod() + { + } + } + + public class DummySecondHidingTestClass : DummyOverridingTestClass + { + public new void BaseTestMethod() + { + } + + public new void DerivedTestMethod() + { + } + } + #endregion } From a09b1444db4623cf361d2fb89ac56600acbdb4b6 Mon Sep 17 00:00:00 2001 From: Paul Spangler Date: Thu, 10 Jan 2019 22:30:37 -0600 Subject: [PATCH 5/5] Fix compile after latest merge from master --- .../Helpers/ReflectHelperTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Helpers/ReflectHelperTests.cs b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Helpers/ReflectHelperTests.cs index 81c7222453..49bb940e5f 100644 --- a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Helpers/ReflectHelperTests.cs +++ b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Helpers/ReflectHelperTests.cs @@ -63,7 +63,7 @@ public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtClassLevel() } /// - /// Testing test category attributes adorned at calss, assembly and method level are getting collected. + /// Testing test category attributes adorned at class, assembly and method level are getting collected. /// [TestMethod] public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtAllLevels() @@ -72,15 +72,15 @@ public void GetTestCategoryAttributeShouldIncludeTestCategoriesAtAllLevels() this.reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), new[] { new UTF.TestCategoryAttribute("AsmLevel3") }, MemberTypes.All); this.reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), new[] { new UTF.TestCategoryAttribute("ClassLevel") }, MemberTypes.TypeInfo); this.reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), new[] { new UTF.TestCategoryAttribute("MethodLevel") }, MemberTypes.Method); - - var actual = this.reflectHelper.GetCategories(this.method.Object).ToArray(); + + var actual = this.reflectHelper.GetCategories(this.method.Object, typeof(ReflectHelperTests)).ToArray(); string[] expected = new[] { "MethodLevel", "ClassLevel", "AsmLevel1", "AsmLevel2", "AsmLevel3" }; CollectionAssert.AreEqual(expected, actual); } /// - /// Testing test category attributes adorned at calss, assembly and method level are getting collected. + /// Testing test category attributes adorned at class, assembly and method level are getting collected. /// [TestMethod] public void GetTestCategoryAttributeShouldConcatCustomAttributeOfSameType() @@ -92,7 +92,7 @@ public void GetTestCategoryAttributeShouldConcatCustomAttributeOfSameType() this.reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), new[] { new UTF.TestCategoryAttribute("MethodLevel1") }, MemberTypes.Method); this.reflectHelper.SetCustomAttribute(typeof(UTF.TestCategoryBaseAttribute), new[] { new UTF.TestCategoryAttribute("MethodLevel2") }, MemberTypes.Method); - var actual = this.reflectHelper.GetCategories(this.method.Object).ToArray(); + var actual = this.reflectHelper.GetCategories(this.method.Object, typeof(ReflectHelperTests)).ToArray(); string[] expected = new[] { "MethodLevel1", "MethodLevel2", "ClassLevel1", "ClassLevel2", "AsmLevel1", "AsmLevel2" }; CollectionAssert.AreEqual(expected, actual);