Skip to content

Port conditional test selection changes to main#15742

Draft
radical wants to merge 23 commits into
mainfrom
port-conditional-test-selection-to-main
Draft

Port conditional test selection changes to main#15742
radical wants to merge 23 commits into
mainfrom
port-conditional-test-selection-to-main

Conversation

@radical

@radical radical commented Apr 1, 2026

Copy link
Copy Markdown
Member

Description

Ports the conditional test selection work from conditional-test-selection-unified-rules-design onto main as a squashed change.

This brings over the workflow updates, selector tooling, Infrastructure coverage, and supporting docs as a single forward port so the branch-only work can be reviewed against current main without replaying the original merge-heavy history.

Validation performed:

  • ./restore.sh
  • ./dotnet.sh build tools/TestSelector/TestSelector.csproj -c Release --nologo
  • ./dotnet.sh build tools/Aspire.TestSelector/Aspire.TestSelector.csproj -c Release --nologo
  • ./dotnet.sh build tools/GenerateTestSummary/GenerateTestSummary.csproj -c Release --nologo
  • ./dotnet.sh build tests/Aspire.Cli.EndToEnd.Tests/Aspire.Cli.EndToEnd.Tests.csproj -c Release --nologo
  • ./dotnet.sh build tests/Aspire.EndToEnd.Tests/Aspire.EndToEnd.Tests.csproj -c Release --nologo
  • ./dotnet.sh build tests/Aspire.Templates.Tests/Aspire.Templates.Tests.csproj -c Release --nologo
  • ./dotnet.sh test tests/Infrastructure.Tests/Infrastructure.Tests.csproj -c Release -- --filter-namespace "*TestSelector*" --filter-namespace "*.PowerShellScripts" --filter-namespace "*.ConditionalSelection" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"

Fixes # (issue)

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@radical radical added the NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) label Apr 1, 2026
@github-actions

github-actions Bot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 15742

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 15742"

radical and others added 5 commits April 7, 2026 02:22
…-selection-to-main

# Conflicts:
#	.github/workflows/tests.yml
#	tests/Infrastructure.Tests/Infrastructure.Tests.csproj
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Restore PR coverage for CLI, polyglot, and extension tests
- Prevent TestSelector fixture projects from compiling during repo builds
- Remove duplicate unused Aspire.TestSelector tool copy

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove unconditional PR execution from standalone conditional jobs
- Add workflow regression coverage for category guards

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@adamint adamint self-assigned this Jun 4, 2026

@adamint adamint left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

High-confidence findings from review:

  1. DotNetAffectedRunner.RunProcessAsync reads stdout to completion before it starts reading stderr. If dotnet affected --verbose fills stderr while stdout is quiet, the child blocks on the stderr pipe and the parent waits forever on stdout. Start both ReadToEndAsync() tasks before awaiting, like the GitHelper code in this PR already does.

  2. This port downgrades existing actions/upload-artifact call sites from the v7.0.0 SHA on main back to v4.6.1, leaving tests.yml with a v4/v7 mix. Please re-pin the touched upload steps to the v7.0.0 SHA used by main.

  3. The Show test selection results step prints ACTIVE_SELECTION_REASON, but that variable is not in the step env: block. The diagnostic group will always show selection_reason=unknown; add ACTIVE_SELECTION_REASON: ${{ steps.detect_active_raw.outputs.selection_reason }}.

@radical radical added the no-review Skip for reviews label Jun 5, 2026
radical and others added 4 commits June 6, 2026 12:34
Resolve conflicts:
- extension/Extension.proj: take origin/main version (restores the
  COREPACK_HOME property block; the prior HEAD had a placeholder
  '<x/>' from an earlier bad conflict resolution).
- tests/Infrastructure.Tests/Infrastructure.Tests.csproj: keep both
  TestSelector and TypeScriptApiCompat ProjectReferences.
- .github/workflows/tests.yml: keep the conditional-selection gating
  pattern (run_all / run_extension / run_polyglot, matrix-non-empty)
  and add the new jobs introduced on main to the 'Fail if any
  dependency failed' check: build_cli_archive_macos_x64,
  prepare_winget_installer_artifacts,
  prepare_homebrew_installer_artifacts, build_cli_e2e_image
  (gated by tests_matrix_requires_cli_archive),
  extension_e2e_tests (gated by extension_e2e_changes), and
  typescript_api_compat.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous merge of origin/main produced two 'if:' keys on the
polyglot_validation job: one from main gating on event/ref, and one
from this PR gating on the conditional-selection outputs. GitHub's
workflow parser rejected the file entirely (run failed with no jobs
created), and actionlint flagged 'key "if" is duplicated'.

Combine both into a single guard: the job runs when (a) the trigger
is a PR or a push to main AND (b) the conditional selector requested
either run_all or run_polyglot. Non-PR events still fall through
because setup_for_tests pre-sets every run_* output to 'true' for
them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Stabilization Check job invokes Arcade's pack pipeline, which
enumerates ProjectToBuild from `tests/**/*.csproj` (eng/Build.props
line 69). That enumeration was picking up the static-analyzer fixture
projects under tests/Infrastructure.Tests/TestSelector/TestFixtures/.
Those .csproj files are deliberate test fixtures consumed as text by
TestSelector's analyzer tests (IgnorePathFilterTests,
NuGetDependentTestDetectorTests, etc.) — they're never meant to be
built. Their adjacent Directory.Build.targets neuters Build/Rebuild/
Restore/Pack/Test as no-ops, which is enough for `build.sh -build`
but not for `build.sh -pack`: the NuGet Pack target still attempts
to read the assembly output (e.g. ConditionalPackableProject.dll) and
fails with NU5026 because Build was a no-op.

Filter the fixtures out of the test-project enumeration so Arcade
never tries to build or pack them. The TestSelector tool's own
testProjectPatterns config already excludes `tests/**/TestFixtures/**`
from its discovery, so this brings the build infrastructure in line
with the same convention.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replayed the 161 PRs merged to main between 2026-05-23 and 2026-06-06
through the audit selector. v1 (the prior file) resolved 89/161 PRs to
'selective' and dropped 26/161 to 'fallback_unmatched' (any unmatched
file forces RunAll). After this change all 161 PRs classify cleanly:
18 skip, 32 trigger_all, 111 selective, 0 fallback.

Additions:

* ignorePaths — files that never need a test gate but were forcing
  fallback today: .vscode/**, .mcp.json, localhive.{sh,ps1},
  .github/{extensions,policies,aw}/**, .agents/** (CLI agent skill
  content, consumed only by humans/agents), eng/scripts/debug-*.{sh,ps1}
  (developer-loop helpers), eng/scripts/cli-starter-validation*.ps1
  (consumed by the always-on cli_starter_validation_windows job, which
  isn't conditional-selector-gated).

* sourceToTestMappings — scripts and infra files that have a specific
  test home but weren't wired up: eng/Publishing.props,
  eng/Signing.props, eng/scripts/{pack,stage,verify,validate}-cli-*
  and tools/TypeScriptApiCompat/** all map to Infrastructure.Tests
  (its Pipelines/ and PowerShellScripts/ folders test these scripts).
  eng/scripts/get-aspire-cli{,-pr}.{sh,ps1}, eng/scripts/verify-cli-archive.ps1,
  eng/homebrew/** and eng/winget/** map to Aspire.Acquisition.Tests
  (the script-suite that exercises them). eng/scripts/update-aspire-
  skills-bundle.ps1 + verify-aspire-skills-bundle.ps1 map to
  Aspire.Cli.Tests (AspireSkillsBundleTests). Two self-mappings for
  tests directories without a 'tests/Aspire.*.Tests/**' shape:
  tests/Infrastructure.Tests/** and
  tests/Aspire.Hosting.CodeGeneration.TypeScript.JsTests/**.

* polyglot.triggerPaths — expanded beyond the workflow file itself to
  cover the polyglot fixture tree (tests/PolyglotAppHosts/**) and the
  per-language Aspire.Hosting.{JavaScript,TypeScript,Python,Go,Java,Rust}
  + CodeGeneration* projects. Without these, polyglot-impacting PRs
  (#17545, #17499, #17419, #17400, #17382) all fell into RunAll.

Local verification: 251 TestSelector/PowerShellScripts/ConditionalSelection
tests still pass. The CI-trigger-pattern coverage test (which loads this
file from disk) is unchanged because we only added to ignorePaths and
sourceToTestMappings — no existing entries were removed or narrowed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

radical and others added 13 commits June 6, 2026 14:23
CI on 5d9adab failed in 'Fail if any dependency failed' because
extension_e2e_tests showed up as 'skipped' while extension_e2e_changes
had output run_extension_e2e=true. Root cause: extension_e2e_tests
lists extension_tests_win in its needs, and the conditional selector
correctly skipped extension_tests_win on this PR (run_extension/run_all
both false). GitHub Actions then auto-skips any dependent whose 'if'
doesn't include a status function — so extension_e2e_tests was forced
to skip transitively, even though run_extension_e2e == 'true' and the
rest of its needs were green.

extension_e2e_changes has its own broader path-detection (extension/,
src/Aspire.Cli/, src/Aspire.Hosting*/, src/Aspire.Dashboard*/,
tests/Aspire.Cli*/, get-aspire-cli scripts, certain workflow files) so
it can fire when 'extension' category is NOT triggered — e.g. a pure
CLI or hosting change should run extension E2E even though no
extension code changed. The fix is to let extension_e2e_tests run when
its own path-detection says yes, regardless of extension_tests_win's
result.

Use `!cancelled() && !failure() && run_extension_e2e == 'true'`. The
status-function prefix disables the implicit-skip behavior; the
failure/cancel checks still propagate real failures from any needed
job (build_packages, build_cli_archive_*, extension_bootstrap_linux,
extension_tests_win) but treat 'skipped' as acceptable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CI on 187550c (the previous attempt to fix this) made
extension_e2e_tests survive extension_tests_win being skipped, but the
E2E job depends on the VSIX artifact produced inside
extension_tests_win's Package VSIX step. With extension_tests_win
skipped, every E2E shard failed:

    Unable to download artifact(s): Artifact not found for name: aspire-extension

The two jobs cannot be decoupled — the unit-test job is also the only
producer of the VSIX. Run extension_tests_win whenever EITHER the
conditional selector triggers the extension category OR
extension_e2e_changes (a broader path-detection covering CLI / hosting
/ dashboard) wants E2E. The 'Fail if any dependency failed' gate is
updated to match: extension_tests_win is only required when one of
those three signals is true.

Revert extension_e2e_tests's if back to the simple form. Now that
extension_tests_win is guaranteed to run whenever
run_extension_e2e == 'true', the implicit GitHub-Actions auto-skip on
needs failure/skip is the desired behavior again — there's no
legitimate case where run_extension_e2e is true and extension_tests_win
is skipped.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ules

When several source patterns all run the same test project, the rules
file used to need one mapping entry per source — a lot of repetition
and visual noise. Example before this change (excerpt of audit.json):

    { "source": "eng/Publishing.props", "test": "tests/Infrastructure.Tests/Infrastructure.Tests.csproj" },
    { "source": "eng/Signing.props",    "test": "tests/Infrastructure.Tests/Infrastructure.Tests.csproj" },
    { "source": "eng/scripts/pack-cli-npm-package.ps1",  "test": "tests/Infrastructure.Tests/Infrastructure.Tests.csproj" },
    { "source": "eng/scripts/verify-cli-npm-package.ps1","test": "tests/Infrastructure.Tests/Infrastructure.Tests.csproj" },
    ... 4 more rows ...

Make `source` accept either a string (existing form) or an array, and
collapse those rows into one. Both shapes resolve identically; the
single-string form is just sugar for a one-element array.

Implementation:

* `SourceToTestMapping.Source` is now `List<string>`, deserialized
  through a `StringOrStringArrayConverter` that accepts both shapes.
* `ProjectMappingResolver.CompiledMapping` keeps one compiled regex
  per source pattern; `TryMatch` / `TryMatchWithDetails` iterate them
  and short-circuit on the first hit. `exclude` is applied uniformly
  across every source pattern in the mapping (one matcher, checked
  before the per-pattern regex loop). The detail-recording path
  reports the *specific* source pattern that matched, not the whole
  list, so `selector-active.json` mapping diagnostics stay precise.
* Schema (`test-selection-rules.schema.json`) updated to `oneOf`
  string / non-empty array. Existing tooling that points at the
  schema gets the new shape with no other changes.
* README adds a short example of the array form alongside the existing
  `{name}` example.

Audit JSON consolidation: 29 mapping entries → 6 (each test project
now has a single entry). Replayed against 161 PRs merged to main
between 2026-05-23 and 2026-06-06: zero per-PR outcome drift versus
the pre-consolidation rules. The CI-trigger-pattern coverage test
(loads the real audit JSON from disk) still passes.

Tests added:

* `LoadFromJson_SourceAsArray_CollectsAllPatterns` — verifies the
  new shape deserializes to the expected list, in order.
* `LoadFromJson_SourceAsString_NormalizesToSingleElementList` —
  pins the back-compat shape.
* `ResolveTestProjects_ArraySource_AllPatternsMapToSameProject`
  (theory, 4 inputs) — equivalence with the prior N-mapping form.
* `ResolveTestProjects_ArraySource_ExcludeAppliesToAllPatterns` —
  exclude is shared across patterns within a mapping.

Existing tests that assigned `Source = "..."` now use the collection
initializer form `Source = ["..."]`; behavior unchanged.

Verification: 258 TestSelector / PowerShellScripts / ConditionalSelection
tests pass (up from 251 by the four new tests, factoring out theory
expansion).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ilename globs match nested paths

Three automated PRs in the last 14d fired more tests than they needed to:

  #17672, #17263  [Automated] Update ATS API Surface Area
  #17534          Move repository skills to .agents

They each touched only api/*.txt or only README/skill markdown, but were
firing trigger_all or selective:integrations — burning the full ~25min
CI critical path. Audit-replay over 161 merged PRs found them, plus a
latent C# glob bug where bare-filename patterns silently ignored
nested matches.

Root causes — two independent bugs:

1. The category-trigger rescue logic in RescueCategoryTriggerFiles built
   a synthetic union of all category triggerPaths without consulting
   per-category excludePaths. An ignored file that textually matched some
   category's glob but was excluded from that category got rescued back
   to active and then hit fallback_unmatched (worse than staying
   ignored). For src/Aspire.Hosting.Foundry/api/*.txt this meant ATS-only
   PRs fired integrations even after `**/api/*.txt` was added to
   ignorePaths.

2. The four glob analyzers (CriticalFileDetector, IgnorePathFilter,
   CategoryMapper.CompiledCategory, ProjectMappingResolver.CompiledMapping)
   handed user-facing patterns directly to FileSystemGlobbing.Matcher.
   The Matcher anchors bare-filename patterns at the repo root, so
   `Directory.Build.props` matched only the root file, not
   `src/Directory.Build.props` or `tests/Directory.Build.props`. The
   Python audit-replay evaluator (eval_rules.py) already documented and
   applied a "prepend **/ to bare-filename patterns" rule; the C#
   analyzers did not, so the two evaluators silently disagreed on
   ~5 patterns across ignorePaths, triggerAllPaths, and sourceToTestMappings.

The fix:

- Rescue now passes config.Categories directly to CategoryMapper so
  CompiledCategory.Matches honors per-category excludes. A file is
  rescued only when at least one category would actually fire on it.
- New PatternNormalization.NormalizeGlob prepends `**/` to any pattern
  without a path separator. Every glob entry point applies it: the four
  analyzers above plus ProjectMappingResolver's regex compiler.
- Rules: integrations.excludePaths gains `tests/Aspire.Acquisition.Tests/**`,
  `tests/Infrastructure.Tests/**`, `**/*.md`, `**/api/*.txt`. The same
  `**/api/*.txt` exclude is added to every category so an ignored ATS
  file can't be rescued back by any category. ignorePaths gains
  `**/api/*.txt`. Acquisition mapping's source list gains the missing
  self-mapping `tests/Aspire.Acquisition.Tests/**` (Templates and
  Infrastructure mappings already had this; Acquisition was an oversight
  exposed only after the new exclude was added).

Verification:

- Audit replay over 161 merged PRs: 4 outcomes change (#17263, #17534,
  #17549, #17672 all move to `skip`); zero regressions; zero
  fallback_unmatched.
- New AuditFixtureTests xUnit [Theory] replays 28 hand-validated PRs
  against the live audit rules. Each row is a separate test, so any
  future rule edit that changes a row's outcome shows up as a visible
  CI failure. Coverage includes templates (#16447), CLI native build
  (#17567), extension multi-category (#17881/17698/17772), Hosting-core
  trigger_all (#17879), polyglot (#17948), and the regression canaries
  for previous fallback_unmatched cases.
- Per-component regression tests pin both bugs: two new tests in
  EndToEndEvaluationTests for rescue+excludes; two more for
  bare-filename matching at nested paths.
- Three pre-existing analyzer tests had asserted the buggy bare-filename
  behavior as expected (e.g. `*.md` not matching `docs/guide.md`).
  Updated with comments explaining the user-intent rule.
- Full TestSelector namespace: 290 tests, all pass. No collateral damage
  on the wider Infrastructure.Tests suite (5 pre-existing baseline
  failures unchanged).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous commit added a `RestrictedProjectFilter.Apply` call in
`TestEvaluator.FilterAndCombineTestProjects` but never `git add`ed the new
`RestrictedProjectFilter.cs` or its test. A clean checkout of HEAD therefore
fails to compile — the type is unresolved. The build only worked locally
because the files existed untracked in the working tree.

Add the two files so HEAD builds on its own and the restricted-test-project
rule is covered by its unit tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When the integrations category matched a changed file but `dotnet-affected`
attributed the change to no project at all, the selector produced an empty
affected-test-projects set and selected nothing. The common trigger is a
changed file that is not an MSBuild input of any project (e.g. a file under a
project with `EnableDefaultItems=false`, or outside the default item globs):
`dotnet-affected`'s reverse-dependency walk returns nothing, so a known change
to a covered source area runs zero integration tests.

Add a conservative guard (`CheckMatchedButZeroProjects`, evaluator step 9b): if
the `integrations` category fired but both the affected-project set and the
resolved test-project set are empty, run all tests instead of selecting none.

Scope is deliberate:

- Gated on the `integrations` category — the boolean-driven categories
  (cli_e2e, extension, polyglot) gate their own jobs via `run_<category>`
  regardless of the affected matrix, so an empty matrix is expected for them
  and must not force a full run.
- Requires zero affected projects — when `dotnet-affected` did see the change
  but it resolved only to a restricted opt-out project or a source project with
  no dependent test, that is a deliberate outcome, not a blind spot, so the
  guard stays out of the way.

The guard is unreachable in the current active (templates-pilot) config: its
`ignorePaths: ['**']` plus sourceToTestMappings covering all template trigger
paths route every active file through the mappings-only shortcut before the
guard runs. It applies to the audit (observational) selector and to any future
promotion of the audit rules to the gating config.

Also bump `dotnet-affected` 6.2.0-preview-1 -> 6.2.0 (now released).

Tests: 4 cases in EndToEndEvaluationTests pinning the fire / no-fire conditions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Changes under four shared test-helper / app-host fixture directories matched no
audit rule, so the selector fell through to its conservative RunAll fallback
(fallback_unmatched) — every change to a test utility re-ran the whole suite:

- tests/Aspire.TestUtilities
- tests/Aspire.Components.Common.TestUtilities
- tests/testproject
- tests/TestingAppHost1

Each is referenced by its dependents via ProjectReference (e.g.
Aspire.Hosting.Tests, Aspire.Hosting.Testing.Tests, Aspire.Hosting.Docker.Tests),
so dotnet-affected can fan a change out to exactly the affected test projects. Add
the four directories to the integrations category triggerPaths so a change is
"accounted for" and routed through dotnet-affected instead of forcing a full run.

This is safe because every tracked file under these directories is an MSBuild
input (default Compile/None/Content globs; none set EnableDefaultItems=false), so
dotnet-affected sees the change. A future non-input file would resolve to zero
projects and is caught by the matched-but-zero guard, which runs all tests.

Tests: AuditRules_SharedTestHelperChange_RoutedToIntegrations asserts each
directory now resolves to a selective integrations run rather than
fallback_unmatched.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The active conditional test-selection config set `ignorePaths: ["**"]`,
which routed every non-template change into the "ignored" bucket. A PR
that touched a template file alongside any other source was narrowed to
Aspire.Templates.Tests only, and the rest of the suite was silently
skipped while CI reported green:

    --changed-files "tests/Aspire.Templates.Tests/X.cs,src/Aspire.Hosting.Redis/RedisBuilder.cs"
    => runAllTests=false, affected=[Aspire.Templates.Tests], ignored=[RedisBuilder.cs]

The same catch-all ignore meant the selector never emitted `run_polyglot`,
so the `polyglot_validation` gate (`run_all || run_polyglot`) was false on
every PR and polyglot validation never ran.

Root cause: `ignorePaths` is a "never trigger a full run" signal, but the
`["**"]` entry was treated as "safe to skip." Ignored files bypass the
unmatched-files safety net that otherwise forces a full run.

Fix: drop the catch-all (`ignorePaths: []`). Non-template changes are now
unmatched and force a full run; pure-template PRs still narrow to
Aspire.Templates.Tests. Polyglot is covered for free because any
non-template change sets `run_all=true`, satisfying its gate.

Also in this change:

- ProjectMappingResolver: a normalized bare-filename pattern (`**/X`)
  compiled to `^.*/X$`, which cannot match the file at the repo root.
  Translate a leading `**/` to `(?:.*/)?` so root and nested both match.

- Audit comparison steps run pwsh with `$ErrorActionPreference='Stop'`
  but had no guard; a malformed or locked TRX could fail an otherwise
  green PR. Mark both observational steps `continue-on-error: true`.

- Restore four `upload-artifact` pins from v4.6.1 back to the repo
  standard v7.0.0.

- Remove the superseded hybrid/comparison design artifacts; keep the
  unified target-design docs that live specs still reference.

Tests: update two active-config tests that had asserted the unsafe
behavior, add a regression guard that fails if a mixed PR stops running
the full suite, and add a bare-filename root-match test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Three review-driven fixes to the conditional test selector.

## Force runtime-coupled tests via category testProjects

A category's tests only run under selective scope if dotnet-affected put
them in `affected_test_projects`. `filter-test-matrix-by-scope.ps1` drops
every matrix entry whose test project is not in that list, and the
`tests_requires_cli_archive` job runs only when its filtered matrix is
non-empty. `run_cli_e2e` gates test enumeration, not this filtering.

Aspire.Cli.EndToEnd.Tests has no `ProjectReference` to src/Aspire.Cli or
src/Aspire.Hosting; it consumes a built CLI archive at runtime. So
dotnet-affected never selects it, and under any selective scope the CLI
end-to-end job would be silently skipped even for direct CLI or Hosting
changes.

Add a `testProjects` list to a category. When the category is triggered,
its projects are injected into the affected set additively — after the
matched-but-zero guard (so that guard still sees a genuinely empty
integrations resolution) and after restricted filtering (forced projects
are explicit opt-ins). Wired in the audit ruleset so cli_e2e forces
Aspire.Cli.EndToEnd.Tests for src/Aspire.Cli, src/Aspire.Hosting, and
eng/clipack changes.

Source-to-integration coverage already flows through dotnet-affected's
project graph (a component change reaches its test via ProjectReference),
so only the runtime-only couplings need explicit expression here.

## Read the real RequiresNugets property

The NuGet-dependent test detector scanned csproj files for a
`<RequiredNuGetsForTesting>` element, which no project defines. The actual
repo property is `<RequiresNugets>` (eng/testing/CITestsProperties.props),
set by the CLI, Templates, and EndToEnd test projects. The detector never
matched anything. Renamed the scan and helper to `RequiresNugets`.

## Drop the unused unified rules config

eng/scripts/test-selection-rules.unified.json and
docs/ci/conditional-test-selection-unified-rules.md described a target
design that nothing references. Removed both and the doc pointers to them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ngine)

The test-selection config has been accreting policy verbs into data: each
new use case added a JSON field plus an interpreter branch
(restrictedTestProjects = suppress, category testProjects = force,
excludePaths = exclude). The cli_e2e gap that needed the "force" knob is a
symptom: relationships and decisions are tangled, and precedence is implicit
across two languages (C# selective + PowerShell RunAll).

Add a forward-looking design spec proposing one line: data = relationships
only (a typed-edge graph), code = decisions only (one fixed selection
engine). Under that split the "force" and "restrict" knobs disappear — force
becomes an ordinary runtime-typed edge, restrict becomes a per-node
inferDeps fact (or edge negation).

The design is validated against prior art (Nx project graph + affected,
Pants dependency inference + explicit-edge augmentation, Bazel data edges +
target-determinator, the dorny/paths-filter and Develocity/Azure-TIA
extremes) and the mechanism-vs-policy principle. Two backbone claims
(Bazel's overapproximation invariant, Pants' hermetic BUILD files) were
verified against primary sources this session; the rest are cited for
spot-checking during review.

The spec is design-only and changes no live CI behavior. The override
mechanisms it reshapes live only in the audit config today, dormant in
production. It documents open decisions (negation granularity, edge
direction, the C#/PowerShell dual-enforcement, implementation language) and
a migration path that lands behind the audit config first.

Add forward pointers from the original spec and the current-behavior doc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two companion docs to the test-selection redesign spec, for review and
implementation in a follow-up session.

- test-selection-redesign-eval.md: a review rubric (Design + Implementation
  criteria with PASS/FAIL and severity, a "cases the schema still can't
  express" section, and a blocker-only Go/No-Go gate). Grounded against the
  current tool code.

- test-selection-redesign-plan.md: a phased implementation plan (Phases 0-6),
  resolved stances on the five open decisions, per-branch falsifiable tests,
  risk/rollback, and an audit->active promotion gate.

The three docs are cross-linked; the design spec now points to both siblings.

Open fork flagged at the top of both docs (rubric item D7): there is no
first-class test->category label, so the design's claim that run_<category>
booleans can never disagree with the matrix is not achievable as written --
deriving the boolean still re-matches test paths against category triggerPaths
globs. The rubric recommends a category label on the edge target (relationship
in data); the plan resolves it via a selected-set membership check in the
engine (relationship in code). To be decided in review.

Design-only: no live CI behavior changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

CLI E2E Tests unknown — 113 passed, 0 failed, 2 unknown (commit 0d83f65)

View all recordings
- Test Detail
AddPackageInteractiveWhileAppHostRunningDetached Recording · Job · CLI logs
AddPackageWhileAppHostRunningDetached Recording · Job · CLI logs
AgentCommands_AllHelpOutputs_AreCorrect Recording · Job · CLI logs
AgentInitCommand_DefaultSelection_InstallsDefaultSkills Recording · Job · CLI logs
AgentInitCommand_MigratesDeprecatedConfig Recording · Job · CLI logs
AgentInit_NonInteractive_BundleOnlySkillsNotInCatalog Recording · Job · CLI logs
AgentMcpListStructuredLogsReturnsLogsFromStarterApp Recording · Job · CLI logs
AgentMcpListStructuredLogsReturnsLogsFromStarterApp_DevLocalhost Recording · Job · CLI logs
AgentMcpListStructuredLogsReturnsLogsFromStarterApp_Isolated Recording · Job · CLI logs
AllPublishMethodsBuildDockerImages Recording · Job · CLI logs
AspireAddAndStartWorkAgainstLegacyAppHostTs Recording · Job · CLI logs
AspireAddPackageVersionToDirectoryPackagesProps Recording · Job · CLI logs
AspireInitSingleFileAppHostRunsViaDotnetRunAppHost Recording · Job · CLI logs
AspireInit_ExistingAppHostDir_RecreatesNuGetConfigKeepsFiles Recording · Job · CLI logs
AspireInit_SolutionFile_BuildsAgainstChannelHive Recording · Job · CLI logs
AspireStartUpdatesStaleTypeScriptAppHostPath Recording · Job · CLI logs
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps Recording · Job · CLI logs
AspireUpdateRemovesOrphanAppHostPackageVersionWhenSdkAlreadyCurrent Recording · Job · CLI logs
Banner_DisplayedOnFirstRun Recording · Job · CLI logs
Banner_DisplayedWithExplicitFlag Recording · Job · CLI logs
Banner_NotDisplayedWithNoLogoFlag Recording · Job · CLI logs
CertificatesClean_RemovesCertificates Recording · Job · CLI logs
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate Recording · Job · CLI logs
CertificatesTrust_WithUntrustedCert_TrustsCertificate Recording · Job · CLI logs
ConfigSetGet_CreatesNestedJsonFormat Recording · Job · CLI logs
CreateAndRunAspireStarterProject Recording · Job · CLI logs
CreateAndRunAspireStarterProjectWithBundle Recording · Job · CLI logs
CreateAndRunEmptyAppHostProject Recording · Job · CLI logs
CreateAndRunJavaEmptyAppHostProject Recording · Job · CLI logs
CreateAndRunJsReactProject Recording · Job · CLI logs
CreateAndRunPolyglotAppHostWithDevLocalhostUrls Recording · Job · CLI logs
CreateAndRunPythonReactProject Recording · Job · CLI logs
CreateAndRunTypeScriptEmptyAppHostProject Recording · Job · CLI logs
CreateAndRunTypeScriptStarterProject Recording · Job · CLI logs
CreateJavaAppHostWithViteApp Recording · Job · CLI logs
CreateTypeScriptAppHostWithViteApp_UsesConfiguredToolchain Recording · Job · CLI logs
DashboardRunWithAgentMcpListTracesReturnsNoTraces Recording · Job · CLI logs
DashboardRunWithAgentMcpListTracesReturnsNoTraces_DevLocalhost Recording · Job · CLI logs
DashboardRunWithOtelTracesReturnsNoTraces Recording · Job · CLI logs
DashboardRunWithOtelTracesReturnsNoTraces_DevLocalhost Recording · Job · CLI logs
DeployK8sBasicApiService Recording · Job · CLI logs
DeployK8sWithExternalHelmChart Recording · Job · CLI logs
DeployK8sWithGarnet Recording · Job · CLI logs
DeployK8sWithMongoDB Recording · Job · CLI logs
DeployK8sWithMySql Recording · Job · CLI logs
DeployK8sWithPostgres Recording · Job · CLI logs
DeployK8sWithRabbitMQ Recording · Job · CLI logs
DeployK8sWithRedis Recording · Job · CLI logs
DeployK8sWithSqlServer Recording · Job · CLI logs
DeployK8sWithValkey Recording · Job · CLI logs
DeployTypeScriptAppToKubernetes Recording · Job · CLI logs
DescribeCommandResolvesReplicaNames Recording · Job · CLI logs
DescribeCommandShowsRunningResources Recording · Job · CLI logs
DetachFormatJsonProducesValidJson Recording · Job · CLI logs
DetachFormatJsonProducesValidJsonWhenRestartingExistingInstance Recording · Job · CLI logs
DoPublishAndDeployListStepsWork Recording · Job · CLI logs
DocsCommand_RendersInteractiveMarkdownFromLocalSource Recording · Job · CLI logs
DoctorCommand_DetectsDeprecatedAgentConfig Recording · Job · CLI logs
DoctorCommand_TypeScriptAppHostReportsMissingConfiguredToolchain Recording · Job · CLI logs
DoctorCommand_WithSslCertDir_ShowsTrusted Recording · Job · CLI logs
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted Recording · Job · CLI logs
DotNetRunFileBasedAppHostUsesAspireCliBundle Recording · Job · CLI logs
DotNetRunProjectAppHostUsesAspireCliBundle Recording · Job · CLI logs
GatewayWithoutExternalEndpoint_FailsPublishWithGuidance Recording · Job · CLI logs
GeneratedAspireDevScript_StartsWatchMode_WithConfiguredToolchain Recording · Job · CLI logs
GlobalMigration_HandlesCommentsAndTrailingCommas Recording · Job · CLI logs
GlobalMigration_HandlesMalformedLegacyJson Recording · Job · CLI logs
GlobalMigration_PreservesAllValueTypes Recording · Job · CLI logs
GlobalMigration_SkipsWhenNewConfigExists Recording · Job · CLI logs
GlobalSettings_MigratedFromLegacyFormat Recording · Job · CLI logs
IngressWithoutExternalEndpoint_FailsPublishWithGuidance Recording · Job · CLI logs
InitTypeScriptAppHost_AugmentsExistingViteRepoInWorkspaceSubdirectory Recording · Job · CLI logs
InteractiveCSharpInitCreatesExpectedFiles Recording · Job · CLI logs
InvalidAppHostPathWithComments_IsHealedOnRun Recording · Job · CLI logs
JavaScriptHostingApisRunFromTypeScriptAppHost Recording · Job · CLI logs
LatestCliCanStartStableChannelAppHost Recording · Job · CLI logs
LatestCliCanStartStableChannelTypeScriptAppHost Recording · Job · CLI logs
LegacySettingsMigration_AdjustsRelativeAppHostPath Recording · Job · CLI logs
LogsCommandShowsResourceLogs Recording · Job · CLI logs
OtelLogsReturnsStructuredLogsFromStarterApp Recording · Job · CLI logs
OtelLogsReturnsStructuredLogsFromStarterAppIsolated Recording · Job · CLI logs
ProcessCommandCallbackReceivesCliArguments Recording · Job · CLI logs
PsCommandListsRunningAppHost Recording · Job · CLI logs
PsFormatJsonOutputsOnlyJsonToStdout Recording · Job · CLI logs
PublishJavaScriptPatternsGeneratesExpectedDockerComposeArtifacts Recording · Job · CLI logs
PublishWithConfigureEnvFileUpdatesEnvOutput Recording · Job · CLI logs
PublishWithDockerComposeServiceCallbackSucceeds Recording · Job · CLI logs
PublishWithoutOutputPathUsesAppHostDirectoryDefault Recording · Job · CLI logs
ResourceCommand_FailedExec_ShowsLogPathAndLogHasEntries Recording · Job · CLI logs
ResourceCommand_SetAndDeleteParameterUpdatesDescribeOutput Recording · Job · CLI logs
RestoreGeneratesSdkFiles Recording · Job · CLI logs
RestoreGeneratesSdkFiles_WithConfiguredToolchain Recording · Job · CLI logs
RestoreRefreshesGeneratedSdkAfterAddingIntegration Recording · Job · CLI logs
RestoreSupportsConfigOnlyHelperPackageAndCrossPackageTypes Recording · Job · CLI logs
RunFromParentDirectory_UsesExistingConfigNearAppHost Recording · Job · CLI logs
RunReportsSyntaxErrorsForDotNetAppHost Recording · Job · CLI logs
RunReportsSyntaxErrorsForTypeScriptAppHost Recording · Job · CLI logs
SecretCrudOnDotNetAppHost Recording · Job · CLI logs
SecretCrudOnTypeScriptAppHost Recording · Job · CLI logs
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels Recording · Job · CLI logs
StartAndWaitForTypeScriptSqlServerAppHostWithNativeAssets Recording · Job · CLI logs
StartReportsSyntaxErrorsForDotNetAppHost Recording · Job · CLI logs
StartReportsSyntaxErrorsForTypeScriptAppHost Recording · Job · CLI logs
StopAllAppHostsFromAppHostDirectory Recording · Job · CLI logs
StopJavaPolyglotAppHostUsingApphostDirectory Recording · Job · CLI logs
StopNonInteractiveSingleAppHost Recording · Job · CLI logs
StopTypeScriptPolyglotAppHostUsingApphostDirectory Recording · Job · CLI logs
StopWithNoRunningAppHostExitsSuccessfully Recording · Job · CLI logs
TypeScriptAppHostRunDoesNotDeadlockWhenLazyOptionsInvokeAsyncCallback Recording · Job · CLI logs
TypeScriptAppHostWithVite_AllowsDifferentGuestPkgManager Recording · Job · CLI logs
UnAwaitedChainsCompileWithAutoResolvePromises Recording · Job · CLI logs
UpdateToStable_CSharpEmptyAppHost_KeepsConfigChannel Recording · Job · CLI logs
UpdateToStable_CSharpSingleFileInit_KeepsConfigChannel Recording · Job · CLI logs
UpdateToStable_TypeScriptSingleFileInit_KeepsConfigChannel Recording · Job · CLI logs
UpdateToStable_TypeScript_PreviewsStablePkgsAndKeepsChannel Recording · Job · CLI logs

📹 Recordings uploaded automatically from CI run #27181539476

@radical radical mentioned this pull request Jun 9, 2026
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-extension NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) no-review Skip for reviews

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants