From 39d3c6542006fa3cd45d85fc00e9d546a1398625 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett (MSFT)" Date: Wed, 19 Feb 2020 13:27:17 -0800 Subject: [PATCH] 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. --- OpenConsole.sln | 17 + src/winconpty/ft_pty/ConPtyTests.cpp | 456 ++++++++++++++++++ src/winconpty/ft_pty/precomp.cpp | 4 + src/winconpty/ft_pty/precomp.h | 12 + .../ft_pty/winconpty.FeatureTests.vcxproj | 39 ++ 5 files changed, 528 insertions(+) create mode 100644 src/winconpty/ft_pty/ConPtyTests.cpp create mode 100644 src/winconpty/ft_pty/precomp.cpp create mode 100644 src/winconpty/ft_pty/precomp.h create mode 100644 src/winconpty/ft_pty/winconpty.FeatureTests.vcxproj diff --git a/OpenConsole.sln b/OpenConsole.sln index e5c72362850..530f602b1a1 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -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} diff --git a/src/winconpty/ft_pty/ConPtyTests.cpp b/src/winconpty/ft_pty/ConPtyTests.cpp new file mode 100644 index 00000000000..ea014c49afb --- /dev/null +++ b/src/winconpty/ft_pty/ConPtyTests.cpp @@ -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 mutableCommandline = std::make_unique(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(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(&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(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(&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(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(&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(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(&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); +} diff --git a/src/winconpty/ft_pty/precomp.cpp b/src/winconpty/ft_pty/precomp.cpp new file mode 100644 index 00000000000..c51e9b31b2f --- /dev/null +++ b/src/winconpty/ft_pty/precomp.cpp @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" diff --git a/src/winconpty/ft_pty/precomp.h b/src/winconpty/ft_pty/precomp.h new file mode 100644 index 00000000000..1573e186a8c --- /dev/null +++ b/src/winconpty/ft_pty/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" diff --git a/src/winconpty/ft_pty/winconpty.FeatureTests.vcxproj b/src/winconpty/ft_pty/winconpty.FeatureTests.vcxproj new file mode 100644 index 00000000000..438f6014dae --- /dev/null +++ b/src/winconpty/ft_pty/winconpty.FeatureTests.vcxproj @@ -0,0 +1,39 @@ + + + + {024052DE-83FB-4653-AEA4-90790D29D5BD} + Win32Proj + ConPTYFeatureTests + winconpty.Tests.Feature + winconpty.Feature.Tests + DynamicLibrary + + + + + + Create + + + + + + + + {58a03bb2-df5a-4b66-91a0-7ef3ba01269a} + + + + + $(ProjectDir);%(AdditionalIncludeDirectories) + + + + + + + + $(OutDir)\conptylib.lib;%(AdditionalDependencies) + + +