Skip to content

Commit

Permalink
Support Scenario-Level Parallelization for MsTest
Browse files Browse the repository at this point in the history
  • Loading branch information
obligaron committed May 7, 2024
1 parent 3319863 commit 31dc28e
Show file tree
Hide file tree
Showing 15 changed files with 265 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* Fix for #81 in which Cucumber Expressions fail when two enums or two custom types with the same short name (differing namespaces) are used as parameters
* Fix: Adding @ignore to an Examples block generates invalid code for NUnit v3+ (#103)
* Fix: #111 @ignore attribute is not inherited to the scenarios from Rule
* Support Scenario-Level Parallelization for MsTest (`ExecutionScope.MethodLevel`)

# v1.0.1 - 2024-02-16

Expand Down
1 change: 1 addition & 0 deletions Reqnroll.Generator/Generation/GeneratorConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class GeneratorConstants
public const string TESTCLASS_CLEANUP_NAME = "FeatureTearDownAsync";
public const string BACKGROUND_NAME = "FeatureBackgroundAsync";
public const string TESTRUNNER_FIELD = "testRunner";
public const string FEATURETESTRUNNER_FIELD = "featureTestRunner";
public const string REQNROLL_NAMESPACE = "Reqnroll";
public const string SCENARIO_OUTLINE_EXAMPLE_TAGS_PARAMETER = "exampleTags";
public const string SCENARIO_TAGS_VARIABLE_NAME = "tagsOfScenario";
Expand Down
5 changes: 5 additions & 0 deletions Reqnroll.Generator/Generation/ScenarioPartHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ public CodeExpression GetTestRunnerExpression()
return new CodeVariableReferenceExpression(GeneratorConstants.TESTRUNNER_FIELD);
}

public CodeExpression GetTestFeatureRunnerExpression()
{
return new CodeVariableReferenceExpression(GeneratorConstants.FEATURETESTRUNNER_FIELD);
}

private CodeExpression GetStringArrayExpression(IEnumerable<string> items, ParameterSubstitution paramToIdentifier)
{
return new CodeArrayCreateExpression(typeof(string[]), items.Select(item => GetSubstitutedString(item, paramToIdentifier)).ToArray());
Expand Down
4 changes: 2 additions & 2 deletions Reqnroll.Generator/Generation/UnitTestFeatureGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ private void SetupTestClassInitializeMethod(TestClassGenerationContext generatio
_testGeneratorProvider.SetTestClassInitializeMethod(generationContext);

//testRunner = TestRunnerManager.GetTestRunnerForAssembly(null, [test_worker_id]);
var testRunnerField = _scenarioPartHelper.GetTestRunnerExpression();
var testRunnerField = generationContext.FeatureRunnerField == null ? _scenarioPartHelper.GetTestRunnerExpression() : _scenarioPartHelper.GetTestFeatureRunnerExpression();

var testRunnerParameters = new[]
{
Expand Down Expand Up @@ -229,7 +229,7 @@ private void SetupTestClassCleanupMethod(TestClassGenerationContext generationCo

_testGeneratorProvider.SetTestClassCleanupMethod(generationContext);

var testRunnerField = _scenarioPartHelper.GetTestRunnerExpression();
var testRunnerField = generationContext.FeatureRunnerField == null ? _scenarioPartHelper.GetTestRunnerExpression() : _scenarioPartHelper.GetTestFeatureRunnerExpression();

// await testRunner.OnFeatureEndAsync();
var expression = new CodeMethodInvokeExpression(
Expand Down
1 change: 1 addition & 0 deletions Reqnroll.Generator/TestClassGenerationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class TestClassGenerationContext
public CodeMemberMethod ScenarioCleanupMethod { get; private set; }
public CodeMemberMethod FeatureBackgroundMethod { get; private set; }
public CodeMemberField TestRunnerField { get; private set; }
public CodeMemberField FeatureRunnerField { get; internal set; }

public bool GenerateRowTests { get; private set; }

Expand Down
16 changes: 15 additions & 1 deletion Reqnroll.Generator/UnitTestProvider/MsTestGeneratorProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ public virtual void SetTestClass(TestClassGenerationContext generationContext, s
new CodeThisReferenceExpression(), TESTCONTEXT_FIELD_NAME), new CodePropertySetValueReferenceExpression()));

generationContext.TestClass.Members.Add(testContextProperty);

// Add a feature Test Runner
var field = new CodeMemberField(CodeDomHelper.GetGlobalizedTypeName(typeof(ITestRunner)), Generation.GeneratorConstants.FEATURETESTRUNNER_FIELD);
field.Attributes |= MemberAttributes.Static;
generationContext.FeatureRunnerField = field;
generationContext.TestClass.Members.Add(field);
}

public virtual void SetTestClassCategories(TestClassGenerationContext generationContext, IEnumerable<string> featureCategories)
Expand Down Expand Up @@ -101,7 +107,6 @@ public virtual void SetTestClassNonParallelizable(TestClassGenerationContext gen
public virtual void SetTestClassInitializeMethod(TestClassGenerationContext generationContext)
{
generationContext.TestClassInitializeMethod.Attributes |= MemberAttributes.Static;
generationContext.TestRunnerField.Attributes |= MemberAttributes.Static;

generationContext.TestClassInitializeMethod.Parameters.Add(new CodeParameterDeclarationExpression(
TESTCONTEXT_TYPE, "testContext"));
Expand All @@ -126,6 +131,15 @@ protected virtual void FixTestRunOrderingIssue(TestClassGenerationContext genera
{
//see https://github.com/reqnroll/Reqnroll/issues/96

var getTestRunnerExpression = new CodeMethodInvokeExpression(
new CodeVariableReferenceExpression(generationContext.FeatureRunnerField.Name),
nameof(ITestRunner.GetScenarioTestRunner));

generationContext.TestInitializeMethod.Statements.Add(
new CodeAssignStatement(
new CodeVariableReferenceExpression(generationContext.TestRunnerField.Name),
getTestRunnerExpression));

//if (testRunner.FeatureContext != null && testRunner.FeatureContext.FeatureInfo.Title != "<current_feature_title>")
// <TestClass>.<TestClassInitialize>(null);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ private void AddUnitTestProviderSpecificConfig()
break;
case UnitTestProvider.MSTest when _parallelTestExecution:
_project.AddFile(
new ProjectFile("MsTestConfiguration.cs", "Compile", "using Microsoft.VisualStudio.TestTools.UnitTesting; [assembly: Parallelize(Workers = 4, Scope = ExecutionScope.ClassLevel)]"));
new ProjectFile("MsTestConfiguration.cs", "Compile", "using Microsoft.VisualStudio.TestTools.UnitTesting; [assembly: Parallelize(Workers = 4, Scope = ExecutionScope.MethodLevel)]"));
break;
case UnitTestProvider.MSTest when !_parallelTestExecution:
_project.AddFile(new ProjectFile("MsTestConfiguration.cs", "Compile", "using Microsoft.VisualStudio.TestTools.UnitTesting; [assembly: DoNotParallelize]"));
Expand Down
2 changes: 2 additions & 0 deletions Reqnroll/ITestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,7 @@ public interface ITestRunner
Task ButAsync(string text, string multilineTextArg, Table tableArg, string keyword = null);

void Pending();

ITestRunner GetScenarioTestRunner();
}
}
49 changes: 49 additions & 0 deletions Reqnroll/Infrastructure/ContextManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ public void Reset()
}
}

private readonly ITestTracer testTracer;
private readonly IObjectContainer testThreadContainer;
private readonly InternalContextManager<ScenarioContext> scenarioContextManager;
private readonly InternalContextManager<FeatureContext> featureContextManager;
Expand All @@ -132,6 +133,7 @@ public ContextManager(ITestTracer testTracer, IObjectContainer testThreadContain
this.featureContextManager = new InternalContextManager<FeatureContext>(testTracer);
this.scenarioContextManager = new InternalContextManager<ScenarioContext>(testTracer);
this.stepContextManager = new StackedInternalContextManager<ScenarioStepContext>(testTracer);
this.testTracer = testTracer;
this.testThreadContainer = testThreadContainer;
this.containerBuilder = containerBuilder;

Expand Down Expand Up @@ -183,6 +185,16 @@ public void InitializeScenarioContext(ScenarioInfo scenarioInfo)
{
var scenarioContainer = containerBuilder.CreateScenarioContainer(FeatureContext.FeatureContainer, scenarioInfo);
var newContext = scenarioContainer.Resolve<ScenarioContext>();

if (isSecnarioContextManager)
{
scenarioContainer.RegisterInstanceAs(this, typeof(IContextManager));
if (scenarioTestExecutionEngine != null)
scenarioContainer.RegisterInstanceAs(scenarioTestExecutionEngine, typeof(ITestExecutionEngine));
if (scenarioTestRunner != null)
scenarioContainer.RegisterInstanceAs(scenarioTestRunner, typeof(ITestRunner));
}

scenarioContextManager.Init(newContext, scenarioContainer);
#pragma warning disable 618
ScenarioContext.Current = newContext;
Expand All @@ -191,6 +203,23 @@ public void InitializeScenarioContext(ScenarioInfo scenarioInfo)
ResetCurrentStepStack();
}

bool isSecnarioContextManager;
ITestExecutionEngine scenarioTestExecutionEngine;
ITestRunner scenarioTestRunner;

public void InitScenarioExecutionEngine(ITestExecutionEngine testExecutionEngine)
{
scenarioTestExecutionEngine = testExecutionEngine;
if (scenarioContextManager.Instance?.ScenarioContainer != null)
scenarioContextManager.Instance.ScenarioContainer.RegisterInstanceAs(testExecutionEngine, typeof(ITestExecutionEngine));
}
public void InitScenarioRunner(ITestRunner testRunner)
{
scenarioTestRunner = testRunner;
if (scenarioContextManager.Instance?.ScenarioContainer != null)
scenarioContextManager.Instance.ScenarioContainer.RegisterInstanceAs(testRunner, typeof(ITestRunner));
}

private void ResetCurrentStepStack()
{
stepContextManager.Reset();
Expand Down Expand Up @@ -225,5 +254,25 @@ public void Dispose()
scenarioContextManager?.Dispose();
stepContextManager?.Dispose();
}

public ContextManager(ContextManager featureContextManager)
{
this.testTracer = featureContextManager.testTracer;
this.testThreadContainer = featureContextManager.testThreadContainer;
this.containerBuilder = featureContextManager.containerBuilder;
this.isSecnarioContextManager = true;

this.featureContextManager = featureContextManager.featureContextManager;
this.scenarioContextManager = new InternalContextManager<ScenarioContext>(testTracer);

this.stepContextManager = new StackedInternalContextManager<ScenarioStepContext>(testTracer);

InitializeTestThreadContext();
}

public IContextManager GetScenarioContextManager()
{
return new ContextManager(this);
}
}
}
5 changes: 5 additions & 0 deletions Reqnroll/Infrastructure/IContextManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ public interface IContextManager
ScenarioStepContext StepContext { get; }
StepDefinitionType? CurrentTopLevelStepDefinitionType { get; }

void InitScenarioExecutionEngine(ITestExecutionEngine testExecutionEngine);
void InitScenarioRunner(ITestRunner testRunner);

void InitializeFeatureContext(FeatureInfo featureInfo);
void CleanupFeatureContext();

Expand All @@ -18,5 +21,7 @@ public interface IContextManager

void InitializeStepContext(StepInfo stepInfo);
void CleanupStepContext();

IContextManager GetScenarioContextManager();
}
}
3 changes: 3 additions & 0 deletions Reqnroll/Infrastructure/ITestExecutionEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@ public interface ITestExecutionEngine
Task StepAsync(StepDefinitionKeyword stepDefinitionKeyword, string keyword, string text, string multilineTextArg, Table tableArg);

void Pending();

ITestExecutionEngine GetScenarioExecutionEngine();
void InitScenarioRunner(ITestRunner testRunner);
}
}
18 changes: 18 additions & 0 deletions Reqnroll/Infrastructure/TestExecutionEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,24 @@ public virtual void Pending()
throw _errorProvider.GetPendingStepDefinitionError();
}

public virtual ITestExecutionEngine GetScenarioExecutionEngine()
{
var scenarioContextManager = _contextManager.GetScenarioContextManager();

var scenarioEngine = new TestExecutionEngine(_stepFormatter, _testTracer, _errorProvider, _stepArgumentTypeConverter, _reqnrollConfiguration, _bindingRegistry, _unitTestRuntimeProvider, scenarioContextManager,
_stepDefinitionMatchService, _bindingInvoker, _obsoleteStepHandler, _analyticsEventProvider, _analyticsTransmitter, _testRunnerManager,
_runtimePluginTestExecutionLifecycleEventEmitter, _testThreadExecutionEventPublisher, _testPendingMessageFactory, _testUndefinedMessageFactory, _testObjectResolver, _testRunContext);

scenarioContextManager.InitScenarioExecutionEngine(scenarioEngine);

return scenarioEngine;
}

public virtual void InitScenarioRunner(ITestRunner testRunner)
{
_contextManager.InitScenarioRunner(testRunner);
}

protected virtual async Task OnBlockStartAsync(ScenarioBlock block)
{
if (block == ScenarioBlock.None)
Expand Down
7 changes: 7 additions & 0 deletions Reqnroll/TestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,12 @@ public void Pending()
{
_executionEngine.Pending();
}

public ITestRunner GetScenarioTestRunner()
{
var testRunner = new TestRunner(_executionEngine.GetScenarioExecutionEngine());
testRunner._executionEngine.InitScenarioRunner(testRunner);
return testRunner;
}
}
}
Loading

0 comments on commit 31dc28e

Please sign in to comment.