@@ -26,11 +26,14 @@ namespace DurableTask.Core.Tests
2626 using System . Xml ;
2727 using DurableTask . Core . Command ;
2828 using DurableTask . Core . History ;
29+ using DurableTask . Core . Tracing ;
2930 using DurableTask . Emulator ;
3031 using DurableTask . Test . Orchestrations ;
3132 using Microsoft . Extensions . Logging ;
3233 using Microsoft . Extensions . Logging . Console ;
3334 using Microsoft . VisualStudio . TestTools . UnitTesting ;
35+ using DiagnosticsActivityStatusCode = System . Diagnostics . ActivityStatusCode ;
36+ using TraceActivityStatusCode = DurableTask . Core . Tracing . ActivityStatusCode ;
3437
3538 [ TestClass ]
3639 public class DispatcherMiddlewareTests
@@ -53,8 +56,15 @@ public void InitializeTests()
5356 // We use `GetAwaiter().GetResult()` because otherwise this method will fail with:
5457 // "X has wrong signature. The method must be non-static, public, does not return a value and should not take any parameter."
5558 this . worker
56- . AddTaskOrchestrations ( typeof ( SimplestGreetingsOrchestration ) , typeof ( ParentWorkflow ) , typeof ( ChildWorkflow ) )
57- . AddTaskActivities ( typeof ( SimplestGetUserTask ) , typeof ( SimplestSendGreetingTask ) )
59+ . AddTaskOrchestrations (
60+ typeof ( SimplestGreetingsOrchestration ) ,
61+ typeof ( ParentWorkflow ) ,
62+ typeof ( ChildWorkflow ) ,
63+ typeof ( ActivityStatusResetOrchestration ) )
64+ . AddTaskActivities (
65+ typeof ( SimplestGetUserTask ) ,
66+ typeof ( SimplestSendGreetingTask ) ,
67+ typeof ( ActivityStatusResetActivity ) )
5868 . StartAsync ( ) . GetAwaiter ( ) . GetResult ( ) ;
5969
6070 this . client = new TaskHubClient ( service ) ;
@@ -451,6 +461,82 @@ public async Task MockActivityOrchestration()
451461 Assert . AreEqual ( OrchestrationStatus . Completed , state . OrchestrationStatus ) ;
452462 Assert . AreEqual ( "FakeActivity,FakeActivityVersion,SomeInput" , state . Output ) ;
453463 }
464+
465+ [ TestMethod ]
466+ public async Task ActivityAndOrchestrationSpansResetStatuses ( )
467+ {
468+ using var activityCollector = new DurableActivityCollector ( ) ;
469+
470+ OrchestrationInstance instance = await this . client . CreateOrchestrationInstanceAsync (
471+ typeof ( ActivityStatusResetOrchestration ) ,
472+ "payload" ) ;
473+
474+ TimeSpan timeout = TimeSpan . FromSeconds ( Debugger . IsAttached ? 1000 : 10 ) ;
475+ OrchestrationState finalState = await this . client . WaitForOrchestrationAsync ( instance , timeout ) ;
476+ Assert . AreEqual ( OrchestrationStatus . Completed , finalState . OrchestrationStatus ) ;
477+
478+ string orchestrationName = NameVersionHelper . GetDefaultName ( typeof ( ActivityStatusResetOrchestration ) ) ;
479+ Activity ? orchestrationActivity = activityCollector . Find ( TraceActivityConstants . Orchestration , orchestrationName , ActivityKind . Server ) ;
480+ Assert . IsNotNull ( orchestrationActivity , "Expected orchestration server trace to be captured." ) ;
481+ Assert . AreEqual ( DiagnosticsActivityStatusCode . Ok , orchestrationActivity ! . Status ) ;
482+
483+ string activityName = NameVersionHelper . GetDefaultName ( typeof ( ActivityStatusResetActivity ) ) ;
484+ Activity ? activitySpan = activityCollector . Find ( TraceActivityConstants . Activity , activityName , ActivityKind . Server ) ;
485+ Assert . IsNotNull ( activitySpan , "Expected activity server trace to be captured." ) ;
486+ Assert . AreEqual ( DiagnosticsActivityStatusCode . Ok , activitySpan ! . Status ) ;
487+ }
488+
489+ private sealed class DurableActivityCollector : IDisposable
490+ {
491+ private readonly ActivityListener listener ;
492+ private readonly ConcurrentBag < Activity > activities = new ConcurrentBag < Activity > ( ) ;
493+
494+ public DurableActivityCollector ( )
495+ {
496+ this . listener = new ActivityListener
497+ {
498+ ShouldListenTo = source => source . Name == "DurableTask.Core" ,
499+ Sample = ( ref ActivityCreationOptions < ActivityContext > _ ) => ActivitySamplingResult . AllData ,
500+ SampleUsingParentId = ( ref ActivityCreationOptions < string > _ ) => ActivitySamplingResult . AllData ,
501+ ActivityStopped = activity => this . activities . Add ( activity ) ,
502+ } ;
503+
504+ ActivitySource . AddActivityListener ( this . listener ) ;
505+ }
506+
507+ public Activity ? Find ( string taskType , string taskName , ActivityKind kind )
508+ {
509+ return this . activities
510+ . Where ( activity => activity . Kind == kind )
511+ . Where ( activity => string . Equals ( Convert . ToString ( activity . GetTagItem ( Schema . Task . Type ) ) , taskType , StringComparison . Ordinal ) )
512+ . Where ( activity => string . Equals ( Convert . ToString ( activity . GetTagItem ( Schema . Task . Name ) ) , taskName , StringComparison . Ordinal ) )
513+ . LastOrDefault ( ) ;
514+ }
515+
516+ public void Dispose ( )
517+ {
518+ this . listener . Dispose ( ) ;
519+ }
520+ }
521+
522+ private sealed class ActivityStatusResetOrchestration : TaskOrchestration < string , string >
523+ {
524+ public override async Task < string > RunTask ( OrchestrationContext context , string input )
525+ {
526+ DistributedTraceActivity . Current ? . SetStatus ( TraceActivityStatusCode . Error , "orchestration instrumentation error" ) ;
527+ string ? activityOutput = await context . ScheduleTask < string > ( typeof ( ActivityStatusResetActivity ) , input ?? "ok" ) ;
528+ return activityOutput ?? string . Empty ;
529+ }
530+ }
531+
532+ private sealed class ActivityStatusResetActivity : TaskActivity < string , string >
533+ {
534+ protected override string Execute ( TaskContext context , string input )
535+ {
536+ Activity . Current ? . SetStatus ( TraceActivityStatusCode . Error , "activity instrumentation error" ) ;
537+ return input ?? "ok" ;
538+ }
539+ }
454540 }
455541}
456542#endif
0 commit comments