Verify support for reporting errors using MSBuild (#1867)

* Verify support for reporting errors using MSBuild
* Verifies that CSC errors get mapped to the right place

Resolves #1850
This commit is contained in:
Ryan Nowak 2017-12-20 16:08:35 -08:00 committed by GitHub
parent d419566534
commit 3f948ad3c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 140 additions and 7 deletions

View File

@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language;
@ -60,7 +59,7 @@ namespace Microsoft.AspNetCore.Razor.GenerateTool
success = false;
foreach (var error in result.CSharpDocument.Diagnostics)
{
Console.Error.WriteLine(error.GetMessage());
Console.Error.WriteLine(error.ToString());
}
}

View File

@ -3,14 +3,17 @@
using System;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
internal class Assert : Xunit.Assert
{
// Matches `{filename}: error {code}: {message} [{project}]
// See https://stackoverflow.com/questions/3441452/msbuild-and-ignorestandarderrorwarningformat/5180353#5180353
private static readonly Regex ErrorRegex = new Regex(@"^(?'location'.+): error (?'errorcode'[A-Z0-9]+): (?'message'.+) \[(?'project'.+)\]$");
public static void BuildPassed(MSBuildResult result)
{
if (result == null)
@ -24,6 +27,52 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
}
}
public static void BuildError(MSBuildResult result, string errorCode, string location = null)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
// We don't really need to search line by line, I'm doing this so that it's possible/easy to debug.
var lines = result.Output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
for (var i = 0; i < lines.Length; i++)
{
var line = lines[i];
var match = ErrorRegex.Match(line);
if (match.Success)
{
if (match.Groups["errorcode"].Value != errorCode)
{
continue;
}
if (location != null && match.Groups["location"].Value != location)
{
continue;
}
// This is a match
return;
}
}
throw new BuildErrorMissingException(result, errorCode);
}
public static void BuildFailed(MSBuildResult result)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
};
if (result.ExitCode == 0)
{
throw new BuildPassedException(result);
}
}
public static void FileExists(MSBuildResult result, params string[] paths)
{
if (result == null)
@ -106,6 +155,20 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
}
}
private class BuildErrorMissingException : MSBuildXunitException
{
public BuildErrorMissingException(MSBuildResult result, string errorCode)
: base(result)
{
ErrorCode = errorCode;
}
public string ErrorCode { get; }
protected override string Heading => $"Error code '{ErrorCode}' was not found.";
}
private class BuildFailedException : MSBuildXunitException
{
public BuildFailedException(MSBuildResult result)
@ -113,7 +176,17 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
}
protected override string Heading => "Build failed:";
protected override string Heading => "Build failed.";
}
private class BuildPassedException : MSBuildXunitException
{
public BuildPassedException(MSBuildResult result)
: base(result)
{
}
protected override string Heading => "Build should have failed, but it passed.";
}
private class FileMissingException : MSBuildXunitException

View File

@ -19,5 +19,24 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.FileExists(result, OutputPath, "SimpleMvc.dll");
Assert.FileExists(result, OutputPath, "SimpleMvc.PrecompiledViews.dll");
}
[Fact]
[InitializeTestProject("SimpleMvc")]
public async Task Build_ErrorInGeneratedCode_ReportsMSBuildError()
{
// Introducing a C# semantic error
ReplaceContent("@{ var foo = \"\".Substring(\"bleh\"); }", "Views", "Home", "Index.cshtml");
var result = await DotnetMSBuild("Build", "/p:RazorCompileOnBuild=true");
Assert.BuildFailed(result);
// Verifying that the error correctly gets mapped to the original source
Assert.BuildError(result, "CS1503", location: Path.Combine("Views", "Home", "Index.cshtml") + "(1,27)");
// Compilation failed without creating the views assembly
Assert.FileExists(result, IntermediateOutputPath, "SimpleMvc.dll");
Assert.FileDoesNotExist(result, IntermediateOutputPath, "SimpleMvc.PrecompiledViews.dll");
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -46,5 +47,26 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
return MSBuildProcessManager.RunProcessAsync(Project, $"{restoreArgument} /t:{target} /p:Configuration={Configuration} {args}", timeout);
}
internal void ReplaceContent(string content, params string[] paths)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
if (paths == null)
{
throw new ArgumentNullException(nameof(paths));
}
var filePath = Path.Combine(Project.DirectoryPath, Path.Combine(paths));
if (!File.Exists(filePath))
{
throw new InvalidOperationException($"File {filePath} could not be found.");
}
File.WriteAllText(filePath, content, Encoding.UTF8);
}
}
}

View File

@ -1,8 +1,6 @@
// 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.IO;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
@ -32,5 +30,27 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Shared", "Error.cs");
Assert.FileCountEquals(result, 8, RazorIntermediateOutputPath, "*.cs");
}
[Fact]
[InitializeTestProject("SimpleMvc")]
public async Task RazorGenerate_ErrorInRazorFile_ReportsMSBuildError()
{
// Introducing a syntax error, an unclosed brace
ReplaceContent("@{", "Views", "Home", "Index.cshtml");
var result = await DotnetMSBuild("RazorGenerate");
Assert.BuildFailed(result);
// Looks like C:\...\Views\Home\Index.cshtml(1,2): error RZ1006: The code block is missi... [C:\Users\rynowak\AppData\Local\Temp\rwnv03ll.wb0\SimpleMvc.csproj]
Assert.BuildError(result, "RZ1006");
// RazorGenerate should compile the assembly, but not the views.
Assert.FileExists(result, IntermediateOutputPath, "SimpleMvc.dll");
Assert.FileDoesNotExist(result, IntermediateOutputPath, "SimpleMvc.PrecompiledViews.dll");
// The file should still be generated even if we had a Razor syntax error.
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "Index.cs");
}
}
}