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

Set IsClassInitializeExecuted=true after base class initialize #705

Merged
merged 1 commit into from
Jun 5, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
77 changes: 28 additions & 49 deletions src/Adapter/MSTest.CoreAdapter/Execution/TestClassInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,53 +252,10 @@ public void RunClassInitialize(TestContext testContext)
MethodInfo initializeMethod = null;
string failedClassInitializeMethodName = string.Empty;

// If class initialization is done, just return
if (this.IsClassInitializeExecuted)
// If class initialization is not done, then do it.
if (!this.IsClassInitializeExecuted)
{
return;
}

// Acquiring a lock is usually a costly operation which does not need to be
// performed every time if the class init is already executed.
lock (this.testClassExecuteSyncObject)
{
// Perform a check again.
if (this.IsClassInitializeExecuted)
{
return;
}

try
{
// ClassInitialize methods for base classes are called in reverse order of discovery
// Base -> Child TestClass
var baseClassInitializeStack = new Stack<Tuple<MethodInfo, MethodInfo>>(
this.BaseClassInitAndCleanupMethods.Where(p => p.Item1 != null));

while (baseClassInitializeStack.Count > 0)
{
var baseInitCleanupMethods = baseClassInitializeStack.Pop();
initializeMethod = baseInitCleanupMethods.Item1;
initializeMethod?.InvokeAsSynchronousTask(null, testContext);

if (baseInitCleanupMethods.Item2 != null)
{
this.BaseClassCleanupMethodsStack.Push(baseInitCleanupMethods.Item2);
}
}
}
catch (Exception ex)
{
this.ClassInitializationException = ex;
failedClassInitializeMethodName = initializeMethod.Name;
}
}

// If class initialization is not done and class initialize method is not null,
// and class initialization exception is null, then do it.
if (!this.IsClassInitializeExecuted && this.classInitializeMethod != null && this.ClassInitializationException == null)
{
// Acquiring a lock is usually a costly operation which does not need to be
// Aquiring a lock is usually a costly operation which does not need to be
// performed every time if the class init is already executed.
lock (this.testClassExecuteSyncObject)
{
Expand All @@ -307,12 +264,34 @@ public void RunClassInitialize(TestContext testContext)
{
try
{
this.ClassInitializeMethod.InvokeAsSynchronousTask(null, testContext);
// ClassInitialize methods for base classes are called in reverse order of discovery
// Base -> Child TestClass
var baseClassInitializeStack = new Stack<Tuple<MethodInfo, MethodInfo>>(
this.BaseClassInitAndCleanupMethods.Where(p => p.Item1 != null));

while (baseClassInitializeStack.Count > 0)
{
var baseInitCleanupMethods = baseClassInitializeStack.Pop();
initializeMethod = baseInitCleanupMethods.Item1;
initializeMethod?.InvokeAsSynchronousTask(null, testContext);

if (baseInitCleanupMethods.Item2 != null)
{
this.BaseClassCleanupMethodsStack.Push(baseInitCleanupMethods.Item2);
}
}

initializeMethod = null;

if (this.classInitializeMethod != null)
{
this.ClassInitializeMethod.InvokeAsSynchronousTask(null, testContext);
}
}
catch (Exception ex)
{
this.ClassInitializationException = ex;
failedClassInitializeMethodName = this.ClassInitializeMethod.Name;
failedClassInitializeMethodName = initializeMethod?.Name ?? this.ClassInitializeMethod.Name;
}
finally
{
Expand Down Expand Up @@ -370,7 +349,7 @@ public string RunClassCleanup()
return null;
}

if (this.IsClassInitializeExecuted || this.ClassInitializeMethod is null || this.BaseClassCleanupMethodsStack.Any())
if (this.IsClassInitializeExecuted || this.ClassInitializeMethod is null)
{
try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Execution
using Assert = FrameworkV1::Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
using StringAssert = FrameworkV1::Microsoft.VisualStudio.TestTools.UnitTesting.StringAssert;
using TestClass = FrameworkV1::Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute;
using TestInitialize = FrameworkV1::Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute;
using TestMethod = FrameworkV1::Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute;
using UnitTestOutcome = Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel.UnitTestOutcome;
using UTF = FrameworkV2::Microsoft.VisualStudio.TestTools.UnitTesting;
Expand Down Expand Up @@ -57,6 +58,20 @@ public TestClassInfoTests()
this.testContext = new Mock<UTFExtension.TestContext>().Object;
}

[TestInitialize]
public void TestInitialize()
{
// Prevent leaking init/cleanup methods between classes
DummyGrandParentTestClass.ClassInitMethodBody = null;
DummyGrandParentTestClass.CleanupClassMethodBody = null;
DummyBaseTestClass.ClassInitializeMethodBody = null;
DummyBaseTestClass.ClassCleanupMethodBody = null;
DummyDerivedTestClass.DerivedClassInitializeMethodBody = null;
DummyDerivedTestClass.DerivedClassCleanupMethodBody = null;
DummyTestClass.ClassInitializeMethodBody = null;
DummyTestClass.ClassCleanupMethodBody = null;
}

[TestMethod]
public void TestClassInfoClassAttributeGetsAReferenceToTheTestClassAttribute()
{
Expand Down Expand Up @@ -265,6 +280,34 @@ public void RunClassInitializeShouldSetClassInitializeExecutedFlag()
Assert.IsTrue(this.testClassInfo.IsClassInitializeExecuted);
}

[TestMethod]
public void RunClassInitializeShouldOnlyRunOnce()
{
var classInitCallCount = 0;
DummyTestClass.ClassInitializeMethodBody = (tc) => classInitCallCount++;
this.testClassInfo.ClassInitializeMethod = typeof(DummyTestClass).GetMethod("ClassInitializeMethod");

this.testClassInfo.RunClassInitialize(this.testContext);
this.testClassInfo.RunClassInitialize(this.testContext);

Assert.AreEqual(1, classInitCallCount, "Class Initialize called only once");
}

[TestMethod]
public void RunClassInitializeShouldRunOnlyOnceIfThereIsNoDerivedClassInitializeAndSetClassInitializeExecutedFlag()
{
var classInitCallCount = 0;
DummyBaseTestClass.ClassInitializeMethodBody = (tc) => classInitCallCount++;
this.testClassInfo.BaseClassInitAndCleanupMethods.Enqueue(
Tuple.Create(typeof(DummyBaseTestClass).GetMethod("InitBaseClassMethod"), (MethodInfo)null));

this.testClassInfo.RunClassInitialize(this.testContext);
Assert.IsTrue(this.testClassInfo.IsClassInitializeExecuted);

this.testClassInfo.RunClassInitialize(this.testContext);
Assert.AreEqual(1, classInitCallCount);
}

[TestMethod]
public void RunClassInitializeShouldSetClassInitializationExceptionOnException()
{
Expand Down Expand Up @@ -309,8 +352,9 @@ public void RunClassInitializeShouldNotExecuteBaseClassInitializeMethodIfClassIn
this.testClassInfo.ClassInitializeMethod = typeof(DummyDerivedTestClass).GetMethod("InitDerivedClassMethod");

this.testClassInfo.RunClassInitialize(this.testContext);
this.testClassInfo.RunClassInitialize(this.testContext); // this one shouldn't run
Assert.IsTrue(this.testClassInfo.IsClassInitializeExecuted);

this.testClassInfo.RunClassInitialize(this.testContext); // this one shouldn't run
Assert.AreEqual(3, classInitCallCount);
}

Expand Down