From 4f347d753be79b80662365f83ecd7f86e1b246a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 4 Sep 2026 17:59:58 +0000 Subject: [PATCH 1/3] Skip CI entirely for API/ATS baseline-only PRs Co-authored-by: radical <1472+radical@users.noreply.github.com> --- docs/ci/test-trigger-map.md | 14 ++--- eng/github-ci/ci-skip-entirely-patterns.txt | 4 ++ eng/github-ci/test-trigger-map.yml | 23 ++++---- .../SelectTestsAcceptanceTests.cs | 52 +++++++++++++------ 4 files changed, 59 insertions(+), 34 deletions(-) diff --git a/docs/ci/test-trigger-map.md b/docs/ci/test-trigger-map.md index 0c42187640f..98cc23e4955 100644 --- a/docs/ci/test-trigger-map.md +++ b/docs/ci/test-trigger-map.md @@ -187,14 +187,14 @@ Highlights: `src/Aspire.Hosting.Integration.Analyzers/**` → `test:Aspire.Hosting.Analyzers.Tests`. - **non-.NET job loose triggers** — only the paths the project graph cannot - attribute, such as `tests/PolyglotAppHosts/**`, checked-in `*.ats.txt` / + attribute, such as `tests/PolyglotAppHosts/**`, checked-in `*.tscompat.suppression.txt` baselines, `tools/TypeScriptApiCompat/**`, and - `extension/**`. A `src/Aspire.Hosting*/api/*.ats.txt` baseline fans out to - **both** `job:typescript-api-compat` (baseline diff) and `job:polyglot`, - because the polyglot playground regenerates and compiles that exported surface - in every language. -- **loose-file deps** — `eng/clipack/**`, `eng/dashboardpack/**`, - `eng/dcppack/**`, `eng/winget/**`, `eng/homebrew/**`, + `extension/**`. Checked-in `*.ats.txt` baselines under `src/**/api/**` are + generated artifacts dropped by the prefilter (see below) when they are the + only changed files, so no dedicated rule routes them here. Hosting integration + source changes additionally route to `job:polyglot` because those exports are + consumed by the per-language AppHost fixtures. +- **loose-file deps** — `eng/clipack/**`, `eng/winget/**`, `eng/homebrew/**`, `src/Aspire.ProjectTemplates/**`, `playground/**`, `.github/workflows/**`, `eng/Bundle.proj`, and `tools/CreateLayout/**`. Runtime consumers are additive: CLI and extension E2E execute the CLI archive and its Dashboard/DCP diff --git a/eng/github-ci/ci-skip-entirely-patterns.txt b/eng/github-ci/ci-skip-entirely-patterns.txt index 62d4f73d650..43f4f819449 100644 --- a/eng/github-ci/ci-skip-entirely-patterns.txt +++ b/eng/github-ci/ci-skip-entirely-patterns.txt @@ -34,6 +34,10 @@ eng/github-ci/ci-skip-entirely-patterns.txt # Documentation **.md +# Generated API and ATS baselines +# These files are generated release/API-surface artifacts and do not require PR CI. +src/**/api/** + # Engineering pipeline scripts (Azure DevOps, not used in the GitHub CI build) eng/pipelines/** eng/test-configuration.json diff --git a/eng/github-ci/test-trigger-map.yml b/eng/github-ci/test-trigger-map.yml index f2505394cea..93706e96d31 100644 --- a/eng/github-ci/test-trigger-map.yml +++ b/eng/github-ci/test-trigger-map.yml @@ -199,17 +199,18 @@ path_rules: - .github/workflows/typescript-api-compat.yml targets: [job:typescript-api-compat] reason: checked-in suppression baselines are not compiled items (Layer 1 blind); Hosting/Cli production projects are in affected_project_rules - # A checked-in *.ats.txt baseline changes EXACTLY when an integration's [AspireExport] surface - # changes -- the same surface the per-language polyglot playground scripts regenerate and compile - # (aspire restore --apphost over tests/PolyglotAppHosts//). So an exported-surface - # change must run BOTH typescript-api-compat (baseline diff) AND polyglot (regenerate+compile in every - # language), even when the author has not also touched the tests/PolyglotAppHosts fixtures. Layer 1 can - # attribute the baseline by project-directory containment, but this explicit rule preserves both jobs - # when Layer 1 is skipped and records the runtime polyglot dependency. - - paths: - - src/Aspire.Hosting*/api/*.ats.txt - targets: [job:typescript-api-compat, job:polyglot] - reason: an integration's *.ats.txt baseline tracks its exported ATS surface; the polyglot playground regenerates+compiles that surface per language, so a baseline change must run polyglot too + # Checked-in API/ATS baselines under src/*/api/ (both *.cs and *.ats.txt) are generated + # release artifacts. They are dropped by the prefilter (ci-skip-entirely-patterns.txt) when + # they are the only changed files, so a baseline-only PR skips CI entirely. When an *.ats.txt + # baseline changes alongside real source (e.g. the integration whose [AspireExport] surface it + # tracks), the source change is not blind to Layer 1/the project rules above and routes normally + # -- no dedicated rule is needed here for the mixed-change case. + # Integration source changes also run polyglot because those exports are consumed by the + # per-language AppHost fixtures, while the prefilter keeps baseline-only changes out of CI. + - paths: + - src/Aspire.Hosting*/** + targets: [job:polyglot] + reason: hosting integration source feeds generated SDKs and polyglot AppHost validation - paths: - extension/** - .vscode/launch.json diff --git a/tests/Infrastructure.Tests/TestTriggerMap/SelectTestsAcceptanceTests.cs b/tests/Infrastructure.Tests/TestTriggerMap/SelectTestsAcceptanceTests.cs index b5cd8823bda..115ce0f1cdc 100644 --- a/tests/Infrastructure.Tests/TestTriggerMap/SelectTestsAcceptanceTests.cs +++ b/tests/Infrastructure.Tests/TestTriggerMap/SelectTestsAcceptanceTests.cs @@ -828,6 +828,14 @@ public void PrefilterReadsPatternsFileAndHonorsKeepRouted() Assert.True(filter.IsExcluded(".vscode/settings.json")); Assert.True(filter.IsExcluded("pyrightconfig.json")); + // Generated API/ATS baselines under src/*/api/: no PR CI is needed when these are the only + // changed files. + Assert.True(filter.IsExcluded("src/Aspire.Hosting/api/Aspire.Hosting.cs")); + Assert.True(filter.IsExcluded("src/Aspire.Hosting/api/Aspire.Hosting.ats.txt")); + Assert.True(filter.IsExcluded("src/Aspire.Hosting.Redis/api/Aspire.Hosting.Redis.cs")); + Assert.True(filter.IsExcluded("src/Components/Aspire.Azure.AI.Inference/api/Aspire.Azure.AI.Inference.cs")); + Assert.False(filter.IsExcluded("src/Aspire.Hosting.Redis/Aspire.Hosting.Redis.tscompat.suppression.txt")); + // Safety carve-outs: NOT dropped, because they can change build/test outcomes -- nested .gitignore // files are shipped CLI-template assets (Layer 1 / conventions route them to their projects), root // launch/task files are validated by extension tests, root .gitignore controls discovery of the @@ -841,6 +849,7 @@ public void PrefilterReadsPatternsFileAndHonorsKeepRouted() // Kept: real source the patterns file does not list. Assert.False(filter.IsExcluded("src/Aspire.Cli/Program.cs")); + Assert.False(filter.IsExcluded("src/Aspire.Hosting/HostingExtensions.cs")); // keep_routed carve-outs: listed by the patterns file but routed by the selector -> NOT dropped. Assert.False(filter.IsExcluded(".github/workflows/backport.yml")); @@ -1037,29 +1046,40 @@ public void RealMapTestSupportProjectDoesNotTriggerProductionJobs() Assert.Empty(result.Jobs); } - // An integration's checked-in *.ats.txt baseline tracks its exported [AspireExport] surface -- the - // same surface the per-language polyglot playground scripts regenerate and compile - // (aspire restore --apphost over tests/PolyglotAppHosts//). A change to that - // baseline must therefore run BOTH typescript-api-compat (baseline diff) AND polyglot (regenerate + - // compile in every language), so a breaking surface change is caught even if the author did not also - // touch the tests/PolyglotAppHosts fixtures. Run with --skip-layer1 semantics (no Layer 1 affected - // set) to prove the curated layer independently preserves both targets. + // A checked-in API/ATS baseline under src/*/api/ is a generated release artifact: the top-level + // prefilter drops it (ci-skip-entirely-patterns.txt) before either layer runs, so a baseline-ONLY + // change selects nothing. When an integration's real source ALSO changes in the same PR, the + // baseline is still dropped by the prefilter, but the source file reaches Layer 1/the selector + // untouched and is selected via the normal project/path rules. This proves the mixed-change case + // still routes CI even though the dedicated *.ats.txt path rule was removed. Filters the changed + // files through ChangedFileFilter first, mirroring how Program.cs feeds Select (see RunCore). [Fact] - public void RealMapIntegrationAtsBaselineChangeRunsTypeScriptApiCompatAndPolyglot() + public void RealMapMixedAtsBaselineAndSourceChangeStillRoutesSourceNormally() { var mapPath = Path.Combine(RepoRoot.Path, "eng", "github-ci", "test-trigger-map.yml"); var matrix = EnumerateMatrixTestProjects(); var selector = new TestSelector(mapPath, matrix, LoadProjectDirectories(), EnumerateAllTestProjects()); + var map = TriggerMap.Load(mapPath); + var filter = ChangedFileFilter.Create(RepoRoot.Path, map.Prefilter); var atsBaseline = FirstIntegrationAtsBaselineWithPolyglotFixture(); - - var r = selector.Select([atsBaseline], [], new SelectorOptions()); - - Assert.False(r.SelectsAll); - Assert.Contains("job:typescript-api-compat", r.Jobs); - Assert.Contains("job:polyglot", r.Jobs); - Assert.Contains(r.JobCauses["job:typescript-api-compat"], cause => cause.Kind == CauseKind.PathRule); - Assert.Contains(r.JobCauses["job:polyglot"], cause => cause.Kind == CauseKind.PathRule); + Assert.True(filter.IsExcluded(atsBaseline)); + + // Baseline-only: the prefilter drops the file before Select ever sees it -- selects nothing. + var baselineOnly = selector.Select([], [], new SelectorOptions()); + Assert.False(baselineOnly.SelectsAll); + Assert.Empty(baselineOnly.TestProjects); + Assert.Empty(baselineOnly.Jobs); + + // Mixed: the baseline is still dropped by the prefilter, but the integration's own source file + // (Layer 1-owned, not a prefilter match) still routes normally through Layer 1 / project rules. + var integrationName = atsBaseline.Split('/')[1]; + var sourceFile = $"src/{integrationName}/{integrationName}.csproj"; + Assert.False(filter.IsExcluded(sourceFile)); + var mixed = selector.Select([sourceFile], [integrationName], new SelectorOptions()); + Assert.False(mixed.SelectsAll); + Assert.NotEmpty(mixed.TestProjects.Union(mixed.Jobs)); + Assert.Contains("job:polyglot", mixed.Jobs); } [Fact] From 3aaadd083f6c7aa631eaf29f7e1b345af1de9096 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Fri, 4 Sep 2026 14:57:30 -0400 Subject: [PATCH 2/3] Fix API baseline and polyglot routing gaps Keep real template sources and suppression baselines in CI while skipping generated API artifacts. Route hosting projects with polyglot fixtures through the job without over-matching unrelated projects. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8c5beb4c-0641-4918-8b29-d53c83727a78 --- docs/ci/test-trigger-map.md | 9 +-- eng/github-ci/ci-skip-entirely-patterns.txt | 5 +- eng/github-ci/test-trigger-map.yml | 59 ++++++++++++++++--- .../SelectTestsAcceptanceTests.cs | 1 + 4 files changed, 62 insertions(+), 12 deletions(-) diff --git a/docs/ci/test-trigger-map.md b/docs/ci/test-trigger-map.md index 98cc23e4955..15a8ae65784 100644 --- a/docs/ci/test-trigger-map.md +++ b/docs/ci/test-trigger-map.md @@ -189,12 +189,13 @@ Highlights: - **non-.NET job loose triggers** — only the paths the project graph cannot attribute, such as `tests/PolyglotAppHosts/**`, checked-in `*.tscompat.suppression.txt` baselines, `tools/TypeScriptApiCompat/**`, and - `extension/**`. Checked-in `*.ats.txt` baselines under `src/**/api/**` are + `extension/**`. Checked-in `*.ats.txt` baselines under the project `api/` + directories are generated artifacts dropped by the prefilter (see below) when they are the only changed files, so no dedicated rule routes them here. Hosting integration - source changes additionally route to `job:polyglot` because those exports are - consumed by the per-language AppHost fixtures. -- **loose-file deps** — `eng/clipack/**`, `eng/winget/**`, `eng/homebrew/**`, + projects with polyglot fixtures are listed in `affected_project_rules`. +- **loose-file deps** — `eng/clipack/**`, `eng/dashboardpack/**`, + `eng/dcppack/**`, `eng/winget/**`, `eng/homebrew/**`, `src/Aspire.ProjectTemplates/**`, `playground/**`, `.github/workflows/**`, `eng/Bundle.proj`, and `tools/CreateLayout/**`. Runtime consumers are additive: CLI and extension E2E execute the CLI archive and its Dashboard/DCP diff --git a/eng/github-ci/ci-skip-entirely-patterns.txt b/eng/github-ci/ci-skip-entirely-patterns.txt index 43f4f819449..9282e3fa070 100644 --- a/eng/github-ci/ci-skip-entirely-patterns.txt +++ b/eng/github-ci/ci-skip-entirely-patterns.txt @@ -36,7 +36,10 @@ eng/github-ci/ci-skip-entirely-patterns.txt # Generated API and ATS baselines # These files are generated release/API-surface artifacts and do not require PR CI. -src/**/api/** +src/*/api/*.cs +src/*/api/*.ats.txt +src/Components/*/api/*.cs +src/Components/*/api/*.ats.txt # Engineering pipeline scripts (Azure DevOps, not used in the GitHub CI build) eng/pipelines/** diff --git a/eng/github-ci/test-trigger-map.yml b/eng/github-ci/test-trigger-map.yml index 93706e96d31..8743beeaf0e 100644 --- a/eng/github-ci/test-trigger-map.yml +++ b/eng/github-ci/test-trigger-map.yml @@ -205,12 +205,6 @@ path_rules: # baseline changes alongside real source (e.g. the integration whose [AspireExport] surface it # tracks), the source change is not blind to Layer 1/the project rules above and routes normally # -- no dedicated rule is needed here for the mixed-change case. - # Integration source changes also run polyglot because those exports are consumed by the - # per-language AppHost fixtures, while the prefilter keeps baseline-only changes out of CI. - - paths: - - src/Aspire.Hosting*/** - targets: [job:polyglot] - reason: hosting integration source feeds generated SDKs and polyglot AppHost validation - paths: - extension/** - .vscode/launch.json @@ -443,8 +437,59 @@ affected_project_rules: - Aspire.Hosting.JavaScript - Aspire.Hosting.Python - Aspire.Hosting.Rust + - Aspire.Hosting + - Aspire.Hosting.Azure + - Aspire.Hosting.Azure.AppConfiguration + - Aspire.Hosting.Azure.AppContainers + - Aspire.Hosting.Azure.AppService + - Aspire.Hosting.Azure.ApplicationInsights + - Aspire.Hosting.Azure.CognitiveServices + - Aspire.Hosting.Azure.ContainerRegistry + - Aspire.Hosting.Azure.CosmosDB + - Aspire.Hosting.Azure.EventHubs + - Aspire.Hosting.Azure.Functions + - Aspire.Hosting.Azure.KeyVault + - Aspire.Hosting.Azure.Kusto + - Aspire.Hosting.Azure.Network + - Aspire.Hosting.Azure.OperationalInsights + - Aspire.Hosting.Azure.PostgreSQL + - Aspire.Hosting.Azure.Redis + - Aspire.Hosting.Azure.Sandboxes + - Aspire.Hosting.Azure.Search + - Aspire.Hosting.Azure.ServiceBus + - Aspire.Hosting.Azure.SignalR + - Aspire.Hosting.Azure.Sql + - Aspire.Hosting.Azure.Storage + - Aspire.Hosting.Azure.WebPubSub + - Aspire.Hosting.Blazor + - Aspire.Hosting.DevTunnels + - Aspire.Hosting.Docker + - Aspire.Hosting.Dotnet + - Aspire.Hosting.EntityFrameworkCore + - Aspire.Hosting.Foundry + - Aspire.Hosting.Garnet + - Aspire.Hosting.GitHub.Models + - Aspire.Hosting.Kafka + - Aspire.Hosting.Keycloak + - Aspire.Hosting.Kubernetes + - Aspire.Hosting.Maui + - Aspire.Hosting.Milvus + - Aspire.Hosting.MongoDB + - Aspire.Hosting.MySql + - Aspire.Hosting.Nats + - Aspire.Hosting.OpenAI + - Aspire.Hosting.Oracle + - Aspire.Hosting.Orleans + - Aspire.Hosting.PostgreSQL + - Aspire.Hosting.Qdrant + - Aspire.Hosting.RabbitMQ + - Aspire.Hosting.Redis + - Aspire.Hosting.Seq + - Aspire.Hosting.SqlServer + - Aspire.Hosting.Valkey + - Aspire.Hosting.Yarp targets: [job:polyglot] - reason: ATS typesystem -> per-language SDK generators -> RemoteHost server + CLI run + fixtures + reason: ATS typesystem and hosting integrations feed the per-language SDK generators and AppHost fixtures - projects: [Aspire.Hosting.CodeGeneration.TypeScript] targets: [job:typescript-sdk] reason: vitest unit tests over generated TS SDK (@aspire/transport, @aspire/base) diff --git a/tests/Infrastructure.Tests/TestTriggerMap/SelectTestsAcceptanceTests.cs b/tests/Infrastructure.Tests/TestTriggerMap/SelectTestsAcceptanceTests.cs index 115ce0f1cdc..488762e9a8d 100644 --- a/tests/Infrastructure.Tests/TestTriggerMap/SelectTestsAcceptanceTests.cs +++ b/tests/Infrastructure.Tests/TestTriggerMap/SelectTestsAcceptanceTests.cs @@ -835,6 +835,7 @@ public void PrefilterReadsPatternsFileAndHonorsKeepRouted() Assert.True(filter.IsExcluded("src/Aspire.Hosting.Redis/api/Aspire.Hosting.Redis.cs")); Assert.True(filter.IsExcluded("src/Components/Aspire.Azure.AI.Inference/api/Aspire.Azure.AI.Inference.cs")); Assert.False(filter.IsExcluded("src/Aspire.Hosting.Redis/Aspire.Hosting.Redis.tscompat.suppression.txt")); + Assert.False(filter.IsExcluded("src/Aspire.Cli/Templating/Templates/java-starter/api/Program.cs")); // Safety carve-outs: NOT dropped, because they can change build/test outcomes -- nested .gitignore // files are shipped CLI-template assets (Layer 1 / conventions route them to their projects), root From 06c985d610fb9a778613f2c9b0c642dbd1cedad4 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Fri, 4 Sep 2026 16:20:13 -0400 Subject: [PATCH 3/3] test(ci): use real TypeScript suppression path Point the prefilter regression test at the actual api/*.tscompat.suppression.txt location. This catches the overbroad api/** skip pattern that would hide the TypeScript compatibility route. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8c5beb4c-0641-4918-8b29-d53c83727a78 --- .../TestTriggerMap/SelectTestsAcceptanceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Infrastructure.Tests/TestTriggerMap/SelectTestsAcceptanceTests.cs b/tests/Infrastructure.Tests/TestTriggerMap/SelectTestsAcceptanceTests.cs index 488762e9a8d..febce445651 100644 --- a/tests/Infrastructure.Tests/TestTriggerMap/SelectTestsAcceptanceTests.cs +++ b/tests/Infrastructure.Tests/TestTriggerMap/SelectTestsAcceptanceTests.cs @@ -834,7 +834,7 @@ public void PrefilterReadsPatternsFileAndHonorsKeepRouted() Assert.True(filter.IsExcluded("src/Aspire.Hosting/api/Aspire.Hosting.ats.txt")); Assert.True(filter.IsExcluded("src/Aspire.Hosting.Redis/api/Aspire.Hosting.Redis.cs")); Assert.True(filter.IsExcluded("src/Components/Aspire.Azure.AI.Inference/api/Aspire.Azure.AI.Inference.cs")); - Assert.False(filter.IsExcluded("src/Aspire.Hosting.Redis/Aspire.Hosting.Redis.tscompat.suppression.txt")); + Assert.False(filter.IsExcluded("src/Aspire.Hosting/api/Aspire.Hosting.tscompat.suppression.txt")); Assert.False(filter.IsExcluded("src/Aspire.Cli/Templating/Templates/java-starter/api/Program.cs")); // Safety carve-outs: NOT dropped, because they can change build/test outcomes -- nested .gitignore