-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Improving UseShellExecute implementation on Process.Start #23705
Conversation
|
||
private string GetPathToProgram(string program) | ||
{ | ||
var path = Environment.GetEnvironmentVariable("PATH"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please review the corefx guidelines for "var" usage - these don't fit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops I didnt realize you guys will get notified of my PR just yet :) I need to cleanup all this now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you create a PR, folks will notice fairly quickly even if they aren't assigned it. So generally you throw up a PR either when you think you're done, or when you need feedback on what you have so far 😄
Assert.Equal(42, p.ExitCode); | ||
} | ||
} | ||
//[Fact] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you commented out tests -- are you looking for feedback on the approach, before you write tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have the failing tests commented out for now, since I'm toggling between different os as I make fixes (win, osx and linux). I will keep them commented out until all get fixes.
actually some of them are failing because I don't know if it is ok to change current behavior of Process.Start just yet or not. I'll explain in more detail in a bit when I have a good summary to share.
using System.Collections.Generic; | ||
using System.ComponentModel; | ||
using System.Globalization; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do'nt think you need all those. I believe VS has a refactoring to remove unnecessary using statements.
var lookupLocation = path.Split(":"); | ||
foreach (var loc in lookupLocation) | ||
{ | ||
string[] dirs = Directory.GetFiles(loc, program); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of dirs
maybe files
? and instead of loc
maybe dir
bd05de2
to
4660ea6
Compare
d88796a
to
dc2d88f
Compare
[Fact] | ||
public void UseShellExecuteIsFalse_ThrowsWithOrWithoutMyChanges() | ||
{ | ||
// The test below fails on linux with or without my changes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Observation 1: the commented out test below fails on linux with or without my changes.
Notice that UseShellExecute is false.
Question: is this ok to fail?
[Fact] | ||
public void UseShellExecuteIsFalse_ThrowsWithOrWithoutMyChanges() | ||
{ | ||
// The test below fails on osx with or without my changes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Observation 2: the commented out test below fails on osx with or without my changes.
Notice that UseShellExecute is false.
Question: is this ok to fail?
using (var p = Process.Start(new ProcessStartInfo { UseShellExecute = true, FileName = "exit", Arguments = "42" })) | ||
{ | ||
Assert.True(p.WaitForExit(WaitInMS)); | ||
Assert.Equal(42, p.ExitCode); // Assert.Equal Failure with after adding my changes: Expected: 42, Actual: 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Observation 3: this Assert.Equals fails on osx only after I submit my changes.
Notice that UseShellExecute is true.
Question: do I need to fix this to keep the old behavior?
using (var p = Process.Start(new ProcessStartInfo { UseShellExecute = true, FileName = "exit", Arguments = "42" })) | ||
{ | ||
Assert.True(p.WaitForExit(WaitInMS)); | ||
// Assert.Equal(42, p.ExitCode); // Assert.Equal Failure with after adding my changes: Expected: 42, Actual: 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Observation 4: this Assert.Equals fails on linux only after I submit my changes.
Notice that UseShellExecute is true.
Question: do I need to fix this to keep the old behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this should work. If it did work, UseShellExecute
would have completely different effects on Linux and Windows. (see my comment)
PS: This also applies to your other Questions from four hours ago.
dc2d88f
to
625a87b
Compare
[OuterLoop("Launches default application")] | ||
public void NewXdg_TestWithFilename_ShouldUseOpenWithDefaultApp() | ||
{ | ||
string fileToOpen = Path.Combine(Environment.CurrentDirectory, "..", "..", "..", "..", "PATENTS.TXT"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to have a txt file in the tests\ directory and have this in your test csproj
<Content Include="foo.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
f4c01d8
to
30b1771
Compare
Making sure we are coherent with mono implementation Added unit tests per linux and osx
30b1771
to
75f34a5
Compare
{ | ||
if (px != null) | ||
{ | ||
Assert.Equal(program, px.ProcessName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this test I mostly care to test which process we ended up using to open the input file. therefore I could remove the extra assertions that check if px exited and the exitcode value assertion as well
Assert.Equal("xdg-open", p.ProcessName); | ||
p.WaitForExit(); | ||
Assert.True(p.HasExited); | ||
Assert.Equal(2, p.ExitCode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this test I wanted to show that when no file exists the xdg-open process will return the expected exitcode it uses when file doesnt exist. so the most important assertion here is Assert.Equal(2, p.ExitCode) and the rest could be removed if needed
using System.Threading; | ||
using System.Security; | ||
using Xunit; | ||
using Xunit.NetCore.Extensions; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to check if I can remove unnecessary usings from here. also, there is room to cleanup the osx tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, you can always remove unnecessary usings. VS should do it for you in a couple clicks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surely. But I have VS installed on windows and VSCode on the other machine.
When I'm using VS on windows, the "remove and sort" feature is not available for me on this file. I think it is because the intellisense for this file is not available, probably because in csproj it is not included for windows.
VSCode also has limited functionality I think. It doesn't have the feature to remove and sort. I think there is limited intellisense functionality available in VSCode in general. I may be wrong, just my observation so far.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right. I wouldn't bother with it then. we don't do it consistently and it's easy for us to do it periodically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@maryamariyan It's definitely not obvious at first, but if you go into the "Configuration Manager" menu in VS, you can switch the build configuration for each project. You can select the OSX configuration for this project and then do refactorings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not that you need to rush to do that or anything, just a tip for the future 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @mellinoe :)
[Fact] | ||
public void TestUseShellExecute_Unix_Succeeds() | ||
public void ProcessStart_UseShellExecuteTrue_OpenNano_OpensNano() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this fails on osx. I need to investigate
private string GetPathToProgram(string program) | ||
{ | ||
string path = Environment.GetEnvironmentVariable("PATH"); | ||
string[] dirs = path.Split(":"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If for some reason PATH is not set in this shell, this will throw NRE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it looks like Mono falls back to the current directory.
https://github.com/mono/mono/blob/master/mono/eglib/gpath.c#L236
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could eliminate all this code and just call ResolvePath()
if you don't mind a slightly different resolution algorithm. I think that's fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, I just noticed ResolvePath() is also calling path to get path. I'm going to extract that logic into a private method and reuse that private method both in ResolvePath() and in here. Thanks
@danmosemsft my plan is to let the build run once with all [OuterLoop] commented out. |
if (!startInfo.UseShellExecute) | ||
{ | ||
// Could not find the file | ||
Debug.Assert(string.IsNullOrEmpty(filename)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If filename was null/empty (and USE=false), then it would go into this block. so why assert? Debug.Assert should only be to catch mistakes by us (you). In this case it seems like it's for bad user input, and we should throw somewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assert is against filename.
- filename is retrieved from ResolvePath() on line 250. This happens when startInfo.FileName is a URL.
I added a comment on the summary of this method to explain this.
Also, it's not expected to reach this line in any way when filename is not null. In that case we would have thrown already on line 264
out childPid, | ||
out stdinFd, out stdoutFd, out stderrFd); | ||
|
||
Debug.Assert(result == 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If result != 0 does that mean you made a mistake in your code? If not, Debug.Assert is probably not the right choice. You should either ignore it (if appropriate) or throw, or some other effect visible in retail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is not expected to reach here and result != 0 so I think the assert is proper here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok fine
Win32Exception e = Assert.Throws<Win32Exception>(() => Process.Start(new ProcessStartInfo { UseShellExecute = false, FileName = "exit", Arguments = "42" })); | ||
} | ||
|
||
[Fact] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this test and the one below, do they match Windows behavior? If so they shoud lbe in ProcessTests.cs (ie., not Unix specific file). This will help make certain the behavior stays the same betwen Unix and windows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and if they don't match Windows behavior, hopefully we thought that through, and a comment here explaining why we chose to diverge would be good
px.Kill(); | ||
px.WaitForExit(); | ||
Assert.True(px.HasExited); | ||
Assert.Equal(137, px.ExitCode); // 137 means the process was killed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
137 = Interop.Sys.Signals.SIGKILL + 128 btw... I think the shell adds 128 to signals as the exit code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, BASH man page The return value of a simple command is its exit status, or 128+n if the command is terminated by signal n.
// [OuterLoop("Opens program")] | ||
public void ProcessStart_OpenFileOnLinux_UsesSpecifiedProgram(string programToOpenWith) | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: line
} | ||
else | ||
{ | ||
Console.WriteLine($"Program specified to open file with {programToOpenWith} is not installed on this machine."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could possibly log Environment.GetenvironmentVariable("PATH") if that might help.
using (var px = Process.Start(programToOpenWith, fileToOpen)) | ||
{ | ||
Console.WriteLine($"in OSX, {nameof(programToOpenWith)} is {programToOpenWith}, while {nameof(px.ProcessName)} is {px.ProcessName}."); | ||
// Assert.Equal(programToOpenWith, px.ProcessName); // on OSX, process name is dotnet for some reason |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you were going to open an issue? if so you could put the URL of hte issue in the comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
created #23972 to track this.
|
||
[Fact, PlatformSpecific(TestPlatforms.OSX)] | ||
// [OuterLoop("Opens program")] | ||
public void ProcessStart_UseShellExecuteTrue_TryOpenFileThatDoesntExist_ReturnsExitCode1() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where is the Linux version of this test? also, why is this OSX specific? If it matches Windows behavior, it should be a test for all platforms?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is another test similar to this for Linux, but since the return code is different for linux I split it to two different tests. (the exit code is coming from xdg-open and open).
I'll add a comment to explain why I split them to two tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM aside from comments. Thanks!
{ | ||
using (var px = Process.Start(new ProcessStartInfo { UseShellExecute = true, FileName = Path.GetTempPath() })) | ||
{ | ||
Assert.Null(px); // Not sure why px returned is null. but the call does not throw and opens folder successfully. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added test for #23969
Looking at the two test failures seems like you need to make some tests platform specific. netcoreapp-nano and uap-RS2 are throwing: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than the comments by the rest of the folks, this LGTM once CI is green.
@dotnet-bot test outerloop please |
It seems like this change will break backward compatibility. Secondary note is that "use shell" has established meaning on Unix. |
@wfurt I agree, that "shellExecute" sounds like command-line-shell (e.g. That said, using
I didn't notice that before but yes, this case should be fixed if it doesn't work currently. (Because Windows .Net does also respect the Path-Environment-Variable when using "UseShellExecute") It should not be fixed by using |
We attempted to match Mono behavior. It's a good point that the original 'sh' behavior we had would be useful for some folks and maybe we should have a way to choose that. Also we can discuss whether UseShellExecute=true on Unix should use sh or xdg-open. In the Windows world, Shell Execute means more or less "here's a file please give it to the correct program". Maybe we need ExecuteWithShell=true|false also. @wfurt will open an issue to discuss this... |
Says who? |
I created #24704 with some more examples of 2.0 behavior. Who ever cares about Process.Start() with shell should jump on that thread. |
Improving UseShellExecute implementation on Process.Start Commit migrated from dotnet/corefx@e670fd3
Fixes both #19956 and #22299