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

Class Initialize inheritance #143 #577

Merged
merged 24 commits into from
Jul 8, 2019
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
41362e3
Implemented Initialize Inheritance for ClassInitialize attribute
parrainc Oct 17, 2018
8de73ff
Implemented Cleanup inheritance behavior for ClassCleanup attr
parrainc Nov 7, 2018
cd2b1e1
Merge branch 'master' of https://github.com/Microsoft/testfx into iss…
parrainc Nov 7, 2018
5d65c80
Merge branch 'master' of https://github.com/Microsoft/testfx into iss…
parrainc Mar 8, 2019
0042875
Removed OnceBeforeAnyDerivedClasses from ClassInitializeInheritance enum
parrainc Mar 9, 2019
9e2e653
Changed UTF.TestClass for custom testclass attr to avoid test ouput w…
parrainc Mar 10, 2019
5e527d5
Updated RunClassCleanup to prevent running base cleanup without havin…
parrainc Mar 10, 2019
50aae98
Fix name typo
parrainc Mar 10, 2019
b258d16
added doc for BaseClassInitializeMethodsDict prop
parrainc Mar 10, 2019
cf6f872
Updates per review comments
parrainc Mar 14, 2019
61c1c36
Merge branch 'master' of https://github.com/Microsoft/testfx into iss…
parrainc Mar 14, 2019
198b7ac
Merge branch 'master' into issue143-classinitializeinh
jayaranigarg Apr 3, 2019
f951588
Merge branch 'master' of https://github.com/Microsoft/testfx into iss…
parrainc Apr 6, 2019
32783e6
Merge branch 'issue143-classinitializeinh' of https://github.com/parr…
parrainc Apr 6, 2019
54ed1f9
Merge branch 'master' into issue143-classinitializeinh
jayaranigarg Apr 9, 2019
5ffaeb6
Merge branch 'master' into issue143-classinitializeinh
jayaranigarg Apr 9, 2019
63f3884
Merge branch 'master' of https://github.com/Microsoft/testfx into iss…
parrainc Apr 10, 2019
2f9264f
Merge branch 'issue143-classinitializeinh' of https://github.com/parr…
parrainc Apr 10, 2019
a6dd345
making inheritance behavior enum more generic and updating tests
parrainc Apr 10, 2019
a256006
done some refactoring and added/fixed a few tests
parrainc Apr 10, 2019
82aad2c
updates as per review comments
parrainc Apr 13, 2019
987ca72
updated classinfo and tests
parrainc Apr 13, 2019
023d041
updated classcleanup 1-param ctor
parrainc Apr 13, 2019
38d28fe
updating doc message for 1-param ctors
parrainc Apr 13, 2019
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
87 changes: 73 additions & 14 deletions src/Adapter/MSTest.CoreAdapter/Execution/TestClassInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand Down Expand Up @@ -48,8 +49,11 @@ internal TestClassInfo(
this.ClassType = type;
this.Constructor = constructor;
this.TestContextProperty = testContextProperty;
this.BaseClassInitializeMethodsDict = new Dictionary<MethodInfo, bool>();
parrainc marked this conversation as resolved.
Show resolved Hide resolved
this.BaseTestInitializeMethodsQueue = new Queue<MethodInfo>();
this.BaseTestCleanupMethodsQueue = new Queue<MethodInfo>();
this.BaseClassInitializeMethodsQueue = new Queue<MethodInfo>();
this.BaseClassCleanupMethodsQueue = new Queue<MethodInfo>();
this.Parent = parent;
this.ClassAttribute = classAttribute;
this.testClassExecuteSyncObject = new object();
Expand Down Expand Up @@ -107,6 +111,11 @@ internal set
/// </summary>
public bool IsClassInitializeExecuted { get; internal set; }

/// <summary>
/// Gets a dictionary of test initialize methods indicating if they have been executed.
parrainc marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public Dictionary<MethodInfo, bool> BaseClassInitializeMethodsDict { get; internal set; }

/// <summary>
/// Gets the exception thrown during <see cref="ClassInitializeAttribute"/> method invocation.
/// </summary>
Expand Down Expand Up @@ -157,6 +166,16 @@ public bool HasExecutableCleanupMethod
}
}

/// <summary>
/// Gets a queue of test initialize methods to call for this type.
/// </summary>
public Queue<MethodInfo> BaseClassInitializeMethodsQueue { get; private set; }
parrainc marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Gets a queue of class cleanup methods to call for this type.
/// </summary>
public Queue<MethodInfo> BaseClassCleanupMethodsQueue { get; private set; }

/// <summary>
/// Gets the test initialize method.
/// </summary>
Expand Down Expand Up @@ -219,19 +238,47 @@ internal set
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
public void RunClassInitialize(TestContext testContext)
{
// If no class initialize return
if (this.ClassInitializeMethod == null)
// If no class initialize and no base class initialize, return
if (this.ClassInitializeMethod is null)
{
return;
if (this.BaseClassInitializeMethodsQueue.Count == 0)
parrainc marked this conversation as resolved.
Show resolved Hide resolved
parrainc marked this conversation as resolved.
Show resolved Hide resolved
{
return;
}
}

if (testContext == null)
{
throw new NullReferenceException(Resource.TestContextIsNull);
}

// If class initialization is not done, then do it.
if (!this.IsClassInitializeExecuted)
MethodInfo initializeMethod = null;
try
{
// ClassInitialize methods for base classes are called in reverse order of discovery
// Base -> Child TestClass
var baseClassInitializeStack = new Stack<MethodInfo>(this.BaseClassInitializeMethodsQueue);
while (baseClassInitializeStack.Count > 0)
{
parrainc marked this conversation as resolved.
Show resolved Hide resolved
initializeMethod = baseClassInitializeStack.Pop();
if (initializeMethod.GetCustomAttribute<ClassInitializeAttribute>().InheritanceBehavior
parrainc marked this conversation as resolved.
Show resolved Hide resolved
is ClassInitializeInheritance.BeforeEachDerivedClass)
{
initializeMethod?.InvokeAsSynchronousTask(null, testContext);
if (!this.BaseClassInitializeMethodsDict.ContainsKey(initializeMethod))
{
this.BaseClassInitializeMethodsDict.Add(initializeMethod, true);
}
}
}
}
catch (Exception ex)
{
this.ClassInitializationException = ex;
}

// If class initialization is not done and class initialize method is not null, then do it.
if (!this.IsClassInitializeExecuted && this.classInitializeMethod != null)
parrainc marked this conversation as resolved.
Show resolved Hide resolved
{
// Aquiring a lock is usually a costly operation which does not need to be
parrainc marked this conversation as resolved.
Show resolved Hide resolved
// performed every time if the class init is already executed.
Expand Down Expand Up @@ -271,15 +318,13 @@ public void RunClassInitialize(TestContext testContext)
var realException = this.ClassInitializationException.InnerException ?? this.ClassInitializationException;

var outcome = UnitTestOutcome.Failed;
string errorMessage = null;
StackTraceInformation exceptionStackTraceInfo = null;
if (!realException.TryGetUnitTestAssertException(out outcome, out errorMessage, out exceptionStackTraceInfo))
if (!realException.TryGetUnitTestAssertException(out outcome, out string errorMessage, out StackTraceInformation exceptionStackTraceInfo))
{
errorMessage = string.Format(
CultureInfo.CurrentCulture,
Resource.UTA_ClassInitMethodThrows,
this.ClassType.FullName,
this.ClassInitializeMethod.Name,
this.ClassInitializeMethod?.Name ?? initializeMethod.Name,
parrainc marked this conversation as resolved.
Show resolved Hide resolved
realException.GetType().ToString(),
StackTraceHelper.GetExceptionMessage(realException));

Expand All @@ -298,19 +343,33 @@ public void RunClassInitialize(TestContext testContext)
/// <returns>
/// Any exception that can be thrown as part of a class cleanup as warning messages.
/// </returns>
[SuppressMessageAttribute("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
parrainc marked this conversation as resolved.
Show resolved Hide resolved
public string RunClassCleanup()
{
if (this.ClassCleanupMethod == null)
if (this.ClassCleanupMethod is null)
{
return null;
if (this.BaseClassCleanupMethodsQueue.Count == 0)
parrainc marked this conversation as resolved.
Show resolved Hide resolved
{
return null;
}
}

if (this.IsClassInitializeExecuted || this.ClassInitializeMethod == null)
if (this.IsClassInitializeExecuted || this.ClassInitializeMethod is null || this.BaseClassInitializeMethodsDict.ContainsValue(true))
{
var classCleanupMethod = this.classCleanupMethod;
parrainc marked this conversation as resolved.
Show resolved Hide resolved
try
{
this.ClassCleanupMethod.InvokeAsSynchronousTask(null);
classCleanupMethod?.InvokeAsSynchronousTask(null);
var baseClassCleanupQueue = new Queue<MethodInfo>(this.BaseClassCleanupMethodsQueue);
while (baseClassCleanupQueue.Count > 0)
{
classCleanupMethod = baseClassCleanupQueue.Dequeue();
if (this.BaseClassInitializeMethodsDict.Any(method => method.Key.DeclaringType
== classCleanupMethod.DeclaringType && method.Value))
parrainc marked this conversation as resolved.
Show resolved Hide resolved
{
classCleanupMethod?.InvokeAsSynchronousTask(null);
}
}

return null;
}
Expand Down
18 changes: 18 additions & 0 deletions src/Adapter/MSTest.CoreAdapter/Execution/TypeCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,24 @@ private TestClassInfo CreateClassInfo(Type classType, TestMethod testMethod)
// Update test initialize/cleanup method from base type.
this.UpdateInfoIfTestInitializeOrCleanupMethod(classInfo, methodInfo, true, instanceMethods, testInitializeAttributeType, testCleanupAttributeType);
}

if (this.IsAssemblyOrClassInitializeMethod(methodInfo, classInitializeAttributeType))
parrainc marked this conversation as resolved.
Show resolved Hide resolved
{
if (methodInfo.GetCustomAttribute<ClassInitializeAttribute>().InheritanceBehavior == ClassInitializeInheritance.None)
{
// if the ClassInitializeInheritance is None, means
// it will pick only the derived class initalize method
break;
parrainc marked this conversation as resolved.
Show resolved Hide resolved
}

// update class initialize queue with new method
classInfo.BaseClassInitializeMethodsQueue.Enqueue(methodInfo);
}
else if (this.IsAssemblyOrClassCleanupMethod(methodInfo, classCleanupAttributeType))
{
// update class cleanup queue with new method
classInfo.BaseClassCleanupMethodsQueue.Enqueue(methodInfo);
}
}

baseType = baseType.GetTypeInfo().BaseType;
Expand Down
34 changes: 32 additions & 2 deletions src/TestFramework/MSTest.Core/Attributes/VSTestAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;

#pragma warning disable SA1402 // FileMayOnlyContainASingleType
#pragma warning disable SA1649 // SA1649FileNameMustMatchTypeName
Expand All @@ -25,6 +23,24 @@ public enum TestTimeout
Infinite = int.MaxValue
}

/// <summary>
/// Enumeration for class initialize, that can be used with the <see cref="ClassInitializeAttribute"/> class.
/// Defines the behavior of the ClassInitialize methods of base classes.
/// The type of the enumeration must match
/// </summary>
public enum ClassInitializeInheritance
parrainc marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// None.
/// </summary>
None,

/// <summary>
/// Before each derived class.
/// </summary>
BeforeEachDerivedClass
}

/// <summary>
/// The test class attribute.
/// </summary>
Expand Down Expand Up @@ -156,6 +172,20 @@ public TestPropertyAttribute(string name, string value)
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class ClassInitializeAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="ClassInitializeAttribute"/> class.
/// ClassInitializeAttribute
/// </summary>
/// <param name="inheritanceBehavior">none</param>
public ClassInitializeAttribute(ClassInitializeInheritance inheritanceBehavior = ClassInitializeInheritance.None)
{
this.InheritanceBehavior = inheritanceBehavior;
}

/// <summary>
/// Gets InheritanceBehavior
/// </summary>
public ClassInitializeInheritance InheritanceBehavior { get; private set; }
}

/// <summary>
Expand Down
Loading