@@ -33,12 +33,12 @@ public W3CTraceContextTests(ITestOutputHelper output)
3333 [ SkipUnlessEnvVarFoundTheory ( W3CTraceContextEnvVarName ) ]
3434 [ InlineData ( "placeholder" ) ]
3535 [ System . Diagnostics . CodeAnalysis . SuppressMessage ( "Usage" , "xUnit1026:Theory methods should use all of their parameters" , Justification = "Need to use SkipUnlessEnvVarFoundTheory" ) ]
36- public void W3CTraceContextTestSuiteAsync ( string value )
36+ public async Task W3CTraceContextTestSuiteAsync ( string value )
3737 {
3838 // configure SDK
3939 using var tracerProvider = Sdk . CreateTracerProviderBuilder ( )
40- . AddAspNetCoreInstrumentation ( )
41- . Build ( ) ;
40+ . AddAspNetCoreInstrumentation ( )
41+ . Build ( ) ;
4242
4343 var builder = WebApplication . CreateBuilder ( ) ;
4444 using var app = builder . Build ( ) ;
@@ -66,14 +66,16 @@ public void W3CTraceContextTestSuiteAsync(string value)
6666 return result ;
6767 } ) ;
6868
69- app . RunAsync ( "http://localhost:5000/" ) ;
69+ _ = app . RunAsync ( "http://localhost:5000/" ) ;
7070
71- string result = RunCommand ( "python" , "trace-context/test/test.py http://localhost:5000/" ) ;
71+ ( var stdout , var stderr ) = await RunCommand ( "python" , "-W ignore trace-context/test/test.py http://localhost:5000/" ) ;
7272
7373 // Assert
74- string lastLine = ParseLastLine ( result ) ;
74+ // TODO: after W3C Trace Context test suite passes, it might go in standard output
75+ string lastLine = ParseLastLine ( stderr ) ;
7576
76- this . output . WriteLine ( "result:" + result ) ;
77+ this . output . WriteLine ( "[stderr]" + stderr ) ;
78+ this . output . WriteLine ( "[stdout]" + stdout ) ;
7779
7880 // Assert on the last line
7981 Assert . StartsWith ( "OK" , lastLine , StringComparison . Ordinal ) ;
@@ -84,9 +86,9 @@ public void Dispose()
8486 this . httpClient . Dispose ( ) ;
8587 }
8688
87- private static string RunCommand ( string command , string args )
89+ private static async Task < ( string StdOut , string StdErr ) > RunCommand ( string command , string args )
8890 {
89- using var proc = new Process
91+ using var process = new Process
9092 {
9193 StartInfo = new ProcessStartInfo
9294 {
@@ -99,12 +101,43 @@ private static string RunCommand(string command, string args)
99101 WorkingDirectory = "." ,
100102 } ,
101103 } ;
102- proc . Start ( ) ;
104+ process . Start ( ) ;
103105
104- // TODO: after W3C Trace Context test suite passes, it might go in standard output
105- var results = proc . StandardError . ReadToEnd ( ) ;
106- proc . WaitForExit ( ) ;
107- return results ;
106+ // See https://stackoverflow.com/a/16326426/1064169 and
107+ // https://learn.microsoft.com/dotnet/api/system.diagnostics.processstartinfo.redirectstandardoutput.
108+ using var outputTokenSource = new CancellationTokenSource ( ) ;
109+
110+ var readOutput = ReadOutputAsync ( process , outputTokenSource . Token ) ;
111+
112+ try
113+ {
114+ await process . WaitForExitAsync ( ) ;
115+ }
116+ catch ( OperationCanceledException )
117+ {
118+ try
119+ {
120+ process . Kill ( entireProcessTree : true ) ;
121+ }
122+ catch ( Exception )
123+ {
124+ // Ignore
125+ }
126+ }
127+ finally
128+ {
129+ await outputTokenSource . CancelAsync ( ) ;
130+ }
131+
132+ try
133+ {
134+ return await readOutput ;
135+ }
136+ finally
137+ {
138+ process . Dispose ( ) ;
139+ outputTokenSource . Dispose ( ) ;
140+ }
108141 }
109142
110143 private static string ParseLastLine ( string output )
@@ -119,6 +152,59 @@ private static string ParseLastLine(string output)
119152 return output . Substring ( lastNewLineCharacterPos + 1 ) ;
120153 }
121154
155+ private static async Task < ( string Output , string Error ) > ReadOutputAsync (
156+ Process process ,
157+ CancellationToken cancellationToken )
158+ {
159+ var processErrors = ConsumeStreamAsync ( process . StandardError , process . StartInfo . RedirectStandardError , cancellationToken ) ;
160+ var processOutput = ConsumeStreamAsync ( process . StandardOutput , process . StartInfo . RedirectStandardOutput , cancellationToken ) ;
161+
162+ await Task . WhenAll ( processErrors , processOutput ) ;
163+
164+ string error = string . Empty ;
165+ string output = string . Empty ;
166+
167+ if ( processErrors . Status == TaskStatus . RanToCompletion )
168+ {
169+ error = ( await processErrors ) . ToString ( ) ;
170+ }
171+
172+ if ( processOutput . Status == TaskStatus . RanToCompletion )
173+ {
174+ output = ( await processOutput ) . ToString ( ) ;
175+ }
176+
177+ return ( output , error ) ;
178+ }
179+
180+ private static Task < StringBuilder > ConsumeStreamAsync (
181+ StreamReader reader ,
182+ bool isRedirected ,
183+ CancellationToken cancellationToken )
184+ {
185+ return isRedirected ?
186+ Task . Run ( ( ) => ProcessStream ( reader , cancellationToken ) , cancellationToken ) :
187+ Task . FromResult ( new StringBuilder ( 0 ) ) ;
188+
189+ static async Task < StringBuilder > ProcessStream (
190+ StreamReader reader ,
191+ CancellationToken cancellationToken )
192+ {
193+ var builder = new StringBuilder ( ) ;
194+
195+ try
196+ {
197+ builder . Append ( await reader . ReadToEndAsync ( cancellationToken ) ) ;
198+ }
199+ catch ( OperationCanceledException )
200+ {
201+ // Ignore
202+ }
203+
204+ return builder ;
205+ }
206+ }
207+
122208#pragma warning disable CA1812 // Avoid uninstantiated internal classes
123209 internal sealed class Data
124210#pragma warning restore CA1812 // Avoid uninstantiated internal classes
0 commit comments