Skip to content

Commit d221dba

Browse files
authored
BypassComparersForSubsequentOnDifference (#1739)
1 parent 0dea3a3 commit d221dba

14 files changed

Lines changed: 154 additions & 13 deletions

docs/comparer.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,35 @@ The returned `CompareResult.NotEqual` takes an optional message that will be ren
4242
**If an input is split into multiple files, and a text file fails, then all subsequent binary comparisons will revert to the default comparison.**
4343

4444

45+
### Bypass comparers for derived targets
46+
47+
When a converter splits an input into multiple targets, for example a source document plus derived outputs such as rendered images or extracted text, a lenient comparer on a derived target can mask a real change in the source. Setting `BypassComparersForSubsequentOnDifference` on the source target ensures that, when the source differs from its verified file, all subsequent targets skip their registered comparers and fall back to exact comparison:
48+
49+
<!-- snippet: BypassComparersForSubsequentOnDifference -->
50+
<a id='snippet-BypassComparersForSubsequentOnDifference'></a>
51+
```cs
52+
// A converter that emits a canonical source document alongside derived targets (eg rendered pages).
53+
// The source is flagged so that, when it differs, the derived targets skip their (potentially lenient)
54+
// comparers and fall back to exact comparison, ensuring a real change in the source is never masked.
55+
public static ConversionResult ConvertDocument(Stream document, IReadOnlyDictionary<string, object> context)
56+
{
57+
Target[] targets =
58+
[
59+
new("docx", document)
60+
{
61+
BypassComparersForSubsequentOnDifference = true
62+
},
63+
new("png", RenderPage(document))
64+
];
65+
return new(info: null, targets);
66+
}
67+
```
68+
<sup><a href='/src/Verify.Tests/Snippets/BypassComparerSnippets.cs#L5-L23' title='Snippet source file'>snippet source</a> | <a href='#snippet-BypassComparersForSubsequentOnDifference' title='Start of snippet'>anchor</a></sup>
69+
<!-- endSnippet -->
70+
71+
The flag must be set on the source target, and that target must precede the derived targets in the conversion result.
72+
73+
4574
### Instance comparer
4675

4776
<!-- snippet: InstanceComparer -->

docs/mdsource/comparer.source.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ The returned `CompareResult.NotEqual` takes an optional message that will be ren
1616
**If an input is split into multiple files, and a text file fails, then all subsequent binary comparisons will revert to the default comparison.**
1717

1818

19+
### Bypass comparers for derived targets
20+
21+
When a converter splits an input into multiple targets, for example a source document plus derived outputs such as rendered images or extracted text, a lenient comparer on a derived target can mask a real change in the source. Setting `BypassComparersForSubsequentOnDifference` on the source target ensures that, when the source differs from its verified file, all subsequent targets skip their registered comparers and fall back to exact comparison:
22+
23+
snippet: BypassComparersForSubsequentOnDifference
24+
25+
The flag must be set on the source target, and that target must precede the derived targets in the conversion result.
26+
27+
1928
### Instance comparer
2029

2130
snippet: InstanceComparer
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
derived-other
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
source-other
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
null
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
derived-other
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
source-content
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
null
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
public class BypassComparerTests
2+
{
3+
// Mimics a converter that emits a canonical source target (eg a document) alongside a derived
4+
// target (eg a rendered image) whose comparer can mask a real difference in the source.
5+
static Target[] BuildTargets() =>
6+
[
7+
new("bypasssource", new MemoryStream("source-content"u8.ToArray()))
8+
{
9+
BypassComparersForSubsequentOnDifference = true
10+
},
11+
new("bypassderived", new MemoryStream("derived-content"u8.ToArray()))
12+
];
13+
14+
static int derivedCompareCount;
15+
16+
// A comparer that masks every difference by always reporting equal.
17+
static Task<CompareResult> MaskingDerivedComparer(Stream received, Stream verified, IReadOnlyDictionary<string, object> context)
18+
{
19+
derivedCompareCount++;
20+
return Task.FromResult(CompareResult.Equal);
21+
}
22+
23+
[Fact]
24+
public async Task SourceEqual_DerivedComparerUsed()
25+
{
26+
derivedCompareCount = 0;
27+
// The source matches its verified file, so no bypass is triggered and the derived comparer
28+
// runs and masks the difference in the derived target.
29+
await Verify(null, BuildTargets())
30+
.UseStreamComparer(MaskingDerivedComparer, "bypassderived")
31+
.DisableDiff();
32+
Assert.Equal(1, derivedCompareCount);
33+
}
34+
35+
[Fact]
36+
public async Task SourceDiffers_DerivedComparerBypassed()
37+
{
38+
derivedCompareCount = 0;
39+
// The source differs from its verified file. Because it is flagged, the derived comparer is
40+
// bypassed (never invoked) and the otherwise-masked derived difference is surfaced.
41+
var exception = await Assert.ThrowsAsync<VerifyException>(
42+
() => Verify(null, BuildTargets())
43+
.UseStreamComparer(MaskingDerivedComparer, "bypassderived")
44+
.DisableDiff());
45+
Assert.Equal(0, derivedCompareCount);
46+
Assert.Contains("bypassderived", exception.Message);
47+
}
48+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#if DEBUG
2+
3+
public class BypassComparerSnippets
4+
{
5+
#region BypassComparersForSubsequentOnDifference
6+
7+
// A converter that emits a canonical source document alongside derived targets (eg rendered pages).
8+
// The source is flagged so that, when it differs, the derived targets skip their (potentially lenient)
9+
// comparers and fall back to exact comparison, ensuring a real change in the source is never masked.
10+
public static ConversionResult ConvertDocument(Stream document, IReadOnlyDictionary<string, object> context)
11+
{
12+
Target[] targets =
13+
[
14+
new("docx", document)
15+
{
16+
BypassComparersForSubsequentOnDifference = true
17+
},
18+
new("png", RenderPage(document))
19+
];
20+
return new(info: null, targets);
21+
}
22+
23+
#endregion
24+
25+
static Stream RenderPage(Stream document) =>
26+
new MemoryStream();
27+
}
28+
29+
#endif

0 commit comments

Comments
 (0)