diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/Helpers/AcceptanceAssert.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/Helpers/AcceptanceAssert.cs index eead86e9ad..76b2c29d8d 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/Helpers/AcceptanceAssert.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/Helpers/AcceptanceAssert.cs @@ -133,6 +133,34 @@ public static void AssertOutputMatchesRegex(this DotnetMuxerResult dotnetMuxerRe public static void AssertOutputDoesNotContain(this TestHostResult testHostResult, string value, [CallerMemberName] string? callerMemberName = null, [CallerFilePath] string? callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0) => Assert.IsFalse(testHostResult.StandardOutput.Contains(value, StringComparison.Ordinal), GenerateFailedAssertionMessage(testHostResult, callerMemberName: callerMemberName, callerFilePath: callerFilePath, callerLineNumber: callerLineNumber)); + /// + /// Asserts that the test host output contains the given value after Unicode normalization (FormC) and + /// non-breaking space (U+00A0) replacement. Use this instead of when + /// comparing localized strings that may use different normalization forms or typographic spacing conventions. + /// + public static void AssertOutputContainsNormalized(this TestHostResult testHostResult, string value, [CallerMemberName] string? callerMemberName = null, [CallerFilePath] string? callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0) + { + string normalizedOutput = NormalizeForLocalization(testHostResult.StandardOutput); + string normalizedValue = NormalizeForLocalization(value); + Assert.IsTrue( + normalizedOutput.Contains(normalizedValue, StringComparison.Ordinal), + GenerateFailedAssertionMessage(testHostResult, callerMemberName, callerFilePath, callerLineNumber)); + } + + /// + /// Asserts that the test host output does not contain the given value after Unicode normalization (FormC) and + /// non-breaking space (U+00A0) replacement. Use this instead of when + /// comparing localized strings that may use different normalization forms or typographic spacing conventions. + /// + public static void AssertOutputDoesNotContainNormalized(this TestHostResult testHostResult, string value, [CallerMemberName] string? callerMemberName = null, [CallerFilePath] string? callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0) + { + string normalizedOutput = NormalizeForLocalization(testHostResult.StandardOutput); + string normalizedValue = NormalizeForLocalization(value); + Assert.IsFalse( + normalizedOutput.Contains(normalizedValue, StringComparison.Ordinal), + GenerateFailedAssertionMessage(testHostResult, callerMemberName, callerFilePath, callerLineNumber)); + } + public static void AssertStandardErrorContains(this TestHostResult testHostResult, string value, [CallerMemberName] string? callerMemberName = null, [CallerFilePath] string? callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0) => Assert.Contains(value, testHostResult.StandardError, StringComparison.Ordinal, GenerateFailedAssertionMessage(testHostResult, callerMemberName: callerMemberName, callerFilePath: callerFilePath, callerLineNumber: callerLineNumber)); @@ -165,4 +193,9 @@ private static string GenerateFailedAssertionMessage(TestHostResult testHostResu private static string GenerateFailedAssertionMessage(DotnetMuxerResult dotnetMuxerResult, string? callerMemberName, string? callerFilePath, int callerLineNumber, [CallerMemberName] string? assertCallerMemberName = null) => $"Expression '{assertCallerMemberName}' failed for member '{callerMemberName}' at line {callerLineNumber} of file '{callerFilePath}'. Output of the dotnet muxer is:{Environment.NewLine}{dotnetMuxerResult}"; + + // Localized resource strings may use different Unicode normalization forms (NFC vs NFD) than C# string literals. + // French locale also uses non-breaking space (U+00A0) before colons per typographic convention. + private static string NormalizeForLocalization(string text) + => text.Normalize(NormalizationForm.FormC).Replace('\u00A0', ' '); } diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/LocalizationFailingTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/LocalizationFailingTests.cs index a18df5479d..ec7421bdbd 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/LocalizationFailingTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/LocalizationFailingTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Text; - namespace Microsoft.Testing.Platform.Acceptance.IntegrationTests; // Temporarily disabled: OneLocBuild keeps reverting the TerminalResources.*.xlf targets to English, @@ -13,23 +11,6 @@ public sealed class LocalizationFailingTests : AcceptanceTestBase text.Normalize(NormalizationForm.FormC).Replace('\u00A0', ' '); - - private static void AssertOutputContainsNormalized(TestHostResult testHostResult, string value) - { - string normalizedOutput = NormalizeForComparison(testHostResult.StandardOutput); - string normalizedValue = NormalizeForComparison(value); - Assert.IsTrue( - normalizedOutput.Contains(normalizedValue, StringComparison.Ordinal), - $"Output does not contain '{value}'.{Environment.NewLine}Output:{Environment.NewLine}{testHostResult.StandardOutput}"); - } - [DynamicData(nameof(TargetFrameworks.AllForDynamicData), typeof(TargetFrameworks))] [TestMethod] public async Task Execution_WithFailingTest_OutputContainsTranslatedFailureSummary(string tfm) @@ -42,8 +23,8 @@ public async Task Execution_WithFailingTest_OutputContainsTranslatedFailureSumma testHostResult.AssertExitCodeIs(ExitCode.AtLeastOneTestFailed); // Verify failure summary is in French ("Résumé de série de tests : Échec!") - AssertOutputContainsNormalized(testHostResult, "Résumé de série de tests : Échec!"); - AssertOutputContainsNormalized(testHostResult, "échec: 1"); + testHostResult.AssertOutputContainsNormalized("Résumé de série de tests : Échec!"); + testHostResult.AssertOutputContainsNormalized("échec: 1"); } public sealed class TestAssetFixture() : TestAssetFixtureBase() diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/LocalizationTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/LocalizationTests.cs index 3a1243b0c8..de8362093b 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/LocalizationTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/LocalizationTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Text; - namespace Microsoft.Testing.Platform.Acceptance.IntegrationTests; // Temporarily disabled: OneLocBuild keeps reverting the TerminalResources.*.xlf targets to English, @@ -13,32 +11,6 @@ public class LocalizationTests : AcceptanceTestBase text.Normalize(NormalizationForm.FormC).Replace('\u00A0', ' '); - - private static void AssertOutputContainsNormalized(TestHostResult testHostResult, string value) - { - string normalizedOutput = NormalizeForComparison(testHostResult.StandardOutput); - string normalizedValue = NormalizeForComparison(value); - Assert.IsTrue( - normalizedOutput.Contains(normalizedValue, StringComparison.Ordinal), - $"Output does not contain '{value}'.{Environment.NewLine}Output:{Environment.NewLine}{testHostResult.StandardOutput}"); - } - - private static void AssertOutputDoesNotContainNormalized(TestHostResult testHostResult, string value) - { - string normalizedOutput = NormalizeForComparison(testHostResult.StandardOutput); - string normalizedValue = NormalizeForComparison(value); - Assert.IsFalse( - normalizedOutput.Contains(normalizedValue, StringComparison.Ordinal), - $"Output should not contain '{value}'.{Environment.NewLine}Output:{Environment.NewLine}{testHostResult.StandardOutput}"); - } - [DynamicData(nameof(TargetFrameworks.AllForDynamicData), typeof(TargetFrameworks))] [TestMethod] public async Task Execution_WithFrenchLocale_OutputContainsTranslatedSummary(string tfm) @@ -51,17 +23,17 @@ public async Task Execution_WithFrenchLocale_OutputContainsTranslatedSummary(str testHostResult.AssertExitCodeIs(ExitCode.Success); // Verify the summary line is in French ("Résumé de série de tests : Réussite!") - AssertOutputContainsNormalized(testHostResult, "Résumé de série de tests : Réussite!"); + testHostResult.AssertOutputContainsNormalized("Résumé de série de tests : Réussite!"); // Verify the count labels are in French - AssertOutputContainsNormalized(testHostResult, "total: 2"); - AssertOutputContainsNormalized(testHostResult, "échec: 0"); - AssertOutputContainsNormalized(testHostResult, "opération réussie: 2"); - AssertOutputContainsNormalized(testHostResult, "ignoré: 0"); + testHostResult.AssertOutputContainsNormalized("total: 2"); + testHostResult.AssertOutputContainsNormalized("échec: 0"); + testHostResult.AssertOutputContainsNormalized("opération réussie: 2"); + testHostResult.AssertOutputContainsNormalized("ignoré: 0"); // Verify English strings are NOT in the output - AssertOutputDoesNotContainNormalized(testHostResult, "Test run summary:"); - AssertOutputDoesNotContainNormalized(testHostResult, "succeeded:"); + testHostResult.AssertOutputDoesNotContainNormalized("Test run summary:"); + testHostResult.AssertOutputDoesNotContainNormalized("succeeded:"); } [DynamicData(nameof(TargetFrameworks.AllForDynamicData), typeof(TargetFrameworks))] @@ -105,8 +77,8 @@ public async Task Execution_WithTestingPlatformUILanguage_TakesPrecedenceOverDot testHostResult.AssertExitCodeIs(ExitCode.Success); // French should win because TESTINGPLATFORM_UI_LANGUAGE has higher precedence - AssertOutputContainsNormalized(testHostResult, "Résumé de série de tests : Réussite!"); - AssertOutputDoesNotContainNormalized(testHostResult, "Resumen de la serie de pruebas:"); + testHostResult.AssertOutputContainsNormalized("Résumé de série de tests : Réussite!"); + testHostResult.AssertOutputDoesNotContainNormalized("Resumen de la serie de pruebas:"); } public sealed class TestAssetFixture() : TestAssetFixtureBase()