Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 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 @@ -6,5 +6,6 @@
// Process tests can conflict with each other, as they modify ambient state
// like the console code page and environment variables
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]
[assembly: System.Diagnostics.Tests.ProcessTestHangDiagnosticsAttribute]

Comment on lines 8 to 10
[assembly: SkipOnPlatform(TestPlatforms.Browser, "System.Diagnostics.Process is not supported on Browser.")]
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,7 @@ private static string GetAssociationDetails()
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsWindowsNanoServer))]
public void ShellExecute_Nano_Fails_Start()
{
ProcessTestHangDiagnostics.Log("ShellExecute_Nano_Fails_Start started.");
string tempFile = GetTestFilePath() + ".txt";
File.Create(tempFile).Dispose();

Expand All @@ -1250,7 +1251,9 @@ public void ShellExecute_Nano_Fails_Start()
// Nano does not support either the STA apartment or ShellExecute.
// Since we try to start an STA thread for ShellExecute, we hit a ThreadStartException
// before we get to the PlatformNotSupportedException.
ProcessTestHangDiagnostics.Log("ShellExecute_Nano_Fails_Start calling Process.Start.");
Assert.Throws<ThreadStartException>(() => Process.Start(info));
ProcessTestHangDiagnostics.Log("ShellExecute_Nano_Fails_Start completed Process.Start.");
}

public static TheoryData<bool> UseShellExecute
Expand Down Expand Up @@ -1295,7 +1298,8 @@ public void StartInfo_BadVerb(bool useShellExecute)
public void StartInfo_BadExe(bool useShellExecute)
{
string tempFile = GetTestFilePath() + ".exe";
File.Create(tempFile).Dispose();
// A DLL is a valid PE that cannot be executed, avoiding malformed-image shell recovery paths.
File.Copy(Path.Combine(Environment.SystemDirectory, "kernel32.dll"), tempFile);

ProcessStartInfo info = new ProcessStartInfo
{
Expand Down Expand Up @@ -1359,6 +1363,7 @@ public void InitializeWithArgumentList_ThrowsArgumentNullException()
[ActiveIssue("https://github.com/dotnet/runtime/issues/34685", TestRuntimes.Mono)]
public void StartInfo_NotepadWithContent_withArgumentList(bool useShellExecute)
{
ProcessTestHangDiagnostics.Log($"StartInfo_NotepadWithContent_withArgumentList started; UseShellExecute={useShellExecute}.");
string tempFile = GetTestFilePath() + ".txt";
File.WriteAllText(tempFile, $"StartInfo_NotepadWithContent({useShellExecute})");

Expand All @@ -1372,8 +1377,10 @@ public void StartInfo_NotepadWithContent_withArgumentList(bool useShellExecute)

info.ArgumentList.Add(tempFile);

ProcessTestHangDiagnostics.Log($"StartInfo_NotepadWithContent_withArgumentList calling Process.Start; UseShellExecute={useShellExecute}.");
using (var process = Process.Start(info))
{
ProcessTestHangDiagnostics.Log($"StartInfo_NotepadWithContent_withArgumentList completed Process.Start; UseShellExecute={useShellExecute}; ProcessId={process?.Id}.");
Assert.True(process != null, $"Could not start {info.FileName} {info.Arguments} UseShellExecute={info.UseShellExecute}");

try
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading;
using Microsoft.Win32;
using Xunit.Sdk;

namespace System.Diagnostics.Tests
{
internal sealed class ProcessTestHangDiagnosticsAttribute : BeforeAfterTestAttribute
{
public override void Before(MethodInfo methodUnderTest)
{
ProcessTestHangDiagnostics.Log($"Starting {methodUnderTest.DeclaringType?.FullName}.{methodUnderTest.Name}.");
}

public override void After(MethodInfo methodUnderTest)
{
ProcessTestHangDiagnostics.Log($"Finished {methodUnderTest.DeclaringType?.FullName}.{methodUnderTest.Name}.");
}
}

internal static class ProcessTestHangDiagnostics
{
#if TargetsWindows
private const string InstallationTypeKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion";
private static readonly TimeSpan WatchdogTimeout = TimeSpan.FromMinutes(3);
private static readonly TextWriter s_log = TextWriter.Synchronized(
new StreamWriter(Console.OpenStandardError(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), bufferSize: 1024, leaveOpen: true) { AutoFlush = true });

[ModuleInitializer]
internal static void Initialize()
{
Log($"ProcessPath={Environment.ProcessPath}; OSVersion={Environment.OSVersion.Version}; Framework={RuntimeInformation.FrameworkDescription}");
ConfigureWindowsErrorReporting();

var watchdog = new Thread(Watchdog)
{
IsBackground = true,
Name = "Process tests hang watchdog"
};
watchdog.Start();
Comment on lines +41 to +48

Log("Reading Windows InstallationType.");
object? installationType = Registry.GetValue(InstallationTypeKey, "InstallationType", defaultValue: null);
Log($"InstallationType={installationType ?? "<null>"}");

Log("Evaluating PlatformDetection.IsWindowsNanoServer and IsWindowsServerCore.");
bool isWindowsNanoServer = PlatformDetection.IsWindowsNanoServer;
bool isWindowsServerCore = PlatformDetection.IsWindowsServerCore;
Log($"IsWindowsNanoServer={isWindowsNanoServer}; IsWindowsServerCore={isWindowsServerCore}");
Comment on lines +50 to +57
}

internal static void Log(string message)
{
s_log.WriteLine($"[Process test hang diagnostics] {message}");
}

private static void ConfigureWindowsErrorReporting()
{
string? dumpFolder = Environment.GetEnvironmentVariable("HELIX_DUMP_FOLDER");
string? uploadFolder = Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT");
string? werDumpFolder = uploadFolder ?? dumpFolder;
string? processPath = Environment.ProcessPath;
if (string.IsNullOrEmpty(werDumpFolder) || string.IsNullOrEmpty(processPath))
{
Log($"WER LocalDumps not configured; HELIX_WORKITEM_UPLOAD_ROOT={uploadFolder ?? "<null>"}; HELIX_DUMP_FOLDER={dumpFolder ?? "<null>"}.");
return;
}

string executableName = Path.GetFileName(processPath);
string keyPath = $@"SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\{executableName}";

try
{
using RegistryKey? key = Registry.LocalMachine.CreateSubKey(keyPath);
if (key is null)
{
Log($"Unable to create WER LocalDumps key HKLM\\{keyPath}.");
return;
}

key.SetValue("DumpCount", 2, RegistryValueKind.DWord);
key.SetValue("DumpFolder", werDumpFolder, RegistryValueKind.ExpandString);
key.SetValue("DumpType", 2, RegistryValueKind.DWord);
Log($"WER LocalDumps configured for {executableName} in {werDumpFolder}.");
}
catch (Exception e) when (e is IOException or SecurityException or UnauthorizedAccessException)
{
Log($"WER LocalDumps configuration failed: {e}");
}
}

private static void Watchdog()
{
Thread.Sleep(WatchdogTimeout);
const string message = "System.Diagnostics.Process.Tests exceeded the diagnostic watchdog timeout.";
Log(message);
Environment.FailFast(message);
}
#else
internal static void Log(string message)
{
}
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
<TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier>
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'windows'">$(DefineConstants);TargetsWindows</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetPlatformIdentifier)' == 'windows'">
<TestDisableParallelization>true</TestDisableParallelization>
<XUnitOptions>$(XUnitOptions) -class System.Diagnostics.Tests.ProcessStartInfoTests -parallel none</XUnitOptions>
<XUnitShowProgress>true</XUnitShowProgress>
</PropertyGroup>
Comment on lines +14 to +18
<ItemGroup>
<Compile Include="$(CommonPath)System\IO\StringParser.cs"
Link="Common\System\IO\StringParser.cs" />
Expand All @@ -33,6 +38,7 @@
<Compile Include="ProcessStreamReadTests.cs" />
<Compile Include="ProcessMultiplexingTests.cs" />
<Compile Include="ProcessStreamingTests.cs" />
<Compile Include="ProcessTestHangDiagnostics.cs" />
<Compile Include="ProcessTestBase.cs" />
<Compile Include="ProcessTestBase.NonUap.cs" />
<Compile Include="ProcessTests.cs" />
Expand Down
Loading