From 16373159f82000afd01a7e46dbe39f41f0eb5c48 Mon Sep 17 00:00:00 2001 From: Sebastian Fischer Date: Sun, 31 Jan 2021 18:30:17 +0100 Subject: [PATCH] added unit tests, fixed issue of comparer not being used --- .gitignore | 6 +- src/Diffs.sln | 6 + src/MyersDiff.cs | 34 +-- test/Diffs.Test.csproj | 20 ++ test/MyersDiffClass.ReferenceCase.cs | 37 ++++ test/MyersDiffClass.ReferenceCases.cs | 290 ++++++++++++++++++++++++++ test/MyersDiffClass.cs | 67 ++++++ 7 files changed, 440 insertions(+), 20 deletions(-) create mode 100644 test/Diffs.Test.csproj create mode 100644 test/MyersDiffClass.ReferenceCase.cs create mode 100644 test/MyersDiffClass.ReferenceCases.cs create mode 100644 test/MyersDiffClass.cs diff --git a/.gitignore b/.gitignore index 1ab252a..d808730 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /src/bin /src/obj +/test/bin +/test/obj *.suo -VSWorkspaceState.json -slnx.sqlite -/src/.vs/Diffs/DesignTimeBuild +.vs diff --git a/src/Diffs.sln b/src/Diffs.sln index 53d969d..f065260 100644 --- a/src/Diffs.sln +++ b/src/Diffs.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.30907.101 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Diffs", "Diffs.csproj", "{DBB54978-242F-4A05-BAB9-52289BD418D5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Diffs.Test", "..\test\Diffs.Test.csproj", "{3CE2D8D3-C4FD-4AFF-BC85-877BE39AB7DC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {DBB54978-242F-4A05-BAB9-52289BD418D5}.Debug|Any CPU.Build.0 = Debug|Any CPU {DBB54978-242F-4A05-BAB9-52289BD418D5}.Release|Any CPU.ActiveCfg = Release|Any CPU {DBB54978-242F-4A05-BAB9-52289BD418D5}.Release|Any CPU.Build.0 = Release|Any CPU + {3CE2D8D3-C4FD-4AFF-BC85-877BE39AB7DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3CE2D8D3-C4FD-4AFF-BC85-877BE39AB7DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3CE2D8D3-C4FD-4AFF-BC85-877BE39AB7DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3CE2D8D3-C4FD-4AFF-BC85-877BE39AB7DC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/MyersDiff.cs b/src/MyersDiff.cs index 05dcc53..7cff734 100644 --- a/src/MyersDiff.cs +++ b/src/MyersDiff.cs @@ -25,9 +25,23 @@ public class MyersDiff /// Item sequence A. /// Item sequence B. public MyersDiff(T[] aValues, T[] bValues) + : this(aValues, bValues, null) { - this.aValues = aValues; - this.bValues = bValues; + } + + /// + /// Creates a new instance of the class + /// and calculates the diff result of sequences A and B + /// using the provided to determine item equality. + /// + /// Item sequence A. + /// Item sequence B. + /// The implementation to determine item equality. + public MyersDiff(T[] aValues, T[] bValues, IEqualityComparer comparer) + { + this.aValues = aValues ?? throw new ArgumentNullException(nameof(aValues)); + this.bValues = bValues ?? throw new ArgumentNullException(nameof(bValues)); + this.comparer = comparer; this.aRemoved = new bool[this.aValues.Length]; this.bAdded = new bool[this.bValues.Length]; @@ -35,7 +49,7 @@ public MyersDiff(T[] aValues, T[] bValues) this.Vf = VArray.CreateFromTo(-VMAX, VMAX); this.Vr = VArray.CreateFromTo(-VMAX, VMAX); - + int[] aIndexes = new int[this.aValues.Length]; for (int i = 0; i < aIndexes.Length; i++) { @@ -55,20 +69,6 @@ public MyersDiff(T[] aValues, T[] bValues) #endif } - /// - /// Creates a new instance of the class - /// and calculates the diff result of sequences A and B - /// using the provided to determine item equality. - /// - /// Item sequence A. - /// Item sequence B. - /// The implementation to determine item equality. - public MyersDiff(T[] aValues, T[] bValues, IEqualityComparer comparer) - : this(aValues, bValues) - { - this.comparer = comparer; - } - private bool AreEqual(int aIndex, int bIndex) { if (this.comparer != null) diff --git a/test/Diffs.Test.csproj b/test/Diffs.Test.csproj new file mode 100644 index 0000000..1343b4c --- /dev/null +++ b/test/Diffs.Test.csproj @@ -0,0 +1,20 @@ + + + + net48;netcoreapp2.1;netcoreapp3.1;net5.0 + false + spkl.Diffs.Test + spkl.Diffs.Test + + + + + + + + + + + + + diff --git a/test/MyersDiffClass.ReferenceCase.cs b/test/MyersDiffClass.ReferenceCase.cs new file mode 100644 index 0000000..f57fcc7 --- /dev/null +++ b/test/MyersDiffClass.ReferenceCase.cs @@ -0,0 +1,37 @@ +namespace spkl.Diffs.Test +{ + public partial class MyersDiffClass + { + public class ReferenceCase + { + public string[] A { get; set; } + + public string AString + { + set + { + this.A = value.Split(','); + } + } + + public string[] B { get; set; } + + public string BString + { + set + { + this.B = value.Split(','); + } + } + + public (ResultType ResultType, string AItem, string BItem)[] Result { get; set; } + + public (int LineA, int LineB, int CountA, int CountB)[] EditScript { get; set; } + + public override string ToString() + { + return $"{string.Join(",", this.A)} / {string.Join(",", this.B)}"; + } + } + } +} \ No newline at end of file diff --git a/test/MyersDiffClass.ReferenceCases.cs b/test/MyersDiffClass.ReferenceCases.cs new file mode 100644 index 0000000..4724e3e --- /dev/null +++ b/test/MyersDiffClass.ReferenceCases.cs @@ -0,0 +1,290 @@ +namespace spkl.Diffs.Test +{ + public partial class MyersDiffClass + { + private static readonly ReferenceCase[] ReferenceCases = new[] + { + new ReferenceCase + { + AString = "a,b,c,a,b,b,a", + BString = "c,b,a,b,a,c", + Result = new (ResultType, string, string)[] + { + (ResultType.A, "a", null), + (ResultType.B, null, "c"), + (ResultType.Both, "b", "b"), + (ResultType.A, "c", null), + (ResultType.Both, "a", "a"), + (ResultType.Both, "b", "b"), + (ResultType.A, "b", null), + (ResultType.Both, "a", "a"), + (ResultType.B, null, "c"), + }, + EditScript = new (int, int, int, int)[] + { + (0, 0, 1, 1), + (2, 2, 1, 0), + (5, 4, 1, 0), + (7, 5, 0, 1) + } + }, + new ReferenceCase + { + AString = "a,b,c,d,e,f,g,h,i,j,k,l", + BString = "0,1,2,3,4,5,6,7,8,9", + Result = new (ResultType, string, string)[] + { + (ResultType.A, "a", null), + (ResultType.A, "b", null), + (ResultType.A, "c", null), + (ResultType.A, "d", null), + (ResultType.A, "e", null), + (ResultType.A, "f", null), + (ResultType.A, "g", null), + (ResultType.A, "h", null), + (ResultType.A, "i", null), + (ResultType.A, "j", null), + (ResultType.A, "k", null), + (ResultType.A, "l", null), + (ResultType.B, null, "0"), + (ResultType.B, null, "1"), + (ResultType.B, null, "2"), + (ResultType.B, null, "3"), + (ResultType.B, null, "4"), + (ResultType.B, null, "5"), + (ResultType.B, null, "6"), + (ResultType.B, null, "7"), + (ResultType.B, null, "8"), + (ResultType.B, null, "9"), + }, + EditScript = new (int, int, int, int)[] + { + (0, 0, 12, 10) + } + }, + new ReferenceCase + { + AString = "0,1,2,3,4,5,6,7,8,9", + BString = "a,b,c,d,e,f,g,h,i,j,k,l", + Result = new (ResultType, string, string)[] + { + (ResultType.A, "0", null), + (ResultType.A, "1", null), + (ResultType.A, "2", null), + (ResultType.A, "3", null), + (ResultType.A, "4", null), + (ResultType.A, "5", null), + (ResultType.A, "6", null), + (ResultType.A, "7", null), + (ResultType.A, "8", null), + (ResultType.A, "9", null), + (ResultType.B, null, "a"), + (ResultType.B, null, "b"), + (ResultType.B, null, "c"), + (ResultType.B, null, "d"), + (ResultType.B, null, "e"), + (ResultType.B, null, "f"), + (ResultType.B, null, "g"), + (ResultType.B, null, "h"), + (ResultType.B, null, "i"), + (ResultType.B, null, "j"), + (ResultType.B, null, "k"), + (ResultType.B, null, "l"), + }, + EditScript = new (int, int, int, int)[] + { + (0, 0, 10, 12) + } + }, + new ReferenceCase + { + AString = "a,b,c,d,e,f,g,h,i,j,k,l", + BString = "a,b,c,d,e,f,g,h,i,j,k,l", + Result = new (ResultType, string, string)[] + { + (ResultType.Both, "a", "a"), + (ResultType.Both, "b", "b"), + (ResultType.Both, "c", "c"), + (ResultType.Both, "d", "d"), + (ResultType.Both, "e", "e"), + (ResultType.Both, "f", "f"), + (ResultType.Both, "g", "g"), + (ResultType.Both, "h", "h"), + (ResultType.Both, "i", "i"), + (ResultType.Both, "j", "j"), + (ResultType.Both, "k", "k"), + (ResultType.Both, "l", "l"), + }, + EditScript = new (int, int, int, int)[] + { + } + }, + new ReferenceCase + { + AString = "a,b,c,d,e,f", + BString = "b,c,d,e,f,x", + Result = new (ResultType, string, string)[] + { + (ResultType.A, "a", null), + (ResultType.Both, "b", "b"), + (ResultType.Both, "c", "c"), + (ResultType.Both, "d", "d"), + (ResultType.Both, "e", "e"), + (ResultType.Both, "f", "f"), + (ResultType.B, null, "x") + }, + EditScript = new (int, int, int, int)[] + { + (0, 0, 1, 0), + (6, 5, 0, 1) + } + }, + new ReferenceCase + { + AString = "b,c,d,e,f,x", + BString = "a,b,c,d,e,f", + Result = new (ResultType, string, string)[] + { + (ResultType.B, null, "a"), + (ResultType.Both, "b", "b"), + (ResultType.Both, "c", "c"), + (ResultType.Both, "d", "d"), + (ResultType.Both, "e", "e"), + (ResultType.Both, "f", "f"), + (ResultType.A, "x", null) + }, + EditScript = new (int, int, int, int)[] + { + (0, 0, 0, 1), + (5, 6, 1, 0) + } + }, + new ReferenceCase + { + AString = "c1,a,c2,b,c,d,e,g,h,i,j,c3,k,l", + BString = "C1,a,C2,b,c,d,e,I1,e,g,h,i,j,C3,k,I2,l", + Result = new (ResultType, string, string)[] + { + (ResultType.A, "c1", null), + (ResultType.B, null, "C1"), + (ResultType.Both, "a", "a"), + (ResultType.A, "c2", null), + (ResultType.B, null, "C2"), + (ResultType.Both, "b", "b"), + (ResultType.Both, "c", "c"), + (ResultType.Both, "d", "d"), + (ResultType.Both, "e", "e"), + (ResultType.B, null, "I1"), + (ResultType.B, null, "e"), + (ResultType.Both, "g", "g"), + (ResultType.Both, "h", "h"), + (ResultType.Both, "i", "i"), + (ResultType.Both, "j", "j"), + (ResultType.A, "c3", null), + (ResultType.B, null, "C3"), + (ResultType.Both, "k", "k"), + (ResultType.B, null, "I2"), + (ResultType.Both, "l", "l"), + }, + EditScript = new (int, int, int, int)[] + { + (0, 0, 1, 1), + (2, 2, 1, 1), + (7, 7, 0, 2), + (11, 13, 1, 1), + (13, 15, 0, 1) + } + }, + new ReferenceCase + { + AString = "F", + BString = "0,F,1,2,3,4,5,6,7", + Result = new (ResultType, string, string)[] + { + (ResultType.B, null, "0"), + (ResultType.Both, "F", "F"), + (ResultType.B, null, "1"), + (ResultType.B, null, "2"), + (ResultType.B, null, "3"), + (ResultType.B, null, "4"), + (ResultType.B, null, "5"), + (ResultType.B, null, "6"), + (ResultType.B, null, "7"), + }, + EditScript = new (int, int, int, int)[] + { + (0, 0, 0, 1), + (1, 2, 0, 7) + } + }, + new ReferenceCase + { + AString = "HELLO,WORLD", + BString = ",,hello,,,,world,", + Result = new (ResultType, string, string)[] + { + (ResultType.A, "HELLO", null), + (ResultType.A, "WORLD", null), + (ResultType.B, null, ""), + (ResultType.B, null, ""), + (ResultType.B, null, "hello"), + (ResultType.B, null, ""), + (ResultType.B, null, ""), + (ResultType.B, null, ""), + (ResultType.B, null, "world"), + (ResultType.B, null, ""), + }, + EditScript = new (int, int, int, int)[] + { + (0, 0, 2, 8), + } + }, + new ReferenceCase + { + AString = "a,b,-,c,d,e,f,f", + BString = "a,b,x,c,e,f", + Result = new (ResultType, string, string)[] + { + (ResultType.Both, "a", "a"), + (ResultType.Both, "b", "b"), + (ResultType.A, "-", null), + (ResultType.B, null, "x"), + (ResultType.Both, "c", "c"), + (ResultType.A, "d", null), + (ResultType.Both, "e", "e"), + (ResultType.A, "f", null), // (Both, f, f) would also be valid + (ResultType.Both, "f", "f"), // (A, f, null) would also be valid + }, + EditScript = new (int, int, int, int)[] + { + (2, 2, 1, 1), + (4, 4, 1, 0), + (6, 5, 1, 0) // (7, 6, 1, 0) would also be valid + } + }, + new ReferenceCase + { + AString = "a,a,a,a,a,a,a,a,a,a", + BString = "a,a,a,a,-,a,a,a,a,a", + Result = new (ResultType, string, string)[] + { + (ResultType.Both, "a", "a"), + (ResultType.Both, "a", "a"), + (ResultType.Both, "a", "a"), + (ResultType.Both, "a", "a"), + (ResultType.A, "a", null), + (ResultType.B, null, "-"), + (ResultType.Both, "a", "a"), + (ResultType.Both, "a", "a"), + (ResultType.Both, "a", "a"), + (ResultType.Both, "a", "a"), + (ResultType.Both, "a", "a"), + }, + EditScript = new (int, int, int, int)[] + { + (4, 4, 1, 1), + } + }, + }; + } +} \ No newline at end of file diff --git a/test/MyersDiffClass.cs b/test/MyersDiffClass.cs new file mode 100644 index 0000000..aa25f7f --- /dev/null +++ b/test/MyersDiffClass.cs @@ -0,0 +1,67 @@ +using NUnit.Framework; +using System; +using System.Linq; + +namespace spkl.Diffs.Test +{ + public partial class MyersDiffClass + { + [Test] + public void InputValidation() + { + Assert.That(() => new MyersDiff(null, new string[] { "" }), Throws.ArgumentNullException); + Assert.That(() => new MyersDiff(new string[] { "" }, null), Throws.ArgumentNullException); + Assert.That(() => new MyersDiff(new string[0], new string[0]), Throws.Nothing); + } + + [Test] + public void CustomComparer() + { + string[] a = new[] { "a", "b", "c", "a", "b", "b", "a" }; + string[] b = new[] { "C", "B", "A", "B", "A", "C" }; + + MyersDiff diff = new MyersDiff(a, b, StringComparer.OrdinalIgnoreCase); + + (ResultType, string, string)[] result = new[] + { + (ResultType.A, "a", null), + (ResultType.B, null, "C"), + (ResultType.Both, "b", "B"), + (ResultType.A, "c", null), + (ResultType.Both, "a", "A"), + (ResultType.Both, "b", "B"), + (ResultType.A, "b", null), + (ResultType.Both, "a", "A"), + (ResultType.B, null, "C"), + }; + + (int, int, int, int)[] editScript = new[] + { + (0, 0, 1, 1), + (2, 2, 1, 0), + (5, 4, 1, 0), + (7, 5, 0, 1) + }; + + Assert.Multiple(() => + { + Assert.That(diff.GetResult().ToArray(), Is.EqualTo(result)); + Assert.That(diff.GetEditScript().ToArray(), Is.EqualTo(editScript)); + }); + } + + [Test] + [TestCaseSource(nameof(ReferenceCases))] + public void ReferenceResult(ReferenceCase testCase) + { + Assert.That(new MyersDiff(testCase.A, testCase.B).GetResult().ToArray(), Is.EqualTo(testCase.Result)); + } + + [Test] + [TestCaseSource(nameof(ReferenceCases))] + public void ReferenceEditScript(ReferenceCase testCase) + { + Assert.That(new MyersDiff(testCase.A, testCase.B).GetEditScript().ToArray(), Is.EqualTo(testCase.EditScript)); + } + } +} \ No newline at end of file