Skip to content

Convert dotnet-format integration tests from pipeline YAML to MSTest/Helix#55399

Open
MichaelSimons wants to merge 7 commits into
mainfrom
format-timings
Open

Convert dotnet-format integration tests from pipeline YAML to MSTest/Helix#55399
MichaelSimons wants to merge 7 commits into
mainfrom
format-timings

Conversation

@MichaelSimons

@MichaelSimons MichaelSimons commented Jul 21, 2026

Copy link
Copy Markdown
Member

Summary

Converts the dotnet-format integration test pipeline legs (YAML + PowerShell) into MSTest integration tests that run on Helix, eliminating duplicate SDK builds in PR and CI validation. It also narrows the repo coverage to the highest-ROI set (SDK, MSBuild, and ProjectSystem), dropping Roslyn, EfCore, and AspNetCore to reduce compute.

Compute Savings

Measured from build 1519902 (main, 2026-07-21):

Each old leg builds its own full SDK copy before running format. The new approach eliminates this by running as Helix work items on the existing Linux x64 build leg.

Leg Old Leg Duration SDK Build Overhead Format Work After this PR
sdk 20m 10s ~14m ~6m ? Kept (saves ~14m build)
Roslyn 31m ~13m ~18m ? Dropped (restore-intensive, low ROI)
msbuild 18m 23s ~11m ~7m ? Kept (saves ~11m build)
project-system 13m 54s ~10m ~4m ? Kept (saves ~10m build)
efcore 17m 16s ~10m ~7m ? Dropped (restore-intensive, low ROI)
aspnetcore 26m 39s ~16m ~11m ? Dropped (restore-intensive, low ROI)
Scenario Total agent-minutes saved
CI builds ~110m (redundant SDK builds eliminated + 3 repos dropped; ~17m format work remains on existing leg)
PR builds (format-related changes) ~110m (same as CI)
PR builds (unrelated changes) ~127m (all legs skipped via conditional test filtering)

What changed

  • New test project: test/dotnet-format.IntegrationTests/ using MSTest.Sdk with one test class per repo (SDK, MSBuild, ProjectSystem). Inherits from the MSTest SdkTest base class for standard test infrastructure (logging, binlog arguments).
  • Base class pattern: FormatIntegrationTestBase handles clone, Arcade restore, global.json manipulation, solution restore, and format execution with stdout validation. Adding or removing a repo requires only adding or deleting a ~15-line class.
  • Dynamic Helix sharding: PartitionByClass=true metadata causes AssemblyScheduler to discover test classes at scheduling time and create one Helix work item per class automatically.
  • Conditional test filtering: Uses the ConditionalTests.props DotnetFormat scope with trigger paths. Tests run in PR validation only when relevant files change; they always run in CI.
  • Platform targeting: Tests run only on Linux x64 (enforced at the UnitTests.proj scheduling level, not in test code).
  • Removed old infrastructure: Deleted eng/dotnet-format/ (YAML template, PowerShell verifier, CMD wrapper, RSP file) and removed pipeline references from .vsts-ci.yml / .vsts-pr.yml.

Design

Each repo is a separate test class inheriting from FormatIntegrationTestBase:

[TestClass]
public class SdkFormatTests : FormatIntegrationTestBase
{
    protected override string RepoUrl => "https://github.com/dotnet/sdk";
    protected override string Sha => "e6bc966cc3d1348265b0831c6daca23267169d8f";
    protected override string TargetSolution => "sdk.slnx";
    protected override string RepoName => "sdk";
}

This one-class-per-repo pattern enables:

  1. Helix sharding - each class becomes its own work item (parallel execution)
  2. Zero maintenance - adding or removing a repo only requires adding or deleting a class file; sharding adapts automatically

Test lifecycle (mirrors the old format-verifier.ps1)

  1. Clone - shallow single-commit fetch of the target SHA
  2. Arcade restore - runs eng/Build.ps1 -restore (or eng/build.sh --restore) while global.json is intact to install Arcade SDK tooling
  3. Strip global.json sdk section - preserves msbuild-sdks for NuGet SDK resolver but forces the SDK under test to be used
  4. Solution restore - restores with the parent SDK's dotnet (NuGet auto-discovers nuget.config via directory walking)
  5. Format workspace - dotnet format <solution> --no-restore --verify-no-changes --verbosity detailed
  6. Format folder - dotnet format whitespace <path> --folder --verify-no-changes --verbosity detailed
  7. Stdout validation - verifies "Formatted X of Y files" appears with Y > 0 (catches silent no-op exits)

Repos dropped

  • AspNetCore, EfCore and Roslyn: These repos are resource intensive and have limited ROI. It is recommended that if there are real coverage lost, a follow up issue logged to defined more targeted tests that are less resource intense.

Open questions

Self-format validation removed (validate.rsp)

The old pipeline ran dotnet format --verify-no-changes against src/Dotnet.Format/dotnet-format.slnf to ensure the format source code itself stayed properly formatted. This PR removes that check because maintaining a dedicated pipeline leg or job solely for one project's formatting is disproportionate to the value it provides.

Options going forward:

  1. Add a format check to an existing build leg - run dotnet format --verify-no-changes once (e.g., on the Linux x64 leg) as a build step.
  2. Drop it entirely - the repo doesn't enforce .editorconfig formatting consistently across all source today, so special-casing one project provides limited value. If format enforcement is desired, it should be applied repo-wide rather than to a single project.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
2 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

</ConditionalTestScope>

<ConditionalTestScope Include="TemplateEngine">
<ConditionalTestScope Include="DotnetFormat">

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This diff of this file may be confusing. I added the DotnetFormat scope as well re-ordered the existing TemplateEngine scope to be in alphabetical order.

@MichaelSimons
MichaelSimons force-pushed the format-timings branch 4 times, most recently from 365b89a to f80991a Compare July 23, 2026 19:25
…Helix

Converts the dotnet-format integration test pipeline legs (YAML + PowerShell)
into MSTest integration tests that run on Helix. This eliminates redundant SDK
builds and reduces PR validation cost by ~92-127 agent-minutes per run.

- New test project: test/dotnet-format.IntegrationTests/ using MSTest.Sdk
- One test class per repo (SDK, Roslyn, MSBuild, ProjectSystem)
- Dynamic Helix sharding via PartitionByClass metadata
- Conditional test filtering via ConditionalTests.props DotnetFormat scope
- Tests run only on Linux x64 (enforced at scheduling level)
- Removed eng/dotnet-format/ (YAML template, PS1 verifier, CMD, RSP)
- Fixed HelixTasks loop variable mutation bug in CreateHelixTestWorkItems.cs

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 14d64044-6feb-4ee6-a053-da4879c22885
@MichaelSimons
MichaelSimons marked this pull request as ready for review July 23, 2026 20:58
@MichaelSimons
MichaelSimons requested a review from MiYanni as a code owner July 23, 2026 20:58
Copilot AI review requested due to automatic review settings July 23, 2026 20:58
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
2 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR migrates the dotnet-format integration coverage from dedicated Azure Pipelines YAML legs (with custom scripts) into MSTest-based Helix work items, aiming to eliminate redundant SDK builds and reduce CI/PR compute while keeping higher-ROI repo coverage.

Changes:

  • Adds a new test/dotnet-format.IntegrationTests MSTest project that clones/restores target repos and runs dotnet format verification.
  • Updates Helix scheduling/conditional filtering to run these tests as one work item per test class (Linux x64 only; CI always, PRs only when relevant files change).
  • Removes the legacy dotnet-format integration pipeline/template/scripts and pipeline references.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/UnitTests.proj Schedules the new dotnet-format integration tests on Helix with PartitionByClass=true (Linux x64 only).
test/dotnet-format.IntegrationTests/dotnet-format.IntegrationTests.csproj New MSTest integration test project definition.
test/dotnet-format.IntegrationTests/FormatIntegrationTestBase.cs Implements clone/restore + dotnet format execution/validation shared by all repo-specific test classes.
test/dotnet-format.IntegrationTests/SdkFormatTests.cs Adds SDK-specific repo/solution/SHA configuration.
test/dotnet-format.IntegrationTests/MsBuildFormatTests.cs Adds MSBuild-specific repo/solution/SHA configuration.
test/dotnet-format.IntegrationTests/ProjectSystemFormatTests.cs Adds ProjectSystem-specific repo/solution/SHA configuration.
test/ConditionalTests.props Introduces a DotnetFormat conditional scope to run format tests only when relevant paths change in PRs (always in CI).
src/Dotnet.Format/dotnet-format.slnf Includes the new integration tests project in the dotnet-format solution filter.
sdk.slnx Adds the new integration test project to the solution.
eng/dotnet-format/validate.rsp Removes legacy self-format validation response file.
eng/dotnet-format/integration-test.cmd Removes legacy Windows wrapper for the format verifier script.
eng/dotnet-format/format-verifier.ps1 Removes legacy PowerShell-based integration test runner.
eng/dotnet-format/dotnet-format-integration.yml Removes legacy pipeline template that ran format integration legs.
.vsts-pr.yml Removes the dotnet-format integration pipeline leg reference from PR pipeline.
.vsts-ci.yml Removes the dotnet-format integration pipeline leg reference from CI pipeline.

Comment thread test/dotnet-format.IntegrationTests/FormatIntegrationTestBase.cs
Comment thread test/dotnet-format.IntegrationTests/FormatIntegrationTestBase.cs
Comment thread test/dotnet-format.IntegrationTests/FormatIntegrationTestBase.cs
Include integration tests via wildcard and update Area-Format owner.

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

Copilot-Session: 9f1c9459-3ee4-427f-b251-93116f51a91f
- Cache initialized repo paths in a static Dictionary<Type, string> so
  clone+Arcade restore+solution restore runs exactly once per concrete
  class per process. MSTest creates a new instance per test method, so
  without this setup ran twice per Helix work item.
- FindSolution: when multiple matches exist, select the root-most path
  (MinBy path length) rather than relying on filesystem ordering.

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.

Comment thread test/ConditionalTests.props
Comment thread test/dotnet-format.IntegrationTests/FormatIntegrationTestBase.cs
Comment thread test/dotnet-format.IntegrationTests/FormatIntegrationTestBase.cs Outdated
Add src/Cli/dotnet/Commands/Format/** to the DotnetFormat conditional test scope so changes to the dotnet format command itself run the integration tests.

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

Copilot-Session: 9f1c9459-3ee4-427f-b251-93116f51a91f
Route the dotnet-format integration test clone path through TestAssetsManager.CreateTestDirectory with both the test type and repo name.

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

Copilot-Session: 9f1c9459-3ee4-427f-b251-93116f51a91f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants