fix(transport): cap LineDiffer input size — oversized script diffs degrade to summary instead of O((N+M)^2) memory

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
Joseph Doherty
2026-07-09 16:08:15 -04:00
parent ef59c752ba
commit 53ce64ba79
2 changed files with 53 additions and 0 deletions
@@ -1,3 +1,5 @@
using System.Diagnostics;
using ZB.MOM.WW.ScadaBridge.Transport.Import;
namespace ZB.MOM.WW.ScadaBridge.Transport.Tests.Import;
@@ -189,4 +191,29 @@ public sealed class LineDifferTests
Assert.Equal(0, result.AddedCount);
Assert.Equal(0, result.RemovedCount);
}
[Fact]
public void OversizedInputs_ZeroCommonLines_DegradesToSummary_WithoutRunningMyers()
{
// Two 10,000-line inputs with zero common lines. Running the Myers trace here would
// record ~20,000 V-snapshots of ~160 KB each (multi-GB transient allocation), so the
// input-size guard must short-circuit to a summary-only result long before that.
var oldText = string.Join("\n", Enumerable.Range(1, 10_000).Select(i => $"old-line-{i}"));
var newText = string.Join("\n", Enumerable.Range(1, 10_000).Select(i => $"new-line-{i}"));
var sw = Stopwatch.StartNew();
var result = LineDiffer.Diff(oldText, newText);
sw.Stop();
// Summary-only shape: same as the output cap — Truncated, full totals, no hunks.
Assert.True(result.Truncated);
Assert.Empty(result.Lines);
Assert.Equal(10_000, result.AddedCount);
Assert.Equal(10_000, result.RemovedCount);
// Must return within a small time budget; pathological slowness is a failure.
Assert.True(
sw.Elapsed < TimeSpan.FromSeconds(2),
$"Diff of oversized inputs took {sw.Elapsed.TotalSeconds:F1}s — the input-size guard did not short-circuit.");
}
}