Skip to content

Commit 7253acb

Browse files
authored
Reject carriage returns in verified files instead of normalizing (#1762)
* Reject carriage returns in verified files instead of normalizing The text comparison previously read the verified file via ReadStringBuilderWithFixedLines, silently converting \r\n and \r to \n. Now it reads the file directly and throws if it contains a \r, linking to the text-file-settings docs. Verified files are LF-only (enforced by .gitattributes), so this surfaces misconfigured line endings rather than masking them.
1 parent 211ad6f commit 7253acb

12 files changed

Lines changed: 52 additions & 86 deletions

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Project>
33
<PropertyGroup>
44
<NoWarn>CA1822;CS1591;CS0649;xUnit1026;xUnit1013;CS1573;VerifyTestsProjectDir;VerifySetParameters;PolyFillTargetsForNuget;xUnit1051;NU1608;NU1109</NoWarn>
5-
<Version>31.20.0</Version>
5+
<Version>31.21.0</Version>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<LangVersion>preview</LangVersion>
88
<AssemblyVersion>1.0.0</AssemblyVersion>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
22
"Array": null
3-
}
3+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
22
"Array": null
3-
}
3+
}

src/Verify.ExceptionParsing.Tests/ExceptionMessageFormatSamples.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public Task AllCategories()
1414
};
1515
var notEquals = new List<NotEqualResult>
1616
{
17-
new(new("txt", Dir("MyTests.Test2.received.txt"), Dir("MyTests.Test2.verified.txt")), null, new("received text"), new("verified text"))
17+
new(new("txt", Dir("MyTests.Test2.received.txt"), Dir("MyTests.Test2.verified.txt")), null, new("received text"), "verified text")
1818
};
1919
var delete = new List<string>
2020
{
@@ -33,7 +33,7 @@ public Task NotEqualWithMessage()
3333
{
3434
var notEquals = new List<NotEqualResult>
3535
{
36-
new(new("txt", Dir("MyTests.Test1.received.txt"), Dir("MyTests.Test1.verified.txt")), "The comparer reported a difference", new("received text"), new("verified text"))
36+
new(new("txt", Dir("MyTests.Test1.received.txt"), Dir("MyTests.Test1.verified.txt")), "The comparer reported a difference", new("received text"), "verified text")
3737
};
3838

3939
return BuildVerify([], notEquals, [], []);

src/Verify.ExceptionParsing.Tests/ExceptionParsingTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public Task WithMessage()
3131
{
3232
var notEquals = new List<NotEqualResult>
3333
{
34-
new(new("txt", receivedTxt, verifiedTxt), "TheMessage", new("receivedText"), new("verifiedText")),
34+
new(new("txt", receivedTxt, verifiedTxt), "TheMessage", new("receivedText"), "verifiedText"),
3535
new(new("bin", receivedBin, verifiedBin), "TheMessage", null, null)
3636
};
3737

@@ -132,7 +132,7 @@ public Task SingleNotEqual()
132132
{
133133
var notEquals = new List<NotEqualResult>
134134
{
135-
new(new("txt", receivedTxt, verifiedTxt), null, new("receivedText"), new("verifiedText"))
135+
new(new("txt", receivedTxt, verifiedTxt), null, new("receivedText"), "verifiedText")
136136
};
137137

138138
return ParseVerify([], notEquals, [], []);
@@ -153,7 +153,7 @@ public Task MultipleItem()
153153
};
154154
var notEquals = new List<NotEqualResult>
155155
{
156-
new(new("txt", receivedTxt, verifiedTxt), null, new("receivedText"), new("verifiedText")),
156+
new(new("txt", receivedTxt, verifiedTxt), null, new("receivedText"), "verifiedText"),
157157
new(new("bin", receivedBin, verifiedBin), null, null, null)
158158
};
159159
var delete = new List<string>
@@ -178,7 +178,7 @@ public Task SingleItem()
178178
};
179179
var notEquals = new List<NotEqualResult>
180180
{
181-
new(new("txt", receivedTxt, verifiedTxt), null, new("receivedText"), new("verifiedText"))
181+
new(new("txt", receivedTxt, verifiedTxt), null, new("receivedText"), "verifiedText")
182182
};
183183
var delete = new List<string>
184184
{
@@ -242,4 +242,4 @@ static Task ParseVerify(
242242
result
243243
});
244244
}
245-
}
245+
}

src/Verify.ExceptionParsing.Tests/VerifyExceptionMessageBuilderTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public Task SingleNotEqual_Text() =>
4242
@new: [],
4343
notEquals:
4444
[
45-
new(new("txt", receivedTxt, verifiedTxt), null, new("received content"), new("verified content"))
45+
new(new("txt", receivedTxt, verifiedTxt), null, new("received content"), "verified content")
4646
],
4747
delete: [],
4848
equal: []);
@@ -53,7 +53,7 @@ public Task SingleNotEqual_WithMessage() =>
5353
@new: [],
5454
notEquals:
5555
[
56-
new(new("txt", receivedTxt, verifiedTxt), "Comparer reported difference", new("received content"), new("verified content"))
56+
new(new("txt", receivedTxt, verifiedTxt), "Comparer reported difference", new("received content"), "verified content")
5757
],
5858
delete: [],
5959
equal: []);
@@ -108,7 +108,7 @@ public Task AllCategories()
108108
};
109109
var notEquals = new List<NotEqualResult>
110110
{
111-
new(new("txt", receivedTxt, verifiedTxt), null, new("received"), new("verified"))
111+
new(new("txt", receivedTxt, verifiedTxt), null, new("received"), "verified")
112112
};
113113
var delete = new List<string>
114114
{
@@ -139,8 +139,8 @@ public Task MultipleNotEqual_MixedMessageAndNoMessage()
139139
{
140140
var notEquals = new List<NotEqualResult>
141141
{
142-
new(new("txt", receivedTxt, verifiedTxt), null, new("received text"), new("verified text")),
143-
new(new("txt", receivedTxt, verifiedTxt), "Custom comparison message", new("received2"), new("verified2"))
142+
new(new("txt", receivedTxt, verifiedTxt), null, new("received text"), "verified text"),
143+
new(new("txt", receivedTxt, verifiedTxt), "Custom comparison message", new("received2"), "verified2")
144144
};
145145

146146
return BuildVerify([], notEquals, [], []);
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{}
1+
{}

src/Verify.Tests/NewLineTests.cs

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -44,35 +44,27 @@ public async Task StringWithDifferingNewline()
4444
{
4545
var fullPath = CurrentFile.Relative("NewLineTests.StringWithDifferingNewline.verified.txt");
4646
File.Delete(fullPath);
47+
var settings = new VerifySettings();
48+
settings.DisableRequireUniquePrefix();
49+
50+
// A verified file containing \r is rejected rather than silently normalized
4751
await File.WriteAllTextAsync(fullPath, "a\r\nb");
48-
await Verify("a\r\nb");
49-
PrefixUnique.Clear();
50-
await Verify("a\rb");
51-
PrefixUnique.Clear();
52-
await Verify("a\nb");
53-
PrefixUnique.Clear();
52+
var crlf = await Assert.ThrowsAnyAsync<Exception>(() => Verify("a\nb", settings));
53+
Assert.Contains("carriage return", crlf.ToString());
5454

55-
File.Delete(fullPath);
55+
await File.WriteAllTextAsync(fullPath, "a\rb");
56+
var cr = await Assert.ThrowsAnyAsync<Exception>(() => Verify("a\nb", settings));
57+
Assert.Contains("carriage return", cr.ToString());
58+
59+
// A verified file using \n still matches received content normalized to \n
5660
await File.WriteAllTextAsync(fullPath, "a\nb");
57-
await Verify("a\r\nb");
58-
PrefixUnique.Clear();
59-
await Verify("a\rb");
60-
PrefixUnique.Clear();
61-
await Verify("a\nb");
62-
PrefixUnique.Clear();
61+
await Verify("a\r\nb", settings);
62+
await Verify("a\rb", settings);
63+
await Verify("a\nb", settings);
6364

6465
File.Delete(fullPath);
65-
await File.WriteAllTextAsync(fullPath, "a\rb");
66-
await Verify("a\r\nb");
67-
PrefixUnique.Clear();
68-
await Verify("a\rb");
69-
PrefixUnique.Clear();
70-
await Verify("a\nb");
71-
File.Delete(fullPath);
7266
}
7367

74-
#if NET9_0
75-
7668
[Fact]
7769
public async Task TrailingNewlinesRaw()
7870
{
@@ -81,26 +73,21 @@ public async Task TrailingNewlinesRaw()
8173
var settings = new VerifySettings();
8274
settings.DisableRequireUniquePrefix();
8375

76+
// A verified file containing \r is rejected
8477
await File.WriteAllTextAsync(file, "a\r\n");
85-
await Verify("a\r\n", settings);
86-
await Verify("a\n", settings);
87-
await Verify("a", settings);
88-
89-
await File.WriteAllTextAsync(file, "a\r\n\r\n");
90-
await Verify("a\r\n\r\n", settings);
91-
await Verify("a\n\n", settings);
92-
await Verify("a\n", settings);
78+
var exception = await Assert.ThrowsAnyAsync<Exception>(() => Verify("a\n", settings));
79+
Assert.Contains("carriage return", exception.ToString());
9380

81+
// Trailing newlines are now compared exactly, with no tolerance
9482
await File.WriteAllTextAsync(file, "a\n");
9583
await Verify("a\n", settings);
96-
await Verify("a", settings);
84+
await Assert.ThrowsAsync<VerifyException>(() => Verify("a", settings));
9785

9886
await File.WriteAllTextAsync(file, "a\n\n");
9987
await Verify("a\n\n", settings);
100-
await Verify("a\n", settings);
88+
await Assert.ThrowsAsync<VerifyException>(() => Verify("a\n", settings));
10189
File.Delete(file);
10290
}
103-
#endif
10491

10592
//TODO: add test for trailing newlines
10693
// [Fact]

src/Verify/Compare/Comparer.cs

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ public static async Task<EqualityResult> Text(FilePair filePair, StringBuilder r
99
return new(Equality.New, null, received, null);
1010
}
1111

12-
var verified = await IoHelpers.ReadStringBuilderWithFixedLines(filePair.VerifiedPath);
12+
var verified = await File.ReadAllTextAsync(filePair.VerifiedPath);
13+
if (verified.Contains('\r'))
14+
{
15+
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");
16+
}
17+
1318
var result = await CompareStrings(filePair.Extension, received, verified, settings, bypassComparer);
1419
if (result.IsEqual)
1520
{
@@ -20,36 +25,16 @@ public static async Task<EqualityResult> Text(FilePair filePair, StringBuilder r
2025
return new(Equality.NotEqual, result.Message, received, verified);
2126
}
2227

23-
static Task<CompareResult> CompareStrings(string extension, StringBuilder received, StringBuilder verified, VerifySettings settings, bool bypassComparer)
28+
static Task<CompareResult> CompareStrings(string extension, StringBuilder received, string verified, VerifySettings settings, bool bypassComparer)
2429
{
25-
if (verified.Length > 0 &&
26-
verified.Length - 1 == received.Length &&
27-
verified.LastChar() == '\n')
28-
{
29-
verified.Length -= 1;
30-
}
31-
32-
// StringBuilder is broken on older .net https://github.com/dotnet/runtime/issues/27684
33-
#if NET6_0_OR_GREATER
34-
var isEqual = verified.Equals(received);
35-
if (!isEqual &&
36-
!bypassComparer &&
37-
settings.TryFindStringComparer(extension, out var compare))
38-
{
39-
return compare(received.ToString(), verified.ToString(), settings.Context);
40-
}
41-
#else
42-
var receivedString = received.ToString();
43-
var verifiedString = verified.ToString();
44-
var isEqual = receivedString.Equals(verifiedString);
30+
var isEqual = received.Equals(verified.AsSpan());
4531
if (!isEqual &&
4632
!bypassComparer &&
4733
settings.TryFindStringComparer(extension, out var compare))
4834
{
49-
return compare(receivedString, verifiedString, settings.Context);
35+
return compare(received.ToString(), verified, settings.Context);
5036
}
51-
#endif
5237

5338
return Task.FromResult(new CompareResult(isEqual));
5439
}
55-
}
40+
}

src/Verify/IoHelpers.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,6 @@ internal static string ResolveDirectoryFromSourceFile(string sourceFile)
208208
throw new($"Unable to resolve directory. sourceFile: {sourceFile}");
209209
}
210210

211-
public static async Task<StringBuilder> ReadStringBuilderWithFixedLines(string path)
212-
{
213-
using var stream = OpenRead(path);
214-
return await stream.ReadStringBuilderWithFixedLines();
215-
}
216-
217211
public static async Task WriteStream(string path, Stream stream)
218212
{
219213
Directory.CreateDirectory(Path.GetDirectoryName(path)!);

0 commit comments

Comments
 (0)