diff --git a/TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs b/TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs
index 4298f11857..66857ab5f0 100644
--- a/TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs
+++ b/TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs
@@ -672,6 +672,27 @@ private static void TryExtractExpectedActual(string message, out string? expecte
expected = null;
actual = null;
if (string.IsNullOrEmpty(message)) return;
+#if NET
+ // EnumerateLines splits on \n and \r\n natively — no Replace/Split allocations.
+ foreach (var rawLine in message.AsSpan().EnumerateLines())
+ {
+ var line = rawLine.TrimStart();
+ if (expected is null && line.StartsWith("Expected:", StringComparison.OrdinalIgnoreCase))
+ {
+ expected = line.Slice("Expected:".Length).Trim().ToString();
+ }
+ else if (actual is null && (line.StartsWith("Actual:", StringComparison.OrdinalIgnoreCase) || line.StartsWith("But was:", StringComparison.OrdinalIgnoreCase)))
+ {
+ var prefixLen = line.StartsWith("Actual:", StringComparison.OrdinalIgnoreCase) ? "Actual:".Length : "But was:".Length;
+ actual = line.Slice(prefixLen).Trim().ToString();
+ }
+
+ if (expected is not null && actual is not null)
+ {
+ break;
+ }
+ }
+#else
var lines = message.Replace("\r\n", "\n").Split('\n');
foreach (var raw in lines)
{
@@ -685,7 +706,13 @@ private static void TryExtractExpectedActual(string message, out string? expecte
var prefixLen = line.StartsWith("Actual:", StringComparison.OrdinalIgnoreCase) ? "Actual:".Length : "But was:".Length;
actual = line.Substring(prefixLen).Trim();
}
+
+ if (expected is not null && actual is not null)
+ {
+ break;
+ }
}
+#endif
if (expected is { Length: 0 }) expected = null;
if (actual is { Length: 0 }) actual = null;
}
@@ -767,14 +794,24 @@ private static bool IsDuplicateKey(ReportKeyValue[] items, int index, string key
{
if (string.IsNullOrEmpty(stderr)) return stderr;
if (stderr!.IndexOf("[TUnit]", StringComparison.Ordinal) < 0) return stderr;
- var lines = stderr.Replace("\r\n", "\n").Split('\n');
var sb = new StringBuilder(stderr.Length);
+#if NET
+ // EnumerateLines splits on \n and \r\n natively — no Replace/Split allocations.
+ foreach (var line in stderr.AsSpan().EnumerateLines())
+ {
+ if (line.TrimStart().StartsWith("[TUnit]", StringComparison.Ordinal)) continue;
+ if (sb.Length > 0) sb.Append('\n');
+ sb.Append(line);
+ }
+#else
+ var lines = stderr.Replace("\r\n", "\n").Split('\n');
foreach (var line in lines)
{
if (line.TrimStart().StartsWith("[TUnit]", StringComparison.Ordinal)) continue;
if (sb.Length > 0) sb.Append('\n');
sb.Append(line);
}
+#endif
return sb.Length == 0 ? null : sb.ToString();
}