Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ea377a7
Many telemetry commands improvements (#14838)
JamesNK Mar 2, 2026
ee83e99
Cherry-pick VS Code extension fixes from main to release/13.2 (#14846)
adamint Mar 2, 2026
7ca15d6
Fix extension command table in README to match actual commands (#14687)
adamint Mar 2, 2026
f65c4f0
Use VS Code file pickers instead of prompting for strings (#14556)
adamint Mar 2, 2026
7f9396c
Pass open AppHost to CLI commands via --project flag (#14688)
adamint Mar 2, 2026
e807cd5
Add Aspire Activity Bar panel with running apphosts and resources tre…
adamint Mar 2, 2026
3f86164
Add more status indicators while work is in-progress (#14834)
JamesNK Mar 2, 2026
d0dd605
Bump VS Code extension version from 1.0.3 to 1.0.4 (#14860)
adamint Mar 2, 2026
02a4294
Add --disableProductStyleUrl to Azurite emulator args (#14855)
eerhardt Mar 2, 2026
547b7e0
[release/13.2] Move CI trigger patterns out of ci.yml into a separate…
Copilot Mar 3, 2026
4348b3c
Merge origin/release/13.2 into main
radical Mar 3, 2026
788b194
[release/13.2][CI] Unified test splitting infrastructure for CI paral…
radical Mar 3, 2026
ce61ece
Consistent resource colors, resource colors match dashboard (#14832)
JamesNK Mar 3, 2026
098531f
Add status bar item showing Aspire app host status (#14869)
adamint Mar 3, 2026
d8157f8
Replace ANSI escape codes with Live display, remove IAnsiConsole depe…
JamesNK Mar 3, 2026
518cd60
Fix flaky E2E test: retry MSBuild evaluation on empty output (#14868)
mitchdenny Mar 3, 2026
cae16ca
Add polyglot exports for AppContainers (#14758)
sebastienros Mar 3, 2026
5a3c52a
Replace Playwright MCP server with Playwright CLI installation (#14569)
mitchdenny Mar 3, 2026
5338db3
Remove temporary nuget.org feed from NuGet.config (#14877)
mitchdenny Mar 3, 2026
7a05783
Merge remote-tracking branch 'origin/release/13.2' into ankj/backmerg…
radical Mar 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
92 changes: 65 additions & 27 deletions .github/actions/check-changed-files/action.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
name: 'Check Changed Files'
description: |
Check if all changed files in a PR match provided regex patterns.
Check if all changed files in a PR match provided glob patterns.
This action compares changed files in a pull request against one or more regex patterns
This action compares changed files in a pull request against one or more glob patterns
and determines if all changed files match at least one of the provided patterns.
It only supports pull_request events.
Inputs:
- patterns: List of regex patterns (multiline string) to match against changed file paths
- patterns_file: Path to a file containing glob patterns (relative to repository root).
Lines starting with '#' and blank lines are ignored.
Pattern syntax:
- ** matches any path including directory separators (recursive)
- * matches any characters except a directory separator
- . is treated as a literal dot (no escaping needed)
Outputs:
- only_changed: Boolean indicating if all changed files matched the patterns
Expand All @@ -15,8 +22,8 @@ description: |
- matched_files: JSON array of files that matched at least one pattern
- unmatched_files: JSON array of files that didn't match any pattern
inputs:
patterns:
description: 'List of regex patterns to match against changed files'
patterns_file:
description: 'Path to a file containing glob patterns (relative to repository root)'
required: true

outputs:
Expand Down Expand Up @@ -57,12 +64,60 @@ runs:
exit 1
fi
# Read patterns from input (multiline string)
PATTERNS_INPUT="${{ inputs.patterns }}"
# Convert a glob pattern to an anchored ERE regex pattern.
# Glob syntax supported:
# ** matches any path including directory separators
# * matches any characters except a directory separator
# . is treated as a literal dot
# All other characters are treated as literals.
glob_to_regex() {
local glob="$1"
local result="$glob"
# Replace ** and * with placeholders before escaping
result="${result//\*\*/__DOUBLESTAR__}"
result="${result//\*/__STAR__}"
# Escape regex metacharacters that could appear in file paths.
# Note: { } ^ $ are not escaped because they are either not special
# in ERE mid-pattern or cannot appear in file paths.
result="${result//\\/\\\\}"
result="${result//./\\.}"
result="${result//+/\\+}"
result="${result//\?/\\?}"
result="${result//\[/\\[}"
result="${result//\]/\\]}"
result="${result//\(/\\(}"
result="${result//\)/\\)}"
result="${result//|/\\|}"
# Restore glob placeholders as regex
result="${result//__STAR__/[^/]*}"
result="${result//__DOUBLESTAR__/.*}"
# Anchor to full path
echo "^${result}$"
}
PATTERNS=()
PATTERNS_FILE="${{ inputs.patterns_file }}"
# Validate patterns input
if [ -z "$PATTERNS_INPUT" ]; then
echo "Error: patterns input is required"
# Read glob patterns from file, skip comments and blank lines
FULL_PATH="${GITHUB_WORKSPACE}/${PATTERNS_FILE}"
if [ ! -f "$FULL_PATH" ]; then
echo "Error: patterns_file '$FULL_PATH' not found"
exit 1
fi
while IFS= read -r line; do
# Remove leading/trailing whitespace
line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# Skip blank lines and comments
if [ -z "$line" ] || [[ "$line" == \#* ]]; then
continue
fi
PATTERNS+=("$(glob_to_regex "$line")")
done < "$FULL_PATH"
# Check if we have any valid patterns
if [ ${#PATTERNS[@]} -eq 0 ]; then
echo "Error: No valid patterns provided"
exit 1
fi
Expand All @@ -77,23 +132,6 @@ runs:
echo "Changed files:"
echo "$CHANGED_FILES"
# Convert patterns to array and filter out empty lines
PATTERNS=()
while IFS= read -r pattern; do
# Remove leading/trailing whitespace
pattern=$(echo "$pattern" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# Skip empty patterns
if [ -n "$pattern" ]; then
PATTERNS+=("$pattern")
fi
done <<< "$PATTERNS_INPUT"
# Check if we have any valid patterns
if [ ${#PATTERNS[@]} -eq 0 ]; then
echo "Error: No valid patterns provided"
exit 1
fi
# Initialize arrays
MATCHED_FILES=()
UNMATCHED_FILES=()
Expand Down
193 changes: 53 additions & 140 deletions .github/actions/enumerate-tests/action.yml
Original file line number Diff line number Diff line change
@@ -1,36 +1,32 @@
name: 'Enumerate test projects'
description: 'Enumerate list of test projects'
inputs:
includeIntegrations:
buildArgs:
required: false
type: boolean
default: false
includeTemplates:
required: false
type: boolean
default: false
includeCliE2E:
required: false
type: boolean
default: false
includeDeployment:
required: false
type: boolean
default: false

type: string
default: ''
description: 'Additional MSBuild arguments passed to the test matrix generation step (e.g., /p:IncludeTemplateTests=true /p:OnlyDeploymentTests=true)'

# Output format: JSON with structure {"include": [{...}, ...]}
# Each entry contains:
# - type: 'regular' | 'collection' | 'class'
# - projectName: Full project name (e.g., 'Aspire.Hosting.Tests')
# - name: Display name for the test run
# - shortname: Short identifier
# - testProjectPath: Relative path to the test project
# - workitemprefix: Prefix for work item naming
# - runs-on: GitHub Actions runner (e.g., 'ubuntu-latest', 'windows-latest')
# - testSessionTimeout: Timeout for the test session (e.g., '20m')
# - testHangTimeout: Timeout for hung tests (e.g., '10m')
# - requiresNugets: Boolean indicating if NuGet packages are needed
# - requiresTestSdk: Boolean indicating if test SDK is needed
# - extraTestArgs: Additional test arguments (e.g., '--filter-trait "Partition=P1"')
# - collection: (collection type only) Collection/partition name
# - classname: (class type only) Fully qualified test class name
outputs:
integrations_tests_matrix:
description: Integration tests matrix
value: ${{ steps.generate_integrations_matrix.outputs.integrations_tests_matrix }}
templates_tests_matrix:
description: Templates tests matrix
value: ${{ steps.generate_templates_matrix.outputs.templates_tests_matrix }}
cli_e2e_tests_matrix:
description: Cli E2E tests matrix
value: ${{ steps.generate_cli_e2e_matrix.outputs.cli_e2e_tests_matrix }}
deployment_tests_matrix:
description: Deployment E2E tests matrix
value: ${{ steps.generate_deployment_matrix.outputs.deployment_tests_matrix }}
all_tests:
description: Combined matrix of all test entries
value: ${{ steps.expand_matrix.outputs.all_tests }}
runs:
using: "composite"
steps:
Expand All @@ -42,123 +38,38 @@ runs:
with:
global-json-file: ${{ github.workspace }}/global.json

- name: Get list of integration tests
if: ${{ inputs.includeIntegrations }}
shell: pwsh
run: >
dotnet build ${{ github.workspace }}/tests/Shared/GetTestProjects.proj
/bl:${{ github.workspace }}/artifacts/log/Debug/GetTestProjects.binlog
/p:TestsListOutputPath=${{ github.workspace }}/artifacts/TestsForGithubActions.list
/p:ContinuousIntegrationBuild=true

- name: Generate list of template tests
if: ${{ inputs.includeTemplates }}
shell: pwsh
run: >
dotnet build ${{ github.workspace }}/tests/Aspire.Templates.Tests/Aspire.Templates.Tests.csproj
"/t:Build;ExtractTestClassNames"
/bl:${{ github.workspace }}/artifacts/log/Debug/BuildTemplatesTests.binlog
-p:ExtractTestClassNamesForHelix=true
-p:PrepareForHelix=true
-p:ExtractTestClassNamesPrefix=Aspire.Templates.Tests
-p:InstallBrowsersForPlaywright=false
- name: Restore
shell: bash
run: ./restore.sh

- name: Generate list of CLI E2E tests
if: ${{ inputs.includeCliE2E }}
shell: pwsh
run: >
dotnet build ${{ github.workspace }}/tests/Aspire.Cli.EndToEnd.Tests/Aspire.Cli.EndToEnd.Tests.csproj
"/t:Build;ExtractTestClassNames"
/bl:${{ github.workspace }}/artifacts/log/Debug/BuildCliEndToEndTests.binlog
-p:ExtractTestClassNamesForHelix=true
-p:PrepareForHelix=true
-p:ExtractTestClassNamesPrefix=Aspire.Cli.EndToEnd.Tests
-p:InstallBrowsersForPlaywright=false
- name: Build ExtractTestPartitions tool
shell: bash
run: dotnet build tools/ExtractTestPartitions/ExtractTestPartitions.csproj -c Release --nologo -v quiet

- name: Generate list of Deployment E2E tests
if: ${{ inputs.includeDeployment }}
shell: pwsh
- name: Generate canonical test matrix
shell: bash
run: >
dotnet build ${{ github.workspace }}/tests/Aspire.Deployment.EndToEnd.Tests/Aspire.Deployment.EndToEnd.Tests.csproj
"/t:Build;ExtractTestClassNames"
/bl:${{ github.workspace }}/artifacts/log/Debug/BuildDeploymentEndToEndTests.binlog
-p:ExtractTestClassNamesForHelix=true
-p:PrepareForHelix=true
-p:ExtractTestClassNamesPrefix=Aspire.Deployment.EndToEnd.Tests

- name: Generate tests matrix
id: generate_integrations_matrix
if: ${{ inputs.includeIntegrations }}
shell: pwsh
run: |
$filePath = "${{ github.workspace }}/artifacts/TestsForGithubActions.list"
$lines = Get-Content $filePath
$jsonObject = @{
"shortname" = $lines | Sort-Object
}
$jsonString = ConvertTo-Json $jsonObject -Compress
"integrations_tests_matrix=$jsonString"
"integrations_tests_matrix=$jsonString" | Out-File -FilePath $env:GITHUB_OUTPUT

- name: Generate templates matrix
id: generate_templates_matrix
if: ${{ inputs.includeTemplates }}
shell: pwsh
run: |
$inputFilePath = "${{ github.workspace }}/artifacts/helix/templates-tests/Aspire.Templates.Tests.tests.list"
$lines = Get-Content $inputFilePath

$prefix = "Aspire.Templates.Tests."
$lines = Get-Content $inputFilePath | ForEach-Object {
$_ -replace "^$prefix", ""
}

$jsonObject = @{
"shortname" = $lines | Sort-Object
}
$jsonString = ConvertTo-Json $jsonObject -Compress
"templates_tests_matrix=$jsonString"
"templates_tests_matrix=$jsonString" | Out-File -FilePath $env:GITHUB_OUTPUT

- name: Generate cli e2e matrix
id: generate_cli_e2e_matrix
if: ${{ inputs.includeCliE2E }}
./build.sh -test
/p:TestRunnerName=TestEnumerationRunsheetBuilder
/p:TestMatrixOutputPath=artifacts/canonical-test-matrix.json
/p:GenerateCIPartitions=true
/bl
${{ inputs.buildArgs }}

- name: Expand matrix for GitHub Actions
id: expand_matrix
shell: pwsh
run: |
$inputFilePath = "${{ github.workspace }}/artifacts/helix/cli-e2e-tests/Aspire.Cli.EndToEnd.Tests.tests.list"
$lines = Get-Content $inputFilePath

$prefix = "Aspire.Cli.EndToEnd.Tests."
$lines = @(Get-Content $inputFilePath | ForEach-Object {
$_ -replace "^$prefix", ""
})

$jsonObject = @{
"shortname" = @($lines | Sort-Object)
}
$jsonString = ConvertTo-Json $jsonObject -Compress
"cli_e2e_tests_matrix=$jsonString"
"cli_e2e_tests_matrix=$jsonString" | Out-File -FilePath $env:GITHUB_OUTPUT

- name: Generate deployment matrix
id: generate_deployment_matrix
if: ${{ inputs.includeDeployment }}
shell: pwsh
run: |
$inputFilePath = "${{ github.workspace }}/artifacts/helix/deployment-e2e-tests/Aspire.Deployment.EndToEnd.Tests.tests.list"
$lines = Get-Content $inputFilePath

$prefix = "Aspire.Deployment.EndToEnd.Tests."
$lines = @(Get-Content $inputFilePath | ForEach-Object {
$_ -replace "^$prefix", ""
})

$jsonObject = @{
"shortname" = @($lines | Sort-Object)
$canonicalMatrixPath = "${{ github.workspace }}/artifacts/canonical-test-matrix.json"
$expandScript = "${{ github.workspace }}/eng/scripts/expand-test-matrix-github.ps1"

if (Test-Path $canonicalMatrixPath) {
& $expandScript -CanonicalMatrixFile $canonicalMatrixPath -OutputToGitHubEnv
} else {
$emptyMatrix = '{"include":[]}'
"all_tests=$emptyMatrix" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
Write-Host "No canonical test matrix found, using empty matrix"
}
$jsonString = ConvertTo-Json $jsonObject -Compress
"deployment_tests_matrix=$jsonString"
"deployment_tests_matrix=$jsonString" | Out-File -FilePath $env:GITHUB_OUTPUT

- name: Upload logs
if: always()
Expand All @@ -167,4 +78,6 @@ runs:
name: logs-enumerate-tests-${{ runner.os }}
path: |
artifacts/log/**/*.binlog
artifacts/**/*.list
artifacts/**/*tests-partitions.json
artifacts/**/*tests-metadata.json
artifacts/canonical-test-matrix.json
2 changes: 1 addition & 1 deletion .github/skills/cli-e2e-testing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ Environment variables set in CI:
- `GH_TOKEN`: GitHub token for API access
- `GITHUB_WORKSPACE`: Workspace root for artifact paths

Each test class runs as a separate CI job via `CliEndToEndTestRunsheetBuilder` for parallel execution.
Each test class runs as a separate CI job via the unified `TestEnumerationRunsheetBuilder` infrastructure (using `SplitTestsOnCI=true`) for parallel execution.

## CI Troubleshooting

Expand Down
23 changes: 1 addition & 22 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,28 +37,7 @@ jobs:
if: ${{ github.event_name == 'pull_request' }}
uses: ./.github/actions/check-changed-files
with:
# Patterns that do NOT require CI to run
patterns: |
\.md$
eng/pipelines/.*
eng/test-configuration.json
\.github/instructions/.*
\.github/skills/.*
\.github/workflows/apply-test-attributes.yml
\.github/workflows/backmerge-release.yml
\.github/workflows/backport.yml
\.github/workflows/dogfood-comment.yml
\.github/workflows/generate-api-diffs.yml
\.github/workflows/generate-ats-diffs.yml
\.github/workflows/labeler-*.yml
\.github/workflows/markdownlint*.yml
\.github/workflows/refresh-manifests.yml
\.github/workflows/reproduce-flaky-tests.yml
\.github/workflows/pr-review-needed.yml
\.github/workflows/specialized-test-runner.yml
\.github/workflows/tests-outerloop.yml
\.github/workflows/tests-quarantine.yml
\.github/workflows/update-*.yml
patterns_file: eng/testing/github-ci-trigger-patterns.txt

- id: compute_version_suffix
name: Compute version suffix for PRs
Expand Down
Loading
Loading