X-Git-Url: https://git.josue.xyz/?p=VSoRC%2F.git;a=blobdiff_plain;f=node_modules%2Fnode-pty%2Fdeps%2Fwinpty%2Fmisc%2FScreenBufferTest.cc;fp=node_modules%2Fnode-pty%2Fdeps%2Fwinpty%2Fmisc%2FScreenBufferTest.cc;h=fa584b9fae901a2038cf966af3812a82cf529de9;hp=0000000000000000000000000000000000000000;hb=e79e4a5a87f3e84f7c1777f10a954453a69bf540;hpb=4339da12467b75fb8b6ca831f4bf0081c485ed2c diff --git a/node_modules/node-pty/deps/winpty/misc/ScreenBufferTest.cc b/node_modules/node-pty/deps/winpty/misc/ScreenBufferTest.cc new file mode 100644 index 0000000..fa584b9 --- /dev/null +++ b/node_modules/node-pty/deps/winpty/misc/ScreenBufferTest.cc @@ -0,0 +1,671 @@ +// +// Windows versions tested +// +// Vista Enterprise SP2 32-bit +// - ver reports [Version 6.0.6002] +// - kernel32.dll product/file versions are 6.0.6002.19381 +// +// Windows 7 Ultimate SP1 32-bit +// - ver reports [Version 6.1.7601] +// - conhost.exe product/file versions are 6.1.7601.18847 +// - kernel32.dll product/file versions are 6.1.7601.18847 +// +// Windows Server 2008 R2 Datacenter SP1 64-bit +// - ver reports [Version 6.1.7601] +// - conhost.exe product/file versions are 6.1.7601.23153 +// - kernel32.dll product/file versions are 6.1.7601.23153 +// +// Windows 8 Enterprise 32-bit +// - ver reports [Version 6.2.9200] +// - conhost.exe product/file versions are 6.2.9200.16578 +// - kernel32.dll product/file versions are 6.2.9200.16859 +// + +// +// Specific version details on working Server 2008 R2: +// +// dwMajorVersion = 6 +// dwMinorVersion = 1 +// dwBuildNumber = 7601 +// dwPlatformId = 2 +// szCSDVersion = Service Pack 1 +// wServicePackMajor = 1 +// wServicePackMinor = 0 +// wSuiteMask = 0x190 +// wProductType = 0x3 +// +// Specific version details on broken Win7: +// +// dwMajorVersion = 6 +// dwMinorVersion = 1 +// dwBuildNumber = 7601 +// dwPlatformId = 2 +// szCSDVersion = Service Pack 1 +// wServicePackMajor = 1 +// wServicePackMinor = 0 +// wSuiteMask = 0x100 +// wProductType = 0x1 +// + +#include +#include +#include + +#include "TestUtil.cc" + +const char *g_prefix = ""; + +static void dumpHandles() { + trace("%sSTDIN=0x%I64x STDOUT=0x%I64x STDERR=0x%I64x", + g_prefix, + (long long)GetStdHandle(STD_INPUT_HANDLE), + (long long)GetStdHandle(STD_OUTPUT_HANDLE), + (long long)GetStdHandle(STD_ERROR_HANDLE)); +} + +static const char *successOrFail(BOOL ret) { + return ret ? "ok" : "FAILED"; +} + +static void startChildInSameConsole(const wchar_t *args, BOOL + bInheritHandles=FALSE) { + wchar_t program[1024]; + wchar_t cmdline[1024]; + GetModuleFileNameW(NULL, program, 1024); + swprintf(cmdline, L"\"%ls\" %ls", program, args); + + STARTUPINFOW sui; + PROCESS_INFORMATION pi; + memset(&sui, 0, sizeof(sui)); + memset(&pi, 0, sizeof(pi)); + sui.cb = sizeof(sui); + + CreateProcessW(program, cmdline, + NULL, NULL, + /*bInheritHandles=*/bInheritHandles, + /*dwCreationFlags=*/0, + NULL, NULL, + &sui, &pi); +} + +static void closeHandle(HANDLE h) { + trace("%sClosing handle 0x%I64x...", g_prefix, (long long)h); + trace("%sClosing handle 0x%I64x... %s", g_prefix, (long long)h, successOrFail(CloseHandle(h))); +} + +static HANDLE createBuffer() { + + // If sa isn't provided, the handle defaults to not-inheritable. + SECURITY_ATTRIBUTES sa = {0}; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + + trace("%sCreating a new buffer...", g_prefix); + HANDLE conout = CreateConsoleScreenBuffer( + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &sa, + CONSOLE_TEXTMODE_BUFFER, NULL); + + trace("%sCreating a new buffer... 0x%I64x", g_prefix, (long long)conout); + return conout; +} + +static HANDLE openConout() { + + // If sa isn't provided, the handle defaults to not-inheritable. + SECURITY_ATTRIBUTES sa = {0}; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + + trace("%sOpening CONOUT...", g_prefix); + HANDLE conout = CreateFileW(L"CONOUT$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &sa, + OPEN_EXISTING, 0, NULL); + trace("%sOpening CONOUT... 0x%I64x", g_prefix, (long long)conout); + return conout; +} + +static void setConsoleActiveScreenBuffer(HANDLE conout) { + trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called...", + g_prefix, (long long)conout); + trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called... %s", + g_prefix, (long long)conout, + successOrFail(SetConsoleActiveScreenBuffer(conout))); +} + +static void writeTest(HANDLE conout, const char *msg) { + char writeData[256]; + sprintf(writeData, "%s%s\n", g_prefix, msg); + + trace("%sWriting to 0x%I64x: '%s'...", + g_prefix, (long long)conout, msg); + DWORD actual = 0; + BOOL ret = WriteConsoleA(conout, writeData, strlen(writeData), &actual, NULL); + trace("%sWriting to 0x%I64x: '%s'... %s", + g_prefix, (long long)conout, msg, + successOrFail(ret && actual == strlen(writeData))); +} + +static void writeTest(const char *msg) { + writeTest(GetStdHandle(STD_OUTPUT_HANDLE), msg); +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST 1 -- create new buffer, activate it, and close the handle. The console +// automatically switches the screen buffer back to the original. +// +// This test passes everywhere. +// + +static void test1(int argc, char *argv[]) { + if (!strcmp(argv[1], "1")) { + startChildProcess(L"1:child"); + return; + } + + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + writeTest(origBuffer, "<-- origBuffer -->"); + + HANDLE newBuffer = createBuffer(); + writeTest(newBuffer, "<-- newBuffer -->"); + setConsoleActiveScreenBuffer(newBuffer); + Sleep(2000); + + writeTest(origBuffer, "TEST PASSED!"); + + // Closing the handle w/o switching the active screen buffer automatically + // switches the console back to the original buffer. + closeHandle(newBuffer); + + while (true) { + Sleep(1000); + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST 2 -- Test program that creates and activates newBuffer, starts a child +// process, then closes its newBuffer handle. newBuffer remains activated, +// because the child keeps it active. (Also see TEST D.) +// + +static void test2(int argc, char *argv[]) { + if (!strcmp(argv[1], "2")) { + startChildProcess(L"2:parent"); + return; + } + + if (!strcmp(argv[1], "2:parent")) { + g_prefix = "parent: "; + dumpHandles(); + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + writeTest(origBuffer, "<-- origBuffer -->"); + + HANDLE newBuffer = createBuffer(); + writeTest(newBuffer, "<-- newBuffer -->"); + setConsoleActiveScreenBuffer(newBuffer); + + Sleep(1000); + writeTest(newBuffer, "bInheritHandles=FALSE:"); + startChildInSameConsole(L"2:child", FALSE); + Sleep(1000); + writeTest(newBuffer, "bInheritHandles=TRUE:"); + startChildInSameConsole(L"2:child", TRUE); + + Sleep(1000); + trace("parent:----"); + + // Close the new buffer. The active screen buffer doesn't automatically + // switch back to origBuffer, because the child process has a handle open + // to the original buffer. + closeHandle(newBuffer); + + Sleep(600 * 1000); + return; + } + + if (!strcmp(argv[1], "2:child")) { + g_prefix = "child: "; + dumpHandles(); + // The child's output isn't visible, because it's still writing to + // origBuffer. + trace("child:----"); + writeTest("writing to STDOUT"); + + // Handle inheritability is curious. The console handles this program + // creates are inheritable, but CreateProcess is called with both + // bInheritHandles=TRUE and bInheritHandles=FALSE. + // + // Vista and Windows 7: bInheritHandles has no effect. The child and + // parent processes have the same STDIN/STDOUT/STDERR handles: + // 0x3, 0x7, and 0xB. The parent has a 0xF handle for newBuffer. + // The child can only write to 0x7, 0xB, and 0xF. Only the writes to + // 0xF are visible (i.e. they touch newBuffer). + // + // Windows 8 or Windows 10 (legacy or non-legacy): the lowest 2 bits of + // the HANDLE to WriteConsole seem to be ignored. The new process' + // console handles always refer to the buffer that was active when they + // started, but the values of the handles depend upon bInheritHandles. + // With bInheritHandles=TRUE, the child has the same + // STDIN/STDOUT/STDERR/newBuffer handles as the parent, and the three + // output handles all work, though their output is all visible. With + // bInheritHandles=FALSE, the child has different STDIN/STDOUT/STDERR + // handles, and only the new STDOUT/STDERR handles work. + // + for (unsigned int i = 0x1; i <= 0xB0; ++i) { + char msg[256]; + sprintf(msg, "Write to handle 0x%x", i); + HANDLE h = reinterpret_cast(i); + writeTest(h, msg); + } + + Sleep(600 * 1000); + return; + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST A -- demonstrate an apparent Windows bug with screen buffers +// +// Steps: +// - The parent starts a child process. +// - The child process creates and activates newBuffer +// - The parent opens CONOUT$ and writes to it. +// - The parent closes CONOUT$. +// - At this point, broken Windows reactivates origBuffer. +// - The child writes to newBuffer again. +// - The child activates origBuffer again, then closes newBuffer. +// +// Test passes if the message "TEST PASSED!" is visible. +// Test commonly fails if conhost.exe crashes. +// +// Results: +// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes +// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS +// - Windows 8 Enterprise 32-bit: PASS +// - Windows 10 64-bit (legacy and non-legacy): PASS +// + +static void testA_parentWork() { + // Open an extra CONOUT$ handle so that the HANDLE values in parent and + // child don't collide. I think it's OK if they collide, but since we're + // trying to track down a Windows bug, it's best to avoid unnecessary + // complication. + HANDLE dummy = openConout(); + + Sleep(3000); + + // Step 2: Open CONOUT$ in the parent. This opens the active buffer, which + // was just created in the child. It's handle 0x13. Write to it. + + HANDLE newBuffer = openConout(); + writeTest(newBuffer, "step2: writing to newBuffer"); + + Sleep(3000); + + // Step 3: Close handle 0x13. With Windows 7, the console switches back to + // origBuffer, and (unless I'm missing something) it shouldn't. + + closeHandle(newBuffer); +} + +static void testA_childWork() { + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + + // + // Step 1: Create the new screen buffer in the child process and make it + // active. (Typically, it's handle 0x0F.) + // + + HANDLE newBuffer = createBuffer(); + + setConsoleActiveScreenBuffer(newBuffer); + writeTest(newBuffer, "<-- newBuffer -->"); + + Sleep(9000); + trace("child:----"); + + // Step 4: write to the newBuffer again. + writeTest(newBuffer, "TEST PASSED!"); + + // + // Step 5: Switch back to the original screen buffer and close the new + // buffer. The switch call succeeds, but the CloseHandle call freezes for + // several seconds, because conhost.exe crashes. + // + Sleep(3000); + + setConsoleActiveScreenBuffer(origBuffer); + writeTest(origBuffer, "writing to origBuffer"); + + closeHandle(newBuffer); + + // The console HWND is NULL. + trace("child: console HWND=0x%I64x", (long long)GetConsoleWindow()); + + // At this point, the console window has closed, but the parent/child + // processes are still running. Calling AllocConsole would fail, but + // calling FreeConsole followed by AllocConsole would both succeed, and a + // new console would appear. +} + +static void testA(int argc, char *argv[]) { + + if (!strcmp(argv[1], "A")) { + startChildProcess(L"A:parent"); + return; + } + + if (!strcmp(argv[1], "A:parent")) { + g_prefix = "parent: "; + trace("parent:----"); + dumpHandles(); + writeTest("<-- origBuffer -->"); + startChildInSameConsole(L"A:child"); + testA_parentWork(); + Sleep(120000); + return; + } + + if (!strcmp(argv[1], "A:child")) { + g_prefix = "child: "; + dumpHandles(); + testA_childWork(); + Sleep(120000); + return; + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST B -- invert TEST A -- also crashes conhost on Windows 7 +// +// Test passes if the message "TEST PASSED!" is visible. +// Test commonly fails if conhost.exe crashes. +// +// Results: +// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes +// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS +// - Windows 8 Enterprise 32-bit: PASS +// - Windows 10 64-bit (legacy and non-legacy): PASS +// + +static void testB(int argc, char *argv[]) { + if (!strcmp(argv[1], "B")) { + startChildProcess(L"B:parent"); + return; + } + + if (!strcmp(argv[1], "B:parent")) { + g_prefix = "parent: "; + startChildInSameConsole(L"B:child"); + writeTest("<-- origBuffer -->"); + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + + // + // Step 1: Create the new buffer and make it active. + // + trace("%s----", g_prefix); + HANDLE newBuffer = createBuffer(); + setConsoleActiveScreenBuffer(newBuffer); + writeTest(newBuffer, "<-- newBuffer -->"); + + // + // Step 4: Attempt to write again to the new buffer. + // + Sleep(9000); + trace("%s----", g_prefix); + writeTest(newBuffer, "TEST PASSED!"); + + // + // Step 5: Switch back to the original buffer. + // + Sleep(3000); + trace("%s----", g_prefix); + setConsoleActiveScreenBuffer(origBuffer); + closeHandle(newBuffer); + writeTest(origBuffer, "writing to the initial buffer"); + + Sleep(60000); + return; + } + + if (!strcmp(argv[1], "B:child")) { + g_prefix = "child: "; + Sleep(3000); + trace("%s----", g_prefix); + + // + // Step 2: Open the newly active buffer and write to it. + // + HANDLE newBuffer = openConout(); + writeTest(newBuffer, "writing to newBuffer"); + + // + // Step 3: Close the newly active buffer. + // + Sleep(3000); + closeHandle(newBuffer); + + Sleep(60000); + return; + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST C -- Interleaving open/close of console handles also seems to break on +// Windows 7. +// +// Test: +// - child creates and activates newBuf1 +// - parent opens newBuf1 +// - child creates and activates newBuf2 +// - parent opens newBuf2, then closes newBuf1 +// - child switches back to newBuf1 +// * At this point, the console starts malfunctioning. +// - parent and child close newBuf2 +// - child closes newBuf1 +// +// Test passes if the message "TEST PASSED!" is visible. +// Test commonly fails if conhost.exe crashes. +// +// Results: +// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes +// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS +// - Windows 8 Enterprise 32-bit: PASS +// - Windows 10 64-bit (legacy and non-legacy): PASS +// + +static void testC(int argc, char *argv[]) { + if (!strcmp(argv[1], "C")) { + startChildProcess(L"C:parent"); + return; + } + + if (!strcmp(argv[1], "C:parent")) { + startChildInSameConsole(L"C:child"); + writeTest("<-- origBuffer -->"); + g_prefix = "parent: "; + + // At time=4, open newBuffer1. + Sleep(4000); + trace("%s---- t=4", g_prefix); + const HANDLE newBuffer1 = openConout(); + + // At time=8, open newBuffer2, and close newBuffer1. + Sleep(4000); + trace("%s---- t=8", g_prefix); + const HANDLE newBuffer2 = openConout(); + closeHandle(newBuffer1); + + // At time=25, cleanup of newBuffer2. + Sleep(17000); + trace("%s---- t=25", g_prefix); + closeHandle(newBuffer2); + + Sleep(240000); + return; + } + + if (!strcmp(argv[1], "C:child")) { + g_prefix = "child: "; + + // At time=2, create newBuffer1 and activate it. + Sleep(2000); + trace("%s---- t=2", g_prefix); + const HANDLE newBuffer1 = createBuffer(); + setConsoleActiveScreenBuffer(newBuffer1); + writeTest(newBuffer1, "<-- newBuffer1 -->"); + + // At time=6, create newBuffer2 and activate it. + Sleep(4000); + trace("%s---- t=6", g_prefix); + const HANDLE newBuffer2 = createBuffer(); + setConsoleActiveScreenBuffer(newBuffer2); + writeTest(newBuffer2, "<-- newBuffer2 -->"); + + // At time=10, attempt to switch back to newBuffer1. The parent process + // has opened and closed its handle to newBuffer1, so does it still exist? + Sleep(4000); + trace("%s---- t=10", g_prefix); + setConsoleActiveScreenBuffer(newBuffer1); + writeTest(newBuffer1, "write to newBuffer1: TEST PASSED!"); + + // At time=25, cleanup of newBuffer2. + Sleep(15000); + trace("%s---- t=25", g_prefix); + closeHandle(newBuffer2); + + // At time=35, cleanup of newBuffer1. The console should switch to the + // initial buffer again. + Sleep(10000); + trace("%s---- t=35", g_prefix); + closeHandle(newBuffer1); + + Sleep(240000); + return; + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST D -- parent creates a new buffer, child launches, writes, +// closes it output handle, then parent writes again. (Also see TEST 2.) +// +// On success, this will appear: +// +// parent: <-- newBuffer --> +// child: writing to newBuffer +// parent: TEST PASSED! +// +// If this appears, it indicates that the child's closing its output handle did +// not destroy newBuffer. +// +// Results: +// - Windows 7 Ultimate SP1 32-bit: PASS +// - Windows 8 Enterprise 32-bit: PASS +// - Windows 10 64-bit (legacy and non-legacy): PASS +// + +static void testD(int argc, char *argv[]) { + if (!strcmp(argv[1], "D")) { + startChildProcess(L"D:parent"); + return; + } + + if (!strcmp(argv[1], "D:parent")) { + g_prefix = "parent: "; + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + writeTest(origBuffer, "<-- origBuffer -->"); + + HANDLE newBuffer = createBuffer(); + writeTest(newBuffer, "<-- newBuffer -->"); + setConsoleActiveScreenBuffer(newBuffer); + + // At t=2, start a child process, explicitly forcing it to use + // newBuffer for its standard handles. These calls are apparently + // redundant on Windows 8 and up. + Sleep(2000); + trace("parent:----"); + trace("parent: starting child process"); + SetStdHandle(STD_OUTPUT_HANDLE, newBuffer); + SetStdHandle(STD_ERROR_HANDLE, newBuffer); + startChildInSameConsole(L"D:child"); + SetStdHandle(STD_OUTPUT_HANDLE, origBuffer); + SetStdHandle(STD_ERROR_HANDLE, origBuffer); + + // At t=6, write again to newBuffer. + Sleep(4000); + trace("parent:----"); + writeTest(newBuffer, "TEST PASSED!"); + + // At t=8, close the newBuffer. In earlier versions of windows + // (including Server 2008 R2), the console then switches back to + // origBuffer. As of Windows 8, it doesn't, because somehow the child + // process is keeping the console on newBuffer, even though the child + // process closed its STDIN/STDOUT/STDERR handles. Killing the child + // process by hand after the test finishes *does* force the console + // back to origBuffer. + Sleep(2000); + closeHandle(newBuffer); + + Sleep(120000); + return; + } + + if (!strcmp(argv[1], "D:child")) { + g_prefix = "child: "; + // At t=2, the child starts. + trace("child:----"); + dumpHandles(); + writeTest("writing to newBuffer"); + + // At t=4, the child explicitly closes its handle. + Sleep(2000); + trace("child:----"); + if (GetStdHandle(STD_ERROR_HANDLE) != GetStdHandle(STD_OUTPUT_HANDLE)) { + closeHandle(GetStdHandle(STD_ERROR_HANDLE)); + } + closeHandle(GetStdHandle(STD_OUTPUT_HANDLE)); + closeHandle(GetStdHandle(STD_INPUT_HANDLE)); + + Sleep(120000); + return; + } +} + + + +int main(int argc, char *argv[]) { + if (argc == 1) { + printf("USAGE: %s testnum\n", argv[0]); + return 0; + } + + if (argv[1][0] == '1') { + test1(argc, argv); + } else if (argv[1][0] == '2') { + test2(argc, argv); + } else if (argv[1][0] == 'A') { + testA(argc, argv); + } else if (argv[1][0] == 'B') { + testB(argc, argv); + } else if (argv[1][0] == 'C') { + testC(argc, argv); + } else if (argv[1][0] == 'D') { + testD(argc, argv); + } + return 0; +}