From 06bf2f4c2fe9da62bd081a1b8c09e08e6715c3d5 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Mon, 20 Feb 2012 19:55:21 +0100 Subject: [PATCH] Add consistency check for incremental tag soup parser --- ...arpCode.NRefactory.ConsistencyCheck.csproj | 10 ++ .../Program.cs | 2 + .../Xml/TagSoupIncrementalTests.cs | 152 ++++++++++++++++++ .../Xml/TestTextSource.cs | 107 ++++++++++++ .../Xml/TestTextSourceVersion.cs | 65 ++++++++ ICSharpCode.NRefactory.Xml/AXmlAttribute.cs | 7 + ICSharpCode.NRefactory.Xml/AXmlTag.cs | 7 + ICSharpCode.NRefactory.Xml/AXmlText.cs | 12 ++ ICSharpCode.NRefactory.Xml/TagReader.cs | 2 +- 9 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 ICSharpCode.NRefactory.ConsistencyCheck/Xml/TagSoupIncrementalTests.cs create mode 100644 ICSharpCode.NRefactory.ConsistencyCheck/Xml/TestTextSource.cs create mode 100644 ICSharpCode.NRefactory.ConsistencyCheck/Xml/TestTextSourceVersion.cs diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/ICSharpCode.NRefactory.ConsistencyCheck.csproj b/ICSharpCode.NRefactory.ConsistencyCheck/ICSharpCode.NRefactory.ConsistencyCheck.csproj index 7e7857d491..405ddae763 100644 --- a/ICSharpCode.NRefactory.ConsistencyCheck/ICSharpCode.NRefactory.ConsistencyCheck.csproj +++ b/ICSharpCode.NRefactory.ConsistencyCheck/ICSharpCode.NRefactory.ConsistencyCheck.csproj @@ -66,6 +66,9 @@ + + + @@ -76,10 +79,17 @@ {53DCA265-3C3C-42F9-B647-F72BA678122B} ICSharpCode.NRefactory.CSharp + + {DC393B66-92ED-4CAD-AB25-CFEF23F3D7C6} + ICSharpCode.NRefactory.Xml + {3B2A5653-EC97-4001-BB9B-D90F1AF2C371} ICSharpCode.NRefactory + + + \ No newline at end of file diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/Program.cs b/ICSharpCode.NRefactory.ConsistencyCheck/Program.cs index cbacb31705..18a697d2cf 100644 --- a/ICSharpCode.NRefactory.ConsistencyCheck/Program.cs +++ b/ICSharpCode.NRefactory.ConsistencyCheck/Program.cs @@ -46,6 +46,8 @@ namespace ICSharpCode.NRefactory.ConsistencyCheck public static void Main(string[] args) { + TagSoupIncrementalTests.Run("c:\\temp\\ClosedXML.xml"); + using (new Timer("Loading solution... ")) { solution = new Solution(SolutionFile); } diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/Xml/TagSoupIncrementalTests.cs b/ICSharpCode.NRefactory.ConsistencyCheck/Xml/TagSoupIncrementalTests.cs new file mode 100644 index 0000000000..82d9e29573 --- /dev/null +++ b/ICSharpCode.NRefactory.ConsistencyCheck/Xml/TagSoupIncrementalTests.cs @@ -0,0 +1,152 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using ICSharpCode.NRefactory.ConsistencyCheck.Xml; +using ICSharpCode.NRefactory.Editor; +using ICSharpCode.NRefactory.Xml; + +namespace ICSharpCode.NRefactory.ConsistencyCheck +{ + /// + /// Tests incremental tag soup parser. + /// + public class TagSoupIncrementalTests + { + static Random sharedRnd = new Random(); + + public static void Run(string fileName) + { + Run(new StringTextSource(File.ReadAllText(fileName))); + } + + public static void Run(ITextSource originalXmlFile) + { + int seed; + lock (sharedRnd) { + seed = sharedRnd.Next(); + } + Random rnd = new Random(seed); + + TagSoupParser parser = new TagSoupParser(); + StringBuilder b = new StringBuilder(originalXmlFile.Text); + IncrementalParserState parserState = null; + TestTextSourceVersion version = new TestTextSourceVersion(); + for (int iteration = 0; iteration < 100; iteration++) { + var textSource = new TextSourceWithVersion(new StringTextSource(b.ToString()), version); + var incrementalResult = parser.ParseIncremental(parserState, textSource, out parserState); + var nonIncrementalResult = parser.Parse(textSource); + CompareResults(incrementalResult, nonIncrementalResult); + // Randomly mutate the file: + + List changes = new List(); + int modifications = rnd.Next(0, 10); + for (int i = 0; i < modifications; i++) { + int offset = rnd.Next(0, b.Length); + int originalOffset = rnd.Next(0, originalXmlFile.TextLength); + int insertionLength; + int removalLength; + switch (rnd.Next(0, 21) / 20) { + case 0: + removalLength = 0; + insertionLength = rnd.Next(0, Math.Min(50, originalXmlFile.TextLength - originalOffset)); + break; + case 1: + removalLength = rnd.Next(0, Math.Min(10, b.Length - offset)); + insertionLength = rnd.Next(0, Math.Min(20, originalXmlFile.TextLength - originalOffset)); + break; + default: + removalLength = rnd.Next(0, b.Length - offset); + insertionLength = rnd.Next(0, originalXmlFile.TextLength - originalOffset); + break; + } + string removedText = b.ToString(offset, removalLength); + b.Remove(offset, removalLength); + string insertedText = originalXmlFile.GetText(originalOffset, insertionLength); + b.Insert(offset, insertedText); + changes.Add(new TextChangeEventArgs(offset, removedText, insertedText)); + } + version = new TestTextSourceVersion(version, changes); + } + } + + static void CompareResults(IList result1, IList result2) + { + if (result1.Count != result2.Count) + throw new InvalidOperationException(); + for (int i = 0; i < result1.Count; i++) { + CompareResults(result1[i], result2[i]); + } + } + + static void CompareResults(AXmlObject obj1, AXmlObject obj2) + { + if (obj1.GetType() != obj2.GetType()) + throw new InvalidOperationException(); + if (obj1.StartOffset != obj2.StartOffset) + throw new InvalidOperationException(); + if (obj1.EndOffset != obj2.EndOffset) + throw new InvalidOperationException(); + + if (obj1.MySyntaxErrors.Count() != obj2.MySyntaxErrors.Count()) + throw new InvalidOperationException(); + foreach (var pair in obj1.MySyntaxErrors.Zip(obj2.MySyntaxErrors, (a,b) => new { a, b })) { + if (pair.a.StartOffset != pair.b.StartOffset) + throw new InvalidOperationException(); + if (pair.a.EndOffset != pair.b.EndOffset) + throw new InvalidOperationException(); + if (pair.a.Description != pair.b.Description) + throw new InvalidOperationException(); + } + + if (obj1 is AXmlText) { + var a = (AXmlText)obj1; + var b = (AXmlText)obj2; + if (a.ContainsOnlyWhitespace != b.ContainsOnlyWhitespace) + throw new InvalidOperationException(); + if (a.Value != b.Value) + throw new InvalidOperationException(); + } else if (obj1 is AXmlTag) { + var a = (AXmlTag)obj1; + var b = (AXmlTag)obj2; + if (a.OpeningBracket != b.OpeningBracket) + throw new InvalidOperationException(); + if (a.ClosingBracket != b.ClosingBracket) + throw new InvalidOperationException(); + if (a.Name != b.Name) + throw new InvalidOperationException(); + } else if (obj1 is AXmlAttribute) { + var a = (AXmlAttribute)obj1; + var b = (AXmlAttribute)obj2; + if (a.Name != b.Name) + throw new InvalidOperationException(); + if (a.Value != b.Value) + throw new InvalidOperationException(); + } else { + throw new NotSupportedException(); + } + + CompareResults(obj1.Children, obj2.Children); + } + } +} diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/Xml/TestTextSource.cs b/ICSharpCode.NRefactory.ConsistencyCheck/Xml/TestTextSource.cs new file mode 100644 index 0000000000..33cdac695a --- /dev/null +++ b/ICSharpCode.NRefactory.ConsistencyCheck/Xml/TestTextSource.cs @@ -0,0 +1,107 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using ICSharpCode.NRefactory.Editor; + +namespace ICSharpCode.NRefactory.ConsistencyCheck.Xml +{ + public class TextSourceWithVersion : ITextSource + { + readonly ITextSource textSource; + readonly ITextSourceVersion version; + + public TextSourceWithVersion(ITextSource textSource, ITextSourceVersion version) + { + this.textSource = textSource; + this.version = version; + } + + ITextSourceVersion ITextSource.Version { + get { return version; } + } + + int ITextSource.TextLength { + get { return textSource.TextLength; } + } + + string ITextSource.Text { + get { return textSource.Text; } + } + + ITextSource ITextSource.CreateSnapshot() + { + throw new NotImplementedException(); + } + + ITextSource ITextSource.CreateSnapshot(int offset, int length) + { + throw new NotImplementedException(); + } + + System.IO.TextReader ITextSource.CreateReader() + { + throw new NotImplementedException(); + } + + System.IO.TextReader ITextSource.CreateReader(int offset, int length) + { + throw new NotImplementedException(); + } + + char ITextSource.GetCharAt(int offset) + { + return textSource.GetCharAt(offset); + } + + string ITextSource.GetText(int offset, int length) + { + return textSource.GetText(offset, length); + } + + string ITextSource.GetText(ISegment segment) + { + return textSource.GetText(segment); + } + + int ITextSource.IndexOf(char c, int startIndex, int count) + { + return textSource.IndexOf(c, startIndex, count); + } + + int ITextSource.IndexOfAny(char[] anyOf, int startIndex, int count) + { + return textSource.IndexOfAny(anyOf, startIndex, count); + } + + int ITextSource.IndexOf(string searchText, int startIndex, int count, StringComparison comparisonType) + { + return textSource.IndexOf(searchText, startIndex, count, comparisonType); + } + + int ITextSource.LastIndexOf(char c, int startIndex, int count) + { + return textSource.LastIndexOf(c, startIndex, count); + } + + int ITextSource.LastIndexOf(string searchText, int startIndex, int count, StringComparison comparisonType) + { + return textSource.LastIndexOf(searchText, startIndex, count, comparisonType); + } + } +} diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/Xml/TestTextSourceVersion.cs b/ICSharpCode.NRefactory.ConsistencyCheck/Xml/TestTextSourceVersion.cs new file mode 100644 index 0000000000..be0ce43667 --- /dev/null +++ b/ICSharpCode.NRefactory.ConsistencyCheck/Xml/TestTextSourceVersion.cs @@ -0,0 +1,65 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using ICSharpCode.NRefactory.Editor; + +namespace ICSharpCode.NRefactory.ConsistencyCheck.Xml +{ + public class TestTextSourceVersion : ITextSourceVersion + { + readonly TestTextSourceVersion baseVersion; + readonly IEnumerable changesFromBaseVersionToThis; + + public TestTextSourceVersion() + { + } + + public TestTextSourceVersion(TestTextSourceVersion baseVersion, IEnumerable changesFromBaseVersionToThis) + { + this.baseVersion = baseVersion; + this.changesFromBaseVersionToThis = changesFromBaseVersionToThis; + } + + public bool BelongsToSameDocumentAs(ITextSourceVersion other) + { + TestTextSourceVersion o = (TestTextSourceVersion)other; + return o == this || o == baseVersion || o.baseVersion == this; + } + + public int CompareAge(ITextSourceVersion other) + { + throw new NotImplementedException(); + } + + public IEnumerable GetChangesTo(ITextSourceVersion other) + { + TestTextSourceVersion o = (TestTextSourceVersion)other; + if (o.baseVersion == this) + return o.changesFromBaseVersionToThis; + else + throw new NotImplementedException(); + } + + public int MoveOffsetTo(ITextSourceVersion other, int oldOffset, AnchorMovementType movement) + { + throw new NotImplementedException(); + } + } +} diff --git a/ICSharpCode.NRefactory.Xml/AXmlAttribute.cs b/ICSharpCode.NRefactory.Xml/AXmlAttribute.cs index a7adbeae73..ae24bcf0db 100644 --- a/ICSharpCode.NRefactory.Xml/AXmlAttribute.cs +++ b/ICSharpCode.NRefactory.Xml/AXmlAttribute.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Globalization; using ICSharpCode.NRefactory.Editor; namespace ICSharpCode.NRefactory.Xml @@ -57,5 +58,11 @@ namespace ICSharpCode.NRefactory.Xml { visitor.VisitAttribute(this); } + + /// + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "[{0} '{1}={2}']", base.ToString(), this.Name, this.Value); + } } } diff --git a/ICSharpCode.NRefactory.Xml/AXmlTag.cs b/ICSharpCode.NRefactory.Xml/AXmlTag.cs index 3584053d1b..adca935c4b 100644 --- a/ICSharpCode.NRefactory.Xml/AXmlTag.cs +++ b/ICSharpCode.NRefactory.Xml/AXmlTag.cs @@ -18,6 +18,7 @@ using System; using System.Collections.ObjectModel; +using System.Globalization; using ICSharpCode.NRefactory.Editor; namespace ICSharpCode.NRefactory.Xml @@ -87,5 +88,11 @@ namespace ICSharpCode.NRefactory.Xml { visitor.VisitTag(this); } + + /// + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "[{0} '{1}{2}{3}' Attr:{4}]", base.ToString(), this.OpeningBracket, this.Name, this.ClosingBracket, this.Children.Count); + } } } diff --git a/ICSharpCode.NRefactory.Xml/AXmlText.cs b/ICSharpCode.NRefactory.Xml/AXmlText.cs index fe50ec48a7..f07895a5b6 100644 --- a/ICSharpCode.NRefactory.Xml/AXmlText.cs +++ b/ICSharpCode.NRefactory.Xml/AXmlText.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Globalization; namespace ICSharpCode.NRefactory.Xml { @@ -30,6 +31,11 @@ namespace ICSharpCode.NRefactory.Xml { } +// /// The type of the text node +// public TextType Type { +// get { return ((InternalText)internalObject).Type; } +// } + /// The text with all entity references resloved public string Value { get { return ((InternalText)internalObject).Value; } @@ -47,5 +53,11 @@ namespace ICSharpCode.NRefactory.Xml { visitor.VisitText(this); } + + /// + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "[{0} Text.Length={1}]", base.ToString(), this.Value.Length); + } } } diff --git a/ICSharpCode.NRefactory.Xml/TagReader.cs b/ICSharpCode.NRefactory.Xml/TagReader.cs index c31d2e6288..bbde517124 100644 --- a/ICSharpCode.NRefactory.Xml/TagReader.cs +++ b/ICSharpCode.NRefactory.Xml/TagReader.cs @@ -220,7 +220,7 @@ namespace ICSharpCode.NRefactory.Xml HashSet attributeNames = new HashSet(); foreach (var obj in tag.NestedObjects) { InternalAttribute attr = obj as InternalAttribute; - if (attr != null && attributeNames.Add(attr.Name)) { + if (attr != null && !attributeNames.Add(attr.Name)) { int attrStart = tagStart + attr.StartRelativeToParent; OnSyntaxError(attrStart, attrStart + attr.Name.Length, "Attribute with name '{0}' already exists", attr.Name); }