feat(transport): pure Myers LineDiffer helper (M8 A3, T20)
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
using ZB.MOM.WW.ScadaBridge.Transport.Import;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Transport.Tests.Import;
|
||||
|
||||
public sealed class LineDifferTests
|
||||
{
|
||||
[Fact]
|
||||
public void IdenticalText_NoAddsOrRemoves_NotTruncated()
|
||||
{
|
||||
var result = LineDiffer.Diff("a\nb\nc", "a\nb\nc");
|
||||
|
||||
Assert.Equal(0, result.AddedCount);
|
||||
Assert.Equal(0, result.RemovedCount);
|
||||
Assert.False(result.Truncated);
|
||||
// All emitted lines (if any) must be Context.
|
||||
Assert.All(result.Lines, l => Assert.Equal(LineDiffOp.Context, l.Op));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OldEmpty_NewThreeLines_ThreeAdds_NewLineNos1To3()
|
||||
{
|
||||
var result = LineDiffer.Diff("", "x\ny\nz");
|
||||
|
||||
var adds = result.Lines.Where(l => l.Op == LineDiffOp.Add).ToList();
|
||||
Assert.Equal(3, result.AddedCount);
|
||||
Assert.Equal(0, result.RemovedCount);
|
||||
Assert.False(result.Truncated);
|
||||
Assert.Equal(3, adds.Count);
|
||||
Assert.Equal(new[] { 1, 2, 3 }, adds.Select(a => a.NewLineNo!.Value).ToArray());
|
||||
Assert.Equal(new[] { "x", "y", "z" }, adds.Select(a => a.Text).ToArray());
|
||||
// Add lines have no old line number.
|
||||
Assert.All(adds, a => Assert.Null(a.OldLineNo));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NewEmpty_OldTwoLines_TwoRemoves_OldLineNos1To2()
|
||||
{
|
||||
var result = LineDiffer.Diff("p\nq", "");
|
||||
|
||||
var removes = result.Lines.Where(l => l.Op == LineDiffOp.Remove).ToList();
|
||||
Assert.Equal(0, result.AddedCount);
|
||||
Assert.Equal(2, result.RemovedCount);
|
||||
Assert.False(result.Truncated);
|
||||
Assert.Equal(2, removes.Count);
|
||||
Assert.Equal(new[] { 1, 2 }, removes.Select(r => r.OldLineNo!.Value).ToArray());
|
||||
Assert.Equal(new[] { "p", "q" }, removes.Select(r => r.Text).ToArray());
|
||||
// Remove lines have no new line number.
|
||||
Assert.All(removes, r => Assert.Null(r.NewLineNo));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleMiddleLineChange_OneRemoveOneAdd_ContextPreserved()
|
||||
{
|
||||
var result = LineDiffer.Diff("a\nB\nc", "a\nX\nc");
|
||||
|
||||
var removes = result.Lines.Where(l => l.Op == LineDiffOp.Remove).ToList();
|
||||
var adds = result.Lines.Where(l => l.Op == LineDiffOp.Add).ToList();
|
||||
var contexts = result.Lines.Where(l => l.Op == LineDiffOp.Context).ToList();
|
||||
|
||||
Assert.Equal(1, result.RemovedCount);
|
||||
Assert.Equal(1, result.AddedCount);
|
||||
Assert.False(result.Truncated);
|
||||
|
||||
var remove = Assert.Single(removes);
|
||||
Assert.Equal("B", remove.Text);
|
||||
Assert.Equal(2, remove.OldLineNo);
|
||||
Assert.Null(remove.NewLineNo);
|
||||
|
||||
var add = Assert.Single(adds);
|
||||
Assert.Equal("X", add.Text);
|
||||
Assert.Equal(2, add.NewLineNo);
|
||||
Assert.Null(add.OldLineNo);
|
||||
|
||||
// Context lines "a" and "c" carry both line numbers.
|
||||
Assert.Equal(2, contexts.Count);
|
||||
var ctxA = contexts.Single(c => c.Text == "a");
|
||||
Assert.Equal(1, ctxA.OldLineNo);
|
||||
Assert.Equal(1, ctxA.NewLineNo);
|
||||
var ctxC = contexts.Single(c => c.Text == "c");
|
||||
Assert.Equal(3, ctxC.OldLineNo);
|
||||
Assert.Equal(3, ctxC.NewLineNo);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CrlfNormalization_NoChanges()
|
||||
{
|
||||
var result = LineDiffer.Diff("a\r\nb", "a\nb");
|
||||
|
||||
Assert.Equal(0, result.AddedCount);
|
||||
Assert.Equal(0, result.RemovedCount);
|
||||
Assert.False(result.Truncated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoneCarriageReturnNormalization_NoChanges()
|
||||
{
|
||||
var result = LineDiffer.Diff("a\rb", "a\nb");
|
||||
|
||||
Assert.Equal(0, result.AddedCount);
|
||||
Assert.Equal(0, result.RemovedCount);
|
||||
Assert.False(result.Truncated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullOld_NewSingleLine_OneAdd()
|
||||
{
|
||||
var result = LineDiffer.Diff(null, "x");
|
||||
|
||||
Assert.Equal(1, result.AddedCount);
|
||||
Assert.Equal(0, result.RemovedCount);
|
||||
var add = Assert.Single(result.Lines, l => l.Op == LineDiffOp.Add);
|
||||
Assert.Equal("x", add.Text);
|
||||
Assert.Equal(1, add.NewLineNo);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullNew_OldSingleLine_OneRemove()
|
||||
{
|
||||
var result = LineDiffer.Diff("x", null);
|
||||
|
||||
Assert.Equal(0, result.AddedCount);
|
||||
Assert.Equal(1, result.RemovedCount);
|
||||
var remove = Assert.Single(result.Lines, l => l.Op == LineDiffOp.Remove);
|
||||
Assert.Equal("x", remove.Text);
|
||||
Assert.Equal(1, remove.OldLineNo);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BothNull_NoChanges()
|
||||
{
|
||||
var result = LineDiffer.Diff(null, null);
|
||||
|
||||
Assert.Equal(0, result.AddedCount);
|
||||
Assert.Equal(0, result.RemovedCount);
|
||||
Assert.False(result.Truncated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SizeCap_OldEmpty_ThousandNewLines_TruncatedToMax()
|
||||
{
|
||||
var newText = string.Join("\n", Enumerable.Range(1, 1000).Select(i => $"line{i}"));
|
||||
|
||||
var result = LineDiffer.Diff("", newText, maxLines: 400);
|
||||
|
||||
Assert.True(result.Lines.Count <= 400);
|
||||
Assert.True(result.Truncated);
|
||||
Assert.Equal(1000, result.AddedCount);
|
||||
Assert.Equal(0, result.RemovedCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InterleavedCase_LcsContextPreserved()
|
||||
{
|
||||
// old: 1 2 3 4 new: 1 3 4 5 => remove 2, context 1/3/4, add 5
|
||||
var result = LineDiffer.Diff("1\n2\n3\n4", "1\n3\n4\n5");
|
||||
|
||||
Assert.Equal(1, result.AddedCount);
|
||||
Assert.Equal(1, result.RemovedCount);
|
||||
Assert.False(result.Truncated);
|
||||
|
||||
var remove = Assert.Single(result.Lines, l => l.Op == LineDiffOp.Remove);
|
||||
Assert.Equal("2", remove.Text);
|
||||
Assert.Equal(2, remove.OldLineNo);
|
||||
|
||||
var add = Assert.Single(result.Lines, l => l.Op == LineDiffOp.Add);
|
||||
Assert.Equal("5", add.Text);
|
||||
Assert.Equal(4, add.NewLineNo);
|
||||
|
||||
var contextTexts = result.Lines
|
||||
.Where(l => l.Op == LineDiffOp.Context)
|
||||
.Select(l => l.Text)
|
||||
.ToList();
|
||||
Assert.Equal(new[] { "1", "3", "4" }, contextTexts);
|
||||
|
||||
// Verify ordering: the remove of "2" appears after context "1" and before context "3".
|
||||
var ops = result.Lines.Select(l => (l.Op, l.Text)).ToList();
|
||||
var idxCtx1 = ops.FindIndex(x => x is { Op: LineDiffOp.Context, Text: "1" });
|
||||
var idxRem2 = ops.FindIndex(x => x is { Op: LineDiffOp.Remove, Text: "2" });
|
||||
var idxCtx3 = ops.FindIndex(x => x is { Op: LineDiffOp.Context, Text: "3" });
|
||||
Assert.True(idxCtx1 < idxRem2 && idxRem2 < idxCtx3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TrailingNewline_ProducesTrailingEmptyLine()
|
||||
{
|
||||
// "a\n" splits to ["a", ""] — both texts identical => no changes.
|
||||
var result = LineDiffer.Diff("a\n", "a\n");
|
||||
|
||||
Assert.Equal(0, result.AddedCount);
|
||||
Assert.Equal(0, result.RemovedCount);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user