-
-
Notifications
You must be signed in to change notification settings - Fork 744
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add incremental generator tests (#1829)
Co-authored-by: Chris Pulman <chris.pulman@yahoo.com>
- Loading branch information
1 parent
1869ca6
commit f2ab216
Showing
5 changed files
with
358 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
Refit.GeneratorTests/Incremental/IncrementalGeneratorRunReasons.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using Microsoft.CodeAnalysis; | ||
|
||
namespace Refit.GeneratorTests.Incremental; | ||
|
||
internal record IncrementalGeneratorRunReasons( | ||
IncrementalStepRunReason BuildMediatorStep, | ||
IncrementalStepRunReason ReportDiagnosticsStep | ||
) | ||
{ | ||
public static readonly IncrementalGeneratorRunReasons New = | ||
new(IncrementalStepRunReason.New, IncrementalStepRunReason.New); | ||
|
||
public static readonly IncrementalGeneratorRunReasons Cached = | ||
new( | ||
// compilation step should always be modified as each time a new compilation is passed | ||
IncrementalStepRunReason.Cached, | ||
IncrementalStepRunReason.Cached | ||
); | ||
|
||
public static readonly IncrementalGeneratorRunReasons Modified = Cached with | ||
{ | ||
ReportDiagnosticsStep = IncrementalStepRunReason.Modified, | ||
BuildMediatorStep = IncrementalStepRunReason.Modified, | ||
}; | ||
|
||
public static readonly IncrementalGeneratorRunReasons ModifiedSource = Cached with | ||
{ | ||
ReportDiagnosticsStep = IncrementalStepRunReason.Unchanged, | ||
BuildMediatorStep = IncrementalStepRunReason.Modified, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
using Microsoft.CodeAnalysis.CSharp; | ||
|
||
namespace Refit.GeneratorTests.Incremental; | ||
|
||
public class IncrementalTest | ||
{ | ||
private const string Default = | ||
""" | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net.Http; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Refit; | ||
|
||
namespace RefitGeneratorTest; | ||
|
||
public interface IGitHubApi | ||
{ | ||
[Get("/users/{user}")] | ||
Task<string> GetUser(string user); | ||
} | ||
"""; | ||
|
||
// [Fact] | ||
public void AddUnrelatedTypeDoesntRegenerate() | ||
{ | ||
var syntaxTree = CSharpSyntaxTree.ParseText(Default, CSharpParseOptions.Default); | ||
var compilation1 = Fixture.CreateLibrary(syntaxTree); | ||
|
||
var driver1 = TestHelper.GenerateTracked(compilation1); | ||
TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); | ||
|
||
var compilation2 = compilation1.AddSyntaxTrees(CSharpSyntaxTree.ParseText("struct MyValue {}")); | ||
var driver2 = driver1.RunGenerators(compilation2); | ||
TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.Cached); | ||
} | ||
|
||
// [Fact] | ||
public void SmallChangeDoesntRegenerate() | ||
{ | ||
var syntaxTree = CSharpSyntaxTree.ParseText(Default, CSharpParseOptions.Default); | ||
var compilation1 = Fixture.CreateLibrary(syntaxTree); | ||
|
||
var driver1 = TestHelper.GenerateTracked(compilation1); | ||
TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); | ||
|
||
// only change body, don't change the method | ||
var compilation2 = TestHelper.ReplaceMemberDeclaration( | ||
compilation1, | ||
"IGitHubApi", | ||
""" | ||
public interface IGitHubApi | ||
{ | ||
[Get("/users/{user}")] | ||
Task<string> GetUser(string user); | ||
|
||
private record Temp(); | ||
} | ||
""" | ||
); | ||
var driver2 = driver1.RunGenerators(compilation2); | ||
TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.Cached); | ||
} | ||
|
||
// [Fact] | ||
public void ModifyParameterNameDoesRegenerate() | ||
{ | ||
var syntaxTree = CSharpSyntaxTree.ParseText(Default, CSharpParseOptions.Default); | ||
var compilation1 = Fixture.CreateLibrary(syntaxTree); | ||
|
||
var driver1 = TestHelper.GenerateTracked(compilation1); | ||
TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); | ||
|
||
// change parameter name | ||
var newInterface = | ||
""" | ||
public interface IGitHubApi | ||
{ | ||
[Get("/users/{myUser}")] | ||
Task<string> GetUser(string myUser); | ||
} | ||
"""; | ||
var compilation2 = TestHelper.ReplaceMemberDeclaration(compilation1, "IGitHubApi", newInterface); | ||
|
||
var driver2 = driver1.RunGenerators(compilation2); | ||
TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); | ||
} | ||
|
||
// [Fact] | ||
public void ModifyParameterTypeDoesRegenerate() | ||
{ | ||
var syntaxTree = CSharpSyntaxTree.ParseText(Default, CSharpParseOptions.Default); | ||
var compilation1 = Fixture.CreateLibrary(syntaxTree); | ||
|
||
var driver1 = TestHelper.GenerateTracked(compilation1); | ||
TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); | ||
|
||
// change parameter type | ||
var newInterface = | ||
""" | ||
public interface IGitHubApi | ||
{ | ||
[Get("/users/{user}")] | ||
Task<string> GetUser(int user); | ||
} | ||
"""; | ||
var compilation2 = TestHelper.ReplaceMemberDeclaration(compilation1, "IGitHubApi", newInterface); | ||
|
||
var driver2 = driver1.RunGenerators(compilation2); | ||
TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); | ||
} | ||
|
||
// [Fact] | ||
public void ModifyParameterNullabilityDoesRegenerate() | ||
{ | ||
var syntaxTree = CSharpSyntaxTree.ParseText(Default, CSharpParseOptions.Default); | ||
var compilation1 = Fixture.CreateLibrary(syntaxTree); | ||
|
||
var driver1 = TestHelper.GenerateTracked(compilation1); | ||
TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); | ||
|
||
// change parameter nullability | ||
var newInterface = | ||
""" | ||
public interface IGitHubApi | ||
{ | ||
[Get("/users/{user}")] | ||
Task<string> GetUser(string? user); | ||
} | ||
"""; | ||
var compilation2 = TestHelper.ReplaceMemberDeclaration(compilation1, "IGitHubApi", newInterface); | ||
|
||
var driver2 = driver1.RunGenerators(compilation2); | ||
TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); | ||
} | ||
|
||
// [Fact] | ||
public void AddParameterDoesRegenerate() | ||
{ | ||
var syntaxTree = CSharpSyntaxTree.ParseText(Default, CSharpParseOptions.Default); | ||
var compilation1 = Fixture.CreateLibrary(syntaxTree); | ||
|
||
var driver1 = TestHelper.GenerateTracked(compilation1); | ||
TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); | ||
|
||
// add parameter | ||
var newInterface = | ||
""" | ||
public interface IGitHubApi | ||
{ | ||
[Get("/users/{user}")] | ||
Task<string> GetUser(string user, [Query] int myParam); | ||
} | ||
"""; | ||
var compilation2 = TestHelper.ReplaceMemberDeclaration(compilation1, "IGitHubApi", newInterface); | ||
|
||
var driver2 = driver1.RunGenerators(compilation2); | ||
TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); | ||
} | ||
|
||
// [Fact] | ||
public void ModifyReturnTypeDoesRegenerate() | ||
{ | ||
var syntaxTree = CSharpSyntaxTree.ParseText(Default, CSharpParseOptions.Default); | ||
var compilation1 = Fixture.CreateLibrary(syntaxTree); | ||
|
||
var driver1 = TestHelper.GenerateTracked(compilation1); | ||
TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); | ||
|
||
// change return type | ||
var newInterface = | ||
""" | ||
public interface IGitHubApi | ||
{ | ||
[Get("/users/{user}")] | ||
Task<int> GetUser(string user); | ||
} | ||
"""; | ||
var compilation2 = TestHelper.ReplaceMemberDeclaration(compilation1, "IGitHubApi", newInterface); | ||
|
||
var driver2 = driver1.RunGenerators(compilation2); | ||
TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); | ||
} | ||
|
||
// [Fact] | ||
public void ModifyReturnNullabilityDoesRegenerate() | ||
{ | ||
var syntaxTree = CSharpSyntaxTree.ParseText(Default, CSharpParseOptions.Default); | ||
var compilation1 = Fixture.CreateLibrary(syntaxTree); | ||
|
||
var driver1 = TestHelper.GenerateTracked(compilation1); | ||
TestHelper.AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); | ||
|
||
// change return nullability | ||
var newInterface = | ||
""" | ||
public interface IGitHubApi | ||
{ | ||
[Get("/users/{user}")] | ||
Task<string?> GetUser(string user); | ||
} | ||
"""; | ||
var compilation2 = TestHelper.ReplaceMemberDeclaration(compilation1, "IGitHubApi", newInterface); | ||
|
||
var driver2 = driver1.RunGenerators(compilation2); | ||
TestHelper.AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
|
||
using Refit.Generator; | ||
|
||
namespace Refit.GeneratorTests.Incremental; | ||
|
||
internal static class TestHelper | ||
{ | ||
private static readonly GeneratorDriverOptions EnableIncrementalTrackingDriverOptions = | ||
new(IncrementalGeneratorOutputKind.None, trackIncrementalGeneratorSteps: true); | ||
|
||
internal static GeneratorDriver GenerateTracked(Compilation compilation) | ||
{ | ||
var generator = new InterfaceStubGeneratorV2(); | ||
|
||
var driver = CSharpGeneratorDriver.Create( | ||
new[] { generator.AsSourceGenerator() }, | ||
driverOptions: EnableIncrementalTrackingDriverOptions | ||
); | ||
return driver.RunGenerators(compilation); | ||
} | ||
|
||
internal static CSharpCompilation ReplaceMemberDeclaration( | ||
CSharpCompilation compilation, | ||
string memberName, | ||
string newMember | ||
) | ||
{ | ||
var syntaxTree = compilation.SyntaxTrees.Single(); | ||
var memberDeclaration = syntaxTree | ||
.GetCompilationUnitRoot() | ||
.DescendantNodes() | ||
.OfType<TypeDeclarationSyntax>() | ||
.Single(x => x.Identifier.Text == memberName); | ||
var updatedMemberDeclaration = SyntaxFactory.ParseMemberDeclaration(newMember)!; | ||
|
||
var newRoot = syntaxTree.GetCompilationUnitRoot().ReplaceNode(memberDeclaration, updatedMemberDeclaration); | ||
var newTree = syntaxTree.WithRootAndOptions(newRoot, syntaxTree.Options); | ||
|
||
return compilation.ReplaceSyntaxTree(compilation.SyntaxTrees.First(), newTree); | ||
} | ||
|
||
internal static CSharpCompilation ReplaceLocalDeclaration( | ||
CSharpCompilation compilation, | ||
string variableName, | ||
string newDeclaration | ||
) | ||
{ | ||
var syntaxTree = compilation.SyntaxTrees.Single(); | ||
|
||
var memberDeclaration = syntaxTree | ||
.GetCompilationUnitRoot() | ||
.DescendantNodes() | ||
.OfType<LocalDeclarationStatementSyntax>() | ||
.Single(x => x.Declaration.Variables.Any(x => x.Identifier.ToString() == variableName)); | ||
var updatedMemberDeclaration = SyntaxFactory.ParseStatement(newDeclaration)!; | ||
|
||
var newRoot = syntaxTree.GetCompilationUnitRoot().ReplaceNode(memberDeclaration, updatedMemberDeclaration); | ||
var newTree = syntaxTree.WithRootAndOptions(newRoot, syntaxTree.Options); | ||
|
||
return compilation.ReplaceSyntaxTree(compilation.SyntaxTrees.First(), newTree); | ||
} | ||
|
||
internal static void AssertRunReasons( | ||
GeneratorDriver driver, | ||
IncrementalGeneratorRunReasons reasons, | ||
int outputIndex = 0 | ||
) | ||
{ | ||
var runResult = driver.GetRunResult().Results[0]; | ||
|
||
AssertRunReason( | ||
runResult, | ||
RefitGeneratorStepName.ReportDiagnostics, | ||
reasons.ReportDiagnosticsStep, | ||
outputIndex | ||
); | ||
AssertRunReason(runResult, RefitGeneratorStepName.BuildRefit, reasons.BuildMediatorStep, outputIndex); | ||
} | ||
|
||
private static void AssertRunReason( | ||
GeneratorRunResult runResult, | ||
string stepName, | ||
IncrementalStepRunReason expectedStepReason, | ||
int outputIndex | ||
) | ||
{ | ||
var actualStepReason = runResult | ||
.TrackedSteps[stepName] | ||
.SelectMany(x => x.Outputs) | ||
.ElementAt(outputIndex) | ||
.Reason; | ||
Assert.Equal(actualStepReason, expectedStepReason); | ||
} | ||
} | ||
|
||
internal static class RefitGeneratorStepName | ||
{ | ||
public const string ReportDiagnostics = "ReportDiagnostics"; | ||
public const string BuildRefit = "BuildRefit"; | ||
} |