/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing#kwsys for details. */ #include "kwsysPrivate.h" // Ignore Windows version levels defined by command-line flags. This // source needs access to all APIs available on the host in order for // the test to run properly. The test binary is not installed anyway. #undef _WIN32_WINNT #undef NTDDI_VERSION #include KWSYS_HEADER(Encoding.hxx) // Work-around CMake dependency scanning limitation. This must // duplicate the above list of headers. #if 0 # include "Encoding.hxx.in" #endif #if defined(_WIN32) # include # include # include # include # include # include # include # include "testConsoleBuf.hxx" # if defined(_MSC_VER) && _MSC_VER >= 1800 # define KWSYS_WINDOWS_DEPRECATED_GetVersion # endif // يونيكود static const WCHAR UnicodeInputTestString[] = L"\u064A\u0648\u0646\u064A\u0643\u0648\u062F!"; static UINT TestCodepage = KWSYS_ENCODING_DEFAULT_CODEPAGE; static const DWORD waitTimeout = 10 * 1000; static STARTUPINFO startupInfo; static PROCESS_INFORMATION processInfo; static HANDLE beforeInputEvent; static HANDLE afterOutputEvent; static std::string encodedInputTestString; static std::string encodedTestString; static void displayError(DWORD errorCode) { std::cerr.setf(std::ios::hex, std::ios::basefield); std::cerr << "Failed with error: 0x" << errorCode << "!" << std::endl; LPWSTR message; if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, errorCode, 0, (LPWSTR)&message, 0, nullptr)) { std::cerr << "Error message: " << kwsys::Encoding::ToNarrow(message) << std::endl; HeapFree(GetProcessHeap(), 0, message); } else { std::cerr << "FormatMessage() failed with error: 0x" << GetLastError() << "!" << std::endl; } std::cerr.unsetf(std::ios::hex); } std::basic_streambuf* errstream(const char* unused) { static_cast(unused); return std::cerr.rdbuf(); } std::basic_streambuf* errstream(const wchar_t* unused) { static_cast(unused); return std::wcerr.rdbuf(); } template static void dumpBuffers(const T* expected, const T* received, size_t size) { std::basic_ostream err(errstream(expected)); err << "Expected output: '" << std::basic_string(expected, size) << "'" << std::endl; if (err.fail()) { err.clear(); err << "--- Error while outputting ---" << std::endl; } err << "Received output: '" << std::basic_string(received, size) << "'" << std::endl; if (err.fail()) { err.clear(); err << "--- Error while outputting ---" << std::endl; } std::cerr << "Expected output | Received output" << std::endl; for (size_t i = 0; i < size; i++) { std::cerr << std::setbase(16) << std::setfill('0') << " " << "0x" << std::setw(8) << static_cast(expected[i]) << " | " << "0x" << std::setw(8) << static_cast(received[i]); if (static_cast(expected[i]) != static_cast(received[i])) { std::cerr << " MISMATCH!"; } std::cerr << std::endl; } std::cerr << std::endl; } static bool createProcess(HANDLE hIn, HANDLE hOut, HANDLE hErr) { BOOL bInheritHandles = FALSE; DWORD dwCreationFlags = 0; memset(&processInfo, 0, sizeof(processInfo)); memset(&startupInfo, 0, sizeof(startupInfo)); startupInfo.cb = sizeof(startupInfo); startupInfo.dwFlags = STARTF_USESHOWWINDOW; startupInfo.wShowWindow = SW_HIDE; if (hIn || hOut || hErr) { startupInfo.dwFlags |= STARTF_USESTDHANDLES; startupInfo.hStdInput = hIn; startupInfo.hStdOutput = hOut; startupInfo.hStdError = hErr; bInheritHandles = TRUE; } WCHAR cmd[MAX_PATH]; if (GetModuleFileNameW(nullptr, cmd, MAX_PATH) == 0) { std::cerr << "GetModuleFileName failed!" << std::endl; return false; } WCHAR* p = cmd + wcslen(cmd); while (p > cmd && *p != L'\\') p--; *(p + 1) = 0; wcscat(cmd, cmdConsoleBufChild); wcscat(cmd, L".exe"); bool success = CreateProcessW(nullptr, // No module name (use command line) cmd, // Command line nullptr, // Process handle not inheritable nullptr, // Thread handle not inheritable bInheritHandles, // Set handle inheritance dwCreationFlags, nullptr, // Use parent's environment block nullptr, // Use parent's starting directory &startupInfo, // Pointer to STARTUPINFO structure &processInfo) != 0; // Pointer to PROCESS_INFORMATION structure if (!success) { DWORD lastError = GetLastError(); std::cerr << "CreateProcess(" << kwsys::Encoding::ToNarrow(cmd) << ")" << std::endl; displayError(lastError); } return success; } static void finishProcess(bool success) { if (success) { success = WaitForSingleObject(processInfo.hProcess, waitTimeout) == WAIT_OBJECT_0; }; if (!success) { TerminateProcess(processInfo.hProcess, 1); } CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); } static bool createPipe(PHANDLE readPipe, PHANDLE writePipe) { SECURITY_ATTRIBUTES securityAttributes; securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); securityAttributes.bInheritHandle = TRUE; securityAttributes.lpSecurityDescriptor = nullptr; return CreatePipe(readPipe, writePipe, &securityAttributes, 0) == 0 ? false : true; } static void finishPipe(HANDLE readPipe, HANDLE writePipe) { if (readPipe != INVALID_HANDLE_VALUE) { CloseHandle(readPipe); } if (writePipe != INVALID_HANDLE_VALUE) { CloseHandle(writePipe); } } static HANDLE createFile(LPCWSTR fileName) { SECURITY_ATTRIBUTES securityAttributes; securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); securityAttributes.bInheritHandle = TRUE; securityAttributes.lpSecurityDescriptor = nullptr; HANDLE file = CreateFileW(fileName, GENERIC_READ | GENERIC_WRITE, 0, // do not share &securityAttributes, CREATE_ALWAYS, // overwrite existing FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, nullptr); // no template if (file == INVALID_HANDLE_VALUE) { DWORD lastError = GetLastError(); std::cerr << "CreateFile(" << kwsys::Encoding::ToNarrow(fileName) << ")" << std::endl; displayError(lastError); } return file; } static void finishFile(HANDLE file) { if (file != INVALID_HANDLE_VALUE) { CloseHandle(file); } } # ifndef MAPVK_VK_TO_VSC # define MAPVK_VK_TO_VSC (0) # endif static void writeInputKeyEvent(INPUT_RECORD inputBuffer[], WCHAR chr) { inputBuffer[0].EventType = KEY_EVENT; inputBuffer[0].Event.KeyEvent.bKeyDown = TRUE; inputBuffer[0].Event.KeyEvent.wRepeatCount = 1; SHORT keyCode = VkKeyScanW(chr); if (keyCode == -1) { // Character can't be entered with current keyboard layout // Just set any, it doesn't really matter keyCode = 'K'; } inputBuffer[0].Event.KeyEvent.wVirtualKeyCode = LOBYTE(keyCode); inputBuffer[0].Event.KeyEvent.wVirtualScanCode = MapVirtualKey( inputBuffer[0].Event.KeyEvent.wVirtualKeyCode, MAPVK_VK_TO_VSC); inputBuffer[0].Event.KeyEvent.uChar.UnicodeChar = chr; inputBuffer[0].Event.KeyEvent.dwControlKeyState = 0; if ((HIBYTE(keyCode) & 1) == 1) { inputBuffer[0].Event.KeyEvent.dwControlKeyState |= SHIFT_PRESSED; } if ((HIBYTE(keyCode) & 2) == 2) { inputBuffer[0].Event.KeyEvent.dwControlKeyState |= RIGHT_CTRL_PRESSED; } if ((HIBYTE(keyCode) & 4) == 4) { inputBuffer[0].Event.KeyEvent.dwControlKeyState |= RIGHT_ALT_PRESSED; } inputBuffer[1].EventType = inputBuffer[0].EventType; inputBuffer[1].Event.KeyEvent.bKeyDown = FALSE; inputBuffer[1].Event.KeyEvent.wRepeatCount = 1; inputBuffer[1].Event.KeyEvent.wVirtualKeyCode = inputBuffer[0].Event.KeyEvent.wVirtualKeyCode; inputBuffer[1].Event.KeyEvent.wVirtualScanCode = inputBuffer[0].Event.KeyEvent.wVirtualScanCode; inputBuffer[1].Event.KeyEvent.uChar.UnicodeChar = inputBuffer[0].Event.KeyEvent.uChar.UnicodeChar; inputBuffer[1].Event.KeyEvent.dwControlKeyState = 0; } static int testPipe() { int didFail = 1; HANDLE inPipeRead = INVALID_HANDLE_VALUE; HANDLE inPipeWrite = INVALID_HANDLE_VALUE; HANDLE outPipeRead = INVALID_HANDLE_VALUE; HANDLE outPipeWrite = INVALID_HANDLE_VALUE; HANDLE errPipeRead = INVALID_HANDLE_VALUE; HANDLE errPipeWrite = INVALID_HANDLE_VALUE; UINT currentCodepage = GetConsoleCP(); char buffer[200]; char buffer2[200]; try { if (!createPipe(&inPipeRead, &inPipeWrite) || !createPipe(&outPipeRead, &outPipeWrite) || !createPipe(&errPipeRead, &errPipeWrite)) { throw std::runtime_error("createFile failed!"); } if (TestCodepage == CP_ACP) { TestCodepage = GetACP(); } if (!SetConsoleCP(TestCodepage)) { throw std::runtime_error("SetConsoleCP failed!"); } DWORD bytesWritten = 0; if (!WriteFile(inPipeWrite, encodedInputTestString.c_str(), (DWORD)encodedInputTestString.size(), &bytesWritten, nullptr) || bytesWritten == 0) { throw std::runtime_error("WriteFile failed!"); } if (createProcess(inPipeRead, outPipeWrite, errPipeWrite)) { try { DWORD status; if ((status = WaitForSingleObject(afterOutputEvent, waitTimeout)) != WAIT_OBJECT_0) { std::cerr.setf(std::ios::hex, std::ios::basefield); std::cerr << "WaitForSingleObject returned unexpected status 0x" << status << std::endl; std::cerr.unsetf(std::ios::hex); throw std::runtime_error("WaitForSingleObject failed!"); } DWORD bytesRead = 0; if (!ReadFile(outPipeRead, buffer, sizeof(buffer), &bytesRead, nullptr) || bytesRead == 0) { throw std::runtime_error("ReadFile#1 failed!"); } buffer[bytesRead] = 0; if ((bytesRead < encodedTestString.size() + 1 + encodedInputTestString.size() && !ReadFile(outPipeRead, buffer + bytesRead, sizeof(buffer) - bytesRead, &bytesRead, nullptr)) || bytesRead == 0) { throw std::runtime_error("ReadFile#2 failed!"); } if (memcmp(buffer, encodedTestString.c_str(), encodedTestString.size()) == 0 && memcmp(buffer + encodedTestString.size() + 1, encodedInputTestString.c_str(), encodedInputTestString.size()) == 0) { bytesRead = 0; if (!ReadFile(errPipeRead, buffer2, sizeof(buffer2), &bytesRead, nullptr) || bytesRead == 0) { throw std::runtime_error("ReadFile#3 failed!"); } buffer2[bytesRead] = 0; didFail = encodedTestString.compare(0, std::string::npos, buffer2, encodedTestString.size()) == 0 ? 0 : 1; } if (didFail != 0) { std::cerr << "Pipe's output didn't match expected output!" << std::endl; dumpBuffers(encodedTestString.c_str(), buffer, encodedTestString.size()); dumpBuffers(encodedInputTestString.c_str(), buffer + encodedTestString.size() + 1, encodedInputTestString.size()); dumpBuffers(encodedTestString.c_str(), buffer2, encodedTestString.size()); } } catch (const std::runtime_error& ex) { DWORD lastError = GetLastError(); std::cerr << "In function testPipe, line " << __LINE__ << ": " << ex.what() << std::endl; displayError(lastError); } finishProcess(didFail == 0); } } catch (const std::runtime_error& ex) { DWORD lastError = GetLastError(); std::cerr << "In function testPipe, line " << __LINE__ << ": " << ex.what() << std::endl; displayError(lastError); } finishPipe(inPipeRead, inPipeWrite); finishPipe(outPipeRead, outPipeWrite); finishPipe(errPipeRead, errPipeWrite); SetConsoleCP(currentCodepage); return didFail; } static int testFile() { int didFail = 1; HANDLE inFile = INVALID_HANDLE_VALUE; HANDLE outFile = INVALID_HANDLE_VALUE; HANDLE errFile = INVALID_HANDLE_VALUE; try { if ((inFile = createFile(L"stdinFile.txt")) == INVALID_HANDLE_VALUE || (outFile = createFile(L"stdoutFile.txt")) == INVALID_HANDLE_VALUE || (errFile = createFile(L"stderrFile.txt")) == INVALID_HANDLE_VALUE) { throw std::runtime_error("createFile failed!"); } DWORD bytesWritten = 0; char buffer[200]; char buffer2[200]; int length; if ((length = WideCharToMultiByte(TestCodepage, 0, UnicodeInputTestString, -1, buffer, sizeof(buffer), nullptr, nullptr)) == 0) { throw std::runtime_error("WideCharToMultiByte failed!"); } buffer[length - 1] = '\n'; if (!WriteFile(inFile, buffer, length, &bytesWritten, nullptr) || bytesWritten == 0) { throw std::runtime_error("WriteFile failed!"); } if (SetFilePointer(inFile, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { throw std::runtime_error("SetFilePointer failed!"); } if (createProcess(inFile, outFile, errFile)) { DWORD bytesRead = 0; try { DWORD status; if ((status = WaitForSingleObject(afterOutputEvent, waitTimeout)) != WAIT_OBJECT_0) { std::cerr.setf(std::ios::hex, std::ios::basefield); std::cerr << "WaitForSingleObject returned unexpected status 0x" << status << std::endl; std::cerr.unsetf(std::ios::hex); throw std::runtime_error("WaitForSingleObject failed!"); } if (SetFilePointer(outFile, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { throw std::runtime_error("SetFilePointer#1 failed!"); } if (!ReadFile(outFile, buffer, sizeof(buffer), &bytesRead, nullptr) || bytesRead == 0) { throw std::runtime_error("ReadFile#1 failed!"); } buffer[bytesRead] = 0; if (memcmp(buffer, encodedTestString.c_str(), encodedTestString.size()) == 0 && memcmp(buffer + encodedTestString.size() + 1, encodedInputTestString.c_str(), encodedInputTestString.size()) == 0) { bytesRead = 0; if (SetFilePointer(errFile, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { throw std::runtime_error("SetFilePointer#2 failed!"); } if (!ReadFile(errFile, buffer2, sizeof(buffer2), &bytesRead, nullptr) || bytesRead == 0) { throw std::runtime_error("ReadFile#2 failed!"); } buffer2[bytesRead] = 0; didFail = encodedTestString.compare(0, std::string::npos, buffer2, encodedTestString.size()) == 0 ? 0 : 1; } if (didFail != 0) { std::cerr << "File's output didn't match expected output!" << std::endl; dumpBuffers(encodedTestString.c_str(), buffer, encodedTestString.size()); dumpBuffers(encodedInputTestString.c_str(), buffer + encodedTestString.size() + 1, encodedInputTestString.size()); dumpBuffers(encodedTestString.c_str(), buffer2, encodedTestString.size()); } } catch (const std::runtime_error& ex) { DWORD lastError = GetLastError(); std::cerr << "In function testFile, line " << __LINE__ << ": " << ex.what() << std::endl; displayError(lastError); } finishProcess(didFail == 0); } } catch (const std::runtime_error& ex) { DWORD lastError = GetLastError(); std::cerr << "In function testFile, line " << __LINE__ << ": " << ex.what() << std::endl; displayError(lastError); } finishFile(inFile); finishFile(outFile); finishFile(errFile); return didFail; } # ifndef _WIN32_WINNT_VISTA # define _WIN32_WINNT_VISTA 0x0600 # endif static bool consoleIsConhost() { wchar_t consoleClassNameBuf[64]; int const consoleClassNameLen = GetClassNameW( GetConsoleWindow(), &consoleClassNameBuf[0], sizeof(consoleClassNameBuf)); // Windows Console Host: ConsoleWindowClass // Windows Terminal / ConPTY: PseudoConsoleWindow (undocumented) return (consoleClassNameLen > 0 && wcscmp(consoleClassNameBuf, L"ConsoleWindowClass") == 0); } static bool charIsNUL(wchar_t c) { return c == 0; } static int testConsole() { int didFail = 1; HANDLE parentIn = GetStdHandle(STD_INPUT_HANDLE); HANDLE parentOut = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE parentErr = GetStdHandle(STD_ERROR_HANDLE); HANDLE hIn = parentIn; HANDLE hOut = parentOut; DWORD consoleMode; bool newConsole = false; bool forceNewConsole = false; bool restoreConsole = false; LPCWSTR TestFaceName = L"Lucida Console"; const DWORD TestFontFamily = 0x00000036; const DWORD TestFontSize = 0x000c0000; HKEY hConsoleKey; WCHAR FaceName[200]; FaceName[0] = 0; DWORD FaceNameSize = sizeof(FaceName); DWORD FontFamily = TestFontFamily; DWORD FontSize = TestFontSize; # ifdef KWSYS_WINDOWS_DEPRECATED_GetVersion # pragma warning(push) # ifdef __INTEL_COMPILER # pragma warning(disable : 1478) # elif defined __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-declarations" # else # pragma warning(disable : 4996) # endif # endif const bool isVistaOrGreater = LOBYTE(LOWORD(GetVersion())) >= HIBYTE(_WIN32_WINNT_VISTA); # ifdef KWSYS_WINDOWS_DEPRECATED_GetVersion # ifdef __clang__ # pragma clang diagnostic pop # else # pragma warning(pop) # endif # endif if (!isVistaOrGreater) { if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Console", 0, KEY_READ | KEY_WRITE, &hConsoleKey) == ERROR_SUCCESS) { DWORD dwordSize = sizeof(DWORD); if (RegQueryValueExW(hConsoleKey, L"FontFamily", nullptr, nullptr, (LPBYTE)&FontFamily, &dwordSize) == ERROR_SUCCESS) { if (FontFamily != TestFontFamily) { RegQueryValueExW(hConsoleKey, L"FaceName", nullptr, nullptr, (LPBYTE)FaceName, &FaceNameSize); RegQueryValueExW(hConsoleKey, L"FontSize", nullptr, nullptr, (LPBYTE)&FontSize, &dwordSize); RegSetValueExW(hConsoleKey, L"FontFamily", 0, REG_DWORD, (BYTE*)&TestFontFamily, sizeof(TestFontFamily)); RegSetValueExW(hConsoleKey, L"FaceName", 0, REG_SZ, (BYTE*)TestFaceName, (DWORD)((wcslen(TestFaceName) + 1) * sizeof(WCHAR))); RegSetValueExW(hConsoleKey, L"FontSize", 0, REG_DWORD, (BYTE*)&TestFontSize, sizeof(TestFontSize)); restoreConsole = true; forceNewConsole = true; } } else { std::cerr << "RegGetValueW(FontFamily) failed!" << std::endl; } RegCloseKey(hConsoleKey); } else { std::cerr << "RegOpenKeyExW(HKEY_CURRENT_USER\\Console) failed!" << std::endl; } } if (forceNewConsole || GetConsoleMode(parentOut, &consoleMode) == 0) { // Not a real console, let's create new one. FreeConsole(); if (!AllocConsole()) { std::cerr << "AllocConsole failed!" << std::endl; return didFail; } SECURITY_ATTRIBUTES securityAttributes; securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); securityAttributes.bInheritHandle = TRUE; securityAttributes.lpSecurityDescriptor = nullptr; hIn = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &securityAttributes, OPEN_EXISTING, 0, nullptr); if (hIn == INVALID_HANDLE_VALUE) { DWORD lastError = GetLastError(); std::cerr << "CreateFile(CONIN$)" << std::endl; displayError(lastError); } hOut = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &securityAttributes, OPEN_EXISTING, 0, nullptr); if (hOut == INVALID_HANDLE_VALUE) { DWORD lastError = GetLastError(); std::cerr << "CreateFile(CONOUT$)" << std::endl; displayError(lastError); } SetStdHandle(STD_INPUT_HANDLE, hIn); SetStdHandle(STD_OUTPUT_HANDLE, hOut); SetStdHandle(STD_ERROR_HANDLE, hOut); newConsole = true; } # if _WIN32_WINNT >= _WIN32_WINNT_VISTA if (isVistaOrGreater) { CONSOLE_FONT_INFOEX consoleFont; memset(&consoleFont, 0, sizeof(consoleFont)); consoleFont.cbSize = sizeof(consoleFont); HMODULE kernel32 = LoadLibraryW(L"kernel32.dll"); typedef BOOL(WINAPI * GetCurrentConsoleFontExFunc)( HANDLE hConsoleOutput, BOOL bMaximumWindow, PCONSOLE_FONT_INFOEX lpConsoleCurrentFontEx); typedef BOOL(WINAPI * SetCurrentConsoleFontExFunc)( HANDLE hConsoleOutput, BOOL bMaximumWindow, PCONSOLE_FONT_INFOEX lpConsoleCurrentFontEx); GetCurrentConsoleFontExFunc getConsoleFont = (GetCurrentConsoleFontExFunc)GetProcAddress(kernel32, "GetCurrentConsoleFontEx"); SetCurrentConsoleFontExFunc setConsoleFont = (SetCurrentConsoleFontExFunc)GetProcAddress(kernel32, "SetCurrentConsoleFontEx"); if (getConsoleFont(hOut, FALSE, &consoleFont)) { if (consoleFont.FontFamily != TestFontFamily) { consoleFont.FontFamily = TestFontFamily; wcscpy(consoleFont.FaceName, TestFaceName); if (!setConsoleFont(hOut, FALSE, &consoleFont)) { std::cerr << "SetCurrentConsoleFontEx failed!" << std::endl; } } } else { std::cerr << "GetCurrentConsoleFontEx failed!" << std::endl; } } else { # endif if (restoreConsole && RegOpenKeyExW(HKEY_CURRENT_USER, L"Console", 0, KEY_WRITE, &hConsoleKey) == ERROR_SUCCESS) { RegSetValueExW(hConsoleKey, L"FontFamily", 0, REG_DWORD, (BYTE*)&FontFamily, sizeof(FontFamily)); if (FaceName[0] != 0) { RegSetValueExW(hConsoleKey, L"FaceName", 0, REG_SZ, (BYTE*)FaceName, FaceNameSize); } else { RegDeleteValueW(hConsoleKey, L"FaceName"); } RegSetValueExW(hConsoleKey, L"FontSize", 0, REG_DWORD, (BYTE*)&FontSize, sizeof(FontSize)); RegCloseKey(hConsoleKey); } # if _WIN32_WINNT >= _WIN32_WINNT_VISTA } # endif if (createProcess(nullptr, nullptr, nullptr)) { try { DWORD status; if ((status = WaitForSingleObject(beforeInputEvent, waitTimeout)) != WAIT_OBJECT_0) { std::cerr.setf(std::ios::hex, std::ios::basefield); std::cerr << "WaitForSingleObject returned unexpected status 0x" << status << std::endl; std::cerr.unsetf(std::ios::hex); throw std::runtime_error("WaitForSingleObject#1 failed!"); } INPUT_RECORD inputBuffer[(sizeof(UnicodeInputTestString) / sizeof(UnicodeInputTestString[0])) * 2]; memset(&inputBuffer, 0, sizeof(inputBuffer)); unsigned int i; for (i = 0; i < (sizeof(UnicodeInputTestString) / sizeof(UnicodeInputTestString[0]) - 1); i++) { writeInputKeyEvent(&inputBuffer[i * 2], UnicodeInputTestString[i]); } writeInputKeyEvent(&inputBuffer[i * 2], VK_RETURN); DWORD eventsWritten = 0; // We need to wait a bit before writing to console so child process have // started waiting for input on stdin. Sleep(300); if (!WriteConsoleInputW(hIn, inputBuffer, sizeof(inputBuffer) / sizeof(inputBuffer[0]), &eventsWritten) || eventsWritten == 0) { throw std::runtime_error("WriteConsoleInput failed!"); } if ((status = WaitForSingleObject(afterOutputEvent, waitTimeout)) != WAIT_OBJECT_0) { std::cerr.setf(std::ios::hex, std::ios::basefield); std::cerr << "WaitForSingleObject returned unexpected status 0x" << status << std::endl; std::cerr.unsetf(std::ios::hex); throw std::runtime_error("WaitForSingleObject#2 failed!"); } CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo; if (!GetConsoleScreenBufferInfo(hOut, &screenBufferInfo)) { throw std::runtime_error("GetConsoleScreenBufferInfo failed!"); } COORD coord; DWORD charsRead = 0; coord.X = 0; coord.Y = screenBufferInfo.dwCursorPosition.Y - 4; WCHAR* outputBuffer = new WCHAR[screenBufferInfo.dwSize.X * 4]; if (!ReadConsoleOutputCharacterW(hOut, outputBuffer, screenBufferInfo.dwSize.X * 4, coord, &charsRead) || charsRead == 0) { delete[] outputBuffer; throw std::runtime_error("ReadConsoleOutputCharacter failed!"); } std::wstring wideTestString = kwsys::Encoding::ToWide(encodedTestString); if (consoleIsConhost()) { // Windows Console Host converts NUL bytes to spaces. std::replace(wideTestString.begin(), wideTestString.end(), '\0', ' '); } else { // Windows Terminal / ConPTY removes NUL bytes. wideTestString.erase(std::remove_if(wideTestString.begin(), wideTestString.end(), charIsNUL), wideTestString.end()); } std::wstring wideInputTestString = kwsys::Encoding::ToWide(encodedInputTestString); if (memcmp(outputBuffer, wideTestString.c_str(), wideTestString.size() * sizeof(wchar_t)) == 0 && memcmp(outputBuffer + screenBufferInfo.dwSize.X * 1, wideTestString.c_str(), wideTestString.size() * sizeof(wchar_t)) == 0 && memcmp(outputBuffer + screenBufferInfo.dwSize.X * 2, UnicodeInputTestString, sizeof(UnicodeInputTestString) - sizeof(WCHAR)) == 0 && memcmp(outputBuffer + screenBufferInfo.dwSize.X * 3, wideInputTestString.c_str(), (wideInputTestString.size() - 1) * sizeof(wchar_t)) == 0) { didFail = 0; } else { std::cerr << "Console's output didn't match expected output!" << std::endl; dumpBuffers(wideTestString.c_str(), outputBuffer, wideTestString.size()); dumpBuffers(wideTestString.c_str(), outputBuffer + screenBufferInfo.dwSize.X * 1, wideTestString.size()); dumpBuffers( UnicodeInputTestString, outputBuffer + screenBufferInfo.dwSize.X * 2, (sizeof(UnicodeInputTestString) - 1) / sizeof(WCHAR)); dumpBuffers(wideInputTestString.c_str(), outputBuffer + screenBufferInfo.dwSize.X * 3, wideInputTestString.size() - 1); } delete[] outputBuffer; } catch (const std::runtime_error& ex) { DWORD lastError = GetLastError(); std::cerr << "In function testConsole, line " << __LINE__ << ": " << ex.what() << std::endl; displayError(lastError); } finishProcess(didFail == 0); } if (newConsole) { SetStdHandle(STD_INPUT_HANDLE, parentIn); SetStdHandle(STD_OUTPUT_HANDLE, parentOut); SetStdHandle(STD_ERROR_HANDLE, parentErr); CloseHandle(hIn); CloseHandle(hOut); FreeConsole(); } return didFail; } #endif int testConsoleBuf(int, char*[]) { int ret = 0; #if defined(_WIN32) beforeInputEvent = CreateEventW(nullptr, FALSE, // auto-reset event FALSE, // initial state is nonsignaled BeforeInputEventName); // object name if (!beforeInputEvent) { std::cerr << "CreateEvent#1 failed " << GetLastError() << std::endl; return 1; } afterOutputEvent = CreateEventW(nullptr, FALSE, FALSE, AfterOutputEventName); if (!afterOutputEvent) { std::cerr << "CreateEvent#2 failed " << GetLastError() << std::endl; return 1; } encodedTestString = kwsys::Encoding::ToNarrow(std::wstring( UnicodeTestString, sizeof(UnicodeTestString) / sizeof(wchar_t) - 1)); encodedInputTestString = kwsys::Encoding::ToNarrow( std::wstring(UnicodeInputTestString, sizeof(UnicodeInputTestString) / sizeof(wchar_t) - 1)); encodedInputTestString += "\n"; ret |= testPipe(); ret |= testFile(); ret |= testConsole(); CloseHandle(beforeInputEvent); CloseHandle(afterOutputEvent); #endif return ret; }