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

Improve code-behind feature file compilation speed #336

Merged
merged 4 commits into from
Dec 2, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Improvements:

* Enhance BoDi error handling to provide the name of the interface being registered when that interface has already been resolved (#324)
* Improve code-behind feature file compilation speed (#336)

## Bug fixes:

Expand Down
12 changes: 11 additions & 1 deletion Reqnroll.Generator/Generation/ScenarioPartHelper.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
Expand Down Expand Up @@ -94,9 +95,18 @@ public void GenerateStep(TestClassGenerationContext generationContext, List<Code

using (new SourceLineScope(_reqnrollConfiguration, _codeDomHelper, statements, generationContext.Document.SourceFilePath, gherkinStep.Location))
{
string stepKeyWord = scenarioStep.StepKeyword switch
{
StepKeyword.Given => "Given",
StepKeyword.When => "When",
StepKeyword.Then => "Then",
StepKeyword.And => "And",
StepKeyword.But => "But",
_ => throw new NotImplementedException(),
};
var expression = new CodeMethodInvokeExpression(
testRunnerField,
scenarioStep.StepKeyword + "Async",
stepKeyWord + "Async",
arguments.ToArray());

_codeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(expression);
Expand Down
13 changes: 9 additions & 4 deletions Reqnroll.Generator/TestGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ protected string GetGeneratedTestCode(FeatureFileInput featureFileInput)

outputWriter.Flush();
var generatedTestCode = outputWriter.ToString();
return FixVb(generatedTestCode);
if (codeDomHelper.TargetLanguage == CodeDomProviderLanguage.VB)
generatedTestCode = FixVb(generatedTestCode);
return generatedTestCode;
}
}

Expand All @@ -121,14 +123,17 @@ private string FixVBNetGlobalNamespace(string generatedTestCode)
.Replace("Global.GlobalVBNetNamespace", "Global")
.Replace("GlobalVBNetNamespace", "Global");
}


static readonly Lazy<Regex> VBFixAsyncFunctionRegex = new Lazy<Regex>(() => new Regex(@"^([^\n]*)Function[ ]*([^(\n]*)(\([^\n]*\)[ ]*As) async([^\n]*)$", RegexOptions.Multiline));
static readonly Lazy<Regex> VBFixAsyncSubRegex = new Lazy<Regex>(() => new Regex(@"^([^\n]*)Sub[ ]*([^(\n]*)(\([^\n]*\)[ ]*As) async([^\n]*)$", RegexOptions.Multiline));

/// <summary>
/// This is a workaround to allow async/await calls in VB.NET. Works in cooperation with CodeDomHelper.MarkCodeMemberMethodAsAsync() method
/// </summary>
private string FixVBNetAsyncMethodDeclarations(string generatedTestCode)
{
var functionRegex = new Regex(@"^([^\n]*)Function[ ]*([^(\n]*)(\([^\n]*\)[ ]*As) async([^\n]*)$", RegexOptions.Multiline);
var subRegex = new Regex(@"^([^\n]*)Sub[ ]*([^(\n]*)(\([^\n]*\)[ ]*As) async([^\n]*)$", RegexOptions.Multiline);
var functionRegex = VBFixAsyncFunctionRegex.Value;
var subRegex = VBFixAsyncSubRegex.Value;

var result = functionRegex.Replace(generatedTestCode, "$1 Async Function $2$3$4");
result = subRegex.Replace(result, "$1 Async Sub $2$3$4");
Expand Down
16 changes: 8 additions & 8 deletions Reqnroll.Parser/ReqnrollGherkinParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ namespace Reqnroll.Parser
public class ReqnrollGherkinParser : IGherkinParser
{
private readonly IGherkinDialectProvider dialectProvider;
private readonly List<ISemanticValidator> semanticValidators;
private static readonly List<ISemanticValidator> semanticValidators = new List<ISemanticValidator>(4)
{
new DuplicateScenariosValidator(),
new DuplicateExamplesValidator(),
new MissingExamplesValidator(),
new DuplicateExamplesColumnHeadersValidator()
};

public IGherkinDialectProvider DialectProvider
{
Expand All @@ -26,13 +32,7 @@ public ReqnrollGherkinParser(IGherkinDialectProvider dialectProvider)
public ReqnrollGherkinParser(CultureInfo defaultLanguage)
: this(new ReqnrollGherkinDialectProvider(defaultLanguage.Name))
{
semanticValidators = new List<ISemanticValidator>
{
new DuplicateScenariosValidator(),
new DuplicateExamplesValidator(),
new MissingExamplesValidator(),
new DuplicateExamplesColumnHeadersValidator()
};

}

private static StepKeyword GetStepKeyword(GherkinDialect dialect, string stepKeyword)
Expand Down
7 changes: 6 additions & 1 deletion Reqnroll.Parser/ReqnrollGherkinParserFactory.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
using Gherkin;
using System;
using System.Collections.Concurrent;
using System.Globalization;

namespace Reqnroll.Parser
{
public class ReqnrollGherkinParserFactory : IGherkinParserFactory
{
readonly ConcurrentDictionary<string, ReqnrollGherkinDialectProvider> _CachedDialectProvider = new ConcurrentDictionary<string, ReqnrollGherkinDialectProvider>();
static readonly Func<string, ReqnrollGherkinDialectProvider> _DialectProviderFactory = (defaultLanguage) => new ReqnrollGherkinDialectProvider(defaultLanguage);

public IGherkinParser Create(IGherkinDialectProvider dialectProvider) => new ReqnrollGherkinParser(dialectProvider);

public IGherkinParser Create(CultureInfo cultureInfo) => new ReqnrollGherkinParser(cultureInfo);
public IGherkinParser Create(CultureInfo cultureInfo) => new ReqnrollGherkinParser(_CachedDialectProvider.GetOrAdd(cultureInfo.Name, _DialectProviderFactory));
}
}
17 changes: 7 additions & 10 deletions Reqnroll/Parser/ReqnrollGherkinDialectProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,13 @@ public override GherkinDialect GetDialect(string language, Location location)
{
if (language.Contains("-"))
{
try
{
return base.GetDialect(language, location);
}
catch (NoSuchLanguageException)
{
var languageBase = language.Split('-')[0];
var languageBaseDialect = base.GetDialect(languageBase, location);
return new GherkinDialect(language, languageBaseDialect.FeatureKeywords, languageBaseDialect.RuleKeywords, languageBaseDialect.BackgroundKeywords, languageBaseDialect.ScenarioKeywords, languageBaseDialect.ScenarioOutlineKeywords, languageBaseDialect.ExamplesKeywords, languageBaseDialect.GivenStepKeywords, languageBaseDialect.WhenStepKeywords, languageBaseDialect.ThenStepKeywords, languageBaseDialect.AndStepKeywords, languageBaseDialect.ButStepKeywords);
}
// Use TryGetDialect to avoid NoSuchLanguageException, if culture specific language is not present
if (TryGetDialect(language, location, out var dialect))
return dialect;

var languageBase = language.Split('-')[0];
var languageBaseDialect = base.GetDialect(languageBase, location);
return new GherkinDialect(language, languageBaseDialect.FeatureKeywords, languageBaseDialect.RuleKeywords, languageBaseDialect.BackgroundKeywords, languageBaseDialect.ScenarioKeywords, languageBaseDialect.ScenarioOutlineKeywords, languageBaseDialect.ExamplesKeywords, languageBaseDialect.GivenStepKeywords, languageBaseDialect.WhenStepKeywords, languageBaseDialect.ThenStepKeywords, languageBaseDialect.AndStepKeywords, languageBaseDialect.ButStepKeywords);
}

return base.GetDialect(language, location);
Expand Down