public async Task ReadsBytesFromGivenFileAtGivenOffsetAsync() { const int fileSize = 4_001; string filePath = GetTestFilePath(); byte[] expected = RandomNumberGenerator.GetBytes(fileSize); File.WriteAllBytes(filePath, expected); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: FileOptions.Asynchronous)) { byte[] actual = new byte[fileSize + 1]; int current = 0; int total = 0; do { Memory <byte> buffer = actual.AsMemory(total, Math.Min(actual.Length - total, fileSize / 4)); current = await RandomAccess.ReadAsync(handle, buffer, fileOffset : total); Assert.InRange(current, 0, buffer.Length); total += current; } while (current != 0); Assert.Equal(fileSize, total); Assert.Equal(expected, actual.Take(total).ToArray()); } }
public async Task ReadAsyncUsingSingleBuffer() { const int fileSize = 1_000_000; // 1 MB string filePath = GetTestFilePath(); byte[] expected = RandomNumberGenerator.GetBytes(fileSize); File.WriteAllBytes(filePath, expected); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: FileOptions.Asynchronous | NoBuffering)) using (SectorAlignedMemory <byte> buffer = SectorAlignedMemory <byte> .Allocate(Environment.SystemPageSize)) { int current = 0; int total = 0; // From https://docs.microsoft.com/en-us/windows/win32/fileio/file-buffering: // "File access sizes, including the optional file offset in the OVERLAPPED structure, // if specified, must be for a number of bytes that is an integer multiple of the volume sector size." // So if buffer and physical sector size is 4096 and the file size is 4097: // the read from offset=0 reads 4096 bytes // the read from offset=4096 reads 1 byte // the read from offset=4097 THROWS (Invalid argument, offset is not a multiple of sector size!) // That is why we stop at the first incomplete read (the next one would throw). // It's possible to get 0 if we are lucky and file size is a multiple of physical sector size. do { current = await RandomAccess.ReadAsync(handle, buffer.Memory, fileOffset : total); Assert.True(expected.AsSpan(total, current).SequenceEqual(buffer.GetSpan().Slice(0, current))); total += current; }while (current == buffer.Memory.Length); Assert.Equal(fileSize, total); } }
public async Task ReadWriteAsyncUsingNonPageSizedMultipleBuffers() { string filePath = GetTestFilePath(); // The Windows scatter/gather APIs accept segments that are exactly one page long. // Combined with the FILE_FLAG_NO_BUFFERING's requirements, the segments must also // be aligned at page size boundaries and have a size of a multiple of the page size. // Using segments with a length of twice the page size adheres to the second requirement // but not the first. The RandomAccess implementation will see it and issue sequential // read/write syscalls per segment, instead of one scatter/gather syscall. // This test verifies that fallback behavior. int bufferSize = Environment.SystemPageSize * 2; int fileSize = bufferSize * 2; byte[] content = RandomNumberGenerator.GetBytes(fileSize); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, FileOptions.Asynchronous | NoBuffering)) using (SectorAlignedMemory <byte> buffer = SectorAlignedMemory <byte> .Allocate(fileSize)) { Memory <byte> firstHalf = buffer.Memory.Slice(0, bufferSize); Memory <byte> secondHalf = buffer.Memory.Slice(bufferSize); content.AsSpan().CopyTo(buffer.GetSpan()); await RandomAccess.WriteAsync(handle, new ReadOnlyMemory <byte>[] { firstHalf, secondHalf }, 0); buffer.GetSpan().Clear(); await RandomAccess.ReadAsync(handle, new Memory <byte>[] { firstHalf, secondHalf }, 0); } Assert.Equal(content, await File.ReadAllBytesAsync(filePath)); }
public void ThrowsArgumentNullExceptionForNullBuffers(FileOptions options) { using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write, options: options)) { AssertExtensions.Throws <ArgumentNullException>("buffers", () => RandomAccess.ReadAsync(handle, buffers: null, 0)); } }
public async Task ReadWriteAsyncUsingMultipleBuffers(bool memoryPageSized) { string filePath = GetTestFilePath(); // We test with buffers both one and two memory pages long. In the former case, // the I/O operations will issue one scatter/gather API call, and in the latter // case they will issue multiple calls; one per buffer. The buffers must still // be aligned to comply with FILE_FLAG_NO_BUFFERING's requirements. int bufferSize = Environment.SystemPageSize * (memoryPageSized ? 1 : 2); int fileSize = bufferSize * 2; byte[] content = RandomNumberGenerator.GetBytes(fileSize); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, FileOptions.Asynchronous | NoBuffering)) using (SectorAlignedMemory <byte> buffer = SectorAlignedMemory <byte> .Allocate(fileSize)) { Memory <byte> firstHalf = buffer.Memory.Slice(0, bufferSize); Memory <byte> secondHalf = buffer.Memory.Slice(bufferSize); content.AsSpan().CopyTo(buffer.GetSpan()); await RandomAccess.WriteAsync(handle, new ReadOnlyMemory <byte>[] { firstHalf, secondHalf }, 0); buffer.GetSpan().Clear(); long nRead = await RandomAccess.ReadAsync(handle, new Memory <byte>[] { firstHalf, secondHalf }, 0); Assert.Equal(buffer.GetSpan().Length, nRead); AssertExtensions.SequenceEqual(buffer.GetSpan(), content.AsSpan()); } }
public async Task ReadUsingSingleBuffer(bool asyncOperation, bool asyncHandle) { const int fileSize = 1_000_000; // 1 MB string filePath = GetTestFilePath(); byte[] expected = RandomNumberGenerator.GetBytes(fileSize); File.WriteAllBytes(filePath, expected); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: GetFileOptions(asyncHandle))) using (SectorAlignedMemory <byte> buffer = SectorAlignedMemory <byte> .Allocate(Environment.SystemPageSize)) { int current = 0; int total = 0; do { current = asyncOperation ? await RandomAccess.ReadAsync(handle, buffer.Memory, fileOffset : total) : RandomAccess.Read(handle, buffer.GetSpan(), fileOffset: total); Assert.True(expected.AsSpan(total, current).SequenceEqual(buffer.GetSpan().Slice(0, current))); total += current; }while (current != 0); Assert.Equal(fileSize, total); } }
public async Task ReadToAnEmptyBufferReturnsZeroAsync() { string filePath = GetTestFilePath(); File.WriteAllBytes(filePath, new byte[1]); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: FileOptions.Asynchronous)) { Assert.Equal(0, await RandomAccess.ReadAsync(handle, Array.Empty <byte>(), fileOffset: 0)); } }
public async Task ReadWriteAsyncUsingEmptyBuffers() { string filePath = GetTestFilePath(); using SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, FileOptions.Asynchronous | NoBuffering); long nRead = await RandomAccess.ReadAsync(handle, Array.Empty <Memory <byte> >(), 0); Assert.Equal(0, nRead); await RandomAccess.WriteAsync(handle, Array.Empty <ReadOnlyMemory <byte> >(), 0); }
public async Task ReadFromBeyondEndOfFileReturnsZeroAsync(FileOptions options) { string filePath = GetTestFilePath(); File.WriteAllBytes(filePath, new byte[100]); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: options)) { long eof = RandomAccess.GetLength(handle); Assert.Equal(0, await RandomAccess.ReadAsync(handle, new Memory <byte>[] { new byte[1] }, fileOffset: eof + 1)); } }
public async Task TaskAlreadyCanceledAsync() { using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.ReadWrite, options: FileOptions.Asynchronous)) { CancellationTokenSource cts = GetCancelledTokenSource(); CancellationToken token = cts.Token; Assert.True(RandomAccess.ReadAsync(handle, new byte[1], 0, token).IsCanceled); TaskCanceledException ex = await Assert.ThrowsAsync <TaskCanceledException>(() => RandomAccess.ReadAsync(handle, new byte[1], 0, token).AsTask()); Assert.Equal(token, ex.CancellationToken); } }
public async Task ReadToTheSameBufferOverwritesContent(FileOptions options) { string filePath = GetTestFilePath(); File.WriteAllBytes(filePath, new byte[3] { 1, 2, 3 }); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: options)) { byte[] buffer = new byte[1]; Assert.Equal(buffer.Length + buffer.Length, await RandomAccess.ReadAsync(handle, Enumerable.Repeat(buffer.AsMemory(), 2).ToList(), fileOffset: 0)); Assert.Equal(2, buffer[0]); } }
public async Task ReadsBytesFromGivenFileAtGivenOffsetAsync(FileOptions options) { const int fileSize = 4_001; string filePath = GetTestFilePath(); byte[] expected = RandomNumberGenerator.GetBytes(fileSize); File.WriteAllBytes(filePath, expected); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: options)) { byte[] actual = new byte[fileSize + 1]; long current = 0; long total = 0; do { int firstBufferLength = (int)Math.Min(actual.Length - total, fileSize / 4); Memory <byte> buffer_1 = actual.AsMemory((int)total, firstBufferLength); Memory <byte> buffer_2 = actual.AsMemory((int)total + firstBufferLength); current = await RandomAccess.ReadAsync( handle, new Memory <byte>[] { buffer_1, Array.Empty <byte>(), buffer_2 }, fileOffset : total); Assert.InRange(current, 0, buffer_1.Length + buffer_2.Length); total += current; } while (current != 0); Assert.Equal(fileSize, total); Assert.Equal(expected, actual.Take((int)total).ToArray()); } }
public async Task ReadAsyncUsingMultipleBuffers(bool asyncOperation, bool asyncHandle) { const int fileSize = 1_000_000; // 1 MB string filePath = GetTestFilePath(); byte[] expected = RandomNumberGenerator.GetBytes(fileSize); File.WriteAllBytes(filePath, expected); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: GetFileOptions(asyncHandle))) using (SectorAlignedMemory <byte> buffer_1 = SectorAlignedMemory <byte> .Allocate(Environment.SystemPageSize)) using (SectorAlignedMemory <byte> buffer_2 = SectorAlignedMemory <byte> .Allocate(Environment.SystemPageSize)) { long current = 0; long total = 0; IReadOnlyList <Memory <byte> > buffers = new Memory <byte>[] { buffer_1.Memory, buffer_2.Memory, }; do { current = asyncOperation ? await RandomAccess.ReadAsync(handle, buffers, fileOffset : total) : RandomAccess.Read(handle, buffers, fileOffset: total); int takeFromFirst = Math.Min(buffer_1.Memory.Length, (int)current); Assert.True(expected.AsSpan((int)total, takeFromFirst).SequenceEqual(buffer_1.GetSpan().Slice(0, takeFromFirst))); int takeFromSecond = (int)current - takeFromFirst; Assert.True(expected.AsSpan((int)total + takeFromFirst, takeFromSecond).SequenceEqual(buffer_2.GetSpan().Slice(0, takeFromSecond))); total += current; } while (current == buffer_1.Memory.Length + buffer_2.Memory.Length); Assert.Equal(fileSize, total); } }
public async Task ThrowsOnWriteAccess() { using (SafeFileHandle handle = GetHandleToExistingFile(FileAccess.Write)) { await Assert.ThrowsAsync <UnauthorizedAccessException>(async() => await RandomAccess.ReadAsync(handle, new byte[1], 0)); } }
protected override ValueTask <int> MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset) => RandomAccess.ReadAsync(handle, bytes, fileOffset);
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; if (syncWrite) { RandomAccess.Write(handle, writeBuffer, fileOffset); } else { 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; } } }
protected override ValueTask <long> MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset) => RandomAccess.ReadAsync(handle, new Memory <byte>[] { bytes }, fileOffset);