From dfc8dff766dfb7a272071deb3a8231423411e5a5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:01:13 +0000 Subject: [PATCH 1/2] Remove ProcessStartOptions type, update ref, src and tests Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../ref/System.Diagnostics.Process.cs | 12 +- .../src/Resources/Strings.resx | 4 +- .../src/System.Diagnostics.Process.csproj | 2 +- .../System/Diagnostics/ProcessStartOptions.cs | 255 ------------------ .../tests/ProcessStartOptionsTests.Unix.cs | 169 ------------ .../tests/ProcessStartOptionsTests.Windows.cs | 199 -------------- .../tests/ProcessStartOptionsTests.cs | 179 ------------ .../System.Diagnostics.Process.Tests.csproj | 6 +- 8 files changed, 6 insertions(+), 820 deletions(-) delete mode 100644 src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessStartOptions.cs delete mode 100644 src/libraries/System.Diagnostics.Process/tests/ProcessStartOptionsTests.Unix.cs delete mode 100644 src/libraries/System.Diagnostics.Process/tests/ProcessStartOptionsTests.Windows.cs delete mode 100644 src/libraries/System.Diagnostics.Process/tests/ProcessStartOptionsTests.cs diff --git a/src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs b/src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs index e05e8c2867db5f..7afcecb2379b72 100644 --- a/src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs +++ b/src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs @@ -268,17 +268,7 @@ public ProcessStartInfo(string fileName, System.Collections.Generic.IEnumerable< [System.Diagnostics.CodeAnalysis.AllowNullAttribute] public string WorkingDirectory { get { throw null; } set { } } } - public sealed partial class ProcessStartOptions - { - public ProcessStartOptions(string fileName) { } - public System.Collections.Generic.IList Arguments { get { throw null; } set { } } - public bool CreateNewProcessGroup { get { throw null; } set { } } - public System.Collections.Generic.IDictionary Environment { get { throw null; } } - public string FileName { get { throw null; } } - public System.Collections.Generic.IList InheritedHandles { get { throw null; } set { } } - public bool KillOnParentExit { get { throw null; } set { } } - public string? WorkingDirectory { get { throw null; } set { } } - } + [System.ComponentModel.DesignerAttribute("System.Diagnostics.Design.ProcessThreadDesigner, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] public partial class ProcessThread : System.ComponentModel.Component { diff --git a/src/libraries/System.Diagnostics.Process/src/Resources/Strings.resx b/src/libraries/System.Diagnostics.Process/src/Resources/Strings.resx index 67d05840e9796b..06e9e38b8eac45 100644 --- a/src/libraries/System.Diagnostics.Process/src/Resources/Strings.resx +++ b/src/libraries/System.Diagnostics.Process/src/Resources/Strings.resx @@ -336,7 +336,5 @@ Invalid performance counter data with type '{0}'. - - Could not resolve the file. - + \ No newline at end of file diff --git a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index 1a4cb17a51ccb4..112087107cc364 100644 --- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessStartOptions.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessStartOptions.cs deleted file mode 100644 index acac5b0c546375..00000000000000 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessStartOptions.cs +++ /dev/null @@ -1,255 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; - -namespace System.Diagnostics -{ - /// - /// Specifies options for starting a new process. - /// - public sealed class ProcessStartOptions - { - private readonly string _fileName; - private IList? _arguments; - private Dictionary? _environment; - private IList? _inheritedHandles; - - /// - /// Gets the absolute path of the application to start. - /// - /// - /// The absolute path to the executable file. This path is resolved from the fileName parameter - /// passed to the constructor by searching through various directories if needed. - /// - /// - /// - /// The path is "resolved" meaning it has been converted to an absolute path and verified to exist. - /// - /// - /// See for complete details on the resolution process. - /// - /// - public string FileName => _fileName; - - /// - /// Gets or sets the command-line arguments to pass to the application. - /// - public IList Arguments - { - get => _arguments ??= new List(); - set - { - ArgumentNullException.ThrowIfNull(value); - _arguments = value; - } - } - - /// - /// Gets the environment variables that apply to this process and its child processes. - /// - /// - /// By default, the environment is a copy of the current process environment. - /// - public IDictionary Environment - { - get - { - if (_environment == null) - { - IDictionary envVars = System.Environment.GetEnvironmentVariables(); - - _environment = new Dictionary( - envVars.Count, - OperatingSystem.IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); - - // Manual use of IDictionaryEnumerator instead of foreach to avoid DictionaryEntry box allocations. - IDictionaryEnumerator e = envVars.GetEnumerator(); - Debug.Assert(!(e is IDisposable), "Environment.GetEnvironmentVariables should not be IDisposable."); - while (e.MoveNext()) - { - DictionaryEntry entry = e.Entry; - _environment.Add((string)entry.Key, (string?)entry.Value); - } - } - return _environment; - } - } - - /// - /// Gets or sets the working directory for the process to be started. - /// - public string? WorkingDirectory { get; set; } - - /// - /// Gets a list of handles that will be inherited by the child process. - /// - /// - /// - /// Handles do not need to have inheritance enabled beforehand. - /// They are also not duplicated, just added as-is to the child process - /// so the exact same handle values can be used in the child process. - /// - /// - /// On Windows, the implementation will automatically enable inheritance on any handle added to this list - /// by modifying the handle's flags using SetHandleInformation. - /// - /// - /// On Unix, the implementation will modify the copy of every handle in the child process - /// by removing FD_CLOEXEC flag. It happens after the fork and before the exec, so it does not affect parent process. - /// - /// - public IList InheritedHandles - { - get => _inheritedHandles ??= new List(); - set - { - ArgumentNullException.ThrowIfNull(value); - _inheritedHandles = value; - } - } - - /// - /// Gets or sets a value indicating whether the child process should be terminated when the parent process exits. - /// - public bool KillOnParentExit { get; set; } - - /// - /// Gets or sets a value indicating whether to create the process in a new process group. - /// - /// - /// - /// Creating a new process group enables sending signals to the process (e.g., SIGINT, SIGQUIT) - /// on Windows and provides process group isolation on all platforms. - /// - /// - /// On Unix systems, child processes in a new process group won't receive signals sent to the parent's - /// process group, which can be useful for background processes that should continue running independently. - /// - /// - public bool CreateNewProcessGroup { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The application to start. - /// is . - /// is empty. - /// cannot be resolved to an existing file. - /// - /// - /// The is resolved to an absolute path. - /// - /// - /// When the is a fully qualified path, it is used as-is without any resolution. - /// - /// - /// When the is a rooted but not fully qualified path (for example, C:foo.exe or \foo\bar.exe on Windows), - /// it is resolved to an absolute path using the current directory context. - /// - /// - /// When the is an explicit relative path containing directory separators (for example, .\foo.exe or ../bar), - /// it is resolved relative to the current directory. - /// - /// - /// When the is a bare filename without directory separators, the system searches for the executable in the following locations: - /// - /// - /// On Windows: - /// - /// - /// The System directory (for example, C:\Windows\System32). - /// The directories listed in the PATH environment variable. - /// - /// - /// On Unix: - /// - /// - /// The directories listed in the PATH environment variable. - /// - /// - /// On Windows, if the does not have an extension and does not contain directory separators, .exe is appended before searching. - /// - /// - public ProcessStartOptions(string fileName) - { - ArgumentException.ThrowIfNullOrEmpty(fileName); - - // The file could be deleted or replaced after this check and before the process is started (TOCTOU). - // In such case, the process creation will fail. - // We resolve the path here to provide unified error handling and to avoid - // starting a process that will fail immediately after creation. - string? resolved = ResolvePath(fileName, out bool requiresExistenceCheck); - if (resolved is null || (requiresExistenceCheck && !File.Exists(resolved))) - { - throw new FileNotFoundException(SR.FileNotFoundResolvePath, fileName); - } - _fileName = resolved; - } - - // There are two ways to create a process on Windows using CreateProcess sys-call: - // 1. With NULL lpApplicationName and non-NULL lpCommandLine, where the first token of the - // command line is the executable name. In this case, the system will resolve the executable - // name to an actual file on disk using an algorithm that is not fully documented. - // 2. With non-NULL lpApplicationName, where the system will use the provided application - // name as-is without any resolution, and the command line is passed as-is to the process. - // - // The recommended way is to use the second approach and provide the resolved executable path. - // - // Changing the resolution logic for existing Process APIs would introduce breaking changes. - // Since we are introducing a new API, we take it as an opportunity to clean up the legacy baggage - // to have simpler, easier to understand and more secure filename resolution algorithm - // that is more consistent across OSes and aligned with other modern platforms. - private static string? ResolvePath(string filename, out bool requiresExistenceCheck) - { - Debug.Assert(!string.IsNullOrEmpty(filename), "Caller should have validated the filename."); - requiresExistenceCheck = true; - - if (Path.IsPathFullyQualified(filename)) - { - return filename; - } - - // Check for filenames that are not bare filenames. It includes: - // - Relative paths with directory separators (e.g., .\foo.exe, ..\foo.exe, subdir\foo.exe) - // - Rooted but not fully qualified paths (e.g., C:foo.exe, \foo.exe on Windows) - if (Path.GetFileName(filename.AsSpan()).Length != filename.Length) - { - return Path.GetFullPath(filename); // Resolve to absolute path - } - - // We want to keep the resolution logic in one place for better maintainability and consistency. - // That is why we don't provide platform-specific implementations files. - if (OperatingSystem.IsWindows()) - { - // From: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw - // "If the file name does not contain an extension, .exe is appended. - // Therefore, if the file name extension is .com, this parameter must include the .com extension. - // If the file name ends in a period (.) with no extension, or if the file name contains a path, .exe is not appended." - - // HasExtension returns false for trailing dot, so we need to check that separately - if (filename[filename.Length - 1] != '.' && !Path.HasExtension(filename)) - { - filename += ".exe"; - } - - // Windows-specific search location: the system directory (e.g., C:\Windows\System32) - string path = Path.Combine(System.Environment.SystemDirectory, filename); - if (File.Exists(path)) - { - requiresExistenceCheck = false; - return path; - } - } - - string? fromPath = ProcessUtils.FindProgramInPath(filename); - requiresExistenceCheck = fromPath is null; - return fromPath; - } - } -} diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessStartOptionsTests.Unix.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessStartOptionsTests.Unix.cs deleted file mode 100644 index a0c5a3b2325262..00000000000000 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessStartOptionsTests.Unix.cs +++ /dev/null @@ -1,169 +0,0 @@ -// 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 Xunit; - -namespace System.Diagnostics.Tests -{ - [PlatformSpecific(TestPlatforms.Linux | TestPlatforms.FreeBSD | TestPlatforms.OSX)] - public partial class ProcessStartOptionsTests - { - [Fact] - public void Constructor_ResolvesShOnUnix() - { - ProcessStartOptions options = new("sh"); - Assert.True(File.Exists(options.FileName)); - // Verify the resolved path ends with "sh" (could be /bin/sh, /usr/bin/sh, etc.) - Assert.EndsWith("sh", options.FileName); - } - - [Fact] - public void ResolvePath_FindsInPath() - { - // sh should be findable in PATH on all Unix systems - ProcessStartOptions options = new("sh"); - Assert.True(File.Exists(options.FileName)); - // Verify the resolved path ends with "sh" (could be /bin/sh, /usr/bin/sh, etc.) - Assert.EndsWith("sh", options.FileName); - } - - [Fact] - public void ResolvePath_DoesNotAddExeExtension() - { - // On Unix, no .exe extension should be added - ProcessStartOptions options = new("sh"); - Assert.False(options.FileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)); - } - - [Theory] - [InlineData("./testscript.sh", true)] - [InlineData("testscript.sh", false)] - public void ResolvePath_UsesCurrentDirectory(string fileNameFormat, bool shouldSucceed) - { - string tempDir = Path.GetTempPath(); - string fileName = "testscript.sh"; - string fullPath = Path.Combine(tempDir, fileName); - - string oldDir = Directory.GetCurrentDirectory(); - try - { - File.WriteAllText(fullPath, "#!/bin/sh\necho test"); - // Make it executable - File.SetUnixFileMode(fullPath, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); - - Directory.SetCurrentDirectory(tempDir); - - if (shouldSucceed) - { - ProcessStartOptions options = new(fileNameFormat); - Assert.True(File.Exists(options.FileName)); - // on macOS, we need to handle /tmp/testscript.sh -> /private/tmp/testscript.sh - Assert.EndsWith(fullPath, options.FileName); - } - else - { - // Without ./ prefix, should not find file in CWD and should throw - Assert.Throws(() => new ProcessStartOptions(fileNameFormat)); - } - } - finally - { - Directory.SetCurrentDirectory(oldDir); - if (File.Exists(fullPath)) - { - File.Delete(fullPath); - } - } - } - - [Fact] - public void ResolvePath_PathSeparatorIsColon() - { - // Create a temp directory and file - string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - Directory.CreateDirectory(tempDir); - string fileName = "testscript"; - string fullPath = Path.Combine(tempDir, fileName); - - string oldPath = Environment.GetEnvironmentVariable("PATH"); - try - { - File.WriteAllText(fullPath, "#!/bin/sh\necho test"); - // Make it executable - File.SetUnixFileMode(fullPath, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); - - // Add temp directory to PATH using colon separator - Environment.SetEnvironmentVariable("PATH", tempDir + ":" + oldPath); - ProcessStartOptions options = new(fileName); - Assert.Equal(Path.GetFullPath(fullPath), options.FileName); - } - finally - { - Environment.SetEnvironmentVariable("PATH", oldPath); - if (File.Exists(fullPath)) - { - File.Delete(fullPath); - } - if (Directory.Exists(tempDir)) - { - Directory.Delete(tempDir, recursive: true); - } - } - } - - [Fact] - public void ResolvePath_AbsolutePathIsNotModified() - { - string tempFile = Path.GetTempFileName(); - try - { - ProcessStartOptions options = new(tempFile); - Assert.Equal(tempFile, options.FileName); - } - finally - { - if (File.Exists(tempFile)) - { - File.Delete(tempFile); - } - } - } - - [Theory] - [InlineData("ls")] - [InlineData("cat")] - [InlineData("echo")] - [InlineData("sh")] - public void ResolvePath_FindsCommonUtilities(string utilName) - { - ProcessStartOptions options = new(utilName); - Assert.True(File.Exists(options.FileName), $"{utilName} should be found and exist"); - Assert.EndsWith(utilName, options.FileName); - } - - [Fact] - public void ResolvePath_RejectsDirectories() - { - // Create a directory with executable permissions - string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - Directory.CreateDirectory(tempDir); - - string oldDir = Directory.GetCurrentDirectory(); - try - { - // Try to use the directory name as a command - Directory.SetCurrentDirectory(Path.GetTempPath()); - Assert.Throws(() => new ProcessStartOptions(Path.GetFileName(tempDir))); - } - finally - { - Directory.SetCurrentDirectory(oldDir); - if (Directory.Exists(tempDir)) - { - Directory.Delete(tempDir); - } - } - } - } -} diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessStartOptionsTests.Windows.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessStartOptionsTests.Windows.cs deleted file mode 100644 index df2dabdae7624d..00000000000000 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessStartOptionsTests.Windows.cs +++ /dev/null @@ -1,199 +0,0 @@ -// 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 Xunit; - -namespace System.Diagnostics.Tests -{ - public partial class ProcessStartOptionsTests - { - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] - public void Constructor_ResolvesCmdOnWindows() - { - ProcessStartOptions options = new("cmd"); - Assert.EndsWith("cmd.exe", options.FileName); - Assert.True(File.Exists(options.FileName)); - } - - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer), nameof(PlatformDetection.IsNotWindowsServerCore))] - public void ResolvePath_AddsExeExtension() - { - // Test that .exe is appended when no extension is provided - ProcessStartOptions options = new("notepad"); - Assert.EndsWith(".exe", options.FileName, StringComparison.OrdinalIgnoreCase); - Assert.True(File.Exists(options.FileName)); - } - - [Fact] - public void ResolvePath_DoesNotAddExeExtensionForTrailingDot() - { - // "If the file name ends in a period (.) with no extension, .exe is not appended." - // This should fail since "notepad." won't exist - Assert.Throws(() => new ProcessStartOptions("notepad.")); - } - - [Fact] - public void ResolvePath_PreservesComExtension() - { - // The .com extension should be preserved - string fileName = "test.com"; - string tempDir = Path.GetTempPath(); - string fullPath = Path.Combine(tempDir, fileName); - - string oldDir = Directory.GetCurrentDirectory(); - try - { - File.WriteAllText(fullPath, "test"); - Directory.SetCurrentDirectory(tempDir); - ProcessStartOptions options = new($".\\{fileName}"); - Assert.EndsWith(".com", options.FileName, StringComparison.Ordinal); - } - finally - { - Directory.SetCurrentDirectory(oldDir); - if (File.Exists(fullPath)) - { - File.Delete(fullPath); - } - } - } - - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] - public void ResolvePath_FindsInSystemDirectory() - { - // cmd.exe should be found in system directory - ProcessStartOptions options = new("cmd"); - Assert.True(File.Exists(options.FileName)); - string expectedPath = Path.Combine(Environment.SystemDirectory, "cmd.exe"); - Assert.Equal(expectedPath, options.FileName); - } - - [Theory] - [InlineData(".\\testapp.exe", true)] - [InlineData("testapp.exe", false)] - public void ResolvePath_UsesCurrentDirectory(string fileNameFormat, bool shouldSucceed) - { - string tempDir = Path.GetTempPath(); - string fileName = "testapp.exe"; - string fullPath = Path.Combine(tempDir, fileName); - - string oldDir = Directory.GetCurrentDirectory(); - try - { - File.WriteAllText(fullPath, "test"); - Directory.SetCurrentDirectory(tempDir); - - if (shouldSucceed) - { - ProcessStartOptions options = new(fileNameFormat); - Assert.Equal(fullPath, options.FileName); - } - else - { - // Without .\ prefix, should not find file in CWD and should throw - Assert.Throws(() => new ProcessStartOptions(fileNameFormat)); - } - } - finally - { - Directory.SetCurrentDirectory(oldDir); - if (File.Exists(fullPath)) - { - File.Delete(fullPath); - } - } - } - - [Fact] - public void ResolvePath_PathSeparatorIsSemicolon() - { - // Create a temp directory and file - string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - Directory.CreateDirectory(tempDir); - string fileName = "testexe.exe"; - string fullPath = Path.Combine(tempDir, fileName); - - string oldPath = Environment.GetEnvironmentVariable("PATH"); - try - { - File.WriteAllText(fullPath, "test"); - Environment.SetEnvironmentVariable("PATH", tempDir + ";" + oldPath); - ProcessStartOptions options = new("testexe"); - Assert.Equal(fullPath, options.FileName); - } - finally - { - Environment.SetEnvironmentVariable("PATH", oldPath); - if (File.Exists(fullPath)) - { - File.Delete(fullPath); - } - if (Directory.Exists(tempDir)) - { - Directory.Delete(tempDir, recursive: true); - } - } - } - - [Fact] - public void ResolvePath_AbsolutePathIsNotModified() - { - string tempFile = Path.GetTempFileName(); - try - { - // Rename to remove extension to test that .exe is not added for absolute paths - string noExtFile = Path.ChangeExtension(tempFile, null); - File.Move(tempFile, noExtFile); - tempFile = noExtFile; - - ProcessStartOptions options = new(tempFile); - Assert.Equal(tempFile, options.FileName); - } - finally - { - if (File.Exists(tempFile)) - { - File.Delete(tempFile); - } - } - } - - [Fact] - public void ResolvePath_RootedButNotFullyQualifiedPath() - { - // Test paths like "C:foo.exe" (without backslash after colon) which are rooted but not fully qualified - // These resolve relative to the current directory on that drive - string tempDir = Path.GetTempPath(); - string fileName = "test_rooted.tmp"; - string fullPath = Path.Combine(tempDir, fileName); - - string oldDir = Directory.GetCurrentDirectory(); - try - { - File.WriteAllText(fullPath, "test"); - Directory.SetCurrentDirectory(tempDir); - - // Create a rooted but not fully qualified path: "C:filename" (no backslash after drive) - string drive = Path.GetPathRoot(tempDir)!.TrimEnd('\\', '/'); // e.g., "C:" - string rootedPath = $"{drive}{fileName}"; // e.g., "C:test_rooted.tmp" - - Assert.True(Path.IsPathRooted(rootedPath)); - Assert.False(Path.IsPathFullyQualified(rootedPath)); - - ProcessStartOptions options = new(rootedPath); - - Assert.True(Path.IsPathFullyQualified(options.FileName)); - Assert.Equal(fullPath, options.FileName); - } - finally - { - Directory.SetCurrentDirectory(oldDir); - if (File.Exists(fullPath)) - { - File.Delete(fullPath); - } - } - } - } -} diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessStartOptionsTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessStartOptionsTests.cs deleted file mode 100644 index ed202b5d22648c..00000000000000 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessStartOptionsTests.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.IO; -using System.Runtime.InteropServices; -using Xunit; - -namespace System.Diagnostics.Tests -{ - public partial class ProcessStartOptionsTests - { - [Fact] - public void Constructor_NullFileName_Throws() - { - Assert.Throws(() => new ProcessStartOptions(null)); - } - - [Fact] - public void Constructor_EmptyFileName_Throws() - { - Assert.Throws(() => new ProcessStartOptions(string.Empty)); - } - - [Fact] - public void Constructor_NonExistentFile_Throws() - { - string nonExistentFile = "ThisFileDoesNotExist_" + Guid.NewGuid().ToString(); - Assert.Throws(() => new ProcessStartOptions(nonExistentFile)); - } - - [Fact] - public void Constructor_WithAbsolutePath() - { - string tempFile = Path.GetTempFileName(); - try - { - ProcessStartOptions options = new(tempFile); - Assert.Equal(tempFile, options.FileName); - } - finally - { - File.Delete(tempFile); - } - } - - [Fact] - public void Arguments_DefaultIsEmpty() - { - ProcessStartOptions options = new(GetCurrentProcessName()); - IList args = options.Arguments; - Assert.NotNull(args); - Assert.Empty(args); - } - - [Fact] - public void Arguments_CanAddAndModify() - { - ProcessStartOptions options = new(GetCurrentProcessName()); - options.Arguments.Add("arg1"); - options.Arguments.Add("arg2"); - Assert.Equal(2, options.Arguments.Count); - Assert.Equal("arg1", options.Arguments[0]); - Assert.Equal("arg2", options.Arguments[1]); - - options.Arguments = new List { "newArg" }; - Assert.Single(options.Arguments); - Assert.Equal("newArg", options.Arguments[0]); - } - - [Fact] - public void Environment_CanAddAndModify() - { - ProcessStartOptions options = new(GetCurrentProcessName()); - IDictionary env = options.Environment; - - int originalCount = env.Count; - env["TestKey1"] = "TestValue1"; - env["TestKey2"] = "TestValue2"; - Assert.Equal(originalCount + 2, env.Count); - Assert.Equal("TestValue1", env["TestKey1"]); - Assert.Equal("TestValue2", env["TestKey2"]); - - env.Remove("TestKey1"); - Assert.Equal(originalCount + 1, env.Count); - Assert.False(env.ContainsKey("TestKey1")); - } - - [Fact] - public void Environment_CaseSensitivityIsPlatformSpecific() - { - ProcessStartOptions options = new(GetCurrentProcessName()); - IDictionary env = options.Environment; - - env["TestKey"] = "TestValue"; - - if (OperatingSystem.IsWindows()) - { - Assert.True(env.ContainsKey("testkey")); - Assert.Equal("TestValue", env["TESTKEY"]); - } - else - { - Assert.False(env.ContainsKey("testkey")); - } - } - - [Fact] - public void InheritedHandles_DefaultIsEmpty() - { - ProcessStartOptions options = new(GetCurrentProcessName()); - IList handles = options.InheritedHandles; - Assert.NotNull(handles); - Assert.Empty(handles); - } - - [Fact] - public void InheritedHandles_CanSet() - { - ProcessStartOptions options = new(GetCurrentProcessName()); - List newHandles = []; - options.InheritedHandles = newHandles; - Assert.Same(newHandles, options.InheritedHandles); - } - - [Fact] - public void WorkingDirectory_DefaultIsNull() - { - ProcessStartOptions options = new(GetCurrentProcessName()); - Assert.Null(options.WorkingDirectory); - } - - [Fact] - public void WorkingDirectory_CanSet() - { - ProcessStartOptions options = new(GetCurrentProcessName()); - string tempDir = Path.GetTempPath(); - options.WorkingDirectory = tempDir; - Assert.Equal(tempDir, options.WorkingDirectory); - } - - [Fact] - public void KillOnParentExit_DefaultIsFalse() - { - ProcessStartOptions options = new(GetCurrentProcessName()); - Assert.False(options.KillOnParentExit); - } - - [Fact] - public void KillOnParentExit_CanSet() - { - ProcessStartOptions options = new(GetCurrentProcessName()); - options.KillOnParentExit = true; - Assert.True(options.KillOnParentExit); - } - - [Fact] - public void CreateNewProcessGroup_DefaultIsFalse() - { - ProcessStartOptions options = new(GetCurrentProcessName()); - Assert.False(options.CreateNewProcessGroup); - } - - [Fact] - public void CreateNewProcessGroup_CanSet() - { - ProcessStartOptions options = new(GetCurrentProcessName()); - options.CreateNewProcessGroup = true; - Assert.True(options.CreateNewProcessGroup); - } - - private string GetCurrentProcessName() - { - return Environment.ProcessPath ?? (OperatingSystem.IsWindows() - ? Path.Combine(Environment.SystemDirectory, "cmd.exe") - : "/bin/sh"); - } - } -} 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 8b99686373df90..c123bf43275f8e 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 @@ -29,7 +29,7 @@ - + @@ -42,7 +42,7 @@ - + - + From f2eaaacc70b81c0da7ae045a2aefe3b66f5215da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:14:21 +0000 Subject: [PATCH 2/2] Clean up blank lines left from ProcessStartOptions removal Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../ref/System.Diagnostics.Process.cs | 1 - .../System.Diagnostics.Process/src/Resources/Strings.resx | 3 +-- .../src/System.Diagnostics.Process.csproj | 1 - .../tests/System.Diagnostics.Process.Tests.csproj | 3 --- 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs b/src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs index 7afcecb2379b72..3904ae89bd0b15 100644 --- a/src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs +++ b/src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs @@ -268,7 +268,6 @@ public ProcessStartInfo(string fileName, System.Collections.Generic.IEnumerable< [System.Diagnostics.CodeAnalysis.AllowNullAttribute] public string WorkingDirectory { get { throw null; } set { } } } - [System.ComponentModel.DesignerAttribute("System.Diagnostics.Design.ProcessThreadDesigner, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] public partial class ProcessThread : System.ComponentModel.Component { diff --git a/src/libraries/System.Diagnostics.Process/src/Resources/Strings.resx b/src/libraries/System.Diagnostics.Process/src/Resources/Strings.resx index 06e9e38b8eac45..24e76b6da21e1e 100644 --- a/src/libraries/System.Diagnostics.Process/src/Resources/Strings.resx +++ b/src/libraries/System.Diagnostics.Process/src/Resources/Strings.resx @@ -336,5 +336,4 @@ Invalid performance counter data with type '{0}'. - - \ No newline at end of file + diff --git a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index 112087107cc364..20397ff4889e42 100644 --- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -27,7 +27,6 @@ - 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 c123bf43275f8e..e44431ae36405a 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 @@ -29,7 +29,6 @@ - @@ -42,7 +41,6 @@ - -