11using System . Collections . Immutable ;
22using Microsoft . CodeAnalysis ;
3-
4- #if ROSLYN_4_4
53using Microsoft . CodeAnalysis . CSharp ;
6- #endif
74
85namespace 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}
0 commit comments