diff --git a/docs/naming.md b/docs/naming.md index d1a6d2789e..2d855a4b19 100644 --- a/docs/naming.md +++ b/docs/naming.md @@ -617,7 +617,7 @@ public static string NameWithParent(this Type type) return type.Name; } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/verify-file.md b/docs/verify-file.md index 60cad1203f..57116bb5bc 100644 --- a/docs/verify-file.md +++ b/docs/verify-file.md @@ -16,7 +16,7 @@ Verifies the contents of a file. public Task VerifyFilePath() => VerifyFile("sample.txt"); ``` -snippet source | anchor +snippet source | anchor @@ -33,7 +33,7 @@ public Task VerifyFileWithInfo() => "sample.txt", info: "the info"); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/verify-xml.md b/docs/verify-xml.md index 8338b4116e..60f9314bf7 100644 --- a/docs/verify-xml.md +++ b/docs/verify-xml.md @@ -19,7 +19,7 @@ Verifies Xml: public Task VerifyFilePath() => VerifyFile("sample.txt"); ``` -snippet source | anchor +snippet source | anchor diff --git a/src/Verify.Tests/UnboundedStream.cs b/src/Verify.Tests/NoLengthStream.cs similarity index 53% rename from src/Verify.Tests/UnboundedStream.cs rename to src/Verify.Tests/NoLengthStream.cs index fa8586ab9e..5865b9cc49 100644 --- a/src/Verify.Tests/UnboundedStream.cs +++ b/src/Verify.Tests/NoLengthStream.cs @@ -1,5 +1,5 @@ -class UnboundedStream : - MemoryStream +class NoLengthStream(byte[] bytes) : + MemoryStream(bytes) { public override long Length => throw new NotImplementedException(); } \ No newline at end of file diff --git a/src/Verify.Tests/StreamTests.NoLengthStream.verified.bin b/src/Verify.Tests/StreamTests.NoLengthStream.verified.bin new file mode 100644 index 0000000000..6b2aaa7640 --- /dev/null +++ b/src/Verify.Tests/StreamTests.NoLengthStream.verified.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Verify.Tests/StreamTests.UnboundedStream.verified.txt b/src/Verify.Tests/StreamTests.UnboundedStream.verified.txt deleted file mode 100644 index cfb012921d..0000000000 --- a/src/Verify.Tests/StreamTests.UnboundedStream.verified.txt +++ /dev/null @@ -1,4 +0,0 @@ -{ - Type: Exception, - Message: Could not read Length property of target stream. Verify does not support unbounded streams. -} \ No newline at end of file diff --git a/src/Verify.Tests/StreamTests.cs b/src/Verify.Tests/StreamTests.cs index 710953ccbc..707a4893c4 100644 --- a/src/Verify.Tests/StreamTests.cs +++ b/src/Verify.Tests/StreamTests.cs @@ -124,13 +124,14 @@ public Task StreamNotAtStartAsText() } [Fact] - public Task UnboundedStream() + public Task NoLengthStream() { - var stream = new UnboundedStream(); + var stream = new NoLengthStream(new byte[] + { + 1 + }); - return ThrowsTask(() => Verify(stream)) - .DisableRequireUniquePrefix() - .IgnoreStackTrace(); + return Verify(stream); } [Fact] diff --git a/src/Verify/Compare/FileComparer.cs b/src/Verify/Compare/FileComparer.cs index 4cbd0ccfd7..b0d41fa5c3 100644 --- a/src/Verify/Compare/FileComparer.cs +++ b/src/Verify/Compare/FileComparer.cs @@ -20,7 +20,7 @@ public static async Task DoCompare(VerifySettings settings, File return await InnerCompare(file, receivedStream, (s1, s2) => compare(s1, s2, settings.Context)); } - if (receivedStream.CanSeek && + if (receivedStream.CanSeekAndReadLength() && IoHelpers.Length(file.VerifiedPath) != receivedStream.Length) { await IoHelpers.WriteStream(file.ReceivedPath, receivedStream); @@ -66,14 +66,14 @@ async Task EqualityResult(Stream receivedStream, Stream verified return new(Equality.NotEqual, compareResult.Message, null, null); } - if (receivedStream.CanSeek) + if (receivedStream.CanSeekAndReadLength()) { receivedStream.MoveToStart(); return await EqualityResult(receivedStream, verifiedStream); } using var memoryStream = new MemoryStream(); - await receivedStream.CopyToAsync(memoryStream); + await receivedStream.SafeCopy(memoryStream); memoryStream.MoveToStart(); return await EqualityResult(memoryStream, verifiedStream); diff --git a/src/Verify/Extensions.cs b/src/Verify/Extensions.cs index 994c6b4374..d4944670f6 100644 --- a/src/Verify/Extensions.cs +++ b/src/Verify/Extensions.cs @@ -1,4 +1,5 @@ -static class Extensions +// ReSharper disable UnusedVariable +static class Extensions { public static string Extension(this FileStream file) => FileExtensions.GetExtension(file.Name); @@ -17,6 +18,37 @@ public static async Task> ToList(this IAsyncEnumerable target) return list; } + // Streams can throw for Length. Eg a http stream that the server has not specified the length header + // Specify buffer to avoid an exception in Stream.CopyToAsync where it reads Length + // https://github.com/dotnet/runtime/issues/43448 + public static Task SafeCopy(this Stream source, Stream target) + { + if (source.CanReadLength()) + { + return source.CopyToAsync(target); + } + + return source.CopyToAsync(target, 81920); + } + + public static bool CanSeekAndReadLength(this Stream stream) => + stream.CanSeek && + CanReadLength(stream); + + static bool CanReadLength(this Stream stream) + { + try + { + var streamLength = stream.Length; + } + catch (NotImplementedException) + { + return false; + } + + return true; + } + public static string TrimPreamble(this string text) => text.TrimStart('\uFEFF'); diff --git a/src/Verify/IoHelpers.cs b/src/Verify/IoHelpers.cs index f70ed728c3..74944d8515 100644 --- a/src/Verify/IoHelpers.cs +++ b/src/Verify/IoHelpers.cs @@ -218,7 +218,8 @@ public static async Task WriteStream(string path, Stream stream) if (!TryCopyFileStream(path, stream)) { await using var targetStream = OpenWrite(path); - await stream.CopyToAsync(targetStream); + await stream.SafeCopy(targetStream); + HandleEmptyFile(path); } } @@ -236,9 +237,18 @@ public static async Task WriteStream(string path, Stream stream) if (!TryCopyFileStream(path, stream)) { using var targetStream = OpenWrite(path); - await stream.CopyToAsync(targetStream); + await stream.SafeCopy(targetStream); + HandleEmptyFile(path); } } #endif + + static void HandleEmptyFile(string path) + { + if (new FileInfo(path).Length == 0) + { + throw new("Empty data is not allowed."); + } + } } \ No newline at end of file diff --git a/src/Verify/Verifier/InnerVerifier_Stream.cs b/src/Verify/Verifier/InnerVerifier_Stream.cs index 2b1d0d1c7f..d58ebfb86b 100644 --- a/src/Verify/Verifier/InnerVerifier_Stream.cs +++ b/src/Verify/Verifier/InnerVerifier_Stream.cs @@ -84,24 +84,6 @@ public async Task VerifyStream(Stream? stream, string extension, o using (stream) { - long GetLength() - { - try - { - return stream.Length; - } - catch (NotImplementedException) - { - throw new("Could not read Length property of target stream. Verify does not support unbounded streams."); - } - } - - var length = GetLength(); - if (length == 0) - { - throw new("Empty data is not allowed."); - } - if (VerifierSettings.HasExtensionConverter(extension)) { var (newInfo, converted, cleanup) = await DoExtensionConversion(extension, stream, info);