Merge branch 'master' of https://github.com/benaadams/Ben.Demystifier
This commit is contained in:
commit
5fbf64f31b
1
.gitignore
vendored
1
.gitignore
vendored
@ -23,6 +23,7 @@ bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
|
||||
@ -13,7 +13,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ben.Demystifier.Test", "tes
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{455921D3-DD54-4355-85CF-F4009DF2AB70}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackTrace", "sample\StackTrace\StackTrace.csproj", "{E161FC12-53C2-47CD-A5FC-3684B86723A9}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StackTrace", "sample\StackTrace\StackTrace.csproj", "{E161FC12-53C2-47CD-A5FC-3684B86723A9}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5937ACDF-0059-488E-9604-D84689C72933}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
appveyor.yml = appveyor.yml
|
||||
build.ps1 = build.ps1
|
||||
directory.build.props = directory.build.props
|
||||
README.md = README.md
|
||||
version.json = version.json
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ben.Demystifier.Benchmarks", "test\Ben.Demystifier.Benchmarks\Ben.Demystifier.Benchmarks.csproj", "{EF5557DF-C48E-4999-846C-D99A92E86373}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@ -33,6 +44,10 @@ Global
|
||||
{E161FC12-53C2-47CD-A5FC-3684B86723A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E161FC12-53C2-47CD-A5FC-3684B86723A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E161FC12-53C2-47CD-A5FC-3684B86723A9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EF5557DF-C48E-4999-846C-D99A92E86373}.Debug|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EF5557DF-C48E-4999-846C-D99A92E86373}.Debug|Any CPU.Build.0 = Release|Any CPU
|
||||
{EF5557DF-C48E-4999-846C-D99A92E86373}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EF5557DF-C48E-4999-846C-D99A92E86373}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -41,6 +56,7 @@ Global
|
||||
{5410A056-89AB-4912-BD1E-A63616AD91D0} = {A2FCCAAC-BE90-4F7E-B95F-A72D46DDD6B3}
|
||||
{B9E150B0-AEEB-4D98-8BE1-92C1296699A2} = {59CA6310-4AA5-4093-95D4-472B94DC0CD4}
|
||||
{E161FC12-53C2-47CD-A5FC-3684B86723A9} = {455921D3-DD54-4355-85CF-F4009DF2AB70}
|
||||
{EF5557DF-C48E-4999-846C-D99A92E86373} = {59CA6310-4AA5-4093-95D4-472B94DC0CD4}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {841B7D5F-E810-4F94-A529-002C7E075216}
|
||||
|
||||
@ -158,3 +158,11 @@ Which is far less helpful, and close to jibberish in places
|
||||
* **return types**
|
||||
|
||||
Skipped entirely from method signature
|
||||
|
||||
### Benchmarks
|
||||
|
||||
To run benchmarks from the repository root:
|
||||
```
|
||||
dotnet run -p .\test\Ben.Demystifier.Benchmarks\ -c Release -f netcoreapp2.0 All
|
||||
```
|
||||
<sub>Note: we're only kicking off via `netcoreapp2.0`, benchmarks will run for all configured platforms like `net462`.</sub>
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
image: Visual Studio 2017
|
||||
|
||||
shallow_clone: true
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
@ -22,7 +20,7 @@ nuget:
|
||||
disable_publish_on_pr: true
|
||||
|
||||
build_script:
|
||||
- ps: .\build.ps1 -target appveyor -buildAssemblyVersion ($env:BuildVersion + $env:APPVEYOR_BUILD_NUMBER) -buildSemanticVersion ($env:BuildSemanticVersion + $env:APPVEYOR_BUILD_NUMBER)
|
||||
- ps: .\build.ps1 -target appveyor
|
||||
|
||||
test: off
|
||||
|
||||
|
||||
@ -22,11 +22,6 @@ function Exec
|
||||
|
||||
if(Test-Path .\artifacts) { Remove-Item .\artifacts -Force -Recurse }
|
||||
|
||||
exec { & dotnet restore }
|
||||
|
||||
$revision = @{ $true = $env:APPVEYOR_BUILD_NUMBER; $false = 1 }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL];
|
||||
$revision = "{0:D4}" -f [convert]::ToInt32($revision, 10)
|
||||
|
||||
exec { & dotnet test .\test\Ben.Demystifier.Test -c Release }
|
||||
|
||||
exec { & dotnet pack .\src\Ben.Demystifier -c Release -o .\artifacts --version-suffix=$revision }
|
||||
exec { & dotnet pack .\src\Ben.Demystifier -c Release -o .\artifacts }
|
||||
|
||||
5
directory.build.props
Normal file
5
directory.build.props
Normal file
@ -0,0 +1,5 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@ -5,14 +5,6 @@
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<LangVersion>7.2</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<LangVersion>7.2</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Ben.Demystifier\Ben.Demystifier.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -9,17 +9,19 @@
|
||||
<PackageProjectUrl>https://github.com/benaadams/Ben.Demystifier</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/benaadams/Ben.Demystifier/blob/master/LICENSE</PackageLicenseUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<IncludeSource>true</IncludeSource>
|
||||
<Version>0.0.7</Version>
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45</TargetFrameworks>
|
||||
<LangVersion>7.1</LangVersion>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Nerdbank.GitVersioning" Version="2.1.17" PrivateAssets="all" />
|
||||
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.7.6" PrivateAssets="all" />
|
||||
<PackageReference Include="System.Reflection.Metadata">
|
||||
<Version>1.5.0</Version>
|
||||
</PackageReference>
|
||||
|
||||
@ -19,7 +19,6 @@ namespace System.Diagnostics
|
||||
{
|
||||
public partial class EnhancedStackTrace
|
||||
{
|
||||
|
||||
private static List<EnhancedStackFrame> GetFrames(Exception exception)
|
||||
{
|
||||
if (exception == null)
|
||||
@ -516,7 +515,7 @@ namespace System.Diagnostics
|
||||
{
|
||||
foreach (var attrib in attribs)
|
||||
{
|
||||
if (attrib is Attribute att && att.GetType().Namespace == "System.Runtime.CompilerServices" && att.GetType().Name == "IsReadOnlyAttribute")
|
||||
if (attrib is Attribute att && att.GetType().IsReadOnlyAttribute())
|
||||
{
|
||||
return "in";
|
||||
}
|
||||
@ -579,6 +578,32 @@ namespace System.Diagnostics
|
||||
}
|
||||
|
||||
private static ResolvedParameter GetValueTupleParameter(IList<string> tupleNames, string prefix, string name, Type parameterType)
|
||||
{
|
||||
string typeName;
|
||||
|
||||
if (parameterType.IsValueTuple())
|
||||
{
|
||||
typeName = GetValueTupleParameterName(tupleNames, parameterType);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Need to unwrap the first generic argument first.
|
||||
var genericTypeName = TypeNameHelper.GetTypeNameForGenericType(parameterType);
|
||||
var valueTupleFullName = GetValueTupleParameterName(tupleNames, parameterType.GetGenericArguments()[0]);
|
||||
typeName = $"{genericTypeName}<{valueTupleFullName}>";
|
||||
}
|
||||
|
||||
return new ResolvedParameter
|
||||
{
|
||||
Prefix = prefix,
|
||||
Name = name,
|
||||
Type = typeName,
|
||||
ResolvedType = parameterType,
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetValueTupleParameterName(IList<string> tupleNames, Type parameterType)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("(");
|
||||
@ -608,14 +633,7 @@ namespace System.Diagnostics
|
||||
}
|
||||
|
||||
sb.Append(")");
|
||||
|
||||
return new ResolvedParameter
|
||||
{
|
||||
Prefix = prefix,
|
||||
Name = name,
|
||||
Type = sb.ToString(),
|
||||
ResolvedType = parameterType,
|
||||
};
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static bool ShowInStackTrace(MethodBase method)
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
// Copyright (c) Ben A Adams. All rights reserved.
|
||||
// Copyright (c) Ben A Adams. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic.Enumerable;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Diagnostics
|
||||
@ -104,21 +105,9 @@ namespace System.Diagnostics
|
||||
var filePath = frame.GetFileName();
|
||||
if (!string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
sb.Append(" in ");
|
||||
var uri = new Uri(filePath);
|
||||
if (uri.IsFile)
|
||||
{
|
||||
sb.Append(System.IO.Path.GetFullPath(filePath));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(uri);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
sb.Append(" in ");
|
||||
sb.Append(TryGetFullPath(filePath));
|
||||
|
||||
}
|
||||
|
||||
var lineNo = frame.GetFileLineNumber();
|
||||
@ -133,5 +122,27 @@ namespace System.Diagnostics
|
||||
EnumerableIList<EnhancedStackFrame> GetEnumerator() => EnumerableIList.Create(_frames);
|
||||
IEnumerator<EnhancedStackFrame> IEnumerable<EnhancedStackFrame>.GetEnumerator() => _frames.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => _frames.GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Tries to convert a given <paramref name="filePath"/> to a full path.
|
||||
/// Returns original value if the conversion isn't possible or a given path is relative.
|
||||
/// </summary>
|
||||
public static string TryGetFullPath(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var uri = new Uri(filePath);
|
||||
if (uri.IsFile)
|
||||
{
|
||||
return uri.AbsolutePath;
|
||||
}
|
||||
|
||||
return uri.ToString();
|
||||
}
|
||||
catch (ArgumentException) { }
|
||||
catch (UriFormatException) { }
|
||||
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,35 +1,54 @@
|
||||
// Copyright (c) Ben A Adams. All rights reserved.
|
||||
// Copyright (c) Ben A Adams. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic.Enumerable;
|
||||
using System.Reflection;
|
||||
|
||||
namespace System.Diagnostics
|
||||
{
|
||||
/// <nodoc />
|
||||
public static class ExceptionExtentions
|
||||
{
|
||||
private static readonly FieldInfo stackTraceString = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
public static T Demystify<T>(this T exception) where T : Exception
|
||||
=> Demystify(exception, originalStacksTracker: null);
|
||||
|
||||
private static string GetStackTracesString(this Exception exception)
|
||||
=> (string)stackTraceString.GetValue(exception);
|
||||
|
||||
private static void SetStackTracesString(this Exception exception, string value)
|
||||
=> stackTraceString.SetValue(exception, value);
|
||||
|
||||
/// <summary>
|
||||
/// Demystifies the given <paramref name="exception"/> and tracks the original stack traces for the whole exception tree.
|
||||
/// </summary>
|
||||
private static T Demystify<T>(this T exception, Dictionary<Exception, string> originalStacksTracker) where T : Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
if (originalStacksTracker?.ContainsKey(exception) == false)
|
||||
{
|
||||
originalStacksTracker[exception] = exception.GetStackTracesString();
|
||||
}
|
||||
|
||||
var stackTrace = new EnhancedStackTrace(exception);
|
||||
|
||||
if (stackTrace.FrameCount > 0)
|
||||
{
|
||||
stackTraceString.SetValue(exception, stackTrace.ToString());
|
||||
exception.SetStackTracesString(stackTrace.ToString());
|
||||
}
|
||||
|
||||
if (exception is AggregateException aggEx)
|
||||
{
|
||||
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
|
||||
{
|
||||
ex.Demystify();
|
||||
ex.Demystify(originalStacksTracker);
|
||||
}
|
||||
}
|
||||
|
||||
exception.InnerException?.Demystify();
|
||||
exception.InnerException?.Demystify(originalStacksTracker);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -38,5 +57,39 @@ namespace System.Diagnostics
|
||||
|
||||
return exception;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets demystified string representation of the <paramref name="exception"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="Demystify{T}"/> method mutates the exception instance that can cause
|
||||
/// issues if a system relies on the stack trace be in the specific form.
|
||||
/// Unlike <see cref="Demystify{T}"/> this method is pure. It calls <see cref="Demystify{T}"/> first,
|
||||
/// computes a demystified string representation and then restores the original state of the exception back.
|
||||
/// </remarks>
|
||||
[Contracts.Pure]
|
||||
public static string ToStringDemystified(this Exception exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
var originalStacks = new Dictionary<Exception, string>();
|
||||
exception.Demystify(originalStacks);
|
||||
|
||||
var result = exception.ToString();
|
||||
|
||||
foreach (var kvp in originalStacks)
|
||||
{
|
||||
kvp.Key.SetStackTracesString(kvp.Value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Processing exceptions shouldn't throw exceptions; if it fails
|
||||
}
|
||||
|
||||
return exception.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
src/Ben.Demystifier/Internal/ReflectionHelper.cs
Normal file
30
src/Ben.Demystifier/Internal/ReflectionHelper.cs
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright (c) Ben A Adams. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace System.Diagnostics.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class that contains utilities methods for dealing with reflection.
|
||||
/// </summary>
|
||||
public static class ReflectionHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true if <paramref name="type"/> is <code>System.Runtime.CompilerServices.IsReadOnlyAttribute</code>.
|
||||
/// </summary>
|
||||
public static bool IsReadOnlyAttribute(this Type type)
|
||||
{
|
||||
return type.Namespace == "System.Runtime.CompilerServices" && type.Name == "IsReadOnlyAttribute";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the <paramref name="type"/> is a value tuple type.
|
||||
/// </summary>
|
||||
public static bool IsValueTuple(this Type type)
|
||||
{
|
||||
return type.Namespace == "System" && type.Name.Contains("ValueTuple`");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -43,6 +43,22 @@ namespace System.Diagnostics
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a name of given generic type without '`'.
|
||||
/// </summary>
|
||||
public static string GetTypeNameForGenericType(Type type)
|
||||
{
|
||||
if (!type.IsGenericType)
|
||||
{
|
||||
throw new ArgumentException("The given type should be generic", nameof(type));
|
||||
}
|
||||
|
||||
var genericPartIndex = type.Name.IndexOf('`');
|
||||
Debug.Assert(genericPartIndex >= 0);
|
||||
|
||||
return type.Name.Substring(0, genericPartIndex);
|
||||
}
|
||||
|
||||
private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options)
|
||||
{
|
||||
if (type.IsGenericType)
|
||||
|
||||
BIN
src/Ben.Demystifier/key.snk
Normal file
BIN
src/Ben.Demystifier/key.snk
Normal file
Binary file not shown.
@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.0;net462</TargetFrameworks>
|
||||
<Configuration>Release</Configuration>
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Ben.Demystifier\Ben.Demystifier.csproj" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.10.12" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
18
test/Ben.Demystifier.Benchmarks/Exceptions.cs
Normal file
18
test/Ben.Demystifier.Benchmarks/Exceptions.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Attributes.Jobs;
|
||||
|
||||
namespace Ben.Demystifier.Benchmarks
|
||||
{
|
||||
[ClrJob, CoreJob]
|
||||
[Config(typeof(Config))]
|
||||
public class ExceptionTests
|
||||
{
|
||||
[Benchmark(Baseline = true, Description = ".ToString()")]
|
||||
public string Baseline() => new Exception().ToString();
|
||||
|
||||
[Benchmark(Description = "Demystify().ToString()")]
|
||||
public string Demystify() => new Exception().Demystify().ToString();
|
||||
}
|
||||
}
|
||||
48
test/Ben.Demystifier.Benchmarks/Program.cs
Normal file
48
test/Ben.Demystifier.Benchmarks/Program.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using BenchmarkDotNet.Running;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ben.Demystifier.Benchmarks
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
private const string BenchmarkSuffix = "Tests";
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var benchmarks = Assembly.GetEntryAssembly()
|
||||
.DefinedTypes.Where(t => t.Name.EndsWith(BenchmarkSuffix))
|
||||
.ToDictionary(t => t.Name.Substring(0, t.Name.Length - BenchmarkSuffix.Length), t => t, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (args.Length > 0 && args[0].Equals("all", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.WriteLine("Running full benchmarks suite");
|
||||
benchmarks.Select(pair => pair.Value).ToList().ForEach(action => BenchmarkRunner.Run(action));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length == 0 || !benchmarks.ContainsKey(args[0]))
|
||||
{
|
||||
Console.WriteLine("Please, select benchmark, list of available:");
|
||||
benchmarks
|
||||
.Select(pair => pair.Key)
|
||||
.ToList()
|
||||
.ForEach(Console.WriteLine);
|
||||
Console.WriteLine("All");
|
||||
return;
|
||||
}
|
||||
|
||||
BenchmarkRunner.Run(benchmarks[args[0]]);
|
||||
|
||||
Console.Read();
|
||||
}
|
||||
}
|
||||
|
||||
internal class Config : ManualConfig
|
||||
{
|
||||
public Config() => Add(new MemoryDiagnoser());
|
||||
}
|
||||
}
|
||||
@ -2,12 +2,11 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Demystify
|
||||
namespace Ben.Demystifier.Test
|
||||
{
|
||||
public class AggregateException
|
||||
{
|
||||
@ -34,11 +33,11 @@ namespace Demystify
|
||||
|
||||
// Assert
|
||||
var stackTrace = demystifiedException.ToString();
|
||||
stackTrace = ReplaceLineEndings.Replace(stackTrace, "");
|
||||
stackTrace = LineEndingsHelper.RemoveLineEndings(stackTrace);
|
||||
var trace = string.Join("", stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
|
||||
// Remove items that vary between test runners
|
||||
.Where(s =>
|
||||
s != " at Task Demystify.DynamicCompilation.DoesNotPreventStackTrace()+() => { }" &&
|
||||
s != " at Task Ben.Demystifier.Test.DynamicCompilation.DoesNotPreventStackTrace()+() => { }" &&
|
||||
!s.Contains("System.Threading.Tasks.Task.WaitAll")
|
||||
)
|
||||
.Skip(1)
|
||||
@ -47,19 +46,19 @@ namespace Demystify
|
||||
.Replace("<---", "");
|
||||
|
||||
var expected = string.Join("", new[] {
|
||||
" at async Task Demystify.AggregateException.Throw1()",
|
||||
" at async void Demystify.AggregateException.DemystifiesAggregateExceptions()+(?) => { }",
|
||||
" at async Task Ben.Demystifier.Test.AggregateException.Throw1()",
|
||||
" at async void Ben.Demystifier.Test.AggregateException.DemystifiesAggregateExceptions()+(?) => { }",
|
||||
" --- End of inner exception stack trace ---",
|
||||
" at void Demystify.AggregateException.DemystifiesAggregateExceptions()",
|
||||
" at void Ben.Demystifier.Test.AggregateException.DemystifiesAggregateExceptions()",
|
||||
"---> (Inner Exception #0) System.ArgumentException: Value does not fall within the expected range.",
|
||||
" at async Task Demystify.AggregateException.Throw1()",
|
||||
" at async void Demystify.AggregateException.DemystifiesAggregateExceptions()+(?) => { }",
|
||||
" at async Task Ben.Demystifier.Test.AggregateException.Throw1()",
|
||||
" at async void Ben.Demystifier.Test.AggregateException.DemystifiesAggregateExceptions()+(?) => { }",
|
||||
"---> (Inner Exception #1) System.NullReferenceException: Object reference not set to an instance of an object.",
|
||||
" at async Task Demystify.AggregateException.Throw2()",
|
||||
" at async void Demystify.AggregateException.DemystifiesAggregateExceptions()+(?) => { }",
|
||||
" at async Task Ben.Demystifier.Test.AggregateException.Throw2()",
|
||||
" at async void Ben.Demystifier.Test.AggregateException.DemystifiesAggregateExceptions()+(?) => { }",
|
||||
"---> (Inner Exception #2) System.InvalidOperationException: Operation is not valid due to the current state of the object.",
|
||||
" at async Task Demystify.AggregateException.Throw3()",
|
||||
" at async void Demystify.AggregateException.DemystifiesAggregateExceptions()+(?) => { }"});
|
||||
" at async Task Ben.Demystifier.Test.AggregateException.Throw3()",
|
||||
" at async void Ben.Demystifier.Test.AggregateException.DemystifiesAggregateExceptions()+(?) => { }"});
|
||||
|
||||
Assert.Equal(expected, trace);
|
||||
}
|
||||
@ -81,8 +80,5 @@ namespace Demystify
|
||||
await Task.Delay(1).ConfigureAwait(false);
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
|
||||
private Regex ReplaceLineEndings = new Regex(" in [^\n\r]+");
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,9 +7,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20170810-02" />
|
||||
<PackageReference Include="xunit" Version="2.2.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
|
||||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -6,7 +6,7 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Demystify
|
||||
namespace Ben.Demystifier.Test
|
||||
{
|
||||
public class DynamicCompilation
|
||||
{
|
||||
@ -37,12 +37,12 @@ namespace Demystify
|
||||
|
||||
// Assert
|
||||
var stackTrace = demystifiedException.ToString();
|
||||
stackTrace = ReplaceLineEndings.Replace(stackTrace, "");
|
||||
stackTrace = LineEndingsHelper.RemoveLineEndings(stackTrace);
|
||||
var trace = stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
|
||||
// Remove items that vary between test runners
|
||||
.Where(s =>
|
||||
s != " at void System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, object state)" &&
|
||||
s != " at Task Demystify.DynamicCompilation.DoesNotPreventStackTrace()+() => { }"
|
||||
s != " at Task Ben.Demystifier.Test.DynamicCompilation.DoesNotPreventStackTrace()+() => { }"
|
||||
)
|
||||
.ToArray();
|
||||
|
||||
@ -50,10 +50,8 @@ namespace Demystify
|
||||
new[] {
|
||||
"System.ArgumentException: Message",
|
||||
" at void lambda_method(Closure)",
|
||||
" at async Task Demystify.DynamicCompilation.DoesNotPreventStackTrace()"},
|
||||
" at async Task Ben.Demystifier.Test.DynamicCompilation.DoesNotPreventStackTrace()"},
|
||||
trace);
|
||||
}
|
||||
|
||||
private Regex ReplaceLineEndings = new Regex(" in [^\n\r]+");
|
||||
}
|
||||
}
|
||||
|
||||
31
test/Ben.Demystifier.Test/EnhancedStackTraceTests.cs
Normal file
31
test/Ben.Demystifier.Test/EnhancedStackTraceTests.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace Ben.Demystifier.Test
|
||||
{
|
||||
public class EnhancedStackTraceTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(@"file://Sources\MySolution\Foo.cs", @"/MySolution/Foo.cs")]
|
||||
[InlineData(@"d:\Public\Src\Foo.cs", @"d:/Public/Src/Foo.cs")]
|
||||
// To be deterministic, the C# compiler can take a /pathmap command line option.
|
||||
// This option force the compiler to emit the same bits even when their built from the
|
||||
// differrent locations.
|
||||
// The binaries built with the pathmap usually don't have an absolute path,
|
||||
// but have some prefix like \.\.
|
||||
// This test case makes sure that EhancedStackTrace can deal with such kind of paths.
|
||||
[InlineData(@"\.\Public\Src\Foo.cs", @"/./Public/Src/Foo.cs")]
|
||||
public void RelativePathIsConvertedToAnAbsolutePath(string original, string expected)
|
||||
{
|
||||
var converted = EnhancedStackTrace.TryGetFullPath(original);
|
||||
Assert.Equal(expected, NormalizePath(converted));
|
||||
}
|
||||
|
||||
// Used in tests to avoid platform-specific issues.
|
||||
private static string NormalizePath(string path)
|
||||
=> path.Replace("\\", "/");
|
||||
}
|
||||
}
|
||||
14
test/Ben.Demystifier.Test/LineEndingsHelper.cs
Normal file
14
test/Ben.Demystifier.Test/LineEndingsHelper.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Ben.Demystifier.Test
|
||||
{
|
||||
internal static class LineEndingsHelper
|
||||
{
|
||||
private static readonly Regex ReplaceLineEndings = new Regex(" in [^\n\r]+");
|
||||
|
||||
public static string RemoveLineEndings(string original)
|
||||
{
|
||||
return ReplaceLineEndings.Replace(original, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Demystify
|
||||
namespace Ben.Demystifier.Test
|
||||
{
|
||||
public class MixedStack
|
||||
{
|
||||
@ -57,16 +57,16 @@ namespace Demystify
|
||||
|
||||
static List<string> ExpectedCallStack = new List<string>()
|
||||
{
|
||||
"IEnumerable<string> Demystify.MixedStack.Iterator()+MoveNext()",
|
||||
"IEnumerable<string> Ben.Demystifier.Test.MixedStack.Iterator()+MoveNext()",
|
||||
"string string.Join(string separator, IEnumerable<string> values)",
|
||||
"string Demystify.MixedStack+GenericClass<T>.GenericMethod<V>(ref V value)",
|
||||
"async Task<string> Demystify.MixedStack.MethodAsync(int value)",
|
||||
"async Task<string> Demystify.MixedStack.MethodAsync<TValue>(TValue value)",
|
||||
"(string val, bool) Demystify.MixedStack.Method(string value)",
|
||||
"ref string Demystify.MixedStack.RefMethod(string value)",
|
||||
"(string val, bool) Demystify.MixedStack.s_func(string s, bool b)",
|
||||
"void Demystify.MixedStack.s_action(string s, bool b)",
|
||||
"void Demystify.MixedStack.Start((string val, bool) param)"
|
||||
"string Ben.Demystifier.Test.MixedStack+GenericClass<T>.GenericMethod<V>(ref V value)",
|
||||
"async Task<string> Ben.Demystifier.Test.MixedStack.MethodAsync(int value)",
|
||||
"async Task<string> Ben.Demystifier.Test.MixedStack.MethodAsync<TValue>(TValue value)",
|
||||
"(string val, bool) Ben.Demystifier.Test.MixedStack.Method(string value)",
|
||||
"ref string Ben.Demystifier.Test.MixedStack.RefMethod(string value)",
|
||||
"(string val, bool) Ben.Demystifier.Test.MixedStack.s_func(string s, bool b)",
|
||||
"void Ben.Demystifier.Test.MixedStack.s_action(string s, bool b)",
|
||||
"void Ben.Demystifier.Test.MixedStack.Start((string val, bool) param)"
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Demystify
|
||||
namespace Ben.Demystifier.Test
|
||||
{
|
||||
public class NonThrownException
|
||||
{
|
||||
@ -28,14 +28,14 @@ namespace Demystify
|
||||
|
||||
// Assert
|
||||
var stackTrace = demystifiedException.ToString();
|
||||
stackTrace = ReplaceLineEndings.Replace(stackTrace, "");
|
||||
stackTrace = LineEndingsHelper.RemoveLineEndings(stackTrace);
|
||||
var trace = stackTrace.Split(new[]{Environment.NewLine}, StringSplitOptions.None);
|
||||
|
||||
Assert.Equal(
|
||||
new[] {
|
||||
"System.Exception: Exception of type 'System.Exception' was thrown. ---> System.Exception: Exception of type 'System.Exception' was thrown.",
|
||||
" at Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()+() => { }",
|
||||
" at async Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()",
|
||||
" at Task Ben.Demystifier.Test.NonThrownException.DoesNotPreventThrowStackTrace()+() => { }",
|
||||
" at async Task Ben.Demystifier.Test.NonThrownException.DoesNotPreventThrowStackTrace()",
|
||||
" --- End of inner exception stack trace ---"},
|
||||
trace);
|
||||
|
||||
@ -51,16 +51,16 @@ namespace Demystify
|
||||
|
||||
// Assert
|
||||
stackTrace = demystifiedException.ToString();
|
||||
stackTrace = ReplaceLineEndings.Replace(stackTrace, "");
|
||||
stackTrace = LineEndingsHelper.RemoveLineEndings(stackTrace);
|
||||
trace = stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
|
||||
|
||||
Assert.Equal(
|
||||
new[] {
|
||||
"System.Exception: Exception of type 'System.Exception' was thrown. ---> System.Exception: Exception of type 'System.Exception' was thrown.",
|
||||
" at Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()+() => { }",
|
||||
" at async Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()",
|
||||
" at Task Ben.Demystifier.Test.NonThrownException.DoesNotPreventThrowStackTrace()+() => { }",
|
||||
" at async Task Ben.Demystifier.Test.NonThrownException.DoesNotPreventThrowStackTrace()",
|
||||
" --- End of inner exception stack trace ---",
|
||||
" at async Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()"
|
||||
" at async Task Ben.Demystifier.Test.NonThrownException.DoesNotPreventThrowStackTrace()"
|
||||
},
|
||||
trace);
|
||||
}
|
||||
@ -76,7 +76,7 @@ namespace Demystify
|
||||
|
||||
// Assert
|
||||
var stackTrace = est.ToString();
|
||||
stackTrace = ReplaceLineEndings.Replace(stackTrace, "");
|
||||
stackTrace = LineEndingsHelper.RemoveLineEndings(stackTrace);
|
||||
var trace = stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
|
||||
// Remove Full framework entries
|
||||
.Where(s => !s.StartsWith(" at bool System.Threading._ThreadPoolWaitCallbac") &&
|
||||
@ -88,7 +88,5 @@ namespace Demystify
|
||||
" at bool System.Threading.ThreadPoolWorkQueue.Dispatch()"},
|
||||
trace);
|
||||
}
|
||||
|
||||
private Regex ReplaceLineEndings = new Regex(" in [^\n\r]+");
|
||||
}
|
||||
}
|
||||
|
||||
27
test/Ben.Demystifier.Test/ReflectionHelperTests.cs
Normal file
27
test/Ben.Demystifier.Test/ReflectionHelperTests.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Diagnostics.Internal;
|
||||
using Xunit;
|
||||
|
||||
namespace Ben.Demystifier.Test
|
||||
{
|
||||
public class ReflectionHelperTest
|
||||
{
|
||||
[Fact]
|
||||
public void IsValueTupleReturnsTrueForTupleWith1Element()
|
||||
{
|
||||
Assert.True(typeof(ValueTuple<int>).IsValueTuple());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValueTupleReturnsTrueForTupleWith1ElementWithOpenedType()
|
||||
{
|
||||
Assert.True(typeof(ValueTuple<>).IsValueTuple());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValueTupleReturnsTrueForTupleWith6ElementsWithOpenedType()
|
||||
{
|
||||
Assert.True(typeof(ValueTuple<,,,,,>).IsValueTuple());
|
||||
}
|
||||
}
|
||||
}
|
||||
48
test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs
Normal file
48
test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Ben.Demystifier.Test
|
||||
{
|
||||
public sealed class ToDemystifiedStringTests
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public ToDemystifiedStringTests(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DemystifyShouldNotAffectTheOriginalStackTrace()
|
||||
{
|
||||
try
|
||||
{
|
||||
SimpleMethodThatThrows().Wait();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var original = e.ToString();
|
||||
var stringDemystified = e.ToStringDemystified();
|
||||
|
||||
_output.WriteLine("Demystified: ");
|
||||
_output.WriteLine(stringDemystified);
|
||||
|
||||
_output.WriteLine("Original: ");
|
||||
var afterDemystified = e.ToString();
|
||||
_output.WriteLine(afterDemystified);
|
||||
|
||||
Assert.Equal(original, afterDemystified);
|
||||
}
|
||||
|
||||
async Task SimpleMethodThatThrows()
|
||||
{
|
||||
throw new InvalidOperationException("message");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
76
test/Ben.Demystifier.Test/TuplesTests.cs
Normal file
76
test/Ben.Demystifier.Test/TuplesTests.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Ben.Demystifier.Test
|
||||
{
|
||||
public class TuplesTests
|
||||
{
|
||||
[Fact]
|
||||
public void DemistifiesAsyncMethodWithTuples()
|
||||
{
|
||||
Exception demystifiedException = null;
|
||||
|
||||
try
|
||||
{
|
||||
AsyncThatReturnsTuple().GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
demystifiedException = ex.Demystify();
|
||||
}
|
||||
|
||||
// Assert
|
||||
var stackTrace = demystifiedException.ToString();
|
||||
stackTrace = LineEndingsHelper.RemoveLineEndings(stackTrace);
|
||||
var trace = string.Join("", stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries));
|
||||
|
||||
var expected = string.Join("", new[] {
|
||||
"System.ArgumentException: Value does not fall within the expected range.",
|
||||
" at async Task<(int left, int right)> Ben.Demystifier.Test.TuplesTests.AsyncThatReturnsTuple()",
|
||||
" at void Ben.Demystifier.Test.TuplesTests.DemistifiesAsyncMethodWithTuples()"});
|
||||
|
||||
Assert.Equal(expected, trace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DemistifiesListOfTuples()
|
||||
{
|
||||
Exception demystifiedException = null;
|
||||
|
||||
try
|
||||
{
|
||||
ListOfTuples();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
demystifiedException = ex.Demystify();
|
||||
}
|
||||
|
||||
// Assert
|
||||
var stackTrace = demystifiedException.ToString();
|
||||
stackTrace = LineEndingsHelper.RemoveLineEndings(stackTrace);
|
||||
var trace = string.Join("", stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries));
|
||||
|
||||
var expected = string.Join("", new[] {
|
||||
"System.ArgumentException: Value does not fall within the expected range.",
|
||||
" at List<(int left, int right)> Ben.Demystifier.Test.TuplesTests.ListOfTuples()",
|
||||
" at void Ben.Demystifier.Test.TuplesTests.DemistifiesListOfTuples()"});
|
||||
|
||||
Assert.Equal(expected, trace);
|
||||
}
|
||||
|
||||
async Task<(int left, int right)> AsyncThatReturnsTuple()
|
||||
{
|
||||
await Task.Delay(1).ConfigureAwait(false);
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
List<(int left, int right)> ListOfTuples()
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
17
version.json
Normal file
17
version.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": "0.1",
|
||||
"publicReleaseRefSpec": [
|
||||
"^refs/heads/master$", // we release out of master
|
||||
"^refs/heads/dev$", // we release out of develop
|
||||
"^refs/tags/v\\d+\\.\\d+" // we also release tags starting with vN.N
|
||||
],
|
||||
"nugetPackageVersion":{
|
||||
"semVer": 2
|
||||
},
|
||||
"cloudBuild": {
|
||||
"buildNumber": {
|
||||
"enabled": true,
|
||||
"setVersionVariables": true
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user