Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Feb 11, 2026

Description

Implements ProcessStartOptions class providing path resolution and process configuration with explicit platform semantics.

Changes

API Surface

  • Added ProcessStartOptions with constructor that resolves executable paths
  • Properties: FileName (resolved), Arguments, Environment, WorkingDirectory, InheritedHandles, KillOnParentExit, CreateNewProcessGroup
  • Arguments, Environment, and InheritedHandles use lazy allocation
  • Environment reuses DictionaryWrapper implementation from ProcessStartInfo
  • Added null validation for Arguments and InheritedHandles setters

Path Resolution

  • ProcessStartOptions.ResolvePath is the primary path resolution implementation for both Windows and Unix
  • Windows: appends .exe, searches System32 (cached), Windows directory (not cached), uses ; PATH separator
  • Unix: validates executable permissions by calling Process.IsExecutable, uses : PATH separator
  • Process.Unix.cs now calls ProcessStartOptions.ResolvePath instead of having its own implementation
  • Made Process.IsExecutable internal for code reuse between ProcessStartOptions and Process
  • Removed Process.ResolvePath to eliminate duplication - all callers now use ProcessStartOptions.ResolvePath
  • Throws FileNotFoundException when path cannot be resolved

Infrastructure

  • Added Interop.GetWindowsDirectoryW for Windows directory lookup
  • Introduced WINDOWS compilation constant for platform-specific code
  • Moved StringParser to common section (required by both platforms)

Tests

  • 25 tests organized as partial classes across 3 files (base, Windows, Unix)
  • Tests use target-typed new() syntax for conciseness
  • ResolvePath_FindsCommonUtilities uses [Theory] with [InlineData] for parameterized testing of multiple utilities ("ls", "cat", "echo", "sh")
  • Platform-specific constructor tests in respective partial class files
  • Windows-specific tests validate .exe appending, system directory search, semicolon PATH separator
  • Unix-specific tests validate executable permission checks, colon PATH separator, directory rejection
  • Test assertions use EndsWith and Path.GetFullPath() for precision and symlink handling
  • All try-finally blocks simplified to single-level nesting
  • Test naming follows repository conventions (no "Test" prefix, e.g., Constructor_NullFileName_Throws)
  • Removed tests that don't properly validate behavior (e.g., incorrect lazy initialization tests)

Example

// Resolves path during construction
var options = new ProcessStartOptions("cmd");
Console.WriteLine(options.FileName); // C:\Windows\System32\cmd.exe

// Lazy-allocated collections
options.Arguments.Add("/c");
options.Arguments.Add("echo test");

// Environment inherits current process environment
options.Environment["MY_VAR"] = "value";

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

Copilot AI and others added 3 commits February 11, 2026 14:07
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot AI changed the title [WIP] Add ProcessStartOptions class to System.Diagnostics namespace Add ProcessStartOptions class with platform-aware path resolution Feb 11, 2026
Copilot AI requested a review from adamsitnik February 11, 2026 14:16
Copy link
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot please address my feedback

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copy link
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address my feedback

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
return filename;
}

#if WINDOWS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To other reviewers: In contrary to other classes, where we have dedicated .Windows.cs and .Unix.cs files, I would prefer this method to use #if defs and have everything in once place to make it easier to ensure they do the same thing on every OS, and when they don't it's also clear.

Copy link
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot please address my feedback

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot AI requested a review from adamsitnik February 11, 2026 18:49
Copy link
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot please address my feedback


namespace System.Diagnostics.Tests
{
public class ProcessStartOptionsTests : ProcessTestBase
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to inherit from ProcessTestBase?

Suggested change
public class ProcessStartOptionsTests : ProcessTestBase
public class ProcessStartOptionsTests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c3517ee - removed inheritance from ProcessTestBase.

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows), nameof(PlatformDetection.IsNotWindowsNanoServer))]
public void TestConstructor_ResolvesCmdOnWindows()
{
ProcessStartOptions options = new ProcessStartOptions("cmd");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer simpler syntax for creating new instances of ProcessStartOptions type in all the new tests.

Suggested change
ProcessStartOptions options = new ProcessStartOptions("cmd");
ProcessStartOptions options = new("cmd");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c3517ee - using new() syntax throughout all tests.

public void TestResolvePath_FindsCommonUtilities()
{
// Test common Unix utilities
string[] commonUtils = { "ls", "cat", "echo", "sh" };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turn this test into Theory that accepts just a string utilName

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c3517ee - converted to Theory with InlineData for "ls", "cat", "echo", "sh".

Comment on lines 42 to 44
[PlatformSpecific(TestPlatforms.Linux | TestPlatforms.FreeBSD | TestPlatforms.OSX)]
[Fact]
public void TestConstructor_ResolvesShOnUnix()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this test to ProcessStartOptionsTests.Unix.cs‎

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c3517ee - moved to ProcessStartOptionsTests.Unix.cs as Constructor_ResolvesShOnUnix.

Comment on lines 34 to 35
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows), nameof(PlatformDetection.IsNotWindowsNanoServer))]
public void TestConstructor_ResolvesCmdOnWindows()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this test to ProcessStartOptionsTests.Windows.cs‎

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c3517ee - moved to ProcessStartOptionsTests.Windows.cs as Constructor_ResolvesCmdOnWindows.

// sh should be findable in PATH on all Unix systems
ProcessStartOptions options = new ProcessStartOptions("sh");
Assert.True(File.Exists(options.FileName));
Assert.EndsWith("sh", options.FileName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it safe to assume it's always going to be equal "/bin/sh"?

Suggested change
Assert.EndsWith("sh", options.FileName);
Assert.Equals("/bin/sh", options.FileName);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c3517ee - using Assert.Equal("/bin/sh", options.FileName).


namespace System.Diagnostics.Tests
{
public class ProcessStartOptionsTests_Windows
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public class ProcessStartOptionsTests_Windows
public partial class ProcessStartOptionsTests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c3517ee - made it a partial class.

{
public class ProcessStartOptionsTests_Windows
{
[Fact]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test fails on Nano because notepad is not available on nano.

Suggested change
[Fact]
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c3517ee - added ConditionalFact back for IsNotWindowsNanoServer.

{
Directory.SetCurrentDirectory(tempDir);
ProcessStartOptions options = new ProcessStartOptions(fileName);
Assert.Equal(fullPath, options.FileName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test keeps failing on OSX, I think it's because it's not the actual full path:

Assert.Equal() Failure: Strings differ
            ↓ (pos 1)
Expected: "/tmp/helix/working/BDA1097F/t/testscript."···
Actual:   "/private/tmp/helix/working/BDA1097F/t/tes"·

Most likely just using Path.GetFullPath should fix it.

Suggested change
Assert.Equal(fullPath, options.FileName);
Assert.Equal(Path.GetFullPath(fullPath), options.FileName);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c3517ee - using Path.GetFullPath(fullPath) for assertion.

{
Directory.SetCurrentDirectory(tempDir);
// Should throw because file is not executable
Assert.Throws<FileNotFoundException>(() => new ProcessStartOptions(fileName));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test fails:

Assert.Throws() Failure: No exception was thrown
Expected: typeof(System.IO.FileNotFoundException)

I think it's because currently we check the permissions only if file was found in PATH:

private static string? FindProgramInPath(string program)
{
string path;
string? pathEnvVar = Environment.GetEnvironmentVariable("PATH");
if (pathEnvVar != null)
{
var pathParser = new StringParser(pathEnvVar, ':', skipEmpty: true);
while (pathParser.MoveNext())
{
string subPath = pathParser.ExtractCurrent();
path = Path.Combine(subPath, program);
if (IsExecutable(path))

And we don't do that for CWD:

// Then check the current directory
path = Path.Combine(Directory.GetCurrentDirectory(), filename);
if (File.Exists(path))
{
return path;
}

So this test should be either fixed or removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c3517ee - removed the ChecksExecutablePermissions test since ProcessStartOptions.ResolvePath doesn't check executable permissions for files in CWD (matching Process.ResolvePath behavior).

…, Theory for utils

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copy link
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot please address my feedback

…h for sh

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants