88
99namespace ExampleTester ;
1010
11- internal class GeneratedExample
11+ public class GeneratedExample
1212{
13+ private static readonly object CodeExecutionLock = new ( ) ;
14+
1315 private readonly string directory ;
1416 internal ExampleMetadata Metadata { get ; }
1517
@@ -20,6 +22,8 @@ private GeneratedExample(string directory)
2022 Metadata = JsonConvert . DeserializeObject < ExampleMetadata > ( metadataJson ) ?? throw new ArgumentException ( $ "Invalid (null) metadata in { directory } ") ;
2123 }
2224
25+ public override string ? ToString ( ) => Metadata . ToString ( ) ;
26+
2327 internal static List < GeneratedExample > LoadAllExamples ( string parentDirectory ) =>
2428 Directory . GetDirectories ( parentDirectory ) . Select ( Load ) . ToList ( ) ;
2529
@@ -134,46 +138,52 @@ bool ValidateOutput()
134138 ? new object [ ] { Metadata . ExecutionArgs ?? new string [ 0 ] }
135139 : new object [ 0 ] ;
136140
137- var oldOut = Console . Out ;
138141 List < string > actualLines ;
139142 Exception ? actualException = null ;
140- try
143+ lock ( CodeExecutionLock )
141144 {
142- var builder = new StringBuilder ( ) ;
143- Console . SetOut ( new StringWriter ( builder ) ) ;
145+ var oldOut = Console . Out ;
144146 try
145147 {
146- var result = method . Invoke ( null , arguments ) ;
147- // For async Main methods, the compilation's entry point is still the Main
148- // method, so we explicitly wait for the returned task just like the synthesized
149- // entry point would.
150- if ( result is Task task )
148+ var builder = new StringBuilder ( ) ;
149+ Console . SetOut ( new StringWriter ( builder ) ) ;
150+ try
151+ {
152+ var result = method . Invoke ( null , arguments ) ;
153+ // For async Main methods, the compilation's entry point is still the Main
154+ // method, so we explicitly wait for the returned task just like the synthesized
155+ // entry point would.
156+ if ( result is Task task )
157+ {
158+ task . GetAwaiter ( ) . GetResult ( ) ;
159+ }
160+
161+ // For some reason, we don't *actually* get the result of all finalizers
162+ // without this. We shouldn't need it (as relevant examples already have it) but
163+ // code that works outside the test harness doesn't work inside it.
164+ GC . Collect ( ) ;
165+ GC . WaitForPendingFinalizers ( ) ;
166+ }
167+ catch ( TargetInvocationException outer )
151168 {
152- task . GetAwaiter ( ) . GetResult ( ) ;
169+ actualException = outer . InnerException ?? throw new InvalidOperationException ( "TargetInvocationException had no nested exception" ) ;
153170 }
154- // For some reason, we don't *actually* get the result of all finalizers
155- // without this. We shouldn't need it (as relevant examples already have it) but
156- // code that works outside the test harness doesn't work inside it.
157- GC . Collect ( ) ;
158- GC . WaitForPendingFinalizers ( ) ;
171+
172+ // Skip blank lines, to avoid unnecessary trailing empties.
173+ // Also trim the end of each actual line, to avoid trailing spaces being necessary in the metadata
174+ // or listed console output.
175+ actualLines = builder . ToString ( )
176+ . Replace ( "\r \n " , "\n " )
177+ . Split ( '\n ' )
178+ . Select ( line => line . TrimEnd ( ) )
179+ . Where ( line => line != "" ) . ToList ( ) ;
159180 }
160- catch ( TargetInvocationException outer )
181+ finally
161182 {
162- actualException = outer . InnerException ?? throw new InvalidOperationException ( "TargetInvocationException had no nested exception" ) ;
183+ Console . SetOut ( oldOut ) ;
163184 }
164- // Skip blank lines, to avoid unnecessary trailing empties.
165- // Also trim the end of each actual line, to avoid trailing spaces being necessary in the metadata
166- // or listed console output.
167- actualLines = builder . ToString ( )
168- . Replace ( "\r \n " , "\n " )
169- . Split ( '\n ' )
170- . Select ( line => line . TrimEnd ( ) )
171- . Where ( line => line != "" ) . ToList ( ) ;
172- }
173- finally
174- {
175- Console . SetOut ( oldOut ) ;
176185 }
186+
177187 var expectedLines = Metadata . ExpectedOutput ?? new List < string > ( ) ;
178188 return ValidateException ( actualException , Metadata . ExpectedException ) &&
179189 ( Metadata . IgnoreOutput || ValidateExpectedAgainstActual ( "output" , expectedLines , actualLines ) ) ;
0 commit comments