mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 10:46:33 +02:00
Allow Run to handle commandlines with spaces (#42016)
This better handles cases where commandlines might have embedded spaces. For something like ``` C:\Program Files\PowerShell\7\pwsh.exe -c write-host dawg ``` we'll now see that `C:\Program` isn't a file, and we'll try to look at `C:\Program Files\PowerShell\7\pwsh.exe` instead. This code is pilfered from https://github.com/microsoft/terminal/pull/12348 which fixed https://github.com/microsoft/terminal/issues/12345. Terminal has great code for normalizing a string into an executable and args, so why not just use it here. related to #41646 related to #41705 (but much more narrowly scoped) ---- I added some tests too. drive-by fix: as I was adding tests, I added a helper for "make a change to a page, and await the page's ItemsChanged". This removes a bunch of `await 1s` calls, and brings the shell page tests from like, 7s to 500ms
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||
using Microsoft.CmdPal.Ext.UnitTestBase;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Shell.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class NormalizeCommandLineTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
private void NormalizeTestCore(string input, string expectedExe, string expectedArgs = "")
|
||||
{
|
||||
ShellListPageHelpers.NormalizeCommandLineAndArgs(input, out var exe, out var args);
|
||||
|
||||
Assert.AreEqual(expectedExe, exe, ignoreCase: true, culture: System.Globalization.CultureInfo.InvariantCulture);
|
||||
Assert.AreEqual(expectedArgs, args);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("ping bing.com", "c:\\Windows\\system32\\ping.exe", "bing.com")]
|
||||
[DataRow("curl bing.com", "c:\\Windows\\system32\\curl.exe", "bing.com")]
|
||||
[DataRow("ipconfig /all", "c:\\Windows\\system32\\ipconfig.exe", "/all")]
|
||||
public void NormalizeCommandLineSimple(string input, string expectedExe, string expectedArgs = "")
|
||||
{
|
||||
NormalizeTestCore(input, expectedExe, expectedArgs);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("\"C:\\Program Files\\Windows Defender\\MsMpEng.exe\"", "C:\\Program Files\\Windows Defender\\MsMpEng.exe")]
|
||||
[DataRow("C:\\Program Files\\Windows Defender\\MsMpEng.exe", "C:\\Program Files\\Windows Defender\\MsMpEng.exe")]
|
||||
public void NormalizeCommandLineSpacesInExecutablePath(string input, string expectedExe, string expectedArgs = "")
|
||||
{
|
||||
NormalizeTestCore(input, expectedExe, expectedArgs);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("%SystemRoot%\\system32\\cmd.exe", "C:\\Windows\\System32\\cmd.exe")]
|
||||
public void NormalizeWithEnvVar(string input, string expectedExe, string expectedArgs = "")
|
||||
{
|
||||
NormalizeTestCore(input, expectedExe, expectedArgs);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("cmd --run --test", "C:\\Windows\\System32\\cmd.exe", "--run --test")]
|
||||
[DataRow("cmd --run --test ", "C:\\Windows\\System32\\cmd.exe", "--run --test")]
|
||||
[DataRow("cmd \"--run --test\" --pass", "C:\\Windows\\System32\\cmd.exe", "--run --test --pass")]
|
||||
public void NormalizeArgsWithSpaces(string input, string expectedExe, string expectedArgs = "")
|
||||
{
|
||||
NormalizeTestCore(input, expectedExe, expectedArgs);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("ThereIsNoWayYouHaveAnExecutableNamedThisOnThePipeline", "ThereIsNoWayYouHaveAnExecutableNamedThisOnThePipeline", "")]
|
||||
[DataRow("C:\\ThisPathDoesNotExist\\NoExecutable.exe", "C:\\ThisPathDoesNotExist\\NoExecutable.exe", "")]
|
||||
public void NormalizeNonExistentExecutable(string input, string expectedExe, string expectedArgs = "")
|
||||
{
|
||||
NormalizeTestCore(input, expectedExe, expectedArgs);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("C:\\Windows", "c:\\Windows", "")]
|
||||
[DataRow("C:\\Windows foo /bar", "c:\\Windows", "foo /bar")]
|
||||
public void NormalizeDirectoryAsExecutable(string input, string expectedExe, string expectedArgs = "")
|
||||
{
|
||||
NormalizeTestCore(input, expectedExe, expectedArgs);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -74,6 +75,8 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
[DataRow("ping bing.com", "ping.exe")]
|
||||
[DataRow("curl bing.com", "curl.exe")]
|
||||
[DataRow("ipconfig /all", "ipconfig.exe")]
|
||||
[DataRow("\"C:\\Program Files\\Windows Defender\\MsMpEng.exe\"", "MsMpEng.exe")]
|
||||
[DataRow("C:\\Program Files\\Windows Defender\\MsMpEng.exe", "MsMpEng.exe")]
|
||||
public async Task QueryWithoutHistoryCommand(string command, string exeName)
|
||||
{
|
||||
// Setup
|
||||
@@ -82,19 +85,24 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
|
||||
var pages = new ShellListPage(settings, mockHistory.Object);
|
||||
|
||||
pages.UpdateSearchText(string.Empty, command);
|
||||
|
||||
// wait for about 1s.
|
||||
await Task.Delay(1000);
|
||||
await UpdatePageAndWaitForItems(pages, () =>
|
||||
{
|
||||
// Test: Search for a command that exists in history
|
||||
pages.UpdateSearchText(string.Empty, command);
|
||||
});
|
||||
|
||||
var commandList = pages.GetItems();
|
||||
|
||||
Assert.AreEqual(1, commandList.Length);
|
||||
|
||||
var executeCommand = commandList.FirstOrDefault();
|
||||
Assert.IsNotNull(executeCommand);
|
||||
Assert.IsNotNull(executeCommand.Icon);
|
||||
Assert.IsTrue(executeCommand.Title.Contains(exeName), $"expect ${exeName} but got ${executeCommand.Title}");
|
||||
var listItem = commandList.FirstOrDefault();
|
||||
Assert.IsNotNull(listItem);
|
||||
|
||||
var runExeListItem = listItem as RunExeItem;
|
||||
Assert.IsNotNull(runExeListItem);
|
||||
Assert.AreEqual(exeName, runExeListItem.Exe);
|
||||
Assert.IsTrue(listItem.Title.Contains(exeName), $"expect ${exeName} but got ${listItem.Title}");
|
||||
Assert.IsNotNull(listItem.Icon);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -109,10 +117,11 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
|
||||
var pages = new ShellListPage(settings, mockHistoryService.Object);
|
||||
|
||||
// Test: Search for a command that exists in history
|
||||
pages.UpdateSearchText(string.Empty, command);
|
||||
|
||||
await Task.Delay(1000);
|
||||
await UpdatePageAndWaitForItems(pages, () =>
|
||||
{
|
||||
// Test: Search for a command that exists in history
|
||||
pages.UpdateSearchText(string.Empty, command);
|
||||
});
|
||||
|
||||
var commandList = pages.GetItems();
|
||||
|
||||
@@ -134,9 +143,11 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
|
||||
var pages = new ShellListPage(settings, mockHistoryService.Object);
|
||||
|
||||
pages.UpdateSearchText("abcdefg", string.Empty);
|
||||
|
||||
await Task.Delay(1000);
|
||||
await UpdatePageAndWaitForItems(pages, () =>
|
||||
{
|
||||
// Test: Search for a command that exists in history
|
||||
pages.UpdateSearchText("abcdefg", string.Empty);
|
||||
});
|
||||
|
||||
var commandList = pages.GetItems();
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
@@ -16,10 +18,23 @@ public class CommandPaletteUnitTestBase
|
||||
|
||||
public IListItem[] Query(string query, IListItem[] candidates)
|
||||
{
|
||||
IListItem[] listItems = candidates
|
||||
var listItems = candidates
|
||||
.Where(item => MatchesFilter(query, item))
|
||||
.ToArray();
|
||||
|
||||
return listItems;
|
||||
}
|
||||
|
||||
public async Task UpdatePageAndWaitForItems(IDynamicListPage page, Action modification)
|
||||
{
|
||||
// Add an event handler for the ItemsChanged event,
|
||||
// Then call the modification action,
|
||||
// and wait for the event to be raised.
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
|
||||
page.ItemsChanged += (sender, args) => tcs.SetResult(null);
|
||||
|
||||
modification();
|
||||
await tcs.Task;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user