From ef00c0dc85643b67f12fe7faee47e605eb8f2b25 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:00:46 -0400 Subject: [PATCH 01/24] Fix invalid assert in StreamBuffer This can be hit by calling code and thus shouldn't be an assert. As an assert it prevents testing. --- src/libraries/Common/src/System/Net/StreamBuffer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/Common/src/System/Net/StreamBuffer.cs b/src/libraries/Common/src/System/Net/StreamBuffer.cs index e8ea8f961d85ad..aa7c74622b07d2 100644 --- a/src/libraries/Common/src/System/Net/StreamBuffer.cs +++ b/src/libraries/Common/src/System/Net/StreamBuffer.cs @@ -343,7 +343,6 @@ public void Reset() { if (_hasWaiter != 0) { - Debug.Fail("Concurrent use is not supported"); throw new InvalidOperationException("Concurrent use is not supported"); } From a590bcf915ab06b6d54f4a28c36b669d61779259 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:01:16 -0400 Subject: [PATCH 02/24] Add ConnectedStreams.CreateUnidirectional --- .../tests/System/IO/ConnectedStreams.cs | 335 ++++++++++++++++-- 1 file changed, 315 insertions(+), 20 deletions(-) diff --git a/src/libraries/Common/tests/System/IO/ConnectedStreams.cs b/src/libraries/Common/tests/System/IO/ConnectedStreams.cs index a5ea76a245fdab..98fa333282c99c 100644 --- a/src/libraries/Common/tests/System/IO/ConnectedStreams.cs +++ b/src/libraries/Common/tests/System/IO/ConnectedStreams.cs @@ -2,10 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. #nullable enable -using System.IO; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -14,6 +14,36 @@ namespace System.IO /// Provides support for in-memory producer/consumer streams. internal sealed class ConnectedStreams { + /// Creates a pair of streams that are connected for unidirectional communication. + /// Writing to one stream produces data readable by the either. + public static (Stream Writer, Stream Reader) CreateUnidirectional() => + CreateUnidirectional(StreamBuffer.DefaultInitialBufferSize, StreamBuffer.DefaultMaxBufferSize); + + /// Creates a pair of streams that are connected for unidirectional communication. + /// The initial buffer size to use when storing data in the connection. + /// Writing to one stream produces data readable by the either. + public static (Stream Writer, Stream Reader) CreateUnidirectional(int initialBufferSize) => + CreateUnidirectional(initialBufferSize, StreamBuffer.DefaultMaxBufferSize); + + /// Creates a pair of streams that are connected for unidirectional communication. + /// The initial buffer size to use when storing data in the connection. + /// + /// The maximum buffer size to use when storing data in the connection. When this limit is reached, + /// writes will block until additional space becomes available. + /// + /// Writing to one stream produces data readable by the either. + public static (Stream Writer, Stream Reader) CreateUnidirectional(int initialBufferSize, int maxBufferSize) + { + var buffer = new StreamBuffer(initialBufferSize, maxBufferSize); + + // The StreamBuffer is shared between the streams: we don't want to dispose of the underlying storage + // in the stream buffer until both streams are done with it, so we ref count and only dispose + // when both streams have been disposed. To share the same integer, it's put onto the heap. + var refCount = new StrongBox(2); + + return (new UnidirectionalStreamBufferStream(buffer, reader: false, refCount), new UnidirectionalStreamBufferStream(buffer, reader: true, refCount)); + } + /// Creates a pair of streams that are connected for bidirectional communication. /// Writing to one stream produces data readable by the either, and vice versa. public static (Stream Stream1, Stream Stream2) CreateBidirectional() => @@ -44,17 +74,232 @@ public static (Stream Stream1, Stream Stream2) CreateBidirectional(int initialBu // when both streams have been disposed. To share the same integer, it's put onto the heap. var refCount = new StrongBox(2); - return (new StreamBufferStream(b1, b2, refCount), new StreamBufferStream(b2, b1, refCount)); + return (new BidirectionalStreamBufferStream(b1, b2, refCount), new BidirectionalStreamBufferStream(b2, b1, refCount)); + } + + private sealed class UnidirectionalStreamBufferStream : Stream + { + private readonly StreamBuffer _buffer; + private readonly StrongBox _refCount; + private readonly bool _reader; + private bool _disposed; + + internal UnidirectionalStreamBufferStream(StreamBuffer buffer, bool reader, StrongBox refCount) + { + _buffer = buffer; + _reader = reader; + _refCount = refCount; + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _disposed = true; + + if (_reader) + { + _buffer.AbortRead(); + } + else + { + _buffer.EndWrite(); + } + + if (Interlocked.Decrement(ref _refCount.Value) == 0) + { + _buffer.Dispose(); + } + } + + base.Dispose(disposing); + } + + public override bool CanRead => _reader & !_disposed; + public override bool CanWrite => !_reader & !_disposed; + public override bool CanSeek => false; + + public override void Flush() => ThrowIfDisposed(); + public override Task FlushAsync(CancellationToken cancellationToken) + { + ThrowIfDisposed(); + + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + return Task.CompletedTask; + } + + public override int Read(byte[] buffer, int offset, int count) + { + ThrowIfDisposed(); + ValidateArgs(buffer, offset, count); + ThrowIfReadingNotSupported(); + + return _buffer.Read(new Span(buffer, offset, count)); + } + + public override int ReadByte() + { + ThrowIfDisposed(); + ThrowIfReadingNotSupported(); + + byte b = 0; + int n = _buffer.Read(MemoryMarshal.CreateSpan(ref b, 1)); + return n != 0 ? b : -1; + } + + public override int Read(Span buffer) + { + ThrowIfDisposed(); + ThrowIfReadingNotSupported(); + + return _buffer.Read(buffer); + } + + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => + TaskToApm.Begin(ReadAsync(buffer, offset, count), callback, state); + + public override int EndRead(IAsyncResult asyncResult) + { + ThrowIfDisposed(); + ThrowIfWritingNotSupported(); + + return TaskToApm.End(asyncResult); + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + ThrowIfDisposed(); + ValidateArgs(buffer, offset, count); + ThrowIfReadingNotSupported(); + + return _buffer.ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); + } + + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + ThrowIfDisposed(); + ThrowIfReadingNotSupported(); + + return _buffer.ReadAsync(buffer, cancellationToken); + } + + public override void Write(byte[] buffer, int offset, int count) + { + ThrowIfDisposed(); + ValidateArgs(buffer, offset, count); + ThrowIfWritingNotSupported(); + + _buffer.Write(new ReadOnlySpan(buffer, offset, count)); + } + + public override void WriteByte(byte value) + { + ThrowIfDisposed(); + ThrowIfWritingNotSupported(); + + _buffer.Write(MemoryMarshal.CreateReadOnlySpan(ref value, 1)); + } + + public override void Write(ReadOnlySpan buffer) + { + ThrowIfDisposed(); + + _buffer.Write(buffer); + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + ThrowIfDisposed(); + ValidateArgs(buffer, offset, count); + ThrowIfWritingNotSupported(); + + return _buffer.WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken).AsTask(); + } + + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + ThrowIfDisposed(); + ThrowIfWritingNotSupported(); + + return _buffer.WriteAsync(buffer, cancellationToken); + } + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => + TaskToApm.Begin(WriteAsync(buffer, offset, count), callback, state); + + public override void EndWrite(IAsyncResult asyncResult) + { + ThrowIfDisposed(); + ThrowIfWritingNotSupported(); + + TaskToApm.End(asyncResult); + } + + public override long Length => throw new NotSupportedException(); + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + + private void ValidateArgs(byte[] buffer, int offset, int count) + { + if (buffer is null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if ((uint)offset > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + if ((uint)count > buffer.Length || offset > buffer.Length - count) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + } + + private void ThrowIfDisposed() + { + if (_disposed) + ThrowDisposedException(); + + [StackTraceHidden] + static void ThrowDisposedException() => throw new ObjectDisposedException(nameof(ConnectedStreams)); + } + + private void ThrowIfReadingNotSupported() + { + if (!_reader) + { + ThrowNotSupportedException(); + } + } + + private void ThrowIfWritingNotSupported() + { + if (_reader) + { + ThrowNotSupportedException(); + } + } + + [DoesNotReturn] + private static void ThrowNotSupportedException() => + throw new NotSupportedException(); } - private sealed class StreamBufferStream : Stream + private sealed class BidirectionalStreamBufferStream : Stream { private readonly StreamBuffer _readBuffer; private readonly StreamBuffer _writeBuffer; private readonly StrongBox _refCount; private bool _disposed; - internal StreamBufferStream(StreamBuffer readBuffer, StreamBuffer writeBuffer, StrongBox refCount) + internal BidirectionalStreamBufferStream(StreamBuffer readBuffer, StreamBuffer writeBuffer, StrongBox refCount) { _readBuffer = readBuffer; _writeBuffer = writeBuffer; @@ -80,67 +325,108 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - public override bool CanRead => true; - public override bool CanWrite => true; + public override bool CanRead => !_disposed; + public override bool CanWrite => !_disposed; public override bool CanSeek => false; - public override void Flush() { } - public override Task FlushAsync(CancellationToken cancellationToken) => - cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : - Task.CompletedTask; + public override void Flush() => ThrowIfDisposed(); + public override Task FlushAsync(CancellationToken cancellationToken) + { + ThrowIfDisposed(); + + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + return Task.CompletedTask; + } public override int Read(byte[] buffer, int offset, int count) { + ThrowIfDisposed(); ValidateArgs(buffer, offset, count); + return _readBuffer.Read(new Span(buffer, offset, count)); } public override int ReadByte() { + ThrowIfDisposed(); + byte b = 0; int n = _readBuffer.Read(MemoryMarshal.CreateSpan(ref b, 1)); return n != 0 ? b : -1; } - public override int Read(Span buffer) => _readBuffer.Read(buffer); + public override int Read(Span buffer) + { + ThrowIfDisposed(); + return _readBuffer.Read(buffer); + } public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => TaskToApm.Begin(ReadAsync(buffer, offset, count), callback, state); - public override int EndRead(IAsyncResult asyncResult) => TaskToApm.End(asyncResult); + public override int EndRead(IAsyncResult asyncResult) + { + ThrowIfDisposed(); + return TaskToApm.End(asyncResult); + } public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { + ThrowIfDisposed(); ValidateArgs(buffer, offset, count); return _readBuffer.ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); } - public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => - _readBuffer.ReadAsync(buffer, cancellationToken); - + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + ThrowIfDisposed(); + return _readBuffer.ReadAsync(buffer, cancellationToken); + } public override void Write(byte[] buffer, int offset, int count) { + ThrowIfDisposed(); ValidateArgs(buffer, offset, count); _writeBuffer.Write(new ReadOnlySpan(buffer, offset, count)); } - public override void WriteByte(byte value) => _writeBuffer.Write(MemoryMarshal.CreateReadOnlySpan(ref value, 1)); + public override void WriteByte(byte value) + { + ThrowIfDisposed(); + _writeBuffer.Write(MemoryMarshal.CreateReadOnlySpan(ref value, 1)); + } - public override void Write(ReadOnlySpan buffer) => _writeBuffer.Write(buffer); + public override void Write(ReadOnlySpan buffer) + { + ThrowIfDisposed(); + _writeBuffer.Write(buffer); + } public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { + ThrowIfDisposed(); ValidateArgs(buffer, offset, count); return _writeBuffer.WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken).AsTask(); } - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => _writeBuffer.WriteAsync(buffer, cancellationToken); + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + ThrowIfDisposed(); + return _writeBuffer.WriteAsync(buffer, cancellationToken); + } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => TaskToApm.Begin(WriteAsync(buffer, offset, count), callback, state); - public override void EndWrite(IAsyncResult asyncResult) => TaskToApm.End(asyncResult); + public override void EndWrite(IAsyncResult asyncResult) + { + ThrowIfDisposed(); + TaskToApm.End(asyncResult); + } public override long Length => throw new NotSupportedException(); public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } @@ -164,6 +450,15 @@ private void ValidateArgs(byte[] buffer, int offset, int count) throw new ArgumentOutOfRangeException(nameof(count)); } } + + private void ThrowIfDisposed() + { + if (_disposed) + ThrowDisposedException(); + + [StackTraceHidden] + static void ThrowDisposedException() => throw new ObjectDisposedException(nameof(ConnectedStreams)); + } } } } From f63c22757d82cf252a2aff25c0238beef599af76 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:03:41 -0400 Subject: [PATCH 03/24] Add initial set of Stream conformance tests These primarily focus on "connected streams", ones that can be arranged to communicate with each other in a producer/consumer pattern or as a bidirectional communication mechanism, e.g. NetworkStream, PipeStream, SslStream wrapped around a NetworkStream, FileStream created around pipes, etc. Later we can add more tests focused on standalone streams, e.g. FileStream created for an on-disk file, MemoryStream, etc. --- .../Tests/System/IO/StreamConformanceTests.cs | 1843 +++++++++++++++++ 1 file changed, 1843 insertions(+) create mode 100644 src/libraries/Common/tests/Tests/System/IO/StreamConformanceTests.cs diff --git a/src/libraries/Common/tests/Tests/System/IO/StreamConformanceTests.cs b/src/libraries/Common/tests/Tests/System/IO/StreamConformanceTests.cs new file mode 100644 index 00000000000000..08700fe74fa4e4 --- /dev/null +++ b/src/libraries/Common/tests/Tests/System/IO/StreamConformanceTests.cs @@ -0,0 +1,1843 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace System.IO.Tests +{ + /// Base class providing tests for any Stream-derived type. + public abstract class StreamConformanceTests + { + /// Gets the name of the byte[] argument to Read/Write methods. + protected virtual string ReadWriteBufferName => "buffer"; + /// Gets the name of the int offset argument to Read/Write methods. + protected virtual string ReadWriteOffsetName => "offset"; + /// Gets the name of the int count argument to Read/Write methods. + protected virtual string ReadWriteCountName => "count"; + /// Gets the name of the IAsyncResult argument to EndRead/Write methods. + protected virtual string ReadWriteAsyncResultName => "asyncResult"; + /// Gets the name of the Stream destination argument to CopyTo{Async}. + protected virtual string CopyToStreamName => "destination"; + /// Gets the name of the int bufferSize argument to CopyTo{Async}. + protected virtual string CopyToBufferSizeName => "bufferSize"; + + /// Gets the type of exception thrown when an invalid IAsyncResult is passed to an EndRead/Write method. + protected virtual Type InvalidIAsyncResultExceptionType => typeof(ArgumentException); + /// Gets the type of exception thrown when a read or write operation is unsupported. + protected virtual Type UnsupportedReadWriteExceptionType => typeof(NotSupportedException); + /// Gets the type of exception thrown when a CopyTo{Async} operation is unsupported. + protected virtual Type UnsupportedCopyExceptionType => typeof(NotSupportedException); + /// Gets the type of exception thrown when setting a Read/WriteTimeout is unsupported. + protected virtual Type UnsupportedTimeoutExceptionType => typeof(InvalidOperationException); + /// + /// Gets the type of exception thrown when an operation is invoked concurrently erroneously, or null if no exception + /// is thrown (either because it's fully supported or not supported and non-deterministic). + /// + protected virtual Type UnsupportedConcurrentExceptionType => typeof(InvalidOperationException); + /// Exception type thrown from operations when the instance is disposed. + protected virtual Type DisposedExceptionType => typeof(ObjectDisposedException); + + /// Gets whether the stream is expected to be seekable. + protected virtual bool CanSeek => false; + /// Gets whether the stream is expected to support timeouts. + protected virtual bool CanTimeout => false; + /// Gets whether it's expected for the Position property to be usable even if CanSeek is false. + protected virtual bool CanGetPositionWhenCanSeekIsFalse => false; + /// Gets whether read/write operations fully support cancellation. + protected virtual bool FullyCancelableOperations => true; + + /// Gets whether the stream's CanRead/Write/etc properties are expected to return false once the stream is disposed. + protected virtual bool CansReturnFalseAfterDispose => true; + /// Gets whether the Stream may be used for additional operations after a read is canceled. + protected virtual bool UsableAfterCanceledReads => true; + + /// Specifies the form of the read/write operation to use. + public enum ReadWriteMode + { + /// ReadByte / WriteByte + SyncByte, + /// Read(Span{byte}) / Write(ReadOnlySpan{byte}) + SyncSpan, + /// Read(byte[], int, int) / Write(byte[], int, int) + SyncArray, + /// ReadAsync(byte[], int, int) / WriteAsync(byte[], int, int) + AsyncArray, + /// ReadAsync(Memory{byte}) / WriteAsync(ReadOnlyMemory{byte}) + AsyncMemory, + /// EndRead(BeginRead(..., null, null)) / EndWrite(BeginWrite(..., null, null)) + SyncAPM, + /// Task.Factory.FromAsync(s.BeginRead, s.EndRead, ...) / Task.Factory.FromAsync(s.BeginWrite, s.EndWrite, ...) + AsyncAPM + } + + protected async Task ValidateMisuseExceptionsAsync(Stream stream) + { + byte[] oneByteBuffer = new byte[1]; + + if (stream.CanRead) + { + // Null arguments + foreach ((int offset, int count) in new[] { (0, 0), (1, 2) }) // validate 0, 0 isn't special-cased to be allowed with a null buffer + { + AssertExtensions.Throws(ReadWriteBufferName, () => { stream.Read(null, offset, count); }); + AssertExtensions.Throws(ReadWriteBufferName, () => { stream.Read(null, offset, count); }); + AssertExtensions.Throws(ReadWriteBufferName, () => { stream.ReadAsync(null, offset, count); }); + AssertExtensions.Throws(ReadWriteBufferName, () => { stream.ReadAsync(null, offset, count, default); }); + AssertExtensions.Throws(ReadWriteBufferName, () => { stream.EndRead(stream.BeginRead(null, offset, count, iar => { }, new object())); }); + AssertExtensions.Throws(ReadWriteAsyncResultName, () => { stream.EndRead(null); }); + } + + // Invalid offset + AssertExtensions.Throws(ReadWriteOffsetName, () => { stream.Read(oneByteBuffer, -1, 0); }); + AssertExtensions.Throws(ReadWriteOffsetName, () => { stream.ReadAsync(oneByteBuffer, -1, 0); }); + AssertExtensions.Throws(ReadWriteOffsetName, () => { stream.ReadAsync(oneByteBuffer, -1, 0, default); }); + AssertExtensions.Throws(ReadWriteOffsetName, () => { stream.EndRead(stream.BeginRead(oneByteBuffer, -1, 0, iar => { }, new object())); }); + Assert.ThrowsAny(() => { stream.Read(oneByteBuffer, 2, 0); }); + Assert.ThrowsAny(() => { stream.ReadAsync(oneByteBuffer, 2, 0); }); + Assert.ThrowsAny(() => { stream.ReadAsync(oneByteBuffer, 2, 0, default); }); + Assert.ThrowsAny(() => { stream.EndRead(stream.BeginRead(oneByteBuffer, 2, 0, iar => { }, new object())); }); + + // Invalid count + AssertExtensions.Throws(ReadWriteCountName, () => { stream.Read(oneByteBuffer, 0, -1); }); + AssertExtensions.Throws(ReadWriteCountName, () => { stream.ReadAsync(oneByteBuffer, 0, -1); }); + AssertExtensions.Throws(ReadWriteCountName, () => { stream.ReadAsync(oneByteBuffer, 0, -1, default); }); + AssertExtensions.Throws(ReadWriteCountName, () => { stream.EndRead(stream.BeginRead(oneByteBuffer, 0, -1, iar => { }, new object())); }); + Assert.ThrowsAny(() => { stream.Read(oneByteBuffer, 0, 2); }); + Assert.ThrowsAny(() => { stream.ReadAsync(oneByteBuffer, 0, 2); }); + Assert.ThrowsAny(() => { stream.ReadAsync(oneByteBuffer, 0, 2, default); }); + Assert.ThrowsAny(() => { stream.EndRead(stream.BeginRead(oneByteBuffer, 0, 2, iar => { }, new object())); }); + + // Invalid offset + count + foreach ((int invalidOffset, int invalidCount) in new[] { (1, 1) }) + { + Assert.ThrowsAny(() => { stream.Read(oneByteBuffer, invalidOffset, invalidCount); }); + Assert.ThrowsAny(() => { stream.ReadAsync(oneByteBuffer, invalidOffset, invalidCount); }); + Assert.ThrowsAny(() => { stream.ReadAsync(oneByteBuffer, invalidOffset, invalidCount, default); }); + Assert.ThrowsAny(() => { stream.EndRead(stream.BeginRead(oneByteBuffer, invalidOffset, invalidCount, iar => { }, new object())); }); + } + + // Unknown arguments + Assert.Throws(InvalidIAsyncResultExceptionType, () => stream.EndRead(new NotImplementedIAsyncResult())); + + // Invalid destination stream + AssertExtensions.Throws(CopyToStreamName, () => { stream.CopyTo(null); }); + AssertExtensions.Throws(CopyToStreamName, () => { stream.CopyTo(null, 1); }); + AssertExtensions.Throws(CopyToStreamName, () => { stream.CopyToAsync(null, default(CancellationToken)); }); + AssertExtensions.Throws(CopyToStreamName, () => { stream.CopyToAsync(null, 1); }); + AssertExtensions.Throws(CopyToStreamName, () => { stream.CopyToAsync(null, 1, default(CancellationToken)); }); + + // Invalid buffer size + var validDestinationStream = new MemoryStream(); + foreach (int invalidBufferSize in new[] { 0, -1 }) + { + AssertExtensions.Throws(CopyToBufferSizeName, () => { stream.CopyTo(validDestinationStream, invalidBufferSize); }); + AssertExtensions.Throws(CopyToBufferSizeName, () => { stream.CopyToAsync(validDestinationStream, invalidBufferSize); }); + AssertExtensions.Throws(CopyToBufferSizeName, () => { stream.CopyToAsync(validDestinationStream, invalidBufferSize, default(CancellationToken)); }); + } + + // Unwriteable destination stream + var unwriteableDestination = new MemoryStream(new byte[1], writable: false); + Assert.Throws(UnsupportedCopyExceptionType, () => { stream.CopyTo(unwriteableDestination); }); + Assert.Throws(UnsupportedCopyExceptionType, () => { stream.CopyToAsync(unwriteableDestination); }); + + // Disposed destination stream + var disposedDestination = new MemoryStream(new byte[1]); + disposedDestination.Dispose(); + Assert.Throws(DisposedExceptionType, () => { stream.CopyTo(disposedDestination); }); + Assert.Throws(DisposedExceptionType, () => { stream.CopyToAsync(disposedDestination); }); + } + else + { + Assert.Throws(UnsupportedReadWriteExceptionType, () => { stream.ReadByte(); }); + Assert.Throws(UnsupportedReadWriteExceptionType, () => { stream.Read(new Span(new byte[1])); }); + Assert.Throws(UnsupportedReadWriteExceptionType, () => { stream.Read(new byte[1], 0, 1); }); + Assert.Throws(UnsupportedReadWriteExceptionType, () => { stream.ReadAsync(new byte[1], 0, 1); }); + Assert.Throws(UnsupportedReadWriteExceptionType, () => { stream.ReadAsync(new Memory(new byte[1])); }); + await Assert.ThrowsAsync(UnsupportedReadWriteExceptionType, () => Task.Factory.FromAsync(stream.BeginRead, stream.EndRead, new byte[1], 0, 1, null)); + Assert.True(Record.Exception(() => stream.EndRead(new NotImplementedIAsyncResult())) is Exception e && (e.GetType() == UnsupportedReadWriteExceptionType || e.GetType() == InvalidIAsyncResultExceptionType)); + Assert.Throws(UnsupportedCopyExceptionType, () => { stream.CopyTo(new MemoryStream()); }); + Assert.Throws(UnsupportedCopyExceptionType, () => { stream.CopyToAsync(new MemoryStream()); }); + } + + if (stream.CanWrite) + { + // Null arguments + foreach ((int offset, int count) in new[] { (0, 0), (1, 2) }) // validate 0, 0 isn't special-cased to be allowed with a null buffer + { + AssertExtensions.Throws(ReadWriteBufferName, () => { stream.Write(null, offset, count); }); + AssertExtensions.Throws(ReadWriteBufferName, () => { stream.WriteAsync(null, offset, count); }); + AssertExtensions.Throws(ReadWriteBufferName, () => { stream.WriteAsync(null, offset, count, default); }); + AssertExtensions.Throws(ReadWriteBufferName, () => { stream.EndWrite(stream.BeginWrite(null, offset, count, iar => { }, new object())); }); + AssertExtensions.Throws(ReadWriteAsyncResultName, () => { stream.EndWrite(null); }); + } + + // Invalid offset + AssertExtensions.Throws(ReadWriteOffsetName, () => { stream.Write(oneByteBuffer, -1, 0); }); + AssertExtensions.Throws(ReadWriteOffsetName, () => { stream.WriteAsync(oneByteBuffer, -1, 0); }); + AssertExtensions.Throws(ReadWriteOffsetName, () => { stream.WriteAsync(oneByteBuffer, -1, 0, default); }); + AssertExtensions.Throws(ReadWriteOffsetName, () => { stream.EndWrite(stream.BeginWrite(oneByteBuffer, -1, 0, iar => { }, new object())); }); + Assert.ThrowsAny(() => { stream.Write(oneByteBuffer, 2, 0); }); + Assert.ThrowsAny(() => { stream.WriteAsync(oneByteBuffer, 2, 0); }); + Assert.ThrowsAny(() => { stream.WriteAsync(oneByteBuffer, 2, 0, default); }); + Assert.ThrowsAny(() => { stream.EndWrite(stream.BeginWrite(oneByteBuffer, 2, 0, iar => { }, new object())); }); + + // Invalid count + AssertExtensions.Throws(ReadWriteCountName, () => { stream.Write(oneByteBuffer, 0, -1); }); + AssertExtensions.Throws(ReadWriteCountName, () => { stream.WriteAsync(oneByteBuffer, 0, -1); }); + AssertExtensions.Throws(ReadWriteCountName, () => { stream.WriteAsync(oneByteBuffer, 0, -1, default); }); + AssertExtensions.Throws(ReadWriteCountName, () => { stream.EndWrite(stream.BeginWrite(oneByteBuffer, 0, -1, iar => { }, new object())); }); + Assert.ThrowsAny(() => { stream.Write(oneByteBuffer, 0, 2); }); + Assert.ThrowsAny(() => { stream.WriteAsync(oneByteBuffer, 0, 2); }); + Assert.ThrowsAny(() => { stream.WriteAsync(oneByteBuffer, 0, 2, default); }); + Assert.ThrowsAny(() => { stream.EndWrite(stream.BeginWrite(oneByteBuffer, 0, 2, iar => { }, new object())); }); + + // Invalid offset + count + foreach ((int invalidOffset, int invalidCount) in new[] { (1, 1) }) + { + Assert.ThrowsAny(() => { stream.Write(oneByteBuffer, invalidOffset, invalidCount); }); + Assert.ThrowsAny(() => { stream.WriteAsync(oneByteBuffer, invalidOffset, invalidCount); }); + Assert.ThrowsAny(() => { stream.WriteAsync(oneByteBuffer, invalidOffset, invalidCount, default); }); + Assert.ThrowsAny(() => { stream.EndWrite(stream.BeginWrite(oneByteBuffer, invalidOffset, invalidCount, iar => { }, new object())); }); + } + + // Unknown arguments + Assert.Throws(InvalidIAsyncResultExceptionType, () => stream.EndWrite(new NotImplementedIAsyncResult())); + } + else + { + Assert.Throws(UnsupportedReadWriteExceptionType, () => { stream.WriteByte(1); }); + Assert.Throws(UnsupportedReadWriteExceptionType, () => { stream.Write(new Span(new byte[1])); }); + Assert.Throws(UnsupportedReadWriteExceptionType, () => { stream.Write(new byte[1], 0, 1); }); + Assert.Throws(UnsupportedReadWriteExceptionType, () => { stream.WriteAsync(new byte[1], 0, 1); }); + Assert.Throws(UnsupportedReadWriteExceptionType, () => { stream.WriteAsync(new Memory(new byte[1])); }); + await Assert.ThrowsAsync(UnsupportedReadWriteExceptionType, () => Task.Factory.FromAsync(stream.BeginWrite, stream.EndWrite, new byte[1], 0, 1, null)); + Assert.True(Record.Exception(() => stream.EndWrite(new NotImplementedIAsyncResult())) is Exception e && (e.GetType() == UnsupportedReadWriteExceptionType || e.GetType() == InvalidIAsyncResultExceptionType)); + } + + Assert.Equal(CanSeek, stream.CanSeek); + if (stream.CanSeek) + { + Assert.Throws(() => { stream.Position = -1; }); + Assert.Throws(() => { stream.Seek(-1, SeekOrigin.Begin); }); + Assert.Throws(() => { stream.Seek(0, (SeekOrigin)(-1)); }); + Assert.Throws(() => { stream.Seek(0, (SeekOrigin)3); }); + Assert.Throws(() => { stream.SetLength(-1); }); + } + else + { + Assert.Throws(() => stream.Length); + if (!CanGetPositionWhenCanSeekIsFalse) + { + Assert.Throws(() => stream.Position); + } + Assert.Throws(() => { stream.Position = 0; }); + Assert.Throws(() => { stream.SetLength(1); }); + Assert.Throws(() => { stream.Seek(0, SeekOrigin.Begin); }); + } + + Assert.Equal(CanTimeout, stream.CanTimeout); + if (stream.CanTimeout) + { + Assert.Throws(() => stream.ReadTimeout = 0); + Assert.Throws(() => stream.ReadTimeout = -2); + Assert.Throws(() => stream.WriteTimeout = 0); + Assert.Throws(() => stream.WriteTimeout = -2); + } + else + { + Assert.Throws(UnsupportedTimeoutExceptionType, () => stream.ReadTimeout); + Assert.Throws(UnsupportedTimeoutExceptionType, () => stream.ReadTimeout = 1); + Assert.Throws(UnsupportedTimeoutExceptionType, () => stream.WriteTimeout); + Assert.Throws(UnsupportedTimeoutExceptionType, () => stream.WriteTimeout = 1); + } + } + + protected async Task ValidateDisposedExceptionsAsync(Stream stream) + { + // NOTE: Some streams may start returning false from the CanXx props after disposal, in which case this test ends up being a nop. + + if (stream.CanRead) + { + Assert.Throws(DisposedExceptionType, () => { stream.ReadByte(); }); + Assert.Throws(DisposedExceptionType, () => { stream.Read(new Span(new byte[1])); }); + Assert.Throws(DisposedExceptionType, () => { stream.Read(new byte[1], 0, 1); }); + await Assert.ThrowsAsync(async () => await stream.ReadAsync(new byte[1], 0, 1)); + await Assert.ThrowsAsync(async () => await stream.ReadAsync(new Memory(new byte[1]))); + Assert.Throws(DisposedExceptionType, () => { stream.EndRead(stream.BeginRead(new byte[1], 0, 1, null, null)); }); + Assert.Throws(DisposedExceptionType, () => { stream.CopyTo(new MemoryStream()); }); + await Assert.ThrowsAsync(async () => await stream.CopyToAsync(new MemoryStream())); + } + + if (stream.CanWrite) + { + Assert.Throws(DisposedExceptionType, () => { stream.WriteByte(1); }); + Assert.Throws(DisposedExceptionType, () => { stream.Write(new Span(new byte[1])); }); + Assert.Throws(DisposedExceptionType, () => { stream.Write(new byte[1], 0, 1); }); + await Assert.ThrowsAsync(async () => await stream.WriteAsync(new byte[1], 0, 1)); + await Assert.ThrowsAsync(async () => await stream.WriteAsync(new Memory(new byte[1]))); + Assert.Throws(DisposedExceptionType, () => { stream.EndWrite(stream.BeginWrite(new byte[1], 0, 1, null, null)); }); + } + + if (stream.CanSeek) + { + Assert.Throws(DisposedExceptionType, () => stream.Length); + Assert.Throws(DisposedExceptionType, () => stream.Position); + Assert.Throws(DisposedExceptionType, () => stream.Position = 0); + Assert.Throws(DisposedExceptionType, () => stream.Seek(0, SeekOrigin.Begin)); + Assert.Throws(DisposedExceptionType, () => stream.SetLength(1)); + } + + if (stream.CanTimeout) + { + Assert.Throws(DisposedExceptionType, () => stream.ReadTimeout); + Assert.Throws(DisposedExceptionType, () => stream.ReadTimeout = 1); + Assert.Throws(DisposedExceptionType, () => stream.WriteTimeout); + Assert.Throws(DisposedExceptionType, () => stream.WriteTimeout = 1); + } + + // Disposal should be idempotent and not throw + stream.Dispose(); + stream.DisposeAsync().AsTask().GetAwaiter().GetResult(); + stream.Close(); + } + + protected async Task ValidatePrecanceledOperations_ThrowsCancellationException(Stream stream) + { + var cts = new CancellationTokenSource(); + cts.Cancel(); + + OperationCanceledException oce; + + if (stream.CanRead) + { + oce = await Assert.ThrowsAnyAsync(() => stream.ReadAsync(new byte[1], 0, 1, cts.Token)); + Assert.Equal(cts.Token, oce.CancellationToken); + + oce = await Assert.ThrowsAnyAsync(async () => { await stream.ReadAsync(new Memory(new byte[1]), cts.Token); }); + Assert.Equal(cts.Token, oce.CancellationToken); + } + + if (stream.CanWrite) + { + oce = await Assert.ThrowsAnyAsync(() => stream.WriteAsync(new byte[1], 0, 1, cts.Token)); + Assert.Equal(cts.Token, oce.CancellationToken); + + oce = await Assert.ThrowsAnyAsync(async () => { await stream.WriteAsync(new ReadOnlyMemory(new byte[1]), cts.Token); }); + Assert.Equal(cts.Token, oce.CancellationToken); + } + + Exception e = await Record.ExceptionAsync(() => stream.FlushAsync(cts.Token)); + if (e != null) + { + Assert.IsAssignableFrom(e); + } + } + + protected async Task ValidateCancelableReads_AfterInvocation_ThrowsCancellationException(Stream stream) + { + if (!stream.CanRead || !FullyCancelableOperations) + { + return; + } + + CancellationTokenSource cts; + OperationCanceledException oce; + + cts = new CancellationTokenSource(1); + oce = await Assert.ThrowsAnyAsync(() => stream.ReadAsync(new byte[1], 0, 1, cts.Token)); + Assert.Equal(cts.Token, oce.CancellationToken); + + cts = new CancellationTokenSource(1); + oce = await Assert.ThrowsAnyAsync(async () => { await stream.ReadAsync(new Memory(new byte[1]), cts.Token); }); + Assert.Equal(cts.Token, oce.CancellationToken); + } + + protected async Task WhenAllOrAnyFailed(Task task1, Task task2) + { + Task completed = await Task.WhenAny(task1, task2); + Task incomplete = task1 == completed ? task2 : task1; + if (completed.IsCompletedSuccessfully) + { + await incomplete; + } + else + { + var cts = new CancellationTokenSource(); + await Task.WhenAny(incomplete, Task.Delay(500, cts.Token)); // give second task a chance to complete + cts.Cancel(); + await (incomplete.IsCompleted ? Task.WhenAll(completed, incomplete) : completed); + } + } + + protected sealed class NotImplementedIAsyncResult : IAsyncResult + { + public object AsyncState => throw new NotImplementedException(); + public WaitHandle AsyncWaitHandle => throw new NotImplementedException(); + public bool CompletedSynchronously => throw new NotImplementedException(); + public bool IsCompleted => throw new NotImplementedException(); + } + + protected sealed class CustomSynchronizationContext : SynchronizationContext + { + public override void Post(SendOrPostCallback d, object state) + { + ThreadPool.QueueUserWorkItem(delegate + { + SetSynchronizationContext(this); + try + { + d(state); + } + finally + { + SetSynchronizationContext(null); + } + }, null); + } + } + + protected sealed class CustomTaskScheduler : TaskScheduler + { + protected override void QueueTask(Task task) => ThreadPool.QueueUserWorkItem(_ => TryExecuteTask(task)); + protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => false; + protected override IEnumerable GetScheduledTasks() => null; + } + + protected readonly struct JumpToThreadPoolAwaiter : ICriticalNotifyCompletion + { + public JumpToThreadPoolAwaiter GetAwaiter() => this; + public bool IsCompleted => false; + public void OnCompleted(Action continuation) => ThreadPool.QueueUserWorkItem(_ => continuation()); + public void UnsafeOnCompleted(Action continuation) => ThreadPool.UnsafeQueueUserWorkItem(_ => continuation(), null); + public void GetResult() { } + } + + protected sealed class MisbehavingDelegatingStream : Stream + { + public enum Mode + { + Default, + ReturnNullTasks, + ReturnTooSmallCounts, + ReturnTooLargeCounts, + ReadSlowly + } + + private readonly Stream _stream; + private readonly Mode _mode; + + public MisbehavingDelegatingStream(Stream innerStream, Mode mode) + { + _stream = innerStream; + _mode = mode; + } + + public override int Read(byte[] buffer, int offset, int count) + { + switch (_mode) + { + case Mode.ReturnTooSmallCounts: + return -1; + case Mode.ReturnTooLargeCounts: + return buffer.Length + 1; + case Mode.ReadSlowly: + return _stream.Read(buffer, offset, 1); + default: + return 0; + } + } + + public override void Write(byte[] buffer, int offset, int count) => + _stream.Write(buffer, offset, count); + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => + _mode == Mode.ReturnNullTasks ? + null : + base.ReadAsync(buffer, offset, count, cancellationToken); + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => + _mode == Mode.ReturnNullTasks ? + null : + base.WriteAsync(buffer, offset, count, cancellationToken); + + public override void Flush() => _stream.Flush(); + public override bool CanRead => _stream.CanRead; + public override bool CanSeek => _stream.CanSeek; + public override bool CanWrite => _stream.CanWrite; + public override long Length => _stream.Length; + public override long Position { get => _stream.Position; set => _stream.Position = value; } + public override long Seek(long offset, SeekOrigin origin) => _stream.Seek(offset, origin); + public override void SetLength(long value) => _stream.SetLength(value); + } + } + + /// Base class providing tests for two streams connected to each other such that writing to one is readable from the other, and vice versa. + public abstract class ConnectedStreamConformanceTests : StreamConformanceTests + { + /// Gets whether ValueTasks returned from Read/WriteAsync methods are expected to be consumable only once. + protected virtual bool ReadWriteValueTasksProtectSingleConsumption => false; + /// Gets whether writes on a connected stream are expected to fail immediately after a reader is disposed. + protected virtual bool BrokenPipePropagatedImmediately => false; + /// Gets the amount of data a writer is able to buffer before blocking subsequent writes, or -1 if there's no such limit known. + protected virtual int BufferedSize => -1; + /// + /// Gets whether the stream requires Flush{Async} to be called in order to send written data to the underlying destination. + /// + protected virtual bool FlushRequiredToWriteData => true; + /// + /// Gets whether the stream guarantees that all data written to it will be flushed as part of Flush{Async}. + /// + protected virtual bool FlushGuaranteesAllDataWritten => true; + /// + /// Gets whether a stream implements an aggressive read that tries to fill the supplied buffer and only + /// stops when it does so or hits EOF. + /// + protected virtual bool ReadsMayBlockUntilBufferFullOrEOF => false; + /// Gets whether reads for a count of 0 bytes block if no bytes are available to read. + protected virtual bool BlocksOnZeroByteReads => false; + /// + /// Gets whether an otherwise bidirectional stream does not support reading/writing concurrently, e.g. due to a semaphore in the base implementation. + /// + protected virtual bool SupportsConcurrentBidirectionalUse => true; + + protected abstract Task CreateConnectedStreamsAsync(); + + protected (Stream writeable, Stream readable) GetReadWritePair(StreamPair streams) => + GetReadWritePairs(streams).First(); + + protected IEnumerable<(Stream writeable, Stream readable)> GetReadWritePairs(StreamPair streams) + { + var pairs = new List<(Stream, Stream)>(2); + + if (streams.Stream1.CanWrite) + { + Assert.True(streams.Stream2.CanRead); + pairs.Add((streams.Stream1, streams.Stream2)); + } + + if (streams.Stream2.CanWrite) + { + Assert.True(streams.Stream1.CanRead); + pairs.Add((streams.Stream2, streams.Stream1)); + } + + Assert.InRange(pairs.Count, 1, 2); + return pairs; + } + + protected static bool Bidirectional(StreamPair streams) => + streams.Stream1.CanRead && streams.Stream1.CanWrite && + streams.Stream2.CanRead && streams.Stream2.CanWrite; + + [Fact] + public virtual async Task ArgumentValidation_ThrowsExpectedException() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + + foreach (Stream stream in streams) + { + await ValidateMisuseExceptionsAsync(stream); + } + } + + [Fact] + public virtual async Task Disposed_ThrowsObjectDisposedException() + { + StreamPair streams = await CreateConnectedStreamsAsync(); + streams.Dispose(); + + foreach (Stream stream in streams) + { + await ValidateDisposedExceptionsAsync(stream); + } + } + + [Fact] + public virtual async Task ReadWriteAsync_Canceled_ThrowsOperationCanceledException() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + + foreach (Stream stream in streams) + { + await ValidatePrecanceledOperations_ThrowsCancellationException(stream); + await ValidateCancelableReads_AfterInvocation_ThrowsCancellationException(stream); + } + } + + [Fact] + public virtual async Task ReadWriteByte_Success() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + byte[] writerBytes = RandomNumberGenerator.GetBytes(42); + var readerBytes = new byte[writerBytes.Length]; + + Task writes = Task.Run(() => + { + foreach (byte b in writerBytes) + { + writeable.WriteByte(b); + } + + if (FlushRequiredToWriteData) + { + if (FlushGuaranteesAllDataWritten) + { + writeable.Flush(); + } + else + { + writeable.Dispose(); + } + } + }); + + for (int i = 0; i < readerBytes.Length; i++) + { + int r = readable.ReadByte(); + Assert.InRange(r, 0, 255); + readerBytes[i] = (byte)r; + } + + Assert.Equal(writerBytes, readerBytes); + + await writes; + + if (!FlushGuaranteesAllDataWritten) + { + break; + } + } + } + + public static IEnumerable ReadWrite_Success_MemberData() => + from mode in new[] { ReadWriteMode.SyncSpan, ReadWriteMode.SyncArray, ReadWriteMode.SyncAPM, ReadWriteMode.AsyncArray, ReadWriteMode.AsyncMemory, ReadWriteMode.AsyncAPM } + from writeSize in new[] { 1, 42, 10 * 1024 } + from startWithFlush in new[] { false, true } + select new object[] { mode, writeSize, startWithFlush }; + + public static IEnumerable ReadWrite_Success_Large_MemberData() => + from mode in new[] { ReadWriteMode.SyncSpan, ReadWriteMode.SyncArray, ReadWriteMode.SyncAPM, ReadWriteMode.AsyncArray, ReadWriteMode.AsyncMemory, ReadWriteMode.AsyncAPM } + from writeSize in new[] { 10 * 1024 * 1024 } + from startWithFlush in new[] { false, true } + select new object[] { mode, writeSize, startWithFlush }; + + [OuterLoop] + [Theory] + [MemberData(nameof(ReadWrite_Success_Large_MemberData))] + public virtual async Task ReadWrite_Success_Large(ReadWriteMode mode, int writeSize, bool startWithFlush) => + await ReadWrite_Success(mode, writeSize, startWithFlush); + + [Theory] + [MemberData(nameof(ReadWrite_Success_MemberData))] + public virtual async Task ReadWrite_Success(ReadWriteMode mode, int writeSize, bool startWithFlush) + { + foreach (CancellationToken nonCanceledToken in new[] { CancellationToken.None, new CancellationTokenSource().Token }) + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + if (startWithFlush) + { + switch (mode) + { + case ReadWriteMode.SyncArray: + case ReadWriteMode.SyncSpan: + case ReadWriteMode.SyncAPM: + writeable.Flush(); + break; + + case ReadWriteMode.AsyncArray: + case ReadWriteMode.AsyncMemory: + case ReadWriteMode.AsyncAPM: + await writeable.FlushAsync(nonCanceledToken); + break; + + default: + throw new Exception($"Unknown mode: {mode}"); + } + } + + byte[] writerBytes = RandomNumberGenerator.GetBytes(writeSize); + var readerBytes = new byte[writerBytes.Length]; + + Task writes = Task.Run(async () => + { + switch (mode) + { + case ReadWriteMode.SyncArray: + writeable.Write(writerBytes, 0, writerBytes.Length); + break; + + case ReadWriteMode.SyncSpan: + writeable.Write(writerBytes); + break; + + case ReadWriteMode.AsyncArray: + await writeable.WriteAsync(writerBytes, 0, writerBytes.Length, nonCanceledToken); + break; + + case ReadWriteMode.AsyncMemory: + await writeable.WriteAsync(writerBytes, nonCanceledToken); + break; + + case ReadWriteMode.SyncAPM: + writeable.EndWrite(writeable.BeginWrite(writerBytes, 0, writerBytes.Length, null, null)); + break; + + case ReadWriteMode.AsyncAPM: + await Task.Factory.FromAsync(writeable.BeginWrite, writeable.EndWrite, writerBytes, 0, writerBytes.Length, null); + break; + + default: + throw new Exception($"Unknown mode: {mode}"); + } + + if (FlushRequiredToWriteData) + { + if (FlushGuaranteesAllDataWritten) + { + await writeable.FlushAsync(); + } + else + { + await writeable.DisposeAsync(); + } + } + }); + + int n = 0; + while (n < readerBytes.Length) + { + int r = mode switch + { + ReadWriteMode.SyncArray => readable.Read(readerBytes, n, readerBytes.Length - n), + ReadWriteMode.SyncSpan => readable.Read(readerBytes.AsSpan(n)), + ReadWriteMode.AsyncArray => await readable.ReadAsync(readerBytes, n, readerBytes.Length - n, nonCanceledToken), + ReadWriteMode.AsyncMemory => await readable.ReadAsync(readerBytes.AsMemory(n), nonCanceledToken), + ReadWriteMode.SyncAPM => readable.EndRead(readable.BeginRead(readerBytes, n, readerBytes.Length - n, null, null)), + ReadWriteMode.AsyncAPM => await Task.Factory.FromAsync(readable.BeginRead, readable.EndRead, readerBytes, n, readerBytes.Length - n, null), + _ => throw new Exception($"Unknown mode: {mode}"), + }; + Assert.InRange(r, 1, readerBytes.Length - n); + n += r; + } + + Assert.Equal(readerBytes.Length, n); + Assert.Equal(writerBytes, readerBytes); + + await writes; + + if (!FlushGuaranteesAllDataWritten) + { + break; + } + } + } + } + + [Theory] + [InlineData(ReadWriteMode.SyncByte, false)] + [InlineData(ReadWriteMode.SyncArray, false)] + [InlineData(ReadWriteMode.SyncSpan, false)] + [InlineData(ReadWriteMode.AsyncArray, false)] + [InlineData(ReadWriteMode.AsyncMemory, false)] + [InlineData(ReadWriteMode.SyncAPM, false)] + [InlineData(ReadWriteMode.AsyncAPM, false)] + [InlineData(ReadWriteMode.SyncByte, true)] + [InlineData(ReadWriteMode.SyncArray, true)] + [InlineData(ReadWriteMode.SyncSpan, true)] + [InlineData(ReadWriteMode.AsyncArray, true)] + [InlineData(ReadWriteMode.AsyncMemory, true)] + [InlineData(ReadWriteMode.SyncAPM, true)] + [InlineData(ReadWriteMode.AsyncAPM, true)] + public virtual async Task Read_Eof_Returns0(ReadWriteMode mode, bool dataAvailableFirst) + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (Stream writeable, Stream readable) = GetReadWritePair(streams); + + Task write; + if (dataAvailableFirst) + { + write = Task.Run(async () => + { + await writeable.WriteAsync(Encoding.UTF8.GetBytes("hello")); + await writeable.DisposeAsync(); + }); + } + else + { + writeable.Dispose(); + write = Task.CompletedTask; + } + + if (dataAvailableFirst) + { + Assert.Equal('h', readable.ReadByte()); + Assert.Equal('e', readable.ReadByte()); + Assert.Equal('l', readable.ReadByte()); + Assert.Equal('l', readable.ReadByte()); + Assert.Equal('o', readable.ReadByte()); + } + + await write; + + if (mode == 0) + { + Assert.Equal(-1, readable.ReadByte()); + } + else + { + Assert.Equal(0, mode switch + { + ReadWriteMode.SyncArray => readable.Read(new byte[1], 0, 1), + ReadWriteMode.SyncSpan => readable.Read(new byte[1]), + ReadWriteMode.AsyncArray => await readable.ReadAsync(new byte[1], 0, 1), + ReadWriteMode.AsyncMemory => await readable.ReadAsync(new byte[1]), + ReadWriteMode.SyncAPM => readable.EndRead(readable.BeginRead(new byte[1], 0, 1, null, null)), + ReadWriteMode.AsyncAPM => await Task.Factory.FromAsync(readable.BeginRead, readable.EndRead, new byte[1], 0, 1, null), + _ => throw new Exception($"Unknown mode: {mode}"), + }); + } + } + + [Theory] + [InlineData(ReadWriteMode.SyncArray)] + [InlineData(ReadWriteMode.AsyncArray)] + [InlineData(ReadWriteMode.AsyncAPM)] + public virtual async Task Read_DataStoredAtDesiredOffset(ReadWriteMode mode) + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (Stream writeable, Stream readable) = GetReadWritePair(streams); + + byte[] buffer = new byte[10]; + int offset = 2; + byte value = 42; + + Task write = Task.Run(() => + { + writeable.WriteByte(value); + writeable.Dispose(); + }); + + Assert.Equal(1, mode switch + { + ReadWriteMode.SyncArray => readable.Read(buffer, offset, buffer.Length - offset), + ReadWriteMode.AsyncArray => await readable.ReadAsync(buffer, offset, buffer.Length - offset), + ReadWriteMode.AsyncAPM => await Task.Factory.FromAsync(readable.BeginRead, readable.EndRead, buffer, offset, buffer.Length - offset, null), + _ => throw new Exception($"Unknown mode: {mode}"), + }); + + for (int i = 0; i < buffer.Length; i++) + { + Assert.Equal(i == offset ? value : 0, buffer[i]); + } + } + + [Theory] + [InlineData(ReadWriteMode.SyncArray)] + [InlineData(ReadWriteMode.AsyncArray)] + [InlineData(ReadWriteMode.AsyncAPM)] + public virtual async Task Write_DataReadFromDesiredOffset(ReadWriteMode mode) + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (Stream writeable, Stream readable) = GetReadWritePair(streams); + + byte[] buffer = new[] { (byte)'\0', (byte)'\0', (byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)'\0', (byte)'\0' }; + const int Offset = 2, Count = 5; + + Task write = Task.Run(async () => + { + switch (mode) + { + case ReadWriteMode.SyncArray: + writeable.Write(buffer, Offset, Count); + break; + + case ReadWriteMode.AsyncArray: + await writeable.WriteAsync(buffer, Offset, Count); + break; + + case ReadWriteMode.AsyncAPM: + await Task.Factory.FromAsync(writeable.BeginWrite, writeable.EndWrite, buffer, Offset, Count, null); + break; + + default: + throw new Exception($"Unknown mode: {mode}"); + } + + writeable.Dispose(); + }); + + Assert.Equal("hello", new StreamReader(readable).ReadToEnd()); + } + + [Fact] + public virtual async Task WriteWithBrokenPipe_Throws() + { + if (!BrokenPipePropagatedImmediately) + { + return; + } + + using StreamPair streams = await CreateConnectedStreamsAsync(); + (Stream writeable, Stream readable) = GetReadWritePair(streams); + + readable.Dispose(); + byte[] buffer = new byte[4]; + + Assert.Throws(() => writeable.WriteByte(123)); + Assert.Throws(() => writeable.Write(buffer)); + Assert.Throws(() => writeable.Write(buffer, 0, buffer.Length)); + await Assert.ThrowsAsync(() => writeable.WriteAsync(buffer).AsTask()); + await Assert.ThrowsAsync(() => writeable.WriteAsync(buffer, 0, buffer.Length)); + Assert.Throws(() => writeable.EndWrite(writeable.BeginWrite(buffer, 0, buffer.Length, null, null))); + await Assert.ThrowsAsync(() => Task.Factory.FromAsync(writeable.BeginWrite, writeable.EndWrite, buffer, 0, buffer.Length, null)); + Assert.Throws(() => writeable.Flush()); + } + + [Fact] + public virtual async Task ReadAsync_NonReusableValueTask_AwaitMultipleTimes_Throws() + { + if (!ReadWriteValueTasksProtectSingleConsumption) + { + return; + } + + using StreamPair streams = await CreateConnectedStreamsAsync(); + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + var bytes = new byte[1]; + + ValueTask r = readable.ReadAsync(bytes); + await writeable.WriteAsync(new byte[] { 42 }); + if (FlushRequiredToWriteData) + { + await writeable.FlushAsync(); + } + Assert.Equal(1, await r); + Assert.Equal(42, bytes[0]); + + await Assert.ThrowsAsync(async () => await r); + Assert.Throws(() => r.GetAwaiter().IsCompleted); + Assert.Throws(() => r.GetAwaiter().OnCompleted(() => { })); + Assert.Throws(() => r.GetAwaiter().GetResult()); + } + } + + [Fact] + public virtual async Task ReadAsync_NonReusableValueTask_MultipleContinuations_Throws() + { + if (!ReadWriteValueTasksProtectSingleConsumption) + { + return; + } + + using StreamPair streams = await CreateConnectedStreamsAsync(); + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + var b = new byte[1]; + ValueTask r = readable.ReadAsync(b); + r.GetAwaiter().OnCompleted(() => { }); + Assert.Throws(() => r.GetAwaiter().OnCompleted(() => { })); + } + } + + public static IEnumerable ReadAsync_ContinuesOnCurrentContextIfDesired_MemberData() => + from flowExecutionContext in new[] { true, false } + from continueOnCapturedContext in new bool?[] { null, false, true } + select new object[] { flowExecutionContext, continueOnCapturedContext }; + + [Theory] + [MemberData(nameof(ReadAsync_ContinuesOnCurrentContextIfDesired_MemberData))] + public virtual async Task ReadAsync_ContinuesOnCurrentSynchronizationContextIfDesired(bool flowExecutionContext, bool? continueOnCapturedContext) + { + await default(JumpToThreadPoolAwaiter); // escape xunit sync ctx + + using StreamPair streams = await CreateConnectedStreamsAsync(); + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + Assert.Null(SynchronizationContext.Current); + + var continuationRan = new TaskCompletionSource(); + var asyncLocal = new AsyncLocal(); + bool schedulerWasFlowed = false; + bool executionContextWasFlowed = false; + Action continuation = () => + { + schedulerWasFlowed = SynchronizationContext.Current is CustomSynchronizationContext; + executionContextWasFlowed = 42 == asyncLocal.Value; + continuationRan.SetResult(true); + }; + + var readBuffer = new byte[1]; + ValueTask readValueTask = readable.ReadAsync(new byte[1]); + + SynchronizationContext.SetSynchronizationContext(new CustomSynchronizationContext()); + asyncLocal.Value = 42; + switch (continueOnCapturedContext) + { + case null: + if (flowExecutionContext) + { + readValueTask.GetAwaiter().OnCompleted(continuation); + } + else + { + readValueTask.GetAwaiter().UnsafeOnCompleted(continuation); + } + break; + default: + if (flowExecutionContext) + { + readValueTask.ConfigureAwait(continueOnCapturedContext.Value).GetAwaiter().OnCompleted(continuation); + } + else + { + readValueTask.ConfigureAwait(continueOnCapturedContext.Value).GetAwaiter().UnsafeOnCompleted(continuation); + } + break; + } + asyncLocal.Value = 0; + SynchronizationContext.SetSynchronizationContext(null); + + Assert.False(readValueTask.IsCompleted); + Assert.False(readValueTask.IsCompletedSuccessfully); + await writeable.WriteAsync(new byte[] { 42 }); + if (FlushRequiredToWriteData) + { + if (FlushGuaranteesAllDataWritten) + { + await writeable.FlushAsync(); + } + else + { + await writeable.DisposeAsync(); + } + } + + await continuationRan.Task; + Assert.True(readValueTask.IsCompleted); + Assert.True(readValueTask.IsCompletedSuccessfully); + + Assert.Equal(continueOnCapturedContext != false, schedulerWasFlowed); + Assert.Equal(flowExecutionContext, executionContextWasFlowed); + + if (!FlushGuaranteesAllDataWritten) + { + break; + } + } + } + + [Theory] + [MemberData(nameof(ReadAsync_ContinuesOnCurrentContextIfDesired_MemberData))] + public virtual async Task ReadAsync_ContinuesOnCurrentTaskSchedulerIfDesired(bool flowExecutionContext, bool? continueOnCapturedContext) + { + await default(JumpToThreadPoolAwaiter); // escape xunit sync ctx + + using StreamPair streams = await CreateConnectedStreamsAsync(); + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + Assert.Null(SynchronizationContext.Current); + + var continuationRan = new TaskCompletionSource(); + var asyncLocal = new AsyncLocal(); + bool schedulerWasFlowed = false; + bool executionContextWasFlowed = false; + Action continuation = () => + { + schedulerWasFlowed = TaskScheduler.Current is CustomTaskScheduler; + executionContextWasFlowed = 42 == asyncLocal.Value; + continuationRan.SetResult(); + }; + + var readBuffer = new byte[1]; + ValueTask readValueTask = readable.ReadAsync(new byte[1]); + + await Task.Factory.StartNew(() => + { + Assert.IsType(TaskScheduler.Current); + asyncLocal.Value = 42; + switch (continueOnCapturedContext) + { + case null: + if (flowExecutionContext) + { + readValueTask.GetAwaiter().OnCompleted(continuation); + } + else + { + readValueTask.GetAwaiter().UnsafeOnCompleted(continuation); + } + break; + default: + if (flowExecutionContext) + { + readValueTask.ConfigureAwait(continueOnCapturedContext.Value).GetAwaiter().OnCompleted(continuation); + } + else + { + readValueTask.ConfigureAwait(continueOnCapturedContext.Value).GetAwaiter().UnsafeOnCompleted(continuation); + } + break; + } + asyncLocal.Value = 0; + }, CancellationToken.None, TaskCreationOptions.None, new CustomTaskScheduler()); + + Assert.False(readValueTask.IsCompleted); + Assert.False(readValueTask.IsCompletedSuccessfully); + await writeable.WriteAsync(new byte[] { 42 }); + if (FlushRequiredToWriteData) + { + if (FlushGuaranteesAllDataWritten) + { + await writeable.FlushAsync(); + } + else + { + await writeable.DisposeAsync(); + } + } + + await continuationRan.Task; + Assert.True(readValueTask.IsCompleted); + Assert.True(readValueTask.IsCompletedSuccessfully); + + Assert.Equal(continueOnCapturedContext != false, schedulerWasFlowed); + Assert.Equal(flowExecutionContext, executionContextWasFlowed); + + if (!FlushGuaranteesAllDataWritten) + { + break; + } + } + } + + [Theory] + [InlineData(ReadWriteMode.SyncArray)] + [InlineData(ReadWriteMode.SyncSpan)] + [InlineData(ReadWriteMode.AsyncArray)] + [InlineData(ReadWriteMode.AsyncMemory)] + [InlineData(ReadWriteMode.SyncAPM)] + [InlineData(ReadWriteMode.AsyncAPM)] + public virtual async Task ZeroByteRead_BlocksUntilDataAvailableOrNops(ReadWriteMode mode) + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + for (int iter = 0; iter < 2; iter++) + { + Task zeroByteRead = mode switch + { + ReadWriteMode.SyncSpan => Task.Run(() => readable.Read(Span.Empty)), + ReadWriteMode.SyncArray => Task.Run(() => readable.Read(new byte[0], 0, 0)), + ReadWriteMode.AsyncArray => readable.ReadAsync(new byte[0], 0, 0), + ReadWriteMode.AsyncMemory => readable.ReadAsync(Memory.Empty).AsTask(), + ReadWriteMode.SyncAPM => Task.Run(() => readable.EndRead(readable.BeginRead(Array.Empty(), 0, 0, null, null))), + ReadWriteMode.AsyncAPM => Task.Factory.FromAsync(readable.BeginRead, readable.EndRead, Array.Empty(), 0, 0, null), + _ => throw new Exception($"Unknown mode: {mode}"), + }; + + if (BlocksOnZeroByteReads) + { + Assert.False(zeroByteRead.IsCompleted); + + Task write = Task.Run(async () => + { + await writeable.WriteAsync(Encoding.UTF8.GetBytes("hello")); + if (FlushRequiredToWriteData) + { + if (FlushGuaranteesAllDataWritten) + { + await writeable.FlushAsync(); + } + else + { + await writeable.DisposeAsync(); + } + } + }); + Assert.Equal(0, await zeroByteRead); + + var readBytes = new byte[5]; + int count = 0; + while (count < readBytes.Length) + { + int n = await readable.ReadAsync(readBytes.AsMemory(count)); + Assert.InRange(n, 1, readBytes.Length - count); + count += n; + } + + Assert.Equal("hello", Encoding.UTF8.GetString(readBytes)); + await write; + } + else + { + Assert.Equal(0, await zeroByteRead); + } + + if (!FlushGuaranteesAllDataWritten) + { + return; + } + } + } + } + + [Theory] + [InlineData(ReadWriteMode.SyncArray)] + [InlineData(ReadWriteMode.SyncSpan)] + [InlineData(ReadWriteMode.AsyncArray)] + [InlineData(ReadWriteMode.AsyncMemory)] + [InlineData(ReadWriteMode.SyncAPM)] + [InlineData(ReadWriteMode.AsyncAPM)] + public virtual async Task ZeroByteWrite_OtherDataReceivedSuccessfully(ReadWriteMode mode) + { + byte[][] buffers = new[] { Array.Empty(), Encoding.UTF8.GetBytes("hello"), Array.Empty(), Encoding.UTF8.GetBytes("world") }; + + using StreamPair streams = await CreateConnectedStreamsAsync(); + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + Task writes; + switch (mode) + { + case ReadWriteMode.SyncSpan: + writes = Task.Run(() => + { + foreach (byte[] buffer in buffers) + { + writeable.Write(buffer.AsSpan()); + } + }); + break; + + case ReadWriteMode.SyncArray: + writes = Task.Run(() => + { + foreach (byte[] buffer in buffers) + { + writeable.Write(buffer, 0, buffer.Length); + } + }); + break; + + case ReadWriteMode.AsyncArray: + writes = Task.Run(async () => + { + foreach (byte[] buffer in buffers) + { + await writeable.WriteAsync(buffer, 0, buffer.Length); + } + }); + break; + + case ReadWriteMode.AsyncMemory: + writes = Task.Run(async () => + { + foreach (byte[] buffer in buffers) + { + await writeable.WriteAsync(buffer); + } + }); + break; + + case ReadWriteMode.SyncAPM: + writes = Task.Run(() => + { + foreach (byte[] buffer in buffers) + { + writeable.EndWrite(writeable.BeginWrite(buffer, 0, buffer.Length, null, null)); + } + }); + break; + + case ReadWriteMode.AsyncAPM: + writes = Task.Run(async () => + { + foreach (byte[] buffer in buffers) + { + await Task.Factory.FromAsync(writeable.BeginWrite, writeable.EndWrite, buffer, 0, buffer.Length, null); + } + }); + break; + + default: + throw new Exception($"Unknown mode: {mode}"); + } + + if (FlushRequiredToWriteData) + { + writes = writes.ContinueWith(t => + { + t.GetAwaiter().GetResult(); + if (FlushGuaranteesAllDataWritten) + { + writeable.Flush(); + } + else + { + writeable.Dispose(); + } + }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default); + } + + var readBytes = new byte[buffers.Sum(b => b.Length)]; + int count = 0; + while (count < readBytes.Length) + { + int n = await readable.ReadAsync(readBytes.AsMemory(count)); + Assert.InRange(n, 1, readBytes.Length - count); + count += n; + } + + Assert.Equal("helloworld", Encoding.UTF8.GetString(readBytes)); + await writes; + + if (!FlushGuaranteesAllDataWritten) + { + break; + } + } + } + + [Fact] + public virtual async Task ConcurrentBidirectionalReadsWrites_Success() + { + if (!SupportsConcurrentBidirectionalUse) + { + return; + } + + using StreamPair streams = await CreateConnectedStreamsAsync(); + Stream client = streams.Stream1, server = streams.Stream2; + if (!(client.CanRead && client.CanWrite && server.CanRead && server.CanWrite)) + { + return; + } + + const string Text = "This is a test. This is only a test."; + byte[] sendBuffer = Encoding.UTF8.GetBytes(Text); + DateTime endTime = DateTime.UtcNow + TimeSpan.FromSeconds(2); + Func work = async (client, server) => + { + var readBuffer = new byte[sendBuffer.Length]; + while (DateTime.UtcNow < endTime) + { + await WhenAllOrAnyFailed( + client.WriteAsync(sendBuffer, 0, sendBuffer.Length), + Task.Run(async () => + { + int received = 0, bytesRead = 0; + while (received < readBuffer.Length && (bytesRead = await server.ReadAsync(readBuffer.AsMemory(received))) > 0) + { + received += bytesRead; + } + Assert.InRange(bytesRead, 1, int.MaxValue); + Assert.Equal(Text, Encoding.UTF8.GetString(readBuffer)); + })); + } + }; + + await WhenAllOrAnyFailed( + Task.Run(() => work(client, server)), + Task.Run(() => work(server, client))); + } + + public static IEnumerable CopyToAsync_AllDataCopied_MemberData() => + from byteCount in new int[] { 0, 1, 1024, 4096, 4095, 1024 * 1024 } + from asyncWrite in new bool[] { true, false } + select new object[] { byteCount, asyncWrite }; + + [Theory] + [MemberData(nameof(CopyToAsync_AllDataCopied_MemberData))] + public virtual async Task CopyToAsync_AllDataCopied(int byteCount, bool asyncWrite) + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (Stream writeable, Stream readable) = GetReadWritePair(streams); + + var results = new MemoryStream(); + byte[] dataToCopy = RandomNumberGenerator.GetBytes(byteCount); + + Task copyTask; + if (asyncWrite) + { + copyTask = readable.CopyToAsync(results); + await writeable.WriteAsync(dataToCopy); + } + else + { + copyTask = Task.Run(() => readable.CopyTo(results)); + writeable.Write(new ReadOnlySpan(dataToCopy)); + } + + writeable.Dispose(); + await copyTask; + + Assert.Equal(dataToCopy, results.ToArray()); + } + + [OuterLoop("May take several seconds")] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public virtual async Task Parallel_ReadWriteMultipleStreamsConcurrently() + { + await Task.WhenAll(Enumerable.Range(0, 20).Select(_ => Task.Run(async () => + { + await CopyToAsync_AllDataCopied(byteCount: 10 * 1024, asyncWrite: true); + }))); + } + + [Fact] + public virtual async Task Timeout_Roundtrips() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + if (writeable.CanTimeout) + { + Assert.Equal(-1, writeable.WriteTimeout); + + writeable.WriteTimeout = 100; + Assert.InRange(writeable.WriteTimeout, 100, int.MaxValue); + writeable.WriteTimeout = 100; // same value again + Assert.InRange(writeable.WriteTimeout, 100, int.MaxValue); + + writeable.WriteTimeout = -1; + Assert.Equal(-1, writeable.WriteTimeout); + } + + if (readable.CanTimeout) + { + Assert.Equal(-1, readable.ReadTimeout); + + readable.ReadTimeout = 100; + Assert.InRange(readable.ReadTimeout, 100, int.MaxValue); + readable.ReadTimeout = 100; // same value again + Assert.InRange(readable.ReadTimeout, 100, int.MaxValue); + + readable.ReadTimeout = -1; + Assert.Equal(-1, readable.ReadTimeout); + } + } + } + + [Fact] + public virtual async Task ReadTimeout_Expires_Throws() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + if (!readable.CanTimeout) + { + continue; + } + + Assert.Equal(-1, readable.ReadTimeout); + + readable.ReadTimeout = 1; + Assert.ThrowsAny(() => readable.Read(new byte[1], 0, 1)); + } + } + + [Fact] + public virtual async Task ReadAsync_CancelPendingRead_DoesntImpactSubsequentReads() + { + if (!UsableAfterCanceledReads) + { + return; + } + + using StreamPair streams = await CreateConnectedStreamsAsync(); + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + await Assert.ThrowsAnyAsync(() => readable.ReadAsync(new byte[1], 0, 1, new CancellationToken(true))); + await Assert.ThrowsAnyAsync(async () => { await readable.ReadAsync(new Memory(new byte[1]), new CancellationToken(true)); }); + + var cts = new CancellationTokenSource(); + Task t = readable.ReadAsync(new byte[1], 0, 1, cts.Token); + cts.Cancel(); + await Assert.ThrowsAnyAsync(() => t); + + cts = new CancellationTokenSource(); + ValueTask vt = readable.ReadAsync(new Memory(new byte[1]), cts.Token); + cts.Cancel(); + await Assert.ThrowsAnyAsync(async () => await vt); + + byte[] buffer = new byte[1]; + vt = readable.ReadAsync(new Memory(buffer)); + Assert.False(vt.IsCompleted); + await writeable.WriteAsync(new ReadOnlyMemory(new byte[1] { 42 })); + if (FlushRequiredToWriteData) + { + await writeable.FlushAsync(); + } + Assert.Equal(1, await vt); + Assert.Equal(42, buffer[0]); + } + } + + [Fact] + public virtual async Task WriteAsync_CancelPendingWrite_SucceedsOrThrowsOperationCanceled() + { + if (BufferedSize == -1) + { + return; + } + + using StreamPair streams = await CreateConnectedStreamsAsync(); + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + await Assert.ThrowsAnyAsync(() => writeable.WriteAsync(new byte[1], 0, 1, new CancellationToken(true))); + await Assert.ThrowsAnyAsync(async () => { await writeable.WriteAsync(new Memory(new byte[1]), new CancellationToken(true)); }); + + var buffer = new byte[BufferedSize + 1]; + Exception e; + + var cts = new CancellationTokenSource(); + Task t = writeable.WriteAsync(buffer, 0, buffer.Length, cts.Token); + cts.Cancel(); + e = await Record.ExceptionAsync(async () => await t); + if (e != null) + { + Assert.IsAssignableFrom(e); + } + + cts = new CancellationTokenSource(); + ValueTask vt = writeable.WriteAsync(new Memory(buffer), cts.Token); + cts.Cancel(); + e = await Record.ExceptionAsync(async () => await vt); + if (e != null) + { + Assert.IsAssignableFrom(e); + } + } + } + + [Fact] + public virtual async Task ClosedConnection_WritesFailImmediately_ThrowException() + { + if (!BrokenPipePropagatedImmediately) + { + return; + } + + using StreamPair streams = await CreateConnectedStreamsAsync(); + (Stream writeable, Stream readable) = GetReadWritePair(streams); + + readable.Dispose(); + Assert.Throws(() => writeable.WriteByte(1)); + Assert.Throws(() => writeable.Write(new byte[1], 0, 1)); + Assert.Throws(() => writeable.Write(new byte[1])); + Assert.Throws(() => writeable.EndWrite(writeable.BeginWrite(new byte[1], 0, 1, null, null))); + await Assert.ThrowsAsync(async () => { await writeable.WriteAsync(new byte[1], 0, 1); }); + await Assert.ThrowsAsync(async () => { await writeable.WriteAsync(new byte[1]); }); + await Assert.ThrowsAsync(async () => { await Task.Factory.FromAsync(writeable.BeginWrite, writeable.EndWrite, new byte[1], 0, 1, null); }); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public virtual async Task ReadAsync_DuringReadAsync_ThrowsIfUnsupported() + { + if (UnsupportedConcurrentExceptionType is null) + { + return; + } + + using StreamPair streams = await CreateConnectedStreamsAsync(); + (Stream writeable, Stream readable) = GetReadWritePair(streams); + + ValueTask read = readable.ReadAsync(new byte[1]); + await Assert.ThrowsAsync(UnsupportedConcurrentExceptionType, async () => await readable.ReadAsync(new byte[1])); + + writeable.WriteByte(1); + writeable.Dispose(); + + Assert.Equal(1, await read); + } + + [Fact] + public virtual async Task Flush_ValidOnWriteableStreamWithNoData_Success() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + foreach (Stream stream in streams) + { + if (stream.CanWrite) + { + stream.Flush(); + await stream.FlushAsync(); + } + } + } + + [Fact] + public virtual async Task Flush_ValidOnReadableStream_Success() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + foreach (Stream stream in streams) + { + if (stream.CanRead && !stream.CanWrite) + { + stream.Flush(); + await stream.FlushAsync(); + } + } + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public virtual async Task Dispose_ClosesStream(int disposeMode) + { + if (!CansReturnFalseAfterDispose) + { + return; + } + + StreamPair streams = await CreateConnectedStreamsAsync(); + + foreach (Stream stream in streams) + { + switch (disposeMode) + { + case 0: stream.Close(); break; + case 1: stream.Dispose(); break; + case 2: await stream.DisposeAsync(); break; + } + + Assert.False(stream.CanRead); + Assert.False(stream.CanWrite); + } + } + } + + /// Base class for a connected stream that wraps another. + public abstract class WrappingConnectedStreamConformanceTests : ConnectedStreamConformanceTests + { + protected abstract Task CreateWrappedConnectedStreamsAsync(StreamPair wrapped, bool leaveOpen = false); + protected virtual bool WrappedUsableAfterClose => true; + protected virtual bool SupportsLeaveOpen => true; + + // TODO: Add tests for sync calling sync and async calling async + [Theory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Flush_FlushesUnderlyingStream(bool flushAsync) + { + if (!FlushGuaranteesAllDataWritten) + { + return; + } + + using StreamPair streams = ConnectedStreams.CreateBidirectional(); + (Stream writeable, Stream readable) = GetReadWritePair(streams); + + var tracker = new CallTrackingStream(writeable); + using StreamPair wrapper = await CreateWrappedConnectedStreamsAsync((tracker, readable)); + + Assert.Equal(0, tracker.TimesCalled(nameof(tracker.Flush)) + tracker.TimesCalled(nameof(tracker.FlushAsync))); + + tracker.WriteByte(1); + + if (flushAsync) + { + await wrapper.Stream1.FlushAsync(); + } + else + { + wrapper.Stream1.Flush(); + } + + Assert.NotEqual(0, tracker.TimesCalled(nameof(tracker.Flush)) + tracker.TimesCalled(nameof(tracker.FlushAsync))); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public virtual async Task Dispose_Flushes(bool useAsync, bool leaveOpen) + { + if (leaveOpen && (!SupportsLeaveOpen || ReadsMayBlockUntilBufferFullOrEOF)) + { + return; + } + + using StreamPair streams = ConnectedStreams.CreateBidirectional(); + using StreamPair wrapper = await CreateWrappedConnectedStreamsAsync(streams, leaveOpen); + (Stream writeable, Stream readable) = GetReadWritePair(wrapper); + + writeable.WriteByte(1); + + if (useAsync) + { + await writeable.DisposeAsync(); + } + else + { + writeable.Dispose(); + } + + Assert.Equal(1, readable.ReadByte()); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public virtual async Task Dispose_ClosesInnerStreamIfDesired(bool useAsync, bool leaveOpen) + { + if (!SupportsLeaveOpen && leaveOpen) + { + return; + } + + using StreamPair streams = ConnectedStreams.CreateBidirectional(); + (Stream writeable, Stream readable) = GetReadWritePair(streams); + using StreamPair wrapper = await CreateWrappedConnectedStreamsAsync((writeable, readable), leaveOpen); + (Stream writeableWrapper, Stream readableWrapper) = GetReadWritePair(wrapper); + + if (useAsync) + { + await writeableWrapper.DisposeAsync(); + } + else + { + writeableWrapper.Dispose(); + } + + if (leaveOpen) + { + await WhenAllOrAnyFailed( + writeable.WriteAsync(new byte[] { 42 }, 0, 1), + Task.Run(() => readable.ReadByte())); + } + else + { + Assert.Throws(DisposedExceptionType, () => writeable.WriteByte(42)); + } + } + + [Fact] + public virtual async Task UseWrappedAfterClose_Success() + { + if (!WrappedUsableAfterClose || !SupportsLeaveOpen) + { + return; + } + + using StreamPair streams = ConnectedStreams.CreateBidirectional(); + + using (StreamPair wrapper = await CreateWrappedConnectedStreamsAsync(streams, leaveOpen: true)) + { + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(wrapper)) + { + writeable.WriteByte(42); + readable.ReadByte(); + } + } + + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + writeable.WriteByte(42); + readable.ReadByte(); + } + } + + [Fact] + public virtual async Task NestedWithinSelf_ReadWrite_Success() + { + using StreamPair streams = ConnectedStreams.CreateBidirectional(); + using StreamPair wrapper1 = await CreateWrappedConnectedStreamsAsync(streams); + using StreamPair wrapper2 = await CreateWrappedConnectedStreamsAsync(wrapper1); + using StreamPair wrapper3 = await CreateWrappedConnectedStreamsAsync(wrapper2); + + if (Bidirectional(wrapper3) && FlushGuaranteesAllDataWritten) + { + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(wrapper3)) + { + for (int i = 0; i < 10; i++) + { + await WhenAllOrAnyFailed( + Task.Run(() => + { + writeable.WriteByte((byte)i); + if (FlushRequiredToWriteData) + { + writeable.Flush(); + } + }), + Task.Run(() => Assert.Equal(i, readable.ReadByte()))); + } + } + } + else + { + (Stream writeable, Stream readable) = GetReadWritePair(wrapper3); + await WhenAllOrAnyFailed( + Task.Run(() => + { + for (int i = 0; i < 10; i++) + { + writeable.WriteByte((byte)i); + } + writeable.Dispose(); + }), + Task.Run(() => + { + for (int i = 0; i < 10; i++) + { + Assert.Equal(i, readable.ReadByte()); + } + Assert.Equal(-1, readable.ReadByte()); + })); + } + } + } + + /// Provides a disposable, enumerable tuple of two streams. + public class StreamPair : IDisposable, IEnumerable + { + public readonly Stream Stream1, Stream2; + + public StreamPair(Stream stream1, Stream stream2) + { + Stream1 = stream1; + Stream2 = stream2; + } + + public StreamPair((Stream, Stream) streams) + { + Stream1 = streams.Item1; + Stream2 = streams.Item2; + } + + public static implicit operator StreamPair((Stream, Stream) streams) => new StreamPair(streams); + public static implicit operator (Stream, Stream)(StreamPair streams) => (streams.Stream1, streams.Stream2); + + public virtual void Dispose() + { + Stream1?.Dispose(); + Stream2?.Dispose(); + } + + public IEnumerator GetEnumerator() + { + yield return Stream1; + yield return Stream2; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} From 59238b730c76edb7097db3b17b7d6c7771026cff Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:10:43 -0400 Subject: [PATCH 04/24] Add ConnectedStreams tests These are currently helpers used by many other tests. At some point they could become public API as well. --- .../Common/tests/Common.Tests.csproj | 6 +++++ .../tests/System/IO/ConnectedStreams.cs | 3 ++- .../Tests/System/IO/ConnectedStreamsTests.cs | 25 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 src/libraries/Common/tests/Tests/System/IO/ConnectedStreamsTests.cs diff --git a/src/libraries/Common/tests/Common.Tests.csproj b/src/libraries/Common/tests/Common.Tests.csproj index b7c9f6b3ebabc4..12e815eb4a5535 100644 --- a/src/libraries/Common/tests/Common.Tests.csproj +++ b/src/libraries/Common/tests/Common.Tests.csproj @@ -102,6 +102,12 @@ Link="Common\System\Threading\Tasks\TaskToApm.cs" /> + + + + + + (asyncResult); } @@ -207,6 +207,7 @@ public override void WriteByte(byte value) public override void Write(ReadOnlySpan buffer) { ThrowIfDisposed(); + ThrowIfWritingNotSupported(); _buffer.Write(buffer); } diff --git a/src/libraries/Common/tests/Tests/System/IO/ConnectedStreamsTests.cs b/src/libraries/Common/tests/Tests/System/IO/ConnectedStreamsTests.cs new file mode 100644 index 00000000000000..0f700bf68f0f71 --- /dev/null +++ b/src/libraries/Common/tests/Tests/System/IO/ConnectedStreamsTests.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading.Tasks; + +namespace System.IO.Tests +{ + public class UnidirectionalConnectedStreamsTests : ConnectedStreamConformanceTests + { + protected override int BufferedSize => StreamBuffer.DefaultMaxBufferSize; + protected override bool FlushRequiredToWriteData => false; + + protected override Task CreateConnectedStreamsAsync() => + Task.FromResult(ConnectedStreams.CreateUnidirectional()); + } + + public class BidirectionalConnectedStreamsTests : ConnectedStreamConformanceTests + { + protected override int BufferedSize => StreamBuffer.DefaultMaxBufferSize; + protected override bool FlushRequiredToWriteData => false; + + protected override Task CreateConnectedStreamsAsync() => + Task.FromResult(ConnectedStreams.CreateBidirectional()); + } +} From 0d61950702d2b488fab01519b78e57dd5dd678ad Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:12:47 -0400 Subject: [PATCH 05/24] Fix NetworkStream argument names Technically a breaking change, but the current divergence from the base Stream class is a bug, and bringing them into sync means argument exceptions that emerge from the derived type make sense when used via the base type, as well as then being able to use shared validation logic across all streams (subsequent to these changes). --- .../ref/System.Net.Sockets.cs | 12 ++--- .../src/System/Net/Sockets/NetworkStream.cs | 48 +++++++++---------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs b/src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs index aeb11216ab04c6..9e96d15ef0dbff 100644 --- a/src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs +++ b/src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs @@ -132,8 +132,8 @@ public NetworkStream(System.Net.Sockets.Socket socket, System.IO.FileAccess acce public System.Net.Sockets.Socket Socket { get { throw null; } } protected bool Writeable { get { throw null; } set { } } public override int WriteTimeout { get { throw null; } set { } } - public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int size, System.AsyncCallback? callback, object? state) { throw null; } - public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int size, System.AsyncCallback? callback, object? state) { throw null; } + public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int count, System.AsyncCallback? callback, object? state) { throw null; } + public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int count, System.AsyncCallback? callback, object? state) { throw null; } public void Close(int timeout) { } protected override void Dispose(bool disposing) { } public override int EndRead(System.IAsyncResult asyncResult) { throw null; } @@ -141,16 +141,16 @@ public override void EndWrite(System.IAsyncResult asyncResult) { } ~NetworkStream() { } public override void Flush() { } public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; } - public override int Read(byte[] buffer, int offset, int size) { throw null; } + public override int Read(byte[] buffer, int offset, int count) { throw null; } public override int Read(System.Span buffer) { throw null; } - public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int size, System.Threading.CancellationToken cancellationToken) { throw null; } + public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public override int ReadByte() { throw null; } public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } public override void SetLength(long value) { } - public override void Write(byte[] buffer, int offset, int size) { } + public override void Write(byte[] buffer, int offset, int count) { } public override void Write(System.ReadOnlySpan buffer) { } - public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int size, System.Threading.CancellationToken cancellationToken) { throw null; } + public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public override void WriteByte(byte value) { } } diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/NetworkStream.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/NetworkStream.cs index 7ee46c5546819e..bd9613de65637c 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/NetworkStream.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/NetworkStream.cs @@ -214,7 +214,7 @@ public override long Seek(long offset, SeekOrigin origin) // Returns: // // Number of bytes we read, or 0 if the socket is closed. - public override int Read(byte[] buffer, int offset, int size) + public override int Read(byte[] buffer, int offset, int count) { bool canRead = CanRead; // Prevent race with Dispose. ThrowIfDisposed(); @@ -232,14 +232,14 @@ public override int Read(byte[] buffer, int offset, int size) { throw new ArgumentOutOfRangeException(nameof(offset)); } - if ((uint)size > buffer.Length - offset) + if ((uint)count > buffer.Length - offset) { - throw new ArgumentOutOfRangeException(nameof(size)); + throw new ArgumentOutOfRangeException(nameof(count)); } try { - return _streamSocket.Receive(buffer, offset, size, 0); + return _streamSocket.Receive(buffer, offset, count, 0); } catch (SocketException socketException) { @@ -295,7 +295,7 @@ public override unsafe int ReadByte() // Number of bytes written. We'll throw an exception if we // can't write everything. It's brutal, but there's no other // way to indicate an error. - public override void Write(byte[] buffer, int offset, int size) + public override void Write(byte[] buffer, int offset, int count) { bool canWrite = CanWrite; // Prevent race with Dispose. ThrowIfDisposed(); @@ -313,16 +313,16 @@ public override void Write(byte[] buffer, int offset, int size) { throw new ArgumentOutOfRangeException(nameof(offset)); } - if ((uint)size > buffer.Length - offset) + if ((uint)count > buffer.Length - offset) { - throw new ArgumentOutOfRangeException(nameof(size)); + throw new ArgumentOutOfRangeException(nameof(count)); } try { // Since the socket is in blocking mode this will always complete // after ALL the requested number of bytes was transferred. - _streamSocket.Send(buffer, offset, size, SocketFlags.None); + _streamSocket.Send(buffer, offset, count, SocketFlags.None); } catch (SocketException socketException) { @@ -414,7 +414,7 @@ protected override void Dispose(bool disposing) // Returns: // // An IASyncResult, representing the read. - public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback? callback, object? state) + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) { bool canRead = CanRead; // Prevent race with Dispose. ThrowIfDisposed(); @@ -432,9 +432,9 @@ public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, Asyn { throw new ArgumentOutOfRangeException(nameof(offset)); } - if ((uint)size > buffer.Length - offset) + if ((uint)count > buffer.Length - offset) { - throw new ArgumentOutOfRangeException(nameof(size)); + throw new ArgumentOutOfRangeException(nameof(count)); } try @@ -442,7 +442,7 @@ public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, Asyn return _streamSocket.BeginReceive( buffer, offset, - size, + count, SocketFlags.None, callback, state); @@ -503,7 +503,7 @@ public override int EndRead(IAsyncResult asyncResult) // Returns: // // An IASyncResult, representing the write. - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback? callback, object? state) + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) { bool canWrite = CanWrite; // Prevent race with Dispose. ThrowIfDisposed(); @@ -521,9 +521,9 @@ public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, Asy { throw new ArgumentOutOfRangeException(nameof(offset)); } - if ((uint)size > buffer.Length - offset) + if ((uint)count > buffer.Length - offset) { - throw new ArgumentOutOfRangeException(nameof(size)); + throw new ArgumentOutOfRangeException(nameof(count)); } try @@ -532,7 +532,7 @@ public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, Asy return _streamSocket.BeginSend( buffer, offset, - size, + count, SocketFlags.None, callback, state); @@ -590,7 +590,7 @@ public override void EndWrite(IAsyncResult asyncResult) // Returns: // // A Task representing the read. - public override Task ReadAsync(byte[] buffer, int offset, int size, CancellationToken cancellationToken) + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { bool canRead = CanRead; // Prevent race with Dispose. ThrowIfDisposed(); @@ -608,15 +608,15 @@ public override Task ReadAsync(byte[] buffer, int offset, int size, Cancell { throw new ArgumentOutOfRangeException(nameof(offset)); } - if ((uint)size > buffer.Length - offset) + if ((uint)count > buffer.Length - offset) { - throw new ArgumentOutOfRangeException(nameof(size)); + throw new ArgumentOutOfRangeException(nameof(count)); } try { return _streamSocket.ReceiveAsync( - new Memory(buffer, offset, size), + new Memory(buffer, offset, count), SocketFlags.None, fromNetworkStream: true, cancellationToken).AsTask(); @@ -673,7 +673,7 @@ public override ValueTask ReadAsync(Memory buffer, CancellationToken // Returns: // // A Task representing the write. - public override Task WriteAsync(byte[] buffer, int offset, int size, CancellationToken cancellationToken) + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { bool canWrite = CanWrite; // Prevent race with Dispose. ThrowIfDisposed(); @@ -691,15 +691,15 @@ public override Task WriteAsync(byte[] buffer, int offset, int size, Cancellatio { throw new ArgumentOutOfRangeException(nameof(offset)); } - if ((uint)size > buffer.Length - offset) + if ((uint)count > buffer.Length - offset) { - throw new ArgumentOutOfRangeException(nameof(size)); + throw new ArgumentOutOfRangeException(nameof(count)); } try { return _streamSocket.SendAsyncForNetworkStream( - new ReadOnlyMemory(buffer, offset, size), + new ReadOnlyMemory(buffer, offset, count), SocketFlags.None, cancellationToken).AsTask(); } From b20d5813217fe96b65d1312d5f3d75634bb9ce4c Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:13:32 -0400 Subject: [PATCH 06/24] Add stream conformance tests for NetworkStream --- .../tests/FunctionalTests/LoggingTest.cs | 2 +- .../FunctionalTests/NetworkStreamTest.cs | 718 +----------------- .../System.Net.Sockets.Tests.csproj | 7 +- .../tests/FunctionalTests/TelemetryTest.cs | 2 +- 4 files changed, 30 insertions(+), 699 deletions(-) diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/LoggingTest.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/LoggingTest.cs index 0dd31ae386d713..77f12d7679c2b8 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/LoggingTest.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/LoggingTest.cs @@ -57,7 +57,7 @@ public void EventSource_EventsRaisedAsExpected() new SendReceiveApm(null).SendRecv_Stream_TCP(IPAddress.Loopback, true).GetAwaiter(); new NetworkStreamTest().CopyToAsync_AllDataCopied(4096, true).GetAwaiter().GetResult(); - new NetworkStreamTest().Timeout_ValidData_Roundtrips().GetAwaiter().GetResult(); + new NetworkStreamTest().Timeout_Roundtrips().GetAwaiter().GetResult(); }); Assert.DoesNotContain(events, ev => ev.EventId == 0); // errors from the EventSource itself Assert.InRange(events.Count, 1, int.MaxValue); diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/NetworkStreamTest.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/NetworkStreamTest.cs index da51240e0f8b25..0c9c3e2cfb4a2e 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/NetworkStreamTest.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/NetworkStreamTest.cs @@ -3,17 +3,37 @@ using System.Collections.Generic; using System.IO; +using System.IO.Tests; using System.Linq; using System.Runtime.InteropServices; -using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using Xunit; namespace System.Net.Sockets.Tests { - public class NetworkStreamTest + public class NetworkStreamTest : ConnectedStreamConformanceTests { + protected override bool BlocksOnZeroByteReads => true; + protected override bool CanTimeout => true; + protected override Type InvalidIAsyncResultExceptionType => typeof(IOException); + protected override bool FlushRequiredToWriteData => false; + protected override int BufferedSize => 99_999_999; // something sufficiently large to exhaust kernel buffer + protected override Type UnsupportedConcurrentExceptionType => null; + protected override bool ReadWriteValueTasksProtectSingleConsumption => true; + protected override Task CreateConnectedStreamsAsync() + { + using Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + listener.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + listener.Listen(); + + var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + client.Connect(listener.LocalEndPoint); + Socket server = listener.Accept(); + + return Task.FromResult((new NetworkStream(client, ownsSocket: true), new NetworkStream(server, ownsSocket: true))); + } + [Fact] public void Ctor_NullSocket_ThrowsArgumentNullExceptions() { @@ -271,36 +291,6 @@ await RunWithConnectedNetworkStreamsAsync(async (server, client) => }); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task DisposedClosed_MembersThrowObjectDisposedException(bool close) - { - await RunWithConnectedNetworkStreamsAsync((server, _) => - { - if (close) server.Close(); - else server.Dispose(); - - Assert.Throws(() => server.DataAvailable); - - Assert.Throws(() => server.Read(new byte[1], 0, 1)); - Assert.Throws(() => server.Write(new byte[1], 0, 1)); - - Assert.Throws(() => server.BeginRead(new byte[1], 0, 1, null, null)); - Assert.Throws(() => server.BeginWrite(new byte[1], 0, 1, null, null)); - - Assert.Throws(() => server.EndRead(null)); - Assert.Throws(() => server.EndWrite(null)); - - Assert.Throws(() => { server.ReadAsync(new byte[1], 0, 1); }); - Assert.Throws(() => { server.WriteAsync(new byte[1], 0, 1); }); - - Assert.Throws(() => { server.CopyToAsync(new MemoryStream()); }); - - return Task.CompletedTask; - }); - } - [Fact] public async Task DisposeSocketDirectly_ReadWriteThrowNetworkException() { @@ -329,17 +319,6 @@ public async Task DisposeSocketDirectly_ReadWriteThrowNetworkException() } } - [Fact] - public async Task InvalidIAsyncResult_EndReadWriteThrows() - { - await RunWithConnectedNetworkStreamsAsync((server, _) => - { - Assert.Throws(() => server.EndRead(Task.CompletedTask)); - Assert.Throws(() => server.EndWrite(Task.CompletedTask)); - return Task.CompletedTask; - }); - } - [Fact] public async Task Close_InvalidArgument_Throws() { @@ -352,69 +331,6 @@ await RunWithConnectedNetworkStreamsAsync((server, _) => }); } - [Fact] - public async Task ReadWrite_InvalidArguments_Throws() - { - await RunWithConnectedNetworkStreamsAsync((server, _) => - { - Assert.Throws(() => server.Read(null, 0, 0)); - Assert.Throws(() => server.Read(new byte[1], -1, 0)); - Assert.Throws(() => server.Read(new byte[1], 2, 0)); - Assert.Throws(() => server.Read(new byte[1], 0, -1)); - Assert.Throws(() => server.Read(new byte[1], 0, 2)); - - Assert.Throws(() => server.BeginRead(null, 0, 0, null, null)); - Assert.Throws(() => server.BeginRead(new byte[1], -1, 0, null, null)); - Assert.Throws(() => server.BeginRead(new byte[1], 2, 0, null, null)); - Assert.Throws(() => server.BeginRead(new byte[1], 0, -1, null, null)); - Assert.Throws(() => server.BeginRead(new byte[1], 0, 2, null, null)); - - Assert.Throws(() => { server.ReadAsync(null, 0, 0); }); - Assert.Throws(() => { server.ReadAsync(new byte[1], -1, 0); }); - Assert.Throws(() => { server.ReadAsync(new byte[1], 2, 0); }); - Assert.Throws(() => { server.ReadAsync(new byte[1], 0, -1); }); - Assert.Throws(() => { server.ReadAsync(new byte[1], 0, 2); }); - - Assert.Throws(() => server.Write(null, 0, 0)); - Assert.Throws(() => server.Write(new byte[1], -1, 0)); - Assert.Throws(() => server.Write(new byte[1], 2, 0)); - Assert.Throws(() => server.Write(new byte[1], 0, -1)); - Assert.Throws(() => server.Write(new byte[1], 0, 2)); - - Assert.Throws(() => server.BeginWrite(null, 0, 0, null, null)); - Assert.Throws(() => server.BeginWrite(new byte[1], -1, 0, null, null)); - Assert.Throws(() => server.BeginWrite(new byte[1], 2, 0, null, null)); - Assert.Throws(() => server.BeginWrite(new byte[1], 0, -1, null, null)); - Assert.Throws(() => server.BeginWrite(new byte[1], 0, 2, null, null)); - - Assert.Throws(() => { server.WriteAsync(null, 0, 0); }); - Assert.Throws(() => { server.WriteAsync(new byte[1], -1, 0); }); - Assert.Throws(() => { server.WriteAsync(new byte[1], 2, 0); }); - Assert.Throws(() => { server.WriteAsync(new byte[1], 0, -1); }); - Assert.Throws(() => { server.WriteAsync(new byte[1], 0, 2); }); - - Assert.Throws(() => server.EndRead(null)); - Assert.Throws(() => server.EndWrite(null)); - - return Task.CompletedTask; - }); - } - - [Fact] - public async Task NotSeekable_OperationsThrowExceptions() - { - await RunWithConnectedNetworkStreamsAsync((server, client) => - { - Assert.False(server.CanSeek && client.CanSeek); - Assert.Throws(() => server.Seek(0, SeekOrigin.Begin)); - Assert.Throws(() => server.Length); - Assert.Throws(() => server.SetLength(1024)); - Assert.Throws(() => server.Position); - Assert.Throws(() => server.Position = 0); - return Task.CompletedTask; - }); - } - [Fact] public async Task ReadableWriteableProperties_Roundtrip() { @@ -456,246 +372,6 @@ public async Task ReadableWriteableProperties_Roundtrip() } } - [Fact] - public async Task ReadWrite_Byte_Success() - { - await RunWithConnectedNetworkStreamsAsync(async (server, client) => - { - for (byte i = 0; i < 10; i++) - { - Task read = Task.Run(() => client.ReadByte()); - Task write = Task.Run(() => server.WriteByte(i)); - await Task.WhenAll(read, write); - Assert.Equal(i, await read); - } - }); - } - - [Fact] - public async Task ReadWrite_Array_Success() - { - await RunWithConnectedNetworkStreamsAsync((server, client) => - { - var clientData = new byte[] { 42 }; - client.Write(clientData, 0, clientData.Length); - - var serverData = new byte[clientData.Length]; - Assert.Equal(serverData.Length, server.Read(serverData, 0, serverData.Length)); - - Assert.Equal(clientData, serverData); - - client.Flush(); // nop - - return Task.CompletedTask; - }); - } - - [OuterLoop] - [Theory] - [MemberData(nameof(NonCanceledTokens))] - public async Task ReadWriteAsync_NonCanceled_Success(CancellationToken nonCanceledToken) - { - await RunWithConnectedNetworkStreamsAsync(async (server, client) => - { - var clientData = new byte[] { 42 }; - await client.WriteAsync(clientData, 0, clientData.Length, nonCanceledToken); - - var serverData = new byte[clientData.Length]; - Assert.Equal(serverData.Length, await server.ReadAsync(serverData, 0, serverData.Length, nonCanceledToken)); - - Assert.Equal(clientData, serverData); - - Assert.Equal(TaskStatus.RanToCompletion, client.FlushAsync().Status); // nop - }); - } - - [Fact] - public async Task BeginEndReadWrite_Sync_Success() - { - await RunWithConnectedNetworkStreamsAsync((server, client) => - { - var clientData = new byte[] { 42 }; - - client.EndWrite(client.BeginWrite(clientData, 0, clientData.Length, null, null)); - - var serverData = new byte[clientData.Length]; - Assert.Equal(serverData.Length, server.EndRead(server.BeginRead(serverData, 0, serverData.Length, null, null))); - - Assert.Equal(clientData, serverData); - - return Task.CompletedTask; - }); - } - - [Fact] - public async Task BeginEndReadWrite_Async_Success() - { - await RunWithConnectedNetworkStreamsAsync(async (server, client) => - { - var clientData = new byte[] { 42 }; - var serverData = new byte[clientData.Length]; - var tcs = new TaskCompletionSource(); - - client.BeginWrite(clientData, 0, clientData.Length, writeIar => - { - try - { - client.EndWrite(writeIar); - server.BeginRead(serverData, 0, serverData.Length, readIar => - { - try - { - Assert.Equal(serverData.Length, server.EndRead(readIar)); - tcs.SetResult(); - } - catch (Exception e2) { tcs.SetException(e2); } - }, null); - } - catch (Exception e1) { tcs.SetException(e1); } - }, null); - - await tcs.Task; - Assert.Equal(clientData, serverData); - }); - } - - [OuterLoop] - [Fact] - public async Task ReadWriteAsync_Canceled_ThrowsOperationCanceledException() - { - await RunWithConnectedNetworkStreamsAsync(async (server, client) => - { - var canceledToken = new CancellationToken(canceled: true); - await Assert.ThrowsAnyAsync(() => client.WriteAsync(new byte[1], 0, 1, canceledToken)); - await Assert.ThrowsAnyAsync(() => server.ReadAsync(new byte[1], 0, 1, canceledToken)); - }); - } - - public static object[][] NonCanceledTokens = new object[][] - { - new object[] { CancellationToken.None }, // CanBeCanceled == false - new object[] { new CancellationTokenSource().Token } // CanBeCanceled == true - }; - - [OuterLoop("Timeouts")] - [Fact] - public async Task ReadTimeout_Expires_Throws() - { - await RunWithConnectedNetworkStreamsAsync((server, client) => - { - Assert.Equal(-1, server.ReadTimeout); - - server.ReadTimeout = 1; - Assert.ThrowsAny(() => server.Read(new byte[1], 0, 1)); - - return Task.CompletedTask; - }); - } - - [Theory] - [InlineData(0)] - [InlineData(-2)] - public async Task Timeout_InvalidData_ThrowsArgumentException(int invalidTimeout) - { - await RunWithConnectedNetworkStreamsAsync((server, client) => - { - AssertExtensions.Throws("value", () => server.ReadTimeout = invalidTimeout); - AssertExtensions.Throws("value", () => server.WriteTimeout = invalidTimeout); - return Task.CompletedTask; - }); - } - - [Fact] - public async Task Timeout_ValidData_Roundtrips() - { - await RunWithConnectedNetworkStreamsAsync((server, client) => - { - Assert.Equal(-1, server.ReadTimeout); - Assert.Equal(-1, server.WriteTimeout); - - server.ReadTimeout = 100; - Assert.InRange(server.ReadTimeout, 100, int.MaxValue); - server.ReadTimeout = 100; // same value again - Assert.InRange(server.ReadTimeout, 100, int.MaxValue); - - server.ReadTimeout = -1; - Assert.Equal(-1, server.ReadTimeout); - - server.WriteTimeout = 100; - Assert.InRange(server.WriteTimeout, 100, int.MaxValue); - server.WriteTimeout = 100; // same value again - Assert.InRange(server.WriteTimeout, 100, int.MaxValue); - - server.WriteTimeout = -1; - Assert.Equal(-1, server.WriteTimeout); - - return Task.CompletedTask; - }); - } - - public static IEnumerable CopyToAsync_AllDataCopied_MemberData() => - from asyncWrite in new bool[] { true, false } - from byteCount in new int[] { 0, 1, 1024, 4096, 4095, 1024 * 1024 } - select new object[] { byteCount, asyncWrite }; - - [Theory] - [MemberData(nameof(CopyToAsync_AllDataCopied_MemberData))] - public async Task CopyToAsync_AllDataCopied(int byteCount, bool asyncWrite) - { - await RunWithConnectedNetworkStreamsAsync(async (server, client) => - { - var results = new MemoryStream(); - byte[] dataToCopy = new byte[byteCount]; - new Random().NextBytes(dataToCopy); - - Task copyTask = client.CopyToAsync(results); - - if (asyncWrite) - { - await server.WriteAsync(dataToCopy, 0, dataToCopy.Length); - } - else - { - server.Write(new ReadOnlySpan(dataToCopy, 0, dataToCopy.Length)); - } - - server.Dispose(); - await copyTask; - - Assert.Equal(dataToCopy, results.ToArray()); - }); - } - - [Fact] - public async Task CopyToAsync_InvalidArguments_Throws() - { - await RunWithConnectedNetworkStreamsAsync((stream, _) => - { - // Null destination - AssertExtensions.Throws("destination", () => { stream.CopyToAsync(null); }); - - // Buffer size out-of-range - AssertExtensions.Throws("bufferSize", () => { stream.CopyToAsync(new MemoryStream(), 0); }); - AssertExtensions.Throws("bufferSize", () => { stream.CopyToAsync(new MemoryStream(), -1, CancellationToken.None); }); - - // Copying to non-writable stream - Assert.Throws(() => { stream.CopyToAsync(new MemoryStream(new byte[0], writable: false)); }); - - // Copying to a disposed stream - Assert.Throws(() => - { - var disposedTarget = new MemoryStream(); - disposedTarget.Dispose(); - stream.CopyToAsync(disposedTarget); - }); - - // Already canceled - Assert.Equal(TaskStatus.Canceled, stream.CopyToAsync(new MemoryStream(new byte[1]), 1, new CancellationToken(canceled: true)).Status); - - return Task.CompletedTask; - }); - } - [Fact] public async Task CopyToAsync_DisposedSourceStream_ThrowsOnWindows_NoThrowOnUnix() { @@ -722,7 +398,6 @@ await RunWithConnectedNetworkStreamsAsync(async (stream, _) => }); } - [Fact] public async Task CopyToAsync_NonReadableSourceStream_Throws() { @@ -734,111 +409,6 @@ await RunWithConnectedNetworkStreamsAsync((stream, _) => }, serverAccess:FileAccess.Write); } - [Fact] - public async Task ReadWrite_Span_Success() - { - await RunWithConnectedNetworkStreamsAsync((server, client) => - { - var clientData = new byte[] { 42 }; - - client.Write((ReadOnlySpan)clientData); - - var serverData = new byte[clientData.Length]; - Assert.Equal(serverData.Length, server.Read((Span)serverData)); - - Assert.Equal(clientData, serverData); - return Task.CompletedTask; - }); - } - - [Fact] - public async Task ReadWrite_Memory_Success() - { - await RunWithConnectedNetworkStreamsAsync(async (server, client) => - { - var clientData = new byte[] { 42 }; - - await client.WriteAsync((ReadOnlyMemory)clientData); - - var serverData = new byte[clientData.Length]; - Assert.Equal(serverData.Length, await server.ReadAsync((Memory)serverData)); - - Assert.Equal(clientData, serverData); - }); - } - - [Fact] - public async Task ReadWrite_Memory_LargeWrite_Success() - { - await RunWithConnectedNetworkStreamsAsync(async (server, client) => - { - var writeBuffer = new byte[10 * 1024 * 1024]; - var readBuffer = new byte[writeBuffer.Length]; - RandomNumberGenerator.Fill(writeBuffer); - - ValueTask writeTask = client.WriteAsync((ReadOnlyMemory)writeBuffer); - - int totalRead = 0; - while (totalRead < readBuffer.Length) - { - int bytesRead = await server.ReadAsync(new Memory(readBuffer).Slice(totalRead)); - Assert.InRange(bytesRead, 0, int.MaxValue); - if (bytesRead == 0) - { - break; - } - totalRead += bytesRead; - } - Assert.Equal(readBuffer.Length, totalRead); - Assert.Equal(writeBuffer, readBuffer); - - await writeTask; - }); - } - - [Fact] - public async Task ReadWrite_Precanceled_Throws() - { - await RunWithConnectedNetworkStreamsAsync(async (server, client) => - { - await Assert.ThrowsAnyAsync(async () => await server.WriteAsync((ArraySegment)new byte[0], new CancellationToken(true))); - await Assert.ThrowsAnyAsync(async () => await server.ReadAsync((ArraySegment)new byte[0], new CancellationToken(true))); - - await Assert.ThrowsAnyAsync(async () => await server.WriteAsync((ReadOnlyMemory)new byte[0], new CancellationToken(true))); - await Assert.ThrowsAnyAsync(async () => await server.ReadAsync((Memory)new byte[0], new CancellationToken(true))); - }); - } - - [Fact] - public async Task ReadAsync_AwaitMultipleTimes_Throws() - { - await RunWithConnectedNetworkStreamsAsync(async (server, client) => - { - var b = new byte[1]; - ValueTask r = server.ReadAsync(b); - await client.WriteAsync(new byte[] { 42 }); - Assert.Equal(1, await r); - Assert.Equal(42, b[0]); - await Assert.ThrowsAsync(async () => await r); - Assert.Throws(() => r.GetAwaiter().IsCompleted); - Assert.Throws(() => r.GetAwaiter().OnCompleted(() => { })); - Assert.Throws(() => r.GetAwaiter().GetResult()); - }); - } - - [Fact] - public async Task ReadAsync_MultipleContinuations_Throws() - { - await RunWithConnectedNetworkStreamsAsync((server, client) => - { - var b = new byte[1]; - ValueTask r = server.ReadAsync(b); - r.GetAwaiter().OnCompleted(() => { }); - Assert.Throws(() => r.GetAwaiter().OnCompleted(() => { })); - return Task.CompletedTask; - }); - } - [Fact] public async Task ReadAsync_MultipleConcurrentValueTaskReads_Success() { @@ -929,250 +499,6 @@ await RunWithConnectedNetworkStreamsAsync(async (server, client) => }); } - public static IEnumerable ReadAsync_ContinuesOnCurrentContextIfDesired_MemberData() => - from flowExecutionContext in new[] { true, false } - from continueOnCapturedContext in new bool?[] { null, false, true } - select new object[] { flowExecutionContext, continueOnCapturedContext }; - - [Theory] - [MemberData(nameof(ReadAsync_ContinuesOnCurrentContextIfDesired_MemberData))] - public async Task ReadAsync_ContinuesOnCurrentSynchronizationContextIfDesired( - bool flowExecutionContext, bool? continueOnCapturedContext) - { - await Task.Run(async () => // escape xunit sync ctx - { - await RunWithConnectedNetworkStreamsAsync(async (server, client) => - { - Assert.Null(SynchronizationContext.Current); - - var continuationRan = new TaskCompletionSource(); - var asyncLocal = new AsyncLocal(); - bool schedulerWasFlowed = false; - bool executionContextWasFlowed = false; - Action continuation = () => - { - schedulerWasFlowed = SynchronizationContext.Current is CustomSynchronizationContext; - executionContextWasFlowed = 42 == asyncLocal.Value; - continuationRan.SetResult(true); - }; - - var readBuffer = new byte[1]; - ValueTask readValueTask = client.ReadAsync((Memory)new byte[1]); - - SynchronizationContext.SetSynchronizationContext(new CustomSynchronizationContext()); - asyncLocal.Value = 42; - switch (continueOnCapturedContext) - { - case null: - if (flowExecutionContext) - { - readValueTask.GetAwaiter().OnCompleted(continuation); - } - else - { - readValueTask.GetAwaiter().UnsafeOnCompleted(continuation); - } - break; - default: - if (flowExecutionContext) - { - readValueTask.ConfigureAwait(continueOnCapturedContext.Value).GetAwaiter().OnCompleted(continuation); - } - else - { - readValueTask.ConfigureAwait(continueOnCapturedContext.Value).GetAwaiter().UnsafeOnCompleted(continuation); - } - break; - } - asyncLocal.Value = 0; - SynchronizationContext.SetSynchronizationContext(null); - - Assert.False(readValueTask.IsCompleted); - Assert.False(readValueTask.IsCompletedSuccessfully); - await server.WriteAsync(new byte[] { 42 }); - - await continuationRan.Task; - Assert.True(readValueTask.IsCompleted); - Assert.True(readValueTask.IsCompletedSuccessfully); - - Assert.Equal(continueOnCapturedContext != false, schedulerWasFlowed); - Assert.Equal(flowExecutionContext, executionContextWasFlowed); - }); - }); - } - - [Theory] - [MemberData(nameof(ReadAsync_ContinuesOnCurrentContextIfDesired_MemberData))] - public async Task ReadAsync_ContinuesOnCurrentTaskSchedulerIfDesired( - bool flowExecutionContext, bool? continueOnCapturedContext) - { - await Task.Run(async () => // escape xunit sync ctx - { - await RunWithConnectedNetworkStreamsAsync(async (server, client) => - { - Assert.Null(SynchronizationContext.Current); - - var continuationRan = new TaskCompletionSource(); - var asyncLocal = new AsyncLocal(); - bool schedulerWasFlowed = false; - bool executionContextWasFlowed = false; - Action continuation = () => - { - schedulerWasFlowed = TaskScheduler.Current is CustomTaskScheduler; - executionContextWasFlowed = 42 == asyncLocal.Value; - continuationRan.SetResult(); - }; - - var readBuffer = new byte[1]; - ValueTask readValueTask = client.ReadAsync((Memory)new byte[1]); - - await Task.Factory.StartNew(() => - { - Assert.IsType(TaskScheduler.Current); - asyncLocal.Value = 42; - switch (continueOnCapturedContext) - { - case null: - if (flowExecutionContext) - { - readValueTask.GetAwaiter().OnCompleted(continuation); - } - else - { - readValueTask.GetAwaiter().UnsafeOnCompleted(continuation); - } - break; - default: - if (flowExecutionContext) - { - readValueTask.ConfigureAwait(continueOnCapturedContext.Value).GetAwaiter().OnCompleted(continuation); - } - else - { - readValueTask.ConfigureAwait(continueOnCapturedContext.Value).GetAwaiter().UnsafeOnCompleted(continuation); - } - break; - } - asyncLocal.Value = 0; - }, CancellationToken.None, TaskCreationOptions.None, new CustomTaskScheduler()); - - Assert.False(readValueTask.IsCompleted); - Assert.False(readValueTask.IsCompletedSuccessfully); - await server.WriteAsync(new byte[] { 42 }); - - await continuationRan.Task; - Assert.True(readValueTask.IsCompleted); - Assert.True(readValueTask.IsCompletedSuccessfully); - - Assert.Equal(continueOnCapturedContext != false, schedulerWasFlowed); - Assert.Equal(flowExecutionContext, executionContextWasFlowed); - }); - }); - } - - [Fact] - public async Task DisposeAsync_ClosesStream() - { - await RunWithConnectedNetworkStreamsAsync(async (server, client) => - { - Assert.True(client.DisposeAsync().IsCompletedSuccessfully); - Assert.True(server.DisposeAsync().IsCompletedSuccessfully); - - await client.DisposeAsync(); - await server.DisposeAsync(); - - Assert.False(server.CanRead); - Assert.False(server.CanWrite); - - Assert.False(client.CanRead); - Assert.False(client.CanWrite); - }); - } - - [Fact] - public async Task ReadAsync_CancelPendingRead_DoesntImpactSubsequentReads() - { - await RunWithConnectedNetworkStreamsAsync(async (server, client) => - { - await Assert.ThrowsAnyAsync(() => client.ReadAsync(new byte[1], 0, 1, new CancellationToken(true))); - await Assert.ThrowsAnyAsync(async () => { await client.ReadAsync(new Memory(new byte[1]), new CancellationToken(true)); }); - - CancellationTokenSource cts = new CancellationTokenSource(); - Task t = client.ReadAsync(new byte[1], 0, 1, cts.Token); - cts.Cancel(); - await Assert.ThrowsAnyAsync(() => t); - - cts = new CancellationTokenSource(); - ValueTask vt = client.ReadAsync(new Memory(new byte[1]), cts.Token); - cts.Cancel(); - await Assert.ThrowsAnyAsync(async () => await vt); - - byte[] buffer = new byte[1]; - vt = client.ReadAsync(new Memory(buffer)); - Assert.False(vt.IsCompleted); - await server.WriteAsync(new ReadOnlyMemory(new byte[1] { 42 })); - Assert.Equal(1, await vt); - Assert.Equal(42, buffer[0]); - }); - } - - [Fact] - public async Task WriteAsync_CancelPendingWrite_SucceedsOrThrowsOperationCanceled() - { - await RunWithConnectedNetworkStreamsAsync(async (server, client) => - { - await Assert.ThrowsAnyAsync(() => client.WriteAsync(new byte[1], 0, 1, new CancellationToken(true))); - await Assert.ThrowsAnyAsync(async () => { await client.WriteAsync(new Memory(new byte[1]), new CancellationToken(true)); }); - - byte[] hugeBuffer = new byte[100_000_000]; - Exception e; - - var cts = new CancellationTokenSource(); - Task t = client.WriteAsync(hugeBuffer, 0, hugeBuffer.Length, cts.Token); - cts.Cancel(); - e = await Record.ExceptionAsync(async () => await t); - if (e != null) - { - Assert.IsAssignableFrom(e); - } - - cts = new CancellationTokenSource(); - ValueTask vt = client.WriteAsync(new Memory(hugeBuffer), cts.Token); - cts.Cancel(); - e = await Record.ExceptionAsync(async () => await vt); - if (e != null) - { - Assert.IsAssignableFrom(e); - } - }); - } - - private sealed class CustomSynchronizationContext : SynchronizationContext - { - public override void Post(SendOrPostCallback d, object state) - { - ThreadPool.QueueUserWorkItem(delegate - { - SetSynchronizationContext(this); - try - { - d(state); - } - finally - { - SetSynchronizationContext(null); - } - }, null); - } - } - - private sealed class CustomTaskScheduler : TaskScheduler - { - protected override void QueueTask(Task task) => ThreadPool.QueueUserWorkItem(_ => TryExecuteTask(task)); - protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => false; - protected override IEnumerable GetScheduledTasks() => null; - } - /// /// Creates a pair of connected NetworkStreams and invokes the provided /// with them as arguments. diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj b/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj index 8c3c0cc02e2941..87813790a04162 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj @@ -91,5 +91,10 @@ Link="Common\System\Diagnostics\Tracing\TestEventListener.cs" /> + + + + + - \ No newline at end of file + diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/TelemetryTest.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/TelemetryTest.cs index bb6a8bd2adf622..144557c77636e7 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/TelemetryTest.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/TelemetryTest.cs @@ -365,7 +365,7 @@ await listener.RunWithCallbackAsync(e => events.Enqueue((e, e.ActivityId)), asyn await new SendReceiveUdpClient().SendToRecvFromAsync_Datagram_UDP_UdpClient(IPAddress.Loopback).ConfigureAwait(false); await new NetworkStreamTest().CopyToAsync_AllDataCopied(4096, true).ConfigureAwait(false); - await new NetworkStreamTest().Timeout_ValidData_Roundtrips().ConfigureAwait(false); + await new NetworkStreamTest().Timeout_Roundtrips().ConfigureAwait(false); await WaitForEventCountersAsync(events); }); From 80e15eb255ae5565e5c4af4e14d3a0faed4acf69 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:14:23 -0400 Subject: [PATCH 07/24] Add stream conformance tests for QuicStream --- ...icStreamConnectedStreamConformanceTests.cs | 77 +++++++++++++++++++ .../System.Net.Quic.Functional.Tests.csproj | 8 +- 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs new file mode 100644 index 00000000000000..9e50c2e50b646b --- /dev/null +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.IO.Tests; +using System.Net.Quic.Implementations; +using System.Net.Security; +using System.Threading.Tasks; +using Xunit; + +namespace System.Net.Quic.Tests +{ + public class QuicStreamConformanceTests : ConnectedStreamConformanceTests + { + // TODO: These are all hanging, likely due to Stream close behavior. + [ActiveIssue("https://github.com/dotnet/runtime/issues/756")] + public override Task Read_Eof_Returns0(ReadWriteMode mode, bool dataAvailableFirst) => base.Read_Eof_Returns0(mode, dataAvailableFirst); + [ActiveIssue("https://github.com/dotnet/runtime/issues/756")] + public override Task CopyToAsync_AllDataCopied(int byteCount, bool asyncWrite) => base.CopyToAsync_AllDataCopied(byteCount, asyncWrite); + [ActiveIssue("https://github.com/dotnet/runtime/issues/756")] + public override Task Dispose_ClosesStream(int disposeMode) => base.Dispose_ClosesStream(disposeMode); + [ActiveIssue("https://github.com/dotnet/runtime/issues/756")] + public override Task Write_DataReadFromDesiredOffset(ReadWriteMode mode) => base.Write_DataReadFromDesiredOffset(mode); + + protected override async Task CreateConnectedStreamsAsync() + { + QuicImplementationProvider provider = QuicImplementationProviders.Mock; + var protocol = new SslApplicationProtocol("quictest"); + + var listener = new QuicListener( + provider, + new IPEndPoint(IPAddress.Loopback, 0), + new SslServerAuthenticationOptions { ApplicationProtocols = new List { protocol } }); + listener.Start(); + + QuicConnection connection1 = null, connection2 = null; + QuicStream stream1 = null, stream2 = null; + + await WhenAllOrAnyFailed( + Task.Run(async () => + { + connection1 = await listener.AcceptConnectionAsync(); + stream1 = await connection1.AcceptStreamAsync(); + }), + Task.Run(async () => + { + connection2 = new QuicConnection( + provider, + listener.ListenEndPoint, + new SslClientAuthenticationOptions() { ApplicationProtocols = new List() { protocol } }); + await connection2.ConnectAsync(); + stream2 = connection2.OpenBidirectionalStream(); + })); + + var result = new StreamPairWithOtherDisposables(stream1, stream2); + result.Disposables.Add(connection1); + result.Disposables.Add(connection2); + result.Disposables.Add(listener); + + return result; + } + + private sealed class StreamPairWithOtherDisposables : StreamPair + { + public readonly List Disposables = new List(); + + public StreamPairWithOtherDisposables(Stream stream1, Stream stream2) : base(stream1, stream2) { } + + public override void Dispose() + { + base.Dispose(); + Disposables.ForEach(d => d.Dispose()); + } + } + } +} diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj b/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj index ab524c2950c765..52343e0f9b0817 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj @@ -1,4 +1,4 @@ - + true true @@ -8,6 +8,12 @@ + + + + + + From a7f69649c2bc1192ff7428f242ac20bdab858264 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:19:07 -0400 Subject: [PATCH 08/24] Fix several CryptoStream behaviors 1. Flushing a stream that wraps another stream for writing should always flush that underlying stream, even if no additional data was written as part of the flush. 2. Argument validation should validate buffers are not null rather than null ref'ing on a null buffer. 3. Checks for the CryptoStream mode should come after argument validation. --- .../Security/Cryptography/CryptoStream.cs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/CryptoStream.cs b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/CryptoStream.cs index 2e426cc4185381..6fc21ad70482c3 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/CryptoStream.cs +++ b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/CryptoStream.cs @@ -167,7 +167,10 @@ private async ValueTask FlushFinalBlockAsync(bool useAsync, CancellationToken ca public override void Flush() { - return; + if (_canWrite) + { + _stream.Flush(); + } } public override Task FlushAsync(CancellationToken cancellationToken) @@ -181,7 +184,8 @@ public override Task FlushAsync(CancellationToken cancellationToken) return cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : - Task.CompletedTask; + !_canWrite ? Task.CompletedTask : + _stream.FlushAsync(cancellationToken); } public override long Seek(long offset, SeekOrigin origin) @@ -269,14 +273,16 @@ public override int Read(byte[] buffer, int offset, int count) private void CheckReadArguments(byte[] buffer, int offset, int count) { - if (!CanRead) - throw new NotSupportedException(SR.NotSupported_UnreadableStream); + if (buffer is null) + throw new ArgumentNullException(nameof(buffer)); if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); if (buffer.Length - offset < count) throw new ArgumentException(SR.Argument_InvalidOffLen); + if (!CanRead) + throw new NotSupportedException(SR.NotSupported_UnreadableStream); } private async Task ReadAsyncCore(byte[] buffer, int offset, int count, CancellationToken cancellationToken, bool useAsync) @@ -516,14 +522,16 @@ public override void Write(byte[] buffer, int offset, int count) private void CheckWriteArguments(byte[] buffer, int offset, int count) { - if (!CanWrite) - throw new NotSupportedException(SR.NotSupported_UnwritableStream); + if (buffer is null) + throw new ArgumentNullException(nameof(buffer)); if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); if (buffer.Length - offset < count) throw new ArgumentException(SR.Argument_InvalidOffLen); + if (!CanWrite) + throw new NotSupportedException(SR.NotSupported_UnwritableStream); } private async ValueTask WriteAsyncCore(byte[] buffer, int offset, int count, CancellationToken cancellationToken, bool useAsync) From 78bc0acf9a3dcf7b4e5462c7c5d9b63d27989ee0 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:19:41 -0400 Subject: [PATCH 09/24] Add stream conformance tests for CryptoStream --- .../tests/CryptoStream.cs | 57 ++++++++----------- ...urity.Cryptography.Primitives.Tests.csproj | 6 ++ 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.Primitives/tests/CryptoStream.cs b/src/libraries/System.Security.Cryptography.Primitives/tests/CryptoStream.cs index 6156e9c2ea200b..2da76648d97f47 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/tests/CryptoStream.cs +++ b/src/libraries/System.Security.Cryptography.Primitives/tests/CryptoStream.cs @@ -2,14 +2,32 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.IO; +using System.IO.Tests; using System.Text; using System.Threading.Tasks; using Xunit; namespace System.Security.Cryptography.Encryption.Tests.Asymmetric { - public static class CryptoStreamTests + public class CryptoStreamTests : WrappingConnectedStreamConformanceTests { + protected override Task CreateConnectedStreamsAsync() + { + (Stream writeable, Stream readable) = ConnectedStreams.CreateBidirectional(); + return CreateWrappedConnectedStreamsAsync((writeable, readable)); + } + + protected override Task CreateWrappedConnectedStreamsAsync(StreamPair wrapped, bool leaveOpen = false) + { + ICryptoTransform transform = new IdentityTransform(1, 1, true); + (Stream writeable, Stream readable) = GetReadWritePair(wrapped); + var encryptedWriteable = new CryptoStream(writeable, transform, CryptoStreamMode.Write, leaveOpen); + var decryptedReadable = new CryptoStream(readable, transform, CryptoStreamMode.Read, leaveOpen); + return Task.FromResult((encryptedWriteable, decryptedReadable)); + } + + protected override Type UnsupportedConcurrentExceptionType => null; + [Fact] public static void Ctor() { @@ -46,7 +64,7 @@ public static void Roundtrip(int inputBlockSize, int outputBlockSize, bool canTr Assert.Throws(() => encryptStream.Position = 0); Assert.Throws(() => encryptStream.Seek(0, SeekOrigin.Begin)); Assert.Throws(() => encryptStream.Read(new byte[0], 0, 0)); - Assert.Throws(() => encryptStream.Write(null, 0, 0)); // No arg validation on buffer? + Assert.Throws(() => encryptStream.Write(null, 0, 0)); Assert.Throws(() => encryptStream.Write(new byte[0], -1, 0)); Assert.Throws(() => encryptStream.Write(new byte[0], 0, -1)); Assert.Throws(() => encryptStream.Write(new byte[0], 0, -1)); @@ -99,7 +117,7 @@ public static void Roundtrip(int inputBlockSize, int outputBlockSize, bool canTr Assert.Throws(() => decryptStream.Position = 0); Assert.Throws(() => decryptStream.Seek(0, SeekOrigin.Begin)); Assert.Throws(() => decryptStream.Write(new byte[0], 0, 0)); - Assert.Throws(() => decryptStream.Read(null, 0, 0)); // No arg validation on buffer? + Assert.Throws(() => decryptStream.Read(null, 0, 0)); Assert.Throws(() => decryptStream.Read(new byte[0], -1, 0)); Assert.Throws(() => decryptStream.Read(new byte[0], 0, -1)); Assert.Throws(() => decryptStream.Read(new byte[0], 0, -1)); @@ -146,18 +164,6 @@ public static void Roundtrip(int inputBlockSize, int outputBlockSize, bool canTr } } - [Fact] - public static void NestedCryptoStreams() - { - ICryptoTransform encryptor = new IdentityTransform(1, 1, true); - using (MemoryStream output = new MemoryStream()) - using (CryptoStream encryptStream1 = new CryptoStream(output, encryptor, CryptoStreamMode.Write)) - using (CryptoStream encryptStream2 = new CryptoStream(encryptStream1, encryptor, CryptoStreamMode.Write)) - { - encryptStream2.Write(new byte[] { 1, 2, 3, 4, 5 }, 0, 5); - } - } - [Fact] public static void Clear() { @@ -170,23 +176,6 @@ public static void Clear() } } - [Fact] - public static void FlushAsync() - { - ICryptoTransform encryptor = new IdentityTransform(1, 1, true); - using (MemoryStream output = new MemoryStream()) - using (CryptoStream encryptStream = new CryptoStream(output, encryptor, CryptoStreamMode.Write)) - { - encryptStream.WriteAsync(new byte[] { 1, 2, 3, 4, 5 }, 0, 5); - Task waitable = encryptStream.FlushAsync(new Threading.CancellationToken(false)); - Assert.False(waitable.IsCanceled); - - encryptStream.WriteAsync(new byte[] { 1, 2, 3, 4, 5 }, 0, 5); - waitable = encryptStream.FlushAsync(new Threading.CancellationToken(true)); - Assert.True(waitable.IsCanceled); - } - } - [Fact] public static async Task FlushFinalBlockAsync() { @@ -202,7 +191,7 @@ public static async Task FlushFinalBlockAsync() } [Fact] - public static async Task FlushFinalBlockAsync_Cancelled() + public static async Task FlushFinalBlockAsync_Canceled() { ICryptoTransform encryptor = new IdentityTransform(1, 1, true); using (MemoryStream output = new MemoryStream()) @@ -216,7 +205,7 @@ public static async Task FlushFinalBlockAsync_Cancelled() } [Fact] - public static void FlushCalledOnFlushAsync_DeriveClass() + public static void FlushCalledOnFlushAsync_DerivedClass() { ICryptoTransform encryptor = new IdentityTransform(1, 1, true); using (MemoryStream output = new MemoryStream()) diff --git a/src/libraries/System.Security.Cryptography.Primitives/tests/System.Security.Cryptography.Primitives.Tests.csproj b/src/libraries/System.Security.Cryptography.Primitives/tests/System.Security.Cryptography.Primitives.Tests.csproj index f03cd0b7678c2f..5f2ff5fa1273a4 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/tests/System.Security.Cryptography.Primitives.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Primitives/tests/System.Security.Cryptography.Primitives.Tests.csproj @@ -22,5 +22,11 @@ + + + + + + From 3586739bff5a7174423feae7643c592d4b9d9ce2 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:21:31 -0400 Subject: [PATCH 10/24] Fix FileStream argument names Technically a breaking change, but the current divergence from the base Stream class is a bug, and bringing them into sync means argument exceptions that emerge from the derived type make sense when used via the base type, as well as then being able to use shared validation logic across all streams (subsequent to these changes). --- .../tests/FileStream/Read.cs | 10 +-- .../tests/FileStream/ReadAsync.cs | 4 +- .../tests/FileStream/Write.cs | 10 +-- .../tests/FileStream/WriteAsync.cs | 4 +- .../src/System/IO/FileStream.cs | 64 +++++++++---------- .../System.Runtime/ref/System.Runtime.cs | 8 +-- 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/Read.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/Read.cs index 0f3ce9ee488811..75ebf98d4391b0 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/Read.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/Read.cs @@ -15,7 +15,7 @@ public void NullArrayThrows() { using (FileStream fs = new FileStream(GetTestFilePath(), FileMode.Create)) { - AssertExtensions.Throws("array", () => fs.Read(null, 0, 1)); + AssertExtensions.Throws("buffer", () => fs.Read(null, 0, 1)); } } @@ -34,7 +34,7 @@ public void NegativeOffsetThrows() AssertExtensions.Throws("offset", () => fs.Read(new byte[1], -1, 1)); // array is checked first - AssertExtensions.Throws("array", () => fs.Read(null, -1, 1)); + AssertExtensions.Throws("buffer", () => fs.Read(null, -1, 1)); } } @@ -49,7 +49,7 @@ public void NegativeCountThrows() AssertExtensions.Throws("offset", () => fs.Read(new byte[1], -1, -1)); // array is checked first - AssertExtensions.Throws("array", () => fs.Read(null, -1, -1)); + AssertExtensions.Throws("buffer", () => fs.Read(null, -1, -1)); } } @@ -96,7 +96,7 @@ public void ReadDisposedThrows() AssertExtensions.Throws("offset", () => fs.Read(new byte[1], -1, -1)); // array is checked first - AssertExtensions.Throws("array", () => fs.Read(null, -1, -1)); + AssertExtensions.Throws("buffer", () => fs.Read(null, -1, -1)); } } @@ -121,7 +121,7 @@ public void WriteOnlyThrows() AssertExtensions.Throws("offset", () => fs.Read(new byte[1], -1, -1)); // array is checked first - AssertExtensions.Throws("array", () => fs.Read(null, -1, -1)); + AssertExtensions.Throws("buffer", () => fs.Read(null, -1, -1)); } } diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ReadAsync.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ReadAsync.cs index c728771ea496dd..5cadaad295282a 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ReadAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ReadAsync.cs @@ -412,7 +412,7 @@ protected override Task ReadAsync(FileStream stream, byte[] buffer, int off iar => stream.EndRead(iar), null); - protected override string BufferParamName => "array"; - protected override string CountParamName => "numBytes"; + protected override string BufferParamName => "buffer"; + protected override string CountParamName => "count"; } } diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/Write.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/Write.cs index 015025f443377a..a51959ef300728 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/Write.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/Write.cs @@ -15,7 +15,7 @@ public void NullArrayThrows() { using (FileStream fs = new FileStream(GetTestFilePath(), FileMode.Create)) { - AssertExtensions.Throws("array", () => fs.Write(null, 0, 1)); + AssertExtensions.Throws("buffer", () => fs.Write(null, 0, 1)); } } @@ -27,7 +27,7 @@ public void NegativeOffsetThrows() AssertExtensions.Throws("offset", () => fs.Write(new byte[1], -1, 1)); // array is checked first - AssertExtensions.Throws("array", () => fs.Write(null, -1, 1)); + AssertExtensions.Throws("buffer", () => fs.Write(null, -1, 1)); } } @@ -42,7 +42,7 @@ public void NegativeCountThrows() AssertExtensions.Throws("offset", () => fs.Write(new byte[1], -1, -1)); // array is checked first - AssertExtensions.Throws("array", () => fs.Write(null, -1, -1)); + AssertExtensions.Throws("buffer", () => fs.Write(null, -1, -1)); } } @@ -89,7 +89,7 @@ public void WriteDisposedThrows() AssertExtensions.Throws("offset", () => fs.Write(new byte[1], -1, -1)); // array is checked first - AssertExtensions.Throws("array", () => fs.Write(null, -1, -1)); + AssertExtensions.Throws("buffer", () => fs.Write(null, -1, -1)); } } @@ -120,7 +120,7 @@ public void ReadOnlyThrows() AssertExtensions.Throws("offset", () => fs.Write(new byte[1], -1, -1)); // array is checked first - AssertExtensions.Throws("array", () => fs.Write(null, -1, -1)); + AssertExtensions.Throws("buffer", () => fs.Write(null, -1, -1)); } } diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/WriteAsync.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/WriteAsync.cs index 7b236db7b4b2f7..6637c6c3bb30fd 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/WriteAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/WriteAsync.cs @@ -470,7 +470,7 @@ protected override Task WriteAsync(FileStream stream, byte[] buffer, int offset, iar => stream.EndWrite(iar), null); - protected override string BufferParamName => "array"; - protected override string CountParamName => "numBytes"; + protected override string BufferParamName => "buffer"; + protected override string CountParamName => "count"; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs index 479420fbc48c0d..d451abc978557e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs @@ -298,12 +298,12 @@ public override Task FlushAsync(CancellationToken cancellationToken) return FlushAsyncInternal(cancellationToken); } - public override int Read(byte[] array, int offset, int count) + public override int Read(byte[] buffer, int offset, int count) { - ValidateReadWriteArgs(array, offset, count); + ValidateReadWriteArgs(buffer, offset, count); return _useAsyncIO ? - ReadAsyncTask(array, offset, count, CancellationToken.None).GetAwaiter().GetResult() : - ReadSpan(new Span(array, offset, count)); + ReadAsyncTask(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult() : + ReadSpan(new Span(buffer, offset, count)); } public override int Read(Span buffer) @@ -402,9 +402,9 @@ public override ValueTask ReadAsync(Memory buffer, CancellationToken new ValueTask(synchronousResult); } - private Task ReadAsyncTask(byte[] array, int offset, int count, CancellationToken cancellationToken) + private Task ReadAsyncTask(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - Task? t = ReadAsyncInternal(new Memory(array, offset, count), cancellationToken, out int synchronousResult); + Task? t = ReadAsyncInternal(new Memory(buffer, offset, count), cancellationToken, out int synchronousResult); if (t == null) { @@ -420,16 +420,16 @@ private Task ReadAsyncTask(byte[] array, int offset, int count, Cancellatio return t; } - public override void Write(byte[] array, int offset, int count) + public override void Write(byte[] buffer, int offset, int count) { - ValidateReadWriteArgs(array, offset, count); + ValidateReadWriteArgs(buffer, offset, count); if (_useAsyncIO) { - WriteAsyncInternal(new ReadOnlyMemory(array, offset, count), CancellationToken.None).AsTask().GetAwaiter().GetResult(); + WriteAsyncInternal(new ReadOnlyMemory(buffer, offset, count), CancellationToken.None).AsTask().GetAwaiter().GetResult(); } else { - WriteSpan(new ReadOnlySpan(array, offset, count)); + WriteSpan(new ReadOnlySpan(buffer, offset, count)); } } @@ -558,18 +558,18 @@ public virtual void Flush(bool flushToDisk) public override bool CanWrite => !_fileHandle.IsClosed && (_access & FileAccess.Write) != 0; /// Validates arguments to Read and Write and throws resulting exceptions. - /// The buffer to read from or write to. - /// The zero-based offset into the array. + /// The buffer to read from or write to. + /// The zero-based offset into the buffer. /// The maximum number of bytes to read or write. - private void ValidateReadWriteArgs(byte[] array, int offset, int count) + private void ValidateReadWriteArgs(byte[] buffer, int offset, int count) { - if (array == null) - throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer); + if (buffer == null) + throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - if (array.Length - offset < count) + if (buffer.Length - offset < count) throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/); if (_fileHandle.IsClosed) throw Error.GetFileNotOpen(); @@ -849,44 +849,44 @@ private void PrepareForWriting() Dispose(false); } - public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback? callback, object? state) + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) { - if (array == null) - throw new ArgumentNullException(nameof(array)); + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum); - if (numBytes < 0) - throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_NeedNonNegNum); - if (array.Length - offset < numBytes) + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); + if (buffer.Length - offset < count) throw new ArgumentException(SR.Argument_InvalidOffLen); if (IsClosed) throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed); if (!CanRead) throw new NotSupportedException(SR.NotSupported_UnreadableStream); if (!IsAsync) - return base.BeginRead(array, offset, numBytes, callback, state); + return base.BeginRead(buffer, offset, count, callback, state); else - return TaskToApm.Begin(ReadAsyncTask(array, offset, numBytes, CancellationToken.None), callback, state); + return TaskToApm.Begin(ReadAsyncTask(buffer, offset, count, CancellationToken.None), callback, state); } - public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, AsyncCallback? callback, object? state) + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) { - if (array == null) - throw new ArgumentNullException(nameof(array)); + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum); - if (numBytes < 0) - throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_NeedNonNegNum); - if (array.Length - offset < numBytes) + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); + if (buffer.Length - offset < count) throw new ArgumentException(SR.Argument_InvalidOffLen); if (IsClosed) throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed); if (!CanWrite) throw new NotSupportedException(SR.NotSupported_UnwritableStream); if (!IsAsync) - return base.BeginWrite(array, offset, numBytes, callback, state); + return base.BeginWrite(buffer, offset, count, callback, state); else - return TaskToApm.Begin(WriteAsyncInternal(new ReadOnlyMemory(array, offset, numBytes), CancellationToken.None).AsTask(), callback, state); + return TaskToApm.Begin(WriteAsyncInternal(new ReadOnlyMemory(buffer, offset, count), CancellationToken.None).AsTask(), callback, state); } public override int EndRead(IAsyncResult asyncResult) diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index f6d327eb35c2cf..93cbf285ddb902 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -7087,8 +7087,8 @@ public FileStream(string path, System.IO.FileMode mode, System.IO.FileAccess acc public virtual string Name { get { throw null; } } public override long Position { get { throw null; } set { } } public virtual Microsoft.Win32.SafeHandles.SafeFileHandle SafeFileHandle { get { throw null; } } - public override System.IAsyncResult BeginRead(byte[] array, int offset, int numBytes, System.AsyncCallback? callback, object? state) { throw null; } - public override System.IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, System.AsyncCallback? callback, object? state) { throw null; } + public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int numBytes, System.AsyncCallback? callback, object? state) { throw null; } + public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int numBytes, System.AsyncCallback? callback, object? state) { throw null; } public override System.Threading.Tasks.Task CopyToAsync(System.IO.Stream destination, int bufferSize, System.Threading.CancellationToken cancellationToken) { throw null; } protected override void Dispose(bool disposing) { } public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } @@ -7099,7 +7099,7 @@ public override void Flush() { } public virtual void Flush(bool flushToDisk) { } public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; } public virtual void Lock(long position, long length) { } - public override int Read(byte[] array, int offset, int count) { throw null; } + public override int Read(byte[] buffer, int offset, int count) { throw null; } public override int Read(System.Span buffer) { throw null; } public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } @@ -7107,7 +7107,7 @@ public virtual void Lock(long position, long length) { } public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } public override void SetLength(long value) { } public virtual void Unlock(long position, long length) { } - public override void Write(byte[] array, int offset, int count) { } + public override void Write(byte[] buffer, int offset, int count) { } public override void Write(System.ReadOnlySpan buffer) { } public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } From 4f32db218199fefb97a1d0ee436e5d6254f7bb0d Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:22:40 -0400 Subject: [PATCH 11/24] Fix BufferedStream argument names Technically a breaking change, but the current divergence from the base Stream class is a bug, and bringing them into sync means argument exceptions that emerge from the derived type make sense when used via the base type, as well as then being able to use shared validation logic across all streams (subsequent to these changes). --- .../src/System/IO/BufferedStream.cs | 42 +++++++++---------- .../System.Runtime/ref/System.Runtime.cs | 4 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/BufferedStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/BufferedStream.cs index a3de5a24f76995..804a5d303a68e3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/BufferedStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/BufferedStream.cs @@ -443,7 +443,7 @@ private async ValueTask FlushWriteAsync(CancellationToken cancellationToken) await _stream.FlushAsync(cancellationToken).ConfigureAwait(false); } - private int ReadFromBuffer(byte[] array, int offset, int count) + private int ReadFromBuffer(byte[] buffer, int offset, int count) { int readbytes = _readLen - _readPos; Debug.Assert(readbytes >= 0); @@ -453,7 +453,7 @@ private int ReadFromBuffer(byte[] array, int offset, int count) if (readbytes > count) readbytes = count; - Buffer.BlockCopy(_buffer!, _readPos, array, offset, readbytes); + Buffer.BlockCopy(_buffer!, _readPos, buffer, offset, readbytes); _readPos += readbytes; return readbytes; @@ -471,12 +471,12 @@ private int ReadFromBuffer(Span destination) return readbytes; } - private int ReadFromBuffer(byte[] array, int offset, int count, out Exception? error) + private int ReadFromBuffer(byte[] buffer, int offset, int count, out Exception? error) { try { error = null; - return ReadFromBuffer(array, offset, count); + return ReadFromBuffer(buffer, offset, count); } catch (Exception ex) { @@ -485,22 +485,22 @@ private int ReadFromBuffer(byte[] array, int offset, int count, out Exception? e } } - public override int Read(byte[] array, int offset, int count) + public override int Read(byte[] buffer, int offset, int count) { - if (array == null) - throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer); + if (buffer == null) + throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - if (array.Length - offset < count) + if (buffer.Length - offset < count) throw new ArgumentException(SR.Argument_InvalidOffLen); EnsureNotClosed(); EnsureCanRead(); Debug.Assert(_stream != null); - int bytesFromBuffer = ReadFromBuffer(array, offset, count); + int bytesFromBuffer = ReadFromBuffer(buffer, offset, count); // We may have read less than the number of bytes the user asked for, but that is part of the Stream Debug. @@ -531,14 +531,14 @@ public override int Read(byte[] array, int offset, int count) // If the requested read is larger than buffer size, avoid the buffer and still use a single read: if (count >= _bufferSize) { - return _stream.Read(array, offset, count) + alreadySatisfied; + return _stream.Read(buffer, offset, count) + alreadySatisfied; } // Ok. We can fill the buffer: EnsureBufferAllocated(); _readLen = _stream.Read(_buffer!, 0, _bufferSize); - bytesFromBuffer = ReadFromBuffer(array, offset, count); + bytesFromBuffer = ReadFromBuffer(buffer, offset, count); // We may have read less than the number of bytes the user asked for, but that is part of the Stream Debug. // Reading again for more data may cause us to block if we're using a device with no clear end of stream, @@ -809,7 +809,7 @@ private int ReadByteSlow() return _buffer![_readPos++]; } - private void WriteToBuffer(byte[] array, ref int offset, ref int count) + private void WriteToBuffer(byte[] buffer, ref int offset, ref int count) { int bytesToWrite = Math.Min(_bufferSize - _writePos, count); @@ -817,7 +817,7 @@ private void WriteToBuffer(byte[] array, ref int offset, ref int count) return; EnsureBufferAllocated(); - Buffer.BlockCopy(array, offset, _buffer!, _writePos, bytesToWrite); + Buffer.BlockCopy(buffer, offset, _buffer!, _writePos, bytesToWrite); _writePos += bytesToWrite; count -= bytesToWrite; @@ -836,15 +836,15 @@ private int WriteToBuffer(ReadOnlySpan buffer) return bytesToWrite; } - public override void Write(byte[] array, int offset, int count) + public override void Write(byte[] buffer, int offset, int count) { - if (array == null) - throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer); + if (buffer == null) + throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - if (array.Length - offset < count) + if (buffer.Length - offset < count) throw new ArgumentException(SR.Argument_InvalidOffLen); EnsureNotClosed(); @@ -926,7 +926,7 @@ public override void Write(byte[] array, int offset, int count) if (useBuffer) { - WriteToBuffer(array, ref offset, ref count); + WriteToBuffer(buffer, ref offset, ref count); if (_writePos < _bufferSize) { @@ -941,7 +941,7 @@ public override void Write(byte[] array, int offset, int count) _stream.Write(_buffer, 0, _writePos); _writePos = 0; - WriteToBuffer(array, ref offset, ref count); + WriteToBuffer(buffer, ref offset, ref count); Debug.Assert(count == 0); Debug.Assert(_writePos < _bufferSize); @@ -958,7 +958,7 @@ public override void Write(byte[] array, int offset, int count) if (totalUserbytes <= (_bufferSize + _bufferSize) && totalUserbytes <= MaxShadowBufferSize) { EnsureShadowBufferAllocated(); - Buffer.BlockCopy(array, offset, _buffer, _writePos, count); + Buffer.BlockCopy(buffer, offset, _buffer, _writePos, count); _stream.Write(_buffer, 0, totalUserbytes); _writePos = 0; return; @@ -969,7 +969,7 @@ public override void Write(byte[] array, int offset, int count) } // Write out user data. - _stream.Write(array, offset, count); + _stream.Write(buffer, offset, count); } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 93cbf285ddb902..873e224819caee 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -6946,14 +6946,14 @@ protected override void Dispose(bool disposing) { } public override void EndWrite(System.IAsyncResult asyncResult) { } public override void Flush() { } public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; } - public override int Read(byte[] array, int offset, int count) { throw null; } + public override int Read(byte[] buffer, int offset, int count) { throw null; } public override int Read(System.Span destination) { throw null; } public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public override int ReadByte() { throw null; } public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } public override void SetLength(long value) { } - public override void Write(byte[] array, int offset, int count) { } + public override void Write(byte[] buffer, int offset, int count) { } public override void Write(System.ReadOnlySpan buffer) { } public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } From cad5f6ac88cf4baa1a42473a9d091a1c961611f6 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:23:36 -0400 Subject: [PATCH 12/24] Add stream conformance tests for FileStream Specifically when used in a connected fashion, wrapped around an anonymous or named pipe. --- .../tests/FileStream/Pipes.cs | 184 +++--------------- .../tests/FileSystemTest.cs | 2 +- .../tests/System.IO.FileSystem.Tests.csproj | 7 + 3 files changed, 39 insertions(+), 154 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/Pipes.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/Pipes.cs index fd366dc702ffff..f2edb09f4d6773 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/Pipes.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/Pipes.cs @@ -3,179 +3,57 @@ using Microsoft.Win32.SafeHandles; using System.IO.Pipes; -using System.Runtime.InteropServices; using System.Threading.Tasks; using Xunit; namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] - public class Pipes : FileSystemTest + public class AnonymousPipeFileStreamConformanceTests : ConnectedStreamConformanceTests { - [Theory] - [InlineData(true)] - [InlineData(false)] - [PlatformSpecific(~TestPlatforms.Browser)] // IO.Pipes not supported - public async Task AnonymousPipeWriteViaFileStream(bool asyncWrites) + protected override Task CreateConnectedStreamsAsync() { - using (var server = new AnonymousPipeServerStream(PipeDirection.In)) - { - Task serverTask = Task.Run(() => - { - for (int i = 0; i < 6; i++) - Assert.Equal(i, server.ReadByte()); - }); + var server = new AnonymousPipeServerStream(PipeDirection.Out); - using (var client = new FileStream(new SafeFileHandle(server.ClientSafePipeHandle.DangerousGetHandle(), false), FileAccess.Write, bufferSize: 3)) - { - var data = new[] { new byte[] { 0, 1 }, new byte[] { 2, 3 }, new byte[] { 4, 5 } }; - foreach (byte[] arr in data) - { - if (asyncWrites) - await client.WriteAsync(arr, 0, arr.Length); - else - client.Write(arr, 0, arr.Length); - } - } + var fs1 = new FileStream(new SafeFileHandle(server.SafePipeHandle.DangerousGetHandle(), true), FileAccess.Write); + var fs2 = new FileStream(new SafeFileHandle(server.ClientSafePipeHandle.DangerousGetHandle(), true), FileAccess.Read); - await serverTask; - } - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/30155")] - [PlatformSpecific(TestPlatforms.AnyUnix)] // Uses P/Invokes - public async Task FifoReadWriteViaFileStream() - { - string fifoPath = GetTestFilePath(); - Assert.Equal(0, mkfifo(fifoPath, 666)); + server.SafePipeHandle.SetHandleAsInvalid(); + server.ClientSafePipeHandle.SetHandleAsInvalid(); - await Task.WhenAll( - Task.Run(() => - { - using (FileStream fs = File.OpenRead(fifoPath)) - { - Assert.Equal(42, fs.ReadByte()); - } - }), - Task.Run(() => - { - using (FileStream fs = File.OpenWrite(fifoPath)) - { - fs.WriteByte(42); - fs.Flush(); - } - })); + return Task.FromResult((fs1, fs2)); } - [Theory] - [InlineData(true)] - [InlineData(false)] - [PlatformSpecific(~TestPlatforms.Browser)] // IO.Pipes not supported - public async Task AnonymousPipeReadViaFileStream(bool asyncReads) - { - using (var server = new AnonymousPipeServerStream(PipeDirection.Out)) - { - Task serverTask = server.WriteAsync(new byte[] { 0, 1, 2, 3, 4, 5 }, 0, 6); - - using (var client = new FileStream(new SafeFileHandle(server.ClientSafePipeHandle.DangerousGetHandle(), false), FileAccess.Read, bufferSize: 3)) - { - var arr = new byte[1]; - for (int i = 0; i < 6; i++) - { - Assert.Equal(1, asyncReads ? - await client.ReadAsync(arr, 0, 1) : - client.Read(arr, 0, 1)); - Assert.Equal(i, arr[0]); - } - } - - await serverTask; - } - } + protected override Type UnsupportedConcurrentExceptionType => null; + protected override bool UsableAfterCanceledReads => false; + protected override bool FullyCancelableOperations => false; + protected override bool BlocksOnZeroByteReads => OperatingSystem.IsWindows(); + protected override bool SupportsConcurrentBidirectionalUse => false; + } - [PlatformSpecific(TestPlatforms.Windows)] // Uses P/Invokes to create async pipe handle - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task NamedPipeWriteViaAsyncFileStream(bool asyncWrites) + public class NamedPipeFileStreamConformanceTests : ConnectedStreamConformanceTests + { + protected override async Task CreateConnectedStreamsAsync() { - string name = GetNamedPipeServerStreamName(); - using (var server = new NamedPipeServerStream(name, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) - { - Task serverTask = Task.Run(async () => - { - await server.WaitForConnectionAsync(); - for (int i = 0; i < 6; i++) - Assert.Equal(i, server.ReadByte()); - }); + string name = FileSystemTest.GetNamedPipeServerStreamName(); - WaitNamedPipeW(@"\\.\pipe\" + name, -1); - using (SafeFileHandle clientHandle = CreateFileW(@"\\.\pipe\" + name, Interop.Kernel32.GenericOperations.GENERIC_WRITE, FileShare.None, IntPtr.Zero, FileMode.Open, (int)PipeOptions.Asynchronous, IntPtr.Zero)) - using (var client = new FileStream(clientHandle, FileAccess.Write, bufferSize: 3, isAsync: true)) - { - var data = new[] { new byte[] { 0, 1 }, new byte[] { 2, 3 }, new byte[] { 4, 5 } }; - foreach (byte[] arr in data) - { - if (asyncWrites) - await client.WriteAsync(arr, 0, arr.Length); - else - client.Write(arr, 0, arr.Length); - } - } + var server = new NamedPipeServerStream(name, PipeDirection.InOut); + var client = new NamedPipeClientStream(".", name, PipeDirection.InOut); - await serverTask; - } - } + await WhenAllOrAnyFailed(server.WaitForConnectionAsync(), client.ConnectAsync()); - [PlatformSpecific(TestPlatforms.Windows)] // Uses P/Invokes to create async pipe handle - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task NamedPipeReadViaAsyncFileStream(bool asyncReads) - { - string name = GetNamedPipeServerStreamName(); - using (var server = new NamedPipeServerStream(name, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) - { - Task serverTask = Task.Run(async () => - { - await server.WaitForConnectionAsync(); - await server.WriteAsync(new byte[] { 0, 1, 2, 3, 4, 5 }, 0, 6); - }); + var fs1 = new FileStream(new SafeFileHandle(server.SafePipeHandle.DangerousGetHandle(), true), FileAccess.ReadWrite); + var fs2 = new FileStream(new SafeFileHandle(client.SafePipeHandle.DangerousGetHandle(), true), FileAccess.ReadWrite); - WaitNamedPipeW(@"\\.\pipe\" + name, -1); - using (SafeFileHandle clientHandle = CreateFileW(@"\\.\pipe\" + name, Interop.Kernel32.GenericOperations.GENERIC_READ, FileShare.None, IntPtr.Zero, FileMode.Open, (int)PipeOptions.Asynchronous, IntPtr.Zero)) - using (var client = new FileStream(clientHandle, FileAccess.Read, bufferSize: 3, isAsync: true)) - { - var arr = new byte[1]; - for (int i = 0; i < 6; i++) - { - Assert.Equal(1, asyncReads ? - await client.ReadAsync(arr, 0, 1) : - client.Read(arr, 0, 1)); - Assert.Equal(i, arr[0]); - } - } + server.SafePipeHandle.SetHandleAsInvalid(); + client.SafePipeHandle.SetHandleAsInvalid(); - await serverTask; - } + return (fs1, fs2); } - #region Windows P/Invokes - // We need to P/Invoke to test the named pipe async behavior with FileStream - // because NamedPipeClientStream internally binds the created handle, - // and that then prevents FileStream's constructor from working with the handle - // when trying to set isAsync to true. - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool WaitNamedPipeW(string name, int timeout); - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern SafeFileHandle CreateFileW( - string lpFileName, int dwDesiredAccess, FileShare dwShareMode, - IntPtr securityAttrs, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile); - - #endregion + protected override Type UnsupportedConcurrentExceptionType => null; + protected override bool UsableAfterCanceledReads => false; + protected override bool FullyCancelableOperations => false; + protected override bool BlocksOnZeroByteReads => OperatingSystem.IsWindows(); + protected override bool SupportsConcurrentBidirectionalUse => false; } } diff --git a/src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs b/src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs index 149cf10d88eca3..c0694e57b32089 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs @@ -79,7 +79,7 @@ public static TheoryData TrailingSeparators return success; }); - protected string GetNamedPipeServerStreamName() + public static string GetNamedPipeServerStreamName() { if (PlatformDetection.IsInAppContainer) { diff --git a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj index 4a921fc8fb4a6f..c99be14542b918 100644 --- a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj +++ b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj @@ -177,5 +177,12 @@ + + + + + + + From ec88166aa31244486f1645d33bdd943d0d56d2a6 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:25:09 -0400 Subject: [PATCH 13/24] Add stream conformance tests for BufferedStream Specifically when used in a connected fashion, wrapped around some other connected stream. --- .../BufferedStream.FlushTests.cs | 76 ------- .../BufferedStream.InvalidParameters.cs | 153 -------------- ...BufferedStreamConnectedConformanceTests.cs | 24 +++ .../BufferedStream/BufferedStreamTests.cs | 192 +++++++----------- .../tests/Stream/Stream.AsyncTests.cs | 29 --- .../System.IO/tests/System.IO.Tests.csproj | 9 +- 6 files changed, 98 insertions(+), 385 deletions(-) delete mode 100644 src/libraries/System.IO/tests/BufferedStream/BufferedStream.FlushTests.cs delete mode 100644 src/libraries/System.IO/tests/BufferedStream/BufferedStream.InvalidParameters.cs create mode 100644 src/libraries/System.IO/tests/BufferedStream/BufferedStreamConnectedConformanceTests.cs delete mode 100644 src/libraries/System.IO/tests/Stream/Stream.AsyncTests.cs diff --git a/src/libraries/System.IO/tests/BufferedStream/BufferedStream.FlushTests.cs b/src/libraries/System.IO/tests/BufferedStream/BufferedStream.FlushTests.cs deleted file mode 100644 index b5af60d7e96b6d..00000000000000 --- a/src/libraries/System.IO/tests/BufferedStream/BufferedStream.FlushTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Tests -{ - public class BufferedStreamFlushTests - { - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task ShouldNotFlushUnderlyingStreamIfReadOnly(bool underlyingCanSeek) - { - var underlying = new DelegateStream( - canReadFunc: () => true, - canWriteFunc: () => false, - canSeekFunc: () => underlyingCanSeek, - readFunc: (_, __, ___) => 123, - writeFunc: (_, __, ___) => - { - throw new NotSupportedException(); - }, - seekFunc: (_, __) => 123L - ); - - var wrapper = new CallTrackingStream(underlying); - - var buffered = new BufferedStream(wrapper); - buffered.ReadByte(); - - buffered.Flush(); - Assert.Equal(0, wrapper.TimesCalled(nameof(wrapper.Flush))); - - await buffered.FlushAsync(); - Assert.Equal(0, wrapper.TimesCalled(nameof(wrapper.FlushAsync))); - } - - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public async Task ShouldAlwaysFlushUnderlyingStreamIfWritable(bool underlyingCanRead, bool underlyingCanSeek) - { - var underlying = new DelegateStream( - canReadFunc: () => underlyingCanRead, - canWriteFunc: () => true, - canSeekFunc: () => underlyingCanSeek, - readFunc: (_, __, ___) => 123, - writeFunc: (_, __, ___) => { }, - seekFunc: (_, __) => 123L - ); - - var wrapper = new CallTrackingStream(underlying); - - var buffered = new BufferedStream(wrapper); - - buffered.Flush(); - Assert.Equal(1, wrapper.TimesCalled(nameof(wrapper.Flush))); - - await buffered.FlushAsync(); - Assert.Equal(1, wrapper.TimesCalled(nameof(wrapper.FlushAsync))); - - buffered.WriteByte(0); - - buffered.Flush(); - Assert.Equal(2, wrapper.TimesCalled(nameof(wrapper.Flush))); - - await buffered.FlushAsync(); - Assert.Equal(2, wrapper.TimesCalled(nameof(wrapper.FlushAsync))); - } - } -} diff --git a/src/libraries/System.IO/tests/BufferedStream/BufferedStream.InvalidParameters.cs b/src/libraries/System.IO/tests/BufferedStream/BufferedStream.InvalidParameters.cs deleted file mode 100644 index 62ea50de644e01..00000000000000 --- a/src/libraries/System.IO/tests/BufferedStream/BufferedStream.InvalidParameters.cs +++ /dev/null @@ -1,153 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; -using Xunit; - -namespace System.IO.Tests -{ - public class BufferedStream_InvalidParameters - { - [Fact] - public static void NullConstructor_Throws_ArgumentNullException() - { - Assert.Throws(() => new BufferedStream(null)); - } - - [Fact] - public static void NegativeBufferSize_Throws_ArgumentOutOfRangeException() - { - Assert.Throws(() => new BufferedStream(new MemoryStream(), -1)); - } - - [Fact] - public static void ZeroBufferSize_Throws_ArgumentNullException() - { - Assert.Throws(() => new BufferedStream(new MemoryStream(), 0)); - } - - [Fact] - public static void UnderlyingStreamDisposed_Throws_ObjectDisposedException() - { - MemoryStream disposedStream = new MemoryStream(); - disposedStream.Dispose(); - Assert.Throws(() => new BufferedStream(disposedStream)); - } - - [Fact] - public static void SetPositionToNegativeValue_Throws_ArgumentOutOfRangeException() - { - using (BufferedStream stream = new BufferedStream(new MemoryStream())) - { - Assert.Throws(() => stream.Position = -1); - } - } - - [Fact] - public static void Read_Arguments() - { - using (BufferedStream stream = new BufferedStream(new MemoryStream())) - { - byte[] array = new byte[10]; - AssertExtensions.Throws("array", () => stream.Read(null, 1, 1)); - Assert.Throws(() => stream.Read(array, -1, 1)); - Assert.Throws(() => stream.Read(array, 1, -1)); - AssertExtensions.Throws(null, () => stream.Read(array, 9, 2)); - } - } - - [Fact] - public static void Write_Arguments() - { - using (BufferedStream stream = new BufferedStream(new MemoryStream())) - { - byte[] array = new byte[10]; - AssertExtensions.Throws("array", () => stream.Write(null, 1, 1)); - Assert.Throws(() => stream.Write(array, -1, 1)); - Assert.Throws(() => stream.Write(array, 1, -1)); - AssertExtensions.Throws(null, () => stream.Write(array, 9, 2)); - } - } - - [Fact] - public static void SetLength_NegativeValue() - { - using (MemoryStream underlying = new MemoryStream()) - using (BufferedStream stream = new BufferedStream(underlying)) - { - Assert.Throws(() => stream.SetLength(-1)); - stream.SetLength(1); - Assert.Equal(1, underlying.Length); - Assert.Equal(1, stream.Length); - } - } - - [Fact] - public static void ReadOnUnreadableStream_Throws_NotSupportedException() - { - using (WrappedMemoryStream underlying = new WrappedMemoryStream(false, true, true)) - using (BufferedStream stream = new BufferedStream(underlying)) - { - Assert.Throws(() => stream.Read(new byte[] { 1 }, 0, 1)); - } - } - - [Fact] - public static void WriteOnUnwritableStream_Throws_NotSupportedException() - { - using (WrappedMemoryStream underlying = new WrappedMemoryStream(true, false, true)) - using (BufferedStream stream = new BufferedStream(underlying)) - { - Assert.Throws(() => stream.Write(new byte[] { 1 }, 0, 1)); - } - } - - [Fact] - public static void SeekOnUnseekableStream_Throws_NotSupportedException() - { - using (WrappedMemoryStream underlying = new WrappedMemoryStream(true, true, false)) - using (BufferedStream stream = new BufferedStream(underlying)) - { - Assert.Throws(() => stream.Seek(0, new SeekOrigin())); - } - } - - [Fact] - public void CopyToAsync_InvalidArguments_Throws() - { - using (var s = new BufferedStream(new MemoryStream())) - { - // Null destination - AssertExtensions.Throws("destination", () => { s.CopyToAsync(null); }); - - // Buffer size out-of-range - AssertExtensions.Throws("bufferSize", () => { s.CopyToAsync(new MemoryStream(), 0); }); - AssertExtensions.Throws("bufferSize", () => { s.CopyToAsync(new MemoryStream(), -1, CancellationToken.None); }); - - // Copying to non-writable stream - Assert.Throws(() => { s.CopyToAsync(new WrappedMemoryStream(canRead: true, canWrite: false, canSeek: true)); }); - - // Copying to a non-writable and non-readable stream - Assert.Throws(() => { s.CopyToAsync(new WrappedMemoryStream(canRead: false, canWrite: false, canSeek: false)); }); - - // Copying after disposing the buffer stream - s.Dispose(); - Assert.Throws(() => { s.CopyToAsync(new MemoryStream()); }); - } - - // Copying after disposing the underlying stream - using (var ms = new MemoryStream()) - using (var s = new BufferedStream(ms)) - { - ms.Dispose(); - Assert.Throws(() => { s.CopyToAsync(new MemoryStream()); }); - } - - // Copying from a non-readable source - using (var s = new BufferedStream(new WrappedMemoryStream(canRead: false, canWrite: true, canSeek: true))) - { - Assert.Throws(() => { s.CopyToAsync(new MemoryStream()); }); - } - } - } -} diff --git a/src/libraries/System.IO/tests/BufferedStream/BufferedStreamConnectedConformanceTests.cs b/src/libraries/System.IO/tests/BufferedStream/BufferedStreamConnectedConformanceTests.cs new file mode 100644 index 00000000000000..8a12f3540bc6f8 --- /dev/null +++ b/src/libraries/System.IO/tests/BufferedStream/BufferedStreamConnectedConformanceTests.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading.Tasks; + +namespace System.IO.Tests +{ + public class BufferedStreamConnectedConformanceTests : WrappingConnectedStreamConformanceTests + { + protected override Task CreateConnectedStreamsAsync() => + CreateWrappedConnectedStreamsAsync(ConnectedStreams.CreateUnidirectional(4096, 16384), leaveOpen: false); + + protected override Task CreateWrappedConnectedStreamsAsync(StreamPair wrapped, bool leaveOpen = false) + { + var b1 = new BufferedStream(wrapped.Stream1, 1024); + var b2 = new BufferedStream(wrapped.Stream2, 1024); + return Task.FromResult((b1, b2)); + } + + protected override bool SupportsLeaveOpen => false; + protected override Type UnsupportedConcurrentExceptionType => null; + protected override int BufferedSize => 1024 + 16384; + } +} diff --git a/src/libraries/System.IO/tests/BufferedStream/BufferedStreamTests.cs b/src/libraries/System.IO/tests/BufferedStream/BufferedStreamTests.cs index 4f7c78890f0411..f958ba10c1d2af 100644 --- a/src/libraries/System.IO/tests/BufferedStream/BufferedStreamTests.cs +++ b/src/libraries/System.IO/tests/BufferedStream/BufferedStreamTests.cs @@ -10,11 +10,76 @@ namespace System.IO.Tests { - public class BufferedStream_StreamAsync : StreamAsync + public class BufferedStream_StreamAsync { - protected override Stream CreateStream() + [Fact] + public static void NullConstructor_Throws_ArgumentNullException() { - return new BufferedStream(new MemoryStream()); + Assert.Throws(() => new BufferedStream(null)); + } + + [Fact] + public static void NegativeBufferSize_Throws_ArgumentOutOfRangeException() + { + Assert.Throws(() => new BufferedStream(new MemoryStream(), -1)); + } + + [Fact] + public static void ZeroBufferSize_Throws_ArgumentNullException() + { + Assert.Throws(() => new BufferedStream(new MemoryStream(), 0)); + } + + [Fact] + public static void UnderlyingStreamDisposed_Throws_ObjectDisposedException() + { + MemoryStream disposedStream = new MemoryStream(); + disposedStream.Dispose(); + Assert.Throws(() => new BufferedStream(disposedStream)); + } + + [Fact] + public void UnderlyingStream() + { + var underlyingStream = new MemoryStream(); + var bufferedStream = new BufferedStream(underlyingStream); + Assert.Same(underlyingStream, bufferedStream.UnderlyingStream); + } + + [Fact] + public void BufferSize() + { + var bufferedStream = new BufferedStream(new MemoryStream(), 1234); + Assert.Equal(1234, bufferedStream.BufferSize); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ShouldNotFlushUnderlyingStreamIfReadOnly(bool underlyingCanSeek) + { + var underlying = new DelegateStream( + canReadFunc: () => true, + canWriteFunc: () => false, + canSeekFunc: () => underlyingCanSeek, + readFunc: (_, __, ___) => 123, + writeFunc: (_, __, ___) => + { + throw new NotSupportedException(); + }, + seekFunc: (_, __) => 123L + ); + + var wrapper = new CallTrackingStream(underlying); + + var buffered = new BufferedStream(wrapper); + buffered.ReadByte(); + + buffered.Flush(); + Assert.Equal(0, wrapper.TimesCalled(nameof(wrapper.Flush))); + + await buffered.FlushAsync(); + Assert.Equal(0, wrapper.TimesCalled(nameof(wrapper.FlushAsync))); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] @@ -331,125 +396,4 @@ public override Task WriteAsync(byte[] buffer, int offset, int count, Cancellati public override Task FlushAsync(CancellationToken cancellationToken) => throw new InvalidOperationException("Exception from FlushAsync"); } - - public class BufferedStream_NS17 - { - protected Stream CreateStream() - { - return new BufferedStream(new MemoryStream()); - } - - private void EndCallback(IAsyncResult ar) - { - } - - [Fact] - public void BeginEndReadTest() - { - Stream stream = CreateStream(); - IAsyncResult result = stream.BeginRead(new byte[1], 0, 1, new AsyncCallback(EndCallback), new object()); - stream.EndRead(result); - } - - [Fact] - public void BeginEndWriteTest() - { - Stream stream = CreateStream(); - IAsyncResult result = stream.BeginWrite(new byte[1], 0, 1, new AsyncCallback(EndCallback), new object()); - stream.EndWrite(result); - } - } - - public class BufferedStreamTests - { - [Fact] - public void UnderlyingStream() - { - var underlyingStream = new MemoryStream(); - var bufferedStream = new BufferedStream(underlyingStream); - Assert.Same(underlyingStream, bufferedStream.UnderlyingStream); - } - - [Fact] - public void BufferSize() - { - var bufferedStream = new BufferedStream(new MemoryStream(), 1234); - Assert.Equal(1234, bufferedStream.BufferSize); - } - - [Theory] - [InlineData(1, 1)] - [InlineData(1, 2)] - [InlineData(1024, 4096)] - [InlineData(4096, 4097)] - [InlineData(4096, 1)] - [InlineData(2047, 4096)] - public void ReadSpan_WriteSpan_AllDataCopied(int spanSize, int bufferSize) - { - byte[] data = new byte[80000]; - new Random(42).NextBytes(data); - - var result = new MemoryStream(); - using (var output = new BufferedStream(result, bufferSize)) - using (var input = new BufferedStream(new MemoryStream(data), bufferSize)) - { - Span span = new byte[spanSize]; - int bytesRead; - while ((bytesRead = input.Read(span)) != 0) - { - output.Write(span.Slice(0, bytesRead)); - } - } - Assert.Equal(data, result.ToArray()); - } - - [Theory] - [InlineData(1, 1)] - [InlineData(1, 2)] - [InlineData(1024, 4096)] - [InlineData(4096, 4097)] - [InlineData(4096, 1)] - [InlineData(2047, 4096)] - public async Task ReadMemory_WriteMemory_AllDataCopied(int spanSize, int bufferSize) - { - byte[] data = new byte[80000]; - new Random(42).NextBytes(data); - - var result = new MemoryStream(); - using (var output = new BufferedStream(result, bufferSize)) - using (var input = new BufferedStream(new MemoryStream(data), bufferSize)) - { - Memory memory = new byte[spanSize]; - int bytesRead; - while ((bytesRead = await input.ReadAsync(memory)) != 0) - { - await output.WriteAsync(memory.Slice(0, bytesRead)); - } - } - Assert.Equal(data, result.ToArray()); - } - - [Fact] - public void ReadWriteMemory_Precanceled_Throws() - { - using (var bs = new BufferedStream(new MemoryStream())) - { - Assert.Equal(TaskStatus.Canceled, bs.ReadAsync(new byte[1], new CancellationToken(true)).AsTask().Status); - Assert.Equal(TaskStatus.Canceled, bs.WriteAsync(new byte[1], new CancellationToken(true)).AsTask().Status); - } - } - - [Fact] - public async Task DisposeAsync_FlushesAndClosesStream() - { - var ms = new MemoryStream(); - var bs = new BufferedStream(ms); - bs.Write(new byte[1], 0, 1); - Assert.Equal(0, ms.Position); - await bs.DisposeAsync(); - Assert.True(bs.DisposeAsync().IsCompletedSuccessfully); - Assert.Throws(() => ms.Position); - Assert.Equal(1, ms.ToArray().Length); - } - } } diff --git a/src/libraries/System.IO/tests/Stream/Stream.AsyncTests.cs b/src/libraries/System.IO/tests/Stream/Stream.AsyncTests.cs deleted file mode 100644 index 9e3c40aebbe2cf..00000000000000 --- a/src/libraries/System.IO/tests/Stream/Stream.AsyncTests.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Linq; -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Tests -{ - public class StreamAsync - { - protected virtual Stream CreateStream() => new MemoryStream(); - - [Fact] - public async Task CopyToAsyncTest() - { - byte[] data = Enumerable.Range(0, 1000).Select(i => (byte)(i % 256)).ToArray(); - - Stream ms = CreateStream(); - ms.Write(data, 0, data.Length); - ms.Position = 0; - - var ms2 = new MemoryStream(); - await ms.CopyToAsync(ms2); - - Assert.Equal(data, ms2.ToArray()); - } - } -} diff --git a/src/libraries/System.IO/tests/System.IO.Tests.csproj b/src/libraries/System.IO/tests/System.IO.Tests.csproj index 301c0274c93bfa..4284642e82d4c5 100644 --- a/src/libraries/System.IO/tests/System.IO.Tests.csproj +++ b/src/libraries/System.IO/tests/System.IO.Tests.csproj @@ -20,8 +20,7 @@ - - + @@ -38,7 +37,6 @@ - @@ -58,5 +56,10 @@ + + + + + From 3f6a0f71947d9141726c40face8c6692baefff2f Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:32:58 -0400 Subject: [PATCH 14/24] Add stream conformance tests for SslStream and NegotiateStream --- .../tests/FunctionalTests/LoggingTest.cs | 1 - .../NegotiateStreamConformanceTests.cs | 31 ++ .../NegotiateStreamStreamToStreamTest.cs | 159 --------- .../SslStreamConformanceTests.cs | 34 ++ .../SslStreamStreamToStreamTest.cs | 326 +----------------- .../System.Net.Security.Tests.csproj | 4 + 6 files changed, 71 insertions(+), 484 deletions(-) create mode 100644 src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateStreamConformanceTests.cs create mode 100644 src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamConformanceTests.cs diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/LoggingTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/LoggingTest.cs index c3b1ee33e7a694..60320d78a0ce2e 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/LoggingTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/LoggingTest.cs @@ -36,7 +36,6 @@ public void EventSource_EventsRaisedAsExpected() // Invoke tests that'll cause some events to be generated var test = new SslStreamStreamToStreamTest_Async(); test.SslStream_StreamToStream_Authentication_Success().GetAwaiter().GetResult(); - test.SslStream_StreamToStream_Successive_ClientWrite_Success().GetAwaiter().GetResult(); }); Assert.DoesNotContain(events, ev => ev.EventId == 0); // errors from the EventSource itself Assert.InRange(events.Count, 1, int.MaxValue); diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateStreamConformanceTests.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateStreamConformanceTests.cs new file mode 100644 index 00000000000000..dcfa02248c2599 --- /dev/null +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateStreamConformanceTests.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using System.IO.Tests; +using System.Threading.Tasks; +using Xunit; + +namespace System.Net.Security.Tests +{ + [PlatformSpecific(TestPlatforms.Windows)] // NegotiateStream client needs explicit credentials or SPNs on unix. + public sealed class NegotiateStreamMemoryConformanceTests : WrappingConnectedStreamConformanceTests + { + protected override bool UsableAfterCanceledReads => false; + protected override bool BlocksOnZeroByteReads => true; + protected override Type UnsupportedConcurrentExceptionType => typeof(NotSupportedException); + + protected override Task CreateConnectedStreamsAsync() => + CreateWrappedConnectedStreamsAsync(ConnectedStreams.CreateBidirectional(), leaveOpen: false); + + protected override async Task CreateWrappedConnectedStreamsAsync(StreamPair wrapped, bool leaveOpen) + { + var negotiate1 = new NegotiateStream(wrapped.Stream1, leaveOpen); + var negotiate2 = new NegotiateStream(wrapped.Stream2, leaveOpen); + + await Task.WhenAll(negotiate1.AuthenticateAsClientAsync(), negotiate2.AuthenticateAsServerAsync()); + + return new StreamPair(negotiate1, negotiate2); + } + } +} diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateStreamStreamToStreamTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateStreamStreamToStreamTest.cs index c8c10e625a844c..043ab1fd09d34c 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateStreamStreamToStreamTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateStreamStreamToStreamTest.cs @@ -232,165 +232,6 @@ public async Task NegotiateStream_StreamToStream_Authentication_EmptyCredentials IdentityValidator.AssertHasName(clientIdentity, new SecurityIdentifier(WellKnownSidType.AnonymousSid, null).Translate(typeof(NTAccount)).Value); } } - - [ConditionalTheory(nameof(IsNtlmInstalled))] - [InlineData(0)] - [InlineData(1)] - public async Task NegotiateStream_StreamToStream_Successive_ClientWrite_Success(int delay) - { - byte[] recvBuf = new byte[s_sampleMsg.Length]; - int bytesRead = 0; - - (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); - using (var client = new NegotiateStream(new DelayStream(stream1, delay))) - using (var server = new NegotiateStream(new DelayStream(stream2, delay))) - { - Assert.False(client.IsAuthenticated); - Assert.False(server.IsAuthenticated); - - Task[] auth = new Task[2]; - auth[0] = AuthenticateAsClientAsync(client, CredentialCache.DefaultNetworkCredentials, string.Empty); - auth[1] = AuthenticateAsServerAsync(server); - - await TestConfiguration.WhenAllOrAnyFailedWithTimeout(auth); - - auth[0] = WriteAsync(client, s_sampleMsg, 0, s_sampleMsg.Length); - auth[1] = ReadAsync(server, recvBuf, 0, s_sampleMsg.Length); - await TestConfiguration.WhenAllOrAnyFailedWithTimeout(auth); - Assert.True(s_sampleMsg.SequenceEqual(recvBuf)); - - await WriteAsync(client, s_sampleMsg, 0, s_sampleMsg.Length); - - // Test partial async read. - bytesRead = await ReadAsync(server, recvBuf, 0, PartialBytesToRead); - Assert.Equal(PartialBytesToRead, bytesRead); - - bytesRead = await ReadAsync(server, recvBuf, PartialBytesToRead, s_sampleMsg.Length - PartialBytesToRead); - Assert.Equal(s_sampleMsg.Length - PartialBytesToRead, bytesRead); - - Assert.True(s_sampleMsg.SequenceEqual(recvBuf)); - } - } - - [ConditionalTheory(nameof(IsNtlmInstalled))] - [InlineData(0)] - [InlineData(1)] - public async Task NegotiateStream_ReadWriteLongMsg_Success(int delay) - { - byte[] recvBuf = new byte[s_longMsg.Length]; - int bytesRead = 0; - - (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); - using (var client = new NegotiateStream(new DelayStream(stream1, delay))) - using (var server = new NegotiateStream(new DelayStream(stream2, delay))) - { - await TestConfiguration.WhenAllOrAnyFailedWithTimeout( - client.AuthenticateAsClientAsync(CredentialCache.DefaultNetworkCredentials, string.Empty), - server.AuthenticateAsServerAsync()); - - await WriteAsync(client, s_longMsg, 0, s_longMsg.Length); - - while (bytesRead < s_longMsg.Length) - { - bytesRead += await ReadAsync(server, recvBuf, bytesRead, s_longMsg.Length - bytesRead); - } - - Assert.True(s_longMsg.SequenceEqual(recvBuf)); - } - } - - [ConditionalFact(nameof(IsNtlmInstalled))] - public void NegotiateStream_StreamToStream_Flush_Propagated() - { - (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); - using (var stream = new CallTrackingStream(stream1)) - using (var negotiateStream = new NegotiateStream(stream)) - using (stream2) - { - Assert.Equal(0, stream.TimesCalled(nameof(Stream.Flush))); - negotiateStream.Flush(); - Assert.NotEqual(0, stream.TimesCalled(nameof(Stream.Flush))); - } - } - - [ConditionalFact(nameof(IsNtlmInstalled))] - public async Task NegotiateStream_StreamToStream_FlushAsync_Propagated() - { - (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); - var tcs = new TaskCompletionSource(); - - using (var stream = new DelegateDelegatingStream(stream1) { FlushAsyncFunc = async cancellationToken => { await tcs.Task.WithCancellation(cancellationToken); await stream1.FlushAsync(cancellationToken); } }) - using (var negotiateStream = new NegotiateStream(stream)) - using (stream2) - { - Task task = negotiateStream.FlushAsync(); - - Assert.False(task.IsCompleted); - tcs.SetResult(); - - await task; - } - } - - [ConditionalFact(nameof(IsNtlmInstalled))] - public async Task NegotiateStream_StreamToStream_Successive_CancelableReadsWrites() - { - if (!SupportsCancelableReadsWrites) - { - return; - } - - byte[] recvBuf = new byte[s_sampleMsg.Length]; - - (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); - using (var clientStream = new DelayStream(stream1)) - using (var serverStream = new DelayStream(stream2)) - using (var client = new NegotiateStream(clientStream)) - using (var server = new NegotiateStream(serverStream)) - { - await TestConfiguration.WhenAllOrAnyFailedWithTimeout( - AuthenticateAsClientAsync(client, CredentialCache.DefaultNetworkCredentials, string.Empty), - AuthenticateAsServerAsync(server)); - - clientStream.DelayMilliseconds = int.MaxValue; - serverStream.DelayMilliseconds = int.MaxValue; - - var cts = new CancellationTokenSource(); - Task t = WriteAsync(client, s_sampleMsg, 0, s_sampleMsg.Length, cts.Token); - Assert.False(t.IsCompleted); - cts.Cancel(); - await Assert.ThrowsAnyAsync(() => t); - - cts = new CancellationTokenSource(); - t = ReadAsync(server, s_sampleMsg, 0, s_sampleMsg.Length, cts.Token); - Assert.False(t.IsCompleted); - cts.Cancel(); - await Assert.ThrowsAnyAsync(() => t); - } - } - - [ConditionalFact(nameof(IsNtlmInstalled))] - public async Task NegotiateStream_ReadToEof_Returns0() - { - (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); - using (var client = new NegotiateStream(stream1)) - using (var server = new NegotiateStream(stream2)) - { - await TestConfiguration.WhenAllOrAnyFailedWithTimeout( - AuthenticateAsClientAsync(client, CredentialCache.DefaultNetworkCredentials, string.Empty), - AuthenticateAsServerAsync(server)); - - client.Write(Encoding.UTF8.GetBytes("hello")); - client.Dispose(); - - Assert.Equal('h', server.ReadByte()); - Assert.Equal('e', server.ReadByte()); - Assert.Equal('l', server.ReadByte()); - Assert.Equal('l', server.ReadByte()); - Assert.Equal('o', server.ReadByte()); - Assert.Equal(-1, server.ReadByte()); - } - } } public sealed class NegotiateStreamStreamToStreamTest_Async_Array : NegotiateStreamStreamToStreamTest diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamConformanceTests.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamConformanceTests.cs new file mode 100644 index 00000000000000..bfacfb14306873 --- /dev/null +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamConformanceTests.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using System.IO.Tests; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; + +namespace System.Net.Security.Tests +{ + public sealed class SslStreamMemoryConformanceTests : ConnectedStreamConformanceTests + { + protected override bool UsableAfterCanceledReads => false; + protected override bool BlocksOnZeroByteReads => true; + protected override Type UnsupportedConcurrentExceptionType => typeof(NotSupportedException); + + protected override async Task CreateConnectedStreamsAsync() + { + (Stream stream1, Stream stream2) = ConnectedStreams.CreateBidirectional(); + + X509Certificate2? cert = Test.Common.Configuration.Certificates.GetServerCertificate(); + var ssl1 = new SslStream(stream1, false, delegate { return true; }); + var ssl2 = new SslStream(stream2, false, delegate { return true; }); + + await new[] + { + ssl1.AuthenticateAsClientAsync(cert.GetNameInfo(X509NameType.SimpleName, false)), + ssl2.AuthenticateAsServerAsync(cert, false, false) + }.WhenAllOrAnyFailed().ConfigureAwait(false); + + return new StreamPair(ssl1, ssl2); + } + } +} diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs index 2d626becea96c9..a563fb18a1ac80 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs @@ -223,267 +223,6 @@ public async Task Write_InvokedSynchronously() } } - [Fact] - public async Task SslStream_StreamToStream_Successive_ClientWrite_WithZeroBytes_Success() - { - byte[] recvBuf = new byte[_sampleMsg.Length]; - - (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); - using (var clientSslStream = new SslStream(stream1, false, AllowAnyServerCertificate)) - using (var serverSslStream = new SslStream(stream2)) - { - await DoHandshake(clientSslStream, serverSslStream); - - await WriteAsync(clientSslStream, Array.Empty(), 0, 0); - await WriteAsync(clientSslStream, _sampleMsg, 0, _sampleMsg.Length); - - int bytesRead = 0; - while (bytesRead < _sampleMsg.Length) - { - bytesRead += await ReadAsync(serverSslStream, recvBuf, bytesRead, _sampleMsg.Length - bytesRead); - } - - Assert.True(VerifyOutput(recvBuf, _sampleMsg), "verify first read data is as expected."); - - await WriteAsync(clientSslStream, _sampleMsg, 0, _sampleMsg.Length); - await WriteAsync(clientSslStream, Array.Empty(), 0, 0); - - bytesRead = 0; - while (bytesRead < _sampleMsg.Length) - { - bytesRead += await ReadAsync(serverSslStream, recvBuf, bytesRead, _sampleMsg.Length - bytesRead); - } - Assert.True(VerifyOutput(recvBuf, _sampleMsg), "verify second read data is as expected."); - } - } - - [Fact] - public async Task SslStream_StreamToStream_ZeroByteRead_SucceedsWhenDataAvailable() - { - (NetworkStream clientStream, NetworkStream serverStream) = TestHelper.GetConnectedTcpStreams(); - using var clientSslStream = new SslStream(clientStream, leaveInnerStreamOpen: false, AllowAnyServerCertificate); - using var serverSslStream = new SslStream(serverStream); - await DoHandshake(clientSslStream, serverSslStream); - - for (int iter = 0; iter < 2; iter++) - { - ValueTask zeroByteRead = clientSslStream.ReadAsync(Memory.Empty); - Assert.False(zeroByteRead.IsCompleted); - - await serverSslStream.WriteAsync(Encoding.UTF8.GetBytes("hello")); - Assert.Equal(0, await zeroByteRead); - - var readBytes = new byte[5]; - int count = 0; - while (count < readBytes.Length) - { - int n = await clientSslStream.ReadAsync(readBytes.AsMemory(count)); - Assert.InRange(n, 1, readBytes.Length - count); - count += n; - } - Assert.Equal("hello", Encoding.UTF8.GetString(readBytes)); - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task SslStream_StreamToStream_LargeWrites_Success(bool randomizedData) - { - (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); - using (var clientSslStream = new SslStream(stream1, false, AllowAnyServerCertificate)) - using (var serverSslStream = new SslStream(stream2)) - { - await DoHandshake(clientSslStream, serverSslStream); - - byte[] largeMsg = new byte[4096 * 5]; // length longer than max read chunk size (16K + headers) - if (randomizedData) - { - new Random().NextBytes(largeMsg); // not very compressible - } - else - { - for (int i = 0; i < largeMsg.Length; i++) - { - largeMsg[i] = unchecked((byte)i); // very compressible - } - } - byte[] receivedLargeMsg = new byte[largeMsg.Length]; - - // First do a large write and read blocks at a time - await WriteAsync(clientSslStream, largeMsg, 0, largeMsg.Length); - int bytesRead = 0, totalRead = 0; - while (totalRead < largeMsg.Length && - (bytesRead = await ReadAsync(serverSslStream, receivedLargeMsg, totalRead, receivedLargeMsg.Length - totalRead)) != 0) - { - totalRead += bytesRead; - } - Assert.Equal(receivedLargeMsg.Length, totalRead); - Assert.Equal(largeMsg, receivedLargeMsg); - - // Then write again and read bytes at a time - await WriteAsync(clientSslStream, largeMsg, 0, largeMsg.Length); - foreach (byte b in largeMsg) - { - Assert.Equal(b, serverSslStream.ReadByte()); - } - } - } - - [Fact] - public async Task SslStream_StreamToStream_Successive_ClientWrite_Success() - { - byte[] recvBuf = new byte[_sampleMsg.Length]; - (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); - using (var clientSslStream = new SslStream(stream1, false, AllowAnyServerCertificate)) - using (var serverSslStream = new SslStream(stream2)) - { - await DoHandshake(clientSslStream, serverSslStream); - - await WriteAsync(clientSslStream, _sampleMsg, 0, _sampleMsg.Length) - .TimeoutAfter(TestConfiguration.PassingTestTimeoutMilliseconds); - - int bytesRead = 0; - while (bytesRead < _sampleMsg.Length) - { - bytesRead += await ReadAsync(serverSslStream, recvBuf, bytesRead, _sampleMsg.Length - bytesRead) - .TimeoutAfter(TestConfiguration.PassingTestTimeoutMilliseconds); - } - - Assert.True(VerifyOutput(recvBuf, _sampleMsg), "verify first read data is as expected."); - - await WriteAsync(clientSslStream, _sampleMsg, 0, _sampleMsg.Length) - .TimeoutAfter(TestConfiguration.PassingTestTimeoutMilliseconds); - - bytesRead = 0; - while (bytesRead < _sampleMsg.Length) - { - bytesRead += await ReadAsync(serverSslStream, recvBuf, bytesRead, _sampleMsg.Length - bytesRead) - .TimeoutAfter(TestConfiguration.PassingTestTimeoutMilliseconds); - } - - Assert.True(VerifyOutput(recvBuf, _sampleMsg), "verify second read data is as expected."); - } - } - - [Fact] - public async Task SslStream_StreamToStream_Write_ReadByte_Success() - { - (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); - using (var clientSslStream = new SslStream(stream1, false, AllowAnyServerCertificate)) - using (var serverSslStream = new SslStream(stream2)) - { - await DoHandshake(clientSslStream, serverSslStream); - - for (int i = 0; i < 3; i++) - { - await WriteAsync(clientSslStream, _sampleMsg, 0, _sampleMsg.Length); - foreach (byte b in _sampleMsg) - { - Assert.Equal(b, serverSslStream.ReadByte()); - } - } - } - } - - [Fact] - public async Task SslStream_StreamToStream_WriteAsync_ReadByte_Success() - { - (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); - using (var clientSslStream = new SslStream(stream1, false, AllowAnyServerCertificate)) - using (var serverSslStream = new SslStream(stream2)) - { - await DoHandshake(clientSslStream, serverSslStream); - - for (int i = 0; i < 3; i++) - { - await WriteAsync(clientSslStream, _sampleMsg, 0, _sampleMsg.Length).ConfigureAwait(false); - foreach (byte b in _sampleMsg) - { - Assert.Equal(b, serverSslStream.ReadByte()); - } - } - } - } - - [Fact] - public async Task SslStream_StreamToStream_WriteAsync_ReadAsync_Pending_Success() - { - if (this is SslStreamStreamToStreamTest_SyncBase) - { - // This test assumes operations complete asynchronously. - return; - } - - (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); - using (var clientSslStream = new SslStream(stream1, false, AllowAnyServerCertificate)) - using (var serverSslStream = new SslStream(stream2)) - { - await DoHandshake(clientSslStream, serverSslStream); - - var serverBuffer = new byte[1]; - - Task readTask = ReadAsync(serverSslStream, serverBuffer, 0, serverBuffer.Length); - - // Should not hang - await WriteAsync(serverSslStream, new byte[] { 1 }, 0, 1).TimeoutAfter(TestConfiguration.PassingTestTimeoutMilliseconds); - - // Read in client - var clientBuffer = new byte[1]; - await ReadAsync(clientSslStream, clientBuffer, 0, clientBuffer.Length); - Assert.Equal(1, clientBuffer[0]); - - // Complete server read task - await WriteAsync(clientSslStream, new byte[] { 2 }, 0, 1); - await readTask; - Assert.Equal(2, serverBuffer[0]); - } - } - - [Fact] - public async Task SslStream_ConcurrentBidirectionalReadsWrites_Success() - { - (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); - using (var clientSslStream = new SslStream(stream1, false, AllowAnyServerCertificate)) - using (var serverSslStream = new SslStream(stream2)) - { - await DoHandshake(clientSslStream, serverSslStream); - - const int BytesPerSend = 100; - DateTime endTime = DateTime.UtcNow + TimeSpan.FromSeconds(3); - await new Task[] - { - Task.Run(async delegate - { - var buffer = new byte[BytesPerSend]; - while (DateTime.UtcNow < endTime) - { - await WriteAsync(clientSslStream, buffer, 0, buffer.Length); - int received = 0, bytesRead = 0; - while (received < BytesPerSend && (bytesRead = await ReadAsync(serverSslStream, buffer, 0, buffer.Length)) != 0) - { - received += bytesRead; - } - Assert.NotEqual(0, bytesRead); - } - }), - Task.Run(async delegate - { - var buffer = new byte[BytesPerSend]; - while (DateTime.UtcNow < endTime) - { - await WriteAsync(serverSslStream, buffer, 0, buffer.Length); - int received = 0, bytesRead = 0; - while (received < BytesPerSend && (bytesRead = await ReadAsync(clientSslStream, buffer, 0, buffer.Length)) != 0) - { - received += bytesRead; - } - Assert.NotEqual(0, bytesRead); - } - }) - }.WhenAllOrAnyFailed(); - } - } [Fact] public async Task SslStream_StreamToStream_Dispose_Throws() @@ -537,32 +276,6 @@ public async Task SslStream_StreamToStream_Dispose_Throws() } } - [Fact] - public void SslStream_StreamToStream_Flush_Propagated() - { - var ms = new MemoryStream(); - var tracking = new CallTrackingStream(ms); - using (var sslStream = new SslStream(tracking, false, AllowAnyServerCertificate)) - { - Assert.Equal(0, tracking.TimesCalled(nameof(Stream.Flush))); - sslStream.Flush(); - Assert.NotEqual(0, tracking.TimesCalled(nameof(Stream.Flush))); - } - } - - [Fact] - public async Task SslStream_StreamToStream_FlushAsync_Propagated() - { - var ms = new MemoryStream(); - var tracking = new CallTrackingStream(ms); - using (var sslStream = new SslStream(tracking, false, AllowAnyServerCertificate)) - { - Assert.Equal(0, tracking.TimesCalled(nameof(Stream.FlushAsync))); - await sslStream.FlushAsync(); - Assert.NotEqual(0, tracking.TimesCalled(nameof(Stream.FlushAsync))); - } - } - [Fact] public async Task SslStream_StreamToStream_EOFDuringFrameRead_ThrowsIOException() { @@ -732,42 +445,7 @@ public ThrowingDelegatingStream(Stream stream) : base(stream) } } - public abstract class SslStreamStreamToStreamTest_CancelableReadWriteAsync : SslStreamStreamToStreamTest - { - [Fact] - public async Task ReadAsync_WriteAsync_Precanceled_ThrowsOperationCanceledException() - { - (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); - using (var clientSslStream = new SslStream(stream1, false, AllowAnyServerCertificate)) - using (var serverSslStream = new SslStream(stream2)) - { - await DoHandshake(clientSslStream, serverSslStream); - await Assert.ThrowsAnyAsync(() => ReadAsync(clientSslStream, new byte[1], 0, 1, new CancellationToken(true))); - await Assert.ThrowsAnyAsync(() => WriteAsync(serverSslStream, new byte[1], 0, 1, new CancellationToken(true))); - } - } - - [Fact] - public async Task ReadAsync_CanceledAfterStart_ThrowsOperationCanceledException() - { - (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); - using (var clientSslStream = new SslStream(stream1, false, AllowAnyServerCertificate)) - using (var serverSslStream = new SslStream(stream2)) - { - await DoHandshake(clientSslStream, serverSslStream); - - var cts = new CancellationTokenSource(); - - Task t = ReadAsync(clientSslStream, new byte[1], 0, 1, cts.Token); - Assert.False(t.IsCompleted); - - cts.Cancel(); - await Assert.ThrowsAnyAsync(() => t); - } - } - } - - public sealed class SslStreamStreamToStreamTest_Async : SslStreamStreamToStreamTest_CancelableReadWriteAsync + public sealed class SslStreamStreamToStreamTest_Async : SslStreamStreamToStreamTest { protected override async Task DoHandshake(SslStream clientSslStream, SslStream serverSslStream, X509Certificate serverCertificate = null, X509Certificate clientCertificate = null) { @@ -939,7 +617,7 @@ await WithServerCertificate(serverCertificate, async (certificate, name) => } } - public sealed class SslStreamStreamToStreamTest_MemoryAsync : SslStreamStreamToStreamTest_CancelableReadWriteAsync + public sealed class SslStreamStreamToStreamTest_MemoryAsync : SslStreamStreamToStreamTest { protected override async Task DoHandshake(SslStream clientSslStream, SslStream serverSslStream, X509Certificate serverCertificate = null, X509Certificate clientCertificate = null) { diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj b/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj index f800c0785f9f3a..2bfd9ee51808db 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj @@ -24,15 +24,19 @@ + + + Date: Mon, 26 Oct 2020 12:35:25 -0400 Subject: [PATCH 15/24] Fix PipeStream.Flush to not fail on readable streams Consumers may expect Stream.Flush to be a nop if the stream is readable only, but PipeStream.Flush is throwing in that case. Stop doing that. --- .../System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs b/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs index f6ad7fd11ea4a0..01c5f1915d46d4 100644 --- a/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs +++ b/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs @@ -351,15 +351,12 @@ public override unsafe void WriteByte(byte value) Write(new ReadOnlySpan(&value, 1)); } - // Does nothing on PipeStreams. We cannot call Interop.FlushFileBuffers here because we can deadlock - // if the other end of the pipe is no longer interested in reading from the pipe. public override void Flush() { CheckWriteOperations(); - if (!CanWrite) - { - throw Error.GetWriteNotSupported(); - } + + // Does nothing on PipeStreams. We cannot call Interop.FlushFileBuffers here because we can deadlock + // if the other end of the pipe is no longer interested in reading from the pipe. } public override Task FlushAsync(CancellationToken cancellationToken) From fb55c4a266cb164ad756de75140d7895b5acb45f Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:36:05 -0400 Subject: [PATCH 16/24] Add stream conformance tests for PipeStream --- .../tests/PipeTest.AclExtensions.cs | 26 +- ...System.IO.Pipes.AccessControl.Tests.csproj | 1 - .../AnonymousPipeTest.CreateClient.cs | 9 +- .../AnonymousPipeTest.CreateServer.cs | 2 +- .../AnonymousPipeTest.Read.cs | 31 - .../AnonymousPipeTest.Specific.cs | 83 +- .../AnonymousPipeTest.Write.cs | 29 - .../AnonymousPipeTestBase.cs | 29 - .../tests/InteropTest.Windows.Win32.cs | 30 - .../tests/InteropTest.Windows.cs | 17 + .../NamedPipeTest.CreateClient.cs | 9 +- .../NamedPipeTest.CreateServer.cs | 18 +- .../NamedPipeTest.CrossProcess.cs | 20 +- .../NamedPipeTest.CurrentUserOnly.Windows.cs | 6 +- .../NamedPipeTest.CurrentUserOnly.cs | 22 +- .../NamedPipeTests/NamedPipeTest.Read.cs | 89 -- .../NamedPipeTest.RunAsClient.Windows.cs | 2 +- .../NamedPipeTests/NamedPipeTest.Simple.cs | 831 ------------------ .../NamedPipeTests/NamedPipeTest.Specific.cs | 51 +- .../NamedPipeTest.UnixDomainSockets.cs | 2 +- .../NamedPipeTests/NamedPipeTest.Write.cs | 69 -- .../tests/NamedPipeTests/NamedPipeTest.cs | 18 - .../tests/NamedPipeTests/NamedPipeTestBase.cs | 120 --- ...s => PipeStream.ProtectedMethods.Tests.cs} | 0 .../tests/PipeStreamConformanceTests.cs | 690 +++++++++++++++ .../System.IO.Pipes/tests/PipeTest.Read.cs | 533 ----------- .../System.IO.Pipes/tests/PipeTest.Write.cs | 311 ------- .../System.IO.Pipes/tests/PipeTestBase.cs | 103 --- .../tests/System.IO.Pipes.Tests.csproj | 50 +- 29 files changed, 859 insertions(+), 2342 deletions(-) delete mode 100644 src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.Read.cs delete mode 100644 src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.Write.cs delete mode 100644 src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTestBase.cs delete mode 100644 src/libraries/System.IO.Pipes/tests/InteropTest.Windows.Win32.cs delete mode 100644 src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Read.cs delete mode 100644 src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Simple.cs delete mode 100644 src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Write.cs delete mode 100644 src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.cs delete mode 100644 src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTestBase.cs rename src/libraries/System.IO.Pipes/tests/{PipeTest.cs => PipeStream.ProtectedMethods.Tests.cs} (100%) create mode 100644 src/libraries/System.IO.Pipes/tests/PipeStreamConformanceTests.cs delete mode 100644 src/libraries/System.IO.Pipes/tests/PipeTest.Read.cs delete mode 100644 src/libraries/System.IO.Pipes/tests/PipeTest.Write.cs delete mode 100644 src/libraries/System.IO.Pipes/tests/PipeTestBase.cs diff --git a/src/libraries/System.IO.Pipes.AccessControl/tests/PipeTest.AclExtensions.cs b/src/libraries/System.IO.Pipes.AccessControl/tests/PipeTest.AclExtensions.cs index 13a16a0f8c005d..89dd40c62304e1 100644 --- a/src/libraries/System.IO.Pipes.AccessControl/tests/PipeTest.AclExtensions.cs +++ b/src/libraries/System.IO.Pipes.AccessControl/tests/PipeTest.AclExtensions.cs @@ -7,7 +7,7 @@ namespace System.IO.Pipes.Tests { - public abstract class PipeTest_AclExtensions : PipeTestBase + public abstract class PipeTest_AclExtensions { [Fact] public void GetAccessControl_NullPipeStream() @@ -97,5 +97,29 @@ public void PipeSecurity_VerifySynchronizeMasks() new PipeAccessRule(si, PipeAccessRights.Synchronize, AccessControlType.Deny); }); } + + protected static string GetUniquePipeName() => + PlatformDetection.IsInAppContainer ? @"LOCAL\" + Path.GetRandomFileName() : + Path.GetRandomFileName(); + + protected abstract ServerClientPair CreateServerClientPair(); + + protected class ServerClientPair : IDisposable + { + public PipeStream readablePipe; + public PipeStream writeablePipe; + + public void Dispose() + { + try + { + readablePipe?.Dispose(); + } + finally + { + writeablePipe.Dispose(); + } + } + } } } diff --git a/src/libraries/System.IO.Pipes.AccessControl/tests/System.IO.Pipes.AccessControl.Tests.csproj b/src/libraries/System.IO.Pipes.AccessControl/tests/System.IO.Pipes.AccessControl.Tests.csproj index 57015e7b67883a..82cd7df4411d82 100644 --- a/src/libraries/System.IO.Pipes.AccessControl/tests/System.IO.Pipes.AccessControl.Tests.csproj +++ b/src/libraries/System.IO.Pipes.AccessControl/tests/System.IO.Pipes.AccessControl.Tests.csproj @@ -13,7 +13,6 @@ - diff --git a/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.CreateClient.cs b/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.CreateClient.cs index beb20ffff1a7a8..a7ac7215517ecb 100644 --- a/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.CreateClient.cs +++ b/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.CreateClient.cs @@ -9,7 +9,7 @@ namespace System.IO.Pipes.Tests /// /// Tests for the constructors of AnonymousPipeClientStream /// - public class AnonymousPipeTest_CreateClient : AnonymousPipeTestBase + public class AnonymousPipeTest_CreateClient { [Fact] public static void NullParameters_Throws_ArgumentNullException() @@ -22,11 +22,8 @@ public static void NullParameters_Throws_ArgumentNullException() [Fact] public static void CreateClientStreamFromStringHandle_Valid() { - using (var server = new AnonymousPipeServerStream(PipeDirection.Out)) - using (var client = new AnonymousPipeClientStream(server.GetClientHandleAsString())) - { - SuppressClientHandleFinalizationIfNetFramework(server); - } + using var server = new AnonymousPipeServerStream(PipeDirection.Out); + new AnonymousPipeClientStream(server.GetClientHandleAsString()).Dispose(); } [Theory] diff --git a/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.CreateServer.cs b/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.CreateServer.cs index 3003ba55e1e79c..6dbca6b861b598 100644 --- a/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.CreateServer.cs +++ b/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.CreateServer.cs @@ -9,7 +9,7 @@ namespace System.IO.Pipes.Tests /// /// Tests for the constructors of AnonymousPipeServerStream /// - public class AnonymousPipeTest_CreateServer : AnonymousPipeTestBase + public class AnonymousPipeTest_CreateServer { [Fact] public static void InOutPipeDirection_Throws_NotSupportedException() diff --git a/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.Read.cs b/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.Read.cs deleted file mode 100644 index 6143a6443241e6..00000000000000 --- a/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.Read.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Pipes.Tests -{ - public class AnonymousPipeTest_Read_ServerIn_ClientOut : PipeTest_Read - { - protected override ServerClientPair CreateServerClientPair() - { - ServerClientPair ret = new ServerClientPair(); - ret.readablePipe = new AnonymousPipeServerStream(PipeDirection.In); - ret.writeablePipe = new AnonymousPipeClientStream(PipeDirection.Out, ((AnonymousPipeServerStream)ret.readablePipe).ClientSafePipeHandle); - return ret; - } - } - - public class AnonymousPipeTest_Read_ServerOut_ClientIn : PipeTest_Read - { - protected override ServerClientPair CreateServerClientPair() - { - ServerClientPair ret = new ServerClientPair(); - ret.writeablePipe = new AnonymousPipeServerStream(PipeDirection.Out); - ret.readablePipe = new AnonymousPipeClientStream(PipeDirection.In, ((AnonymousPipeServerStream)ret.writeablePipe).ClientSafePipeHandle); - return ret; - } - } -} diff --git a/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.Specific.cs b/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.Specific.cs index f7ef3d6afa8f73..909a91b82c2b4d 100644 --- a/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.Specific.cs +++ b/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.Specific.cs @@ -9,24 +9,22 @@ namespace System.IO.Pipes.Tests /// The Specific AnonymousPipe tests cover edge cases or otherwise narrow cases that /// show up within particular server/client directional combinations. /// - public class AnonymousPipeTest_Specific : AnonymousPipeTestBase + public class AnonymousPipeTest_Specific { [Fact] public static void DisposeLocalCopyOfClientHandle_BeforeServerRead() { using (AnonymousPipeServerStream server = new AnonymousPipeServerStream(PipeDirection.In)) + using (AnonymousPipeClientStream client = new AnonymousPipeClientStream(PipeDirection.Out, server.ClientSafePipeHandle)) { - using (AnonymousPipeClientStream client = new AnonymousPipeClientStream(PipeDirection.Out, server.ClientSafePipeHandle)) - { - byte[] sent = new byte[] { 123 }; - byte[] received = new byte[] { 0 }; - client.Write(sent, 0, 1); + byte[] sent = new byte[] { 123 }; + byte[] received = new byte[] { 0 }; + client.Write(sent, 0, 1); - server.DisposeLocalCopyOfClientHandle(); + server.DisposeLocalCopyOfClientHandle(); - Assert.Equal(1, server.Read(received, 0, 1)); - Assert.Equal(sent[0], received[0]); - } + Assert.Equal(1, server.Read(received, 0, 1)); + Assert.Equal(sent[0], received[0]); } } @@ -34,24 +32,22 @@ public static void DisposeLocalCopyOfClientHandle_BeforeServerRead() public static void ClonedServer_ActsAsOriginalServer() { using (AnonymousPipeServerStream serverBase = new AnonymousPipeServerStream(PipeDirection.Out)) + using (AnonymousPipeServerStream server = new AnonymousPipeServerStream(PipeDirection.Out, serverBase.SafePipeHandle, serverBase.ClientSafePipeHandle)) { - using (AnonymousPipeServerStream server = new AnonymousPipeServerStream(PipeDirection.Out, serverBase.SafePipeHandle, serverBase.ClientSafePipeHandle)) + Assert.True(server.IsConnected); + using (AnonymousPipeClientStream client = new AnonymousPipeClientStream(PipeDirection.In, server.ClientSafePipeHandle)) { Assert.True(server.IsConnected); - using (AnonymousPipeClientStream client = new AnonymousPipeClientStream(PipeDirection.In, server.ClientSafePipeHandle)) - { - Assert.True(server.IsConnected); - Assert.True(client.IsConnected); - - byte[] sent = new byte[] { 123 }; - byte[] received = new byte[] { 0 }; - server.Write(sent, 0, 1); - - Assert.Equal(1, client.Read(received, 0, 1)); - Assert.Equal(sent[0], received[0]); - } - Assert.Throws(() => server.WriteByte(5)); + Assert.True(client.IsConnected); + + byte[] sent = new byte[] { 123 }; + byte[] received = new byte[] { 0 }; + server.Write(sent, 0, 1); + + Assert.Equal(1, client.Read(received, 0, 1)); + Assert.Equal(sent[0], received[0]); } + Assert.Throws(() => server.WriteByte(5)); } } @@ -59,22 +55,18 @@ public static void ClonedServer_ActsAsOriginalServer() public static void ClonedClient_ActsAsOriginalClient() { using (AnonymousPipeServerStream server = new AnonymousPipeServerStream(PipeDirection.Out)) + using (AnonymousPipeClientStream clientBase = new AnonymousPipeClientStream(PipeDirection.In, server.ClientSafePipeHandle)) + using (AnonymousPipeClientStream client = new AnonymousPipeClientStream(PipeDirection.In, clientBase.SafePipeHandle)) { - using (AnonymousPipeClientStream clientBase = new AnonymousPipeClientStream(PipeDirection.In, server.ClientSafePipeHandle)) - { - using (AnonymousPipeClientStream client = new AnonymousPipeClientStream(PipeDirection.In, clientBase.SafePipeHandle)) - { - Assert.True(server.IsConnected); - Assert.True(client.IsConnected); - - byte[] sent = new byte[] { 123 }; - byte[] received = new byte[] { 0 }; - server.Write(sent, 0, 1); - - Assert.Equal(1, client.Read(received, 0, 1)); - Assert.Equal(sent[0], received[0]); - } - } + Assert.True(server.IsConnected); + Assert.True(client.IsConnected); + + byte[] sent = new byte[] { 123 }; + byte[] received = new byte[] { 0 }; + server.Write(sent, 0, 1); + + Assert.Equal(1, client.Read(received, 0, 1)); + Assert.Equal(sent[0], received[0]); } } @@ -145,13 +137,16 @@ public static void Windows_BufferSizeRoundtripping() } } - [Fact] - public void PipeTransmissionMode_Returns_Byte() + [Theory] + [InlineData(PipeDirection.Out, PipeDirection.In)] + [InlineData(PipeDirection.In, PipeDirection.Out)] + public void PipeTransmissionMode_Returns_Byte(PipeDirection serverDirection, PipeDirection clientDirection) { - using (ServerClientPair pair = CreateServerClientPair()) + using (AnonymousPipeServerStream server = new AnonymousPipeServerStream(serverDirection)) + using (AnonymousPipeClientStream client = new AnonymousPipeClientStream(clientDirection, server.ClientSafePipeHandle)) { - Assert.Equal(PipeTransmissionMode.Byte, pair.writeablePipe.TransmissionMode); - Assert.Equal(PipeTransmissionMode.Byte, pair.readablePipe.TransmissionMode); + Assert.Equal(PipeTransmissionMode.Byte, server.TransmissionMode); + Assert.Equal(PipeTransmissionMode.Byte, client.TransmissionMode); } } diff --git a/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.Write.cs b/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.Write.cs deleted file mode 100644 index 57ab3f49606687..00000000000000 --- a/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.Write.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Xunit; - -namespace System.IO.Pipes.Tests -{ - public class AnonymousPipeTest_Write_ServerIn_ClientOut : PipeTest_Write - { - protected override ServerClientPair CreateServerClientPair() - { - ServerClientPair ret = new ServerClientPair(); - ret.readablePipe = new AnonymousPipeServerStream(PipeDirection.In); - ret.writeablePipe = new AnonymousPipeClientStream(PipeDirection.Out, ((AnonymousPipeServerStream)ret.readablePipe).ClientSafePipeHandle); - return ret; - } - } - - public class AnonymousPipeTest_Write_ServerOut_ClientIn : PipeTest_Write - { - protected override ServerClientPair CreateServerClientPair() - { - ServerClientPair ret = new ServerClientPair(); - ret.writeablePipe = new AnonymousPipeServerStream(PipeDirection.Out); - ret.readablePipe = new AnonymousPipeClientStream(PipeDirection.In, ((AnonymousPipeServerStream)ret.writeablePipe).ClientSafePipeHandle); - return ret; - } - } -} diff --git a/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTestBase.cs b/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTestBase.cs deleted file mode 100644 index 7c4a207794d4ef..00000000000000 --- a/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTestBase.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Win32.SafeHandles; - -namespace System.IO.Pipes.Tests -{ - /// - /// Contains helper methods used in AnonymousPipeTests - /// - public class AnonymousPipeTestBase : PipeTestBase - { - protected override ServerClientPair CreateServerClientPair() - { - ServerClientPair ret = new ServerClientPair(); - ret.readablePipe = new AnonymousPipeServerStream(PipeDirection.In); - ret.writeablePipe = new AnonymousPipeClientStream(PipeDirection.Out, ((AnonymousPipeServerStream)ret.readablePipe).ClientSafePipeHandle); - return ret; - } - - protected static void StartClient(PipeDirection direction, SafePipeHandle clientPipeHandle) - { - using (AnonymousPipeClientStream client = new AnonymousPipeClientStream(direction, clientPipeHandle)) - { - DoStreamOperations(client); - } - } - } -} diff --git a/src/libraries/System.IO.Pipes/tests/InteropTest.Windows.Win32.cs b/src/libraries/System.IO.Pipes/tests/InteropTest.Windows.Win32.cs deleted file mode 100644 index 0bc62ae31ed5a8..00000000000000 --- a/src/libraries/System.IO.Pipes/tests/InteropTest.Windows.Win32.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Win32.SafeHandles; - -namespace System.IO.Pipes.Tests -{ - /// - /// The class contains interop declarations and helpers methods for them. - /// - internal static partial class InteropTest - { - private static unsafe bool TryHandleGetImpersonationUserNameError(SafePipeHandle handle, int error, uint userNameMaxLength, char* userName, out string impersonationUserName) - { - if ((error == Interop.Errors.ERROR_SUCCESS || error == Interop.Errors.ERROR_CANNOT_IMPERSONATE) && Environment.Is64BitProcess) - { - Interop.Kernel32.LoadLibraryEx("sspicli.dll", IntPtr.Zero, Interop.Kernel32.LOAD_LIBRARY_SEARCH_SYSTEM32); - - if (Interop.Kernel32.GetNamedPipeHandleStateW(handle, null, null, null, null, userName, userNameMaxLength)) - { - impersonationUserName = new string(userName); - return true; - } - } - - impersonationUserName = string.Empty; - return false; - } - } -} diff --git a/src/libraries/System.IO.Pipes/tests/InteropTest.Windows.cs b/src/libraries/System.IO.Pipes/tests/InteropTest.Windows.cs index ec326561013a3a..087237e2352c6b 100644 --- a/src/libraries/System.IO.Pipes/tests/InteropTest.Windows.cs +++ b/src/libraries/System.IO.Pipes/tests/InteropTest.Windows.cs @@ -47,5 +47,22 @@ internal static unsafe bool TryGetNumberOfServerInstances(SafePipeHandle handle, // @todo: These are called by some Unix-specific tests. Those tests should really be split out into // partial classes and included only in Unix builds. internal static bool TryGetHostName(out string hostName) { throw new Exception("Should not call on Windows."); } + + private static unsafe bool TryHandleGetImpersonationUserNameError(SafePipeHandle handle, int error, uint userNameMaxLength, char* userName, out string impersonationUserName) + { + if ((error == Interop.Errors.ERROR_SUCCESS || error == Interop.Errors.ERROR_CANNOT_IMPERSONATE) && Environment.Is64BitProcess) + { + Interop.Kernel32.LoadLibraryEx("sspicli.dll", IntPtr.Zero, Interop.Kernel32.LOAD_LIBRARY_SEARCH_SYSTEM32); + + if (Interop.Kernel32.GetNamedPipeHandleStateW(handle, null, null, null, null, userName, userNameMaxLength)) + { + impersonationUserName = new string(userName); + return true; + } + } + + impersonationUserName = string.Empty; + return false; + } } } diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CreateClient.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CreateClient.cs index f233395f462b47..35fac2a2ddbf42 100644 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CreateClient.cs +++ b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CreateClient.cs @@ -10,7 +10,7 @@ namespace System.IO.Pipes.Tests /// /// Tests for the constructors for NamedPipeClientStream /// - public class NamedPipeTest_CreateClient : NamedPipeTestBase + public class NamedPipeTest_CreateClient { [Fact] public static void NullPipeName_Throws_ArgumentNullException() @@ -153,5 +153,12 @@ public static void BadHandleKind_Throws_IOException(PipeDirection direction) } } } + + [Fact] + public void NamedPipeClientStream_InvalidHandleInerhitability() + { + AssertExtensions.Throws("inheritability", () => new NamedPipeClientStream("a", "b", PipeDirection.Out, 0, TokenImpersonationLevel.Delegation, HandleInheritability.None - 1)); + AssertExtensions.Throws("inheritability", () => new NamedPipeClientStream("a", "b", PipeDirection.Out, 0, TokenImpersonationLevel.Delegation, HandleInheritability.Inheritable + 1)); + } } } diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CreateServer.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CreateServer.cs index 9c09cb7117429f..10a97116eb7da0 100644 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CreateServer.cs +++ b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CreateServer.cs @@ -9,7 +9,7 @@ namespace System.IO.Pipes.Tests /// /// Tests for the constructors for NamedPipeServerStream /// - public class NamedPipeTest_CreateServer : NamedPipeTestBase + public class NamedPipeTest_CreateServer { [Theory] [InlineData(PipeDirection.In)] @@ -57,13 +57,13 @@ public static void ReservedPipeName_Throws_ArgumentOutOfRangeException(PipeDirec [Fact] public static void Create_PipeName() { - new NamedPipeServerStream(GetUniquePipeName()).Dispose(); + new NamedPipeServerStream(PipeStreamConformanceTests.GetUniquePipeName()).Dispose(); } [Fact] public static void Create_PipeName_Direction_MaxInstances() { - new NamedPipeServerStream(GetUniquePipeName(), PipeDirection.Out, 1).Dispose(); + new NamedPipeServerStream(PipeStreamConformanceTests.GetUniquePipeName(), PipeDirection.Out, 1).Dispose(); } [Fact] @@ -72,7 +72,7 @@ public static void CreateWithNegativeOneServerInstances_DefaultsToMaxServerInsta { // When passed -1 as the maxnumberofserverisntances, the NamedPipeServerStream.Windows class // will translate that to the platform specific maximum number (255) - using (var server = new NamedPipeServerStream(GetUniquePipeName(), PipeDirection.InOut, -1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) + using (var server = new NamedPipeServerStream(PipeStreamConformanceTests.GetUniquePipeName(), PipeDirection.InOut, -1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) using (var server2 = new NamedPipeServerStream(PipeDirection.InOut, false, true, server.SafePipeHandle)) using (var server3 = new NamedPipeServerStream(PipeDirection.InOut, false, true, server.SafePipeHandle)) { @@ -197,7 +197,7 @@ public static void BadHandleKind_Throws_IOException(PipeDirection direction) public static void Windows_CreateFromDisposedServerHandle_Throws_ObjectDisposedException(PipeDirection direction) { // The pipe is closed when we try to make a new Stream with it - var pipe = new NamedPipeServerStream(GetUniquePipeName(), direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + var pipe = new NamedPipeServerStream(PipeStreamConformanceTests.GetUniquePipeName(), direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); SafePipeHandle handle = pipe.SafePipeHandle; pipe.Dispose(); Assert.Throws(() => new NamedPipeServerStream(direction, true, true, pipe.SafePipeHandle).Dispose()); @@ -207,7 +207,7 @@ public static void Windows_CreateFromDisposedServerHandle_Throws_ObjectDisposedE [PlatformSpecific(TestPlatforms.AnyUnix)] // accessing SafePipeHandle on Unix fails for a non-connected stream public static void Unix_GetHandleOfNewServerStream_Throws_InvalidOperationException() { - using (var pipe = new NamedPipeServerStream(GetUniquePipeName(), PipeDirection.Out, 1, PipeTransmissionMode.Byte)) + using (var pipe = new NamedPipeServerStream(PipeStreamConformanceTests.GetUniquePipeName(), PipeDirection.Out, 1, PipeTransmissionMode.Byte)) { Assert.Throws(() => pipe.SafePipeHandle); } @@ -221,7 +221,7 @@ public static void Unix_GetHandleOfNewServerStream_Throws_InvalidOperationExcept public static void Windows_CreateFromAlreadyBoundHandle_Throws_ArgumentException(PipeDirection direction) { // The pipe is already bound - using (var pipe = new NamedPipeServerStream(GetUniquePipeName(), direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) + using (var pipe = new NamedPipeServerStream(PipeStreamConformanceTests.GetUniquePipeName(), direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) { AssertExtensions.Throws("handle", () => new NamedPipeServerStream(direction, true, true, pipe.SafePipeHandle)); } @@ -231,7 +231,7 @@ public static void Windows_CreateFromAlreadyBoundHandle_Throws_ArgumentException [PlatformSpecific(TestPlatforms.Windows)] // NumberOfServerInstances > 1 isn't supported and has undefined behavior on Unix public static void ServerCountOverMaxServerInstances_Throws_IOException() { - string uniqueServerName = GetUniquePipeName(); + string uniqueServerName = PipeStreamConformanceTests.GetUniquePipeName(); using (NamedPipeServerStream server = new NamedPipeServerStream(uniqueServerName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) { Assert.Throws(() => new NamedPipeServerStream(uniqueServerName)); @@ -242,7 +242,7 @@ public static void ServerCountOverMaxServerInstances_Throws_IOException() [PlatformSpecific(TestPlatforms.Windows)] // NumberOfServerInstances > 1 isn't supported and has undefined behavior on Unix public static void Windows_ServerCloneWithDifferentDirection_Throws_UnauthorizedAccessException() { - string uniqueServerName = GetUniquePipeName(); + string uniqueServerName = PipeStreamConformanceTests.GetUniquePipeName(); using (NamedPipeServerStream server = new NamedPipeServerStream(uniqueServerName, PipeDirection.In, 2, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) { Assert.Throws(() => new NamedPipeServerStream(uniqueServerName, PipeDirection.Out)); diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CrossProcess.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CrossProcess.cs index 00258edd9bc333..83e52342abd5b4 100644 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CrossProcess.cs +++ b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CrossProcess.cs @@ -16,7 +16,7 @@ public sealed class NamedPipeTest_CrossProcess [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void InheritHandles_AvailableInChildProcess() { - string pipeName = GetUniquePipeName(); + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); using (var server = new NamedPipeServerStream(pipeName, PipeDirection.In)) using (var client = new NamedPipeClientStream(".", pipeName, PipeDirection.Out, PipeOptions.None, TokenImpersonationLevel.None, HandleInheritability.Inheritable)) @@ -48,8 +48,8 @@ void ChildFunc(string handle) public void PingPong_Sync() { // Create names for two pipes - string outName = GetUniquePipeName(); - string inName = GetUniquePipeName(); + string outName = PipeStreamConformanceTests.GetUniquePipeName(); + string inName = PipeStreamConformanceTests.GetUniquePipeName(); // Create the two named pipes, one for each direction, then create // another process with which to communicate @@ -74,8 +74,8 @@ public void PingPong_Sync() public async Task PingPong_Async() { // Create names for two pipes - string outName = GetUniquePipeName(); - string inName = GetUniquePipeName(); + string outName = PipeStreamConformanceTests.GetUniquePipeName(); + string inName = PipeStreamConformanceTests.GetUniquePipeName(); // Create the two named pipes, one for each direction, then create // another process with which to communicate @@ -117,15 +117,5 @@ private static void PingPong_OtherProcess(string inName, string outName) } } } - - private static string GetUniquePipeName() - { - if (PlatformDetection.IsInAppContainer) - { - return @"LOCAL\" + Path.GetRandomFileName(); - } - return Path.GetRandomFileName(); - } - } } diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.Windows.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.Windows.cs index 9e81abd8a38ff0..08c45dee85cef0 100644 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.Windows.cs +++ b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.Windows.cs @@ -121,7 +121,7 @@ public void RunImpersonated(Action action) /// /// Negative tests for PipeOptions.CurrentUserOnly in Windows. /// - public class NamedPipeTest_CurrentUserOnly_Windows : NamedPipeTestBase, IClassFixture + public class NamedPipeTest_CurrentUserOnly_Windows : IClassFixture { public static bool IsAdminOnSupportedWindowsVersions => PlatformDetection.IsWindowsAndElevated && !PlatformDetection.IsWindows7 @@ -144,7 +144,7 @@ public NamedPipeTest_CurrentUserOnly_Windows(TestAccountImpersonator testAccount public void Connection_UnderDifferentUsers_BehavesAsExpected( PipeOptions serverPipeOptions, PipeOptions clientPipeOptions) { - string name = GetUniquePipeName(); + string name = PipeStreamConformanceTests.GetUniquePipeName(); using (var cts = new CancellationTokenSource()) using (var server = new NamedPipeServerStream(name, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, serverPipeOptions | PipeOptions.Asynchronous)) { @@ -169,7 +169,7 @@ public void Connection_UnderDifferentUsers_BehavesAsExpected( [ConditionalFact(nameof(IsAdminOnSupportedWindowsVersions))] public void Allow_Connection_UnderDifferentUsers_ForClientReading() { - string name = GetUniquePipeName(); + string name = PipeStreamConformanceTests.GetUniquePipeName(); using (var server = new NamedPipeServerStream( name, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) { diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.cs index 88de18920f76b7..5667e74c94b036 100644 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.cs +++ b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.cs @@ -10,26 +10,26 @@ namespace System.IO.Pipes.Tests /// /// Tests for the constructors for NamedPipeClientStream /// - public class NamedPipeTest_CurrentUserOnly : NamedPipeTestBase + public class NamedPipeTest_CurrentUserOnly { [Fact] public static void CreateClient_CurrentUserOnly() { // Should not throw. - new NamedPipeClientStream(".", GetUniquePipeName(), PipeDirection.InOut, PipeOptions.CurrentUserOnly).Dispose(); + new NamedPipeClientStream(".", PipeStreamConformanceTests.GetUniquePipeName(), PipeDirection.InOut, PipeOptions.CurrentUserOnly).Dispose(); } [Fact] public static void CreateServer_CurrentUserOnly() { // Should not throw. - new NamedPipeServerStream(GetUniquePipeName(), PipeDirection.InOut, 2, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly).Dispose(); + new NamedPipeServerStream(PipeStreamConformanceTests.GetUniquePipeName(), PipeDirection.InOut, 2, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly).Dispose(); } [Fact] public static void CreateServer_ConnectClient() { - string name = GetUniquePipeName(); + string name = PipeStreamConformanceTests.GetUniquePipeName(); using (var server = new NamedPipeServerStream(name, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly)) { using (var client = new NamedPipeClientStream(".", name, PipeDirection.InOut, PipeOptions.CurrentUserOnly)) @@ -43,7 +43,7 @@ public static void CreateServer_ConnectClient() [Fact] public static void CreateServer_ConnectClient_UsingUnixAbsolutePath() { - string name = Path.Combine("/tmp", GetUniquePipeName()); + string name = Path.Combine("/tmp", PipeStreamConformanceTests.GetUniquePipeName()); using (var server = new NamedPipeServerStream(name, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly)) { using (var client = new NamedPipeClientStream(".", name, PipeDirection.InOut, PipeOptions.CurrentUserOnly)) @@ -58,7 +58,7 @@ public static void CreateServer_ConnectClient_UsingUnixAbsolutePath() [InlineData(PipeOptions.CurrentUserOnly, PipeOptions.None)] public static void Connection_UnderSameUser_SingleSide_CurrentUserOnly_Works(PipeOptions serverPipeOptions, PipeOptions clientPipeOptions) { - string name = GetUniquePipeName(); + string name = PipeStreamConformanceTests.GetUniquePipeName(); using (var server = new NamedPipeServerStream(name, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, serverPipeOptions)) using (var client = new NamedPipeClientStream(".", name, PipeDirection.InOut, clientPipeOptions)) { @@ -75,9 +75,9 @@ public static void Connection_UnderSameUser_SingleSide_CurrentUserOnly_Works(Pip [Fact] public static void CreateMultipleServers_ConnectMultipleClients() { - string name1 = GetUniquePipeName(); - string name2 = GetUniquePipeName(); - string name3 = GetUniquePipeName(); + string name1 = PipeStreamConformanceTests.GetUniquePipeName(); + string name2 = PipeStreamConformanceTests.GetUniquePipeName(); + string name3 = PipeStreamConformanceTests.GetUniquePipeName(); using (var server1 = new NamedPipeServerStream(name1, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly)) using (var server2 = new NamedPipeServerStream(name2, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly)) using (var server3 = new NamedPipeServerStream(name3, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly)) @@ -101,7 +101,7 @@ public static void CreateMultipleServers_ConnectMultipleClients_MultipleThreads( { tasks.Add(Task.Run(() => { - var name = GetUniquePipeName(); + var name = PipeStreamConformanceTests.GetUniquePipeName(); using (var server = new NamedPipeServerStream(name, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly)) { using (var client = new NamedPipeClientStream(".", name, PipeDirection.InOut, PipeOptions.CurrentUserOnly)) @@ -126,7 +126,7 @@ public static void CreateMultipleConcurrentServers_ConnectMultipleClients(PipeOp try { - string pipeName = GetUniquePipeName(); + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); for (var i = 0; i < pipeServers.Length; i++) { pipeServers[i] = new NamedPipeServerStream( diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Read.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Read.cs deleted file mode 100644 index d91b7490ee8471..00000000000000 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Read.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Pipes.Tests -{ - public class NamedPipeTest_Read_ServerOut_ClientIn : PipeTest_Read - { - protected override ServerClientPair CreateServerClientPair() - { - ServerClientPair ret = new ServerClientPair(); - string pipeName = GetUniquePipeName(); - var writeablePipe = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); - var readablePipe = new NamedPipeClientStream(".", pipeName, PipeDirection.In, PipeOptions.Asynchronous); - - Task clientConnect = readablePipe.ConnectAsync(); - writeablePipe.WaitForConnection(); - clientConnect.Wait(); - - ret.readablePipe = readablePipe; - ret.writeablePipe = writeablePipe; - return ret; - } - } - - public class NamedPipeTest_Read_ServerIn_ClientOut : PipeTest_Read - { - protected override ServerClientPair CreateServerClientPair() - { - ServerClientPair ret = new ServerClientPair(); - string pipeName = GetUniquePipeName(); - var readablePipe = new NamedPipeServerStream(pipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); - var writeablePipe = new NamedPipeClientStream(".", pipeName, PipeDirection.Out, PipeOptions.Asynchronous); - - Task clientConnect = writeablePipe.ConnectAsync(); - readablePipe.WaitForConnection(); - clientConnect.Wait(); - - ret.readablePipe = readablePipe; - ret.writeablePipe = writeablePipe; - return ret; - } - } - - public class NamedPipeTest_Read_ServerInOut_ClientInOut : PipeTest_Read - { - protected override ServerClientPair CreateServerClientPair() - { - ServerClientPair ret = new ServerClientPair(); - string pipeName = GetUniquePipeName(); - var readablePipe = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); - var writeablePipe = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); - - Task clientConnect = writeablePipe.ConnectAsync(); - readablePipe.WaitForConnection(); - clientConnect.Wait(); - - ret.readablePipe = readablePipe; - ret.writeablePipe = writeablePipe; - return ret; - } - - public override bool SupportsBidirectionalReadingWriting => true; - } - - public class NamedPipeTest_Read_ServerInOut_ClientInOut_APMWaitForConnection : PipeTest_Read - { - protected override ServerClientPair CreateServerClientPair() - { - ServerClientPair ret = new ServerClientPair(); - string pipeName = GetUniquePipeName(); - var readablePipe = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); - var writeablePipe = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); - - Task serverConnect = Task.Factory.FromAsync(readablePipe.BeginWaitForConnection, readablePipe.EndWaitForConnection, null); - writeablePipe.Connect(); - serverConnect.Wait(); - - ret.readablePipe = readablePipe; - ret.writeablePipe = writeablePipe; - return ret; - } - - public override bool SupportsBidirectionalReadingWriting => true; - } -} diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.RunAsClient.Windows.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.RunAsClient.Windows.cs index 5dcd33bb89a847..d24988d7623751 100644 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.RunAsClient.Windows.cs +++ b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.RunAsClient.Windows.cs @@ -21,7 +21,7 @@ public partial class NamedPipeTest_RunAsClient [PlatformSpecific(TestPlatforms.Windows)] // Uses P/Invokes public async Task RunAsClient_Windows(TokenImpersonationLevel tokenImpersonationLevel) { - string pipeName = GetUniquePipeName(); + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); using (var server = new NamedPipeServerStream(pipeName)) using (var client = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.None, tokenImpersonationLevel)) { diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Simple.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Simple.cs deleted file mode 100644 index d89887dbe0a029..00000000000000 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Simple.cs +++ /dev/null @@ -1,831 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Pipes.Tests -{ - /// - /// The Simple NamedPipe tests cover potentially every-day scenarios that are shared - /// by all NamedPipes whether they be Server/Client or In/Out/Inout. - /// - public abstract class NamedPipeTest_Simple : NamedPipeTestBase - { - /// - /// Yields every combination of testing options for the OneWayReadWrites test - /// - /// - public static IEnumerable OneWayReadWritesMemberData() - { - var options = new[] { PipeOptions.None, PipeOptions.Asynchronous }; - var bools = new[] { false, true }; - foreach (PipeOptions serverOption in options) - foreach (PipeOptions clientOption in options) - foreach (bool asyncServerOps in bools) - foreach (bool asyncClientOps in bools) - yield return new object[] { serverOption, clientOption, asyncServerOps, asyncClientOps }; - } - - [Theory] - [MemberData(nameof(OneWayReadWritesMemberData))] - public async Task OneWayReadWrites(PipeOptions serverOptions, PipeOptions clientOptions, bool asyncServerOps, bool asyncClientOps) - { - using (NamedPipePair pair = CreateNamedPipePair(serverOptions, clientOptions)) - { - NamedPipeClientStream client = pair.clientStream; - NamedPipeServerStream server = pair.serverStream; - byte[] received = new byte[] { 0 }; - Task clientTask = Task.Run(async () => - { - if (asyncClientOps) - { - await client.ConnectAsync(); - if (pair.writeToServer) - { - received = await ReadBytesAsync(client, sendBytes.Length); - } - else - { - await WriteBytesAsync(client, sendBytes); - } - } - else - { - client.Connect(); - if (pair.writeToServer) - { - received = ReadBytes(client, sendBytes.Length); - } - else - { - WriteBytes(client, sendBytes); - } - } - }); - if (asyncServerOps) - { - await server.WaitForConnectionAsync(); - if (pair.writeToServer) - { - await WriteBytesAsync(server, sendBytes); - } - else - { - received = await ReadBytesAsync(server, sendBytes.Length); - } - } - else - { - server.WaitForConnection(); - if (pair.writeToServer) - { - WriteBytes(server, sendBytes); - } - else - { - received = ReadBytes(server, sendBytes.Length); - } - } - - await clientTask; - Assert.Equal(sendBytes, received); - - server.Disconnect(); - Assert.False(server.IsConnected); - } - } - - [Fact] - public async Task ClonedServer_ActsAsOriginalServer() - { - byte[] msg1 = new byte[] { 5, 7, 9, 10 }; - byte[] received1 = new byte[] { 0, 0, 0, 0 }; - - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeServerStream serverBase = pair.serverStream; - NamedPipeClientStream client = pair.clientStream; - pair.Connect(); - - if (pair.writeToServer) - { - Task clientTask = client.ReadAsync(received1, 0, received1.Length); - using (NamedPipeServerStream server = new NamedPipeServerStream(PipeDirection.Out, false, true, serverBase.SafePipeHandle)) - { - if (OperatingSystem.IsWindows()) - { - Assert.Equal(1, client.NumberOfServerInstances); - } - server.Write(msg1, 0, msg1.Length); - int receivedLength = await clientTask; - Assert.Equal(msg1.Length, receivedLength); - Assert.Equal(msg1, received1); - } - } - else - { - Task clientTask = client.WriteAsync(msg1, 0, msg1.Length); - using (NamedPipeServerStream server = new NamedPipeServerStream(PipeDirection.In, false, true, serverBase.SafePipeHandle)) - { - int receivedLength = server.Read(received1, 0, msg1.Length); - Assert.Equal(msg1.Length, receivedLength); - Assert.Equal(msg1, received1); - await clientTask; - } - } - } - } - - [Fact] - public async Task ClonedClient_ActsAsOriginalClient() - { - byte[] msg1 = new byte[] { 5, 7, 9, 10 }; - byte[] received1 = new byte[] { 0, 0, 0, 0 }; - - using (NamedPipePair pair = CreateNamedPipePair()) - { - pair.Connect(); - NamedPipeServerStream server = pair.serverStream; - if (pair.writeToServer) - { - using (NamedPipeClientStream client = new NamedPipeClientStream(PipeDirection.In, false, true, pair.clientStream.SafePipeHandle)) - { - if (OperatingSystem.IsWindows()) - { - Assert.Equal(1, client.NumberOfServerInstances); - } - Task clientTask = client.ReadAsync(received1, 0, received1.Length); - server.Write(msg1, 0, msg1.Length); - int receivedLength = await clientTask; - Assert.Equal(msg1.Length, receivedLength); - Assert.Equal(msg1, received1); - } - } - else - { - using (NamedPipeClientStream client = new NamedPipeClientStream(PipeDirection.Out, false, true, pair.clientStream.SafePipeHandle)) - { - Task clientTask = client.WriteAsync(msg1, 0, msg1.Length); - int receivedLength = server.Read(received1, 0, msg1.Length); - Assert.Equal(msg1.Length, receivedLength); - Assert.Equal(msg1, received1); - await clientTask; - } - } - } - } - - [Fact] - public void ConnectOnAlreadyConnectedClient_Throws_InvalidOperationException() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeServerStream server = pair.serverStream; - NamedPipeClientStream client = pair.clientStream; - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - pair.Connect(); - - Assert.True(client.IsConnected); - Assert.True(server.IsConnected); - - Assert.Throws(() => client.Connect()); - } - } - - [Fact] - public void WaitForConnectionOnAlreadyConnectedServer_Throws_InvalidOperationException() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeServerStream server = pair.serverStream; - NamedPipeClientStream client = pair.clientStream; - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - pair.Connect(); - - Assert.True(client.IsConnected); - Assert.True(server.IsConnected); - - Assert.Throws(() => server.WaitForConnection()); - } - } - - [Fact] - public async Task CancelTokenOn_ServerWaitForConnectionAsync_Throws_OperationCanceledException() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeServerStream server = pair.serverStream; - var ctx = new CancellationTokenSource(); - - if (OperatingSystem.IsWindows()) // cancellation token after the operation has been initiated - { - Task serverWaitTimeout = server.WaitForConnectionAsync(ctx.Token); - ctx.Cancel(); - await Assert.ThrowsAnyAsync(() => serverWaitTimeout); - } - - ctx.Cancel(); - Assert.True(server.WaitForConnectionAsync(ctx.Token).IsCanceled); - } - } - - [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // P/Invoking to Win32 functions - public async Task CancelTokenOff_ServerWaitForConnectionAsyncWithOuterCancellation_Throws_OperationCanceledException() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeServerStream server = pair.serverStream; - Task waitForConnectionTask = server.WaitForConnectionAsync(CancellationToken.None); - - Assert.True(InteropTest.CancelIoEx(server.SafePipeHandle), "Outer cancellation failed"); - await Assert.ThrowsAnyAsync(() => waitForConnectionTask); - Assert.True(waitForConnectionTask.IsCanceled); - } - } - - [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // P/Invoking to Win32 functions - public async Task CancelTokenOn_ServerWaitForConnectionAsyncWithOuterCancellation_Throws_IOException() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - var cts = new CancellationTokenSource(); - NamedPipeServerStream server = pair.serverStream; - Task waitForConnectionTask = server.WaitForConnectionAsync(cts.Token); - - Assert.True(InteropTest.CancelIoEx(server.SafePipeHandle), "Outer cancellation failed"); - await Assert.ThrowsAsync(() => waitForConnectionTask); - } - } - - [Fact] - public async Task OperationsOnDisconnectedServer() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeServerStream server = pair.serverStream; - pair.Connect(); - - Assert.Throws(() => server.IsMessageComplete); - Assert.Throws(() => server.WaitForConnection()); - await Assert.ThrowsAsync(() => server.WaitForConnectionAsync()); // fails because allowed connections is set to 1 - - server.Disconnect(); - - Assert.Throws(() => server.Disconnect()); // double disconnect - - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - if (pair.writeToServer) - { - Assert.Throws(() => server.Write(buffer, 0, buffer.Length)); - Assert.Throws(() => server.WriteByte(5)); - Assert.Throws(() => { server.WriteAsync(buffer, 0, buffer.Length); }); - } - else - { - Assert.Throws(() => server.Read(buffer, 0, buffer.Length)); - Assert.Throws(() => server.ReadByte()); - Assert.Throws(() => { server.ReadAsync(buffer, 0, buffer.Length); }); - } - - Assert.Throws(() => server.Flush()); - Assert.Throws(() => server.IsMessageComplete); - Assert.Throws(() => server.GetImpersonationUserName()); - } - } - - [Fact] - public virtual async Task OperationsOnDisconnectedClient() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeServerStream server = pair.serverStream; - NamedPipeClientStream client = pair.clientStream; - pair.Connect(); - - Assert.Throws(() => client.IsMessageComplete); - Assert.Throws(() => client.Connect()); - await Assert.ThrowsAsync(() => client.ConnectAsync()); - - server.Disconnect(); - - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - if (!pair.writeToServer) - { - if (OperatingSystem.IsWindows()) // writes on Unix may still succeed after other end disconnects, due to socket being used - { - // Pipe is broken - Assert.Throws(() => client.Write(buffer, 0, buffer.Length)); - Assert.Throws(() => client.WriteByte(5)); - Assert.Throws(() => { client.WriteAsync(buffer, 0, buffer.Length); }); - Assert.Throws(() => client.Flush()); - Assert.Throws(() => client.NumberOfServerInstances); - } - } - else - { - // Nothing for the client to read, but no exception throwing - Assert.Equal(0, client.Read(buffer, 0, buffer.Length)); - Assert.Equal(-1, client.ReadByte()); - - if (!OperatingSystem.IsWindows()) // NumberOfServerInstances not supported on Unix - { - Assert.Throws(() => client.NumberOfServerInstances); - } - } - - Assert.Throws(() => client.IsMessageComplete); - } - } - - [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Unix implemented on sockets, where disposal information doesn't propagate - public async Task Windows_OperationsOnNamedServerWithDisposedClient() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeServerStream server = pair.serverStream; - pair.Connect(); - pair.clientStream.Dispose(); - - Assert.Throws(() => server.WaitForConnection()); - await Assert.ThrowsAsync(() => server.WaitForConnectionAsync()); - Assert.Throws(() => server.GetImpersonationUserName()); - } - } - - [Fact] - [PlatformSpecific(TestPlatforms.AnyUnix)] // Unix implemented on sockets, where disposal information doesn't propagate - public async Task Unix_OperationsOnNamedServerWithDisposedClient() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeServerStream server = pair.serverStream; - pair.Connect(); - pair.clientStream.Dispose(); - - // On Unix, the server still thinks that it is connected after client Disposal. - Assert.Throws(() => server.WaitForConnection()); - await Assert.ThrowsAsync(() => server.WaitForConnectionAsync()); - Assert.NotNull(server.GetImpersonationUserName()); - } - } - - [Fact] - public void OperationsOnUnconnectedServer() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeServerStream server = pair.serverStream; - - // doesn't throw exceptions - PipeTransmissionMode transmitMode = server.TransmissionMode; - Assert.Throws(() => server.ReadMode = (PipeTransmissionMode)999); - - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - if (pair.writeToServer) - { - Assert.Equal(0, server.OutBufferSize); - Assert.Throws(() => server.Write(buffer, 0, buffer.Length)); - Assert.Throws(() => server.WriteByte(5)); - Assert.Throws(() => { server.WriteAsync(buffer, 0, buffer.Length); }); - } - else - { - Assert.Equal(0, server.InBufferSize); - PipeTransmissionMode readMode = server.ReadMode; - Assert.Throws(() => server.Read(buffer, 0, buffer.Length)); - Assert.Throws(() => server.ReadByte()); - Assert.Throws(() => { server.ReadAsync(buffer, 0, buffer.Length); }); - } - - Assert.Throws(() => server.Disconnect()); // disconnect when not connected - Assert.Throws(() => server.IsMessageComplete); - } - } - - [Fact] - public void OperationsOnUnconnectedClient() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeClientStream client = pair.clientStream; - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - if (client.CanRead) - { - Assert.Throws(() => client.Read(buffer, 0, buffer.Length)); - Assert.Throws(() => client.ReadByte()); - Assert.Throws(() => { client.ReadAsync(buffer, 0, buffer.Length); }); - Assert.Throws(() => client.ReadMode); - Assert.Throws(() => client.ReadMode = PipeTransmissionMode.Byte); - } - if (client.CanWrite) - { - Assert.Throws(() => client.Write(buffer, 0, buffer.Length)); - Assert.Throws(() => client.WriteByte(5)); - Assert.Throws(() => { client.WriteAsync(buffer, 0, buffer.Length); }); - } - - Assert.Throws(() => client.NumberOfServerInstances); - Assert.Throws(() => client.TransmissionMode); - Assert.Throws(() => client.InBufferSize); - Assert.Throws(() => client.OutBufferSize); - Assert.Throws(() => client.SafePipeHandle); - } - } - - [Fact] - public async Task DisposedServerPipe_Throws_ObjectDisposedException() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeServerStream pipe = pair.serverStream; - pipe.Dispose(); - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - Assert.Throws(() => pipe.Disconnect()); - Assert.Throws(() => pipe.GetImpersonationUserName()); - Assert.Throws(() => pipe.WaitForConnection()); - await Assert.ThrowsAsync(() => pipe.WaitForConnectionAsync()); - } - } - - [Fact] - public async Task DisposedClientPipe_Throws_ObjectDisposedException() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - pair.Connect(); - NamedPipeClientStream pipe = pair.clientStream; - pipe.Dispose(); - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - Assert.Throws(() => pipe.Connect()); - await Assert.ThrowsAsync(() => pipe.ConnectAsync()); - Assert.Throws(() => pipe.NumberOfServerInstances); - } - } - - [Fact] - public async Task ReadAsync_DisconnectDuringRead_Returns0() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - pair.Connect(); - Task readTask; - if (pair.clientStream.CanRead) - { - readTask = pair.clientStream.ReadAsync(new byte[1], 0, 1); - pair.serverStream.Dispose(); - } - else - { - readTask = pair.serverStream.ReadAsync(new byte[1], 0, 1); - pair.clientStream.Dispose(); - } - Assert.Equal(0, await readTask); - } - } - - [PlatformSpecific(TestPlatforms.Windows)] // Unix named pipes are on sockets, where small writes with an empty buffer will succeed immediately - [Fact] - public async Task WriteAsync_DisconnectDuringWrite_Throws() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - pair.Connect(); - Task writeTask; - if (pair.clientStream.CanWrite) - { - writeTask = pair.clientStream.WriteAsync(new byte[1], 0, 1); - pair.serverStream.Dispose(); - } - else - { - writeTask = pair.serverStream.WriteAsync(new byte[1], 0, 1); - pair.clientStream.Dispose(); - } - await Assert.ThrowsAsync(() => writeTask); - } - } - - [Fact] - public async Task Server_ReadWriteCancelledToken_Throws_OperationCanceledException() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeServerStream server = pair.serverStream; - NamedPipeClientStream client = pair.clientStream; - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - pair.Connect(); - - if (server.CanRead && client.CanWrite) - { - var ctx1 = new CancellationTokenSource(); - - Task serverReadToken = server.ReadAsync(buffer, 0, buffer.Length, ctx1.Token); - ctx1.Cancel(); - await Assert.ThrowsAnyAsync(() => serverReadToken); - - ctx1.Cancel(); - Assert.True(server.ReadAsync(buffer, 0, buffer.Length, ctx1.Token).IsCanceled); - } - - if (server.CanWrite) - { - var ctx1 = new CancellationTokenSource(); - if (OperatingSystem.IsWindows()) // On Unix WriteAsync's aren't cancelable once initiated - { - Task serverWriteToken = server.WriteAsync(buffer, 0, buffer.Length, ctx1.Token); - ctx1.Cancel(); - await Assert.ThrowsAnyAsync(() => serverWriteToken); - } - ctx1.Cancel(); - Assert.True(server.WriteAsync(buffer, 0, buffer.Length, ctx1.Token).IsCanceled); - } - } - } - - [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // P/Invoking to Win32 functions - public async Task CancelTokenOff_Server_ReadWriteCancelledToken_Throws_OperationCanceledException() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeServerStream server = pair.serverStream; - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - pair.Connect(); - - if (server.CanRead) - { - Task serverReadToken = server.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None); - - Assert.True(InteropTest.CancelIoEx(server.SafePipeHandle), "Outer cancellation failed"); - await Assert.ThrowsAnyAsync(() => serverReadToken); - Assert.True(serverReadToken.IsCanceled); - } - if (server.CanWrite) - { - Task serverWriteToken = server.WriteAsync(buffer, 0, buffer.Length, CancellationToken.None); - - Assert.True(InteropTest.CancelIoEx(server.SafePipeHandle), "Outer cancellation failed"); - await Assert.ThrowsAnyAsync(() => serverWriteToken); - Assert.True(serverWriteToken.IsCanceled); - } - } - } - - [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // P/Invoking to Win32 functions - public async Task CancelTokenOn_Server_ReadWriteCancelledToken_Throws_OperationCanceledException() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeServerStream server = pair.serverStream; - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - pair.Connect(); - - if (server.CanRead) - { - var cts = new CancellationTokenSource(); - Task serverReadToken = server.ReadAsync(buffer, 0, buffer.Length, cts.Token); - - Assert.True(InteropTest.CancelIoEx(server.SafePipeHandle), "Outer cancellation failed"); - await Assert.ThrowsAnyAsync(() => serverReadToken); - } - if (server.CanWrite) - { - var cts = new CancellationTokenSource(); - Task serverWriteToken = server.WriteAsync(buffer, 0, buffer.Length, cts.Token); - - Assert.True(InteropTest.CancelIoEx(server.SafePipeHandle), "Outer cancellation failed"); - await Assert.ThrowsAnyAsync(() => serverWriteToken); - } - } - } - - [Fact] - public async Task Client_ReadWriteCancelledToken_Throws_OperationCanceledException() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeClientStream client = pair.clientStream; - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - pair.Connect(); - - if (client.CanRead) - { - var ctx1 = new CancellationTokenSource(); - - Task serverReadToken = client.ReadAsync(buffer, 0, buffer.Length, ctx1.Token); - ctx1.Cancel(); - await Assert.ThrowsAnyAsync(() => serverReadToken); - - Assert.True(client.ReadAsync(buffer, 0, buffer.Length, ctx1.Token).IsCanceled); - } - - if (client.CanWrite) - { - var ctx1 = new CancellationTokenSource(); - if (OperatingSystem.IsWindows()) // On Unix WriteAsync's aren't cancelable once initiated - { - Task serverWriteToken = client.WriteAsync(buffer, 0, buffer.Length, ctx1.Token); - ctx1.Cancel(); - await Assert.ThrowsAnyAsync(() => serverWriteToken); - } - ctx1.Cancel(); - Assert.True(client.WriteAsync(buffer, 0, buffer.Length, ctx1.Token).IsCanceled); - } - } - } - - [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // P/Invoking to Win32 functions - public async Task CancelTokenOff_Client_ReadWriteCancelledToken_Throws_OperationCanceledException() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeClientStream client = pair.clientStream; - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - pair.Connect(); - if (client.CanRead) - { - Task clientReadToken = client.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None); - - Assert.True(InteropTest.CancelIoEx(client.SafePipeHandle), "Outer cancellation failed"); - await Assert.ThrowsAnyAsync(() => clientReadToken); - Assert.True(clientReadToken.IsCanceled); - } - if (client.CanWrite) - { - Task clientWriteToken = client.WriteAsync(buffer, 0, buffer.Length, CancellationToken.None); - - Assert.True(InteropTest.CancelIoEx(client.SafePipeHandle), "Outer cancellation failed"); - await Assert.ThrowsAnyAsync(() => clientWriteToken); - Assert.True(clientWriteToken.IsCanceled); - } - } - } - - [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // P/Invoking to Win32 functions - public async Task CancelTokenOn_Client_ReadWriteCancelledToken_Throws_OperationCanceledException() - { - using (NamedPipePair pair = CreateNamedPipePair()) - { - NamedPipeClientStream client = pair.clientStream; - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - pair.Connect(); - if (client.CanRead) - { - var cts = new CancellationTokenSource(); - Task clientReadToken = client.ReadAsync(buffer, 0, buffer.Length, cts.Token); - - Assert.True(InteropTest.CancelIoEx(client.SafePipeHandle), "Outer cancellation failed"); - await Assert.ThrowsAnyAsync(() => clientReadToken); - } - if (client.CanWrite) - { - var cts = new CancellationTokenSource(); - Task clientWriteToken = client.WriteAsync(buffer, 0, buffer.Length, cts.Token); - - Assert.True(InteropTest.CancelIoEx(client.SafePipeHandle), "Outer cancellation failed"); - await Assert.ThrowsAnyAsync(() => clientWriteToken); - } - } - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task ManyConcurrentOperations(bool cancelable) - { - using (NamedPipePair pair = CreateNamedPipePair(PipeOptions.Asynchronous, PipeOptions.Asynchronous)) - { - await Task.WhenAll(pair.serverStream.WaitForConnectionAsync(), pair.clientStream.ConnectAsync()); - - const int NumOps = 100; - const int DataPerOp = 512; - byte[] sendingData = new byte[NumOps * DataPerOp]; - byte[] readingData = new byte[sendingData.Length]; - new Random().NextBytes(sendingData); - var cancellationToken = cancelable ? new CancellationTokenSource().Token : CancellationToken.None; - - Stream reader = pair.writeToServer ? (Stream)pair.clientStream : pair.serverStream; - Stream writer = pair.writeToServer ? (Stream)pair.serverStream : pair.clientStream; - - var reads = new Task[NumOps]; - var writes = new Task[NumOps]; - - for (int i = 0; i < reads.Length; i++) - reads[i] = reader.ReadAsync(readingData, i * DataPerOp, DataPerOp, cancellationToken); - for (int i = 0; i < reads.Length; i++) - writes[i] = writer.WriteAsync(sendingData, i * DataPerOp, DataPerOp, cancellationToken); - - const int WaitTimeout = 30000; - Assert.True(Task.WaitAll(writes, WaitTimeout)); - Assert.True(Task.WaitAll(reads, WaitTimeout)); - - // The data of each write may not be written atomically, and as such some of the data may be - // interleaved rather than entirely in the order written. - Assert.Equal(sendingData.OrderBy(b => b), readingData.OrderBy(b => b)); - } - } - } - - public class NamedPipeTest_Simple_ServerInOutRead_ClientInOutWrite : NamedPipeTest_Simple - { - protected override NamedPipePair CreateNamedPipePair(PipeOptions serverOptions, PipeOptions clientOptions) - { - NamedPipePair ret = new NamedPipePair(); - string pipeName = GetUniquePipeName(); - ret.serverStream = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, serverOptions); - ret.clientStream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, clientOptions); - ret.writeToServer = false; - return ret; - } - } - - public class NamedPipeTest_Simple_ServerInOutWrite_ClientInOutRead : NamedPipeTest_Simple - { - protected override NamedPipePair CreateNamedPipePair(PipeOptions serverOptions, PipeOptions clientOptions) - { - NamedPipePair ret = new NamedPipePair(); - string pipeName = GetUniquePipeName(); - ret.serverStream = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, serverOptions); - ret.clientStream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, clientOptions); - ret.writeToServer = true; - return ret; - } - } - - public class NamedPipeTest_Simple_ServerInOut_ClientIn : NamedPipeTest_Simple - { - protected override NamedPipePair CreateNamedPipePair(PipeOptions serverOptions, PipeOptions clientOptions) - { - NamedPipePair ret = new NamedPipePair(); - string pipeName = GetUniquePipeName(); - ret.serverStream = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, serverOptions); - ret.clientStream = new NamedPipeClientStream(".", pipeName, PipeDirection.In, clientOptions); - ret.writeToServer = true; - return ret; - } - } - - public class NamedPipeTest_Simple_ServerInOut_ClientOut : NamedPipeTest_Simple - { - protected override NamedPipePair CreateNamedPipePair(PipeOptions serverOptions, PipeOptions clientOptions) - { - NamedPipePair ret = new NamedPipePair(); - string pipeName = GetUniquePipeName(); - ret.serverStream = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, serverOptions); - ret.clientStream = new NamedPipeClientStream(".", pipeName, PipeDirection.Out, clientOptions); - ret.writeToServer = false; - return ret; - } - } - - public class NamedPipeTest_Simple_ServerOut_ClientIn : NamedPipeTest_Simple - { - protected override NamedPipePair CreateNamedPipePair(PipeOptions serverOptions, PipeOptions clientOptions) - { - NamedPipePair ret = new NamedPipePair(); - string pipeName = GetUniquePipeName(); - ret.serverStream = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, serverOptions); - ret.clientStream = new NamedPipeClientStream(".", pipeName, PipeDirection.In, clientOptions); - ret.writeToServer = true; - return ret; - } - } - - public class NamedPipeTest_Simple_ServerIn_ClientOut : NamedPipeTest_Simple - { - protected override NamedPipePair CreateNamedPipePair(PipeOptions serverOptions, PipeOptions clientOptions) - { - NamedPipePair ret = new NamedPipePair(); - string pipeName = GetUniquePipeName(); - ret.serverStream = new NamedPipeServerStream(pipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, serverOptions); - ret.clientStream = new NamedPipeClientStream(".", pipeName, PipeDirection.Out, clientOptions); - ret.writeToServer = false; - return ret; - } - } - -} diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Specific.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Specific.cs index ff2644d3a34e4d..220be9d65ca8a8 100644 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Specific.cs +++ b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Specific.cs @@ -14,7 +14,7 @@ namespace System.IO.Pipes.Tests /// The Specific NamedPipe tests cover edge cases or otherwise narrow cases that /// show up within particular server/client directional combinations. /// - public class NamedPipeTest_Specific : NamedPipeTestBase + public class NamedPipeTest_Specific { [Fact] public void InvalidConnectTimeout_Throws_ArgumentOutOfRangeException() @@ -58,7 +58,7 @@ public async Task CancelConnectToNonExistentServer_Throws_OperationCanceledExcep [PlatformSpecific(TestPlatforms.Windows)] // Unix implementation uses bidirectional sockets public void ConnectWithConflictingDirections_Throws_UnauthorizedAccessException() { - string serverName1 = GetUniquePipeName(); + string serverName1 = PipeStreamConformanceTests.GetUniquePipeName(); using (NamedPipeServerStream server = new NamedPipeServerStream(serverName1, PipeDirection.Out)) using (NamedPipeClientStream client = new NamedPipeClientStream(".", serverName1, PipeDirection.Out)) { @@ -66,7 +66,7 @@ public void ConnectWithConflictingDirections_Throws_UnauthorizedAccessException( Assert.False(client.IsConnected); } - string serverName2 = GetUniquePipeName(); + string serverName2 = PipeStreamConformanceTests.GetUniquePipeName(); using (NamedPipeServerStream server = new NamedPipeServerStream(serverName2, PipeDirection.In)) using (NamedPipeClientStream client = new NamedPipeClientStream(".", serverName2, PipeDirection.In)) { @@ -80,7 +80,7 @@ public void ConnectWithConflictingDirections_Throws_UnauthorizedAccessException( [InlineData(3)] public async Task MultipleWaitingClients_ServerServesOneAtATime(int numClients) { - string name = GetUniquePipeName(); + string name = PipeStreamConformanceTests.GetUniquePipeName(); using (NamedPipeServerStream server = new NamedPipeServerStream(name)) { var clients = new List(from i in Enumerable.Range(0, numClients) select ConnectClientAndReadAsync()); @@ -115,7 +115,7 @@ async Task ConnectClientAndReadAsync() [Fact] public void MaxNumberOfServerInstances_TooManyServers_Throws() { - string name = GetUniquePipeName(); + string name = PipeStreamConformanceTests.GetUniquePipeName(); using (new NamedPipeServerStream(name, PipeDirection.InOut, 1)) { @@ -152,7 +152,7 @@ public void MaxNumberOfServerInstances_TooManyServers_Throws() [InlineData(4)] public async Task MultipleServers_ServeMultipleClientsConcurrently(int numServers) { - string name = GetUniquePipeName(); + string name = PipeStreamConformanceTests.GetUniquePipeName(); var servers = new NamedPipeServerStream[numServers]; var clients = new NamedPipeClientStream[servers.Length]; @@ -204,7 +204,7 @@ public void Windows_MessagePipeTransmissionMode(PipeOptions serverOptions) byte[] received4 = new byte[] { 0, 0, 0, 0 }; byte[] received5 = new byte[] { 0, 0 }; byte[] received6 = new byte[] { 0, 0, 0, 0 }; - string pipeName = GetUniquePipeName(); + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); using (var server = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Message, serverOptions)) { @@ -288,7 +288,7 @@ public void Windows_MessagePipeTransmissionMode(PipeOptions serverOptions) [PlatformSpecific(TestPlatforms.Windows)] // Unix doesn't support MaxNumberOfServerInstances public async Task Windows_Get_NumberOfServerInstances_Succeed() { - string pipeName = GetUniquePipeName(); + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); using (var server = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 3)) { @@ -314,7 +314,7 @@ public async Task Windows_Get_NumberOfServerInstances_Succeed() [PlatformSpecific(TestPlatforms.Windows)] // Win32 P/Invokes to verify the user name public async Task Windows_GetImpersonationUserName_Succeed(TokenImpersonationLevel level, bool expectedResult) { - string pipeName = GetUniquePipeName(); + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); using (var server = new NamedPipeServerStream(pipeName)) { @@ -348,7 +348,7 @@ public async Task Windows_GetImpersonationUserName_Succeed(TokenImpersonationLev [PlatformSpecific(TestPlatforms.AnyUnix)] // Uses P/Invoke to verify the user name public async Task Unix_GetImpersonationUserName_Succeed() { - string pipeName = GetUniquePipeName(); + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); using (var server = new NamedPipeServerStream(pipeName)) using (var client = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.Impersonation)) @@ -368,7 +368,7 @@ public async Task Unix_GetImpersonationUserName_Succeed() [PlatformSpecific(TestPlatforms.AnyUnix)] // Unix currently doesn't support message mode public void Unix_MessagePipeTransmissionMode() { - Assert.Throws(() => new NamedPipeServerStream(GetUniquePipeName(), PipeDirection.InOut, 1, PipeTransmissionMode.Message)); + Assert.Throws(() => new NamedPipeServerStream(PipeStreamConformanceTests.GetUniquePipeName(), PipeDirection.InOut, 1, PipeTransmissionMode.Message)); } [Theory] @@ -379,7 +379,7 @@ public void Unix_MessagePipeTransmissionMode() public static void Unix_BufferSizeRoundtripping(PipeDirection direction) { int desiredBufferSize = 0; - string pipeName = GetUniquePipeName(); + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); using (var server = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, desiredBufferSize, desiredBufferSize)) using (var client = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut)) { @@ -414,7 +414,7 @@ public static void Unix_BufferSizeRoundtripping(PipeDirection direction) public static void Windows_BufferSizeRoundtripping() { int desiredBufferSize = 10; - string pipeName = GetUniquePipeName(); + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); using (var server = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, desiredBufferSize, desiredBufferSize)) using (var client = new NamedPipeClientStream(".", pipeName, PipeDirection.In)) { @@ -439,12 +439,15 @@ public static void Windows_BufferSizeRoundtripping() } [Fact] - public void PipeTransmissionMode_Returns_Byte() + public async Task PipeTransmissionMode_Returns_Byte() { - using (ServerClientPair pair = CreateServerClientPair()) + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); + using (var server = new NamedPipeServerStream(pipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) + using (var client = new NamedPipeClientStream(".", pipeName, PipeDirection.Out)) { - Assert.Equal(PipeTransmissionMode.Byte, pair.writeablePipe.TransmissionMode); - Assert.Equal(PipeTransmissionMode.Byte, pair.readablePipe.TransmissionMode); + await Task.WhenAll(server.WaitForConnectionAsync(), client.ConnectAsync()); + Assert.Equal(PipeTransmissionMode.Byte, server.TransmissionMode); + Assert.Equal(PipeTransmissionMode.Byte, client.TransmissionMode); } } @@ -452,7 +455,7 @@ public void PipeTransmissionMode_Returns_Byte() [PlatformSpecific(TestPlatforms.Windows)] // Unix doesn't currently support message mode public void Windows_SetReadModeTo__PipeTransmissionModeByte() { - string pipeName = GetUniquePipeName(); + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); using (var server = new NamedPipeServerStream(pipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) using (var client = new NamedPipeClientStream(".", pipeName, PipeDirection.Out)) { @@ -493,7 +496,7 @@ public void Windows_SetReadModeTo__PipeTransmissionModeByte() [PlatformSpecific(TestPlatforms.AnyUnix)] // Unix doesn't currently support message mode public void Unix_SetReadModeTo__PipeTransmissionModeByte() { - string pipeName = GetUniquePipeName(); + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); using (var server = new NamedPipeServerStream(pipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) using (var client = new NamedPipeClientStream(".", pipeName, PipeDirection.Out)) { @@ -533,7 +536,7 @@ public void Unix_SetReadModeTo__PipeTransmissionModeByte() [InlineData(PipeDirection.In, PipeDirection.Out)] public void InvalidReadMode_Throws_ArgumentOutOfRangeException(PipeDirection serverDirection, PipeDirection clientDirection) { - string pipeName = GetUniquePipeName(); + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); using (var server = new NamedPipeServerStream(pipeName, serverDirection, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) using (var client = new NamedPipeClientStream(".", pipeName, clientDirection)) { @@ -594,7 +597,7 @@ public void NameTooLong_MaxLengthPerPlatform() [Fact] public void ClientConnect_Throws_Timeout_When_Pipe_Not_Found() { - string pipeName = GetUniquePipeName(); + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); using (NamedPipeClientStream client = new NamedPipeClientStream(pipeName)) { Assert.Throws(() => client.Connect(91)); @@ -605,7 +608,7 @@ public void ClientConnect_Throws_Timeout_When_Pipe_Not_Found() [MemberData(nameof(GetCancellationTokens))] public async Task ClientConnectAsync_Throws_Timeout_When_Pipe_Not_Found(CancellationToken cancellationToken) { - string pipeName = GetUniquePipeName(); + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); using (NamedPipeClientStream client = new NamedPipeClientStream(pipeName)) { Task waitingClient = client.ConnectAsync(92, cancellationToken); @@ -617,7 +620,7 @@ public async Task ClientConnectAsync_Throws_Timeout_When_Pipe_Not_Found(Cancella [PlatformSpecific(TestPlatforms.Windows)] // Unix ignores MaxNumberOfServerInstances and second client also connects. public void ClientConnect_Throws_Timeout_When_Pipe_Busy() { - string pipeName = GetUniquePipeName(); + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); using (NamedPipeServerStream server = new NamedPipeServerStream(pipeName)) using (NamedPipeClientStream firstClient = new NamedPipeClientStream(pipeName)) @@ -641,7 +644,7 @@ public void ClientConnect_Throws_Timeout_When_Pipe_Busy() [PlatformSpecific(TestPlatforms.Windows)] // Unix ignores MaxNumberOfServerInstances and second client also connects. public async Task ClientConnectAsync_With_Cancellation_Throws_Timeout_When_Pipe_Busy(CancellationToken cancellationToken) { - string pipeName = GetUniquePipeName(); + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); using (NamedPipeServerStream server = new NamedPipeServerStream(pipeName)) using (NamedPipeClientStream firstClient = new NamedPipeClientStream(pipeName)) diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.UnixDomainSockets.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.UnixDomainSockets.cs index 7cf4d5fcb3e04a..3f5a9e352d3484 100644 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.UnixDomainSockets.cs +++ b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.UnixDomainSockets.cs @@ -7,7 +7,7 @@ namespace System.IO.Pipes.Tests { - public class NamedPipeTest_UnixDomainSockets : NamedPipeTestBase + public class NamedPipeTest_UnixDomainSockets { [Fact] [PlatformSpecific(TestPlatforms.AnyUnix)] diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Write.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Write.cs deleted file mode 100644 index 174cc6eccb7389..00000000000000 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Write.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Pipes.Tests -{ - public class NamedPipeTest_Write_ServerOut_ClientIn : PipeTest_Write - { - protected override ServerClientPair CreateServerClientPair() - { - ServerClientPair ret = new ServerClientPair(); - string pipeName = GetUniquePipeName(); - var writeablePipe = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); - var readablePipe = new NamedPipeClientStream(".", pipeName, PipeDirection.In); - - Task clientConnect = readablePipe.ConnectAsync(); - writeablePipe.WaitForConnection(); - clientConnect.Wait(); - - ret.readablePipe = readablePipe; - ret.writeablePipe = writeablePipe; - return ret; - } - } - - public class NamedPipeTest_Write_ServerIn_ClientOut : PipeTest_Write - { - protected override ServerClientPair CreateServerClientPair() - { - ServerClientPair ret = new ServerClientPair(); - string pipeName = GetUniquePipeName(); - var readablePipe = new NamedPipeServerStream(pipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); - var writeablePipe = new NamedPipeClientStream(".", pipeName, PipeDirection.Out); - - Task clientConnect = writeablePipe.ConnectAsync(); - readablePipe.WaitForConnection(); - clientConnect.Wait(); - - ret.readablePipe = readablePipe; - ret.writeablePipe = writeablePipe; - return ret; - } - } - - public class NamedPipeTest_Write_ServerInOut_ClientInOut : PipeTest_Write - { - protected override ServerClientPair CreateServerClientPair() - { - ServerClientPair ret = new ServerClientPair(); - string pipeName = GetUniquePipeName(); - var readablePipe = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); - var writeablePipe = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut); - - Task clientConnect = writeablePipe.ConnectAsync(); - readablePipe.WaitForConnection(); - clientConnect.Wait(); - - ret.readablePipe = readablePipe; - ret.writeablePipe = writeablePipe; - return ret; - } - - // InOut pipes can be written/read from either direction - public override bool SupportsBidirectionalReadingWriting => true; - } -} diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.cs deleted file mode 100644 index e0deb9b01be592..00000000000000 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Security.Principal; -using Xunit; - -namespace System.IO.Pipes.Tests -{ - public class NamedPipeTest_netstandard17 : NamedPipeTestBase - { - [Fact] - public void NamedPipeClientStream_InvalidHandleInerhitability() - { - AssertExtensions.Throws("inheritability", () => new NamedPipeClientStream("a", "b", PipeDirection.Out, 0, TokenImpersonationLevel.Delegation, HandleInheritability.None - 1)); - AssertExtensions.Throws("inheritability", () => new NamedPipeClientStream("a", "b", PipeDirection.Out, 0, TokenImpersonationLevel.Delegation, HandleInheritability.Inheritable + 1)); - } - } -} diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTestBase.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTestBase.cs deleted file mode 100644 index ac7177f89849e8..00000000000000 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTestBase.cs +++ /dev/null @@ -1,120 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Pipes.Tests -{ - /// - /// Contains helper methods specifically for Named pipes - /// - public class NamedPipeTestBase : PipeTestBase - { - /// - /// Represents a Server-Client pair in which both pipes are specifically - /// Named Pipes. Used for tests that do not have an AnonymousPipe equivalent - /// - public class NamedPipePair : IDisposable - { - public NamedPipeServerStream serverStream; - public NamedPipeClientStream clientStream; - public bool writeToServer; // True if the serverStream should be written to in one-way tests - - public void Dispose() - { - if (clientStream != null) - clientStream.Dispose(); - serverStream.Dispose(); - } - - public void Connect() - { - Task clientConnect = clientStream.ConnectAsync(); - serverStream.WaitForConnection(); - clientConnect.Wait(); - } - } - - protected virtual NamedPipePair CreateNamedPipePair() - { - return CreateNamedPipePair(PipeOptions.Asynchronous, PipeOptions.Asynchronous); - } - - protected virtual NamedPipePair CreateNamedPipePair(PipeOptions serverOptions, PipeOptions clientOptions) - { - return null; - } - - protected override ServerClientPair CreateServerClientPair() - { - ServerClientPair ret = new ServerClientPair(); - string pipeName = GetUniquePipeName(); - var readablePipe = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); - var writeablePipe = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut); - - Task clientConnect = writeablePipe.ConnectAsync(); - readablePipe.WaitForConnection(); - clientConnect.Wait(); - - ret.readablePipe = readablePipe; - ret.writeablePipe = writeablePipe; - return ret; - } - - protected static byte[] ReadBytes(PipeStream pipeStream, int length) - { - Assert.True(pipeStream.IsConnected); - - byte[] buffer = new byte[length]; - Assert.True(length > 0); - - buffer[0] = (byte)pipeStream.ReadByte(); - if (length > 1) - { - int len = pipeStream.Read(buffer, 1, length - 1); - Assert.Equal(length - 1, len); - } - - return buffer; - } - - protected static void WriteBytes(PipeStream pipeStream, byte[] buffer) - { - Assert.True(pipeStream.IsConnected); - Assert.True(buffer.Length > 0); - - pipeStream.WriteByte(buffer[0]); - if (buffer.Length > 1) - { - pipeStream.Write(buffer, 1, buffer.Length - 1); - } - } - - protected static async Task ReadBytesAsync(PipeStream pipeStream, int length) - { - Assert.True(pipeStream.IsConnected); - - byte[] buffer = new byte[length]; - Assert.True(length > 0); - - int readSoFar = 0; - - while (readSoFar < length) - { - int len = await pipeStream.ReadAsync(buffer, readSoFar, length - readSoFar); - if (len == 0) break; - readSoFar += len; - } - - return buffer; - } - - protected static Task WriteBytesAsync(PipeStream pipeStream, byte[] buffer) - { - Assert.True(pipeStream.IsConnected); - Assert.True(buffer.Length > 0); - return pipeStream.WriteAsync(buffer, 0, buffer.Length); - } - } -} diff --git a/src/libraries/System.IO.Pipes/tests/PipeTest.cs b/src/libraries/System.IO.Pipes/tests/PipeStream.ProtectedMethods.Tests.cs similarity index 100% rename from src/libraries/System.IO.Pipes/tests/PipeTest.cs rename to src/libraries/System.IO.Pipes/tests/PipeStream.ProtectedMethods.Tests.cs diff --git a/src/libraries/System.IO.Pipes/tests/PipeStreamConformanceTests.cs b/src/libraries/System.IO.Pipes/tests/PipeStreamConformanceTests.cs new file mode 100644 index 00000000000000..444ea87431c06e --- /dev/null +++ b/src/libraries/System.IO.Pipes/tests/PipeStreamConformanceTests.cs @@ -0,0 +1,690 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO.Tests; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace System.IO.Pipes.Tests +{ + public abstract class PipeStreamConformanceTests : ConnectedStreamConformanceTests + { + /// Get a unique pipe name very unlikely to be in use elsewhere. + public static string GetUniquePipeName() => + PlatformDetection.IsInAppContainer ? @"LOCAL\" + Path.GetRandomFileName() : + Path.GetRandomFileName(); + + protected override Type UnsupportedConcurrentExceptionType => null; + protected override bool UsableAfterCanceledReads => false; + protected override bool CansReturnFalseAfterDispose => false; + protected override bool FullyCancelableOperations => false; + + [PlatformSpecific(TestPlatforms.Windows)] // WaitForPipeDrain isn't supported on Unix + [Fact] + public async Task PipeStream_WaitForPipeDrain() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + byte[] sent = new byte[] { 123 }; + byte[] received = new byte[] { 0 }; + + Task t = Task.Run(() => writeable.Write(sent, 0, sent.Length)); + Assert.Equal(sent.Length, readable.Read(received, 0, sent.Length)); + Assert.Equal(sent, received); + ((PipeStream)writeable).WaitForPipeDrain(); + await t; + } + } + } + + public abstract class AnonymousPipeStreamConformanceTests : PipeStreamConformanceTests + { + protected override bool BrokenPipePropagatedImmediately => true; + + protected abstract (AnonymousPipeServerStream Server, AnonymousPipeClientStream Client) CreateServerAndClientStreams(); + + protected sealed override Task CreateConnectedStreamsAsync() + { + (AnonymousPipeServerStream server, AnonymousPipeClientStream client) = CreateServerAndClientStreams(); + + Assert.True(server.IsConnected); + Assert.True(client.IsConnected); + + return Task.FromResult((server, client)); + } + } + + public abstract class NamedPipeStreamConformanceTests : PipeStreamConformanceTests + { + protected override bool BrokenPipePropagatedImmediately => OperatingSystem.IsWindows(); // On Unix, implemented on Sockets, where it won't propagate immediate + + protected abstract (NamedPipeServerStream Server, NamedPipeClientStream Client) CreateServerAndClientStreams(); + + protected sealed override async Task CreateConnectedStreamsAsync() + { + (NamedPipeServerStream server, NamedPipeClientStream client) = CreateServerAndClientStreams(); + await Task.WhenAll(client.ConnectAsync(), server.WaitForConnectionAsync()); + + Assert.True(server.IsConnected); + Assert.True(client.IsConnected); + + return (server, client); + } + + protected (NamedPipeServerStream Server, NamedPipeClientStream Client) GetClientAndServer(StreamPair streams) + { + if (streams.Stream1 is NamedPipeServerStream) + { + Assert.IsType(streams.Stream2); + return ((NamedPipeServerStream)streams.Stream1, (NamedPipeClientStream)streams.Stream2); + } + + Assert.IsType(streams.Stream1); + return ((NamedPipeServerStream)streams.Stream2, (NamedPipeClientStream)streams.Stream1); + } + + /// + /// Yields every combination of testing options for the OneWayReadWrites test + /// + /// + public static IEnumerable OneWayReadWritesMemberData() => + from serverOption in new[] { PipeOptions.None, PipeOptions.Asynchronous } + from clientOption in new[] { PipeOptions.None, PipeOptions.Asynchronous } + from asyncServerOps in new[] { false, true } + from asyncClientOps in new[] { false, true } + select new object[] { serverOption, clientOption, asyncServerOps, asyncClientOps }; + + [Fact] + public async Task ClonedServer_ActsAsOriginalServer() + { + byte[] msg1 = new byte[] { 5, 7, 9, 10 }; + byte[] received1 = new byte[] { 0, 0, 0, 0 }; + + using StreamPair streams = await CreateConnectedStreamsAsync(); + (Stream writeable, Stream readable) = GetReadWritePair(streams); + + if (writeable is NamedPipeServerStream serverBase) + { + Task clientTask = readable.ReadAsync(received1, 0, received1.Length); + using (NamedPipeServerStream server = new NamedPipeServerStream(PipeDirection.Out, false, true, serverBase.SafePipeHandle)) + { + if (OperatingSystem.IsWindows()) + { + Assert.Equal(1, ((NamedPipeClientStream)readable).NumberOfServerInstances); + } + server.Write(msg1, 0, msg1.Length); + int receivedLength = await clientTask; + Assert.Equal(msg1.Length, receivedLength); + Assert.Equal(msg1, received1); + } + } + else + { + Task clientTask = writeable.WriteAsync(msg1, 0, msg1.Length); + using (NamedPipeServerStream server = new NamedPipeServerStream(PipeDirection.In, false, true, ((NamedPipeServerStream)readable).SafePipeHandle)) + { + int receivedLength = server.Read(received1, 0, msg1.Length); + Assert.Equal(msg1.Length, receivedLength); + Assert.Equal(msg1, received1); + await clientTask; + } + } + } + + [Fact] + public async Task ClonedClient_ActsAsOriginalClient() + { + byte[] msg1 = new byte[] { 5, 7, 9, 10 }; + byte[] received1 = new byte[] { 0, 0, 0, 0 }; + + using StreamPair streams = await CreateConnectedStreamsAsync(); + (Stream writeable, Stream readable) = GetReadWritePair(streams); + + if (writeable is NamedPipeServerStream server) + { + using (NamedPipeClientStream client = new NamedPipeClientStream(PipeDirection.In, false, true, ((NamedPipeClientStream)readable).SafePipeHandle)) + { + if (OperatingSystem.IsWindows()) + { + Assert.Equal(1, client.NumberOfServerInstances); + } + Task clientTask = client.ReadAsync(received1, 0, received1.Length); + server.Write(msg1, 0, msg1.Length); + int receivedLength = await clientTask; + Assert.Equal(msg1.Length, receivedLength); + Assert.Equal(msg1, received1); + } + } + else + { + using (NamedPipeClientStream client = new NamedPipeClientStream(PipeDirection.Out, false, true, ((NamedPipeClientStream)writeable).SafePipeHandle)) + { + Task clientTask = client.WriteAsync(msg1, 0, msg1.Length); + int receivedLength = readable.Read(received1, 0, msg1.Length); + Assert.Equal(msg1.Length, receivedLength); + Assert.Equal(msg1, received1); + await clientTask; + } + } + } + + [Fact] + public async Task ConnectOnAlreadyConnectedClient_Throws_InvalidOperationException() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + + NamedPipeClientStream client = streams.Stream1 as NamedPipeClientStream ?? (NamedPipeClientStream)streams.Stream2; + + Assert.Throws(() => client.Connect()); + } + + [Fact] + public async Task WaitForConnectionOnAlreadyConnectedServer_Throws_InvalidOperationException() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + + NamedPipeServerStream server = streams.Stream1 as NamedPipeServerStream ?? (NamedPipeServerStream)streams.Stream2; + + Assert.Throws(() => server.WaitForConnection()); + } + + [Fact] + public async Task CancelTokenOn_ServerWaitForConnectionAsync_Throws_OperationCanceledException() + { + (NamedPipeServerStream server, NamedPipeClientStream client) = CreateServerAndClientStreams(); + using StreamPair streams = (server, client); + + var ctx = new CancellationTokenSource(); + + if (OperatingSystem.IsWindows()) // cancellation token after the operation has been initiated + { + Task serverWaitTimeout = server.WaitForConnectionAsync(ctx.Token); + ctx.Cancel(); + await Assert.ThrowsAnyAsync(() => serverWaitTimeout); + } + + ctx.Cancel(); + Assert.True(server.WaitForConnectionAsync(ctx.Token).IsCanceled); + } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] // P/Invoking to Win32 functions + public async Task CancelTokenOff_ServerWaitForConnectionAsyncWithOuterCancellation_Throws_OperationCanceledException() + { + (NamedPipeServerStream server, NamedPipeClientStream client) = CreateServerAndClientStreams(); + using StreamPair streams = (server, client); + + Task waitForConnectionTask = server.WaitForConnectionAsync(CancellationToken.None); + + Assert.True(InteropTest.CancelIoEx(server.SafePipeHandle), "Outer cancellation failed"); + await Assert.ThrowsAnyAsync(() => waitForConnectionTask); + Assert.True(waitForConnectionTask.IsCanceled); + } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] // P/Invoking to Win32 functions + public async Task CancelTokenOn_ServerWaitForConnectionAsyncWithOuterCancellation_Throws_IOException() + { + (NamedPipeServerStream server, NamedPipeClientStream client) = CreateServerAndClientStreams(); + using StreamPair streams = (server, client); + + var cts = new CancellationTokenSource(); + Task waitForConnectionTask = server.WaitForConnectionAsync(cts.Token); + + Assert.True(InteropTest.CancelIoEx(server.SafePipeHandle), "Outer cancellation failed"); + await Assert.ThrowsAsync(() => waitForConnectionTask); + } + + [Fact] + public async Task OperationsOnDisconnectedServer() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (NamedPipeServerStream server, NamedPipeClientStream client) = GetClientAndServer(streams); + + Assert.Throws(() => server.IsMessageComplete); + Assert.Throws(() => server.WaitForConnection()); + await Assert.ThrowsAsync(() => server.WaitForConnectionAsync()); // fails because allowed connections is set to 1 + + server.Disconnect(); + Assert.Throws(() => server.Disconnect()); // double disconnect + + byte[] buffer = new byte[4]; + + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + if (ReferenceEquals(writeable, server)) + { + Assert.Throws(() => server.Write(buffer, 0, buffer.Length)); + Assert.Throws(() => server.WriteByte(5)); + Assert.Throws(() => { server.WriteAsync(buffer, 0, buffer.Length); }); + } + else + { + Assert.Throws(() => server.Read(buffer, 0, buffer.Length)); + Assert.Throws(() => server.ReadByte()); + Assert.Throws(() => { server.ReadAsync(buffer, 0, buffer.Length); }); + } + } + + Assert.Throws(() => server.Flush()); + Assert.Throws(() => server.IsMessageComplete); + Assert.Throws(() => server.GetImpersonationUserName()); + } + + [Fact] + public virtual async Task OperationsOnDisconnectedClient() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (NamedPipeServerStream server, NamedPipeClientStream client) = GetClientAndServer(streams); + + Assert.Throws(() => client.IsMessageComplete); + Assert.Throws(() => client.Connect()); + await Assert.ThrowsAsync(() => client.ConnectAsync()); + + server.Disconnect(); + + var buffer = new byte[4]; + + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + if (ReferenceEquals(writeable, client)) + { + if (OperatingSystem.IsWindows()) // writes on Unix may still succeed after other end disconnects, due to socket being used + { + // Pipe is broken + Assert.Throws(() => client.Write(buffer, 0, buffer.Length)); + Assert.Throws(() => client.WriteByte(5)); + Assert.Throws(() => { client.WriteAsync(buffer, 0, buffer.Length); }); + Assert.Throws(() => client.Flush()); + Assert.Throws(() => client.NumberOfServerInstances); + } + } + else + { + // Nothing for the client to read, but no exception throwing + Assert.Equal(0, client.Read(buffer, 0, buffer.Length)); + Assert.Equal(-1, client.ReadByte()); + + if (!OperatingSystem.IsWindows()) // NumberOfServerInstances not supported on Unix + { + Assert.Throws(() => client.NumberOfServerInstances); + } + } + } + + Assert.Throws(() => client.IsMessageComplete); + } + + [Fact] + public async Task Windows_OperationsOnNamedServerWithDisposedClient() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (NamedPipeServerStream server, NamedPipeClientStream client) = GetClientAndServer(streams); + + client.Dispose(); + + if (OperatingSystem.IsWindows()) + { + Assert.Throws(() => server.WaitForConnection()); + await Assert.ThrowsAsync(() => server.WaitForConnectionAsync()); + Assert.Throws(() => server.GetImpersonationUserName()); + } + else + { + // On Unix, the server still thinks that it is connected after client Disposal. + Assert.Throws(() => server.WaitForConnection()); + await Assert.ThrowsAsync(() => server.WaitForConnectionAsync()); + Assert.NotNull(server.GetImpersonationUserName()); + } + } + + [Fact] + public void OperationsOnUnconnectedServer() + { + (NamedPipeServerStream server, NamedPipeClientStream client) = CreateServerAndClientStreams(); + using StreamPair streams = (server, client); + + // doesn't throw exceptions + PipeTransmissionMode transmitMode = server.TransmissionMode; + Assert.Throws(() => server.ReadMode = (PipeTransmissionMode)999); + + var buffer = new byte[4]; + + foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) + { + if (ReferenceEquals(writeable, server)) + { + Assert.Equal(0, server.OutBufferSize); + Assert.Throws(() => server.Write(buffer, 0, buffer.Length)); + Assert.Throws(() => server.WriteByte(5)); + Assert.Throws(() => { server.WriteAsync(buffer, 0, buffer.Length); }); + } + else + { + Assert.Equal(0, server.InBufferSize); + PipeTransmissionMode readMode = server.ReadMode; + Assert.Throws(() => server.Read(buffer, 0, buffer.Length)); + Assert.Throws(() => server.ReadByte()); + Assert.Throws(() => { server.ReadAsync(buffer, 0, buffer.Length); }); + } + } + + Assert.Throws(() => server.Disconnect()); // disconnect when not connected + Assert.Throws(() => server.IsMessageComplete); + } + + [Fact] + public void OperationsOnUnconnectedClient() + { + (NamedPipeServerStream server, NamedPipeClientStream client) = CreateServerAndClientStreams(); + using StreamPair streams = (server, client); + + var buffer = new byte[4]; + + if (client.CanRead) + { + Assert.Throws(() => client.Read(buffer, 0, buffer.Length)); + Assert.Throws(() => client.ReadByte()); + Assert.Throws(() => { client.ReadAsync(buffer, 0, buffer.Length); }); + Assert.Throws(() => client.ReadMode); + Assert.Throws(() => client.ReadMode = PipeTransmissionMode.Byte); + } + + if (client.CanWrite) + { + Assert.Throws(() => client.Write(buffer, 0, buffer.Length)); + Assert.Throws(() => client.WriteByte(5)); + Assert.Throws(() => { client.WriteAsync(buffer, 0, buffer.Length); }); + } + + Assert.Throws(() => client.NumberOfServerInstances); + Assert.Throws(() => client.TransmissionMode); + Assert.Throws(() => client.InBufferSize); + Assert.Throws(() => client.OutBufferSize); + Assert.Throws(() => client.SafePipeHandle); + } + + [Fact] + public async Task DisposedServerPipe_Throws_ObjectDisposedException() + { + (NamedPipeServerStream server, NamedPipeClientStream client) = CreateServerAndClientStreams(); + server.Dispose(); + + Assert.Throws(() => server.Disconnect()); + Assert.Throws(() => server.GetImpersonationUserName()); + Assert.Throws(() => server.WaitForConnection()); + await Assert.ThrowsAsync(() => server.WaitForConnectionAsync()); + } + + [Fact] + public async Task DisposedClientPipe_Throws_ObjectDisposedException() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (NamedPipeServerStream server, NamedPipeClientStream client) = GetClientAndServer(streams); + client.Dispose(); + + Assert.Throws(() => client.Connect()); + await Assert.ThrowsAsync(() => client.ConnectAsync()); + Assert.Throws(() => client.NumberOfServerInstances); + } + + [Fact] + public async Task ReadAsync_DisconnectDuringRead_Returns0() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (Stream writeable, Stream readable) = GetReadWritePair(streams); + + Task readTask = readable.ReadAsync(new byte[1], 0, 1); + writeable.Dispose(); + Assert.Equal(0, await readTask); + } + + [PlatformSpecific(TestPlatforms.Windows)] // Unix named pipes are on sockets, where small writes with an empty buffer will succeed immediately + [Fact] + public async Task WriteAsync_DisconnectDuringWrite_Throws() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (Stream writeable, Stream readable) = GetReadWritePair(streams); + + Task writeTask = writeable.WriteAsync(new byte[1], 0, 1); + readable.Dispose(); + await Assert.ThrowsAsync(() => writeTask); + } + + [Fact] + public async Task Server_ReadWriteCancelledToken_Throws_OperationCanceledException() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (NamedPipeServerStream server, NamedPipeClientStream client) = GetClientAndServer(streams); + + var buffer = new byte[4]; + + if (server.CanRead && client.CanWrite) + { + var ctx1 = new CancellationTokenSource(); + + Task serverReadToken = server.ReadAsync(buffer, 0, buffer.Length, ctx1.Token); + ctx1.Cancel(); + await Assert.ThrowsAnyAsync(() => serverReadToken); + + ctx1.Cancel(); + Assert.True(server.ReadAsync(buffer, 0, buffer.Length, ctx1.Token).IsCanceled); + } + + if (server.CanWrite) + { + var ctx1 = new CancellationTokenSource(); + if (OperatingSystem.IsWindows()) // On Unix WriteAsync's aren't cancelable once initiated + { + Task serverWriteToken = server.WriteAsync(buffer, 0, buffer.Length, ctx1.Token); + ctx1.Cancel(); + await Assert.ThrowsAnyAsync(() => serverWriteToken); + } + ctx1.Cancel(); + Assert.True(server.WriteAsync(buffer, 0, buffer.Length, ctx1.Token).IsCanceled); + } + } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] // P/Invoking to Win32 functions + public async Task CancelTokenOff_Server_ReadWriteCancelledToken_Throws_OperationCanceledException() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (NamedPipeServerStream server, NamedPipeClientStream client) = GetClientAndServer(streams); + + var buffer = new byte[4]; + + if (server.CanRead) + { + Task serverReadToken = server.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None); + + Assert.True(InteropTest.CancelIoEx(server.SafePipeHandle), "Outer cancellation failed"); + await Assert.ThrowsAnyAsync(() => serverReadToken); + Assert.True(serverReadToken.IsCanceled); + } + + if (server.CanWrite) + { + Task serverWriteToken = server.WriteAsync(buffer, 0, buffer.Length, CancellationToken.None); + + Assert.True(InteropTest.CancelIoEx(server.SafePipeHandle), "Outer cancellation failed"); + await Assert.ThrowsAnyAsync(() => serverWriteToken); + Assert.True(serverWriteToken.IsCanceled); + } + } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] // P/Invoking to Win32 functions + public async Task CancelTokenOn_Server_ReadWriteCancelledToken_Throws_OperationCanceledException() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (NamedPipeServerStream server, NamedPipeClientStream client) = GetClientAndServer(streams); + + var buffer = new byte[4]; + + if (server.CanRead) + { + var cts = new CancellationTokenSource(); + Task serverReadToken = server.ReadAsync(buffer, 0, buffer.Length, cts.Token); + + Assert.True(InteropTest.CancelIoEx(server.SafePipeHandle), "Outer cancellation failed"); + await Assert.ThrowsAnyAsync(() => serverReadToken); + } + if (server.CanWrite) + { + var cts = new CancellationTokenSource(); + Task serverWriteToken = server.WriteAsync(buffer, 0, buffer.Length, cts.Token); + + Assert.True(InteropTest.CancelIoEx(server.SafePipeHandle), "Outer cancellation failed"); + await Assert.ThrowsAnyAsync(() => serverWriteToken); + } + } + + [Fact] + public async Task Client_ReadWriteCancelledToken_Throws_OperationCanceledException() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (NamedPipeServerStream server, NamedPipeClientStream client) = GetClientAndServer(streams); + + var buffer = new byte[4]; + + if (client.CanRead) + { + var ctx1 = new CancellationTokenSource(); + + Task serverReadToken = client.ReadAsync(buffer, 0, buffer.Length, ctx1.Token); + ctx1.Cancel(); + await Assert.ThrowsAnyAsync(() => serverReadToken); + + Assert.True(client.ReadAsync(buffer, 0, buffer.Length, ctx1.Token).IsCanceled); + } + + if (client.CanWrite) + { + var ctx1 = new CancellationTokenSource(); + if (OperatingSystem.IsWindows()) // On Unix WriteAsync's aren't cancelable once initiated + { + Task serverWriteToken = client.WriteAsync(buffer, 0, buffer.Length, ctx1.Token); + ctx1.Cancel(); + await Assert.ThrowsAnyAsync(() => serverWriteToken); + } + ctx1.Cancel(); + Assert.True(client.WriteAsync(buffer, 0, buffer.Length, ctx1.Token).IsCanceled); + } + } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] // P/Invoking to Win32 functions + public async Task CancelTokenOff_Client_ReadWriteCancelledToken_Throws_OperationCanceledException() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (NamedPipeServerStream server, NamedPipeClientStream client) = GetClientAndServer(streams); + + var buffer = new byte[4]; + + if (client.CanRead) + { + Task clientReadToken = client.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None); + + Assert.True(InteropTest.CancelIoEx(client.SafePipeHandle), "Outer cancellation failed"); + await Assert.ThrowsAnyAsync(() => clientReadToken); + Assert.True(clientReadToken.IsCanceled); + } + + if (client.CanWrite) + { + Task clientWriteToken = client.WriteAsync(buffer, 0, buffer.Length, CancellationToken.None); + + Assert.True(InteropTest.CancelIoEx(client.SafePipeHandle), "Outer cancellation failed"); + await Assert.ThrowsAnyAsync(() => clientWriteToken); + Assert.True(clientWriteToken.IsCanceled); + } + } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] // P/Invoking to Win32 functions + public async Task CancelTokenOn_Client_ReadWriteCancelledToken_Throws_OperationCanceledException() + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (NamedPipeServerStream server, NamedPipeClientStream client) = GetClientAndServer(streams); + + var buffer = new byte[4]; + + if (client.CanRead) + { + var cts = new CancellationTokenSource(); + Task clientReadToken = client.ReadAsync(buffer, 0, buffer.Length, cts.Token); + + Assert.True(InteropTest.CancelIoEx(client.SafePipeHandle), "Outer cancellation failed"); + await Assert.ThrowsAnyAsync(() => clientReadToken); + } + + if (client.CanWrite) + { + var cts = new CancellationTokenSource(); + Task clientWriteToken = client.WriteAsync(buffer, 0, buffer.Length, cts.Token); + + Assert.True(InteropTest.CancelIoEx(client.SafePipeHandle), "Outer cancellation failed"); + await Assert.ThrowsAnyAsync(() => clientWriteToken); + } + } + } + + public sealed class AnonymousPipeTest_ServerIn_ClientOut : AnonymousPipeStreamConformanceTests + { + protected override (AnonymousPipeServerStream Server, AnonymousPipeClientStream Client) CreateServerAndClientStreams() + { + var server = new AnonymousPipeServerStream(PipeDirection.In); + var client = new AnonymousPipeClientStream(PipeDirection.Out, server.ClientSafePipeHandle); + return (server, client); + } + } + + public sealed class AnonymousPipeTest_ServerOut_ClientIn : AnonymousPipeStreamConformanceTests + { + protected override (AnonymousPipeServerStream Server, AnonymousPipeClientStream Client) CreateServerAndClientStreams() + { + var server = new AnonymousPipeServerStream(PipeDirection.Out); + var client = new AnonymousPipeClientStream(PipeDirection.In, server.ClientSafePipeHandle); + return (server, client); + } + } + + public sealed class NamedPipeTest_ServerOut_ClientIn : NamedPipeStreamConformanceTests + { + protected override (NamedPipeServerStream Server, NamedPipeClientStream Client) CreateServerAndClientStreams() + { + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); + var server = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + var client = new NamedPipeClientStream(".", pipeName, PipeDirection.In, PipeOptions.Asynchronous); + return (server, client); + } + } + + public sealed class NamedPipeTest_ServerIn_ClientOut : NamedPipeStreamConformanceTests + { + protected override (NamedPipeServerStream Server, NamedPipeClientStream Client) CreateServerAndClientStreams() + { + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); + var server = new NamedPipeServerStream(pipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + var client = new NamedPipeClientStream(".", pipeName, PipeDirection.Out, PipeOptions.Asynchronous); + return (server, client); + } + } + + public sealed class NamedPipeTest_ServerInOut_ClientInOut : NamedPipeStreamConformanceTests + { + protected override (NamedPipeServerStream Server, NamedPipeClientStream Client) CreateServerAndClientStreams() + { + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); + var server = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + var client = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); + return (server, client); + } + } +} diff --git a/src/libraries/System.IO.Pipes/tests/PipeTest.Read.cs b/src/libraries/System.IO.Pipes/tests/PipeTest.Read.cs deleted file mode 100644 index b3ebf7f17200fd..00000000000000 --- a/src/libraries/System.IO.Pipes/tests/PipeTest.Read.cs +++ /dev/null @@ -1,533 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Pipes.Tests -{ - /// - /// Tests that cover Read and ReadAsync behaviors that are shared between - /// AnonymousPipes and NamedPipes - /// - public abstract class PipeTest_Read : PipeTestBase - { - public virtual bool SupportsBidirectionalReadingWriting => false; - - [Fact] - public void ReadWithNullBuffer_Throws_ArgumentNullException() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.readablePipe; - Assert.True(pipe.IsConnected); - Assert.True(pipe.CanRead); - - // Null is an invalid Buffer - AssertExtensions.Throws("buffer", () => pipe.Read(null, 0, 1)); - AssertExtensions.Throws("buffer", () => { pipe.ReadAsync(null, 0, 1); }); - - // Buffer validity is checked before Offset - AssertExtensions.Throws("buffer", () => pipe.Read(null, -1, 1)); - AssertExtensions.Throws("buffer", () => { pipe.ReadAsync(null, -1, 1); }); - - // Buffer validity is checked before Count - AssertExtensions.Throws("buffer", () => pipe.Read(null, -1, -1)); - AssertExtensions.Throws("buffer", () => { pipe.ReadAsync(null, -1, -1); }); - } - } - - [Fact] - public void ReadWithNegativeOffset_Throws_ArgumentOutOfRangeException() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.readablePipe; - Assert.True(pipe.IsConnected); - Assert.True(pipe.CanRead); - - // Offset must be nonnegative - AssertExtensions.Throws("offset", () => pipe.Read(new byte[6], -1, 1)); - AssertExtensions.Throws("offset", () => { pipe.ReadAsync(new byte[4], -1, 1); }); - } - } - - [Fact] - public void ReadWithNegativeCount_Throws_ArgumentOutOfRangeException() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.readablePipe; - Assert.True(pipe.IsConnected); - Assert.True(pipe.CanRead); - - // Count must be nonnegative - AssertExtensions.Throws("count", () => pipe.Read(new byte[3], 0, -1)); - AssertExtensions.Throws("count", () => { pipe.ReadAsync(new byte[7], 0, -1); }); - } - } - - [Fact] - public void ReadWithOutOfBoundsArray_Throws_ArgumentException() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.readablePipe; - Assert.True(pipe.IsConnected); - Assert.True(pipe.CanRead); - - // offset out of bounds - AssertExtensions.Throws(null, () => pipe.Read(new byte[1], 1, 1)); - - // offset out of bounds for 0 count read - AssertExtensions.Throws(null, () => pipe.Read(new byte[1], 2, 0)); - - // offset out of bounds even for 0 length buffer - AssertExtensions.Throws(null, () => pipe.Read(new byte[0], 1, 0)); - - // combination offset and count out of bounds - AssertExtensions.Throws(null, () => pipe.Read(new byte[2], 1, 2)); - - // edges - AssertExtensions.Throws(null, () => pipe.Read(new byte[0], int.MaxValue, 0)); - AssertExtensions.Throws(null, () => pipe.Read(new byte[0], int.MaxValue, int.MaxValue)); - - AssertExtensions.Throws(null, () => pipe.Read(new byte[5], 3, 4)); - - // offset out of bounds - AssertExtensions.Throws(null, () => { pipe.ReadAsync(new byte[1], 1, 1); }); - - // offset out of bounds for 0 count read - AssertExtensions.Throws(null, () => { pipe.ReadAsync(new byte[1], 2, 0); }); - - // offset out of bounds even for 0 length buffer - AssertExtensions.Throws(null, () => { pipe.ReadAsync(new byte[0], 1, 0); }); - - // combination offset and count out of bounds - AssertExtensions.Throws(null, () => { pipe.ReadAsync(new byte[2], 1, 2); }); - - // edges - AssertExtensions.Throws(null, () => { pipe.ReadAsync(new byte[0], int.MaxValue, 0); }); - AssertExtensions.Throws(null, () => { pipe.ReadAsync(new byte[0], int.MaxValue, int.MaxValue); }); - - AssertExtensions.Throws(null, () => { pipe.ReadAsync(new byte[5], 3, 4); }); - } - } - - [Fact] - public void WriteToReadOnlyPipe_Throws_NotSupportedException() - { - if (SupportsBidirectionalReadingWriting) - { - return; - } - - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.readablePipe; - Assert.True(pipe.IsConnected); - Assert.False(pipe.CanWrite); - Assert.False(pipe.CanSeek); - - Assert.Throws(() => pipe.Write(new byte[5], 0, 5)); - - Assert.Throws(() => pipe.WriteByte(123)); - - Assert.Throws(() => pipe.Flush()); - - Assert.Throws(() => pipe.OutBufferSize); - - Assert.Throws(() => pipe.WaitForPipeDrain()); - - Assert.Throws(() => { pipe.WriteAsync(new byte[5], 0, 5); }); - } - } - - [Fact] - public async Task ReadWithZeroLengthBuffer_Nop() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.readablePipe; - var buffer = new byte[] { }; - - Assert.Equal(0, pipe.Read(buffer, 0, 0)); - Task read = pipe.ReadAsync(buffer, 0, 0); - Assert.Equal(0, await read); - } - } - - [Fact] - public void ReadPipeUnsupportedMembers_Throws_NotSupportedException() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.readablePipe; - Assert.True(pipe.IsConnected); - - Assert.Throws(() => pipe.Length); - - Assert.Throws(() => pipe.SetLength(10L)); - - Assert.Throws(() => pipe.Position); - - Assert.Throws(() => pipe.Position = 10L); - - Assert.Throws(() => pipe.Seek(10L, System.IO.SeekOrigin.Begin)); - } - } - - [Fact] - public void ReadOnDisposedReadablePipe_Throws_ObjectDisposedException() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.readablePipe; - pipe.Dispose(); - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - Assert.Throws(() => pipe.Flush()); - Assert.Throws(() => pipe.Read(buffer, 0, buffer.Length)); - Assert.Throws(() => pipe.ReadByte()); - Assert.Throws(() => { pipe.ReadAsync(buffer, 0, buffer.Length); }); - Assert.Throws(() => pipe.IsMessageComplete); - Assert.Throws(() => pipe.ReadMode); - } - } - - [Fact] - public void CopyToAsync_InvalidArgs_Throws() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - AssertExtensions.Throws("destination", () => { pair.readablePipe.CopyToAsync(null); }); - AssertExtensions.Throws("bufferSize", () => { pair.readablePipe.CopyToAsync(new MemoryStream(), 0); }); - Assert.Throws(() => { pair.readablePipe.CopyToAsync(new MemoryStream(new byte[1], writable: false)); }); - if (!pair.writeablePipe.CanRead) - { - Assert.Throws(() => { pair.writeablePipe.CopyToAsync(new MemoryStream()); }); - } - } - } - - [Fact] - public virtual async Task ReadFromPipeWithClosedPartner_ReadNoBytes() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - pair.writeablePipe.Dispose(); - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - // The pipe won't be marked as Broken until the first read, so prime it - // to test both the case where it's not yet marked as "Broken" and then - // where it is. - Assert.Equal(0, pair.readablePipe.Read(buffer, 0, buffer.Length)); - - Assert.Equal(0, pair.readablePipe.Read(buffer, 0, buffer.Length)); - Assert.Equal(-1, pair.readablePipe.ReadByte()); - Assert.Equal(0, await pair.readablePipe.ReadAsync(buffer, 0, buffer.Length)); - } - } - - [Fact] - public async Task ValidWriteAsync_ValidReadAsync() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - Assert.True(pair.writeablePipe.IsConnected); - Assert.True(pair.readablePipe.IsConnected); - - byte[] sent = new byte[] { 123, 0, 5 }; - byte[] received = new byte[] { 0, 0, 0 }; - - Task write = pair.writeablePipe.WriteAsync(sent, 0, sent.Length); - Assert.Equal(sent.Length, await pair.readablePipe.ReadAsync(received, 0, sent.Length)); - Assert.Equal(sent, received); - await write; - } - } - - [Fact] - public void ValidWrite_ValidRead() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - Assert.True(pair.writeablePipe.IsConnected); - Assert.True(pair.readablePipe.IsConnected); - - byte[] sent = new byte[] { 123, 0, 5 }; - byte[] received = new byte[] { 0, 0, 0 }; - - Task.Run(() => { pair.writeablePipe.Write(sent, 0, sent.Length); }); - Assert.Equal(sent.Length, pair.readablePipe.Read(received, 0, sent.Length)); - Assert.Equal(sent, received); - if (OperatingSystem.IsWindows()) // WaitForPipeDrain isn't supported on Unix - pair.writeablePipe.WaitForPipeDrain(); - } - } - - [Fact] - public void ValidWriteByte_ValidReadByte() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - Assert.True(pair.writeablePipe.IsConnected); - Assert.True(pair.readablePipe.IsConnected); - - Task.Run(() => pair.writeablePipe.WriteByte(123)); - Assert.Equal(123, pair.readablePipe.ReadByte()); - } - } - - [Theory] - [OuterLoop] - [MemberData(nameof(AsyncReadWriteChain_MemberData))] - public async Task AsyncReadWriteChain_ReadWrite(int iterations, int writeBufferSize, int readBufferSize, bool cancelableToken) - { - var writeBuffer = new byte[writeBufferSize]; - var readBuffer = new byte[readBufferSize]; - var rand = new Random(); - var cancellationToken = cancelableToken ? new CancellationTokenSource().Token : CancellationToken.None; - - using (ServerClientPair pair = CreateServerClientPair()) - { - // Repeatedly and asynchronously write to the writable pipe and read from the readable pipe, - // verifying that the correct data made it through. - for (int iter = 0; iter < iterations; iter++) - { - rand.NextBytes(writeBuffer); - Task writerTask = pair.writeablePipe.WriteAsync(writeBuffer, 0, writeBuffer.Length, cancellationToken); - - int totalRead = 0; - while (totalRead < writeBuffer.Length) - { - int numRead = await pair.readablePipe.ReadAsync(readBuffer, 0, readBuffer.Length, cancellationToken); - Assert.True(numRead > 0); - Assert.Equal( - new ArraySegment(writeBuffer, totalRead, numRead), - new ArraySegment(readBuffer, 0, numRead)); - totalRead += numRead; - } - Assert.Equal(writeBuffer.Length, totalRead); - - await writerTask; - } - } - } - - [Theory] - [OuterLoop] - [MemberData(nameof(AsyncReadWriteChain_MemberData))] - public async Task AsyncReadWriteChain_CopyToAsync(int iterations, int writeBufferSize, int readBufferSize, bool cancelableToken) - { - var writeBuffer = new byte[writeBufferSize * iterations]; - new Random().NextBytes(writeBuffer); - var cancellationToken = cancelableToken ? new CancellationTokenSource().Token : CancellationToken.None; - - using (ServerClientPair pair = CreateServerClientPair()) - { - var readData = new MemoryStream(); - Task copyTask = pair.readablePipe.CopyToAsync(readData, readBufferSize, cancellationToken); - - for (int iter = 0; iter < iterations; iter++) - { - await pair.writeablePipe.WriteAsync(writeBuffer, iter * writeBufferSize, writeBufferSize, cancellationToken); - } - pair.writeablePipe.Dispose(); - - await copyTask; - Assert.Equal(writeBuffer.Length, readData.Length); - Assert.Equal(writeBuffer, readData.ToArray()); - } - } - - public static IEnumerable AsyncReadWriteChain_MemberData() - { - foreach (bool cancelableToken in new[] { true, false }) - { - yield return new object[] { 5000, 1, 1, cancelableToken }; // very small buffers - yield return new object[] { 500, 21, 18, cancelableToken }; // lots of iterations, with read buffer smaller than write buffer - yield return new object[] { 500, 18, 21, cancelableToken }; // lots of iterations, with write buffer smaller than read buffer - yield return new object[] { 5, 128000, 64000, cancelableToken }; // very large buffers - } - } - - [Fact] - public async Task ValidWriteAsync_ValidReadAsync_APM() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - Assert.True(pair.writeablePipe.IsConnected); - Assert.True(pair.readablePipe.IsConnected); - - byte[] sent = new byte[] { 123, 0, 5 }; - byte[] received = new byte[] { 0, 0, 0 }; - - Task write = Task.Factory.FromAsync(pair.writeablePipe.BeginWrite, pair.writeablePipe.EndWrite, sent, 0, sent.Length, null); - Task read = Task.Factory.FromAsync(pair.readablePipe.BeginRead, pair.readablePipe.EndRead, received, 0, received.Length, null); - Assert.Equal(sent.Length, await read); - Assert.Equal(sent, received); - await write; - } - } - - [Theory] - [OuterLoop] - [MemberData(nameof(AsyncReadWriteChain_MemberData))] - public async Task AsyncReadWriteChain_ReadWrite_APM(int iterations, int writeBufferSize, int readBufferSize, bool cancelableToken) - { - var writeBuffer = new byte[writeBufferSize]; - var readBuffer = new byte[readBufferSize]; - var rand = new Random(); - var cancellationToken = cancelableToken ? new CancellationTokenSource().Token : CancellationToken.None; - - using (ServerClientPair pair = CreateServerClientPair()) - { - // Repeatedly and asynchronously write to the writable pipe and read from the readable pipe, - // verifying that the correct data made it through. - for (int iter = 0; iter < iterations; iter++) - { - rand.NextBytes(writeBuffer); - Task write = Task.Factory.FromAsync(pair.writeablePipe.BeginWrite, pair.writeablePipe.EndWrite, writeBuffer, 0, writeBuffer.Length, null); - - int totalRead = 0; - while (totalRead < writeBuffer.Length) - { - Task read = Task.Factory.FromAsync(pair.readablePipe.BeginRead, pair.readablePipe.EndRead, readBuffer, 0, readBuffer.Length, null); - int numRead = await read; - Assert.True(numRead > 0); - Assert.Equal( - new ArraySegment(writeBuffer, totalRead, numRead), - new ArraySegment(readBuffer, 0, numRead)); - totalRead += numRead; - } - Assert.Equal(writeBuffer.Length, totalRead); - - await write; - } - } - } - - [Fact] - public void WriteToReadOnlyPipe_Span_Throws_NotSupportedException() - { - if (SupportsBidirectionalReadingWriting) - { - return; - } - - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.readablePipe; - Assert.True(pipe.IsConnected); - Assert.False(pipe.CanWrite); - Assert.False(pipe.CanSeek); - - Assert.Throws(() => pipe.Write(new ReadOnlySpan(new byte[5]))); - Assert.Throws(() => { pipe.WriteAsync(new ReadOnlyMemory(new byte[5])); }); - } - } - - [Fact] - public async Task ReadWithZeroLengthBuffer_Span_Nop() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.readablePipe; - var buffer = new byte[] { }; - - Assert.Equal(0, pipe.Read(new Span(buffer))); - ValueTask read = pipe.ReadAsync(new Memory(buffer)); - Assert.Equal(0, await read); - } - } - - [Fact] - public void ReadOnDisposedReadablePipe_Span_Throws_ObjectDisposedException() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.readablePipe; - pipe.Dispose(); - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - Assert.Throws(() => pipe.Read(new Span(buffer))); - Assert.Throws(() => { pipe.ReadAsync(new Memory(buffer)); }); - } - } - - [Fact] - public virtual async Task ReadFromPipeWithClosedPartner_Span_ReadNoBytes() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - pair.writeablePipe.Dispose(); - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - // The pipe won't be marked as Broken until the first read, so prime it - // to test both the case where it's not yet marked as "Broken" and then - // where it is. - Assert.Equal(0, pair.readablePipe.Read(new Span(buffer))); - - Assert.Equal(0, pair.readablePipe.Read(new Span(buffer))); - Assert.Equal(0, await pair.readablePipe.ReadAsync(new Memory(buffer))); - } - } - - [Fact] - public async Task ValidWriteAsync_Span_ValidReadAsync() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - Assert.True(pair.writeablePipe.IsConnected); - Assert.True(pair.readablePipe.IsConnected); - - byte[] sent = new byte[] { 123, 0, 5 }; - byte[] received = new byte[] { 0, 0, 0 }; - - ValueTask write = pair.writeablePipe.WriteAsync(new ReadOnlyMemory(sent)); - Assert.Equal(sent.Length, await pair.readablePipe.ReadAsync(new Memory(received, 0, sent.Length))); - Assert.Equal(sent, received); - await write; - } - } - - [Theory] - [OuterLoop] - [MemberData(nameof(AsyncReadWriteChain_MemberData))] - public async Task AsyncReadWriteChain_Span_ReadWrite(int iterations, int writeBufferSize, int readBufferSize, bool cancelableToken) - { - var writeBuffer = new byte[writeBufferSize]; - var readBuffer = new byte[readBufferSize]; - var rand = new Random(); - var cancellationToken = cancelableToken ? new CancellationTokenSource().Token : CancellationToken.None; - - using (ServerClientPair pair = CreateServerClientPair()) - { - // Repeatedly and asynchronously write to the writable pipe and read from the readable pipe, - // verifying that the correct data made it through. - for (int iter = 0; iter < iterations; iter++) - { - rand.NextBytes(writeBuffer); - ValueTask writerTask = pair.writeablePipe.WriteAsync(new ReadOnlyMemory(writeBuffer), cancellationToken); - - int totalRead = 0; - while (totalRead < writeBuffer.Length) - { - int numRead = await pair.readablePipe.ReadAsync(new Memory(readBuffer), cancellationToken); - Assert.True(numRead > 0); - Assert.Equal( - new ArraySegment(writeBuffer, totalRead, numRead), - new ArraySegment(readBuffer, 0, numRead)); - totalRead += numRead; - } - Assert.Equal(writeBuffer.Length, totalRead); - - await writerTask; - } - } - } - } -} diff --git a/src/libraries/System.IO.Pipes/tests/PipeTest.Write.cs b/src/libraries/System.IO.Pipes/tests/PipeTest.Write.cs deleted file mode 100644 index 69ab803cd7dd1f..00000000000000 --- a/src/libraries/System.IO.Pipes/tests/PipeTest.Write.cs +++ /dev/null @@ -1,311 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Pipes.Tests -{ - /// - /// Tests that cover Write and WriteAsync behaviors that are shared between - /// AnonymousPipes and NamedPipes - /// - public abstract class PipeTest_Write : PipeTestBase - { - public virtual bool SupportsBidirectionalReadingWriting => false; - - [Fact] - public void WriteWithNullBuffer_Throws_ArgumentNullException() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.writeablePipe; - Assert.True(pipe.IsConnected); - Assert.True(pipe.CanWrite); - - // Null is an invalid Buffer - AssertExtensions.Throws("buffer", () => pipe.Write(null, 0, 1)); - AssertExtensions.Throws("buffer", () => { pipe.WriteAsync(null, 0, 1); }); - - // Buffer validity is checked before Offset - AssertExtensions.Throws("buffer", () => pipe.Write(null, -1, 1)); - AssertExtensions.Throws("buffer", () => { pipe.WriteAsync(null, -1, 1); }); - - // Buffer validity is checked before Count - AssertExtensions.Throws("buffer", () => pipe.Write(null, -1, -1)); - AssertExtensions.Throws("buffer", () => { pipe.WriteAsync(null, -1, -1); }); - - } - } - - [Fact] - public void WriteWithNegativeOffset_Throws_ArgumentOutOfRangeException() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.writeablePipe; - Assert.True(pipe.IsConnected); - Assert.True(pipe.CanWrite); - Assert.False(pipe.CanSeek); - - // Offset must be nonnegative - AssertExtensions.Throws("offset", () => pipe.Write(new byte[5], -1, 1)); - AssertExtensions.Throws("offset", () => { pipe.WriteAsync(new byte[5], -1, 1); }); - } - } - - [Fact] - public void WriteWithNegativeCount_Throws_ArgumentOutOfRangeException() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.writeablePipe; - Assert.True(pipe.IsConnected); - Assert.True(pipe.CanWrite); - - // Count must be nonnegative - AssertExtensions.Throws("count", () => pipe.Write(new byte[5], 0, -1)); - AssertExtensions.Throws("count", () => { pipe.WriteAsync(new byte[5], 0, -1); }); - } - } - - [Fact] - public void WriteWithOutOfBoundsArray_Throws_ArgumentException() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.writeablePipe; - Assert.True(pipe.IsConnected); - Assert.True(pipe.CanWrite); - - // offset out of bounds - AssertExtensions.Throws(null, () => pipe.Write(new byte[1], 1, 1)); - - // offset out of bounds for 0 count read - AssertExtensions.Throws(null, () => pipe.Write(new byte[1], 2, 0)); - - // offset out of bounds even for 0 length buffer - AssertExtensions.Throws(null, () => pipe.Write(new byte[0], 1, 0)); - - // combination offset and count out of bounds - AssertExtensions.Throws(null, () => pipe.Write(new byte[2], 1, 2)); - - // edges - AssertExtensions.Throws(null, () => pipe.Write(new byte[0], int.MaxValue, 0)); - AssertExtensions.Throws(null, () => pipe.Write(new byte[0], int.MaxValue, int.MaxValue)); - - AssertExtensions.Throws(null, () => pipe.Write(new byte[5], 3, 4)); - - // offset out of bounds - AssertExtensions.Throws(null, () => { pipe.WriteAsync(new byte[1], 1, 1); }); - - // offset out of bounds for 0 count read - AssertExtensions.Throws(null, () => { pipe.WriteAsync(new byte[1], 2, 0); }); - - // offset out of bounds even for 0 length buffer - AssertExtensions.Throws(null, () => { pipe.WriteAsync(new byte[0], 1, 0); }); - - // combination offset and count out of bounds - AssertExtensions.Throws(null, () => { pipe.WriteAsync(new byte[2], 1, 2); }); - - // edges - AssertExtensions.Throws(null, () => { pipe.WriteAsync(new byte[0], int.MaxValue, 0); }); - AssertExtensions.Throws(null, () => { pipe.WriteAsync(new byte[0], int.MaxValue, int.MaxValue); }); - - AssertExtensions.Throws(null, () => { pipe.WriteAsync(new byte[5], 3, 4); }); - } - } - - [Fact] - public void ReadOnWriteOnlyPipe_Throws_NotSupportedException() - { - if (SupportsBidirectionalReadingWriting) - { - return; - } - - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.writeablePipe; - Assert.True(pipe.IsConnected); - Assert.False(pipe.CanRead); - - Assert.Throws(() => pipe.Read(new byte[9], 0, 5)); - - Assert.Throws(() => pipe.ReadByte()); - - Assert.Throws(() => pipe.InBufferSize); - - Assert.Throws(() => { pipe.ReadAsync(new byte[10], 0, 5); }); - } - } - - [Fact] - public async Task WriteZeroLengthBuffer_Nop() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.writeablePipe; - - // Shouldn't throw - pipe.Write(Array.Empty(), 0, 0); - - Task writeTask = pipe.WriteAsync(Array.Empty(), 0, 0); - await writeTask; - } - } - - [Fact] - public void WritePipeUnsupportedMembers_Throws_NotSupportedException() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.writeablePipe; - Assert.True(pipe.IsConnected); - - Assert.Throws(() => pipe.Length); - - Assert.Throws(() => pipe.SetLength(10L)); - - Assert.Throws(() => pipe.Position); - - Assert.Throws(() => pipe.Position = 10L); - - Assert.Throws(() => pipe.Seek(10L, System.IO.SeekOrigin.Begin)); - } - } - - [Fact] - public void WriteToDisposedWriteablePipe_Throws_ObjectDisposedException() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.writeablePipe; - pipe.Dispose(); - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - Assert.Throws(() => pipe.Write(buffer, 0, buffer.Length)); - Assert.Throws(() => pipe.WriteByte(5)); - Assert.Throws(() => { pipe.WriteAsync(buffer, 0, buffer.Length); }); - Assert.Throws(() => pipe.Flush()); - Assert.Throws(() => pipe.IsMessageComplete); - Assert.Throws(() => pipe.ReadMode); - } - } - - [Fact] - public virtual void WriteToPipeWithClosedPartner_Throws_IOException() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - if (!OperatingSystem.IsWindows() && - (pair.readablePipe is NamedPipeClientStream || pair.writeablePipe is NamedPipeClientStream)) - { - // On Unix, NamedPipe*Stream is implemented in term of sockets, where information - // about shutdown is not immediately propagated. - return; - } - - pair.readablePipe.Dispose(); - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - Assert.Throws(() => pair.writeablePipe.Write(buffer, 0, buffer.Length)); - Assert.Throws(() => pair.writeablePipe.WriteByte(123)); - Assert.Throws(() => { pair.writeablePipe.WriteAsync(buffer, 0, buffer.Length); }); - Assert.Throws(() => pair.writeablePipe.Flush()); - } - } - - [Fact] - public async Task ValidFlush_DoesntThrow() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - Task write = Task.Run(() => pair.writeablePipe.WriteByte(123)); - pair.writeablePipe.Flush(); - Assert.Equal(123, pair.readablePipe.ReadByte()); - await write; - await pair.writeablePipe.FlushAsync(); - } - } - - [Fact] - public void ReadOnWriteOnlyPipe_Span_Throws_NotSupportedException() - { - if (SupportsBidirectionalReadingWriting) - { - return; - } - - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.writeablePipe; - Assert.True(pipe.IsConnected); - Assert.False(pipe.CanRead); - - Assert.Throws(() => pipe.Read(new Span(new byte[9]))); - Assert.Throws(() => { pipe.ReadAsync(new Memory(new byte[10])); }); - } - } - - [Fact] - public async Task WriteZeroLengthBuffer_Span_Nop() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.writeablePipe; - - // Shouldn't throw - pipe.Write(new Span(Array.Empty())); - await pipe.WriteAsync(new Memory(Array.Empty())); - } - } - - [Fact] - public void WriteToDisposedWriteablePipe_Span_Throws_ObjectDisposedException() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - PipeStream pipe = pair.writeablePipe; - pipe.Dispose(); - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - Assert.Throws(() => pipe.Write(new Span(buffer))); - Assert.Throws(() => { pipe.WriteAsync(new Memory(buffer)); }); - } - } - - [Fact] - public virtual void WriteToPipeWithClosedPartner_Span_Throws_IOException() - { - using (ServerClientPair pair = CreateServerClientPair()) - { - if (!OperatingSystem.IsWindows() && - (pair.readablePipe is NamedPipeClientStream || pair.writeablePipe is NamedPipeClientStream)) - { - // On Unix, NamedPipe*Stream is implemented in term of sockets, where information - // about shutdown is not immediately propagated. - return; - } - - pair.readablePipe.Dispose(); - byte[] buffer = new byte[] { 0, 0, 0, 0 }; - - Assert.Throws(() => pair.writeablePipe.Write(new Span(buffer))); - Assert.Throws(() => { pair.writeablePipe.WriteAsync(new Memory(buffer)); }); - } - } - - [Fact] - public void DisposeAsync_NothingWrittenNeedsToBeFlushed_CompletesSynchronously() - { - ServerClientPair pair = CreateServerClientPair(); - for (int i = 0; i < 2; i++) - { - Assert.True(pair.readablePipe.DisposeAsync().IsCompletedSuccessfully); - Assert.True(pair.writeablePipe.DisposeAsync().IsCompletedSuccessfully); - } - Assert.Throws(() => pair.writeablePipe.Write(new Span(new byte[1]))); - } - } -} diff --git a/src/libraries/System.IO.Pipes/tests/PipeTestBase.cs b/src/libraries/System.IO.Pipes/tests/PipeTestBase.cs deleted file mode 100644 index 5e07256016ece1..00000000000000 --- a/src/libraries/System.IO.Pipes/tests/PipeTestBase.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Xunit; - -namespace System.IO.Pipes.Tests -{ - /// - /// The class that all Pipes tests will inherit from. - /// - /// Contains methods and variables with code that is frequently repeated - /// - public class PipeTestBase - { - protected static byte[] sendBytes = new byte[] { 123, 234 }; - - protected static void DoStreamOperations(PipeStream stream) - { - if (stream.CanWrite) - { - stream.Write(new byte[] { 123, 124 }, 0, 2); - } - if (stream.CanRead) - { - Assert.Equal(123, stream.ReadByte()); - Assert.Equal(124, stream.ReadByte()); - } - } - - protected static void SuppressClientHandleFinalizationIfNetFramework(AnonymousPipeServerStream serverStream) - { - if (PlatformDetection.IsNetFramework) - { - // See https://github.com/dotnet/corefx/pull/1871. When AnonymousPipeServerStream.GetClientHandleAsString() - // is called, the assumption is that this string is going to be passed to another process, rather than wrapped - // into a SafeHandle in the same process. If it's wrapped into a SafeHandle in the same process, there are then - // two SafeHandles that believe they own the same underlying handle, which leads to use-after-free and recycling - // bugs. AnonymousPipeServerStream incorrectly deals with this in desktop: it marks the SafeHandle as having - // been exposed, but that then only prevents the disposal of AnonymousPipeServerStream from calling Dispose - // on the SafeHandle... it doesn't prevent the SafeHandle itself from getting finalized, which leads to random - // "The handle is invalid" or "Pipe is broken" errors at some later point when the handle is recycled and used - // for another instance. In core, this was addressed in 1871 by calling GC.SuppressFinalize(_clientHandle) - // in GetClientHandleAsString. For desktop, we work around this by suppressing the handle in this explicit call. - GC.SuppressFinalize(serverStream.ClientSafePipeHandle); - } - } - - /// - /// Represents a Server-Client pair where "readablePipe" refers to whichever - /// of the two streams is defined with PipeDirection.In and "writeablePipe" is - /// defined with PipeDirection.Out. - /// - /// - /// For tests where InOut is used, writeablePipe will refer to whichever pipe - /// the tests should be treating as the one with PipeDirection.Out. - /// - protected class ServerClientPair : IDisposable - { - public PipeStream readablePipe; - public PipeStream writeablePipe; - - public void Dispose() - { - try - { - if (readablePipe != null) - readablePipe.Dispose(); - } - finally - { - writeablePipe.Dispose(); - } - } - } - - /// - /// Get a unique pipe name very unlikely to be in use elsewhere. - /// - /// - protected static string GetUniquePipeName() - { - if (PlatformDetection.IsInAppContainer) - { - return @"LOCAL\" + Path.GetRandomFileName(); - } - else - { - return Path.GetRandomFileName(); - } - } - - /// - /// Virtual method to create a Server-Client PipeStream pair - /// that the test methods can override and make use of. - /// - /// The default (in PipeTest) will return a null ServerClientPair. - /// - protected virtual ServerClientPair CreateServerClientPair() - { - return null; - } - } -} diff --git a/src/libraries/System.IO.Pipes/tests/System.IO.Pipes.Tests.csproj b/src/libraries/System.IO.Pipes/tests/System.IO.Pipes.Tests.csproj index ead46c1faaa2e1..90c00efb8d3cbe 100644 --- a/src/libraries/System.IO.Pipes/tests/System.IO.Pipes.Tests.csproj +++ b/src/libraries/System.IO.Pipes/tests/System.IO.Pipes.Tests.csproj @@ -7,59 +7,47 @@ + - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + - + - + From c6fa2f18a0b375b0f2bbe96e212b2498abb89f7d Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:37:43 -0400 Subject: [PATCH 17/24] Fix several BrotliStream behaviors 1. When passed a null buffer, it's throwing an exception with the argument name "array", even though the parameter's name is "buffer". 2. Even if there's no data written as part of the Flush{Async} call on a writeable stream, it should be calling flush on the wrapped stream. --- .../src/System/IO/Compression/BrotliStream.cs | 8 ++++---- .../System/IO/Compression/enc/BrotliStream.Compress.cs | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.IO.Compression.Brotli/src/System/IO/Compression/BrotliStream.cs b/src/libraries/System.IO.Compression.Brotli/src/System/IO/Compression/BrotliStream.cs index 4bca2bac59c148..e865158b03e9da 100644 --- a/src/libraries/System.IO.Compression.Brotli/src/System/IO/Compression/BrotliStream.cs +++ b/src/libraries/System.IO.Compression.Brotli/src/System/IO/Compression/BrotliStream.cs @@ -112,10 +112,10 @@ private void ReleaseStateForDispose() } } - private static void ValidateParameters(byte[] array, int offset, int count) + private static void ValidateParameters(byte[] buffer, int offset, int count) { - if (array == null) - throw new ArgumentNullException(nameof(array)); + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedPosNum); @@ -123,7 +123,7 @@ private static void ValidateParameters(byte[] array, int offset, int count) if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedPosNum); - if (array.Length - offset < count) + if (buffer.Length - offset < count) throw new ArgumentException(SR.InvalidArgumentOffsetCount); } diff --git a/src/libraries/System.IO.Compression.Brotli/src/System/IO/Compression/enc/BrotliStream.Compress.cs b/src/libraries/System.IO.Compression.Brotli/src/System/IO/Compression/enc/BrotliStream.Compress.cs index 87171b9c1ee3be..61a94832887f4d 100644 --- a/src/libraries/System.IO.Compression.Brotli/src/System/IO/Compression/enc/BrotliStream.Compress.cs +++ b/src/libraries/System.IO.Compression.Brotli/src/System/IO/Compression/enc/BrotliStream.Compress.cs @@ -127,6 +127,8 @@ public override void Flush() _stream.Write(output.Slice(0, bytesWritten)); } } + + _stream.Flush(); } } @@ -160,6 +162,8 @@ private async Task FlushAsyncCore(CancellationToken cancellationToken) if (bytesWritten > 0) await _stream.WriteAsync(output.Slice(0, bytesWritten), cancellationToken).ConfigureAwait(false); } + + await _stream.FlushAsync(cancellationToken).ConfigureAwait(false); } finally { From af13fb940b6521e83c1551cb21442f1b116e2b5e Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:41:48 -0400 Subject: [PATCH 18/24] Fix several DeflateStream (and friends) issues 1. DeflateStream.Flush{Async} when writing needs to always flush the underlying stream, even if there's no data written as part of the flush call itself. 2. Several byte[] array arguments should be byte[] buffer. Technically a breaking change, but the current divergence from the base Stream class is a bug, and bringing them into sync means argument exceptions that emerge from the derived type make sense when used via the base type, as well as then being able to use shared validation logic across all streams (subsequent to these changes). 3. DeflateStream.EndRead/Write needs to do additional state validation. 4. Not a bug, but simplify ReadAsync to match the sync Read implementation flow. --- .../ref/System.IO.Compression.cs | 34 +- .../DeflateManaged/DeflateManagedStream.cs | 28 +- .../Compression/DeflateZLib/DeflateStream.cs | 337 ++++++++---------- .../src/System/IO/Compression/GZipStream.cs | 28 +- .../src/System/IO/Compression/ZLibStream.cs | 36 +- 5 files changed, 209 insertions(+), 254 deletions(-) diff --git a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs index 0a8d9639e9a551..b98fca5db50b9b 100644 --- a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs +++ b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs @@ -30,7 +30,7 @@ public DeflateStream(System.IO.Stream stream, System.IO.Compression.CompressionM public override long Length { get { throw null; } } public override long Position { get { throw null; } set { } } public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int count, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; } - public override System.IAsyncResult BeginWrite(byte[] array, int offset, int count, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; } + public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int count, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; } public override void CopyTo(System.IO.Stream destination, int bufferSize) { } public override System.Threading.Tasks.Task CopyToAsync(System.IO.Stream destination, int bufferSize, System.Threading.CancellationToken cancellationToken) { throw null; } protected override void Dispose(bool disposing) { } @@ -39,16 +39,16 @@ protected override void Dispose(bool disposing) { } public override void EndWrite(System.IAsyncResult asyncResult) { } public override void Flush() { } public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; } - public override int Read(byte[] array, int offset, int count) { throw null; } + public override int Read(byte[] buffer, int offset, int count) { throw null; } public override int Read(System.Span buffer) { throw null; } - public override System.Threading.Tasks.Task ReadAsync(byte[] array, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } + public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public override int ReadByte() { throw null; } public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } public override void SetLength(long value) { } - public override void Write(byte[] array, int offset, int count) { } + public override void Write(byte[] buffer, int offset, int count) { } public override void Write(System.ReadOnlySpan buffer) { } - public override System.Threading.Tasks.Task WriteAsync(byte[] array, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } + public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } public partial class GZipStream : System.IO.Stream @@ -63,8 +63,8 @@ public GZipStream(System.IO.Stream stream, System.IO.Compression.CompressionMode public override bool CanWrite { get { throw null; } } public override long Length { get { throw null; } } public override long Position { get { throw null; } set { } } - public override System.IAsyncResult BeginRead(byte[] array, int offset, int count, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; } - public override System.IAsyncResult BeginWrite(byte[] array, int offset, int count, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; } + public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int count, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; } + public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int count, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; } public override void CopyTo(System.IO.Stream destination, int bufferSize) { } public override System.Threading.Tasks.Task CopyToAsync(System.IO.Stream destination, int bufferSize, System.Threading.CancellationToken cancellationToken) { throw null; } protected override void Dispose(bool disposing) { } @@ -73,16 +73,16 @@ protected override void Dispose(bool disposing) { } public override void EndWrite(System.IAsyncResult asyncResult) { } public override void Flush() { } public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; } - public override int Read(byte[] array, int offset, int count) { throw null; } + public override int Read(byte[] buffer, int offset, int count) { throw null; } public override int Read(System.Span buffer) { throw null; } - public override System.Threading.Tasks.Task ReadAsync(byte[] array, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } + public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public override int ReadByte() { throw null; } public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } public override void SetLength(long value) { } - public override void Write(byte[] array, int offset, int count) { } + public override void Write(byte[] buffer, int offset, int count) { } public override void Write(System.ReadOnlySpan buffer) { } - public override System.Threading.Tasks.Task WriteAsync(byte[] array, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } + public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } public partial class ZipArchive : System.IDisposable @@ -133,8 +133,8 @@ public ZLibStream(System.IO.Stream stream, System.IO.Compression.CompressionMode public override bool CanWrite { get { throw null; } } public override long Length { get { throw null; } } public override long Position { get { throw null; } set { } } - public override System.IAsyncResult BeginRead(byte[] array, int offset, int count, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; } - public override System.IAsyncResult BeginWrite(byte[] array, int offset, int count, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; } + public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int count, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; } + public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int count, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; } public override void CopyTo(System.IO.Stream destination, int bufferSize) { } public override System.Threading.Tasks.Task CopyToAsync(System.IO.Stream destination, int bufferSize, System.Threading.CancellationToken cancellationToken) { throw null; } protected override void Dispose(bool disposing) { } @@ -143,17 +143,17 @@ protected override void Dispose(bool disposing) { } public override void EndWrite(System.IAsyncResult asyncResult) { } public override void Flush() { } public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; } - public override int Read(byte[] array, int offset, int count) { throw null; } + public override int Read(byte[] buffer, int offset, int count) { throw null; } public override int Read(System.Span buffer) { throw null; } - public override System.Threading.Tasks.Task ReadAsync(byte[] array, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } + public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public override int ReadByte() { throw null; } public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } public override void SetLength(long value) { } - public override void Write(byte[] array, int offset, int count) { } + public override void Write(byte[] buffer, int offset, int count) { } public override void Write(System.ReadOnlySpan buffer) { } public override void WriteByte(byte value) { } - public override System.Threading.Tasks.Task WriteAsync(byte[] array, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } + public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } } diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateManaged/DeflateManagedStream.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateManaged/DeflateManagedStream.cs index ea6644fd9279ea..0c2a6f02ff38cc 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateManaged/DeflateManagedStream.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateManaged/DeflateManagedStream.cs @@ -94,9 +94,9 @@ public override void SetLength(long value) throw new NotSupportedException(SR.NotSupported); } - public override int Read(byte[] array, int offset, int count) + public override int Read(byte[] buffer, int offset, int count) { - ValidateParameters(array, offset, count); + ValidateParameters(buffer, offset, count); EnsureNotDisposed(); int bytesRead; @@ -105,7 +105,7 @@ public override int Read(byte[] array, int offset, int count) while (true) { - bytesRead = _inflater.Inflate(array, currentOffset, remainingCount); + bytesRead = _inflater.Inflate(buffer, currentOffset, remainingCount); currentOffset += bytesRead; remainingCount -= bytesRead; @@ -139,10 +139,10 @@ public override int Read(byte[] array, int offset, int count) return count - remainingCount; } - private void ValidateParameters(byte[] array, int offset, int count) + private void ValidateParameters(byte[] buffer, int offset, int count) { - if (array == null) - throw new ArgumentNullException(nameof(array)); + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); @@ -150,7 +150,7 @@ private void ValidateParameters(byte[] array, int offset, int count) if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); - if (array.Length - offset < count) + if (buffer.Length - offset < count) throw new ArgumentException(SR.InvalidArgumentOffsetCount); } @@ -171,13 +171,13 @@ public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, Asy public override int EndRead(IAsyncResult asyncResult) => TaskToApm.End(asyncResult); - public override Task ReadAsync(byte[] array, int offset, int count, CancellationToken cancellationToken) + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { // We use this checking order for compat to earlier versions: if (_asyncOperations != 0) throw new InvalidOperationException(SR.InvalidBeginCall); - ValidateParameters(array, offset, count); + ValidateParameters(buffer, offset, count); EnsureNotDisposed(); if (cancellationToken.IsCancellationRequested) @@ -191,7 +191,7 @@ public override Task ReadAsync(byte[] array, int offset, int count, Cancell try { // Try to read decompressed data in output buffer - int bytesRead = _inflater.Inflate(array, offset, count); + int bytesRead = _inflater.Inflate(buffer, offset, count); if (bytesRead != 0) { // If decompression output buffer is not empty, return immediately. @@ -212,7 +212,7 @@ public override Task ReadAsync(byte[] array, int offset, int count, Cancell throw new InvalidOperationException(SR.NotSupported_UnreadableStream); } - return ReadAsyncCore(readTask, array, offset, count, cancellationToken); + return ReadAsyncCore(readTask, buffer, offset, count, cancellationToken); } finally { @@ -224,7 +224,7 @@ public override Task ReadAsync(byte[] array, int offset, int count, Cancell } } - private async Task ReadAsyncCore(Task readTask, byte[] array, int offset, int count, CancellationToken cancellationToken) + private async Task ReadAsyncCore(Task readTask, byte[] buffer, int offset, int count, CancellationToken cancellationToken) { try { @@ -249,7 +249,7 @@ private async Task ReadAsyncCore(Task readTask, byte[] array, int offs // Feed the data from base stream into decompression engine _inflater.SetInput(_buffer, 0, bytesRead); - bytesRead = _inflater.Inflate(array, offset, count); + bytesRead = _inflater.Inflate(buffer, offset, count); if (bytesRead == 0 && !_inflater.Finished()) { @@ -273,7 +273,7 @@ private async Task ReadAsyncCore(Task readTask, byte[] array, int offs } } - public override void Write(byte[] array, int offset, int count) + public override void Write(byte[] buffer, int offset, int count) { throw new InvalidOperationException(SR.CannotWriteToDeflateStream); } diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/DeflateStream.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/DeflateStream.cs index 4a34fe859a58b1..c2c759888c3b41 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/DeflateStream.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/DeflateStream.cs @@ -174,34 +174,40 @@ public override Task FlushAsync(CancellationToken cancellationToken) if (cancellationToken.IsCancellationRequested) return Task.FromCanceled(cancellationToken); - return _mode != CompressionMode.Compress || !_wroteBytes ? Task.CompletedTask : FlushAsyncCore(cancellationToken); - } + return _mode != CompressionMode.Compress ? + Task.CompletedTask : + Core(cancellationToken); - private async Task FlushAsyncCore(CancellationToken cancellationToken) - { - AsyncOperationStarting(); - try + async Task Core(CancellationToken cancellationToken) { - Debug.Assert(_deflater != null && _buffer != null); - // Compress any bytes left: - await WriteDeflaterOutputAsync(cancellationToken).ConfigureAwait(false); - - // Pull out any bytes left inside deflater: - bool flushSuccessful; - do + AsyncOperationStarting(); + try { - int compressedBytes; - flushSuccessful = _deflater.Flush(_buffer, out compressedBytes); - if (flushSuccessful) + Debug.Assert(_deflater != null && _buffer != null); + + // Compress any bytes left: + await WriteDeflaterOutputAsync(cancellationToken).ConfigureAwait(false); + + // Pull out any bytes left inside deflater: + bool flushSuccessful; + do { - await _stream.WriteAsync(new ReadOnlyMemory(_buffer, 0, compressedBytes), cancellationToken).ConfigureAwait(false); - } - Debug.Assert(flushSuccessful == (compressedBytes > 0)); - } while (flushSuccessful); - } - finally - { - AsyncOperationCompleting(); + int compressedBytes; + flushSuccessful = _deflater.Flush(_buffer, out compressedBytes); + if (flushSuccessful) + { + await _stream.WriteAsync(new ReadOnlyMemory(_buffer, 0, compressedBytes), cancellationToken).ConfigureAwait(false); + } + Debug.Assert(flushSuccessful == (compressedBytes > 0)); + } while (flushSuccessful); + + // Always flush on the underlying stream + await _stream.FlushAsync(cancellationToken).ConfigureAwait(false); + } + finally + { + AsyncOperationCompleting(); + } } } @@ -227,10 +233,10 @@ public override int ReadByte() return _inflater.Inflate(out b) ? b : base.ReadByte(); } - public override int Read(byte[] array, int offset, int count) + public override int Read(byte[] buffer, int offset, int count) { - ValidateParameters(array, offset, count); - return ReadCore(new Span(array, offset, count)); + ValidateParameters(buffer, offset, count); + return ReadCore(new Span(buffer, offset, count)); } public override int Read(Span buffer) @@ -297,10 +303,10 @@ internal int ReadCore(Span buffer) return totalRead; } - private void ValidateParameters(byte[] array, int offset, int count) + private void ValidateParameters(byte[] buffer, int offset, int count) { - if (array == null) - throw new ArgumentNullException(nameof(array)); + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); @@ -308,8 +314,8 @@ private void ValidateParameters(byte[] array, int offset, int count) if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); - if (array.Length - offset < count) - throw new ArgumentException(SR.InvalidArgumentOffsetCount); + if (buffer.Length - offset < count) + throw new ArgumentOutOfRangeException(SR.InvalidArgumentOffsetCount); } private void EnsureNotDisposed() @@ -348,13 +354,17 @@ private static void ThrowCannotWriteToDeflateStreamException() public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? asyncCallback, object? asyncState) => TaskToApm.Begin(ReadAsync(buffer, offset, count, CancellationToken.None), asyncCallback, asyncState); - public override int EndRead(IAsyncResult asyncResult) => - TaskToApm.End(asyncResult); + public override int EndRead(IAsyncResult asyncResult) + { + EnsureDecompressionMode(); + EnsureNotDisposed(); + return TaskToApm.End(asyncResult); + } - public override Task ReadAsync(byte[] array, int offset, int count, CancellationToken cancellationToken) + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - ValidateParameters(array, offset, count); - return ReadAsyncMemory(new Memory(array, offset, count), cancellationToken).AsTask(); + ValidateParameters(buffer, offset, count); + return ReadAsyncMemory(new Memory(buffer, offset, count), cancellationToken).AsTask(); } public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default(CancellationToken)) @@ -384,128 +394,67 @@ internal ValueTask ReadAsyncMemory(Memory buffer, CancellationToken c EnsureBufferInitialized(); - bool cleanup = true; - AsyncOperationStarting(); - try - { - Debug.Assert(_inflater != null); - while (true) - { - // Finish inflating any bytes in the input buffer - int bytesRead = 0, bytesReadIteration = -1; - while (bytesRead < buffer.Length && bytesReadIteration != 0) - { - bytesReadIteration = _inflater.Inflate(buffer.Span.Slice(bytesRead)); - bytesRead += bytesReadIteration; - } - - if (bytesRead != 0) - { - // If decompression output buffer is not empty, return immediately. - return new ValueTask(bytesRead); - } - - // If the stream is finished then we have a few potential cases here: - // 1. DeflateStream that is finished => return - // 2. GZipStream that is finished but may have an additional GZipStream appended => feed more input - // 3. GZipStream that is finished and appended with garbage => return - if (_inflater.Finished() && (!_inflater.IsGzipStream() || !_inflater.NeedsInput())) - { - return new ValueTask(0); - } + return Core(buffer, cancellationToken); - if (_inflater.NeedsInput()) - { - // If there is no data on the output buffer and we are not at - // the end of the stream, we need to get more data from the base stream - ValueTask readTask = _stream.ReadAsync(_buffer, cancellationToken); - cleanup = false; - return FinishReadAsyncMemory(readTask, buffer, cancellationToken); - } - } - } - finally + async ValueTask Core(Memory buffer, CancellationToken cancellationToken) { - // if we haven't started any async work, decrement the counter to end the transaction - if (cleanup) + AsyncOperationStarting(); + try { - AsyncOperationCompleting(); - } - } - } + int totalRead = 0; - private async ValueTask FinishReadAsyncMemory( - ValueTask readTask, Memory buffer, CancellationToken cancellationToken) - { - try - { - Debug.Assert(_inflater != null && _buffer != null); - while (true) - { - if (_inflater.NeedsInput()) + Debug.Assert(_inflater != null); + while (true) { - int bytesRead = await readTask.ConfigureAwait(false); - EnsureNotDisposed(); - - if (bytesRead <= 0) + int bytesRead = _inflater.Inflate(buffer.Span.Slice(totalRead)); + totalRead += bytesRead; + if (totalRead == buffer.Length) { - // This indicates the base stream has received EOF - return 0; + break; } - else if (bytesRead > _buffer.Length) + + // If the stream is finished then we have a few potential cases here: + // 1. DeflateStream => return + // 2. GZipStream that is finished but may have an additional GZipStream appended => feed more input + // 3. GZipStream that is finished and appended with garbage => return + if (_inflater.Finished() && (!_inflater.IsGzipStream() || !_inflater.NeedsInput())) { - // The stream is either malicious or poorly implemented and returned a number of - // bytes larger than the buffer supplied to it. - throw new InvalidDataException(SR.GenericInvalidData); + break; } - cancellationToken.ThrowIfCancellationRequested(); - - // Feed the data from base stream into decompression engine - _inflater.SetInput(_buffer, 0, bytesRead); - } + if (_inflater.NeedsInput()) + { + Debug.Assert(_buffer != null); + int bytes = await _stream.ReadAsync(_buffer, cancellationToken).ConfigureAwait(false); + EnsureNotDisposed(); + if (bytes <= 0) + { + break; + } + else if (bytes > _buffer.Length) + { + // The stream is either malicious or poorly implemented and returned a number of + // bytes larger than the buffer supplied to it. + throw new InvalidDataException(SR.GenericInvalidData); + } - // Finish inflating any bytes in the input buffer - int inflatedBytes = 0, bytesReadIteration = -1; - while (inflatedBytes < buffer.Length && bytesReadIteration != 0) - { - bytesReadIteration = _inflater.Inflate(buffer.Span.Slice(inflatedBytes)); - inflatedBytes += bytesReadIteration; + _inflater.SetInput(_buffer, 0, bytes); + } } - // There are a few different potential states here - // 1. DeflateStream or GZipStream that succesfully read bytes => return those bytes - // 2. DeflateStream or GZipStream that didn't read bytes and isn't finished => feed more input - // 3. DeflateStream that didn't read bytes, but is finished => return 0 - // 4. GZipStream that is finished but is appended with another gzip stream => feed more input - // 5. GZipStream that is finished and appended with garbage => return 0 - if (inflatedBytes != 0) - { - // If decompression output buffer is not empty, return immediately. - return inflatedBytes; - } - else if (_inflater.Finished() && (!_inflater.IsGzipStream() || !_inflater.NeedsInput())) - { - return 0; - } - else if (_inflater.NeedsInput()) - { - // We could have read in head information and didn't get any data. - // Read from the base stream again. - readTask = _stream.ReadAsync(_buffer, cancellationToken); - } + return totalRead; + } + finally + { + AsyncOperationCompleting(); } - } - finally - { - AsyncOperationCompleting(); } } - public override void Write(byte[] array, int offset, int count) + public override void Write(byte[] buffer, int offset, int count) { - ValidateParameters(array, offset, count); - WriteCore(new ReadOnlySpan(array, offset, count)); + ValidateParameters(buffer, offset, count); + WriteCore(new ReadOnlySpan(buffer, offset, count)); } public override void Write(ReadOnlySpan buffer) @@ -560,7 +509,6 @@ private void WriteDeflaterOutput() // This is called by Flush: private void FlushBuffers() { - // Make sure to only "flush" when we actually had some input: if (_wroteBytes) { // Compress any bytes left: @@ -580,6 +528,9 @@ private void FlushBuffers() Debug.Assert(flushSuccessful == (compressedBytes > 0)); } while (flushSuccessful); } + + // Always flush on the underlying stream + _stream.Flush(); } // This is called by Dispose: @@ -729,48 +680,48 @@ protected override void Dispose(bool disposing) public override ValueTask DisposeAsync() { return GetType() == typeof(DeflateStream) ? - DisposeAsyncCore() : + Core() : base.DisposeAsync(); - } - private async ValueTask DisposeAsyncCore() - { - // Same logic as Dispose(true), except with async counterparts. - try - { - await PurgeBuffersAsync().ConfigureAwait(false); - } - finally + async ValueTask Core() { - // Close the underlying stream even if PurgeBuffers threw. - // Stream.Close() may throw here (may or may not be due to the same error). - // In this case, we still need to clean up internal resources, hence the inner finally blocks. - Stream stream = _stream; - _stream = null!; + // Same logic as Dispose(true), except with async counterparts. try { - if (!_leaveOpen && stream != null) - await stream.DisposeAsync().ConfigureAwait(false); + await PurgeBuffersAsync().ConfigureAwait(false); } finally { + // Close the underlying stream even if PurgeBuffers threw. + // Stream.Close() may throw here (may or may not be due to the same error). + // In this case, we still need to clean up internal resources, hence the inner finally blocks. + Stream stream = _stream; + _stream = null!; try { - _deflater?.Dispose(); - _inflater?.Dispose(); + if (!_leaveOpen && stream != null) + await stream.DisposeAsync().ConfigureAwait(false); } finally { - _deflater = null; - _inflater = null; - - byte[]? buffer = _buffer; - if (buffer != null) + try { - _buffer = null; - if (!AsyncOperationIsActive) + _deflater?.Dispose(); + _inflater?.Dispose(); + } + finally + { + _deflater = null; + _inflater = null; + + byte[]? buffer = _buffer; + if (buffer != null) { - ArrayPool.Shared.Return(buffer); + _buffer = null; + if (!AsyncOperationIsActive) + { + ArrayPool.Shared.Return(buffer); + } } } } @@ -778,16 +729,20 @@ private async ValueTask DisposeAsyncCore() } } - public override IAsyncResult BeginWrite(byte[] array, int offset, int count, AsyncCallback? asyncCallback, object? asyncState) => - TaskToApm.Begin(WriteAsync(array, offset, count, CancellationToken.None), asyncCallback, asyncState); + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? asyncCallback, object? asyncState) => + TaskToApm.Begin(WriteAsync(buffer, offset, count, CancellationToken.None), asyncCallback, asyncState); - public override void EndWrite(IAsyncResult asyncResult) => + public override void EndWrite(IAsyncResult asyncResult) + { + EnsureCompressionMode(); + EnsureNotDisposed(); TaskToApm.End(asyncResult); + } - public override Task WriteAsync(byte[] array, int offset, int count, CancellationToken cancellationToken) + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - ValidateParameters(array, offset, count); - return WriteAsyncMemory(new ReadOnlyMemory(array, offset, count), cancellationToken).AsTask(); + ValidateParameters(buffer, offset, count); + return WriteAsyncMemory(new ReadOnlyMemory(buffer, offset, count), cancellationToken).AsTask(); } public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) @@ -812,27 +767,27 @@ internal ValueTask WriteAsyncMemory(ReadOnlyMemory buffer, CancellationTok return cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled(cancellationToken) : - WriteAsyncMemoryCore(buffer, cancellationToken); - } + Core(buffer, cancellationToken); - private async ValueTask WriteAsyncMemoryCore(ReadOnlyMemory buffer, CancellationToken cancellationToken) - { - AsyncOperationStarting(); - try + async ValueTask Core(ReadOnlyMemory buffer, CancellationToken cancellationToken) { - await WriteDeflaterOutputAsync(cancellationToken).ConfigureAwait(false); + AsyncOperationStarting(); + try + { + await WriteDeflaterOutputAsync(cancellationToken).ConfigureAwait(false); - Debug.Assert(_deflater != null); - // Pass new bytes through deflater - _deflater.SetInput(buffer); + Debug.Assert(_deflater != null); + // Pass new bytes through deflater + _deflater.SetInput(buffer); - await WriteDeflaterOutputAsync(cancellationToken).ConfigureAwait(false); + await WriteDeflaterOutputAsync(cancellationToken).ConfigureAwait(false); - _wroteBytes = true; - } - finally - { - AsyncOperationCompleting(); + _wroteBytes = true; + } + finally + { + AsyncOperationCompleting(); + } } } diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipStream.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipStream.cs index bbdf8b3dfe62e9..8e674d13b97a82 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipStream.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipStream.cs @@ -71,16 +71,16 @@ public override int ReadByte() return _deflateStream.ReadByte(); } - public override IAsyncResult BeginRead(byte[] array, int offset, int count, AsyncCallback? asyncCallback, object? asyncState) => - TaskToApm.Begin(ReadAsync(array, offset, count, CancellationToken.None), asyncCallback, asyncState); + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? asyncCallback, object? asyncState) => + TaskToApm.Begin(ReadAsync(buffer, offset, count, CancellationToken.None), asyncCallback, asyncState); public override int EndRead(IAsyncResult asyncResult) => - TaskToApm.End(asyncResult); + _deflateStream.EndRead(asyncResult); - public override int Read(byte[] array, int offset, int count) + public override int Read(byte[] buffer, int offset, int count) { CheckDeflateStream(); - return _deflateStream.Read(array, offset, count); + return _deflateStream.Read(buffer, offset, count); } public override int Read(Span buffer) @@ -99,16 +99,16 @@ public override int Read(Span buffer) } } - public override IAsyncResult BeginWrite(byte[] array, int offset, int count, AsyncCallback? asyncCallback, object? asyncState) => - TaskToApm.Begin(WriteAsync(array, offset, count, CancellationToken.None), asyncCallback, asyncState); + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? asyncCallback, object? asyncState) => + TaskToApm.Begin(WriteAsync(buffer, offset, count, CancellationToken.None), asyncCallback, asyncState); public override void EndWrite(IAsyncResult asyncResult) => - TaskToApm.End(asyncResult); + _deflateStream.EndWrite(asyncResult); - public override void Write(byte[] array, int offset, int count) + public override void Write(byte[] buffer, int offset, int count) { CheckDeflateStream(); - _deflateStream.Write(array, offset, count); + _deflateStream.Write(buffer, offset, count); } public override void Write(ReadOnlySpan buffer) @@ -168,10 +168,10 @@ public override ValueTask DisposeAsync() public Stream BaseStream => _deflateStream?.BaseStream!; - public override Task ReadAsync(byte[] array, int offset, int count, CancellationToken cancellationToken) + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { CheckDeflateStream(); - return _deflateStream.ReadAsync(array, offset, count, cancellationToken); + return _deflateStream.ReadAsync(buffer, offset, count, cancellationToken); } public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default(CancellationToken)) @@ -190,10 +190,10 @@ public override Task ReadAsync(byte[] array, int offset, int count, Cancell } } - public override Task WriteAsync(byte[] array, int offset, int count, CancellationToken cancellationToken) + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { CheckDeflateStream(); - return _deflateStream.WriteAsync(array, offset, count, cancellationToken); + return _deflateStream.WriteAsync(buffer, offset, count, cancellationToken); } public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibStream.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibStream.cs index 73cb438c1030c4..96a3b30cfe759e 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibStream.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibStream.cs @@ -95,16 +95,16 @@ public override int ReadByte() } /// Begins an asynchronous read operation. - /// The byte array to read the data into. + /// The byte array to read the data into. /// The byte offset in array at which to begin reading data from the stream. /// The maximum number of bytes to read. /// An optional asynchronous callback, to be called when the read operation is complete. /// A user-provided object that distinguishes this particular asynchronous read request from other requests. /// An object that represents the asynchronous read operation, which could still be pending. - public override IAsyncResult BeginRead(byte[] array, int offset, int count, AsyncCallback? asyncCallback, object? asyncState) + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? asyncCallback, object? asyncState) { ThrowIfClosed(); - return _deflateStream.BeginRead(array, offset, count, asyncCallback, asyncState); + return _deflateStream.BeginRead(buffer, offset, count, asyncCallback, asyncState); } /// Waits for the pending asynchronous read to complete. @@ -114,14 +114,14 @@ public override int EndRead(IAsyncResult asyncResult) => _deflateStream.EndRead(asyncResult); /// Reads a number of decompressed bytes into the specified byte array. - /// The byte array to read the data into. + /// The byte array to read the data into. /// The byte offset in array at which to begin reading data from the stream. /// The maximum number of bytes to read. /// The number of bytes that were read into the byte array. - public override int Read(byte[] array, int offset, int count) + public override int Read(byte[] buffer, int offset, int count) { ThrowIfClosed(); - return _deflateStream.Read(array, offset, count); + return _deflateStream.Read(buffer, offset, count); } /// Reads a number of decompressed bytes into the specified byte span. @@ -134,15 +134,15 @@ public override int Read(Span buffer) } /// Asynchronously reads a sequence of bytes from the current stream, advances the position within the stream by the number of bytes read, and monitors cancellation requests. - /// The byte array to read the data into. + /// The byte array to read the data into. /// The byte offset in array at which to begin reading data from the stream. /// The maximum number of bytes to read. /// The token to monitor for cancellation requests. /// A task that represents the asynchronous completion of the operation. - public override Task ReadAsync(byte[] array, int offset, int count, CancellationToken cancellationToken) + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { ThrowIfClosed(); - return _deflateStream.ReadAsync(array, offset, count, cancellationToken); + return _deflateStream.ReadAsync(buffer, offset, count, cancellationToken); } /// Asynchronously reads a sequence of bytes from the current stream, advances the position within the stream by the number of bytes read, and monitors cancellation requests. @@ -156,16 +156,16 @@ public override ValueTask ReadAsync(Memory buffer, CancellationToken } /// Begins an asynchronous write operation. - /// The buffer to write data from. + /// The buffer to write data from. /// The byte offset in buffer to begin writing from. /// The maximum number of bytes to write. /// An optional asynchronous callback, to be called when the write operation is complete. /// A user-provided object that distinguishes this particular asynchronous write request from other requests. /// An object that represents the asynchronous write operation, which could still be pending. - public override IAsyncResult BeginWrite(byte[] array, int offset, int count, AsyncCallback? asyncCallback, object? asyncState) + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? asyncCallback, object? asyncState) { ThrowIfClosed(); - return _deflateStream.BeginWrite(array, offset, count, asyncCallback, asyncState); + return _deflateStream.BeginWrite(buffer, offset, count, asyncCallback, asyncState); } /// Ends an asynchronous write operation. @@ -174,13 +174,13 @@ public override void EndWrite(IAsyncResult asyncResult) => _deflateStream.EndWrite(asyncResult); /// Writes compressed bytes to the underlying stream from the specified byte array. - /// The buffer to write data from. + /// The buffer to write data from. /// The byte offset in buffer to begin writing from. /// The maximum number of bytes to write. - public override void Write(byte[] array, int offset, int count) + public override void Write(byte[] buffer, int offset, int count) { ThrowIfClosed(); - _deflateStream.Write(array, offset, count); + _deflateStream.Write(buffer, offset, count); } /// Writes compressed bytes to the underlying stream from the specified byte span. @@ -192,15 +192,15 @@ public override void Write(ReadOnlySpan buffer) } /// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests. - /// The buffer to write data from. + /// The buffer to write data from. /// The byte offset in buffer to begin writing from. /// The maximum number of bytes to write. /// The token to monitor for cancellation requests. /// A task that represents the asynchronous completion of the operation. - public override Task WriteAsync(byte[] array, int offset, int count, CancellationToken cancellationToken) + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { ThrowIfClosed(); - return _deflateStream.WriteAsync(array, offset, count, cancellationToken); + return _deflateStream.WriteAsync(buffer, offset, count, cancellationToken); } /// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests. From 296b99a90f1ce1d32f81067ec53cc07dc1d20679 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 12:44:07 -0400 Subject: [PATCH 19/24] Add stream conformance tests for Deflate/ZLib/GZip/BrotliStream --- .../Compression/CompressionStreamTestBase.cs | 22 +- .../CompressionStreamUnitTestBase.cs | 914 +----------------- .../tests/BrotliEncoderTests.cs | 411 -------- .../CompressionStreamUnitTests.Brotli.cs | 369 ++++++- .../System.IO.Compression.Brotli.Tests.csproj | 6 +- .../CompressionStreamUnitTests.Deflate.cs | 16 +- .../tests/CompressionStreamUnitTests.Gzip.cs | 16 +- .../tests/System.IO.Compression.Tests.csproj | 7 +- 8 files changed, 435 insertions(+), 1326 deletions(-) delete mode 100644 src/libraries/System.IO.Compression.Brotli/tests/BrotliEncoderTests.cs diff --git a/src/libraries/Common/tests/System/IO/Compression/CompressionStreamTestBase.cs b/src/libraries/Common/tests/System/IO/Compression/CompressionStreamTestBase.cs index e9ec7cd3ad363f..366c547d2c600d 100644 --- a/src/libraries/Common/tests/System/IO/Compression/CompressionStreamTestBase.cs +++ b/src/libraries/Common/tests/System/IO/Compression/CompressionStreamTestBase.cs @@ -2,10 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.IO.Tests; +using System.Threading.Tasks; namespace System.IO.Compression { - public abstract class CompressionTestBase + public abstract class CompressionTestBase : WrappingConnectedStreamConformanceTests { public static IEnumerable UncompressedTestFiles() { @@ -36,8 +38,22 @@ public abstract class CompressionStreamTestBase : CompressionTestBase public abstract Stream CreateStream(Stream stream, CompressionLevel level); public abstract Stream CreateStream(Stream stream, CompressionLevel level, bool leaveOpen); public abstract Stream BaseStream(Stream stream); - public virtual bool FlushCompletes { get => true; } - public virtual bool FlushNoOps { get => false; } public virtual int BufferSize { get => 8192; } + + protected override Task CreateConnectedStreamsAsync() + { + (Stream stream1, Stream stream2) = ConnectedStreams.CreateBidirectional(4 * 1024, 16 * 1024); + return Task.FromResult((CreateStream(stream1, CompressionMode.Compress), CreateStream(stream2, CompressionMode.Decompress))); + } + + protected override Task CreateWrappedConnectedStreamsAsync(StreamPair wrapped, bool leaveOpen) => + Task.FromResult((CreateStream(wrapped.Stream1, CompressionMode.Compress, leaveOpen), CreateStream(wrapped.Stream2, CompressionMode.Decompress, leaveOpen))); + + protected override int BufferedSize => 16 * 1024 + BufferSize; + protected override bool UsableAfterCanceledReads => false; + protected override Type UnsupportedReadWriteExceptionType => typeof(InvalidOperationException); + protected override bool WrappedUsableAfterClose => false; + protected override bool FlushRequiredToWriteData => true; + protected override bool FlushGuaranteesAllDataWritten => false; } } diff --git a/src/libraries/Common/tests/System/IO/Compression/CompressionStreamUnitTestBase.cs b/src/libraries/Common/tests/System/IO/Compression/CompressionStreamUnitTestBase.cs index 96db35c4be9a37..7e81c077709312 100644 --- a/src/libraries/Common/tests/System/IO/Compression/CompressionStreamUnitTestBase.cs +++ b/src/libraries/Common/tests/System/IO/Compression/CompressionStreamUnitTestBase.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -17,8 +16,6 @@ public abstract class CompressionStreamUnitTestBase : CompressionStreamTestBase [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public virtual void FlushAsync_DuringWriteAsync() { - if (FlushNoOps) - return; byte[] buffer = new byte[100000]; Random rand = new Random(); rand.NextBytes(buffer); @@ -52,8 +49,6 @@ public virtual void FlushAsync_DuringWriteAsync() [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public async Task FlushAsync_DuringReadAsync() { - if (FlushNoOps) - return; byte[] buffer = new byte[32]; string testFilePath = CompressedTestFile(UncompressedTestFile()); using (var readStream = await ManualSyncMemoryStream.GetStreamFromFileAsync(testFilePath, false)) @@ -81,8 +76,6 @@ public async Task FlushAsync_DuringReadAsync() [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public async Task FlushAsync_DuringFlushAsync() { - if (FlushNoOps) - return; byte[] buffer = null; string testFilePath = CompressedTestFile(UncompressedTestFile()); using (var origStream = await LocalMemoryStream.readAppFileAsync(testFilePath)) @@ -121,64 +114,6 @@ public async Task FlushAsync_DuringFlushAsync() } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - public virtual void WriteAsync_DuringWriteAsync() - { - byte[] buffer = new byte[100000]; - Random rand = new Random(); - rand.NextBytes(buffer); - - using (var writeStream = new ManualSyncMemoryStream(false)) - using (var compressor = CreateStream(writeStream, CompressionMode.Compress)) - { - Task task = null; - try - { - // Write needs to be big enough to trigger a write to the underlying base stream so the WriteAsync call doesn't immediately complete. - task = compressor.WriteAsync(buffer, 0, buffer.Length); - while (task.IsCompleted) - { - rand.NextBytes(buffer); - task = compressor.WriteAsync(buffer, 0, buffer.Length); - } - Assert.Throws(() => { compressor.WriteAsync(buffer, 32, 32); }); // "overlapping write" - } - finally - { - // Unblock Async operations - writeStream.manualResetEvent.Set(); - // The original WriteAsync should be able to complete - Assert.True(task.Wait(TaskTimeout), "Original WriteAsync Task did not complete in time"); - Assert.True(writeStream.WriteHit, "BaseStream Write function was not called"); - } - } - } - - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - public async Task ReadAsync_DuringReadAsync() - { - byte[] buffer = new byte[32]; - string testFilePath = CompressedTestFile(UncompressedTestFile()); - using (var readStream = await ManualSyncMemoryStream.GetStreamFromFileAsync(testFilePath, false)) - using (var decompressor = CreateStream(readStream, CompressionMode.Decompress, true)) - { - Task task = null; - try - { - task = decompressor.ReadAsync(buffer, 0, 32); - Assert.Throws(() => { decompressor.ReadAsync(buffer, 0, 32); }); // "overlapping read" - } - finally - { - // Unblock Async operations - readStream.manualResetEvent.Set(); - // The original ReadAsync should be able to complete - Assert.True(task.Wait(TaskTimeout), "The original ReadAsync should be able to complete"); - Assert.True(readStream.ReadHit, "BaseStream ReadAsync should have been called"); - } - } - } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public virtual async Task Dispose_WithUnfinishedReadAsync() { @@ -287,20 +222,6 @@ public async Task Read_BaseStreamSlowly() } } - [Theory] - [InlineData(CompressionMode.Compress)] - [InlineData(CompressionMode.Decompress)] - public void CanReadCanWrite(CompressionMode mode) - { - var ms = new MemoryStream(); - var compressor = CreateStream(ms, mode); - Assert.True(mode == CompressionMode.Compress ? compressor.CanWrite : compressor.CanRead); - - compressor.Dispose(); - Assert.False(compressor.CanRead); - Assert.False(compressor.CanWrite); - } - [Theory] [InlineData(CompressionMode.Compress)] [InlineData(CompressionMode.Decompress)] @@ -332,7 +253,7 @@ public void Ctor_DisposedBaseStream(CompressionMode mode) [Theory] [InlineData(CompressionMode.Compress)] [InlineData(CompressionMode.Decompress)] - public void Ctor_InvalidStreamCanReadCanWrite(CompressionMode mode) + public void Ctor_InvalidStream_Throws(CompressionMode mode) { LocalMemoryStream ms = new LocalMemoryStream(); ms.SetCanRead(mode == CompressionMode.Compress); @@ -341,27 +262,26 @@ public void Ctor_InvalidStreamCanReadCanWrite(CompressionMode mode) AssertExtensions.Throws("stream", () => CreateStream(ms, mode)); } - public IEnumerable> CtorFunctions() - { - CompressionLevel[] legalValues = new CompressionLevel[] { CompressionLevel.Optimal, CompressionLevel.Fastest, CompressionLevel.NoCompression }; - yield return new Func((stream) => CreateStream(stream, CompressionMode.Compress)); - foreach (CompressionLevel level in legalValues) - { - yield return new Func((stream) => CreateStream(stream, level)); - bool[] boolValues = new bool[] { true, false }; + [Fact] + public void TestCompressCtor() + { + IEnumerable> CtorFunctions() + { + yield return new Func((stream) => CreateStream(stream, CompressionMode.Compress)); - foreach (bool remainsOpen in boolValues) + foreach (CompressionLevel level in new[] { CompressionLevel.Optimal, CompressionLevel.Fastest, CompressionLevel.NoCompression }) { - yield return new Func((stream) => CreateStream(stream, level, remainsOpen)); + yield return new Func((stream) => CreateStream(stream, level)); + + foreach (bool remainsOpen in new[] { true, false }) + { + yield return new Func((stream) => CreateStream(stream, level, remainsOpen)); + } } } - } - [Fact] - public void TestCompressCtor() - { Assert.All(CtorFunctions(), (create) => { //Create the Stream @@ -400,41 +320,6 @@ public void TestCompressCtor() }); } - [Theory] - [InlineData(CompressionMode.Compress)] - [InlineData(CompressionMode.Decompress)] - public void TestLeaveOpen(CompressionMode mode) - { - //Create the Stream - var baseStream = new MemoryStream(); - Stream compressor = CreateStream(baseStream, mode, leaveOpen: false); - compressor.Dispose(); - - //Check that Close has really closed the underlying stream - Assert.Throws(() => baseStream.Write(new byte[] { }, 0, 0)); - } - - [Fact] - public void TestLeaveOpenAfterValidCompress() - { - //Create the Stream - int _bufferSize = 1024; - var bytes = new byte[_bufferSize]; - var baseStream = new MemoryStream(bytes, writable: true); - Stream compressor = CreateStream(baseStream, CompressionMode.Compress, leaveOpen: false); - - //Write some data and Close the stream - string strData = "Test Data"; - var encoding = Encoding.UTF8; - byte[] data = encoding.GetBytes(strData); - compressor.Write(data, 0, data.Length); - compressor.Flush(); - compressor.Dispose(); - - //Check that Close has really closed the underlying stream - Assert.Throws(() => baseStream.Write(bytes, 0, bytes.Length)); - } - [Fact] public async Task TestLeaveOpenAfterValidDecompress() { @@ -470,619 +355,6 @@ public void Ctor_ArgumentValidation() AssertExtensions.Throws("stream", () => CreateStream(new MemoryStream(new byte[1], writable: false), CompressionLevel.Optimal)); } - [Theory] - [InlineData(CompressionMode.Compress)] - [InlineData(CompressionMode.Decompress)] - public async Task Flush(CompressionMode mode) - { - var ms = new MemoryStream(); - var compressor = CreateStream(ms, mode); - compressor.Flush(); - await compressor.FlushAsync(); - } - - [Theory] - [InlineData(CompressionMode.Compress)] - [InlineData(CompressionMode.Decompress)] - public void Flush_Double(CompressionMode mode) - { - var ms = new MemoryStream(); - var compressor = CreateStream(ms, mode); - compressor.Flush(); - compressor.Flush(); - } - - [Theory] - [InlineData(CompressionMode.Compress)] - [InlineData(CompressionMode.Decompress)] - public void Dispose_Double(CompressionMode mode) - { - var ms = new MemoryStream(); - var compressor = CreateStream(ms, mode); - compressor.Dispose(); - compressor.Dispose(); - } - - [Theory] - [InlineData(CompressionMode.Compress)] - [InlineData(CompressionMode.Decompress)] - public void Flush_FollowedByDispose(CompressionMode mode) - { - var ms = new MemoryStream(); - var compressor = CreateStream(ms, mode); - compressor.Flush(); - compressor.Dispose(); - } - - [Theory] - [InlineData(CompressionMode.Compress)] - [InlineData(CompressionMode.Decompress)] - public void Dispose_FollowedBySyncOperations(CompressionMode mode) - { - var ms = new MemoryStream(); - var compressor = CreateStream(ms, mode); - compressor.Dispose(); - - if (mode == CompressionMode.Compress) - Assert.Throws(() => compressor.Write(new byte[1], 0, 1)); - else - Assert.Throws(() => compressor.Read(new byte[1], 0, 1)); - Assert.Throws(() => compressor.Flush()); - Assert.Throws(() => compressor.CopyTo(new MemoryStream())); - } - - [Theory] - [InlineData(CompressionMode.Compress)] - [InlineData(CompressionMode.Decompress)] - public virtual async Task Dispose_FollowedByAsyncOperations(CompressionMode mode) - { - var ms = new MemoryStream(); - var compressor = CreateStream(ms, mode); - compressor.Dispose(); - - if (mode == CompressionMode.Compress) - await Assert.ThrowsAsync(async () => await compressor.WriteAsync(new byte[1], 0, 1)); - else - await Assert.ThrowsAsync(async () => await compressor.ReadAsync(new byte[1], 0, 1)); - await Assert.ThrowsAsync(async () => await compressor.FlushAsync()); - await Assert.ThrowsAsync(async () => await compressor.CopyToAsync(new MemoryStream())); - } - - [Theory] - [InlineData(CompressionMode.Compress)] - [InlineData(CompressionMode.Decompress)] - public void TestSeekMethods(CompressionMode mode) - { - var ms = new MemoryStream(); - var decompressor = CreateStream(ms, mode); - Assert.False(decompressor.CanSeek, "CanSeek should be false"); - Assert.Throws(() => decompressor.Length); - Assert.Throws(() => decompressor.SetLength(1)); - Assert.Throws(() => decompressor.Position); - Assert.Throws(() => decompressor.Position = 100L); - Assert.Throws(() => decompressor.Seek(100L, SeekOrigin.Begin)); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public virtual void Write_ArgumentValidation(bool useAsync) - { - using (var decompressor = CreateStream(new MemoryStream(), CompressionMode.Compress)) - { - Assert.Throws(() => { if (useAsync) { decompressor.WriteAsync(null, 0, 0).Wait(); } else { decompressor.Write(null, 0, 0); } }); - Assert.Throws(() => { if (useAsync) { decompressor.WriteAsync(new byte[1], -1, 0).Wait(); } else { decompressor.Write(new byte[1], -1, 0); } }); - Assert.Throws(() => { if (useAsync) { decompressor.WriteAsync(new byte[1], 0, -1).Wait(); } else { decompressor.Write(new byte[1], 0, -1); } }); - Assert.Throws(null, () => { if (useAsync) { decompressor.WriteAsync(new byte[1], 0, 2).Wait(); } else { decompressor.Write(new byte[1], 0, 2); } }); - Assert.Throws(null, () => { if (useAsync) { decompressor.WriteAsync(new byte[1], 1, 1).Wait(); } else { decompressor.Write(new byte[1], 1, 1); } }); - Assert.Throws(() => useAsync ? decompressor.ReadAsync(new byte[1], 0, 1).Result : decompressor.Read(new byte[1], 0, 1)); - Assert.Throws(null, () => useAsync ? decompressor.ReadAsync(new byte[1], 1, 1).Result : decompressor.Read(new byte[1], 1, 1)); - if (useAsync) - { decompressor.WriteAsync(new byte[1], 0, 1).Wait(); } - else - { decompressor.Write(new byte[1], 0, 1); } - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public virtual void Read_ArgumentValidation(bool useAsync) - { - using (var decompressor = CreateStream(new MemoryStream(), CompressionMode.Decompress)) - { - Assert.Throws(() => useAsync ? decompressor.ReadAsync(null, 0, 0).Result : decompressor.Read(null, 0, 0)); - Assert.Throws(() => useAsync ? decompressor.ReadAsync(new byte[1], -1, 0).Result : decompressor.Read(new byte[1], -1, 0)); - Assert.Throws(() => useAsync ? decompressor.ReadAsync(new byte[1], 0, -1).Result : decompressor.Read(new byte[1], 0, -1)); - AssertExtensions.Throws(null, () => useAsync ? decompressor.ReadAsync(new byte[1], 0, 2).Result : decompressor.Read(new byte[1], 0, 2)); - AssertExtensions.Throws(null, () => useAsync ? decompressor.ReadAsync(new byte[1], 1, 1).Result : decompressor.Read(new byte[1], 1, 1)); - Assert.Throws(() => { if (useAsync) { decompressor.WriteAsync(new byte[1], 0, 1).Wait(); } else { decompressor.Write(new byte[1], 0, 1); } }); - Assert.Throws(null, () => { if (useAsync) { decompressor.WriteAsync(new byte[1], 1, 1).Wait(); } else { decompressor.Write(new byte[1], 1, 1); } }); - - var data = new byte[1] { 42 }; - Assert.Equal(0, useAsync ? decompressor.ReadAsync(data, 0, 0).Result : decompressor.Read(data, 0, 0)); - Assert.Equal(42, data[0]); - } - } - - [Theory] - [InlineData(CompressionMode.Compress)] - [InlineData(CompressionMode.Decompress)] - public void CopyToAsync_ArgumentValidation(CompressionMode mode) - { - using (Stream compressor = CreateStream(new MemoryStream(), mode)) - { - AssertExtensions.Throws("destination", () => { compressor.CopyToAsync(null); }); - AssertExtensions.Throws("bufferSize", () => { compressor.CopyToAsync(new MemoryStream(), 0); }); - Assert.Throws(() => { compressor.CopyToAsync(new MemoryStream(new byte[1], writable: false)); }); - compressor.Dispose(); - Assert.Throws(() => { compressor.CopyToAsync(new MemoryStream()); }); - } - } - - [Theory] - [InlineData(CompressionMode.Compress)] - [InlineData(CompressionMode.Decompress)] - public void CopyTo_ArgumentValidation(CompressionMode mode) - { - using (Stream compressor = CreateStream(new MemoryStream(), mode)) - { - AssertExtensions.Throws("destination", () => { compressor.CopyTo(null); }); - AssertExtensions.Throws("bufferSize", () => { compressor.CopyTo(new MemoryStream(), 0); }); - Assert.Throws(() => { compressor.CopyTo(new MemoryStream(new byte[1], writable: false)); }); - compressor.Dispose(); - Assert.Throws(() => { compressor.CopyTo(new MemoryStream()); }); - } - } - - public enum ReadWriteMode - { - SyncArray, - SyncSpan, - AsyncArray, - AsyncMemory, - AsyncBeginEnd - } - - public static IEnumerable RoundtripCompressDecompressOuterData - { - get - { - foreach (ReadWriteMode readWriteMode in new[] { ReadWriteMode.SyncArray, ReadWriteMode.SyncSpan, ReadWriteMode.AsyncArray, ReadWriteMode.AsyncMemory, ReadWriteMode.AsyncBeginEnd }) - { - foreach (var level in new[] { CompressionLevel.Fastest, CompressionLevel.Optimal, CompressionLevel.NoCompression }) // compression level - { - yield return new object[] { readWriteMode, 1, 5, level }; // smallest possible writes - yield return new object[] { readWriteMode, 1023, 1023 * 10, level }; // overflowing internal buffer - yield return new object[] { readWriteMode, 1024 * 1024, 1024 * 1024, level }; // large single write - } - } - } - } - - [Fact] - public async Task CompressDecompress_RoundTrip() - { - await CompressDecompress_RoundTrip_OuterLoop(ReadWriteMode.SyncArray, chunkSize: 1, totalSize: 10, level: CompressionLevel.Fastest); - await CompressDecompress_RoundTrip_OuterLoop(ReadWriteMode.SyncSpan, chunkSize: 1, totalSize: 10, level: CompressionLevel.Fastest); - await CompressDecompress_RoundTrip_OuterLoop(ReadWriteMode.AsyncArray, chunkSize: 1, totalSize: 10, level: CompressionLevel.Fastest); - await CompressDecompress_RoundTrip_OuterLoop(ReadWriteMode.AsyncMemory, chunkSize: 1, totalSize: 10, level: CompressionLevel.Fastest); - await CompressDecompress_RoundTrip_OuterLoop(ReadWriteMode.AsyncBeginEnd, chunkSize: 1, totalSize: 10, level: CompressionLevel.Fastest); - await CompressDecompress_RoundTrip_OuterLoop(ReadWriteMode.AsyncArray, chunkSize: 1024, totalSize: 8192, level: CompressionLevel.Optimal); - } - - [OuterLoop] - [Theory] - [MemberData(nameof(RoundtripCompressDecompressOuterData))] - public async Task CompressDecompress_RoundTrip_OuterLoop(ReadWriteMode readWriteMode, int chunkSize, int totalSize, CompressionLevel level) - { - byte[] data = new byte[totalSize]; - new Random(42).NextBytes(data); - - var compressed = new MemoryStream(); - using (var compressor = CreateStream(compressed, level, true)) - { - for (int i = 0; i < data.Length; i += chunkSize) // not using CopyTo{Async} due to optimizations in MemoryStream's implementation that avoid what we're trying to test - { - switch (readWriteMode) - { - case ReadWriteMode.AsyncArray: - await compressor.WriteAsync(data, i, chunkSize); - break; - case ReadWriteMode.SyncArray: - compressor.Write(data, i, chunkSize); - break; - case ReadWriteMode.SyncSpan: - compressor.Write(new ReadOnlySpan(data, i, chunkSize)); - break; - case ReadWriteMode.AsyncMemory: - await compressor.WriteAsync(new ReadOnlyMemory(data, i, chunkSize)); - break; - case ReadWriteMode.AsyncBeginEnd: - await Task.Factory.FromAsync(compressor.BeginWrite, compressor.EndWrite, data, i, chunkSize, null); - break; - } - } - } - compressed.Position = 0; - await ReadAndValidateCompressedData(readWriteMode, chunkSize, compressed, data); - compressed.Dispose(); - } - - [Fact] - public async Task Flush_RoundTrip() - { - if (FlushNoOps) - return; - await Flush_RoundTrip_OuterLoop(ReadWriteMode.SyncArray, chunkSize: 1, totalSize: 10, level: CompressionLevel.Fastest); - await Flush_RoundTrip_OuterLoop(ReadWriteMode.AsyncArray, chunkSize: 1024, totalSize: 8192, level: CompressionLevel.Optimal); - } - - [OuterLoop] - [Theory] - [MemberData(nameof(RoundtripCompressDecompressOuterData))] - public async Task Flush_RoundTrip_OuterLoop(ReadWriteMode readWriteMode, int chunkSize, int totalSize, CompressionLevel level) - { - if (FlushNoOps) - return; - byte[] data = new byte[totalSize]; - new Random(42).NextBytes(data); - - using (var compressed = new MemoryStream()) - using (var compressor = CreateStream(compressed, level, true)) - { - for (int i = 0; i < data.Length; i += chunkSize) // not using CopyTo{Async} due to optimizations in MemoryStream's implementation that avoid what we're trying to test - { - switch (readWriteMode) - { - case ReadWriteMode.AsyncArray: - await compressor.WriteAsync(data, i, chunkSize); - break; - case ReadWriteMode.SyncArray: - compressor.Write(data, i, chunkSize); - break; - case ReadWriteMode.SyncSpan: - compressor.Write(new ReadOnlySpan(data, i, chunkSize)); - break; - case ReadWriteMode.AsyncMemory: - await compressor.WriteAsync(new ReadOnlyMemory(data, i, chunkSize)); - break; - case ReadWriteMode.AsyncBeginEnd: - await Task.Factory.FromAsync(compressor.BeginWrite, compressor.EndWrite, data, i, chunkSize, null); - break; - } - } - switch (readWriteMode) - { - case ReadWriteMode.AsyncArray: - case ReadWriteMode.AsyncMemory: - case ReadWriteMode.AsyncBeginEnd: - await compressor.FlushAsync(); - break; - case ReadWriteMode.SyncSpan: - case ReadWriteMode.SyncArray: - compressor.Flush(); - break; - } - if (!FlushCompletes) - compressor.Dispose(); - compressed.Position = 0; - await ReadAndValidateCompressedData(readWriteMode, chunkSize, compressed, data); - } - } - - [Fact] - public async Task Flush_Consecutive() - { - if (FlushNoOps) - return; - await Flush_Consecutive_OuterLoop(ReadWriteMode.SyncArray, chunkSize: 1, totalSize: 10, level: CompressionLevel.Fastest); - await Flush_Consecutive_OuterLoop(ReadWriteMode.AsyncArray, chunkSize: 1024, totalSize: 8192, level: CompressionLevel.Optimal); - } - - [OuterLoop] - [Theory] - [MemberData(nameof(RoundtripCompressDecompressOuterData))] - public async Task Flush_Consecutive_OuterLoop(ReadWriteMode readWriteMode, int chunkSize, int totalSize, CompressionLevel level) - { - if (FlushNoOps) - return; - byte[] data = new byte[totalSize]; - List expected = new List(); - new Random(42).NextBytes(data); - - using (var compressed = new MemoryStream()) - using (var compressor = CreateStream(compressed, level, true)) - { - for (int i = 0; i < data.Length; i += chunkSize) // not using CopyTo{Async} due to optimizations in MemoryStream's implementation that avoid what we're trying to test - { - switch (readWriteMode) - { - case ReadWriteMode.AsyncArray: - await compressor.WriteAsync(data, i, chunkSize); - break; - case ReadWriteMode.SyncArray: - compressor.Write(data, i, chunkSize); - break; - case ReadWriteMode.SyncSpan: - compressor.Write(new ReadOnlySpan(data, i, chunkSize)); - break; - case ReadWriteMode.AsyncMemory: - await compressor.WriteAsync(new ReadOnlyMemory(data, i, chunkSize)); - break; - case ReadWriteMode.AsyncBeginEnd: - await Task.Factory.FromAsync(compressor.BeginWrite, compressor.EndWrite, data, i, chunkSize, null); - break; - } - for (int j = i; j < i + chunkSize; j++) - expected.Insert(j, data[j]); - - switch (readWriteMode) - { - case ReadWriteMode.AsyncArray: - case ReadWriteMode.AsyncMemory: - case ReadWriteMode.AsyncBeginEnd: - await compressor.FlushAsync(); - break; - case ReadWriteMode.SyncSpan: - case ReadWriteMode.SyncArray: - compressor.Flush(); - break; - } - - if (FlushCompletes) - { - MemoryStream partiallyCompressed = new MemoryStream(compressed.ToArray()); - partiallyCompressed.Position = 0; - await ReadAndValidateCompressedData(readWriteMode, chunkSize, partiallyCompressed, expected.ToArray()); - } - } - if (!FlushCompletes) - compressor.Dispose(); - MemoryStream fullyCompressed = new MemoryStream(compressed.ToArray()); - fullyCompressed.Position = 0; - await ReadAndValidateCompressedData(readWriteMode, chunkSize, fullyCompressed, expected.ToArray()); - } - } - - [Fact] - public async Task Flush_BeforeFirstWrites() - { - if (FlushNoOps) - return; - await Flush_BeforeFirstWrites_OuterLoop(ReadWriteMode.SyncArray, chunkSize: 1, totalSize: 10, level: CompressionLevel.Fastest); - await Flush_BeforeFirstWrites_OuterLoop(ReadWriteMode.AsyncArray, chunkSize: 1024, totalSize: 8192, level: CompressionLevel.Optimal); - } - - [OuterLoop] - [Theory] - [MemberData(nameof(RoundtripCompressDecompressOuterData))] - public async Task Flush_BeforeFirstWrites_OuterLoop(ReadWriteMode readWriteMode, int chunkSize, int totalSize, CompressionLevel level) - { - if (FlushNoOps) - return; - byte[] data = new byte[totalSize]; - new Random(42).NextBytes(data); - - using (var compressed = new MemoryStream()) - using (var compressor = CreateStream(compressed, level, true)) - { - switch (readWriteMode) - { - case ReadWriteMode.AsyncArray: - case ReadWriteMode.AsyncMemory: - case ReadWriteMode.AsyncBeginEnd: - await compressor.FlushAsync(); - break; - case ReadWriteMode.SyncSpan: - case ReadWriteMode.SyncArray: - compressor.Flush(); - break; - } - - for (int i = 0; i < data.Length; i += chunkSize) // not using CopyTo{Async} due to optimizations in MemoryStream's implementation that avoid what we're trying to test - { - switch (readWriteMode) - { - case ReadWriteMode.AsyncArray: - await compressor.WriteAsync(data, i, chunkSize); - break; - case ReadWriteMode.SyncArray: - compressor.Write(data, i, chunkSize); - break; - case ReadWriteMode.SyncSpan: - compressor.Write(new ReadOnlySpan(data, i, chunkSize)); - break; - case ReadWriteMode.AsyncMemory: - await compressor.WriteAsync(new ReadOnlyMemory(data, i, chunkSize)); - break; - case ReadWriteMode.AsyncBeginEnd: - await Task.Factory.FromAsync(compressor.BeginWrite, compressor.EndWrite, data, i, chunkSize, null); - break; - } - } - - switch (readWriteMode) - { - case ReadWriteMode.AsyncArray: - case ReadWriteMode.AsyncMemory: - case ReadWriteMode.AsyncBeginEnd: - await compressor.FlushAsync(); - break; - case ReadWriteMode.SyncSpan: - case ReadWriteMode.SyncArray: - compressor.Flush(); - break; - } - if (!FlushCompletes) - compressor.Dispose(); - compressed.Position = 0; - await ReadAndValidateCompressedData(readWriteMode, chunkSize, compressed, data); - } - } - - /// - /// Given a MemoryStream of compressed data and a byte array of desired output, decompresses - /// the stream and validates that it is equal to the expected array. - /// - private async Task ReadAndValidateCompressedData(ReadWriteMode readWriteMode, int chunkSize, MemoryStream compressed, byte[] expected) - { - using (MemoryStream decompressed = new MemoryStream()) - using (Stream decompressor = CreateStream(compressed, CompressionMode.Decompress, true)) - { - int bytesRead; - var buffer = new byte[chunkSize]; - switch (readWriteMode) - { - case ReadWriteMode.SyncSpan: - while ((bytesRead = decompressor.Read(new Span(buffer))) != 0) - { - decompressed.Write(buffer, 0, bytesRead); - } - break; - case ReadWriteMode.SyncArray: - while ((bytesRead = decompressor.Read(buffer, 0, buffer.Length)) != 0) - { - decompressed.Write(buffer, 0, bytesRead); - } - break; - case ReadWriteMode.AsyncArray: - while ((bytesRead = await decompressor.ReadAsync(buffer, 0, buffer.Length)) != 0) - { - decompressed.Write(buffer, 0, bytesRead); - } - break; - case ReadWriteMode.AsyncMemory: - while ((bytesRead = await decompressor.ReadAsync(new Memory(buffer))) != 0) - { - decompressed.Write(buffer, 0, bytesRead); - } - break; - case ReadWriteMode.AsyncBeginEnd: - while ((bytesRead = await Task.Factory.FromAsync(decompressor.BeginRead, decompressor.EndRead, buffer, 0, buffer.Length, null)) != 0) - { - decompressed.Write(buffer, 0, bytesRead); - } - break; - } - Assert.Equal(expected, decompressed.ToArray()); - } - } - - [Theory] - [InlineData(ReadWriteMode.SyncArray)] - [InlineData(ReadWriteMode.AsyncArray)] - [InlineData(ReadWriteMode.SyncSpan)] - [InlineData(ReadWriteMode.AsyncMemory)] - [InlineData(ReadWriteMode.AsyncBeginEnd)] - public async Task Read_SequentialReadsOnMemoryStream_Return_SameBytes(ReadWriteMode readWriteMode) - { - byte[] data = new byte[1024 * 10]; - new Random(42).NextBytes(data); - - var compressed = new MemoryStream(); - using (var compressor = CreateStream(compressed, CompressionMode.Compress, true)) - { - for (int i = 0; i < data.Length; i += 1024) - { - switch (readWriteMode) - { - case ReadWriteMode.SyncArray: - compressor.Write(data, i, 1024); - break; - case ReadWriteMode.AsyncArray: - await compressor.WriteAsync(data, i, 1024); - break; - case ReadWriteMode.SyncSpan: - compressor.Write(new Span(data, i, 1024)); - break; - case ReadWriteMode.AsyncMemory: - await compressor.WriteAsync(new ReadOnlyMemory(data, i, 1024)); - break; - case ReadWriteMode.AsyncBeginEnd: - await Task.Factory.FromAsync(compressor.BeginWrite, compressor.EndWrite, data, i, 1024, null); - break; - } - } - } - compressed.Position = 0; - - using (var decompressor = CreateStream(compressed, CompressionMode.Decompress, true)) - { - int i, j; - byte[] array = new byte[100]; - byte[] array2 = new byte[100]; - - // only read in the first 100 bytes - switch (readWriteMode) - { - case ReadWriteMode.SyncArray: - decompressor.Read(array, 0, array.Length); - break; - case ReadWriteMode.AsyncArray: - await decompressor.ReadAsync(array, 0, array.Length); - break; - case ReadWriteMode.SyncSpan: - decompressor.Read(new Span(array)); - break; - case ReadWriteMode.AsyncMemory: - await decompressor.ReadAsync(new Memory(array)); - break; - case ReadWriteMode.AsyncBeginEnd: - await Task.Factory.FromAsync(decompressor.BeginRead, decompressor.EndRead, array, 0, array.Length, null); - break; - } - for (i = 0; i < array.Length; i++) - { - Assert.Equal(data[i], array[i]); - } - - // read in the next 100 bytes and make sure nothing is missing - switch (readWriteMode) - { - case ReadWriteMode.SyncArray: - decompressor.Read(array2, 0, array2.Length); - break; - case ReadWriteMode.AsyncArray: - await decompressor.ReadAsync(array2, 0, array2.Length); - break; - case ReadWriteMode.SyncSpan: - decompressor.Read(new Span(array2)); - break; - case ReadWriteMode.AsyncMemory: - await decompressor.ReadAsync(new Memory(array2)); - break; - case ReadWriteMode.AsyncBeginEnd: - await Task.Factory.FromAsync(decompressor.BeginRead, decompressor.EndRead, array2, 0, array2.Length, null); - break; - } - for (j = 0; j < array2.Length; j++) - { - Assert.Equal(data[j], array[j]); - } - } - } - - [Fact] - public void WriteByte_RountTrip() - { - byte[] data = new byte[1024 * 10]; - new Random(42).NextBytes(data); - - var compressed = new MemoryStream(); - using (var compressor = CreateStream(compressed, CompressionMode.Compress, true)) - { - compressor.Write(data, 0, data.Length); - } - compressed.Position = 0; - - using (var decompressor = CreateStream(compressed, CompressionMode.Decompress, true)) - { - for (int i = 0; i < data.Length; i++) - Assert.Equal(data[i], decompressor.ReadByte()); - } - } - [Fact] public async Task WrapNullReturningTasksStream() { @@ -1100,68 +372,12 @@ public async Task WrapStreamReturningBadReadValues() using (var decompressor = CreateStream(new BadWrappedStream(BadWrappedStream.Mode.ReturnTooLargeCounts), CompressionMode.Decompress)) await Assert.ThrowsAsync(async () => { await decompressor.ReadAsync(new Memory(new byte[1024])); }); - if (!FlushNoOps) - { - using (var decompressor = CreateStream(new BadWrappedStream(BadWrappedStream.Mode.ReturnTooSmallCounts), CompressionMode.Decompress)) - Assert.Equal(0, decompressor.Read(new byte[1024], 0, 1024)); - using (var decompressor = CreateStream(new BadWrappedStream(BadWrappedStream.Mode.ReturnTooSmallCounts), CompressionMode.Decompress)) - Assert.Equal(0, await decompressor.ReadAsync(new byte[1024], 0, 1024)); - using (var decompressor = CreateStream(new BadWrappedStream(BadWrappedStream.Mode.ReturnTooSmallCounts), CompressionMode.Decompress)) - Assert.Equal(0, await decompressor.ReadAsync(new Memory(new byte[1024]))); - } - } - - [Theory] - [OuterLoop] - [InlineData(true)] - [InlineData(false)] - public async Task CopyTo_Roundtrip_OutputMatchesInput(bool useAsync) - { - var rand = new Random(); - foreach (int dataSize in new[] { 1, 1024, 4095, 1024 * 1024 }) - { - var data = new byte[dataSize]; - rand.NextBytes(data); - - var compressed = new MemoryStream(); - using (var ds = CreateStream(compressed, CompressionMode.Compress, leaveOpen: true)) - { - ds.Write(data, 0, data.Length); - } - byte[] compressedData = compressed.ToArray(); - - foreach (int copyBufferSize in new[] { 1, 4096, 80 * 1024 }) - { - // Memory source - var m = new MemoryStream(compressedData, writable: false); - await CopyTo_Roundtrip_OutputMatchesInput_Verify(data, copyBufferSize, m, useAsync); - - // File sources, sync and async - foreach (bool useAsyncFileOptions in new[] { true, false }) - { - string path = Path.GetTempFileName(); - File.WriteAllBytes(path, compressedData); - - FileOptions options = FileOptions.DeleteOnClose; - if (useAsyncFileOptions) - options |= FileOptions.Asynchronous; - await CopyTo_Roundtrip_OutputMatchesInput_Verify(data, copyBufferSize, new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x1000, options), useAsync); - } - } - } - } - - private async Task CopyTo_Roundtrip_OutputMatchesInput_Verify(byte[] expectedDecrypted, int copyBufferSize, Stream source, bool useAsync) - { - var m = new MemoryStream(); - using (Stream ds = CreateStream(source, CompressionMode.Decompress)) - { - if (useAsync) - await ds.CopyToAsync(m); - else - ds.CopyTo(m, copyBufferSize); - } - Assert.Equal(expectedDecrypted, m.ToArray()); + using (var decompressor = CreateStream(new BadWrappedStream(BadWrappedStream.Mode.ReturnTooSmallCounts), CompressionMode.Decompress)) + Assert.Equal(0, decompressor.Read(new byte[1024], 0, 1024)); + using (var decompressor = CreateStream(new BadWrappedStream(BadWrappedStream.Mode.ReturnTooSmallCounts), CompressionMode.Decompress)) + Assert.Equal(0, await decompressor.ReadAsync(new byte[1024], 0, 1024)); + using (var decompressor = CreateStream(new BadWrappedStream(BadWrappedStream.Mode.ReturnTooSmallCounts), CompressionMode.Decompress)) + Assert.Equal(0, await decompressor.ReadAsync(new Memory(new byte[1024]))); } [Theory] @@ -1225,88 +441,6 @@ public async Task BaseStream_ValidAfterDisposeWithTrueLeaveOpen(CompressionMode else baseStream.Read(bytes, 0, size); } - - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - public async Task Parallel_CompressDecompressMultipleStreamsConcurrently() - { - const int ParallelOperations = 20; - const int DataSize = 10 * 1024; - - var sourceData = new byte[DataSize]; - new Random().NextBytes(sourceData); - - await Task.WhenAll(Enumerable.Range(0, ParallelOperations).Select(_ => Task.Run(async () => - { - var compressedStream = new MemoryStream(); - using (Stream ds = CreateStream(compressedStream, CompressionMode.Compress, leaveOpen: true)) - { - await ds.WriteAsync(sourceData, 0, sourceData.Length); - } - - compressedStream.Position = 0; - - var decompressedStream = new MemoryStream(); - using (Stream ds = CreateStream(compressedStream, CompressionMode.Decompress, leaveOpen: true)) - { - await ds.CopyToAsync(decompressedStream); - } - - Assert.Equal(sourceData, decompressedStream.ToArray()); - }))); - } - - [Fact] - public void Precancellation() - { - var ms = new MemoryStream(); - using (Stream compressor = CreateStream(ms, CompressionMode.Compress, leaveOpen: true)) - { - Assert.True(compressor.WriteAsync(new byte[1], 0, 1, new CancellationToken(true)).IsCanceled); - Assert.True(compressor.FlushAsync(new CancellationToken(true)).IsCanceled); - } - using (Stream decompressor = CreateStream(ms, CompressionMode.Decompress, leaveOpen: true)) - { - Assert.True(decompressor.ReadAsync(new byte[1], 0, 1, new CancellationToken(true)).IsCanceled); - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task DisposeAsync_Flushes(bool leaveOpen) - { - var ms = new MemoryStream(); - var cs = CreateStream(ms, CompressionMode.Compress, leaveOpen); - cs.WriteByte(1); - await cs.FlushAsync(); - - long pos = ms.Position; - cs.WriteByte(1); - Assert.Equal(pos, ms.Position); - - await cs.DisposeAsync(); - Assert.InRange(ms.ToArray().Length, pos + 1, int.MaxValue); - if (leaveOpen) - { - Assert.InRange(ms.Position, pos + 1, int.MaxValue); - } - else - { - Assert.Throws(() => ms.Position); - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task DisposeAsync_MultipleCallsAllowed(bool leaveOpen) - { - using (var cs = CreateStream(new MemoryStream(), CompressionMode.Compress, leaveOpen)) - { - await cs.DisposeAsync(); - await cs.DisposeAsync(); - } - } } internal sealed class BadWrappedStream : MemoryStream @@ -1386,7 +520,7 @@ public ManualSyncMemoryStream(bool sync = false) : base() public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) => TaskToApm.Begin(WriteAsync(buffer, offset, count), callback, state); public override void EndWrite(IAsyncResult asyncResult) => TaskToApm.End(asyncResult); - public override async Task ReadAsync(byte[] array, int offset, int count, CancellationToken cancellationToken) + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { ReadHit = true; if (isSync) @@ -1398,10 +532,10 @@ public override async Task ReadAsync(byte[] array, int offset, int count, C await Task.Run(() => manualResetEvent.Wait(cancellationToken)).ConfigureAwait(false); } - return await base.ReadAsync(array, offset, count, cancellationToken); + return await base.ReadAsync(buffer, offset, count, cancellationToken); } - public override async Task WriteAsync(byte[] array, int offset, int count, CancellationToken cancellationToken) + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { WriteHit = true; if (isSync) @@ -1413,7 +547,7 @@ public override async Task WriteAsync(byte[] array, int offset, int count, Cance await Task.Run(() => manualResetEvent.Wait(cancellationToken)).ConfigureAwait(false); } - await base.WriteAsync(array, offset, count, cancellationToken); + await base.WriteAsync(buffer, offset, count, cancellationToken); } public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) diff --git a/src/libraries/System.IO.Compression.Brotli/tests/BrotliEncoderTests.cs b/src/libraries/System.IO.Compression.Brotli/tests/BrotliEncoderTests.cs deleted file mode 100644 index 8b6f4c28169ca5..00000000000000 --- a/src/libraries/System.IO.Compression.Brotli/tests/BrotliEncoderTests.cs +++ /dev/null @@ -1,411 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Buffers; -using System.Linq; -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Compression.Tests -{ - public class BrotliEncoderTests : CompressionTestBase - { - protected override string CompressedTestFile(string uncompressedPath) => Path.Combine("BrotliTestData", Path.GetFileName(uncompressedPath) + ".br"); - - [Fact] - public void InvalidQuality() - { - Assert.Throws("quality", () => new BrotliEncoder(-1, 11)); - Assert.Throws("quality", () => new BrotliEncoder(12, 11)); - Assert.Throws("quality", () => BrotliEncoder.TryCompress(new ReadOnlySpan(), new Span(), out int bytesWritten, -1, 13)); - Assert.Throws("quality", () => BrotliEncoder.TryCompress(new ReadOnlySpan(), new Span(), out int bytesWritten, 12, 13)); - } - - [Fact] - public void InvalidWindow() - { - Assert.Throws("window", () => new BrotliEncoder(10, -1)); - Assert.Throws("window", () => new BrotliEncoder(10, 9)); - Assert.Throws("window", () => new BrotliEncoder(10, 25)); - Assert.Throws("window", () => BrotliEncoder.TryCompress(new ReadOnlySpan(), new Span(), out int bytesWritten, 6, -1)); - Assert.Throws("window", () => BrotliEncoder.TryCompress(new ReadOnlySpan(), new Span(), out int bytesWritten, 6, 9)); - Assert.Throws("window", () => BrotliEncoder.TryCompress(new ReadOnlySpan(), new Span(), out int bytesWritten, 6, 25)); - } - - [Fact] - public void GetMaxCompressedSize_Basic() - { - Assert.Throws("inputSize", () => BrotliEncoder.GetMaxCompressedLength(-1)); - Assert.Throws("inputSize", () => BrotliEncoder.GetMaxCompressedLength(2147483133)); - Assert.InRange(BrotliEncoder.GetMaxCompressedLength(2147483132), 0, int.MaxValue); - Assert.Equal(1, BrotliEncoder.GetMaxCompressedLength(0)); - } - - [Fact] - public void GetMaxCompressedSize() - { - string uncompressedFile = UncompressedTestFile(); - string compressedFile = CompressedTestFile(uncompressedFile); - int maxCompressedSize = BrotliEncoder.GetMaxCompressedLength((int)new FileInfo(uncompressedFile).Length); - int actualCompressedSize = (int)new FileInfo(compressedFile).Length; - Assert.True(maxCompressedSize >= actualCompressedSize, $"MaxCompressedSize: {maxCompressedSize}, ActualCompressedSize: {actualCompressedSize}"); - } - - /// - /// Test to ensure that when given an empty Destination span, the decoder will consume no input and write no output. - /// - [Fact] - public void Decompress_WithEmptyDestination() - { - string testFile = UncompressedTestFile(); - byte[] sourceBytes = File.ReadAllBytes(CompressedTestFile(testFile)); - byte[] destinationBytes = new byte[0]; - ReadOnlySpan source = new ReadOnlySpan(sourceBytes); - Span destination = new Span(destinationBytes); - - Assert.False(BrotliDecoder.TryDecompress(source, destination, out int bytesWritten), "TryDecompress completed successfully but should have failed due to too short of a destination array"); - Assert.Equal(0, bytesWritten); - - BrotliDecoder decoder = default; - var result = decoder.Decompress(source, destination, out int bytesConsumed, out bytesWritten); - Assert.Equal(0, bytesWritten); - Assert.Equal(0, bytesConsumed); - Assert.Equal(OperationStatus.DestinationTooSmall, result); - } - - /// - /// Test to ensure that when given an empty source span, the decoder will consume no input and write no output - /// - [Fact] - public void Decompress_WithEmptySource() - { - string testFile = UncompressedTestFile(); - byte[] sourceBytes = new byte[0]; - byte[] destinationBytes = new byte[100000]; - ReadOnlySpan source = new ReadOnlySpan(sourceBytes); - Span destination = new Span(destinationBytes); - - Assert.False(BrotliDecoder.TryDecompress(source, destination, out int bytesWritten), "TryDecompress completed successfully but should have failed due to too short of a source array"); - Assert.Equal(0, bytesWritten); - - BrotliDecoder decoder = default; - var result = decoder.Decompress(source, destination, out int bytesConsumed, out bytesWritten); - Assert.Equal(0, bytesWritten); - Assert.Equal(0, bytesConsumed); - Assert.Equal(OperationStatus.NeedMoreData, result); - } - - /// - /// Test to ensure that when given an empty Destination span, the encoder consume no input and write no output - /// - [Fact] - public void Compress_WithEmptyDestination() - { - string testFile = UncompressedTestFile(); - byte[] correctUncompressedBytes = File.ReadAllBytes(testFile); - byte[] compressedBytes = File.ReadAllBytes(CompressedTestFile(testFile)); - byte[] empty = new byte[0]; - ReadOnlySpan source = new ReadOnlySpan(correctUncompressedBytes); - Span destination = new Span(empty); - - Assert.False(BrotliEncoder.TryCompress(source, destination, out int bytesWritten), "TryCompress completed successfully but should have failed due to too short of a destination array"); - Assert.Equal(0, bytesWritten); - - BrotliEncoder encoder = default; - var result = encoder.Compress(source, destination, out int bytesConsumed, out bytesWritten, false); - Assert.Equal(0, bytesWritten); - Assert.Equal(0, bytesConsumed); - Assert.Equal(OperationStatus.DestinationTooSmall, result); - - result = encoder.Compress(source, destination, out bytesConsumed, out bytesWritten, isFinalBlock: true); - Assert.Equal(0, bytesWritten); - Assert.Equal(0, bytesConsumed); - Assert.Equal(OperationStatus.DestinationTooSmall, result); - } - - /// - /// Test to ensure that when given an empty source span, the decoder will consume no input and write no output (until the finishing block) - /// - [Fact] - public void Compress_WithEmptySource() - { - string testFile = UncompressedTestFile(); - byte[] sourceBytes = new byte[0]; - byte[] destinationBytes = new byte[100000]; - ReadOnlySpan source = new ReadOnlySpan(sourceBytes); - Span destination = new Span(destinationBytes); - - Assert.True(BrotliEncoder.TryCompress(source, destination, out int bytesWritten)); - // The only byte written should be the Brotli end of stream byte which varies based on the window/quality - Assert.Equal(1, bytesWritten); - - BrotliEncoder encoder = default; - var result = encoder.Compress(source, destination, out int bytesConsumed, out bytesWritten, false); - Assert.Equal(0, bytesWritten); - Assert.Equal(0, bytesConsumed); - Assert.Equal(OperationStatus.Done, result); - - result = encoder.Compress(source, destination, out bytesConsumed, out bytesWritten, isFinalBlock: true); - Assert.Equal(1, bytesWritten); - Assert.Equal(0, bytesConsumed); - Assert.Equal(OperationStatus.Done, result); - } - - /// - /// Test that the decoder can handle partial chunks of flushed encoded data sent from the BrotliEncoder - /// - [Fact] - public void RoundTrip_Chunks() - { - int chunkSize = 100; - int totalSize = 20000; - BrotliEncoder encoder = default; - BrotliDecoder decoder = default; - for (int i = 0; i < totalSize; i += chunkSize) - { - byte[] uncompressed = new byte[chunkSize]; - new Random().NextBytes(uncompressed); - byte[] compressed = new byte[BrotliEncoder.GetMaxCompressedLength(chunkSize)]; - byte[] decompressed = new byte[chunkSize]; - var uncompressedSpan = new ReadOnlySpan(uncompressed); - var compressedSpan = new Span(compressed); - var decompressedSpan = new Span(decompressed); - - int totalWrittenThisIteration = 0; - var compress = encoder.Compress(uncompressedSpan, compressedSpan, out int bytesConsumed, out int bytesWritten, isFinalBlock: false); - totalWrittenThisIteration += bytesWritten; - compress = encoder.Flush(compressedSpan.Slice(bytesWritten), out bytesWritten); - totalWrittenThisIteration += bytesWritten; - - var res = decoder.Decompress(compressedSpan.Slice(0, totalWrittenThisIteration), decompressedSpan, out int decompressbytesConsumed, out int decompressbytesWritten); - Assert.Equal(totalWrittenThisIteration, decompressbytesConsumed); - Assert.Equal(bytesConsumed, decompressbytesWritten); - Assert.Equal(uncompressed, decompressedSpan.ToArray()); - } - } - - [Theory] - [OuterLoop("Full set of UncompressedTestFiles takes around 15s to run")] - [MemberData(nameof(UncompressedTestFiles))] - public void ReadFully(string testFile) - { - byte[] correctUncompressedBytes = File.ReadAllBytes(testFile); - byte[] compressedBytes = File.ReadAllBytes(CompressedTestFile(testFile)); - byte[] actualUncompressedBytes = new byte[correctUncompressedBytes.Length + 10000]; - ReadOnlySpan source = new ReadOnlySpan(compressedBytes); - Span destination = new Span(actualUncompressedBytes); - Assert.True(BrotliDecoder.TryDecompress(source, destination, out int bytesWritten), "TryDecompress did not complete successfully"); - Assert.Equal(correctUncompressedBytes.Length, bytesWritten); - Assert.Equal(correctUncompressedBytes, actualUncompressedBytes.AsSpan(0, correctUncompressedBytes.Length).ToArray()); - } - - [Theory] - [OuterLoop("Full set of UncompressedTestFiles takes around 15s to run")] - [MemberData(nameof(UncompressedTestFiles))] - public void ReadWithState(string testFile) - { - byte[] correctUncompressedBytes = File.ReadAllBytes(testFile); - byte[] compressedBytes = File.ReadAllBytes(CompressedTestFile(testFile)); - byte[] actualUncompressedBytes = new byte[correctUncompressedBytes.Length]; - Decompress_WithState(compressedBytes, actualUncompressedBytes); - - Assert.Equal(correctUncompressedBytes, actualUncompressedBytes); - } - - [Theory] - [OuterLoop("Full set of UncompressedTestFiles takes around 15s to run")] - [MemberData(nameof(UncompressedTestFiles))] - public void ReadWithoutState(string testFile) - { - byte[] correctUncompressedBytes = File.ReadAllBytes(testFile); - byte[] compressedBytes = File.ReadAllBytes(CompressedTestFile(testFile)); - byte[] actualUncompressedBytes = new byte[correctUncompressedBytes.Length]; - Decompress_WithoutState(compressedBytes, actualUncompressedBytes); - - Assert.Equal(correctUncompressedBytes, actualUncompressedBytes); - } - - [Theory] - [OuterLoop("Full set of UncompressedTestFiles takes around 15s to run")] - [MemberData(nameof(UncompressedTestFiles))] - public void WriteFully(string testFile) - { - byte[] correctUncompressedBytes = File.ReadAllBytes(testFile); - byte[] compressedBytes = new byte[BrotliEncoder.GetMaxCompressedLength(correctUncompressedBytes.Length)]; - byte[] actualUncompressedBytes = new byte[BrotliEncoder.GetMaxCompressedLength(correctUncompressedBytes.Length)]; - - Span destination = new Span(compressedBytes); - - Assert.True(BrotliEncoder.TryCompress(correctUncompressedBytes, destination, out int bytesWritten)); - Assert.True(BrotliDecoder.TryDecompress(destination, actualUncompressedBytes, out bytesWritten)); - Assert.Equal(correctUncompressedBytes.Length, bytesWritten); - - Assert.Equal(correctUncompressedBytes, actualUncompressedBytes.AsSpan(0, correctUncompressedBytes.Length).ToArray()); - } - - [Theory] - [OuterLoop("Full set of UncompressedTestFiles takes around 15s to run")] - [MemberData(nameof(UncompressedTestFiles))] - public void WriteWithState(string testFile) - { - byte[] correctUncompressedBytes = File.ReadAllBytes(testFile); - byte[] compressedBytes = new byte[BrotliEncoder.GetMaxCompressedLength(correctUncompressedBytes.Length)]; - byte[] actualUncompressedBytes = new byte[correctUncompressedBytes.Length]; - - Compress_WithState(correctUncompressedBytes, compressedBytes); - Decompress_WithState(compressedBytes, actualUncompressedBytes); - - Assert.Equal(correctUncompressedBytes, actualUncompressedBytes); - } - - [Theory] - [OuterLoop("Full set of UncompressedTestFiles takes around 15s to run")] - [MemberData(nameof(UncompressedTestFiles))] - public void WriteWithoutState(string testFile) - { - byte[] correctUncompressedBytes = File.ReadAllBytes(testFile); - byte[] compressedBytes = new byte[BrotliEncoder.GetMaxCompressedLength(correctUncompressedBytes.Length)]; - byte[] actualUncompressedBytes = new byte[correctUncompressedBytes.Length]; - - Compress_WithoutState(correctUncompressedBytes, compressedBytes); - Decompress_WithoutState(compressedBytes, actualUncompressedBytes); - - Assert.Equal(correctUncompressedBytes, actualUncompressedBytes); - } - - [Theory] - [OuterLoop("Full set of UncompressedTestFiles takes around 15s to run")] - [MemberData(nameof(UncompressedTestFiles))] - public void WriteStream(string testFile) - { - byte[] correctUncompressedBytes = File.ReadAllBytes(testFile); - byte[] compressedBytes = Compress_Stream(correctUncompressedBytes, CompressionLevel.Optimal).ToArray(); - byte[] actualUncompressedBytes = Decompress_Stream(compressedBytes).ToArray(); - - Assert.Equal(correctUncompressedBytes, actualUncompressedBytes); - } - - [OuterLoop("Full set of tests takes seconds to run")] - [Theory] - [InlineData(1, 0x400001, false)] - [InlineData(1, 0x400001, true)] - [InlineData(4, 0x800000, false)] - [InlineData(4, 0x800000, true)] - [InlineData(53, 12345, false)] - [InlineData(53, 12345, true)] - public static async Task Roundtrip_VaryingSizeReadsAndLengths_Success(int readSize, int totalLength, bool useAsync) - { - byte[] correctUncompressedBytes = Enumerable.Range(0, totalLength).Select(i => (byte)i).ToArray(); - byte[] compressedBytes = Compress_Stream(correctUncompressedBytes, CompressionLevel.Fastest).ToArray(); - byte[] actualBytes = new byte[correctUncompressedBytes.Length]; - - using (var s = new BrotliStream(new MemoryStream(compressedBytes), CompressionMode.Decompress)) - { - int totalRead = 0; - while (totalRead < actualBytes.Length) - { - int numRead = useAsync ? - await s.ReadAsync(actualBytes, totalRead, Math.Min(readSize, actualBytes.Length - totalRead)) : - s.Read(actualBytes, totalRead, Math.Min(readSize, actualBytes.Length - totalRead)); - totalRead += numRead; - } - - Assert.Equal(correctUncompressedBytes, actualBytes); - } - } - - [Theory] - [InlineData(1000, CompressionLevel.Fastest)] - [InlineData(1000, CompressionLevel.Optimal)] - [InlineData(1000, CompressionLevel.NoCompression)] - public static void Roundtrip_WriteByte_ReadByte_Success(int totalLength, CompressionLevel level) - { - byte[] correctUncompressedBytes = Enumerable.Range(0, totalLength).Select(i => (byte)i).ToArray(); - - byte[] compressedBytes; - using (var ms = new MemoryStream()) - { - var bs = new BrotliStream(ms, level); - foreach (byte b in correctUncompressedBytes) - { - bs.WriteByte(b); - } - bs.Dispose(); - compressedBytes = ms.ToArray(); - } - - byte[] decompressedBytes = new byte[correctUncompressedBytes.Length]; - using (var ms = new MemoryStream(compressedBytes)) - using (var bs = new BrotliStream(ms, CompressionMode.Decompress)) - { - for (int i = 0; i < decompressedBytes.Length; i++) - { - int b = bs.ReadByte(); - Assert.InRange(b, 0, 255); - decompressedBytes[i] = (byte)b; - } - Assert.Equal(-1, bs.ReadByte()); - Assert.Equal(-1, bs.ReadByte()); - } - - Assert.Equal(correctUncompressedBytes, decompressedBytes); - } - - private static void Compress_WithState(ReadOnlySpan input, Span output) - { - BrotliEncoder encoder = default; - while (!input.IsEmpty && !output.IsEmpty) - { - encoder.Compress(input, output, out int bytesConsumed, out int written, isFinalBlock: false); - input = input.Slice(bytesConsumed); - output = output.Slice(written); - } - encoder.Compress(ReadOnlySpan.Empty, output, out int bytesConsumed2, out int bytesWritten, isFinalBlock: true); - } - - private static void Decompress_WithState(ReadOnlySpan input, Span output) - { - BrotliDecoder decoder = default; - while (!input.IsEmpty && !output.IsEmpty) - { - decoder.Decompress(input, output, out int bytesConsumed, out int written); - input = input.Slice(bytesConsumed); - output = output.Slice(written); - } - } - - private static void Compress_WithoutState(ReadOnlySpan input, Span output) - { - BrotliEncoder.TryCompress(input, output, out int bytesWritten); - } - - private static void Decompress_WithoutState(ReadOnlySpan input, Span output) - { - BrotliDecoder.TryDecompress(input, output, out int bytesWritten); - } - - private static MemoryStream Compress_Stream(ReadOnlySpan input, CompressionLevel compressionLevel) - { - using (var inputStream = new MemoryStream(input.ToArray())) - { - var outputStream = new MemoryStream(); - var compressor = new BrotliStream(outputStream, compressionLevel, true); - inputStream.CopyTo(compressor); - compressor.Dispose(); - return outputStream; - } - } - - private static MemoryStream Decompress_Stream(ReadOnlySpan input) - { - using (var inputStream = new MemoryStream(input.ToArray())) - { - var outputStream = new MemoryStream(); - var decompressor = new BrotliStream(inputStream, CompressionMode.Decompress, true); - decompressor.CopyTo(outputStream); - decompressor.Dispose(); - return outputStream; - } - } - } -} diff --git a/src/libraries/System.IO.Compression.Brotli/tests/CompressionStreamUnitTests.Brotli.cs b/src/libraries/System.IO.Compression.Brotli/tests/CompressionStreamUnitTests.Brotli.cs index f1c3b00b94e564..86b2c42591441f 100644 --- a/src/libraries/System.IO.Compression.Brotli/tests/CompressionStreamUnitTests.Brotli.cs +++ b/src/libraries/System.IO.Compression.Brotli/tests/CompressionStreamUnitTests.Brotli.cs @@ -1,8 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Threading; -using System.Threading.Tasks; +using System.Buffers; +using System.Linq; using Xunit; namespace System.IO.Compression @@ -14,6 +14,7 @@ public class BrotliStreamUnitTests : CompressionStreamUnitTestBase public override Stream CreateStream(Stream stream, CompressionLevel level) => new BrotliStream(stream, level); public override Stream CreateStream(Stream stream, CompressionLevel level, bool leaveOpen) => new BrotliStream(stream, level, leaveOpen); public override Stream BaseStream(Stream stream) => ((BrotliStream)stream).BaseStream; + protected override bool ReadsMayBlockUntilBufferFullOrEOF => true; // The tests are relying on an implementation detail of BrotliStream, using knowledge of its internal buffer size // in various test calculations. Currently the implementation is using the ArrayPool, which will round up to a @@ -28,7 +29,367 @@ public class BrotliStreamUnitTests : CompressionStreamUnitTestBase public override void FlushAsync_DuringWriteAsync() { base.FlushAsync_DuringWriteAsync(); } [Fact] - [OuterLoop("Test takes ~6 seconds to run")] - public override void WriteAsync_DuringWriteAsync() { base.WriteAsync_DuringWriteAsync(); } + public void InvalidQuality() + { + Assert.Throws("quality", () => new BrotliEncoder(-1, 11)); + Assert.Throws("quality", () => new BrotliEncoder(12, 11)); + Assert.Throws("quality", () => BrotliEncoder.TryCompress(new ReadOnlySpan(), new Span(), out int bytesWritten, -1, 13)); + Assert.Throws("quality", () => BrotliEncoder.TryCompress(new ReadOnlySpan(), new Span(), out int bytesWritten, 12, 13)); + } + + [Fact] + public void InvalidWindow() + { + Assert.Throws("window", () => new BrotliEncoder(10, -1)); + Assert.Throws("window", () => new BrotliEncoder(10, 9)); + Assert.Throws("window", () => new BrotliEncoder(10, 25)); + Assert.Throws("window", () => BrotliEncoder.TryCompress(new ReadOnlySpan(), new Span(), out int bytesWritten, 6, -1)); + Assert.Throws("window", () => BrotliEncoder.TryCompress(new ReadOnlySpan(), new Span(), out int bytesWritten, 6, 9)); + Assert.Throws("window", () => BrotliEncoder.TryCompress(new ReadOnlySpan(), new Span(), out int bytesWritten, 6, 25)); + } + + [Fact] + public void GetMaxCompressedSize_Basic() + { + Assert.Throws("inputSize", () => BrotliEncoder.GetMaxCompressedLength(-1)); + Assert.Throws("inputSize", () => BrotliEncoder.GetMaxCompressedLength(2147483133)); + Assert.InRange(BrotliEncoder.GetMaxCompressedLength(2147483132), 0, int.MaxValue); + Assert.Equal(1, BrotliEncoder.GetMaxCompressedLength(0)); + } + + [Fact] + public void GetMaxCompressedSize() + { + string uncompressedFile = UncompressedTestFile(); + string compressedFile = CompressedTestFile(uncompressedFile); + int maxCompressedSize = BrotliEncoder.GetMaxCompressedLength((int)new FileInfo(uncompressedFile).Length); + int actualCompressedSize = (int)new FileInfo(compressedFile).Length; + Assert.True(maxCompressedSize >= actualCompressedSize, $"MaxCompressedSize: {maxCompressedSize}, ActualCompressedSize: {actualCompressedSize}"); + } + + /// + /// Test to ensure that when given an empty Destination span, the decoder will consume no input and write no output. + /// + [Fact] + public void Decompress_WithEmptyDestination() + { + string testFile = UncompressedTestFile(); + byte[] sourceBytes = File.ReadAllBytes(CompressedTestFile(testFile)); + byte[] destinationBytes = new byte[0]; + ReadOnlySpan source = new ReadOnlySpan(sourceBytes); + Span destination = new Span(destinationBytes); + + Assert.False(BrotliDecoder.TryDecompress(source, destination, out int bytesWritten), "TryDecompress completed successfully but should have failed due to too short of a destination array"); + Assert.Equal(0, bytesWritten); + + BrotliDecoder decoder = default; + var result = decoder.Decompress(source, destination, out int bytesConsumed, out bytesWritten); + Assert.Equal(0, bytesWritten); + Assert.Equal(0, bytesConsumed); + Assert.Equal(OperationStatus.DestinationTooSmall, result); + } + + /// + /// Test to ensure that when given an empty source span, the decoder will consume no input and write no output + /// + [Fact] + public void Decompress_WithEmptySource() + { + byte[] sourceBytes = new byte[0]; + byte[] destinationBytes = new byte[100000]; + ReadOnlySpan source = new ReadOnlySpan(sourceBytes); + Span destination = new Span(destinationBytes); + + Assert.False(BrotliDecoder.TryDecompress(source, destination, out int bytesWritten), "TryDecompress completed successfully but should have failed due to too short of a source array"); + Assert.Equal(0, bytesWritten); + + BrotliDecoder decoder = default; + var result = decoder.Decompress(source, destination, out int bytesConsumed, out bytesWritten); + Assert.Equal(0, bytesWritten); + Assert.Equal(0, bytesConsumed); + Assert.Equal(OperationStatus.NeedMoreData, result); + } + + /// + /// Test to ensure that when given an empty Destination span, the encoder consume no input and write no output + /// + [Fact] + public void Compress_WithEmptyDestination() + { + string testFile = UncompressedTestFile(); + byte[] correctUncompressedBytes = File.ReadAllBytes(testFile); + byte[] empty = new byte[0]; + ReadOnlySpan source = new ReadOnlySpan(correctUncompressedBytes); + Span destination = new Span(empty); + + Assert.False(BrotliEncoder.TryCompress(source, destination, out int bytesWritten), "TryCompress completed successfully but should have failed due to too short of a destination array"); + Assert.Equal(0, bytesWritten); + + BrotliEncoder encoder = default; + var result = encoder.Compress(source, destination, out int bytesConsumed, out bytesWritten, false); + Assert.Equal(0, bytesWritten); + Assert.Equal(0, bytesConsumed); + Assert.Equal(OperationStatus.DestinationTooSmall, result); + + result = encoder.Compress(source, destination, out bytesConsumed, out bytesWritten, isFinalBlock: true); + Assert.Equal(0, bytesWritten); + Assert.Equal(0, bytesConsumed); + Assert.Equal(OperationStatus.DestinationTooSmall, result); + } + + /// + /// Test to ensure that when given an empty source span, the decoder will consume no input and write no output (until the finishing block) + /// + [Fact] + public void Compress_WithEmptySource() + { + byte[] sourceBytes = new byte[0]; + byte[] destinationBytes = new byte[100000]; + ReadOnlySpan source = new ReadOnlySpan(sourceBytes); + Span destination = new Span(destinationBytes); + + Assert.True(BrotliEncoder.TryCompress(source, destination, out int bytesWritten)); + // The only byte written should be the Brotli end of stream byte which varies based on the window/quality + Assert.Equal(1, bytesWritten); + + BrotliEncoder encoder = default; + var result = encoder.Compress(source, destination, out int bytesConsumed, out bytesWritten, false); + Assert.Equal(0, bytesWritten); + Assert.Equal(0, bytesConsumed); + Assert.Equal(OperationStatus.Done, result); + + result = encoder.Compress(source, destination, out bytesConsumed, out bytesWritten, isFinalBlock: true); + Assert.Equal(1, bytesWritten); + Assert.Equal(0, bytesConsumed); + Assert.Equal(OperationStatus.Done, result); + } + + /// + /// Test that the decoder can handle partial chunks of flushed encoded data sent from the BrotliEncoder + /// + [Fact] + public void RoundTrip_Chunks() + { + int chunkSize = 100; + int totalSize = 20000; + BrotliEncoder encoder = default; + BrotliDecoder decoder = default; + for (int i = 0; i < totalSize; i += chunkSize) + { + byte[] uncompressed = new byte[chunkSize]; + new Random().NextBytes(uncompressed); + byte[] compressed = new byte[BrotliEncoder.GetMaxCompressedLength(chunkSize)]; + byte[] decompressed = new byte[chunkSize]; + var uncompressedSpan = new ReadOnlySpan(uncompressed); + var compressedSpan = new Span(compressed); + var decompressedSpan = new Span(decompressed); + + int totalWrittenThisIteration = 0; + var compress = encoder.Compress(uncompressedSpan, compressedSpan, out int bytesConsumed, out int bytesWritten, isFinalBlock: false); + totalWrittenThisIteration += bytesWritten; + compress = encoder.Flush(compressedSpan.Slice(bytesWritten), out bytesWritten); + totalWrittenThisIteration += bytesWritten; + + var res = decoder.Decompress(compressedSpan.Slice(0, totalWrittenThisIteration), decompressedSpan, out int decompressbytesConsumed, out int decompressbytesWritten); + Assert.Equal(totalWrittenThisIteration, decompressbytesConsumed); + Assert.Equal(bytesConsumed, decompressbytesWritten); + Assert.Equal(uncompressed, decompressedSpan.ToArray()); + } + } + + [Theory] + [OuterLoop("Full set of UncompressedTestFiles takes around 15s to run")] + [MemberData(nameof(UncompressedTestFiles))] + public void ReadFully(string testFile) + { + byte[] correctUncompressedBytes = File.ReadAllBytes(testFile); + byte[] compressedBytes = File.ReadAllBytes(CompressedTestFile(testFile)); + byte[] actualUncompressedBytes = new byte[correctUncompressedBytes.Length + 10000]; + ReadOnlySpan source = new ReadOnlySpan(compressedBytes); + Span destination = new Span(actualUncompressedBytes); + Assert.True(BrotliDecoder.TryDecompress(source, destination, out int bytesWritten), "TryDecompress did not complete successfully"); + Assert.Equal(correctUncompressedBytes.Length, bytesWritten); + Assert.Equal(correctUncompressedBytes, actualUncompressedBytes.AsSpan(0, correctUncompressedBytes.Length).ToArray()); + } + + [Theory] + [OuterLoop("Full set of UncompressedTestFiles takes around 15s to run")] + [MemberData(nameof(UncompressedTestFiles))] + public void ReadWithState(string testFile) + { + byte[] correctUncompressedBytes = File.ReadAllBytes(testFile); + byte[] compressedBytes = File.ReadAllBytes(CompressedTestFile(testFile)); + byte[] actualUncompressedBytes = new byte[correctUncompressedBytes.Length]; + Decompress_WithState(compressedBytes, actualUncompressedBytes); + + Assert.Equal(correctUncompressedBytes, actualUncompressedBytes); + } + + [Theory] + [OuterLoop("Full set of UncompressedTestFiles takes around 15s to run")] + [MemberData(nameof(UncompressedTestFiles))] + public void ReadWithoutState(string testFile) + { + byte[] correctUncompressedBytes = File.ReadAllBytes(testFile); + byte[] compressedBytes = File.ReadAllBytes(CompressedTestFile(testFile)); + byte[] actualUncompressedBytes = new byte[correctUncompressedBytes.Length]; + Decompress_WithoutState(compressedBytes, actualUncompressedBytes); + + Assert.Equal(correctUncompressedBytes, actualUncompressedBytes); + } + + [Theory] + [OuterLoop("Full set of UncompressedTestFiles takes around 15s to run")] + [MemberData(nameof(UncompressedTestFiles))] + public void WriteFully(string testFile) + { + byte[] correctUncompressedBytes = File.ReadAllBytes(testFile); + byte[] compressedBytes = new byte[BrotliEncoder.GetMaxCompressedLength(correctUncompressedBytes.Length)]; + byte[] actualUncompressedBytes = new byte[BrotliEncoder.GetMaxCompressedLength(correctUncompressedBytes.Length)]; + + Span destination = new Span(compressedBytes); + + Assert.True(BrotliEncoder.TryCompress(correctUncompressedBytes, destination, out int bytesWritten)); + Assert.True(BrotliDecoder.TryDecompress(destination, actualUncompressedBytes, out bytesWritten)); + Assert.Equal(correctUncompressedBytes.Length, bytesWritten); + + Assert.Equal(correctUncompressedBytes, actualUncompressedBytes.AsSpan(0, correctUncompressedBytes.Length).ToArray()); + } + + [Theory] + [OuterLoop("Full set of UncompressedTestFiles takes around 15s to run")] + [MemberData(nameof(UncompressedTestFiles))] + public void WriteWithState(string testFile) + { + byte[] correctUncompressedBytes = File.ReadAllBytes(testFile); + byte[] compressedBytes = new byte[BrotliEncoder.GetMaxCompressedLength(correctUncompressedBytes.Length)]; + byte[] actualUncompressedBytes = new byte[correctUncompressedBytes.Length]; + + Compress_WithState(correctUncompressedBytes, compressedBytes); + Decompress_WithState(compressedBytes, actualUncompressedBytes); + + Assert.Equal(correctUncompressedBytes, actualUncompressedBytes); + } + + [Theory] + [OuterLoop("Full set of UncompressedTestFiles takes around 15s to run")] + [MemberData(nameof(UncompressedTestFiles))] + public void WriteWithoutState(string testFile) + { + byte[] correctUncompressedBytes = File.ReadAllBytes(testFile); + byte[] compressedBytes = new byte[BrotliEncoder.GetMaxCompressedLength(correctUncompressedBytes.Length)]; + byte[] actualUncompressedBytes = new byte[correctUncompressedBytes.Length]; + + Compress_WithoutState(correctUncompressedBytes, compressedBytes); + Decompress_WithoutState(compressedBytes, actualUncompressedBytes); + + Assert.Equal(correctUncompressedBytes, actualUncompressedBytes); + } + + [Theory] + [OuterLoop("Full set of UncompressedTestFiles takes around 15s to run")] + [MemberData(nameof(UncompressedTestFiles))] + public void WriteStream(string testFile) + { + byte[] correctUncompressedBytes = File.ReadAllBytes(testFile); + byte[] compressedBytes = Compress_Stream(correctUncompressedBytes, CompressionLevel.Optimal).ToArray(); + byte[] actualUncompressedBytes = Decompress_Stream(compressedBytes).ToArray(); + + Assert.Equal(correctUncompressedBytes, actualUncompressedBytes); + } + + [Theory] + [InlineData(1000, CompressionLevel.Fastest)] + [InlineData(1000, CompressionLevel.Optimal)] + [InlineData(1000, CompressionLevel.NoCompression)] + public static void Roundtrip_WriteByte_ReadByte_Success(int totalLength, CompressionLevel level) + { + byte[] correctUncompressedBytes = Enumerable.Range(0, totalLength).Select(i => (byte)i).ToArray(); + + byte[] compressedBytes; + using (var ms = new MemoryStream()) + { + var bs = new BrotliStream(ms, level); + foreach (byte b in correctUncompressedBytes) + { + bs.WriteByte(b); + } + bs.Dispose(); + compressedBytes = ms.ToArray(); + } + + byte[] decompressedBytes = new byte[correctUncompressedBytes.Length]; + using (var ms = new MemoryStream(compressedBytes)) + using (var bs = new BrotliStream(ms, CompressionMode.Decompress)) + { + for (int i = 0; i < decompressedBytes.Length; i++) + { + int b = bs.ReadByte(); + Assert.InRange(b, 0, 255); + decompressedBytes[i] = (byte)b; + } + Assert.Equal(-1, bs.ReadByte()); + Assert.Equal(-1, bs.ReadByte()); + } + + Assert.Equal(correctUncompressedBytes, decompressedBytes); + } + + private static void Compress_WithState(ReadOnlySpan input, Span output) + { + BrotliEncoder encoder = default; + while (!input.IsEmpty && !output.IsEmpty) + { + encoder.Compress(input, output, out int bytesConsumed, out int written, isFinalBlock: false); + input = input.Slice(bytesConsumed); + output = output.Slice(written); + } + encoder.Compress(ReadOnlySpan.Empty, output, out int bytesConsumed2, out int bytesWritten, isFinalBlock: true); + } + + private static void Decompress_WithState(ReadOnlySpan input, Span output) + { + BrotliDecoder decoder = default; + while (!input.IsEmpty && !output.IsEmpty) + { + decoder.Decompress(input, output, out int bytesConsumed, out int written); + input = input.Slice(bytesConsumed); + output = output.Slice(written); + } + } + + private static void Compress_WithoutState(ReadOnlySpan input, Span output) + { + BrotliEncoder.TryCompress(input, output, out int bytesWritten); + } + + private static void Decompress_WithoutState(ReadOnlySpan input, Span output) + { + BrotliDecoder.TryDecompress(input, output, out int bytesWritten); + } + + private static MemoryStream Compress_Stream(ReadOnlySpan input, CompressionLevel compressionLevel) + { + using (var inputStream = new MemoryStream(input.ToArray())) + { + var outputStream = new MemoryStream(); + var compressor = new BrotliStream(outputStream, compressionLevel, true); + inputStream.CopyTo(compressor); + compressor.Dispose(); + return outputStream; + } + } + + private static MemoryStream Decompress_Stream(ReadOnlySpan input) + { + using (var inputStream = new MemoryStream(input.ToArray())) + { + var outputStream = new MemoryStream(); + var decompressor = new BrotliStream(inputStream, CompressionMode.Decompress, true); + decompressor.CopyTo(outputStream); + decompressor.Dispose(); + return outputStream; + } + } } } diff --git a/src/libraries/System.IO.Compression.Brotli/tests/System.IO.Compression.Brotli.Tests.csproj b/src/libraries/System.IO.Compression.Brotli/tests/System.IO.Compression.Brotli.Tests.csproj index 31950ce9235bb9..86112e76c058ac 100644 --- a/src/libraries/System.IO.Compression.Brotli/tests/System.IO.Compression.Brotli.Tests.csproj +++ b/src/libraries/System.IO.Compression.Brotli/tests/System.IO.Compression.Brotli.Tests.csproj @@ -4,7 +4,6 @@ - + + + + + diff --git a/src/libraries/System.IO.Compression/tests/CompressionStreamUnitTests.Deflate.cs b/src/libraries/System.IO.Compression/tests/CompressionStreamUnitTests.Deflate.cs index 493bb6bac1fa15..2cdf0224710dd8 100644 --- a/src/libraries/System.IO.Compression/tests/CompressionStreamUnitTests.Deflate.cs +++ b/src/libraries/System.IO.Compression/tests/CompressionStreamUnitTests.Deflate.cs @@ -76,28 +76,28 @@ private sealed class DerivedDeflateStream : DeflateStream public bool ReadArrayInvoked = false, WriteArrayInvoked = false; internal DerivedDeflateStream(Stream stream, CompressionMode mode, bool leaveOpen) : base(stream, mode, leaveOpen) { } - public override int Read(byte[] array, int offset, int count) + public override int Read(byte[] buffer, int offset, int count) { ReadArrayInvoked = true; - return base.Read(array, offset, count); + return base.Read(buffer, offset, count); } - public override Task ReadAsync(byte[] array, int offset, int count, CancellationToken cancellationToken) + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { ReadArrayInvoked = true; - return base.ReadAsync(array, offset, count, cancellationToken); + return base.ReadAsync(buffer, offset, count, cancellationToken); } - public override void Write(byte[] array, int offset, int count) + public override void Write(byte[] buffer, int offset, int count) { WriteArrayInvoked = true; - base.Write(array, offset, count); + base.Write(buffer, offset, count); } - public override Task WriteAsync(byte[] array, int offset, int count, CancellationToken cancellationToken) + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { WriteArrayInvoked = true; - return base.WriteAsync(array, offset, count, cancellationToken); + return base.WriteAsync(buffer, offset, count, cancellationToken); } } } diff --git a/src/libraries/System.IO.Compression/tests/CompressionStreamUnitTests.Gzip.cs b/src/libraries/System.IO.Compression/tests/CompressionStreamUnitTests.Gzip.cs index b3a45d0c25f94d..2f7195e7aaf8b8 100644 --- a/src/libraries/System.IO.Compression/tests/CompressionStreamUnitTests.Gzip.cs +++ b/src/libraries/System.IO.Compression/tests/CompressionStreamUnitTests.Gzip.cs @@ -287,28 +287,28 @@ private sealed class DerivedGZipStream : GZipStream internal DerivedGZipStream(Stream stream, CompressionMode mode) : base(stream, mode) { } internal DerivedGZipStream(Stream stream, CompressionMode mode, bool leaveOpen) : base(stream, mode, leaveOpen) { } - public override int Read(byte[] array, int offset, int count) + public override int Read(byte[] buffer, int offset, int count) { ReadArrayInvoked = true; - return base.Read(array, offset, count); + return base.Read(buffer, offset, count); } - public override Task ReadAsync(byte[] array, int offset, int count, CancellationToken cancellationToken) + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { ReadArrayInvoked = true; - return base.ReadAsync(array, offset, count, cancellationToken); + return base.ReadAsync(buffer, offset, count, cancellationToken); } - public override void Write(byte[] array, int offset, int count) + public override void Write(byte[] buffer, int offset, int count) { WriteArrayInvoked = true; - base.Write(array, offset, count); + base.Write(buffer, offset, count); } - public override Task WriteAsync(byte[] array, int offset, int count, CancellationToken cancellationToken) + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { WriteArrayInvoked = true; - return base.WriteAsync(array, offset, count, cancellationToken); + return base.WriteAsync(buffer, offset, count, cancellationToken); } } } diff --git a/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj b/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj index 80fc263c1b5859..63c5661623fc5d 100644 --- a/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj +++ b/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj @@ -25,8 +25,13 @@ + + + + + - \ No newline at end of file + From 230c85303862d6809622ace299e0aeb8da359ab4 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 13:29:32 -0400 Subject: [PATCH 20/24] Fix PipeReader/WriterStream argument validation --- .../System.IO.Pipelines/src/Resources/Strings.resx | 12 ++++++++++++ .../src/System.IO.Pipelines.csproj | 2 ++ .../src/System/IO/Pipelines/PipeReaderStream.cs | 12 ++++++++++++ .../src/System/IO/Pipelines/PipeWriterStream.cs | 10 ++++++++++ 4 files changed, 36 insertions(+) diff --git a/src/libraries/System.IO.Pipelines/src/Resources/Strings.resx b/src/libraries/System.IO.Pipelines/src/Resources/Strings.resx index 1801eb4fbe37e6..457f167172113f 100644 --- a/src/libraries/System.IO.Pipelines/src/Resources/Strings.resx +++ b/src/libraries/System.IO.Pipelines/src/Resources/Strings.resx @@ -120,6 +120,9 @@ The PipeReader has already advanced past the provided position. + + Positive number required. + Concurrent reads or writes are not supported. @@ -138,9 +141,18 @@ The PipeReader returned 0 bytes when the ReadResult was not completed or canceled. + + Cannot access a closed stream. + No reading operation to complete. + + Stream does not support reading. + + + Stream does not support writing. + Read was canceled on underlying PipeReader. diff --git a/src/libraries/System.IO.Pipelines/src/System.IO.Pipelines.csproj b/src/libraries/System.IO.Pipelines/src/System.IO.Pipelines.csproj index 127ab3b2439963..eacedb553fe7a0 100644 --- a/src/libraries/System.IO.Pipelines/src/System.IO.Pipelines.csproj +++ b/src/libraries/System.IO.Pipelines/src/System.IO.Pipelines.csproj @@ -36,6 +36,8 @@ + diff --git a/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/PipeReaderStream.cs b/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/PipeReaderStream.cs index e654113a6fecda..e87c44c7bc997a 100644 --- a/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/PipeReaderStream.cs +++ b/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/PipeReaderStream.cs @@ -47,6 +47,11 @@ public override void Flush() public override int Read(byte[] buffer, int offset, int count) { + if (buffer is null) + { + throw new ArgumentNullException(nameof(buffer)); + } + return ReadAsync(buffer, offset, count).GetAwaiter().GetResult(); } @@ -64,6 +69,11 @@ public sealed override int EndRead(IAsyncResult asyncResult) => public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { + if (buffer is null) + { + throw new ArgumentNullException(nameof(buffer)); + } + return ReadAsyncInternal(new Memory(buffer, offset, count), cancellationToken).AsTask(); } @@ -118,6 +128,8 @@ private async ValueTask ReadAsyncInternal(Memory buffer, Cancellation public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { + StreamHelpers.ValidateCopyToArgs(this, destination, bufferSize); + // Delegate to CopyToAsync on the PipeReader return _pipeReader.CopyToAsync(destination, cancellationToken); } diff --git a/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriterStream.cs b/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriterStream.cs index 915927f264c11b..6ef20672174dbc 100644 --- a/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriterStream.cs +++ b/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriterStream.cs @@ -58,11 +58,21 @@ public sealed override void EndWrite(IAsyncResult asyncResult) => public override void Write(byte[] buffer, int offset, int count) { + if (buffer is null) + { + throw new ArgumentNullException(nameof(buffer)); + } + WriteAsync(buffer, offset, count).GetAwaiter().GetResult(); } public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { + if (buffer is null) + { + throw new ArgumentNullException(nameof(buffer)); + } + ValueTask valueTask = _pipeWriter.WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken); return GetFlushResultAsTask(valueTask); From 5e5afba2a9dee54f2695f005ee5c7b3c0eba4a50 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 13:29:50 -0400 Subject: [PATCH 21/24] Add stream conformance tests for PipeReader/WriterStream --- ...eReaderWriterStreamTests.nonnetstandard.cs | 27 +++++++++++++++++++ .../tests/System.IO.Pipelines.Tests.csproj | 6 +++++ 2 files changed, 33 insertions(+) create mode 100644 src/libraries/System.IO.Pipelines/tests/PipeReaderWriterStreamTests.nonnetstandard.cs diff --git a/src/libraries/System.IO.Pipelines/tests/PipeReaderWriterStreamTests.nonnetstandard.cs b/src/libraries/System.IO.Pipelines/tests/PipeReaderWriterStreamTests.nonnetstandard.cs new file mode 100644 index 00000000000000..7f0d39527ffa21 --- /dev/null +++ b/src/libraries/System.IO.Pipelines/tests/PipeReaderWriterStreamTests.nonnetstandard.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO.Tests; +using System.Threading.Tasks; +using Xunit; + +namespace System.IO.Pipelines.Tests +{ + public class PipeReaderWriterStreamTests : ConnectedStreamConformanceTests + { + protected override Task CreateConnectedStreamsAsync() + { + var p = new Pipe(); + return Task.FromResult((p.Writer.AsStream(), p.Reader.AsStream())); + } + + protected override bool BlocksOnZeroByteReads => true; + protected override bool CansReturnFalseAfterDispose => false; + protected override string ReadWriteOffsetName => null; + protected override string ReadWriteCountName => null; + protected override Type UnsupportedConcurrentExceptionType => null; + + [ActiveIssue("Implementation doesn't currently special-case Dispose, treating it instead as completion.")] + public override Task Disposed_ThrowsObjectDisposedException() => base.Disposed_ThrowsObjectDisposedException(); + } +} diff --git a/src/libraries/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj b/src/libraries/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj index 5c0a2a305f4fee..ac34979c3b715f 100644 --- a/src/libraries/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj +++ b/src/libraries/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj @@ -43,6 +43,12 @@ + + + + + + From 6dc26b6f048accd952162b49936c7986230321c9 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 26 Oct 2020 15:53:39 -0400 Subject: [PATCH 22/24] Remove erroneous asserts from Stream.ReadWriteTask --- .../System.Private.CoreLib/src/System/IO/Stream.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs index d6a4a70e99e2da..68e4a631232e83 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs @@ -280,13 +280,13 @@ internal IAsyncResult BeginReadInternal( // As we're currently inside of it, we can get the current task // and grab the parameters from it. var thisTask = Task.InternalCurrent as ReadWriteTask; - Debug.Assert(thisTask != null && thisTask._stream != null && thisTask._buffer != null, - "Inside ReadWriteTask, InternalCurrent should be the ReadWriteTask, and stream and buffer should be set"); + Debug.Assert(thisTask != null && thisTask._stream != null, + "Inside ReadWriteTask, InternalCurrent should be the ReadWriteTask, and stream should be set"); try { // Do the Read and return the number of bytes read - return thisTask._stream.Read(thisTask._buffer, thisTask._offset, thisTask._count); + return thisTask._stream.Read(thisTask._buffer!, thisTask._offset, thisTask._count); } finally { @@ -443,13 +443,13 @@ internal IAsyncResult BeginWriteInternal( // As we're currently inside of it, we can get the current task // and grab the parameters from it. var thisTask = Task.InternalCurrent as ReadWriteTask; - Debug.Assert(thisTask != null && thisTask._stream != null && thisTask._buffer != null, - "Inside ReadWriteTask, InternalCurrent should be the ReadWriteTask, and stream and buffer should be set"); + Debug.Assert(thisTask != null && thisTask._stream != null, + "Inside ReadWriteTask, InternalCurrent should be the ReadWriteTask, and stream should be set"); try { // Do the Write - thisTask._stream.Write(thisTask._buffer, thisTask._offset, thisTask._count); + thisTask._stream.Write(thisTask._buffer!, thisTask._offset, thisTask._count); return 0; // not used, but signature requires a value be returned } finally @@ -590,7 +590,6 @@ public ReadWriteTask( { Debug.Assert(function != null); Debug.Assert(stream != null); - Debug.Assert(buffer != null); // Store the arguments _isRead = isRead; From c46cdd19d348d01bb30aa3502647ce4016f971d0 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 27 Oct 2020 16:27:26 -0400 Subject: [PATCH 23/24] Address PR feedback --- .../Tests/System/IO/StreamConformanceTests.cs | 122 ++++++++++-------- .../tests/FileStream/Pipes.cs | 8 +- ...icStreamConnectedStreamConformanceTests.cs | 17 ++- .../SslStreamConformanceTests.cs | 24 +++- .../SslStreamNetworkStreamTest.cs | 59 --------- .../FunctionalTests/NetworkStreamTest.cs | 17 +++ 6 files changed, 121 insertions(+), 126 deletions(-) diff --git a/src/libraries/Common/tests/Tests/System/IO/StreamConformanceTests.cs b/src/libraries/Common/tests/Tests/System/IO/StreamConformanceTests.cs index 08700fe74fa4e4..d13d95c723099f 100644 --- a/src/libraries/Common/tests/Tests/System/IO/StreamConformanceTests.cs +++ b/src/libraries/Common/tests/Tests/System/IO/StreamConformanceTests.cs @@ -42,8 +42,6 @@ public abstract class StreamConformanceTests /// is thrown (either because it's fully supported or not supported and non-deterministic). /// protected virtual Type UnsupportedConcurrentExceptionType => typeof(InvalidOperationException); - /// Exception type thrown from operations when the instance is disposed. - protected virtual Type DisposedExceptionType => typeof(ObjectDisposedException); /// Gets whether the stream is expected to be seekable. protected virtual bool CanSeek => false; @@ -151,8 +149,8 @@ protected async Task ValidateMisuseExceptionsAsync(Stream stream) // Disposed destination stream var disposedDestination = new MemoryStream(new byte[1]); disposedDestination.Dispose(); - Assert.Throws(DisposedExceptionType, () => { stream.CopyTo(disposedDestination); }); - Assert.Throws(DisposedExceptionType, () => { stream.CopyToAsync(disposedDestination); }); + Assert.Throws(() => { stream.CopyTo(disposedDestination); }); + Assert.Throws(() => { stream.CopyToAsync(disposedDestination); }); } else { @@ -262,51 +260,57 @@ protected async Task ValidateMisuseExceptionsAsync(Stream stream) protected async Task ValidateDisposedExceptionsAsync(Stream stream) { - // NOTE: Some streams may start returning false from the CanXx props after disposal, in which case this test ends up being a nop. + // Disposal should be idempotent and not throw + stream.Dispose(); + stream.DisposeAsync().AsTask().GetAwaiter().GetResult(); + stream.Close(); - if (stream.CanRead) - { - Assert.Throws(DisposedExceptionType, () => { stream.ReadByte(); }); - Assert.Throws(DisposedExceptionType, () => { stream.Read(new Span(new byte[1])); }); - Assert.Throws(DisposedExceptionType, () => { stream.Read(new byte[1], 0, 1); }); - await Assert.ThrowsAsync(async () => await stream.ReadAsync(new byte[1], 0, 1)); - await Assert.ThrowsAsync(async () => await stream.ReadAsync(new Memory(new byte[1]))); - Assert.Throws(DisposedExceptionType, () => { stream.EndRead(stream.BeginRead(new byte[1], 0, 1, null, null)); }); - Assert.Throws(DisposedExceptionType, () => { stream.CopyTo(new MemoryStream()); }); - await Assert.ThrowsAsync(async () => await stream.CopyToAsync(new MemoryStream())); - } + AssertDisposed(() => { stream.ReadByte(); }); + AssertDisposed(() => { stream.Read(new Span(new byte[1])); }); + AssertDisposed(() => { stream.Read(new byte[1], 0, 1); }); + await AssertDisposedAsync(async () => await stream.ReadAsync(new byte[1], 0, 1)); + await AssertDisposedAsync(async() => await stream.ReadAsync(new Memory(new byte[1]))); + AssertDisposed(() => { stream.EndRead(stream.BeginRead(new byte[1], 0, 1, null, null)); }); - if (stream.CanWrite) - { - Assert.Throws(DisposedExceptionType, () => { stream.WriteByte(1); }); - Assert.Throws(DisposedExceptionType, () => { stream.Write(new Span(new byte[1])); }); - Assert.Throws(DisposedExceptionType, () => { stream.Write(new byte[1], 0, 1); }); - await Assert.ThrowsAsync(async () => await stream.WriteAsync(new byte[1], 0, 1)); - await Assert.ThrowsAsync(async () => await stream.WriteAsync(new Memory(new byte[1]))); - Assert.Throws(DisposedExceptionType, () => { stream.EndWrite(stream.BeginWrite(new byte[1], 0, 1, null, null)); }); - } + AssertDisposed(() => { stream.WriteByte(1); }); + AssertDisposed(() => { stream.Write(new Span(new byte[1])); }); + AssertDisposed(() => { stream.Write(new byte[1], 0, 1); }); + await AssertDisposedAsync(async () => await stream.WriteAsync(new byte[1], 0, 1)); + await AssertDisposedAsync(async() => await stream.WriteAsync(new Memory(new byte[1]))); + AssertDisposed(() => { stream.EndWrite(stream.BeginWrite(new byte[1], 0, 1, null, null)); }); - if (stream.CanSeek) - { - Assert.Throws(DisposedExceptionType, () => stream.Length); - Assert.Throws(DisposedExceptionType, () => stream.Position); - Assert.Throws(DisposedExceptionType, () => stream.Position = 0); - Assert.Throws(DisposedExceptionType, () => stream.Seek(0, SeekOrigin.Begin)); - Assert.Throws(DisposedExceptionType, () => stream.SetLength(1)); - } + AssertDisposed(() => stream.Flush(), successAllowed: true); + await AssertDisposedAsync(() => stream.FlushAsync(), successAllowed: true); - if (stream.CanTimeout) + AssertDisposed(() => { stream.CopyTo(new MemoryStream()); }); + await AssertDisposedAsync(async () => await stream.CopyToAsync(new MemoryStream())); + + AssertDisposed(() => _ = stream.Length); + AssertDisposed(() => _ = stream.Position); + AssertDisposed(() => stream.Position = 0); + AssertDisposed(() => stream.Seek(0, SeekOrigin.Begin)); + AssertDisposed(() => stream.SetLength(1)); + + AssertDisposed(() => _ = stream.ReadTimeout); + AssertDisposed(() => stream.ReadTimeout = 1); + AssertDisposed(() => _ = stream.WriteTimeout); + AssertDisposed(() => stream.WriteTimeout = 1); + + void AssertDisposed(Action action, bool successAllowed = false) => ValidateDisposedException(Record.Exception(action), successAllowed); + + async Task AssertDisposedAsync(Func func, bool successAllowed = false) => ValidateDisposedException(await Record.ExceptionAsync(func).ConfigureAwait(false), successAllowed); + + void ValidateDisposedException(Exception e, bool successAllowed = false) { - Assert.Throws(DisposedExceptionType, () => stream.ReadTimeout); - Assert.Throws(DisposedExceptionType, () => stream.ReadTimeout = 1); - Assert.Throws(DisposedExceptionType, () => stream.WriteTimeout); - Assert.Throws(DisposedExceptionType, () => stream.WriteTimeout = 1); + // Behavior when disposed is inconsistent, and isn't specified by the Stream contract: types aren't supposed to be used + // after they're disposed. So, at least until we decide to be more strict, these tests are very liberal in what they except. + Assert.True( + (e is null && successAllowed) || + e is ObjectDisposedException || + e is NotSupportedException || + e is InvalidOperationException, + $"Unexpected: {e?.GetType().ToString() ?? "(null)"}"); } - - // Disposal should be idempotent and not throw - stream.Dispose(); - stream.DisposeAsync().AsTask().GetAwaiter().GetResult(); - stream.Close(); } protected async Task ValidatePrecanceledOperations_ThrowsCancellationException(Stream stream) @@ -793,7 +797,7 @@ public virtual async Task Read_Eof_Returns0(ReadWriteMode mode, bool dataAvailab await write; - if (mode == 0) + if (mode == ReadWriteMode.SyncByte) { Assert.Equal(-1, readable.ReadByte()); } @@ -839,6 +843,8 @@ public virtual async Task Read_DataStoredAtDesiredOffset(ReadWriteMode mode) _ => throw new Exception($"Unknown mode: {mode}"), }); + await write; + for (int i = 0; i < buffer.Length; i++) { Assert.Equal(i == offset ? value : 0, buffer[i]); @@ -1356,13 +1362,20 @@ await WhenAllOrAnyFailed( } public static IEnumerable CopyToAsync_AllDataCopied_MemberData() => - from byteCount in new int[] { 0, 1, 1024, 4096, 4095, 1024 * 1024 } - from asyncWrite in new bool[] { true, false } - select new object[] { byteCount, asyncWrite }; + from byteCount in new int[] { 0, 1, 1024, 4095, 4096 } + from useAsync in new bool[] { true, false } + select new object[] { byteCount, useAsync }; + + [OuterLoop] + [Theory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task CopyToAsync_AllDataCopied_Large(bool useAsync) => + await CopyToAsync_AllDataCopied(1024 * 1024, useAsync); [Theory] [MemberData(nameof(CopyToAsync_AllDataCopied_MemberData))] - public virtual async Task CopyToAsync_AllDataCopied(int byteCount, bool asyncWrite) + public virtual async Task CopyToAsync_AllDataCopied(int byteCount, bool useAsync) { using StreamPair streams = await CreateConnectedStreamsAsync(); (Stream writeable, Stream readable) = GetReadWritePair(streams); @@ -1371,7 +1384,7 @@ public virtual async Task CopyToAsync_AllDataCopied(int byteCount, bool asyncWri byte[] dataToCopy = RandomNumberGenerator.GetBytes(byteCount); Task copyTask; - if (asyncWrite) + if (useAsync) { copyTask = readable.CopyToAsync(results); await writeable.WriteAsync(dataToCopy); @@ -1394,7 +1407,7 @@ public virtual async Task Parallel_ReadWriteMultipleStreamsConcurrently() { await Task.WhenAll(Enumerable.Range(0, 20).Select(_ => Task.Run(async () => { - await CopyToAsync_AllDataCopied(byteCount: 10 * 1024, asyncWrite: true); + await CopyToAsync_AllDataCopied(byteCount: 10 * 1024, useAsync: true); }))); } @@ -1585,7 +1598,7 @@ public virtual async Task Flush_ValidOnReadableStream_Success() using StreamPair streams = await CreateConnectedStreamsAsync(); foreach (Stream stream in streams) { - if (stream.CanRead && !stream.CanWrite) + if (stream.CanRead) { stream.Flush(); await stream.FlushAsync(); @@ -1628,7 +1641,6 @@ public abstract class WrappingConnectedStreamConformanceTests : ConnectedStreamC protected virtual bool WrappedUsableAfterClose => true; protected virtual bool SupportsLeaveOpen => true; - // TODO: Add tests for sync calling sync and async calling async [Theory] [InlineData(false)] [InlineData(true)] @@ -1645,7 +1657,7 @@ public virtual async Task Flush_FlushesUnderlyingStream(bool flushAsync) var tracker = new CallTrackingStream(writeable); using StreamPair wrapper = await CreateWrappedConnectedStreamsAsync((tracker, readable)); - Assert.Equal(0, tracker.TimesCalled(nameof(tracker.Flush)) + tracker.TimesCalled(nameof(tracker.FlushAsync))); + int orig = tracker.TimesCalled(nameof(tracker.Flush)) + tracker.TimesCalled(nameof(tracker.FlushAsync)); tracker.WriteByte(1); @@ -1658,7 +1670,7 @@ public virtual async Task Flush_FlushesUnderlyingStream(bool flushAsync) wrapper.Stream1.Flush(); } - Assert.NotEqual(0, tracker.TimesCalled(nameof(tracker.Flush)) + tracker.TimesCalled(nameof(tracker.FlushAsync))); + Assert.InRange(tracker.TimesCalled(nameof(tracker.Flush)) + tracker.TimesCalled(nameof(tracker.FlushAsync)), orig + 1, int.MaxValue); } [Theory] @@ -1725,7 +1737,7 @@ await WhenAllOrAnyFailed( } else { - Assert.Throws(DisposedExceptionType, () => writeable.WriteByte(42)); + Assert.Throws(() => writeable.WriteByte(42)); } } diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/Pipes.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/Pipes.cs index f2edb09f4d6773..d4312b5055b827 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/Pipes.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/Pipes.cs @@ -36,13 +36,13 @@ protected override async Task CreateConnectedStreamsAsync() { string name = FileSystemTest.GetNamedPipeServerStreamName(); - var server = new NamedPipeServerStream(name, PipeDirection.InOut); - var client = new NamedPipeClientStream(".", name, PipeDirection.InOut); + var server = new NamedPipeServerStream(name, PipeDirection.In); + var client = new NamedPipeClientStream(".", name, PipeDirection.Out); await WhenAllOrAnyFailed(server.WaitForConnectionAsync(), client.ConnectAsync()); - var fs1 = new FileStream(new SafeFileHandle(server.SafePipeHandle.DangerousGetHandle(), true), FileAccess.ReadWrite); - var fs2 = new FileStream(new SafeFileHandle(client.SafePipeHandle.DangerousGetHandle(), true), FileAccess.ReadWrite); + var fs1 = new FileStream(new SafeFileHandle(server.SafePipeHandle.DangerousGetHandle(), true), FileAccess.Read); + var fs2 = new FileStream(new SafeFileHandle(client.SafePipeHandle.DangerousGetHandle(), true), FileAccess.Write); server.SafePipeHandle.SetHandleAsInvalid(); client.SafePipeHandle.SetHandleAsInvalid(); diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs index 9e50c2e50b646b..f7c1a4e0882a16 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs @@ -11,7 +11,18 @@ namespace System.Net.Quic.Tests { - public class QuicStreamConformanceTests : ConnectedStreamConformanceTests + public sealed class MockQuicStreamConformanceTests : QuicStreamConformanceTests + { + protected override QuicImplementationProvider Provider => QuicImplementationProviders.Mock; + } + + [ConditionalClass(typeof(QuicTestBase), nameof(QuicTestBase.IsSupported))] + public sealed class MsQuicQuicStreamConformanceTests : QuicStreamConformanceTests + { + protected override QuicImplementationProvider Provider => QuicImplementationProviders.MsQuic; + } + + public abstract class QuicStreamConformanceTests : ConnectedStreamConformanceTests { // TODO: These are all hanging, likely due to Stream close behavior. [ActiveIssue("https://github.com/dotnet/runtime/issues/756")] @@ -23,9 +34,11 @@ public class QuicStreamConformanceTests : ConnectedStreamConformanceTests [ActiveIssue("https://github.com/dotnet/runtime/issues/756")] public override Task Write_DataReadFromDesiredOffset(ReadWriteMode mode) => base.Write_DataReadFromDesiredOffset(mode); + protected abstract QuicImplementationProvider Provider { get; } + protected override async Task CreateConnectedStreamsAsync() { - QuicImplementationProvider provider = QuicImplementationProviders.Mock; + QuicImplementationProvider provider = Provider; var protocol = new SslApplicationProtocol("quictest"); var listener = new QuicListener( diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamConformanceTests.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamConformanceTests.cs index bfacfb14306873..152d7d75deccf9 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamConformanceTests.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamConformanceTests.cs @@ -8,19 +8,17 @@ namespace System.Net.Security.Tests { - public sealed class SslStreamMemoryConformanceTests : ConnectedStreamConformanceTests + public abstract class SslStreamConformanceTests : WrappingConnectedStreamConformanceTests { protected override bool UsableAfterCanceledReads => false; protected override bool BlocksOnZeroByteReads => true; protected override Type UnsupportedConcurrentExceptionType => typeof(NotSupportedException); - protected override async Task CreateConnectedStreamsAsync() + protected override async Task CreateWrappedConnectedStreamsAsync(StreamPair wrapped, bool leaveOpen = false) { - (Stream stream1, Stream stream2) = ConnectedStreams.CreateBidirectional(); - X509Certificate2? cert = Test.Common.Configuration.Certificates.GetServerCertificate(); - var ssl1 = new SslStream(stream1, false, delegate { return true; }); - var ssl2 = new SslStream(stream2, false, delegate { return true; }); + var ssl1 = new SslStream(wrapped.Stream1, leaveOpen, delegate { return true; }); + var ssl2 = new SslStream(wrapped.Stream2, leaveOpen, delegate { return true; }); await new[] { @@ -31,4 +29,18 @@ await new[] return new StreamPair(ssl1, ssl2); } } + + public sealed class SslStreamMemoryConformanceTests : SslStreamConformanceTests + { + protected override Task CreateConnectedStreamsAsync() => + CreateWrappedConnectedStreamsAsync(ConnectedStreams.CreateBidirectional()); + } + + public sealed class SslStreamNetworkConformanceTests : SslStreamConformanceTests + { + protected override bool CanTimeout => true; + + protected override Task CreateConnectedStreamsAsync() => + CreateWrappedConnectedStreamsAsync(TestHelper.GetConnectedTcpStreams()); + } } diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs index ce8c2625b19dbb..40c6b200349aa1 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs @@ -27,65 +27,6 @@ static SslStreamNetworkStreamTest() (_serverCert, _serverChain) = TestHelper.GenerateCertificates("localhost", nameof(SslStreamNetworkStreamTest)); } - [Fact] - public async Task SslStream_SendReceiveOverNetworkStream_Ok() - { - TcpListener listener = new TcpListener(IPAddress.Loopback, 0); - - using (X509Certificate2 serverCertificate = Configuration.Certificates.GetServerCertificate()) - using (TcpClient client = new TcpClient()) - { - listener.Start(); - - Task clientConnectTask = client.ConnectAsync(IPAddress.Loopback, ((IPEndPoint)listener.LocalEndpoint).Port); - Task listenerAcceptTask = listener.AcceptTcpClientAsync(); - - await Task.WhenAll(clientConnectTask, listenerAcceptTask); - - TcpClient server = listenerAcceptTask.Result; - using (SslStream clientStream = new SslStream( - client.GetStream(), - false, - new RemoteCertificateValidationCallback(ValidateServerCertificate), - null, - EncryptionPolicy.RequireEncryption)) - using (SslStream serverStream = new SslStream( - server.GetStream(), - false, - null, - null, - EncryptionPolicy.RequireEncryption)) - { - - Task clientAuthenticationTask = clientStream.AuthenticateAsClientAsync( - serverCertificate.GetNameInfo(X509NameType.SimpleName, false), - null, - SslProtocols.Tls12, - false); - - Task serverAuthenticationTask = serverStream.AuthenticateAsServerAsync( - serverCertificate, - false, - SslProtocols.Tls12, - false); - - await Task.WhenAll(clientAuthenticationTask, serverAuthenticationTask); - - byte[] writeBuffer = new byte[256]; - Task writeTask = clientStream.WriteAsync(writeBuffer, 0, writeBuffer.Length); - - byte[] readBuffer = new byte[256]; - Task readTask = serverStream.ReadAsync(readBuffer, 0, readBuffer.Length); - - await TestConfiguration.WhenAllOrAnyFailedWithTimeout(writeTask, readTask); - - Assert.InRange(readTask.Result, 1, 256); - } - } - - listener.Stop(); - } - [ConditionalFact] [PlatformSpecific(TestPlatforms.Linux)] // This only applies where OpenSsl is used. public async Task SslStream_SendReceiveOverNetworkStream_AuthenticationException() diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/NetworkStreamTest.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/NetworkStreamTest.cs index 0c9c3e2cfb4a2e..5557dc84ae9402 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/NetworkStreamTest.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/NetworkStreamTest.cs @@ -291,6 +291,23 @@ await RunWithConnectedNetworkStreamsAsync(async (server, client) => }); } + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task DisposedClosed_MembersThrowObjectDisposedException(bool close) + { + await RunWithConnectedNetworkStreamsAsync((server, _) => + { + if (close) server.Close(); + else server.Dispose(); + + // Unique members to NetworkStream; others covered by stream conformance tests + Assert.Throws(() => server.DataAvailable); + + return Task.CompletedTask; + }); + } + [Fact] public async Task DisposeSocketDirectly_ReadWriteThrowNetworkException() { From ac6aff54bf39820ce58556eac97b166cc1da9211 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 27 Oct 2020 22:05:08 -0400 Subject: [PATCH 24/24] Fix a few tests in CI --- .../Common/tests/Tests/System/IO/StreamConformanceTests.cs | 4 +--- .../QuicStreamConnectedStreamConformanceTests.cs | 4 +++- .../tests/FunctionalTests/NetworkStreamTest.cs | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libraries/Common/tests/Tests/System/IO/StreamConformanceTests.cs b/src/libraries/Common/tests/Tests/System/IO/StreamConformanceTests.cs index d13d95c723099f..9df2a9c3062aac 100644 --- a/src/libraries/Common/tests/Tests/System/IO/StreamConformanceTests.cs +++ b/src/libraries/Common/tests/Tests/System/IO/StreamConformanceTests.cs @@ -14,6 +14,7 @@ namespace System.IO.Tests { /// Base class providing tests for any Stream-derived type. + [PlatformSpecific(~TestPlatforms.Browser)] // lots of operations aren't supported on browser public abstract class StreamConformanceTests { /// Gets the name of the byte[] argument to Read/Write methods. @@ -1511,9 +1512,6 @@ public virtual async Task WriteAsync_CancelPendingWrite_SucceedsOrThrowsOperatio using StreamPair streams = await CreateConnectedStreamsAsync(); foreach ((Stream writeable, Stream readable) in GetReadWritePairs(streams)) { - await Assert.ThrowsAnyAsync(() => writeable.WriteAsync(new byte[1], 0, 1, new CancellationToken(true))); - await Assert.ThrowsAnyAsync(async () => { await writeable.WriteAsync(new Memory(new byte[1]), new CancellationToken(true)); }); - var buffer = new byte[BufferedSize + 1]; Exception e; diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs index f7c1a4e0882a16..73914bdc2fe7ce 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs @@ -28,7 +28,9 @@ public abstract class QuicStreamConformanceTests : ConnectedStreamConformanceTes [ActiveIssue("https://github.com/dotnet/runtime/issues/756")] public override Task Read_Eof_Returns0(ReadWriteMode mode, bool dataAvailableFirst) => base.Read_Eof_Returns0(mode, dataAvailableFirst); [ActiveIssue("https://github.com/dotnet/runtime/issues/756")] - public override Task CopyToAsync_AllDataCopied(int byteCount, bool asyncWrite) => base.CopyToAsync_AllDataCopied(byteCount, asyncWrite); + public override Task CopyToAsync_AllDataCopied(int byteCount, bool useAsync) => base.CopyToAsync_AllDataCopied(byteCount, useAsync); + [ActiveIssue("https://github.com/dotnet/runtime/issues/756")] + public override Task CopyToAsync_AllDataCopied_Large(bool useAsync) => base.CopyToAsync_AllDataCopied_Large(useAsync); [ActiveIssue("https://github.com/dotnet/runtime/issues/756")] public override Task Dispose_ClosesStream(int disposeMode) => base.Dispose_ClosesStream(disposeMode); [ActiveIssue("https://github.com/dotnet/runtime/issues/756")] diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/NetworkStreamTest.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/NetworkStreamTest.cs index 5557dc84ae9402..b812da8c7fd7ea 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/NetworkStreamTest.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/NetworkStreamTest.cs @@ -18,7 +18,6 @@ public class NetworkStreamTest : ConnectedStreamConformanceTests protected override bool CanTimeout => true; protected override Type InvalidIAsyncResultExceptionType => typeof(IOException); protected override bool FlushRequiredToWriteData => false; - protected override int BufferedSize => 99_999_999; // something sufficiently large to exhaust kernel buffer protected override Type UnsupportedConcurrentExceptionType => null; protected override bool ReadWriteValueTasksProtectSingleConsumption => true; protected override Task CreateConnectedStreamsAsync()