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"); } 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" /> + + + + + + 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/Common/tests/System/IO/ConnectedStreams.cs b/src/libraries/Common/tests/System/IO/ConnectedStreams.cs index a5ea76a245fdab..8d084e2e3cfd60 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,233 @@ 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(); + ThrowIfReadingNotSupported(); + + 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(); + ThrowIfWritingNotSupported(); + + _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 +326,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 +451,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)); + } } } } 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()); + } +} 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..9df2a9c3062aac --- /dev/null +++ b/src/libraries/Common/tests/Tests/System/IO/StreamConformanceTests.cs @@ -0,0 +1,1853 @@ +// 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. + [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. + 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); + + /// 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(() => { stream.CopyTo(disposedDestination); }); + Assert.Throws(() => { 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) + { + // Disposal should be idempotent and not throw + stream.Dispose(); + stream.DisposeAsync().AsTask().GetAwaiter().GetResult(); + stream.Close(); + + 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)); }); + + 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)); }); + + AssertDisposed(() => stream.Flush(), successAllowed: true); + await AssertDisposedAsync(() => stream.FlushAsync(), successAllowed: true); + + 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) + { + // 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)"}"); + } + } + + 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 == ReadWriteMode.SyncByte) + { + 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}"), + }); + + await write; + + 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, 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 useAsync) + { + using StreamPair streams = await CreateConnectedStreamsAsync(); + (Stream writeable, Stream readable) = GetReadWritePair(streams); + + var results = new MemoryStream(); + byte[] dataToCopy = RandomNumberGenerator.GetBytes(byteCount); + + Task copyTask; + if (useAsync) + { + 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, useAsync: 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)) + { + 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.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; + + [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)); + + int orig = tracker.TimesCalled(nameof(tracker.Flush)) + tracker.TimesCalled(nameof(tracker.FlushAsync)); + + tracker.WriteByte(1); + + if (flushAsync) + { + await wrapper.Stream1.FlushAsync(); + } + else + { + wrapper.Stream1.Flush(); + } + + Assert.InRange(tracker.TimesCalled(nameof(tracker.Flush)) + tracker.TimesCalled(nameof(tracker.FlushAsync)), orig + 1, int.MaxValue); + } + + [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(() => 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(); + } +} 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 { 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/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. 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 + diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/Pipes.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/Pipes.cs index fd366dc702ffff..d4312b5055b827 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.In); + var client = new NamedPipeClientStream(".", name, PipeDirection.Out); - 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.Read); + var fs2 = new FileStream(new SafeFileHandle(client.SafePipeHandle.DangerousGetHandle(), true), FileAccess.Write); - 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/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.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 @@ + + + + + + + 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); 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 @@ + + + + + + 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/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) 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 @@ + - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + - + - + 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 @@ + + + + + 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..73914bdc2fe7ce --- /dev/null +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs @@ -0,0 +1,92 @@ +// 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 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")] + 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 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")] + public override Task Write_DataReadFromDesiredOffset(ReadWriteMode mode) => base.Write_DataReadFromDesiredOffset(mode); + + protected abstract QuicImplementationProvider Provider { get; } + + protected override async Task CreateConnectedStreamsAsync() + { + QuicImplementationProvider provider = Provider; + 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 @@ + + + + + + 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..152d7d75deccf9 --- /dev/null +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamConformanceTests.cs @@ -0,0 +1,46 @@ +// 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 abstract class SslStreamConformanceTests : WrappingConnectedStreamConformanceTests + { + protected override bool UsableAfterCanceledReads => false; + protected override bool BlocksOnZeroByteReads => true; + protected override Type UnsupportedConcurrentExceptionType => typeof(NotSupportedException); + + protected override async Task CreateWrappedConnectedStreamsAsync(StreamPair wrapped, bool leaveOpen = false) + { + X509Certificate2? cert = Test.Common.Configuration.Certificates.GetServerCertificate(); + var ssl1 = new SslStream(wrapped.Stream1, leaveOpen, delegate { return true; }); + var ssl2 = new SslStream(wrapped.Stream2, leaveOpen, 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); + } + } + + 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.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 @@ + + + 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(); } 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..b812da8c7fd7ea 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/NetworkStreamTest.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/NetworkStreamTest.cs @@ -3,17 +3,36 @@ 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 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,34 +290,21 @@ await RunWithConnectedNetworkStreamsAsync(async (server, client) => }); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task DisposedClosed_MembersThrowObjectDisposedException(bool close) - { - await RunWithConnectedNetworkStreamsAsync((server, _) => + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task DisposedClosed_MembersThrowObjectDisposedException(bool close) + { + await RunWithConnectedNetworkStreamsAsync((server, _) => { if (close) server.Close(); - else server.Dispose(); + else server.Dispose(); + // Unique members to NetworkStream; others covered by stream conformance tests 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; - }); + return Task.CompletedTask; + }); } [Fact] @@ -329,17 +335,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 +347,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 +388,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 +414,6 @@ await RunWithConnectedNetworkStreamsAsync(async (stream, _) => }); } - [Fact] public async Task CopyToAsync_NonReadableSourceStream_Throws() { @@ -734,111 +425,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 +515,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); }); 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.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.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; diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index f6d327eb35c2cf..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; } @@ -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; } 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) 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 @@ + + + + + +