Skip to content
5 changes: 3 additions & 2 deletions src/libraries/Common/tests/Common.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,9 @@
Link="System\IO\PathInternal.cs" />
<Compile Include="System\IO\ConnectedStreams.cs" Link="System\IO\ConnectedStreams.cs" />
<Compile Include="Tests\System\IO\ConnectedStreamsTests.cs" />
<Compile Include="Tests\System\IO\StreamConformanceTests.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="Common\System\Net\ArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)'=='true'">
<Compile Include="$(CoreLibSharedDir)System\IO\PathInternal.Windows.cs"
Expand Down Expand Up @@ -154,4 +152,7 @@
<Folder Include="System\Net\Sockets\" />
<Folder Include="System\Net\VirtualNetwork\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(CommonTestPath)StreamConformanceTests\StreamConformanceTests.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFrameworks>$(NetCoreAppCurrent)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Include="System\IO\*" />
</ItemGroup>
<ItemGroup>
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="System\Threading\Tasks\TaskToApm.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="Common\System\Net\ArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonPath)System\IO\DelegatingStream.cs" Link="Common\System\IO\DelegatingStream.cs" />
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
Comment on lines +11 to +16

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should most of these files be in TestUtilities instead?

@geoffkizer geoffkizer Mar 7, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(edit to explain in more detail)

The ones in CommonPath are shared source files (not test files) and thus have to be internal, and can't be in TestUtilities.

CallTrackingStream is a shared test code, so we have more flexibility there. We could move it to TestUtilities, but it wasn't there before so I didn't. Right now it's only used in the stream conformance tests. so another possibility would be to move it out of shared test code and into the stream conformance tests proper. I don't have a strong opinion here.

</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DotNet.XUnitExtensions" Version="$(MicrosoftDotNetXUnitExtensionsVersion)" />
<PackageReference Include="xunit.core" Version="$(XUnitVersion)" ExcludeAssets="build" />
<PackageReference Include="xunit.assert" Version="$(XUnitVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(CommonTestPath)TestUtilities\TestUtilities.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2612,6 +2612,11 @@ public abstract class WrappingConnectedStreamConformanceTests : ConnectedStreamC
protected abstract Task<StreamPair> CreateWrappedConnectedStreamsAsync(StreamPair wrapped, bool leaveOpen = false);
protected virtual bool WrappedUsableAfterClose => true;
protected virtual bool SupportsLeaveOpen => true;
/// <summary>
/// Indicates whether the stream will issue a zero byte read on the underlying stream when a user performs
/// a zero byte read and no data is currently available to return to the user.
/// </summary>
protected virtual bool ZeroByteReadPerformsZeroByteReadOnUnderlyingStream => false;

[Theory]
[InlineData(false)]
Expand Down Expand Up @@ -2788,6 +2793,146 @@ await WhenAllOrAnyFailed(
}));
}
}

[Theory]
[InlineData(ReadWriteMode.SyncArray)]
[InlineData(ReadWriteMode.SyncSpan)]
[InlineData(ReadWriteMode.AsyncArray)]
[InlineData(ReadWriteMode.AsyncMemory)]
[InlineData(ReadWriteMode.SyncAPM)]
[InlineData(ReadWriteMode.AsyncAPM)]
public virtual async Task ZeroByteRead_PerformsZeroByteReadOnUnderlyingStreamWhenDataNeeded(ReadWriteMode mode)
{
if (!ZeroByteReadPerformsZeroByteReadOnUnderlyingStream)
{
return;
}

// This is the data we will send across the connected streams. We assume this data will both
// (a) produce at least two readable bytes, so we can unblock the reader and read a single byte without clearing its buffer; and
// (b) produce no more than 1K of readable bytes, so we can clear the reader buffer below.
// If this isn't the case for some Stream(s), we can modify the data or parameterize it per Stream.
byte[] data = Encoding.UTF8.GetBytes("hello world");

using StreamPair innerStreams = ConnectedStreams.CreateBidirectional();
(Stream innerWriteable, Stream innerReadable) = GetReadWritePair(innerStreams);

var tracker = new ZeroByteReadTrackingStream(innerReadable);
using StreamPair streams = await CreateWrappedConnectedStreamsAsync((innerWriteable, tracker));

(Stream writeable, Stream readable) = GetReadWritePair(streams);

for (int iter = 0; iter < 2; iter++)
{
// Register to be signalled for the zero byte read.
var signalTask = tracker.WaitForZeroByteReadAsync();

// Issue zero byte read against wrapper stream.
Task<int> zeroByteRead = Task.Run(() => ReadAsync(mode, readable, Array.Empty<byte>(), 0, 0));

// The tracker stream will signal us when the zero byte read actually happens.
await signalTask;

// Write some data (see notes above re 'data')
await writeable.WriteAsync(data);
if (FlushRequiredToWriteData)
{
await writeable.FlushAsync();
}

// Reader should be unblocked, and we should have issued a zero byte read against the underlying stream as part of unblocking.
int bytesRead = await zeroByteRead;
Assert.Equal(0, bytesRead);

byte[] buffer = new byte[1024];

// Should be able to read one byte without blocking
var readTask = ReadAsync(mode, readable, buffer, 0, 1);
Assert.True(readTask.IsCompleted);
bytesRead = await readTask;
Assert.Equal(1, bytesRead);

// Issue zero byte read against wrapper stream. Since there is still data available, this should complete immediately and not do another zero-byte read.
readTask = ReadAsync(mode, readable, Array.Empty<byte>(), 0, 0);
Assert.True(readTask.IsCompleted);
Assert.Equal(0, await readTask);

// Clear the reader stream of any buffered data by doing a large read, which again should not block.
readTask = ReadAsync(mode, readable, buffer, 1, buffer.Length - 1);
Assert.True(readTask.IsCompleted);
bytesRead += await readTask;

if (FlushGuaranteesAllDataWritten)
{
AssertExtensions.SequenceEqual(data, buffer.AsSpan().Slice(0, bytesRead));
}
}
}

private sealed class ZeroByteReadTrackingStream : DelegatingStream
{
private TaskCompletionSource? _signal;

public ZeroByteReadTrackingStream(Stream innerStream) : base(innerStream)
{
}

public Task WaitForZeroByteReadAsync()
{
if (_signal is not null)
{
throw new Exception("Already registered to wait");
}

_signal = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
return _signal.Task;
}

private void CheckForZeroByteRead(int bufferLength)
{
if (bufferLength == 0)
{
var signal = _signal;
if (signal is null)
{
throw new Exception("Unexpected zero byte read");
}

_signal = null;
signal.SetResult();
}
}

public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)
{
CheckForZeroByteRead(count);
return base.BeginRead(buffer, offset, count, callback, state);
}

public override int Read(byte[] buffer, int offset, int count)
{
CheckForZeroByteRead(count);
return base.Read(buffer, offset, count);
}

public override int Read(Span<byte> buffer)
{
CheckForZeroByteRead(buffer.Length);
return base.Read(buffer);
}

public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
CheckForZeroByteRead(count);
return base.ReadAsync(buffer, offset, count, cancellationToken);
}

public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
CheckForZeroByteRead(buffer.Length);
return base.ReadAsync(buffer, cancellationToken);
}
}
}

/// <summary>Provides a disposable, enumerable tuple of two streams.</summary>
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/Common/tests/System/IO/CallTrackingStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace System.IO
{
public class CallTrackingStream : Stream
internal class CallTrackingStream : Stream
{
private readonly Dictionary<string, int> _callCounts; // maps names of methods -> how many times they were called

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
Link="Common\System\IO\TempFile.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\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\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(CommonTestPath)StreamConformanceTests\StreamConformanceTests.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.IO.Compression.TestData" Version="$(SystemIOCompressionTestDataVersion)" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\ZipTestHelper.cs" Link="Common\System\IO\Compression\ZipTestHelper.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\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(CommonTestPath)StreamConformanceTests\StreamConformanceTests.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.IO.Compression.TestData" Version="$(SystemIOCompressionTestDataVersion)" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,15 +159,9 @@
<Compile Include="$(CommonTestPath)System\Buffers\NativeMemoryManager.cs" Link="Common\System\Buffers\NativeMemoryManager.cs" />
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
<Compile Include="$(CommonTestPath)System\IO\PathFeatures.cs" Link="Common\System\IO\PathFeatures.cs" />

<Content Include="DirectoryInfo\test-dir\dummy.txt" Link="test-dir\dummy.txt" />

<Compile Include="$(CommonTestPath)Tests\System\IO\StreamConformanceTests.cs" Link="Common\System\IO\StreamConformanceTests.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(CommonTestPath)StreamConformanceTests\StreamConformanceTests.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,14 @@
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
<Compile Include="SafeMemoryMappedViewHandleTests.cs" />
<Compile Include="XunitAssemblyAttributes.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\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.BOOL.cs" Link="ProductionCode\Common\Interop\Windows\Interop.BOOL.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" Link="ProductionCode\Common\Interop\Windows\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CreateFileMapping.cs" Link="ProductionCode\Common\Interop\Windows\Kernel32\Interop.CreateFileMapping.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.MapViewOfFile.cs" Link="ProductionCode\Common\Interop\Windows\Kernel32\Interop.MapViewOfFile.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.MemOptions.cs" Link="ProductionCode\Common\Interop\Windows\Kernel32\Interop.MemOptions.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs" Link="ProductionCode\Common\Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(CommonTestPath)StreamConformanceTests\StreamConformanceTests.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,6 @@
<Compile Include="PipeWriterStreamTests.nonnetstandard.cs" />
<Compile Include="PipeReaderStreamTests.nonnetstandard.cs" />
<Compile Include="PipeReaderWriterStreamTests.nonnetstandard.cs" />
<Compile Include="$(CommonTestPath)Tests\System\IO\StreamConformanceTests.cs" Link="Common\System\IO\StreamConformanceTests.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\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == '$(NetCoreAppCurrent)'">
<!-- Some internal types are needed, so we reference the implementation assembly, rather than the reference assembly. -->
Expand All @@ -58,4 +52,7 @@
<ItemGroup Condition="'$(TargetFramework)' == 'net461'">
<ProjectReference Include="..\src\System.IO.Pipelines.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == '$(NetCoreAppCurrent)'">
<ProjectReference Include="$(CommonTestPath)StreamConformanceTests\StreamConformanceTests.csproj" />
</ItemGroup>
</Project>
10 changes: 3 additions & 7 deletions src/libraries/System.IO.Pipes/tests/System.IO.Pipes.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,6 @@
<Compile Include="PipeStream.ProtectedMethods.Tests.cs" />
<Compile Include="PipeStreamConformanceTests.cs" />
<Compile Include="$(CommonTestPath)System\Threading\Tasks\TaskTimeoutExtensions.cs" Link="Common\System\Threading\Tasks\TaskTimeoutExtensions.cs" />
<Compile Include="$(CommonTestPath)Tests\System\IO\StreamConformanceTests.cs" Link="Common\System\IO\StreamConformanceTests.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\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
<Compile Include="NamedPipeTests\NamedPipeTest.CurrentUserOnly.cs" />
Expand All @@ -52,4 +45,7 @@
<ProjectReference Include="$(LibrariesProjectRoot)System.DirectoryServices\src\System.DirectoryServices.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.DirectoryServices.AccountManagement\src\System.DirectoryServices.AccountManagement.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(CommonTestPath)StreamConformanceTests\StreamConformanceTests.csproj" />
</ItemGroup>
</Project>
Loading