diff --git a/.gitignore b/.gitignore
index 940794e..a1b7e6e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,7 @@ bld/
[Bb]in/
[Oo]bj/
[Ll]og/
+BenchmarkDotNet.Artifacts/
# Visual Studio 2015 cache/options directory
.vs/
diff --git a/Ben.Demystifier.sln b/Ben.Demystifier.sln
index dbecaf3..5945254 100644
--- a/Ben.Demystifier.sln
+++ b/Ben.Demystifier.sln
@@ -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}
diff --git a/README.md b/README.md
index f2f2946..944d392 100644
--- a/README.md
+++ b/README.md
@@ -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
+```
+Note: we're only kicking off via `netcoreapp2.0`, benchmarks will run for all configured platforms like `net462`.
diff --git a/appveyor.yml b/appveyor.yml
index 75a183e..9f08f22 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -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
@@ -31,4 +29,4 @@ deploy: off
artifacts:
- path: artifacts/build
- path: artifacts/packages
-- path: artifacts/test
\ No newline at end of file
+- path: artifacts/test
diff --git a/build.ps1 b/build.ps1
index d0bacc3..7b5ffcc 100644
--- a/build.ps1
+++ b/build.ps1
@@ -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 }
\ No newline at end of file
+exec { & dotnet pack .\src\Ben.Demystifier -c Release -o .\artifacts }
diff --git a/directory.build.props b/directory.build.props
new file mode 100644
index 0000000..44c7d4e
--- /dev/null
+++ b/directory.build.props
@@ -0,0 +1,5 @@
+
+
+ 7.2
+
+
\ No newline at end of file
diff --git a/sample/StackTrace/StackTrace.csproj b/sample/StackTrace/StackTrace.csproj
index c74a7c5..3e72966 100644
--- a/sample/StackTrace/StackTrace.csproj
+++ b/sample/StackTrace/StackTrace.csproj
@@ -5,14 +5,6 @@
netcoreapp2.0
-
- 7.2
-
-
-
- 7.2
-
-
diff --git a/src/Ben.Demystifier/Ben.Demystifier.csproj b/src/Ben.Demystifier/Ben.Demystifier.csproj
index e43a689..c1cd196 100644
--- a/src/Ben.Demystifier/Ben.Demystifier.csproj
+++ b/src/Ben.Demystifier/Ben.Demystifier.csproj
@@ -9,17 +9,19 @@
https://github.com/benaadams/Ben.Demystifier
https://github.com/benaadams/Ben.Demystifier/blob/master/LICENSE
git
- true
true
- 0.0.7
+ embedded
netstandard2.0;net45
- 7.1
+ true
+ key.snk
+
+
1.5.0
diff --git a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs
index 5b6e1d7..d2acf07 100644
--- a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs
+++ b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs
@@ -19,7 +19,6 @@ namespace System.Diagnostics
{
public partial class EnhancedStackTrace
{
-
private static List 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 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 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)
diff --git a/src/Ben.Demystifier/EnhancedStackTrace.cs b/src/Ben.Demystifier/EnhancedStackTrace.cs
index 181bfef..d93b014 100644
--- a/src/Ben.Demystifier/EnhancedStackTrace.cs
+++ b/src/Ben.Demystifier/EnhancedStackTrace.cs
@@ -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 GetEnumerator() => EnumerableIList.Create(_frames);
IEnumerator IEnumerable.GetEnumerator() => _frames.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _frames.GetEnumerator();
+
+ ///
+ /// Tries to convert a given to a full path.
+ /// Returns original value if the conversion isn't possible or a given path is relative.
+ ///
+ 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;
+ }
}
}
diff --git a/src/Ben.Demystifier/ExceptionExtentions.cs b/src/Ben.Demystifier/ExceptionExtentions.cs
index b4ab1b9..23203d1 100644
--- a/src/Ben.Demystifier/ExceptionExtentions.cs
+++ b/src/Ben.Demystifier/ExceptionExtentions.cs
@@ -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
{
+ ///
public static class ExceptionExtentions
{
private static readonly FieldInfo stackTraceString = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);
public static T Demystify(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);
+
+ ///
+ /// Demystifies the given and tracks the original stack traces for the whole exception tree.
+ ///
+ private static T Demystify(this T exception, Dictionary 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;
}
+
+ ///
+ /// Gets demystified string representation of the .
+ ///
+ ///
+ /// method mutates the exception instance that can cause
+ /// issues if a system relies on the stack trace be in the specific form.
+ /// Unlike this method is pure. It calls first,
+ /// computes a demystified string representation and then restores the original state of the exception back.
+ ///
+ [Contracts.Pure]
+ public static string ToStringDemystified(this Exception exception)
+ {
+ try
+ {
+ var originalStacks = new Dictionary();
+ 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();
+ }
}
}
diff --git a/src/Ben.Demystifier/Internal/ReflectionHelper.cs b/src/Ben.Demystifier/Internal/ReflectionHelper.cs
new file mode 100644
index 0000000..9306532
--- /dev/null
+++ b/src/Ben.Demystifier/Internal/ReflectionHelper.cs
@@ -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
+{
+ ///
+ /// A helper class that contains utilities methods for dealing with reflection.
+ ///
+ public static class ReflectionHelper
+ {
+ ///
+ /// Returns true if is System.Runtime.CompilerServices.IsReadOnlyAttribute.
+ ///
+ public static bool IsReadOnlyAttribute(this Type type)
+ {
+ return type.Namespace == "System.Runtime.CompilerServices" && type.Name == "IsReadOnlyAttribute";
+ }
+
+ ///
+ /// Returns true if the is a value tuple type.
+ ///
+ public static bool IsValueTuple(this Type type)
+ {
+ return type.Namespace == "System" && type.Name.Contains("ValueTuple`");
+ }
+ }
+}
diff --git a/src/Ben.Demystifier/TypeNameHelper.cs b/src/Ben.Demystifier/TypeNameHelper.cs
index 67c98f5..a4ca3a8 100644
--- a/src/Ben.Demystifier/TypeNameHelper.cs
+++ b/src/Ben.Demystifier/TypeNameHelper.cs
@@ -43,6 +43,22 @@ namespace System.Diagnostics
return builder.ToString();
}
+ ///
+ /// Returns a name of given generic type without '`'.
+ ///
+ 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)
diff --git a/src/Ben.Demystifier/key.snk b/src/Ben.Demystifier/key.snk
new file mode 100644
index 0000000..9367ae1
Binary files /dev/null and b/src/Ben.Demystifier/key.snk differ
diff --git a/test/Ben.Demystifier.Benchmarks/Ben.Demystifier.Benchmarks.csproj b/test/Ben.Demystifier.Benchmarks/Ben.Demystifier.Benchmarks.csproj
new file mode 100644
index 0000000..75d99f0
--- /dev/null
+++ b/test/Ben.Demystifier.Benchmarks/Ben.Demystifier.Benchmarks.csproj
@@ -0,0 +1,11 @@
+
+
+ netcoreapp2.0;net462
+ Release
+ Exe
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/Ben.Demystifier.Benchmarks/Exceptions.cs b/test/Ben.Demystifier.Benchmarks/Exceptions.cs
new file mode 100644
index 0000000..0bc8881
--- /dev/null
+++ b/test/Ben.Demystifier.Benchmarks/Exceptions.cs
@@ -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();
+ }
+}
diff --git a/test/Ben.Demystifier.Benchmarks/Program.cs b/test/Ben.Demystifier.Benchmarks/Program.cs
new file mode 100644
index 0000000..4b8a7e4
--- /dev/null
+++ b/test/Ben.Demystifier.Benchmarks/Program.cs
@@ -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());
+ }
+}
diff --git a/test/Ben.Demystifier.Test/AggregateException.cs b/test/Ben.Demystifier.Test/AggregateException.cs
index b2a8667..3c0034a 100644
--- a/test/Ben.Demystifier.Test/AggregateException.cs
+++ b/test/Ben.Demystifier.Test/AggregateException.cs
@@ -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]+");
}
}
diff --git a/test/Ben.Demystifier.Test/Ben.Demystifier.Test.csproj b/test/Ben.Demystifier.Test/Ben.Demystifier.Test.csproj
index 9b4760b..9400fa6 100644
--- a/test/Ben.Demystifier.Test/Ben.Demystifier.Test.csproj
+++ b/test/Ben.Demystifier.Test/Ben.Demystifier.Test.csproj
@@ -1,5 +1,5 @@
-
+
netcoreapp2.0;net46
@@ -7,9 +7,8 @@
-
-
-
+
+
diff --git a/test/Ben.Demystifier.Test/DynamicCompilation.cs b/test/Ben.Demystifier.Test/DynamicCompilation.cs
index d05f3d6..3731011 100644
--- a/test/Ben.Demystifier.Test/DynamicCompilation.cs
+++ b/test/Ben.Demystifier.Test/DynamicCompilation.cs
@@ -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]+");
}
}
diff --git a/test/Ben.Demystifier.Test/EnhancedStackTraceTests.cs b/test/Ben.Demystifier.Test/EnhancedStackTraceTests.cs
new file mode 100644
index 0000000..c00acc9
--- /dev/null
+++ b/test/Ben.Demystifier.Test/EnhancedStackTraceTests.cs
@@ -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("\\", "/");
+ }
+}
diff --git a/test/Ben.Demystifier.Test/LineEndingsHelper.cs b/test/Ben.Demystifier.Test/LineEndingsHelper.cs
new file mode 100644
index 0000000..8a475f2
--- /dev/null
+++ b/test/Ben.Demystifier.Test/LineEndingsHelper.cs
@@ -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, "");
+ }
+ }
+}
diff --git a/test/Ben.Demystifier.Test/MixedStack.cs b/test/Ben.Demystifier.Test/MixedStack.cs
index c98db5d..0fde176 100644
--- a/test/Ben.Demystifier.Test/MixedStack.cs
+++ b/test/Ben.Demystifier.Test/MixedStack.cs
@@ -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 ExpectedCallStack = new List()
{
- "IEnumerable Demystify.MixedStack.Iterator()+MoveNext()",
+ "IEnumerable Ben.Demystifier.Test.MixedStack.Iterator()+MoveNext()",
"string string.Join(string separator, IEnumerable values)",
- "string Demystify.MixedStack+GenericClass.GenericMethod(ref V value)",
- "async Task Demystify.MixedStack.MethodAsync(int value)",
- "async Task Demystify.MixedStack.MethodAsync(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.GenericMethod(ref V value)",
+ "async Task Ben.Demystifier.Test.MixedStack.MethodAsync(int value)",
+ "async Task Ben.Demystifier.Test.MixedStack.MethodAsync(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)"
};
diff --git a/test/Ben.Demystifier.Test/NonThrownException.cs b/test/Ben.Demystifier.Test/NonThrownException.cs
index 643ff88..ef5abf2 100644
--- a/test/Ben.Demystifier.Test/NonThrownException.cs
+++ b/test/Ben.Demystifier.Test/NonThrownException.cs
@@ -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]+");
}
}
diff --git a/test/Ben.Demystifier.Test/ReflectionHelperTests.cs b/test/Ben.Demystifier.Test/ReflectionHelperTests.cs
new file mode 100644
index 0000000..5773e6b
--- /dev/null
+++ b/test/Ben.Demystifier.Test/ReflectionHelperTests.cs
@@ -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).IsValueTuple());
+ }
+
+ [Fact]
+ public void IsValueTupleReturnsTrueForTupleWith1ElementWithOpenedType()
+ {
+ Assert.True(typeof(ValueTuple<>).IsValueTuple());
+ }
+
+ [Fact]
+ public void IsValueTupleReturnsTrueForTupleWith6ElementsWithOpenedType()
+ {
+ Assert.True(typeof(ValueTuple<,,,,,>).IsValueTuple());
+ }
+ }
+}
diff --git a/test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs b/test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs
new file mode 100644
index 0000000..47ff7e2
--- /dev/null
+++ b/test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs
@@ -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");
+ }
+ }
+ }
+}
diff --git a/test/Ben.Demystifier.Test/TuplesTests.cs b/test/Ben.Demystifier.Test/TuplesTests.cs
new file mode 100644
index 0000000..698336e
--- /dev/null
+++ b/test/Ben.Demystifier.Test/TuplesTests.cs
@@ -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();
+ }
+ }
+}
diff --git a/version.json b/version.json
new file mode 100644
index 0000000..2a116e5
--- /dev/null
+++ b/version.json
@@ -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
+ }
+ }
+}