diff --git a/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs b/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs index 111a096a8e..7b618018a9 100644 --- a/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs +++ b/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs @@ -71,13 +71,17 @@ public static Task CreateServerModeBuilderAsync(string[ /// The task representing the asynchronous operation. public static async Task CreateBuilderAsync(string[] args, TestApplicationOptions? testApplicationOptions = null) { + SystemEnvironment systemEnvironment = new(); + + // See AB#2304879. + UILanguageOverride.SetCultureSpecifiedByUser(systemEnvironment); + // We get the time to save it in the logs for testcontrollers troubleshooting. SystemClock systemClock = new(); DateTimeOffset createBuilderStart = systemClock.UtcNow; string createBuilderEntryTime = createBuilderStart.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture); testApplicationOptions ??= new TestApplicationOptions(); - SystemEnvironment systemEnvironment = new(); LaunchAttachDebugger(systemEnvironment); // First step is to parse the command line from where we get the second input layer. diff --git a/src/Platform/Microsoft.Testing.Platform/Helpers/UILanguageOverride.cs b/src/Platform/Microsoft.Testing.Platform/Helpers/UILanguageOverride.cs new file mode 100644 index 0000000000..e2244a0f58 --- /dev/null +++ b/src/Platform/Microsoft.Testing.Platform/Helpers/UILanguageOverride.cs @@ -0,0 +1,101 @@ +// 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.Globalization; + +using Microsoft.Testing.Platform.Helpers; + +namespace Microsoft.Testing.Platform; + +// Borrowed from dotnet/sdk with some tweaks to allow testing +internal static class UILanguageOverride +{ +#pragma warning disable SA1310 // Field names should not contain underscore - That's how we want to name the environment variables! + private const string TESTINGPLATFORM_UI_LANGUAGE = nameof(TESTINGPLATFORM_UI_LANGUAGE); + private const string DOTNET_CLI_UI_LANGUAGE = nameof(DOTNET_CLI_UI_LANGUAGE); +#pragma warning restore SA1310 // Field names should not contain underscore + + private const string VSLANG = nameof(VSLANG); + private const string PreferredUILang = nameof(PreferredUILang); + + internal static void SetCultureSpecifiedByUser(IEnvironment environment) + { + CultureInfo? language = GetOverriddenUILanguage(environment); + if (language == null) + { + return; + } + + ApplyOverrideToCurrentProcess(language); + FlowOverrideToChildProcesses(language, environment); + } + + private static void ApplyOverrideToCurrentProcess(CultureInfo language) + => CultureInfo.DefaultThreadCurrentUICulture = language; + + private static CultureInfo? GetOverriddenUILanguage(IEnvironment environment) + { + // For MTP, TESTINGPLATFORM_UI_LANGUAGE environment variable is the highest precedence. + string? testingPlatformLanguage = environment.GetEnvironmentVariable(TESTINGPLATFORM_UI_LANGUAGE); + if (testingPlatformLanguage is not null) + { + try + { + return CultureInfo.GetCultureInfo(testingPlatformLanguage); + } + catch (CultureNotFoundException) + { + } + } + + // If TESTINGPLATFORM_UI_LANGUAGE is not set or is invalid, then DOTNET_CLI_UI_LANGUAGE= is the main way for users to customize the CLI's UI language. + string? dotnetCliLanguage = environment.GetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE); + if (dotnetCliLanguage is not null) + { + try + { + return new CultureInfo(dotnetCliLanguage); + } + catch (CultureNotFoundException) + { + } + } + + // VSLANG= is set by VS and we respect that as well so that we will respect the VS + // language preference if we're invoked by VS. + string? vsLang = environment.GetEnvironmentVariable(VSLANG); + if (vsLang != null && int.TryParse(vsLang, out int vsLcid)) + { + try + { + return new CultureInfo(vsLcid); + } + catch (ArgumentOutOfRangeException) + { + } + catch (CultureNotFoundException) + { + } + } + + return null; + } + + private static void FlowOverrideToChildProcesses(CultureInfo language, IEnvironment environment) + { + // Do not override any environment variables that are already set as we do not want to clobber a more granular setting with our global setting. + SetIfNotAlreadySet(TESTINGPLATFORM_UI_LANGUAGE, language.Name, environment); + SetIfNotAlreadySet(DOTNET_CLI_UI_LANGUAGE, language.Name, environment); + SetIfNotAlreadySet(VSLANG, language.LCID.ToString(CultureInfo.CurrentCulture), environment); // for tools following VS guidelines to just work in CLI + SetIfNotAlreadySet(PreferredUILang, language.Name, environment); // for C#/VB targets that pass $(PreferredUILang) to compiler + } + + private static void SetIfNotAlreadySet(string environmentVariableName, string value, IEnvironment environment) + { + string? currentValue = environment.GetEnvironmentVariable(environmentVariableName); + if (currentValue == null) + { + environment.SetEnvironmentVariable(environmentVariableName, value); + } + } +} diff --git a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/RunsettingsTests.cs b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/RunsettingsTests.cs index 8b9ec37177..d7d87424a3 100644 --- a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/RunsettingsTests.cs +++ b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/RunsettingsTests.cs @@ -1,10 +1,12 @@ // 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.Globalization; using System.Runtime.InteropServices; using Microsoft.Testing.Platform.Acceptance.IntegrationTests; using Microsoft.Testing.Platform.Acceptance.IntegrationTests.Helpers; +using Microsoft.Testing.Platform.Helpers; namespace MSTest.Acceptance.IntegrationTests; @@ -22,6 +24,27 @@ internal static IEnumerable> TfmList() yield return TargetFrameworks.NetFramework.First(); } + internal static IEnumerable> LocalizationTestCases() + { + // Show that TestingPlatformUILanguage is respected. + yield return new TestArgumentsEntry<(string?, string?, string?, string)>(("fr-FR", null, null, "fr-FR"), "TestingPlatformUILanguage: fr-FR, expected: fr-FR"); + + // Show that TestingPlatformUILanguage takes precedence over DotnetCLILanguage. + yield return new TestArgumentsEntry<(string?, string?, string?, string)>(("fr-FR", "it-IT", null, "fr-FR"), "TestingPlatformUILanguage: fr-FR, CLI: it-IT, expected: fr-FR"); + + // Show that DotnetCLILanguage is respected. + yield return new TestArgumentsEntry<(string?, string?, string?, string)>((null, "it-IT", null, "it-IT"), "CLI: it-IT, expected: it-IT"); + + // Show that DotnetCLILanguage takes precedence over VSLang. + yield return new TestArgumentsEntry<(string?, string?, string?, string)>((null, "it-IT", "fr-FR", "it-IT"), "CLI: it-IT, VSLang: fr-FR, expected: it-IT"); + + // Show that VSLang is respected. + yield return new TestArgumentsEntry<(string?, string?, string?, string)>((null, null, "it-IT", "it-IT"), "VSLang: it-IT, expected: it-IT"); + + // Show that TestingPlatformUILanguage takes precedence over everything. + yield return new TestArgumentsEntry<(string?, string?, string?, string)>(("fr-FR", "it-IT", "it-IT", "fr-FR"), "TestingPlatformUILanguage: fr-FR, CLI: it-IT, VSLang: it-IT, expected: fr-FR"); + } + [ArgumentsProvider(nameof(TfmList))] public async Task UnsupportedRunSettingsEntriesAreFlagged(string tfm) { @@ -48,6 +71,35 @@ public async Task UnsupportedRunSettingsEntriesAreFlagged(string tfm) testHostResult.AssertOutputContains("Runsettings attribute 'TreatNoTestsAsError' is not supported by Microsoft.Testing.Platform and will be ignored"); } + [ArgumentsProvider(nameof(LocalizationTestCases))] + public async Task UnsupportedRunSettingsEntriesAreFlagged_Localization((string? TestingPlatformUILanguage, string? DotnetCLILanguage, string? VSLang, string? ExpectedLocale) testArgument) + { + var testHost = TestHost.LocateFrom(_testAssetFixture.ProjectPath, TestAssetFixture.ProjectName, TargetFrameworks.NetCurrent.Arguments); + TestHostResult testHostResult = await testHost.ExecuteAsync("--settings my.runsettings", environmentVariables: new() + { + ["TESTINGPLATFORM_UI_LANGUAGE"] = testArgument.TestingPlatformUILanguage, + ["DOTNET_CLI_UI_LANGUAGE"] = testArgument.DotnetCLILanguage, + ["VSLANG"] = testArgument.VSLang is null ? null : new CultureInfo(testArgument.VSLang).LCID.ToString(CultureInfo.CurrentCulture), + }); + + // Assert + testHostResult.AssertExitCodeIs(0); + + switch (testArgument.ExpectedLocale) + { + case "fr-FR": + testHostResult.AssertOutputContains("Les loggers Runsettings ne sont pas pris en charge par Microsoft.Testing.Platform et seront ignorés"); + testHostResult.AssertOutputContains("Les datacollecteurs Runsettings ne sont pas pris en charge par Microsoft.Testing.Platform et seront ignorés"); + break; + case "it-IT": + testHostResult.AssertOutputContains("I logger Runsettings non sono supportati da Microsoft.Testing.Platform e verranno ignorati"); + testHostResult.AssertOutputContains("I datacollector Runsettings non sono supportati da Microsoft.Testing.Platform e verranno ignorati"); + break; + default: + throw ApplicationStateGuard.Unreachable(); + } + } + [TestFixture(TestFixtureSharingStrategy.PerTestGroup)] public sealed class TestAssetFixture(AcceptanceFixture acceptanceFixture) : TestAssetFixtureBase(acceptanceFixture.NuGetGlobalPackagesFolder) { diff --git a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/ServerMode/ServerModeTestsBase.cs b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/ServerMode/ServerModeTestsBase.cs index 9cea463f82..d31bf62902 100644 --- a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/ServerMode/ServerModeTestsBase.cs +++ b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/ServerMode/ServerModeTestsBase.cs @@ -16,7 +16,7 @@ namespace MSTest.Acceptance.IntegrationTests.Messages.V100; public partial /* for codegen regx */ class ServerModeTestsBase : AcceptanceTestBase { private static readonly string Root = RootFinder.Find(); - private static readonly Dictionary DefaultEnvironmentVariables = new() + private static readonly Dictionary DefaultEnvironmentVariables = new() { { "DOTNET_ROOT", $"{Root}/.dotnet" }, { "DOTNET_INSTALL_DIR", $"{Root}/.dotnet" }, @@ -31,7 +31,7 @@ protected ServerModeTestsBase(ITestExecutionContext testExecutionContext) protected async Task StartAsServerAndConnectToTheClientAsync(TestHost testHost) { - var environmentVariables = new Dictionary(DefaultEnvironmentVariables); + var environmentVariables = new Dictionary(DefaultEnvironmentVariables); foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables()) { // Skip all unwanted environment variables. @@ -80,7 +80,7 @@ protected async Task StartAsServerAndConnectToTheClientAs protected async Task StartAsServerAndConnectAsync(TestHost testHost, bool enableDiagnostic = false) { - var environmentVariables = new Dictionary(DefaultEnvironmentVariables); + var environmentVariables = new Dictionary(DefaultEnvironmentVariables); foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables()) { // Skip all unwanted environment variables. diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/CrashPlusHangDumpTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/CrashPlusHangDumpTests.cs index 9fc3d49b6e..44bd63c540 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/CrashPlusHangDumpTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/CrashPlusHangDumpTests.cs @@ -20,7 +20,7 @@ public async Task CrashPlusHangDump_InCaseOfCrash_CreateCrashDump() var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, "CrashPlusHangDump", TargetFrameworks.NetCurrent.Arguments); TestHostResult testHostResult = await testHost.ExecuteAsync( $"--hangdump --hangdump-timeout 5m --crashdump --results-directory {resultDirectory}", - new Dictionary + new Dictionary { { "SLEEPTIMEMS1", "4000" }, { "SLEEPTIMEMS2", "600000" }, @@ -41,7 +41,7 @@ public async Task CrashPlusHangDump_InCaseOfHang_CreateHangDump() var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, "CrashPlusHangDump", TargetFrameworks.NetCurrent.Arguments); TestHostResult testHostResult = await testHost.ExecuteAsync( $"--hangdump --hangdump-timeout 8s --crashdump --results-directory {resultDirectory}", - new Dictionary + new Dictionary { { "SLEEPTIMEMS1", "4000" }, { "SLEEPTIMEMS2", "600000" }, diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/CustomBannerTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/CustomBannerTests.cs index ba88d37dae..4c3920dbfd 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/CustomBannerTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/CustomBannerTests.cs @@ -31,7 +31,7 @@ public async Task UsingNoBanner_InTheEnvironmentVars_TheBannerDoesNotAppear(stri var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, AssetName, tfm); TestHostResult testHostResult = await testHost.ExecuteAsync( null, - new Dictionary + new Dictionary { { "TESTINGPLATFORM_NOBANNER", "true" }, }); @@ -46,7 +46,7 @@ public async Task UsingDotnetNoLogo_InTheEnvironmentVars_TheBannerDoesNotAppear( var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, AssetName, tfm); TestHostResult testHostResult = await testHost.ExecuteAsync( null, - new Dictionary + new Dictionary { { "DOTNET_NOLOGO", "true" }, }); diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/DiagnosticTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/DiagnosticTests.cs index 3c20acdb5a..4a2fb47dab 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/DiagnosticTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/DiagnosticTests.cs @@ -110,7 +110,7 @@ public async Task Diag_EnableWithEnvironmentVariables_Succeeded(string tfm) var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, AssetName, tfm); TestHostResult testHostResult = await testHost.ExecuteAsync( null, - new Dictionary + new Dictionary { { EnvironmentVariableConstants.TESTINGPLATFORM_DIAGNOSTIC, "1" }, }); @@ -127,7 +127,7 @@ public async Task Diag_EnableWithEnvironmentVariables_Verbosity_Succeeded(string var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, AssetName, tfm); TestHostResult testHostResult = await testHost.ExecuteAsync( null, - new Dictionary + new Dictionary { { EnvironmentVariableConstants.TESTINGPLATFORM_DIAGNOSTIC, "1" }, { EnvironmentVariableConstants.TESTINGPLATFORM_DIAGNOSTIC_VERBOSITY, "Trace" }, @@ -145,7 +145,7 @@ public async Task Diag_EnableWithEnvironmentVariables_CustomPrefix_Succeeded(str var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, AssetName, tfm); TestHostResult testHostResult = await testHost.ExecuteAsync( null, - new Dictionary + new Dictionary { { EnvironmentVariableConstants.TESTINGPLATFORM_DIAGNOSTIC, "1" }, { EnvironmentVariableConstants.TESTINGPLATFORM_DIAGNOSTIC_OUTPUT_FILEPREFIX, "MyPrefix" }, @@ -163,7 +163,7 @@ public async Task Diag_EnableWithEnvironmentVariables_SynchronousWrite_Succeeded var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, AssetName, tfm); TestHostResult testHostResult = await testHost.ExecuteAsync( null, - new Dictionary + new Dictionary { { EnvironmentVariableConstants.TESTINGPLATFORM_DIAGNOSTIC, "1" }, { EnvironmentVariableConstants.TESTINGPLATFORM_DIAGNOSTIC_FILELOGGER_SYNCHRONOUSWRITE, "1" }, @@ -179,7 +179,7 @@ public async Task Diag_EnableWithEnvironmentVariables_Disable_Succeeded(string t TestHostResult testHostResult = await testHost.ExecuteAsync( "--diagnostic", - new Dictionary + new Dictionary { { EnvironmentVariableConstants.TESTINGPLATFORM_DIAGNOSTIC, "0" }, }); diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ExitOnProcessExitTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ExitOnProcessExitTests.cs index d546f4e581..98b741b9e9 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ExitOnProcessExitTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ExitOnProcessExitTests.cs @@ -21,7 +21,7 @@ public void ExitOnProcessExit_Succeed(string tfm) // Create the mutex name used to wait for the PID file created by the test host. string waitPid = Guid.NewGuid().ToString("N"); - _ = testHost.ExecuteAsync(environmentVariables: new Dictionary { { "WaitPid", waitPid } }); + _ = testHost.ExecuteAsync(environmentVariables: new Dictionary { { "WaitPid", waitPid } }); Process? process; var startTime = Stopwatch.StartNew(); diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/HangDumpOutputTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/HangDumpOutputTests.cs index e5bb4b6ff3..2309c7082e 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/HangDumpOutputTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/HangDumpOutputTests.cs @@ -26,7 +26,7 @@ public async Task HangDump_Outputs_HangingTests_EvenWhenHangingTestsHaveTheSameD var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, "HangDump", TargetFrameworks.NetCurrent.Arguments); TestHostResult testHostResult = await testHost.ExecuteAsync( $"--hangdump --hangdump-timeout 8s --hangdump-type {format} --results-directory {resultDirectory} --no-progress", - new Dictionary + new Dictionary { { "SLEEPTIMEMS1", "100" }, { "SLEEPTIMEMS2", "600000" }, diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/HangDumpTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/HangDumpTests.cs index d524c88c57..4fe5ac589e 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/HangDumpTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/HangDumpTests.cs @@ -21,7 +21,7 @@ public async Task HangDump_DefaultSetting_CreateDump(string tfm) var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, "HangDump", tfm); TestHostResult testHostResult = await testHost.ExecuteAsync( $"--hangdump --hangdump-timeout 8s --results-directory {resultDirectory}", - new Dictionary + new Dictionary { { "SLEEPTIMEMS1", "4000" }, { "SLEEPTIMEMS2", "600000" }, @@ -37,7 +37,7 @@ public async Task HangDump_CustomFileName_CreateDump() var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, "HangDump", TargetFrameworks.NetCurrent.Arguments); TestHostResult testHostResult = await testHost.ExecuteAsync( $"--hangdump --hangdump-timeout 8s --hangdump-filename myhungdumpfile_%p.dmp --results-directory {resultDirectory}", - new Dictionary + new Dictionary { { "SLEEPTIMEMS1", "4000" }, { "SLEEPTIMEMS2", "600000" }, @@ -55,7 +55,7 @@ public async Task HangDump_PathWithSpaces_CreateDump() var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, "HangDump", TargetFrameworks.NetCurrent.Arguments); TestHostResult testHostResult = await testHost.ExecuteAsync( $"""--hangdump --hangdump-timeout 8s --hangdump-filename myhungdumpfile_%p.dmp --results-directory "{resultDirectory}" """, - new Dictionary + new Dictionary { { "SLEEPTIMEMS1", "4000" }, { "SLEEPTIMEMS2", "20000" }, @@ -75,7 +75,7 @@ public async Task HangDump_Formats_CreateDump(string format) var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, "HangDump", TargetFrameworks.NetCurrent.Arguments); TestHostResult testHostResult = await testHost.ExecuteAsync( $"--hangdump --hangdump-timeout 8s --hangdump-type {format} --results-directory {resultDirectory}", - new Dictionary + new Dictionary { { "SLEEPTIMEMS1", "4000" }, { "SLEEPTIMEMS2", "600000" }, @@ -91,7 +91,7 @@ public async Task HangDump_InvalidFormat_ShouldFail() var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, "HangDump", TargetFrameworks.NetCurrent.Arguments); TestHostResult testHostResult = await testHost.ExecuteAsync( $"--hangdump --hangdump-timeout 8s --hangdump-type invalid --results-directory {resultDirectory}", - new Dictionary + new Dictionary { { "SLEEPTIMEMS1", "4000" }, { "SLEEPTIMEMS2", "600000" }, diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/IgnoreExitCodeTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/IgnoreExitCodeTests.cs index 8739a515bb..b1b27a3e97 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/IgnoreExitCodeTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/IgnoreExitCodeTests.cs @@ -113,7 +113,7 @@ public async Task If_IgnoreExitCode_Specified_Should_Return_Success_ExitCode(str var host = TestInfrastructure.TestHost.LocateFrom(assetPath, AssetName, tfm, buildConfiguration: buildConfiguration); TestHostResult hostResult = await host.ExecuteAsync( command: commandLine, - environmentVariables: new Dictionary + environmentVariables: new Dictionary { { EnvironmentVariableConstants.TESTINGPLATFORM_EXITCODE_IGNORE, environmentVariable }, }); diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MSBuildTests.Test.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MSBuildTests.Test.cs index 09b2e7129f..fa4a775ae4 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MSBuildTests.Test.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MSBuildTests.Test.cs @@ -132,7 +132,7 @@ public async Task Invoke_DotnetTest_With_Arch_Switch_x86_Should_Work() string root = RootFinder.Find(); string x86Muxer = Path.Combine(root, ".dotnet", "x86"); - var dotnetRootX86 = new Dictionary + var dotnetRootX86 = new Dictionary { { "DOTNET_ROOT_X86", x86Muxer }, }; @@ -168,7 +168,7 @@ public async Task Invoke_DotnetTest_With_DOTNET_HOST_PATH_Should_Work() string root = RootFinder.Find(); string dotnetHostPath = Path.Combine(root, ".dotnet", "dotnet.exe"); - var dotnetHostPathEnvVar = new Dictionary + var dotnetHostPathEnvVar = new Dictionary { { "DOTNET_HOST_PATH", dotnetHostPath }, }; diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/NoBannerTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/NoBannerTests.cs index 83d2017756..599edb8d81 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/NoBannerTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/NoBannerTests.cs @@ -32,7 +32,7 @@ public async Task UsingNoBanner_InTheEnvironmentVars_TheBannerDoesNotAppear(stri var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, AssetName, tfm); TestHostResult testHostResult = await testHost.ExecuteAsync( null, - new Dictionary + new Dictionary { { "TESTINGPLATFORM_NOBANNER", "true" }, }); @@ -47,7 +47,7 @@ public async Task UsingDotnetNoLogo_InTheEnvironmentVars_TheBannerDoesNotAppear( var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, AssetName, tfm); TestHostResult testHostResult = await testHost.ExecuteAsync( null, - new Dictionary + new Dictionary { { "DOTNET_NOLOGO", "true" }, }); diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/TelemetryTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/TelemetryTests.cs index c533062424..48cafde0c2 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/TelemetryTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/TelemetryTests.cs @@ -49,7 +49,7 @@ public async Task Telemetry_WhenOptingOutTelemetry_WithEnvironmentVariable_Telem var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, AssetName, tfm); TestHostResult testHostResult = await testHost.ExecuteAsync( "--diagnostic", - new Dictionary + new Dictionary { { EnvironmentVariableConstants.TESTINGPLATFORM_TELEMETRY_OPTOUT, "1" }, }, @@ -76,7 +76,7 @@ public async Task Telemetry_WhenOptingOutTelemetry_With_DOTNET_CLI_EnvironmentVa var testHost = TestInfrastructure.TestHost.LocateFrom(_testAssetFixture.TargetAssetPath, AssetName, tfm); TestHostResult testHostResult = await testHost.ExecuteAsync( "--diagnostic", - new Dictionary + new Dictionary { { EnvironmentVariableConstants.DOTNET_CLI_TELEMETRY_OPTOUT, "1" }, }, diff --git a/test/Utilities/Microsoft.Testing.TestInfrastructure/CommandLine.cs b/test/Utilities/Microsoft.Testing.TestInfrastructure/CommandLine.cs index b535a6f366..73693a1896 100644 --- a/test/Utilities/Microsoft.Testing.TestInfrastructure/CommandLine.cs +++ b/test/Utilities/Microsoft.Testing.TestInfrastructure/CommandLine.cs @@ -41,7 +41,7 @@ public static int MaxOutstandingCommands public async Task RunAsync( string commandLine, - IDictionary? environmentVariables = null) + IDictionary? environmentVariables = null) { int exitCode = await RunAsyncAndReturnExitCodeAsync(commandLine, environmentVariables); if (exitCode != 0) @@ -57,7 +57,7 @@ public async Task RunAsync( public async Task RunAsyncAndReturnExitCodeAsync( string commandLine, - IDictionary? environmentVariables = null, + IDictionary? environmentVariables = null, string? workingDirectory = null, bool cleanDefaultEnvironmentVariableIfCustomAreProvided = false, int timeoutInSeconds = 60) diff --git a/test/Utilities/Microsoft.Testing.TestInfrastructure/DotnetCli.cs b/test/Utilities/Microsoft.Testing.TestInfrastructure/DotnetCli.cs index a786be904e..be08498905 100644 --- a/test/Utilities/Microsoft.Testing.TestInfrastructure/DotnetCli.cs +++ b/test/Utilities/Microsoft.Testing.TestInfrastructure/DotnetCli.cs @@ -52,7 +52,7 @@ public static async Task RunAsync( string args, string nugetGlobalPackagesFolder, string? workingDirectory = null, - Dictionary? environmentVariables = null, + Dictionary? environmentVariables = null, bool failIfReturnValueIsNotZero = true, bool disableTelemetry = true, int timeoutInSeconds = 50, @@ -64,7 +64,7 @@ public static async Task RunAsync( await s_maxOutstandingCommands_semaphore.WaitAsync(); try { - environmentVariables ??= new Dictionary(); + environmentVariables ??= new Dictionary(); foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables()) { // Skip all unwanted environment variables. @@ -123,7 +123,7 @@ public static async Task RunAsync( } } - private static async Task CallTheMuxerAsync(string args, Dictionary environmentVariables, string? workingDirectory, int timeoutInSeconds, bool failIfReturnValueIsNotZero) + private static async Task CallTheMuxerAsync(string args, Dictionary environmentVariables, string? workingDirectory, int timeoutInSeconds, bool failIfReturnValueIsNotZero) { if (args.StartsWith("dotnet ", StringComparison.OrdinalIgnoreCase)) { diff --git a/test/Utilities/Microsoft.Testing.TestInfrastructure/DotnetMuxer.cs b/test/Utilities/Microsoft.Testing.TestInfrastructure/DotnetMuxer.cs index af24e37218..cb798a263e 100644 --- a/test/Utilities/Microsoft.Testing.TestInfrastructure/DotnetMuxer.cs +++ b/test/Utilities/Microsoft.Testing.TestInfrastructure/DotnetMuxer.cs @@ -8,8 +8,8 @@ namespace Microsoft.Testing.TestInfrastructure; public class DotnetMuxer : IDisposable { private static readonly string Root = RootFinder.Find(); - private static readonly IDictionary DefaultEnvironmentVariables - = new Dictionary + private static readonly IDictionary DefaultEnvironmentVariables + = new Dictionary { { "DOTNET_ROOT", $"{Root}/.dotnet" }, { "DOTNET_INSTALL_DIR", $"{Root}/.dotnet" }, @@ -18,21 +18,21 @@ private static readonly IDictionary DefaultEnvironmentVariables }; private readonly string _dotnet; - private readonly IDictionary _environmentVariables; + private readonly IDictionary _environmentVariables; private readonly CommandLine _commandLine; private bool _isDisposed; public DotnetMuxer() : this( DefaultEnvironmentVariables, - new Dictionary(), + new Dictionary(), mergeDefaultEnvironmentVariables: true, useDefaultArtifactsPackages: true) { } public DotnetMuxer( - IDictionary environmentVariables, + IDictionary environmentVariables, bool mergeEnvironmentVariables = true, bool useDefaultArtifactPackages = true) : this( @@ -44,8 +44,8 @@ public DotnetMuxer( } private DotnetMuxer( - IDictionary defaultEnvironmentVariables, - IDictionary environmentVariables, + IDictionary defaultEnvironmentVariables, + IDictionary environmentVariables, bool mergeDefaultEnvironmentVariables = true, bool useDefaultArtifactsPackages = true) { @@ -98,7 +98,7 @@ public async Task ExecuteAsync(string arguments, string? workingDirectory = public async Task ExecuteAsync( string arguments, string? workingDirectory, - IDictionary environmentVariables, + IDictionary environmentVariables, int timeoutInSeconds = 60) => await _commandLine.RunAsyncAndReturnExitCodeAsync( $"{_dotnet} {arguments}", @@ -107,22 +107,22 @@ public async Task ExecuteAsync( cleanDefaultEnvironmentVariableIfCustomAreProvided: true, timeoutInSeconds: timeoutInSeconds); - private IDictionary MergeEnvironmentVariables( - IDictionary environmentVariables1, - IDictionary environmentVariables2) + private IDictionary MergeEnvironmentVariables( + IDictionary environmentVariables1, + IDictionary environmentVariables2) { if (environmentVariables1.Count == 0) { - return new Dictionary(environmentVariables2); + return new Dictionary(environmentVariables2); } if (environmentVariables2.Count == 0) { - return new Dictionary(environmentVariables1); + return new Dictionary(environmentVariables1); } - IDictionary mergedEnvironmentVariables = new Dictionary(environmentVariables1); - foreach (KeyValuePair kvp in environmentVariables2) + IDictionary mergedEnvironmentVariables = new Dictionary(environmentVariables1); + foreach (KeyValuePair kvp in environmentVariables2) { mergedEnvironmentVariables[kvp.Key] = kvp.Value; } diff --git a/test/Utilities/Microsoft.Testing.TestInfrastructure/ProcessConfiguration.cs b/test/Utilities/Microsoft.Testing.TestInfrastructure/ProcessConfiguration.cs index fc05c4bc25..783d50ffc9 100644 --- a/test/Utilities/Microsoft.Testing.TestInfrastructure/ProcessConfiguration.cs +++ b/test/Utilities/Microsoft.Testing.TestInfrastructure/ProcessConfiguration.cs @@ -13,7 +13,7 @@ public sealed class ProcessConfiguration public string? WorkingDirectory { get; init; } - public IDictionary? EnvironmentVariables { get; init; } + public IDictionary? EnvironmentVariables { get; init; } public Action? OnErrorOutput { get; init; } diff --git a/test/Utilities/Microsoft.Testing.TestInfrastructure/ProcessFactory.cs b/test/Utilities/Microsoft.Testing.TestInfrastructure/ProcessFactory.cs index 798e3fe9c0..4b8d554e4d 100644 --- a/test/Utilities/Microsoft.Testing.TestInfrastructure/ProcessFactory.cs +++ b/test/Utilities/Microsoft.Testing.TestInfrastructure/ProcessFactory.cs @@ -33,7 +33,7 @@ public static IProcessHandle Start(ProcessConfiguration config, bool cleanDefaul processStartInfo.EnvironmentVariables.Clear(); } - foreach (KeyValuePair kvp in config.EnvironmentVariables) + foreach (KeyValuePair kvp in config.EnvironmentVariables) { if (kvp.Value is null) { diff --git a/test/Utilities/Microsoft.Testing.TestInfrastructure/TestHost.cs b/test/Utilities/Microsoft.Testing.TestInfrastructure/TestHost.cs index f6926cfaed..60676a556a 100644 --- a/test/Utilities/Microsoft.Testing.TestInfrastructure/TestHost.cs +++ b/test/Utilities/Microsoft.Testing.TestInfrastructure/TestHost.cs @@ -43,7 +43,7 @@ public static int MaxOutstandingExecutions public async Task ExecuteAsync( string? command = null, - Dictionary? environmentVariables = null, + Dictionary? environmentVariables = null, bool disableTelemetry = true, int timeoutSeconds = 60) { @@ -55,7 +55,7 @@ public async Task ExecuteAsync( throw new InvalidOperationException($"Command should not start with module name '{_testHostModuleName}'."); } - environmentVariables ??= new Dictionary(); + environmentVariables ??= new Dictionary(); if (disableTelemetry) { @@ -71,7 +71,11 @@ public async Task ExecuteAsync( continue; } - environmentVariables.Add(key!, entry.Value!.ToString()!); + // We use TryAdd to let tests "overwrite" existing environment variables. + // Consider that the given dictionary has "TESTINGPLATFORM_UI_LANGUAGE" as a key. + // And also Environment.GetEnvironmentVariables() is returning TESTINGPLATFORM_UI_LANGUAGE. + // In that case, we do a "TryAdd" which effectively means the value from the original dictionary wins. + environmentVariables.TryAdd(key!, entry!.Value!.ToString()!); } // Define DOTNET_ROOT to point to the dotnet we install for this repository, to avoid