diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 94ea338..480b5cf 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -9,6 +9,11 @@ implementations of your own. [#33](https://github.com/xmlunit/xmlunit.net/pull/33). +* `DefaultNodeMatcher` with multiple `ElementSelector`s could fail to + find the best matches as the order of `ElementSelector`s should + select them. + Issue similar to [xmlunit/#197](https://github.com/xmlunit/xmlunit/issues/197) + ## XMLUnit.NET 2.8.0 - /Released 2020-05-12/ This version contains a backwards incompatible change to the diff --git a/src/main/net-core/Diff/DefaultNodeMatcher.cs b/src/main/net-core/Diff/DefaultNodeMatcher.cs index 39fddd8..0bc1fd5 100644 --- a/src/main/net-core/Diff/DefaultNodeMatcher.cs +++ b/src/main/net-core/Diff/DefaultNodeMatcher.cs @@ -99,16 +99,25 @@ public IEnumerable> unmatchedTestIndexes.Add(i); } int controlSize = controlList.Count; - MatchInfo lastMatch = new MatchInfo(null, -1); + ICollection unmatchedControlIndexes = new HashSet(); for (int i = 0; i < controlSize; i++) { - XmlNode control = controlList[i]; - MatchInfo testMatch = FindMatchingNode(control, testList, - lastMatch.Index, - unmatchedTestIndexes); - if (testMatch != null) { - unmatchedTestIndexes.Remove(testMatch.Index); - matches.AddLast(new KeyValuePair(control, testMatch.Node)); + unmatchedControlIndexes.Add(i); + } + foreach (ElementSelector e in elementSelectors) { + MatchInfo lastMatch = new MatchInfo(null, -1); + for (int i = 0; i < controlSize; i++) { + if (!unmatchedControlIndexes.Contains(i)) { + continue; + } + XmlNode control = controlList[i]; + MatchInfo testMatch = FindMatchingNode(control, testList, + lastMatch.Index, unmatchedTestIndexes, e); + if (testMatch != null) { + unmatchedControlIndexes.Remove(i); + unmatchedTestIndexes.Remove(testMatch.Index); + matches.AddLast(new KeyValuePair(control, testMatch.Node)); + } } } return matches; @@ -117,26 +126,14 @@ public IEnumerable> private MatchInfo FindMatchingNode(XmlNode searchFor, IList searchIn, int indexOfLastMatch, - ICollection availableIndexes) { + ICollection availableIndexes, + ElementSelector e) { MatchInfo m = SearchIn(searchFor, searchIn, availableIndexes, - indexOfLastMatch + 1, searchIn.Count); + indexOfLastMatch + 1, searchIn.Count, e); return m ?? SearchIn(searchFor, searchIn, availableIndexes, - 0, indexOfLastMatch); - } - - private MatchInfo SearchIn(XmlNode searchFor, - IList searchIn, - ICollection availableIndexes, - int fromInclusive, int toExclusive) { - foreach (ElementSelector e in elementSelectors) { - MatchInfo m = SearchIn(searchFor, searchIn, availableIndexes, fromInclusive, toExclusive, e); - if (m != null) { - return m; - } - } - return null; + 0, indexOfLastMatch, e); } private MatchInfo SearchIn(XmlNode searchFor, @@ -195,4 +192,4 @@ public static bool DefaultNodeTypeMatcher(XmlNodeType controlType, && testType == XmlNodeType.CDATA); } } -} \ No newline at end of file +} diff --git a/src/tests/net-core/Diff/DefaultNodeMatcherTest.cs b/src/tests/net-core/Diff/DefaultNodeMatcherTest.cs new file mode 100644 index 0000000..1a70e78 --- /dev/null +++ b/src/tests/net-core/Diff/DefaultNodeMatcherTest.cs @@ -0,0 +1,120 @@ +/* + This file is licensed to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using NUnit.Framework; + +namespace Org.XmlUnit.Diff { + + [TestFixture] + public class DefaultNodeMatcherTest { + + private XmlDocument doc; + + [SetUp] + public void CreateDoc() { + doc = new XmlDocument(); + } + + [Test] + public void ElementSelectorsAreQueriedInSequence() { + XmlElement control1 = doc.CreateElement("a"); + control1.AppendChild(doc.CreateTextNode("foo")); + XmlElement control2 = doc.CreateElement("a"); + control2.AppendChild(doc.CreateTextNode("bar")); + + XmlElement test1 = doc.CreateElement("a"); + test1.AppendChild(doc.CreateTextNode("baz")); + XmlElement test2 = doc.CreateElement("a"); + test2.AppendChild(doc.CreateTextNode("foo")); + + DefaultNodeMatcher m = + new DefaultNodeMatcher(ElementSelectors.ByNameAndText, + ElementSelectors.ByName); + List> result = + m.Match(new XmlNode[] { control1, control2}, + new XmlNode[] { test1, test2 }).ToList(); + Assert.AreEqual(result.Count, 2); + + // ByNameAndText + Assert.AreSame(result[0].Key, control1); + Assert.AreSame(result[0].Value, test2); + + // ByName + Assert.AreSame(result[1].Key, control2); + Assert.AreSame(result[1].Value, test1); + } + + [Test] + // https://github.com/xmlunit/xmlunit/issues/197 + public void ElementSelectorsAreQueriedInSequenceWithConditionalSelector() { + XmlElement control1 = doc.CreateElement("a"); + control1.AppendChild(doc.CreateTextNode("foo")); + XmlElement control2 = doc.CreateElement("a"); + control2.AppendChild(doc.CreateTextNode("bar")); + + XmlElement test1 = doc.CreateElement("a"); + test1.AppendChild(doc.CreateTextNode("baz")); + XmlElement test2 = doc.CreateElement("a"); + test2.AppendChild(doc.CreateTextNode("foo")); + + DefaultNodeMatcher m = + new DefaultNodeMatcher(ElementSelectors.SelectorForElementNamed("a", ElementSelectors.ByNameAndText), + ElementSelectors.ByName); + List> result = + m.Match(new XmlNode[] { control1, control2}, + new XmlNode[] { test1, test2 }).ToList(); + Assert.AreEqual(result.Count, 2); + + // ByNameAndText + Assert.AreSame(result[0].Key, control1); + Assert.AreSame(result[0].Value, test2); + + // ByName + Assert.AreSame(result[1].Key, control2); + Assert.AreSame(result[1].Value, test1); + } + + [Test] + public void ElementSelectorsAreQueriedInSequenceWithControlNodesSwapped() { + XmlElement control1 = doc.CreateElement("a"); + control1.AppendChild(doc.CreateTextNode("bar")); + XmlElement control2 = doc.CreateElement("a"); + control2.AppendChild(doc.CreateTextNode("foo")); + + XmlElement test1 = doc.CreateElement("a"); + test1.AppendChild(doc.CreateTextNode("foo")); + XmlElement test2 = doc.CreateElement("a"); + test2.AppendChild(doc.CreateTextNode("baz")); + + DefaultNodeMatcher m = + new DefaultNodeMatcher(ElementSelectors.ByNameAndText, + ElementSelectors.ByName); + List> result = + m.Match(new XmlNode[] { control1, control2}, + new XmlNode[] { test1, test2 }).ToList(); + Assert.AreEqual(result.Count, 2); + + // ByNameAndText + Assert.AreSame(result[0].Key, control2); + Assert.AreSame(result[0].Value, test1); + + // ByName + Assert.AreSame(result[1].Key, control1); + Assert.AreSame(result[1].Value, test2); + } + } +} diff --git a/src/tests/net-core/NetFramework/XMLUnit.Core.Tests.NetFramework.csproj b/src/tests/net-core/NetFramework/XMLUnit.Core.Tests.NetFramework.csproj index b184305..d05bed1 100644 --- a/src/tests/net-core/NetFramework/XMLUnit.Core.Tests.NetFramework.csproj +++ b/src/tests/net-core/NetFramework/XMLUnit.Core.Tests.NetFramework.csproj @@ -74,6 +74,7 @@ +