Skip to content

Commit

Permalink
Add stream conformance tests for TranscodingStream (#44248)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
stephentoub authored Nov 5, 2020
1 parent e2d115e commit 82e5039
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,11 @@ public override void Write(ReadOnlySpan<byte> buffer)
{
EnsurePreWriteConditions();

if (buffer.IsEmpty)
{
return;
}

int rentalLength = Math.Clamp(buffer.Length, MinWriteRentedArraySize, MaxWriteRentedArraySize);

char[] scratchChars = ArrayPool<char>.Shared.Rent(rentalLength);
Expand Down Expand Up @@ -527,6 +532,11 @@ public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationTo
return ValueTask.FromCanceled(cancellationToken);
}

if (buffer.IsEmpty)
{
return ValueTask.CompletedTask;
}

return WriteAsyncCore(buffer, cancellationToken);
async ValueTask WriteAsyncCore(ReadOnlyMemory<byte> remainingOuterEncodedBytes, CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,30 @@
// 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;
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
Expand Down Expand Up @@ -668,7 +682,7 @@ public void Write_WithInvalidArgs_Throws()
}

[Fact]
public async Task WriteAsync()
public async Task WriteAsync_WithFullData()
{
MemoryStream sink = new MemoryStream();
CancellationToken expectedFlushAsyncCancellationToken = new CancellationTokenSource().Token;
Expand Down Expand Up @@ -892,5 +906,41 @@ public override bool MovePrevious()
}
}
}

/// <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>();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@
<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" />
Expand Down

0 comments on commit 82e5039

Please sign in to comment.