Migrate the ConPTY functional tests out of Windows (#4648)
## Summary of the Pull Request This will allow us to run the ConPTY tests in CI. ## PR Checklist * [x] Closes MSFT:24265197 * [X] I've discussed this with core contributors already. ## Validation Steps Performed I've run the tests. Please note: this code is unchanged (apart from `wil::ScopeExit` -> `wil::scope_exit`) from Windows. Now is not the time to comment on their perfectness.
This commit is contained in:
parent
deccf7e12b
commit
39d3c65420
|
@ -294,6 +294,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{D3EF
|
|||
build\scripts\Test-WindowsTerminalPackage.ps1 = build\scripts\Test-WindowsTerminalPackage.ps1
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winconpty.Tests.Feature", "src\winconpty\ft_pty\winconpty.FeatureTests.vcxproj", "{024052DE-83FB-4653-AEA4-90790D29D5BD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
AuditMode|Any CPU = AuditMode|Any CPU
|
||||
|
@ -1419,6 +1421,20 @@ Global
|
|||
{A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x64.Build.0 = Release|x64
|
||||
{A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x86.ActiveCfg = Release|Win32
|
||||
{A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x86.Build.0 = Release|Win32
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|x64.Build.0 = Debug|x64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|x86.Build.0 = Debug|Win32
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|Any CPU.ActiveCfg = Release|Win32
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|x64.ActiveCfg = Release|x64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|x64.Build.0 = Release|x64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|x86.ActiveCfg = Release|Win32
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -1493,6 +1509,7 @@ Global
|
|||
{53DD5520-E64C-4C06-B472-7CE62CA539C9} = {04170EEF-983A-4195-BFEF-2321E5E38A1E}
|
||||
{6B5A44ED-918D-4747-BFB1-2472A1FCA173} = {04170EEF-983A-4195-BFEF-2321E5E38A1E}
|
||||
{D3EF7B96-CD5E-47C9-B9A9-136259563033} = {04170EEF-983A-4195-BFEF-2321E5E38A1E}
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}
|
||||
|
|
|
@ -0,0 +1,456 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "../winconpty.h"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
|
||||
class ConPtyTests
|
||||
{
|
||||
TEST_CLASS(ConPtyTests);
|
||||
const COORD defaultSize = { 80, 30 };
|
||||
TEST_METHOD(CreateConPtyNoPipes);
|
||||
TEST_METHOD(CreateConPtyBadSize);
|
||||
TEST_METHOD(GoodCreate);
|
||||
TEST_METHOD(GoodCreateMultiple);
|
||||
TEST_METHOD(SurvivesOnBreakInput);
|
||||
TEST_METHOD(SurvivesOnBreakOutput);
|
||||
TEST_METHOD(DiesOnBreakBoth);
|
||||
TEST_METHOD(DiesOnClose);
|
||||
};
|
||||
|
||||
HRESULT _CreatePseudoConsole(const COORD size,
|
||||
const HANDLE hInput,
|
||||
const HANDLE hOutput,
|
||||
const DWORD dwFlags,
|
||||
_Inout_ PseudoConsole* pPty)
|
||||
{
|
||||
return _CreatePseudoConsole(INVALID_HANDLE_VALUE, size, hInput, hOutput, dwFlags, pPty);
|
||||
}
|
||||
|
||||
HRESULT AttachPseudoConsole(HPCON hPC, LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList)
|
||||
{
|
||||
BOOL fSuccess = UpdateProcThreadAttribute(lpAttributeList,
|
||||
0,
|
||||
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
|
||||
hPC,
|
||||
sizeof(HANDLE),
|
||||
NULL,
|
||||
NULL);
|
||||
return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
void _CreateChildProcess(std::wstring& command, STARTUPINFOEXW* psiEx, PROCESS_INFORMATION* ppi)
|
||||
{
|
||||
std::unique_ptr<wchar_t[]> mutableCommandline = std::make_unique<wchar_t[]>(command.length() + 1);
|
||||
VERIFY_IS_NOT_NULL(mutableCommandline);
|
||||
VERIFY_SUCCEEDED(StringCchCopyW(mutableCommandline.get(), command.length() + 1, command.c_str()));
|
||||
VERIFY_IS_TRUE(CreateProcessW(
|
||||
nullptr,
|
||||
mutableCommandline.get(),
|
||||
nullptr, // lpProcessAttributes
|
||||
nullptr, // lpThreadAttributes
|
||||
true, // bInheritHandles
|
||||
EXTENDED_STARTUPINFO_PRESENT, // dwCreationFlags
|
||||
nullptr, // lpEnvironment
|
||||
nullptr, // lpCurrentDirectory
|
||||
&psiEx->StartupInfo, // lpStartupInfo
|
||||
ppi // lpProcessInformation
|
||||
));
|
||||
}
|
||||
|
||||
void ConPtyTests::CreateConPtyNoPipes()
|
||||
{
|
||||
PseudoConsole pcon{};
|
||||
|
||||
const HANDLE goodIn = (HANDLE)0x4;
|
||||
const HANDLE goodOut = (HANDLE)0x8;
|
||||
|
||||
// We only need one of the two handles to start successfully. However,
|
||||
// INVALID_HANDLE for either will be rejected by CreateProcess, but nullptr
|
||||
// will be acceptable.
|
||||
// So make sure INVALID_HANDLE always fails, and nullptr succeeds as long as one is real.
|
||||
VERIFY_FAILED(_CreatePseudoConsole(defaultSize, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, &pcon));
|
||||
VERIFY_FAILED(_CreatePseudoConsole(defaultSize, INVALID_HANDLE_VALUE, goodOut, 0, &pcon));
|
||||
VERIFY_FAILED(_CreatePseudoConsole(defaultSize, goodIn, INVALID_HANDLE_VALUE, 0, &pcon));
|
||||
|
||||
VERIFY_FAILED(_CreatePseudoConsole(defaultSize, nullptr, nullptr, 0, &pcon));
|
||||
|
||||
VERIFY_SUCCEEDED(_CreatePseudoConsole(defaultSize, nullptr, goodOut, 0, &pcon));
|
||||
_ClosePseudoConsoleMembers(&pcon);
|
||||
|
||||
VERIFY_SUCCEEDED(_CreatePseudoConsole(defaultSize, goodIn, nullptr, 0, &pcon));
|
||||
_ClosePseudoConsoleMembers(&pcon);
|
||||
}
|
||||
|
||||
void ConPtyTests::CreateConPtyBadSize()
|
||||
{
|
||||
PseudoConsole pcon{};
|
||||
COORD badSize = { 0, 0 };
|
||||
const HANDLE goodIn = (HANDLE)0x4;
|
||||
const HANDLE goodOut = (HANDLE)0x8;
|
||||
VERIFY_FAILED(_CreatePseudoConsole(badSize, goodIn, goodOut, 0, &pcon));
|
||||
|
||||
badSize = { 0, defaultSize.Y };
|
||||
VERIFY_FAILED(_CreatePseudoConsole(badSize, goodIn, goodOut, 0, &pcon));
|
||||
|
||||
badSize = { defaultSize.X, 0 };
|
||||
VERIFY_FAILED(_CreatePseudoConsole(badSize, goodIn, goodOut, 0, &pcon));
|
||||
}
|
||||
|
||||
void ConPtyTests::GoodCreate()
|
||||
{
|
||||
PseudoConsole pcon{};
|
||||
wil::unique_handle outPipeOurSide;
|
||||
wil::unique_handle inPipeOurSide;
|
||||
wil::unique_handle outPipePseudoConsoleSide;
|
||||
wil::unique_handle inPipePseudoConsoleSide;
|
||||
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.bInheritHandle = TRUE;
|
||||
sa.lpSecurityDescriptor = NULL;
|
||||
|
||||
VERIFY_IS_TRUE(CreatePipe(inPipePseudoConsoleSide.addressof(), inPipeOurSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(CreatePipe(outPipeOurSide.addressof(), outPipePseudoConsoleSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(inPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(outPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
|
||||
VERIFY_SUCCEEDED(
|
||||
_CreatePseudoConsole(defaultSize,
|
||||
inPipePseudoConsoleSide.get(),
|
||||
outPipePseudoConsoleSide.get(),
|
||||
0,
|
||||
&pcon));
|
||||
|
||||
auto closePty = wil::scope_exit([&] {
|
||||
_ClosePseudoConsoleMembers(&pcon);
|
||||
});
|
||||
}
|
||||
|
||||
void ConPtyTests::GoodCreateMultiple()
|
||||
{
|
||||
PseudoConsole pcon1{};
|
||||
PseudoConsole pcon2{};
|
||||
wil::unique_handle outPipeOurSide;
|
||||
wil::unique_handle inPipeOurSide;
|
||||
wil::unique_handle outPipePseudoConsoleSide;
|
||||
wil::unique_handle inPipePseudoConsoleSide;
|
||||
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.bInheritHandle = TRUE;
|
||||
sa.lpSecurityDescriptor = NULL;
|
||||
VERIFY_IS_TRUE(CreatePipe(inPipePseudoConsoleSide.addressof(), inPipeOurSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(CreatePipe(outPipeOurSide.addressof(), outPipePseudoConsoleSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(inPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(outPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
|
||||
VERIFY_SUCCEEDED(
|
||||
_CreatePseudoConsole(defaultSize,
|
||||
inPipePseudoConsoleSide.get(),
|
||||
outPipePseudoConsoleSide.get(),
|
||||
0,
|
||||
&pcon1));
|
||||
auto closePty1 = wil::scope_exit([&] {
|
||||
_ClosePseudoConsoleMembers(&pcon1);
|
||||
});
|
||||
|
||||
VERIFY_SUCCEEDED(
|
||||
_CreatePseudoConsole(defaultSize,
|
||||
inPipePseudoConsoleSide.get(),
|
||||
outPipePseudoConsoleSide.get(),
|
||||
0,
|
||||
&pcon2));
|
||||
auto closePty2 = wil::scope_exit([&] {
|
||||
_ClosePseudoConsoleMembers(&pcon2);
|
||||
});
|
||||
}
|
||||
|
||||
void ConPtyTests::SurvivesOnBreakInput()
|
||||
{
|
||||
PseudoConsole pty = { 0 };
|
||||
wil::unique_handle outPipeOurSide;
|
||||
wil::unique_handle inPipeOurSide;
|
||||
wil::unique_handle outPipePseudoConsoleSide;
|
||||
wil::unique_handle inPipePseudoConsoleSide;
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.bInheritHandle = TRUE;
|
||||
sa.lpSecurityDescriptor = NULL;
|
||||
VERIFY_IS_TRUE(CreatePipe(inPipePseudoConsoleSide.addressof(), inPipeOurSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(CreatePipe(outPipeOurSide.addressof(), outPipePseudoConsoleSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(inPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(outPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
|
||||
VERIFY_SUCCEEDED(
|
||||
_CreatePseudoConsole(defaultSize,
|
||||
inPipePseudoConsoleSide.get(),
|
||||
outPipePseudoConsoleSide.get(),
|
||||
0,
|
||||
&pty));
|
||||
auto closePty1 = wil::scope_exit([&] {
|
||||
_ClosePseudoConsoleMembers(&pty);
|
||||
});
|
||||
|
||||
DWORD dwExit;
|
||||
VERIFY_IS_TRUE(GetExitCodeProcess(pty.hConPtyProcess, &dwExit));
|
||||
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE);
|
||||
|
||||
STARTUPINFOEXW siEx;
|
||||
siEx = { 0 };
|
||||
siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW);
|
||||
size_t size;
|
||||
VERIFY_IS_FALSE(InitializeProcThreadAttributeList(NULL, 1, 0, (PSIZE_T)&size));
|
||||
BYTE* attrList = new BYTE[size];
|
||||
auto freeAttrList = wil::scope_exit([&] {
|
||||
delete[] attrList;
|
||||
});
|
||||
|
||||
siEx.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(attrList);
|
||||
VERIFY_IS_TRUE(InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, (PSIZE_T)&size));
|
||||
auto deleteAttrList = wil::scope_exit([&] {
|
||||
DeleteProcThreadAttributeList(siEx.lpAttributeList);
|
||||
});
|
||||
VERIFY_SUCCEEDED(
|
||||
AttachPseudoConsole(reinterpret_cast<HPCON>(&pty), siEx.lpAttributeList));
|
||||
|
||||
wil::unique_process_information piClient;
|
||||
std::wstring realCommand = L"cmd.exe";
|
||||
_CreateChildProcess(realCommand, &siEx, piClient.addressof());
|
||||
|
||||
VERIFY_IS_TRUE(GetExitCodeProcess(piClient.hProcess, &dwExit));
|
||||
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE);
|
||||
|
||||
VERIFY_IS_TRUE(CloseHandle(inPipeOurSide.get()));
|
||||
|
||||
// Wait for a couple seconds, make sure the child is still alive.
|
||||
VERIFY_ARE_EQUAL(WaitForSingleObject(pty.hConPtyProcess, 2000), (DWORD)WAIT_TIMEOUT);
|
||||
VERIFY_IS_TRUE(GetExitCodeProcess(pty.hConPtyProcess, &dwExit));
|
||||
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE);
|
||||
}
|
||||
|
||||
void ConPtyTests::SurvivesOnBreakOutput()
|
||||
{
|
||||
PseudoConsole pty = { 0 };
|
||||
wil::unique_handle outPipeOurSide;
|
||||
wil::unique_handle inPipeOurSide;
|
||||
wil::unique_handle outPipePseudoConsoleSide;
|
||||
wil::unique_handle inPipePseudoConsoleSide;
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.bInheritHandle = TRUE;
|
||||
sa.lpSecurityDescriptor = NULL;
|
||||
VERIFY_IS_TRUE(CreatePipe(inPipePseudoConsoleSide.addressof(), inPipeOurSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(CreatePipe(outPipeOurSide.addressof(), outPipePseudoConsoleSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(inPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(outPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
|
||||
VERIFY_SUCCEEDED(
|
||||
_CreatePseudoConsole(defaultSize,
|
||||
inPipePseudoConsoleSide.get(),
|
||||
outPipePseudoConsoleSide.get(),
|
||||
0,
|
||||
&pty));
|
||||
auto closePty1 = wil::scope_exit([&] {
|
||||
_ClosePseudoConsoleMembers(&pty);
|
||||
});
|
||||
|
||||
DWORD dwExit;
|
||||
VERIFY_IS_TRUE(GetExitCodeProcess(pty.hConPtyProcess, &dwExit));
|
||||
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE);
|
||||
|
||||
STARTUPINFOEXW siEx;
|
||||
siEx = { 0 };
|
||||
siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW);
|
||||
size_t size;
|
||||
VERIFY_IS_FALSE(InitializeProcThreadAttributeList(NULL, 1, 0, (PSIZE_T)&size));
|
||||
BYTE* attrList = new BYTE[size];
|
||||
auto freeAttrList = wil::scope_exit([&] {
|
||||
delete[] attrList;
|
||||
});
|
||||
|
||||
siEx.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(attrList);
|
||||
VERIFY_IS_TRUE(InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, (PSIZE_T)&size));
|
||||
auto deleteAttrList = wil::scope_exit([&] {
|
||||
DeleteProcThreadAttributeList(siEx.lpAttributeList);
|
||||
});
|
||||
VERIFY_SUCCEEDED(
|
||||
AttachPseudoConsole(reinterpret_cast<HPCON>(&pty), siEx.lpAttributeList));
|
||||
|
||||
wil::unique_process_information piClient;
|
||||
std::wstring realCommand = L"cmd.exe";
|
||||
_CreateChildProcess(realCommand, &siEx, piClient.addressof());
|
||||
|
||||
VERIFY_IS_TRUE(GetExitCodeProcess(piClient.hProcess, &dwExit));
|
||||
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE);
|
||||
|
||||
VERIFY_IS_TRUE(CloseHandle(outPipeOurSide.get()));
|
||||
|
||||
// Wait for a couple seconds, make sure the child is still alive.
|
||||
VERIFY_ARE_EQUAL(WaitForSingleObject(pty.hConPtyProcess, 2000), (DWORD)WAIT_TIMEOUT);
|
||||
VERIFY_IS_TRUE(GetExitCodeProcess(pty.hConPtyProcess, &dwExit));
|
||||
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE);
|
||||
}
|
||||
|
||||
void ConPtyTests::DiesOnBreakBoth()
|
||||
{
|
||||
PseudoConsole pty = { 0 };
|
||||
wil::unique_handle outPipeOurSide;
|
||||
wil::unique_handle inPipeOurSide;
|
||||
wil::unique_handle outPipePseudoConsoleSide;
|
||||
wil::unique_handle inPipePseudoConsoleSide;
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.bInheritHandle = TRUE;
|
||||
sa.lpSecurityDescriptor = NULL;
|
||||
VERIFY_IS_TRUE(CreatePipe(inPipePseudoConsoleSide.addressof(), inPipeOurSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(CreatePipe(outPipeOurSide.addressof(), outPipePseudoConsoleSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(inPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(outPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
|
||||
VERIFY_SUCCEEDED(
|
||||
_CreatePseudoConsole(defaultSize,
|
||||
inPipePseudoConsoleSide.get(),
|
||||
outPipePseudoConsoleSide.get(),
|
||||
0,
|
||||
&pty));
|
||||
auto closePty1 = wil::scope_exit([&] {
|
||||
_ClosePseudoConsoleMembers(&pty);
|
||||
});
|
||||
|
||||
DWORD dwExit;
|
||||
VERIFY_IS_TRUE(GetExitCodeProcess(pty.hConPtyProcess, &dwExit));
|
||||
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE);
|
||||
|
||||
STARTUPINFOEXW siEx;
|
||||
siEx = { 0 };
|
||||
siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW);
|
||||
size_t size;
|
||||
VERIFY_IS_FALSE(InitializeProcThreadAttributeList(NULL, 1, 0, (PSIZE_T)&size));
|
||||
BYTE* attrList = new BYTE[size];
|
||||
auto freeAttrList = wil::scope_exit([&] {
|
||||
delete[] attrList;
|
||||
});
|
||||
|
||||
siEx.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(attrList);
|
||||
VERIFY_IS_TRUE(InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, (PSIZE_T)&size));
|
||||
auto deleteAttrList = wil::scope_exit([&] {
|
||||
DeleteProcThreadAttributeList(siEx.lpAttributeList);
|
||||
});
|
||||
VERIFY_SUCCEEDED(
|
||||
AttachPseudoConsole(reinterpret_cast<HPCON>(&pty), siEx.lpAttributeList));
|
||||
|
||||
wil::unique_process_information piClient;
|
||||
std::wstring realCommand = L"cmd.exe";
|
||||
_CreateChildProcess(realCommand, &siEx, piClient.addressof());
|
||||
|
||||
VERIFY_IS_TRUE(GetExitCodeProcess(piClient.hProcess, &dwExit));
|
||||
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE);
|
||||
|
||||
// Close one of the pipes...
|
||||
VERIFY_IS_TRUE(CloseHandle(outPipeOurSide.get()));
|
||||
|
||||
// ... Wait for a couple seconds, make sure the child is still alive.
|
||||
VERIFY_ARE_EQUAL(WaitForSingleObject(pty.hConPtyProcess, 2000), (DWORD)WAIT_TIMEOUT);
|
||||
VERIFY_IS_TRUE(GetExitCodeProcess(pty.hConPtyProcess, &dwExit));
|
||||
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE);
|
||||
|
||||
// Tricky - write some input to the pcon. We need to do this so conhost can
|
||||
// realize that the output pipe has broken.
|
||||
VERIFY_SUCCEEDED(WriteFile(inPipeOurSide.get(), L"a", sizeof(wchar_t), nullptr, nullptr));
|
||||
|
||||
// Close the other pipe, and make sure conhost dies
|
||||
VERIFY_IS_TRUE(CloseHandle(inPipeOurSide.get()));
|
||||
|
||||
VERIFY_ARE_EQUAL(WaitForSingleObject(pty.hConPtyProcess, 10000), (DWORD)WAIT_OBJECT_0);
|
||||
VERIFY_IS_TRUE(GetExitCodeProcess(pty.hConPtyProcess, &dwExit));
|
||||
VERIFY_ARE_NOT_EQUAL(dwExit, (DWORD)STILL_ACTIVE);
|
||||
}
|
||||
|
||||
void ConPtyTests::DiesOnClose()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:commandline",
|
||||
L"{"
|
||||
// TODO: MSFT:20146938 - investigate and possibly re-enable this case
|
||||
// L"cmd.exe /c dir,"
|
||||
L"ping localhost,"
|
||||
L"cmd.exe /c echo Hello World,"
|
||||
L"cmd.exe /c for /L %i () DO echo Hello World %i,"
|
||||
L"cmd.exe"
|
||||
L"}")
|
||||
END_TEST_METHOD_PROPERTIES();
|
||||
String testCommandline;
|
||||
VERIFY_SUCCEEDED(TestData::TryGetValue(L"commandline", testCommandline), L"Get a commandline to test");
|
||||
|
||||
PseudoConsole pty = { 0 };
|
||||
wil::unique_handle outPipeOurSide;
|
||||
wil::unique_handle inPipeOurSide;
|
||||
wil::unique_handle outPipePseudoConsoleSide;
|
||||
wil::unique_handle inPipePseudoConsoleSide;
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.bInheritHandle = TRUE;
|
||||
sa.lpSecurityDescriptor = NULL;
|
||||
VERIFY_IS_TRUE(CreatePipe(inPipePseudoConsoleSide.addressof(), inPipeOurSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(CreatePipe(outPipeOurSide.addressof(), outPipePseudoConsoleSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(inPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(outPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
|
||||
VERIFY_SUCCEEDED(
|
||||
_CreatePseudoConsole(defaultSize,
|
||||
inPipePseudoConsoleSide.get(),
|
||||
outPipePseudoConsoleSide.get(),
|
||||
0,
|
||||
&pty));
|
||||
auto closePty1 = wil::scope_exit([&] {
|
||||
_ClosePseudoConsoleMembers(&pty);
|
||||
});
|
||||
|
||||
DWORD dwExit;
|
||||
VERIFY_IS_TRUE(GetExitCodeProcess(pty.hConPtyProcess, &dwExit));
|
||||
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE);
|
||||
|
||||
STARTUPINFOEXW siEx;
|
||||
siEx = { 0 };
|
||||
siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW);
|
||||
size_t size;
|
||||
VERIFY_IS_FALSE(InitializeProcThreadAttributeList(NULL, 1, 0, (PSIZE_T)&size));
|
||||
BYTE* attrList = new BYTE[size];
|
||||
auto freeAttrList = wil::scope_exit([&] {
|
||||
delete[] attrList;
|
||||
});
|
||||
|
||||
siEx.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(attrList);
|
||||
VERIFY_IS_TRUE(InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, (PSIZE_T)&size));
|
||||
auto deleteAttrList = wil::scope_exit([&] {
|
||||
DeleteProcThreadAttributeList(siEx.lpAttributeList);
|
||||
});
|
||||
VERIFY_SUCCEEDED(
|
||||
AttachPseudoConsole(reinterpret_cast<HPCON>(&pty), siEx.lpAttributeList));
|
||||
|
||||
wil::unique_process_information piClient;
|
||||
std::wstring realCommand = testCommandline;
|
||||
_CreateChildProcess(realCommand, &siEx, piClient.addressof());
|
||||
|
||||
VERIFY_IS_TRUE(GetExitCodeProcess(piClient.hProcess, &dwExit));
|
||||
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE);
|
||||
|
||||
// Stash the pty process, it'll get zero'd after the call to close
|
||||
const auto hConPtyProcess = pty.hConPtyProcess;
|
||||
|
||||
VERIFY_IS_TRUE(GetExitCodeProcess(hConPtyProcess, &dwExit));
|
||||
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE);
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Sleep a bit to let the process attach"));
|
||||
Sleep(100);
|
||||
|
||||
_ClosePseudoConsoleMembers(&pty);
|
||||
|
||||
GetExitCodeProcess(hConPtyProcess, &dwExit);
|
||||
VERIFY_ARE_NOT_EQUAL(dwExit, (DWORD)STILL_ACTIVE);
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Use winconpty's precomp wince we hit a lot of the same APIs
|
||||
#include "../precomp.h"
|
||||
|
||||
#include "WexTestClass.h"
|
||||
|
||||
// This includes support libraries from the CRT, STL, WIL, and GSL
|
||||
#include "LibraryIncludes.h"
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{024052DE-83FB-4653-AEA4-90790D29D5BD}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>ConPTYFeatureTests</RootNamespace>
|
||||
<ProjectName>winconpty.Tests.Feature</ProjectName>
|
||||
<TargetName>winconpty.Feature.Tests</TargetName>
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)src\common.build.pre.props" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ConPtyTests.cpp" />
|
||||
<ClCompile Include="precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="precomp.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\lib\winconptylib.vcxproj">
|
||||
<Project>{58a03bb2-df5a-4b66-91a0-7ef3ba01269a}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
|
||||
<Import Project="$(SolutionDir)src\common.build.post.props" />
|
||||
<Import Project="$(SolutionDir)src\common.build.tests.props" />
|
||||
<ItemDefinitionGroup>
|
||||
<Link>
|
||||
<AdditionalDependencies>$(OutDir)\conptylib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
</Project>
|
Loading…
Reference in New Issue