a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
384 lines
15 KiB
C#
384 lines
15 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
|
|
|
|
//#define PARSER_TRACE
|
|
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Web.Razor.Generator;
|
|
using System.Web.Razor.Parser;
|
|
using System.Web.Razor.Parser.SyntaxTree;
|
|
using System.Web.Razor.Text;
|
|
using Xunit;
|
|
|
|
namespace System.Web.Razor.Test.Framework
|
|
{
|
|
public abstract class ParserTestBase
|
|
{
|
|
protected static Block IgnoreOutput = new IgnoreOutputBlock();
|
|
|
|
public SpanFactory Factory { get; private set; }
|
|
|
|
protected ParserTestBase()
|
|
{
|
|
Factory = CreateSpanFactory();
|
|
}
|
|
|
|
public abstract ParserBase CreateMarkupParser();
|
|
public abstract ParserBase CreateCodeParser();
|
|
|
|
protected abstract ParserBase SelectActiveParser(ParserBase codeParser, ParserBase markupParser);
|
|
|
|
public virtual ParserContext CreateParserContext(ITextDocument input, ParserBase codeParser, ParserBase markupParser)
|
|
{
|
|
return new ParserContext(input, codeParser, markupParser, SelectActiveParser(codeParser, markupParser));
|
|
}
|
|
|
|
protected abstract SpanFactory CreateSpanFactory();
|
|
|
|
protected virtual void ParseBlockTest(string document)
|
|
{
|
|
ParseBlockTest(document, null, false, new RazorError[0]);
|
|
}
|
|
|
|
protected virtual void ParseBlockTest(string document, bool designTimeParser)
|
|
{
|
|
ParseBlockTest(document, null, designTimeParser, new RazorError[0]);
|
|
}
|
|
|
|
protected virtual void ParseBlockTest(string document, params RazorError[] expectedErrors)
|
|
{
|
|
ParseBlockTest(document, false, expectedErrors);
|
|
}
|
|
|
|
protected virtual void ParseBlockTest(string document, bool designTimeParser, params RazorError[] expectedErrors)
|
|
{
|
|
ParseBlockTest(document, null, designTimeParser, expectedErrors);
|
|
}
|
|
|
|
protected virtual void ParseBlockTest(string document, Block expectedRoot)
|
|
{
|
|
ParseBlockTest(document, expectedRoot, false, null);
|
|
}
|
|
|
|
protected virtual void ParseBlockTest(string document, Block expectedRoot, bool designTimeParser)
|
|
{
|
|
ParseBlockTest(document, expectedRoot, designTimeParser, null);
|
|
}
|
|
|
|
protected virtual void ParseBlockTest(string document, Block expectedRoot, params RazorError[] expectedErrors)
|
|
{
|
|
ParseBlockTest(document, expectedRoot, false, expectedErrors);
|
|
}
|
|
|
|
protected virtual void ParseBlockTest(string document, Block expectedRoot, bool designTimeParser, params RazorError[] expectedErrors)
|
|
{
|
|
RunParseTest(document, parser => parser.ParseBlock, expectedRoot, (expectedErrors ?? new RazorError[0]).ToList(), designTimeParser);
|
|
}
|
|
|
|
protected virtual void SingleSpanBlockTest(string document, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
|
|
{
|
|
SingleSpanBlockTest(document, blockType, spanType, acceptedCharacters, expectedError: null);
|
|
}
|
|
|
|
protected virtual void SingleSpanBlockTest(string document, string spanContent, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
|
|
{
|
|
SingleSpanBlockTest(document, spanContent, blockType, spanType, acceptedCharacters, expectedErrors: null);
|
|
}
|
|
|
|
protected virtual void SingleSpanBlockTest(string document, BlockType blockType, SpanKind spanType, params RazorError[] expectedError)
|
|
{
|
|
SingleSpanBlockTest(document, document, blockType, spanType, expectedError);
|
|
}
|
|
|
|
protected virtual void SingleSpanBlockTest(string document, string spanContent, BlockType blockType, SpanKind spanType, params RazorError[] expectedErrors)
|
|
{
|
|
SingleSpanBlockTest(document, spanContent, blockType, spanType, AcceptedCharacters.Any, expectedErrors ?? new RazorError[0]);
|
|
}
|
|
|
|
protected virtual void SingleSpanBlockTest(string document, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters, params RazorError[] expectedError)
|
|
{
|
|
SingleSpanBlockTest(document, document, blockType, spanType, acceptedCharacters, expectedError);
|
|
}
|
|
|
|
protected virtual void SingleSpanBlockTest(string document, string spanContent, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters, params RazorError[] expectedErrors)
|
|
{
|
|
BlockBuilder builder = new BlockBuilder();
|
|
builder.Type = blockType;
|
|
ParseBlockTest(
|
|
document,
|
|
ConfigureAndAddSpanToBlock(
|
|
builder,
|
|
Factory.Span(spanType, spanContent, spanType == SpanKind.Markup)
|
|
.Accepts(acceptedCharacters)),
|
|
expectedErrors ?? new RazorError[0]);
|
|
}
|
|
|
|
protected virtual ParserResults RunParse(string document, Func<ParserBase, Action> parserActionSelector, bool designTimeParser)
|
|
{
|
|
// Create the source
|
|
ParserResults results = null;
|
|
using (SeekableTextReader reader = new SeekableTextReader(document))
|
|
{
|
|
try
|
|
{
|
|
ParserBase codeParser = CreateCodeParser();
|
|
ParserBase markupParser = CreateMarkupParser();
|
|
ParserContext context = CreateParserContext(reader, codeParser, markupParser);
|
|
context.DesignTimeMode = designTimeParser;
|
|
|
|
codeParser.Context = context;
|
|
markupParser.Context = context;
|
|
|
|
// Run the parser
|
|
parserActionSelector(context.ActiveParser)();
|
|
results = context.CompleteParse();
|
|
}
|
|
finally
|
|
{
|
|
if (results != null && results.Document != null)
|
|
{
|
|
WriteTraceLine(String.Empty);
|
|
WriteTraceLine("Actual Parse Tree:");
|
|
WriteNode(0, results.Document);
|
|
}
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
protected virtual void RunParseTest(string document, Func<ParserBase, Action> parserActionSelector, Block expectedRoot, IList<RazorError> expectedErrors, bool designTimeParser)
|
|
{
|
|
// Create the source
|
|
ParserResults results = RunParse(document, parserActionSelector, designTimeParser);
|
|
|
|
// Evaluate the results
|
|
if (!ReferenceEquals(expectedRoot, IgnoreOutput))
|
|
{
|
|
EvaluateResults(results, expectedRoot, expectedErrors);
|
|
}
|
|
}
|
|
|
|
[Conditional("PARSER_TRACE")]
|
|
private void WriteNode(int indent, SyntaxTreeNode node)
|
|
{
|
|
string content = node.ToString().Replace("\r", "\\r")
|
|
.Replace("\n", "\\n")
|
|
.Replace("{", "{{")
|
|
.Replace("}", "}}");
|
|
if (indent > 0)
|
|
{
|
|
content = new String('.', indent * 2) + content;
|
|
}
|
|
WriteTraceLine(content);
|
|
Block block = node as Block;
|
|
if (block != null)
|
|
{
|
|
foreach (SyntaxTreeNode child in block.Children)
|
|
{
|
|
WriteNode(indent + 1, child);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void EvaluateResults(ParserResults results, Block expectedRoot)
|
|
{
|
|
EvaluateResults(results, expectedRoot, null);
|
|
}
|
|
|
|
public static void EvaluateResults(ParserResults results, Block expectedRoot, IList<RazorError> expectedErrors)
|
|
{
|
|
EvaluateParseTree(results.Document, expectedRoot);
|
|
EvaluateRazorErrors(results.ParserErrors, expectedErrors);
|
|
}
|
|
|
|
public static void EvaluateParseTree(Block actualRoot, Block expectedRoot)
|
|
{
|
|
// Evaluate the result
|
|
ErrorCollector collector = new ErrorCollector();
|
|
|
|
// Link all the nodes
|
|
expectedRoot.LinkNodes();
|
|
|
|
if (expectedRoot == null)
|
|
{
|
|
Assert.Null(actualRoot);
|
|
}
|
|
else
|
|
{
|
|
Assert.NotNull(actualRoot);
|
|
EvaluateSyntaxTreeNode(collector, actualRoot, expectedRoot);
|
|
if (collector.Success)
|
|
{
|
|
WriteTraceLine("Parse Tree Validation Succeeded:\r\n{0}", collector.Message);
|
|
}
|
|
else
|
|
{
|
|
Assert.True(false, String.Format("\r\n{0}", collector.Message));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void EvaluateSyntaxTreeNode(ErrorCollector collector, SyntaxTreeNode actual, SyntaxTreeNode expected)
|
|
{
|
|
if (actual == null)
|
|
{
|
|
AddNullActualError(collector, actual, expected);
|
|
}
|
|
|
|
if (actual.IsBlock != expected.IsBlock)
|
|
{
|
|
AddMismatchError(collector, actual, expected);
|
|
}
|
|
else
|
|
{
|
|
if (expected.IsBlock)
|
|
{
|
|
EvaluateBlock(collector, (Block)actual, (Block)expected);
|
|
}
|
|
else
|
|
{
|
|
EvaluateSpan(collector, (Span)actual, (Span)expected);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void EvaluateSpan(ErrorCollector collector, Span actual, Span expected)
|
|
{
|
|
if (!Equals(expected, actual))
|
|
{
|
|
AddMismatchError(collector, actual, expected);
|
|
}
|
|
else
|
|
{
|
|
AddPassedMessage(collector, expected);
|
|
}
|
|
}
|
|
|
|
private static void EvaluateBlock(ErrorCollector collector, Block actual, Block expected)
|
|
{
|
|
if (actual.Type != expected.Type || !expected.CodeGenerator.Equals(actual.CodeGenerator))
|
|
{
|
|
AddMismatchError(collector, actual, expected);
|
|
}
|
|
else
|
|
{
|
|
AddPassedMessage(collector, expected);
|
|
using (collector.Indent())
|
|
{
|
|
IEnumerator<SyntaxTreeNode> expectedNodes = expected.Children.GetEnumerator();
|
|
IEnumerator<SyntaxTreeNode> actualNodes = actual.Children.GetEnumerator();
|
|
while (expectedNodes.MoveNext())
|
|
{
|
|
if (!actualNodes.MoveNext())
|
|
{
|
|
collector.AddError("{0} - FAILED :: No more elements at this node", expectedNodes.Current);
|
|
}
|
|
else
|
|
{
|
|
EvaluateSyntaxTreeNode(collector, actualNodes.Current, expectedNodes.Current);
|
|
}
|
|
}
|
|
while (actualNodes.MoveNext())
|
|
{
|
|
collector.AddError("End of Node - FAILED :: Found Node: {0}", actualNodes.Current);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void AddPassedMessage(ErrorCollector collector, SyntaxTreeNode expected)
|
|
{
|
|
collector.AddMessage("{0} - PASSED", expected);
|
|
}
|
|
|
|
private static void AddMismatchError(ErrorCollector collector, SyntaxTreeNode actual, SyntaxTreeNode expected)
|
|
{
|
|
collector.AddError("{0} - FAILED :: Actual: {1}", expected, actual);
|
|
}
|
|
|
|
private static void AddNullActualError(ErrorCollector collector, SyntaxTreeNode actual, SyntaxTreeNode expected)
|
|
{
|
|
collector.AddError("{0} - FAILED :: Actual: << Null >>", expected);
|
|
}
|
|
|
|
public static void EvaluateRazorErrors(IList<RazorError> actualErrors, IList<RazorError> expectedErrors)
|
|
{
|
|
// Evaluate the errors
|
|
if (expectedErrors == null || expectedErrors.Count == 0)
|
|
{
|
|
Assert.True(actualErrors.Count == 0,
|
|
String.Format("Expected that no errors would be raised, but the following errors were:\r\n{0}", FormatErrors(actualErrors)));
|
|
}
|
|
else
|
|
{
|
|
Assert.True(expectedErrors.Count == actualErrors.Count,
|
|
String.Format("Expected that {0} errors would be raised, but {1} errors were.\r\nExpected Errors: \r\n{2}\r\nActual Errors: \r\n{3}",
|
|
expectedErrors.Count,
|
|
actualErrors.Count,
|
|
FormatErrors(expectedErrors),
|
|
FormatErrors(actualErrors)));
|
|
Assert.Equal(expectedErrors.ToArray(), actualErrors.ToArray());
|
|
}
|
|
WriteTraceLine("Expected Errors were raised:\r\n{0}", FormatErrors(expectedErrors));
|
|
}
|
|
|
|
public static string FormatErrors(IList<RazorError> errors)
|
|
{
|
|
if (errors == null)
|
|
{
|
|
return "\t<< No Errors >>";
|
|
}
|
|
|
|
StringBuilder builder = new StringBuilder();
|
|
foreach (RazorError err in errors)
|
|
{
|
|
builder.AppendFormat("\t{0}", err);
|
|
builder.AppendLine();
|
|
}
|
|
return builder.ToString();
|
|
}
|
|
|
|
[Conditional("PARSER_TRACE")]
|
|
private static void WriteTraceLine(string format, params object[] args)
|
|
{
|
|
Trace.WriteLine(String.Format(format, args));
|
|
}
|
|
|
|
protected virtual Block CreateSimpleBlockAndSpan(string spanContent, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
|
|
{
|
|
SpanConstructor span = Factory.Span(spanType, spanContent, spanType == SpanKind.Markup).Accepts(acceptedCharacters);
|
|
BlockBuilder b = new BlockBuilder()
|
|
{
|
|
Type = blockType
|
|
};
|
|
return ConfigureAndAddSpanToBlock(b, span);
|
|
}
|
|
|
|
protected virtual Block ConfigureAndAddSpanToBlock(BlockBuilder block, SpanConstructor span)
|
|
{
|
|
switch (block.Type)
|
|
{
|
|
case BlockType.Markup:
|
|
span.With(new MarkupCodeGenerator());
|
|
break;
|
|
case BlockType.Statement:
|
|
span.With(new StatementCodeGenerator());
|
|
break;
|
|
case BlockType.Expression:
|
|
block.CodeGenerator = new ExpressionCodeGenerator();
|
|
span.With(new ExpressionCodeGenerator());
|
|
break;
|
|
}
|
|
block.Children.Add(span);
|
|
return block.Build();
|
|
}
|
|
|
|
private class IgnoreOutputBlock : Block
|
|
{
|
|
public IgnoreOutputBlock() : base(BlockType.Template, Enumerable.Empty<SyntaxTreeNode>(), null) { }
|
|
}
|
|
}
|
|
}
|