Unify duration parsing across MTP CLI options (#8430)#8506
Conversation
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>
There was a problem hiding this comment.
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-safeTryParse, new “require suffix” API, and configurable default unit for bare numbers). - Updates
--timeouthandling to use the shared parser and adds a validation guard forTimer.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
Evangelink
left a comment
There was a problem hiding this comment.
✅ 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 (
TryCreateTimeSpanwrapper) Timer.MaxSupportedTimeoutvalidation preventsCancelAftercrashes- Case-insensitive suffix matching (
OrdinalIgnoreCase) InvariantCulturenumber parsing- Comprehensive test coverage (89/89 tests passing)
.xlffiles 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:
.resxchanges proper,.xlfauto-generated - ✅ API Surface: New
TimeSpanDefaultUnitenum andTryParseRequireSuffixare appropriate additions (internal scope) - ✅ Cross-TFM:
#if NET7_0_OR_GREATERcorrectly gates source-generated regex - ✅ Code Structure: Clean, readable, uses pattern matching and switch expressions appropriately
- ✅ Naming:
TryParseRequireSuffixclearly communicates intent - ✅ Documentation: Error messages and help text accurate and helpful
Behavior Changes (documented in PR description):
--timeoutnow accepts long-form suffixes +ms/d(strict superset, backward compatible)- Rejects previously-silently-accepted bogus inputs (
-1s,1e3s,+1s,.5s,30monkey) - 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>
|
@copilot resolve the merge conflicts in this pull request |
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Conflicts are resolved and merged with |
Remove duplicate <value>/<source>+<target> entries for PlatformCommandLineTimeoutArgumentErrorMessage left over from merging origin/main, which caused 'trans-unit has invalid child element source' build errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rtedTimeout, remove dead TryParseTimeoutArgument Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address PR #8506 review: the TimeSpanParser regex accepts mil/mils/sec/secs/min/mins short-form aliases but they were not documented in the --timeout help text or error message, leading users to think only 'ms'/'s'/'m'/'h'/'d' were valid. Align the --timeout description and error message with the existing HangDump style by listing all accepted suffixes exhaustively. - Update PlatformCommandLineTimeoutOptionDescription and PlatformCommandLineTimeoutArgumentErrorMessage to list every accepted suffix. - Update TimeSpanParser.SuffixGrammar (used by other consumers via GetGrammarHint) to keep the wording consistent. - Update HelpInfo* acceptance test expectations to match. - Add TimeoutWithValidArg_WithAliasSuffix integration test pinning that '500mil' flows through the --timeout option (not just the shared parser). - Fix a pre-existing test/resx mismatch for --retry-failed-tests-delay in HelpInfoAllExtensionsTests that was introduced earlier in this PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (1)
src/Platform/Microsoft.Testing.Extensions.HangDump/Resources/ExtensionResources.resx:192
HangDumpTimeoutOptionInvalidArgumentis returned when--hangdump-timeoutfailsTimeSpanParser.TryParse(...), but the message currently says the option "expects a single timeout argument". Arity is alreadyExactlyOne, so this error is misleading for format/unit failures (e.g.--hangdump-timeout 30monkey). Consider changing the resource text to say it requires a valid time value and (optionally) include a few accepted examples / mention the bare-number default to milliseconds to match the updated description.
Default is 30m.</value>
</data>
<data name="HangDumpTimeoutOptionInvalidArgument" xml:space="preserve">
<value>'--hangdump-timeout' expects a single timeout argument</value>
</data>
- Files reviewed: 52/52 changed files
- Comments generated: 0 new
|
@copilot resolve the merge conflicts in this pull request |
…duration-parser Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Merged |
The merge between this branch (which updated PlatformCommandLineTimeoutArgumentErrorMessage to a new longer English source) and origin/main (which had a OneLocBuild update translating the previous shorter source for pt-BR and zh-Hant) was resolved by stacking both <source>/<target> pairs inside a single <trans-unit>. This left those two files structurally invalid (duplicate <source> and <target> children, effectively a missing </trans-unit>) and inconsistent with all other languages. Remove the stale OneLocBuild <source>/<target> pair (which was for the old English source) so each <trans-unit> has a single <source>+<target> matching the structure of the other localized files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes #8430.
The three duration-style CLI options used inconsistent grammars:
--timeoutused a hand-rolled parser that only accepted single-character suffixes (h|m|s) and rejected bare numbers entirely.--hangdump-timeoutand--retry-failed-tests-delayused the sharedTimeSpanParser, 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 90minerrored while--hangdump-timeout 90minworked;--timeout 200was rejected;--retry-failed-tests-delay 200parsed as 200 ms). The existing parser also silently accepted bogus alphabetic tails (30monkeybecame 30 minutes because the regex'ss?[a-z]*tail matched and dispatch ranStartsWith(""m"")).What this PR does
All three options now route through a single parser:
TimeSpanParserregex tightened to an explicit suffix vocabulary (ms|mils?|milliseconds?|s|secs?|seconds?|m|mins?|minutes?|h|hours?|d|days?).TryParseRequireSuffixoverload for callers (i.e.--timeout) that must require an explicit unit; a newTimeSpanDefaultUnitenum lets other callers express their own bare-number default unit (preserving existing ms default for--hangdump-timeoutand--retry-failed-tests-delay).OrdinalIgnoreCase(wasOrdinal, breaking1H/1M/1D).InvariantCulture;TimeSpan.From*calls are wrapped so overflow returnsfalseinstead of throwing.--timeoutvalidator also bounds the value toTimer.MaxSupportedTimeout(uint.MaxValue - 1ms, ~49.7 days) so--timeout 60dproduces a friendly CLI error instead of crashing insideCancellationTokenSource.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
--timeoutis now a strict superset of its previous grammar (long-form suffixes,ms,dnow accepted) but rejects-1s,1e3s,+1s, and.5s(the oldfloat.TryParseaccepted those).30monkeyare now hard-rejected on all three options. Previously the regex silently accepted them and dispatched on the first letter.--timeoutnow validates againstTimer.MaxSupportedTimeout.Validation
TimeSpanParserTests: 89/89 pass.HelpInfoTests+HelpInfoAllExtensionsTests+TimeoutTests(including 5 new tests for the new grammar / range guard /30monkeyrejection): 63/63 pass.HelpInfoTests: 6/6 pass.Reviews
Two rubber-duck + expert-reviewer passes. MAJOR (
CancelAfterrange guard), two MODERATE (API renameRequired→TryParseRequireSuffix, HangDump/Retry help parity), and several NITs were addressed before pushing.