From 89d4d00abd777a5297bb365d9e095199eed1672a Mon Sep 17 00:00:00 2001 From: David Negstad Date: Tue, 7 Jul 2026 18:16:45 -0700 Subject: [PATCH 1/4] Add Aspire regression workflow Add a GitHub Actions regression workflow that builds DCP, installs the latest daily Aspire CLI, scaffolds a file-based AppHost, and validates that a real Aspire app reaches healthy state against the local DCP binary. Keep the AzDO stage disabled for later policy review. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .azure/pipelines/aspire-test.yml | 101 +++++++++++------ .github/workflows/aspire-regression.yml | 69 +++++++++++ CONTRIBUTING.md | 20 ++++ test/aspire/apphost-body.cs | 90 +++++++++++++++ test/aspire/run-regression.sh | 145 ++++++++++++++++++++++++ 5 files changed, 389 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/aspire-regression.yml create mode 100644 test/aspire/apphost-body.cs create mode 100755 test/aspire/run-regression.sh diff --git a/.azure/pipelines/aspire-test.yml b/.azure/pipelines/aspire-test.yml index 4530f2b4..c43ac954 100644 --- a/.azure/pipelines/aspire-test.yml +++ b/.azure/pipelines/aspire-test.yml @@ -1,41 +1,70 @@ +parameters: +- name: linuxPool + displayName: 'Linux Pool' + type: object + default: + name: NetCore1ESPool-Internal + image: 1es-ubuntu-2204 + os: linux + stages: - stage: AspireTest + displayName: 'Aspire Regression' + # Disabled and not wired into any pipeline yet: enabling this in AzDO needs a + # review of pipeline policies (network isolation, feed access, container + # runtime availability on the pool). The GitHub Actions workflow + # (.github/workflows/aspire-regression.yml) provides this coverage in the + # meantime. To enable, remove this condition and add a template reference in + # pullrequest.yml and/or official.yml. condition: false - displayName: 'Aspire Tests' dependsOn: - Build - templateContext: - inputs: - - input: pipelineArtifact - artifactName: $(os)$(subos)_$(arch)_build - targetPath: $(Build.ArtifactStagingDirectory)/build - strategy: - matrix: - windows-amd64: - os: windows - arch: amd64 - extension: .exe - linux-amd64: - os: linux - arch: amd64 - extension: '' - macos-amd64: - os: darwin - arch: amd64 - extension: '' - steps: - - checkout: aspire - - task: UseDotNet@2 - displayName: 'Use .NET SDK' - inputs: - packageType: sdk - includePreviewVersions: true - useGlobalJson: true - workingDirectory: $(Build.SourcesDirectory)/aspire - - task: DotNetCoreCLI@2 - displayName: 'Run Aspire Tests' - inputs: - command: test - projects: '$(Build.SourcesDirectory)/aspire/tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj' - env: - DcpPublisher__CliPath: $(Build.ArtifactStagingDirectory)/build/dcp$(extension) \ No newline at end of file + jobs: + - job: AspireRegressionLinux + displayName: 'Aspire Regression (latest daily)' + pool: ${{ parameters.linuxPool }} + templateContext: + inputs: + # Consume the Linux DCP binary produced by the Build stage. + - input: pipelineArtifact + artifactName: linux_amd64_build + targetPath: $(Pipeline.Workspace)/dcp + steps: + - checkout: self + + - task: UseDotNet@2 + displayName: 'Use .NET SDK' + inputs: + packageType: sdk + version: '10.0.x' + includePreviewVersions: true + + - task: bash@3 + displayName: 'Install latest daily Aspire CLI' + inputs: + targetType: inline + script: | + set -euo pipefail + curl -sSL https://aspire.dev/install.sh | bash -s -- -q dev --install-path "$HOME/.aspire/bin" + echo "##vso[task.prependpath]$HOME/.aspire/bin" + + - task: bash@3 + displayName: 'Run Aspire regression test' + inputs: + targetType: inline + workingDirectory: '$(Build.SourcesDirectory)' + script: | + set -euo pipefail + chmod +x "$(Pipeline.Workspace)/dcp/dcp" + test/aspire/run-regression.sh --dcp "$(Pipeline.Workspace)/dcp/dcp" + env: + DCP_DIAGNOSTICS_LOG_LEVEL: debug + DCP_DIAGNOSTICS_LOG_FOLDER: $(Build.ArtifactStagingDirectory)/aspire-test-logs + DCP_PRESERVE_EXECUTABLE_LOGS: 'true' + + - task: 1ES.PublishPipelineArtifact@1 + displayName: 'Publish Aspire Regression Logs' + condition: or(failed(), eq(variables['Agent.Diagnostic'], 'true')) + inputs: + targetPath: $(Build.ArtifactStagingDirectory)/aspire-test-logs + artifactName: aspire_test_logs diff --git a/.github/workflows/aspire-regression.yml b/.github/workflows/aspire-regression.yml new file mode 100644 index 00000000..961de8c8 --- /dev/null +++ b/.github/workflows/aspire-regression.yml @@ -0,0 +1,69 @@ +name: Aspire Regression + +# Runs a real Aspire AppHost, orchestrated by a freshly built DCP binary, against +# the latest Aspire daily build. This catches DCP regressions in the real Aspire +# integration (process orchestration, container creation, container networking) +# before a DCP build is inserted into Aspire. +# +# Runs on Linux only, where a Docker runtime is available on the hosted runner so +# the container resources in the AppHost can actually start. + +on: + workflow_dispatch: + schedule: + # Nightly, to catch drift against the moving Aspire daily build even when DCP + # itself has not changed. + - cron: '0 7 * * *' + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + contents: read + +env: + GOPRIVATE: github.com/microsoft/usvc-*,github.com/microsoft/dcp + +jobs: + aspire-regression: + name: Aspire regression (latest daily) + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Go environment + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + architecture: 'x64' + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + + - name: Build DCP + run: make build + + - name: Install latest daily Aspire CLI + # The install script appends its install directory to $GITHUB_PATH when + # GITHUB_ACTIONS=true, so 'aspire' is available in later steps. + run: curl -sSL https://aspire.dev/install.sh | bash -s -- -q dev + + - name: Run Aspire regression test + run: test/aspire/run-regression.sh + env: + DCP_DIAGNOSTICS_LOG_LEVEL: debug + DCP_DIAGNOSTICS_LOG_FOLDER: ${{ github.workspace }}/test-logs + DCP_PRESERVE_EXECUTABLE_LOGS: 'true' + + - name: Upload DCP logs + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: aspire-regression-logs + path: ${{ github.workspace }}/test-logs/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5d9a7597..b638fcc8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,6 +96,26 @@ If the test is killed while running under the debugger, may leave orphaned `dcp` ## Running Aspire tests You can run the Aspire tests against a local DCP instance using the `DcpPublisher__CliPath` environment variable. Set it with the absolute path to a local DCP executable, e.g. `DcpPublisher__CliPath=/bin/dcp` and when you run Aspire tests, they'll use your local build instead of the current version inserted into the Aspire repo. +### Aspire end-to-end regression test +The `test/aspire` folder contains a self-contained end-to-end regression test that runs a real Aspire AppHost, orchestrated by a locally built DCP binary, against the latest Aspire daily build. It is intended to catch DCP regressions in the real Aspire integration (process orchestration, container creation, and container networking) before a DCP build is inserted into Aspire. + +`test/aspire/run-regression.sh` uses the [Aspire CLI](https://learn.microsoft.com/dotnet/aspire/cli/) to scaffold a minimal AppHost from the daily channel, preserves the generated daily `#:sdk` directive, appends `test/aspire/apphost-body.cs` (which adds an executable, a session container, and persistent containers on a shared network, covering both default container naming and one explicit mixed-case container name/aliases), and runs it with `DcpPublisher__CliPath` pointing Aspire at your local DCP build. The committed AppHost body intentionally has no pinned Aspire SDK version; the script passes only if the AppHost reports that it started, every expected resource reports healthy, and the AppHost exits cleanly. + +To run it locally: + +```bash +# Prerequisites: a .NET 10 SDK, a container runtime (Docker/Podman), and the +# daily Aspire CLI: +# curl -sSL https://aspire.dev/install.sh | bash -s -- -q dev +make build +test/aspire/run-regression.sh # uses bin/dcp by default +test/aspire/run-regression.sh --dcp /path/to/dcp +``` + +The script is intended for CI and does not remove the container runtime resources it creates. On GitHub-hosted runners these are cleaned up with the job environment; local runs may leave containers and persistent networks behind. + +In CI this runs on Linux (where the hosted runner provides a Docker runtime) via the `.github/workflows/aspire-regression.yml` workflow, on a nightly schedule, on pushes to `main`, and on demand through **Run workflow**. + ## Troubleshooting and debugging tips ### `make lint` times out (or ends with an error that says "Killed") diff --git a/test/aspire/apphost-body.cs b/test/aspire/apphost-body.cs new file mode 100644 index 00000000..9bac86ff --- /dev/null +++ b/test/aspire/apphost-body.cs @@ -0,0 +1,90 @@ +// Aspire end-to-end regression AppHost for DCP. +// +// This file is the body of a file-based (single-file) Aspire AppHost that is +// run against a locally built DCP binary to catch integration regressions before +// a DCP build is inserted into Aspire. It intentionally does not contain a +// "#:sdk" directive: run-regression.sh scaffolds a fresh AppHost with the +// latest Aspire daily CLI, preserves the generated daily SDK directive, appends +// this body, and runs it. +// +// The AppHost starts the model, waits for every resource to become healthy, and +// exits (0 on success, non-zero on failure). The scenario includes both session +// and persistent containers. Resource names, container names, and container +// network aliases intentionally use mixed casing to exercise DCP's case handling +// (a past regression attached containers to the wrong network when network names +// were mixed case). + +using Microsoft.Extensions.DependencyInjection; +using Aspire.Hosting.ApplicationModel; + +var runId = Environment.GetEnvironmentVariable("DCP_ASPIRE_REGRESSION_RUN_ID"); +if (string.IsNullOrWhiteSpace(runId)) +{ + runId = "manual"; +} + +var builder = DistributedApplication.CreateBuilder(new DistributedApplicationOptions +{ + Args = args, + DisableDashboard = true, +}); + +// Executable resource: exercises DCP process orchestration. +var worker = builder.AddExecutable("worker", "sleep", ".", "3600"); + +// Session container resource: exercises DCP container creation and cleanup. +var cache = builder.AddContainer("cache", "mcr.microsoft.com/cbl-mariner/busybox", "2.0") + .WithEntrypoint("sleep") + .WithArgs("3600") + .WithEnvironment("DCP_ASPIRE_REGRESSION_RUN_ID", runId) + .WithContainerNetworkAlias("Cache-Alias"); + +// Persistent containers: exercises Aspire's persistent container path and the +// DCP persistent network resources it creates for persistent containers. +var persistentCache = builder.AddContainer("persistent-cache", "mcr.microsoft.com/cbl-mariner/busybox", "2.0") + .WithEntrypoint("sleep") + .WithArgs("3600") + .WithEnvironment("DCP_ASPIRE_REGRESSION_RUN_ID", runId) + .WithLifetime(ContainerLifetime.Persistent) + .WithContainerName($"Persistent-Cache-MixedCase-{runId}") + .WithContainerNetworkAlias("Persistent-Cache-Alias"); + +var persistentWorker = builder.AddContainer("persistent-worker", "mcr.microsoft.com/cbl-mariner/busybox", "2.0") + .WithEntrypoint("sleep") + .WithArgs("3600") + .WithEnvironment("DCP_ASPIRE_REGRESSION_RUN_ID", runId) + .WithLifetime(ContainerLifetime.Persistent) + .WithContainerNetworkAlias("Persistent-Worker-Alias") + .WaitFor(persistentCache); + +// Session container depending on both session and persistent containers: +// exercises container network attachment and dependency ordering. +var web = builder.AddContainer("web", "mcr.microsoft.com/cbl-mariner/busybox", "2.0") + .WithEntrypoint("sleep") + .WithArgs("3600") + .WithEnvironment("DCP_ASPIRE_REGRESSION_RUN_ID", runId) + .WithContainerNetworkAlias("Web-Alias") + .WaitFor(cache) + .WaitFor(persistentWorker); + +await using var app = builder.Build(); +using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); + +try +{ + await app.StartAsync(cts.Token); + Console.WriteLine("DCP-REGRESSION-APPHOST-STARTED"); + + var notifications = app.Services.GetRequiredService(); + foreach (var resourceName in new[] { "worker", "cache", "persistent-cache", "persistent-worker", "web" }) + { + await notifications.WaitForResourceHealthyAsync(resourceName, cts.Token); + Console.WriteLine($"DCP-REGRESSION-RESOURCE-HEALTHY: {resourceName}"); + } + + Console.WriteLine("DCP-REGRESSION-OK: all resources healthy"); +} +finally +{ + await app.StopAsync(cts.Token); +} diff --git a/test/aspire/run-regression.sh b/test/aspire/run-regression.sh new file mode 100755 index 00000000..b31f5099 --- /dev/null +++ b/test/aspire/run-regression.sh @@ -0,0 +1,145 @@ +#!/usr/bin/env bash +# +# Runs the Aspire end-to-end regression test against a locally built DCP binary. +# +# It scaffolds a minimal AppHost with the installed (ideally latest daily) Aspire +# CLI, keeps the scaffolded daily "#:sdk" directive, appends +# test/aspire/apphost-body.cs, and runs it while pointing Aspire at the local DCP +# build via DcpPublisher__CliPath. The test passes only if every resource becomes +# healthy and the AppHost exits cleanly. +# +# Prerequisites: +# - The Aspire CLI on PATH (install the daily channel with: +# curl -sSL https://aspire.dev/install.sh | bash -s -- -q dev ) +# - A .NET 10 SDK on PATH +# - A container runtime (Docker/Podman) available for the container resources +# +# Usage: +# test/aspire/run-regression.sh [--dcp /path/to/dcp] +# +# The DCP binary defaults to /bin/dcp (or bin/dcp.exe on Windows) and can +# be overridden with --dcp or the DCP_CLI_PATH environment variable. + +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "${script_dir}/../.." && pwd)" + +dcp_ext="" +case "$(uname -s 2>/dev/null || echo)" in + MINGW* | MSYS* | CYGWIN* | Windows_NT) dcp_ext=".exe" ;; +esac + +dcp_cli_path="${DCP_CLI_PATH:-${repo_root}/bin/dcp${dcp_ext}}" + +while [[ $# -gt 0 ]]; do + case "$1" in + --dcp) + dcp_cli_path="$2" + shift 2 + ;; + --dcp=*) + dcp_cli_path="${1#*=}" + shift + ;; + -h | --help) + sed -n '2,25p' "${BASH_SOURCE[0]}" + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + exit 2 + ;; + esac +done + +if [[ ! -x "${dcp_cli_path}" ]]; then + echo "DCP binary not found or not executable at: ${dcp_cli_path}" >&2 + echo "Build it first (e.g. 'make build') or pass --dcp ." >&2 + exit 1 +fi + +if ! command -v aspire >/dev/null 2>&1; then + echo "The 'aspire' CLI was not found on PATH." >&2 + echo "Install the daily channel with:" >&2 + echo " curl -sSL https://aspire.dev/install.sh | bash -s -- -q dev" >&2 + exit 1 +fi + +work_dir="$(mktemp -d 2>/dev/null || mktemp -d -t dcp-aspire-regression)" +run_id="$(date -u +%Y%m%d%H%M%S)-$$" + +cleanup() { + rm -rf "${work_dir}" +} +trap cleanup EXIT + +echo "==> Scaffolding a minimal AppHost with the Aspire daily CLI" +aspire new aspire-empty \ + --name DcpRegression \ + --output "${work_dir}" \ + --channel daily \ + --language csharp \ + --non-interactive \ + --nologo \ + --suppress-agent-init + +generated_apphost="${work_dir}/apphost.cs" +if [[ ! -f "${generated_apphost}" ]]; then + echo "Scaffolding did not produce an apphost.cs in ${work_dir}" >&2 + exit 1 +fi + +# Preserve the daily SDK pin that the CLI scaffolded and append the committed +# AppHost body. The committed body intentionally has no "#:sdk" directive so the +# regression always runs against the latest daily Aspire AppHost SDK selected by +# the CLI scaffold. +sdk_line="$(grep -m1 '^#:sdk' "${generated_apphost}" || true)" +if [[ -z "${sdk_line}" ]]; then + echo "Could not find a '#:sdk' directive in the scaffolded apphost.cs" >&2 + exit 1 +fi + +echo "==> Using scaffolded SDK pin: ${sdk_line}" +{ + echo "${sdk_line}" + cat "${script_dir}/apphost-body.cs" +} >"${generated_apphost}" + +echo "==> Running the regression AppHost against DCP at: ${dcp_cli_path}" +output_log="${work_dir}/run.log" +set +e +( + cd "${work_dir}" + DcpPublisher__CliPath="${dcp_cli_path}" \ + DCP_ASPIRE_REGRESSION_RUN_ID="${run_id}" \ + ASPIRE_ALLOW_UNSECURED_TRANSPORT="true" \ + DOTNET_ENVIRONMENT="Development" \ + dotnet run apphost.cs +) 2>&1 | tee "${output_log}" +run_status=${PIPESTATUS[0]} +set -e + +if [[ ${run_status} -ne 0 ]]; then + echo "AppHost exited with status ${run_status}" >&2 + exit "${run_status}" +fi + +if ! grep -q "DCP-REGRESSION-APPHOST-STARTED" "${output_log}"; then + echo "AppHost exited cleanly but did not report that it started" >&2 + exit 1 +fi + +for resource_name in worker cache persistent-cache persistent-worker web; do + if ! grep -q "DCP-REGRESSION-RESOURCE-HEALTHY: ${resource_name}" "${output_log}"; then + echo "AppHost exited cleanly but did not report resource healthy: ${resource_name}" >&2 + exit 1 + fi +done + +if ! grep -q "DCP-REGRESSION-OK" "${output_log}"; then + echo "AppHost exited cleanly but did not report all resources healthy" >&2 + exit 1 +fi + +echo "==> Aspire regression test passed" From 3ca0c84068bdee4bc21b8f8be6bc92158c68b24c Mon Sep 17 00:00:00 2001 From: David Negstad Date: Tue, 7 Jul 2026 18:27:08 -0700 Subject: [PATCH 2/4] Use Aspire CLI waits in regression harness Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- CONTRIBUTING.md | 2 +- test/aspire/apphost-body.cs | 35 +++++------------------------ test/aspire/run-regression.sh | 42 ++++++++++++++--------------------- 3 files changed, 24 insertions(+), 55 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b638fcc8..65216d09 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -99,7 +99,7 @@ You can run the Aspire tests against a local DCP instance using the `DcpPublishe ### Aspire end-to-end regression test The `test/aspire` folder contains a self-contained end-to-end regression test that runs a real Aspire AppHost, orchestrated by a locally built DCP binary, against the latest Aspire daily build. It is intended to catch DCP regressions in the real Aspire integration (process orchestration, container creation, and container networking) before a DCP build is inserted into Aspire. -`test/aspire/run-regression.sh` uses the [Aspire CLI](https://learn.microsoft.com/dotnet/aspire/cli/) to scaffold a minimal AppHost from the daily channel, preserves the generated daily `#:sdk` directive, appends `test/aspire/apphost-body.cs` (which adds an executable, a session container, and persistent containers on a shared network, covering both default container naming and one explicit mixed-case container name/aliases), and runs it with `DcpPublisher__CliPath` pointing Aspire at your local DCP build. The committed AppHost body intentionally has no pinned Aspire SDK version; the script passes only if the AppHost reports that it started, every expected resource reports healthy, and the AppHost exits cleanly. +`test/aspire/run-regression.sh` uses the [Aspire CLI](https://learn.microsoft.com/dotnet/aspire/cli/) to scaffold a minimal AppHost from the daily channel, preserves the generated daily `#:sdk` directive, appends `test/aspire/apphost-body.cs` (which adds an executable, a session container, and persistent containers on a shared network, covering both default container naming and one explicit mixed-case container name/aliases), and runs it with `DcpPublisher__CliPath` pointing Aspire at your local DCP build. The committed AppHost body intentionally has no pinned Aspire SDK version; the script passes only if `aspire run --detach` starts the AppHost and `aspire wait` reports every expected resource healthy. To run it locally: diff --git a/test/aspire/apphost-body.cs b/test/aspire/apphost-body.cs index 9bac86ff..fc57e562 100644 --- a/test/aspire/apphost-body.cs +++ b/test/aspire/apphost-body.cs @@ -7,14 +7,11 @@ // latest Aspire daily CLI, preserves the generated daily SDK directive, appends // this body, and runs it. // -// The AppHost starts the model, waits for every resource to become healthy, and -// exits (0 on success, non-zero on failure). The scenario includes both session -// and persistent containers. Resource names, container names, and container -// network aliases intentionally use mixed casing to exercise DCP's case handling -// (a past regression attached containers to the wrong network when network names -// were mixed case). +// The scenario includes both session and persistent containers. Resource names, +// container names, and container network aliases intentionally use mixed casing +// to exercise DCP's case handling (a past regression attached containers to the +// wrong network when network names were mixed case). -using Microsoft.Extensions.DependencyInjection; using Aspire.Hosting.ApplicationModel; var runId = Environment.GetEnvironmentVariable("DCP_ASPIRE_REGRESSION_RUN_ID"); @@ -59,7 +56,7 @@ // Session container depending on both session and persistent containers: // exercises container network attachment and dependency ordering. -var web = builder.AddContainer("web", "mcr.microsoft.com/cbl-mariner/busybox", "2.0") +builder.AddContainer("web", "mcr.microsoft.com/cbl-mariner/busybox", "2.0") .WithEntrypoint("sleep") .WithArgs("3600") .WithEnvironment("DCP_ASPIRE_REGRESSION_RUN_ID", runId) @@ -67,24 +64,4 @@ .WaitFor(cache) .WaitFor(persistentWorker); -await using var app = builder.Build(); -using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); - -try -{ - await app.StartAsync(cts.Token); - Console.WriteLine("DCP-REGRESSION-APPHOST-STARTED"); - - var notifications = app.Services.GetRequiredService(); - foreach (var resourceName in new[] { "worker", "cache", "persistent-cache", "persistent-worker", "web" }) - { - await notifications.WaitForResourceHealthyAsync(resourceName, cts.Token); - Console.WriteLine($"DCP-REGRESSION-RESOURCE-HEALTHY: {resourceName}"); - } - - Console.WriteLine("DCP-REGRESSION-OK: all resources healthy"); -} -finally -{ - await app.StopAsync(cts.Token); -} +builder.Build().Run(); diff --git a/test/aspire/run-regression.sh b/test/aspire/run-regression.sh index b31f5099..ef0e3dbb 100755 --- a/test/aspire/run-regression.sh +++ b/test/aspire/run-regression.sh @@ -5,8 +5,8 @@ # It scaffolds a minimal AppHost with the installed (ideally latest daily) Aspire # CLI, keeps the scaffolded daily "#:sdk" directive, appends # test/aspire/apphost-body.cs, and runs it while pointing Aspire at the local DCP -# build via DcpPublisher__CliPath. The test passes only if every resource becomes -# healthy and the AppHost exits cleanly. +# build via DcpPublisher__CliPath. The test passes only if the Aspire CLI can +# start the AppHost and every expected resource becomes healthy. # # Prerequisites: # - The Aspire CLI on PATH (install the daily channel with: @@ -68,8 +68,16 @@ fi work_dir="$(mktemp -d 2>/dev/null || mktemp -d -t dcp-aspire-regression)" run_id="$(date -u +%Y%m%d%H%M%S)-$$" +apphost_started=false cleanup() { + if [[ "${apphost_started}" == "true" ]]; then + ( + cd "${work_dir}" + aspire stop --non-interactive --apphost apphost.cs >/dev/null 2>&1 || true + ) + fi + rm -rf "${work_dir}" } trap cleanup EXIT @@ -108,38 +116,22 @@ echo "==> Using scaffolded SDK pin: ${sdk_line}" echo "==> Running the regression AppHost against DCP at: ${dcp_cli_path}" output_log="${work_dir}/run.log" -set +e ( cd "${work_dir}" DcpPublisher__CliPath="${dcp_cli_path}" \ DCP_ASPIRE_REGRESSION_RUN_ID="${run_id}" \ ASPIRE_ALLOW_UNSECURED_TRANSPORT="true" \ DOTNET_ENVIRONMENT="Development" \ - dotnet run apphost.cs + aspire run --detach --isolated --format Json --non-interactive --apphost apphost.cs ) 2>&1 | tee "${output_log}" -run_status=${PIPESTATUS[0]} -set -e - -if [[ ${run_status} -ne 0 ]]; then - echo "AppHost exited with status ${run_status}" >&2 - exit "${run_status}" -fi - -if ! grep -q "DCP-REGRESSION-APPHOST-STARTED" "${output_log}"; then - echo "AppHost exited cleanly but did not report that it started" >&2 - exit 1 -fi +apphost_started=true for resource_name in worker cache persistent-cache persistent-worker web; do - if ! grep -q "DCP-REGRESSION-RESOURCE-HEALTHY: ${resource_name}" "${output_log}"; then - echo "AppHost exited cleanly but did not report resource healthy: ${resource_name}" >&2 - exit 1 - fi + echo "==> Waiting for Aspire resource to become healthy: ${resource_name}" + ( + cd "${work_dir}" + aspire wait "${resource_name}" --status healthy --timeout 300 --non-interactive --apphost apphost.cs + ) done -if ! grep -q "DCP-REGRESSION-OK" "${output_log}"; then - echo "AppHost exited cleanly but did not report all resources healthy" >&2 - exit 1 -fi - echo "==> Aspire regression test passed" From 0f1d1146fa99f15e5340ec319e84ee44dee9443e Mon Sep 17 00:00:00 2001 From: David Negstad Date: Tue, 7 Jul 2026 21:43:04 -0700 Subject: [PATCH 3/4] Address Aspire regression review feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- CONTRIBUTING.md | 2 +- test/aspire/run-regression.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 65216d09..21a03ee8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,7 +114,7 @@ test/aspire/run-regression.sh --dcp /path/to/dcp The script is intended for CI and does not remove the container runtime resources it creates. On GitHub-hosted runners these are cleaned up with the job environment; local runs may leave containers and persistent networks behind. -In CI this runs on Linux (where the hosted runner provides a Docker runtime) via the `.github/workflows/aspire-regression.yml` workflow, on a nightly schedule, on pushes to `main`, and on demand through **Run workflow**. +In CI this runs on Linux (where the hosted runner provides a Docker runtime) via the `.github/workflows/aspire-regression.yml` workflow, on a nightly schedule, on pushes to `main`, on pull requests targeting `main`, and on demand through **Run workflow**. ## Troubleshooting and debugging tips diff --git a/test/aspire/run-regression.sh b/test/aspire/run-regression.sh index ef0e3dbb..e84ea7fa 100755 --- a/test/aspire/run-regression.sh +++ b/test/aspire/run-regression.sh @@ -43,7 +43,7 @@ while [[ $# -gt 0 ]]; do shift ;; -h | --help) - sed -n '2,25p' "${BASH_SOURCE[0]}" + sed -n '2,21p' "${BASH_SOURCE[0]}" exit 0 ;; *) From 06dfd2bab2af2e3db5093017fff46e90bcd0666e Mon Sep 17 00:00:00 2001 From: David Negstad Date: Wed, 8 Jul 2026 11:25:02 -0700 Subject: [PATCH 4/4] Remove unnecessary Aspire GOPRIVATE Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/aspire-regression.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/aspire-regression.yml b/.github/workflows/aspire-regression.yml index 961de8c8..4f8d0bdb 100644 --- a/.github/workflows/aspire-regression.yml +++ b/.github/workflows/aspire-regression.yml @@ -24,9 +24,6 @@ on: permissions: contents: read -env: - GOPRIVATE: github.com/microsoft/usvc-*,github.com/microsoft/dcp - jobs: aspire-regression: name: Aspire regression (latest daily)