Skip to content

Conditional test filtering framework for PR builds#55166

Draft
MichaelSimons wants to merge 5 commits into
mainfrom
michaelsimons-conditionalize-illinker-tests
Draft

Conditional test filtering framework for PR builds#55166
MichaelSimons wants to merge 5 commits into
mainfrom
michaelsimons-conditionalize-illinker-tests

Conversation

@MichaelSimons

Copy link
Copy Markdown
Member

Summary

Introduces a framework for conditionally running tests on PR builds based on what source files changed. Tests that are unrelated to the PR's changes can be skipped, reducing Helix compute consumption and improving PR validation times. All tests continue to run unconditionally on CI builds (main, release branches).

This is posted as a draft to seek feedback on the approach. The implementation is included to illustrate the direction — it is intentionally simple and small despite addressing a complex problem.

Design

See documentation/project-docs/pr-test-filtering.md for the full motivation, mechanisms, and instructions for adding new scopes.

What's included

  • Design document with motivation, mechanisms, and instructions for adding scopes
  • C# evaluation script
  • Central config file (ConditionalTests.props) with TemplateEngine as the first scope
  • MSBuild + pipeline integration
  • Agent instructions update

Introduces a framework for conditionally running tests on PR builds
based on what source files changed, reducing Helix compute consumption.

- Design document (documentation/project-docs/pr-test-filtering.md)
- C# evaluation script (scripts/EvaluateConditionalTestScopes.cs)
- Central config (test/ConditionalTests.props) with TemplateEngine scope
- MSBuild + pipeline integration
- Agent instructions update

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
var targetBranch = GetArg("--target-branch");
var buildReason = GetArg("--build-reason") ?? "";

// Locate repo root by finding test/ConditionalTests.props relative to the script or cwd.

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.

If we refactor, is this at risk of breaking? Can't you pass the repo root from the .csproj and generate a .cs file that's added to <Compile Include="..."/> That way the code always tracks the real root?

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.

Good point on the refactor fragility. Given this is used as a pipeline script the generated file feels more complex than necessary. I think it would be sufficient to pass in the path to the props file as an arg.

@nagilson nagilson 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.

Overall, I'm in favor of the proposal.

However, I want to poke on two points:

  1. How will we define TriggerPaths?
    I think this should be more than the folder containing relevant source code for that repo - e.g. dotnet-watch project should not be conditioned on just dotnet-watch but also include the ProjectReference(s) such as ..\..\Cli\Microsoft.DotNet.Cli.Definitions\Microsoft.DotNet.Cli.Definitions.csproj. Changes to PackageReference(s) such as Microsoft.Build.Locator" may also result in a break.

dotnetup already uses conditional test filtering to a degree but we've seen more breaks than expected when running all of the tests. So, there is a risk involved in disabling these tests though on average I think it's the right approach. We should try to minimize that risk.

There have been times where we caught product bugs (e.g. system command line, roslyn, and others) because tests in other areas started failing even when roslyn's tests were all passing. But we shouldn't rely on test coverage of others like that.

  1. When can we ignore 'CoditionalTests' - I think we should be able to ignore that. We can still get a lot of the time saving on codeflow or certain dev PRs... but I think for example in dotnet/dotnet flow we will still want to run all of the tests so we get full filtering.

I'd propose to have the conditional tests condition test runs out on code flow for specific repos - e.g. a change to dotnet/templating code flow shouldn't need to run the watch tests. If we are all encompassing on dependencies with 1 then maybe we can be looser on that?

- Added 'Choosing trigger paths' subsection covering dependency awareness
- Moved dependency flow RunAlways future inline with the metadata description
- Consistent with how class/method-level futures are described inline

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

Copy link
Copy Markdown
Member Author

@noahfalk, thanks for the insightful feedback.

  1. How will we define TriggerPaths?

You are correct that defining just the src path for the affected area is often not sufficient. I did not elaborate on this in the documentation but should call it out in order to help devs and AI make better decisions when defining new test scopes.

So, there is a risk involved in disabling these tests though on average I think it's the right approach. We should try to minimize that risk.

Yes this is in ways a big hammer so it must be used correctly. Having good documentation calling these gotchas is imperative.

  1. When can we ignore 'CoditionalTests'

I do see it valuable to be able to specify a RunAlways setting to be able to force tests to run on code flows. Given the vmr, it might not provide substantive value in providing fined grained support.

I updated the doc in d518adb in response to this feedback.

@MichaelSimons MichaelSimons requested a review from Evangelink July 7, 2026 19:07
@marcpopMSFT marcpopMSFT requested review from JoeRobich, sbomer and tmat July 7, 2026 19:16
Simplifies the evaluation script by requiring the caller to pass the
props file path explicitly. Removes the FindPropsFile fallback. The
pipeline YAML now passes the full path via Build.SourcesDirectory.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment on lines +36 to +37
Even these five scopes together represent only ~22% of the total 2,000-minute budget.
Meaningful overall reduction requires applying this pattern broadly across many test

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.

You're proposing starting with these five but potentially expanding it later to more? I assume if there were any more obvious buckets, they'd be here. Is the main remaining problem that we have too many really large or generic test assemblies (like dotnet-tests)?

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.

Yes, these are some initial scopes to consider mostly used to illustrate the size impact of filtering. I updated the doc to specify some initial aspirational goals which are to define scopes for 1/3 to 1/2 of the total test time. Anything beyond that is additional investment that must be weighed against the ROI.

Also note that tests do not necessarily have to be separated into separate assemblies to be filtered. The document does call out how future support could be added to condition tests at the TestClass level. The SDK currently has some classes that are rather expensive to run where splitting them into a stand alone assembly may not be the best decision.

MichaelSimons and others added 2 commits July 7, 2026 15:08
- Added one-third minimum goal for scope coverage
- Clarified 'initial candidates' framing for example scopes
- Added 'Choosing trigger paths' section covering dependency awareness
- Inlined dependency flow RunAlways future with metadata description

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Notes AI-based test selection and incremental build improvements as
aspirational alternatives, framing PR velocity as the primary driver
for the current pragmatic approach.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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.

4 participants