Uh oh!
There was an error while loading. Please reload this page.
[perf-improver] perf: replace Regex array in AnsiDetector with direct string comparisons - #8683
Conversation
Eliminates 17 Regex object allocations and their compilation cost by replacing regex patterns with equivalent StartsWith/Contains string operations. The terminal-type detection patterns are all simple prefix/substring checks that don't need regex. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Replaces a static readonly Regex[] of 17 patterns in AnsiDetector.IsAnsiSupported with a chain of StartsWith/Contains calls (all StringComparison.Ordinal) to remove the regex allocations/compilation that happen at class init for what are effectively prefix/substring checks.
Changes:
- Remove
TerminalsRegexesarray and.Any(regex => regex.IsMatch(...))loop. - Inline 17 equivalent string comparisons, including the negative-lookahead case for
etermvseterm-color.
Show a summary per file
| File | Description |
|---|---|
| src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/AnsiDetector.cs | Replaces the regex array with a chained StartsWith/Contains expression. |
Copilot's findings
- Files reviewed: 1/1 changed files
- Comments generated: 1
Uh oh!
There was an error while loading. Please reload this page.
Amaury Levé (Evangelink)
commented
May 29, 2026
🔍 Build Failure AnalysisSummary — The Root cause: |
| File | Line | Call |
|---|---|---|
AnsiDetector.cs:26 | 26 | termType.Contains("eterm", StringComparison.Ordinal) |
AnsiDetector.cs:29 | 29 | termType.Contains("tmux", StringComparison.Ordinal) |
AnsiDetector.cs:34 | 34 | termType.Contains("ansi", StringComparison.Ordinal) |
AnsiDetector.cs:35 | 35 | termType.Contains("scoansi", StringComparison.Ordinal) |
AnsiDetector.cs:36 | 36 | termType.Contains("cygwin", StringComparison.Ordinal) |
AnsiDetector.cs:37 | 37 | termType.Contains("linux", StringComparison.Ordinal) |
AnsiDetector.cs:38 | 38 | termType.Contains("konsole", StringComparison.Ordinal) |
AnsiDetector.cs:39 | 39 | termType.Contains("bvterm", StringComparison.Ordinal) |
AnsiDetector.cs:41 | 41 | termType.Contains("alacritty", StringComparison.Ordinal) |
Proposed fix
Replace every termType.Contains(value, StringComparison.Ordinal) with termType.IndexOf(value, StringComparison.Ordinal) >= 0. string.IndexOf(string, StringComparison) is available in netstandard2.0. The StartsWith calls are fine — that overload exists on netstandard2.0.
Example diff for all 9 affected lines:
- || (termType.Contains("eterm", StringComparison.Ordinal)+ || (termType.IndexOf("eterm", StringComparison.Ordinal) >= 0- || termType.Contains("tmux", StringComparison.Ordinal)+ || termType.IndexOf("tmux", StringComparison.Ordinal) >= 0- || termType.Contains("ansi", StringComparison.Ordinal)+ || termType.IndexOf("ansi", StringComparison.Ordinal) >= 0- || termType.Contains("scoansi", StringComparison.Ordinal)+ || termType.IndexOf("scoansi", StringComparison.Ordinal) >= 0- || termType.Contains("cygwin", StringComparison.Ordinal)+ || termType.IndexOf("cygwin", StringComparison.Ordinal) >= 0- || termType.Contains("linux", StringComparison.Ordinal)+ || termType.IndexOf("linux", StringComparison.Ordinal) >= 0- || termType.Contains("konsole", StringComparison.Ordinal)+ || termType.IndexOf("konsole", StringComparison.Ordinal) >= 0- || termType.Contains("bvterm", StringComparison.Ordinal)+ || termType.IndexOf("bvterm", StringComparison.Ordinal) >= 0- || termType.Contains("alacritty", StringComparison.Ordinal);+ || termType.IndexOf("alacritty", StringComparison.Ordinal) >= 0;Build overview
- Project:
Microsoft.Testing.Platform.csproj - Target framework:
netstandard2.0 - Error:
CS1501— No overload for method 'Contains' takes 2 arguments (9 occurrences) - File:
src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/AnsiDetector.cs
🤖 Generated by the Build Failure Analysis workflow · commit bc95b9f
Generated by Build Failure Analysis for issue #8683 · sonnet46 2.3M · ◷
Amaury Levé (Evangelink)
left a comment
There was a problem hiding this comment.
Generated by Build Failure Analysis for issue #8683 · sonnet46 2.3M
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…ingComparison) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Uh oh!
There was an error while loading. Please reload this page.
🤖 This is an automated contribution from Perf Improver.
Goal and Rationale
AnsiDetectorheld astatic readonly Regex[] TerminalsRegexesfield initialised with 17new Regex(...)instances. These were allocated (and in some runtimes compiled) at class-initialisation time, purely to perform very simple prefix/substring checks against a terminal-type string. Every pattern maps directly to aStartsWithorContainscall, so no regex engine is needed.Approach
Replace the
Regex[]field and its.Any(regex => regex.IsMatch(...))loop with a singleboolexpression chainingstring.StartsWith/string.Contains(all withStringComparison.Ordinal), preserving each original pattern's semantics exactly — including the negative-lookahead case (^(?!eterm-color).*eterm.*→Contains("eterm") && !StartsWith("eterm-color")).Performance Evidence
Regexinstances allocated at startupRegex[]array allocationIsAnsiSupportedis called once per process (fromNativeMethods) so this is a startup-cost win. The method now allocates nothing and executes with pure string comparison instructions.Methodology: code inspection + BenchmarkDotNet would show reduced allocations on first call; the key claim (17 fewer allocations) is directly verifiable from the diff.
Trade-offs
Test Status
Microsoft.Testing.Platform.UnitTests(net8.0): 992 passed, 0 failed, 3 skipped ✅Reproducibility
./build.sh # restore + build artifacts/bin/Microsoft.Testing.Platform.UnitTests/Debug/net8.0/Microsoft.Testing.Platform.UnitTestsAdd this agentic workflows to your repo
To install this agentic workflow, run