diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs
index c633dd8ef..fca762972 100644
--- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs
+++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs
@@ -38,6 +38,8 @@ public NullFileSystemStream() : base(Null, ".", true)
private readonly MockFileData fileData;
private bool disposed;
+ private byte[] lastKnownContents;
+
///
public MockFileStream(
IMockFileDataAccessor mockFileDataAccessor,
@@ -104,6 +106,7 @@ public MockFileStream(
mockFileDataAccessor.FileHandles.AddHandle(path, guid, access, share);
this.access = access;
this.share = share;
+ lastKnownContents = fileData.Contents;
}
private static void ThrowIfInvalidModeAccess(FileMode mode, FileAccess access)
@@ -138,11 +141,46 @@ private static void ThrowIfInvalidModeAccess(FileMode mode, FileAccess access)
///
public override int Read(byte[] buffer, int offset, int count)
{
+ RefreshBufferFromSharedFileData();
mockFileDataAccessor.AdjustTimes(fileData,
TimeAdjustments.LastAccessTime);
return base.Read(buffer, offset, count);
}
+#if FEATURE_SPAN
+ ///
+ public override int Read(Span buffer)
+ {
+ RefreshBufferFromSharedFileData();
+ return base.Read(buffer);
+ }
+#endif
+
+ ///
+ public override int ReadByte()
+ {
+ RefreshBufferFromSharedFileData();
+ return base.ReadByte();
+ }
+
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count,
+ CancellationToken cancellationToken)
+ {
+ RefreshBufferFromSharedFileData();
+ return base.ReadAsync(buffer, offset, count, cancellationToken);
+ }
+
+#if FEATURE_SPAN
+ ///
+ public override ValueTask ReadAsync(Memory buffer,
+ CancellationToken cancellationToken = new())
+ {
+ RefreshBufferFromSharedFileData();
+ return base.ReadAsync(buffer, cancellationToken);
+ }
+#endif
+
///
protected override void Dispose(bool disposing)
{
@@ -296,16 +334,63 @@ private void InternalFlush()
/* reset back to the beginning .. */
var position = Position;
Seek(0, SeekOrigin.Begin);
- /* .. read everything out */
+ /* .. read everything out (bypassing the read-refresh, so we capture this handle's own buffer) */
var data = new byte[Length];
- _ = Read(data, 0, (int)Length);
+ _ = base.Read(data, 0, (int)Length);
/* restore to original position */
Seek(position, SeekOrigin.Begin);
- /* .. put it in the mock system */
- mockFileData.Contents = data;
+ /* .. put it in the mock system, but only if this handle actually changed the contents since it last synchronized. */
+ if (!ContentsEqual(data, lastKnownContents))
+ {
+ mockFileData.Contents = data;
+ lastKnownContents = data;
+ }
}
}
+ private static bool ContentsEqual(byte[] left, byte[] right)
+ {
+ if (ReferenceEquals(left, right))
+ {
+ return true;
+ }
+
+ if (left == null || right == null || left.Length != right.Length)
+ {
+ return false;
+ }
+
+ for (var i = 0; i < left.Length; i++)
+ {
+ if (left[i] != right[i])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void RefreshBufferFromSharedFileData()
+ {
+ var sharedContents = fileData.Contents;
+ if (ReferenceEquals(sharedContents, lastKnownContents))
+ {
+ return;
+ }
+
+ var position = base.Position;
+ base.Position = 0;
+ base.SetLength(sharedContents.Length);
+ if (sharedContents.Length > 0)
+ {
+ base.Write(sharedContents, 0, sharedContents.Length);
+ }
+
+ base.Position = position;
+ lastKnownContents = sharedContents;
+ }
+
private void OnClose()
{
if (options.HasFlag(FileOptions.DeleteOnClose) && mockFileDataAccessor.FileExists(path))
diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net10.0.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net10.0.txt
index 28283ec09..d7c082044 100644
--- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net10.0.txt
+++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net10.0.txt
@@ -404,7 +404,11 @@ namespace System.IO.Abstractions.TestingHelpers
public object GetAccessControl() { }
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
public object GetAccessControl(System.IO.Abstractions.IFileSystemAclSupport.AccessControlSections includeSections) { }
+ public override int Read(System.Span buffer) { }
public override int Read(byte[] buffer, int offset, int count) { }
+ public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default) { }
+ public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { }
+ public override int ReadByte() { }
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
public void SetAccessControl(object value) { }
public override void SetLength(long value) { }
diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net472.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net472.txt
index bce734d4f..e64a25b2e 100644
--- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net472.txt
+++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net472.txt
@@ -316,6 +316,8 @@ namespace System.IO.Abstractions.TestingHelpers
public object GetAccessControl() { }
public object GetAccessControl(System.IO.Abstractions.IFileSystemAclSupport.AccessControlSections includeSections) { }
public override int Read(byte[] buffer, int offset, int count) { }
+ public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { }
+ public override int ReadByte() { }
public void SetAccessControl(object value) { }
public override void SetLength(long value) { }
public override void Write(byte[] buffer, int offset, int count) { }
diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net6.0.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net6.0.txt
index 7fa0eecba..9aff85574 100644
--- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net6.0.txt
+++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net6.0.txt
@@ -366,7 +366,11 @@ namespace System.IO.Abstractions.TestingHelpers
public object GetAccessControl() { }
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
public object GetAccessControl(System.IO.Abstractions.IFileSystemAclSupport.AccessControlSections includeSections) { }
+ public override int Read(System.Span buffer) { }
public override int Read(byte[] buffer, int offset, int count) { }
+ public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default) { }
+ public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { }
+ public override int ReadByte() { }
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
public void SetAccessControl(object value) { }
public override void SetLength(long value) { }
diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net8.0.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net8.0.txt
index e898230a1..e5788bd55 100644
--- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net8.0.txt
+++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net8.0.txt
@@ -390,7 +390,11 @@ namespace System.IO.Abstractions.TestingHelpers
public object GetAccessControl() { }
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
public object GetAccessControl(System.IO.Abstractions.IFileSystemAclSupport.AccessControlSections includeSections) { }
+ public override int Read(System.Span buffer) { }
public override int Read(byte[] buffer, int offset, int count) { }
+ public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default) { }
+ public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { }
+ public override int ReadByte() { }
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
public void SetAccessControl(object value) { }
public override void SetLength(long value) { }
diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net9.0.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net9.0.txt
index dea869d1f..35cf35b2e 100644
--- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net9.0.txt
+++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net9.0.txt
@@ -404,7 +404,11 @@ namespace System.IO.Abstractions.TestingHelpers
public object GetAccessControl() { }
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
public object GetAccessControl(System.IO.Abstractions.IFileSystemAclSupport.AccessControlSections includeSections) { }
+ public override int Read(System.Span buffer) { }
public override int Read(byte[] buffer, int offset, int count) { }
+ public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default) { }
+ public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { }
+ public override int ReadByte() { }
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
public void SetAccessControl(object value) { }
public override void SetLength(long value) { }
diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.0.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.0.txt
index fd8f7e4fc..1dc0ed02e 100644
--- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.0.txt
+++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.0.txt
@@ -316,6 +316,8 @@ namespace System.IO.Abstractions.TestingHelpers
public object GetAccessControl() { }
public object GetAccessControl(System.IO.Abstractions.IFileSystemAclSupport.AccessControlSections includeSections) { }
public override int Read(byte[] buffer, int offset, int count) { }
+ public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { }
+ public override int ReadByte() { }
public void SetAccessControl(object value) { }
public override void SetLength(long value) { }
public override void Write(byte[] buffer, int offset, int count) { }
diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.1.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.1.txt
index 0bf9bd3e7..cba08bc11 100644
--- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.1.txt
+++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.1.txt
@@ -341,7 +341,11 @@ namespace System.IO.Abstractions.TestingHelpers
public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { }
public object GetAccessControl() { }
public object GetAccessControl(System.IO.Abstractions.IFileSystemAclSupport.AccessControlSections includeSections) { }
+ public override int Read(System.Span buffer) { }
public override int Read(byte[] buffer, int offset, int count) { }
+ public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default) { }
+ public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { }
+ public override int ReadByte() { }
public void SetAccessControl(object value) { }
public override void SetLength(long value) { }
public override void Write(System.ReadOnlySpan buffer) { }
diff --git a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs
index a8f815a15..77fe218af 100644
--- a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs
+++ b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs
@@ -10,6 +10,39 @@
[TestFixture]
public class MockFileStreamTests
{
+ [Test]
+ public async Task MockFileStream_SharedReadWrite_SecondHandleSeesWritesFromFirstHandle()
+ {
+ // Arrange
+ var filename = XFS.Path(@"C:\temp\shared.bin");
+ var fileSystem = new MockFileSystem();
+ fileSystem.AddEmptyFile(filename);
+ var results = new List();
+
+ // Act
+ using (var file1 = fileSystem.FileStream.New(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
+ using (var file2 = fileSystem.FileStream.New(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
+ {
+ var buffer = new byte[4];
+ for (int ix = 0; ix < 10; ix++)
+ {
+ var bytes = BitConverter.GetBytes(ix);
+ file1.Position = 0;
+ file1.Write(bytes, 0, bytes.Length);
+ file1.Flush();
+
+ file2.Position = 0;
+ file2.Flush();
+ var bytesRead = file2.Read(buffer, 0, buffer.Length);
+ await That(bytesRead).IsEqualTo(buffer.Length);
+ results.Add(BitConverter.ToInt32(buffer, 0));
+ }
+ }
+
+ // Assert
+ await That(results).IsEqualTo(new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
+ }
+
[Test]
public async Task MockFileStream_Flush_WritesByteToFile()
{