// 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.Linq; using System.Linq.Expressions; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.Extensions.StackTrace.Sources; using ThrowingLibrary; using Xunit; namespace Microsoft.Extensions.Internal { public class StackTraceHelperTest { [Fact] public void StackTraceHelper_IncludesLineNumbersForFiles() { // Arrange Exception exception = null; try { // Throwing an exception in the current assembly always seems to populate the full stack // trace regardless of symbol type. Crossing assembly boundaries ensures PortablePdbReader gets used // on desktop. Thrower.Throw(); } catch (Exception ex) { exception = ex; } // Act var stackFrames = StackTraceHelper.GetFrames(exception); // Assert Assert.Collection(stackFrames, frame => { Assert.Contains("Thrower.cs", frame.FilePath); Assert.Equal(17, frame.LineNumber); }, frame => { Assert.Contains("StackTraceHelperTest.cs", frame.FilePath); }); } [Fact] public void StackTraceHelper_PrettyPrintsStackTraceForGenericMethods() { // Arrange var exception = Record.Exception(() => GenericMethod(null)); // Act var stackFrames = StackTraceHelper.GetFrames(exception); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.GenericMethod(T val)", methods[0]); } [Fact] public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithOutParameters() { // Arrange var exception = Record.Exception(() => MethodWithOutParameter(out var value)); // Act var stackFrames = StackTraceHelper.GetFrames(exception); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithOutParameter(out int value)", methods[0]); } [Fact] public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithGenericOutParameters() { // Arrange var exception = Record.Exception(() => MethodWithGenericOutParameter("Test", out int value)); // Act var stackFrames = StackTraceHelper.GetFrames(exception); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithGenericOutParameter(string a, out TVal value)", methods[0]); } [Fact] public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithRefParameters() { // Arrange var value = 0; var exception = Record.Exception(() => MethodWithRefParameter(ref value)); // Act var stackFrames = StackTraceHelper.GetFrames(exception); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithRefParameter(ref int value)", methods[0]); } [Fact] public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithGenericRefParameters() { // Arrange var value = 0; var exception = Record.Exception(() => MethodWithGenericRefParameter(ref value)); // Act var stackFrames = StackTraceHelper.GetFrames(exception); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithGenericRefParameter(ref TVal value)", methods[0]); } [Fact] public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithNullableParameters() { // Arrange var value = 0; var exception = Record.Exception(() => MethodWithNullableParameter(value)); // Act var stackFrames = StackTraceHelper.GetFrames(exception); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithNullableParameter(Nullable value)", methods[0]); } [Fact] public void StackTraceHelper_PrettyPrintsStackTraceForMethodsOnGenericTypes() { // Arrange var exception = Record.Exception(() => new GenericClass().Throw(0)); // Act var stackFrames = StackTraceHelper.GetFrames(exception); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest+GenericClass.Throw(T parameter)", methods[0]); } [Fact] public void StackTraceHelper_ProducesReadableOutput() { // Arrange var expectedCallStack = new List() { "Microsoft.Extensions.Internal.StackTraceHelperTest.Iterator()+MoveNext()", "string.Join(string separator, IEnumerable values)", "Microsoft.Extensions.Internal.StackTraceHelperTest+GenericClass.GenericMethod(ref V value)", "Microsoft.Extensions.Internal.StackTraceHelperTest.MethodAsync(int value)", "Microsoft.Extensions.Internal.StackTraceHelperTest.MethodAsync(TValue value)", "Microsoft.Extensions.Internal.StackTraceHelperTest.Method(string value)", "Microsoft.Extensions.Internal.StackTraceHelperTest.StackTraceHelper_ProducesReadableOutput()", }; Exception exception = null; try { Method("test"); } catch (Exception ex) { exception = ex; } // Act var stackFrames = StackTraceHelper.GetFrames(exception); var methodNames = stackFrames.Select(stackFrame => stackFrame.MethodDisplayInfo.ToString()).ToArray(); // Assert Assert.Equal(expectedCallStack, methodNames); } [Fact] public void StackTraceHelper_DoesNotIncludeInstanceMethodsOnTypesWithStackTraceHiddenAttribute() { // Arrange var exception = Record.Exception(() => InvokeMethodOnTypeWithStackTraceHiddenAttribute()); // Act var stackFrames = StackTraceHelper.GetFrames(exception); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]); Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.InvokeMethodOnTypeWithStackTraceHiddenAttribute()", methods[1]); } [Fact] public void StackTraceHelper_DoesNotIncludeStaticMethodsOnTypesWithStackTraceHiddenAttribute() { // Arrange var exception = Record.Exception(() => InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute()); // Act var stackFrames = StackTraceHelper.GetFrames(exception); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]); Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute()", methods[1]); } [Fact] public void StackTraceHelper_DoesNotIncludeMethodsWithStackTraceHiddenAttribute() { // Arrange var exception = Record.Exception(() => new TypeWithMethodWithStackTraceHiddenAttribute().Throw()); // Act var stackFrames = StackTraceHelper.GetFrames(exception); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]); Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest+TypeWithMethodWithStackTraceHiddenAttribute.Throw()", methods[1]); } [Fact] public void GetFrames_DoesNotFailForDynamicallyGeneratedAssemblies() { // Arrange var action = (Action)Expression.Lambda( Expression.Throw( Expression.New(typeof(Exception)))).Compile(); var exception = Record.Exception(action); // Act var frames = StackTraceHelper.GetFrames(exception).ToArray(); // Assert var frame = frames[0]; Assert.Null(frame.FilePath); Assert.Equal($"lambda_method(Closure )", frame.MethodDisplayInfo.ToString()); } [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] async Task MethodAsync(int value) { await Task.Delay(0); return GenericClass.GenericMethod(ref value); } [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] async Task MethodAsync(TValue value) { return await MethodAsync(1); } [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] string Method(string value) { return MethodAsync(value).GetAwaiter().GetResult(); } [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] static IEnumerable Iterator() { yield return "Success"; throw new Exception(); } [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] void MethodWithOutParameter(out int value) => throw new Exception(); [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] void MethodWithGenericOutParameter(string a, out TVal value) => throw new Exception(); [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] void MethodWithRefParameter(ref int value) => throw new Exception(); [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] void MethodWithGenericRefParameter(ref TVal value) => throw new Exception(); [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] void MethodWithNullableParameter(int? value) => throw new Exception(); [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] void InvokeMethodOnTypeWithStackTraceHiddenAttribute() => new TypeWithStackTraceHiddenAttribute().Throw(); [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] void InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute() => TypeWithStackTraceHiddenAttribute.ThrowStatic(); class GenericClass { [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] public static string GenericMethod(ref V value) { var returnVal = ""; for (var i = 0; i < 10; i++) { returnVal += string.Join(", ", Iterator()); } return returnVal; } [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] public void Throw(T parameter) => throw new Exception(); } [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] private void GenericMethod(T val) where T : class => throw new Exception(); private class StackTraceHiddenAttribute : Attribute { } [StackTraceHidden] private class TypeWithStackTraceHiddenAttribute { [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] public void Throw() => ThrowCore(); [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] public static void ThrowStatic() => ThrowCore(); } private class TypeWithMethodWithStackTraceHiddenAttribute { [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] [StackTraceHidden] public void MethodWithStackTraceHiddenAttribute() { ThrowCore(); } [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] public void Throw() => MethodWithStackTraceHiddenAttribute(); } [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] private static void ThrowCore() => throw new Exception(); } }