Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,22 @@ This is an INTERNAL, source-only package: it shares - as compiled source - the p
BuildAction="Compile"
PackagePath="contentFiles/cs/any/TerminalReporter/%(Filename)%(Extension)" />

<!--
EmbeddedAttribute polyfill: the shared source marks its types [Microsoft.CodeAnalysis.Embedded]; that attribute
is not present in a vanilla consumer (e.g. dotnet/sdk's dotnet.csproj), so ship the trivial polyfill as source.
It is 'internal sealed partial' so it merges harmlessly if the consumer ever defines its own.
-->
<None Include="$(RepoRoot)src\Polyfills\EmbeddedAttribute.cs"
Pack="true"
BuildAction="Compile"
PackagePath="contentFiles/cs/any/TerminalReporter/EmbeddedAttribute.cs" />

<!--
Terminal localized resources. The resx + xlf are shipped under build/ (NOT contentFiles) so they are not
auto-compiled with the wrong metadata; the auto-imported build-extension props below adds the resx as a
strongly-typed <EmbeddedResource> (Generator="MSBuild:Compile" plus StronglyTyped* metadata) with a pinned
ManifestResourceName, and XliffTasks (present in the consumer, e.g. dotnet/sdk via Arcade) finds the sibling
xlf/ to emit satellite assemblies.
auto-compiled with the wrong metadata; the auto-imported build-extension props below adds the resx as an
<EmbeddedResource GenerateSource="true"> with a pinned Namespace (and ManifestResourceName) so XliffTasks
generates the strongly-typed TerminalResources accessor in the namespace the shared reporter source expects,
and XliffTasks (present in the consumer, e.g. dotnet/sdk via Arcade) finds the sibling xlf/ to emit satellites.
-->
<None Include="@(TerminalReporterContractResource)"
Pack="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,34 @@
(NuGet imports build/<PackageId>.props). It wires the terminal reporter's localized resources - shipped by this
package under build/TerminalReporter/ - into the consumer's own compilation:

* The resx is added as a strongly-typed <EmbeddedResource> (Generator="MSBuild:Compile" plus the
StronglyTyped* metadata) so the consumer's build generates the `TerminalResources` accessor and embeds
the strings into the consumer assembly. This does not depend on Arcade's GenerateSource pipeline.
* StronglyTypedNamespace/StronglyTypedClassName pin the generated accessor to
Microsoft.Testing.Platform.OutputDevice.Terminal.TerminalResources - the type the shared reporter source
expects - regardless of the consumer's RootNamespace, and ManifestResourceName keeps the embedded
manifest name in sync so the runtime ResourceManager lookup resolves the same resource set.
* The sibling xlf/ folder lets XliffTasks emit satellite assemblies for each language. This requires the
consumer to have XliffTasks; dotnet/sdk (the intended consumer) has it via Arcade.
* The resx is added as an <EmbeddedResource GenerateSource="true"> using the Arcade/XliffTasks convention
(dotnet/sdk, the intended consumer, provides both): the consumer's build generates the `TerminalResources`
strongly-typed accessor (embedding the strings into the consumer assembly, with the accessor's
ResourceManager bound to the consumer assembly) AND lets XliffTasks emit per-language satellites from the
sibling xlf/ folder. We intentionally do NOT use the manual Generator="MSBuild:Compile" + StronglyTyped*
metadata here: that path collides with XliffTasks (the per-culture resx XliffTasks generates inherit the
StronglyTyped metadata, so GenerateResource sees multiple sources for one strongly-typed class -> MSB3573).
* Namespace pins the generated accessor to Microsoft.Testing.Platform.OutputDevice.Terminal (the namespace the
shared reporter source expects) regardless of the consumer's RootNamespace; the class name defaults to the
resx file name (TerminalResources). ManifestResourceName pins the embedded resource name to
Microsoft.Testing.Platform.OutputDevice.Terminal.TerminalResources so the generated accessor's ResourceManager
resolves it. We deliberately do NOT set Link: the resx lives outside the consumer's project cone (it ships in
this package), and a Link would feed into the derived manifest resource name / accessor binding, which is
exactly what TerminalReporterContract.props avoids for the same reason.
-->
<ItemGroup>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)TerminalReporter\TerminalResources.resx"
Generator="MSBuild:Compile"
StronglyTypedFileName="$(IntermediateOutputPath)TerminalResources.g.cs"
StronglyTypedLanguage="C#"
StronglyTypedNamespace="Microsoft.Testing.Platform.OutputDevice.Terminal"
StronglyTypedClassName="TerminalResources"
ManifestResourceName="Microsoft.Testing.Platform.OutputDevice.Terminal.TerminalResources"
Link="TerminalReporter\TerminalResources.resx" />
GenerateSource="true"
Namespace="Microsoft.Testing.Platform.OutputDevice.Terminal"
ManifestResourceName="Microsoft.Testing.Platform.OutputDevice.Terminal.TerminalResources" />
</ItemGroup>

<!--
The shared reporter source is authored against the testfx global usings (ImplicitUsings=enable plus these
namespaces). Supply them here so the consumer compiles the source without per-file usings. These only take
effect when the consumer has ImplicitUsings enabled (the package also relies on LangVersion supporting the
'field' keyword, i.e. preview/latest, and on the Microsoft.CodeAnalysis.EmbeddedAttribute polyfill - which
dotnet/sdk already provides). See PACKAGE.md.
'field' keyword, i.e. preview/latest). The Microsoft.CodeAnalysis.EmbeddedAttribute the shared types need is
shipped by this package itself as a source polyfill (see the .csproj). See PACKAGE.md.
-->
<ItemGroup Condition=" '$(ImplicitUsings)' == 'enable' or '$(ImplicitUsings)' == 'true' ">
<Using Include="System.Collections" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal static FlatException[] Flatten(string? errorMessage, Exception? excepti
IEnumerable<Exception?> aggregateExceptions = exception switch
{
AggregateException aggregate => aggregate.Flatten().InnerExceptions,
_ => [exception?.InnerException],
_ => new Exception?[] { exception?.InnerException },
};

foreach (Exception? aggregate in aggregateExceptions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,18 @@ internal static partial class TerminalResources

internal static string @ConsoleIsAlreadyInBatchingMode => GetResourceString("ConsoleIsAlreadyInBatchingMode");

internal static string @DiscoveredTestsInAssembly => GetResourceString("DiscoveredTestsInAssembly");

internal static string @DiscoveredTestsSummary => GetResourceString("DiscoveredTestsSummary");

internal static string @DiscoveredTestsSummarySingular => GetResourceString("DiscoveredTestsSummarySingular");

internal static string @DiscoveringTestsFrom => GetResourceString("DiscoveringTestsFrom");

internal static string @DurationLowercase => GetResourceString("DurationLowercase");

internal static string @Error => GetResourceString("Error");

internal static string @ExitCode => GetResourceString("ExitCode");

internal static string @Expected => GetResourceString("Expected");
Expand All @@ -69,6 +79,10 @@ internal static partial class TerminalResources

internal static string @PressCtrlCAgainToForceExit => GetResourceString("PressCtrlCAgainToForceExit");

internal static string @Retried => GetResourceString("Retried");

internal static string @RunningTestsFrom => GetResourceString("RunningTestsFrom");

internal static string @SkippedLowercase => GetResourceString("SkippedLowercase");

internal static string @StackFrameAt => GetResourceString("StackFrameAt");
Expand Down Expand Up @@ -119,6 +133,8 @@ internal static partial class TerminalResources

internal static string @TotalLowercase => GetResourceString("TotalLowercase");

internal static string @Try => GetResourceString("Try");

internal static string @ZeroTestsRan => GetResourceString("ZeroTestsRan");

#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,4 +290,36 @@ Valid values are 'All', 'Failed', 'None'. Default is 'All' (or 'Failed' when an
<value>failed with {0} error(s)</value>
<comment>{0} is the number of errors in an assembly summary line</comment>
</data>
<data name="Try" xml:space="preserve">
<value>try {0}</value>
<comment>number or tries of the current test assembly when test assembly is being retried. {0} is number that starts at 1</comment>
</data>
<data name="Retried" xml:space="preserve">
<value>retried</value>
<comment>Suffix appended to the total test count in the run summary to indicate how many failed tests were retried, e.g. 'total: 5 (+2 retried)'.</comment>
</data>
<data name="RunningTestsFrom" xml:space="preserve">
<value>Running tests from</value>
<comment>Per-assembly banner printed by the dotnet test orchestrator before running an assembly; followed by the assembly path/target framework/architecture.</comment>
</data>
<data name="DiscoveringTestsFrom" xml:space="preserve">
<value>Discovering tests from</value>
<comment>Per-assembly banner printed by the dotnet test orchestrator before discovering tests in an assembly; followed by the assembly path/target framework/architecture.</comment>
</data>
<data name="Error" xml:space="preserve">
<value>error</value>
<comment>Label for the count of assemblies that failed without producing a failed test (crash / non-zero exit / handshake failure) in the run summary, e.g. 'error: 1'.</comment>
</data>
<data name="DiscoveredTestsInAssembly" xml:space="preserve">
<value>Discovered {0} tests in assembly</value>
<comment>Per-assembly discovery header printed by the dotnet test orchestrator; followed by the assembly path/target framework/architecture. {0} is the discovered test count.</comment>
</data>
<data name="DiscoveredTestsSummarySingular" xml:space="preserve">
<value>Discovered {0} tests.</value>
<comment>dotnet test orchestrator discovery total for a single assembly. {0} is the discovered test count.</comment>
</data>
<data name="DiscoveredTestsSummary" xml:space="preserve">
<value>Discovered {0} tests in {1} assemblies.</value>
<comment>dotnet test orchestrator discovery total across multiple assemblies. {0} is the discovered test count, {1} the assembly count.</comment>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ public bool HasHandshakeFailure
}
}

/// <summary>
/// Gets the number of assemblies that failed to handshake. Counted toward the run summary's "error" line.
/// </summary>
private int HandshakeFailureCount
{
get
{
lock (_handshakeFailuresLock)
{
return _handshakeFailures.Count;
}
}
}

/// <summary>
/// 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 <see cref="HasHandshakeFailure"/> flips and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ namespace Microsoft.Testing.Platform.OutputDevice.Terminal;
internal sealed partial class TerminalTestReporter
{
private bool _isHelp;
private bool _isRetry;

public void TestExecutionStarted(DateTimeOffset testStartTime, int workerCount, bool isDiscovery, bool isHelp, bool isRetry)
{
_isDiscovery = isDiscovery;
_isHelp = isHelp;
_isRetry = isRetry;
_testExecutionStartTime = testStartTime;
_terminalWithProgress.StartShowingProgress(workerCount);
}
Expand All @@ -24,6 +26,33 @@ public void AssemblyRunStarted(string assembly, string? targetFramework, string?
// in-process host starts a single assembly with a single fixed instance id (one attempt).
TestProgressState assemblyRun = GetOrAddAssemblyRun(assembly, targetFramework, architecture, executionId);
assemblyRun.NotifyHandshake(instanceId);

// Defensive: even if the orchestrator did not flag the run as a retry up front (e.g. the retry parameter
// could not be parsed), a second handshake for the same assembly means a retry is happening. Flip _isRetry
// so the per-test "(try N)" annotation and the summary's "(+N retried)" suffix are shown. The in-process
// host only ever handshakes once (TryCount stays 1), so this never trips for it.
_isRetry |= assemblyRun.TryCount > 1;

// Orchestrator-only: print the per-assembly "Running tests from <assembly>" banner (or "Discovering tests
// from" in discovery mode), prefixed with "(try N)" on a retry. Gated on ShowAssembly + ShowAssemblyStartAndComplete,
// which the in-process host leaves off, so its output is unchanged.
if (_options.ShowAssembly && _options.ShowAssemblyStartAndComplete)
{
_terminalWithProgress.WriteToTerminal(terminal =>
{
if (_isRetry)
{
terminal.SetColor(TerminalColor.DarkGray);
terminal.Append($"({string.Format(CultureInfo.CurrentCulture, TerminalResources.Try, assemblyRun.TryCount)}) ");
terminal.ResetColor();
}

terminal.Append(_isDiscovery ? TerminalResources.DiscoveringTestsFrom : TerminalResources.RunningTestsFrom);
terminal.Append(' ');
AppendAssemblyLinkTargetFrameworkAndArchitecture(terminal, assembly, targetFramework, architecture);
terminal.AppendLine();
});
}
}

private TestProgressState GetOrAddAssemblyRun(string assembly, string? targetFramework, string? architecture, string executionId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ private void WriteMessage(string text, TerminalColor? color = null, int? padding
}
});

/// <summary>
/// Orchestrator overload (<c>dotnet test</c>): carries the assembly/target-framework/architecture and per-attempt
/// instance id known to the multi-process orchestrator. The shared in-progress tracking is keyed by execution id,
/// so those extra arguments are accepted for signature parity and the overload delegates to the core method.
/// </summary>
public void TestInProgress(
string assembly,
string? targetFramework,
string? architecture,
string executionId,
string instanceId,
string testNodeUid,
string displayName)
=> TestInProgress(executionId, testNodeUid, displayName);
Comment thread
Evangelink marked this conversation as resolved.
Comment thread
Evangelink marked this conversation as resolved.

Comment thread
Evangelink marked this conversation as resolved.
public void TestInProgress(
string executionId,
string testNodeUid,
Expand Down
Loading