Merge pull request #17 from benaadams/aggregate

AggregateException + Full framework testing
This commit is contained in:
Ben Adams 2017-11-13 13:16:48 +01:00 committed by GitHub
commit afb07c740e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 181 additions and 35 deletions

View File

@ -16,9 +16,11 @@ class Program
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine(ex);
exception = ex.Demystify(); exception = ex.Demystify();
} }
Console.WriteLine();
Console.WriteLine(exception); Console.WriteLine(exception);
} }
@ -63,9 +65,14 @@ class Program
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static async Task<string> MethodAsync<TValue>(TValue value) static async Task<string> MethodAsync<TValue>(TValue value)
{
return await MethodLocalAsync();
async Task<string> MethodLocalAsync()
{ {
return await MethodAsync(1); return await MethodAsync(1);
} }
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static void RunAction(Action<object> lambda, object state) static void RunAction(Action<object> lambda, object state)

View File

@ -12,6 +12,7 @@ using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices; using System.Runtime.ExceptionServices;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace System.Diagnostics namespace System.Diagnostics
@ -297,6 +298,17 @@ namespace System.Diagnostics
candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName);
if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) return true; if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) return true;
if (methodName == ".cctor")
{
candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName);
foreach (var cctor in candidateConstructors)
{
method = cctor;
type = dt;
return true;
}
}
return false; return false;
} }
@ -321,7 +333,6 @@ namespace System.Diagnostics
} }
} }
var rawIL = nethodBody?.GetILAsByteArray(); var rawIL = nethodBody?.GetILAsByteArray();
if (rawIL == null) continue; if (rawIL == null) continue;
@ -342,17 +353,9 @@ namespace System.Diagnostics
return true; return true;
} }
} }
else if (reader.Operand is Type t)
{
if (t == type)
{
method = candidateMethod;
type = method.DeclaringType;
return true;
}
}
} }
} }
return false; return false;
} }
@ -584,7 +587,28 @@ namespace System.Diagnostics
{ {
return false; return false;
} }
if (type == typeof(Task) && method.Name == "ExecuteWithThreadLocal") if (type == typeof(Task))
{
switch (method.Name)
{
case "ExecuteWithThreadLocal":
case "Execute":
case "ExecutionContextCallback":
case "ExecuteEntry":
case "InnerInvoke":
return false;
}
}
if (type == typeof(ExecutionContext))
{
switch (method.Name)
{
case "RunInternal":
case "Run":
return false;
}
}
if (type.Namespace == "System.Threading" && (type.Name?.StartsWith("_") ?? false))
{ {
return false; return false;
} }

View File

@ -1,6 +1,7 @@
// 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic.Enumerable;
using System.Reflection; using System.Reflection;
namespace System.Diagnostics namespace System.Diagnostics
@ -20,6 +21,14 @@ namespace System.Diagnostics
stackTraceString.SetValue(exception, stackTrace.ToString()); stackTraceString.SetValue(exception, stackTrace.ToString());
} }
if (exception is AggregateException aggEx)
{
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
{
ex.Demystify();
}
}
exception.InnerException?.Demystify(); exception.InnerException?.Demystify();
} }
catch catch

View File

@ -19,7 +19,7 @@ namespace System.Diagnostics.Internal
public OpCode OpCode { get; private set; } public OpCode OpCode { get; private set; }
public int MetadataToken { get; private set; } public int MetadataToken { get; private set; }
public object Operand { get; private set; } public MemberInfo Operand { get; private set; }
public bool Read(MethodBase methodInfo) public bool Read(MethodBase methodInfo)
{ {
@ -35,13 +35,13 @@ namespace System.Diagnostics.Internal
OpCode ReadOpCode() OpCode ReadOpCode()
{ {
byte instruction = ReadByte(); byte instruction = ReadByte();
if (instruction != 254) if (instruction < 254)
return singleByteOpCode[instruction]; return singleByteOpCode[instruction];
else else
return doubleByteOpCode[ReadByte()]; return doubleByteOpCode[ReadByte()];
} }
object ReadOperand(OpCode code, MethodBase methodInfo) MemberInfo ReadOperand(OpCode code, MethodBase methodInfo)
{ {
MetadataToken = 0; MetadataToken = 0;
switch (code.OperandType) switch (code.OperandType)
@ -50,12 +50,24 @@ namespace System.Diagnostics.Internal
MetadataToken = ReadInt(); MetadataToken = ReadInt();
Type[] methodArgs = null; Type[] methodArgs = null;
if (methodInfo.GetType() != typeof(ConstructorInfo) && !methodInfo.GetType().IsSubclassOf(typeof(ConstructorInfo))) if (methodInfo.GetType() != typeof(ConstructorInfo) && !methodInfo.GetType().IsSubclassOf(typeof(ConstructorInfo)))
{
methodArgs = methodInfo.GetGenericArguments(); methodArgs = methodInfo.GetGenericArguments();
}
Type[] typeArgs = null; Type[] typeArgs = null;
if (methodInfo.DeclaringType != null) if (methodInfo.DeclaringType != null)
{
typeArgs = methodInfo.DeclaringType.GetGenericArguments(); typeArgs = methodInfo.DeclaringType.GetGenericArguments();
}
try
{
return methodInfo.Module.ResolveMember(MetadataToken, typeArgs, methodArgs); return methodInfo.Module.ResolveMember(MetadataToken, typeArgs, methodArgs);
} }
catch
{
// Can return System.ArgumentException : Token xxx is not a valid MemberInfo token in the scope of module xxx.dll
return null;
}
}
return null; return null;
} }

View File

@ -0,0 +1,88 @@
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
{
public class AggregateException
{
[Fact]
public void DemystifiesAggregateExceptions()
{
Exception demystifiedException = null;
try
{
var tasks = new List<Task>
{
Task.Run(async () => await Throw1()),
Task.Run(async () => await Throw2()),
Task.Run(async () => await Throw3())
};
Task.WaitAll(tasks.ToArray());
}
catch (Exception ex)
{
demystifiedException = ex.Demystify();
}
// Assert
var stackTrace = demystifiedException.ToString();
stackTrace = ReplaceLineEndings.Replace(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.Contains("System.Threading.Tasks.Task.WaitAll")
)
.Skip(1)
.ToArray())
// Remove Full framework back arrow
.Replace("<---", "");
var expected = string.Join("", new[] {
" at async Task Demystify.AggregateException.Throw1()",
" at async void Demystify.AggregateException.DemystifiesAggregateExceptions()+(?)=>{}",
" --- End of inner exception stack trace ---",
" at void Demystify.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()+(?)=>{}",
"---> (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()+(?)=>{}",
"---> (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()+(?)=>{}"});
Assert.Equal(expected, trace);
}
async Task Throw1()
{
await Task.Delay(1).ConfigureAwait(false);
throw new ArgumentException();
}
async Task Throw2()
{
await Task.Delay(1).ConfigureAwait(false);
throw new NullReferenceException();
}
async Task Throw3()
{
await Task.Delay(1).ConfigureAwait(false);
throw new InvalidOperationException();
}
private Regex ReplaceLineEndings = new Regex(" in [^\n\r]+");
}
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFrameworks>netcoreapp2.0;net46</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>

View File

@ -38,7 +38,7 @@ namespace Demystify
// Assert // Assert
var stackTrace = demystifiedException.ToString(); var stackTrace = demystifiedException.ToString();
stackTrace = ReplaceLineEndings.Replace(stackTrace, ""); stackTrace = ReplaceLineEndings.Replace(stackTrace, "");
var trace = stackTrace.Split(Environment.NewLine) var trace = stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
// Remove items that vary between test runners // Remove items that vary between test runners
.Where(s => .Where(s =>
s != " at void System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, object state)" && s != " at void System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, object state)" &&

View File

@ -22,16 +22,21 @@ namespace Demystify
stackFrame => stackFrame.MethodInfo.ToString() stackFrame => stackFrame.MethodInfo.ToString()
) )
// Remove Framework method that can be optimized out (inlined) // Remove Framework method that can be optimized out (inlined)
.Where(methodName => methodName != "System.Collections.Generic.List<T>+Enumerator.MoveNext()") .Where(methodName => !methodName.StartsWith("bool System.Collections.Generic.List<T>+"));
// Don't include this method as call stack shared between multiple tests
.SkipLast(1); var count = methodNames.Count();
methodNames = methodNames.Take(count - 1);
foreach (var method in methodNames)
{
Console.WriteLine(method.ToString());
}
// Assert // Assert
Assert.Equal (ExpectedCallStack, methodNames.ToList()); var expected = ExpectedCallStack.ToArray();
var trace = methodNames.ToArray();
Assert.Equal(expected.Length, trace.Length);
for (var i = 0; i < expected.Length; i++)
{
Assert.Equal(expected[i], trace[i]);
}
} }
@ -52,7 +57,6 @@ namespace Demystify
static List<string> ExpectedCallStack = new List<string>() static List<string> ExpectedCallStack = new List<string>()
{ {
"bool System.Collections.Generic.List<T>+Enumerator.MoveNextRare()",
"IEnumerable<string> Demystify.MixedStack.Iterator()+MoveNext()", "IEnumerable<string> Demystify.MixedStack.Iterator()+MoveNext()",
"string string.Join(string separator, IEnumerable<string> values)", "string string.Join(string separator, IEnumerable<string> values)",
"string Demystify.MixedStack+GenericClass<T>.GenericMethod<V>(ref V value)", "string Demystify.MixedStack+GenericClass<T>.GenericMethod<V>(ref V value)",

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xunit; using Xunit;
@ -28,13 +29,12 @@ namespace Demystify
// Assert // Assert
var stackTrace = demystifiedException.ToString(); var stackTrace = demystifiedException.ToString();
stackTrace = ReplaceLineEndings.Replace(stackTrace, ""); stackTrace = ReplaceLineEndings.Replace(stackTrace, "");
var trace = stackTrace.Split(Environment.NewLine); var trace = stackTrace.Split(new[]{Environment.NewLine}, StringSplitOptions.None);
Assert.Equal( Assert.Equal(
new[] { new[] {
"System.Exception: Exception of type 'System.Exception' was thrown. ---> System.Exception: Exception of type 'System.Exception' was thrown.", "System.Exception: Exception of type 'System.Exception' was thrown. ---> System.Exception: Exception of type 'System.Exception' was thrown.",
" at Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()+()=>{}", " at Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()+()=>{}",
" at void System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, object state)",
" at async Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()", " at async Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()",
" --- End of inner exception stack trace ---"}, " --- End of inner exception stack trace ---"},
trace); trace);
@ -52,13 +52,12 @@ namespace Demystify
// Assert // Assert
stackTrace = demystifiedException.ToString(); stackTrace = demystifiedException.ToString();
stackTrace = ReplaceLineEndings.Replace(stackTrace, ""); stackTrace = ReplaceLineEndings.Replace(stackTrace, "");
trace = stackTrace.Split(Environment.NewLine); trace = stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
Assert.Equal( Assert.Equal(
new[] { new[] {
"System.Exception: Exception of type 'System.Exception' was thrown. ---> System.Exception: Exception of type 'System.Exception' was thrown.", "System.Exception: Exception of type 'System.Exception' was thrown. ---> System.Exception: Exception of type 'System.Exception' was thrown.",
" at Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()+()=>{}", " at Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()+()=>{}",
" at void System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, object state)",
" at async Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()", " at async Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()",
" --- End of inner exception stack trace ---", " --- End of inner exception stack trace ---",
" at async Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()" " at async Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()"
@ -78,11 +77,14 @@ namespace Demystify
// Assert // Assert
var stackTrace = est.ToString(); var stackTrace = est.ToString();
stackTrace = ReplaceLineEndings.Replace(stackTrace, ""); stackTrace = ReplaceLineEndings.Replace(stackTrace, "");
var trace = stackTrace.Split(Environment.NewLine); var trace = stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
// Remove Full framework entries
.Where(s => !s.StartsWith(" at bool System.Threading._ThreadPoolWaitCallbac") &&
!s.StartsWith(" at void System.Threading.Tasks.Task.System.Thre"));
Assert.Equal( Assert.Equal(
new[] { new[] {
" at void System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, object state)",
" at bool System.Threading.ThreadPoolWorkQueue.Dispatch()"}, " at bool System.Threading.ThreadPoolWorkQueue.Dispatch()"},
trace); trace);
} }