Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Cli/dotnet/Commands/CliCommandStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1962,6 +1962,10 @@ Your project targets multiple frameworks. Specify which framework to run using '
<data name="StandardOutput" xml:space="preserve">
<value>Standard output</value>
</data>
<data name="TestApplicationOutputTruncated" xml:space="preserve">
<value>... output truncated, {0} lines omitted ...</value>
<comment>{0} is the number of output lines that were omitted from the summary.</comment>
</data>
<data name="StatusColumnHeader" xml:space="preserve">
<value>Status</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using Microsoft.DotNet.Cli.Commands.Test.IPC.Models;
Expand Down Expand Up @@ -866,7 +867,7 @@ private static void AppendExecutableSummary(ITerminal terminal, int? exitCode, s
terminal.Append(CliCommandStrings.ExitCode);
terminal.Append(": ");
terminal.AppendLine(exitCode?.ToString(CultureInfo.CurrentCulture) ?? "<null>");
AppendOutputWhenPresent(CliCommandStrings.StandardOutput, outputData);
AppendOutputWhenPresent(CliCommandStrings.StandardOutput, TruncateOutputForSummary(outputData));
AppendOutputWhenPresent(CliCommandStrings.StandardError, errorData);

void AppendOutputWhenPresent(string description, string? output)
Expand All @@ -878,6 +879,48 @@ void AppendOutputWhenPresent(string description, string? output)
}
}

// A test host that exits with a failure often prints its entire command-line help — hundreds of
// lines — to standard output. The classic case is an invalid argument (e.g. an unexpected value
// for a known option): the platform reports the error on the first few lines and then dumps the
// full usage, which buries the actual error in noise. Keep the beginning (where the real error
// is) and the end (where trailing diagnostics tend to be) and collapse the middle so the error
// stays easy to find. See https://github.com/dotnet/sdk/issues/52297.
private const int StandardOutputSummaryHeadLines = 30;
private const int StandardOutputSummaryTailLines = 10;
private const int StandardOutputSummaryMaxLines = StandardOutputSummaryHeadLines + StandardOutputSummaryTailLines;

internal static string? TruncateOutputForSummary(string? output)
{
if (string.IsNullOrWhiteSpace(output))
{
return output;
}

string[] lines = output.Split(NewLineStrings, StringSplitOptions.None);
if (lines.Length <= StandardOutputSummaryMaxLines)
{
return output;
}

int omitted = lines.Length - StandardOutputSummaryMaxLines;
var builder = new StringBuilder();
for (int i = 0; i < StandardOutputSummaryHeadLines; i++)
{
builder.AppendLine(lines[i]);
}

builder.AppendLine(string.Format(CultureInfo.CurrentCulture, CliCommandStrings.TestApplicationOutputTruncated, omitted));

for (int i = lines.Length - StandardOutputSummaryTailLines; i < lines.Length; i++)
{
builder.AppendLine(lines[i]);
}

// Drop the trailing newline so the value flows through AppendIndentedLine the same way an
// untruncated payload would.
return builder.ToString().TrimEnd('\r', '\n');
}

private static string? NormalizeSpecialCharacters(string? text)
=> text?.Replace('\0', '\x2400')
// escape char
Expand Down
5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,85 @@ public void AssemblyRunCompleted_WhenTestsWereRetried_ShowsRetriedCount()
assemblyLine.Should().Contain("[+1/x0/?0/r1]");
}

/// <summary>
/// Output that fits within the summary budget must be echoed verbatim (no truncation marker).
/// </summary>
[TestMethod]
public void TruncateOutputForSummary_WhenOutputIsSmall_ReturnsOutputUnchanged()
{
string output = string.Join(Environment.NewLine, Enumerable.Range(1, 10).Select(i => $"line {i}"));

string? result = TerminalTestReporter.TruncateOutputForSummary(output);

result.Should().Be(output);
}

/// <summary>
/// Regression test for https://github.com/dotnet/sdk/issues/52297: when a failing test host dumps
/// hundreds of lines (typically its full command-line help after an invalid argument), the summary
/// must keep the head (where the actual error is) and the tail, collapse the middle, and note how
/// many lines were omitted — instead of burying the error under a wall of noise.
/// </summary>
[TestMethod]
public void TruncateOutputForSummary_WhenOutputIsLarge_KeepsHeadAndTailAndNotesOmittedCount()
{
// The error the user cares about is on the first line; the rest is help noise.
var lines = new List<string> { "Option '--show-live-output' has invalid arguments: Invalid value 'true' (must be one of: 'on', 'off')" };
lines.AddRange(Enumerable.Range(1, 699).Select(i => $"help line {i}"));
string output = string.Join(Environment.NewLine, lines);

string result = TerminalTestReporter.TruncateOutputForSummary(output)!;
string[] resultLines = result.Split(Environment.NewLine);

// Head is preserved so the invalid-argument error stays visible.
result.Should().Contain("Option '--show-live-output' has invalid arguments");
// Tail is preserved.
result.Should().Contain("help line 699");
// The bulk of the help noise in the middle is dropped.
result.Should().NotContain("help line 350");
// The omission is reported (700 total lines, 40 kept -> 660 omitted).
result.Should().Contain("660 lines omitted");
// Head + marker + tail only.
resultLines.Length.Should().Be(30 + 1 + 10);
}

/// <summary>
/// End-to-end through <see cref="TerminalTestReporter.AssemblyRunCompleted"/>: a non-zero exit whose
/// captured standard output is a large help dump must be truncated in the emitted summary so the
/// error is not buried (https://github.com/dotnet/sdk/issues/52297).
/// </summary>
[TestMethod]
public void AssemblyRunCompleted_WithLargeStandardOutput_TruncatesInSummary()
{
var capturingConsole = new CapturingConsole();

var options = new TerminalTestReporterOptions
{
AnsiMode = AnsiMode.SimpleAnsi,
ShowProgress = false,
};

using var reporter = new TerminalTestReporter(capturingConsole, options);

reporter.TestExecutionStarted(DateTimeOffset.UtcNow, workerCount: 1, isDiscovery: false, isHelp: false, isRetry: false);

const string assembly = "/repo/bin/Debug/net9.0/MyTests.dll";
const string executionId = "exec-1";
reporter.AssemblyRunStarted(assembly, targetFramework: "net9.0", architecture: "x64", executionId, instanceId: "inst-1");

var lines = new List<string> { "Option '--show-live-output' has invalid arguments: Invalid value 'true' (must be one of: 'on', 'off')" };
lines.AddRange(Enumerable.Range(1, 699).Select(i => $"help line {i}"));
string largeOutput = string.Join(Environment.NewLine, lines);

reporter.AssemblyRunCompleted(executionId, exitCode: 5, outputData: largeOutput, errorData: null);

string output = StripAnsi(capturingConsole.GetOutput());

output.Should().Contain("Option '--show-live-output' has invalid arguments");
output.Should().Contain("lines omitted");
output.Should().NotContain("help line 350");
}

private static void ReportTest(TerminalTestReporter reporter, string assembly, string executionId, string instanceId, string testUid, TestOutcome outcome)
{
reporter.TestCompleted(
Expand Down
Loading