Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
55 changes: 32 additions & 23 deletions docs/comparer.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,35 +119,41 @@ public static async Task<CompareResult> AreEqual(Stream stream1, Stream stream2)
EnsureAtStart(stream1);
EnsureAtStart(stream2);

var buffer1 = new byte[bufferSize];
var buffer2 = new byte[bufferSize];

while (true)
var buffer1 = ArrayPool<byte>.Shared.Rent(bufferSize);
var buffer2 = ArrayPool<byte>.Shared.Rent(bufferSize);
try
{
var count1 = await ReadBufferAsync(stream1, buffer1);
var count2 = await ReadBufferAsync(stream2, buffer2);

// Callers do not always guarantee the streams are the same length
// (e.g. a non-seekable received stream), so a length difference must
// be treated as not-equal instead of a short-circuit to equal.
if (count1 != count2)
while (true)
{
return CompareResult.NotEqual();
}
var count1 = await ReadBufferAsync(stream1, buffer1);
var count2 = await ReadBufferAsync(stream2, buffer2);

if (count1 == 0)
{
return CompareResult.Equal;
}
// Callers do not always guarantee the streams are the same length
// (e.g. a non-seekable received stream), so a length difference must
// be treated as not-equal instead of a short-circuit to equal.
if (count1 != count2)
{
return CompareResult.NotEqual();
}

for (var i = 0; i < count1; i += sizeof(long))
{
if (BitConverter.ToInt64(buffer1, i) != BitConverter.ToInt64(buffer2, i))
if (count1 == 0)
{
return CompareResult.Equal;
}

// Compare exactly the bytes read (a rented buffer's trailing bytes
// are arbitrary, so they must not be included).
if (!buffer1.AsSpan(0, count1).SequenceEqual(buffer2.AsSpan(0, count1)))
{
return CompareResult.NotEqual();
}
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer1);
ArrayPool<byte>.Shared.Return(buffer2);
}
}

static void EnsureAtStart(Stream stream)
Expand All @@ -161,10 +167,13 @@ static void EnsureAtStart(Stream stream)

static async Task<int> ReadBufferAsync(Stream stream, byte[] buffer)
{
// Read up to bufferSize (not buffer.Length): a rented buffer may be
// larger, and both streams must be read in equal-sized chunks so the
// length comparison above stays aligned.
var bytesRead = 0;
while (bytesRead < buffer.Length)
while (bytesRead < bufferSize)
{
var read = await stream.ReadAsync(buffer, bytesRead, buffer.Length - bytesRead);
var read = await stream.ReadAsync(buffer, bytesRead, bufferSize - bytesRead);
if (read == 0)
{
// Reached end of stream.
Expand All @@ -177,7 +186,7 @@ static async Task<int> ReadBufferAsync(Stream stream, byte[] buffer)
return bytesRead;
}
```
<sup><a href='/src/Verify/Compare/StreamComparer.cs#L3-L70' title='Snippet source file'>snippet source</a> | <a href='#snippet-DefualtCompare' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify/Compare/StreamComparer.cs#L3-L79' title='Snippet source file'>snippet source</a> | <a href='#snippet-DefualtCompare' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand Down
2 changes: 1 addition & 1 deletion docs/naming.md
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,7 @@ public static string NameWithParent(this Type type)
return type.Name;
}
```
<sup><a href='/src/Verify/Extensions.cs#L123-L135' title='Snippet source file'>snippet source</a> | <a href='#snippet-NameWithParent' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify/Extensions.cs#L132-L144' title='Snippet source file'>snippet source</a> | <a href='#snippet-NameWithParent' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Any path calculated in `DerivePathInfo` should be fully qualified to remove the inconsistency of the current directory.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
content
30 changes: 30 additions & 0 deletions src/Verify.Tests/Converters/AsyncCleanupTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
public class AsyncCleanupTests
{
static bool asyncCleanupRan;

[ModuleInitializer]
public static void Init() =>
VerifierSettings.RegisterFileConverter<TargetForAsyncCleanup>(
(instance, _) => new(
info: null,
"txt",
instance.Value,
cleanup: async () =>
{
await Task.Delay(200);
asyncCleanupRan = true;
}));

[Fact]
public async Task ConverterAsyncCleanupIsAwaited()
{
asyncCleanupRan = false;
var target = new TargetForAsyncCleanup("content");
await Verify(target);
// The converter's async cleanup must be awaited before Verify returns;
// composing cleanups with += only awaits the last one.
Assert.True(asyncCleanupRan);
}

public record TargetForAsyncCleanup(string Value);
}
30 changes: 30 additions & 0 deletions src/Verify.Tests/StreamComparerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,36 @@ public async Task BinaryEquals()
Assert.True(result.IsEqual);
}

[Fact]
public async Task EqualWithLengthNotMultipleOfEight()
{
// 13 bytes exercises a partial final block; the trailing bytes of a
// rented buffer must not affect the result.
var bytes = new byte[13];
for (var index = 0; index < bytes.Length; index++)
{
bytes[index] = (byte) index;
}

using var stream1 = new MemoryStream(bytes);
using var stream2 = new MemoryStream((byte[]) bytes.Clone());
var result = await StreamComparer.AreEqual(stream1, stream2);
Assert.True(result.IsEqual);
}

[Fact]
public async Task NotEqualInPartialFinalBlock()
{
var bytes1 = new byte[13];
var bytes2 = new byte[13];
bytes2[12] = 1;

using var stream1 = new MemoryStream(bytes1);
using var stream2 = new MemoryStream(bytes2);
var result = await StreamComparer.AreEqual(stream1, stream2);
Assert.False(result.IsEqual);
}

[Fact]
public async Task BinaryNotEqualsSameLength()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<root />
11 changes: 11 additions & 0 deletions src/Verify.Tests/VerifyXmlStreamTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
public class VerifyXmlStreamTests
{
[Fact]
public async Task DisposesStream()
{
var stream = new MemoryStream("<root />"u8.ToArray());
await VerifyXml(stream);
// VerifyXml(Stream) must dispose the stream, like the other stream APIs.
Assert.False(stream.CanRead);
}
}
4 changes: 3 additions & 1 deletion src/Verify/Compare/Comparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ public static async Task<EqualityResult> Text(FilePair filePair, StringBuilder r
return new(Equality.New, null, received, null);
}

var verified = await File.ReadAllTextAsync(filePair.VerifiedPath);
// Read with the configured encoding so a BOM-less non-UTF8 UseEncoding
// round-trips (writes go through the same encoding).
var verified = await File.ReadAllTextAsync(filePair.VerifiedPath, VerifierSettings.Encoding);
if (verified.Contains('\r'))
{
throw new($@"Verified file must use \n line endings, but it contains a \r (carriage return). Path: {filePair.VerifiedPath}. See https://github.com/verifytests/verify#text-file-settings");
Expand Down
53 changes: 31 additions & 22 deletions src/Verify/Compare/StreamComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,41 @@ public static async Task<CompareResult> AreEqual(Stream stream1, Stream stream2)
EnsureAtStart(stream1);
EnsureAtStart(stream2);

var buffer1 = new byte[bufferSize];
var buffer2 = new byte[bufferSize];

while (true)
var buffer1 = ArrayPool<byte>.Shared.Rent(bufferSize);
var buffer2 = ArrayPool<byte>.Shared.Rent(bufferSize);
try
{
var count1 = await ReadBufferAsync(stream1, buffer1);
var count2 = await ReadBufferAsync(stream2, buffer2);

// Callers do not always guarantee the streams are the same length
// (e.g. a non-seekable received stream), so a length difference must
// be treated as not-equal instead of a short-circuit to equal.
if (count1 != count2)
while (true)
{
return CompareResult.NotEqual();
}
var count1 = await ReadBufferAsync(stream1, buffer1);
var count2 = await ReadBufferAsync(stream2, buffer2);

if (count1 == 0)
{
return CompareResult.Equal;
}
// Callers do not always guarantee the streams are the same length
// (e.g. a non-seekable received stream), so a length difference must
// be treated as not-equal instead of a short-circuit to equal.
if (count1 != count2)
{
return CompareResult.NotEqual();
}

for (var i = 0; i < count1; i += sizeof(long))
{
if (BitConverter.ToInt64(buffer1, i) != BitConverter.ToInt64(buffer2, i))
if (count1 == 0)
{
return CompareResult.Equal;
}

// Compare exactly the bytes read (a rented buffer's trailing bytes
// are arbitrary, so they must not be included).
if (!buffer1.AsSpan(0, count1).SequenceEqual(buffer2.AsSpan(0, count1)))
{
return CompareResult.NotEqual();
}
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer1);
ArrayPool<byte>.Shared.Return(buffer2);
}
}

static void EnsureAtStart(Stream stream)
Expand All @@ -51,10 +57,13 @@ static void EnsureAtStart(Stream stream)

static async Task<int> ReadBufferAsync(Stream stream, byte[] buffer)
{
// Read up to bufferSize (not buffer.Length): a rented buffer may be
// larger, and both streams must be read in equal-sized chunks so the
// length comparison above stays aligned.
var bytesRead = 0;
while (bytesRead < buffer.Length)
while (bytesRead < bufferSize)
{
var read = await stream.ReadAsync(buffer, bytesRead, buffer.Length - bytesRead);
var read = await stream.ReadAsync(buffer, bytesRead, bufferSize - bytesRead);
if (read == 0)
{
// Reached end of stream.
Expand Down
9 changes: 9 additions & 0 deletions src/Verify/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ public static string Extension(this FileStream file)
public static Version MajorMinor(this Version version) =>
new(version.Major, version.Minor);

// Compose two async actions so both are awaited in order. Using `+=` on a
// Func<Task> builds a multicast delegate that only awaits the last target.
public static Func<Task> Then(this Func<Task> first, Func<Task> second) =>
async () =>
{
await first();
await second();
};

public static async Task<List<T>> ToList<T>(this IAsyncEnumerable<T> target)
{
var list = new List<T>();
Expand Down
16 changes: 11 additions & 5 deletions src/Verify/Verifier/InnerVerifier_Inner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async Task<VerifyResult> VerifyInner(object? root, Func<Task>? cleanup, IEnumera
cleanup ??= () => Task.CompletedTask;

var (extraTargets, extraCleanup) = await GetTargets(targets, doExtensionConversion);
cleanup += extraCleanup;
cleanup = cleanup.Then(extraCleanup);
resultTargets.AddRange(extraTargets);
var engine = new VerifyEngine(
directory,
Expand All @@ -27,9 +27,15 @@ async Task<VerifyResult> VerifyInner(object? root, Func<Task>? cleanup, IEnumera
settings.TypeName ?? typeName,
settings.MethodName ?? methodName);

await engine.HandleResults(resultTargets);

await cleanup();
try
{
await engine.HandleResults(resultTargets);
}
finally
{
// Always run cleanup (stream/converter disposal), even if comparison throws.
await cleanup();
}

await engine.ThrowIfRequired();

Expand Down Expand Up @@ -59,7 +65,7 @@ async Task<VerifyResult> VerifyInner(object? root, Func<Task>? cleanup, IEnumera
}

var (info, converted, itemCleanup) = await DoExtensionConversion(target.Extension, target.StreamData, null, target.Name);
cleanup += itemCleanup;
cleanup = cleanup.Then(itemCleanup);
if (info != null)
{
result.Add(
Expand Down
2 changes: 1 addition & 1 deletion src/Verify/Verifier/InnerVerifier_Stream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ static async Task<Target> GetTarget(Stream stream, string extension)
var result = await conversion(target.Name, targetStream, settings.Context);
if (result.Cleanup != null)
{
cleanup += result.Cleanup;
cleanup = cleanup.Then(result.Cleanup);
}

if (result.Info != null)
Expand Down
7 changes: 5 additions & 2 deletions src/Verify/Verifier/InnerVerifier_Xml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ public async Task<VerifyResult> VerifyXml(Stream? target)
return await VerifyInner(target, null, emptyTargets, true, false);
}

var document = await XDocument.LoadAsync(target, LoadOptions.None, default);
return await VerifyXml(document);
using (target)
{
var document = await XDocument.LoadAsync(target, LoadOptions.None, default);
return await VerifyXml(document);
}
}

async Task<VerifyResult> VerifyXml(XmlNode? target)
Expand Down
11 changes: 5 additions & 6 deletions src/Verify/Verifier/VerifyEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,11 @@ public async Task ThrowIfRequired()

internal bool IsAutoVerify(string verifiedFile)
{
if (typeName == null)
{
return false;
}

if (VerifierSettings.autoVerify != null)
// The global delegate needs the type/method name; the per-settings
// delegate does not, so it must not be gated on typeName (which is null
// for the file-level InnerVerifier(directory, name) API).
if (typeName != null &&
VerifierSettings.autoVerify != null)
{
return VerifierSettings.autoVerify(typeName, methodName!, verifiedFile);
}
Expand Down
Loading