mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-09 04:37:30 +02:00
[UI automation test] Add basic tests case for powerrename module. (#40393)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request 1. Add command args support in ui test core 2. Add command line parse logic in powerrename 3. Add some test cases. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] **Closes:** #xxx - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Yu Leng <yuleng@microsoft.com>
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/logger/logger_settings.h>
|
||||
@@ -28,6 +30,61 @@ std::vector<std::wstring> g_files;
|
||||
|
||||
const std::wstring moduleName = L"PowerRename";
|
||||
|
||||
// Helper function to parse command line arguments for file paths
|
||||
std::vector<std::wstring> ParseCommandLineArgs(const std::wstring& commandLine)
|
||||
{
|
||||
std::vector<std::wstring> filePaths;
|
||||
|
||||
// Skip executable name
|
||||
size_t argsStart = 0;
|
||||
if (!commandLine.empty() && commandLine[0] == L'"')
|
||||
{
|
||||
argsStart = commandLine.find(L'"', 1);
|
||||
if (argsStart != std::wstring::npos) argsStart++;
|
||||
}
|
||||
else
|
||||
{
|
||||
argsStart = commandLine.find_first_of(L" \t");
|
||||
}
|
||||
|
||||
if (argsStart == std::wstring::npos) return filePaths;
|
||||
|
||||
// Get the arguments part
|
||||
std::wstring args = commandLine.substr(argsStart);
|
||||
|
||||
// Simple split with quote handling
|
||||
std::wstring current;
|
||||
bool inQuotes = false;
|
||||
|
||||
for (wchar_t ch : args)
|
||||
{
|
||||
if (ch == L'"')
|
||||
{
|
||||
inQuotes = !inQuotes;
|
||||
}
|
||||
else if ((ch == L' ' || ch == L'\t') && !inQuotes)
|
||||
{
|
||||
if (!current.empty())
|
||||
{
|
||||
filePaths.push_back(current);
|
||||
current.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
current += ch;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last argument if any
|
||||
if (!current.empty())
|
||||
{
|
||||
filePaths.push_back(current);
|
||||
}
|
||||
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
@@ -69,131 +126,147 @@ void App::OnLaunched(LaunchActivatedEventArgs const&)
|
||||
}
|
||||
|
||||
auto args = std::wstring{ GetCommandLine() };
|
||||
size_t pos{ args.rfind(L"\\\\.\\pipe\\") };
|
||||
|
||||
std::wstring pipe_name;
|
||||
if (pos != std::wstring::npos)
|
||||
|
||||
// Try to parse command line arguments first
|
||||
std::vector<std::wstring> cmdLineFiles = ParseCommandLineArgs(args);
|
||||
|
||||
if (!cmdLineFiles.empty())
|
||||
{
|
||||
pipe_name = args.substr(pos);
|
||||
}
|
||||
|
||||
HANDLE hStdin;
|
||||
|
||||
if (pipe_name.size() > 0)
|
||||
{
|
||||
while (1)
|
||||
// Use command line arguments for UI testing
|
||||
for (const auto& filePath : cmdLineFiles)
|
||||
{
|
||||
hStdin = CreateFile(
|
||||
pipe_name.c_str(), // pipe name
|
||||
GENERIC_READ | GENERIC_WRITE, // read and write
|
||||
0, // no sharing
|
||||
NULL, // default security attributes
|
||||
OPEN_EXISTING, // opens existing pipe
|
||||
0, // default attributes
|
||||
NULL); // no template file
|
||||
|
||||
// Break if the pipe handle is valid.
|
||||
if (hStdin != INVALID_HANDLE_VALUE)
|
||||
break;
|
||||
|
||||
// Exit if an error other than ERROR_PIPE_BUSY occurs.
|
||||
auto error = GetLastError();
|
||||
if (error != ERROR_PIPE_BUSY)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!WaitNamedPipe(pipe_name.c_str(), 3))
|
||||
{
|
||||
printf("Could not open pipe: 20 second wait timed out.");
|
||||
}
|
||||
g_files.push_back(filePath);
|
||||
}
|
||||
Logger::debug(L"Starting PowerRename with {} files from command line", g_files.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
hStdin = GetStdHandle(STD_INPUT_HANDLE);
|
||||
}
|
||||
// Use original pipe/stdin logic for normal operation
|
||||
size_t pos{ args.rfind(L"\\\\.\\pipe\\") };
|
||||
|
||||
if (hStdin == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
Logger::error(L"Invalid input handle.");
|
||||
ExitProcess(1);
|
||||
}
|
||||
std::wstring pipe_name;
|
||||
if (pos != std::wstring::npos)
|
||||
{
|
||||
pipe_name = args.substr(pos);
|
||||
}
|
||||
|
||||
HANDLE hStdin;
|
||||
|
||||
if (pipe_name.size() > 0)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
hStdin = CreateFile(
|
||||
pipe_name.c_str(), // pipe name
|
||||
GENERIC_READ | GENERIC_WRITE, // read and write
|
||||
0, // no sharing
|
||||
NULL, // default security attributes
|
||||
OPEN_EXISTING, // opens existing pipe
|
||||
0, // default attributes
|
||||
NULL); // no template file
|
||||
|
||||
// Break if the pipe handle is valid.
|
||||
if (hStdin != INVALID_HANDLE_VALUE)
|
||||
break;
|
||||
|
||||
// Exit if an error other than ERROR_PIPE_BUSY occurs.
|
||||
auto error = GetLastError();
|
||||
if (error != ERROR_PIPE_BUSY)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!WaitNamedPipe(pipe_name.c_str(), 3))
|
||||
{
|
||||
printf("Could not open pipe: 20 second wait timed out.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hStdin = GetStdHandle(STD_INPUT_HANDLE);
|
||||
}
|
||||
|
||||
if (hStdin == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
Logger::error(L"Invalid input handle.");
|
||||
ExitProcess(1);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_BENCHMARK_100K_ENTRIES
|
||||
const std::wstring_view ROOT_PATH = L"R:\\PowerRenameBenchmark";
|
||||
const std::wstring_view ROOT_PATH = L"R:\\PowerRenameBenchmark";
|
||||
|
||||
std::wstring subdirectory_name = L"0";
|
||||
std::error_code _;
|
||||
std::wstring subdirectory_name = L"0";
|
||||
std::error_code _;
|
||||
|
||||
#if 1
|
||||
constexpr bool recreate_files = true;
|
||||
constexpr bool recreate_files = true;
|
||||
#else
|
||||
constexpr bool recreate_files = false;
|
||||
constexpr bool recreate_files = false;
|
||||
#endif
|
||||
if constexpr (recreate_files)
|
||||
fs::remove_all(ROOT_PATH, _);
|
||||
|
||||
g_files.push_back(fs::path{ ROOT_PATH });
|
||||
constexpr int pow2_threshold = 10;
|
||||
constexpr int num_files = 100'000;
|
||||
for (int i = 0; i < num_files; ++i)
|
||||
{
|
||||
fs::path file_path{ ROOT_PATH };
|
||||
// Create a subdirectory for each subsequent 2^pow2_threshold files, o/w filesystem becomes too slow to create them in a reasonable time.
|
||||
if ((i & ((1 << pow2_threshold) - 1)) == 0)
|
||||
{
|
||||
subdirectory_name = std::to_wstring(i >> pow2_threshold);
|
||||
}
|
||||
|
||||
file_path /= subdirectory_name;
|
||||
|
||||
if constexpr (recreate_files)
|
||||
fs::remove_all(ROOT_PATH, _);
|
||||
|
||||
g_files.push_back(fs::path{ ROOT_PATH });
|
||||
constexpr int pow2_threshold = 10;
|
||||
constexpr int num_files = 100'000;
|
||||
for (int i = 0; i < num_files; ++i)
|
||||
{
|
||||
fs::create_directories(file_path, _);
|
||||
file_path /= std::to_wstring(i) + L".txt";
|
||||
HANDLE hFile = CreateFileW(
|
||||
file_path.c_str(),
|
||||
GENERIC_WRITE,
|
||||
0,
|
||||
nullptr,
|
||||
CREATE_NEW,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
nullptr);
|
||||
CloseHandle(hFile);
|
||||
fs::path file_path{ ROOT_PATH };
|
||||
// Create a subdirectory for each subsequent 2^pow2_threshold files, o/w filesystem becomes too slow to create them in a reasonable time.
|
||||
if ((i & ((1 << pow2_threshold) - 1)) == 0)
|
||||
{
|
||||
subdirectory_name = std::to_wstring(i >> pow2_threshold);
|
||||
}
|
||||
|
||||
file_path /= subdirectory_name;
|
||||
|
||||
if constexpr (recreate_files)
|
||||
{
|
||||
fs::create_directories(file_path, _);
|
||||
file_path /= std::to_wstring(i) + L".txt";
|
||||
HANDLE hFile = CreateFileW(
|
||||
file_path.c_str(),
|
||||
GENERIC_WRITE,
|
||||
0,
|
||||
nullptr,
|
||||
CREATE_NEW,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
nullptr);
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define BUFSIZE 4096 * 4
|
||||
|
||||
BOOL bSuccess;
|
||||
WCHAR chBuf[BUFSIZE];
|
||||
DWORD dwRead;
|
||||
for (;;)
|
||||
{
|
||||
// Read from standard input and stop on error or no data.
|
||||
bSuccess = ReadFile(hStdin, chBuf, BUFSIZE * sizeof(wchar_t), &dwRead, NULL);
|
||||
|
||||
if (!bSuccess || dwRead == 0)
|
||||
break;
|
||||
|
||||
std::wstring inputBatch{ chBuf, dwRead / sizeof(wchar_t) };
|
||||
|
||||
std::wstringstream ss(inputBatch);
|
||||
std::wstring item;
|
||||
wchar_t delimiter = '?';
|
||||
while (std::getline(ss, item, delimiter))
|
||||
BOOL bSuccess;
|
||||
WCHAR chBuf[BUFSIZE];
|
||||
DWORD dwRead;
|
||||
for (;;)
|
||||
{
|
||||
g_files.push_back(item);
|
||||
// Read from standard input and stop on error or no data.
|
||||
bSuccess = ReadFile(hStdin, chBuf, BUFSIZE * sizeof(wchar_t), &dwRead, NULL);
|
||||
|
||||
if (!bSuccess || dwRead == 0)
|
||||
break;
|
||||
|
||||
std::wstring inputBatch{ chBuf, dwRead / sizeof(wchar_t) };
|
||||
|
||||
std::wstringstream ss(inputBatch);
|
||||
std::wstring item;
|
||||
wchar_t delimiter = '?';
|
||||
while (std::getline(ss, item, delimiter))
|
||||
{
|
||||
g_files.push_back(item);
|
||||
}
|
||||
|
||||
if (!bSuccess)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!bSuccess)
|
||||
break;
|
||||
}
|
||||
CloseHandle(hStdin);
|
||||
CloseHandle(hStdin);
|
||||
#endif
|
||||
|
||||
Logger::debug(L"Starting PowerRename with {} files selected", g_files.size());
|
||||
Logger::debug(L"Starting PowerRename with {} files from pipe/stdin", g_files.size());
|
||||
}
|
||||
|
||||
window = make<MainWindow>();
|
||||
window.Activate();
|
||||
|
||||
Reference in New Issue
Block a user