From a88c3c15e907d4db340688e0f28ff7f2b92718d7 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Sat, 17 Jan 2026 04:34:42 +0100 Subject: [PATCH] Disable progress on non-capable terminals --- .../OutputDevice/Terminal/NativeMethods.cs | 13 ++++++++++++- .../Terminal/TerminalTestReporter.cs | 18 ++++-------------- .../Terminal/TerminalTestReporterOptions.cs | 3 ++- .../Terminal/TestProgressStateAwareTerminal.cs | 16 ++++++---------- .../OutputDevice/TerminalOutputDevice.cs | 6 ++++-- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/NativeMethods.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/NativeMethods.cs index e887cbc622..8cc712cd58 100644 --- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/NativeMethods.cs +++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/NativeMethods.cs @@ -25,6 +25,7 @@ internal static bool IsWindows } } + // Copy of https://github.com/dotnet/msbuild/blob/4618ab514861c1aec4e9a5550ce685590de44bc7/src/Framework/NativeMethods.cs#L1613 internal static (bool AcceptAnsiColorCodes, bool OutputIsScreen, uint? OriginalConsoleMode) QueryIsScreenAndTryEnableAnsiColorCodes(StreamHandleType handleType = StreamHandleType.StdOut) { if (Console.IsOutputRedirected) @@ -33,6 +34,16 @@ internal static (bool AcceptAnsiColorCodes, bool OutputIsScreen, uint? OriginalC return (AcceptAnsiColorCodes: false, OutputIsScreen: false, OriginalConsoleMode: null); } + if (!OperatingSystem.IsAndroid() && !OperatingSystem.IsBrowser() && !OperatingSystem.IsIOS() && !OperatingSystem.IsTvOS()) + { + if (Console.BufferHeight == 0 || Console.BufferWidth == 0) + { + // The current console doesn't have a valid buffer size, which means it is not a real console. let's default to not using TL + // in those scenarios. + return (AcceptAnsiColorCodes: false, OutputIsScreen: false, OriginalConsoleMode: null); + } + } + bool acceptAnsiColorCodes = false; bool outputIsScreen = false; uint? originalConsoleMode = null; @@ -40,7 +51,7 @@ internal static (bool AcceptAnsiColorCodes, bool OutputIsScreen, uint? OriginalC { try { - nint outputStream = GetStdHandle((int)handleType); + IntPtr outputStream = GetStdHandle((int)handleType); if (GetConsoleMode(outputStream, out uint consoleMode)) { if ((consoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == ENABLE_VIRTUAL_TERMINAL_PROCESSING) diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs index f664b35901..992cac2034 100644 --- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs +++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs @@ -86,17 +86,11 @@ public TerminalTestReporter( _testApplicationCancellationTokenSource = testApplicationCancellationTokenSource; _options = options; - Func showProgress = _options.ShowProgress; - TestProgressStateAwareTerminal terminalWithProgress; - - // When not writing to ANSI we write the progress to screen and leave it there so we don't want to write it more often than every few seconds. - int nonAnsiUpdateCadenceInMs = 3_000; - // When writing to ANSI we update the progress in place and it should look responsive so we update every half second, because we only show seconds on the screen, so it is good enough. - int ansiUpdateCadenceInMs = 500; + ITerminal terminal; if (_options.AnsiMode == AnsiMode.SimpleAnsi) { // We are told externally that we are in CI, use simplified ANSI mode. - terminalWithProgress = new TestProgressStateAwareTerminal(new SimpleAnsiTerminal(console), showProgress, writeProgressImmediatelyAfterOutput: true, updateEvery: nonAnsiUpdateCadenceInMs); + terminal = new SimpleAnsiTerminal(console); } else { @@ -111,14 +105,10 @@ public TerminalTestReporter( _ => throw ApplicationStateGuard.Unreachable(), }; - terminalWithProgress = new TestProgressStateAwareTerminal( - useAnsi ? new AnsiTerminal(console) : new NonAnsiTerminal(console), - showProgress, - writeProgressImmediatelyAfterOutput: useAnsi, - updateEvery: useAnsi ? ansiUpdateCadenceInMs : nonAnsiUpdateCadenceInMs); + terminal = useAnsi ? new AnsiTerminal(console) : new NonAnsiTerminal(console); } - _terminalWithProgress = terminalWithProgress; + _terminalWithProgress = new TestProgressStateAwareTerminal(terminal, _options.ShowProgress); } public void TestExecutionStarted(DateTimeOffset testStartTime, int workerCount, bool isDiscovery) diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporterOptions.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporterOptions.cs index 9830de21e7..a9eef16b5a 100644 --- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporterOptions.cs +++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporterOptions.cs @@ -16,7 +16,8 @@ internal sealed class TerminalTestReporterOptions public int MinimumExpectedTests { get; init; } /// - /// Gets a value indicating whether we should write the progress periodically to screen. When ANSI is allowed we update the progress as often as we can. When ANSI is not allowed we update it every 3 seconds. + /// Gets a value indicating whether we should write the progress periodically to screen. When ANSI is allowed we update the progress as often as we can. + /// When ANSI is not allowed we never have progress. /// This is a callback to nullable bool, because we don't know if we are running as test host controller until after we setup the console. So we should be polling for the value, until we get non-null boolean /// and then cache that value. /// diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.cs index fdcb1f67f1..7a832684e3 100644 --- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.cs +++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.cs @@ -21,8 +21,6 @@ internal sealed partial class TestProgressStateAwareTerminal : IDisposable private readonly ITerminal _terminal; private readonly Func _showProgress; - private readonly bool _writeProgressImmediatelyAfterOutput; - private readonly int _updateEvery; private TestProgressState?[] _progressItems = []; private bool? _showProgressCached; @@ -43,7 +41,10 @@ private void ThreadProc() { try { - while (!_cts.Token.WaitHandle.WaitOne(_updateEvery)) + // When writing to ANSI, we update the progress in place and it should look responsive so we + // update every half second, because we only show seconds on the screen, so it is good enough. + // When writing to non-ANSI, we never show progress as the output can get long and messy. + while (!_cts.Token.WaitHandle.WaitOne(500)) { lock (_lock) { @@ -69,12 +70,10 @@ private void ThreadProc() _terminal.EraseProgress(); } - public TestProgressStateAwareTerminal(ITerminal terminal, Func showProgress, bool writeProgressImmediatelyAfterOutput, int updateEvery) + public TestProgressStateAwareTerminal(ITerminal terminal, Func showProgress) { _terminal = terminal; _showProgress = showProgress; - _writeProgressImmediatelyAfterOutput = writeProgressImmediatelyAfterOutput; - _updateEvery = updateEvery; } public int AddWorker(TestProgressState testWorker) @@ -134,10 +133,7 @@ internal void WriteToTerminal(Action write) _terminal.StartUpdate(); _terminal.EraseProgress(); write(_terminal); - if (_writeProgressImmediatelyAfterOutput) - { - _terminal.RenderProgress(_progressItems); - } + _terminal.RenderProgress(_progressItems); } finally { diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs index 237ca0d325..41c9cc30d0 100644 --- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs +++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs @@ -152,9 +152,11 @@ await _policiesService.RegisterOnAbortCallbackAsync( showPassed = () => true; } - Func shouldShowProgress = noProgress + Func shouldShowProgress = noProgress || ansiMode is AnsiMode.NoAnsi or AnsiMode.SimpleAnsi // User preference is to not show progress. - ? () => false + // Or, we are in terminal that's not capable of changing cursor and we can't update progress in-place. + // In that case, we force disable progress as well. + ? static () => false // User preference is to allow showing progress, figure if we should actually show it based on whether or not we are a testhost controller. // // TestHost controller is not running any tests and it should not be writing progress.