Skip to content

Add dependency version validation pipeline#1336

Open
YunchuWang wants to merge 5 commits intomainfrom
wangbill/dep-version-validation
Open

Add dependency version validation pipeline#1336
YunchuWang wants to merge 5 commits intomainfrom
wangbill/dep-version-validation

Conversation

@YunchuWang
Copy link
Copy Markdown
Member

Summary

Adds a GitHub Actions workflow (dep-version-validation.yml) that validates dependency version bumps in Directory.Packages.props do not break the DTFx NuGet packages (DurableTask.Core and DurableTask.Emulator) at build or runtime.

What this pipeline does

  1. Packs DurableTask.Core and DurableTask.Emulator from source into a local NuGet feed
  2. Builds a standalone console app that consumes those packages via \PackageReference\ with \packageSourceMapping\ to ensure packages come from the local feed
  3. Runs a HelloCities orchestration using the in-memory Emulator backend
  4. Validates orchestration completes successfully with expected output
  5. Prints loaded assembly versions for verification

Trigger paths

  • \Directory.Packages.props\
  • \ ools/DurableTask.props\
  • \Test/SmokeTests/**\

Motivation

DTFx Core is consumed downstream by:

  • azure-functions-durable-extension (the WebJobs extension for Durable Functions)
  • AAPT-DTMB (DTS data plane)

The existing CI (\�ng/ci/public-build.yml) runs unit tests, which validates DTFx internally. However, there was no validation that the packed NuGet packages resolve correctly and function at runtime after dependency changes. A dep version change that introduces assembly binding issues would only be caught when downstream consumers update their references.

This pipeline catches those issues at the source. Mirrors the SDK-PR-Validation pipeline pattern from AAPT-DTMB.

Add a GitHub Actions workflow that validates dependency version bumps
in Directory.Packages.props do not break the DTFx NuGet packages
(DurableTask.Core and DurableTask.Emulator) at build or runtime.

The pipeline:
- Packs DurableTask.Core and DurableTask.Emulator from source
- Builds a console app using those packages from a local NuGet feed
- Runs a HelloCities orchestration using the in-memory Emulator backend
- Validates orchestration output and loaded assembly versions

DTFx Core is consumed downstream by azure-functions-durable-extension
(the WebJobs extension for Durable Functions) and by AAPT-DTMB (DTS
data plane). This pipeline catches dep-related regressions before
they propagate to those consumers.

Mirrors the SDK-PR-Validation pipeline pattern used in AAPT-DTMB.
Copilot AI review requested due to automatic review settings April 8, 2026 19:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a GitHub Actions workflow to validate that dependency version changes don’t break the packed DTFx NuGet packages (Core + Emulator) by building and running an emulator-backed orchestration smoke test against locally-packed artifacts.

Changes:

  • Added an Emulator-based smoke test console app that runs HelloCities and validates output/version info.
  • Added NuGet source mapping + local package feed configuration for deterministic package resolution.
  • Added a GitHub Actions workflow to pack local NuGets, build the smoke test, and run it in CI.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
Test/SmokeTests/EmulatorSmokeTest/Program.cs Implements the runtime smoke test (run orchestration + validate output + print assembly versions).
Test/SmokeTests/EmulatorSmokeTest/NuGet.config Configures local feed + packageSourceMapping to force DTFx packages from the local feed.
Test/SmokeTests/EmulatorSmokeTest/EmulatorSmokeTest.csproj Defines the net8 console app and references Core/Emulator packages via an injected version property.
Test/SmokeTests/EmulatorSmokeTest/.gitignore Ignores the generated local NuGet feed directory.
.github/workflows/dep-version-validation.yml Builds/packs local DTFx NuGets and runs the smoke test on dependency-related changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +24 to +26
string? infoVersion = asm
.GetCustomAttribute<System.Reflection.AssemblyInformationalVersionAttribute>()
?.InformationalVersion;
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asm.GetCustomAttribute<T>() is an extension method from System.Reflection (CustomAttributeExtensions). With implicit usings, System.Reflection typically isn’t imported, so this may fail to compile. Fix by adding using System.Reflection; at the top, or by calling the extension method with a fully-qualified name.

Copilot uses AI. Check for mistakes.
Comment on lines +65 to +67
Console.WriteLine("FAIL: Orchestration did not complete successfully.");
Environment.Exit(1);
}
Copy link

Copilot AI Apr 8, 2026

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 uses AI. Check for mistakes.
Comment on lines +74 to +76
Console.WriteLine("FAIL: Orchestration output did not contain expected greetings.");
Environment.Exit(1);
}
Copy link

Copilot AI Apr 8, 2026

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 uses AI. Check for mistakes.

Console.WriteLine();
Console.WriteLine("PASS: HelloCities orchestration completed with expected output.");
Environment.Exit(0);
Copy link

Copilot AI Apr 8, 2026

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 uses AI. Check for mistakes.
Comment on lines +116 to +128
# Emulator may use a different version scheme - check that at least one Emulator package exists
EMULATOR_FOUND=0
for f in "$LOCAL_PACKAGES"/Microsoft.Azure.DurableTask.Emulator.*.nupkg; do
if [ -f "$f" ]; then
echo " OK: $(basename $f)"
EMULATOR_FOUND=1
fi
done
if [ $EMULATOR_FOUND -eq 0 ]; then
echo "FAIL: No Emulator package found"
ls -1 "$LOCAL_PACKAGES"
exit 1
fi
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow allows the Emulator package to have a different version (it only checks that some Microsoft.Azure.DurableTask.Emulator.*.nupkg exists), but the smoke test project pins Emulator to $(SmokeTestDtfxVersion) (same as Core). If Emulator’s packed version doesn’t match DTFX_VERSION, restore/build will fail. Fix by ensuring Emulator is packed with the same version you pass into SmokeTestDtfxVersion (e.g., bump/version both consistently), or by passing separate version properties for Core vs Emulator and wiring them in the csproj.

Suggested change
# Emulator may use a different version scheme - check that at least one Emulator package exists
EMULATOR_FOUND=0
for f in "$LOCAL_PACKAGES"/Microsoft.Azure.DurableTask.Emulator.*.nupkg; do
if [ -f "$f" ]; then
echo " OK: $(basename $f)"
EMULATOR_FOUND=1
fi
done
if [ $EMULATOR_FOUND -eq 0 ]; then
echo "FAIL: No Emulator package found"
ls -1 "$LOCAL_PACKAGES"
exit 1
fi
EMULATOR_PKG="Microsoft.Azure.DurableTask.Emulator.${DTFX_VERSION}.nupkg"
if [ ! -f "$LOCAL_PACKAGES/$EMULATOR_PKG" ]; then
echo "FAIL: Missing Emulator package: $EMULATOR_PKG"
echo "Available packages:"
ls -1 "$LOCAL_PACKAGES"
exit 1
fi
echo " OK: $EMULATOR_PKG"

Copilot uses AI. Check for mistakes.
Comment on lines +132 to +138
- name: Build smoke test app
env:
DTFX_VERSION: ${{ steps.version.outputs.dtfx_version }}
run: |
set -e
cd Test/SmokeTests/EmulatorSmokeTest
dotnet build EmulatorSmokeTest.csproj -c Release -p:SmokeTestDtfxVersion=$DTFX_VERSION -v normal
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow allows the Emulator package to have a different version (it only checks that some Microsoft.Azure.DurableTask.Emulator.*.nupkg exists), but the smoke test project pins Emulator to $(SmokeTestDtfxVersion) (same as Core). If Emulator’s packed version doesn’t match DTFX_VERSION, restore/build will fail. Fix by ensuring Emulator is packed with the same version you pass into SmokeTestDtfxVersion (e.g., bump/version both consistently), or by passing separate version properties for Core vs Emulator and wiring them in the csproj.

Copilot uses AI. Check for mistakes.
Comment on lines +142 to +155
run: |
set -e
echo "Verifying DTFx packages were restored from local-packages..."
ASSETS_FILE="Test/SmokeTests/EmulatorSmokeTest/obj/project.assets.json"
for pkg in "Microsoft.Azure.DurableTask.Core" "Microsoft.Azure.DurableTask.Emulator"; do
if grep -qi "$pkg" "$ASSETS_FILE"; then
echo " OK: $pkg found in project.assets.json"
else
echo " FAIL: $pkg NOT found"
exit 1
fi
done
echo "PASS: DTFx packages verified in build output."

Copy link

Copilot AI Apr 8, 2026

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 from local-packages (or that the resolved version matches the locally packed one). A more reliable validation is to assert the resolved versions equal $DTFX_VERSION and that the package folder/path in project.assets.json points 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.

Suggested change
run: |
set -e
echo "Verifying DTFx packages were restored from local-packages..."
ASSETS_FILE="Test/SmokeTests/EmulatorSmokeTest/obj/project.assets.json"
for pkg in "Microsoft.Azure.DurableTask.Core" "Microsoft.Azure.DurableTask.Emulator"; do
if grep -qi "$pkg" "$ASSETS_FILE"; then
echo " OK: $pkg found in project.assets.json"
else
echo " FAIL: $pkg NOT found"
exit 1
fi
done
echo "PASS: DTFx packages verified in build output."
env:
DTFX_VERSION: ${{ steps.version.outputs.dtfx_version }}
run: |
set -e
echo "Verifying DTFx packages were restored from local-packages..."
ASSETS_FILE="Test/SmokeTests/EmulatorSmokeTest/obj/project.assets.json"
LOCAL_PACKAGES="Test/SmokeTests/EmulatorSmokeTest/local-packages"
python3 - <<'PY'
import json
import os
import sys
assets_file = os.path.abspath("Test/SmokeTests/EmulatorSmokeTest/obj/project.assets.json")
local_packages = os.path.abspath("Test/SmokeTests/EmulatorSmokeTest/local-packages")
dtfx_version = os.environ["DTFX_VERSION"]
expected_packages = [
"Microsoft.Azure.DurableTask.Core",
"Microsoft.Azure.DurableTask.Emulator",
]
with open(assets_file, "r", encoding="utf-8") as f:
assets = json.load(f)
restore = assets.get("project", {}).get("restore", {})
sources = restore.get("sources", {})
normalized_sources = set()
for source in sources.keys():
normalized = source
if normalized.startswith("file://"):
normalized = normalized[7:]
normalized_sources.add(os.path.abspath(normalized))
if local_packages in normalized_sources:
print(f" OK: local restore source found: {local_packages}")
else:
print(f" FAIL: local restore source not found in project.assets.json: {local_packages}")
if normalized_sources:
print(" Restore sources found:")
for source in sorted(normalized_sources):
print(f" - {source}")
else:
print(" No restore sources found in project.assets.json")
sys.exit(1)
libraries = assets.get("libraries", {})
for package in expected_packages:
resolved_key = f"{package}/{dtfx_version}"
if resolved_key in libraries:
print(f" OK: {package} resolved to version {dtfx_version}")
else:
matching_versions = sorted(
key.split("/", 1)[1]
for key in libraries.keys()
if key.startswith(package + "/")
)
if matching_versions:
print(
f" FAIL: {package} did not resolve to version {dtfx_version}. "
f"Found: {', '.join(matching_versions)}"
)
else:
print(f" FAIL: {package} was not resolved in project.assets.json")
sys.exit(1)
print("PASS: DTFx packages verified from local source with expected versions.")
PY

Copilot uses AI. Check for mistakes.
- Core uses version 3.7.x, Emulator inherits 2.6.0 from DurableTask.props
- Use separate SmokeTestCoreVersion and SmokeTestEmulatorVersion properties
- Add missing 'using System.Reflection;' import
- Replace Environment.Exit() with return for proper cleanup
- Verify exact Emulator package version instead of wildcard
The Dependency Submission (submit-nuget) workflow restores all csproj
files without passing -p:SmokeTestCoreVersion/-p:SmokeTestEmulatorVersion.
Add default fallback versions in the csproj so standalone restore works.
Also add nuget.org mapping for DTFx packages so restore succeeds when
local-packages folder is empty (outside CI pipeline).
Copilot AI review requested due to automatic review settings April 8, 2026 22:25
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +16 to +24
<packageSource key="local-packages">
<package pattern="Microsoft.Azure.DurableTask.Core" />
<package pattern="Microsoft.Azure.DurableTask.Emulator" />
</packageSource>
<packageSource key="nuget.org">
<package pattern="*" />
<package pattern="Microsoft.Azure.DurableTask.Core" />
<package pattern="Microsoft.Azure.DurableTask.Emulator" />
</packageSource>
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mapping allows the DTFx packages to be restored from either local-packages or nuget.org (both sources match), which undermines the stated goal of forcing CI to consume the locally packed nupkgs. To make the workflow effective when the package version matches an already-published version, map Microsoft.Azure.DurableTask.Core and Microsoft.Azure.DurableTask.Emulator only to local-packages in CI (e.g., remove those two patterns from the nuget.org source mapping, or use a CI-specific NuGet.config / restore argument strategy). Otherwise the restore can legally (and silently) select nuget.org/global-cache content, making the validation less reliable.

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +71
# Parse Core version from its csproj
CORE_CSPROJ="src/DurableTask.Core/DurableTask.Core.csproj"
MAJOR=$(grep -oP '<MajorVersion>\K[^<]+' "$CORE_CSPROJ")
MINOR=$(grep -oP '<MinorVersion>\K[^<]+' "$CORE_CSPROJ")
PATCH=$(grep -oP '<PatchVersion>\K[^<]+' "$CORE_CSPROJ")
CORE_VERSION="${MAJOR}.${MINOR}.${PATCH}"
echo "Core version: ${CORE_VERSION}"
echo "core_version=${CORE_VERSION}" >> "$GITHUB_OUTPUT"

# Emulator inherits its version from tools/DurableTask.props
PROPS_FILE="tools/DurableTask.props"
EMULATOR_VERSION=$(grep -oP '<Version>\K[^<]+' "$PROPS_FILE")
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version parsing is brittle and can produce incorrect versions:\n- Core: composing ${MAJOR}.${MINOR}.${PATCH} ignores any prerelease/labels (and will fail if version is computed via imports/conditions), which can make the later *.nupkg filename checks wrong.\n- Emulator: grepping <Version> can return multiple matches if the props file contains more than one <Version> element.\nA more robust approach is to ask MSBuild for the evaluated Version/PackageVersion (e.g., dotnet msbuild ... -getProperty:PackageVersion / Version) so imports/conditions/prerelease labels are handled correctly.

Suggested change
# Parse Core version from its csproj
CORE_CSPROJ="src/DurableTask.Core/DurableTask.Core.csproj"
MAJOR=$(grep -oP '<MajorVersion>\K[^<]+' "$CORE_CSPROJ")
MINOR=$(grep -oP '<MinorVersion>\K[^<]+' "$CORE_CSPROJ")
PATCH=$(grep -oP '<PatchVersion>\K[^<]+' "$CORE_CSPROJ")
CORE_VERSION="${MAJOR}.${MINOR}.${PATCH}"
echo "Core version: ${CORE_VERSION}"
echo "core_version=${CORE_VERSION}" >> "$GITHUB_OUTPUT"
# Emulator inherits its version from tools/DurableTask.props
PROPS_FILE="tools/DurableTask.props"
EMULATOR_VERSION=$(grep -oP '<Version>\K[^<]+' "$PROPS_FILE")
get_msbuild_version() {
local project_file="$1"
local version
version=$(dotnet msbuild "$project_file" -nologo -getProperty:PackageVersion | tail -n 1 | tr -d '\r')
if [ -z "$version" ]; then
version=$(dotnet msbuild "$project_file" -nologo -getProperty:Version | tail -n 1 | tr -d '\r')
fi
if [ -z "$version" ]; then
echo "Failed to resolve version for $project_file" >&2
exit 1
fi
echo "$version"
}
# Resolve Core version from evaluated MSBuild properties
CORE_CSPROJ="src/DurableTask.Core/DurableTask.Core.csproj"
CORE_VERSION=$(get_msbuild_version "$CORE_CSPROJ")
echo "Core version: ${CORE_VERSION}"
echo "core_version=${CORE_VERSION}" >> "$GITHUB_OUTPUT"
# Resolve Emulator version from evaluated MSBuild properties
EMULATOR_CSPROJ="src/DurableTask.Emulator/DurableTask.Emulator.csproj"
EMULATOR_VERSION=$(get_msbuild_version "$EMULATOR_CSPROJ")

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +34
// Print loaded DTFx assembly versions
Console.WriteLine("Loaded DTFx assembly versions:");
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
string? name = asm.GetName().Name;
if (name != null && name.StartsWith("DurableTask.", StringComparison.OrdinalIgnoreCase))
{
string? infoVersion = asm
.GetCustomAttribute<System.Reflection.AssemblyInformationalVersionAttribute>()
?.InformationalVersion;
if (infoVersion != null && infoVersion.Contains('+'))
{
infoVersion = infoVersion[..infoVersion.IndexOf('+')];
}
Console.WriteLine($" {name} = {infoVersion ?? asm.GetName().Version?.ToString() ?? "unknown"}");
}
}
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This prints the currently loaded assemblies before the code first touches any DTFx types (the first concrete usage is later at new LocalOrchestrationService() / new TaskHubWorker(...)). On .NET, referenced assemblies are often loaded lazily, so this can end up printing nothing (or an incomplete list), reducing the usefulness of the validation output. Consider moving this block to after the worker/client are created (or after the orchestration run), or explicitly forcing load via known types (e.g., typeof(TaskHubClient).Assembly) before enumerating.

Copilot uses AI. Check for mistakes.
Ensures the local-packages directory exists in the repo so NuGet
restore doesn't fail with NU1301 when the Dependency Submission
workflow scans all csproj files.
…printing

- Use dotnet msbuild -getProperty:PackageVersion instead of grep to
  resolve versions, handling imports/conditions/prerelease correctly
- Move assembly version printing to after orchestration run so all
  DTFx assemblies are loaded (lazy loading means they may not be
  present at startup)
Copilot AI review requested due to automatic review settings April 8, 2026 22:40
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// Wait for completion
OrchestrationState result = await client.WaitForOrchestrationAsync(
instance, TimeSpan.FromSeconds(30), CancellationToken.None);

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WaitForOrchestrationAsync can return null on timeout in some DTFx versions/implementations; the current code will then throw a NullReferenceException when accessing result.OrchestrationStatus / result.Output. Handle the timeout case explicitly (e.g., check for null and treat it as a FAIL with a clear message) before dereferencing result.

Suggested change
if (result == null)
{
Console.WriteLine("FAIL: Orchestration did not complete within the timeout.");
await worker.StopAsync(true);
return 1;
}

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +43
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);

Copy link

Copilot AI Apr 8, 2026

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.

Suggested change
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 uses AI. Check for mistakes.
Comment on lines +15 to +25
<packageSourceMapping>
<packageSource key="local-packages">
<package pattern="Microsoft.Azure.DurableTask.Core" />
<package pattern="Microsoft.Azure.DurableTask.Emulator" />
</packageSource>
<packageSource key="nuget.org">
<package pattern="*" />
<package pattern="Microsoft.Azure.DurableTask.Core" />
<package pattern="Microsoft.Azure.DurableTask.Emulator" />
</packageSource>
</packageSourceMapping>
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the DTFx package IDs mapped to both local-packages and nuget.org, NuGet is allowed to resolve those packages from nuget.org even during CI, which undermines the workflow’s goal of validating the locally packed artifacts. To make this deterministic, map the DTFx package IDs only to local-packages (and keep nuget.org mapped to * for other dependencies), or use a CI-only NuGet.config that enforces local-only for the two DTFx packages.

Copilot uses AI. Check for mistakes.
Comment on lines +147 to +167
dotnet build EmulatorSmokeTest.csproj -c Release \
-p:SmokeTestCoreVersion=$CORE_VERSION \
-p:SmokeTestEmulatorVersion=$EMULATOR_VERSION \
-v normal

# ---- Verify DTFx packages resolved from local-packages ----
- name: Verify DTFx packages from local source
run: |
set -e
echo "Verifying DTFx packages were restored from local-packages..."
ASSETS_FILE="Test/SmokeTests/EmulatorSmokeTest/obj/project.assets.json"
for pkg in "Microsoft.Azure.DurableTask.Core" "Microsoft.Azure.DurableTask.Emulator"; do
if grep -qi "$pkg" "$ASSETS_FILE"; then
echo " OK: $pkg found in project.assets.json"
else
echo " FAIL: $pkg NOT found"
exit 1
fi
done
echo "PASS: DTFx packages verified in build output."

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check only verifies that the package IDs appear in project.assets.json, not that they were actually restored from local-packages. As written, it will pass even if the packages were pulled from nuget.org. To make this a true validation, enforce local-only via packageSourceMapping/CI NuGet.config (preferred), or add a verification step that inspects restore output/logs for local-packages being the source for the two DTFx packages.

Suggested change
dotnet build EmulatorSmokeTest.csproj -c Release \
-p:SmokeTestCoreVersion=$CORE_VERSION \
-p:SmokeTestEmulatorVersion=$EMULATOR_VERSION \
-v normal
# ---- Verify DTFx packages resolved from local-packages ----
- name: Verify DTFx packages from local source
run: |
set -e
echo "Verifying DTFx packages were restored from local-packages..."
ASSETS_FILE="Test/SmokeTests/EmulatorSmokeTest/obj/project.assets.json"
for pkg in "Microsoft.Azure.DurableTask.Core" "Microsoft.Azure.DurableTask.Emulator"; do
if grep -qi "$pkg" "$ASSETS_FILE"; then
echo " OK: $pkg found in project.assets.json"
else
echo " FAIL: $pkg NOT found"
exit 1
fi
done
echo "PASS: DTFx packages verified in build output."
RESTORE_LOG="$RUNNER_TEMP/emulator-smoke-restore.log"
dotnet restore EmulatorSmokeTest.csproj \
-p:SmokeTestCoreVersion=$CORE_VERSION \
-p:SmokeTestEmulatorVersion=$EMULATOR_VERSION \
-v diag 2>&1 | tee "$RESTORE_LOG"
dotnet build EmulatorSmokeTest.csproj -c Release --no-restore \
-p:SmokeTestCoreVersion=$CORE_VERSION \
-p:SmokeTestEmulatorVersion=$EMULATOR_VERSION \
-v normal
# ---- Verify DTFx packages resolved from local-packages ----
- name: Verify DTFx packages from local source
env:
CORE_VERSION: ${{ steps.version.outputs.core_version }}
EMULATOR_VERSION: ${{ steps.version.outputs.emulator_version }}
run: |
set -e
echo "Verifying DTFx packages were restored from local-packages..."
RESTORE_LOG="$RUNNER_TEMP/emulator-smoke-restore.log"
LOCAL_PACKAGES="$(realpath Test/SmokeTests/EmulatorSmokeTest/local-packages)"
if [ ! -f "$RESTORE_LOG" ]; then
echo "FAIL: Restore log not found: $RESTORE_LOG"
exit 1
fi
check_restore_source() {
pkg="$1"
version="$2"
if awk -v pkg="$pkg" -v version="$version" -v src="$LOCAL_PACKAGES" 'index($0, pkg) && index($0, version) && index($0, src) { found=1 } END { exit found ? 0 : 1 }' "$RESTORE_LOG"; then
echo " OK: $pkg $version restored from $LOCAL_PACKAGES"
else
echo " FAIL: $pkg $version was not verified as restored from $LOCAL_PACKAGES"
echo " Relevant restore log lines:"
grep -F "$pkg" "$RESTORE_LOG" || true
exit 1
fi
}
check_restore_source "Microsoft.Azure.DurableTask.Core" "$CORE_VERSION"
check_restore_source "Microsoft.Azure.DurableTask.Emulator" "$EMULATOR_VERSION"
echo "PASS: DTFx packages verified as restored from local-packages."

Copilot uses AI. Check for mistakes.
Comment on lines +57 to +74
run: |
set -e

# Use MSBuild to resolve the evaluated PackageVersion/Version
# so imports, conditions, and prerelease labels are handled correctly.
get_msbuild_version() {
local project_file="$1"
local version

version=$(dotnet msbuild "$project_file" -nologo -getProperty:PackageVersion 2>/dev/null | tail -n 1 | tr -d '\r')
if [ -z "$version" ]; then
version=$(dotnet msbuild "$project_file" -nologo -getProperty:Version 2>/dev/null | tail -n 1 | tr -d '\r')
fi

if [ -z "$version" ]; then
echo "Failed to resolve version for $project_file" >&2
exit 1
fi
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script suppresses MSBuild stderr (2>/dev/null), which makes version-resolution failures hard to diagnose in CI. Also, because the MSBuild call is inside a pipeline, set -e alone won’t reliably fail the step on MSBuild errors unless pipefail is enabled. Consider using set -euo pipefail and avoiding stderr suppression (or capture and print stderr on failure).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants