Skip to content

Commit c73f015

Browse files
committed
More flexible helpers for tests
1 parent b5f5551 commit c73f015

File tree

7 files changed

+457
-260
lines changed

7 files changed

+457
-260
lines changed

src/AutoCtor.Tests/ExampleTests.cs

Lines changed: 125 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
using System.Collections.Immutable;
22
using Microsoft.CodeAnalysis;
3-
4-
#if ROSLYN_4_4
53
using Microsoft.CodeAnalysis.CSharp;
6-
#endif
74

85
namespace AutoCtor.Tests;
96

@@ -13,11 +10,12 @@ public class ExampleTests
1310
[MemberData(nameof(GetExamples))]
1411
public async Task ExamplesGeneratedCode(CodeFileTheoryData theoryData)
1512
{
16-
var compilation = await Helpers.Compile<AutoConstructAttribute>(theoryData.Codes,
17-
langPreview: theoryData.LangPreview,
18-
preprocessorSymbols: s_preprocessorSymbols);
19-
var generator = new AutoConstructSourceGenerator().AsSourceGenerator();
20-
var driver = Helpers.CreateDriver(theoryData.Options, theoryData.LangPreview, generator)
13+
var builder = CreateCompilation(theoryData);
14+
var compilation = await builder.Build(nameof(ExampleTests));
15+
var driver = new GeneratorDriverBuilder()
16+
.AddGenerator(new AutoConstructSourceGenerator())
17+
.WithAnalyzerOptions(theoryData.Options)
18+
.Build(builder.ParseOptions)
2119
.RunGenerators(compilation);
2220

2321
await Verify(driver)
@@ -29,11 +27,12 @@ await Verify(driver)
2927
[MemberData(nameof(GetExamples))]
3028
public async Task CodeCompilesProperly(CodeFileTheoryData theoryData)
3129
{
32-
var compilation = await Helpers.Compile<AutoConstructAttribute>(theoryData.Codes,
33-
langPreview: theoryData.LangPreview,
34-
preprocessorSymbols: s_preprocessorSymbols);
35-
var generator = new AutoConstructSourceGenerator().AsSourceGenerator();
36-
Helpers.CreateDriver(theoryData.Options, theoryData.LangPreview, generator)
30+
var builder = CreateCompilation(theoryData);
31+
var compilation = await builder.Build(nameof(ExampleTests));
32+
new GeneratorDriverBuilder()
33+
.AddGenerator(new AutoConstructSourceGenerator())
34+
.WithAnalyzerOptions(theoryData.Options)
35+
.Build(builder.ParseOptions)
3736
.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out _);
3837

3938
Assert.Empty(outputCompilation.GetDiagnostics().Where(d => !theoryData.IgnoredCompileDiagnostics.Contains(d.Id)));
@@ -44,22 +43,27 @@ public async Task CodeCompilesProperly(CodeFileTheoryData theoryData)
4443
[MemberData(nameof(GetExamples))]
4544
public async Task EnsureRunsAreCachedCorrectly(CodeFileTheoryData theoryData)
4645
{
47-
var compilation = await Helpers.Compile<AutoConstructAttribute>(theoryData.Codes,
48-
langPreview: theoryData.LangPreview,
49-
preprocessorSymbols: s_preprocessorSymbols);
50-
var generator = new AutoConstructSourceGenerator().AsSourceGenerator();
46+
var builder = CreateCompilation(theoryData);
47+
var compilation = await builder.Build(nameof(ExampleTests));
48+
49+
var driver = new GeneratorDriverBuilder()
50+
.AddGenerator(new AutoConstructSourceGenerator())
51+
.WithAnalyzerOptions(theoryData.Options)
52+
.Build(builder.ParseOptions);
5153

52-
var driver = Helpers.CreateDriver(theoryData.Options, theoryData.LangPreview, generator);
5354
driver = driver.RunGenerators(compilation);
5455
var firstResult = driver.GetRunResult();
56+
57+
// Change the compilation
5558
compilation = compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText("// dummy",
5659
CSharpParseOptions.Default.WithLanguageVersion(theoryData.LangPreview
5760
? LanguageVersion.Preview
5861
: LanguageVersion.Latest)));
62+
5963
driver = driver.RunGenerators(compilation);
6064
var secondResult = driver.GetRunResult();
6165

62-
Helpers.AssertRunsEqual(firstResult, secondResult,
66+
AssertRunsEqual(firstResult, secondResult,
6367
AutoConstructSourceGenerator.TrackingNames.AllTrackers);
6468
}
6569
#endif
@@ -84,6 +88,22 @@ public async Task EnsureRunsAreCachedCorrectly(CodeFileTheoryData theoryData)
8488
#endif
8589
];
8690

91+
private static CompilationBuilder CreateCompilation(CodeFileTheoryData theoryData)
92+
{
93+
var builder = new CompilationBuilder()
94+
.AddNetCoreReference()
95+
.AddAssemblyReference<AutoConstructAttribute>()
96+
.WithPreprocessorSymbols(s_preprocessorSymbols)
97+
.AddCodes(theoryData.Codes);
98+
99+
if (theoryData.LangPreview)
100+
{
101+
builder = builder.WithLanguageVersion(LanguageVersion.Preview);
102+
}
103+
104+
return builder;
105+
}
106+
87107
private static DirectoryInfo? BaseDir { get; } = new DirectoryInfo(Environment.CurrentDirectory)?.Parent?.Parent;
88108

89109
private static IEnumerable<string> GetExamplesFiles(string path) => Directory.GetFiles(Path.Combine(BaseDir?.FullName ?? "", path), "*.cs").Where(e => !e.Contains(".g."));
@@ -107,13 +127,13 @@ public static TheoryData<CodeFileTheoryData> GetExamples()
107127
{
108128
data.Add(new CodeFileTheoryData(guardExample) with
109129
{
110-
Options = [new("build_property.AutoCtorGuards", "true")]
130+
Options = new() { { "build_property.AutoCtorGuards", "true" } }
111131
});
112132
}
113133

114134
foreach (var langExample in GetExamplesFiles("LangExamples"))
115135
{
116-
var verifiedName = "Verified_" + s_preprocessorSymbols.Last().Substring(7);
136+
var verifiedName = string.Concat("Verified_", s_preprocessorSymbols.Last().AsSpan(7));
117137
data.Add(new CodeFileTheoryData(langExample) with
118138
{
119139
VerifiedDirectory = Path.Combine(Path.GetDirectoryName(langExample) ?? "", verifiedName),
@@ -123,4 +143,88 @@ public static TheoryData<CodeFileTheoryData> GetExamples()
123143

124144
return data;
125145
}
146+
147+
#if ROSLYN_4
148+
private static void AssertRunsEqual(
149+
GeneratorDriverRunResult runResult1,
150+
GeneratorDriverRunResult runResult2,
151+
IEnumerable<string> trackingNames)
152+
{
153+
// We're given all the tracking names, but not all the
154+
// stages will necessarily execute, so extract all the
155+
// output steps, and filter to ones we know about
156+
var trackedSteps1 = GetTrackedSteps(runResult1, trackingNames);
157+
var trackedSteps2 = GetTrackedSteps(runResult2, trackingNames);
158+
159+
// Both runs should have the same tracked steps
160+
Assert.NotEmpty(trackedSteps1);
161+
Assert.Equal(trackedSteps1.Count, trackedSteps2.Count);
162+
Assert.Equal(trackedSteps1.Keys, trackedSteps2.Keys);
163+
164+
// Get the IncrementalGeneratorRunStep collection for each run
165+
foreach (var (trackingName, runSteps1) in trackedSteps1)
166+
{
167+
// Assert that both runs produced the same outputs
168+
var runSteps2 = trackedSteps2[trackingName];
169+
AssertStepsEqual(runSteps1, runSteps2, trackingName);
170+
}
171+
172+
return;
173+
174+
// Local function that extracts the tracked steps
175+
static Dictionary<string, ImmutableArray<IncrementalGeneratorRunStep>> GetTrackedSteps(
176+
GeneratorDriverRunResult runResult, IEnumerable<string> trackingNames)
177+
=> runResult
178+
.Results[0] // We're only running a single generator, so this is safe
179+
.TrackedSteps // Get the pipeline outputs
180+
.Where(step => trackingNames.Contains(step.Key)) // filter to known steps
181+
.ToDictionary(x => x.Key, x => x.Value); // Convert to a dictionary
182+
}
183+
184+
private static void AssertStepsEqual(
185+
ImmutableArray<IncrementalGeneratorRunStep> runSteps1,
186+
ImmutableArray<IncrementalGeneratorRunStep> runSteps2,
187+
string stepName)
188+
{
189+
Assert.Equal(runSteps1.Length, runSteps2.Length);
190+
191+
for (var i = 0; i < runSteps1.Length; i++)
192+
{
193+
var runStep1 = runSteps1[i];
194+
var runStep2 = runSteps2[i];
195+
196+
// The outputs should be equal between different runs
197+
var outputs1 = runStep1.Outputs.Select(x => x.Value);
198+
var outputs2 = runStep2.Outputs.Select(x => x.Value);
199+
200+
WithMessage(() => Assert.Equal(outputs1, outputs2),
201+
$"because step {stepName} should produce cacheable outputs");
202+
203+
// Therefore, on the second run the results should always be cached or unchanged!
204+
// - Unchanged is when the _input_ has changed, but the output hasn't
205+
// - Cached is when the the input has not changed, so the cached output is used
206+
IEnumerable<IncrementalStepRunReason> acceptableReasons =
207+
[IncrementalStepRunReason.Cached, IncrementalStepRunReason.Unchanged];
208+
foreach (var step in runStep2.Outputs)
209+
{
210+
Assert.Contains(step.Reason, acceptableReasons);
211+
}
212+
}
213+
}
214+
215+
private static void WithMessage(Action action, string message)
216+
{
217+
try
218+
{
219+
action();
220+
}
221+
catch (Exception ex)
222+
{
223+
throw new AssertMessageException(message, ex);
224+
}
225+
}
226+
227+
[Serializable]
228+
public class AssertMessageException(string message, Exception inner) : Exception(message, inner);
229+
#endif
126230
}

src/AutoCtor.Tests/GeneratedAttributeTests.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
using Microsoft.CodeAnalysis;
2-
3-
namespace AutoCtor.Tests;
1+
namespace AutoCtor.Tests;
42

53
public class GeneratedAttributeTests
64
{
75
[Fact]
86
public async Task AttributeGeneratedCode()
97
{
10-
var compilation = await Helpers.Compile<AutoConstructAttribute>([], preprocessorSymbols: ["AUTOCTOR_EMBED_ATTRIBUTES"]);
11-
var generator = new AttributeSourceGenerator().AsSourceGenerator();
12-
var driver = Helpers.CreateDriver(generator)
8+
var builder = new CompilationBuilder()
9+
.AddNetCoreReference()
10+
.WithPreprocessorSymbols(["AUTOCTOR_EMBED_ATTRIBUTES"]);
11+
var compilation = await builder.Build(nameof(GeneratedAttributeTests));
12+
var driver = new GeneratorDriverBuilder()
13+
.AddGenerator(new AttributeSourceGenerator())
14+
.Build(builder.ParseOptions)
1315
.RunGenerators(compilation);
1416

1517
await Verify(driver).UseDirectory("Verified");
@@ -18,9 +20,13 @@ public async Task AttributeGeneratedCode()
1820
[Fact]
1921
public async Task AttributeCompilesProperly()
2022
{
21-
var compilation = await Helpers.Compile<AutoConstructAttribute>([], preprocessorSymbols: ["AUTOCTOR_EMBED_ATTRIBUTES"]);
22-
var generator = new AttributeSourceGenerator().AsSourceGenerator();
23-
Helpers.CreateDriver(generator)
23+
var builder = new CompilationBuilder()
24+
.AddNetCoreReference()
25+
.WithPreprocessorSymbols(["AUTOCTOR_EMBED_ATTRIBUTES"]);
26+
var compilation = await builder.Build(nameof(GeneratedAttributeTests));
27+
var driver = new GeneratorDriverBuilder()
28+
.AddGenerator(new AttributeSourceGenerator())
29+
.Build(builder.ParseOptions)
2430
.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics);
2531

2632
Assert.Empty(diagnostics);

src/AutoCtor.Tests/Issue73.cs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ public class Issue73
88
[Fact]
99
public async Task VerifyGeneratedCode()
1010
{
11-
var compilation = await Compile();
12-
var generator = new AutoConstructSourceGenerator().AsSourceGenerator();
13-
var driver = Helpers.CreateDriver(generator).RunGenerators(compilation);
11+
var common = Common();
12+
var compilation = await Compile(common);
13+
var driver = new GeneratorDriverBuilder()
14+
.AddGenerator(new AutoConstructSourceGenerator())
15+
.Build(common.ParseOptions)
16+
.RunGenerators(compilation);
1417

1518
await Verify(driver).UseDirectory("Verified");
1619
}
@@ -20,16 +23,25 @@ public async Task CodeCompilesWithoutErrors()
2023
{
2124
string[] ignoredWarnings = ["CS0414"]; // Ignore unused fields
2225

23-
var compilation = await Compile();
24-
var generator = new AutoConstructSourceGenerator().AsSourceGenerator();
25-
Helpers.CreateDriver(generator)
26+
var common = Common();
27+
var compilation = await Compile(common);
28+
new GeneratorDriverBuilder()
29+
.AddGenerator(new AutoConstructSourceGenerator())
30+
.Build(common.ParseOptions)
2631
.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics);
2732

2833
Assert.Empty(diagnostics);
2934
Assert.Empty(outputCompilation.GetDiagnostics().Where(d => !ignoredWarnings.Contains(d.Id)));
3035
}
3136

32-
private static async Task<CSharpCompilation> Compile()
37+
private static CompilationBuilder Common()
38+
{
39+
return new CompilationBuilder()
40+
.AddNetCoreReference()
41+
.AddAssemblyReference<AutoConstructAttribute>();
42+
}
43+
44+
private static async Task<CSharpCompilation> Compile(CompilationBuilder common)
3345
{
3446
var projectACode = @"
3547
namespace A
@@ -51,10 +63,14 @@ public abstract class BaseClass<T, U, V>
5163
public sealed partial class TheClass : BaseClass<object, int, string>{}
5264
}
5365
";
66+
var projectA = await common
67+
.AddCode(projectACode)
68+
.Build("ProjectA");
5469

55-
var projectA = await Helpers.Compile<AutoConstructAttribute>([projectACode], assemblyName: "ProjectA");
56-
var projectB = await Helpers.Compile<AutoConstructAttribute>([projectBCode], assemblyName: "ProjectB",
57-
extraReferences: [projectA.ToMetadataReference()]);
70+
var projectB = await common
71+
.AddCompilationReference(projectA)
72+
.AddCode(projectBCode)
73+
.Build("ProjectB");
5874

5975
return projectB;
6076
}

src/AutoCtor.Tests/Utilities/CodeFileTheoryData.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public record CodeFileTheoryData : IXunitSerializable
88
public required string[] Codes { get; set; }
99
public required string VerifiedDirectory { get; set; }
1010

11-
public (string, string)[] Options { get; set; } = [];
11+
public Dictionary<string, string> Options { get; set; } = [];
1212
public bool LangPreview { get; set; }
1313
public string[] IgnoredCompileDiagnostics { get; set; } = [];
1414

@@ -32,8 +32,7 @@ public void Deserialize(IXunitSerializationInfo info)
3232
?? throw new Exception($"Missing {nameof(VerifiedDirectory)} in theory serialization");
3333
Options = info.GetValue<string[]>(nameof(Options))?
3434
.Select(o => o.Split('|'))
35-
.Select(o => (o[0], o[1]))
36-
.ToArray()
35+
.ToDictionary(o => o[0], o => o[1])
3736
?? throw new Exception($"Missing {nameof(Options)} in theory serialization");
3837
LangPreview = info.GetValue<bool>(nameof(LangPreview));
3938
IgnoredCompileDiagnostics = info.GetValue<string[]>(nameof(IgnoredCompileDiagnostics))
@@ -45,7 +44,7 @@ public void Serialize(IXunitSerializationInfo info)
4544
info.AddValue(nameof(Name), Name);
4645
info.AddValue(nameof(Codes), Codes);
4746
info.AddValue(nameof(VerifiedDirectory), VerifiedDirectory);
48-
info.AddValue(nameof(Options), Options.Select(o => $"{o.Item1}|{o.Item2}").ToArray());
47+
info.AddValue(nameof(Options), Options.Select(o => $"{o.Key}|{o.Value}").ToArray());
4948
info.AddValue(nameof(LangPreview), LangPreview);
5049
info.AddValue(nameof(IgnoredCompileDiagnostics), IgnoredCompileDiagnostics);
5150
}

0 commit comments

Comments
 (0)