diff --git a/src/libraries/System.Diagnostics.Process/tests/AssemblyInfo.cs b/src/libraries/System.Diagnostics.Process/tests/AssemblyInfo.cs index 80471364814581..f15a1cb605513a 100644 --- a/src/libraries/System.Diagnostics.Process/tests/AssemblyInfo.cs +++ b/src/libraries/System.Diagnostics.Process/tests/AssemblyInfo.cs @@ -3,8 +3,10 @@ using Xunit; -// Process tests can conflict with each other, as they modify ambient state -// like the console code page and environment variables +#if TARGET_OSX +// Retain serialization until process-management hangs under concurrent test classes on macOS are resolved. +// https://github.com/dotnet/runtime/issues/135294 [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] +#endif [assembly: SkipOnPlatform(TestPlatforms.Browser, "System.Diagnostics.Process is not supported on Browser.")] diff --git a/src/libraries/System.Diagnostics.Process/tests/Interop.cs b/src/libraries/System.Diagnostics.Process/tests/Interop.cs index 220f8495532a4b..c1109e98d80223 100644 --- a/src/libraries/System.Diagnostics.Process/tests/Interop.cs +++ b/src/libraries/System.Diagnostics.Process/tests/Interop.cs @@ -98,12 +98,18 @@ public struct SID_AND_ATTRIBUTES [DllImport("kernel32.dll")] internal static extern int GetConsoleOutputCP(); - [DllImport("kernel32.dll")] + [DllImport("kernel32.dll", SetLastError = true)] internal static extern int SetConsoleCP(int codePage); - [DllImport("kernel32.dll")] + [DllImport("kernel32.dll", SetLastError = true)] internal static extern int SetConsoleOutputCP(int codePage); + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern int FreeConsole(); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern int AllocConsole(); + [DllImport("advapi32.dll")] internal static extern bool OpenProcessToken(SafeProcessHandle ProcessHandle, uint DesiredAccess, out SafeProcessHandle TokenHandle); diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessStandardConsoleTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessStandardConsoleTests.cs index d89f4909505e35..ada396358371dc 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessStandardConsoleTests.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessStandardConsoleTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.ComponentModel; using System.IO; using System.IO.Pipes; using System.Text; @@ -18,22 +19,6 @@ public void TestChangesInConsoleEncoding() { const int ConsoleEncoding = 437; - void RunWithExpectedCodePage(int expectedCodePage) - { - Process p = CreateProcessLong(); - p.StartInfo.RedirectStandardInput = true; - p.StartInfo.RedirectStandardOutput = true; - p.StartInfo.RedirectStandardError = true; - p.Start(); - - Assert.Equal(expectedCodePage, p.StandardInput.Encoding.CodePage); - Assert.Equal(expectedCodePage, p.StandardOutput.CurrentEncoding.CodePage); - Assert.Equal(expectedCodePage, p.StandardError.CurrentEncoding.CodePage); - - p.Kill(); - Assert.True(p.WaitForExit(WaitInMS)); - }; - // Don't test this on Windows containers, as there is a known issue. // See https://github.com/dotnet/runtime/issues/42000 for more details. if (!OperatingSystem.IsWindows() || PlatformDetection.IsInContainer) @@ -42,21 +27,41 @@ void RunWithExpectedCodePage(int expectedCodePage) return; } - int inputEncoding = Interop.GetConsoleCP(); - int outputEncoding = Interop.GetConsoleOutputCP(); - - try + RemoteExecutor.Invoke(static () => { - Interop.SetConsoleCP(ConsoleEncoding); - Interop.SetConsoleOutputCP(ConsoleEncoding); + // Remote processes inherit the runner's console; allocate a private one before changing its code pages. + if (Interop.FreeConsole() == 0) + { + throw new Win32Exception(); + } + if (Interop.AllocConsole() == 0) + { + throw new Win32Exception(); + } + if (Interop.SetConsoleCP(ConsoleEncoding) == 0 || Interop.SetConsoleOutputCP(ConsoleEncoding) == 0) + { + throw new Win32Exception(); + } - RunWithExpectedCodePage(ConsoleEncoding); - } - finally - { - Interop.SetConsoleCP(inputEncoding); - Interop.SetConsoleOutputCP(outputEncoding); - } + using var tests = new ProcessStandardConsoleTests(); + tests.RunWithExpectedCodePage(ConsoleEncoding); + }).Dispose(); + } + + private void RunWithExpectedCodePage(int expectedCodePage) + { + Process p = CreateProcessLong(); + p.StartInfo.RedirectStandardInput = true; + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.RedirectStandardError = true; + p.Start(); + + Assert.Equal(expectedCodePage, p.StandardInput.Encoding.CodePage); + Assert.Equal(expectedCodePage, p.StandardOutput.CurrentEncoding.CodePage); + Assert.Equal(expectedCodePage, p.StandardError.CurrentEncoding.CodePage); + + p.Kill(); + Assert.True(p.WaitForExit(WaitInMS)); } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessStartInfoTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessStartInfoTests.cs index a187c49c733c4b..696a36ef4d8dd0 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessStartInfoTests.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessStartInfoTests.cs @@ -235,35 +235,41 @@ public void EnvironmentVariableContainingNull_ThrowsArgumentException() [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void TestSetEnvironmentOnChildProcess() { - const string name = "b5a715d3-d74f-465d-abb7-2abe844750c9"; - Environment.SetEnvironmentVariable(name, "parent-process-value"); - - Process p = CreateProcess(() => + RemoteExecutor.Invoke(static () => { - if (Environment.GetEnvironmentVariable(name) != "child-process-value") - return 1; + using var tests = new ProcessStartInfoTests(); + const string name = "b5a715d3-d74f-465d-abb7-2abe844750c9"; + Environment.SetEnvironmentVariable(name, "parent-process-value"); - return RemoteExecutor.SuccessExitCode; - }); - p.StartInfo.Environment.Add(name, "child-process-value"); - p.Start(); + Process p = tests.CreateProcess(() => + { + if (Environment.GetEnvironmentVariable(name) != "child-process-value") + return 1; - Assert.True(p.WaitForExit(WaitInMS)); - Assert.Equal(RemoteExecutor.SuccessExitCode, p.ExitCode); + return RemoteExecutor.SuccessExitCode; + }); + p.StartInfo.Environment.Add(name, "child-process-value"); + p.Start(); + + Assert.True(p.WaitForExit(WaitInMS)); + Assert.Equal(RemoteExecutor.SuccessExitCode, p.ExitCode); + }).Dispose(); } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void TestEnvironmentOfChildProcess() { - const string ExtraEnvVar = "TestEnvironmentOfChildProcess_SpecialStuff"; - Environment.SetEnvironmentVariable(ExtraEnvVar, "\x1234" + Environment.NewLine + "\x5678"); // ensure some Unicode characters and newlines are in the output - const string EmptyEnvVar = "TestEnvironmentOfChildProcess_Empty"; - Environment.SetEnvironmentVariable(EmptyEnvVar, ""); - try + RemoteExecutor.Invoke(static () => { + using var tests = new ProcessStartInfoTests(); + const string ExtraEnvVar = "TestEnvironmentOfChildProcess_SpecialStuff"; + Environment.SetEnvironmentVariable(ExtraEnvVar, "\x1234" + Environment.NewLine + "\x5678"); // ensure some Unicode characters and newlines are in the output + const string EmptyEnvVar = "TestEnvironmentOfChildProcess_Empty"; + Environment.SetEnvironmentVariable(EmptyEnvVar, ""); + // Schedule a process to see what env vars it gets. Have it write out those variables // to its output stream so we can read them. - Process p = CreateProcess(() => + Process p = tests.CreateProcess(() => { Console.Write(string.Join(ItemSeparator, Environment.GetEnvironmentVariables().Cast().Select(e => Convert.ToBase64String(Encoding.UTF8.GetBytes(e.Key + "=" + e.Value))))); return RemoteExecutor.SuccessExitCode; @@ -294,22 +300,19 @@ public void TestEnvironmentOfChildProcess() string.Join(", ", envEnv.Except(actualEnv)), Environment.NewLine, string.Join(", ", actualEnv.Except(envEnv)))); - } - finally - { - Environment.SetEnvironmentVariable(ExtraEnvVar, null); - Environment.SetEnvironmentVariable(EmptyEnvVar, null); - } + }).Dispose(); } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void EnvironmentNullValue() { - const string NullEnvVar = "TestEnvironmentOfChildProcess_Null"; - Environment.SetEnvironmentVariable(NullEnvVar, ""); - try + RemoteExecutor.Invoke(static () => { - Process p = CreateProcess(() => + using var tests = new ProcessStartInfoTests(); + const string NullEnvVar = "TestEnvironmentOfChildProcess_Null"; + Environment.SetEnvironmentVariable(NullEnvVar, ""); + + Process p = tests.CreateProcess(() => { // Verify that setting the value to null in StartInfo is going to remove the process environment. Assert.Null(Environment.GetEnvironmentVariable(NullEnvVar)); @@ -319,11 +322,7 @@ public void EnvironmentNullValue() Assert.Null(p.StartInfo.Environment[NullEnvVar]); p.Start(); Assert.True(p.WaitForExit(WaitInMS)); - } - finally - { - Environment.SetEnvironmentVariable(NullEnvVar, null); - } + }).Dispose(); } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs index c54b1fac83e955..f916cedb732608 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs @@ -93,35 +93,27 @@ public void ProcessStart_UseShellExecute_OnUnix_ThrowsWhenNoOpenerOnPath() }, options).Dispose(); } - [Fact] + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] [OuterLoop("Opens program")] [SkipOnPlatform(TestPlatforms.MacCatalyst, "In App Sandbox mode, the process doesn't have read access to the binary.")] [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.Android | TestPlatforms.Browser, "Not supported on iOS/tvOS/Android/Browser.")] public void ProcessStart_DirectoryNameInCurDirectorySameAsFileNameInExecDirectory_Success() { - string fileToOpen = "dotnet"; - string curDir = Environment.CurrentDirectory; - string dotnetFolder = Path.Combine(Path.GetTempPath(),"dotnet"); - bool shouldDelete = !Directory.Exists(dotnetFolder); - try + Directory.CreateDirectory(Path.Combine(TestDirectory, "dotnet")); + + RemoteExecutor.Invoke(StartDotnet, new RemoteInvokeOptions { - Directory.SetCurrentDirectory(Path.GetTempPath()); - Directory.CreateDirectory(dotnetFolder); + StartInfo = new ProcessStartInfo { WorkingDirectory = TestDirectory } + }).Dispose(); + static void StartDotnet() + { + string fileToOpen = "dotnet"; using (var px = Process.Start(fileToOpen)) { Assert.NotNull(px); } } - finally - { - if (shouldDelete) - { - Directory.Delete(dotnetFolder); - } - - Directory.SetCurrentDirectory(curDir); - } } [Fact] diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Windows.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Windows.cs index 4d45040d893400..4f8ccf876862b9 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Windows.cs @@ -123,6 +123,11 @@ private static unsafe void ReEnableCtrlCHandlerIfNeeded(PosixSignal signal) [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void Kill_EntireProcessTree_MinimalExceptions() + { + RemoteExecutor.Invoke(KillEntireProcessTreeMinimalExceptions).Dispose(); + } + + private static void KillEntireProcessTreeMinimalExceptions() { // This test validates that Kill(true) doesn't throw excessive exceptions internally // during process enumeration, which causes severe performance degradation with debugger attached. diff --git a/src/libraries/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests.csproj b/src/libraries/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests.csproj index 100ff174567dab..8a00172f4ddee7 100644 --- a/src/libraries/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests.csproj +++ b/src/libraries/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests.csproj @@ -10,6 +10,7 @@ $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) $(DefineConstants);TargetsWindows + $(DefineConstants);TARGET_OSX