Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

all RandomAccess methods should work for both sync and async file handles #54266

Merged
merged 17 commits into from
Jul 2, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 11 additions & 36 deletions src/libraries/System.IO.FileSystem/tests/RandomAccess/Base.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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.Pipes;
using System.Threading;
using Microsoft.Win32.SafeHandles;
Expand All @@ -12,12 +13,14 @@ public abstract class RandomAccess_Base<T> : FileSystemTest
{
protected abstract T MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset);

protected virtual bool ShouldThrowForSyncHandle => false;

protected virtual bool ShouldThrowForAsyncHandle => false;

protected virtual bool UsesOffsets => true;

public static IEnumerable<object[]> GetSyncAsyncOptions()
{
yield return new object[] { FileOptions.None };
yield return new object[] { FileOptions.Asynchronous };
}

[Fact]
public void ThrowsArgumentNullExceptionForNullHandle()
{
Expand Down Expand Up @@ -52,58 +55,30 @@ public void ThrowsNotSupportedExceptionForUnseekableFile()
}
}

[Fact]
public void ThrowsArgumentOutOfRangeExceptionForNegativeFileOffset()
[Theory]
[MemberData(nameof(GetSyncAsyncOptions))]
public void ThrowsArgumentOutOfRangeExceptionForNegativeFileOffset(FileOptions options)
{
if (UsesOffsets)
{
FileOptions options = ShouldThrowForAsyncHandle ? FileOptions.None : FileOptions.Asynchronous;
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write, options: options))
{
AssertExtensions.Throws<ArgumentOutOfRangeException>("fileOffset", () => MethodUnderTest(handle, Array.Empty<byte>(), -1));
}
}
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[SkipOnPlatform(TestPlatforms.Browser, "async file IO is not supported on browser")]
public void ThrowsArgumentExceptionForAsyncFileHandle()
{
if (ShouldThrowForAsyncHandle)
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write, options: FileOptions.Asynchronous))
{
AssertExtensions.Throws<ArgumentException>("handle", () => MethodUnderTest(handle, new byte[100], 0));
}
}
}

[Fact]
public void ThrowsArgumentExceptionForSyncFileHandle()
{
if (ShouldThrowForSyncHandle)
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write, options: FileOptions.None))
{
AssertExtensions.Throws<ArgumentException>("handle", () => MethodUnderTest(handle, new byte[100], 0));
}
}
}

protected static CancellationTokenSource GetCancelledTokenSource()
{
CancellationTokenSource source = new CancellationTokenSource();
source.Cancel();
return source;
}

protected SafeFileHandle GetHandleToExistingFile(FileAccess access)
protected SafeFileHandle GetHandleToExistingFile(FileAccess access, FileOptions options)
{
string filePath = GetTestFilePath();
File.WriteAllBytes(filePath, new byte[1]);

FileOptions options = ShouldThrowForAsyncHandle ? FileOptions.None : FileOptions.Asynchronous;
return File.OpenHandle(filePath, FileMode.Open, access, FileShare.None, options);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,25 @@ protected override long MethodUnderTest(SafeFileHandle handle, byte[] bytes, lon

protected override bool UsesOffsets => false;

[Fact]
public void ReturnsZeroForEmptyFile()
[Theory]
[MemberData(nameof(GetSyncAsyncOptions))]
public void ReturnsZeroForEmptyFile(FileOptions options)
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write))
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write, options: options))
{
Assert.Equal(0, RandomAccess.GetLength(handle));
}
}

[Fact]
public void ReturnsExactSizeForNonEmptyFiles()
[Theory]
[MemberData(nameof(GetSyncAsyncOptions))]
public void ReturnsExactSizeForNonEmptyFiles(FileOptions options)
{
const int fileSize = 123;
string filePath = GetTestFilePath();
File.WriteAllBytes(filePath, new byte[fileSize]);

using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open))
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: options))
{
Assert.Equal(fileSize, RandomAccess.GetLength(handle));
}
Expand Down
130 changes: 130 additions & 0 deletions src/libraries/System.IO.FileSystem/tests/RandomAccess/Mixed.Windows.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// 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.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
using Xunit;

namespace System.IO.Tests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[PlatformSpecific(TestPlatforms.Windows)]
public class RandomAccess_Mixed : FileSystemTest
{
[DllImport(Interop.Libraries.Kernel32, EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)]
private static extern unsafe SafeFileHandle CreateFileW(
string lpFileName,
FileAccess dwDesiredAccess,
FileShare dwShareMode,
IntPtr lpSecurityAttributes,
FileMode dwCreationDisposition,
int dwFlagsAndAttributes,
IntPtr hTemplateFile);

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UsingSingleBuffer(bool async)
{
string filePath = GetTestFilePath();
FileOptions options = async ? FileOptions.Asynchronous : FileOptions.None;

// we want to test all combinations: starting with sync|async write, then sync|async read etc
foreach (bool syncWrite in new bool[] { true, false })
{
foreach (bool syncRead in new bool[] { true, false })
{
// File.OpenHandle initializes ThreadPoolBinding for async file handles on Windows
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Create, FileAccess.ReadWrite, options: options))
{
await Validate(handle, options, new bool[] { syncWrite, !syncWrite }, new bool[] { syncRead, !syncRead });
}

// tests code path where ThreadPoolBinding is not initialized
using (SafeFileHandle tpBindingNotInitialized = CreateFileW(filePath, FileAccess.ReadWrite, FileShare.None, IntPtr.Zero, FileMode.Create, (int)options, IntPtr.Zero))
{
await Validate(tpBindingNotInitialized, options, new bool[] { syncWrite, !syncWrite }, new bool[] { syncRead, !syncRead });
}
}
}

static async Task Validate(SafeFileHandle handle, FileOptions options, bool[] syncWrites, bool[] syncReads)
{
byte[] writeBuffer = new byte[1];
byte[] readBuffer = new byte[2];
long fileOffset = 0;

foreach (bool syncWrite in syncWrites)
{
foreach (bool syncRead in syncReads)
{
writeBuffer[0] = (byte)fileOffset;

Assert.Equal(writeBuffer.Length, syncWrite ? RandomAccess.Write(handle, writeBuffer, fileOffset) : await RandomAccess.WriteAsync(handle, writeBuffer, fileOffset));
Assert.Equal(writeBuffer.Length, syncRead ? RandomAccess.Read(handle, readBuffer, fileOffset) : await RandomAccess.ReadAsync(handle, readBuffer, fileOffset));
Assert.Equal(writeBuffer[0], readBuffer[0]);

fileOffset += 1;
}
}
}
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UsingMultipleBuffers(bool async)
{
string filePath = GetTestFilePath();
FileOptions options = async ? FileOptions.Asynchronous : FileOptions.None;

foreach (bool syncWrite in new bool[] { true, false })
{
foreach (bool syncRead in new bool[] { true, false })
{
// File.OpenHandle initializes ThreadPoolBinding for async file handles on Windows
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Create, FileAccess.ReadWrite, options: options))
{
await Validate(handle, options, new bool[] { syncWrite, !syncWrite }, new bool[] { syncRead, !syncRead });
}

// tests code path where ThreadPoolBinding is not initialized
using (SafeFileHandle tpBindingNotInitialized = CreateFileW(filePath, FileAccess.ReadWrite, FileShare.None, IntPtr.Zero, FileMode.Create, (int)options, IntPtr.Zero))
{
await Validate(tpBindingNotInitialized, options, new bool[] { syncWrite, !syncWrite }, new bool[] { syncRead, !syncRead });
}
}
}

static async Task Validate(SafeFileHandle handle, FileOptions options, bool[] syncWrites, bool[] syncReads)
{
byte[] writeBuffer_1 = new byte[1];
byte[] writeBuffer_2 = new byte[1];
byte[] readBuffer_1 = new byte[1];
byte[] readBuffer_2 = new byte[1];
long fileOffset = 0;

IReadOnlyList<Memory<byte>> readBuffers = new Memory<byte>[] { readBuffer_1, readBuffer_2 };
IReadOnlyList<ReadOnlyMemory<byte>> writeBuffers = new ReadOnlyMemory<byte>[] { writeBuffer_1, writeBuffer_2 };

foreach (bool syncWrite in syncWrites)
{
foreach (bool syncRead in syncReads)
{
writeBuffer_1[0] = (byte)fileOffset;
writeBuffer_2[0] = (byte)(fileOffset+1);

Assert.Equal(writeBuffer_1.Length + writeBuffer_2.Length, syncWrite ? RandomAccess.Write(handle, writeBuffers, fileOffset) : await RandomAccess.WriteAsync(handle, writeBuffers, fileOffset));
Assert.Equal(writeBuffer_1.Length + writeBuffer_2.Length, syncRead ? RandomAccess.Read(handle, readBuffers, fileOffset) : await RandomAccess.ReadAsync(handle, readBuffers, fileOffset));
Assert.Equal(writeBuffer_1[0], readBuffer_1[0]);
Assert.Equal(writeBuffer_2[0], readBuffer_2[0]);

fileOffset += 2;
}
}
}
}
}
}
Loading