Skip to content

Unify duration parsing across MTP CLI options (#8430)#8506

Open
Evangelink wants to merge 2 commits into
mainfrom
dev/amauryleve/unify-duration-parser
Open

Unify duration parsing across MTP CLI options (#8430)#8506
Evangelink wants to merge 2 commits into
mainfrom
dev/amauryleve/unify-duration-parser

Conversation

@Evangelink
Copy link
Copy Markdown
Member

Fixes #8430.

The three duration-style CLI options used inconsistent grammars:

  • --timeout used a hand-rolled parser that only accepted single-character suffixes (h|m|s) and rejected bare numbers entirely.
  • --hangdump-timeout and --retry-failed-tests-delay used the shared TimeSpanParser, which accepts long-form suffixes (90min, 1.5hour, 5400seconds), ms/s/m/h/d, and treats bare numbers as milliseconds.

Copy-pasting a value across options would silently break (--timeout 90min errored while --hangdump-timeout 90min worked; --timeout 200 was rejected; --retry-failed-tests-delay 200 parsed as 200 ms). The existing parser also silently accepted bogus alphabetic tails (30monkey became 30 minutes because the regex's s?[a-z]* tail matched and dispatch ran StartsWith(""m"")).

What this PR does

All three options now route through a single parser:

  • TimeSpanParser regex tightened to an explicit suffix vocabulary (ms|mils?|milliseconds?|s|secs?|seconds?|m|mins?|minutes?|h|hours?|d|days?).
  • New TryParseRequireSuffix overload for callers (i.e. --timeout) that must require an explicit unit; a new TimeSpanDefaultUnit enum lets other callers express their own bare-number default unit (preserving existing ms default for --hangdump-timeout and --retry-failed-tests-delay).
  • Suffix dispatch uses OrdinalIgnoreCase (was Ordinal, breaking 1H/1M/1D).
  • Number parsing uses InvariantCulture; TimeSpan.From* calls are wrapped so overflow returns false instead of throwing.
  • --timeout validator also bounds the value to Timer.MaxSupportedTimeout (uint.MaxValue - 1 ms, ~49.7 days) so --timeout 60d produces a friendly CLI error instead of crashing inside CancellationTokenSource.CancelAfter.

Help text and error messages for all three options now describe the same grammar; XLFs regenerated for Platform, HangDump, and Retry.

Behavior changes worth calling out in release notes

  1. --timeout is now a strict superset of its previous grammar (long-form suffixes, ms, d now accepted) but rejects -1s, 1e3s, +1s, and .5s (the old float.TryParse accepted those).
  2. Bogus suffixes like 30monkey are now hard-rejected on all three options. Previously the regex silently accepted them and dispatched on the first letter.
  3. --timeout now validates against Timer.MaxSupportedTimeout.

Validation

  • TimeSpanParserTests: 89/89 pass.
  • Platform CommandLine + TimeSpan unit tests: 253/253 pass.
  • Platform acceptance HelpInfoTests + HelpInfoAllExtensionsTests + TimeoutTests (including 5 new tests for the new grammar / range guard / 30monkey rejection): 63/63 pass.
  • MSTest acceptance HelpInfoTests: 6/6 pass.

Reviews

Two rubber-duck + expert-reviewer passes. MAJOR (CancelAfter range guard), two MODERATE (API rename RequiredTryParseRequireSuffix, HangDump/Retry help parity), and several NITs were addressed before pushing.

The three duration-style CLI options used inconsistent grammars:

* --timeout used a hand-rolled parser that only accepted single-character
  suffixes (h|m|s) and rejected bare numbers entirely.
* --hangdump-timeout and --retry-failed-tests-delay used the shared
  TimeSpanParser, which accepts long-form suffixes (90min, 1.5hour,
  5400seconds), ms/s/m/h/d, and treats bare numbers as milliseconds.

Copy-pasting a value across options would silently break: `--timeout 90min`
errored while `--hangdump-timeout 90min` worked. `--timeout 200` was
rejected; `--retry-failed-tests-delay 200` parsed as 200 ms. And the
existing parser silently accepted bogus alphabetic tails (`30monkey`
became 30 minutes because the regex's `s?[a-z]*` tail matched and dispatch
ran StartsWith("m")).

All three options now route through a single parser:

* TimeSpanParser regex tightened to an explicit suffix vocabulary
  (ms|mils?|milliseconds?|s|secs?|seconds?|m|mins?|minutes?|h|hours?|d|days?),
  so bogus tails like 30monkey are rejected.
* New TryParseRequireSuffix overload for callers (i.e. --timeout) that
  must require an explicit unit; a new TimeSpanDefaultUnit enum lets
  other callers express their own bare-number default unit.
* Suffix dispatch uses OrdinalIgnoreCase so 1H/1M/1D parse correctly.
* Number parsing uses InvariantCulture; TimeSpan.From* calls are
  wrapped so overflow returns false instead of throwing.
* --timeout validator also bounds the value to Timer.MaxSupportedTimeout
  (uint.MaxValue - 1 ms, ~49.7 days) so `--timeout 60d` produces a
  friendly CLI error instead of crashing inside
  CancellationTokenSource.CancelAfter.

Help text and error messages for all three options now describe the same
grammar; XLFs regenerated for Platform, HangDump, and Retry.

Behavior changes worth calling out:

1. --timeout is now a strict superset of its previous grammar (long-form
   suffixes, ms, d now accepted) but rejects -1s, 1e3s, +1s, and .5s
   (the old float.TryParse accepted those).
2. Bogus suffixes like 30monkey are now hard-rejected on all three
   options. Previously the regex silently accepted them and dispatched
   on the first letter.
3. --timeout now validates against Timer.MaxSupportedTimeout.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 22, 2026 15:27
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

Unifies duration parsing for Microsoft.Testing.Platform (MTP) CLI options by routing --timeout, --hangdump-timeout, and --retry-failed-tests-delay through a single TimeSpanParser grammar, and updates help/error text + tests to match the new behavior.

Changes:

  • Tightens and extends TimeSpanParser (explicit suffix vocabulary, case-insensitive suffix handling, invariant-culture parsing, overflow-safe TryParse, new “require suffix” API, and configurable default unit for bare numbers).
  • Updates --timeout handling to use the shared parser and adds a validation guard for Timer.MaxSupportedTimeout.
  • Refreshes help/info expectations and localized resources (Platform, HangDump, Retry) to document the unified grammar, and adds/updates unit + acceptance tests.
Show a summary per file
File Description
test/UnitTests/Microsoft.Testing.Platform.UnitTests/Helpers/TimeSpanParserTests.cs Adds unit tests for the expanded duration grammar, default-unit behavior, and edge cases.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/HelpInfoTests.cs Updates MSTest help text expectations for the new --timeout grammar.
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/TimeoutTests.cs Updates timeout acceptance tests and adds new scenarios (bogus suffix tail, max-range guard, new suffix forms).
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/HelpInfoTests.cs Updates MTP help/info expectations for --timeout to the unified grammar.
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/HelpInfoAllExtensionsTests.cs Updates help/info expectations for --timeout, --hangdump-timeout, and --retry-failed-tests-delay with unified grammar details.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hant.xlf Updates localized resource entries impacted by the new --timeout strings.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hans.xlf Updates localized resource entries impacted by the new --timeout strings.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.tr.xlf Updates localized resource entries impacted by the new --timeout strings.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ru.xlf Updates localized resource entries impacted by the new --timeout strings.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pt-BR.xlf Updates localized resource entries impacted by the new --timeout strings.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pl.xlf Updates localized resource entries impacted by the new --timeout strings.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ko.xlf Updates localized resource entries impacted by the new --timeout strings.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ja.xlf Updates localized resource entries impacted by the new --timeout strings.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.it.xlf Updates localized resource entries impacted by the new --timeout strings.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.fr.xlf Updates localized resource entries impacted by the new --timeout strings.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.es.xlf Updates localized resource entries impacted by the new --timeout strings.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.de.xlf Updates localized resource entries impacted by the new --timeout strings.
src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.cs.xlf Updates localized resource entries impacted by the new --timeout strings.
src/Platform/Microsoft.Testing.Platform/Resources/PlatformResources.resx Updates the English help/validation strings for --timeout to the unified grammar.
src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.CommonServices.cs Switches runtime consumption of --timeout to the shared TimeSpanParser API.
src/Platform/Microsoft.Testing.Platform/Helpers/TimeSpanParser.cs Implements the unified duration parsing behavior and new APIs (default units + require-suffix).
src/Platform/Microsoft.Testing.Platform/CommandLine/PlatformCommandLineProvider.cs Updates --timeout validation to use TimeSpanParser and applies the max supported timeout guard.
src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.zh-Hant.xlf Updates Retry extension localized strings for the unified duration examples/grammar.
src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.zh-Hans.xlf Updates Retry extension localized strings for the unified duration examples/grammar.
src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.tr.xlf Updates Retry extension localized strings for the unified duration examples/grammar.
src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.ru.xlf Updates Retry extension localized strings for the unified duration examples/grammar.
src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.pt-BR.xlf Updates Retry extension localized strings for the unified duration examples/grammar.
src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.pl.xlf Updates Retry extension localized strings for the unified duration examples/grammar.
src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.ko.xlf Updates Retry extension localized strings for the unified duration examples/grammar.
src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.ja.xlf Updates Retry extension localized strings for the unified duration examples/grammar.
src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.it.xlf Updates Retry extension localized strings for the unified duration examples/grammar.
src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.fr.xlf Updates Retry extension localized strings for the unified duration examples/grammar.
src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.es.xlf Updates Retry extension localized strings for the unified duration examples/grammar.
src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.de.xlf Updates Retry extension localized strings for the unified duration examples/grammar.
src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.cs.xlf Updates Retry extension localized strings for the unified duration examples/grammar.
src/Platform/Microsoft.Testing.Extensions.Retry/Resources/ExtensionResources.resx Updates Retry extension English strings to include unified examples (e.g., 500ms, 1d).
src/Platform/Microsoft.Testing.Extensions.HangDump/Resources/xlf/ExtensionResources.zh-Hant.xlf Updates HangDump extension localized help text to list unified suffix formats and bare-number default.
src/Platform/Microsoft.Testing.Extensions.HangDump/Resources/xlf/ExtensionResources.zh-Hans.xlf Updates HangDump extension localized help text to list unified suffix formats and bare-number default.
src/Platform/Microsoft.Testing.Extensions.HangDump/Resources/xlf/ExtensionResources.tr.xlf Updates HangDump extension localized help text to list unified suffix formats and bare-number default.
src/Platform/Microsoft.Testing.Extensions.HangDump/Resources/xlf/ExtensionResources.ru.xlf Updates HangDump extension localized help text to list unified suffix formats and bare-number default.
src/Platform/Microsoft.Testing.Extensions.HangDump/Resources/xlf/ExtensionResources.pt-BR.xlf Updates HangDump extension localized help text to list unified suffix formats and bare-number default.
src/Platform/Microsoft.Testing.Extensions.HangDump/Resources/xlf/ExtensionResources.pl.xlf Updates HangDump extension localized help text to list unified suffix formats and bare-number default.
src/Platform/Microsoft.Testing.Extensions.HangDump/Resources/xlf/ExtensionResources.ko.xlf Updates HangDump extension localized help text to list unified suffix formats and bare-number default.
src/Platform/Microsoft.Testing.Extensions.HangDump/Resources/xlf/ExtensionResources.ja.xlf Updates HangDump extension localized help text to list unified suffix formats and bare-number default.
src/Platform/Microsoft.Testing.Extensions.HangDump/Resources/xlf/ExtensionResources.it.xlf Updates HangDump extension localized help text to list unified suffix formats and bare-number default.
src/Platform/Microsoft.Testing.Extensions.HangDump/Resources/xlf/ExtensionResources.fr.xlf Updates HangDump extension localized help text to list unified suffix formats and bare-number default.
src/Platform/Microsoft.Testing.Extensions.HangDump/Resources/xlf/ExtensionResources.es.xlf Updates HangDump extension localized help text to list unified suffix formats and bare-number default.
src/Platform/Microsoft.Testing.Extensions.HangDump/Resources/xlf/ExtensionResources.de.xlf Updates HangDump extension localized help text to list unified suffix formats and bare-number default.
src/Platform/Microsoft.Testing.Extensions.HangDump/Resources/xlf/ExtensionResources.cs.xlf Updates HangDump extension localized help text to list unified suffix formats and bare-number default.
src/Platform/Microsoft.Testing.Extensions.HangDump/Resources/ExtensionResources.resx Updates HangDump extension English help text to include ms/d formats and bare-number default behavior.

Copilot's findings

  • Files reviewed: 50/50 changed files
  • Comments generated: 3

Comment thread src/Platform/Microsoft.Testing.Platform/Helpers/TimeSpanParser.cs
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.

✅ 21/21 dimensions clean — no findings.

This PR successfully unifies duration parsing across MTP CLI options. The implementation is well-designed:

Strengths:

  • Regex tightened to explicit vocabulary, rejecting bogus suffixes
  • Overflow handled gracefully (TryCreateTimeSpan wrapper)
  • Timer.MaxSupportedTimeout validation prevents CancelAfter crashes
  • Case-insensitive suffix matching (OrdinalIgnoreCase)
  • InvariantCulture number parsing
  • Comprehensive test coverage (89/89 tests passing)
  • .xlf files properly auto-generated (not manually edited)
  • Help text consistent across all three options

Technical Review:

  • Algorithmic Correctness: Parser logic correct, edge cases handled (null, overflow, invalid input)
  • Defensive Coding: Input validation robust, range checks in place
  • Localization: .resx changes proper, .xlf auto-generated
  • API Surface: New TimeSpanDefaultUnit enum and TryParseRequireSuffix are appropriate additions (internal scope)
  • Cross-TFM: #if NET7_0_OR_GREATER correctly gates source-generated regex
  • Code Structure: Clean, readable, uses pattern matching and switch expressions appropriately
  • Naming: TryParseRequireSuffix clearly communicates intent
  • Documentation: Error messages and help text accurate and helpful

Behavior Changes (documented in PR description):

  1. --timeout now accepts long-form suffixes + ms/d (strict superset, backward compatible)
  2. Rejects previously-silently-accepted bogus inputs (-1s, 1e3s, +1s, .5s, 30monkey)
  3. Range validation against Timer.MaxSupportedTimeout (prevents runtime crash)

All changes are improvements. No issues found.

Generated by Expert Code Review (on open) for issue #8506 · ● 8.1M

- Tighten TimeSpanParser regex so trailing whitespace is only accepted
  when a unit suffix follows (e.g. "90 " no longer matches).
- Expand PlatformCommandLineTimeoutArgumentErrorMessage to mention the
  positive value and supported range (~49.7 days), not just the suffix
  grammar. Regenerated XLFs.
- Update MSTest acceptance TimeoutTests to expect the new message.
- Update TryParse_UppercaseSuffix_ParsesCorrectly to assert the exact
  TimeSpan for each input instead of just non-zero.

Addresses review feedback on PR #8506.

Co-authored-by: Copilot <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.

[MTP CLI Analysis] Duration-style options use inconsistent grammars and units

2 participants