Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/Import/LineDifferTests.cs
T

193 lines
6.6 KiB
C#

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);
}
}