Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply TestCategory from derived class on inherited test methods #513

Merged
merged 15 commits into from
Apr 9, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Adapter/MSTest.CoreAdapter/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -90,6 +92,7 @@ internal static class Constants
/// These Property names should not be localized.
/// </summary>
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";
Expand Down
4 changes: 2 additions & 2 deletions src/Adapter/MSTest.CoreAdapter/Discovery/TypeEnumerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
22 changes: 19 additions & 3 deletions src/Adapter/MSTest.CoreAdapter/Execution/TypeCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -606,10 +606,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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
20 changes: 11 additions & 9 deletions src/Adapter/MSTest.CoreAdapter/Helpers/ReflectHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -313,10 +313,11 @@ internal virtual bool IsMethodDeclaredInSameAssemblyAsType(MethodInfo method, Ty
/// Get categories applied to the test method
/// </summary>
/// <param name="categoryAttributeProvider">The member to inspect.</param>
/// <param name="owningType">The reflected type that owns <paramref name="categoryAttributeProvider"/>.</param>
/// <returns>Categories defined.</returns>
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<string> testCategories = new List<string>();

if (categories != null)
Expand Down Expand Up @@ -344,11 +345,12 @@ internal ParallelizeAttribute GetParallelizeAttribute(Assembly assembly)
/// Get the parallelization behavior for a test method.
/// </summary>
/// <param name="testMethod">Test method.</param>
/// <param name="owningType">The type that owns <paramref name="testMethod"/>.</param>
/// <returns>True if test method should not run in parallel.</returns>
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();
}

/// <summary>
Expand All @@ -365,19 +367,20 @@ internal bool IsDoNotParallelizeSet(Assembly assembly)
/// Gets custom attributes at the class and assembly for a method.
/// </summary>
/// <param name="attributeProvider">Method Info or Member Info or a Type</param>
/// <param name="owningType">The type that owns <paramref name="attributeProvider"/>.</param>
/// <param name="type"> What type of CustomAttribute you need. For instance: TestCategory, Owner etc.,</param>
/// <returns>The categories of the specified type on the method. </returns>
internal IEnumerable<object> GetCustomAttributesRecursively(MemberInfo attributeProvider, Type type)
internal IEnumerable<object> 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(), typeof(TestCategoryBaseAttribute))).ToArray();
}

if (categories != null)
Expand All @@ -399,8 +402,7 @@ internal virtual Attribute[] GetCustomAttributeForAssembly(MemberInfo memberInfo
{
return
PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes(
memberInfo.DeclaringType.GetTypeInfo().Assembly,
type).OfType<Attribute>().ToArray();
memberInfo.Module.Assembly, type).OfType<Attribute>().ToArray();
}

/// <summary>
Expand Down
3 changes: 2 additions & 1 deletion src/Adapter/MSTest.CoreAdapter/ObjectModel/TestMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ public string DeclaringAssemblyName
}

/// <summary>
/// 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.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions src/Adapter/MSTest.CoreAdapter/ObjectModel/UnitTestElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public interface ITestMethod
string FullClassName { get; }

/// <summary>
/// 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.
/// </summary>
string DeclaringClassFullName { get; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, object>()),
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<string, object>()),
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
Expand Down Expand Up @@ -1311,6 +1353,10 @@ public void TestMethodWithMultipleExpectedException()
[DummyTestClass]
internal class DerivedTestClass : BaseTestClass
{
[UTF.TestMethod]
public new void OverloadedTestMethod()
{
}
}

internal class BaseTestClass
Expand All @@ -1319,6 +1365,11 @@ internal class BaseTestClass
public void DummyTestMethod()
{
}

[UTF.TestMethod]
public void OverloadedTestMethod()
{
}
}

private class DummyTestClassWithNoDefaultConstructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ public void IntializeTests()
this.reflectHelper = new TestableReflectHelper();
this.method = new Mock<MethodInfo>();
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();
Expand All @@ -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);
}
Expand All @@ -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);
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}

Expand All @@ -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);
}
Expand Down
Loading