From 78df9eaff16b7d0736e86659c93070660d22611f Mon Sep 17 00:00:00 2001 From: Bernd Verst Date: Thu, 19 Mar 2026 17:02:31 -0700 Subject: [PATCH 01/12] Upgrade test/sample projects to net8.0 and bump dependencies - Retarget all test projects from net6.0 to net8.0 (net48 multi-target preserved) - Retarget all sample projects from net6.0 to net8.0 - Update framework-conditional ItemGroups in Directory.Packages.props - Apply pending dependabot package updates: - Azure.Storage.Blobs 12.24.0 -> 12.27.0 - Azure.Monitor.OpenTelemetry.Exporter 1.0.0-beta.4 -> 1.6.0 - Azure.Identity 1.12.0 -> 1.18.0 - Microsoft.ApplicationInsights 2.21.0 -> 2.23.0 - System.Text.RegularExpressions 4.3.1 (security pin) - Bump transitive dependencies to resolve NU1605 downgrades: - Microsoft.Bcl.AsyncInterfaces 6.0.0 -> 8.0.0 - System.Text.Json 6.0.10 -> 10.0.3 - Microsoft.Extensions.Logging* 8.0.0 -> 10.0.3 (net8.0) - Fix compilation issues from API changes: - Add explicit parameters for Blob API calls (12.27.0 removed defaults) - Suppress SYSLIB0050/0051 for obsolete formatter serialization in tests - Suppress CS8633/CS8766 for ILogger.BeginScope nullability changes --- Directory.Packages.props | 27 ++++++++++--------- .../Correlation.Samples.csproj | 2 +- .../ApplicationInsightsSample.csproj | 2 +- .../OpenTelemetry/OpenTelemetrySample.csproj | 2 +- .../ManagedIdentity.AzStorageV1.csproj | 2 +- .../AzureStorageScaleTests.cs | 2 +- .../AzureStorageScenarioTests.cs | 4 +-- .../DurableTask.AzureStorage.Tests.csproj | 3 ++- .../Obsolete/LegacyTableEntityConverter.cs | 2 ++ .../CustomExceptionsTests.cs | 7 ++++- .../DurableTask.Core.Tests.csproj | 2 +- .../ExceptionHandlingIntegrationTests.cs | 4 +++ .../WorkItemDispatcherTests.cs | 2 ++ .../DurableTask.Emulator.Tests.csproj | 2 +- .../DurableTask.ServiceBus.Tests.csproj | 6 ++--- .../DurableTask.Stress.Tests.csproj | 2 +- 16 files changed, 43 insertions(+), 28 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 2bdd483f9..844d46238 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,14 +12,14 @@ - + - + - + @@ -47,8 +47,8 @@ - - + + @@ -60,13 +60,14 @@ - - + + + - - + + @@ -88,7 +89,7 @@ - + @@ -103,9 +104,9 @@ - - - + + + diff --git a/samples/Correlation.Samples/Correlation.Samples.csproj b/samples/Correlation.Samples/Correlation.Samples.csproj index 4ad6588de..81757ac8b 100644 --- a/samples/Correlation.Samples/Correlation.Samples.csproj +++ b/samples/Correlation.Samples/Correlation.Samples.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 diff --git a/samples/DistributedTraceSample/ApplicationInsights/ApplicationInsightsSample.csproj b/samples/DistributedTraceSample/ApplicationInsights/ApplicationInsightsSample.csproj index efbc910c6..160690471 100644 --- a/samples/DistributedTraceSample/ApplicationInsights/ApplicationInsightsSample.csproj +++ b/samples/DistributedTraceSample/ApplicationInsights/ApplicationInsightsSample.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 enable d4d9b2e3-fb2a-4de6-9747-3d6d3b639d1a dummy-value diff --git a/samples/DistributedTraceSample/OpenTelemetry/OpenTelemetrySample.csproj b/samples/DistributedTraceSample/OpenTelemetry/OpenTelemetrySample.csproj index 970d3b7fc..3f6ac2af1 100644 --- a/samples/DistributedTraceSample/OpenTelemetry/OpenTelemetrySample.csproj +++ b/samples/DistributedTraceSample/OpenTelemetry/OpenTelemetrySample.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 enable diff --git a/samples/ManagedIdentitySample/DTFx.AzureStorage v1.x/ManagedIdentity.AzStorageV1.csproj b/samples/ManagedIdentitySample/DTFx.AzureStorage v1.x/ManagedIdentity.AzStorageV1.csproj index 51ce684b2..cdb974eb9 100644 --- a/samples/ManagedIdentitySample/DTFx.AzureStorage v1.x/ManagedIdentity.AzStorageV1.csproj +++ b/samples/ManagedIdentitySample/DTFx.AzureStorage v1.x/ManagedIdentity.AzStorageV1.csproj @@ -4,7 +4,7 @@ Latest enable Exe - net6.0 + net8.0 diff --git a/test/DurableTask.AzureStorage.Tests/AzureStorageScaleTests.cs b/test/DurableTask.AzureStorage.Tests/AzureStorageScaleTests.cs index 854aa3007..b31326a78 100644 --- a/test/DurableTask.AzureStorage.Tests/AzureStorageScaleTests.cs +++ b/test/DurableTask.AzureStorage.Tests/AzureStorageScaleTests.cs @@ -195,7 +195,7 @@ async Task EnsureTaskHubAsync( private async Task EnsureLeasesMatchControlQueue(string directoryReference, BlobContainerClient taskHubContainer, ControlQueue[] controlQueues) { - BlobItem[] leaseBlobs = await taskHubContainer.GetBlobsAsync(prefix: directoryReference).ToArrayAsync(); + BlobItem[] leaseBlobs = await taskHubContainer.GetBlobsAsync(traits: BlobTraits.None, states: BlobStates.None, prefix: directoryReference, cancellationToken: default).ToArrayAsync(); Assert.AreEqual(controlQueues.Length, leaseBlobs.Length, "Expected to see the same number of control queues and lease blobs."); foreach (BlobItem blobItem in leaseBlobs) { diff --git a/test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs b/test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs index 3a48ee5ff..7a949d75f 100644 --- a/test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs +++ b/test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs @@ -557,7 +557,7 @@ private async Task GetBlobCount(string containerName, string directoryName) var containerClient = client.GetBlobContainerClient(containerName); await containerClient.CreateIfNotExistsAsync(); - return await containerClient.GetBlobsAsync(traits: BlobTraits.Metadata, prefix: directoryName).CountAsync(); + return await containerClient.GetBlobsAsync(traits: BlobTraits.Metadata, states: BlobStates.None, prefix: directoryName, cancellationToken: default).CountAsync(); } @@ -2236,7 +2236,7 @@ private static async Task ValidateLargeMessageBlobUrlAsync(string taskHubName, s BlobContainerClient container = serviceClient.GetBlobContainerClient(containerName); Assert.IsTrue(await container.ExistsAsync(), $"Blob container {containerName} is expected to exist."); BlobItem blob = await container - .GetBlobsByHierarchyAsync(BlobTraits.Metadata, prefix: sanitizedInstanceId) + .GetBlobsByHierarchyAsync(traits: BlobTraits.Metadata, states: BlobStates.None, delimiter: null, prefix: sanitizedInstanceId, cancellationToken: default) .Where(x => x.IsBlob && x.Blob.Name == sanitizedInstanceId + "/" + blobName) .Select(x => x.Blob) .SingleOrDefaultAsync(); diff --git a/test/DurableTask.AzureStorage.Tests/DurableTask.AzureStorage.Tests.csproj b/test/DurableTask.AzureStorage.Tests/DurableTask.AzureStorage.Tests.csproj index df42c925b..3d00b0228 100644 --- a/test/DurableTask.AzureStorage.Tests/DurableTask.AzureStorage.Tests.csproj +++ b/test/DurableTask.AzureStorage.Tests/DurableTask.AzureStorage.Tests.csproj @@ -2,7 +2,7 @@ - net6.0;net48 + net8.0;net48 @@ -52,6 +52,7 @@ + Always diff --git a/test/DurableTask.AzureStorage.Tests/Obsolete/LegacyTableEntityConverter.cs b/test/DurableTask.AzureStorage.Tests/Obsolete/LegacyTableEntityConverter.cs index 5347fffa7..fa3103dd1 100644 --- a/test/DurableTask.AzureStorage.Tests/Obsolete/LegacyTableEntityConverter.cs +++ b/test/DurableTask.AzureStorage.Tests/Obsolete/LegacyTableEntityConverter.cs @@ -75,7 +75,9 @@ public object ConvertFromTableEntity(DynamicTableEntity tableEntity, Func propertyConverters = this.converterCache.GetOrAdd( objectType, diff --git a/test/DurableTask.Core.Tests/CustomExceptionsTests.cs b/test/DurableTask.Core.Tests/CustomExceptionsTests.cs index fd3f69442..1bcb53e55 100644 --- a/test/DurableTask.Core.Tests/CustomExceptionsTests.cs +++ b/test/DurableTask.Core.Tests/CustomExceptionsTests.cs @@ -106,8 +106,9 @@ public void CustomExceptionSerialization() var streamingContext = new StreamingContext(); +#pragma warning disable SYSLIB0050 // Formatter-based serialization is obsolete - needed for testing var info = new SerializationInfo(_, new FormatterConverter()); - +#pragma warning restore SYSLIB0050 // ReSharper disable once AccessToModifiedClosure // Add base properties ignoredProperties.ToList().ForEach(keyValuePair => info.AddValue(keyValuePair.Key, GetValueFromType(keyValuePair.Value))); @@ -130,8 +131,12 @@ public void CustomExceptionSerialization() var exception = (Exception)constructor.Invoke(new object[] { info, streamingContext }); // Make sure the values were serialized property +#pragma warning disable SYSLIB0050 // Formatter-based serialization is obsolete - needed for testing info = new SerializationInfo(_, new FormatterConverter()); +#pragma warning restore SYSLIB0050 +#pragma warning disable SYSLIB0051 exception.GetObjectData(info, streamingContext); +#pragma warning restore SYSLIB0051 foreach (PropertyInfo propertyInfo in properties) { diff --git a/test/DurableTask.Core.Tests/DurableTask.Core.Tests.csproj b/test/DurableTask.Core.Tests/DurableTask.Core.Tests.csproj index 7012064ad..6068df4e2 100644 --- a/test/DurableTask.Core.Tests/DurableTask.Core.Tests.csproj +++ b/test/DurableTask.Core.Tests/DurableTask.Core.Tests.csproj @@ -2,7 +2,7 @@ - net6.0;net48 + net8.0;net48 diff --git a/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs b/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs index a82897ca1..f9a2e77a4 100644 --- a/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs +++ b/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs @@ -486,6 +486,7 @@ public CustomBusinessException(string message, string userId, string businessCon TestNullObject = null; // Explicitly set to null for testing } +#pragma warning disable SYSLIB0051, CS0672 protected CustomBusinessException(SerializationInfo info, StreamingContext context) : base(info, context) { @@ -501,6 +502,7 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont info.AddValue(nameof(BusinessContext), BusinessContext); info.AddValue(nameof(TestNullObject), TestNullObject); } +#pragma warning restore SYSLIB0051, CS0672 } // Test provider that includes null values in different ways @@ -538,10 +540,12 @@ public CustomException(string message) { } +#pragma warning disable SYSLIB0051 protected CustomException(SerializationInfo info, StreamingContext context) : base(info, context) { } +#pragma warning restore SYSLIB0051 } [TestMethod] diff --git a/test/DurableTask.Core.Tests/WorkItemDispatcherTests.cs b/test/DurableTask.Core.Tests/WorkItemDispatcherTests.cs index 275503748..549937386 100644 --- a/test/DurableTask.Core.Tests/WorkItemDispatcherTests.cs +++ b/test/DurableTask.Core.Tests/WorkItemDispatcherTests.cs @@ -404,7 +404,9 @@ public InMemoryLogger(ConcurrentBag logs) this.logs = logs; } +#pragma warning disable CS8633, CS8766 public IDisposable BeginScope(TState state) => NoOpDisposable.Instance; +#pragma warning restore CS8633, CS8766 public bool IsEnabled(LogLevel logLevel) => true; diff --git a/test/DurableTask.Emulator.Tests/DurableTask.Emulator.Tests.csproj b/test/DurableTask.Emulator.Tests/DurableTask.Emulator.Tests.csproj index f660dde7d..b0de3cbd9 100644 --- a/test/DurableTask.Emulator.Tests/DurableTask.Emulator.Tests.csproj +++ b/test/DurableTask.Emulator.Tests/DurableTask.Emulator.Tests.csproj @@ -2,7 +2,7 @@ - net6.0;net48 + net8.0;net48 diff --git a/test/DurableTask.ServiceBus.Tests/DurableTask.ServiceBus.Tests.csproj b/test/DurableTask.ServiceBus.Tests/DurableTask.ServiceBus.Tests.csproj index 6029411f4..c3bf4e3de 100644 --- a/test/DurableTask.ServiceBus.Tests/DurableTask.ServiceBus.Tests.csproj +++ b/test/DurableTask.ServiceBus.Tests/DurableTask.ServiceBus.Tests.csproj @@ -2,7 +2,7 @@ - net6.0;net48 + net8.0;net48 @@ -28,7 +28,7 @@ - + @@ -60,7 +60,7 @@ - + PreserveNewest diff --git a/test/DurableTask.Stress.Tests/DurableTask.Stress.Tests.csproj b/test/DurableTask.Stress.Tests/DurableTask.Stress.Tests.csproj index 0a04d077f..c3e45b48b 100644 --- a/test/DurableTask.Stress.Tests/DurableTask.Stress.Tests.csproj +++ b/test/DurableTask.Stress.Tests/DurableTask.Stress.Tests.csproj @@ -2,7 +2,7 @@ - net6.0;net48 + net8.0;net48 DurableTask.Stress.Tests.Program Exe From e1078d8396050b6010b7727472488ee191ebdc53 Mon Sep 17 00:00:00 2001 From: Bernd Verst Date: Thu, 19 Mar 2026 17:13:07 -0700 Subject: [PATCH 02/12] Address PR review comments - Fix GetBlobsByHierarchyAsync delimiter from null to '/' to preserve hierarchy semantics - Add explanatory comment for SYSLIB0050 suppression in LegacyTableEntityConverter --- .../DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs | 2 +- .../Obsolete/LegacyTableEntityConverter.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs b/test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs index 7a949d75f..6f1cec739 100644 --- a/test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs +++ b/test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs @@ -2236,7 +2236,7 @@ private static async Task ValidateLargeMessageBlobUrlAsync(string taskHubName, s BlobContainerClient container = serviceClient.GetBlobContainerClient(containerName); Assert.IsTrue(await container.ExistsAsync(), $"Blob container {containerName} is expected to exist."); BlobItem blob = await container - .GetBlobsByHierarchyAsync(traits: BlobTraits.Metadata, states: BlobStates.None, delimiter: null, prefix: sanitizedInstanceId, cancellationToken: default) + .GetBlobsByHierarchyAsync(traits: BlobTraits.Metadata, states: BlobStates.None, delimiter: "/", prefix: sanitizedInstanceId, cancellationToken: default) .Where(x => x.IsBlob && x.Blob.Name == sanitizedInstanceId + "/" + blobName) .Select(x => x.Blob) .SingleOrDefaultAsync(); diff --git a/test/DurableTask.AzureStorage.Tests/Obsolete/LegacyTableEntityConverter.cs b/test/DurableTask.AzureStorage.Tests/Obsolete/LegacyTableEntityConverter.cs index fa3103dd1..080f5321e 100644 --- a/test/DurableTask.AzureStorage.Tests/Obsolete/LegacyTableEntityConverter.cs +++ b/test/DurableTask.AzureStorage.Tests/Obsolete/LegacyTableEntityConverter.cs @@ -76,6 +76,8 @@ public object ConvertFromTableEntity(DynamicTableEntity tableEntity, Func Date: Thu, 19 Mar 2026 17:18:53 -0700 Subject: [PATCH 03/12] Update CI pipelines to install .NET 8 SDK - Replace .NET 6 SDK install with .NET 8 in Azure DevOps build-steps.yml - Add .NET 8 SDK setup step in CodeQL GitHub Actions workflow --- .github/workflows/codeQL.yml | 5 +++++ eng/templates/build-steps.yml | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeQL.yml b/.github/workflows/codeQL.yml index 58eac188b..dae391413 100644 --- a/.github/workflows/codeQL.yml +++ b/.github/workflows/codeQL.yml @@ -66,6 +66,11 @@ jobs: with: dotnet-version: '3.1.x' + - name: Set up .NET 8 + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '8.0.x' + - name: Restore dependencies run: dotnet restore $solution diff --git a/eng/templates/build-steps.yml b/eng/templates/build-steps.yml index 746a1dba7..cb6300182 100644 --- a/eng/templates/build-steps.yml +++ b/eng/templates/build-steps.yml @@ -25,10 +25,10 @@ steps: version: '3.1.x' - task: UseDotNet@2 - displayName: 'Use the .NET 6 SDK' + displayName: 'Use the .NET 8 SDK' inputs: packageType: 'sdk' - version: '6.0.x' + version: '8.0.x' - task: DotNetCoreCLI@2 displayName: 'Restore nuget dependencies' From 9eea4d27af80e114f8f7c93a30b7df357655623f Mon Sep 17 00:00:00 2001 From: Bernd Verst Date: Thu, 19 Mar 2026 18:56:37 -0700 Subject: [PATCH 04/12] Address review comments: conditional compilation and tighter pragma scoping - Use #if NET8_0_OR_GREATER for BeginScope to match ILogger contract per TFM - Split pragma suppressions for serialization ctor and GetObjectData into separate blocks --- .../ExceptionHandlingIntegrationTests.cs | 4 +++- test/DurableTask.Core.Tests/WorkItemDispatcherTests.cs | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs b/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs index f9a2e77a4..b21790a69 100644 --- a/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs +++ b/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs @@ -486,7 +486,7 @@ public CustomBusinessException(string message, string userId, string businessCon TestNullObject = null; // Explicitly set to null for testing } -#pragma warning disable SYSLIB0051, CS0672 +#pragma warning disable SYSLIB0051 protected CustomBusinessException(SerializationInfo info, StreamingContext context) : base(info, context) { @@ -494,7 +494,9 @@ protected CustomBusinessException(SerializationInfo info, StreamingContext conte BusinessContext = info.GetString(nameof(BusinessContext)) ?? string.Empty; TestNullObject = info.GetString(nameof(TestNullObject)); // This will be null } +#pragma warning restore SYSLIB0051 +#pragma warning disable SYSLIB0051, CS0672 public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); diff --git a/test/DurableTask.Core.Tests/WorkItemDispatcherTests.cs b/test/DurableTask.Core.Tests/WorkItemDispatcherTests.cs index 549937386..0e05f3e07 100644 --- a/test/DurableTask.Core.Tests/WorkItemDispatcherTests.cs +++ b/test/DurableTask.Core.Tests/WorkItemDispatcherTests.cs @@ -404,9 +404,11 @@ public InMemoryLogger(ConcurrentBag logs) this.logs = logs; } -#pragma warning disable CS8633, CS8766 +#if NET8_0_OR_GREATER + public IDisposable? BeginScope(TState state) where TState : notnull => NoOpDisposable.Instance; +#else public IDisposable BeginScope(TState state) => NoOpDisposable.Instance; -#pragma warning restore CS8633, CS8766 +#endif public bool IsEnabled(LogLevel logLevel) => true; From 68bcb49dad1f1f820619254c64228cd7ab39d91d Mon Sep 17 00:00:00 2001 From: Bernd Verst Date: Thu, 19 Mar 2026 20:33:33 -0700 Subject: [PATCH 05/12] Increase DTFxASValidate job timeout to 90 minutes Azure Storage tests exceed the default 60-minute job timeout due to the larger test suite and Azurite emulator startup overhead. --- eng/ci/public-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/ci/public-build.yml b/eng/ci/public-build.yml index 95b1518a3..0f3ea2f6d 100644 --- a/eng/ci/public-build.yml +++ b/eng/ci/public-build.yml @@ -79,6 +79,7 @@ extends: dependsOn: [] jobs: - job: Validate + timeoutInMinutes: 90 strategy: parallel: 13 steps: From 6ff4ee8e6865acacc791efe580b85142cf22e48b Mon Sep 17 00:00:00 2001 From: Bernd Verst Date: Thu, 19 Mar 2026 22:17:23 -0700 Subject: [PATCH 06/12] Increase DTFxASValidate timeout to 150min and update vsTestVersion - Increase DTFxASValidate job timeout from 90 to 150 minutes - Update VSTest version from 17.0 to latest for better .NET 8 test runtime support --- eng/ci/public-build.yml | 2 +- eng/templates/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/ci/public-build.yml b/eng/ci/public-build.yml index 0f3ea2f6d..6a262ff61 100644 --- a/eng/ci/public-build.yml +++ b/eng/ci/public-build.yml @@ -79,7 +79,7 @@ extends: dependsOn: [] jobs: - job: Validate - timeoutInMinutes: 90 + timeoutInMinutes: 150 strategy: parallel: 13 steps: diff --git a/eng/templates/test.yml b/eng/templates/test.yml index eed9f610f..e8dcb95c8 100644 --- a/eng/templates/test.yml +++ b/eng/templates/test.yml @@ -36,7 +36,7 @@ steps: testAssemblyVer2: | $(System.DefaultWorkingDirectory)/${{ parameters.testAssembly }} testFiltercriteria: 'TestCategory!=DisabledInCI' - vsTestVersion: 17.0 + vsTestVersion: latest distributionBatchType: basedOnExecutionTime platform: 'any cpu' configuration: 'Debug' From a7962eb14b96d1a0afe5c3254c92efddeaf3c5e1 Mon Sep 17 00:00:00 2001 From: Bernd Verst Date: Thu, 19 Mar 2026 23:23:53 -0700 Subject: [PATCH 07/12] Fix GetBlobsByHierarchyAsync delimiter to null (original behavior) The previous fix changed delimiter from null (the SDK default) to '/' per a review suggestion. However, with delimiter='/' and prefix without a trailing slash, blobs are returned as virtual directory prefixes (IsBlob=false) rather than blob items, causing the Where(x => x.IsBlob) filter to return no results. --- .../DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs b/test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs index 6f1cec739..7a949d75f 100644 --- a/test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs +++ b/test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs @@ -2236,7 +2236,7 @@ private static async Task ValidateLargeMessageBlobUrlAsync(string taskHubName, s BlobContainerClient container = serviceClient.GetBlobContainerClient(containerName); Assert.IsTrue(await container.ExistsAsync(), $"Blob container {containerName} is expected to exist."); BlobItem blob = await container - .GetBlobsByHierarchyAsync(traits: BlobTraits.Metadata, states: BlobStates.None, delimiter: "/", prefix: sanitizedInstanceId, cancellationToken: default) + .GetBlobsByHierarchyAsync(traits: BlobTraits.Metadata, states: BlobStates.None, delimiter: null, prefix: sanitizedInstanceId, cancellationToken: default) .Where(x => x.IsBlob && x.Blob.Name == sanitizedInstanceId + "/" + blobName) .Select(x => x.Blob) .SingleOrDefaultAsync(); From 60f7a9ec68a0ea85856160e46ec91426e3944294 Mon Sep 17 00:00:00 2001 From: Bernd Verst Date: Thu, 19 Mar 2026 23:35:22 -0700 Subject: [PATCH 08/12] Fix sync-over-async blocking patterns causing .NET 8 threadpool starvation Move service.CreateAsync() from TestOrchestrationHost constructor (where it blocked synchronously via .GetAwaiter().GetResult()) to StartAsync() (where it is properly awaited). This eliminates the primary source of threadpool starvation when many tests run in parallel under .NET 8. Also convert remaining .Result and .GetAwaiter().GetResult() calls to proper await patterns in AzureStorageScaleTests and TestTablePartitionManager. --- .../AzureStorageScaleTests.cs | 13 ++++++++----- .../TestOrchestrationHost.cs | 6 +++--- .../TestTablePartitionManager.cs | 8 ++++---- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/test/DurableTask.AzureStorage.Tests/AzureStorageScaleTests.cs b/test/DurableTask.AzureStorage.Tests/AzureStorageScaleTests.cs index b31326a78..7d7ac8a78 100644 --- a/test/DurableTask.AzureStorage.Tests/AzureStorageScaleTests.cs +++ b/test/DurableTask.AzureStorage.Tests/AzureStorageScaleTests.cs @@ -145,7 +145,7 @@ async Task EnsureTaskHubAsync( try { - Assert.IsTrue(trackingStore.ExistsAsync().Result, $"Tracking Store was not created."); + Assert.IsTrue(await trackingStore.ExistsAsync(), $"Tracking Store was not created."); } catch (NotSupportedException) { } @@ -182,7 +182,7 @@ async Task EnsureTaskHubAsync( try { - Assert.IsFalse(trackingStore.ExistsAsync().Result, $"Tracking Store was not deleted."); + Assert.IsFalse(await trackingStore.ExistsAsync(), $"Tracking Store was not deleted."); } catch (NotSupportedException) { } @@ -322,9 +322,12 @@ public async Task MultiWorkerLeaseMovement(PartitionManagerType partitionManager Assert.IsTrue( service.OwnedControlQueues.All(q => ownedLeases.Any(l => l.Name.Contains(q.Name))), "Mismatch between queue assignment and lease ownership."); - Assert.IsTrue( - service.OwnedControlQueues.All(q => q.InnerQueue.ExistsAsync().GetAwaiter().GetResult()), - $"One or more control queues owned by {service.WorkerId} do not exist"); + foreach (var q in service.OwnedControlQueues) + { + Assert.IsTrue( + await q.InnerQueue.ExistsAsync(), + $"Control queue {q.Name} owned by {service.WorkerId} does not exist"); + } } Assert.AreEqual(totalLeaseCount, allQueueNames.Count, "Unexpected number of queues!"); diff --git a/test/DurableTask.AzureStorage.Tests/TestOrchestrationHost.cs b/test/DurableTask.AzureStorage.Tests/TestOrchestrationHost.cs index f5e630b6f..34210c00f 100644 --- a/test/DurableTask.AzureStorage.Tests/TestOrchestrationHost.cs +++ b/test/DurableTask.AzureStorage.Tests/TestOrchestrationHost.cs @@ -39,7 +39,6 @@ internal sealed class TestOrchestrationHost : IDisposable public TestOrchestrationHost(AzureStorageOrchestrationServiceSettings settings, VersioningSettings versioningSettings = null) { this.service = new AzureStorageOrchestrationService(settings); - this.service.CreateAsync().GetAwaiter().GetResult(); this.settings = settings; this.worker = new TaskHubWorker(service, loggerFactory: settings.LoggerFactory, versioningSettings: versioningSettings); @@ -55,9 +54,10 @@ public void Dispose() this.worker.Dispose(); } - public Task StartAsync() + public async Task StartAsync() { - return this.worker.StartAsync(); + await this.service.CreateAsync(); + await this.worker.StartAsync(); } public Task StopAsync() diff --git a/test/DurableTask.AzureStorage.Tests/TestTablePartitionManager.cs b/test/DurableTask.AzureStorage.Tests/TestTablePartitionManager.cs index 3c41974c1..7fdb42e04 100644 --- a/test/DurableTask.AzureStorage.Tests/TestTablePartitionManager.cs +++ b/test/DurableTask.AzureStorage.Tests/TestTablePartitionManager.cs @@ -701,12 +701,12 @@ await WaitForConditionAsync( // read the partition table var results = partitionTable.ExecuteQueryAsync(); - var numResults = results.CountAsync().Result; + var numResults = await results.CountAsync(); Assert.AreEqual(numResults, 1); // there should only be 1 partition // We want to test that worker 0 starts listening to the control queue without claiming the lease. // Therefore, we force the table to be in a state where worker 0 is still the current owner of the partition. - var partitionData = results.FirstAsync().Result; + var partitionData = await results.FirstAsync(); partitionData.NextOwner = null; partitionData.IsDraining = false; partitionData.CurrentOwner = "0"; @@ -715,9 +715,9 @@ await WaitForConditionAsync( // guarantee table is corrrectly updated results = partitionTable.ExecuteQueryAsync(); - numResults = results.CountAsync().Result; + numResults = await results.CountAsync(); Assert.AreEqual(numResults, 1); // there should only be 1 partition - Assert.AreEqual(results.FirstAsync().Result.CurrentOwner, "0"); // ensure current owner is partition "0" + Assert.AreEqual((await results.FirstAsync()).CurrentOwner, "0"); // ensure current owner is partition "0" // create and start new worker with the same settings, ensure it is actively listening to the queue worker = new TaskHubWorker(service); From 7e534307ded6f3f98db2a6067d4b39cd49717091 Mon Sep 17 00:00:00 2001 From: Bernd Verst Date: Thu, 19 Mar 2026 23:36:58 -0700 Subject: [PATCH 09/12] Address review comments: condition RegularExpressions, pin vsTestVersion, add pragma comments - Condition System.Text.RegularExpressions PackageReference on net48 only - Revert vsTestVersion from 'latest' back to '17.0' for CI reproducibility - Add justification comments on SYSLIB0051 pragma suppressions in ExceptionHandlingIntegrationTests.cs --- eng/templates/test.yml | 2 +- .../DurableTask.AzureStorage.Tests.csproj | 5 ++++- .../ExceptionHandlingIntegrationTests.cs | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/eng/templates/test.yml b/eng/templates/test.yml index e8dcb95c8..0e492bed0 100644 --- a/eng/templates/test.yml +++ b/eng/templates/test.yml @@ -36,7 +36,7 @@ steps: testAssemblyVer2: | $(System.DefaultWorkingDirectory)/${{ parameters.testAssembly }} testFiltercriteria: 'TestCategory!=DisabledInCI' - vsTestVersion: latest + vsTestVersion: '17.0' distributionBatchType: basedOnExecutionTime platform: 'any cpu' configuration: 'Debug' diff --git a/test/DurableTask.AzureStorage.Tests/DurableTask.AzureStorage.Tests.csproj b/test/DurableTask.AzureStorage.Tests/DurableTask.AzureStorage.Tests.csproj index 3d00b0228..c1b403fe6 100644 --- a/test/DurableTask.AzureStorage.Tests/DurableTask.AzureStorage.Tests.csproj +++ b/test/DurableTask.AzureStorage.Tests/DurableTask.AzureStorage.Tests.csproj @@ -51,8 +51,11 @@ - + + + + Always diff --git a/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs b/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs index b21790a69..d4ab393f9 100644 --- a/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs +++ b/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs @@ -486,6 +486,7 @@ public CustomBusinessException(string message, string userId, string businessCon TestNullObject = null; // Explicitly set to null for testing } + // Intentional use of obsolete BinaryFormatter serialization APIs for test coverage of legacy serialization paths. #pragma warning disable SYSLIB0051 protected CustomBusinessException(SerializationInfo info, StreamingContext context) : base(info, context) @@ -496,6 +497,7 @@ protected CustomBusinessException(SerializationInfo info, StreamingContext conte } #pragma warning restore SYSLIB0051 + // Intentional use of obsolete BinaryFormatter serialization APIs for test coverage of legacy serialization paths. #pragma warning disable SYSLIB0051, CS0672 public override void GetObjectData(SerializationInfo info, StreamingContext context) { @@ -542,6 +544,7 @@ public CustomException(string message) { } + // Intentional use of obsolete BinaryFormatter serialization APIs for test coverage of legacy serialization paths. #pragma warning disable SYSLIB0051 protected CustomException(SerializationInfo info, StreamingContext context) : base(info, context) From 5729c87395e6db59640852f01eaf682a55a01328 Mon Sep 17 00:00:00 2001 From: Bernd Verst Date: Fri, 20 Mar 2026 01:35:31 -0700 Subject: [PATCH 10/12] Reduce console log level to Warning, remove excessive timeout Reduce TestHelpers LoggerFactory console log level from Trace to Warning to reduce contention from verbose output under Microsoft.Extensions.Logging 10.x. Remove the 150-minute timeout override (revert to default) since the sync-over-async fix should prevent threadpool starvation hangs. --- eng/ci/public-build.yml | 1 - test/DurableTask.AzureStorage.Tests/TestHelpers.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/eng/ci/public-build.yml b/eng/ci/public-build.yml index 6a262ff61..95b1518a3 100644 --- a/eng/ci/public-build.yml +++ b/eng/ci/public-build.yml @@ -79,7 +79,6 @@ extends: dependsOn: [] jobs: - job: Validate - timeoutInMinutes: 150 strategy: parallel: 13 steps: diff --git a/test/DurableTask.AzureStorage.Tests/TestHelpers.cs b/test/DurableTask.AzureStorage.Tests/TestHelpers.cs index 4264c1e92..7ed63f64d 100644 --- a/test/DurableTask.AzureStorage.Tests/TestHelpers.cs +++ b/test/DurableTask.AzureStorage.Tests/TestHelpers.cs @@ -53,7 +53,7 @@ public static AzureStorageOrchestrationServiceSettings GetTestAzureStorageOrches var loggerFactory = LoggerFactory.Create(builder => { - builder.AddConsole().SetMinimumLevel(LogLevel.Trace); + builder.AddConsole().SetMinimumLevel(LogLevel.Warning); }); var settings = new AzureStorageOrchestrationServiceSettings From 26fa1b59bd9047cc47a0ef5985b234d2933cc13e Mon Sep 17 00:00:00 2001 From: Bernd Verst Date: Fri, 20 Mar 2026 02:52:16 -0700 Subject: [PATCH 11/12] Fix Azurite API version compatibility with Azure.Storage.Blobs 12.27.0 Azure.Storage.Blobs 12.27.0 uses API version 2026-02-06 which is not supported by the Azurite version installed in CI via 'npm install -g azurite'. This causes tests to hang as the SDK's retry logic repeatedly fails. Add --skipApiVersionCheck to the Azurite startup command to allow the newer API version to work with the installed Azurite version. --- eng/templates/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/templates/test.yml b/eng/templates/test.yml index 0e492bed0..f4fc29a7f 100644 --- a/eng/templates/test.yml +++ b/eng/templates/test.yml @@ -24,7 +24,7 @@ steps: npm install -g azurite mkdir azurite1 echo "azurite installed" - azurite --silent --location azurite1 --debug azurite1\debug.txt --queuePort 10001 & + azurite --silent --location azurite1 --debug azurite1\debug.txt --queuePort 10001 --skipApiVersionCheck & echo "azurite started" sleep 5 displayName: 'Install and Run Azurite' From 0f0053f127f0c87553c565fdc27ff191c5d6f859 Mon Sep 17 00:00:00 2001 From: Bernd Verst Date: Fri, 20 Mar 2026 10:04:26 -0700 Subject: [PATCH 12/12] Fix OpenTelemetry tests for SDK 1.14.0 compatibility The BaseProcessor in OpenTelemetry SDK 1.14.0 (transitive from Azure.Monitor.OpenTelemetry.Exporter 1.6.0) changed method signatures - OnShutdown and OnForceFlush now take int timeoutMilliseconds parameters. This broke the hard-coded processor.Invocations[index] pattern which assumed fixed invocation ordering. Replace fragile index-based access with filtering for OnEnd method invocations, which gives us only the completed Activity objects regardless of SDK-internal invocation changes. --- .../AzureStorageScenarioTests.cs | 265 +++++++----------- 1 file changed, 108 insertions(+), 157 deletions(-) diff --git a/test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs b/test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs index 7a949d75f..d7a8fccaa 100644 --- a/test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs +++ b/test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs @@ -3698,58 +3698,47 @@ public async Task OpenTelemetry_SayHelloWithActivity(bool enableExtendedSessions } } - // (1) Explanation about indexes: - // The orchestration Activity's start at Invocation[1] and each action logs - // two activities - (Processor.OnStart(Activity) and Processor.OnEnd(Activity) - // The Activity for orchestration execution is "started" (with the same Id, SpanId, etc.) - // upon every replay of the orchestration so will have an OnStart invocation for each such replay, - // but an OnEnd at the end of orchestration execution. - // The first OnEnd invocation is at index 2, so we start from there. - - // (2) Additional invocations: - // processor.Invocations[0] - processor.SetParentProvider(TracerProviderSdk) - // processor.Invocations[10] - processor.OnShutdown() - // processor.Invocations[11] - processor.Dispose(true) + // Collect only the OnEnd activities from the processor invocations. + // Other invocations (SetParentProvider, OnStart, OnShutdown, OnForceFlush, Dispose) + // vary across OpenTelemetry SDK versions, so we filter by method name. + var endedActivities = processor.Invocations + .Where(i => i.Method.Name == "OnEnd") + .Select(i => (Activity)i.Arguments[0]) + .ToList(); + + Assert.AreEqual(4, endedActivities.Count); // Create orchestration Activity - Activity activity2 = (Activity)processor.Invocations[2].Arguments[0]; + Activity createOrchestration = endedActivities[0]; // Task execution Activity - Activity activity5 = (Activity)processor.Invocations[5].Arguments[0]; + Activity taskExecution = endedActivities[1]; // Task completed Activity - Activity activity8 = (Activity)processor.Invocations[8].Arguments[0]; + Activity taskCompleted = endedActivities[2]; // Orchestration execution Activity - Activity activity9 = (Activity)processor.Invocations[9].Arguments[0]; - - // Checking total number activities - Assert.AreEqual(12, processor.Invocations.Count); + Activity orchestrationExecution = endedActivities[3]; // Checking tag values - string activity2TypeValue = activity2.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; - string activity5TypeValue = activity5.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; - string activity8TypeValue = activity8.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; - string activity9TypeValue = activity9.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; - - ActivityKind activity2Kind = activity2.Kind; - ActivityKind activity5Kind = activity5.Kind; - ActivityKind activity8Kind = activity8.Kind; - ActivityKind activity9Kind = activity9.Kind; - - Assert.AreEqual("orchestration", activity2TypeValue); - Assert.AreEqual("activity", activity5TypeValue); - Assert.AreEqual("activity", activity8TypeValue); - Assert.AreEqual("orchestration", activity9TypeValue); - Assert.AreEqual(ActivityKind.Producer, activity2Kind); - Assert.AreEqual(ActivityKind.Server, activity5Kind); - Assert.AreEqual(ActivityKind.Client, activity8Kind); - Assert.AreEqual(ActivityKind.Server, activity9Kind); + string createOrchestrationTypeValue = createOrchestration.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; + string taskExecutionTypeValue = taskExecution.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; + string taskCompletedTypeValue = taskCompleted.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; + string orchestrationExecutionTypeValue = orchestrationExecution.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; + + Assert.AreEqual("orchestration", createOrchestrationTypeValue); + Assert.AreEqual("activity", taskExecutionTypeValue); + Assert.AreEqual("activity", taskCompletedTypeValue); + Assert.AreEqual("orchestration", orchestrationExecutionTypeValue); + Assert.AreEqual(ActivityKind.Producer, createOrchestration.Kind); + Assert.AreEqual(ActivityKind.Server, taskExecution.Kind); + Assert.AreEqual(ActivityKind.Client, taskCompleted.Kind); + Assert.AreEqual(ActivityKind.Server, orchestrationExecution.Kind); // Checking span ID correlation between parent and child - Assert.AreEqual(activity2.SpanId, activity9.ParentSpanId); - Assert.AreEqual(activity8.SpanId, activity5.ParentSpanId); - Assert.AreEqual(activity9.SpanId, activity8.ParentSpanId); + Assert.AreEqual(createOrchestration.SpanId, orchestrationExecution.ParentSpanId); + Assert.AreEqual(taskCompleted.SpanId, taskExecution.ParentSpanId); + Assert.AreEqual(orchestrationExecution.SpanId, taskCompleted.ParentSpanId); // Checking trace ID values - Assert.AreEqual(activity2.TraceId.ToString(), activity5.TraceId.ToString(), activity8.TraceId.ToString(), activity9.TraceId.ToString()); + Assert.AreEqual(createOrchestration.TraceId.ToString(), taskExecution.TraceId.ToString(), taskCompleted.TraceId.ToString(), orchestrationExecution.TraceId.ToString()); } /// @@ -3787,52 +3776,40 @@ public async Task OpenTelemetry_ExternalEvent_RaiseEvent(bool enableExtendedSess } } - // (1) Explanation about indexes: - // The orchestration Activity's start at Invocation[1] and each action logs - // two activities - (Processor.OnStart(Activity) and Processor.OnEnd(Activity) - // The Activity for orchestration execution is "started" (with the same Id, SpanId, etc.) - // upon every replay of the orchestration so will have an OnStart invocation for each such replay, - // but an OnEnd at the end of orchestration execution. - // The first OnEnd invocation is at index 2, so we start from there. + // Collect only the OnEnd activities from the processor invocations. + var endedActivities = processor.Invocations + .Where(i => i.Method.Name == "OnEnd") + .Select(i => (Activity)i.Arguments[0]) + .ToList(); - // (2) Additional invocations: - // processor.Invocations[0] - processor.SetParentProvider(TracerProviderSdk) - // processor.Invocations[8] - processor.OnShutdown() - // processor.Invocations[9] - processor.Dispose(true) + Assert.AreEqual(3, endedActivities.Count); // Create orchestration Activity - Activity activity2 = (Activity)processor.Invocations[2].Arguments[0]; + Activity createOrchestration = endedActivities[0]; // External event Activity - Activity activity5 = (Activity)processor.Invocations[5].Arguments[0]; + Activity externalEvent = endedActivities[1]; // Orchestration execution Activity - Activity activity7 = (Activity)processor.Invocations[7].Arguments[0]; - - // Checking total number activities - Assert.AreEqual(10, processor.Invocations.Count); + Activity orchestrationExecution = endedActivities[2]; // Checking tag values - string activity2TypeValue = activity2.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; - string activity5TypeValue = activity5.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; - string activity7TypeValue = activity7.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; - string activity5TargetInstanceIdValue = activity5.Tags.First(k => (k.Key).Equals("durabletask.event.target_instance_id")).Value; - - ActivityKind activity2Kind = activity2.Kind; - ActivityKind activity5Kind = activity5.Kind; - ActivityKind activity7Kind = activity7.Kind; - - Assert.AreEqual("orchestration", activity2TypeValue); - Assert.AreEqual("event", activity5TypeValue); - Assert.AreEqual("orchestration", activity7TypeValue); - Assert.AreEqual(instanceId, activity5TargetInstanceIdValue); - Assert.AreEqual(ActivityKind.Producer, activity2Kind); - Assert.AreEqual(ActivityKind.Producer, activity5Kind); - Assert.AreEqual(ActivityKind.Server, activity7Kind); + string createOrchestrationTypeValue = createOrchestration.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; + string externalEventTypeValue = externalEvent.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; + string orchestrationExecutionTypeValue = orchestrationExecution.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; + string externalEventTargetInstanceIdValue = externalEvent.Tags.First(k => (k.Key).Equals("durabletask.event.target_instance_id")).Value; + + Assert.AreEqual("orchestration", createOrchestrationTypeValue); + Assert.AreEqual("event", externalEventTypeValue); + Assert.AreEqual("orchestration", orchestrationExecutionTypeValue); + Assert.AreEqual(instanceId, externalEventTargetInstanceIdValue); + Assert.AreEqual(ActivityKind.Producer, createOrchestration.Kind); + Assert.AreEqual(ActivityKind.Producer, externalEvent.Kind); + Assert.AreEqual(ActivityKind.Server, orchestrationExecution.Kind); // Checking span ID correlation between parent and child - Assert.AreEqual(activity2.SpanId, activity7.ParentSpanId); + Assert.AreEqual(createOrchestration.SpanId, orchestrationExecution.ParentSpanId); // Checking trace ID values (the external event from the client is its own trace) - Assert.AreEqual(activity2.TraceId.ToString(), activity7.TraceId.ToString()); + Assert.AreEqual(createOrchestration.TraceId.ToString(), orchestrationExecution.TraceId.ToString()); } /// @@ -3866,51 +3843,39 @@ public async Task OpenTelemetry_TimerFired(bool enableExtendedSessions) } } - // (1) Explanation about indexes: - // The orchestration Activity's start at Invocation[1] and each action logs - // two activities - (Processor.OnStart(Activity) and Processor.OnEnd(Activity) - // The Activity for orchestration execution is "started" (with the same Id, SpanId, etc.) - // upon every replay of the orchestration so will have an OnStart invocation for each such replay, - // but an OnEnd at the end of orchestration execution. - // The first OnEnd invocation is at index 2, so we start from there. + // Collect only the OnEnd activities from the processor invocations. + var endedActivities = processor.Invocations + .Where(i => i.Method.Name == "OnEnd") + .Select(i => (Activity)i.Arguments[0]) + .ToList(); - // (2) Additional invocations: - // processor.Invocations[0] - processor.SetParentProvider(TracerProviderSdk) - // processor.Invocations[8] - processor.OnShutdown() - // processor.Invocations[9] - processor.Dispose(true) + Assert.AreEqual(3, endedActivities.Count); // Create orchestration Activity - Activity activity2 = (Activity)processor.Invocations[2].Arguments[0]; + Activity createOrchestration = endedActivities[0]; // Timer fired Activity - Activity activity6 = (Activity)processor.Invocations[6].Arguments[0]; + Activity timerFired = endedActivities[1]; // Orchestration execution Activity - Activity activity7 = (Activity)processor.Invocations[7].Arguments[0]; - - // Checking total number activities - Assert.AreEqual(10, processor.Invocations.Count); + Activity orchestrationExecution = endedActivities[2]; // Checking tag values - string activity2TypeValue = activity2.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; - string activity6TypeValue = activity6.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; - string activity7TypeValue = activity7.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; - - ActivityKind activity2Kind = activity2.Kind; - ActivityKind activity6Kind = activity6.Kind; - ActivityKind activity7Kind = activity7.Kind; + string createOrchestrationTypeValue = createOrchestration.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; + string timerFiredTypeValue = timerFired.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; + string orchestrationExecutionTypeValue = orchestrationExecution.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; - Assert.AreEqual("orchestration", activity2TypeValue); - Assert.AreEqual("timer", activity6TypeValue); - Assert.AreEqual("orchestration", activity7TypeValue); - Assert.AreEqual(ActivityKind.Producer, activity2Kind); - Assert.AreEqual(ActivityKind.Internal, activity6Kind); - Assert.AreEqual(ActivityKind.Server, activity7Kind); + Assert.AreEqual("orchestration", createOrchestrationTypeValue); + Assert.AreEqual("timer", timerFiredTypeValue); + Assert.AreEqual("orchestration", orchestrationExecutionTypeValue); + Assert.AreEqual(ActivityKind.Producer, createOrchestration.Kind); + Assert.AreEqual(ActivityKind.Internal, timerFired.Kind); + Assert.AreEqual(ActivityKind.Server, orchestrationExecution.Kind); // Checking span ID correlation between parent and child - Assert.AreEqual(activity2.SpanId, activity7.ParentSpanId); - Assert.AreEqual(activity7.SpanId, activity6.ParentSpanId); + Assert.AreEqual(createOrchestration.SpanId, orchestrationExecution.ParentSpanId); + Assert.AreEqual(orchestrationExecution.SpanId, timerFired.ParentSpanId); // Checking trace ID values - Assert.AreEqual(activity2.TraceId.ToString(), activity6.TraceId.ToString(), activity7.TraceId.ToString()); + Assert.AreEqual(createOrchestration.TraceId.ToString(), timerFired.TraceId.ToString(), orchestrationExecution.TraceId.ToString()); } /// @@ -3945,66 +3910,52 @@ public async Task OpenTelemetry_ExternalEvent_SendEvent(bool enableExtendedSessi } } - // (1) Explanation about indexes: - // The orchestration Activity's start at Invocation[1] and each action logs - // two activities - (Processor.OnStart(Activity) and Processor.OnEnd(Activity) - // The Activity for orchestration execution is "started" (with the same Id, SpanId, etc.) - // upon every replay of the orchestration so will have an OnStart invocation for each such replay, - // but an OnEnd at the end of orchestration execution. - // The first OnEnd invocation is at index 2, so we start from there. + // Collect only the OnEnd activities from the processor invocations. + var endedActivities = processor.Invocations + .Where(i => i.Method.Name == "OnEnd") + .Select(i => (Activity)i.Arguments[0]) + .ToList(); - // (2) Additional invocations: - // processor.Invocations[0] - processor.SetParentProvider(TracerProviderSdk) - // processor.Invocations[10] - processor.OnShutdown() - // processor.Invocations[11] - processor.Dispose(true) + Assert.AreEqual(4, endedActivities.Count); - var invocations = processor.Invocations; // Create orchestration (AutoStartOrchestration) Activity - Activity activity2 = (Activity)processor.Invocations[2].Arguments[0]; + Activity createOrchestration = endedActivities[0]; // Send event to AutoStartOrchestration.Responder Activity - Activity activity5 = (Activity)processor.Invocations[5].Arguments[0]; + Activity sendEvent = endedActivities[1]; // Send event from AutoStartOrchestration.Responder back to AutoStartOrchestration Activity - Activity activity7 = (Activity)processor.Invocations[7].Arguments[0]; + Activity sendEventBack = endedActivities[2]; // Orchestration execution Activity - Activity activity9 = (Activity)processor.Invocations[9].Arguments[0]; - - // Checking total number activities - Assert.AreEqual(12, processor.Invocations.Count); + Activity orchestrationExecution = endedActivities[3]; // Checking tag values - string activity2TypeValue = activity2.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; - string activity5TypeValue = activity5.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; - string activity7TypeValue = activity7.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; - string activity9TypeValue = activity9.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; - string activity5InstanceIdValue = activity5.Tags.First(k => (k.Key).Equals("durabletask.task.instance_id")).Value; - string activity5TargetInstanceIdValue = activity5.Tags.First(k => (k.Key).Equals("durabletask.event.target_instance_id")).Value; - string activity7InstanceIdValue = activity7.Tags.First(k => (k.Key).Equals("durabletask.task.instance_id")).Value; - string activity7TargetInstanceIdValue = activity7.Tags.First(k => (k.Key).Equals("durabletask.event.target_instance_id")).Value; - - ActivityKind activity2Kind = activity2.Kind; - ActivityKind activity5Kind = activity5.Kind; - ActivityKind activity7Kind = activity7.Kind; - ActivityKind activity9Kind = activity9.Kind; - - Assert.AreEqual("orchestration", activity2TypeValue); - Assert.AreEqual("event", activity5TypeValue); - Assert.AreEqual("event", activity7TypeValue); - Assert.AreEqual("orchestration", activity9TypeValue); - Assert.AreEqual(instanceId, activity5InstanceIdValue); - Assert.AreEqual(responderId, activity5TargetInstanceIdValue); - Assert.AreEqual(responderId, activity7InstanceIdValue); - Assert.AreEqual(instanceId, activity7TargetInstanceIdValue); - Assert.AreEqual(ActivityKind.Producer, activity2Kind); - Assert.AreEqual(ActivityKind.Producer, activity5Kind); - Assert.AreEqual(ActivityKind.Producer, activity7Kind); - Assert.AreEqual(ActivityKind.Server, activity9Kind); + string createOrchestrationTypeValue = createOrchestration.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; + string sendEventTypeValue = sendEvent.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; + string sendEventBackTypeValue = sendEventBack.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; + string orchestrationExecutionTypeValue = orchestrationExecution.Tags.First(k => (k.Key).Equals("durabletask.type")).Value; + string sendEventInstanceIdValue = sendEvent.Tags.First(k => (k.Key).Equals("durabletask.task.instance_id")).Value; + string sendEventTargetInstanceIdValue = sendEvent.Tags.First(k => (k.Key).Equals("durabletask.event.target_instance_id")).Value; + string sendEventBackInstanceIdValue = sendEventBack.Tags.First(k => (k.Key).Equals("durabletask.task.instance_id")).Value; + string sendEventBackTargetInstanceIdValue = sendEventBack.Tags.First(k => (k.Key).Equals("durabletask.event.target_instance_id")).Value; + + Assert.AreEqual("orchestration", createOrchestrationTypeValue); + Assert.AreEqual("event", sendEventTypeValue); + Assert.AreEqual("event", sendEventBackTypeValue); + Assert.AreEqual("orchestration", orchestrationExecutionTypeValue); + Assert.AreEqual(instanceId, sendEventInstanceIdValue); + Assert.AreEqual(responderId, sendEventTargetInstanceIdValue); + Assert.AreEqual(responderId, sendEventBackInstanceIdValue); + Assert.AreEqual(instanceId, sendEventBackTargetInstanceIdValue); + Assert.AreEqual(ActivityKind.Producer, createOrchestration.Kind); + Assert.AreEqual(ActivityKind.Producer, sendEvent.Kind); + Assert.AreEqual(ActivityKind.Producer, sendEventBack.Kind); + Assert.AreEqual(ActivityKind.Server, orchestrationExecution.Kind); // Checking span ID correlation between parent and child - Assert.AreEqual(activity2.SpanId, activity9.ParentSpanId); - Assert.AreEqual(activity9.SpanId, activity5.ParentSpanId); + Assert.AreEqual(createOrchestration.SpanId, orchestrationExecution.ParentSpanId); + Assert.AreEqual(orchestrationExecution.SpanId, sendEvent.ParentSpanId); // Checking trace ID values - Assert.AreEqual(activity2.TraceId.ToString(), activity5.TraceId.ToString(), activity9.TraceId.ToString()); + Assert.AreEqual(createOrchestration.TraceId.ToString(), sendEvent.TraceId.ToString(), orchestrationExecution.TraceId.ToString()); } #endif