Add stream conformance tests for TranscodingStream (#44248)
authorStephen Toub <stoub@microsoft.com>
Thu, 5 Nov 2020 18:32:44 +0000 (13:32 -0500)
committerGitHub <noreply@github.com>
Thu, 5 Nov 2020 18:32:44 +0000 (13:32 -0500)
* Add stream conformance tests for TranscodingStream

* Special-case 0-length input buffers to TranscodingStream.Write{Async}

The base implementation of Encoder.Convert doesn't like empty inputs.  Regardless, if the input is empty, we can avoid a whole bunch of unnecessary work.

src/libraries/System.Private.CoreLib/src/System/Text/TranscodingStream.cs
src/libraries/System.Text.Encoding/tests/Encoding/TranscodingStreamTests.cs
src/libraries/System.Text.Encoding/tests/System.Text.Encoding.Tests.csproj

index 4bc5f5f..71a8d4b 100644 (file)
@@ -459,6 +459,11 @@ namespace System.Text
         {
             EnsurePreWriteConditions();
 
+            if (buffer.IsEmpty)
+            {
+                return;
+            }
+
             int rentalLength = Math.Clamp(buffer.Length, MinWriteRentedArraySize, MaxWriteRentedArraySize);
 
             char[] scratchChars = ArrayPool<char>.Shared.Rent(rentalLength);
@@ -527,6 +532,11 @@ namespace System.Text
                 return ValueTask.FromCanceled(cancellationToken);
             }
 
+            if (buffer.IsEmpty)
+            {
+                return ValueTask.CompletedTask;
+            }
+
             return WriteAsyncCore(buffer, cancellationToken);
             async ValueTask WriteAsyncCore(ReadOnlyMemory<byte> remainingOuterEncodedBytes, CancellationToken cancellationToken)
             {
index 5eb16c1..c937793 100644 (file)
@@ -2,7 +2,9 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.IO;
+using System.IO.Tests;
 using System.Threading;
 using System.Threading.Tasks;
 using Moq;
@@ -10,8 +12,20 @@ using Xunit;
 
 namespace System.Text.Tests
 {
-    public class TranscodingStreamTests
+    public class TranscodingStreamTests : ConnectedStreamConformanceTests
     {
+        protected override bool FlushRequiredToWriteData => true;
+        protected override bool FlushGuaranteesAllDataWritten => false;
+        protected override bool BlocksOnZeroByteReads => true;
+
+        protected override Task<StreamPair> CreateConnectedStreamsAsync()
+        {
+            (Stream stream1, Stream stream2) = ConnectedStreams.CreateBidirectional();
+            return Task.FromResult<StreamPair>(
+                (Encoding.CreateTranscodingStream(stream1, new IdentityEncoding(), new IdentityEncoding()),
+                 Encoding.CreateTranscodingStream(stream2, new IdentityEncoding(), new IdentityEncoding())));
+        }
+
         public static IEnumerable<object[]> ReadWriteTestBufferLengths
         {
             get
@@ -668,7 +682,7 @@ namespace System.Text.Tests
         }
 
         [Fact]
-        public async Task WriteAsync()
+        public async Task WriteAsync_WithFullData()
         {
             MemoryStream sink = new MemoryStream();
             CancellationToken expectedFlushAsyncCancellationToken = new CancellationTokenSource().Token;
@@ -892,5 +906,41 @@ namespace System.Text.Tests
                 }
             }
         }
+
+        /// <summary>A custom encoding that's used to roundtrip from bytes to bytes through a string.</summary>
+        private sealed class IdentityEncoding : Encoding
+        {
+            public override int GetByteCount(char[] chars, int index, int count) => count;
+
+            public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
+            {
+                Span<char> span = chars.AsSpan(charIndex, charCount);
+                for (int i = 0; i < span.Length; i++)
+                {
+                    Debug.Assert(span[i] <= 0xFF);
+                    bytes[byteIndex + i] = (byte)span[i];
+                }
+                return charCount;
+            }
+
+            public override int GetCharCount(byte[] bytes, int index, int count) => count;
+
+            public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
+            {
+                Span<byte> span = bytes.AsSpan(byteIndex, byteCount);
+                for (int i = 0; i < span.Length; i++)
+                {
+                    Debug.Assert(span[i] <= 0xFF);
+                    chars[charIndex + i] = (char)span[i];
+                }
+                return byteCount;
+            }
+
+            public override int GetMaxByteCount(int charCount) => charCount;
+
+            public override int GetMaxCharCount(int byteCount) => byteCount;
+
+            public override byte[] GetPreamble() => Array.Empty<byte>();
+        }
     }
 }
index 78c22e0..74bcfdc 100644 (file)
     <Compile Include="UnicodeEncoding\UnicodeEncoding.cs" />
     <Compile Include="Decoder\Decoder.cs" />
     <Compile Include="Encoder\Encoder.cs" />
+    <Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
+    <Compile Include="$(CommonTestPath)Tests\System\IO\StreamConformanceTests.cs" Link="Common\System\IO\StreamConformanceTests.cs" />
+    <Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
+    <Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
+    <Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
+    <Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
   </ItemGroup>
   <ItemGroup>
     <None Include="runtimeconfig.template.json" />