Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 65 additions & 36 deletions .azure/pipelines/aspire-test.yml
Original file line number Diff line number Diff line change
@@ -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)
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
69 changes: 69 additions & 0 deletions .github/workflows/aspire-regression.yml
Original file line number Diff line number Diff line change
@@ -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
Comment thread
danegsta marked this conversation as resolved.

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/
20 changes: 20 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<dcprepo>/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 `aspire run --detach` starts the AppHost and `aspire wait` reports every expected resource healthy.

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`, on pull requests targeting `main`, and on demand through **Run workflow**.

## Troubleshooting and debugging tips

### `make lint` times out (or ends with an error that says "Killed")
Expand Down
67 changes: 67 additions & 0 deletions test/aspire/apphost-body.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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 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 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.
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);

builder.Build().Run();
Loading
Loading