Split TestCommon -> Remote{Worker,Handle} and add TestCommon.h uber-header
* TestCommon.h is a convenience header for the test cases. It aliases Remote{Worker,Handle} back to {Worker,Handle}, and it includes many basic C/C++ headers, as well as the parts of the harness suitable for test cases.
This commit is contained in:
parent
6c5a5aa670
commit
fb7a19c4f1
@ -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 \
|
||||
|
@ -4,13 +4,7 @@
|
||||
// Windows 10, inclusive.
|
||||
//
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include <TestCommon.h>
|
||||
#include <OsVersion.h>
|
||||
|
||||
#define CHECK(cond) \
|
||||
do { \
|
||||
|
@ -6,15 +6,7 @@
|
||||
// * Windows 8 and up (at least to Windows 10)
|
||||
//
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cwchar>
|
||||
#include <iostream>
|
||||
|
||||
#include <OsVersion.h>
|
||||
#include <TestCommon.h>
|
||||
#include <UnicodeConversions.h>
|
||||
#include <winpty_wcsnlen.h>
|
||||
|
||||
#define CHECK_EQ(actual, expected) \
|
||||
do { \
|
||||
|
178
misc/buffer-tests/harness/RemoteHandle.cc
Executable file
178
misc/buffer-tests/harness/RemoteHandle.cc
Executable file
@ -0,0 +1,178 @@
|
||||
#include "RemoteHandle.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "RemoteWorker.h"
|
||||
|
||||
#include <DebugClient.h>
|
||||
#include <WinptyAssert.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
54
misc/buffer-tests/harness/RemoteHandle.h
Executable file
54
misc/buffer-tests/harness/RemoteHandle.h
Executable file
@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
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<HANDLE>(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<uint64_t>(m_value); }
|
||||
RemoteWorker &worker() const { return *m_worker; }
|
||||
|
||||
private:
|
||||
HANDLE m_value;
|
||||
RemoteWorker *m_worker;
|
||||
};
|
154
misc/buffer-tests/harness/RemoteWorker.cc
Executable file
154
misc/buffer-tests/harness/RemoteWorker.cc
Executable file
@ -0,0 +1,154 @@
|
||||
#include "RemoteWorker.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "UnicodeConversions.h"
|
||||
|
||||
#include <DebugClient.h>
|
||||
#include <WinptyAssert.h>
|
||||
|
||||
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<int>(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<RemoteHandle> RemoteWorker::scanForConsoleHandles() {
|
||||
rpc(Command::ScanForConsoleHandles);
|
||||
auto &rpcTable = cmd().u.scanForConsoleHandles;
|
||||
std::vector<RemoteHandle> 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<wchar_t, 1024> buf;
|
||||
DWORD ret = titleInternal(buf, buf.size());
|
||||
ret = std::min<DWORD>(ret, buf.size() - 1);
|
||||
buf[std::min<size_t>(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<wchar_t, 1024> &buf, DWORD bufSize) {
|
||||
cmd().dword = bufSize;
|
||||
cmd().u.consoleTitle = buf;
|
||||
rpc(Command::GetConsoleTitle);
|
||||
buf = cmd().u.consoleTitle;
|
||||
return cmd().dword;
|
||||
}
|
||||
|
||||
std::vector<DWORD> RemoteWorker::consoleProcessList() {
|
||||
rpc(Command::GetConsoleProcessList);
|
||||
DWORD count = cmd().dword;
|
||||
ASSERT(count <= cmd().u.processList.size());
|
||||
return std::vector<DWORD>(
|
||||
&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();
|
||||
}
|
111
misc/buffer-tests/harness/RemoteWorker.h
Executable file
111
misc/buffer-tests/harness/RemoteWorker.h
Executable file
@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<RemoteHandle> 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<wchar_t, 1024> &buf, DWORD bufSize);
|
||||
std::vector<DWORD> 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<Command> m_parcel;
|
||||
Event m_startEvent;
|
||||
Event m_finishEvent;
|
||||
HANDLE m_process = NULL;
|
||||
};
|
@ -1,317 +0,0 @@
|
||||
#include "TestCommon.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "UnicodeConversions.h"
|
||||
|
||||
#include <DebugClient.h>
|
||||
#include <WinptyAssert.h>
|
||||
|
||||
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<int>(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<Handle> Worker::scanForConsoleHandles() {
|
||||
rpc(Command::ScanForConsoleHandles);
|
||||
auto &rpcTable = cmd().u.scanForConsoleHandles;
|
||||
std::vector<Handle> 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<wchar_t, 1024> buf;
|
||||
DWORD ret = titleInternal(buf, buf.size());
|
||||
ret = std::min<DWORD>(ret, buf.size() - 1);
|
||||
buf[std::min<size_t>(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<wchar_t, 1024> &buf, DWORD bufSize) {
|
||||
cmd().dword = bufSize;
|
||||
cmd().u.consoleTitle = buf;
|
||||
rpc(Command::GetConsoleTitle);
|
||||
buf = cmd().u.consoleTitle;
|
||||
return cmd().dword;
|
||||
}
|
||||
|
||||
std::vector<DWORD> Worker::consoleProcessList() {
|
||||
rpc(Command::GetConsoleProcessList);
|
||||
DWORD count = cmd().dword;
|
||||
ASSERT(count <= cmd().u.processList.size());
|
||||
return std::vector<DWORD>(
|
||||
&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();
|
||||
}
|
@ -1,151 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <cwchar>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#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 <DebugClient.h>
|
||||
#include <WinptyAssert.h>
|
||||
#include <winpty_wcsnlen.h>
|
||||
|
||||
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<HANDLE>(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<uint64_t>(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<Handle> 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<wchar_t, 1024> &buf, DWORD bufSize);
|
||||
std::vector<DWORD> 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<Command> m_parcel;
|
||||
Event m_startEvent;
|
||||
Event m_finishEvent;
|
||||
HANDLE m_process = NULL;
|
||||
};
|
||||
using Handle = RemoteHandle;
|
||||
using Worker = RemoteWorker;
|
||||
|
@ -8,11 +8,14 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <cwchar>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
Loading…
Reference in New Issue
Block a user