-
-
Notifications
You must be signed in to change notification settings - Fork 622
Refactor shell plugin, add tests & fix bugs #4554
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+522
−126
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
552579e
Remove duplicate setting of UseShellExecute in RunCommand branch of s…
DavidGBrett 38d6fbf
Add matching static methods to configure the process start info for e…
DavidGBrett 7bb0147
Rename notifyStr to closePrompt in shell plugin
DavidGBrett 0448bfc
Simplify runAsAdministratorArg boolean logic in shell plugin
DavidGBrett ff86a38
Extract static part of PrepareProcessStartInfo to new CreateProcessSt…
DavidGBrett f64d94f
Make shell plugin tests use CreateProcessStartInfo instead of shell s…
DavidGBrett 20678a3
Add more tests in shell plugin for all shell types
DavidGBrett 91dfeb5
Remove trailing /c from CMD close after press pause command in shell …
DavidGBrett 049ffe5
Rewrite ConfigureRunCommandStartInfo with proper argument and quoted-…
DavidGBrett File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,47 +1,358 @@ | ||
| using System; | ||
| using System.Diagnostics; | ||
| using System.Linq; | ||
| using Flow.Launcher.Plugin; | ||
| using Flow.Launcher.Plugin.Shell; | ||
| using NUnit.Framework; | ||
| using NUnit.Framework.Legacy; | ||
|
|
||
| namespace Flow.Launcher.Test.Plugins | ||
| { | ||
| [TestFixture] | ||
| public class ShellPluginTest | ||
| { | ||
| private const string ClosePrompt = "Press any key to close..."; | ||
|
|
||
| private static ProcessStartInfo Create( | ||
| string command = "test", | ||
| Shell shell = Shell.Cmd, | ||
| bool leaveShellOpen = false, | ||
| bool closeShellAfterPress = false, | ||
| bool useWindowsTerminal = false, | ||
| bool runAsAdmin = false) | ||
| => Main.CreateProcessStartInfo( | ||
| command, | ||
| shell, | ||
| leaveShellOpen, | ||
| closeShellAfterPress, | ||
| useWindowsTerminal, | ||
| runAsAdmin, | ||
| ClosePrompt); | ||
|
|
||
| #region CMD | ||
|
|
||
| [Test] | ||
| public void Cmd_ShouldPreserveQuotedCommands() | ||
| { | ||
| var info = Create( | ||
| command: "\"cmd.exe\"", | ||
| shell: Shell.Cmd); | ||
|
|
||
| Assert.That(info.FileName, Is.EqualTo("cmd.exe")); | ||
| Assert.That(info.Arguments, Is.EqualTo("/c \"cmd.exe\"")); | ||
| Assert.That(info.ArgumentList, Is.Empty); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Cmd_ShouldUseArgumentListForWindowsTerminal() | ||
| { | ||
| var info = Create( | ||
| command: "\"cmd.exe\"", | ||
| shell: Shell.Cmd, | ||
| useWindowsTerminal: true); | ||
|
|
||
| Assert.That(info.FileName, Is.EqualTo("wt.exe")); | ||
| Assert.That(info.ArgumentList, Is.EqualTo(["cmd", "/c", "\"cmd.exe\""])); | ||
| Assert.That(info.Arguments, Is.Empty); | ||
| } | ||
|
|
||
| [TestCase(true, "/k")] | ||
| [TestCase(false, "/c")] | ||
| public void Cmd_UsesCorrectShellSwitch(bool leaveShellOpen, string expectedSwitch) | ||
| { | ||
| const string command = "test"; | ||
| var info = Create( | ||
| command: command, | ||
| leaveShellOpen: leaveShellOpen, | ||
| shell: Shell.Cmd); | ||
|
|
||
| Assert.That(info.Arguments, Is.EqualTo($"{expectedSwitch} {command}")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Cmd_CloseShellAfterPress_AppendsPause() | ||
| { | ||
| const string command = "test"; | ||
| var info = Create( | ||
| command: command, | ||
| closeShellAfterPress: true, | ||
| shell: Shell.Cmd); | ||
|
|
||
| Assert.That(info.Arguments, Is.EqualTo($"/c {command} && echo {ClosePrompt} && pause > nul")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void ConfigureCmdProcessStartInfo_ShouldPreserveQuotedCommands() | ||
| public void Cmd_TrimsCommandWhitespace() | ||
| { | ||
| var info = new ProcessStartInfo(); | ||
| var info = Create( | ||
| command: " dir ", | ||
| shell: Shell.Cmd); | ||
|
|
||
| Assert.That(info.Arguments, Is.EqualTo("/c dir")); | ||
| } | ||
|
|
||
| Main.ConfigureCmdProcessStartInfo( | ||
| info, | ||
| "\"cmd.exe\"", | ||
| leaveShellOpen: false, | ||
| closeShellAfterPress: false, | ||
| notifyStr: "Press any key to close", | ||
| useWindowsTerminal: false); | ||
| [Test] | ||
| public void Cmd_WithSpacesAndQuotes_PassesThroughUnchanged() | ||
| { | ||
| var info = Create( | ||
| command: "\"C:\\Program Files\\app.exe\" --flag", | ||
| shell: Shell.Cmd); | ||
|
|
||
| ClassicAssert.AreEqual("cmd.exe", info.FileName); | ||
| ClassicAssert.AreEqual("/c \"cmd.exe\"", info.Arguments); | ||
| ClassicAssert.IsEmpty(info.ArgumentList); | ||
| Assert.That(info.Arguments, Is.EqualTo("/c \"C:\\Program Files\\app.exe\" --flag")); | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region PowerShell | ||
|
|
||
| [Test] | ||
| public void ConfigureCmdProcessStartInfo_ShouldKeepArgumentListForWindowsTerminal() | ||
| public void Powershell_DirectExecution() | ||
| { | ||
| var info = new ProcessStartInfo(); | ||
| var info = Create(shell: Shell.Powershell); | ||
|
|
||
| Assert.That(info.FileName, Is.EqualTo("powershell.exe")); | ||
| Assert.That(info.ArgumentList, Is.EqualTo(["-Command", "test;"])); | ||
| } | ||
|
|
||
| Main.ConfigureCmdProcessStartInfo( | ||
| info, | ||
| "\"cmd.exe\"", | ||
| leaveShellOpen: false, | ||
| closeShellAfterPress: false, | ||
| notifyStr: "Press any key to close", | ||
| [Test] | ||
| public void Powershell_WithSpecialCharacters_PassesThrough() | ||
| { | ||
| var info = Create( | ||
| command: "$env:USERNAME", | ||
| shell: Shell.Powershell); | ||
|
|
||
| Assert.That(info.ArgumentList, Is.EqualTo(["-Command", "$env:USERNAME;"])); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Powershell_CloseShellAfterPress_AppendsPrompt() | ||
| { | ||
| var info = Create( | ||
| shell: Shell.Powershell, | ||
| closeShellAfterPress: true); | ||
|
|
||
| var commandArg = info.ArgumentList.Last(); | ||
| Assert.That(commandArg, Does.Contain($"Write-Host '{ClosePrompt}'")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Powershell_WT_UsesWindowsTerminal() | ||
| { | ||
| var info = Create( | ||
| shell: Shell.Powershell, | ||
| useWindowsTerminal: true); | ||
|
|
||
| ClassicAssert.AreEqual("wt.exe", info.FileName); | ||
| CollectionAssert.AreEqual(new[] { "cmd", "/c", "\"cmd.exe\"" }, info.ArgumentList); | ||
| ClassicAssert.IsEmpty(info.Arguments); | ||
| Assert.That(info.FileName, Is.EqualTo("wt.exe")); | ||
| Assert.That(info.ArgumentList, Does.Contain("powershell")); | ||
| } | ||
|
|
||
| [TestCase(false)] | ||
| [TestCase(true)] | ||
| public void Powershell_AddsNoExitOnlyWhenLeaveShellOpen(bool leaveShellOpen) | ||
| { | ||
| var info = Create( | ||
| shell: Shell.Powershell, | ||
| leaveShellOpen: leaveShellOpen); | ||
|
|
||
| Assert.That(info.ArgumentList, leaveShellOpen | ||
| ? Does.Contain("-NoExit") | ||
| : Does.Not.Contain("-NoExit")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Powershell_LeaveShellOpen_OmitsCommandSwitch() | ||
| { | ||
| var info = Create( | ||
| shell: Shell.Powershell, | ||
| leaveShellOpen: true); | ||
|
|
||
| Assert.That(info.ArgumentList, Does.Not.Contain("-Command")); | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region Pwsh | ||
|
|
||
| [Test] | ||
| public void Pwsh_AlwaysAddsCommandSwitch() | ||
| { | ||
| var info = Create(shell: Shell.Pwsh); | ||
|
|
||
| Assert.That(info.FileName, Is.EqualTo("pwsh.exe")); | ||
| Assert.That(info.ArgumentList, Does.Contain("-Command")); | ||
| } | ||
|
|
||
| [TestCase(false)] | ||
| [TestCase(true)] | ||
| public void Pwsh_AddsNoExitOnlyWhenLeaveShellOpen(bool leaveShellOpen) | ||
| { | ||
| var info = Create( | ||
| shell: Shell.Pwsh, | ||
| leaveShellOpen: leaveShellOpen); | ||
|
|
||
| Assert.That( | ||
| info.ArgumentList, | ||
| leaveShellOpen | ||
| ? Does.Contain("-NoExit") | ||
| : Does.Not.Contain("-NoExit") | ||
| ); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Pwsh_CloseShellAfterPress_AppendsPrompt() | ||
| { | ||
| var info = Create( | ||
| shell: Shell.Pwsh, | ||
| closeShellAfterPress: true); | ||
|
|
||
| var commandArg = info.ArgumentList.Last(); | ||
| Assert.That(commandArg, Does.Contain($"Write-Host '{ClosePrompt}'")); | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region RunCommand | ||
|
|
||
| [Test] | ||
| public void RunCommand_SingleWord_SetsFileName() | ||
| { | ||
| var info = Create( | ||
| command: "notepad", | ||
| shell: Shell.RunCommand); | ||
|
|
||
| Assert.That(info.FileName, Is.EqualTo("notepad")); | ||
| Assert.That(info.Arguments, Is.Empty); | ||
| } | ||
|
|
||
| [Test] | ||
| public void RunCommand_UnknownExecutable_SetsWholeCommandAsFileName() | ||
| { | ||
| var info = Create( | ||
| command: "nonexistentapp123 argument", | ||
| shell: Shell.RunCommand); | ||
|
|
||
| Assert.That(info.FileName, Is.EqualTo("nonexistentapp123 argument")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void RunCommand_UnknownQuotedExecutable_SetsWholeCommandAsFileName() | ||
| { | ||
| var info = Create( | ||
| command: "\"C:\\nonexistent\\app.exe\" --flag", | ||
| shell: Shell.RunCommand); | ||
|
|
||
| Assert.That(info.FileName, Is.EqualTo("\"C:\\nonexistent\\app.exe\" --flag")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void RunCommand_QuotedPathNoArgs_ExtractsFileName() | ||
| { | ||
| var systemDir = Environment.SystemDirectory; | ||
| var info = Create( | ||
| command: $"\"{systemDir}\\cmd.exe\"", | ||
| shell: Shell.RunCommand); | ||
|
|
||
| Assert.That(info.FileName, Is.EqualTo($"{systemDir}\\cmd.exe")); | ||
| Assert.That(info.Arguments, Is.Empty); | ||
| } | ||
|
|
||
| [Test] | ||
| public void RunCommand_QuotedPath_WithQuotedArgs_Preserved() | ||
| { | ||
| var systemDir = Environment.SystemDirectory; | ||
| var info = Create( | ||
| command: $"\"{systemDir}\\cmd.exe\" /c echo \"hello world\"", | ||
| shell: Shell.RunCommand); | ||
|
|
||
| Assert.That(info.FileName, Is.EqualTo($"{systemDir}\\cmd.exe")); | ||
| Assert.That(info.Arguments, Is.EqualTo("/c echo \"hello world\"")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void RunCommand_UsesArgumentsForCommandTail() | ||
| { | ||
| var info = Create( | ||
| command: "cmd /c echo hello", | ||
| shell: Shell.RunCommand); | ||
|
|
||
| Assert.That(info.FileName, Is.EqualTo("cmd")); | ||
| Assert.That(info.Arguments, Is.EqualTo("/c echo hello")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void RunCommand_QuotedExecutablePath_ExtractsFileName() | ||
| { | ||
| var systemDir = Environment.SystemDirectory; | ||
| var info = Create( | ||
| command: $"\"{systemDir}\\cmd.exe\" /c echo hello", | ||
| shell: Shell.RunCommand); | ||
|
|
||
| Assert.That(info.FileName, Is.EqualTo($"{systemDir}\\cmd.exe")); | ||
| Assert.That(info.Arguments, Is.EqualTo("/c echo hello")); | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region Common | ||
|
|
||
| [TestCase(false, "")] | ||
| [TestCase(true, "runas")] | ||
| public void SetsRunAsAdminVerb(bool runAsAdmin, string expectedVerb) | ||
| { | ||
| var info = Create(runAsAdmin: runAsAdmin); | ||
|
|
||
| Assert.That(info.Verb, Is.EqualTo(expectedVerb)); | ||
| } | ||
|
|
||
| [TestCase(Shell.Cmd)] | ||
| [TestCase(Shell.Powershell)] | ||
| [TestCase(Shell.Pwsh)] | ||
| [TestCase(Shell.RunCommand)] | ||
| public void SetsWorkingDirectory(Shell shell) | ||
| { | ||
| var info = Create(shell: shell); | ||
|
|
||
| var expected = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); | ||
| Assert.That(info.WorkingDirectory, Is.EqualTo(expected)); | ||
| } | ||
|
|
||
| [TestCase(Shell.Cmd)] | ||
| [TestCase(Shell.Powershell)] | ||
| [TestCase(Shell.Pwsh)] | ||
| [TestCase(Shell.RunCommand)] | ||
| public void SetsUseShellExecute(Shell shell) | ||
| { | ||
| var info = Create(shell: shell); | ||
|
|
||
| Assert.That(info.UseShellExecute, Is.True); | ||
| } | ||
|
|
||
| [TestCase(Shell.Cmd)] | ||
| [TestCase(Shell.Powershell)] | ||
| [TestCase(Shell.Pwsh)] | ||
| [TestCase(Shell.RunCommand)] | ||
| public void ExpandsEnvironmentVariables(Shell shell) | ||
| { | ||
| var info = Create( | ||
| command: "%USERPROFILE%\\test", | ||
| shell: shell); | ||
|
|
||
| var expandedPath = Environment.ExpandEnvironmentVariables("%USERPROFILE%\\test"); | ||
|
|
||
| switch (shell) | ||
| { | ||
| case Shell.Cmd: | ||
| Assert.That(info.Arguments, Is.EqualTo($"/c {expandedPath}")); | ||
| break; | ||
| case Shell.Powershell: | ||
| case Shell.Pwsh: | ||
| Assert.That(info.ArgumentList, Does.Contain(expandedPath + ";")); | ||
| break; | ||
| case Shell.RunCommand: | ||
| Assert.That(info.FileName, Is.EqualTo(expandedPath)); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| #endregion | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.