-
Notifications
You must be signed in to change notification settings - Fork 325
Add dependency version validation pipeline #1336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9ebd349
5c066ba
4ac04b0
bf40ad8
6e3b924
9a62502
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| # Validates that dependency version changes do not break the DTFx NuGet packages. | ||
| # Packs DurableTask.Core and DurableTask.Emulator from source, builds a console | ||
| # app using the local packages, runs a HelloCities orchestration with the | ||
| # Emulator backend, and validates orchestration output. | ||
| name: Dependency Version Validation | ||
|
|
||
| on: | ||
| push: | ||
| branches: [ main ] | ||
| paths: | ||
| - 'Directory.Packages.props' | ||
| - 'tools/DurableTask.props' | ||
| - 'Test/SmokeTests/**' | ||
| pull_request: | ||
| paths: | ||
| - 'Directory.Packages.props' | ||
| - 'tools/DurableTask.props' | ||
| - 'Test/SmokeTests/**' | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| dep-validation-smoke-test: | ||
| name: 'Emulator Orchestration Smoke Test (NuGet Packages)' | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 10 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-dotnet@v4 | ||
| with: { dotnet-version: '8.0.x' } | ||
|
|
||
| - name: Parse DTFx package versions | ||
| id: version | ||
| run: | | ||
| set -e | ||
| get_version() { | ||
| local v | ||
| v=$(dotnet msbuild "$1" -nologo -getProperty:PackageVersion 2>/dev/null | tail -n 1 | tr -d '\r') | ||
| [ -z "$v" ] && v=$(dotnet msbuild "$1" -nologo -getProperty:Version 2>/dev/null | tail -n 1 | tr -d '\r') | ||
| [ -z "$v" ] && { echo "FAIL: Cannot resolve version for $1" >&2; exit 1; } | ||
| echo "$v" | ||
| } | ||
| CORE_VERSION=$(get_version "src/DurableTask.Core/DurableTask.Core.csproj") | ||
| EMULATOR_VERSION=$(get_version "src/DurableTask.Emulator/DurableTask.Emulator.csproj") | ||
| echo "Core: $CORE_VERSION, Emulator: $EMULATOR_VERSION" | ||
| echo "core_version=${CORE_VERSION}" >> "$GITHUB_OUTPUT" | ||
| echo "emulator_version=${EMULATOR_VERSION}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Pack DTFx packages | ||
| run: | | ||
| set -e | ||
| PKG=Test/SmokeTests/EmulatorSmokeTest/local-packages; mkdir -p "$PKG" | ||
| dotnet build src/DurableTask.Core/DurableTask.Core.csproj -c Release | ||
| dotnet build src/DurableTask.Emulator/DurableTask.Emulator.csproj -c Release | ||
| dotnet pack src/DurableTask.Core/DurableTask.Core.csproj -c Release --no-build --output "$PKG" | ||
| dotnet pack src/DurableTask.Emulator/DurableTask.Emulator.csproj -c Release --no-build --output "$PKG" | ||
| ls -la "$PKG" | ||
|
|
||
| - name: Verify packed packages | ||
| env: | ||
| CORE_VERSION: ${{ steps.version.outputs.core_version }} | ||
| EMULATOR_VERSION: ${{ steps.version.outputs.emulator_version }} | ||
| run: | | ||
| set -e | ||
| PKG=Test/SmokeTests/EmulatorSmokeTest/local-packages | ||
| [ -f "$PKG/Microsoft.Azure.DurableTask.Core.${CORE_VERSION}.nupkg" ] && echo "OK: Core ${CORE_VERSION}" || { echo "FAIL: Core missing"; ls -1 "$PKG"; exit 1; } | ||
| [ -f "$PKG/Microsoft.Azure.DurableTask.Emulator.${EMULATOR_VERSION}.nupkg" ] && echo "OK: Emulator ${EMULATOR_VERSION}" || { echo "FAIL: Emulator missing"; ls -1 "$PKG"; exit 1; } | ||
|
|
||
| - name: Generate NuGet.config | ||
| run: | | ||
| cat > Test/SmokeTests/EmulatorSmokeTest/NuGet.config << 'EOF' | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <configuration> | ||
| <packageSources><clear /> | ||
| <add key="local-packages" value="./local-packages" /> | ||
| <add key="nuget.org" value="https://api.nuget.org/v3/index.json" /> | ||
| </packageSources> | ||
| <packageSourceMapping> | ||
| <packageSource key="local-packages"> | ||
| <package pattern="Microsoft.Azure.DurableTask.Core" /> | ||
| <package pattern="Microsoft.Azure.DurableTask.Emulator" /> | ||
| </packageSource> | ||
| <packageSource key="nuget.org"><package pattern="*" /></packageSource> | ||
| </packageSourceMapping> | ||
| </configuration> | ||
| EOF | ||
|
|
||
| - name: Build smoke test app | ||
| env: | ||
| CORE_VERSION: ${{ steps.version.outputs.core_version }} | ||
| EMULATOR_VERSION: ${{ steps.version.outputs.emulator_version }} | ||
| run: | | ||
| dotnet build Test/SmokeTests/EmulatorSmokeTest/EmulatorSmokeTest.csproj -c Release \ | ||
| -p:SmokeTestCoreVersion=$CORE_VERSION \ | ||
| -p:SmokeTestEmulatorVersion=$EMULATOR_VERSION | ||
|
|
||
| - name: Verify package versions in assets | ||
| env: | ||
| CORE_VERSION: ${{ steps.version.outputs.core_version }} | ||
| EMULATOR_VERSION: ${{ steps.version.outputs.emulator_version }} | ||
| run: | | ||
| set -e | ||
| ASSETS=Test/SmokeTests/EmulatorSmokeTest/obj/project.assets.json | ||
| grep -qi "Microsoft.Azure.DurableTask.Core/${CORE_VERSION}" "$ASSETS" && echo "OK: Core ${CORE_VERSION}" || { echo "FAIL: Core not found"; exit 1; } | ||
| grep -qi "Microsoft.Azure.DurableTask.Emulator/${EMULATOR_VERSION}" "$ASSETS" && echo "OK: Emulator ${EMULATOR_VERSION}" || { echo "FAIL: Emulator not found"; exit 1; } | ||
|
|
||
| - name: Run smoke test | ||
| env: | ||
| CORE_VERSION: ${{ steps.version.outputs.core_version }} | ||
| EMULATOR_VERSION: ${{ steps.version.outputs.emulator_version }} | ||
| run: | | ||
| dotnet run --project Test/SmokeTests/EmulatorSmokeTest/EmulatorSmokeTest.csproj \ | ||
| -c Release --no-build \ | ||
| -p:SmokeTestCoreVersion=$CORE_VERSION \ | ||
| -p:SmokeTestEmulatorVersion=$EMULATOR_VERSION | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| local-packages/ | ||
| NuGet.config |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFramework>net8.0</TargetFramework> | ||
| <Nullable>enable</Nullable> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <IsPackable>false</IsPackable> | ||
| <IsTestProject>false</IsTestProject> | ||
| <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally> | ||
| <!-- Default versions for standalone restore (e.g. Dependency Submission workflow). | ||
| These are overridden by -p:SmokeTestCoreVersion / -p:SmokeTestEmulatorVersion in the CI pipeline. --> | ||
| <SmokeTestCoreVersion Condition="'$(SmokeTestCoreVersion)' == ''">3.7.1</SmokeTestCoreVersion> | ||
| <SmokeTestEmulatorVersion Condition="'$(SmokeTestEmulatorVersion)' == ''">2.6.0</SmokeTestEmulatorVersion> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <!-- | ||
| DTFx packages under test (from local-packages feed). | ||
| Core and Emulator use different version schemes, so they have separate properties. | ||
| Injected via -p:SmokeTestCoreVersion=... -p:SmokeTestEmulatorVersion=... at build time. | ||
| --> | ||
| <PackageReference Include="Microsoft.Azure.DurableTask.Core" Version="$(SmokeTestCoreVersion)" /> | ||
| <PackageReference Include="Microsoft.Azure.DurableTask.Emulator" Version="$(SmokeTestEmulatorVersion)" /> | ||
| <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,111 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Copyright (c) Microsoft Corporation. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Licensed under the MIT License. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Reflection; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using DurableTask.Core; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using DurableTask.Emulator; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using Microsoft.Extensions.Logging; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Console.WriteLine("=== DurableTask Emulator Smoke Test ==="); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Console.WriteLine(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.AddConsole(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.SetMinimumLevel(LogLevel.Warning); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create the in-memory orchestration service | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var orchestrationService = new LocalOrchestrationService(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Start the worker with our orchestration and activities | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var worker = new TaskHubWorker(orchestrationService, loggerFactory); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await worker | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .AddTaskOrchestrations(typeof(HelloCitiesOrchestration)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .AddTaskActivities(typeof(SayHelloActivity)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .StartAsync(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create a client and start the orchestration | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var client = new TaskHubClient(orchestrationService, loggerFactory: loggerFactory); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| OrchestrationInstance instance = await client.CreateOrchestrationInstanceAsync( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| typeof(HelloCitiesOrchestration), input: null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Console.WriteLine($"Started orchestration: {instance.InstanceId}"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Wait for completion | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| OrchestrationState result = await client.WaitForOrchestrationAsync( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| instance, TimeSpan.FromSeconds(30), CancellationToken.None); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (result == null) | |
| { | |
| Console.WriteLine("FAIL: Orchestration did not complete within the timeout."); | |
| await worker.StopAsync(true); | |
| return 1; | |
| } |
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If anything throws between StartAsync() and StopAsync(true) (e.g., timeout/transport exceptions), the worker won’t be stopped, which can cause noisy teardown and occasionally hangs in CI depending on background threads/timers. Wrap the worker lifecycle in try/finally (or use an async disposal pattern if supported) to guarantee StopAsync(true) always runs.
| await worker | |
| .AddTaskOrchestrations(typeof(HelloCitiesOrchestration)) | |
| .AddTaskActivities(typeof(SayHelloActivity)) | |
| .StartAsync(); | |
| // Create a client and start the orchestration | |
| var client = new TaskHubClient(orchestrationService, loggerFactory: loggerFactory); | |
| OrchestrationInstance instance = await client.CreateOrchestrationInstanceAsync( | |
| typeof(HelloCitiesOrchestration), input: null); | |
| Console.WriteLine($"Started orchestration: {instance.InstanceId}"); | |
| // Wait for completion | |
| OrchestrationState result = await client.WaitForOrchestrationAsync( | |
| instance, TimeSpan.FromSeconds(30), CancellationToken.None); | |
| Console.WriteLine($"Orchestration status: {result.OrchestrationStatus}"); | |
| Console.WriteLine($"Orchestration output: {result.Output}"); | |
| await worker.StopAsync(true); | |
| worker | |
| .AddTaskOrchestrations(typeof(HelloCitiesOrchestration)) | |
| .AddTaskActivities(typeof(SayHelloActivity)); | |
| await worker.StartAsync(); | |
| OrchestrationState result = null!; | |
| try | |
| { | |
| // Create a client and start the orchestration | |
| var client = new TaskHubClient(orchestrationService, loggerFactory: loggerFactory); | |
| OrchestrationInstance instance = await client.CreateOrchestrationInstanceAsync( | |
| typeof(HelloCitiesOrchestration), input: null); | |
| Console.WriteLine($"Started orchestration: {instance.InstanceId}"); | |
| // Wait for completion | |
| result = await client.WaitForOrchestrationAsync( | |
| instance, TimeSpan.FromSeconds(30), CancellationToken.None); | |
| Console.WriteLine($"Orchestration status: {result.OrchestrationStatus}"); | |
| Console.WriteLine($"Orchestration output: {result.Output}"); | |
| } | |
| finally | |
| { | |
| await worker.StopAsync(true); | |
| } |
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Environment.Exit(...) bypasses normal shutdown/cleanup, including disposing the loggerFactory declared with using and any other pending finalizers/flushes. Prefer setting Environment.ExitCode and returning from top-level statements (or throwing) so disposals and flushes run deterministically.
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Environment.Exit(...) bypasses normal shutdown/cleanup, including disposing the loggerFactory declared with using and any other pending finalizers/flushes. Prefer setting Environment.ExitCode and returning from top-level statements (or throwing) so disposals and flushes run deterministically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check only verifies the package names appear in
project.assets.json, which doesn’t prove they were restored fromlocal-packages(or that the resolved version matches the locally packed one). A more reliable validation is to assert the resolved versions equal$DTFX_VERSIONand that the package folder/path inproject.assets.jsonpoints to the local feed (or that NuGet used source mapping as intended). This will make the pipeline’s failure mode clearer and prevent false positives.