@@ -54,51 +54,61 @@ public async Task<int> RunAsync()
5454
5555 using var process = Process . Start ( processStartInfo ) ! ;
5656
57- // Reading from process stdout/stderr is done on separate threads to avoid blocking IO on the threadpool.
58- // Note: even with 'process.StandardOutput.ReadToEndAsync()' or 'process.BeginOutputReadLine()', we ended up with
59- // many TP threads just doing synchronous IO, slowing down the progress of the test run.
60- // We want to read requests coming through the pipe and sending responses back to the test app as fast as possible.
61- // We are using ConcurrentQueue to avoid thread-safety issues for the timeout case.
62- // In the timeout case, we leave stdOutTask and stdErrTask running, just we stop observing them.
63- var stdOutBuilder = new ConcurrentQueue < string > ( ) ;
64- var stdErrBuilder = new ConcurrentQueue < string > ( ) ;
65-
66- var stdOutTask = Task . Factory . StartNew ( ( ) =>
57+ int exitCode ;
58+ if ( ! _handler . ShouldRedirectOutputAndError )
6759 {
68- var stdOut = process . StandardOutput ;
69- string ? currentLine ;
70- while ( ( currentLine = stdOut . ReadLine ( ) ) is not null )
60+ await process . WaitForExitAsync ( ) ;
61+ exitCode = process . ExitCode ;
62+ _handler . OnTestProcessExited ( exitCode , null , null ) ;
63+ }
64+ else
65+ {
66+ // Reading from process stdout/stderr is done on separate threads to avoid blocking IO on the threadpool.
67+ // Note: even with 'process.StandardOutput.ReadToEndAsync()' or 'process.BeginOutputReadLine()', we ended up with
68+ // many TP threads just doing synchronous IO, slowing down the progress of the test run.
69+ // We want to read requests coming through the pipe and sending responses back to the test app as fast as possible.
70+ // We are using ConcurrentQueue to avoid thread-safety issues for the timeout case.
71+ // In the timeout case, we leave stdOutTask and stdErrTask running, just we stop observing them.
72+ var stdOutBuilder = new ConcurrentQueue < string > ( ) ;
73+ var stdErrBuilder = new ConcurrentQueue < string > ( ) ;
74+
75+ var stdOutTask = Task . Factory . StartNew ( ( ) =>
7176 {
72- stdOutBuilder . Enqueue ( currentLine ) ;
73- }
74- } , TaskCreationOptions . LongRunning ) ;
77+ var stdOut = process . StandardOutput ;
78+ string ? currentLine ;
79+ while ( ( currentLine = stdOut . ReadLine ( ) ) is not null )
80+ {
81+ stdOutBuilder . Enqueue ( currentLine ) ;
82+ }
83+ } , TaskCreationOptions . LongRunning ) ;
7584
76- var stdErrTask = Task . Factory . StartNew ( ( ) =>
77- {
78- var stdErr = process . StandardError ;
79- string ? currentLine ;
80- while ( ( currentLine = stdErr . ReadLine ( ) ) is not null )
85+ var stdErrTask = Task . Factory . StartNew ( ( ) =>
8186 {
82- stdErrBuilder . Enqueue ( currentLine ) ;
83- }
84- } , TaskCreationOptions . LongRunning ) ;
87+ var stdErr = process . StandardError ;
88+ string ? currentLine ;
89+ while ( ( currentLine = stdErr . ReadLine ( ) ) is not null )
90+ {
91+ stdErrBuilder . Enqueue ( currentLine ) ;
92+ }
93+ } , TaskCreationOptions . LongRunning ) ;
8594
86- // WaitForExitAsync only waits for process exit (and doesn't wait for output) for our usage here.
87- // If we use BeginOutputReadLine/BeginErrorReadLine, it will also wait for output which can deadlock.
88- await process . WaitForExitAsync ( ) ;
95+ // WaitForExitAsync only waits for process exit (and doesn't wait for output) for our usage here.
96+ // If we use BeginOutputReadLine/BeginErrorReadLine, it will also wait for output which can deadlock.
97+ await process . WaitForExitAsync ( ) ;
8998
90- // At this point, process already exited. Allow for 5 seconds to consume stdout/stderr.
91- // We might not be able to consume all the output if the test app has exited but left a child process alive.
92- try
93- {
94- await Task . WhenAll ( stdOutTask , stdErrTask ) . WaitAsync ( TimeSpan . FromSeconds ( 5 ) ) ;
95- }
96- catch ( TimeoutException )
97- {
98- }
99+ // At this point, process already exited. Allow for 5 seconds to consume stdout/stderr.
100+ // We might not be able to consume all the output if the test app has exited but left a child process alive.
101+ try
102+ {
103+ await Task . WhenAll ( stdOutTask , stdErrTask ) . WaitAsync ( TimeSpan . FromSeconds ( 5 ) ) ;
104+ }
105+ catch ( TimeoutException )
106+ {
107+ }
99108
100- var exitCode = process . ExitCode ;
101- _handler . OnTestProcessExited ( exitCode , string . Join ( Environment . NewLine , stdOutBuilder ) , string . Join ( Environment . NewLine , stdErrBuilder ) ) ;
109+ exitCode = process . ExitCode ;
110+ _handler . OnTestProcessExited ( exitCode , string . Join ( Environment . NewLine , stdOutBuilder ) , string . Join ( Environment . NewLine , stdErrBuilder ) ) ;
111+ }
102112
103113 // This condition is to prevent considering the test app as successful when we didn't receive test session end.
104114 // We don't produce the exception if the exit code is already non-zero to avoid surfacing this exception when there is already a known failure.
@@ -121,15 +131,16 @@ public async Task<int> RunAsync()
121131
122132 private ProcessStartInfo CreateProcessStartInfo ( )
123133 {
134+ var shouldRedirect = _handler . ShouldRedirectOutputAndError ;
124135 var processStartInfo = new ProcessStartInfo
125136 {
126137 // We should get correct RunProperties right away.
127138 // For the case of dotnet test --test-modules path/to/dll, the TestModulesFilterHandler is responsible
128139 // for providing the dotnet muxer as RunCommand, and `exec "path/to/dll"` as RunArguments.
129140 FileName = Module . RunProperties . Command ,
130141 Arguments = GetArguments ( ) ,
131- RedirectStandardOutput = true ,
132- RedirectStandardError = true ,
142+ RedirectStandardOutput = shouldRedirect ,
143+ RedirectStandardError = shouldRedirect ,
133144 // False is already the default on .NET Core, but prefer to be explicit.
134145 UseShellExecute = false ,
135146 } ;
@@ -254,7 +265,7 @@ private Task<IResponse> OnRequest(NamedPipeServer server, IRequest request)
254265 case HandshakeMessage handshakeMessage :
255266 _handshakes . Add ( server , handshakeMessage ) ;
256267 string negotiatedVersion = GetSupportedProtocolVersion ( handshakeMessage ) ;
257- OnHandshakeMessage ( handshakeMessage , negotiatedVersion . Length > 0 ) ;
268+ OnHandshakeMessage ( handshakeMessage , negotiatedVersion ) ;
258269 return Task . FromResult ( ( IResponse ) CreateHandshakeMessage ( negotiatedVersion ) ) ;
259270
260271 case CommandLineOptionMessages commandLineOptionMessages :
@@ -317,14 +328,15 @@ private static string GetSupportedProtocolVersion(HandshakeMessage handshakeMess
317328 return string . Empty ;
318329 }
319330
320- // NOTE: Today, ProtocolConstants.Version is only 1.0.0 (i.e, SDK supports only a single version).
321- // Whenever we support multiple versions in SDK, we should do intersection
322- // between protocolVersions given by MTP, and the versions supported by SDK.
323- // Then we return the "highest" version from the intersection.
324- // The current logic **assumes** that ProtocolConstants.SupportedVersions is a single version.
325- if ( protocolVersions . Split ( ";" ) . Contains ( ProtocolConstants . SupportedVersions ) )
331+ // ProtocolConstant.SupportedVersions is the SDK supported versions, ordered from highest version to lowest version.
332+ // So, we take the first highest version that is also supported by MTP.
333+ var protocolVersionsByMTP = protocolVersions . Split ( ";" ) ;
334+ foreach ( var sdkSupportedVersion in ProtocolConstants . SupportedVersions )
326335 {
327- return ProtocolConstants . SupportedVersions ;
336+ if ( protocolVersionsByMTP . Contains ( sdkSupportedVersion ) )
337+ {
338+ return sdkSupportedVersion ;
339+ }
328340 }
329341
330342 // The version given by MTP is not supported by SDK.
@@ -341,8 +353,8 @@ private static HandshakeMessage CreateHandshakeMessage(string version) =>
341353 { HandshakeMessagePropertyNames . SupportedProtocolVersions , version }
342354 } ) ;
343355
344- public void OnHandshakeMessage ( HandshakeMessage handshakeMessage , bool gotSupportedVersion )
345- => _handler . OnHandshakeReceived ( handshakeMessage , gotSupportedVersion ) ;
356+ public void OnHandshakeMessage ( HandshakeMessage handshakeMessage , string negotiatedVersion )
357+ => _handler . OnHandshakeReceived ( handshakeMessage , negotiatedVersion ) ;
346358
347359 private void OnCommandLineOptionMessages ( CommandLineOptionMessages commandLineOptionMessages )
348360 {
0 commit comments