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 @@ -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.")]
10 changes: 8 additions & 2 deletions src/libraries/System.Diagnostics.Process/tests/Interop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)
Expand All @@ -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))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<DictionaryEntry>().Select(e => Convert.ToBase64String(Encoding.UTF8.GetBytes(e.Key + "=" + e.Value)))));
return RemoteExecutor.SuccessExitCode;
Expand Down Expand Up @@ -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));
Expand All @@ -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))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<PropertyGroup>
<TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier>
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'windows'">$(DefineConstants);TargetsWindows</DefineConstants>
<DefineConstants Condition="'$(TargetOS)' == 'osx' and '$(TargetPlatformIdentifier)' == 'unix'">$(DefineConstants);TARGET_OSX</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(CommonPath)System\IO\StringParser.cs"
Expand Down
Loading