@@ -33,12 +33,12 @@ public W3CTraceContextTests(ITestOutputHelper output)
33
33
[ SkipUnlessEnvVarFoundTheory ( W3CTraceContextEnvVarName ) ]
34
34
[ InlineData ( "placeholder" ) ]
35
35
[ 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 )
37
37
{
38
38
// configure SDK
39
39
using var tracerProvider = Sdk . CreateTracerProviderBuilder ( )
40
- . AddAspNetCoreInstrumentation ( )
41
- . Build ( ) ;
40
+ . AddAspNetCoreInstrumentation ( )
41
+ . Build ( ) ;
42
42
43
43
var builder = WebApplication . CreateBuilder ( ) ;
44
44
using var app = builder . Build ( ) ;
@@ -66,14 +66,16 @@ public void W3CTraceContextTestSuiteAsync(string value)
66
66
return result ;
67
67
} ) ;
68
68
69
- app . RunAsync ( "http://localhost:5000/" ) ;
69
+ _ = app . RunAsync ( "http://localhost:5000/" ) ;
70
70
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/" ) ;
72
72
73
73
// 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 ) ;
75
76
76
- this . output . WriteLine ( "result:" + result ) ;
77
+ this . output . WriteLine ( "[stderr]" + stderr ) ;
78
+ this . output . WriteLine ( "[stdout]" + stdout ) ;
77
79
78
80
// Assert on the last line
79
81
Assert . StartsWith ( "OK" , lastLine , StringComparison . Ordinal ) ;
@@ -84,9 +86,9 @@ public void Dispose()
84
86
this . httpClient . Dispose ( ) ;
85
87
}
86
88
87
- private static string RunCommand ( string command , string args )
89
+ private static async Task < ( string StdOut , string StdErr ) > RunCommand ( string command , string args )
88
90
{
89
- using var proc = new Process
91
+ using var process = new Process
90
92
{
91
93
StartInfo = new ProcessStartInfo
92
94
{
@@ -99,12 +101,43 @@ private static string RunCommand(string command, string args)
99
101
WorkingDirectory = "." ,
100
102
} ,
101
103
} ;
102
- proc . Start ( ) ;
104
+ process . Start ( ) ;
103
105
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
+ }
108
141
}
109
142
110
143
private static string ParseLastLine ( string output )
@@ -119,6 +152,59 @@ private static string ParseLastLine(string output)
119
152
return output . Substring ( lastNewLineCharacterPos + 1 ) ;
120
153
}
121
154
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
+
122
208
#pragma warning disable CA1812 // Avoid uninstantiated internal classes
123
209
internal sealed class Data
124
210
#pragma warning restore CA1812 // Avoid uninstantiated internal classes
0 commit comments