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
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ This package extends Microsoft Testing Platform to produce self-contained HTML t
<Compile Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform\Helpers\ExitCodes.cs" Link="Helpers\ExitCodes.cs" />
<Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\ReportFileNameHelper.cs" Link="Helpers\ReportFileNameHelper.cs" />
<Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\ReportFileNameSanitizer.cs" Link="Helpers\ReportFileNameSanitizer.cs" />
<Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\TestResultCaptureHelper.cs" Link="Helpers\TestResultCaptureHelper.cs" />
<Compile Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform\Services\ArtifactNamingHelper.cs" Link="Services\ArtifactNamingHelper.cs" />
<Compile Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform\OutputDevice\TargetFrameworkParser.cs" Link="Helpers\TargetFrameworkParser.cs" />
<Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\TargetFrameworkMonikerHelper.cs" Link="Helpers\TargetFrameworkMonikerHelper.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Testing.Platform;
using Microsoft.Testing.Platform.Extensions.Messages;
using Microsoft.Testing.Platform.Helpers;
using Microsoft.Testing.Platform.Messages;

namespace Microsoft.Testing.Extensions.HtmlReport;

Expand All @@ -13,11 +10,11 @@ namespace Microsoft.Testing.Extensions.HtmlReport;
// stdout/stderr/stack traces) in memory for the whole session.
internal static class TestResultCapture
{
internal const int MaxStandardStreamLength = 32 * 1024;
internal const int MaxStackTraceLength = 32 * 1024;
internal const int MaxMessageLength = 16 * 1024;
internal const int MaxIdentityFieldLength = 4 * 1024;
internal const int MaxTraitFieldLength = 1024;
internal const int MaxStandardStreamLength = TestResultCaptureHelper.MaxStandardStreamLength;
internal const int MaxStackTraceLength = TestResultCaptureHelper.MaxStackTraceLength;
internal const int MaxMessageLength = TestResultCaptureHelper.MaxMessageLength;
internal const int MaxIdentityFieldLength = TestResultCaptureHelper.MaxIdentityFieldLength;
internal const int MaxTraitFieldLength = TestResultCaptureHelper.MaxTraitFieldLength;

public static CapturedTestResult? TryCapture(TestNode node)
{
Expand All @@ -32,7 +29,8 @@ internal static class TestResultCapture
TimingProperty? timing = node.Properties.SingleOrDefault<TimingProperty>();
TimeSpan duration = timing?.GlobalTiming.Duration ?? TimeSpan.Zero;

(string? className, string? methodName) = GetClassAndMethodName(node);
TestMethodIdentifierProperty? identifier = node.Properties.SingleOrDefault<TestMethodIdentifierProperty>();
(string? className, string? methodName) = TestResultCaptureHelper.GetClassAndMethodName(identifier);

string? errorMessage = state.Explanation;
string? stackTrace = null;
Expand Down Expand Up @@ -96,51 +94,11 @@ internal static class TestResultCapture
}

private static string ClassifyOutcome(TestNodeStateProperty state)
=> state switch
{
PassedTestNodeStateProperty => "passed",
SkippedTestNodeStateProperty => "skipped",
TimeoutTestNodeStateProperty => "timedOut",
ErrorTestNodeStateProperty => "errored",
FailedTestNodeStateProperty => "failed",
_ when Array.IndexOf(TestNodePropertiesCategories.WellKnownTestNodeTestRunOutcomeFailedProperties, state.GetType()) >= 0
=> "failed",
_ => throw ApplicationStateGuard.Unreachable(),
};

private static (string? ClassName, string? MethodName) GetClassAndMethodName(TestNode node)
{
TestMethodIdentifierProperty? identifier = node.Properties.SingleOrDefault<TestMethodIdentifierProperty>();
if (identifier is null)
{
return (null, null);
}

string className = RoslynString.IsNullOrEmpty(identifier.Namespace)
? identifier.TypeName
: $"{identifier.Namespace}.{identifier.TypeName}";

return (className, identifier.MethodName);
}
// Cancellation is intentionally handled in report-specific wrappers. HTML has
// historically let cancellation fall through to the failed outcome category, so
// this wrapper simply delegates to the shared helper with no special-casing.
=> TestResultCaptureHelper.ClassifyOutcome(state);

internal static string? Truncate(string? value, int maxLength)
{
if (value is null || value.Length <= maxLength)
{
return value;
}

// Don't split a surrogate pair when truncating: drop the high surrogate too.
// The early return above guarantees cut < value.Length here, but we keep the
// explicit guard so the invariant is locally self-documenting and survives
// any future refactor that might call this block with cut == value.Length.
int cut = maxLength;
if (cut > 0 && cut < value.Length && char.IsHighSurrogate(value[cut - 1]))
{
cut--;
}

return value.Substring(0, cut)
+ $"\n…[truncated, original length: {value.Length.ToString(CultureInfo.InvariantCulture)}]";
}
=> TestResultCaptureHelper.Truncate(value, maxLength);
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ This package extends Microsoft Testing Platform to produce JUnit XML test report
<Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\ReportFileNameHelper.cs" Link="Helpers\ReportFileNameHelper.cs" />
<Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\ReportFileNameSanitizer.cs" Link="Helpers\ReportFileNameSanitizer.cs" />
<Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\ReportFileNameValidator.cs" Link="Helpers\ReportFileNameValidator.cs" />
<Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\TestResultCaptureHelper.cs" Link="Helpers\TestResultCaptureHelper.cs" />
<Compile Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform\Services\ArtifactNamingHelper.cs" Link="Services\ArtifactNamingHelper.cs" />
<Compile Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform\OutputDevice\TargetFrameworkParser.cs" Link="Helpers\TargetFrameworkParser.cs" />
<Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\TargetFrameworkMonikerHelper.cs" Link="Helpers\TargetFrameworkMonikerHelper.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Testing.Platform;
using Microsoft.Testing.Platform.Extensions.Messages;
using Microsoft.Testing.Platform.Helpers;
using Microsoft.Testing.Platform.Messages;

namespace Microsoft.Testing.Extensions.JUnitReport;

Expand All @@ -13,11 +10,11 @@ namespace Microsoft.Testing.Extensions.JUnitReport;
// stdout/stderr/stack traces) in memory for the whole session.
internal static class TestResultCapture
{
internal const int MaxStandardStreamLength = 32 * 1024;
internal const int MaxStackTraceLength = 32 * 1024;
internal const int MaxMessageLength = 16 * 1024;
internal const int MaxIdentityFieldLength = 4 * 1024;
internal const int MaxTraitFieldLength = 1024;
internal const int MaxStandardStreamLength = TestResultCaptureHelper.MaxStandardStreamLength;
internal const int MaxStackTraceLength = TestResultCaptureHelper.MaxStackTraceLength;
internal const int MaxMessageLength = TestResultCaptureHelper.MaxMessageLength;
internal const int MaxIdentityFieldLength = TestResultCaptureHelper.MaxIdentityFieldLength;
internal const int MaxTraitFieldLength = TestResultCaptureHelper.MaxTraitFieldLength;

// The display name of a test node is also captured for non-terminal (Discovered /
// InProgress) messages so that the engine can reconstruct the parent chain for
Expand Down Expand Up @@ -84,7 +81,7 @@ static TProperty GetSingleOrDefaultValue<TProperty>(TProperty? existingProperty,

string outcome = ClassifyOutcome(state);
TimeSpan duration = timing?.GlobalTiming.Duration ?? TimeSpan.Zero;
(string? className, string? methodName) = GetClassAndMethodName(identifier);
(string? className, string? methodName) = TestResultCaptureHelper.GetClassAndMethodName(identifier);

string? errorMessage = state.Explanation;
string? stackTrace = null;
Expand Down Expand Up @@ -135,54 +132,21 @@ static TProperty GetSingleOrDefaultValue<TProperty>(TProperty? existingProperty,
}

private static string ClassifyOutcome(TestNodeStateProperty state)
=> state switch
{
PassedTestNodeStateProperty => "passed",
SkippedTestNodeStateProperty => "skipped",
TimeoutTestNodeStateProperty => "timedOut",
ErrorTestNodeStateProperty => "errored",
FailedTestNodeStateProperty => "failed",
#pragma warning disable CS0618, MTP0001 // CancelledTestNodeStateProperty is obsolete
// Cancellation is an interruption, not an assertion failure. The RFC
// maps it to <error> in the generated XML; classifying it as its own
// bucket here (rather than letting it fall through to "failed") keeps
// that mapping local to the engine's outcome switch.
CancelledTestNodeStateProperty => "cancelled",
#pragma warning restore CS0618, MTP0001
_ when Array.IndexOf(TestNodePropertiesCategories.WellKnownTestNodeTestRunOutcomeFailedProperties, state.GetType()) >= 0
=> "failed",
_ => throw ApplicationStateGuard.Unreachable(),
};

private static (string? ClassName, string? MethodName) GetClassAndMethodName(TestMethodIdentifierProperty? identifier)
{
if (identifier is null)
#pragma warning disable CS0618, MTP0001 // CancelledTestNodeStateProperty is obsolete
// Cancellation is an interruption, not an assertion failure. The RFC maps it
// to <error> in the generated XML; classifying it as its own bucket here
// keeps that mapping local to JUnit. Keep the cancellation decision out of
// the shared helper so each report format preserves its existing behavior.
if (state is CancelledTestNodeStateProperty)
{
return (null, null);
return "cancelled";
}
#pragma warning restore CS0618, MTP0001

string className = RoslynString.IsNullOrEmpty(identifier.Namespace)
? identifier.TypeName
: $"{identifier.Namespace}.{identifier.TypeName}";

return (className, identifier.MethodName);
return TestResultCaptureHelper.ClassifyOutcome(state);
}

internal static string? Truncate(string? value, int maxLength)
{
if (value is null || value.Length <= maxLength)
{
return value;
}

// Don't split a surrogate pair when truncating: drop the high surrogate too.
int cut = maxLength;
if (cut > 0 && char.IsHighSurrogate(value[cut - 1]))
{
cut--;
}

return value.Substring(0, cut)
+ $"\n…[truncated, original length: {value.Length.ToString(CultureInfo.InvariantCulture)}]";
}
=> TestResultCaptureHelper.Truncate(value, maxLength);
}
66 changes: 66 additions & 0 deletions src/Platform/SharedExtensionHelpers/TestResultCaptureHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Testing.Platform;
using Microsoft.Testing.Platform.Extensions.Messages;
using Microsoft.Testing.Platform.Helpers;
using Microsoft.Testing.Platform.Messages;

namespace Microsoft.Testing.Extensions;

internal static class TestResultCaptureHelper
{
internal const int MaxStandardStreamLength = 32 * 1024;
internal const int MaxStackTraceLength = 32 * 1024;
internal const int MaxMessageLength = 16 * 1024;
internal const int MaxIdentityFieldLength = 4 * 1024;
internal const int MaxTraitFieldLength = 1024;

internal static string ClassifyOutcome(TestNodeStateProperty state)
=> state switch
{
PassedTestNodeStateProperty => "passed",
SkippedTestNodeStateProperty => "skipped",
TimeoutTestNodeStateProperty => "timedOut",
ErrorTestNodeStateProperty => "errored",
FailedTestNodeStateProperty => "failed",
_ when Array.IndexOf(TestNodePropertiesCategories.WellKnownTestNodeTestRunOutcomeFailedProperties, state.GetType()) >= 0
=> "failed",
_ => throw ApplicationStateGuard.Unreachable(),
};

internal static (string? ClassName, string? MethodName) GetClassAndMethodName(TestMethodIdentifierProperty? identifier)
{
if (identifier is null)
{
return (null, null);
}

string className = RoslynString.IsNullOrEmpty(identifier.Namespace)
? identifier.TypeName
: $"{identifier.Namespace}.{identifier.TypeName}";

return (className, identifier.MethodName);
}

internal static string? Truncate(string? value, int maxLength)
Comment thread
Evangelink marked this conversation as resolved.
{
if (value is null || value.Length <= maxLength)
{
return value;
}

// Don't split a surrogate pair when truncating: drop the high surrogate too.
// The early return above guarantees cut < value.Length here, but we keep the
// explicit guard so the invariant is locally self-documenting and survives
// any future refactor that might call this block with cut == value.Length.
int cut = maxLength;
if (cut > 0 && cut < value.Length && char.IsHighSurrogate(value[cut - 1]))
{
cut--;
}

return value.Substring(0, cut)
+ $"\n…[truncated, original length: {value.Length.ToString(CultureInfo.InvariantCulture)}]";
}
}
Loading
Loading