src/Bicep.Core.IntegrationTests/LexerTests.cs (123 lines of code) (raw):

// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Diagnostics.CodeAnalysis; using System.Text; using Bicep.Core.Diagnostics; using Bicep.Core.Extensions; using Bicep.Core.Parsing; using Bicep.Core.Samples; using Bicep.Core.Syntax; using Bicep.Core.Text; using Bicep.Core.UnitTests.Assertions; using Bicep.Core.UnitTests.Utils; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Bicep.Core.IntegrationTests { [TestClass] public class LexerTests { [NotNull] public TestContext? TestContext { get; set; } [DataTestMethod] [DynamicData(nameof(GetData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] public void LexerShouldRoundtrip(DataSet dataSet) { var lexer = new Lexer(new SlidingTextWindow(dataSet.Bicep), ToListDiagnosticWriter.Create()); lexer.Lex(); var serialized = new StringBuilder(); new TokenWriter(serialized).WriteTokens(lexer.GetTokens()); serialized.ToString().Should().Be(dataSet.Bicep, "because the lexer should not lose information"); } [DataTestMethod] [DynamicData(nameof(GetData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] public void LexerShouldProduceValidTokenLocations(DataSet dataSet) { var lexer = new Lexer(new SlidingTextWindow(dataSet.Bicep), ToListDiagnosticWriter.Create()); lexer.Lex(); foreach (Token token in lexer.GetTokens()) { // lookup the text of the token in original contents by token's span string tokenText = dataSet.Bicep[new Range(token.Span.Position, token.GetEndPosition())]; tokenText.Should().Be(token.Text, "because token text at location should match original contents at the same location"); } } [DataTestMethod] [DynamicData(nameof(GetData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] public void LexerShouldProduceContiguousSpans(DataSet dataSet) { var lexer = new Lexer(new SlidingTextWindow(dataSet.Bicep), ToListDiagnosticWriter.Create()); lexer.Lex(); int visitedPosition = 0; // local function void VisitSpan(TextSpan span, string text) { // the token should begin at the position where the previous token ended span.Position.Should().Be(visitedPosition, $"because token or trivia '{text}' at span '{span}' should begin at position {visitedPosition}."); // cover the span of the token visitedPosition += span.Length; } // local function void VisitTrivia(IEnumerable<SyntaxTrivia> trivia) { foreach (var trivium in trivia) { VisitSpan(trivium.Span, trivium.Text); } } var tokens = lexer.GetTokens(); foreach (var token in tokens) { VisitTrivia(token.LeadingTrivia); VisitSpan(token.Span, token.Text); VisitTrivia(token.TrailingTrivia); } // when we're done visited position should match the length of the file visitedPosition.Should().Be(dataSet.Bicep.Length); } [DataTestMethod] [DynamicData(nameof(GetData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] [TestCategory(BaselineHelper.BaselineTestCategory)] public void LexerShouldProduceExpectedTokens(DataSet dataSet) { var lexer = new Lexer(new SlidingTextWindow(dataSet.Bicep), ToListDiagnosticWriter.Create()); lexer.Lex(); string getLoggingString(Token token) { return $"{token.Type} |{token.Text}|"; } var sourceTextWithDiags = DataSet.AddDiagsToSourceText(dataSet, lexer.GetTokens(), getLoggingString); var resultsFile = FileHelper.SaveResultFile(this.TestContext, Path.Combine(dataSet.Name, DataSet.TestFileMainTokens), sourceTextWithDiags); sourceTextWithDiags.Should().EqualWithLineByLineDiffOutput( TestContext, dataSet.Tokens, expectedPath: DataSet.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainTokens), actualPath: resultsFile); lexer.GetTokens().Count(token => token.Type == TokenType.EndOfFile).Should().Be(1, "because there should only be 1 EOF token"); lexer.GetTokens().Last().Type.Should().Be(TokenType.EndOfFile, "because the last token should always be EOF."); } [DataTestMethod] [BaselineData_Bicepparam.TestData()] [TestCategory(BaselineHelper.BaselineTestCategory)] public void ParamsFile_LexerShouldProduceExpectedTokens(BaselineData_Bicepparam baselineData) { var data = baselineData.GetData(TestContext); var lexer = new Lexer(new SlidingTextWindow(data.Parameters.EmbeddedFile.Contents), ToListDiagnosticWriter.Create()); lexer.Lex(); string getLoggingString(Token token) { return $"{token.Type} |{token.Text}|"; } var sourceTextWithDiags = OutputHelper.AddDiagsToSourceText(data.Parameters.EmbeddedFile.Contents, "\n", lexer.GetTokens(), getLoggingString); data.Tokens.WriteToOutputFolder(sourceTextWithDiags); data.Tokens.ShouldHaveExpectedValue(); lexer.GetTokens().Count(token => token.Type == TokenType.EndOfFile).Should().Be(1, "because there should only be 1 EOF token"); lexer.GetTokens().Last().Type.Should().Be(TokenType.EndOfFile, "because the last token should always be EOF."); } [DataTestMethod] [DynamicData(nameof(GetValidData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] public void LexerShouldProduceValidStringLiteralTokensOnValidFiles(DataSet dataSet) { var lexer = new Lexer(new SlidingTextWindow(dataSet.Bicep), ToListDiagnosticWriter.Create()); lexer.Lex(); foreach (Token stringToken in lexer.GetTokens().Where(token => token.Type == TokenType.StringComplete)) { Lexer.TryGetStringValue(stringToken).Should().NotBeNull($"because string token at span {stringToken.Span} should have a string value. Token Text = {stringToken.Text}"); } } private static IEnumerable<object[]> GetData() => DataSets.AllDataSets.ToDynamicTestData(); private static IEnumerable<object[]> GetValidData() => DataSets.AllDataSets.Where(ds => ds.IsValid).ToDynamicTestData(); } }