diff --git a/misc/buffer-tests/Makefile b/misc/buffer-tests/Makefile index b88bb15..57a69d3 100644 --- a/misc/buffer-tests/Makefile +++ b/misc/buffer-tests/Makefile @@ -51,7 +51,7 @@ COMMON_OBJECTS = \ build/winpty_wcsnlen.o WORKER_OBJECTS = build/WorkerProgram.o -TEST_OBJECTS = build/TestCommon.o +TEST_OBJECTS = build/RemoteHandle.o build/RemoteWorker.o all : \ build/Test_GetConsoleTitleW.exe \ diff --git a/misc/buffer-tests/TestHandleInheritance.cc b/misc/buffer-tests/TestHandleInheritance.cc index 8c75adc..dd1787a 100644 --- a/misc/buffer-tests/TestHandleInheritance.cc +++ b/misc/buffer-tests/TestHandleInheritance.cc @@ -4,13 +4,7 @@ // Windows 10, inclusive. // -#include -#include -#include -#include - #include -#include #define CHECK(cond) \ do { \ diff --git a/misc/buffer-tests/Test_GetConsoleTitleW.cc b/misc/buffer-tests/Test_GetConsoleTitleW.cc index 40983db..e386a75 100644 --- a/misc/buffer-tests/Test_GetConsoleTitleW.cc +++ b/misc/buffer-tests/Test_GetConsoleTitleW.cc @@ -6,15 +6,7 @@ // * Windows 8 and up (at least to Windows 10) // -#include -#include -#include -#include - -#include #include -#include -#include #define CHECK_EQ(actual, expected) \ do { \ diff --git a/misc/buffer-tests/harness/RemoteHandle.cc b/misc/buffer-tests/harness/RemoteHandle.cc new file mode 100755 index 0000000..b685394 --- /dev/null +++ b/misc/buffer-tests/harness/RemoteHandle.cc @@ -0,0 +1,178 @@ +#include "RemoteHandle.h" + +#include + +#include "RemoteWorker.h" + +#include +#include + +RemoteHandle RemoteHandle::dup(HANDLE h, RemoteWorker &target, + BOOL bInheritHandle) { + HANDLE targetHandle; + BOOL success = DuplicateHandle( + GetCurrentProcess(), + h, + target.m_process, + &targetHandle, + 0, bInheritHandle, DUPLICATE_SAME_ACCESS); + ASSERT(success && "DuplicateHandle failed"); + return RemoteHandle(targetHandle, target); +} + +RemoteHandle &RemoteHandle::activate() { + worker().cmd().handle = m_value; + worker().rpc(Command::SetActiveBuffer); + return *this; +} + +void RemoteHandle::write(const std::string &msg) { + worker().cmd().handle = m_value; + worker().cmd().u.writeText = msg; + worker().rpc(Command::WriteText); +} + +void RemoteHandle::close() { + worker().cmd().handle = m_value; + worker().rpc(Command::Close); +} + +RemoteHandle &RemoteHandle::setStdin() { + worker().cmd().handle = m_value; + worker().rpc(Command::SetStdin); + return *this; +} + +RemoteHandle &RemoteHandle::setStdout() { + worker().cmd().handle = m_value; + worker().rpc(Command::SetStdout); + return *this; +} + +RemoteHandle &RemoteHandle::setStderr() { + worker().cmd().handle = m_value; + worker().rpc(Command::SetStderr); + return *this; +} + +RemoteHandle RemoteHandle::dup(RemoteWorker &target, BOOL bInheritHandle) { + HANDLE targetProcessFromSource; + + if (&target == &worker()) { + targetProcessFromSource = GetCurrentProcess(); + } else { + // Allow the source worker to see the target worker. + targetProcessFromSource = INVALID_HANDLE_VALUE; + BOOL success = DuplicateHandle( + GetCurrentProcess(), + target.m_process, + worker().m_process, + &targetProcessFromSource, + 0, FALSE, DUPLICATE_SAME_ACCESS); + ASSERT(success && "Process handle duplication failed"); + } + + // Do the user-level duplication in the source process. + worker().cmd().handle = m_value; + worker().cmd().targetProcess = targetProcessFromSource; + worker().cmd().bInheritHandle = bInheritHandle; + worker().rpc(Command::Duplicate); + HANDLE retHandle = worker().cmd().handle; + + if (&target != &worker()) { + // Cleanup targetProcessFromSource. + worker().cmd().handle = targetProcessFromSource; + worker().rpc(Command::CloseQuietly); + ASSERT(worker().cmd().success && + "Error closing remote process handle"); + } + + return RemoteHandle(retHandle, target); +} + +CONSOLE_SCREEN_BUFFER_INFO RemoteHandle::screenBufferInfo() { + CONSOLE_SCREEN_BUFFER_INFO ret; + bool success = tryScreenBufferInfo(&ret); + ASSERT(success && "GetConsoleScreenBufferInfo failed"); + return ret; +} + +bool RemoteHandle::tryScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO *info) { + worker().cmd().handle = m_value; + worker().rpc(Command::GetConsoleScreenBufferInfo); + if (worker().cmd().success && info != nullptr) { + *info = worker().cmd().u.consoleScreenBufferInfo; + } + return worker().cmd().success; +} + +DWORD RemoteHandle::flags() { + DWORD ret; + bool success = tryFlags(&ret); + ASSERT(success && "GetHandleInformation failed"); + return ret; +} + +bool RemoteHandle::tryFlags(DWORD *flags) { + worker().cmd().handle = m_value; + worker().rpc(Command::GetHandleInformation); + if (worker().cmd().success && flags != nullptr) { + *flags = worker().cmd().dword; + } + return worker().cmd().success; +} + +void RemoteHandle::setFlags(DWORD mask, DWORD flags) { + bool success = trySetFlags(mask, flags); + ASSERT(success && "SetHandleInformation failed"); +} + +bool RemoteHandle::trySetFlags(DWORD mask, DWORD flags) { + worker().cmd().handle = m_value; + worker().cmd().u.setFlags.mask = mask; + worker().cmd().u.setFlags.flags = flags; + worker().rpc(Command::SetHandleInformation); + return worker().cmd().success; +} + +wchar_t RemoteHandle::firstChar() { + // The "first char" is useful for identifying which output buffer a handle + // refers to. + worker().cmd().handle = m_value; + const SMALL_RECT region = {}; + auto &io = worker().cmd().u.consoleIo; + io.bufferSize = { 1, 1 }; + io.bufferCoord = {}; + io.ioRegion = region; + worker().rpc(Command::ReadConsoleOutput); + ASSERT(worker().cmd().success); + ASSERT(!memcmp(&io.ioRegion, ®ion, sizeof(region))); + return io.buffer[0].Char.UnicodeChar; +} + +RemoteHandle &RemoteHandle::setFirstChar(wchar_t ch) { + // The "first char" is useful for identifying which output buffer a handle + // refers to. + worker().cmd().handle = m_value; + const SMALL_RECT region = {}; + auto &io = worker().cmd().u.consoleIo; + io.buffer[0].Char.UnicodeChar = ch; + io.buffer[0].Attributes = 7; + io.bufferSize = { 1, 1 }; + io.bufferCoord = {}; + io.ioRegion = region; + worker().rpc(Command::WriteConsoleOutput); + ASSERT(worker().cmd().success); + ASSERT(!memcmp(&io.ioRegion, ®ion, sizeof(region))); + return *this; +} + +bool RemoteHandle::tryNumberOfConsoleInputEvents(DWORD *ret) { + worker().cmd().handle = m_value; + worker().rpc(Command::GetNumberOfConsoleInputEvents); + if (worker().cmd().success && ret != nullptr) { + *ret = worker().cmd().dword; + } + return worker().cmd().success; +} + diff --git a/misc/buffer-tests/harness/RemoteHandle.h b/misc/buffer-tests/harness/RemoteHandle.h new file mode 100755 index 0000000..658e944 --- /dev/null +++ b/misc/buffer-tests/harness/RemoteHandle.h @@ -0,0 +1,54 @@ +#pragma once + +#include + +#include +#include + +class RemoteWorker; + +class RemoteHandle { + friend class RemoteWorker; + +private: + RemoteHandle(HANDLE value, RemoteWorker &worker) : + m_value(value), m_worker(&worker) + { + } + +public: + static RemoteHandle invent(HANDLE h, RemoteWorker &worker) { + return RemoteHandle(h, worker); + } + static RemoteHandle invent(uint64_t h, RemoteWorker &worker) { + return RemoteHandle(reinterpret_cast(h), worker); + } + RemoteHandle &activate(); + void write(const std::string &msg); + void close(); + RemoteHandle &setStdin(); + RemoteHandle &setStdout(); + RemoteHandle &setStderr(); + RemoteHandle dup(RemoteWorker &target, BOOL bInheritHandle=FALSE); + RemoteHandle dup(BOOL bInheritHandle=FALSE) { + return dup(worker(), bInheritHandle); + } + static RemoteHandle dup(HANDLE h, RemoteWorker &target, + BOOL bInheritHandle=FALSE); + CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo(); + bool tryScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO *info=nullptr); + DWORD flags(); + bool tryFlags(DWORD *flags=nullptr); + void setFlags(DWORD mask, DWORD flags); + bool trySetFlags(DWORD mask, DWORD flags); + wchar_t firstChar(); + RemoteHandle &setFirstChar(wchar_t ch); + bool tryNumberOfConsoleInputEvents(DWORD *ret=nullptr); + HANDLE value() const { return m_value; } + uint64_t uvalue() const { return reinterpret_cast(m_value); } + RemoteWorker &worker() const { return *m_worker; } + +private: + HANDLE m_value; + RemoteWorker *m_worker; +}; diff --git a/misc/buffer-tests/harness/RemoteWorker.cc b/misc/buffer-tests/harness/RemoteWorker.cc new file mode 100755 index 0000000..f4dd1b5 --- /dev/null +++ b/misc/buffer-tests/harness/RemoteWorker.cc @@ -0,0 +1,154 @@ +#include "RemoteWorker.h" + +#include + +#include "UnicodeConversions.h" + +#include +#include + +namespace { + +static std::string timeString() { + FILETIME fileTime; + GetSystemTimeAsFileTime(&fileTime); + auto ret = ((uint64_t)fileTime.dwHighDateTime << 32) | + fileTime.dwLowDateTime; + return std::to_string(ret); +} + +static std::string newWorkerName() { + static int workerCounter = 0; + static auto initialTimeString = timeString(); + return std::string("WinptyBufferTests-") + + std::to_string(static_cast(GetCurrentProcessId())) + "-" + + initialTimeString + "-" + + std::to_string(++workerCounter); +} + +} // anonymous namespace + +RemoteWorker::RemoteWorker(const std::string &name) : + m_name(name), + m_parcel(name + "-shmem", ShmemParcel::CreateNew), + m_startEvent(name + "-start"), + m_finishEvent(name + "-finish") +{ + m_finishEvent.set(); +} + +RemoteWorker::RemoteWorker(SpawnParams params) : + RemoteWorker(newWorkerName()) { + m_process = spawn(m_name, params); + // Perform an RPC just to ensure that the worker process is ready, and + // the console exists, before returning. + rpc(Command::GetStdin); +} + +RemoteWorker RemoteWorker::child(SpawnParams params) { + RemoteWorker ret(newWorkerName()); + cmd().u.spawn.spawnName = ret.m_name; + cmd().u.spawn.spawnParams = params; + rpc(Command::SpawnChild); + BOOL dupSuccess = DuplicateHandle( + m_process, + cmd().handle, + GetCurrentProcess(), + &ret.m_process, + 0, FALSE, DUPLICATE_SAME_ACCESS); + ASSERT(dupSuccess && "RemoteWorker::child: DuplicateHandle failed"); + rpc(Command::CloseQuietly); + ASSERT(cmd().success && "RemoteWorker::child: CloseHandle failed"); + // Perform an RPC just to ensure that the worker process is ready, and + // the console exists, before returning. + ret.rpc(Command::GetStdin); + return ret; +} + +RemoteWorker::~RemoteWorker() { + cleanup(); +} + +void RemoteWorker::cleanup() { + if (m_valid) { + cmd().dword = 0; + rpcAsync(Command::Exit); + DWORD result = WaitForSingleObject(m_process, INFINITE); + ASSERT(result == WAIT_OBJECT_0 && + "WaitForSingleObject failed while killing worker"); + CloseHandle(m_process); + m_valid = false; + } +} + +CONSOLE_SELECTION_INFO RemoteWorker::selectionInfo() { + rpc(Command::GetConsoleSelectionInfo); + ASSERT(cmd().success); + return cmd().u.consoleSelectionInfo; +} + +void RemoteWorker::dumpConsoleHandles(BOOL writeToEach) { + cmd().writeToEach = writeToEach; + rpc(Command::DumpConsoleHandles); +} + +std::vector RemoteWorker::scanForConsoleHandles() { + rpc(Command::ScanForConsoleHandles); + auto &rpcTable = cmd().u.scanForConsoleHandles; + std::vector ret; + for (int i = 0; i < rpcTable.count; ++i) { + ret.push_back(RemoteHandle(rpcTable.table[i], *this)); + } + return ret; +} + +bool RemoteWorker::setTitleInternal(const std::wstring &wstr) { + ASSERT(wstr.size() < cmd().u.consoleTitle.size()); + ASSERT(wstr.size() == wcslen(wstr.c_str())); + wcscpy(cmd().u.consoleTitle.data(), wstr.c_str()); + rpc(Command::SetConsoleTitle); + return cmd().success; +} + +std::string RemoteWorker::title() { + std::array buf; + DWORD ret = titleInternal(buf, buf.size()); + ret = std::min(ret, buf.size() - 1); + buf[std::min(buf.size() - 1, ret)] = L'\0'; + return narrowString(std::wstring(buf.data())); +} + +// This API is more low-level than typical, because GetConsoleTitleW is buggy +// in older versions of Windows, and this method is used to test the bugs. +DWORD RemoteWorker::titleInternal(std::array &buf, DWORD bufSize) { + cmd().dword = bufSize; + cmd().u.consoleTitle = buf; + rpc(Command::GetConsoleTitle); + buf = cmd().u.consoleTitle; + return cmd().dword; +} + +std::vector RemoteWorker::consoleProcessList() { + rpc(Command::GetConsoleProcessList); + DWORD count = cmd().dword; + ASSERT(count <= cmd().u.processList.size()); + return std::vector( + &cmd().u.processList[0], + &cmd().u.processList[count]); +} + +void RemoteWorker::rpc(Command::Kind kind) { + rpcImpl(kind); + m_finishEvent.wait(); +} + +void RemoteWorker::rpcAsync(Command::Kind kind) { + rpcImpl(kind); +} + +void RemoteWorker::rpcImpl(Command::Kind kind) { + m_finishEvent.wait(); + m_finishEvent.reset(); + cmd().kind = kind; + m_startEvent.set(); +} diff --git a/misc/buffer-tests/harness/RemoteWorker.h b/misc/buffer-tests/harness/RemoteWorker.h new file mode 100755 index 0000000..0a57591 --- /dev/null +++ b/misc/buffer-tests/harness/RemoteWorker.h @@ -0,0 +1,111 @@ +#pragma once + +#include + +#include +#include + +#include "Command.h" +#include "Event.h" +#include "RemoteHandle.h" +#include "ShmemParcel.h" +#include "Spawn.h" +#include "UnicodeConversions.h" + +class RemoteWorker { + friend class RemoteHandle; + +private: + RemoteWorker(const std::string &name); +public: + RemoteWorker() : RemoteWorker(SpawnParams { false, CREATE_NEW_CONSOLE }) {} + RemoteWorker(SpawnParams params); + RemoteWorker child() { return child(SpawnParams {}); } + RemoteWorker child(SpawnParams params); + ~RemoteWorker(); +private: + void cleanup(); +public: + + // basic worker info + HANDLE processHandle() { return m_process; } + DWORD pid() { return GetProcessId(m_process); } + + // allow moving + RemoteWorker(RemoteWorker &&other) : + m_name(std::move(other.m_name)), + m_parcel(std::move(other.m_parcel)), + m_startEvent(std::move(other.m_startEvent)), + m_finishEvent(std::move(other.m_finishEvent)), + m_process(std::move(other.m_process)) + { + other.m_valid = false; + } + RemoteWorker &operator=(RemoteWorker &&other) { + cleanup(); + m_name = std::move(other.m_name); + m_parcel = std::move(other.m_parcel); + m_startEvent = std::move(other.m_startEvent); + m_finishEvent = std::move(other.m_finishEvent); + m_process = std::move(other.m_process); + other.m_valid = false; + m_valid = true; + return *this; + } + + // Commands + RemoteHandle getStdin() { rpc(Command::GetStdin); return RemoteHandle(cmd().handle, *this); } + RemoteHandle getStdout() { rpc(Command::GetStdout); return RemoteHandle(cmd().handle, *this); } + RemoteHandle getStderr() { rpc(Command::GetStderr); return RemoteHandle(cmd().handle, *this); } + bool detach() { rpc(Command::FreeConsole); return cmd().success; } + bool attach(RemoteWorker &worker) { cmd().dword = GetProcessId(worker.m_process); rpc(Command::AttachConsole); return cmd().success; } + bool alloc() { rpc(Command::AllocConsole); return cmd().success; } + void dumpStandardHandles() { rpc(Command::DumpStandardHandles); } + int system(const std::string &arg) { cmd().u.systemText = arg; rpc(Command::System); return cmd().dword; } + HWND consoleWindow() { rpc(Command::GetConsoleWindow); return cmd().hwnd; } + + CONSOLE_SELECTION_INFO selectionInfo(); + void dumpConsoleHandles(BOOL writeToEach=FALSE); + std::vector scanForConsoleHandles(); + void setTitle(const std::string &str) { auto b = setTitleInternal(widenString(str)); ASSERT(b && "setTitle failed"); } + bool setTitleInternal(const std::wstring &str); + std::string title(); + DWORD titleInternal(std::array &buf, DWORD bufSize); + std::vector consoleProcessList(); + + RemoteHandle openConin(BOOL bInheritHandle=FALSE) { + cmd().bInheritHandle = bInheritHandle; + rpc(Command::OpenConin); + return RemoteHandle(cmd().handle, *this); + } + + RemoteHandle openConout(BOOL bInheritHandle=FALSE) { + cmd().bInheritHandle = bInheritHandle; + rpc(Command::OpenConout); + return RemoteHandle(cmd().handle, *this); + } + + RemoteHandle newBuffer(BOOL bInheritHandle=FALSE, wchar_t firstChar=L'\0') { + cmd().bInheritHandle = bInheritHandle; + rpc(Command::NewBuffer); + auto h = RemoteHandle(cmd().handle, *this); + if (firstChar != L'\0') { + h.setFirstChar(firstChar); + } + return h; + } + +private: + Command &cmd() { return m_parcel.value(); } + void rpc(Command::Kind kind); + void rpcAsync(Command::Kind kind); + void rpcImpl(Command::Kind kind); + +private: + bool m_valid = true; + std::string m_name; + ShmemParcelTyped m_parcel; + Event m_startEvent; + Event m_finishEvent; + HANDLE m_process = NULL; +}; diff --git a/misc/buffer-tests/harness/TestCommon.cc b/misc/buffer-tests/harness/TestCommon.cc deleted file mode 100644 index 13140a2..0000000 --- a/misc/buffer-tests/harness/TestCommon.cc +++ /dev/null @@ -1,317 +0,0 @@ -#include "TestCommon.h" - -#include - -#include "UnicodeConversions.h" - -#include -#include - -Handle Handle::dup(HANDLE h, Worker &target, BOOL bInheritHandle) { - HANDLE targetHandle; - BOOL success = DuplicateHandle( - GetCurrentProcess(), - h, - target.m_process, - &targetHandle, - 0, bInheritHandle, DUPLICATE_SAME_ACCESS); - ASSERT(success && "DuplicateHandle failed"); - return Handle(targetHandle, target); -} - -Handle &Handle::activate() { - worker().cmd().handle = m_value; - worker().rpc(Command::SetActiveBuffer); - return *this; -} - -void Handle::write(const std::string &msg) { - worker().cmd().handle = m_value; - worker().cmd().u.writeText = msg; - worker().rpc(Command::WriteText); -} - -void Handle::close() { - worker().cmd().handle = m_value; - worker().rpc(Command::Close); -} - -Handle &Handle::setStdin() { - worker().cmd().handle = m_value; - worker().rpc(Command::SetStdin); - return *this; -} - -Handle &Handle::setStdout() { - worker().cmd().handle = m_value; - worker().rpc(Command::SetStdout); - return *this; -} - -Handle &Handle::setStderr() { - worker().cmd().handle = m_value; - worker().rpc(Command::SetStderr); - return *this; -} - -Handle Handle::dup(Worker &target, BOOL bInheritHandle) { - HANDLE targetProcessFromSource; - - if (&target == &worker()) { - targetProcessFromSource = GetCurrentProcess(); - } else { - // Allow the source worker to see the target worker. - targetProcessFromSource = INVALID_HANDLE_VALUE; - BOOL success = DuplicateHandle( - GetCurrentProcess(), - target.m_process, - worker().m_process, - &targetProcessFromSource, - 0, FALSE, DUPLICATE_SAME_ACCESS); - ASSERT(success && "Process handle duplication failed"); - } - - // Do the user-level duplication in the source process. - worker().cmd().handle = m_value; - worker().cmd().targetProcess = targetProcessFromSource; - worker().cmd().bInheritHandle = bInheritHandle; - worker().rpc(Command::Duplicate); - HANDLE retHandle = worker().cmd().handle; - - if (&target != &worker()) { - // Cleanup targetProcessFromSource. - worker().cmd().handle = targetProcessFromSource; - worker().rpc(Command::CloseQuietly); - ASSERT(worker().cmd().success && - "Error closing remote process handle"); - } - - return Handle(retHandle, target); -} - -CONSOLE_SCREEN_BUFFER_INFO Handle::screenBufferInfo() { - CONSOLE_SCREEN_BUFFER_INFO ret; - bool success = tryScreenBufferInfo(&ret); - ASSERT(success && "GetConsoleScreenBufferInfo failed"); - return ret; -} - -bool Handle::tryScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO *info) { - worker().cmd().handle = m_value; - worker().rpc(Command::GetConsoleScreenBufferInfo); - if (worker().cmd().success && info != nullptr) { - *info = worker().cmd().u.consoleScreenBufferInfo; - } - return worker().cmd().success; -} - -DWORD Handle::flags() { - DWORD ret; - bool success = tryFlags(&ret); - ASSERT(success && "GetHandleInformation failed"); - return ret; -} - -bool Handle::tryFlags(DWORD *flags) { - worker().cmd().handle = m_value; - worker().rpc(Command::GetHandleInformation); - if (worker().cmd().success && flags != nullptr) { - *flags = worker().cmd().dword; - } - return worker().cmd().success; -} - -void Handle::setFlags(DWORD mask, DWORD flags) { - bool success = trySetFlags(mask, flags); - ASSERT(success && "SetHandleInformation failed"); -} - -bool Handle::trySetFlags(DWORD mask, DWORD flags) { - worker().cmd().handle = m_value; - worker().cmd().u.setFlags.mask = mask; - worker().cmd().u.setFlags.flags = flags; - worker().rpc(Command::SetHandleInformation); - return worker().cmd().success; -} - -wchar_t Handle::firstChar() { - // The "first char" is useful for identifying which output buffer a handle - // refers to. - worker().cmd().handle = m_value; - const SMALL_RECT region = {}; - auto &io = worker().cmd().u.consoleIo; - io.bufferSize = { 1, 1 }; - io.bufferCoord = {}; - io.ioRegion = region; - worker().rpc(Command::ReadConsoleOutput); - ASSERT(worker().cmd().success); - ASSERT(!memcmp(&io.ioRegion, ®ion, sizeof(region))); - return io.buffer[0].Char.UnicodeChar; -} - -Handle &Handle::setFirstChar(wchar_t ch) { - // The "first char" is useful for identifying which output buffer a handle - // refers to. - worker().cmd().handle = m_value; - const SMALL_RECT region = {}; - auto &io = worker().cmd().u.consoleIo; - io.buffer[0].Char.UnicodeChar = ch; - io.buffer[0].Attributes = 7; - io.bufferSize = { 1, 1 }; - io.bufferCoord = {}; - io.ioRegion = region; - worker().rpc(Command::WriteConsoleOutput); - ASSERT(worker().cmd().success); - ASSERT(!memcmp(&io.ioRegion, ®ion, sizeof(region))); - return *this; -} - -bool Handle::tryNumberOfConsoleInputEvents(DWORD *ret) { - worker().cmd().handle = m_value; - worker().rpc(Command::GetNumberOfConsoleInputEvents); - if (worker().cmd().success && ret != nullptr) { - *ret = worker().cmd().dword; - } - return worker().cmd().success; -} - -static std::string timeString() { - FILETIME fileTime; - GetSystemTimeAsFileTime(&fileTime); - auto ret = ((uint64_t)fileTime.dwHighDateTime << 32) | - fileTime.dwLowDateTime; - return std::to_string(ret); -} - -static std::string newWorkerName() { - static int workerCounter = 0; - static auto initialTimeString = timeString(); - return std::string("WinptyBufferTests-") + - std::to_string(static_cast(GetCurrentProcessId())) + "-" + - initialTimeString + "-" + - std::to_string(++workerCounter); -} - -Worker::Worker(const std::string &name) : - m_name(name), - m_parcel(name + "-shmem", ShmemParcel::CreateNew), - m_startEvent(name + "-start"), - m_finishEvent(name + "-finish") -{ - m_finishEvent.set(); -} - -Worker::Worker(SpawnParams params) : Worker(newWorkerName()) { - m_process = spawn(m_name, params); - // Perform an RPC just to ensure that the worker process is ready, and - // the console exists, before returning. - rpc(Command::GetStdin); -} - -Worker Worker::child(SpawnParams params) { - Worker ret(newWorkerName()); - cmd().u.spawn.spawnName = ret.m_name; - cmd().u.spawn.spawnParams = params; - rpc(Command::SpawnChild); - BOOL dupSuccess = DuplicateHandle( - m_process, - cmd().handle, - GetCurrentProcess(), - &ret.m_process, - 0, FALSE, DUPLICATE_SAME_ACCESS); - ASSERT(dupSuccess && "Worker::child: DuplicateHandle failed"); - rpc(Command::CloseQuietly); - ASSERT(cmd().success && "Worker::child: CloseHandle failed"); - // Perform an RPC just to ensure that the worker process is ready, and - // the console exists, before returning. - ret.rpc(Command::GetStdin); - return ret; -} - -Worker::~Worker() { - cleanup(); -} - -void Worker::cleanup() { - if (m_valid) { - cmd().dword = 0; - rpcAsync(Command::Exit); - DWORD result = WaitForSingleObject(m_process, INFINITE); - ASSERT(result == WAIT_OBJECT_0 && - "WaitForSingleObject failed while killing worker"); - CloseHandle(m_process); - m_valid = false; - } -} - -CONSOLE_SELECTION_INFO Worker::selectionInfo() { - rpc(Command::GetConsoleSelectionInfo); - ASSERT(cmd().success); - return cmd().u.consoleSelectionInfo; -} - -void Worker::dumpConsoleHandles(BOOL writeToEach) { - cmd().writeToEach = writeToEach; - rpc(Command::DumpConsoleHandles); -} - -std::vector Worker::scanForConsoleHandles() { - rpc(Command::ScanForConsoleHandles); - auto &rpcTable = cmd().u.scanForConsoleHandles; - std::vector ret; - for (int i = 0; i < rpcTable.count; ++i) { - ret.push_back(Handle(rpcTable.table[i], *this)); - } - return ret; -} - -bool Worker::setTitleInternal(const std::wstring &wstr) { - ASSERT(wstr.size() < cmd().u.consoleTitle.size()); - ASSERT(wstr.size() == wcslen(wstr.c_str())); - wcscpy(cmd().u.consoleTitle.data(), wstr.c_str()); - rpc(Command::SetConsoleTitle); - return cmd().success; -} - -std::string Worker::title() { - std::array buf; - DWORD ret = titleInternal(buf, buf.size()); - ret = std::min(ret, buf.size() - 1); - buf[std::min(buf.size() - 1, ret)] = L'\0'; - return narrowString(std::wstring(buf.data())); -} - -// This API is more low-level than typical, because GetConsoleTitleW is buggy -// in older versions of Windows, and this method is used to test the bugs. -DWORD Worker::titleInternal(std::array &buf, DWORD bufSize) { - cmd().dword = bufSize; - cmd().u.consoleTitle = buf; - rpc(Command::GetConsoleTitle); - buf = cmd().u.consoleTitle; - return cmd().dword; -} - -std::vector Worker::consoleProcessList() { - rpc(Command::GetConsoleProcessList); - DWORD count = cmd().dword; - ASSERT(count <= cmd().u.processList.size()); - return std::vector( - &cmd().u.processList[0], - &cmd().u.processList[count]); -} - -void Worker::rpc(Command::Kind kind) { - rpcImpl(kind); - m_finishEvent.wait(); -} - -void Worker::rpcAsync(Command::Kind kind) { - rpcImpl(kind); -} - -void Worker::rpcImpl(Command::Kind kind) { - m_finishEvent.wait(); - m_finishEvent.reset(); - cmd().kind = kind; - m_startEvent.set(); -} diff --git a/misc/buffer-tests/harness/TestCommon.h b/misc/buffer-tests/harness/TestCommon.h index 82b2554..15a2503 100644 --- a/misc/buffer-tests/harness/TestCommon.h +++ b/misc/buffer-tests/harness/TestCommon.h @@ -1,151 +1,29 @@ #pragma once -#include - +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include +#include #include -#include "Command.h" -#include "Event.h" -#include "ShmemParcel.h" -#include "Spawn.h" +#include "NtHandleQuery.h" +#include "OsVersion.h" +#include "RemoteHandle.h" +#include "RemoteWorker.h" #include "UnicodeConversions.h" #include +#include +#include -class Worker; - -class Handle { - friend class Worker; - -private: - Handle(HANDLE value, Worker &worker) : m_value(value), m_worker(&worker) {} - -public: - static Handle invent(HANDLE h, Worker &worker) { return Handle(h, worker); } - static Handle invent(uint64_t h, Worker &worker) { return Handle(reinterpret_cast(h), worker); } - Handle &activate(); - void write(const std::string &msg); - void close(); - Handle &setStdin(); - Handle &setStdout(); - Handle &setStderr(); - Handle dup(Worker &target, BOOL bInheritHandle=FALSE); - Handle dup(BOOL bInheritHandle=FALSE) { return dup(worker(), bInheritHandle); } - static Handle dup(HANDLE h, Worker &target, BOOL bInheritHandle=FALSE); - CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo(); - bool tryScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO *info=nullptr); - DWORD flags(); - bool tryFlags(DWORD *flags=nullptr); - void setFlags(DWORD mask, DWORD flags); - bool trySetFlags(DWORD mask, DWORD flags); - wchar_t firstChar(); - Handle &setFirstChar(wchar_t ch); - bool tryNumberOfConsoleInputEvents(DWORD *ret=nullptr); - HANDLE value() const { return m_value; } - uint64_t uvalue() const { return reinterpret_cast(m_value); } - Worker &worker() const { return *m_worker; } - -private: - HANDLE m_value; - Worker *m_worker; -}; - -class Worker { - friend class Handle; - -private: - Worker(const std::string &name); -public: - Worker() : Worker(SpawnParams { false, CREATE_NEW_CONSOLE }) {} - Worker(SpawnParams params); - Worker child() { return child(SpawnParams {}); } - Worker child(SpawnParams params); - ~Worker(); -private: - void cleanup(); -public: - - // basic worker info - HANDLE processHandle() { return m_process; } - DWORD pid() { return GetProcessId(m_process); } - - // allow moving - Worker(Worker &&other) : - m_name(std::move(other.m_name)), - m_parcel(std::move(other.m_parcel)), - m_startEvent(std::move(other.m_startEvent)), - m_finishEvent(std::move(other.m_finishEvent)), - m_process(std::move(other.m_process)) - { - other.m_valid = false; - } - Worker &operator=(Worker &&other) { - cleanup(); - m_name = std::move(other.m_name); - m_parcel = std::move(other.m_parcel); - m_startEvent = std::move(other.m_startEvent); - m_finishEvent = std::move(other.m_finishEvent); - m_process = std::move(other.m_process); - other.m_valid = false; - m_valid = true; - return *this; - } - - // Commands - Handle getStdin() { rpc(Command::GetStdin); return Handle(cmd().handle, *this); } - Handle getStdout() { rpc(Command::GetStdout); return Handle(cmd().handle, *this); } - Handle getStderr() { rpc(Command::GetStderr); return Handle(cmd().handle, *this); } - bool detach() { rpc(Command::FreeConsole); return cmd().success; } - bool attach(Worker &worker) { cmd().dword = GetProcessId(worker.m_process); rpc(Command::AttachConsole); return cmd().success; } - bool alloc() { rpc(Command::AllocConsole); return cmd().success; } - void dumpStandardHandles() { rpc(Command::DumpStandardHandles); } - int system(const std::string &arg) { cmd().u.systemText = arg; rpc(Command::System); return cmd().dword; } - HWND consoleWindow() { rpc(Command::GetConsoleWindow); return cmd().hwnd; } - - CONSOLE_SELECTION_INFO selectionInfo(); - void dumpConsoleHandles(BOOL writeToEach=FALSE); - std::vector scanForConsoleHandles(); - void setTitle(const std::string &str) { auto b = setTitleInternal(widenString(str)); ASSERT(b && "setTitle failed"); } - bool setTitleInternal(const std::wstring &str); - std::string title(); - DWORD titleInternal(std::array &buf, DWORD bufSize); - std::vector consoleProcessList(); - - Handle openConin(BOOL bInheritHandle=FALSE) { - cmd().bInheritHandle = bInheritHandle; - rpc(Command::OpenConin); - return Handle(cmd().handle, *this); - } - - Handle openConout(BOOL bInheritHandle=FALSE) { - cmd().bInheritHandle = bInheritHandle; - rpc(Command::OpenConout); - return Handle(cmd().handle, *this); - } - - Handle newBuffer(BOOL bInheritHandle=FALSE, wchar_t firstChar=L'\0') { - cmd().bInheritHandle = bInheritHandle; - rpc(Command::NewBuffer); - auto h = Handle(cmd().handle, *this); - if (firstChar != L'\0') { - h.setFirstChar(firstChar); - } - return h; - } - -private: - Command &cmd() { return m_parcel.value(); } - void rpc(Command::Kind kind); - void rpcAsync(Command::Kind kind); - void rpcImpl(Command::Kind kind); - -private: - bool m_valid = true; - std::string m_name; - ShmemParcelTyped m_parcel; - Event m_startEvent; - Event m_finishEvent; - HANDLE m_process = NULL; -}; +using Handle = RemoteHandle; +using Worker = RemoteWorker; diff --git a/misc/buffer-tests/harness/pch.h b/misc/buffer-tests/harness/pch.h index 05f3440..06be30c 100755 --- a/misc/buffer-tests/harness/pch.h +++ b/misc/buffer-tests/harness/pch.h @@ -8,11 +8,14 @@ #include #include +#include #include #include #include #include #include +#include +#include #include #include #include