Skip to content

Support Microsoft Testing Platform simple test filters + wildcards - #145

Open
mattleibow wants to merge 3 commits into
mainfrom
mattleibow-mtp-test-filtering-args
Open

Support Microsoft Testing Platform simple test filters + wildcards#145
mattleibow wants to merge 3 commits into
mainfrom
mattleibow-mtp-test-filtering-args

Conversation

@mattleibow

@mattleibow mattleibow commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Summary

Adds support for the eight xUnit v3 Microsoft Testing Platform (MTP) "simple" test-filter switches to the device-runners CLI, and adds * wildcard support to the on-device filter evaluator.

This was triggered by reports of a new --filter-class arg. That switch is part of MTP's typed "simple" filter family (an alternative to the VSTest-style --filter expression). This PR brings all 8 of them to DeviceRunners and makes the matching faithful via real wildcard support.

What changed

On-device evaluator (TestCaseFilter)

  • Added * wildcard support to the equals family (= / !=) via an anchored, case-insensitive regex. * matches zero or more characters.
  • This is a superset of the VSTest grammar — any filter without * matches exactly as before (parity preserved).
  • The wildcard regex uses RegexOptions.NonBacktracking for guaranteed linear-time matching (no catastrophic-backtracking / ReDoS risk on adversarial patterns).
  • As a bonus, dotnet test --filter "ClassName=Calc*" now works too.

CLI (device-runners <platform> test)

  • Added the 8 repeatable MTP switches: --filter-class / --filter-not-class, --filter-method / --filter-not-method, --filter-namespace / --filter-not-namespace, --filter-trait / --filter-not-trait.
  • New MtpFilterTranslator maps them into the existing --filter expression: same-kind values OR together, different kinds AND together, not- variants exclude (AND-ed negations), structural chars escaped, * left intact.
  • --filter and the simple filters are mutually exclusive (validation error if both supplied, mirroring xUnit).
  • Malformed --filter-trait values (missing = separator or empty trait name) are rejected with a clear validation error rather than being silently reinterpreted.
  • The translated expression flows through the unchanged delivery channels (DEVICE_RUNNERS_FILTER env var / MSIX arg / WASM query string), including the Windows loose-MSIX launch path.

Filter mapping

MTP switch Maps to
--filter-class ClassName=
--filter-method FullyQualifiedName=
--filter-namespace Namespace=
--filter-trait name=value name=value
--filter-not-* negated (!=) variants

Tests

  • New MtpFilterTranslator unit tests (per-kind OR, cross-kind AND, not- exclusion, wildcard pass-through, trait split, escaping, empty→null, trait validation).
  • Extended env-var + validation tests (DEVICE_RUNNERS_FILTER built from simple filters, precedence, mutual-exclusion, malformed-trait rejection).
  • New wildcard matching tests in TestCaseFilterTests (starts-with / ends-with / contains / mid-string, multi-segment, != negation, trait values, case-insensitivity, exact-match parity).
  • CLI: 96 passed · VisualRunners: 223 passed.

Docs

  • using-devicerunners-cli.md: new Filtering Tests section (raw --filter + the 8 simple switches, combine rules).
  • using-dotnet-test.md: * wildcard note + examples.
  • xunit-v3-support.md: MTP-parity table and a --filter-query explainer (explained only — not implemented).

Scoping note

Android delivers filters via build-time MSBuild env-baking, so device-runners android test (which launches an already-installed app) doesn't re-apply these switches — Android filtering goes through the dotnet test --filter path (which now has wildcards). This is documented. Wiring the simple switches into the MSBuild/Android path can be a follow-up if full parity there is desired.

Review feedback addressed

Two-model review (GPT-5.5 + Opus 4.8). Fixes in 340c47d:

  • ReDoS hardening — wildcard regex now uses RegexOptions.NonBacktracking (linear-time, no catastrophic backtracking).
  • Windows loose-MSIX path — now forwards the effective (translated) filter, so the simple switches aren't dropped on that launch path.
  • Malformed trait validation — a --filter-trait entry with no = or an empty name now fails validation instead of being silently treated as a FullyQualifiedName filter.

mattleibow and others added 2 commits June 25, 2026 20:22
Add the eight xUnit v3 Microsoft Testing Platform 'simple' filter switches
(--filter-class/-not-class, --filter-method/-not-method,
--filter-namespace/-not-namespace, --filter-trait/-not-trait) to the
device-runners CLI test command, translating them into the existing --filter
expression that the on-device TestCaseFilter evaluates.

Also add '*' wildcard support to the equals family (= / !=) of the on-device
evaluator, a superset of the VSTest grammar (no-wildcard filters are unchanged),
so the simple filters map faithfully and 'dotnet test --filter "ClassName=Calc*"'
now works too.

- TestCaseFilter: '*' glob via anchored regex for = / != conditions
- MtpFilterTranslator: maps the 8 switches into a combined --filter expression
  (same-kind OR, cross-kind AND, not- exclusions, structural-char escaping)
- BaseTestCommandSettings: 8 repeatable options, mutual-exclusion validation,
  GetEffectiveFilter() feeding DEVICE_RUNNERS_FILTER (env/arg/WASM query string)
- Tests for the translator, env-var wiring, validation, and wildcard matching
- Docs: CLI filtering section, dotnet test wildcard note, xUnit v3 MTP parity
  and a --filter-query explainer (explained, not implemented)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
From GPT-5.5 and Opus 4.8 reviews of #145:

- TestCaseFilter: build the wildcard regex with RegexOptions.NonBacktracking so
  a pathological filter (many '*' segments) cannot trigger catastrophic
  backtracking; matching is now guaranteed linear-time (GPT-5.5, medium).
- Windows loose-MSIX launch path: forward GetEffectiveFilter(settings) instead
  of the raw settings.Filter, so --filter-class/--filter-* are no longer
  silently dropped on that path (GPT-5.5, medium).
- MtpFilterTranslator.ValidateTraits + Validate(): reject malformed trait
  filters (missing '=' or empty name) instead of silently reinterpreting a
  nameless trait like '=Fast' as a FullyQualifiedName filter (Opus 4.8, low).
- Tests: multi-wildcard and no-match-many-segment matching, trait-format
  validation (well-formed, missing separator, empty name, non-trait), and a
  malformed-trait Validate() rejection.

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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds Microsoft Testing Platform (xUnit v3) “simple” filter switches to the DeviceRunners CLI by translating them into the existing --filter expression, and extends the on-device TestCaseFilter evaluator to support * wildcards for =/!= comparisons (with docs + tests).

Changes:

  • Implemented MtpFilterTranslator + CLI settings/options/validation to support the 8 MTP simple filter switches (including mutual exclusivity with raw --filter).
  • Added * wildcard support to TestCaseFilter for =/!= via anchored, case-insensitive regex matching.
  • Added/updated unit tests and documentation to cover the new filter switches and wildcard behavior.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/DeviceRunners.VisualRunners.Tests/Testing/TestCaseFilterTests.cs Adds wildcard-matching test coverage for TestCaseFilter, including traits and negation.
test/DeviceRunners.Cli.Tests/Commands/MtpFilterTranslatorTests.cs New unit tests validating translation semantics (OR/AND rules, negation, escaping, traits).
test/DeviceRunners.Cli.Tests/Commands/AppEnvironmentVariableTests.cs Extends CLI tests to ensure simple filters translate into DEVICE_RUNNERS_FILTER and validate exclusivity.
src/DeviceRunners.VisualRunners/Filtering/TestCaseFilter.cs Implements * wildcard support for equals/not-equals evaluation using RegexOptions.NonBacktracking.
src/DeviceRunners.Cli/Commands/Windows/TestCommand.cs Uses the resolved “effective filter” (raw or translated) when launching Windows apps.
src/DeviceRunners.Cli/Commands/Wasm/WasmTestCommand.cs Uses the resolved “effective filter” when constructing the WASM test URL query string.
src/DeviceRunners.Cli/Commands/MtpFilterTranslator.cs New translator for MTP simple filters into DeviceRunners’ existing filter grammar, including escaping.
src/DeviceRunners.Cli/Commands/BaseTestCommand.cs Adds CLI options for the 8 simple filters, validation (mutual exclusivity + trait format), and effective-filter resolution.
docs/articles/xunit-v3-support.md Documents MTP simple-filter parity and clarifies --filter-query is not implemented.
docs/articles/using-dotnet-test.md Documents wildcard behavior for =/!= and provides wildcard examples.
docs/articles/using-devicerunners-cli.md Adds a “Filtering Tests” section describing both --filter and the MTP simple filters + combination rules.

Comment thread src/DeviceRunners.VisualRunners/Filtering/TestCaseFilter.cs Outdated
Comment thread docs/articles/using-dotnet-test.md Outdated
Honor the documented backslash-escape contract for the new '*' wildcard:
the tokenizer previously unescaped '\*' to a bare '*', which was then treated
as a wildcard, so a literal '*' could never be matched with '='/'!='. Carry an
escaped star through tokenization as a private-use sentinel so an unescaped '*'
wildcards while '\*' matches a literal '*'. Direct comparisons collapse the
sentinel back to '*'; the wildcard regex builder does the same per literal
segment.

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.

2 participants