diff --git a/src/TestableIO.System.IO.Abstractions/FileSystemStream.cs b/src/TestableIO.System.IO.Abstractions/FileSystemStream.cs
index 21e9a85d8..8709ecbce 100644
--- a/src/TestableIO.System.IO.Abstractions/FileSystemStream.cs
+++ b/src/TestableIO.System.IO.Abstractions/FileSystemStream.cs
@@ -101,20 +101,32 @@ public override IAsyncResult BeginWrite(byte[] buffer,
object? state)
=> _stream.BeginWrite(buffer, offset, count, callback, state);
+ ///
+ public override void Close()
+ {
+ base.Close();
+ _stream.Close();
+ }
+
///
#if NETSTANDARD2_0 || NET462
- public new virtual void CopyTo(Stream destination, int bufferSize)
- => _stream.CopyTo(destination, bufferSize);
+ public new virtual void CopyTo(Stream destination, int bufferSize)
#else
public override void CopyTo(Stream destination, int bufferSize)
- => _stream.CopyTo(destination, bufferSize);
#endif
+ {
+ ValidateCopyToArguments(this, destination, bufferSize);
+ _stream.CopyTo(destination, bufferSize);
+ }
///
public override Task CopyToAsync(Stream destination,
int bufferSize,
CancellationToken cancellationToken)
- => _stream.CopyToAsync(destination, bufferSize, cancellationToken);
+ {
+ ValidateCopyToArguments(this, destination, bufferSize);
+ return _stream.CopyToAsync(destination, bufferSize, cancellationToken);
+ }
///
public override int EndRead(IAsyncResult asyncResult)
@@ -141,9 +153,9 @@ public override int Read(byte[] buffer, int offset, int count)
=> _stream.Read(buffer, offset, count);
#if FEATURE_SPAN
- ///
- public override int Read(Span buffer)
- => _stream.Read(buffer);
+ ///
+ public override int Read(Span buffer)
+ => _stream.Read(buffer);
#endif
///
@@ -154,10 +166,10 @@ public override Task ReadAsync(byte[] buffer,
=> _stream.ReadAsync(buffer, offset, count, cancellationToken);
#if FEATURE_SPAN
- ///
- public override ValueTask ReadAsync(Memory buffer,
- CancellationToken cancellationToken = new())
- => _stream.ReadAsync(buffer, cancellationToken);
+ ///
+ public override ValueTask ReadAsync(Memory buffer,
+ CancellationToken cancellationToken = new())
+ => _stream.ReadAsync(buffer, cancellationToken);
#endif
///
@@ -181,9 +193,9 @@ public override void Write(byte[] buffer, int offset, int count)
=> _stream.Write(buffer, offset, count);
#if FEATURE_SPAN
- ///
- public override void Write(ReadOnlySpan buffer)
- => _stream.Write(buffer);
+ ///
+ public override void Write(ReadOnlySpan buffer)
+ => _stream.Write(buffer);
#endif
///
@@ -194,10 +206,10 @@ public override Task WriteAsync(byte[] buffer,
=> _stream.WriteAsync(buffer, offset, count, cancellationToken);
#if FEATURE_SPAN
- ///
- public override ValueTask WriteAsync(ReadOnlyMemory buffer,
- CancellationToken cancellationToken = new())
- => _stream.WriteAsync(buffer, cancellationToken);
+ ///
+ public override ValueTask WriteAsync(ReadOnlyMemory buffer,
+ CancellationToken cancellationToken = new())
+ => _stream.WriteAsync(buffer, cancellationToken);
#endif
///
@@ -211,6 +223,15 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
}
+#if FEATURE_ASYNC_FILE
+ ///
+ public override async ValueTask DisposeAsync()
+ {
+ await _stream.DisposeAsync();
+ await base.DisposeAsync();
+ }
+#endif
+
///
/// Allows to cast the internal Stream to a FileStream
///
@@ -220,5 +241,38 @@ public static explicit operator FileStream(FileSystemStream fsStream)
{
return (FileStream) fsStream._stream;
}
+
+ private static void ValidateCopyToArguments(Stream source, Stream destination, int bufferSize)
+ {
+ if (destination == null)
+ {
+ throw new ArgumentNullException(nameof(destination), "Destination cannot be null.");
+ }
+
+ if (bufferSize <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(bufferSize), "Buffer size must be greater than zero.");
+ }
+
+ if (!destination.CanWrite)
+ {
+ if (destination.CanRead)
+ {
+ throw new NotSupportedException("Stream does not support writing.");
+ }
+
+ throw new ObjectDisposedException("Cannot access a closed Stream.");
+ }
+
+ if (!source.CanRead)
+ {
+ if (source.CanWrite)
+ {
+ throw new NotSupportedException("Stream does not support reading.");
+ }
+
+ throw new ObjectDisposedException("Cannot access a closed Stream.");
+ }
+ }
}
}
\ No newline at end of file
diff --git a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs
index fcfb0e4a1..a55257c27 100644
--- a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs
+++ b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs
@@ -293,5 +293,84 @@ public void MockFileStream_Null_ShouldHaveExpectedProperties()
Assert.That(result.Length, Is.Zero);
Assert.That(result.IsAsync, Is.True);
}
+
+ [Test]
+ [TestCase(0)]
+ [TestCase(-1)]
+ public void MockFileStream_WhenBufferSizeIsNotPositive_ShouldThrowArgumentNullException(int bufferSize)
+ {
+ var fileSystem = new MockFileSystem();
+ fileSystem.File.WriteAllText("foo.txt", "");
+ fileSystem.File.WriteAllText("bar.txt", "");
+ using var source = fileSystem.FileInfo.New(@"foo.txt").OpenRead();
+ using var destination = fileSystem.FileInfo.New(@"bar.txt").OpenWrite();
+
+ Assert.ThrowsAsync(async () =>
+ await source.CopyToAsync(destination, bufferSize));
+ }
+
+ [Test]
+ public void MockFileStream_WhenDestinationIsClosed_ShouldThrowObjectDisposedException()
+ {
+ var fileSystem = new MockFileSystem();
+ fileSystem.File.WriteAllText("foo.txt", "");
+ using var source = fileSystem.FileInfo.New(@"foo.txt").OpenRead();
+ using var destination = new MemoryStream();
+ destination.Close();
+
+ Assert.ThrowsAsync(async () =>
+ await source.CopyToAsync(destination));
+ }
+
+ [Test]
+ public void MockFileStream_WhenDestinationIsNull_ShouldThrowArgumentNullException()
+ {
+ var fileSystem = new MockFileSystem();
+ fileSystem.File.WriteAllText("foo.txt", "");
+ using var source = fileSystem.FileInfo.New(@"foo.txt").OpenRead();
+
+ Assert.ThrowsAsync(async () =>
+ await source.CopyToAsync(null));
+ }
+
+ [Test]
+ public void MockFileStream_WhenDestinationIsReadOnly_ShouldThrowNotSupportedException()
+ {
+ var fileSystem = new MockFileSystem();
+ fileSystem.File.WriteAllText("foo.txt", "");
+ fileSystem.File.WriteAllText("bar.txt", "");
+ using var source = fileSystem.FileInfo.New(@"foo.txt").OpenRead();
+ using var destination = fileSystem.FileInfo.New(@"bar.txt").OpenRead();
+
+ Assert.ThrowsAsync(async () =>
+ await source.CopyToAsync(destination));
+ }
+
+ [Test]
+ public void MockFileStream_WhenSourceIsClosed_ShouldThrowObjectDisposedException()
+ {
+ var fileSystem = new MockFileSystem();
+ fileSystem.File.WriteAllText("foo.txt", "");
+ fileSystem.File.WriteAllText("bar.txt", "");
+ using var source = fileSystem.FileInfo.New(@"foo.txt").OpenRead();
+ using var destination = fileSystem.FileInfo.New(@"bar.txt").OpenWrite();
+ source.Close();
+
+ Assert.ThrowsAsync(async () =>
+ await source.CopyToAsync(destination));
+ }
+
+ [Test]
+ public void MockFileStream_WhenSourceIsWriteOnly_ShouldThrowNotSupportedException()
+ {
+ var fileSystem = new MockFileSystem();
+ fileSystem.File.WriteAllText("foo.txt", "");
+ fileSystem.File.WriteAllText("bar.txt", "");
+ using var source = fileSystem.FileInfo.New(@"foo.txt").OpenWrite();
+ using var destination = fileSystem.FileInfo.New(@"bar.txt").OpenWrite();
+
+ Assert.ThrowsAsync(async () =>
+ await source.CopyToAsync(destination));
+ }
}
}