StackTraceHelperTest.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Linq.Expressions;
  7. using System.Runtime.CompilerServices;
  8. using System.Threading.Tasks;
  9. using Microsoft.Extensions.StackTrace.Sources;
  10. using ThrowingLibrary;
  11. using Xunit;
  12. namespace Microsoft.Extensions.Internal
  13. {
  14. public class StackTraceHelperTest
  15. {
  16. [Fact]
  17. public void StackTraceHelper_IncludesLineNumbersForFiles()
  18. {
  19. // Arrange
  20. Exception exception = null;
  21. try
  22. {
  23. // Throwing an exception in the current assembly always seems to populate the full stack
  24. // trace regardless of symbol type. Crossing assembly boundaries ensures PortablePdbReader gets used
  25. // on desktop.
  26. Thrower.Throw();
  27. }
  28. catch (Exception ex)
  29. {
  30. exception = ex;
  31. }
  32. // Act
  33. var stackFrames = StackTraceHelper.GetFrames(exception);
  34. // Assert
  35. Assert.Collection(stackFrames,
  36. frame =>
  37. {
  38. Assert.Contains("Thrower.cs", frame.FilePath);
  39. Assert.Equal(17, frame.LineNumber);
  40. },
  41. frame =>
  42. {
  43. Assert.Contains("StackTraceHelperTest.cs", frame.FilePath);
  44. });
  45. }
  46. [Fact]
  47. public void StackTraceHelper_PrettyPrintsStackTraceForGenericMethods()
  48. {
  49. // Arrange
  50. var exception = Record.Exception(() => GenericMethod<string>(null));
  51. // Act
  52. var stackFrames = StackTraceHelper.GetFrames(exception);
  53. // Assert
  54. var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
  55. Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.GenericMethod<T>(T val)", methods[0]);
  56. }
  57. [Fact]
  58. public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithOutParameters()
  59. {
  60. // Arrange
  61. var exception = Record.Exception(() => MethodWithOutParameter(out var value));
  62. // Act
  63. var stackFrames = StackTraceHelper.GetFrames(exception);
  64. // Assert
  65. var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
  66. Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithOutParameter(out int value)", methods[0]);
  67. }
  68. [Fact]
  69. public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithGenericOutParameters()
  70. {
  71. // Arrange
  72. var exception = Record.Exception(() => MethodWithGenericOutParameter("Test", out int value));
  73. // Act
  74. var stackFrames = StackTraceHelper.GetFrames(exception);
  75. // Assert
  76. var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
  77. Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithGenericOutParameter<TVal>(string a, out TVal value)", methods[0]);
  78. }
  79. [Fact]
  80. public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithRefParameters()
  81. {
  82. // Arrange
  83. var value = 0;
  84. var exception = Record.Exception(() => MethodWithRefParameter(ref value));
  85. // Act
  86. var stackFrames = StackTraceHelper.GetFrames(exception);
  87. // Assert
  88. var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
  89. Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithRefParameter(ref int value)", methods[0]);
  90. }
  91. [Fact]
  92. public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithGenericRefParameters()
  93. {
  94. // Arrange
  95. var value = 0;
  96. var exception = Record.Exception(() => MethodWithGenericRefParameter(ref value));
  97. // Act
  98. var stackFrames = StackTraceHelper.GetFrames(exception);
  99. // Assert
  100. var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
  101. Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithGenericRefParameter<TVal>(ref TVal value)", methods[0]);
  102. }
  103. [Fact]
  104. public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithNullableParameters()
  105. {
  106. // Arrange
  107. var value = 0;
  108. var exception = Record.Exception(() => MethodWithNullableParameter(value));
  109. // Act
  110. var stackFrames = StackTraceHelper.GetFrames(exception);
  111. // Assert
  112. var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
  113. Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithNullableParameter(Nullable<int> value)", methods[0]);
  114. }
  115. [Fact]
  116. public void StackTraceHelper_PrettyPrintsStackTraceForMethodsOnGenericTypes()
  117. {
  118. // Arrange
  119. var exception = Record.Exception(() => new GenericClass<int>().Throw(0));
  120. // Act
  121. var stackFrames = StackTraceHelper.GetFrames(exception);
  122. // Assert
  123. var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
  124. Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest+GenericClass<T>.Throw(T parameter)", methods[0]);
  125. }
  126. [Fact]
  127. public void StackTraceHelper_ProducesReadableOutput()
  128. {
  129. // Arrange
  130. var expectedCallStack = new List<string>()
  131. {
  132. "Microsoft.Extensions.Internal.StackTraceHelperTest.Iterator()+MoveNext()",
  133. "string.Join(string separator, IEnumerable<string> values)",
  134. "Microsoft.Extensions.Internal.StackTraceHelperTest+GenericClass<T>.GenericMethod<V>(ref V value)",
  135. "Microsoft.Extensions.Internal.StackTraceHelperTest.MethodAsync(int value)",
  136. "Microsoft.Extensions.Internal.StackTraceHelperTest.MethodAsync<TValue>(TValue value)",
  137. "Microsoft.Extensions.Internal.StackTraceHelperTest.Method(string value)",
  138. "Microsoft.Extensions.Internal.StackTraceHelperTest.StackTraceHelper_ProducesReadableOutput()",
  139. };
  140. Exception exception = null;
  141. try
  142. {
  143. Method("test");
  144. }
  145. catch (Exception ex)
  146. {
  147. exception = ex;
  148. }
  149. // Act
  150. var stackFrames = StackTraceHelper.GetFrames(exception);
  151. var methodNames = stackFrames.Select(stackFrame => stackFrame.MethodDisplayInfo.ToString()).ToArray();
  152. // Assert
  153. Assert.Equal(expectedCallStack, methodNames);
  154. }
  155. [Fact]
  156. public void StackTraceHelper_DoesNotIncludeInstanceMethodsOnTypesWithStackTraceHiddenAttribute()
  157. {
  158. // Arrange
  159. var exception = Record.Exception(() => InvokeMethodOnTypeWithStackTraceHiddenAttribute());
  160. // Act
  161. var stackFrames = StackTraceHelper.GetFrames(exception);
  162. // Assert
  163. var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
  164. Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]);
  165. Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.InvokeMethodOnTypeWithStackTraceHiddenAttribute()", methods[1]);
  166. }
  167. [Fact]
  168. public void StackTraceHelper_DoesNotIncludeStaticMethodsOnTypesWithStackTraceHiddenAttribute()
  169. {
  170. // Arrange
  171. var exception = Record.Exception(() => InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute());
  172. // Act
  173. var stackFrames = StackTraceHelper.GetFrames(exception);
  174. // Assert
  175. var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
  176. Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]);
  177. Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute()", methods[1]);
  178. }
  179. [Fact]
  180. public void StackTraceHelper_DoesNotIncludeMethodsWithStackTraceHiddenAttribute()
  181. {
  182. // Arrange
  183. var exception = Record.Exception(() => new TypeWithMethodWithStackTraceHiddenAttribute().Throw());
  184. // Act
  185. var stackFrames = StackTraceHelper.GetFrames(exception);
  186. // Assert
  187. var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
  188. Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]);
  189. Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest+TypeWithMethodWithStackTraceHiddenAttribute.Throw()", methods[1]);
  190. }
  191. [Fact]
  192. public void GetFrames_DoesNotFailForDynamicallyGeneratedAssemblies()
  193. {
  194. // Arrange
  195. var action = (Action)Expression.Lambda(
  196. Expression.Throw(
  197. Expression.New(typeof(Exception)))).Compile();
  198. var exception = Record.Exception(action);
  199. // Act
  200. var frames = StackTraceHelper.GetFrames(exception).ToArray();
  201. // Assert
  202. var frame = frames[0];
  203. Assert.Null(frame.FilePath);
  204. Assert.Equal($"lambda_method(Closure )", frame.MethodDisplayInfo.ToString());
  205. }
  206. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  207. async Task<string> MethodAsync(int value)
  208. {
  209. await Task.Delay(0);
  210. return GenericClass<byte>.GenericMethod(ref value);
  211. }
  212. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  213. async Task<string> MethodAsync<TValue>(TValue value)
  214. {
  215. return await MethodAsync(1);
  216. }
  217. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  218. string Method(string value)
  219. {
  220. return MethodAsync(value).GetAwaiter().GetResult();
  221. }
  222. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  223. static IEnumerable<string> Iterator()
  224. {
  225. yield return "Success";
  226. throw new Exception();
  227. }
  228. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  229. void MethodWithOutParameter(out int value) => throw new Exception();
  230. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  231. void MethodWithGenericOutParameter<TVal>(string a, out TVal value) => throw new Exception();
  232. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  233. void MethodWithRefParameter(ref int value) => throw new Exception();
  234. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  235. void MethodWithGenericRefParameter<TVal>(ref TVal value) => throw new Exception();
  236. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  237. void MethodWithNullableParameter(int? value) => throw new Exception();
  238. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  239. void InvokeMethodOnTypeWithStackTraceHiddenAttribute() => new TypeWithStackTraceHiddenAttribute().Throw();
  240. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  241. void InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute() => TypeWithStackTraceHiddenAttribute.ThrowStatic();
  242. class GenericClass<T>
  243. {
  244. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  245. public static string GenericMethod<V>(ref V value)
  246. {
  247. var returnVal = "";
  248. for (var i = 0; i < 10; i++)
  249. {
  250. returnVal += string.Join(", ", Iterator());
  251. }
  252. return returnVal;
  253. }
  254. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  255. public void Throw(T parameter) => throw new Exception();
  256. }
  257. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  258. private void GenericMethod<T>(T val) where T : class => throw new Exception();
  259. private class StackTraceHiddenAttribute : Attribute
  260. {
  261. }
  262. [StackTraceHidden]
  263. private class TypeWithStackTraceHiddenAttribute
  264. {
  265. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  266. public void Throw() => ThrowCore();
  267. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  268. public static void ThrowStatic() => ThrowCore();
  269. }
  270. private class TypeWithMethodWithStackTraceHiddenAttribute
  271. {
  272. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  273. [StackTraceHidden]
  274. public void MethodWithStackTraceHiddenAttribute()
  275. {
  276. ThrowCore();
  277. }
  278. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  279. public void Throw() => MethodWithStackTraceHiddenAttribute();
  280. }
  281. [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  282. private static void ThrowCore() => throw new Exception();
  283. }
  284. }