Skip to content

Implement System.Diagnostics.Process tests for Android#126099

Merged
adamsitnik merged 11 commits intomainfrom
copilot/implement-process-tests-mobile
Mar 27, 2026
Merged

Implement System.Diagnostics.Process tests for Android#126099
adamsitnik merged 11 commits intomainfrom
copilot/implement-process-tests-mobile

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 25, 2026

All existing System.Diagnostics.Process tests depend on RemoteExecutor, which is unsupported on mobile platforms — leaving zero test coverage for Android.

Changes

System.Diagnostics.Process.Tests.csproj

  • Added android to TargetFrameworks
  • Extended the Unix compilation condition to include android — necessary because ProcessTests.cs calls WriteScriptFile defined in ProcessTests.Unix.cs
  • Added Android-only conditional <Compile> entry for new ProcessTests.Android.cs

ProcessTests.Android.cs (new)

Five Android-specific tests using ls and sleep, resolved via PATH (which includes /system/bin on Android via toybox/busybox). Tool names are passed directly to Process.Start. The ls tests target Path.GetTempPath() rather than / to avoid Android SELinux denying untrusted_app access to the root filesystem.

Test Validates
Process_Start_InheritedIO_ExitsSuccessfully ls <TempPath> exits 0 with inherited parent handles, HasExited = true
Process_Start_RedirectedStandardOutput_ReadsOutput stdout redirect yields non-empty output
Process_Kill_TerminatesRunningProcess sleep 600 can be killed; Id > 0; HasExited transitions correctly
Process_Start_ExitCode_ReflectsCommandFailure failed command produces non-zero exit code and non-empty stderr
Process_ProcessName_IsSetForStartedProcess ProcessName is populated
Process_Start_WithStandardHandles_CanRedirectIO uses StandardInputHandle (File.OpenNullHandle()), StandardOutputHandle (anonymous pipe, output asserted non-empty), and StandardErrorHandle (temp file) APIs

All tests are gated with [PlatformSpecific(TestPlatforms.Android)].

ProcessHandlesTests.cs (existing)

  • Annotated the entire ProcessHandlesTests class with [SkipOnPlatform(TestPlatforms.Android, "sh is not available on Android")] — the tests in this class use sh which is not available on Android, causing failures when the test project runs on that platform.
Original prompt

This section details on the original issue you should resolve

<issue_title>Implement System.Diagnostics.Process tests for mobile platforms</issue_title>
<issue_description>As of today, all our Process tests use RemoteExecutor which is simply not supported on mobile platforms:

https://github.com/dotnet/dotnet/blob/1948989e277a41b84849a83c57f0637d685b0f09/src/arcade/src/Microsoft.DotNet.RemoteExecutor/src/RemoteExecutor.cs#L110-L118

We need to provide Android tests that would ideally work on iOS as well (run some simple Unix tools available for both emulators?).

Related: #126097</issue_description>

<agent_instructions>Find out which Unix command line tools are available from Android emulator (ideally that would be available for iOS as well) and provide some basic Process tests.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


⚡ Quickly spin up Copilot coding agent tasks from anywhere on your macOS or Windows machine with Raycast.

@adamsitnik
Copy link
Copy Markdown
Member

  • Add android, ios, tvos, maccatalyst to TargetFrameworks in System.Diagnostics.Process.Tests.csproj

@copilot For now, please stick only with android

Copilot AI changed the title [WIP] Add System.Diagnostics.Process tests for mobile platforms Implement System.Diagnostics.Process tests for mobile platforms (Android/iOS) Mar 25, 2026
Copilot AI requested a review from adamsitnik March 25, 2026 16:33
…droid.cs

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/800d4e96-f860-4a1c-89f3-e764451d1da9
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 25, 2026

  • Add android, ios, tvos, maccatalyst to TargetFrameworks in System.Diagnostics.Process.Tests.csproj

@copilot For now, please stick only with android

Done in 403f131. Removed ios, tvos, and maccatalyst from TargetFrameworks, and renamed the test file to ProcessTests.Android.cs with its compile condition scoped to android only.

Copy link
Copy Markdown
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

Copilot AI changed the title Implement System.Diagnostics.Process tests for mobile platforms (Android/iOS) Implement System.Diagnostics.Process tests for Android Mar 25, 2026
Copilot AI requested a review from adamsitnik March 25, 2026 16:41
…throughout

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/8c73b007-1f6a-4064-8e02-ff21eaec112c
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 25, 2026

@copilot address my feedback

Addressed in ba2dc9c — removed the GetAndroidProgramPath helper, replaced all var with explicit types, and dropped the now-unused System.IO import.

@adamsitnik
Copy link
Copy Markdown
Member

/azp list

@azure-pipelines

This comment was marked as resolved.

@adamsitnik
Copy link
Copy Markdown
Member

/azp run runtime-android runtime-androidemulator

@azure-pipelines
Copy link
Copy Markdown

No pipelines are associated with this pull request.

@adamsitnik
Copy link
Copy Markdown
Member

/azp run runtime-android

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@adamsitnik
Copy link
Copy Markdown
Member

/azp run runtime-androidemulator

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Copy Markdown
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 I can see that only 3 test have failed (log):

I am not sure why. If we were missing permissions to launch the app, we would not get to comparing exit codes.

Perhaps we need to provide working directory in explicit way for the tests that use ls? Because the one using sleep has passed. @copilot give it a try (provide path to TEMP for example ProcessStartInfo.WorkingDirectory) unless you have better idea

                 03-25 11:06:25.769  6855  6881 I DOTNET  : 	[PASS] System.Diagnostics.Tests.ProcessTests.GetProcesses_InvalidMachineName_ThrowsInvalidOperationException
                 03-25 11:06:25.828  6889  6889 W ls      : type=1400 audit(0.0:5108): avc: denied { read } for name="/" dev="dm-3" ino=2 scontext=u:r:untrusted_app:s0:c14,c295,c512,c768 tcontext=u:object_r:rootfs:s0 tclass=dir permissive=0
                 03-25 11:06:25.910  6855  6881 I DOTNET  : 	[FAIL] System.Diagnostics.Tests.ProcessTests.Process_Start_SimpleCommand_ExitsSuccessfully
                 03-25 11:06:25.910  6855  6881 I DOTNET  : Assert.Equal() Failure: Values differ
                 03-25 11:06:25.910  6855  6881 I DOTNET  : Expected: 0
                 03-25 11:06:25.910  6855  6881 I DOTNET  : Actual:   1
                 03-25 11:06:25.910  6855  6881 I DOTNET  :    at System.Diagnostics.Tests.ProcessTests.Process_Start_SimpleCommand_ExitsSuccessfully()
                 03-25 11:06:25.910  6855  6881 I DOTNET  :    at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
                 03-25 11:06:25.910  6855  6881 I DOTNET  :    at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
                 03-25 11:06:25.914  6855  6881 I DOTNET  : 	[PASS] System.Diagnostics.Tests.ProcessTests.HandleCount_GetNotStarted_ThrowsInvalidOperationException
                 03-25 11:06:25.914  6855  6881 I DOTNET  : 	[IGNORED] System.Diagnostics.Tests.ProcessTests.TestExitTime
                 03-25 11:06:25.971  6890  6890 W ls      : type=1400 audit(0.0:5109): avc: denied { read } for name="/" dev="dm-3" ino=2 scontext=u:r:untrusted_app:s0:c14,c295,c512,c768 tcontext=u:object_r:rootfs:s0 tclass=dir permissive=0
                 03-25 11:06:25.978  6855  6881 I DOTNET  : 	[FAIL] System.Diagnostics.Tests.ProcessTests.Process_Start_CaptureStdout_ReadsOutput
                 03-25 11:06:25.978  6855  6881 I DOTNET  : Assert.Equal() Failure: Values differ
                 03-25 11:06:25.978  6855  6881 I DOTNET  : Expected: 0
                 03-25 11:06:25.978  6855  6881 I DOTNET  : Actual:   1
                 03-25 11:06:25.978  6855  6881 I DOTNET  :    at System.Diagnostics.Tests.ProcessTests.Process_Start_CaptureStdout_ReadsOutput()
                 03-25 11:06:25.978  6855  6881 I DOTNET  :    at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
                 03-25 11:06:25.978  6855  6881 I DOTNET  :    at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
                 03-25 11:06:26.901  6855  6881 I DOTNET  : 	[IGNORED] System.Diagnostics.Tests.ProcessTests.TestCreateNewProcessGroup_HandlerReceivesExpectedSignal
                 03-25 11:06:26.971  6897  6897 W ls      : type=1400 audit(0.0:5113): avc: denied { read } for name="/" dev="dm-3" ino=2 scontext=u:r:untrusted_app:s0:c14,c295,c512,c768 tcontext=u:object_r:rootfs:s0 tclass=dir permissive=0
                 03-25 11:06:26.981  6855  6881 I DOTNET  : 	[FAIL] System.Diagnostics.Tests.ProcessTests.Process_Start_WithArgumentList_ExitsSuccessfully
                 03-25 11:06:26.981  6855  6881 I DOTNET  : Assert.Equal() Failure: Values differ
                 03-25 11:06:26.981  6855  6881 I DOTNET  : Expected: 0
                 03-25 11:06:26.981  6855  6881 I DOTNET  : Actual:   1
                 03-25 11:06:26.981  6855  6881 I DOTNET  :    at System.Diagnostics.Tests.ProcessTests.Process_Start_WithArgumentList_ExitsSuccessfully()
                 03-25 11:06:26.981  6855  6881 I DOTNET  :    at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
                 03-25 11:06:26.981  6855  6881 I DOTNET  :    at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

cc @lateralusX

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@adamsitnik adamsitnik added this to the 11.0.0 milestone Mar 26, 2026
@adamsitnik adamsitnik added the test-enhancement Improvements of test source code label Mar 26, 2026
Copy link
Copy Markdown
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 the tests fail due to recently introduced ProcessHandlesTests tests. Go to ProcessHandlesTests.cs and annotate the entire class with [SkipOnPlatform(TestPlatforms.Android, "sh is not available on Android")]

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/1cc664b6-0eef-437b-8198-828ddf712f1e

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

Copilot AI commented Mar 26, 2026

@copilot the tests fail due to recently introduced ProcessHandlesTests tests. Go to [ProcessHandlesTests.cs](https://github.com/dotnet/runtime/blob/7602c30b43e12e3ced08087cc1503f017ac4206e/src/libra...

Done in a2efbde. Added [SkipOnPlatform(TestPlatforms.Android, "sh is not available on Android")] to the ProcessHandlesTests class.

Copilot AI requested a review from adamsitnik March 26, 2026 17:27
@adamsitnik
Copy link
Copy Markdown
Member

/azp run runtime-android

@adamsitnik
Copy link
Copy Markdown
Member

/azp run runtime-androidemulator

@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown
Contributor

🤖 Copilot Code Review — PR #126099

Note

This review was generated by GitHub Copilot using multi-model analysis (Claude Opus 4.6 primary, Claude Sonnet 4.5 secondary). Findings were independently verified against full source files and native code.

Holistic Assessment

Motivation: This PR adds StandardInputHandle, StandardOutputHandle, and StandardErrorHandle properties to ProcessStartInfo, enabling users to pass pre-created SafeFileHandle objects directly to child processes. This addresses a real need (process piping, redirecting to files/null without going through StreamReader/StreamWriter). The refactoring to centralize pipe creation in Process.cs and unify the process-start lock across platforms is well-motivated.

Approach: The approach is sound — moving pipe creation from platform-specific StartCore methods into the shared Start() method reduces duplication and makes handle management consistent. The native ForkAndExecProcess API change (accepting pre-created fds instead of creating pipes internally) is clean. The DuplicateAsInheritableIfNeeded pattern on Windows avoids unnecessary handle duplication.

Summary: ⚠️ Needs Human Review. The code is well-structured and the handle ownership model is correct. However, I identified two concerns that warrant human reviewer attention: (1) API approval verification for issue #125848 (I cannot verify the api-approved label), and (2) a minor handle leak risk in the Windows duplication path. The behavioral change to usesTerminal detection on Unix is actually more correct but should be consciously acknowledged.


Detailed Findings

⚠️ API Approval — Cannot verify api-approved label on #125848

This PR adds new public API surface in ref/System.Diagnostics.Process.cs:

public SafeFileHandle? StandardErrorHandle { get; set; }
public SafeFileHandle? StandardInputHandle { get; set; }
public SafeFileHandle? StandardOutputHandle { get; set; }

The commit message references issue #125848. Per dotnet/runtime policy, new public APIs require an api-approved label on the linked issue. I cannot access the GitHub issue to verify this label. A human reviewer should confirm that #125848 has api-approved and that the implementation matches the approved shape exactly (property names, types, nullability).

⚠️ Windows Handle Leak on Partial Duplication Failure

File: Process.Windows.cs lines 458-460

DuplicateAsInheritableIfNeeded(stdinHandle, ref inheritableStdinHandle);
DuplicateAsInheritableIfNeeded(stdoutHandle, ref inheritableStdoutHandle);   // If this throws...
DuplicateAsInheritableIfNeeded(stderrHandle, ref inheritableStderrHandle);   // ...never reached

If DuplicateHandle succeeds for stdin but fails for stdout, the exception propagates to the catch block at line 589 which disposes procSH, then the finally block at line 594 disposes inheritableStdinHandle (correct). However, the catch block does not explicitly clean up duplicated handles — it relies on the finally block. This actually works correctly because finally always runs and disposes all three nullable handles. (Flagged as advisory — verified it's handled, but the code flow is non-obvious.)

⚠️ usesTerminal Detection Behavioral Change (Unix)

File: Process.Unix.cs lines 389-392

Old behavior:

bool usesTerminal = !(startInfo.RedirectStandardInput &&
                      startInfo.RedirectStandardOutput &&
                      startInfo.RedirectStandardError);

New behavior:

bool usesTerminal = (stdinHandle is not null && Interop.Sys.IsATty(stdinHandle))
    || (stdoutHandle is not null && Interop.Sys.IsATty(stdoutHandle))
    || (stderrHandle is not null && Interop.Sys.IsATty(stderrHandle));

The new code is more correct — it checks whether the actual handles point to a TTY rather than relying on the heuristic "any non-redirected stream implies terminal use." This means a process whose parent has stdout piped to a file will no longer trigger unnecessary terminal configuration. However, this is a behavioral change that should be consciously acknowledged. It could affect edge cases where the old heuristic triggered terminal configuration that happened to be needed.

✅ Handle Ownership Model — Correct

The ownership model in Process.cs Start() is well-designed:

  • User-provided handles (StandardInputHandle/etc.) are NOT disposed by the framework (lines 1410-1421 check is null to skip disposal).
  • Pipe handles created by CreateAnonymousPipe are properly disposed in finally blocks.
  • Console handles from Console.OpenStandard*Handle() are safe to dispose (they don't own the underlying resource).
  • Parent pipe handles are disposed in the catch block on failure, and wrapped in streams on success.

No double-close or use-after-dispose risks were found.

✅ Android Support — Correct

On Android, Console.OpenStandard*Handle() throws PlatformNotSupportedException. The code correctly guards with !OperatingSystem.IsAndroid() checks (lines 1349, 1362, 1375). When handles are null, the native ForkAndExecProcess receives fd=-1, which correctly causes the child to inherit the parent's handles (verified in pal_process.c line 362: stdinFd != -1 && ...).

✅ Locking Strategy — Sound

  • s_processStartLock correctly moved to ProcessUtils.cs for cross-platform sharing.
  • Windows always acquires a write lock for CreateProcess (same serialization as old lock(s_createProcessLock) — no regression).
  • Unix uses read locks for ForkAndExecProcess (allows concurrent starts) and write locks for terminal configuration and SIGCHLD handling.
  • Conditional pipe-creation lock in Start() only activates on Unix without pipe2 support — correctly avoids unnecessary locking when atomic non-inheritable pipe creation is available.
  • No deadlock risks identified (lock upgrade in Unix finally block properly exits read lock before entering write lock).

✅ Native Interop Changes — Correct

Interop.ForkAndExecProcess.cs correctly uses DangerousAddRef/DangerousRelease to pin handles during the native call (lines 30-67). The SafeFileHandle?int conversion handles null correctly (defaults to -1). The native C code in pal_process.c skips dup2 for fd=-1, correctly inheriting parent handles.

✅ Test Coverage — Good

ProcessHandlesTests.cs covers key scenarios: pipe redirection (sync/async), separate vs. combined stdout/stderr, inherited handles, process piping, and mutual exclusion with RedirectStandard* properties. The test is correctly skipped on Android (no sh). ProcessTests.Android.cs provides basic Android coverage.

💡 Error Message Precision

File: Process.cs lines 1291-1301

When StandardInputHandle conflicts with RedirectStandardInput, the error message SR.CantSetHandleAndRedirect references all six properties:

"The StandardInputHandle, StandardOutputHandle, and StandardErrorHandle properties cannot be used together with the corresponding RedirectStandardInput, RedirectStandardOutput, and RedirectStandardError properties."

This is thrown even when only one pair conflicts. A more precise message identifying the specific conflicting pair would be clearer, though the current message is acceptable since it explains the general constraint.

💡 Unix OpenStream Ownership Transfer Pattern

File: Process.Unix.cs lines 718-720

SafePipeHandle safePipeHandle = new(handle.DangerousGetHandle(), ownsHandle: true);
handle.SetHandleAsInvalid();

This ownership transfer via DangerousGetHandle() + SetHandleAsInvalid() is correct but non-obvious. If the SafePipeHandle constructor threw between these two lines, the handle would leak. In practice, the constructor can't throw for a valid IntPtr, so this is safe. The existing comment explains the intent well.

Generated by Code Review for issue #126099 ·

@adamsitnik
Copy link
Copy Markdown
Member

Motivation: This PR adds StandardInputHandle, StandardOutputHandle, and StandardErrorHandle properties to ProcessStartInfo, enabling users to pass pre-created SafeFileHandle objects directly to child processes. This addresses a

@lateralusX lateralusX self-requested a review March 27, 2026 11:07
Copy link
Copy Markdown
Member

@lateralusX lateralusX left a comment

Choose a reason for hiding this comment

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

Android process tests LGTM!

@adamsitnik
Copy link
Copy Markdown
Member

/ba-g failures are unrelated, the Android tests for Process are passing now

@adamsitnik adamsitnik merged commit 3106c4f into main Mar 27, 2026
81 of 90 checks passed
@adamsitnik adamsitnik deleted the copilot/implement-process-tests-mobile branch March 27, 2026 12:16
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to 'arch-android': @vitek-karas, @simonrozsival, @steveisok, @akoeplinger
See info in area-owners.md if you want to be subscribed.

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.

Implement System.Diagnostics.Process tests for mobile platforms

4 participants