diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalResources.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalResources.cs
index e3fc923e8a..3cd8f6b91a 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalResources.cs
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalResources.cs
@@ -43,6 +43,8 @@ internal static partial class TerminalResources
internal static string @DurationLowercase => GetResourceString("DurationLowercase");
+ internal static string @ExitCode => GetResourceString("ExitCode");
+
internal static string @Expected => GetResourceString("Expected");
internal static string @Failed => GetResourceString("Failed");
@@ -51,6 +53,8 @@ internal static partial class TerminalResources
internal static string @ForTest => GetResourceString("ForTest");
+ internal static string @HandshakeFailuresHeader => GetResourceString("HandshakeFailuresHeader");
+
internal static string @InProcessArtifactsProduced => GetResourceString("InProcessArtifactsProduced");
internal static string @MinimumExpectedTestsPolicyViolation => GetResourceString("MinimumExpectedTestsPolicyViolation");
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalResources.resx b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalResources.resx
index f9a29f9686..e5686be7fc 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalResources.resx
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalResources.resx
@@ -280,4 +280,10 @@ Valid values are 'All', 'Failed', 'None'. Default is 'All' (or 'Failed' when an
succeeded
+
+ Handshake failures:
+
+
+ Exit code
+
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.Formatting.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.Formatting.cs
index 78e4bad8a3..709e5666b4 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.Formatting.cs
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.Formatting.cs
@@ -124,26 +124,29 @@ private static void FormatStandardAndErrorOutput(ITerminal terminal, string? sta
}
private static void AppendAssemblyLinkTargetFrameworkAndArchitecture(ITerminal terminal, TestProgressState assemblyRun)
+ => AppendAssemblyLinkTargetFrameworkAndArchitecture(terminal, assemblyRun.Assembly, assemblyRun.TargetFramework, assemblyRun.Architecture);
+
+ private static void AppendAssemblyLinkTargetFrameworkAndArchitecture(ITerminal terminal, string assembly, string? targetFramework, string? architecture)
{
- terminal.AppendLink(assemblyRun.Assembly, lineNumber: null);
- if (assemblyRun.TargetFramework == null && assemblyRun.Architecture == null)
+ terminal.AppendLink(assembly, lineNumber: null);
+ if (targetFramework == null && architecture == null)
{
return;
}
terminal.Append(" (");
- if (assemblyRun.TargetFramework != null)
+ if (targetFramework != null)
{
- terminal.Append(assemblyRun.TargetFramework);
- if (assemblyRun.Architecture != null)
+ terminal.Append(targetFramework);
+ if (architecture != null)
{
terminal.Append('|');
}
}
- if (assemblyRun.Architecture != null)
+ if (architecture != null)
{
- terminal.Append(assemblyRun.Architecture);
+ terminal.Append(architecture);
}
terminal.Append(')');
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.Handshake.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.Handshake.cs
new file mode 100644
index 0000000000..3ade68c58e
--- /dev/null
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.Handshake.cs
@@ -0,0 +1,130 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.Testing.Platform.OutputDevice.Terminal;
+
+[UnsupportedOSPlatform("browser")]
+internal sealed partial class TerminalTestReporter
+{
+#if NET9_0_OR_GREATER
+ private readonly Lock _handshakeFailuresLock = new();
+#else
+ private readonly object _handshakeFailuresLock = new();
+#endif
+ private readonly List _handshakeFailures = [];
+
+ ///
+ /// Gets a value indicating whether any assembly failed to handshake (e.g. a child test host process exited
+ /// before it could start a session). Used by the dotnet test orchestrator to surface a non-success exit
+ /// even when no test result was ever reported.
+ ///
+ public bool HasHandshakeFailure
+ {
+ get
+ {
+ // Read the count under the same lock that guards every mutation of the list, so the flag and the list
+ // are always observed in a consistent state (see HandshakeFailure / the reset in TestExecutionCompleted).
+ lock (_handshakeFailuresLock)
+ {
+ return _handshakeFailures.Count > 0;
+ }
+ }
+ }
+
+ ///
+ /// Report that an assembly failed to produce a usable handshake. This is an orchestrator-only path (the
+ /// in-process host always handshakes); it records the failure so flips and
+ /// the failure is re-printed in the end-of-run recap, and prints the immediate failure context.
+ ///
+ internal void HandshakeFailure(string assemblyPath, string? targetFramework, int exitCode, string outputData, string errorData, bool reportEvenWhenHelp = false)
+ {
+ if (_isHelp && !reportEvenWhenHelp)
+ {
+ // Backward-compat workaround: older Microsoft.Testing.Platform versions don't perform a handshake on the
+ // --help path (the host just prints help and exits). In that case the orchestrator routes here with the
+ // "process exited without a usable handshake" payload, which is expected and should not be reported as a
+ // failure. Explicit protocol-level rejections pass reportEvenWhenHelp=true and are still surfaced.
+ return;
+ }
+
+ lock (_handshakeFailuresLock)
+ {
+ _handshakeFailures.Add(new HandshakeFailureRecord(assemblyPath, targetFramework, exitCode, outputData, errorData));
+ }
+
+ _terminalWithProgress.WriteToTerminal(terminal =>
+ {
+ terminal.ResetColor();
+
+ // assemblyPath can be empty on the defensive "unknown execution id" path, where AppendLink would emit
+ // nothing; skip the link (and its trailing space) so we don't render a blank, unactionable prefix.
+ if (!RoslynString.IsNullOrEmpty(assemblyPath))
+ {
+ AppendAssemblyLinkTargetFrameworkAndArchitecture(terminal, assemblyPath, targetFramework, architecture: null);
+ terminal.Append(' ');
+ }
+
+ terminal.SetColor(TerminalColor.DarkRed);
+ terminal.Append(TerminalResources.ZeroTestsRan);
+ terminal.ResetColor();
+ terminal.AppendLine();
+ AppendExecutableSummary(terminal, exitCode, outputData, errorData);
+ });
+ }
+
+ ///
+ /// Re-print handshake failures captured during the run so that — even when there is a lot of diagnostic output
+ /// before the summary — the user sees the actionable failure context (assembly, exit code, stdout, stderr) at
+ /// the end of the run rather than having to scroll back.
+ ///
+ private void AppendHandshakeFailureRecap(ITerminal terminal)
+ {
+ HandshakeFailureRecord[] failures;
+ lock (_handshakeFailuresLock)
+ {
+ if (_handshakeFailures.Count == 0)
+ {
+ return;
+ }
+
+ failures = _handshakeFailures.ToArray();
+ }
+
+ terminal.AppendLine();
+ terminal.SetColor(TerminalColor.DarkRed);
+ terminal.AppendLine(TerminalResources.HandshakeFailuresHeader);
+ terminal.ResetColor();
+
+ foreach (HandshakeFailureRecord failure in failures)
+ {
+ terminal.Append(SingleIndentation);
+ AppendAssemblyLinkTargetFrameworkAndArchitecture(terminal, failure.AssemblyPath, failure.TargetFramework, architecture: null);
+ terminal.AppendLine();
+ AppendExecutableSummary(terminal, failure.ExitCode, failure.OutputData, failure.ErrorData);
+ }
+ }
+
+ private static void AppendExecutableSummary(ITerminal terminal, int? exitCode, string? outputData, string? errorData)
+ {
+ terminal.Append(TerminalResources.ExitCode);
+ terminal.Append(": ");
+ terminal.AppendLine(exitCode?.ToString(CultureInfo.CurrentCulture) ?? "");
+ AppendOutputWhenPresent(TerminalResources.StandardOutput, outputData);
+ AppendOutputWhenPresent(TerminalResources.StandardError, errorData);
+
+ void AppendOutputWhenPresent(string description, string? output)
+ {
+ if (!RoslynString.IsNullOrWhiteSpace(output))
+ {
+ AppendIndentedLine(terminal, $"{description}: {output}", SingleIndentation);
+ }
+ }
+ }
+
+ private readonly record struct HandshakeFailureRecord(
+ string AssemblyPath,
+ string? TargetFramework,
+ int ExitCode,
+ string OutputData,
+ string ErrorData);
+}
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.Lifecycle.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.Lifecycle.cs
index 5068f8b578..217f2c11a1 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.Lifecycle.cs
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.Lifecycle.cs
@@ -61,6 +61,33 @@ internal void AssemblyRunCompleted(string executionId)
_terminalWithProgress.RemoveWorker(assemblyRun.SlotIndex);
}
+ ///
+ /// Orchestrator overload: completes an assembly run with the child process exit code and captured output. When
+ /// the was never registered (the process exited before a usable handshake), the
+ /// completion is surfaced as a rather
+ /// than throwing. On a non-zero exit code the executable summary (exit code + stdout/stderr) is printed.
+ ///
+ internal void AssemblyRunCompleted(string executionId, int exitCode, string? outputData, string? errorData)
+ {
+ if (!_assemblies.TryGetValue(executionId, out TestProgressState? assemblyRun))
+ {
+ HandshakeFailure(assemblyPath: string.Empty, targetFramework: null, exitCode, outputData ?? string.Empty, errorData ?? string.Empty);
+ return;
+ }
+
+ assemblyRun.Success = exitCode == 0 && assemblyRun.FailedTests == 0;
+ assemblyRun.Stopwatch.Stop();
+ _terminalWithProgress.RemoveWorker(assemblyRun.SlotIndex);
+
+ if (exitCode == 0)
+ {
+ // Report nothing on success; we don't want to report on test-discovery etc.
+ return;
+ }
+
+ _terminalWithProgress.WriteToTerminal(terminal => AppendExecutableSummary(terminal, exitCode, outputData, errorData));
+ }
+
public void TestExecutionCompleted(DateTimeOffset endTime, int? exitCode)
{
_testExecutionEndTime = endTime;
@@ -73,11 +100,15 @@ public void TestExecutionCompleted(DateTimeOffset endTime, int? exitCode)
// This is relevant for HotReload scenarios. We want the next test sessions to start fresh, so we reset all
// per-run state here (after the summary above has consumed it): the per-assembly runs, the collected
- // artifacts, and the cancellation flag. Otherwise a later session would re-print the previous session's
- // artifacts or stay stuck in the aborted state.
+ // artifacts, the handshake failures, and the cancellation flag. Otherwise a later session would re-print the
+ // previous session's artifacts/handshake failures or stay stuck in the aborted state.
_assemblies.Clear();
_artifacts.Clear();
WasCancelled = false;
+ lock (_handshakeFailuresLock)
+ {
+ _handshakeFailures.Clear();
+ }
_testExecutionStartTime = null;
_testExecutionEndTime = null;
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.Summary.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.Summary.cs
index 74d8a70fb3..0e62562d97 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.Summary.cs
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.Summary.cs
@@ -56,7 +56,7 @@ private void AppendTestRunSummary(ITerminal terminal)
// Two sibling sites mirror this decision and must stay in lockstep:
// - TestApplicationResult.ConsumeAsync (excludes skipped from `_totalRanTests` -> exit code 8)
// - Microsoft.Testing.Platform.MSBuild InvokeTestingPlatformTask (run-summary verdict)
- bool runFailed = TestRunSummaryHelper.IsRunFailed(totalTests, totalFailedTests, totalSkippedTests, WasCancelled, _options.MinimumExpectedTests);
+ bool runFailed = TestRunSummaryHelper.IsRunFailed(totalTests, totalFailedTests, totalSkippedTests, WasCancelled, _options.MinimumExpectedTests) || HasHandshakeFailure;
terminal.SetColor(runFailed ? TerminalColor.DarkRed : TerminalColor.DarkGreen);
terminal.Append(TerminalResources.TestRunSummary);
@@ -133,6 +133,10 @@ private void AppendTestRunSummary(ITerminal terminal)
terminal.Append(durationText);
AppendLongDuration(terminal, runDuration, wrapInParentheses: false, colorize: false);
terminal.AppendLine();
+
+ // Re-print any handshake failures (orchestrator-only) at the very end so they aren't lost above the summary.
+ // No-op for the in-process host, which never reports handshake failures.
+ AppendHandshakeFailureRecap(terminal);
}
internal void TestDiscovered(string executionId, string displayName)
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs
index 9a039a1892..992ddffeb9 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs
@@ -77,6 +77,15 @@ private bool WasCancelled
private int _counter;
+ ///
+ /// Initializes a new instance of the class for orchestrator callers that
+ /// drive cancellation out-of-band (via ) rather than through a cancellation token.
+ ///
+ public TerminalTestReporter(IConsole console, TerminalTestReporterOptions options)
+ : this(console, static () => false, options)
+ {
+ }
+
///
/// Initializes a new instance of the class with custom terminal and manual refresh for testing.
///
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressState.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressState.cs
index b032ba1ffa..314affb652 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressState.cs
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressState.cs
@@ -52,4 +52,7 @@ public TestProgressState(long id, string assembly, string? targetFramework, stri
public List DiscoveredTestDisplayNames { get; internal set; } = [];
public bool IsDiscovery { get; }
+
+ /// Gets or sets a value indicating whether the assembly run completed successfully (set by the orchestrator on completion).
+ public bool Success { get; internal set; }
}
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.cs.xlf b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.cs.xlf
index 74fb91043d..dd5cb92988 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.cs.xlf
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.cs.xlf
@@ -2,7 +2,7 @@
-
+ AbortedPřerušeno
@@ -32,6 +32,11 @@
doba trvání
+
+ Exit code
+ Exit code
+
+ ExpectedOčekáváno
@@ -52,6 +57,11 @@
Pro testováníis followed by test name
+
+ Handshake failures:
+ Handshake failures:
+
+ In process file artifacts produced:Vytvořené artefakty souboru v procesu:
@@ -242,4 +252,4 @@ Platné hodnoty jsou All, Failed, None. Výchozí hodnota je All (nebo Failed, k
-
+
\ No newline at end of file
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.de.xlf b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.de.xlf
index 7862af3829..fba2485160 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.de.xlf
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.de.xlf
@@ -2,7 +2,7 @@
-
+ AbortedAbgebrochen
@@ -32,6 +32,11 @@
Dauer
+
+ Exit code
+ Exit code
+
+ ExpectedErwartet
@@ -52,6 +57,11 @@
Für Testis followed by test name
+
+ Handshake failures:
+ Handshake failures:
+
+ In process file artifacts produced:In Bearbeitung Dateiartefakte erstellt:
@@ -242,4 +252,4 @@ Gültige Werte sind „All“, „Failed“ und „None“. Der Standardwert ist
-
+
\ No newline at end of file
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.es.xlf b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.es.xlf
index 1abcbd5148..d5ef60be34 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.es.xlf
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.es.xlf
@@ -2,7 +2,7 @@
-
+ AbortedAnulado
@@ -32,6 +32,11 @@
duración
+
+ Exit code
+ Exit code
+
+ ExpectedSe esperaba
@@ -52,6 +57,11 @@
Para pruebais followed by test name
+
+ Handshake failures:
+ Handshake failures:
+
+ In process file artifacts produced:Artefactos de archivo en proceso producidos:
@@ -242,4 +252,4 @@ Los valores válidos son "All", "Failed", "None". El valor predeterminado es "Al
-
+
\ No newline at end of file
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.fr.xlf b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.fr.xlf
index ed9ad88b58..ca11060262 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.fr.xlf
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.fr.xlf
@@ -2,7 +2,7 @@
-
+ AbortedAbandonné
@@ -32,6 +32,11 @@
durée
+
+ Exit code
+ Exit code
+
+ ExpectedAttendu
@@ -52,6 +57,11 @@
Pour le testis followed by test name
+
+ Handshake failures:
+ Handshake failures:
+
+ In process file artifacts produced:Artéfacts produits dans les dossiers en cours de traitement :
@@ -242,4 +252,4 @@ Les valeurs valides sont « All », « Failed » et « None ». La valeur
-
+
\ No newline at end of file
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.it.xlf b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.it.xlf
index 4a27db74b8..62982f24a8 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.it.xlf
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.it.xlf
@@ -2,7 +2,7 @@
-
+ AbortedOperazione interrotta
@@ -32,6 +32,11 @@
durata
+
+ Exit code
+ Exit code
+
+ ExpectedPrevisto
@@ -52,6 +57,11 @@
Per testis followed by test name
+
+ Handshake failures:
+ Handshake failures:
+
+ In process file artifacts produced:Artefatti file in fase di elaborazione prodotti:
@@ -242,4 +252,4 @@ I valori validi sono 'All', 'Failed', 'None'. L'impostazione predefinita è 'All
-
+
\ No newline at end of file
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.ja.xlf b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.ja.xlf
index c51b657a25..0a8723c522 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.ja.xlf
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.ja.xlf
@@ -2,7 +2,7 @@
-
+ Aborted中止されました
@@ -32,6 +32,11 @@
期間
+
+ Exit code
+ Exit code
+
+ Expected予想
@@ -52,6 +57,11 @@
テスト用is followed by test name
+
+ Handshake failures:
+ Handshake failures:
+
+ In process file artifacts produced:インプロセスのファイル成果物が生成されました:
@@ -242,4 +252,4 @@ Valid values are 'All', 'Failed', 'None'. Default is 'All' (or 'Failed' when an
-
+
\ No newline at end of file
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.ko.xlf b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.ko.xlf
index 6ad5ad3691..dd86dc23cb 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.ko.xlf
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.ko.xlf
@@ -2,7 +2,7 @@
-
+ Aborted중단됨
@@ -32,6 +32,11 @@
기간
+
+ Exit code
+ Exit code
+
+ Expected필요
@@ -52,6 +57,11 @@
테스트용is followed by test name
+
+ Handshake failures:
+ Handshake failures:
+
+ In process file artifacts produced:생성된 In process 파일 아티팩트:
@@ -242,4 +252,4 @@ Valid values are 'All', 'Failed', 'None'. Default is 'All' (or 'Failed' when an
-
+
\ No newline at end of file
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.pl.xlf b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.pl.xlf
index d43df880e8..1324ddfe90 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.pl.xlf
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.pl.xlf
@@ -2,7 +2,7 @@
-
+ AbortedPrzerwano
@@ -32,6 +32,11 @@
czas trwania
+
+ Exit code
+ Exit code
+
+ ExpectedOczekiwane
@@ -52,6 +57,11 @@
Na potrzeby testuis followed by test name
+
+ Handshake failures:
+ Handshake failures:
+
+ In process file artifacts produced:Wygenerowane artefakty pliku w trakcie procesu:
@@ -242,4 +252,4 @@ Prawidłowe wartości to „All”, „Failed”, „None”. Wartość domyśln
-
+
\ No newline at end of file
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.pt-BR.xlf b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.pt-BR.xlf
index 7b3cd8df4f..a69d886a45 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.pt-BR.xlf
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.pt-BR.xlf
@@ -2,7 +2,7 @@
-
+ AbortedAnulado
@@ -32,6 +32,11 @@
duração
+
+ Exit code
+ Exit code
+
+ ExpectedEsperado
@@ -52,6 +57,11 @@
Para testeis followed by test name
+
+ Handshake failures:
+ Handshake failures:
+
+ In process file artifacts produced:Artefatos de arquivo de processo produzidos:
@@ -242,4 +252,4 @@ Os valores válidos são 'All', 'Failed', 'None'. O padrão é 'All' (ou 'Failed
-
+
\ No newline at end of file
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.ru.xlf b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.ru.xlf
index 108d86c697..a6e47520b0 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.ru.xlf
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.ru.xlf
@@ -2,7 +2,7 @@
-
+ AbortedПрервано
@@ -32,6 +32,11 @@
длительность
+
+ Exit code
+ Exit code
+
+ ExpectedОжидалось
@@ -52,6 +57,11 @@
Для тестированияis followed by test name
+
+ Handshake failures:
+ Handshake failures:
+
+ In process file artifacts produced:Созданные в процессе артефакты файлов:
@@ -242,4 +252,4 @@ Valid values are 'All', 'Failed', 'None'. Default is 'All' (or 'Failed' when an
-
+
\ No newline at end of file
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.tr.xlf b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.tr.xlf
index e507532f0d..caf4977ea8 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.tr.xlf
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.tr.xlf
@@ -2,7 +2,7 @@
-
+ AbortedDurduruldu
@@ -32,6 +32,11 @@
süre
+
+ Exit code
+ Exit code
+
+ ExpectedBeklenen
@@ -52,6 +57,11 @@
Test içinis followed by test name
+
+ Handshake failures:
+ Handshake failures:
+
+ In process file artifacts produced:Üretilen işlem içi dosya yapıtları:
@@ -242,4 +252,4 @@ Geçerli değerler: 'All', 'Failed', 'None'. Varsayılan değer 'All' (veya bir
-
+
\ No newline at end of file
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.zh-Hans.xlf b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.zh-Hans.xlf
index d5c4f06e62..82e3d0fda2 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.zh-Hans.xlf
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.zh-Hans.xlf
@@ -2,7 +2,7 @@
-
+ Aborted已中止
@@ -32,6 +32,11 @@
持续时间
+
+ Exit code
+ Exit code
+
+ Expected预期
@@ -52,6 +57,11 @@
用于测试is followed by test name
+
+ Handshake failures:
+ Handshake failures:
+
+ In process file artifacts produced:生成的进程内文件项目:
@@ -242,4 +252,4 @@ Valid values are 'All', 'Failed', 'None'. Default is 'All' (or 'Failed' when an
-
+
\ No newline at end of file
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.zh-Hant.xlf b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.zh-Hant.xlf
index 929728208a..a4932daa58 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.zh-Hant.xlf
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/xlf/TerminalResources.zh-Hant.xlf
@@ -2,7 +2,7 @@
-
+ Aborted已中止
@@ -32,6 +32,11 @@
持續時間
+
+ Exit code
+ Exit code
+
+ Expected預期
@@ -52,6 +57,11 @@
用於測試is followed by test name
+
+ Handshake failures:
+ Handshake failures:
+
+ In process file artifacts produced:產生的流程内檔案成品:
@@ -242,4 +252,4 @@ Valid values are 'All', 'Failed', 'None'. Default is 'All' (or 'Failed' when an
-
+
\ No newline at end of file
diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.cs
index c9086b12dd..351609060c 100644
--- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.cs
+++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.cs
@@ -1195,6 +1195,69 @@ public void TerminalTestReporter_WhenMultipleAssemblies_AggregatesCountsAndOmits
Assert.DoesNotContain(secondAssembly, output);
}
+ // Ported from the dotnet/sdk TerminalTestReporterTests (regression for dotnet/sdk#51608) to validate the
+ // orchestrator handshake-failure surface of the shared reporter: if a child test host process exits before a
+ // session was ever started (so the execution id is never registered), the orchestrator overload of
+ // AssemblyRunCompleted must not throw — it must surface the exit as a handshake failure and render the
+ // actionable context (exit code + captured stdout/stderr) instead.
+ [TestMethod]
+ public void AssemblyRunCompleted_WhenExecutionIdUnknown_DoesNotThrowAndReportsHandshakeFailure()
+ {
+ var stringBuilderConsole = new StringBuilderConsole();
+ var terminalReporter = new TerminalTestReporter(stringBuilderConsole, new TerminalTestReporterOptions
+ {
+ AnsiMode = AnsiMode.SimpleAnsi,
+ ShowProgress = () => false,
+ });
+
+ // Must not throw even though "never-registered" was never passed to AssemblyRunStarted.
+ terminalReporter.AssemblyRunCompleted(executionId: "never-registered", exitCode: 1, outputData: "stdout", errorData: "stderr");
+
+ Assert.IsTrue(terminalReporter.HasHandshakeFailure);
+
+ // Validate the rendered UI state: the immediate failure context is printed.
+ string output = stringBuilderConsole.Output;
+ Assert.Contains(TerminalResources.ZeroTestsRan, output);
+ Assert.Contains($"{TerminalResources.ExitCode}: 1", output);
+ Assert.Contains("stdout", output);
+ Assert.Contains("stderr", output);
+ }
+
+ // Companion to the test above covering the full lifecycle: after a handshake failure, TestExecutionCompleted must
+ // re-print the failure recap in the summary and (via runFailed |= HasHandshakeFailure) mark the run as failed even
+ // though no test ever ran.
+ [TestMethod]
+ public void AssemblyRunCompleted_WhenExecutionIdUnknown_SummaryReprintsRecapAndReportsFailure()
+ {
+ var stringBuilderConsole = new StringBuilderConsole();
+ var terminalReporter = new TerminalTestReporter(stringBuilderConsole, new TerminalTestReporterOptions
+ {
+ AnsiMode = AnsiMode.NoAnsi,
+ ShowProgress = () => false,
+ });
+
+ terminalReporter.TestExecutionStarted(DateTimeOffset.MinValue, workerCount: 1, isDiscovery: false, isHelp: false, isRetry: false);
+ terminalReporter.AssemblyRunCompleted("never-registered", exitCode: 1, outputData: "the out", errorData: "the err");
+
+ // The flag is observable before the run completes (the orchestrator reads it to force a non-zero exit).
+ Assert.IsTrue(terminalReporter.HasHandshakeFailure);
+
+ terminalReporter.TestExecutionCompleted(DateTimeOffset.MaxValue, exitCode: null);
+
+ string output = stringBuilderConsole.Output;
+
+ // The end-of-run recap header is re-printed in the summary with the captured failure context.
+ Assert.Contains(TerminalResources.HandshakeFailuresHeader, output);
+ Assert.Contains($"{TerminalResources.ExitCode}: 1", output);
+ Assert.Contains("the err", output);
+
+ // No test ran, so the run verdict is the red "Zero tests ran" (runFailed also includes HasHandshakeFailure).
+ Assert.Contains(TerminalResources.ZeroTestsRan, output);
+
+ // Per-run state is reset after completion so a subsequent session starts fresh.
+ Assert.IsFalse(terminalReporter.HasHandshakeFailure);
+ }
+
[TestMethod]
public void TerminalTestReporter_WhenReusedAcrossSessions_DoesNotLeakArtifactsOrCancelledState()
{