2
2
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
3
3
4
4
using System ;
5
+ using System . Buffers ;
5
6
using System . Collections . Generic ;
6
7
using System . IO ;
7
8
using System . IO . Pipelines ;
@@ -22,24 +23,19 @@ public StreamPipeReaderTestBase(ITestOutputHelper logger)
22
23
{
23
24
}
24
25
26
+ protected virtual bool EmulatePipelinesStreamPipeReader => true ;
27
+
25
28
[ Fact ]
26
29
public void ThrowsOnNull ( )
27
30
{
28
31
Assert . Throws < ArgumentNullException > ( ( ) => this . CreatePipeReader ( ( Stream ) null ! ) ) ;
29
32
}
30
33
31
- [ Fact ]
32
- public void NonReadableStream ( )
33
- {
34
- var unreadableStream = new Mock < Stream > ( MockBehavior . Strict ) ;
35
- unreadableStream . SetupGet ( s => s . CanRead ) . Returns ( false ) ;
36
- Assert . Throws < ArgumentException > ( ( ) => this . CreatePipeReader ( unreadableStream . Object ) ) ;
37
- unreadableStream . VerifyAll ( ) ;
38
- }
39
-
40
- [ Fact ]
34
+ [ SkippableFact ]
41
35
public async Task Stream ( )
42
36
{
37
+ Skip . If ( this is IOPipelinesStreamPipeReaderTests , "OnWriterCompleted isn't supported." ) ;
38
+
43
39
byte [ ] expectedBuffer = this . GetRandomBuffer ( 2048 ) ;
44
40
var stream = new MemoryStream ( expectedBuffer ) ;
45
41
var reader = this . CreatePipeReader ( stream , sizeHint : 50 ) ;
@@ -141,8 +137,110 @@ public async Task TryRead()
141
137
}
142
138
143
139
[ Fact ]
140
+ public void TryRead_FalseStillTurnsOnReadingMode ( )
141
+ {
142
+ var reader = this . CreatePipeReader ( new MemoryStream ( new byte [ ] { 1 , 2 , 3 } ) ) ;
143
+
144
+ bool tryReadResult = reader . TryRead ( out var readResult ) ;
145
+
146
+ // The non-strict PipeReader is timing sensitive and may or may not be able to synchronously produce a read.
147
+ // So only assert the False result for the strict readers.
148
+ if ( this . EmulatePipelinesStreamPipeReader )
149
+ {
150
+ Assert . False ( tryReadResult ) ;
151
+ }
152
+
153
+ reader . AdvanceTo ( readResult . Buffer . End ) ;
154
+ }
155
+
156
+ [ Fact ]
157
+ public void TryRead_FalseCanBeCalledRepeatedly ( )
158
+ {
159
+ var reader = this . CreatePipeReader ( new MemoryStream ( new byte [ ] { 1 , 2 , 3 } ) ) ;
160
+
161
+ // Verify that it's safe to call TryRead repeatedly when it returns False.
162
+ Assert . False ( reader . TryRead ( out var readResult ) ) ;
163
+ Assert . False ( reader . TryRead ( out readResult ) ) ;
164
+ }
165
+
166
+ [ Fact ]
167
+ public async Task TryRead_AdvanceTo_AfterEndReached ( )
168
+ {
169
+ var reader = this . CreatePipeReader ( new MemoryStream ( new byte [ ] { 1 , 2 , 3 } ) ) ;
170
+
171
+ var readResult = await reader . ReadAsync ( this . TimeoutToken ) ;
172
+ reader . AdvanceTo ( readResult . Buffer . End ) ;
173
+
174
+ reader . TryRead ( out readResult ) ;
175
+ reader . AdvanceTo ( readResult . Buffer . End ) ;
176
+ }
177
+
178
+ [ Theory , PairwiseData ]
179
+ public async Task ReadAsync_TwiceInARow ( bool emptyStream )
180
+ {
181
+ var stream = new MemoryStream ( emptyStream ? Array . Empty < byte > ( ) : new byte [ 3 ] ) ;
182
+ var reader = this . CreatePipeReader ( stream , sizeHint : 50 ) ;
183
+ var result1 = await reader . ReadAsync ( this . TimeoutToken ) ;
184
+ if ( emptyStream )
185
+ {
186
+ Assert . True ( result1 . IsCompleted ) ;
187
+ }
188
+
189
+ if ( this . EmulatePipelinesStreamPipeReader )
190
+ {
191
+ var result2 = await reader . ReadAsync ( this . TimeoutToken ) ;
192
+ Assert . Equal ( result1 . Buffer . Length , result2 . Buffer . Length ) ;
193
+ Assert . Equal ( emptyStream , result2 . IsCompleted ) ;
194
+ }
195
+ else
196
+ {
197
+ await Assert . ThrowsAsync < InvalidOperationException > ( async ( ) => await reader . ReadAsync ( this . TimeoutToken ) ) ;
198
+ }
199
+ }
200
+
201
+ [ Theory , PairwiseData ]
202
+ public async Task TryRead_TwiceInARow ( bool emptyStream )
203
+ {
204
+ var stream = new MemoryStream ( emptyStream ? Array . Empty < byte > ( ) : new byte [ 3 ] ) ;
205
+ var reader = this . CreatePipeReader ( stream , sizeHint : 50 ) ;
206
+
207
+ // Read asynchronously once to guarantee that there's a non-empty buffer in the reader.
208
+ var readResult = await reader . ReadAsync ( this . TimeoutToken ) ;
209
+ reader . AdvanceTo ( readResult . Buffer . Start ) ;
210
+
211
+ Assert . Equal ( ! emptyStream || ! this . EmulatePipelinesStreamPipeReader , reader . TryRead ( out _ ) ) ;
212
+ if ( this . EmulatePipelinesStreamPipeReader )
213
+ {
214
+ Assert . Equal ( ! emptyStream , reader . TryRead ( out _ ) ) ;
215
+ }
216
+ else
217
+ {
218
+ Assert . Throws < InvalidOperationException > ( ( ) => reader . TryRead ( out _ ) ) ;
219
+ }
220
+ }
221
+
222
+ [ Fact ]
223
+ public void AdvanceTo_BeforeRead ( )
224
+ {
225
+ var stream = new MemoryStream ( ) ;
226
+ var reader = this . CreatePipeReader ( stream , sizeHint : 50 ) ;
227
+ if ( this . EmulatePipelinesStreamPipeReader )
228
+ {
229
+ reader . AdvanceTo ( default ) ;
230
+ }
231
+ else
232
+ {
233
+ Assert . Throws < InvalidOperationException > ( ( ) => reader . AdvanceTo ( default ) ) ;
234
+ }
235
+
236
+ var ex = Assert . ThrowsAny < Exception > ( ( ) => reader . AdvanceTo ( ReadOnlySequence < byte > . Empty . Start ) ) ;
237
+ Assert . True ( ex is InvalidCastException || ex is InvalidOperationException ) ;
238
+ }
239
+
240
+ [ SkippableFact ]
144
241
public async Task OnWriterCompleted ( )
145
242
{
243
+ Skip . If ( this is IOPipelinesStreamPipeReaderTests , "OnWriterCompleted isn't supported." ) ;
146
244
byte [ ] expectedBuffer = this . GetRandomBuffer ( 50 ) ;
147
245
var stream = new MemoryStream ( expectedBuffer ) ;
148
246
var reader = this . CreatePipeReader ( stream , sizeHint : 50 ) ;
@@ -184,19 +282,50 @@ public async Task CancelPendingRead_WithCancellationToken()
184
282
}
185
283
186
284
[ Fact ]
187
- public async Task Complete_DoesNotCauseStreamDisposal ( )
285
+ public async Task Complete_MayCauseStreamDisposal ( )
188
286
{
189
287
var stream = new HalfDuplexStream ( ) ;
190
- var reader = this . CreatePipeReader ( stream ) ;
288
+ var ms = new MonitoringStream ( stream ) ;
289
+ var disposal = new AsyncManualResetEvent ( ) ;
290
+ ms . Disposed += ( s , e ) => disposal . Set ( ) ;
291
+ var reader = this . CreatePipeReader ( ms ) ;
191
292
reader . Complete ( ) ;
192
293
193
- var timeout = ExpectedTimeoutToken ;
194
- while ( ! stream . IsDisposed && ! timeout . IsCancellationRequested )
294
+ if ( this is IOPipelinesStreamPipeReaderTests )
295
+ {
296
+ await disposal . WaitAsync ( this . TimeoutToken ) ;
297
+ }
298
+ else
195
299
{
196
- await Task . Yield ( ) ;
300
+ await Assert . ThrowsAsync < OperationCanceledException > ( ( ) => disposal . WaitAsync ( ExpectedTimeoutToken ) ) ;
197
301
}
302
+ }
303
+
304
+ [ Fact ]
305
+ public async Task CancelPendingRead ( )
306
+ {
307
+ var stream = new HalfDuplexStream ( ) ;
308
+ var reader = this . CreatePipeReader ( stream , sizeHint : 50 ) ;
198
309
199
- Assert . False ( stream . IsDisposed ) ;
310
+ ValueTask < ReadResult > readTask = reader . ReadAsync ( this . TimeoutToken ) ;
311
+ reader . CancelPendingRead ( ) ;
312
+ var readResult = await readTask . AsTask ( ) . WithCancellation ( this . TimeoutToken ) ;
313
+ Assert . True ( readResult . IsCanceled ) ;
314
+
315
+ // Verify we can read after that without cancellation.
316
+ readTask = reader . ReadAsync ( this . TimeoutToken ) ;
317
+ stream . Write ( new byte [ ] { 1 , 2 , 3 } , 0 , 3 ) ;
318
+ await stream . FlushAsync ( this . TimeoutToken ) ;
319
+ readResult = await readTask ;
320
+ Assert . False ( readResult . IsCanceled ) ;
321
+ Assert . Equal ( 3 , readResult . Buffer . Length ) ;
322
+ reader . AdvanceTo ( readResult . Buffer . End ) ;
323
+
324
+ // Now cancel again
325
+ readTask = reader . ReadAsync ( this . TimeoutToken ) ;
326
+ reader . CancelPendingRead ( ) ;
327
+ readResult = await readTask ;
328
+ Assert . True ( readResult . IsCanceled ) ;
200
329
}
201
330
202
331
protected abstract PipeReader CreatePipeReader ( Stream stream , int sizeHint = 0 ) ;
0 commit comments