@@ -15,21 +15,24 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
15
15
{
16
16
public class AwaitableProcess : IDisposable
17
17
{
18
+ private readonly object _testOutputLock = new object ( ) ;
19
+
18
20
private Process _process ;
19
21
private readonly ProcessSpec _spec ;
20
22
private readonly List < string > _lines ;
21
23
private BufferBlock < string > _source ;
22
24
private ITestOutputHelper _logger ;
23
25
private TaskCompletionSource < int > _exited ;
24
26
private bool _started ;
27
+ private bool _disposed ;
25
28
26
29
public AwaitableProcess ( ProcessSpec spec , ITestOutputHelper logger )
27
30
{
28
31
_spec = spec ;
29
32
_logger = logger ;
30
33
_source = new BufferBlock < string > ( ) ;
31
34
_lines = new List < string > ( ) ;
32
- _exited = new TaskCompletionSource < int > ( ) ;
35
+ _exited = new TaskCompletionSource < int > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
33
36
}
34
37
35
38
public IEnumerable < string > Output => _lines ;
@@ -72,25 +75,25 @@ public void Start()
72
75
_process . ErrorDataReceived += OnData ;
73
76
_process . Exited += OnExit ;
74
77
75
- _logger . WriteLine ( $ "{ DateTime . Now } : starting process: '{ _process . StartInfo . FileName } { _process . StartInfo . Arguments } '") ;
78
+ WriteTestOutput ( $ "{ DateTime . Now } : starting process: '{ _process . StartInfo . FileName } { _process . StartInfo . Arguments } '") ;
76
79
_process . Start ( ) ;
77
80
_started = true ;
78
81
_process . BeginErrorReadLine ( ) ;
79
82
_process . BeginOutputReadLine ( ) ;
80
- _logger . WriteLine ( $ "{ DateTime . Now } : process started: '{ _process . StartInfo . FileName } { _process . StartInfo . Arguments } '") ;
83
+ WriteTestOutput ( $ "{ DateTime . Now } : process started: '{ _process . StartInfo . FileName } { _process . StartInfo . Arguments } '") ;
81
84
}
82
85
83
86
public async Task < string > GetOutputLineAsync ( string message , TimeSpan timeout )
84
87
{
85
- _logger . WriteLine ( $ "Waiting for output line [msg == '{ message } ']. Will wait for { timeout . TotalSeconds } sec.") ;
88
+ WriteTestOutput ( $ "Waiting for output line [msg == '{ message } ']. Will wait for { timeout . TotalSeconds } sec.") ;
86
89
var cts = new CancellationTokenSource ( ) ;
87
90
cts . CancelAfter ( timeout ) ;
88
91
return await GetOutputLineAsync ( $ "[msg == '{ message } ']", m => string . Equals ( m , message , StringComparison . Ordinal ) , cts . Token ) ;
89
92
}
90
93
91
94
public async Task < string > GetOutputLineStartsWithAsync ( string message , TimeSpan timeout )
92
95
{
93
- _logger . WriteLine ( $ "Waiting for output line [msg.StartsWith('{ message } ')]. Will wait for { timeout . TotalSeconds } sec.") ;
96
+ WriteTestOutput ( $ "Waiting for output line [msg.StartsWith('{ message } ')]. Will wait for { timeout . TotalSeconds } sec.") ;
94
97
var cts = new CancellationTokenSource ( ) ;
95
98
cts . CancelAfter ( timeout ) ;
96
99
return await GetOutputLineAsync ( $ "[msg.StartsWith('{ message } ')]", m => m != null && m . StartsWith ( message , StringComparison . Ordinal ) , cts . Token ) ;
@@ -105,7 +108,7 @@ private async Task<string> GetOutputLineAsync(string predicateName, Predicate<st
105
108
var next = await _source . ReceiveAsync ( cancellationToken ) ;
106
109
_lines . Add ( next ) ;
107
110
var match = predicate ( next ) ;
108
- _logger . WriteLine ( $ "{ DateTime . Now } : recv: '{ next } '. { ( match ? "Matches" : "Does not match" ) } condition '{ predicateName } '.") ;
111
+ WriteTestOutput ( $ "{ DateTime . Now } : recv: '{ next } '. { ( match ? "Matches" : "Does not match" ) } condition '{ predicateName } '.") ;
109
112
if ( match )
110
113
{
111
114
return next ;
@@ -124,7 +127,7 @@ public async Task<IList<string>> GetAllOutputLinesAsync(CancellationToken cancel
124
127
while ( await _source . OutputAvailableAsync ( cancellationToken ) )
125
128
{
126
129
var next = await _source . ReceiveAsync ( cancellationToken ) ;
127
- _logger . WriteLine ( $ "{ DateTime . Now } : recv: '{ next } '") ;
130
+ WriteTestOutput ( $ "{ DateTime . Now } : recv: '{ next } '") ;
128
131
lines . Add ( next ) ;
129
132
}
130
133
}
@@ -134,30 +137,50 @@ public async Task<IList<string>> GetAllOutputLinesAsync(CancellationToken cancel
134
137
private void OnData ( object sender , DataReceivedEventArgs args )
135
138
{
136
139
var line = args . Data ?? string . Empty ;
137
- _logger . WriteLine ( $ "{ DateTime . Now } : post: '{ line } '") ;
140
+
141
+ WriteTestOutput ( $ "{ DateTime . Now } : post: '{ line } '") ;
138
142
_source . Post ( line ) ;
139
143
}
140
144
145
+ private void WriteTestOutput ( string text )
146
+ {
147
+ lock ( _testOutputLock )
148
+ {
149
+ if ( ! _disposed )
150
+ {
151
+ _logger . WriteLine ( text ) ;
152
+ }
153
+ }
154
+ }
155
+
141
156
private void OnExit ( object sender , EventArgs args )
142
157
{
143
158
// Wait to ensure the process has exited and all output consumed
144
159
_process . WaitForExit ( ) ;
145
160
_source . Complete ( ) ;
146
161
_exited . TrySetResult ( _process . ExitCode ) ;
147
- _logger . WriteLine ( $ "Process { _process . Id } has exited") ;
162
+ WriteTestOutput ( $ "Process { _process . Id } has exited") ;
148
163
}
149
164
150
165
public void Dispose ( )
151
166
{
152
167
_source . Complete ( ) ;
153
168
169
+ lock ( _testOutputLock )
170
+ {
171
+ _disposed = true ;
172
+ }
173
+
154
174
if ( _process != null )
155
175
{
156
176
if ( _started && ! _process . HasExited )
157
177
{
158
178
_process . KillTree ( ) ;
159
179
}
160
180
181
+ _process . CancelErrorRead ( ) ;
182
+ _process . CancelOutputRead ( ) ;
183
+
161
184
_process . ErrorDataReceived -= OnData ;
162
185
_process . OutputDataReceived -= OnData ;
163
186
_process . Exited -= OnExit ;
0 commit comments