From 398f36d470c8e12d050f6e1e94f8100f93625ba1 Mon Sep 17 00:00:00 2001 From: Geert van Horrik Date: Tue, 23 May 2023 12:58:56 +0200 Subject: [PATCH] Use reference instead of definition when using message in async state machine (#527) --- .../ClassWithAsyncMethod.cs | 9 ++ .../ClassWithAsyncMethod.cs | 21 +++ MethodTimer.Fody/AsyncMethodProcessor.cs | 4 +- .../WithInterceptorAndFormattingTests.cs | 24 +++ .../WithoutInterceptorTests.async.cs | 151 ++++++++++++++++++ .../WithoutInterceptorTests.cs | 132 +-------------- 6 files changed, 208 insertions(+), 133 deletions(-) create mode 100644 Tests/AssemblyTesterSets/WithoutInterceptorTests.async.cs diff --git a/AssemblyWithInterceptorAndFormatting/ClassWithAsyncMethod.cs b/AssemblyWithInterceptorAndFormatting/ClassWithAsyncMethod.cs index 652e6b8c..21237c8c 100644 --- a/AssemblyWithInterceptorAndFormatting/ClassWithAsyncMethod.cs +++ b/AssemblyWithInterceptorAndFormatting/ClassWithAsyncMethod.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using MethodTimer; #pragma warning disable 414 @@ -80,6 +81,14 @@ public async Task MethodWithFastPathAsync(bool recurse, string fileName, int id) isRunning = false; } + [Time("some message")] + public async Task> MethodWithGenericResultAsync() + { + await Task.Delay(50); + + return new List(); + } + public override string ToString() => "TEST VALUE"; } \ No newline at end of file diff --git a/AssemblyWithoutInterceptor/ClassWithAsyncMethod.cs b/AssemblyWithoutInterceptor/ClassWithAsyncMethod.cs index ff86ba4c..a58b5822 100644 --- a/AssemblyWithoutInterceptor/ClassWithAsyncMethod.cs +++ b/AssemblyWithoutInterceptor/ClassWithAsyncMethod.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using MethodTimer; #pragma warning disable 414 @@ -102,3 +103,23 @@ public async Task ComplexMethodWithAwaitAsync(int instructionsToHandle) public async Task MethodWithExceptionAsync() => await Task.Factory.StartNew(() => throw new ArgumentOutOfRangeException()); } + +public class ClassWithGenericResultAsyncMethodBase +{ + public virtual async Task> DoSomethingAsync() + { + await Task.Delay(50); + + return new List(); + } +} + +public class ClassWithGenericResultAsyncMethod : ClassWithGenericResultAsyncMethodBase +{ + [Time()] + public async Task> DoSomethingWithoutMessageAsync() + { + var result = await base.DoSomethingAsync(); + return result; + } +} \ No newline at end of file diff --git a/MethodTimer.Fody/AsyncMethodProcessor.cs b/MethodTimer.Fody/AsyncMethodProcessor.cs index 03be8305..f18531cf 100644 --- a/MethodTimer.Fody/AsyncMethodProcessor.cs +++ b/MethodTimer.Fody/AsyncMethodProcessor.cs @@ -348,14 +348,14 @@ IEnumerable GetWriteTimeInstruction(MethodBody methodBody) { yield return Instruction.Create(OpCodes.Call, ModuleWeaver.ElapsedMilliseconds); yield return Instruction.Create(OpCodes.Ldarg_0); - yield return Instruction.Create(OpCodes.Ldfld, formattedFieldDefinition); + yield return Instruction.Create(OpCodes.Ldfld, formattedFieldReference); yield return Instruction.Create(OpCodes.Call, logWithMessageMethodUsingLong); } else { yield return Instruction.Create(OpCodes.Call, ModuleWeaver.Elapsed); yield return Instruction.Create(OpCodes.Ldarg_0); - yield return Instruction.Create(OpCodes.Ldfld, formattedFieldDefinition); + yield return Instruction.Create(OpCodes.Ldfld, formattedFieldReference); yield return Instruction.Create(OpCodes.Call, logWithMessageMethodUsingTimeSpan); } } diff --git a/Tests/AssemblyTesterSets/WithInterceptorAndFormattingTests.cs b/Tests/AssemblyTesterSets/WithInterceptorAndFormattingTests.cs index b19c0f19..0cdf3b4e 100644 --- a/Tests/AssemblyTesterSets/WithInterceptorAndFormattingTests.cs +++ b/Tests/AssemblyTesterSets/WithInterceptorAndFormattingTests.cs @@ -199,6 +199,30 @@ public void ClassWithAsyncMethodWithFastPath(bool recurse) Assert.Equal("File name '123' with id '42'", message); } + [Fact] + public void ClassWithGenericAsyncMethod() + { + ClearMessage(); + + var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); + var instance = (dynamic)Activator.CreateInstance(type); + TraceRunner.Capture(() => + { + var task = (Task)instance.MethodWithGenericResultAsync(); + task.Wait(); + }); + + var methodBases = GetMethodInfoField(); + var methodBase = methodBases.Last(); + Assert.Equal("MethodWithGenericResultAsync", methodBase.Name); + + var messages = GetMessagesField(); + Assert.Single(messages); + + var message = messages.First(); + Assert.Equal("some message", message); + } + static void ClearMessage() { methodBaseField.SetValue(null, new List()); diff --git a/Tests/AssemblyTesterSets/WithoutInterceptorTests.async.cs b/Tests/AssemblyTesterSets/WithoutInterceptorTests.async.cs new file mode 100644 index 00000000..c7beb9d0 --- /dev/null +++ b/Tests/AssemblyTesterSets/WithoutInterceptorTests.async.cs @@ -0,0 +1,151 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +public partial class WithoutInterceptorTests +{ + [Fact] + public void MethodWithEmptyAsync() + { + var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); + var instance = (dynamic)Activator.CreateInstance(type); + var message = TraceRunner.Capture(() => + { + var task = (Task)instance.MethodWithEmptyAsync(); + task.Wait(); + }); + + Assert.Single(message); + Assert.StartsWith("ClassWithAsyncMethod.MethodWithEmptyAsync ", message.First()); + } + + [Fact] + public void ClassWithAsyncMethod() + { + var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); + var instance = (dynamic)Activator.CreateInstance(type); + var message = TraceRunner.Capture(() => + { + var task = (Task)instance.MethodWithAwaitAsync(); + task.Wait(); + }); + + Assert.Single(message); + Assert.StartsWith("ClassWithAsyncMethod.MethodWithAwaitAsync ", message.First()); + } + + [Fact] + public void ClassWithAsyncMethodThatThrowsException() + { + var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); + var instance = (dynamic)Activator.CreateInstance(type); + var message = TraceRunner.Capture(() => + { + try + { + var task = (Task)instance.MethodWithAwaitAndExceptionAsync(); + task.Wait(); + } + catch (Exception) + { + // Expected + } + }); + + Assert.Single(message); + Assert.StartsWith("ClassWithAsyncMethod.MethodWithAwaitAndExceptionAsync ", message.First()); + } + + [Fact] + public async Task ClassWithGenericTaskWithoutMessageAsyncMethod() + { + var type = testResult.Assembly.GetType("ClassWithGenericResultAsyncMethod"); + var instance = (dynamic)Activator.CreateInstance(type); + var message = await TraceRunner.CaptureAsync(async () => + { + var task = (Task)instance.DoSomethingWithoutMessageAsync(); + await task; + }); + + Assert.Single(message); + Assert.StartsWith("ClassWithGenericResultAsyncMethod.DoSomethingWithoutMessageAsync", message.First()); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ClassWithAsyncMethodWithFastPath(bool recurse) + { + var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); + var instance = (dynamic)Activator.CreateInstance(type); + var message = TraceRunner.Capture(() => + { + var task = (Task)instance.MethodWithFastPathAsync(recurse); + task.Wait(); + }); + + Assert.Equal(recurse ? 2 : 1, message.Count); + Assert.StartsWith("ClassWithAsyncMethod.MethodWithFastPathAsync ", message.First()); + } + + [Fact] + public void ClassWithExceptionAsyncMethod() + { + var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); + var instance = (dynamic)Activator.CreateInstance(type); + var message = TraceRunner.Capture(() => + { + var task = (Task)instance.ComplexMethodWithAwaitAsync(-1); + task.Wait(); + }); + + Assert.Single(message); + Assert.StartsWith("ClassWithAsyncMethod.ComplexMethodWithAwaitAsync ", message.First()); + } + + [Fact] + public void ClassWithFastComplexAsyncMethod() + { + var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); + var instance = (dynamic)Activator.CreateInstance(type); + var message = TraceRunner.Capture(() => + { + var task = (Task)instance.ComplexMethodWithAwaitAsync(0); + task.Wait(); + }); + + Assert.Single(message); + Assert.StartsWith("ClassWithAsyncMethod.ComplexMethodWithAwaitAsync ", message.First()); + } + + [Fact] + public void ClassWithMediumComplexAsyncMethod() + { + var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); + var instance = (dynamic)Activator.CreateInstance(type); + var message = TraceRunner.Capture(() => + { + var task = (Task)instance.ComplexMethodWithAwaitAsync(2); + task.Wait(); + }); + + Assert.Single(message); + Assert.StartsWith("ClassWithAsyncMethod.ComplexMethodWithAwaitAsync ", message.First()); + } + + [Fact] + public void ClassWithSlowComplexAsyncMethod() + { + var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); + var instance = (dynamic)Activator.CreateInstance(type); + var message = TraceRunner.Capture(() => + { + var task = (Task)instance.ComplexMethodWithAwaitAsync(100); + task.Wait(); + }); + + Assert.Single(message); + Assert.StartsWith("ClassWithAsyncMethod.ComplexMethodWithAwaitAsync ", message.First()); + } +} \ No newline at end of file diff --git a/Tests/AssemblyTesterSets/WithoutInterceptorTests.cs b/Tests/AssemblyTesterSets/WithoutInterceptorTests.cs index b839f46a..0977f73f 100644 --- a/Tests/AssemblyTesterSets/WithoutInterceptorTests.cs +++ b/Tests/AssemblyTesterSets/WithoutInterceptorTests.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using Fody; using Xunit; -public class WithoutInterceptorTests +public partial class WithoutInterceptorTests { static TestResult testResult; @@ -53,135 +52,6 @@ public void ClassWithYieldMethod() //Assert.True(message.First().StartsWith("ClassWithYieldMethod.YieldMethod ")); } - [Fact] - public void MethodWithEmptyAsync() - { - var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); - var instance = (dynamic)Activator.CreateInstance(type); - var message = TraceRunner.Capture(() => - { - var task = (Task)instance.MethodWithEmptyAsync(); - task.Wait(); - }); - - Assert.Single(message); - Assert.StartsWith("ClassWithAsyncMethod.MethodWithEmptyAsync ", message.First()); - } - - [Fact] - public void ClassWithAsyncMethod() - { - var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); - var instance = (dynamic)Activator.CreateInstance(type); - var message = TraceRunner.Capture(() => - { - var task = (Task)instance.MethodWithAwaitAsync(); - task.Wait(); - }); - - Assert.Single(message); - Assert.StartsWith("ClassWithAsyncMethod.MethodWithAwaitAsync ", message.First()); - } - - [Fact] - public void ClassWithAsyncMethodThatThrowsException() - { - var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); - var instance = (dynamic)Activator.CreateInstance(type); - var message = TraceRunner.Capture(() => - { - try - { - var task = (Task)instance.MethodWithAwaitAndExceptionAsync(); - task.Wait(); - } - catch (Exception) - { - // Expected - } - }); - - Assert.Single(message); - Assert.StartsWith("ClassWithAsyncMethod.MethodWithAwaitAndExceptionAsync ", message.First()); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void ClassWithAsyncMethodWithFastPath(bool recurse) - { - var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); - var instance = (dynamic)Activator.CreateInstance(type); - var message = TraceRunner.Capture(() => - { - var task = (Task)instance.MethodWithFastPathAsync(recurse); - task.Wait(); - }); - - Assert.Equal(recurse ? 2 : 1, message.Count); - Assert.StartsWith("ClassWithAsyncMethod.MethodWithFastPathAsync ", message.First()); - } - - [Fact] - public void ClassWithExceptionAsyncMethod() - { - var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); - var instance = (dynamic)Activator.CreateInstance(type); - var message = TraceRunner.Capture(() => - { - var task = (Task)instance.ComplexMethodWithAwaitAsync(-1); - task.Wait(); - }); - - Assert.Single(message); - Assert.StartsWith("ClassWithAsyncMethod.ComplexMethodWithAwaitAsync ", message.First()); - } - - [Fact] - public void ClassWithFastComplexAsyncMethod() - { - var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); - var instance = (dynamic)Activator.CreateInstance(type); - var message = TraceRunner.Capture(() => - { - var task = (Task)instance.ComplexMethodWithAwaitAsync(0); - task.Wait(); - }); - - Assert.Single(message); - Assert.StartsWith("ClassWithAsyncMethod.ComplexMethodWithAwaitAsync ", message.First()); - } - - [Fact] - public void ClassWithMediumComplexAsyncMethod() - { - var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); - var instance = (dynamic)Activator.CreateInstance(type); - var message = TraceRunner.Capture(() => - { - var task = (Task)instance.ComplexMethodWithAwaitAsync(2); - task.Wait(); - }); - - Assert.Single(message); - Assert.StartsWith("ClassWithAsyncMethod.ComplexMethodWithAwaitAsync ", message.First()); - } - - [Fact] - public void ClassWithSlowComplexAsyncMethod() - { - var type = testResult.Assembly.GetType("ClassWithAsyncMethod"); - var instance = (dynamic)Activator.CreateInstance(type); - var message = TraceRunner.Capture(() => - { - var task = (Task)instance.ComplexMethodWithAwaitAsync(100); - task.Wait(); - }); - - Assert.Single(message); - Assert.StartsWith("ClassWithAsyncMethod.ComplexMethodWithAwaitAsync ", message.First()); - } - [Fact] public void ClassWithConstructor() {