520 lines
18 KiB
C#
520 lines
18 KiB
C#
// 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.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Runtime.Versioning;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNet.Builder;
|
|
using Microsoft.AspNet.FileProviders;
|
|
using Microsoft.AspNet.Hosting;
|
|
using Microsoft.AspNet.TestHost;
|
|
using Microsoft.AspNet.Testing;
|
|
using Microsoft.Extensions.PlatformAbstractions;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Primitives;
|
|
using Xunit;
|
|
using StackFrame = Microsoft.AspNet.Diagnostics.Views.StackFrame;
|
|
|
|
namespace Microsoft.AspNet.Diagnostics
|
|
{
|
|
public class DeveloperExceptionPageMiddlewareTest
|
|
{
|
|
public static TheoryData RelativePathsData
|
|
{
|
|
get
|
|
{
|
|
var data = new TheoryData<string>
|
|
{
|
|
"TestFiles/SourceFile.txt"
|
|
};
|
|
|
|
if (!(TestPlatformHelper.IsLinux || TestPlatformHelper.IsMac))
|
|
{
|
|
data.Add(@"TestFiles\SourceFile.txt");
|
|
}
|
|
|
|
return data;
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(RelativePathsData))]
|
|
public void UsesDefaultFileProvider_IfNotProvidedOnOptions(string relativePath)
|
|
{
|
|
// Arrange & Act
|
|
var middleware = GetErrorPageMiddleware(fileProvider: null);
|
|
var stackFrame = middleware.GetStackFrame("func1", relativePath, lineNumber: 10);
|
|
|
|
// Assert
|
|
// Lines 4-16 (inclusive) is the code block
|
|
Assert.Equal(4, stackFrame.PreContextLine);
|
|
Assert.Equal(GetCodeLines(4, 9), stackFrame.PreContextCode);
|
|
Assert.Equal(GetCodeLines(10, 10), stackFrame.ContextCode);
|
|
Assert.Equal(GetCodeLines(11, 16), stackFrame.PostContextCode);
|
|
}
|
|
|
|
public static TheoryData<string> AbsolutePathsData
|
|
{
|
|
get
|
|
{
|
|
var rootPath = Directory.GetCurrentDirectory();
|
|
|
|
var data = new TheoryData<string>()
|
|
{
|
|
Path.Combine(rootPath, "TestFiles/SourceFile.txt")
|
|
};
|
|
|
|
if (!TestPlatformHelper.IsMono)
|
|
{
|
|
Path.Combine(rootPath, @"TestFiles\SourceFile.txt");
|
|
}
|
|
|
|
return data;
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(AbsolutePathsData))]
|
|
public void DisplaysSourceCodeLines_ForAbsolutePaths(string absoluteFilePath)
|
|
{
|
|
// Arrange
|
|
var rootPath = Directory.GetCurrentDirectory();
|
|
// PhysicalFileProvider handles only relative paths but we fall back to work with absolute paths too
|
|
using (var provider = new PhysicalFileProvider(rootPath))
|
|
{
|
|
// Act
|
|
var middleware = GetErrorPageMiddleware(provider);
|
|
var stackFrame = middleware.GetStackFrame("func1", absoluteFilePath, lineNumber: 10);
|
|
|
|
// Assert
|
|
// Lines 4-16 (inclusive) is the code block
|
|
Assert.Equal(4, stackFrame.PreContextLine);
|
|
Assert.Equal(GetCodeLines(4, 9), stackFrame.PreContextCode);
|
|
Assert.Equal(GetCodeLines(10, 10), stackFrame.ContextCode);
|
|
Assert.Equal(GetCodeLines(11, 16), stackFrame.PostContextCode);
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(RelativePathsData))]
|
|
public void DisplaysSourceCodeLines_ForRelativePaths(string relativePath)
|
|
{
|
|
// Arrange
|
|
var rootPath = Directory.GetCurrentDirectory();
|
|
using (var provider = new PhysicalFileProvider(rootPath))
|
|
{
|
|
// Act
|
|
var middleware = GetErrorPageMiddleware(provider);
|
|
var stackFrame = middleware.GetStackFrame("func1", relativePath, lineNumber: 10);
|
|
|
|
// Assert
|
|
// Lines 4-16 (inclusive) is the code block
|
|
Assert.Equal(4, stackFrame.PreContextLine);
|
|
Assert.Equal(GetCodeLines(4, 9), stackFrame.PreContextCode);
|
|
Assert.Equal(GetCodeLines(10, 10), stackFrame.ContextCode);
|
|
Assert.Equal(GetCodeLines(11, 16), stackFrame.PostContextCode);
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("TestFiles/EmbeddedSourceFile.txt")]
|
|
//[InlineData(@"TestFiles\EmbeddedSourceFile.txt")]
|
|
public void DisplaysSourceCodeLines_ForRelativeEmbeddedPaths(string relativePath)
|
|
{
|
|
// Arrange
|
|
var provider = new EmbeddedFileProvider(
|
|
GetType().GetTypeInfo().Assembly,
|
|
baseNamespace: $"{typeof(DeveloperExceptionPageMiddlewareTest).GetTypeInfo().Assembly.GetName().Name}.Resources");
|
|
|
|
// Act
|
|
var middleware = GetErrorPageMiddleware(provider);
|
|
var stackFrame = middleware.GetStackFrame("func1", relativePath, lineNumber: 10);
|
|
|
|
// Assert
|
|
// Lines 4-16 (inclusive) is the code block
|
|
Assert.Equal(4, stackFrame.PreContextLine);
|
|
Assert.Equal(GetCodeLines(4, 9), stackFrame.PreContextCode);
|
|
Assert.Equal(GetCodeLines(10, 10), stackFrame.ContextCode);
|
|
Assert.Equal(GetCodeLines(11, 16), stackFrame.PostContextCode);
|
|
}
|
|
|
|
public static TheoryData<ErrorData> DisplaysSourceCodeLines_PreAndPostErrorLineData
|
|
{
|
|
get
|
|
{
|
|
return new TheoryData<ErrorData>()
|
|
{
|
|
new ErrorData()
|
|
{
|
|
AllLines = GetCodeLines(1, 30),
|
|
ErrorStartLine = 10,
|
|
ErrorEndLine = 10,
|
|
ExpectedPreContextLine = 4,
|
|
ExpectedPreErrorCode = GetCodeLines(4, 9),
|
|
ExpectedErrorCode = GetCodeLines(10, 10),
|
|
ExpectedPostErrorCode = GetCodeLines(11, 16)
|
|
},
|
|
new ErrorData()
|
|
{
|
|
AllLines = GetCodeLines(1, 30),
|
|
ErrorStartLine = 10,
|
|
ErrorEndLine = 13, // multi-line error
|
|
ExpectedPreContextLine = 4,
|
|
ExpectedPreErrorCode = GetCodeLines(4, 9),
|
|
ExpectedErrorCode = GetCodeLines(10, 13),
|
|
ExpectedPostErrorCode = GetCodeLines(14, 19)
|
|
},
|
|
|
|
// PreErrorCode less than source code line count
|
|
new ErrorData()
|
|
{
|
|
AllLines = GetCodeLines(1, 10),
|
|
ErrorStartLine = 1,
|
|
ErrorEndLine = 1,
|
|
ExpectedPreContextLine = 1,
|
|
ExpectedPreErrorCode = Enumerable.Empty<string>(),
|
|
ExpectedErrorCode = GetCodeLines(1, 1),
|
|
ExpectedPostErrorCode = GetCodeLines(2, 7)
|
|
},
|
|
new ErrorData()
|
|
{
|
|
AllLines = GetCodeLines(1, 10),
|
|
ErrorStartLine = 3,
|
|
ErrorEndLine = 5,
|
|
ExpectedPreContextLine = 1,
|
|
ExpectedPreErrorCode = GetCodeLines(1, 2),
|
|
ExpectedErrorCode = GetCodeLines(3, 5),
|
|
ExpectedPostErrorCode = GetCodeLines(6, 10)
|
|
},
|
|
|
|
// PostErrorCode less than source code line count
|
|
new ErrorData()
|
|
{
|
|
AllLines = GetCodeLines(1, 10),
|
|
ErrorStartLine = 10,
|
|
ErrorEndLine = 10,
|
|
ExpectedPreContextLine = 4,
|
|
ExpectedPreErrorCode = GetCodeLines(4, 9),
|
|
ExpectedErrorCode = GetCodeLines(10, 10),
|
|
ExpectedPostErrorCode = Enumerable.Empty<string>()
|
|
},
|
|
new ErrorData()
|
|
{
|
|
AllLines = GetCodeLines(1, 10),
|
|
ErrorStartLine = 7,
|
|
ErrorEndLine = 10,
|
|
ExpectedPreContextLine = 1,
|
|
ExpectedPreErrorCode = GetCodeLines(1, 6),
|
|
ExpectedErrorCode = GetCodeLines(7, 10),
|
|
ExpectedPostErrorCode = Enumerable.Empty<string>()
|
|
},
|
|
new ErrorData()
|
|
{
|
|
AllLines = GetCodeLines(1, 10),
|
|
ErrorStartLine = 5,
|
|
ErrorEndLine = 8,
|
|
ExpectedPreContextLine = 1,
|
|
ExpectedPreErrorCode = GetCodeLines(1, 4),
|
|
ExpectedErrorCode = GetCodeLines(5, 8),
|
|
ExpectedPostErrorCode = GetCodeLines(9, 10)
|
|
},
|
|
|
|
// Pre and Post error code less than source code line count
|
|
new ErrorData()
|
|
{
|
|
AllLines = GetCodeLines(1, 4),
|
|
ErrorStartLine = 2,
|
|
ErrorEndLine = 3,
|
|
ExpectedPreContextLine = 1,
|
|
ExpectedPreErrorCode = GetCodeLines(1, 1),
|
|
ExpectedErrorCode = GetCodeLines(2, 3),
|
|
ExpectedPostErrorCode = GetCodeLines(4, 4)
|
|
},
|
|
new ErrorData()
|
|
{
|
|
AllLines = GetCodeLines(1, 4),
|
|
ErrorStartLine = 1,
|
|
ErrorEndLine = 4,
|
|
ExpectedPreContextLine = 1,
|
|
ExpectedPreErrorCode = Enumerable.Empty<string>(),
|
|
ExpectedErrorCode = GetCodeLines(1, 4),
|
|
ExpectedPostErrorCode = Enumerable.Empty<string>()
|
|
},
|
|
|
|
// change source code line count
|
|
new ErrorData()
|
|
{
|
|
SourceCodeLineCount = 1,
|
|
AllLines = GetCodeLines(1, 1),
|
|
ErrorStartLine = 1,
|
|
ErrorEndLine = 1,
|
|
ExpectedPreContextLine = 1,
|
|
ExpectedPreErrorCode = Enumerable.Empty<string>(),
|
|
ExpectedErrorCode = GetCodeLines(1, 1),
|
|
ExpectedPostErrorCode = Enumerable.Empty<string>()
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(DisplaysSourceCodeLines_PreAndPostErrorLineData))]
|
|
public void DisplaysSourceCodeLines_PreAndPostErrorLine(ErrorData errorData)
|
|
{
|
|
// Arrange
|
|
var middleware = GetErrorPageMiddleware();
|
|
var stackFrame = new StackFrame();
|
|
|
|
// Act
|
|
middleware.ReadFrameContent(
|
|
stackFrame, errorData.AllLines, errorData.ErrorStartLine, errorData.ErrorEndLine);
|
|
|
|
// Assert
|
|
Assert.Equal(errorData.ExpectedPreContextLine, stackFrame.PreContextLine);
|
|
Assert.Equal(errorData.ExpectedPreErrorCode, stackFrame.PreContextCode);
|
|
Assert.Equal(errorData.ExpectedErrorCode, stackFrame.ContextCode);
|
|
Assert.Equal(errorData.ExpectedPostErrorCode, stackFrame.PostContextCode);
|
|
}
|
|
|
|
private static IEnumerable<string> GetCodeLines(int fromLine, int toLine)
|
|
{
|
|
var start = fromLine;
|
|
var count = toLine - fromLine + 1;
|
|
return Enumerable.Range(start, count).Select(i => string.Format("Line{0}", i));
|
|
}
|
|
|
|
private DeveloperExceptionPageMiddleware GetErrorPageMiddleware(
|
|
IFileProvider fileProvider = null, int sourceCodeLineCount = 6)
|
|
{
|
|
var options = new DeveloperExceptionPageOptions();
|
|
options.SourceCodeLineCount = sourceCodeLineCount;
|
|
|
|
if (fileProvider != null)
|
|
{
|
|
options.FileProvider = fileProvider;
|
|
}
|
|
|
|
var middleware = new DeveloperExceptionPageMiddleware(
|
|
(httpContext) => { return Task.FromResult(0); },
|
|
options,
|
|
new LoggerFactory(),
|
|
new TestApplicationEnvironment(),
|
|
new DiagnosticListener("Microsoft.Aspnet"));
|
|
|
|
return middleware;
|
|
}
|
|
|
|
private class TestApplicationEnvironment : IApplicationEnvironment
|
|
{
|
|
public string ApplicationBasePath
|
|
{
|
|
get
|
|
{
|
|
return Directory.GetCurrentDirectory();
|
|
}
|
|
}
|
|
|
|
public string ApplicationName
|
|
{
|
|
get
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
public string ApplicationVersion
|
|
{
|
|
get
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
public string Configuration
|
|
{
|
|
get
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
public FrameworkName RuntimeFramework
|
|
{
|
|
get
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
public string Version
|
|
{
|
|
get
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
public object GetData(string name)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void SetData(string name, object value)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
private class TestFileProvider : IFileProvider
|
|
{
|
|
private readonly IEnumerable<string> _sourceCodeLines;
|
|
|
|
public TestFileProvider(IEnumerable<string> sourceCodeLines)
|
|
{
|
|
_sourceCodeLines = sourceCodeLines;
|
|
}
|
|
|
|
public IDirectoryContents GetDirectoryContents(string subpath)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public IFileInfo GetFileInfo(string subpath)
|
|
{
|
|
return new TestFileInfo(_sourceCodeLines);
|
|
}
|
|
|
|
public IChangeToken Watch(string filter)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
private class TestFileInfo : IFileInfo
|
|
{
|
|
private readonly MemoryStream _stream;
|
|
|
|
public TestFileInfo(IEnumerable<string> sourceCodeLines)
|
|
{
|
|
_stream = new MemoryStream();
|
|
using (var writer = new StreamWriter(_stream, Encoding.UTF8, 1024, leaveOpen: true))
|
|
{
|
|
foreach (var line in sourceCodeLines)
|
|
{
|
|
writer.WriteLine(line);
|
|
}
|
|
}
|
|
_stream.Seek(0, SeekOrigin.Begin);
|
|
}
|
|
|
|
public bool Exists
|
|
{
|
|
get
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public bool IsDirectory
|
|
{
|
|
get
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
public DateTimeOffset LastModified
|
|
{
|
|
get
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
public long Length
|
|
{
|
|
get
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
public string Name
|
|
{
|
|
get
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
public string PhysicalPath
|
|
{
|
|
get
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public Stream CreateReadStream()
|
|
{
|
|
return _stream;
|
|
}
|
|
}
|
|
|
|
public class ErrorData
|
|
{
|
|
public int SourceCodeLineCount { get; set; } = 6;
|
|
public IEnumerable<string> AllLines { get; set; }
|
|
public int ErrorStartLine { get; set; }
|
|
public int ErrorEndLine { get; set; }
|
|
public int ExpectedPreContextLine { get; set; }
|
|
public IEnumerable<string> ExpectedPreErrorCode { get; set; }
|
|
public IEnumerable<string> ExpectedErrorCode { get; set; }
|
|
public IEnumerable<string> ExpectedPostErrorCode { get; set; }
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UnhandledErrorsWriteToDiagnosticWhenUsingExceptionPage()
|
|
{
|
|
// Arrange
|
|
DiagnosticListener diagnosticListener = null;
|
|
var builder = new WebApplicationBuilder()
|
|
.Configure(app =>
|
|
{
|
|
diagnosticListener = app.ApplicationServices.GetRequiredService<DiagnosticListener>();
|
|
app.UseDeveloperExceptionPage();
|
|
app.Run(context =>
|
|
{
|
|
throw new Exception("Test exception");
|
|
});
|
|
});
|
|
var server = new TestServer(builder);
|
|
var listener = new TestDiagnosticListener();
|
|
diagnosticListener.SubscribeWithAdapter(listener);
|
|
|
|
// Act
|
|
await server.CreateClient().GetAsync("/path");
|
|
|
|
// This ensures that all diagnostics are completely written to the diagnostic listener
|
|
Thread.Sleep(1000);
|
|
|
|
// Assert
|
|
Assert.NotNull(listener.EndRequest?.HttpContext);
|
|
Assert.Null(listener.HostingUnhandledException?.HttpContext);
|
|
Assert.Null(listener.HostingUnhandledException?.Exception);
|
|
Assert.NotNull(listener.DiagnosticUnhandledException?.HttpContext);
|
|
Assert.NotNull(listener.DiagnosticUnhandledException?.Exception);
|
|
Assert.Null(listener.DiagnosticHandledException?.HttpContext);
|
|
Assert.Null(listener.DiagnosticHandledException?.Exception);
|
|
}
|
|
}
|
|
}
|