Skip to content

Commit

Permalink
Prototype to support method-level parallelization for MsTest
Browse files Browse the repository at this point in the history
  • Loading branch information
gasparnagy committed Oct 3, 2024
1 parent 83624ed commit d56b04f
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 13 deletions.
122 changes: 112 additions & 10 deletions Reqnroll.Generator/Generation/UnitTestFeatureGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,16 +189,16 @@ private void SetupTestClassInitializeMethod(TestClassGenerationContext generatio
//FeatureInfo featureInfo = new FeatureInfo("xxxx");
testClassInitializeMethod.Statements.Add(
new CodeVariableDeclarationStatement(_codeDomHelper.GetGlobalizedTypeName(typeof(FeatureInfo)), "featureInfo",
new CodeObjectCreateExpression(_codeDomHelper.GetGlobalizedTypeName(typeof(FeatureInfo)),
new CodeObjectCreateExpression(typeof(CultureInfo),
new CodePrimitiveExpression(generationContext.Feature.Language)),
new CodePrimitiveExpression(generationContext.Document.DocumentLocation?.FeatureFolderPath),
new CodePrimitiveExpression(generationContext.Feature.Name),
new CodePrimitiveExpression(generationContext.Feature.Description),
new CodeFieldReferenceExpression(
new CodeTypeReferenceExpression(_codeDomHelper.GetGlobalizedTypeName(typeof(Reqnroll.ProgrammingLanguage))),
_codeDomHelper.TargetLanguage.ToString()),
new CodeFieldReferenceExpression(null, GeneratorConstants.FEATURE_TAGS_VARIABLE_NAME))));
new CodeObjectCreateExpression(_codeDomHelper.GetGlobalizedTypeName(typeof(FeatureInfo)),
new CodeObjectCreateExpression(typeof(CultureInfo),
new CodePrimitiveExpression(generationContext.Feature.Language)),
new CodePrimitiveExpression(generationContext.Document.DocumentLocation?.FeatureFolderPath),
new CodePrimitiveExpression(generationContext.Feature.Name),
new CodePrimitiveExpression(generationContext.Feature.Description),
new CodeFieldReferenceExpression(
new CodeTypeReferenceExpression(_codeDomHelper.GetGlobalizedTypeName(typeof(Reqnroll.ProgrammingLanguage))),
_codeDomHelper.TargetLanguage.ToString()),
new CodeFieldReferenceExpression(null, GeneratorConstants.FEATURE_TAGS_VARIABLE_NAME))));

//await testRunner.OnFeatureStartAsync(featureInfo);
var onFeatureStartExpression = new CodeMethodInvokeExpression(
Expand Down Expand Up @@ -257,6 +257,97 @@ private void SetupTestInitializeMethod(TestClassGenerationContext generationCont
_codeDomHelper.MarkCodeMemberMethodAsAsync(testInitializeMethod);

_testGeneratorProvider.SetTestInitializeMethod(generationContext);

if (generationContext.UnitTestGeneratorProvider is not MsTestGeneratorProvider)
return; // only MsTest is implemented in this prototype

// Step 4: Obtain the test runner for executing a single test

// testRunner = global::Reqnroll.TestRunnerManager.GetTestRunnerForAssembly();

var testRunnerField = _scenarioPartHelper.GetTestRunnerExpression();

var getTestRunnerExpression = new CodeMethodInvokeExpression(
new CodeTypeReferenceExpression(_codeDomHelper.GetGlobalizedTypeName(typeof(TestRunnerManager))),
nameof(TestRunnerManager.GetTestRunnerForAssembly));

testInitializeMethod.Statements.Add(
new CodeAssignStatement(
testRunnerField,
getTestRunnerExpression));


// Step 5 (part 1): "Finish" current feature if needed & "Start" feature if needed
// The similar code in custom test runner codes is not needed
// The feature initialization steps are copied from TestClassInitializeMethod

//if (testRunner.FeatureContext != null && testRunner.FeatureContext.FeatureInfo.Title != "<current_feature_title>")
// await testRunner.OnFeatureEndAsync(); // finish if different

var featureContextExpression = new CodePropertyReferenceExpression(
testRunnerField,
"FeatureContext");

var onFeatureEndAsyncExpression = new CodeMethodInvokeExpression(
testRunnerField,
nameof(ITestRunner.OnFeatureEndAsync));
_codeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(onFeatureEndAsyncExpression);

//if (testRunner.FeatureContext != null && testRunner.FeatureContext.FeatureInfo.Title != "<current_feature_title>")
// await testRunner.OnFeatureEndAsync(); // finish if different
testInitializeMethod.Statements.Add(
new CodeConditionStatement(
new CodeBinaryOperatorExpression(
new CodeBinaryOperatorExpression(
featureContextExpression,
CodeBinaryOperatorType.IdentityInequality,
new CodePrimitiveExpression(null)),
CodeBinaryOperatorType.BooleanAnd,
new CodeBinaryOperatorExpression(
new CodePropertyReferenceExpression(
new CodePropertyReferenceExpression(
featureContextExpression,
"FeatureInfo"),
"Title"),
CodeBinaryOperatorType.IdentityInequality,
new CodePrimitiveExpression(generationContext.Feature.Name))),
new CodeExpressionStatement(
onFeatureEndAsyncExpression)));


//if (testRunner.FeatureContext == null) {
// FeatureInfo featureInfo = new FeatureInfo("xxxx");
// await testRunner.OnFeatureStartAsync(featureInfo);
//}

var featureInfoInitializeStatement =
new CodeVariableDeclarationStatement(_codeDomHelper.GetGlobalizedTypeName(typeof(FeatureInfo)), "featureInfo",
new CodeObjectCreateExpression(_codeDomHelper.GetGlobalizedTypeName(typeof(FeatureInfo)),
new CodeObjectCreateExpression(typeof(CultureInfo),
new CodePrimitiveExpression(generationContext.Feature.Language)),
new CodePrimitiveExpression(generationContext.Document.DocumentLocation?.FeatureFolderPath),
new CodePrimitiveExpression(generationContext.Feature.Name),
new CodePrimitiveExpression(generationContext.Feature.Description),
new CodeFieldReferenceExpression(
new CodeTypeReferenceExpression(_codeDomHelper.GetGlobalizedTypeName(typeof(Reqnroll.ProgrammingLanguage))),
_codeDomHelper.TargetLanguage.ToString()),
new CodeFieldReferenceExpression(null, GeneratorConstants.FEATURE_TAGS_VARIABLE_NAME)));

var onFeatureStartExpression = new CodeMethodInvokeExpression(
testRunnerField,
nameof(ITestRunner.OnFeatureStartAsync),
new CodeVariableReferenceExpression("featureInfo"));
_codeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(onFeatureStartExpression);

testInitializeMethod.Statements.Add(
new CodeConditionStatement(
new CodeBinaryOperatorExpression(
featureContextExpression,
CodeBinaryOperatorType.IdentityEquality,
new CodePrimitiveExpression(null)),
featureInfoInitializeStatement,
new CodeExpressionStatement(
onFeatureStartExpression)));
}

private void SetupTestCleanupMethod(TestClassGenerationContext generationContext)
Expand All @@ -280,6 +371,17 @@ private void SetupTestCleanupMethod(TestClassGenerationContext generationContext
_codeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(expression);

testCleanupMethod.Statements.Add(expression);

if (generationContext.UnitTestGeneratorProvider is not MsTestGeneratorProvider)
return; // only MsTest is implemented in this prototype

// Step 6: "Release" the TestRunner, so that other threads can pick it up (moved from TestClassCleanupMethod)
// TestRunnerManager.ReleaseTestRunner(testRunner);
testCleanupMethod.Statements.Add(
new CodeMethodInvokeExpression(
new CodeTypeReferenceExpression(_codeDomHelper.GetGlobalizedTypeName(typeof(TestRunnerManager))),
nameof(TestRunnerManager.ReleaseTestRunner),
testRunnerField));
}

private void SetupScenarioInitializeMethod(TestClassGenerationContext generationContext)
Expand Down
15 changes: 12 additions & 3 deletions Reqnroll.Generator/UnitTestProvider/MsTestGeneratorProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,16 @@ public virtual void SetTestClassNonParallelizable(TestClassGenerationContext gen
public virtual void SetTestClassInitializeMethod(TestClassGenerationContext generationContext)
{
generationContext.TestClassInitializeMethod.Attributes |= MemberAttributes.Static;
generationContext.TestRunnerField.Attributes |= MemberAttributes.Static;
// Step 1: make 'testRunner' instance field
//generationContext.TestRunnerField.Attributes |= MemberAttributes.Static;

generationContext.TestClassInitializeMethod.Parameters.Add(new CodeParameterDeclarationExpression(
TESTCONTEXT_TYPE, "testContext"));

CodeDomHelper.AddAttribute(generationContext.TestClassInitializeMethod, TESTFIXTURESETUP_ATTR);

// Step 2: Remove TestClassInitializeMethod (not needed)
generationContext.TestClass.Members.Remove(generationContext.TestClassInitializeMethod);
}

public void SetTestClassCleanupMethod(TestClassGenerationContext generationContext)
Expand All @@ -117,13 +121,18 @@ public void SetTestClassCleanupMethod(TestClassGenerationContext generationConte
// [Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute(Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupBehavior.EndOfClass)]
var attribute = CodeDomHelper.AddAttribute(generationContext.TestClassCleanupMethod, TESTFIXTURETEARDOWN_ATTR);
attribute.Arguments.Add(new CodeAttributeArgument(new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(CLASSCLEANUPBEHAVIOR_ENUM), CLASSCLEANUPBEHAVIOR_ENDOFCLASS)));

// Step 3: Remove TestClassCleanupMethod (not needed)
generationContext.TestClass.Members.Remove(generationContext.TestClassCleanupMethod);
}


public virtual void SetTestInitializeMethod(TestClassGenerationContext generationContext)
{
CodeDomHelper.AddAttribute(generationContext.TestInitializeMethod, TESTSETUP_ATTR);
FixTestRunOrderingIssue(generationContext);

// Step 5 (part 2): remove feature handling from here as it is moved to UnitTestGenerator.SetupTestInitializeMethod
//FixTestRunOrderingIssue(generationContext);
}

protected virtual void FixTestRunOrderingIssue(TestClassGenerationContext generationContext)
Expand All @@ -146,7 +155,7 @@ protected virtual void FixTestRunOrderingIssue(TestClassGenerationContext genera
new CodePrimitiveExpression(null));

CodeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(callTestClassInitializeMethodExpression);

generationContext.TestInitializeMethod.Statements.Add(
new CodeConditionStatement(
new CodeBinaryOperatorExpression(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public class TraceSteps
featureData.Duration.ElapsedMilliseconds.Should().BeLessThan(9 * WaitTimeInMS, because: "Test should be processed (parallel) in time");
}
public static int startIndex = 0;
[When(@"I do something in Scenario '(.*)'")]
void WhenIDoSomething(string scenario)
{
Expand All @@ -82,6 +84,10 @@ public class TraceSteps
featureData.BindingInstances.TryAdd(this, 1);
var currentStartIndex = Interlocked.Increment(ref featureData.StepCount);
_traceListener.WriteTestOutput($"Start index: {currentStartIndex}, Worker: {_testRunner.TestWorkerId}");
var currentStartIndex2 = System.Threading.Interlocked.Increment(ref startIndex);
_traceListener.WriteTestOutput($"Start index2: {currentStartIndex2}, Worker: {_testRunner.TestWorkerId}");
Thread.Sleep(WaitTimeInMS);
var afterStartIndex = featureData.StepCount;
if (afterStartIndex == currentStartIndex)
Expand All @@ -92,6 +98,16 @@ public class TraceSteps
{
_traceListener.WriteTestOutput("Was parallel");
}
var afterStartIndex2 = startIndex;
if (afterStartIndex2 == currentStartIndex2)
{
_traceListener.WriteTestOutput("XWas not parallel");
}
else
{
_traceListener.WriteTestOutput("XWas parallel");
}
}
}
"""
Expand Down

0 comments on commit d56b04f

Please sign in to comment.