@@ -23,6 +23,17 @@ public SignatureTest(ITestOutputHelper output)
2323
2424 [ TestAppFact ( "Samples.Computer01" , new [ ] { "net48" , "netcoreapp3.1" , "net6.0" , "net8.0" , } ) ] // FIXME: .NET 9 skipping .NET 9 for now
2525 public void ValidateSignatures ( string appName , string framework , string appAssembly )
26+ {
27+ CheckExceptionsInProfiles ( framework , GetExceptionSamples ( appName , framework , appAssembly ) ) ;
28+ }
29+
30+ [ TestAppFact ( "Samples.Computer01" , new [ ] { "net48" , "netcoreapp3.1" , "net6.0" , "net8.0" , } ) ] // FIXME: .NET 9 skipping .NET 9 for now
31+ public void ValidateAsyncStateMachineSignatures ( string appName , string framework , string appAssembly )
32+ {
33+ CheckAsyncStateMachineFrames ( GetExceptionSamples ( appName , framework , appAssembly ) ) ;
34+ }
35+
36+ private ( string Type , string Message , long Count , StackTrace Stacktrace ) [ ] GetExceptionSamples ( string appName , string framework , string appAssembly )
2637 {
2738 var runner = new TestApplicationRunner ( appName , framework , appAssembly , _output , commandLine : "--scenario 20" ) ;
2839 EnvironmentHelper . DisableDefaultProfilers ( runner ) ;
@@ -33,8 +44,47 @@ public void ValidateSignatures(string appName, string framework, string appAssem
3344 runner . Run ( agent ) ;
3445 Assert . True ( agent . NbCallsOnProfilingEndpoint > 0 ) ;
3546
36- var exceptionSamples = SamplesHelper . ExtractExceptionSamples ( runner . Environment . PprofDir ) . ToArray ( ) ;
37- CheckExceptionsInProfiles ( framework , exceptionSamples ) ;
47+ return SamplesHelper . ExtractExceptionSamples ( runner . Environment . PprofDir ) . ToArray ( ) ;
48+ }
49+
50+ private static void CheckAsyncStateMachineFrames ( ( string Type , string Message , long Count , StackTrace Stacktrace ) [ ] exceptionSamples )
51+ {
52+ // A state machine is a type nested in the type declaring the async method: the generic
53+ // parameter of Start<TStateMachine> must carry its namespace and its enclosing type, exactly
54+ // like the signature already does. The frame is AsyncMethodBuilderCore.Start on .NET Core
55+ // and AsyncTaskMethodBuilder.Start on .NET Framework.
56+ var frames = exceptionSamples
57+ . SelectMany ( sample => Enumerable . Range ( 0 , sample . Stacktrace . FramesCount ) . Select ( i => sample . Stacktrace [ i ] ) )
58+ . Distinct ( )
59+ . ToArray ( ) ;
60+
61+ // split code to debug more easily outside of LINQ
62+ var startFrames = frames
63+ . Where ( frame => frame . Function == "Start" )
64+ . Distinct ( )
65+ . ToArray ( ) ;
66+
67+ var startAdornments = startFrames
68+ . Select ( frame => frame . FunctionAdornment )
69+ . Distinct ( )
70+ . ToArray ( ) ;
71+
72+ #if DEBUG
73+ // MethodsSignature drives its own state machine: a struct is never shared between
74+ // instantiations so its exact type is always known, whatever the configuration used to
75+ // build the sample
76+ startAdornments . Should ( ) . Contain ( "<Samples.Computer01.MethodsSignature.SyncOverAsyncStateMachine>" ) ;
77+
78+ // the state machines generated by the compiler are structs in release builds but classes in
79+ // debug builds: in the latter case, only the shared canonical instantiation is known (<T0>)
80+ startAdornments . Should ( ) . Contain ( "<T0>" ) ;
81+ #else
82+ startAdornments . Should ( ) . Contain ( "<Samples.Computer01.MethodsSignature.<GetSyncOverAsync>d__2>" ) ;
83+ startAdornments . Should ( ) . Contain ( "<Samples.Computer01.MethodsSignature.<WithResult>d__1>" ) ;
84+ #endif
85+
86+ // regression test to check the "type with empty namespace" bug is fixed
87+ frames . Should ( ) . OnlyContain ( frame => ! frame . FunctionAdornment . StartsWith ( "<." ) ) ;
3888 }
3989
4090 private static void CheckExceptionsInProfiles ( string framework , ( string Type , string Message , long Count , StackTrace Stacktrace ) [ ] exceptionSamples )
@@ -56,6 +106,7 @@ private static void CheckExceptionsInProfiles(string framework, (string Type, st
56106 new StackFrame ( "|lm:Samples.Computer01 |ns:Samples.Computer01 |ct:GenericClassForValueTypeTest |cg:<System.Int32, System.Boolean> |fn:ThrowOneGenericFromType |fg: |sg:(TKey value)" ) ,
57107 new StackFrame ( "|lm:Samples.Computer01 |ns:Samples.Computer01 |ct:MethodsSignature |cg: |fn:ThrowGenericMethod2 |fg:<T0, System.Int32, T2, T3> |sg:(T0 key1, System.Int32 value1, System.Int32 value2, T2 key2, T3 key3, System.Collections.Generic.List<System.Int32> listOfTValue)" ) ,
58108 new StackFrame ( "|lm:Samples.Computer01 |ns:Samples.Computer01 |ct:MethodsSignature |cg: |fn:ThrowGenericMethod1 |fg:<T0> |sg:(T0 element)" ) ,
109+ new StackFrame ( "|lm:Samples.Computer01 |ns:Samples.Computer01 |ct:MethodsSignature |cg: |fn:ThrowGenericMethod1 |fg:<GlobalStruct> |sg:(GlobalStruct element)" ) ,
59110 new StackFrame ( "|lm:Samples.Computer01 |ns:Samples.Computer01 |ct:MethodsSignature |cg: |fn:ThrowGenericMethod1 |fg:<Samples.Computer01.MyStruct> |sg:(Samples.Computer01.MyStruct element)" ) ,
60111 new StackFrame ( "|lm:Samples.Computer01 |ns:Samples.Computer01 |ct:MethodsSignature |cg: |fn:ThrowGenericMethod1 |fg:<T0> |sg:(T0 element)" ) ,
61112 new StackFrame ( "|lm:Samples.Computer01 |ns:Samples.Computer01 |ct:MethodsSignature |cg: |fn:ThrowGenericMethod1 |fg:<System.Boolean> |sg:(System.Boolean element)" ) ,
@@ -80,6 +131,7 @@ private static void CheckExceptionsInProfiles(string framework, (string Type, st
80131 new StackFrame ( "|lm:Samples.Computer01 |ns:Samples.Computer01 |ct:GenericClassForValueTypeTest |cg:<System.Int32, System.Boolean> |fn:ThrowOneGenericFromType |fg: |sg:(TVal value)" ) ,
81132 new StackFrame ( "|lm:Samples.Computer01 |ns:Samples.Computer01 |ct:MethodsSignature |cg: |fn:ThrowGenericMethod2 |fg:<T0, System.Int32, T2, T3> |sg:(T0 key1, System.Int32 value1, System.Int32 value2, T2 key2, T3 key3, System.Collections.Generic.List<System.Int32> listOfTValue)" ) ,
82133 new StackFrame ( "|lm:Samples.Computer01 |ns:Samples.Computer01 |ct:MethodsSignature |cg: |fn:ThrowGenericMethod1 |fg:<T0> |sg:(T0 element)" ) ,
134+ new StackFrame ( "|lm:Samples.Computer01 |ns:Samples.Computer01 |ct:MethodsSignature |cg: |fn:ThrowGenericMethod1 |fg:<GlobalStruct> |sg:(GlobalStruct element)" ) ,
83135 new StackFrame ( "|lm:Samples.Computer01 |ns:Samples.Computer01 |ct:MethodsSignature |cg: |fn:ThrowGenericMethod1 |fg:<Samples.Computer01.MyStruct> |sg:(Samples.Computer01.MyStruct element)" ) ,
84136 new StackFrame ( "|lm:Samples.Computer01 |ns:Samples.Computer01 |ct:MethodsSignature |cg: |fn:ThrowGenericMethod1 |fg:<T0> |sg:(T0 element)" ) ,
85137 new StackFrame ( "|lm:Samples.Computer01 |ns:Samples.Computer01 |ct:MethodsSignature |cg: |fn:ThrowGenericMethod1 |fg:<System.Boolean> |sg:(System.Boolean element)" ) ,
0 commit comments