diff --git a/misc/buffer-tests/TestHandleInheritance.cc b/misc/buffer-tests/TestHandleInheritance.cc deleted file mode 100644 index 04e8a9c8..00000000 --- a/misc/buffer-tests/TestHandleInheritance.cc +++ /dev/null @@ -1,474 +0,0 @@ -// Console handle tests -// -// These tests should pass on all client and server OSs from XP/2003 to -// Windows 10, inclusive. -// - -#include - -// Verify that the child's open console handle set is as expected from having -// just attached to or spawned from a source worker. -// * The set of child handles should exactly match the set of inheritable -// source handles. -// * Every open child handle should be inheritable. -static void checkAttachHandleSet(Worker &child, Worker &source) { - auto cvec = child.scanForConsoleHandles(); - auto cvecInherit = inheritableHandles(cvec); - auto svecInherit = inheritableHandles(source.scanForConsoleHandles()); - auto hv = &handleValues; - if (hv(cvecInherit) == hv(svecInherit) && hv(cvecInherit) == hv(cvec)) { - return; - } - source.dumpConsoleHandles(); - child.dumpConsoleHandles(); - CHECK(false && "checkAttachHandleSet failed"); -} - -static void Test_IntrinsicInheritFlags() { - // Console handles have an inherit flag, just as kernel handles do. - // - // In Windows 7, there is a bug where DuplicateHandle(h, FALSE) makes the - // new handle inheritable if the old handle was inheritable. - printTestName("testIntrinsicInheritFlags"); - - Worker p; - auto n = p.newBuffer(FALSE); - auto y = p.newBuffer(TRUE); - auto nn = n.dup(FALSE); - auto yn = y.dup(FALSE); - auto ny = n.dup(TRUE); - auto yy = y.dup(TRUE); - p.dumpConsoleHandles(); - - CHECK(n.inheritable() == false); - CHECK(nn.inheritable() == false); - CHECK(yn.inheritable() == isWin7()); - CHECK(y.inheritable() == true); - CHECK(ny.inheritable() == true); - CHECK(yy.inheritable() == true); - - for (auto &h : (Handle[]){ n, y, nn, ny, yn, yy }) { - const bool v = h.inheritable(); - if (isWin7()) { - // In Windows 7, the console handle inherit flags could not be - // changed. - CHECK(h.trySetInheritable(v) == false); - CHECK(h.trySetInheritable(!v) == false); - CHECK(h.inheritable() == v); - } else { - // With older and newer operating systems, the inheritability can - // be changed. (In newer operating systems, i.e. Windows 8 and up, - // the console handles are just normal kernel handles.) - CHECK(h.trySetInheritable(!v) == true); - CHECK(h.inheritable() == !v); - } - } - p.dumpConsoleHandles(); - - // For sanity's sake, check that DuplicateHandle(h, FALSE) does the right - // thing with an inheritable pipe handle, even on Windows 7. - auto pipeY = std::get<0>(newPipe(p, TRUE)); - auto pipeN = pipeY.dup(FALSE); - CHECK(pipeY.inheritable() == true); - CHECK(pipeN.inheritable() == false); -} - -// -// Test "traditional" console handle inheritance, as it worked prior to -// Windows 8. Details: -// -// * During console initialization (either process startup or AttachConsole), -// the process assumes the exact set of inheritable console handles as are -// open in the source process (either parent or attachee), with the same -// values. -// -// * AttachConsole resets the STD_{INPUT,OUTPUT,ERROR} handles to exactly -// 0x3, 0x7, and 0xb, even if those handles are invalid or if the previous -// standard handles were non-console handles. -// -// * FreeConsole has no effect on the standard handles. After FreeConsole -// returns, there is no console attached, so console handles are useless. -// They are closed, effectively or actually. -// -// * XXX: How does process startup set the standard handles? -// * XXX: Potential interactions could come from: -// - dwCreationFlags: -// CREATE_NEW_CONSOLE -// CREATE_NEW_PROCESS_GROUP -// CREATE_NO_WINDOW -// DETACHED_PROCESS -// EXTENDED_STARTUPINFO_PRESENT -// - STARTUPINFO -// dwFlags: STARTF_USESTDHANDLES -// hStdInput -// hStdOutput -// hStdError -// - GetStdHandle(STD_{INPUT,OUTPUT,ERROR}_HANDLE) -// - -static void Test_CreateNoWindow() { - // The documentation for CREATE_NO_WINDOW is confusing: - // - // The process is a console application that is being run without a - // console window. Therefore, the console handle for the application is - // not set. - // - // This flag is ignored if the application is not a console application, - // or if it is used with either CREATE_NEW_CONSOLE or DETACHED_PROCESS. - // - // Here's what's evident from examining the OS behavior: - // - // * Specifying both CREATE_NEW_CONSOLE and DETACHED_PROCESS causes the - // CreateProcess call to fail. - // - // * If CREATE_NO_WINDOW is specified together with CREATE_NEW_CONSOLE or - // DETACHED_PROCESS, it is quietly ignored, just as documented. - // - // * Otherwise, CreateProcess behaves the same with CREATE_NO_WINDOW as it - // does with CREATE_NEW_CONSOLE, except that the new console either has - // a hidden window or has no window at all. - // - printTestName("Test_CreateNoWindow"); - - // XXX: Test that CREATE_NEW_CONSOLE + DETACHED_PROCESS ==> failure - // XXX: Test that CREATE_NEW_CONSOLE + CREATE_NO_WINDOW ==> CREATE_NEW_CONSOLE - // XXX: Test that DETACHED_PROCESS + CREATE_NO_WINDOW ==> DETACHED_PROCESS - - Worker p; - // Open some handles to demonstrate the "clean slate" outcome. - p.getStdin().dup(TRUE).setStdin(); - p.newBuffer(TRUE).setStderr().dup(TRUE).setStdout().activate(); - auto c = p.child({ true, CREATE_NO_WINDOW }); - c.dumpConsoleHandles(); - // Verify a blank slate. - auto handles = c.scanForConsoleHandles(); - CHECK(handleValues(handles) == (std::vector { - c.getStdin().value(), - c.getStdout().value(), - c.getStderr().value(), - })); - if (isAtLeastWin7()) { - // As of Windows 7, there is no console window. - CHECK(c.consoleWindow() == NULL); - } else { - // On earlier operating systems, there is a console window, but it's - // hidden. - CHECK(!IsWindowVisible(c.consoleWindow())); - } -} - -static void Test_NewConsole_Resets_Everything() { - // Test that creating a new console properly resets everything. - // - // Tests: - // * CreateProcess - CREATE_NEW_CONSOLE - // * AllocConsole - // - printTestName("Test_NewConsole_Resets_Everything"); - Worker p; - - // Open some handles to demonstrate the "clean slate" outcome. - auto orig = { p.getStdin(), p.getStdout(), p.getStderr() }; - p.getStdin().dup(TRUE).setStdin(); - p.newBuffer(TRUE).setStderr().dup(TRUE).setStdout().activate(); - for (auto &h : orig) { - Handle::invent(h.value(), p).close(); - } - - // XXX: On Win8+, I doubt we can expect particular console handle values... - // XXX: Actually, I'm not sure how much of this test is valid there at all... - auto checkClean = [](Worker &proc) { - proc.dumpConsoleHandles(); - CHECK(proc.getStdin().uvalue() == 0x3); - CHECK(proc.getStdout().uvalue() == 0x7); - CHECK(proc.getStderr().uvalue() == 0xb); - auto handles = proc.scanForConsoleHandles(); - CHECK(handleValues(handles) == (std::vector { - proc.getStdin().value(), - proc.getStdout().value(), - proc.getStderr().value(), - })); - for (auto &h : handles) { - CHECK(h.inheritable()); - } - }; - - // A child with a new console is reset to a blank slate. - auto c = p.child({ true, CREATE_NEW_CONSOLE }); - checkClean(c); - - // Similarly, detaching and allocating a new console resets everything. - p.detach(); - p.alloc(); - checkClean(p); -} - -static void Test_DetachedProcess() { - // A child with DETACHED_PROCESS has no console, and its standard handles - // are set to 0 by default. - printTestName("Test_DetachedProcess"); - Worker p; - - p.getStdin().dup(TRUE).setStdin(); - p.getStdout().dup(TRUE).setStdout(); - p.getStderr().dup(TRUE).setStderr(); - - auto c = p.child({ true, DETACHED_PROCESS }); - - CHECK(c.getStdin().uvalue() == 0); - CHECK(c.getStdout().uvalue() == 0); - CHECK(c.getStderr().uvalue() == 0); - CHECK(c.scanForConsoleHandles().empty()); - CHECK(c.consoleWindow() == NULL); - - // XXX: With bInheritHandles=TRUE and DETACHED_PROCESS, are the standard handles always reset? - // - There are multiple cases to consider. i.e. Omitting STARTF_USESTDHANDLES vs specifying it. - // XXX: What do GetConsoleCP and GetConsoleOutputCP do when no console is attached? - - // Verify that we have a blank slate even with an implicit console - // creation. - auto c2 = c.child({ true }); - auto c2h = c2.scanForConsoleHandles(); - CHECK(handleValues(c2h) == (std::vector { - c2.getStdin().value(), - c2.getStdout().value(), - c2.getStderr().value(), - })); -} - -static void Test_Creation_bInheritHandles_Flag() { - // The bInheritHandles flags to CreateProcess has no effect on console - // handles. - // XXX: I think it *does* on Windows 8 and up. - printTestName("Test_Creation_bInheritHandles_Flag"); - Worker p; - for (auto &h : (Handle[]){ - p.getStdin(), - p.getStdout(), - p.getStderr(), - p.newBuffer(FALSE), - p.newBuffer(TRUE), - }) { - h.dup(FALSE); - h.dup(TRUE); - } - auto cY = p.child({ true }); - auto cN = p.child({ false }); - CHECK(handleValues(cY.scanForConsoleHandles()) == - handleValues(cN.scanForConsoleHandles())); -} - -static void Test_HandleAllocationOrder() { - // When a new handle is created, it always assumes the lowest unused value. - printTestName("Test_HandleAllocationOrder"); - Worker p; - - auto h3 = p.getStdin(); - auto h7 = p.getStdout(); - auto hb = p.getStderr(); - auto hf = h7.dup(TRUE); - auto h13 = h3.dup(TRUE); - auto h17 = hb.dup(TRUE); - - CHECK(h3.uvalue() == 0x3); - CHECK(h7.uvalue() == 0x7); - CHECK(hb.uvalue() == 0xb); - CHECK(hf.uvalue() == 0xf); - CHECK(h13.uvalue() == 0x13); - CHECK(h17.uvalue() == 0x17); - - hf.close(); - h13.close(); - h7.close(); - - h7 = h3.dup(TRUE); - hf = h3.dup(TRUE); - h13 = h3.dup(TRUE); - auto h1b = h3.dup(TRUE); - - CHECK(h7.uvalue() == 0x7); - CHECK(hf.uvalue() == 0xf); - CHECK(h13.uvalue() == 0x13); - CHECK(h1b.uvalue() == 0x1b); -} - -static void Test_InheritNothing() { - // It's possible for the standard handles to be non-inheritable. - // - // Avoid calling DuplicateHandle(h, FALSE), because it produces inheritable - // console handles on Windows 7. - printTestName("Test_InheritNothing"); - Worker p; - auto conin = p.openConin(); - auto conout = p.openConout(); - p.getStdin().close(); - p.getStdout().close(); - p.getStderr().close(); - conin.setStdin(); - conout.setStdout().dup().setStderr(); - p.dumpConsoleHandles(); - - auto c = p.child({ true }); - // The child has no open console handles. - CHECK(c.scanForConsoleHandles().empty()); - c.dumpConsoleHandles(); - // The standard handle values are inherited, even though they're invalid. - CHECK(c.getStdin().value() == p.getStdin().value()); - CHECK(c.getStdout().value() == p.getStdout().value()); - CHECK(c.getStderr().value() == p.getStderr().value()); - // Verify a console is attached. - CHECK(c.consoleWindow() != NULL); - CHECK(c.openConin().value() != INVALID_HANDLE_VALUE); - CHECK(c.openConout().value() != INVALID_HANDLE_VALUE); - CHECK(c.newBuffer().value() != INVALID_HANDLE_VALUE); -} - -// XXX: Does specifying a handle in STD_{...}_HANDLE or hStd{Input,...} -// influence whether it is inherited, in any situation? - -static void Test_Detach_Does_Not_Change_Standard_Handles() { - // Detaching the current console does not affect the standard handles. - printTestName("Test_Detach_Does_Not_Change_Standard_Handles"); - auto check = [](Worker &p) { - Handle h[] = { p.getStdin(), p.getStdout(), p.getStderr() }; - p.detach(); - CHECK(p.getStdin().value() == h[0].value()); - CHECK(p.getStdout().value() == h[1].value()); - CHECK(p.getStderr().value() == h[2].value()); - }; - // Simplest form of the test. - Worker p1; - check(p1); - // Also do a test with duplicated handles, just in case detaching resets - // the handles to their defaults. - Worker p2; - p2.getStdin().dup(TRUE).setStdin(); - p2.getStdout().dup(TRUE).setStdout(); - p2.getStderr().dup(TRUE).setStderr(); - check(p2); -} - -static void Test_Activate_Does_Not_Change_Standard_Handles() { - // Setting a console as active does not change the standard handles. - printTestName("Test_Activate_Does_Not_Change_Standard_Handles"); - Worker p; - auto out = p.getStdout(); - auto err = p.getStderr(); - p.newBuffer(TRUE).activate(); - CHECK(p.getStdout().value() == out.value()); - CHECK(p.getStderr().value() == err.value()); -} - -static void Test_AttachConsole_And_CreateProcess_Inheritance() { - printTestName("Test_AttachConsole_And_CreateProcess_Inheritance"); - Worker p; - Worker unrelated({ false, DETACHED_PROCESS }); - - auto conin = p.getStdin().dup(TRUE); - auto conout1 = p.getStdout().dup(TRUE); - auto conout2 = p.getStderr().dup(TRUE); - p.openConout(FALSE); // an extra handle for checkAttachHandleSet testing - p.openConout(TRUE); // an extra handle for checkAttachHandleSet testing - p.getStdin().close(); - p.getStdout().close(); - p.getStderr().close(); - conin.setStdin(); - conout1.setStdout(); - conout2.setStderr(); - - auto c = p.child({ true }); - - auto c2 = c.child({ true }); - c2.detach(); - c2.attach(c); - - unrelated.attach(p); - - // The first child will have the same standard handles as the parent. - CHECK(c.getStdin().value() == p.getStdin().value()); - CHECK(c.getStdout().value() == p.getStdout().value()); - CHECK(c.getStderr().value() == p.getStderr().value()); - - // AttachConsole always sets the handles to (0x3, 0x7, 0xb) regardless of - // handle validity. In this case, c2 initially had non-default handles, - // and it attached to a process that has and also initially had - // non-default handles. Nevertheless, the new standard handles are the - // defaults. - for (auto proc : {&c2, &unrelated}) { - CHECK(proc->getStdin().uvalue() == 0x3); - CHECK(proc->getStdout().uvalue() == 0x7); - CHECK(proc->getStderr().uvalue() == 0xb); - } - - // The set of inheritable console handles in the two children exactly match - // that of the parent. - checkAttachHandleSet(c, p); - checkAttachHandleSet(c2, p); - checkAttachHandleSet(unrelated, p); -} - -static void Test_Detach_Implicitly_Closes_Handles() { - // After detaching, calling GetHandleInformation fails on previous console - // handles. Prior to Windows 8, this property applied to all console - // handles; in Windows 8, it only applies to some handles, apparently. - - printTestName("Test_Detach_Implicitly_Closes_Handles"); - Worker p; - Handle orig1[] = { - p.getStdin(), - p.getStdout(), - p.getStderr(), - }; - Handle orig2[] = { - p.getStdin().dup(TRUE), - p.getStdout().dup(TRUE), - p.getStderr().dup(TRUE), - p.openConin(TRUE), - p.openConout(TRUE), - }; - - p.detach(); - - // After detaching the console, these handles are closed. - for (auto h : orig1) { - CHECK(!h.tryFlags()); - } - if (!isAtLeastWin8()) { - // Previously, these handles were also closed. - for (auto h : orig2) { - CHECK(!h.tryFlags()); - } - } else { - // As of Windows 8, these handles aren't closed. - for (auto h : orig2) { - CHECK(h.inheritable()); - } - } -} - -static void Test_Conin_Versus_Conout() { - // Ensure that APIs meant for the other kind of handle fail. - printTestName("Test_Conin_Versus_Conout"); - Worker p; - CHECK(!p.getStdin().tryScreenBufferInfo()); - CHECK(!p.getStdout().tryNumberOfConsoleInputEvents()); -} - -int main() { - CHECK(false && "Just making sure the CHECK macro is working..."); - - Test_IntrinsicInheritFlags(); - Test_CreateNoWindow(); - Test_NewConsole_Resets_Everything(); - Test_DetachedProcess(); - Test_Creation_bInheritHandles_Flag(); - Test_HandleAllocationOrder(); - Test_InheritNothing(); - Test_Detach_Does_Not_Change_Standard_Handles(); - Test_Activate_Does_Not_Change_Standard_Handles(); - Test_AttachConsole_And_CreateProcess_Inheritance(); - Test_Detach_Implicitly_Closes_Handles(); - Test_Conin_Versus_Conout(); - - return 0; -} diff --git a/misc/buffer-tests/tests.mk b/misc/buffer-tests/tests.mk index 99b68d91..93f6efad 100755 --- a/misc/buffer-tests/tests.mk +++ b/misc/buffer-tests/tests.mk @@ -1,8 +1,7 @@ TESTS += \ build/Test_GetConsoleTitleW.exe \ build/Win7Bug_InheritHandles.exe \ - build/Win7Bug_RaceCondition.exe \ - build/TestHandleInheritance.exe + build/Win7Bug_RaceCondition.exe # To add tests that aren't checked-in, create an mk file of this name: -include local_tests.mk