// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.IO; using System.Reflection; #if NET451 using System.Runtime.Remoting; using System.Runtime.Remoting.Messaging; #else using System.Threading; #endif using System.Text; using Microsoft.AspNetCore.Razor.Evolution.Intermediate; using Microsoft.AspNetCore.Razor.Evolution.Legacy; using Xunit; using Xunit.Sdk; using System.Runtime.InteropServices; namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests { [IntializeTestFile] public abstract class IntegrationTestBase { private static readonly string ThisProjectName = typeof(IntializeTestFileAttribute).GetTypeInfo().Assembly.GetName().Name; private static readonly string TestProjectRoot = FindTestProjectRoot(); private static string FindTestProjectRoot() { #if NET451 var currentDirectory = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory); #else var currentDirectory = new DirectoryInfo(AppContext.BaseDirectory); #endif while (currentDirectory != null && !string.Equals(currentDirectory.Name, ThisProjectName, StringComparison.Ordinal)) { currentDirectory = currentDirectory.Parent; } var normalizedSeparators = currentDirectory.FullName.Replace(Path.DirectorySeparatorChar, '/'); return currentDirectory.FullName; } #if GENERATE_BASELINES private static readonly bool GenerateBaselines = true; #else private static readonly bool GenerateBaselines = false; #endif #if !NET451 private static readonly AsyncLocal _filename = new AsyncLocal(); #endif // Used by the test framework to set the 'base' name for test files. public static string Filename { #if NET451 get { var handle = (ObjectHandle)CallContext.LogicalGetData("IntegrationTestBase_Filename"); return (string)handle.Unwrap(); } set { CallContext.LogicalSetData("IntegrationTestBase_Filename", new ObjectHandle(value)); } #else get { return _filename.Value; } set { _filename.Value = value; } #endif } protected virtual RazorCodeDocument CreateCodeDocument() { if (Filename == null) { var message = $"{nameof(CreateCodeDocument)} should only be called from an integration test ({nameof(Filename)} is null)."; throw new InvalidOperationException(message); } var sourceFilename = Path.ChangeExtension(Filename, ".cshtml"); var testFile = TestFile.Create(sourceFilename); if (!testFile.Exists()) { throw new XunitException($"The resource {sourceFilename} was not found."); } var codeDocument = RazorCodeDocument.Create(TestRazorSourceDocument.CreateResource(sourceFilename)); // This will ensure that we're not putting any randomly generated data in a baseline. codeDocument.Items[DefaultRazorRuntimeCSharpLoweringPhase.SuppressUniqueIds] = "test"; return codeDocument; } protected void AssertIRMatchesBaseline(DocumentIRNode document) { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return; } if (Filename == null) { var message = $"{nameof(AssertIRMatchesBaseline)} should only be called from an integration test ({nameof(Filename)} is null)."; throw new InvalidOperationException(message); } var baselineFilename = Path.ChangeExtension(Filename, ".ir.txt"); if (GenerateBaselines) { var baselineFullPath = Path.Combine(TestProjectRoot, baselineFilename); File.WriteAllText(baselineFullPath, RazorIRNodeSerializer.Serialize(document)); return; } var testFile = TestFile.Create(baselineFilename); if (!testFile.Exists()) { throw new XunitException($"The resource {baselineFilename} was not found."); } var baseline = testFile.ReadAllText().Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); RazorIRNodeVerifier.Verify(document, baseline); } protected void AssertCSharpDocumentMatchesBaseline(RazorCSharpDocument document) { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return; } if (Filename == null) { var message = $"{nameof(AssertCSharpDocumentMatchesBaseline)} should only be called from an integration test ({nameof(Filename)} is null)."; throw new InvalidOperationException(message); } var baselineFilename = Path.ChangeExtension(Filename, ".codegen.cs"); if (GenerateBaselines) { var baselineFullPath = Path.Combine(TestProjectRoot, baselineFilename); File.WriteAllText(baselineFullPath, document.GeneratedCode); return; } var testFile = TestFile.Create(baselineFilename); if (!testFile.Exists()) { throw new XunitException($"The resource {baselineFilename} was not found."); } var baseline = testFile.ReadAllText(); // Normalize newlines to match those in the baseline. var actual = document.GeneratedCode.Replace("\r", "").Replace("\n", "\r\n"); Assert.Equal(baseline, actual); } protected void AssertDesignTimeDocumentMatchBaseline(RazorCodeDocument document) { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return; } if (Filename == null) { var message = $"{nameof(AssertDesignTimeDocumentMatchBaseline)} should only be called from an integration test ({nameof(Filename)} is null)."; throw new InvalidOperationException(message); } var csharpDocument = document.GetCSharpDocument(); Assert.NotNull(csharpDocument); var syntaxTree = document.GetSyntaxTree(); Assert.NotNull(syntaxTree); Assert.True(syntaxTree.Options.DesignTimeMode); // Validate generated code. AssertCSharpDocumentMatchesBaseline(csharpDocument); var baselineFilename = Path.ChangeExtension(Filename, ".mappings.txt"); var serializedMappings = LineMappingsSerializer.Serialize(csharpDocument, document.Source); if (GenerateBaselines) { var baselineFullPath = Path.Combine(TestProjectRoot, baselineFilename); File.WriteAllText(baselineFullPath, serializedMappings); return; } var testFile = TestFile.Create(baselineFilename); if (!testFile.Exists()) { throw new XunitException($"The resource {baselineFilename} was not found."); } var baseline = testFile.ReadAllText(); // Normalize newlines to match those in the baseline. var actual = serializedMappings.Replace("\r", "").Replace("\n", "\r\n"); Assert.Equal(baseline, actual); } } }