// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using ClassLibraryWithPortablePdbs; using Microsoft.Extensions.StackTrace.Sources; using Xunit; namespace Microsoft.Extensions.Internal.Test { public class StackTraceTest { public static TheoryData CanGetStackTraceData => new TheoryData { { TestMethods.ThrowsException, $"{typeof(TestMethods).GetTypeInfo().FullName}.{nameof(TestMethods.ThrowsException)}()" }, { new ExceptionType().MethodThatThrows, $"{typeof(ExceptionType).GetTypeInfo().FullName}.{nameof(ExceptionType.MethodThatThrows)}()" }, { ExceptionType.StaticMethodThatThrows, $"{typeof(ExceptionType).GetTypeInfo().FullName}.{nameof(ExceptionType.StaticMethodThatThrows)}()" } }; [Theory] [MemberData(nameof(CanGetStackTraceData))] public void GetFrames_CanGetStackTrace(Action action, string expectedDisplay) { try { action(); } catch (Exception exception) { // Arrange and Act var frames = StackTraceHelper.GetFrames(exception); // Assert Assert.Equal(expectedDisplay, frames.First().MethodDisplayInfo.ToString()); Assert.Equal( $"{typeof(StackTraceTest).GetTypeInfo().FullName}.{nameof(GetFrames_CanGetStackTrace)}" + "(Action action, string expectedDisplay)", frames.Last().MethodDisplayInfo.ToString()); } } [Fact] public void GetFrames_DoesNotFailForDynamicallyGeneratedAssemblies() { // Arrange var action = (Action)Expression.Lambda( Expression.Throw( Expression.New(typeof(Exception)))).Compile(); // Act try { action(); } catch (Exception exception) { var frames = StackTraceHelper.GetFrames(exception); // Assert Assert.Null(frames.First().FilePath); Assert.Equal( $"{typeof(StackTraceTest).GetTypeInfo().FullName}.{nameof(GetFrames_DoesNotFailForDynamicallyGeneratedAssemblies)}()", frames.Last().MethodDisplayInfo.ToString()); } } public static TheoryData GetMethodDisplayString_ReturnsTypeNameQualifiedMethodsData { get { var intParse = ((Func)int.Parse).GetMethodInfo(); var dateTimeOffsetTryParse = typeof(DateTimeOffset).GetMethods() .First(m => m.Name == nameof(DateTimeOffset.TryParse) && m.GetParameters().Length == 2); var genericTypeMethod = typeof(List).GetMethod(nameof(List.Remove)); var genericMethod = typeof(TestMethods).GetMethod(nameof(TestMethods.GenericMethod)); var multiGenericMethod = typeof(TestMethods).GetMethod(nameof(TestMethods.MultiParameterGenericMethod)); var byRefMethod = typeof(TestMethods).GetMethod(nameof(TestMethods.ByRefMethod)); var asyncMethod = typeof(TestMethods).GetMethod(nameof(TestMethods.AsyncMethod)); var nullableParam = typeof(TestMethods).GetMethod(nameof(TestMethods.MethodWithNullableParams)); var nestedMethod = typeof(StackTraceTest).GetNestedType(nameof(NestedType), BindingFlags.Public) .GetMethod(nameof(NestedType.NestedMethod)); var nestedGenericMethod = typeof(StackTraceTest).GetNestedType(nameof(NestedType), BindingFlags.Public) .GetMethod(nameof(NestedType.NestedGenericMethod)); return new TheoryData { { intParse, "int.Parse(string s)" }, { dateTimeOffsetTryParse, "System.DateTimeOffset.TryParse(string input, out DateTimeOffset result)" }, { genericTypeMethod, "System.Collections.Generic.List.Remove(Process item)" }, { genericMethod, $"{typeof(TestMethods)}.{nameof(TestMethods.GenericMethod)}(TVal value)" }, { multiGenericMethod, $"{typeof(TestMethods)}.{nameof(TestMethods.MultiParameterGenericMethod)}(KeyValuePair keyValuePair)" }, { byRefMethod, $"{typeof(TestMethods)}.{nameof(TestMethods.ByRefMethod)}(int a, CultureInfo b, ref long c)" }, { asyncMethod, $"{typeof(TestMethods)}.{nameof(TestMethods.AsyncMethod)}(string name)" }, { nullableParam, $"{typeof(TestMethods)}.{nameof(TestMethods.MethodWithNullableParams)}(Nullable name, string value)" }, { nestedMethod, $"{typeof(NestedType)}.{nameof(NestedType.NestedMethod)}(string value)" }, { nestedGenericMethod, $"{typeof(NestedType)}.{nameof(NestedType.NestedGenericMethod)}(NestedParameterType a, TKey key)" } }; } } [Theory] [MemberData(nameof(GetMethodDisplayString_ReturnsTypeNameQualifiedMethodsData))] public void GetMethodDisplayString_ReturnsTypeNameQualifiedMethods(MethodBase method, string expected) { // Act var actual = StackTraceHelper.GetMethodDisplayString(method); // Assert Assert.Equal(expected, actual.ToString()); } public class TestMethods { public static void ThrowsException() { throw new Exception(); } public string GenericMethod(TVal value) => value.ToString(); public void MultiParameterGenericMethod(KeyValuePair keyValuePair) { } public decimal ByRefMethod(int a, CultureInfo b, ref long c) => a + c; public async Task AsyncMethod(string name) => await Task.FromResult(0); public void MethodWithNullableParams(int? name, string value) { } } public class NestedType { public void NestedMethod(string value) => Console.WriteLine("Hello world"); public TKey NestedGenericMethod(NestedParameterType a, TKey key) => key; } public class NestedParameterType { } } }