Skip to content

Commit

Permalink
Improve code-behind feature file compilation speed (#336)
Browse files Browse the repository at this point in the history
* TestGenerator: Don't call VB.NET fixes when compiling C# code and compile RegEx in VB.NET

* ReqnrollGherkinParserFactory: Cache ReqnrollGherkinDialectProvider

* ScenarioPartHelper: Avoid Enum reflaction

* ReqnrollGherkinDialectProvider: Avoid NoSuchLanguageException
  • Loading branch information
obligaron authored Dec 2, 2024
1 parent 6eb1ff5 commit 4642769
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 24 deletions.
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

0 comments on commit 4642769

Please sign in to comment.