From 871878dd2519c92721122e31e55f2271ea6d7294 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 14 Apr 2020 21:53:57 -0700 Subject: [PATCH] Use node comparison for generate and init comparison (#361) --- .../ConditionalFactAttribute.cs | 6 +- .../ConditionalFactDiscoverer.cs | 6 +- .../Infrastructure/EqualityYamlNodeVisitor.cs | 115 ++++++++++++++++++ test/E2ETest/Infrastructure/ITestCondition.cs | 6 +- .../E2ETest/Infrastructure/SkippedTestCase.cs | 6 +- .../Infrastructure/StringExtensions.cs | 12 -- .../Infrastructure/TestMethodExtensions.cs | 6 +- test/E2ETest/Infrastructure/YamlAssert.cs | 45 +++++++ test/E2ETest/TyeGenerateTests.cs | 18 +-- test/E2ETest/TyeInitTests.cs | 16 +-- 10 files changed, 200 insertions(+), 36 deletions(-) create mode 100644 test/E2ETest/Infrastructure/EqualityYamlNodeVisitor.cs delete mode 100644 test/E2ETest/Infrastructure/StringExtensions.cs create mode 100644 test/E2ETest/Infrastructure/YamlAssert.cs diff --git a/test/E2ETest/Infrastructure/ConditionalFactAttribute.cs b/test/E2ETest/Infrastructure/ConditionalFactAttribute.cs index 6047c821..b7b69327 100644 --- a/test/E2ETest/Infrastructure/ConditionalFactAttribute.cs +++ b/test/E2ETest/Infrastructure/ConditionalFactAttribute.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using Xunit; using Xunit.Sdk; diff --git a/test/E2ETest/Infrastructure/ConditionalFactDiscoverer.cs b/test/E2ETest/Infrastructure/ConditionalFactDiscoverer.cs index 023cf5f2..c2e614b5 100644 --- a/test/E2ETest/Infrastructure/ConditionalFactDiscoverer.cs +++ b/test/E2ETest/Infrastructure/ConditionalFactDiscoverer.cs @@ -1,4 +1,8 @@ -using Xunit.Abstractions; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit.Abstractions; using Xunit.Sdk; // Do not change this namespace without changing the usage in ConditionalFactAttribute diff --git a/test/E2ETest/Infrastructure/EqualityYamlNodeVisitor.cs b/test/E2ETest/Infrastructure/EqualityYamlNodeVisitor.cs new file mode 100644 index 00000000..02b6a1c1 --- /dev/null +++ b/test/E2ETest/Infrastructure/EqualityYamlNodeVisitor.cs @@ -0,0 +1,115 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.CommandLine; +using Tye.Serialization; +using YamlDotNet.RepresentationModel; + +namespace E2ETest +{ + public class EqualityYamlNodeVisitor + { + public EqualityYamlNodeVisitor() + { + } + + public void Visit(YamlNode node, YamlNode otherNode) + { + if (node == null) + { + throw new ArgumentNullException(nameof(node)); + } + + if (otherNode == null) + { + throw new ArgumentNullException(nameof(otherNode)); + } + + VisitInternal(node, otherNode); + } + + private void VisitInternal(YamlNode node, YamlNode otherNode) + { + if (node.NodeType != otherNode.NodeType) + { + throw new TyeYamlException($"Node types differ, Expected: {node.NodeType} at ({node.Start.Line}, {node.Start.Column}). " + + $"Actual: {node.NodeType} at ({otherNode.Start.Line}, {otherNode.Start.Column})."); + } + + if (node.Tag?.Equals(otherNode.Tag) == false) + { + throw new TyeYamlException($"Expected tags to be equal. " + + $"Expected: ({node.Start.Line}, {node.Start.Column}). Actual: ({otherNode.Start.Line}, {otherNode.Start.Column})."); + } + + if (node.NodeType == YamlNodeType.Mapping) + { + VisitMapping((YamlMappingNode)node, (YamlMappingNode)otherNode); + } + else if (node.NodeType == YamlNodeType.Scalar) + { + VisitScalar((YamlScalarNode)node, (YamlScalarNode)otherNode); + } + else if (node.NodeType == YamlNodeType.Sequence) + { + VisitSequence((YamlSequenceNode)node, (YamlSequenceNode)otherNode); + } + } + + public void VisitSequence(YamlSequenceNode node, YamlSequenceNode otherNode) + { + if (node.Children.Count > otherNode.Children.Count) + { + throw new TyeYamlException($"Extra children in expected yaml sequence, Expected: ({node.Start.Line}, {node.Start.Column}). Actual: ({otherNode.Start.Line}, {otherNode.Start.Column})"); + } + + for (var i = 0; i < node.Children.Count; ++i) + { + var childNode = node.Children[i]; + var otherChildNode = otherNode.Children[i]; + + VisitInternal(childNode, otherChildNode); + } + + if (node.Children.Count < otherNode.Children.Count) + { + throw new TyeYamlException($"Extra children in actual yaml sequence, Expected: ({node.Start.Line}, {node.Start.Column}). Actual: ({otherNode.Start.Line}, {otherNode.Start.Column})."); + } + } + + public void VisitMapping(YamlMappingNode node, YamlMappingNode otherNode) + { + foreach (var child in node.Children) + { + var childValue = child.Value; + + if (!otherNode.Children.TryGetValue(child.Key, out var otherChildValue)) + { + throw new TyeYamlException($"YamlMapping missing node, difference starting at Expected: ({node.Start.Line}, {node.Start.Column}). " + + $"Actual: ({otherNode.Start.Line}, {otherNode.Start.Column})."); + } + + VisitInternal(childValue, otherChildValue); + } + + if (node.Children.Count < otherNode.Children.Count) + { + throw new TyeYamlException($"Extra children in actual yaml mapping, Expected: ({node.Start.Line}, {node.Start.Column}). Actual: ({otherNode.Start.Line}, {otherNode.Start.Column})."); + } + } + + public void VisitScalar(YamlScalarNode node, YamlScalarNode otherNode) + { + if (!node.Equals(otherNode)) + { + throw new TyeYamlException($"Scalar nodes have different values." + Environment.NewLine + + $"Expected:" + Environment.NewLine + + $"{node.Value} at ({node.Start.Line}, {node.Start.Column}). " + Environment.NewLine + + $"Actual:" + Environment.NewLine + + $"{otherNode.Value} at ({otherNode.Start.Line}, {otherNode.Start.Column})."); + } + } + } +} diff --git a/test/E2ETest/Infrastructure/ITestCondition.cs b/test/E2ETest/Infrastructure/ITestCondition.cs index 17c13e67..1656f49b 100644 --- a/test/E2ETest/Infrastructure/ITestCondition.cs +++ b/test/E2ETest/Infrastructure/ITestCondition.cs @@ -1,4 +1,8 @@ -namespace E2ETest +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace E2ETest { public interface ITestCondition { diff --git a/test/E2ETest/Infrastructure/SkippedTestCase.cs b/test/E2ETest/Infrastructure/SkippedTestCase.cs index e9d4315d..07edbbd7 100644 --- a/test/E2ETest/Infrastructure/SkippedTestCase.cs +++ b/test/E2ETest/Infrastructure/SkippedTestCase.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using Xunit.Abstractions; using Xunit.Sdk; diff --git a/test/E2ETest/Infrastructure/StringExtensions.cs b/test/E2ETest/Infrastructure/StringExtensions.cs deleted file mode 100644 index bed0e4ce..00000000 --- a/test/E2ETest/Infrastructure/StringExtensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace System -{ - internal static class StringExtensions - { - public static string NormalizeNewLines(this string value) - { - return value - .Replace("\r\n", "\n") - .Replace("\n", Environment.NewLine); - } - } -} diff --git a/test/E2ETest/Infrastructure/TestMethodExtensions.cs b/test/E2ETest/Infrastructure/TestMethodExtensions.cs index e36bbdce..8ad5c860 100644 --- a/test/E2ETest/Infrastructure/TestMethodExtensions.cs +++ b/test/E2ETest/Infrastructure/TestMethodExtensions.cs @@ -1,4 +1,8 @@ -using System.Linq; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; using Xunit.Abstractions; using Xunit.Sdk; diff --git a/test/E2ETest/Infrastructure/YamlAssert.cs b/test/E2ETest/Infrastructure/YamlAssert.cs new file mode 100644 index 00000000..d72859a7 --- /dev/null +++ b/test/E2ETest/Infrastructure/YamlAssert.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Xunit; +using Xunit.Abstractions; +using YamlDotNet.RepresentationModel; + +namespace E2ETest +{ + public static class YamlAssert + { + public static void Equals(string expected, string actual, ITestOutputHelper output = null!) + { + var yamlStream = new YamlStream(); + using var reader = new StringReader(expected); + yamlStream.Load(reader); + + var otherYamlStream = new YamlStream(); + using var otherReader = new StringReader(expected); + otherYamlStream.Load(new StringReader(actual)); + + var yamlEqualityVisitor = new EqualityYamlNodeVisitor(); + + try + { + Assert.Equal(yamlStream.Documents.Count, yamlStream.Documents.Count); + + for (var i = 0; i < yamlStream.Documents.Count; i++) + { + yamlEqualityVisitor.Visit(yamlStream.Documents[i].RootNode, otherYamlStream.Documents[i].RootNode); + } + } + catch (Exception) + { + output?.WriteLine("Expected:"); + output?.WriteLine(expected); + output?.WriteLine("Actual:"); + output?.WriteLine(actual); + + throw; + } + } + } +} diff --git a/test/E2ETest/TyeGenerateTests.cs b/test/E2ETest/TyeGenerateTests.cs index 98d25575..f54b915d 100644 --- a/test/E2ETest/TyeGenerateTests.cs +++ b/test/E2ETest/TyeGenerateTests.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.Tye; using Xunit; using Xunit.Abstractions; +using YamlDotNet.RepresentationModel; using static E2ETest.TestHelpers; namespace E2ETest @@ -50,7 +51,8 @@ namespace E2ETest var content = await File.ReadAllTextAsync(Path.Combine(projectDirectory.DirectoryPath, $"{projectName}-generate-{environment}.yaml")); var expectedContent = await File.ReadAllTextAsync($"testassets/generate/{projectName}.yaml"); - Assert.Equal(expectedContent.NormalizeNewLines(), content.NormalizeNewLines()); + YamlAssert.Equals(expectedContent, content, output); + await DockerAssert.AssertImageExistsAsync(output, "test/test-project"); } finally @@ -87,7 +89,7 @@ namespace E2ETest var content = await File.ReadAllTextAsync(Path.Combine(projectDirectory.DirectoryPath, $"{projectName}-generate-{environment}.yaml")); var expectedContent = await File.ReadAllTextAsync($"testassets/generate/{projectName}.yaml"); - Assert.Equal(expectedContent.NormalizeNewLines(), content.NormalizeNewLines()); + YamlAssert.Equals(expectedContent, content, output); await DockerAssert.AssertImageExistsAsync(output, "test/backend"); await DockerAssert.AssertImageExistsAsync(output, "test/frontend"); @@ -128,7 +130,7 @@ namespace E2ETest var content = await File.ReadAllTextAsync(Path.Combine(projectDirectory.DirectoryPath, $"{projectName}-generate-{environment}.yaml")); var expectedContent = await File.ReadAllTextAsync($"testassets/generate/{projectName}.yaml"); - Assert.Equal(expectedContent.NormalizeNewLines(), content.NormalizeNewLines()); + YamlAssert.Equals(expectedContent, content, output); await DockerAssert.AssertImageExistsAsync(output, "test/backend"); await DockerAssert.AssertImageExistsAsync(output, "test/frontend"); @@ -166,7 +168,7 @@ namespace E2ETest var content = await File.ReadAllTextAsync(Path.Combine(projectDirectory.DirectoryPath, $"{projectName}-generate-{environment}.yaml")); var expectedContent = await File.ReadAllTextAsync($"testassets/generate/{projectName}-noregistry.yaml"); - Assert.Equal(expectedContent.NormalizeNewLines(), content.NormalizeNewLines()); + YamlAssert.Equals(expectedContent, content, output); await DockerAssert.AssertImageExistsAsync(output, "test-project"); } @@ -201,7 +203,7 @@ namespace E2ETest var content = await File.ReadAllTextAsync(Path.Combine(projectDirectory.DirectoryPath, $"{applicationName}-generate-{environment}.yaml")); var expectedContent = await File.ReadAllTextAsync($"testassets/generate/dapr.yaml"); - Assert.Equal(expectedContent.NormalizeNewLines(), content.NormalizeNewLines()); + YamlAssert.Equals(expectedContent, content, output); await DockerAssert.AssertImageExistsAsync(output, projectName); } @@ -236,7 +238,7 @@ namespace E2ETest var content = await File.ReadAllTextAsync(Path.Combine(projectDirectory.DirectoryPath, $"{applicationName}-generate-{environment}.yaml")); var expectedContent = await File.ReadAllTextAsync($"testassets/generate/{applicationName}.yaml"); - Assert.Equal(expectedContent.NormalizeNewLines(), content.NormalizeNewLines()); + YamlAssert.Equals(expectedContent, content, output); await DockerAssert.AssertImageExistsAsync(output, projectName); } @@ -271,7 +273,7 @@ namespace E2ETest var content = await File.ReadAllTextAsync(Path.Combine(projectDirectory.DirectoryPath, $"{applicationName}-generate-{environment}.yaml")); var expectedContent = await File.ReadAllTextAsync($"testassets/generate/{applicationName}.yaml"); - Assert.Equal(expectedContent.NormalizeNewLines(), content.NormalizeNewLines()); + YamlAssert.Equals(expectedContent, content, output); await DockerAssert.AssertImageExistsAsync(output, projectName); } @@ -306,7 +308,7 @@ namespace E2ETest var content = await File.ReadAllTextAsync(Path.Combine(projectDirectory.DirectoryPath, $"{applicationName}-generate-{environment}.yaml")); var expectedContent = await File.ReadAllTextAsync($"testassets/generate/{applicationName}.yaml"); - Assert.Equal(expectedContent.NormalizeNewLines(), content.NormalizeNewLines()); + YamlAssert.Equals(expectedContent, content, output); await DockerAssert.AssertImageExistsAsync(output, projectName); } diff --git a/test/E2ETest/TyeInitTests.cs b/test/E2ETest/TyeInitTests.cs index 6e4aefb3..15b9192c 100644 --- a/test/E2ETest/TyeInitTests.cs +++ b/test/E2ETest/TyeInitTests.cs @@ -10,6 +10,7 @@ using Xunit; using Xunit.Abstractions; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; +using YamlDotNet.RepresentationModel; using static E2ETest.TestHelpers; namespace E2ETest @@ -18,15 +19,12 @@ namespace E2ETest { private readonly ITestOutputHelper output; private readonly TestOutputLogEventSink sink; - private readonly IDeserializer _deserializer; public TyeInitTests(ITestOutputHelper output) { this.output = output; sink = new TestOutputLogEventSink(output); - _deserializer = new DeserializerBuilder() - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .Build(); + } [Fact] @@ -38,12 +36,10 @@ namespace E2ETest var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "multi-project.sln")); var (content, _) = InitHost.CreateTyeFileContent(projectFile, force: false); - var actual = _deserializer.Deserialize(content); var expectedContent = File.ReadAllText("testassets/init/multi-project.yaml"); - var expected = _deserializer.Deserialize(expectedContent); - TyeAssert.Equal(expected, actual); + YamlAssert.Equals(expectedContent, content); } [Fact] @@ -61,7 +57,7 @@ namespace E2ETest output.WriteLine(content); - Assert.Equal(expectedContent.NormalizeNewLines(), content.NormalizeNewLines()); + YamlAssert.Equals(expectedContent, content); } // Tests our logic that excludes non-applications (unit tests, classlibs, etc) @@ -73,12 +69,10 @@ namespace E2ETest var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "project-types.sln")); var (content, _) = InitHost.CreateTyeFileContent(projectFile, force: false); - var actual = _deserializer.Deserialize(content); var expectedContent = File.ReadAllText("testassets/init/project-types.yaml"); - var expected = _deserializer.Deserialize(expectedContent); - TyeAssert.Equal(expected, actual); + YamlAssert.Equals(expectedContent, content); } } }