Skip to content

Add PR-time reminder: CLI options provider changes may need help-text expectation updates#8901

Open
Evangelink wants to merge 3 commits into
mainfrom
dev/amauryleve/cli-help-drift-guard
Open

Add PR-time reminder: CLI options provider changes may need help-text expectation updates#8901
Evangelink wants to merge 3 commits into
mainfrom
dev/amauryleve/cli-help-drift-guard

Conversation

@Evangelink
Copy link
Copy Markdown
Member

Summary

Adds a fast, non-blocking PR-time reminder: when a PR touches an ICommandLineOptionsProvider implementation but does not also touch any of the four --help / --info acceptance-test expectation files documented in .github/copilot-instructions.md, the workflow surfaces a reminder via \ and a ::notice annotation that shows up in the PR Checks tab.

Why

copilot-instructions.md (CLI options guidelines) says:

When you add a new CLI option, rename an existing one, or change the description/arguments of an existing one [...], you MUST update the corresponding --help and --info acceptance test expectations so they keep matching the actual output.

…and enumerates four expectation files:

  • test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/HelpInfoTests.cs
  • test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/HelpInfoAllExtensionsTests.cs
  • test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MSBuild.KnownExtensionRegistration.cs
  • test/IntegrationTests/MSTest.Acceptance.IntegrationTests/HelpInfoTests.cs

Today the policy is enforced only by the acceptance tests themselves, which run late in CI. By that point, fixing the expectations costs a follow-up push (and sometimes a CI re-run). This guard fires at PR creation/synchronize so the author can fold the fix into the same push.

Why non-blocking

The script always exits 0. Reasons:

  • Pure refactors of provider files commonly produce no observable change to the rendered help output.
  • The acceptance tests in CI are the authoritative gate — they fail when wildcards no longer match.
  • A hard failure here would create false positives for visibility-only changes (e.g., renaming a private method on a provider class).

The reminder surfaces as a yellow ⚠ in the Checks tab, which is hard to miss without being a blocker.

What changed

File Purpose
.github/scripts/check_cli_help_drift.py Parses git diff --name-only output (or full unified diff), classifies paths into "provider" / "expectation file", and emits the reminder when providers are touched without expectations. --diff-file flag for offline testing.
.github/workflows/cli-help-drift-reminder.yml pull_request workflow, paths-scoped to providers + expectations + the script/workflow themselves. contents: read only. Concurrency-cancelling per-PR.

Validation

Locally tested with three synthetic diffs:

  1. Provider changed, no expectations → produces the reminder + ::notice annotation, exit 0.
  2. Provider + expectation changed → reports "contract plausibly satisfied", exit 0.
  3. Neither changed → "No CLI-options provider files changed", exit 0.

The matching is name-based (CommandLineOptionsProvider*.cs, PlatformCommandLineProvider*.cs, MSTestExtension.cs) rather than path-based, so it stays robust to folder moves.

Risk

  • contents: read only — workflow cannot mutate anything.
  • Non-blocking — cannot regress merge throughput.
  • Paths-scoped — only runs on PRs that actually touch a provider or expectation file (or the guard itself).

🤖 Authored with GitHub Copilot CLI based on a one-month review-comment audit of microsoft/testfx.

When a PR touches a `*CommandLineOptionsProvider*.cs` (or
`PlatformCommandLineProvider*.cs`, or `MSTestExtension.cs`) but does not
also touch any of the four `--help` / `--info` acceptance-test
expectation files documented in copilot-instructions.md, this workflow
posts a soft reminder via:

- a markdown block written to `\`, AND
- a `::notice` workflow annotation that surfaces in the PR Checks tab.

The reminder is intentionally NON-BLOCKING (script always exits 0). Pure
refactors of provider files commonly do not change the rendered help
output, and the acceptance tests at CI time are the authoritative gate.
This guard just makes the policy visible at PR creation/synchronize so the
author can update expectations in the same push.

Files:
- .github/scripts/check_cli_help_drift.py - works with both `git diff
  --name-only` output and full unified diffs; supports `--diff-file` for
  offline testing.
- .github/workflows/cli-help-drift-reminder.yml - `pull_request`
  workflow, paths-scoped, contents:read only.

Tested locally with three synthetic diffs (provider only / provider +
expectation / neither): produces the correct reminder, satisfied message,
and skip message respectively, exiting 0 in all three cases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 7, 2026 08:30
Comment thread .github/scripts/check_cli_help_drift.py Fixed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 adds a non-blocking GitHub Actions check to surface an early reminder when a pull request modifies CLI options provider implementations without also updating the corresponding --help / --info acceptance-test expectation files, per the repo’s documented policy.

Changes:

  • Add a pull_request workflow that runs only when relevant provider/expectation paths change and emits a soft reminder.
  • Add a Python script that diffs the PR, classifies changed files (providers vs expectation files), and prints a notice + step summary when expectations weren’t touched.
Show a summary per file
File Description
.github/workflows/cli-help-drift-reminder.yml New PR-scoped workflow to run the drift reminder check.
.github/scripts/check_cli_help_drift.py New script to classify changed files and emit the reminder + annotation.

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 4

Comment on lines +20 to +22
- '**/*CommandLineOptionsProvider*.cs'
- '**/PlatformCommandLineProvider*.cs'
- '**/MSTestExtension.cs'
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 catch — fixed in 9953b0d. Workflow paths: now uses **/*CommandLineOptionsProvider.cs and **/PlatformCommandLineProvider.cs (no trailing *), so test files like *ProviderTests.cs no longer kick off the workflow.

Comment on lines +61 to +67
# File-name patterns that indicate a CLI-options provider has been touched.
# Names rather than paths so the check stays robust against folder moves.
PROVIDER_PATTERNS: Tuple[re.Pattern[str], ...] = (
re.compile(r"CommandLineOptionsProvider.*\.cs$", re.IGNORECASE),
re.compile(r"^PlatformCommandLineProvider.*\.cs$", re.IGNORECASE),
re.compile(r"^MSTestExtension\.cs$"),
)
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.

Fixed in 9953b0d. PROVIDER_PATTERNS now requires the filename to end exactly at Provider.cs: CommandLineOptionsProvider\.cs$ and ^PlatformCommandLineProvider\.cs$. Smoke-tested with both MyCommandLineOptionsProviderTests.cs and PlatformCommandLineProviderTests.cs — both are now classified as non-provider and produce no reminder.

Comment on lines +18 to +27
Exit codes:
0 — no provider files changed, OR provider files changed and at least
one help-expectation file is in the diff (i.e. the contract is
plausibly satisfied; the acceptance tests in CI are the final word).
0 — provider files changed without expectation-file changes. We still
exit 0 so the workflow stays a *reminder*, not a blocker; refactors
and visibility tweaks that don't touch any help output are common.
The reminder is surfaced via stdout + GITHUB_STEP_SUMMARY +
a workflow notice annotation produced by the calling workflow.
2 — usage / IO error.
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.

Fixed in 9953b0d. Consolidated to a single 0 — non-blocking by design entry that enumerates the three success conditions, and kept 2 — usage / IO error separate.

Comment on lines +55 to +58
- name: Run reminder
env:
BASE_SHA: origin/${{ github.event.pull_request.base.ref }}
run: python .github/scripts/check_cli_help_drift.py
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.

Fixed in 9953b0d. Renamed the workflow env var to BASE_REF (the value is a named ref like origin/main, not a SHA). The script's --base default still falls back to \$BASE_SHA for backward compatibility in case any local invocations relied on the old name.

Copy link
Copy Markdown
Member Author

@Evangelink Evangelink left a comment

Choose a reason for hiding this comment

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

Note

🤖 Automated review by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account — the account owner did not write or approve this content personally. Generated by the Expert Code Review workflow. To request a follow-up action, reply by tagging @copilot directly.

# Dimension Verdict
1 Algorithmic Correctness 🟠 1 MAJOR
20 Build Infrastructure & Dependencies 🟡 1 MODERATE
17 Documentation Accuracy ⚪ 1 NIT

✅ 14/21 dimensions clean. 6/21 N/A (Threading, Public API, Performance, Cross-TFM, IPC Wire, Localization, Resource Mgmt, Test Isolation, Assertion Quality, Flakiness, Data-Driven, Analyzer Quality, Test Completeness1).

  • Algorithmic Correctness — Six real ICommandLineOptionsProvider implementations (AzureDevOpsCommandLineProvider.cs, CrashDumpCommandLineProvider.cs, HangDumpCommandLineProvider.cs, MSBuildCommandLineProvider.cs, HtmlReportGeneratorCommandLine.cs, TrxCommandLine.cs) don't match PROVIDER_PATTERNS or the workflow paths: filter because their filenames use *CommandLineProvider.cs or *CommandLine.cs rather than *CommandLineOptionsProvider.cs. PRs touching only those files will neither trigger the workflow nor produce a reminder. See inline comments.
  • Build Infrastructureactions/checkout@v4 and actions/setup-python@v5 use mutable version tags; every other workflow in this repo uses full SHA pins. See inline comment.

1 Test Completeness: the parse_changed_paths() function has non-trivial parsing logic (dual unified-diff / name-only mode, deduplication) but no automated test coverage. The --diff-file flag enables offline testing and the PR mentions three synthetic cases were validated manually, which is pragmatic for a CI script in a .NET repo. Consider adding a tests/ neighbour with pytest parametrization if this script evolves further.

Generated by Expert Code Review (on open) for issue #8901 · sonnet46 4.1M

re.compile(r"CommandLineOptionsProvider.*\.cs$", re.IGNORECASE),
re.compile(r"^PlatformCommandLineProvider.*\.cs$", re.IGNORECASE),
re.compile(r"^MSTestExtension\.cs$"),
)
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.

[MAJOR] Algorithmic Correctness — Provider pattern coverage gap

Six real ICommandLineOptionsProvider / CommandLineOptionsProviderBase implementations in the repo have filenames that do not contain "CommandLineOptionsProvider" and will therefore never be flagged by this script:

File Naming pattern
AzureDevOpsCommandLineProvider.cs *CommandLineProvider.cs
CrashDumpCommandLineProvider.cs *CommandLineProvider.cs
HangDumpCommandLineProvider.cs *CommandLineProvider.cs
MSBuildCommandLineProvider.cs *CommandLineProvider.cs
HtmlReportGeneratorCommandLine.cs *CommandLine.cs
TrxCommandLine.cs *CommandLine.cs

Because the workflow paths: filter uses the same glob (**/*CommandLineOptionsProvider*.cs), those PRs will not even trigger the workflow. The reminder would fire for TerminalTestReporterCommandLineOptionsProvider.cs changes but stay silent for CrashDumpCommandLineProvider.cs changes — an inconsistent signal.

Recommendation: Expand the patterns to cover both naming conventions in the codebase:

PROVIDER_PATTERNS: Tuple[re.Pattern[str], ...] = (
    re.compile(r"CommandLineOptionsProvider.*\.cs$", re.IGNORECASE),
    re.compile(r"CommandLineProvider.*\.cs$", re.IGNORECASE),   # AzureDevOps, CrashDump, HangDump, MSBuild
    re.compile(r".*CommandLine\.cs$", re.IGNORECASE),            # HtmlReportGeneratorCommandLine, TrxCommandLine
    re.compile(r"^PlatformCommandLineProvider.*\.cs$", re.IGNORECASE),
    re.compile(r"^MSTestExtension\.cs$"),
)

And add matching globs to the workflow paths: filter.

# to folder moves.
- '**/*CommandLineOptionsProvider*.cs'
- '**/PlatformCommandLineProvider*.cs'
- '**/MSTestExtension.cs'
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.

[MAJOR] Algorithmic Correctness — Workflow paths: filter has the same coverage gap

The globs here mirror the PROVIDER_PATTERNS in the script, so they share the same false-negative set: PRs that only change AzureDevOpsCommandLineProvider.cs, CrashDumpCommandLineProvider.cs, HangDumpCommandLineProvider.cs, MSBuildCommandLineProvider.cs, HtmlReportGeneratorCommandLine.cs, or TrxCommandLine.cs will not trigger this workflow at all.

Recommendation: Add the missing globs alongside the script fixes:

paths:
  - '**/*CommandLineOptionsProvider*.cs'
  - '**/*CommandLineProvider*.cs'   # AzureDevOps, CrashDump, HangDump, MSBuild
  - '**/*CommandLine.cs'             # HtmlReportGeneratorCommandLine, TrxCommandLine
  - '**/PlatformCommandLineProvider*.cs'
  - '**/MSTestExtension.cs'

runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
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.

[MODERATE] Build Infrastructure — Actions not SHA-pinned

Every other workflow in this repo pins third-party actions to a specific commit SHA (e.g. actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2). Using a mutable version tag (@v4, @v5) means a tag update — even an unintentional one — changes the workflow without a diff. The repo-wide convention is SHA pins.

Recommendation: Pin both actions:

- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
  with:
    fetch-depth: 0

- uses: actions/setup-python@42375524bcc0f35d6ed4e1d8a3c3a1eb3b82b49e # v5.4.0
  with:
    python-version: '3.12'

(Verify the latest SHA via gh release view --repo actions/checkout before merging.)

Comment thread .github/scripts/check_cli_help_drift.py Outdated
exit 0 so the workflow stays a *reminder*, not a blocker; refactors
and visibility tweaks that don't touch any help output are common.
The reminder is surfaced via stdout + GITHUB_STEP_SUMMARY +
a workflow notice annotation produced by the calling workflow.
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.

[NIT] Documentation Accuracy — Docstring is misleading

Line 26 says:

a workflow notice annotation produced by the calling workflow.

But the ::notice annotation is actually printed by this script (print("::notice ...") in main()), not by the calling workflow. The workflow only runs the script — it does not produce annotations itself.

Suggestion: Change to a workflow notice annotation produced by this script.

* Tighten PROVIDER_PATTERNS so test files do not trigger false
  positives. The previous regexes were
  `CommandLineOptionsProvider.*\.cs$` and
  `^PlatformCommandLineProvider.*\.cs$`, which matched
  `MyCommandLineOptionsProviderTests.cs` and
  `PlatformCommandLineProviderTests.cs`. Patterns now require the
  filename to end exactly at `Provider.cs`.

* Tighten the workflow `paths:` filter the same way. Removed the
  trailing `*` from `*CommandLineOptionsProvider*.cs` and
  `PlatformCommandLineProvider*.cs` so unit-test edits don't kick
  off this workflow.

* Consolidate the docstring's two `0` exit-code entries into one.
  The script is intentionally non-blocking, so all success paths
  return 0; spelling them out twice as separate exit codes was
  confusing.

* Rename the env var from `BASE_SHA` to `BASE_REF`. The value is a
  named ref (`origin/main`), not a SHA, so the old name misled
  readers. The script's `--base` default still reads `$BASE_SHA`
  as a fallback for backward compatibility in case any local
  invocations relied on the old name.

* Empty except: narrow `except Exception` to
  `except (AttributeError, ValueError, OSError)` and add an
  explanatory comment. The previous bare except swallowed any
  programmer error from `sys.stdout.reconfigure`; the new tuple
  matches the documented failure modes only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread .github/scripts/check_cli_help_drift.py Fixed
The previous fix added an explanatory comment ABOVE the try/except, but
github-code-quality's empty-except rule looks for a comment INSIDE the
except body. Move/extend the comment so it lives next to the `pass`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 7, 2026 11:24
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 1

Comment on lines +58 to +60
# Named ref (not a SHA) — the script's --base default also reads
# $BASE_SHA for backward compatibility.
BASE_REF: origin/${{ github.event.pull_request.base.ref }}
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