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:
Dustin L. Howett (MSFT) 2020-02-19 13:27:17 -08:00 committed by GitHub
parent deccf7e12b
commit 39d3c65420
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 528 additions and 0 deletions

View File

@ -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}

View File

@ -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);
}

View File

@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"

View File

@ -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"

View File

@ -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>