2546 lines
115 KiB
C++
2546 lines
115 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "precomp.h"
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
|
|
#define ENGLISH_US_CP 437u
|
|
#define JAPANESE_CP 932u
|
|
|
|
using WEX::Logging::Log;
|
|
using WEX::TestExecution::TestData;
|
|
using namespace WEX::Common;
|
|
|
|
namespace DbcsWriteRead
|
|
{
|
|
enum WriteMode
|
|
{
|
|
CrtWrite = 0,
|
|
WriteConsoleOutputFunc = 1,
|
|
WriteConsoleOutputCharacterFunc = 2,
|
|
WriteConsoleFunc = 3
|
|
};
|
|
|
|
enum ReadMode
|
|
{
|
|
ReadConsoleOutputFunc = 0,
|
|
ReadConsoleOutputCharacterFunc = 1
|
|
};
|
|
|
|
void TestRunner(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
_In_opt_ WORD* const pwAttrOverride,
|
|
const bool fUseTrueType,
|
|
const DbcsWriteRead::WriteMode WriteMode,
|
|
const bool fWriteInUnicode,
|
|
const DbcsWriteRead::ReadMode ReadMode,
|
|
const bool fReadWithUnicode);
|
|
|
|
bool Setup(_In_ unsigned int uiCodePage,
|
|
_In_ bool fIsTrueType,
|
|
_Out_ HANDLE* const phOut,
|
|
_Out_ WORD* const pwAttributes);
|
|
|
|
void SendOutput(const HANDLE hOut,
|
|
_In_ unsigned int const uiCodePage,
|
|
const WriteMode WriteMode,
|
|
const bool fIsUnicode,
|
|
_In_ PCSTR pszTestString,
|
|
const WORD wAttr);
|
|
|
|
void RetrieveOutput(const HANDLE hOut,
|
|
const DbcsWriteRead::ReadMode ReadMode,
|
|
const bool fReadUnicode,
|
|
_Out_writes_(cChars) CHAR_INFO* const rgChars,
|
|
const SHORT cChars);
|
|
|
|
void Verify(_In_reads_(cExpected) CHAR_INFO* const rgExpected,
|
|
const size_t cExpected,
|
|
_In_reads_(cExpected) CHAR_INFO* const rgActual);
|
|
|
|
void PrepExpected(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
const DbcsWriteRead::WriteMode WriteMode,
|
|
const bool fWriteWithUnicode,
|
|
const bool fIsTrueTypeFont,
|
|
const DbcsWriteRead::ReadMode ReadMode,
|
|
const bool fReadWithUnicode,
|
|
_Outptr_result_buffer_(*pcExpected) CHAR_INFO** const ppciExpected,
|
|
_Out_ size_t* const pcExpected);
|
|
|
|
void PrepReadConsoleOutput(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
const DbcsWriteRead::WriteMode WriteMode,
|
|
const bool fWriteWithUnicode,
|
|
const bool fIsTrueTypeFont,
|
|
const bool fReadWithUnicode,
|
|
_Inout_updates_all_(cExpectedNeeded) CHAR_INFO* const rgciExpected,
|
|
const size_t cExpectedNeeded);
|
|
|
|
void PrepReadConsoleOutputCharacter(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
const DbcsWriteRead::WriteMode WriteMode,
|
|
const bool fWriteWithUnicode,
|
|
const bool fIsTrueTypeFont,
|
|
const bool fReadWithUnicode,
|
|
_Inout_updates_all_(cExpectedNeeded) CHAR_INFO* const rgciExpected,
|
|
const size_t cExpectedNeeded);
|
|
|
|
namespace PrepPattern
|
|
{
|
|
// There are 14 different patterns that result from the various combinations of our APIs.
|
|
// These patterns are simply recognized based on the existing v1 console behavior and generated
|
|
// here as a black box test to maintain compatibility based on the variations in API usage.
|
|
// It can be assumed that calling this pattern means that the combinations of APIs used for the test
|
|
// resulted in output that looks like this pattern on the v1 console.
|
|
//
|
|
// All patterns will be documented with their sample before and afters above the comment.
|
|
// We will use *KI* to represent a Japanese Hiragana character that is romanized and
|
|
// no * to represent US ASCII text.
|
|
//
|
|
// We don't store the Hiragana directly in this file because Visual Studio and Git fight over the
|
|
// proper encoding of UTF-8.
|
|
|
|
// 1
|
|
void SpacePaddedDedupeW(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected);
|
|
|
|
// 2
|
|
void SpacePaddedDedupeTruncatedW(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected);
|
|
|
|
// 3
|
|
void NullPaddedDedupeW(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected);
|
|
|
|
// 4
|
|
void DoubledWNegativeOneTrailing(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected);
|
|
|
|
// 5
|
|
void DoubledW(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected);
|
|
|
|
// 6
|
|
void A(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected);
|
|
|
|
// 7
|
|
void AStompsWNegativeOnePatternTruncateSpacePadded(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected);
|
|
|
|
// 8
|
|
void AOnDoubledWNegativeOneTrailing(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected);
|
|
|
|
// 9
|
|
void AOnDoubledW(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected);
|
|
|
|
// 10
|
|
void WNullCoverAChar(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected);
|
|
|
|
// 11
|
|
void WSpaceFill(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected);
|
|
|
|
// 12
|
|
void ACoverAttrSpacePaddedDedupeTruncatedW(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected);
|
|
|
|
// 13
|
|
void SpacePaddedDedupeA(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected);
|
|
|
|
// 14
|
|
void TrueTypeCharANullWithAttrs(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected);
|
|
};
|
|
};
|
|
|
|
class DbcsTests
|
|
{
|
|
BEGIN_TEST_CLASS(DbcsTests)
|
|
TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class")
|
|
END_TEST_CLASS();
|
|
|
|
TEST_METHOD_SETUP(DbcsTestSetup);
|
|
|
|
// This test must come before ones that launch another process as launching another process can tamper with the codepage
|
|
// in ways that this test is not expecting.
|
|
TEST_METHOD(TestMultibyteInputRetrieval);
|
|
|
|
BEGIN_TEST_METHOD(TestDbcsWriteRead)
|
|
TEST_METHOD_PROPERTY(L"Data:uiCodePage", L"{437, 932}")
|
|
TEST_METHOD_PROPERTY(L"Data:fUseTrueTypeFont", L"{true, false}")
|
|
TEST_METHOD_PROPERTY(L"Data:WriteMode", L"{0, 1, 2, 3}")
|
|
TEST_METHOD_PROPERTY(L"Data:fWriteInUnicode", L"{true, false}")
|
|
TEST_METHOD_PROPERTY(L"Data:ReadMode", L"{0, 1}")
|
|
TEST_METHOD_PROPERTY(L"Data:fReadInUnicode", L"{true, false}")
|
|
END_TEST_METHOD()
|
|
|
|
TEST_METHOD(TestDbcsBisect);
|
|
|
|
BEGIN_TEST_METHOD(TestDbcsBisectWriteCellsBeginW)
|
|
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
|
END_TEST_METHOD()
|
|
|
|
BEGIN_TEST_METHOD(TestDbcsBisectWriteCellsEndW)
|
|
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
|
END_TEST_METHOD()
|
|
|
|
BEGIN_TEST_METHOD(TestDbcsBisectWriteCellsBeginA)
|
|
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
|
END_TEST_METHOD()
|
|
|
|
BEGIN_TEST_METHOD(TestDbcsBisectWriteCellsEndA)
|
|
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
|
END_TEST_METHOD()
|
|
|
|
BEGIN_TEST_METHOD(TestDbcsOneByOne)
|
|
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
|
END_TEST_METHOD()
|
|
|
|
BEGIN_TEST_METHOD(TestDbcsTrailLead)
|
|
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
|
END_TEST_METHOD()
|
|
|
|
BEGIN_TEST_METHOD(TestDbcsStdCoutScenario)
|
|
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
|
END_TEST_METHOD()
|
|
};
|
|
|
|
bool DbcsTests::DbcsTestSetup()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool DbcsWriteRead::Setup(_In_ unsigned int uiCodePage,
|
|
_In_ bool fIsTrueType,
|
|
_Out_ HANDLE* const phOut,
|
|
_Out_ WORD* const pwAttributes)
|
|
{
|
|
HANDLE const hOut = GetStdOutputHandle();
|
|
|
|
// Ensure that the console is set into the appropriate codepage for the test
|
|
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(SetConsoleCP(uiCodePage));
|
|
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(SetConsoleOutputCP(uiCodePage));
|
|
|
|
// Now set up the font. Many of these APIs are oddly dependent on font, so set as appropriate.
|
|
CONSOLE_FONT_INFOEX cfiex = { 0 };
|
|
cfiex.cbSize = sizeof(cfiex);
|
|
if (!fIsTrueType)
|
|
{
|
|
// We use Terminal as the raster font name always.
|
|
wcscpy_s(cfiex.FaceName, L"Terminal");
|
|
|
|
// Use default raster font size from Japanese system.
|
|
cfiex.dwFontSize.X = 8;
|
|
cfiex.dwFontSize.Y = 18;
|
|
}
|
|
else
|
|
{
|
|
switch (uiCodePage)
|
|
{
|
|
case JAPANESE_CP:
|
|
wcscpy_s(cfiex.FaceName, L"MS Gothic");
|
|
break;
|
|
case ENGLISH_US_CP:
|
|
wcscpy_s(cfiex.FaceName, L"Consolas");
|
|
break;
|
|
}
|
|
|
|
cfiex.dwFontSize.Y = 16;
|
|
}
|
|
|
|
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(OneCoreDelay::SetCurrentConsoleFontEx(hOut, FALSE, &cfiex));
|
|
|
|
// Ensure that we set the font we expected to set
|
|
CONSOLE_FONT_INFOEX cfiexGet = { 0 };
|
|
cfiexGet.cbSize = sizeof(cfiexGet);
|
|
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(OneCoreDelay::GetCurrentConsoleFontEx(hOut, FALSE, &cfiexGet));
|
|
|
|
if (0 != NoThrowString(cfiex.FaceName).CompareNoCase(cfiexGet.FaceName))
|
|
{
|
|
Log::Comment(L"Could not change font. This system doesn't have the fonts we need to perform this test. Skipping.");
|
|
Log::Result(WEX::Logging::TestResults::Result::Skipped);
|
|
return false;
|
|
}
|
|
|
|
// Retrieve some of the information about the preferences/settings for the console buffer including
|
|
// the size of the buffer and the default colors (attributes) to use.
|
|
CONSOLE_SCREEN_BUFFER_INFOEX sbiex = { 0 };
|
|
sbiex.cbSize = sizeof(sbiex);
|
|
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(GetConsoleScreenBufferInfoEx(hOut, &sbiex));
|
|
|
|
// ensure first line of console is cleared out with spaces so nothing interferes with the text these tests will be writing.
|
|
COORD coordZero = { 0 };
|
|
DWORD dwWritten;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(FillConsoleOutputCharacterW(hOut, L'\x20', sbiex.dwSize.X, coordZero, &dwWritten));
|
|
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(FillConsoleOutputAttribute(hOut, sbiex.wAttributes, sbiex.dwSize.X, coordZero, &dwWritten));
|
|
|
|
// Move the cursor to the 0,0 position into our empty line so the tests can write (important for the CRT tests that specify no location)
|
|
if (!SetConsoleCursorPosition(GetStdOutputHandle(), coordZero))
|
|
{
|
|
VERIFY_FAIL(L"Failed to set cursor position");
|
|
}
|
|
|
|
// Give back the output handle and the default attributes so tests can verify attributes didn't change on roundtrip
|
|
*phOut = hOut;
|
|
*pwAttributes = sbiex.wAttributes;
|
|
|
|
return true;
|
|
}
|
|
|
|
void DbcsWriteRead::SendOutput(const HANDLE hOut,
|
|
_In_ unsigned int const uiCodePage,
|
|
const DbcsWriteRead::WriteMode WriteMode,
|
|
const bool fIsUnicode,
|
|
_In_ PCSTR pszTestString,
|
|
const WORD wAttr)
|
|
{
|
|
// DBCS is very dependent on knowing the byte length in the original codepage of the input text.
|
|
// Save off the original length of the string so we know what its A length was.
|
|
SHORT const cTestString = (SHORT)strlen(pszTestString);
|
|
|
|
// If we're in Unicode mode, we will need to translate the test string to Unicode before passing into the console
|
|
PWSTR pwszTestString = nullptr;
|
|
if (fIsUnicode)
|
|
{
|
|
// Use double-call pattern to find space to allocate, allocate it, then convert.
|
|
int const icchNeeded = MultiByteToWideChar(uiCodePage, 0, pszTestString, -1, nullptr, 0);
|
|
|
|
pwszTestString = new WCHAR[icchNeeded];
|
|
VERIFY_IS_NOT_NULL(pwszTestString);
|
|
|
|
int const iRes = MultiByteToWideChar(uiCodePage, 0, pszTestString, -1, pwszTestString, icchNeeded);
|
|
CheckLastErrorZeroFail(iRes, L"MultiByteToWideChar");
|
|
}
|
|
|
|
// Calculate the number of cells/characters/calls we will need to fill with our input depending on the mode.
|
|
SHORT cChars = 0;
|
|
if (fIsUnicode)
|
|
{
|
|
cChars = (SHORT)wcslen(pwszTestString);
|
|
}
|
|
else
|
|
{
|
|
cChars = cTestString;
|
|
}
|
|
|
|
// These parameters will be used to print out the written rectangle if we used the console APIs (not the CRT APIs)
|
|
// This information will be stored and printed out at the very end after we move the cursor off of the text we just printed.
|
|
// The cursor auto-moves for CRT, but we have to manually move it for some of the Console APIs.
|
|
bool fUseRectWritten = false;
|
|
SMALL_RECT srWrittenExpected = { 0 };
|
|
SMALL_RECT srWritten = { 0 };
|
|
|
|
bool fUseDwordWritten = false;
|
|
DWORD dwWritten = 0;
|
|
|
|
switch (WriteMode)
|
|
{
|
|
case DbcsWriteRead::WriteMode::CrtWrite:
|
|
{
|
|
// Align the CRT's mode with the text we're about to write.
|
|
// If you call a W function on the CRT while the mode is still set to A,
|
|
// the CRT will helpfully back-convert your text from W to A before sending it to the driver.
|
|
if (fIsUnicode)
|
|
{
|
|
_setmode(_fileno(stdout), _O_WTEXT);
|
|
}
|
|
else
|
|
{
|
|
_setmode(_fileno(stdout), _O_TEXT);
|
|
}
|
|
|
|
// Write each character in the string individually out through the CRT
|
|
if (fIsUnicode)
|
|
{
|
|
for (SHORT i = 0; i < cChars; i++)
|
|
{
|
|
putwchar(pwszTestString[i]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (SHORT i = 0; i < cChars; i++)
|
|
{
|
|
putchar(pszTestString[i]);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case DbcsWriteRead::WriteMode::WriteConsoleOutputFunc:
|
|
{
|
|
// If we're going to be using WriteConsoleOutput, we need to create up a nice
|
|
// CHAR_INFO buffer to pass into the method containing the string and possibly attributes
|
|
CHAR_INFO* rgChars = new CHAR_INFO[cChars];
|
|
VERIFY_IS_NOT_NULL(rgChars);
|
|
|
|
for (SHORT i = 0; i < cChars; i++)
|
|
{
|
|
rgChars[i].Attributes = wAttr;
|
|
|
|
if (fIsUnicode)
|
|
{
|
|
rgChars[i].Char.UnicodeChar = pwszTestString[i];
|
|
}
|
|
else
|
|
{
|
|
// Ensure the top half of the union is filled with 0 for comparison purposes later.
|
|
rgChars[i].Char.UnicodeChar = 0;
|
|
rgChars[i].Char.AsciiChar = pszTestString[i];
|
|
}
|
|
}
|
|
|
|
// This is the stated size of the buffer we're passing.
|
|
// This console API can treat the buffer as a 2D array. We're only doing 1 dimension so the Y is 1 and the X is the number of CHAR_INFO characters.
|
|
COORD coordBufferSize = { 0 };
|
|
coordBufferSize.Y = 1;
|
|
coordBufferSize.X = cChars;
|
|
|
|
// We want to write to the coordinate 0,0 of the buffer. The test setup function has blanked out that line.
|
|
COORD coordBufferTarget = { 0 };
|
|
|
|
// inclusive rectangle (bottom and right are INSIDE the read area. usually are exclusive.)
|
|
SMALL_RECT srWriteRegion = { 0 };
|
|
|
|
// Since we could have full-width characters, we have to "allow" the console to write up to the entire A string length (up to double the W length)
|
|
srWriteRegion.Right = cTestString - 1;
|
|
|
|
// Save the expected written rectangle for comparison after the call
|
|
srWrittenExpected = { 0 };
|
|
srWrittenExpected.Right = cChars - 1; // we expect that the written report will be the number of characters inserted, not the size of buffer consumed
|
|
|
|
// NOTE: Don't VERIFY these calls or we will overwrite the text in the buffer with the log message.
|
|
if (fIsUnicode)
|
|
{
|
|
WriteConsoleOutputW(hOut, rgChars, coordBufferSize, coordBufferTarget, &srWriteRegion);
|
|
}
|
|
else
|
|
{
|
|
WriteConsoleOutputA(hOut, rgChars, coordBufferSize, coordBufferTarget, &srWriteRegion);
|
|
}
|
|
|
|
// Save write region so we can print it out after we move the cursor out of the way
|
|
srWritten = srWriteRegion;
|
|
fUseRectWritten = true;
|
|
|
|
delete[] rgChars;
|
|
break;
|
|
}
|
|
case DbcsWriteRead::WriteMode::WriteConsoleOutputCharacterFunc:
|
|
{
|
|
COORD coordBufferTarget = { 0 };
|
|
|
|
if (fIsUnicode)
|
|
{
|
|
WriteConsoleOutputCharacterW(hOut, pwszTestString, cChars, coordBufferTarget, &dwWritten);
|
|
}
|
|
else
|
|
{
|
|
WriteConsoleOutputCharacterA(hOut, pszTestString, cChars, coordBufferTarget, &dwWritten);
|
|
}
|
|
|
|
fUseDwordWritten = true;
|
|
break;
|
|
}
|
|
case DbcsWriteRead::WriteMode::WriteConsoleFunc:
|
|
{
|
|
if (fIsUnicode)
|
|
{
|
|
WriteConsoleW(hOut, pwszTestString, cChars, &dwWritten, nullptr);
|
|
}
|
|
else
|
|
{
|
|
WriteConsoleA(hOut, pszTestString, cChars, &dwWritten, nullptr);
|
|
}
|
|
|
|
fUseDwordWritten = true;
|
|
break;
|
|
}
|
|
default:
|
|
VERIFY_FAIL(L"Unsupported write mode.");
|
|
}
|
|
|
|
// Free memory if appropriate (if we had to convert A to W)
|
|
if (nullptr != pwszTestString)
|
|
{
|
|
delete[] pwszTestString;
|
|
}
|
|
|
|
// Move the cursor down a line in case log info prints out.
|
|
COORD coordSetCursor = { 0 };
|
|
coordSetCursor.Y = 1;
|
|
SetConsoleCursorPosition(hOut, coordSetCursor);
|
|
|
|
// If we had log info to print, print it now that it's safe (cursor out of the test data we printed)
|
|
// This only matters for when the test is run in the same window as the runner and could print log information.
|
|
if (fUseRectWritten)
|
|
{
|
|
Log::Comment(NoThrowString().Format(L"WriteRegion T: %d L: %d B: %d R: %d", srWritten.Top, srWritten.Left, srWritten.Bottom, srWritten.Right));
|
|
VERIFY_ARE_EQUAL(srWrittenExpected, srWritten);
|
|
}
|
|
else if (fUseDwordWritten)
|
|
{
|
|
Log::Comment(NoThrowString().Format(L"Chars Written: %d", dwWritten));
|
|
VERIFY_ARE_EQUAL((DWORD)cChars, dwWritten);
|
|
}
|
|
}
|
|
|
|
// 3
|
|
// From Input String: "Q(Hiragana I)(Hiragana KA)(Hiragana NA)ZYXWVUT(Hiragana NI)
|
|
// With Default Attribute 0x7 (before writing) and Applied Attribute 0x29 (written with text)
|
|
// ...
|
|
// Receive Output Table:
|
|
// attr | wchar (char) | symbol
|
|
// ------------------------------------
|
|
// 0x029 | 0x0051 (0x51) | Q
|
|
// 0x029 | 0x3044 (0x44) | Hiragana I
|
|
// 0x029 | 0x304B (0x4B) | Hiragana KA
|
|
// 0x029 | 0x306A (0x6A) | Hiragana NA
|
|
// 0x029 | 0x005A (0x5A) | Z
|
|
// 0x029 | 0x0059 (0x59) | Y
|
|
// 0x029 | 0x0058 (0x58) | X
|
|
// 0x029 | 0x0057 (0x57) | W
|
|
// 0x029 | 0x0056 (0x56) | V
|
|
// 0x029 | 0x0055 (0x55) | U
|
|
// 0x029 | 0x0054 (0x54) | T
|
|
// 0x029 | 0x306B (0x6B) | Hiragana NI
|
|
// 0x000 | 0x0000 (0x00) | <null>
|
|
// 0x000 | 0x0000 (0x00) | <null>
|
|
// 0x000 | 0x0000 (0x00) | <null>
|
|
// 0x000 | 0x0000 (0x00) | <null>
|
|
// ...
|
|
// "Null Padded" means any unused data in the buffer will be filled with null and null attribute.
|
|
// "Dedupe" means that any full-width characters in the buffer (despite being stored doubled inside the buffer)
|
|
// will be returned as single copies.
|
|
// "W" means that we intend Unicode data to be browsed in the resulting struct (even though wchar and char are unioned.)
|
|
void DbcsWriteRead::PrepPattern::NullPaddedDedupeW(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD /*wAttrOriginal*/,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected)
|
|
{
|
|
Log::Comment(L"Pattern 3");
|
|
int const iwchNeeded = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, nullptr, 0);
|
|
PWSTR pwszTestData = new wchar_t[iwchNeeded];
|
|
VERIFY_IS_NOT_NULL(pwszTestData);
|
|
int const iSuccess = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, pwszTestData, iwchNeeded);
|
|
CheckLastErrorZeroFail(iSuccess, L"MultiByteToWideChar");
|
|
|
|
size_t const cWideTestData = wcslen(pwszTestData);
|
|
VERIFY_IS_GREATER_THAN_OR_EQUAL(cExpected, cWideTestData);
|
|
|
|
for (size_t i = 0; i < cWideTestData; i++)
|
|
{
|
|
CHAR_INFO* const pciCurrent = &pciExpected[i];
|
|
wchar_t const wch = pwszTestData[i];
|
|
|
|
pciCurrent->Attributes = wAttrWritten;
|
|
pciCurrent->Char.UnicodeChar = wch;
|
|
}
|
|
|
|
delete[] pwszTestData;
|
|
}
|
|
|
|
// 1
|
|
// From Input String: "Q(Hiragana I)(Hiragana KA)(Hiragana NA)ZYXWVUT(Hiragana NI)
|
|
// With Default Attribute 0x7 (before writing) and Applied Attribute 0x29 (written with text)
|
|
// ...
|
|
// Receive Output Table:
|
|
// attr | wchar (char) | symbol
|
|
// ------------------------------------
|
|
// 0x029 | 0x0051 (0x51) | Q
|
|
// 0x029 | 0x3044 (0x44) | Hiragana I
|
|
// 0x029 | 0x304B (0x4B) | Hiragana KA
|
|
// 0x029 | 0x306A (0x6A) | Hiragana NA
|
|
// 0x029 | 0x005A (0x5A) | Z
|
|
// 0x029 | 0x0059 (0x59) | Y
|
|
// 0x029 | 0x0058 (0x58) | X
|
|
// 0x029 | 0x0057 (0x57) | W
|
|
// 0x029 | 0x0056 (0x56) | V
|
|
// 0x029 | 0x0055 (0x55) | U
|
|
// 0x029 | 0x0054 (0x54) | T
|
|
// 0x029 | 0x306B (0x6B) | Hiragana NI
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// ...
|
|
// "Space Padded" means any unused data in the buffer will be filled with spaces and the default attribute.
|
|
// "Dedupe" means that any full-width characters in the buffer (despite being stored doubled inside the buffer)
|
|
// will be returned as single copies.
|
|
// "W" means that we intend Unicode data to be browsed in the resulting struct (even though wchar and char are unioned.)
|
|
void DbcsWriteRead::PrepPattern::SpacePaddedDedupeW(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected)
|
|
{
|
|
Log::Comment(L"Pattern 1");
|
|
DbcsWriteRead::PrepPattern::NullPaddedDedupeW(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, pciExpected, cExpected);
|
|
|
|
for (size_t i = 0; i < cExpected; i++)
|
|
{
|
|
CHAR_INFO* const pciCurrent = &pciExpected[i];
|
|
|
|
if (0 == pciCurrent->Attributes && 0 == pciCurrent->Char.UnicodeChar)
|
|
{
|
|
pciCurrent->Attributes = wAttrOriginal;
|
|
pciCurrent->Char.UnicodeChar = L'\x20';
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2
|
|
// From Input String: "Q(Hiragana I)(Hiragana KA)(Hiragana NA)ZYXWVUT(Hiragana NI)
|
|
// With Default Attribute 0x7 (before writing) and Applied Attribute 0x29 (written with text)
|
|
// ...
|
|
// Receive Output Table:
|
|
// attr | wchar (char) | symbol
|
|
// ------------------------------------
|
|
// 0x029 | 0x0051 (0x51) | Q
|
|
// 0x029 | 0x3044 (0x44) | Hiragana I
|
|
// 0x029 | 0x304B (0x4B) | Hiragana KA
|
|
// 0x029 | 0x306A (0x6A) | Hiragana NA
|
|
// 0x029 | 0x005A (0x5A) | Z
|
|
// 0x029 | 0x0059 (0x59) | Y
|
|
// 0x029 | 0x0058 (0x58) | X
|
|
// 0x029 | 0x0057 (0x57) | W
|
|
// 0x029 | 0x0056 (0x56) | V
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x000 | 0x0000 (0x00) | <null>
|
|
// 0x000 | 0x0000 (0x00) | <null>
|
|
// 0x000 | 0x0000 (0x00) | <null>
|
|
// ...
|
|
// "Space Padded" means most of the unused data in the buffer will be filled with spaces and the default attribute.
|
|
// "Dedupe" means that any full-width characters in the buffer (despite being stored doubled inside the buffer)
|
|
// will be returned as single copies.
|
|
// "W" means that we intend Unicode data to be browsed in the resulting struct (even though wchar and char are unioned.)
|
|
// "Truncated" means that this pattern trims off some of the end of the buffer with NULLs.
|
|
void DbcsWriteRead::PrepPattern::SpacePaddedDedupeTruncatedW(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected)
|
|
{
|
|
Log::Comment(L"Pattern 2");
|
|
|
|
int const iwchNeeded = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, nullptr, 0);
|
|
PWSTR pwszTestData = new wchar_t[iwchNeeded];
|
|
VERIFY_IS_NOT_NULL(pwszTestData);
|
|
int const iSuccess = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, pwszTestData, iwchNeeded);
|
|
CheckLastErrorZeroFail(iSuccess, L"MultiByteToWideChar");
|
|
|
|
size_t const cWideData = wcslen(pwszTestData);
|
|
|
|
// The maximum number of columns the console will consume is the number of wide characters there are in the string.
|
|
// This is whether or not the characters themselves are halfwidth or fullwidth (1 col or 2 col respectively.)
|
|
// This means that for 4 wide characters that are halfwidth (1 col), the console will copy out all 4 of them.
|
|
// For 4 wide characters that are fullwidth (2 col each), the console will copy out 2 of them (because it will count each fullwidth as 2 when filling)
|
|
// For a mixed string that is something like half, full, half (4 columns, 3 wchars), we will receive half, full (3 columns worth) and truncate the last half.
|
|
|
|
size_t const cMaxColumns = cWideData;
|
|
size_t iColumnsConsumed = 0;
|
|
|
|
size_t iNarrow = 0;
|
|
size_t iWide = 0;
|
|
size_t iExpected = 0;
|
|
|
|
size_t iNulls = 0;
|
|
|
|
while (iColumnsConsumed < cMaxColumns)
|
|
{
|
|
CHAR_INFO* const pciCurrent = &pciExpected[iExpected];
|
|
char const chCurrent = pszTestData[iWide];
|
|
wchar_t const wchCurrent = pwszTestData[iWide];
|
|
|
|
pciCurrent->Attributes = wAttrWritten;
|
|
pciCurrent->Char.UnicodeChar = wchCurrent;
|
|
|
|
if (IsDBCSLeadByteEx(uiCodePage, chCurrent))
|
|
{
|
|
iColumnsConsumed += 2;
|
|
iNarrow += 2;
|
|
iNulls++;
|
|
}
|
|
else
|
|
{
|
|
iColumnsConsumed++;
|
|
iNarrow++;
|
|
}
|
|
|
|
iWide++;
|
|
iExpected++;
|
|
}
|
|
|
|
// Fill remaining with spaces and original attribute
|
|
while (iExpected < cExpected - iNulls)
|
|
{
|
|
CHAR_INFO* const pciCurrent = &pciExpected[iExpected];
|
|
pciCurrent->Attributes = wAttrOriginal;
|
|
pciCurrent->Char.UnicodeChar = L'\x20';
|
|
|
|
iExpected++;
|
|
}
|
|
|
|
delete[] pwszTestData;
|
|
}
|
|
|
|
// 13
|
|
// From Input String: "Q(Hiragana I)(Hiragana KA)(Hiragana NA)ZYXWVUT(Hiragana NI)
|
|
// With Default Attribute 0x7 (before writing) and Applied Attribute 0x29 (written with text)
|
|
// ...
|
|
// Receive Output Table:
|
|
// attr | wchar (char) | symbol
|
|
// ------------------------------------
|
|
// 0x029 | 0x0051 (0x51) | Q
|
|
// 0x129 | 0x0082 (0x82) | Hiragana I Shift-JIS Codepage 932 Lead Byte
|
|
// 0x229 | 0x00A2 (0xA2) | Hiragana I Shift-JIS Codepage 932 Trail Byte
|
|
// 0x129 | 0x0082 (0x82) | Hiragana KA Shift-JIS Codepage 932 Lead Byte
|
|
// 0x229 | 0x00A9 (0xA9) | Hiragana KA Shift-JIS Codepage 932 Trail Byte
|
|
// 0x129 | 0x0082 (0x82) | Hiragana NA Shift-JIS Codepage 932 Lead Byte
|
|
// 0x229 | 0x00C8 (0xC8) | Hiragana NA Shift-JIS Codepage 932 Trail Byte
|
|
// 0x029 | 0x005A (0x5A) | Z
|
|
// 0x029 | 0x0059 (0x59) | Y
|
|
// 0x029 | 0x0058 (0x58) | X
|
|
// 0x029 | 0x0057 (0x57) | W
|
|
// 0x029 | 0x0056 (0x56) | V
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// ...
|
|
// "Space Padded" means most of the unused data in the buffer will be filled with spaces and the default attribute.
|
|
// "Dedupe" means that any full-width characters in the buffer (despite being stored doubled inside the buffer)
|
|
// will be returned as single copies.
|
|
// "A" means that we intend in-codepage (char) data to be browsed in the resulting struct (even though wchar and char are unioned.)
|
|
void DbcsWriteRead::PrepPattern::SpacePaddedDedupeA(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected)
|
|
{
|
|
Log::Comment(L"Pattern 13");
|
|
|
|
int const iwchNeeded = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, nullptr, 0);
|
|
PWSTR pwszTestData = new wchar_t[iwchNeeded];
|
|
VERIFY_IS_NOT_NULL(pwszTestData);
|
|
int const iSuccess = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, pwszTestData, iwchNeeded);
|
|
CheckLastErrorZeroFail(iSuccess, L"MultiByteToWideChar");
|
|
|
|
size_t const cWideData = wcslen(pwszTestData);
|
|
|
|
// The maximum number of columns the console will consume is the number of wide characters there are in the string.
|
|
// This is whether or not the characters themselves are halfwidth or fullwidth (1 col or 2 col respectively.)
|
|
// This means that for 4 wide characters that are halfwidth (1 col), the console will copy out all 4 of them.
|
|
// For 4 wide characters that are fullwidth (2 col each), the console will copy out 2 of them (because it will count each fullwidth as 2 when filling)
|
|
// For a mixed string that is something like half, full, half (4 columns, 3 wchars), we will receive half, full (3 columns worth) and truncate the last half.
|
|
|
|
size_t const cMaxColumns = cWideData;
|
|
|
|
bool fIsNextTrailing = false;
|
|
size_t i = 0;
|
|
for (; i < cMaxColumns; i++)
|
|
{
|
|
CHAR_INFO* const pciCurrent = &pciExpected[i];
|
|
char const chCurrent = pszTestData[i];
|
|
|
|
pciCurrent->Attributes = wAttrWritten;
|
|
pciCurrent->Char.AsciiChar = chCurrent;
|
|
|
|
if (IsDBCSLeadByteEx(uiCodePage, chCurrent))
|
|
{
|
|
pciCurrent->Attributes |= COMMON_LVB_LEADING_BYTE;
|
|
fIsNextTrailing = true;
|
|
}
|
|
else if (fIsNextTrailing)
|
|
{
|
|
pciCurrent->Attributes |= COMMON_LVB_TRAILING_BYTE;
|
|
fIsNextTrailing = false;
|
|
}
|
|
}
|
|
|
|
// Fill remaining with spaces and original attribute
|
|
while (i < cExpected)
|
|
{
|
|
CHAR_INFO* const pciCurrent = &pciExpected[i];
|
|
pciCurrent->Attributes = wAttrOriginal;
|
|
pciCurrent->Char.UnicodeChar = L'\x20';
|
|
|
|
i++;
|
|
}
|
|
|
|
delete[] pwszTestData;
|
|
}
|
|
|
|
// 5
|
|
// From Input String: "Q(Hiragana I)(Hiragana KA)(Hiragana NA)ZYXWVUT(Hiragana NI)
|
|
// With Default Attribute 0x7 (before writing) and Applied Attribute 0x29 (written with text)
|
|
// ...
|
|
// Receive Output Table:
|
|
// attr | wchar (char) | symbol
|
|
// ------------------------------------
|
|
// 0x029 | 0x0051 (0x51) | Q
|
|
// 0x129 | 0x3044 (0x44) | Hiragana I
|
|
// 0x229 | 0x3044 (0x44) | Hiragana I
|
|
// 0x129 | 0x304B (0x4B) | Hiragana KA
|
|
// 0x229 | 0x304B (0x4B) | Hiragana KA
|
|
// 0x129 | 0x306A (0x6A) | Hiragana NA
|
|
// 0x229 | 0x306A (0x6A) | Hiragana NA
|
|
// 0x029 | 0x005A (0x5A) | Z
|
|
// 0x029 | 0x0059 (0x59) | Y
|
|
// 0x029 | 0x0058 (0x58) | X
|
|
// 0x029 | 0x0057 (0x57) | W
|
|
// 0x029 | 0x0056 (0x56) | V
|
|
// 0x029 | 0x0055 (0x55) | U
|
|
// 0x029 | 0x0054 (0x54) | T
|
|
// 0x129 | 0x306B (0x6B) | Hiragana NI
|
|
// 0x229 | 0x306B (0x6B) | Hiragana NI
|
|
// ...
|
|
// "Doubled" means that any full-width characters in the buffer are returned twice with a leading and trailing byte marker.
|
|
// "W" means that we intend Unicode data to be browsed in the resulting struct (even though wchar and char are unioned.)
|
|
void DbcsWriteRead::PrepPattern::DoubledW(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD /*wAttrOriginal*/,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected)
|
|
{
|
|
Log::Comment(L"Pattern 5");
|
|
size_t const cTestData = strlen(pszTestData);
|
|
VERIFY_IS_GREATER_THAN_OR_EQUAL(cExpected, cTestData);
|
|
|
|
int const iwchNeeded = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, nullptr, 0);
|
|
PWSTR pwszTestData = new wchar_t[iwchNeeded];
|
|
VERIFY_IS_NOT_NULL(pwszTestData);
|
|
int const iSuccess = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, pwszTestData, iwchNeeded);
|
|
CheckLastErrorZeroFail(iSuccess, L"MultiByteToWideChar");
|
|
|
|
size_t iWide = 0;
|
|
wchar_t wchRepeat = L'\0';
|
|
bool fIsNextTrailing = false;
|
|
for (size_t i = 0; i < cTestData; i++)
|
|
{
|
|
CHAR_INFO* const pciCurrent = &pciExpected[i];
|
|
char const chTest = pszTestData[i];
|
|
wchar_t const wchCopy = pwszTestData[iWide];
|
|
|
|
pciCurrent->Attributes = wAttrWritten;
|
|
|
|
if (IsDBCSLeadByteEx(uiCodePage, chTest))
|
|
{
|
|
pciCurrent->Char.UnicodeChar = wchCopy;
|
|
iWide++;
|
|
|
|
pciCurrent->Attributes |= COMMON_LVB_LEADING_BYTE;
|
|
|
|
wchRepeat = wchCopy;
|
|
fIsNextTrailing = true;
|
|
}
|
|
else if (fIsNextTrailing)
|
|
{
|
|
pciCurrent->Char.UnicodeChar = wchRepeat;
|
|
|
|
pciCurrent->Attributes |= COMMON_LVB_TRAILING_BYTE;
|
|
|
|
fIsNextTrailing = false;
|
|
}
|
|
else
|
|
{
|
|
pciCurrent->Char.UnicodeChar = wchCopy;
|
|
iWide++;
|
|
}
|
|
}
|
|
|
|
delete[] pwszTestData;
|
|
}
|
|
|
|
// 4
|
|
// From Input String: "Q(Hiragana I)(Hiragana KA)(Hiragana NA)ZYXWVUT(Hiragana NI)
|
|
// With Default Attribute 0x7 (before writing) and Applied Attribute 0x29 (written with text)
|
|
// ...
|
|
// Receive Output Table:
|
|
// attr | wchar (char) | symbol
|
|
// ------------------------------------
|
|
// 0x029 | 0x0051 (0x51) | Q
|
|
// 0x129 | 0x3044 (0x44) | Hiragana I
|
|
// 0x229 | 0xFFFF (0xFF) | Invalid Unicode Character
|
|
// 0x129 | 0x304B (0x4B) | Hiragana KA
|
|
// 0x229 | 0xFFFF (0xFF) | Invalid Unicode Character
|
|
// 0x129 | 0x306A (0x6A) | Hiragana NA
|
|
// 0x229 | 0xFFFF (0xFF) | Invalid Unicode Character
|
|
// 0x029 | 0x005A (0x5A) | Z
|
|
// 0x029 | 0x0059 (0x59) | Y
|
|
// 0x029 | 0x0058 (0x58) | X
|
|
// 0x029 | 0x0057 (0x57) | W
|
|
// 0x029 | 0x0056 (0x56) | V
|
|
// 0x029 | 0x0055 (0x55) | U
|
|
// 0x029 | 0x0054 (0x54) | T
|
|
// 0x129 | 0x306B (0x6B) | Hiragana NI
|
|
// 0x229 | 0xFFFF (0xFF) | Invalid Unicode Character
|
|
// ...
|
|
// "Doubled" means that any full-width characters in the buffer are returned twice with a leading and trailing byte marker.
|
|
// "W" means that we intend Unicode data to be browsed in the resulting struct (even though wchar and char are unioned.)
|
|
// "NegativeOneTrailing" means that all trailing bytes have their character replaced with the value -1 or 0xFFFF
|
|
void DbcsWriteRead::PrepPattern::DoubledWNegativeOneTrailing(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected)
|
|
{
|
|
Log::Comment(L"Pattern 4");
|
|
DbcsWriteRead::PrepPattern::DoubledW(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, pciExpected, cExpected);
|
|
|
|
for (size_t i = 0; i < cExpected; i++)
|
|
{
|
|
CHAR_INFO* pciCurrent = &pciExpected[i];
|
|
|
|
if (WI_IsFlagSet(pciCurrent->Attributes, COMMON_LVB_TRAILING_BYTE))
|
|
{
|
|
pciCurrent->Char.UnicodeChar = 0xFFFF;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 7
|
|
// From Input String: "Q(Hiragana I)(Hiragana KA)(Hiragana NA)ZYXWVUT(Hiragana NI)
|
|
// With Default Attribute 0x7 (before writing) and Applied Attribute 0x29 (written with text)
|
|
// ...
|
|
// Receive Output Table:
|
|
// attr | wchar (char) | symbol
|
|
// ------------------------------------
|
|
// 0x029 | 0x0051 (0x51) | Q
|
|
// 0x129 | 0x3082 (0x82) | Hiragana I Unicode 0x3044 with the lower byte covered by Shift-JIS Codepage 932 Lead Byte 0x82.
|
|
// 0x229 | 0xFFA2 (0xA2) | Invalid Unicode Character 0xFFFF with the lower byte covered by Shift-JIS Codepage 932 Trail Byte 0xA2
|
|
// 0x129 | 0x3082 (0x82) | Hiragana KA Unicode 0x304B with the lower byte covered by Shift-JIS Codepage 932 Lead Byte 0x82.
|
|
// 0x229 | 0xFFA9 (0xA9) | Invalid Unicode Character 0xFFFF with the lower byte covered by Shift-JIS Codepage 932 Trail Byte 0xA9
|
|
// 0x129 | 0x3082 (0x82) | Hiragana NA 0x306A with the lower byte covered by Shift-JIS Codepage 932 Lead Byte 0x82.
|
|
// 0x229 | 0xFFC8 (0xC8) | Invalid Unicode Character 0xFFFF with the lower byte covered by Shift-JIS Codepage 932 Trail Byte 0xC8
|
|
// 0x029 | 0x005A (0x5A) | Z
|
|
// 0x029 | 0x0059 (0x59) | Y
|
|
// 0x029 | 0x0058 (0x58) | X
|
|
// 0x029 | 0x0057 (0x57) | W
|
|
// 0x029 | 0x0056 (0x56) | V
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// ...
|
|
// "AStompsW" means that the Unicode characters were fit into the result buffer first, then the Multibyte conversion
|
|
// was written over the top of the lower byte. This makes an invalid Unicode character, but can be understood
|
|
// as in-codepage from the char portion of the union.
|
|
// "NegativeOnePattern" means that every trailing byte started as -1 or 0xFFFF
|
|
// "TruncateSpacePadded" means that we only allowed ourselves to return as many characters as is in the unicode length
|
|
// of the string and then filled the rest of the buffer after that with spaces.
|
|
void DbcsWriteRead::PrepPattern::AStompsWNegativeOnePatternTruncateSpacePadded(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected)
|
|
{
|
|
Log::Comment(L"Pattern 7");
|
|
DbcsWriteRead::PrepPattern::DoubledWNegativeOneTrailing(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, pciExpected, cExpected);
|
|
|
|
// Stomp all A portions of the structure from the existing pattern with the A characters
|
|
size_t const cTestData = strlen(pszTestData);
|
|
for (size_t i = 0; i < cTestData; i++)
|
|
{
|
|
CHAR_INFO* const pciCurrent = &pciExpected[i];
|
|
pciCurrent->Char.AsciiChar = pszTestData[i];
|
|
}
|
|
|
|
// Now truncate down and space fill the space based on the max column count.
|
|
int const iwchNeeded = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, nullptr, 0);
|
|
PWSTR pwszTestData = new wchar_t[iwchNeeded];
|
|
VERIFY_IS_NOT_NULL(pwszTestData);
|
|
int const iSuccess = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, pwszTestData, iwchNeeded);
|
|
CheckLastErrorZeroFail(iSuccess, L"MultiByteToWideChar");
|
|
|
|
size_t const cWideData = wcslen(pwszTestData);
|
|
|
|
// The maximum number of columns the console will consume is the number of wide characters there are in the string.
|
|
// This is whether or not the characters themselves are halfwidth or fullwidth (1 col or 2 col respectively.)
|
|
// This means that for 4 wide characters that are halfwidth (1 col), the console will copy out all 4 of them.
|
|
// For 4 wide characters that are fullwidth (2 col each), the console will copy out 2 of them (because it will count each fullwidth as 2 when filling)
|
|
// For a mixed string that is something like half, full, half (4 columns, 3 wchars), we will receive half, full (3 columns worth) and truncate the last half.
|
|
|
|
size_t const cMaxColumns = cWideData;
|
|
|
|
for (size_t i = cMaxColumns; i < cExpected; i++)
|
|
{
|
|
CHAR_INFO* const pciCurrent = &pciExpected[i];
|
|
pciCurrent->Char.UnicodeChar = L'\x20';
|
|
pciCurrent->Attributes = wAttrOriginal;
|
|
}
|
|
|
|
delete[] pwszTestData;
|
|
}
|
|
|
|
// 6
|
|
// From Input String: "Q(Hiragana I)(Hiragana KA)(Hiragana NA)ZYXWVUT(Hiragana NI)
|
|
// With Default Attribute 0x7 (before writing) and Applied Attribute 0x29 (written with text)
|
|
// ...
|
|
// Receive Output Table:
|
|
// attr | wchar (char) | symbol
|
|
// ------------------------------------
|
|
// 0x029 | 0x0051 (0x51) | Q
|
|
// 0x129 | 0x0082 (0x82) | Hiragana I Shift-JIS Codepage 932 Lead Byte
|
|
// 0x229 | 0x00A2 (0xA2) | Hiragana I Shift-JIS Codepage 932 Trail Byte
|
|
// 0x129 | 0x0082 (0x82) | Hiragana KA Shift-JIS Codepage 932 Lead Byte
|
|
// 0x229 | 0x00A9 (0xA9) | Hiragana KA Shift-JIS Codepage 932 Trail Byte
|
|
// 0x129 | 0x0082 (0x82) | Hiragana NA Shift-JIS Codepage 932 Lead Byte
|
|
// 0x229 | 0x00C8 (0xC8) | Hiragana NA Shift-JIS Codepage 932 Trail Byte
|
|
// 0x029 | 0x005A (0x5A) | Z
|
|
// 0x029 | 0x0059 (0x59) | Y
|
|
// 0x029 | 0x0058 (0x58) | X
|
|
// 0x029 | 0x0057 (0x57) | W
|
|
// 0x029 | 0x0056 (0x56) | V
|
|
// 0x029 | 0x0055 (0x55) | U
|
|
// 0x029 | 0x0054 (0x54) | T
|
|
// 0x129 | 0x0082 (0x82) | Hiragana NI Shift-JIS Codepage 932 Lead Byte
|
|
// 0x229 | 0x00C9 (0xC9) | Hiragana NI Shift-JIS Codepage 932 Trail Byte
|
|
// ...
|
|
// "A" means that we intend in-codepage (char) data to be browsed in the resulting struct.
|
|
// This one returns pretty much exactly as expected.
|
|
void DbcsWriteRead::PrepPattern::A(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD /*wAttrOriginal*/,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected)
|
|
{
|
|
Log::Comment(L"Pattern 6");
|
|
size_t const cTestData = strlen(pszTestData);
|
|
VERIFY_IS_GREATER_THAN_OR_EQUAL(cExpected, cTestData);
|
|
|
|
bool fIsNextTrailing = false;
|
|
for (size_t i = 0; i < cTestData; i++)
|
|
{
|
|
CHAR_INFO* const pciCurrent = &pciExpected[i];
|
|
char const ch = pszTestData[i];
|
|
|
|
pciCurrent->Attributes = wAttrWritten;
|
|
pciCurrent->Char.AsciiChar = ch;
|
|
|
|
if (IsDBCSLeadByteEx(uiCodePage, ch))
|
|
{
|
|
pciCurrent->Attributes |= COMMON_LVB_LEADING_BYTE;
|
|
fIsNextTrailing = true;
|
|
}
|
|
else if (fIsNextTrailing)
|
|
{
|
|
pciCurrent->Attributes |= COMMON_LVB_TRAILING_BYTE;
|
|
fIsNextTrailing = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 10
|
|
// From Input String: "Q(Hiragana I)(Hiragana KA)(Hiragana NA)ZYXWVUT(Hiragana NI)
|
|
// With Default Attribute 0x7 (before writing) and Applied Attribute 0x29 (written with text)
|
|
// ...
|
|
// Receive Output Table:
|
|
// attr | wchar (char) | symbol
|
|
// ------------------------------------
|
|
// 0x029 | 0x0051 (0x51) | Q
|
|
// 0x129 | 0x3044 (0x44) | Hiragana I
|
|
// 0x229 | 0x304B (0x4B) | Hiragana KA
|
|
// 0x129 | 0x306A (0x6A) | Hiragana NA
|
|
// 0x229 | 0x005A (0x5A) | Z
|
|
// 0x129 | 0x0059 (0x59) | Y
|
|
// 0x229 | 0x0058 (0x58) | X
|
|
// 0x029 | 0x0057 (0x57) | W
|
|
// 0x029 | 0x0056 (0x56) | V
|
|
// 0x029 | 0x0055 (0x55) | U
|
|
// 0x029 | 0x0054 (0x54) | T
|
|
// 0x029 | 0x306B (0x6B) | Hiragana NI
|
|
// 0x029 | 0x0000 (0x00) | <null>
|
|
// 0x029 | 0x0000 (0x00) | <null>
|
|
// 0x129 | 0x0000 (0x00) | <null>
|
|
// 0x229 | 0x0000 (0x00) | <null>
|
|
// ...
|
|
// "Null" means any unused data in the buffer will be filled with null and null attribute.
|
|
// "CoverAChar" means that the attributes belong to the A version of the call, but we've placed de-duped W characters over the top.
|
|
// "W" means that we intend Unicode data to be browsed in the resulting struct (even though wchar and char are unioned.)
|
|
void DbcsWriteRead::PrepPattern::WNullCoverAChar(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected)
|
|
{
|
|
Log::Comment(L"Pattern 10");
|
|
DbcsWriteRead::PrepPattern::A(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, pciExpected, cExpected);
|
|
|
|
int const iwchNeeded = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, nullptr, 0);
|
|
PWSTR pwszTestData = new wchar_t[iwchNeeded];
|
|
VERIFY_IS_NOT_NULL(pwszTestData);
|
|
int const iSuccess = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, pwszTestData, iwchNeeded);
|
|
CheckLastErrorZeroFail(iSuccess, L"MultiByteToWideChar");
|
|
size_t const cWideData = wcslen(pwszTestData);
|
|
|
|
size_t i = 0;
|
|
for (; i < cWideData; i++)
|
|
{
|
|
pciExpected[i].Char.UnicodeChar = pwszTestData[i];
|
|
}
|
|
|
|
for (; i < cExpected; i++)
|
|
{
|
|
pciExpected[i].Char.UnicodeChar = L'\0';
|
|
}
|
|
|
|
delete[] pwszTestData;
|
|
}
|
|
|
|
// 11
|
|
// From Input String: "Q(Hiragana I)(Hiragana KA)(Hiragana NA)ZYXWVUT(Hiragana NI)
|
|
// With Default Attribute 0x7 (before writing) and Applied Attribute 0x29 (written with text)
|
|
// ...
|
|
// Receive Output Table:
|
|
// attr | wchar (char) | symbol
|
|
// ------------------------------------
|
|
// 0x029 | 0x0051 (0x51) | Q
|
|
// 0x029 | 0x3044 (0x44) | Hiragana I
|
|
// 0x029 | 0x304B (0x4B) | Hiragana KA
|
|
// 0x029 | 0x306A (0x6A) | Hiragana NA
|
|
// 0x029 | 0x005A (0x5A) | Z
|
|
// 0x029 | 0x0059 (0x59) | Y
|
|
// 0x029 | 0x0058 (0x58) | X
|
|
// 0x029 | 0x0057 (0x57) | W
|
|
// 0x029 | 0x0056 (0x56) | V
|
|
// 0x029 | 0x0055 (0x55) | U
|
|
// 0x029 | 0x0054 (0x54) | T
|
|
// 0x029 | 0x306B (0x6B) | Hiragana NI
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// ...
|
|
// "Space fill" means any unused data in the buffer will be filled with space and default attribute
|
|
// "W" means that we intend Unicode data to be browsed in the resulting struct (even though wchar and char are unioned.)
|
|
void DbcsWriteRead::PrepPattern::WSpaceFill(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected)
|
|
{
|
|
Log::Comment(L"Pattern 11");
|
|
DbcsWriteRead::PrepPattern::WNullCoverAChar(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, pciExpected, cExpected);
|
|
|
|
int const iwchNeeded = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, nullptr, 0);
|
|
PWSTR pwszTestData = new wchar_t[iwchNeeded];
|
|
VERIFY_IS_NOT_NULL(pwszTestData);
|
|
int const iSuccess = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, pwszTestData, iwchNeeded);
|
|
CheckLastErrorZeroFail(iSuccess, L"MultiByteToWideChar");
|
|
size_t const cWideData = wcslen(pwszTestData);
|
|
|
|
size_t i = 0;
|
|
for (; i < cWideData; i++)
|
|
{
|
|
pciExpected[i].Attributes = wAttrWritten;
|
|
}
|
|
|
|
for (; i < cExpected; i++)
|
|
{
|
|
pciExpected[i].Char.UnicodeChar = L'\x20';
|
|
pciExpected[i].Attributes = wAttrOriginal;
|
|
}
|
|
|
|
delete[] pwszTestData;
|
|
}
|
|
|
|
//8
|
|
// From Input String: "Q(Hiragana I)(Hiragana KA)(Hiragana NA)ZYXWVUT(Hiragana NI)
|
|
// With Default Attribute 0x7 (before writing) and Applied Attribute 0x29 (written with text)
|
|
// ...
|
|
// Receive Output Table:
|
|
// attr | wchar (char) | symbol
|
|
// ------------------------------------
|
|
// 0x029 | 0x0051 (0x51) | Q
|
|
// 0x129 | 0x3082 (0x82) | Hiragana I Unicode 0x3044 with the lower byte covered by Shift-JIS Codepage 932 Lead Byte 0x82.
|
|
// 0x229 | 0xFFA2 (0xA2) | Invalid Unicode Character 0xFFFF with the lower byte covered by Shift-JIS Codepage 932 Trail Byte 0xA2
|
|
// 0x129 | 0x3082 (0x82) | Hiragana KA Unicode 0x304B with the lower byte covered by Shift-JIS Codepage 932 Lead Byte 0x82.
|
|
// 0x229 | 0xFFA9 (0xA9) | Invalid Unicode Character 0xFFFF with the lower byte covered by Shift-JIS Codepage 932 Trail Byte 0xA9
|
|
// 0x129 | 0x3082 (0x82) | Hiragana NA 0x306A with the lower byte covered by Shift-JIS Codepage 932 Lead Byte 0x82.
|
|
// 0x229 | 0xFFC8 (0xC8) | Invalid Unicode Character 0xFFFF with the lower byte covered by Shift-JIS Codepage 932 Trail Byte 0xC8
|
|
// 0x029 | 0x005A (0x5A) | Z
|
|
// 0x029 | 0x0059 (0x59) | Y
|
|
// 0x029 | 0x0058 (0x58) | X
|
|
// 0x029 | 0x0057 (0x57) | W
|
|
// 0x029 | 0x0056 (0x56) | V
|
|
// 0x029 | 0x0055 (0x55) | U
|
|
// 0x029 | 0x0054 (0x54) | T
|
|
// 0x129 | 0x3082 (0x30) | Hiragana NI 0x306B with the lower byte covered by Shift-JIS Codepage 932 Lead Byte 0x82.
|
|
// 0x229 | 0xFFC9 (0xC9) | Invalid Unicode Character 0xFFFF with the lower byte covered by Shift-JIS Codepage 932 Trail Byte 0xC9
|
|
// ...
|
|
// "AOn" means that the Unicode characters were fit into the result buffer first, then the Multibyte conversion
|
|
// was written over the top of the lower byte. This makes an invalid Unicode character, but can be understood
|
|
// as in-codepage from the char portion of the union.
|
|
// "DoubledW" means that the full-width Unicode characters were inserted twice into the buffer (and marked lead/trailing)
|
|
// "NegativeOneTrailing" means that every trailing byte started as -1 or 0xFFFF
|
|
void DbcsWriteRead::PrepPattern::AOnDoubledWNegativeOneTrailing(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected)
|
|
{
|
|
Log::Comment(L"Pattern 8");
|
|
|
|
DbcsWriteRead::PrepPattern::DoubledWNegativeOneTrailing(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, pciExpected, cExpected);
|
|
|
|
// Stomp all A portions of the structure from the existing pattern with the A characters
|
|
size_t const cTestData = strlen(pszTestData);
|
|
VERIFY_IS_GREATER_THAN_OR_EQUAL(cExpected, cTestData);
|
|
for (size_t i = 0; i < cTestData; i++)
|
|
{
|
|
CHAR_INFO* const pciCurrent = &pciExpected[i];
|
|
pciCurrent->Char.AsciiChar = pszTestData[i];
|
|
}
|
|
}
|
|
|
|
// 9
|
|
// From Input String: "Q(Hiragana I)(Hiragana KA)(Hiragana NA)ZYXWVUT(Hiragana NI)
|
|
// With Default Attribute 0x7 (before writing) and Applied Attribute 0x29 (written with text)
|
|
// ...
|
|
// Receive Output Table:
|
|
// attr | wchar (char) | symbol
|
|
// ------------------------------------
|
|
// 0x029 | 0x0051 (0x51) | Q
|
|
// 0x129 | 0x3082 (0x82) | Hiragana I Unicode 0x3044 with the lower byte covered by Shift-JIS Codepage 932 Lead Byte 0x82.
|
|
// 0x229 | 0x30A2 (0xA2) | Hiragana I Unicode 0x3044 with the lower byte covered by Shift-JIS Codepage 932 Trail Byte 0xA2
|
|
// 0x129 | 0x3082 (0x82) | Hiragana KA Unicode 0x304B with the lower byte covered by Shift-JIS Codepage 932 Lead Byte 0x82.
|
|
// 0x229 | 0x30A9 (0xA9) | Hiragana KA Unicode 0x304B with the lower byte covered by Shift-JIS Codepage 932 Trail Byte 0xA9
|
|
// 0x129 | 0x3082 (0x82) | Hiragana NA 0x306A with the lower byte covered by Shift-JIS Codepage 932 Lead Byte 0x82.
|
|
// 0x229 | 0x39C8 (0xC8) | Hiragana NA 0x306A with the lower byte covered by Shift-JIS Codepage 932 Trail Byte 0xC8
|
|
// 0x029 | 0x005A (0x5A) | Z
|
|
// 0x029 | 0x0059 (0x59) | Y
|
|
// 0x029 | 0x0058 (0x58) | X
|
|
// 0x029 | 0x0057 (0x57) | W
|
|
// 0x029 | 0x0056 (0x56) | V
|
|
// 0x029 | 0x0055 (0x55) | U
|
|
// 0x029 | 0x0054 (0x54) | T
|
|
// 0x129 | 0x3082 (0x30) | Hiragana NI 0x306B with the lower byte covered by Shift-JIS Codepage 932 Lead Byte 0x82.
|
|
// 0x229 | 0x30C9 (0xC9) | Hiragana NI 0x306B with the lower byte covered by Shift-JIS Codepage 932 Trail Byte 0xC9
|
|
// ...
|
|
// "AOn" means that the Unicode characters were fit into the result buffer first, then the Multibyte conversion
|
|
// was written over the top of the lower byte. This makes an invalid Unicode character, but can be understood
|
|
// as in-codepage from the char portion of the union.
|
|
// "DoubledW" means that the full-width Unicode characters were inserted twice into the buffer (and marked lead/trailing)
|
|
// "NegativeOneTrailing" means that every trailing byte started as -1 or 0xFFFF
|
|
void DbcsWriteRead::PrepPattern::AOnDoubledW(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected)
|
|
{
|
|
Log::Comment(L"Pattern 9");
|
|
|
|
DbcsWriteRead::PrepPattern::DoubledW(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, pciExpected, cExpected);
|
|
|
|
// Stomp all A portions of the structure from the existing pattern with the A characters
|
|
size_t const cTestData = strlen(pszTestData);
|
|
VERIFY_IS_GREATER_THAN_OR_EQUAL(cExpected, cTestData);
|
|
for (size_t i = 0; i < cTestData; i++)
|
|
{
|
|
CHAR_INFO* const pciCurrent = &pciExpected[i];
|
|
pciCurrent->Char.AsciiChar = pszTestData[i];
|
|
}
|
|
}
|
|
|
|
// 12
|
|
// From Input String: "Q(Hiragana I)(Hiragana KA)(Hiragana NA)ZYXWVUT(Hiragana NI)
|
|
// With Default Attribute 0x7 (before writing) and Applied Attribute 0x29 (written with text)
|
|
// ...
|
|
// Receive Output Table:
|
|
// attr | wchar (char) | symbol
|
|
// ------------------------------------
|
|
// 0x029 | 0x0051 (0x51) | Q
|
|
// 0x129 | 0x3044 (0x44) | Hiragana I
|
|
// 0x229 | 0x304B (0x4B) | Hiragana KA
|
|
// 0x129 | 0x306A (0x6A) | Hiragana NA
|
|
// 0x229 | 0x005A (0x5A) | Z
|
|
// 0x129 | 0x0059 (0x59) | Y
|
|
// 0x229 | 0x0058 (0x58) | X
|
|
// 0x029 | 0x0057 (0x57) | W
|
|
// 0x029 | 0x0056 (0x56) | V
|
|
// 0x029 | 0x0020 (0x20) | <space>
|
|
// 0x029 | 0x0020 (0x20) | <space>
|
|
// 0x029 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0020 (0x20) | <space>
|
|
// 0x007 | 0x0000 (0x00) | <null>
|
|
// 0x007 | 0x0000 (0x00) | <null>
|
|
// 0x007 | 0x0000 (0x00) | <null>
|
|
// ...
|
|
// "Space Padded" means most of the unused data in the buffer will be filled with spaces and the default attribute.
|
|
// "Dedupe" means that any full-width characters in the buffer (despite being stored doubled inside the buffer)
|
|
// will be returned as single copies.
|
|
// "W" means that we intend Unicode data to be browsed in the resulting struct (even though wchar and char are unioned.)
|
|
// "Truncated" means that this pattern trims off some of the end of the buffer with NULLs.
|
|
// "A Cover Attr" means that after all the other operations, we will finally run through and cover up the attributes
|
|
// again with what they would have been for multi-byte data (leading and trailing flags)
|
|
void DbcsWriteRead::PrepPattern::ACoverAttrSpacePaddedDedupeTruncatedW(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected)
|
|
{
|
|
Log::Comment(L"Pattern 12");
|
|
DbcsWriteRead::PrepPattern::SpacePaddedDedupeTruncatedW(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, pciExpected, cExpected);
|
|
|
|
int const iwchNeeded = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, nullptr, 0);
|
|
PWSTR pwszTestData = new wchar_t[iwchNeeded];
|
|
VERIFY_IS_NOT_NULL(pwszTestData);
|
|
int const iSuccess = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, pwszTestData, iwchNeeded);
|
|
CheckLastErrorZeroFail(iSuccess, L"MultiByteToWideChar");
|
|
size_t const cWideData = wcslen(pwszTestData);
|
|
|
|
size_t i = 0;
|
|
bool fIsNextTrailing = false;
|
|
for (; i < cWideData; i++)
|
|
{
|
|
pciExpected[i].Attributes = wAttrWritten;
|
|
|
|
if (IsDBCSLeadByteEx(uiCodePage, pszTestData[i]))
|
|
{
|
|
pciExpected[i].Attributes |= COMMON_LVB_LEADING_BYTE;
|
|
fIsNextTrailing = true;
|
|
}
|
|
else if (fIsNextTrailing)
|
|
{
|
|
pciExpected[i].Attributes |= COMMON_LVB_TRAILING_BYTE;
|
|
fIsNextTrailing = false;
|
|
}
|
|
}
|
|
|
|
for (; i < cExpected; i++)
|
|
{
|
|
pciExpected[i].Attributes = wAttrOriginal;
|
|
}
|
|
|
|
delete[] pwszTestData;
|
|
}
|
|
|
|
// 14
|
|
// From Input String: "Q(Hiragana I)(Hiragana KA)(Hiragana NA)ZYXWVUT(Hiragana NI)
|
|
// With Default Attribute 0x7 (before writing) and Applied Attribute 0x29 (written with text)
|
|
// ...
|
|
// Receive Output Table:
|
|
// attr | wchar (char) | symbol
|
|
// ------------------------------------
|
|
// 0x029 | 0x0000 (0x00) | <null>
|
|
// 0x029 | 0x0000 (0x00) | <null>
|
|
// 0x029 | 0x0000 (0x00) | <null>
|
|
// 0x029 | 0x0000 (0x00) | <null>
|
|
// 0x029 | 0x0000 (0x00) | <null>
|
|
// 0x029 | 0x0000 (0x00) | <null>
|
|
// 0x029 | 0x0000 (0x00) | <null>
|
|
// 0x029 | 0x0000 (0x00) | <null>
|
|
// 0x029 | 0x0000 (0x00) | <null>
|
|
// 0x029 | 0x0000 (0x00) | <null>
|
|
// 0x029 | 0x0000 (0x00) | <null>
|
|
// 0x029 | 0x0000 (0x00) | <null>
|
|
// 0x007 | 0x0000 (0x00) | <null>
|
|
// 0x007 | 0x0000 (0x00) | <null>
|
|
// 0x007 | 0x0000 (0x00) | <null>
|
|
// 0x007 | 0x0000 (0x00) | <null>
|
|
// ...
|
|
// "Space Padded" means most of the unused data in the buffer will be filled with spaces and the default attribute.
|
|
// "Dedupe" means that any full-width characters in the buffer (despite being stored doubled inside the buffer)
|
|
// will be returned as single copies.
|
|
// "W" means that we intend Unicode data to be browsed in the resulting struct (even though wchar and char are unioned.)
|
|
// "Truncated" means that this pattern trims off some of the end of the buffer with NULLs.
|
|
// "A Cover Attr" means that after all the other operations, we will finally run through and cover up the attributes
|
|
// again with what they would have been for multi-byte data (leading and trailing flags)
|
|
void DbcsWriteRead::PrepPattern::TrueTypeCharANullWithAttrs(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
_Inout_updates_all_(cExpected) CHAR_INFO* const pciExpected,
|
|
const size_t cExpected)
|
|
{
|
|
Log::Comment(L"Pattern 14");
|
|
int const iwchNeeded = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, nullptr, 0);
|
|
PWSTR pwszTestData = new wchar_t[iwchNeeded];
|
|
VERIFY_IS_NOT_NULL(pwszTestData);
|
|
int const iSuccess = MultiByteToWideChar(uiCodePage, 0, pszTestData, -1, pwszTestData, iwchNeeded);
|
|
CheckLastErrorZeroFail(iSuccess, L"MultiByteToWideChar");
|
|
size_t const cWideData = wcslen(pwszTestData);
|
|
|
|
// Fill the number of columns worth of wide characters with the write attribute. The rest get the original attribute.
|
|
size_t i;
|
|
for (i = 0; i < cWideData; i++)
|
|
{
|
|
pciExpected[i].Attributes = wAttrWritten;
|
|
}
|
|
|
|
for (; i < cExpected; i++)
|
|
{
|
|
pciExpected[i].Attributes = wAttrOriginal;
|
|
}
|
|
|
|
// For characters, if the string contained NO double-byte characters, it will return. Otherwise, it won't return due to
|
|
// a long standing bug in the console's way it calls RtlUnicodeToOemN
|
|
size_t const cTestData = strlen(pszTestData);
|
|
if (cWideData == cTestData)
|
|
{
|
|
for (i = 0; i < cTestData; i++)
|
|
{
|
|
pciExpected[i].Char.AsciiChar = pszTestData[i];
|
|
}
|
|
}
|
|
|
|
delete[] pwszTestData;
|
|
}
|
|
|
|
void DbcsWriteRead::PrepReadConsoleOutput(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
const DbcsWriteRead::WriteMode WriteMode,
|
|
const bool fWriteWithUnicode,
|
|
const bool fIsTrueTypeFont,
|
|
const bool fReadWithUnicode,
|
|
_Inout_updates_all_(cExpectedNeeded) CHAR_INFO* const rgciExpected,
|
|
const size_t cExpectedNeeded)
|
|
{
|
|
switch (WriteMode)
|
|
{
|
|
case DbcsWriteRead::WriteMode::WriteConsoleOutputFunc:
|
|
{
|
|
// If we wrote with WriteConsoleOutput*, things are going to be munged depending on the font and the A/W status of both the write and the read.
|
|
if (!fReadWithUnicode)
|
|
{
|
|
// If we read it back with the A functions, the font might matter.
|
|
// We will get different results dependent on whether the original text was written with the W or A method.
|
|
if (fWriteWithUnicode)
|
|
{
|
|
if (fIsTrueTypeFont)
|
|
{
|
|
// When written with WriteConsoleOutputW and read back with ReadConsoleOutputA under TT font, we will get a deduplicated
|
|
// set of Unicode characters (YES. Unicode characters despite calling the A API to read back) that is space padded out
|
|
// There will be no lead/trailing markings.
|
|
DbcsWriteRead::PrepPattern::SpacePaddedDedupeW(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
}
|
|
else
|
|
{
|
|
// When written with WriteConsoleOutputW and read back with ReadConsoleOutputA under Raster font, we will get the
|
|
// double-byte sequences stomped on top of a Unicode filled CHAR_INFO structure that used -1 for trailing bytes.
|
|
DbcsWriteRead::PrepPattern::AStompsWNegativeOnePatternTruncateSpacePadded(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// When written with WriteConsoleOutputA and read back with ReadConsoleOutputA,
|
|
// we will get back the double-byte sequences appropriately labeled with leading/trailing bytes.
|
|
//DbcsWriteRead::PrepPattern::A(pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
DbcsWriteRead::PrepPattern::AOnDoubledWNegativeOneTrailing(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we read it back with the W functions, both the font and the original write mode (A vs. W) matter
|
|
if (fIsTrueTypeFont)
|
|
{
|
|
if (fWriteWithUnicode)
|
|
{
|
|
// When written with WriteConsoleOutputW and read back with ReadConsoleOutputW when the font is TrueType,
|
|
// we will get a deduplicated set of Unicode characters with no lead/trailing markings and space padded at the end.
|
|
DbcsWriteRead::PrepPattern::SpacePaddedDedupeW(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
}
|
|
else
|
|
{
|
|
// When written with WriteConsoleOutputW and read back with ReadConsoleOutputA when the font is TrueType,
|
|
// we will get back Unicode characters doubled up and marked with leading and trailing bytes...
|
|
// ... except all the trailing bytes character values will be -1.
|
|
DbcsWriteRead::PrepPattern::DoubledWNegativeOneTrailing(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (fWriteWithUnicode)
|
|
{
|
|
// When written with WriteConsoleOutputW and read back with ReadConsoleOutputW when the font is Raster,
|
|
// we will get a deduplicated set of Unicode characters with no lead/trailing markings and space padded at the end...
|
|
// ... except something weird happens with truncation (TODO figure out what)
|
|
DbcsWriteRead::PrepPattern::SpacePaddedDedupeTruncatedW(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
}
|
|
else
|
|
{
|
|
// When written with WriteConsoleOutputA and read back with ReadConsoleOutputW when the font is Raster,
|
|
// we will get back de-duplicated Unicode characters with no lead / trail markings.The extra array space will remain null.
|
|
DbcsWriteRead::PrepPattern::NullPaddedDedupeW(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case DbcsWriteRead::WriteMode::CrtWrite:
|
|
case DbcsWriteRead::WriteMode::WriteConsoleOutputCharacterFunc:
|
|
case DbcsWriteRead::WriteMode::WriteConsoleFunc:
|
|
{
|
|
// Writing with the CRT down here.
|
|
if (!fReadWithUnicode)
|
|
{
|
|
// If we wrote with the CRT and are reading with A functions, the font doesn't matter.
|
|
// We will always get back the double-byte sequences appropriately labeled with leading/trailing bytes.
|
|
//DbcsWriteRead::PrepPattern::(pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
DbcsWriteRead::PrepPattern::AOnDoubledW(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
}
|
|
else
|
|
{
|
|
// If we wrote with the CRT and are reading back with the W functions, the font does matter.
|
|
if (fIsTrueTypeFont)
|
|
{
|
|
// In a TrueType font, we will get back Unicode characters doubled up and marked with leading and trailing bytes.
|
|
DbcsWriteRead::PrepPattern::DoubledW(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
}
|
|
else
|
|
{
|
|
// In a Raster font, we will get back de-duplicated Unicode characters with no lead/trail markings. The extra array space will remain null.
|
|
DbcsWriteRead::PrepPattern::NullPaddedDedupeW(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
VERIFY_FAIL(L"Unsupported write mode");
|
|
}
|
|
}
|
|
|
|
void DbcsWriteRead::PrepReadConsoleOutputCharacter(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
const DbcsWriteRead::WriteMode WriteMode,
|
|
const bool fWriteWithUnicode,
|
|
const bool fIsTrueTypeFont,
|
|
const bool fReadWithUnicode,
|
|
_Inout_updates_all_(cExpectedNeeded) CHAR_INFO* const rgciExpected,
|
|
const size_t cExpectedNeeded)
|
|
{
|
|
if (DbcsWriteRead::WriteMode::WriteConsoleOutputFunc == WriteMode && fWriteWithUnicode)
|
|
{
|
|
if (fIsTrueTypeFont)
|
|
{
|
|
if (fReadWithUnicode)
|
|
{
|
|
DbcsWriteRead::PrepPattern::WSpaceFill(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
}
|
|
else
|
|
{
|
|
DbcsWriteRead::PrepPattern::TrueTypeCharANullWithAttrs(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (fReadWithUnicode)
|
|
{
|
|
DbcsWriteRead::PrepPattern::ACoverAttrSpacePaddedDedupeTruncatedW(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
}
|
|
else
|
|
{
|
|
DbcsWriteRead::PrepPattern::SpacePaddedDedupeA(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!fReadWithUnicode)
|
|
{
|
|
DbcsWriteRead::PrepPattern::A(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
}
|
|
else
|
|
{
|
|
DbcsWriteRead::PrepPattern::WNullCoverAChar(uiCodePage, pszTestData, wAttrOriginal, wAttrWritten, rgciExpected, cExpectedNeeded);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DbcsWriteRead::PrepExpected(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
const WORD wAttrOriginal,
|
|
const WORD wAttrWritten,
|
|
const DbcsWriteRead::WriteMode WriteMode,
|
|
const bool fWriteWithUnicode,
|
|
const bool fIsTrueTypeFont,
|
|
const DbcsWriteRead::ReadMode ReadMode,
|
|
const bool fReadWithUnicode,
|
|
_Outptr_result_buffer_(*pcExpected) CHAR_INFO** const ppciExpected,
|
|
_Out_ size_t* const pcExpected)
|
|
{
|
|
// We will expect to read back one CHAR_INFO for every A character we sent to the console using the assumption above.
|
|
// We expect that reading W characters will always be less than or equal to that.
|
|
size_t const cExpectedNeeded = strlen(pszTestData);
|
|
|
|
// Allocate and zero out the space so comparisons don't fail from garbage bytes.
|
|
CHAR_INFO* rgciExpected = new CHAR_INFO[cExpectedNeeded];
|
|
VERIFY_IS_NOT_NULL(rgciExpected);
|
|
ZeroMemory(rgciExpected, sizeof(CHAR_INFO) * cExpectedNeeded);
|
|
|
|
switch (ReadMode)
|
|
{
|
|
case DbcsWriteRead::ReadMode::ReadConsoleOutputFunc:
|
|
{
|
|
DbcsWriteRead::PrepReadConsoleOutput(uiCodePage,
|
|
pszTestData,
|
|
wAttrOriginal,
|
|
wAttrWritten,
|
|
WriteMode,
|
|
fWriteWithUnicode,
|
|
fIsTrueTypeFont,
|
|
fReadWithUnicode,
|
|
rgciExpected,
|
|
cExpectedNeeded);
|
|
break;
|
|
}
|
|
case DbcsWriteRead::ReadMode::ReadConsoleOutputCharacterFunc:
|
|
{
|
|
DbcsWriteRead::PrepReadConsoleOutputCharacter(uiCodePage,
|
|
pszTestData,
|
|
wAttrOriginal,
|
|
wAttrWritten,
|
|
WriteMode,
|
|
fWriteWithUnicode,
|
|
fIsTrueTypeFont,
|
|
fReadWithUnicode,
|
|
rgciExpected,
|
|
cExpectedNeeded);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
VERIFY_FAIL(L"Unknown read mode.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Return the expected array and the length that should be used for comparison at the end of the test.
|
|
*ppciExpected = rgciExpected;
|
|
*pcExpected = cExpectedNeeded;
|
|
}
|
|
|
|
void DbcsWriteRead::RetrieveOutput(const HANDLE hOut,
|
|
const DbcsWriteRead::ReadMode ReadMode,
|
|
const bool fReadUnicode,
|
|
_Out_writes_(cChars) CHAR_INFO* const rgChars,
|
|
const SHORT cChars)
|
|
{
|
|
COORD coordBufferTarget = { 0 };
|
|
|
|
switch (ReadMode)
|
|
{
|
|
case DbcsWriteRead::ReadMode::ReadConsoleOutputFunc:
|
|
{
|
|
// Since we wrote (in SendOutput function) to the 0,0 line, we need to read back the same width from that line.
|
|
COORD coordBufferSize = { 0 };
|
|
coordBufferSize.Y = 1;
|
|
coordBufferSize.X = cChars;
|
|
|
|
SMALL_RECT srReadRegion = { 0 }; // inclusive rectangle (bottom and right are INSIDE the read area. usually are exclusive.)
|
|
srReadRegion.Right = cChars - 1;
|
|
|
|
// return value for read region shouldn't change
|
|
SMALL_RECT const srReadRegionExpected = srReadRegion;
|
|
|
|
if (!fReadUnicode)
|
|
{
|
|
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(ReadConsoleOutputA(hOut, rgChars, coordBufferSize, coordBufferTarget, &srReadRegion));
|
|
}
|
|
else
|
|
{
|
|
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(ReadConsoleOutputW(hOut, rgChars, coordBufferSize, coordBufferTarget, &srReadRegion));
|
|
}
|
|
|
|
Log::Comment(NoThrowString().Format(L"ReadRegion T: %d L: %d B: %d R: %d", srReadRegion.Top, srReadRegion.Left, srReadRegion.Bottom, srReadRegion.Right));
|
|
VERIFY_ARE_EQUAL(srReadRegionExpected, srReadRegion);
|
|
break;
|
|
}
|
|
case DbcsWriteRead::ReadMode::ReadConsoleOutputCharacterFunc:
|
|
{
|
|
DWORD dwRead = 0;
|
|
if (!fReadUnicode)
|
|
{
|
|
PSTR psRead = new char[cChars];
|
|
VERIFY_IS_NOT_NULL(psRead);
|
|
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(ReadConsoleOutputCharacterA(hOut, psRead, cChars, coordBufferTarget, &dwRead));
|
|
|
|
for (size_t i = 0; i < dwRead; i++)
|
|
{
|
|
rgChars[i].Char.AsciiChar = psRead[i];
|
|
}
|
|
|
|
delete[] psRead;
|
|
}
|
|
else
|
|
{
|
|
PWSTR pwsRead = new wchar_t[cChars];
|
|
VERIFY_IS_NOT_NULL(pwsRead);
|
|
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(ReadConsoleOutputCharacterW(hOut, pwsRead, cChars, coordBufferTarget, &dwRead));
|
|
|
|
for (size_t i = 0; i < dwRead; i++)
|
|
{
|
|
rgChars[i].Char.UnicodeChar = pwsRead[i];
|
|
}
|
|
|
|
delete[] pwsRead;
|
|
}
|
|
|
|
PWORD pwAttrs = new WORD[cChars];
|
|
VERIFY_IS_NOT_NULL(pwAttrs);
|
|
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(ReadConsoleOutputAttribute(hOut, pwAttrs, cChars, coordBufferTarget, &dwRead));
|
|
|
|
for (size_t i = 0; i < dwRead; i++)
|
|
{
|
|
rgChars[i].Attributes = pwAttrs[i];
|
|
}
|
|
|
|
delete[] pwAttrs;
|
|
break;
|
|
}
|
|
default:
|
|
VERIFY_FAIL(L"Unknown read mode");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void DbcsWriteRead::Verify(_In_reads_(cExpected) CHAR_INFO* const rgExpected,
|
|
const size_t cExpected,
|
|
_In_reads_(cExpected) CHAR_INFO* const rgActual)
|
|
{
|
|
// We will walk through for the number of CHAR_INFOs expected.
|
|
for (size_t i = 0; i < cExpected; i++)
|
|
{
|
|
// Uncomment these lines for help debugging the verification.
|
|
/*
|
|
Log::Comment(NoThrowString().Format(L"Index: %d:", i));
|
|
Log::Comment(VerifyOutputTraits<CHAR_INFO>::ToString(rgExpected[i]));
|
|
Log::Comment(VerifyOutputTraits<CHAR_INFO>::ToString(rgActual[i]));
|
|
*/
|
|
|
|
VERIFY_ARE_EQUAL(rgExpected[i], rgActual[i]);
|
|
}
|
|
}
|
|
|
|
void DbcsWriteRead::TestRunner(_In_ unsigned int const uiCodePage,
|
|
_In_ PCSTR pszTestData,
|
|
_In_opt_ WORD* const pwAttrOverride,
|
|
const bool fUseTrueType,
|
|
const DbcsWriteRead::WriteMode WriteMode,
|
|
const bool fWriteInUnicode,
|
|
const DbcsWriteRead::ReadMode ReadMode,
|
|
const bool fReadWithUnicode)
|
|
{
|
|
// First we need to set up the tests by clearing out the first line of the buffer,
|
|
// retrieving the appropriate output handle, and getting the colors (attributes)
|
|
// used by default in the buffer (set during clearing as well).
|
|
HANDLE hOut;
|
|
WORD wAttributes;
|
|
if (!DbcsWriteRead::Setup(uiCodePage, fUseTrueType, &hOut, &wAttributes))
|
|
{
|
|
// If we can't set up (setup will detect systems where this test cannot operate) then return early.
|
|
return;
|
|
}
|
|
|
|
WORD const wAttrOriginal = wAttributes;
|
|
|
|
// Some tests might want to override the colors applied to ensure both parts of the CHAR_INFO union
|
|
// work for methods that support sending that union. (i.e. not the CRT path)
|
|
if (nullptr != pwAttrOverride)
|
|
{
|
|
wAttributes = *pwAttrOverride;
|
|
}
|
|
|
|
// The console bases the space it walks for DBCS conversions on the length of the A version of the text.
|
|
// Store that length now so we have it for our read/write operations.
|
|
size_t const cTestData = strlen(pszTestData);
|
|
|
|
// Write the string under test into the appropriate WRITE API for this test.
|
|
DbcsWriteRead::SendOutput(hOut, uiCodePage, WriteMode, fWriteInUnicode, pszTestData, wAttributes);
|
|
|
|
// Prepare the array of CHAR_INFO structs that we expect to receive back when we will call read in a moment.
|
|
// This can vary based on font, unicode/non-unicode (when reading AND writing), and codepage.
|
|
CHAR_INFO* pciExpected;
|
|
size_t cExpected;
|
|
DbcsWriteRead::PrepExpected(uiCodePage, pszTestData, wAttrOriginal, wAttributes, WriteMode, fWriteInUnicode, fUseTrueType, ReadMode, fReadWithUnicode, &pciExpected, &cExpected);
|
|
|
|
// Now call the appropriate READ API for this test.
|
|
CHAR_INFO* pciActual = new CHAR_INFO[cTestData];
|
|
VERIFY_IS_NOT_NULL(pciActual);
|
|
ZeroMemory(pciActual, sizeof(CHAR_INFO) * cTestData);
|
|
DbcsWriteRead::RetrieveOutput(hOut, ReadMode, fReadWithUnicode, pciActual, (SHORT)cTestData);
|
|
|
|
// Loop through and verify that our expected array matches what was actually returned by the given API.
|
|
DbcsWriteRead::Verify(pciExpected, cExpected, pciActual);
|
|
|
|
// Free allocated structures
|
|
delete[] pciActual;
|
|
delete[] pciExpected;
|
|
}
|
|
|
|
void DbcsTests::TestDbcsWriteRead()
|
|
{
|
|
unsigned int uiCodePage;
|
|
VERIFY_SUCCEEDED(TestData::TryGetValue(L"uiCodePage", uiCodePage));
|
|
|
|
bool fUseTrueTypeFont;
|
|
VERIFY_SUCCEEDED(TestData::TryGetValue(L"fUseTrueTypeFont", fUseTrueTypeFont));
|
|
|
|
int iWriteMode;
|
|
VERIFY_SUCCEEDED(TestData::TryGetValue(L"WriteMode", iWriteMode));
|
|
DbcsWriteRead::WriteMode WriteMode = (DbcsWriteRead::WriteMode)iWriteMode;
|
|
|
|
bool fWriteInUnicode;
|
|
VERIFY_SUCCEEDED(TestData::TryGetValue(L"fWriteInUnicode", fWriteInUnicode));
|
|
|
|
int iReadMode;
|
|
VERIFY_SUCCEEDED(TestData::TryGetValue(L"ReadMode", iReadMode));
|
|
DbcsWriteRead::ReadMode ReadMode = (DbcsWriteRead::ReadMode)iReadMode;
|
|
|
|
bool fReadInUnicode;
|
|
VERIFY_SUCCEEDED(TestData::TryGetValue(L"fReadInUnicode", fReadInUnicode));
|
|
|
|
PCWSTR pwszWriteMode = L"";
|
|
switch (WriteMode)
|
|
{
|
|
case DbcsWriteRead::WriteMode::CrtWrite:
|
|
pwszWriteMode = L"CRT";
|
|
break;
|
|
case DbcsWriteRead::WriteMode::WriteConsoleOutputFunc:
|
|
pwszWriteMode = L"WriteConsoleOutput";
|
|
break;
|
|
case DbcsWriteRead::WriteMode::WriteConsoleOutputCharacterFunc:
|
|
pwszWriteMode = L"WriteConsoleOutputCharacter";
|
|
break;
|
|
case DbcsWriteRead::WriteMode::WriteConsoleFunc:
|
|
pwszWriteMode = L"WriteConsole";
|
|
break;
|
|
default:
|
|
VERIFY_FAIL(L"Write mode not supported");
|
|
}
|
|
|
|
PCWSTR pwszReadMode = L"";
|
|
switch (ReadMode)
|
|
{
|
|
case DbcsWriteRead::ReadMode::ReadConsoleOutputFunc:
|
|
pwszReadMode = L"ReadConsoleOutput";
|
|
break;
|
|
case DbcsWriteRead::ReadMode::ReadConsoleOutputCharacterFunc:
|
|
pwszReadMode = L"ReadConsoleOutputCharacter";
|
|
break;
|
|
default:
|
|
VERIFY_FAIL(L"Read mode not supported");
|
|
}
|
|
|
|
auto testInfo = NoThrowString().Format(L"\r\n\r\n\r\nUse '%ls' font. Write with %ls '%ls'. Check Read with %ls '%ls' API. Use %d codepage.\r\n",
|
|
fUseTrueTypeFont ? L"TrueType" : L"Raster",
|
|
pwszWriteMode,
|
|
fWriteInUnicode ? L"W" : L"A",
|
|
pwszReadMode,
|
|
fReadInUnicode ? L"W" : L"A",
|
|
uiCodePage);
|
|
|
|
Log::Comment(testInfo);
|
|
|
|
PCSTR pszTestData = "";
|
|
switch (uiCodePage)
|
|
{
|
|
case ENGLISH_US_CP:
|
|
pszTestData = "QWERTYUIOP";
|
|
break;
|
|
case JAPANESE_CP:
|
|
// Q (Hiragana I) (Hiragana KA) (Hiragana NA) Z Y X W V U T (Hiragana NI) in Shift-JIS (Codepage 932)
|
|
pszTestData = "Q\x82\xA2\x82\xa9\x82\xc8ZYXWVUT\x82\xc9";
|
|
break;
|
|
default:
|
|
VERIFY_FAIL(L"No test data for this codepage");
|
|
break;
|
|
}
|
|
|
|
WORD wAttributes = 0;
|
|
|
|
if (WriteMode == 1)
|
|
{
|
|
Log::Comment(L"We will also try to change the color since WriteConsoleOutput supports it.");
|
|
wAttributes = FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_GREEN;
|
|
}
|
|
|
|
DbcsWriteRead::TestRunner(uiCodePage,
|
|
pszTestData,
|
|
wAttributes != 0 ? &wAttributes : nullptr,
|
|
fUseTrueTypeFont,
|
|
WriteMode,
|
|
fWriteInUnicode,
|
|
ReadMode,
|
|
fReadInUnicode);
|
|
|
|
Log::Comment(testInfo);
|
|
}
|
|
|
|
// This test covers bisect-prevention handling. This is the behavior where a double-wide character will not be spliced
|
|
// across a line boundary and will instead be advanced onto the next line.
|
|
// It additionally exercises the word wrap functionality to ensure that the bisect calculations continue
|
|
// to apply properly when wrap occurs.
|
|
void DbcsTests::TestDbcsBisect()
|
|
{
|
|
HANDLE const hOut = GetStdOutputHandle();
|
|
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleCP(JAPANESE_CP));
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleOutputCP(JAPANESE_CP));
|
|
|
|
UINT dwCP = GetConsoleCP();
|
|
VERIFY_ARE_EQUAL(dwCP, JAPANESE_CP);
|
|
|
|
UINT dwOutputCP = GetConsoleOutputCP();
|
|
VERIFY_ARE_EQUAL(dwOutputCP, JAPANESE_CP);
|
|
|
|
CONSOLE_SCREEN_BUFFER_INFOEX sbiex = { 0 };
|
|
sbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
|
|
BOOL fSuccess = GetConsoleScreenBufferInfoEx(hOut, &sbiex);
|
|
|
|
if (CheckLastError(fSuccess, L"GetConsoleScreenBufferInfoEx"))
|
|
{
|
|
Log::Comment(L"Set cursor position to the last column in the buffer width.");
|
|
sbiex.dwCursorPosition.X = sbiex.dwSize.X - 1;
|
|
|
|
COORD const coordEndOfLine = sbiex.dwCursorPosition; // this is the end of line position we're going to write at
|
|
COORD coordStartOfNextLine;
|
|
coordStartOfNextLine.X = 0;
|
|
coordStartOfNextLine.Y = sbiex.dwCursorPosition.Y + 1;
|
|
|
|
fSuccess = SetConsoleCursorPosition(hOut, sbiex.dwCursorPosition);
|
|
if (CheckLastError(fSuccess, L"SetConsoleScreenBufferInfoEx"))
|
|
{
|
|
Log::Comment(L"Attempt to write (standard WriteConsole) a double-wide character and ensure that it is placed onto the following line, not bisected.");
|
|
DWORD dwWritten = 0;
|
|
WCHAR const wchHiraganaU = L'\x3046';
|
|
WCHAR const wchSpace = L' ';
|
|
fSuccess = WriteConsoleW(hOut, &wchHiraganaU, 1, &dwWritten, nullptr);
|
|
|
|
if (CheckLastError(fSuccess, L"WriteConsoleW"))
|
|
{
|
|
VERIFY_ARE_EQUAL(1u, dwWritten, L"We should have only written the one character.");
|
|
|
|
// Read the end of line character and the start of the next line.
|
|
// A proper bisect should have left the end of line character empty (a space)
|
|
// and then put the character at the beginning of the next line.
|
|
|
|
Log::Comment(L"Confirm that the end of line was left empty to prevent bisect.");
|
|
WCHAR wchBuffer;
|
|
fSuccess = ReadConsoleOutputCharacterW(hOut, &wchBuffer, 1, coordEndOfLine, &dwWritten);
|
|
if (CheckLastError(fSuccess, L"ReadConsoleOutputCharacterW"))
|
|
{
|
|
VERIFY_ARE_EQUAL(1u, dwWritten, L"We should have only read one character back at the end of the line.");
|
|
|
|
VERIFY_ARE_EQUAL(wchSpace, wchBuffer, L"A space character should have been left at the end of the line.");
|
|
|
|
Log::Comment(L"Confirm that the wide character was written on the next line down instead.");
|
|
WCHAR wchBuffer2[2];
|
|
fSuccess = ReadConsoleOutputCharacterW(hOut, wchBuffer2, 2, coordStartOfNextLine, &dwWritten);
|
|
if (CheckLastError(fSuccess, L"ReadConsoleOutputCharacterW"))
|
|
{
|
|
VERIFY_ARE_EQUAL(1u, dwWritten, L"We should have only read one character back at the beginning of the next line.");
|
|
|
|
VERIFY_ARE_EQUAL(wchHiraganaU, wchBuffer2[0], L"The same character we passed in should have been read back.");
|
|
|
|
Log::Comment(L"Confirm that the cursor has advanced past the double wide character.");
|
|
fSuccess = GetConsoleScreenBufferInfoEx(hOut, &sbiex);
|
|
if (CheckLastError(fSuccess, L"GetConsoleScreenBufferInfoEx"))
|
|
{
|
|
VERIFY_ARE_EQUAL(coordStartOfNextLine.Y, sbiex.dwCursorPosition.Y, L"Cursor has moved down to next line.");
|
|
VERIFY_ARE_EQUAL(coordStartOfNextLine.X + 2, sbiex.dwCursorPosition.X, L"Cursor has advanced two spaces on next line for double wide character.");
|
|
|
|
// TODO: This bit needs to move into a UIA test
|
|
/*Log::Comment(L"We can only run the resize test in the v2 console. We'll skip it if it turns out v2 is off.");
|
|
if (IsV2Console())
|
|
{
|
|
Log::Comment(L"Test that the character moves back up when the window is unwrapped. Make the window one larger.");
|
|
sbiex.srWindow.Right++;
|
|
sbiex.dwSize.X++;
|
|
fSuccess = SetConsoleScreenBufferInfoEx(hOut, &sbiex);
|
|
if (CheckLastError(fSuccess, L"SetConsoleScreenBufferInfoEx"))
|
|
{
|
|
ZeroMemory(wchBuffer2, ARRAYSIZE(wchBuffer2) * sizeof(WCHAR));
|
|
Log::Comment(L"Check that the character rolled back up onto the previous line.");
|
|
fSuccess = ReadConsoleOutputCharacterW(hOut, wchBuffer2, 2, coordEndOfLine, &dwWritten);
|
|
if (CheckLastError(fSuccess, L"ReadConsoleOutputCharacterW"))
|
|
{
|
|
VERIFY_ARE_EQUAL(1u, dwWritten, L"We should have read 1 character up on the previous line.");
|
|
|
|
VERIFY_ARE_EQUAL(wchHiraganaU, wchBuffer2[0], L"The character should now be up one line.");
|
|
|
|
Log::Comment(L"Now shrink the window one more time and make sure the character rolls back down a line.");
|
|
sbiex.srWindow.Right--;
|
|
sbiex.dwSize.X--;
|
|
fSuccess = SetConsoleScreenBufferInfoEx(hOut, &sbiex);
|
|
if (CheckLastError(fSuccess, L"SetConsoleScreenBufferInfoEx"))
|
|
{
|
|
ZeroMemory(wchBuffer2, ARRAYSIZE(wchBuffer2) * sizeof(WCHAR));
|
|
Log::Comment(L"Check that the character rolled down onto the next line again.");
|
|
fSuccess = ReadConsoleOutputCharacterW(hOut, wchBuffer2, 2, coordStartOfNextLine, &dwWritten);
|
|
if (CheckLastError(fSuccess, L"ReadConsoleOutputCharacterW"))
|
|
{
|
|
VERIFY_ARE_EQUAL(1u, dwWritten, L"We should have read 1 character back down again on the next line.");
|
|
|
|
VERIFY_ARE_EQUAL(wchHiraganaU, wchBuffer2[0], L"The character should now be down on the 2nd line again.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The following W versions of the tests check that we can't insert a bisecting cell even
|
|
// when we try to force one in by writing cell-by-cell.
|
|
// NOTE: This is a change in behavior from the legacy behavior.
|
|
// V1 console would allow a lead byte to be stored in the final cell and then display it improperly.
|
|
// It would also allow this data to be read back.
|
|
// I believe this was a long standing bug because every other API entry fastidiously checked that it wasn't possible to
|
|
// "bisect" a cell and all sorts of portions of the rest of the console code try to enforce that bisects across lines can't happen.
|
|
// For the most recent revision of the V2 console (approx November 2018), we're trying to make sure that the TextBuffer's internal state
|
|
// is always correct at insert (instead of correcting it on every read).
|
|
// If it turns out that we are proven wrong in the future and this causes major problems,
|
|
// the legacy behavior is to just let it be stored and compensate for it later. (On read in every API but ReadConsoleOutput and in the selection).
|
|
void DbcsTests::TestDbcsBisectWriteCellsEndW()
|
|
{
|
|
const auto out = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
|
|
CONSOLE_SCREEN_BUFFER_INFOEX info = { 0 };
|
|
info.cbSize = sizeof(info);
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(out, &info));
|
|
|
|
CHAR_INFO originalCell;
|
|
originalCell.Char.UnicodeChar = L'\x30a2'; // Japanese full-width katakana A
|
|
originalCell.Attributes = COMMON_LVB_LEADING_BYTE | FOREGROUND_RED;
|
|
|
|
SMALL_RECT writeRegion;
|
|
writeRegion.Top = 0;
|
|
writeRegion.Bottom = 0;
|
|
writeRegion.Left = info.dwSize.X - 1;
|
|
writeRegion.Right = info.dwSize.X - 1;
|
|
|
|
const auto originalWriteRegion = writeRegion;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(out, &originalCell, { 1, 1 }, { 0, 0 }, &writeRegion));
|
|
VERIFY_ARE_EQUAL(originalWriteRegion, writeRegion);
|
|
|
|
SMALL_RECT readRegion = originalWriteRegion;
|
|
const auto originalReadRegion = readRegion;
|
|
CHAR_INFO readCell;
|
|
|
|
CHAR_INFO expectedCell;
|
|
expectedCell.Char.UnicodeChar = L' ';
|
|
expectedCell.Attributes = originalCell.Attributes;
|
|
WI_ClearAllFlags(expectedCell.Attributes, COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
|
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(out, &readCell, { 1, 1 }, { 0, 0 }, &readRegion));
|
|
VERIFY_ARE_EQUAL(originalReadRegion, readRegion);
|
|
|
|
VERIFY_ARE_NOT_EQUAL(originalCell, readCell);
|
|
VERIFY_ARE_EQUAL(expectedCell, readCell);
|
|
}
|
|
|
|
// This test also reflects a change in the legacy behavior (see above)
|
|
void DbcsTests::TestDbcsBisectWriteCellsBeginW()
|
|
{
|
|
const auto out = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
|
|
CONSOLE_SCREEN_BUFFER_INFOEX info = { 0 };
|
|
info.cbSize = sizeof(info);
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(out, &info));
|
|
|
|
CHAR_INFO originalCell;
|
|
originalCell.Char.UnicodeChar = L'\x30a2';
|
|
originalCell.Attributes = COMMON_LVB_TRAILING_BYTE | FOREGROUND_RED;
|
|
|
|
SMALL_RECT writeRegion;
|
|
writeRegion.Top = 0;
|
|
writeRegion.Bottom = 0;
|
|
writeRegion.Left = 0;
|
|
writeRegion.Right = 0;
|
|
const auto originalWriteRegion = writeRegion;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(out, &originalCell, { 1, 1 }, { 0, 0 }, &writeRegion));
|
|
VERIFY_ARE_EQUAL(originalWriteRegion, writeRegion);
|
|
|
|
SMALL_RECT readRegion = originalWriteRegion;
|
|
const auto originalReadRegion = readRegion;
|
|
CHAR_INFO readCell;
|
|
|
|
CHAR_INFO expectedCell;
|
|
expectedCell.Char.UnicodeChar = L' ';
|
|
expectedCell.Attributes = originalCell.Attributes;
|
|
WI_ClearAllFlags(expectedCell.Attributes, COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
|
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(out, &readCell, { 1, 1 }, { 0, 0 }, &readRegion));
|
|
VERIFY_ARE_EQUAL(originalReadRegion, readRegion);
|
|
|
|
VERIFY_ARE_NOT_EQUAL(originalCell, readCell);
|
|
VERIFY_ARE_EQUAL(expectedCell, readCell);
|
|
}
|
|
|
|
void DbcsTests::TestDbcsBisectWriteCellsEndA()
|
|
{
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleCP(JAPANESE_CP));
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleOutputCP(JAPANESE_CP));
|
|
|
|
const auto out = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
|
|
CONSOLE_SCREEN_BUFFER_INFOEX info = { 0 };
|
|
info.cbSize = sizeof(info);
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(out, &info));
|
|
|
|
CHAR_INFO originalCell;
|
|
originalCell.Char.AsciiChar = '\x82';
|
|
originalCell.Attributes = COMMON_LVB_LEADING_BYTE | FOREGROUND_RED;
|
|
|
|
SMALL_RECT writeRegion;
|
|
writeRegion.Top = 0;
|
|
writeRegion.Bottom = 0;
|
|
writeRegion.Left = info.dwSize.X - 1;
|
|
writeRegion.Right = info.dwSize.X - 1;
|
|
const auto originalWriteRegion = writeRegion;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputA(out, &originalCell, { 1, 1 }, { 0, 0 }, &writeRegion));
|
|
VERIFY_ARE_EQUAL(originalWriteRegion, writeRegion);
|
|
|
|
SMALL_RECT readRegion = originalWriteRegion;
|
|
const auto originalReadRegion = readRegion;
|
|
CHAR_INFO readCell;
|
|
|
|
CHAR_INFO expectedCell;
|
|
expectedCell.Char.UnicodeChar = L' ';
|
|
expectedCell.Attributes = originalCell.Attributes;
|
|
WI_ClearAllFlags(expectedCell.Attributes, COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
|
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputA(out, &readCell, { 1, 1 }, { 0, 0 }, &readRegion));
|
|
VERIFY_ARE_EQUAL(originalReadRegion, readRegion);
|
|
|
|
VERIFY_ARE_NOT_EQUAL(originalCell, readCell);
|
|
VERIFY_ARE_EQUAL(expectedCell, readCell);
|
|
}
|
|
|
|
// This test maintains the legacy behavior for the 932 A codepage route.
|
|
void DbcsTests::TestDbcsBisectWriteCellsBeginA()
|
|
{
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleCP(JAPANESE_CP));
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleOutputCP(JAPANESE_CP));
|
|
|
|
const auto out = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
|
|
CONSOLE_SCREEN_BUFFER_INFOEX info = { 0 };
|
|
info.cbSize = sizeof(info);
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(out, &info));
|
|
|
|
CHAR_INFO originalCell;
|
|
originalCell.Char.AsciiChar = '\xA9';
|
|
originalCell.Attributes = COMMON_LVB_TRAILING_BYTE | FOREGROUND_RED;
|
|
|
|
SMALL_RECT writeRegion;
|
|
writeRegion.Top = 0;
|
|
writeRegion.Bottom = 0;
|
|
writeRegion.Left = 0;
|
|
writeRegion.Right = 0;
|
|
const auto originalWriteRegion = writeRegion;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputA(out, &originalCell, { 1, 1 }, { 0, 0 }, &writeRegion));
|
|
VERIFY_ARE_EQUAL(originalWriteRegion, writeRegion);
|
|
|
|
SMALL_RECT readRegion = originalWriteRegion;
|
|
const auto originalReadRegion = readRegion;
|
|
CHAR_INFO readCell;
|
|
|
|
CHAR_INFO expectedCell;
|
|
expectedCell.Char.UnicodeChar = L'\xffff';
|
|
expectedCell.Char.AsciiChar = originalCell.Char.AsciiChar;
|
|
expectedCell.Attributes = originalCell.Attributes;
|
|
WI_ClearAllFlags(expectedCell.Attributes, COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
|
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputA(out, &readCell, { 1, 1 }, { 0, 0 }, &readRegion));
|
|
VERIFY_ARE_EQUAL(originalReadRegion, readRegion);
|
|
|
|
VERIFY_ARE_NOT_EQUAL(originalCell, readCell);
|
|
VERIFY_ARE_EQUAL(expectedCell, readCell);
|
|
}
|
|
|
|
struct MultibyteInputData
|
|
{
|
|
PCWSTR pwszInputText;
|
|
PCSTR pszExpectedText;
|
|
};
|
|
|
|
// clang-format off
|
|
const MultibyteInputData MultibyteTestDataSet[] = {
|
|
{ L"\x3042", "\x82\xa0" },
|
|
{ L"\x3042" L"3", "\x82\xa0\x33" },
|
|
{ L"3" L"\x3042", "\x33\x82\xa0" },
|
|
{ L"3" L"\x3042" L"\x3044", "\x33\x82\xa0\x82\xa2" },
|
|
{ L"3" L"\x3042" L"\x3044" L"\x3042", "\x33\x82\xa0\x82\xa2\x82\xa0" },
|
|
{ L"3" L"\x3042" L"\x3044" L"\x3042" L"\x3044", "\x33\x82\xa0\x82\xa2\x82\xa0\x82\xa2" },
|
|
};
|
|
// clang-format on
|
|
|
|
void WriteStringToInput(HANDLE hIn, PCWSTR pwszString)
|
|
{
|
|
size_t const cchString = wcslen(pwszString);
|
|
size_t const cRecords = cchString * 2; // We need double the input records for button down then button up.
|
|
|
|
INPUT_RECORD* const irString = new INPUT_RECORD[cRecords];
|
|
VERIFY_IS_NOT_NULL(irString);
|
|
|
|
for (size_t i = 0; i < cRecords; i++)
|
|
{
|
|
irString[i].EventType = KEY_EVENT;
|
|
irString[i].Event.KeyEvent.bKeyDown = (i % 2 == 0) ? TRUE : FALSE;
|
|
irString[i].Event.KeyEvent.dwControlKeyState = 0;
|
|
irString[i].Event.KeyEvent.uChar.UnicodeChar = pwszString[i / 2];
|
|
irString[i].Event.KeyEvent.wRepeatCount = 1;
|
|
irString[i].Event.KeyEvent.wVirtualKeyCode = 0;
|
|
irString[i].Event.KeyEvent.wVirtualScanCode = 0;
|
|
}
|
|
|
|
DWORD dwWritten;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInputW(hIn, irString, (DWORD)cRecords, &dwWritten));
|
|
|
|
VERIFY_ARE_EQUAL(cRecords, dwWritten, L"We should have written the number of records that were sent in by our buffer.");
|
|
|
|
delete[] irString;
|
|
}
|
|
|
|
void ReadStringWithGetCh(PCSTR pszExpectedText)
|
|
{
|
|
size_t const cchString = strlen(pszExpectedText);
|
|
|
|
for (size_t i = 0; i < cchString; i++)
|
|
{
|
|
if (!VERIFY_ARE_EQUAL((BYTE)pszExpectedText[i], _getch()))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ReadStringWithReadConsoleInputAHelper(HANDLE hIn, PCSTR pszExpectedText, size_t cbBuffer)
|
|
{
|
|
Log::Comment(String().Format(L" = Attempting to read back the text with a %d record length buffer. =", cbBuffer));
|
|
|
|
// Find out how many bytes we need to read.
|
|
size_t const cchExpectedText = strlen(pszExpectedText);
|
|
|
|
// Increment read buffer of the size we were told.
|
|
INPUT_RECORD* const irRead = new INPUT_RECORD[cbBuffer];
|
|
VERIFY_IS_NOT_NULL(irRead);
|
|
|
|
// Loop reading and comparing until we've read enough times to get all the text we expect.
|
|
size_t cchRead = 0;
|
|
|
|
while (cchRead < cchExpectedText)
|
|
{
|
|
// expected read is either the size of the buffer or the number of characters remaining, whichever is smaller.
|
|
DWORD const dwReadExpected = (DWORD)std::min(cbBuffer, cchExpectedText - cchRead);
|
|
|
|
DWORD dwRead;
|
|
if (!VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleInputA(hIn, irRead, (DWORD)cbBuffer, &dwRead), L"Attempt to read input into buffer."))
|
|
{
|
|
break;
|
|
}
|
|
|
|
VERIFY_IS_GREATER_THAN_OR_EQUAL(dwRead, (DWORD)0, L"Verify we read non-negative bytes.");
|
|
|
|
for (size_t i = 0; i < dwRead; i++)
|
|
{
|
|
// We might read more events than the ones we're looking for because some other type of event was
|
|
// inserted into the queue by outside action. Only look at the key down events.
|
|
if (irRead[i].EventType == KEY_EVENT &&
|
|
irRead[i].Event.KeyEvent.bKeyDown == TRUE)
|
|
{
|
|
if (!VERIFY_ARE_EQUAL((BYTE)pszExpectedText[cchRead], (BYTE)irRead[i].Event.KeyEvent.uChar.AsciiChar))
|
|
{
|
|
break;
|
|
}
|
|
cchRead++;
|
|
}
|
|
}
|
|
}
|
|
|
|
delete[] irRead;
|
|
}
|
|
|
|
void ReadStringWithReadConsoleInputA(HANDLE hIn, PCWSTR pwszWriteText, PCSTR pszExpectedText)
|
|
{
|
|
// Figure out how long the expected length is.
|
|
size_t const cchExpectedText = strlen(pszExpectedText);
|
|
|
|
// Test every buffer size variation from 1 to the size of the string.
|
|
for (size_t i = 1; i <= cchExpectedText; i++)
|
|
{
|
|
FlushConsoleInputBuffer(hIn);
|
|
WriteStringToInput(hIn, pwszWriteText);
|
|
ReadStringWithReadConsoleInputAHelper(hIn, pszExpectedText, i);
|
|
}
|
|
}
|
|
|
|
void DbcsTests::TestMultibyteInputRetrieval()
|
|
{
|
|
SetConsoleCP(932);
|
|
|
|
UINT dwCP = GetConsoleCP();
|
|
if (!VERIFY_ARE_EQUAL(JAPANESE_CP, dwCP, L"Ensure input codepage is Japanese."))
|
|
{
|
|
return;
|
|
}
|
|
|
|
HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
|
|
if (!VERIFY_ARE_NOT_EQUAL(INVALID_HANDLE_VALUE, hIn, L"Get input handle."))
|
|
{
|
|
return;
|
|
}
|
|
|
|
size_t const cDataSet = ARRAYSIZE(MultibyteTestDataSet);
|
|
|
|
// for each item in our test data set...
|
|
for (size_t i = 0; i < cDataSet; i++)
|
|
{
|
|
MultibyteInputData data = MultibyteTestDataSet[i];
|
|
|
|
Log::Comment(String().Format(L"=== TEST #%d ===", i));
|
|
Log::Comment(String().Format(L"=== Input '%ws' ===", data.pwszInputText));
|
|
|
|
// test by writing the string and reading back the _getch way.
|
|
Log::Comment(L" == SUBTEST A: Use _getch to retrieve. == ");
|
|
FlushConsoleInputBuffer(hIn);
|
|
WriteStringToInput(hIn, data.pwszInputText);
|
|
ReadStringWithGetCh(data.pszExpectedText);
|
|
|
|
// test by writing the string and reading back with variable length buffers the ReadConsoleInputA way.
|
|
Log::Comment(L" == SUBTEST B: Use ReadConsoleInputA with variable length buffers to retrieve. == ");
|
|
ReadStringWithReadConsoleInputA(hIn, data.pwszInputText, data.pszExpectedText);
|
|
}
|
|
|
|
FlushConsoleInputBuffer(hIn);
|
|
}
|
|
|
|
void DbcsTests::TestDbcsOneByOne()
|
|
{
|
|
HANDLE const hOut = GetStdOutputHandle();
|
|
VERIFY_IS_NOT_NULL(hOut, L"Verify output handle is valid.");
|
|
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleOutputCP(936), L"Ensure output codepage is set to Simplified Chinese 936.");
|
|
|
|
// This is Unicode characters U+6D4B U+8BD5 U+4E2D U+6587 in Simplified Chinese Codepage 936.
|
|
// The English translation is "Test Chinese".
|
|
// We write the bytes in hex to prevent storage/interpretation issues by the source control and compiler.
|
|
char test[] = "\xb2\xe2\xca\xd4\xd6\xd0\xce\xc4";
|
|
|
|
// Prepare structures for readback.
|
|
COORD coordReadPos = { 0 };
|
|
DWORD const cchReadBack = 2u;
|
|
char chReadBack[2];
|
|
DWORD dwReadOrWritten = 0u;
|
|
|
|
for (size_t i = 0; i < strlen(test); i++)
|
|
{
|
|
bool const fIsLeadByte = (i % 2 == 0);
|
|
Log::Comment(fIsLeadByte ? L"Writing lead byte." : L"Writing trailing byte.");
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleA(hOut, &(test[i]), 1u, &dwReadOrWritten, nullptr));
|
|
VERIFY_ARE_EQUAL(1u, dwReadOrWritten, L"Verify the byte was reported written.");
|
|
|
|
dwReadOrWritten = 0;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputCharacterA(hOut, chReadBack, cchReadBack, coordReadPos, &dwReadOrWritten), L"Read back character.");
|
|
if (fIsLeadByte)
|
|
{
|
|
Log::Comment(L"Characters should be empty (space) because we only wrote a lead. It should be held for later.");
|
|
VERIFY_ARE_EQUAL((unsigned char)' ', (unsigned char)chReadBack[0]);
|
|
VERIFY_ARE_EQUAL((unsigned char)' ', (unsigned char)chReadBack[1]);
|
|
}
|
|
else
|
|
{
|
|
Log::Comment(L"After trailing is written, character should be valid from Chinese plane (not checking exactly, just that it was composed.");
|
|
VERIFY_IS_LESS_THAN((unsigned char)'\x80', (unsigned char)chReadBack[0]);
|
|
VERIFY_IS_LESS_THAN((unsigned char)'\x80', (unsigned char)chReadBack[1]);
|
|
coordReadPos.X += 2; // advance X for next read back. Move 2 positions because it's a wide char.
|
|
}
|
|
}
|
|
}
|
|
|
|
void DbcsTests::TestDbcsTrailLead()
|
|
{
|
|
HANDLE const hOut = GetStdOutputHandle();
|
|
VERIFY_IS_NOT_NULL(hOut, L"Verify output handle is valid.");
|
|
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleOutputCP(936), L"Ensure output codepage is set to Simplified Chinese 936.");
|
|
|
|
// This is Unicode characters U+6D4B U+8BD5 U+4E2D U+6587 in Simplified Chinese Codepage 936.
|
|
// The English translation is "Test Chinese".
|
|
// We write the bytes in hex to prevent storage/interpretation issues by the source control and compiler.
|
|
char test[] = "\xb2";
|
|
char test2[] = "\xe2\xca";
|
|
char test3[] = "\xd4\xd6\xd0\xce\xc4";
|
|
|
|
// Prepare structures for readback.
|
|
COORD const coordReadPos = { 0 };
|
|
DWORD const cchReadBack = 8u;
|
|
char chReadBack[9];
|
|
DWORD dwReadOrWritten = 0u;
|
|
DWORD cchTestLength = 0;
|
|
|
|
Log::Comment(L"1. Write lead byte only.");
|
|
cchTestLength = (DWORD)strlen(test);
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleA(hOut, test, cchTestLength, &dwReadOrWritten, nullptr), L"Write the string.");
|
|
VERIFY_ARE_EQUAL(cchTestLength, dwReadOrWritten, L"Verify all characters reported as written.");
|
|
dwReadOrWritten = 0;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputCharacterA(hOut, chReadBack, 2, coordReadPos, &dwReadOrWritten), L"Read back buffer.");
|
|
Log::Comment(L"Verify nothing is written/displayed yet. The read byte should have been consumed/stored but not yet displayed.");
|
|
VERIFY_ARE_EQUAL((unsigned char)' ', (unsigned char)chReadBack[0]);
|
|
VERIFY_ARE_EQUAL((unsigned char)' ', (unsigned char)chReadBack[1]);
|
|
|
|
Log::Comment(L"2. Write trailing and next lead.");
|
|
cchTestLength = (DWORD)strlen(test2);
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleA(hOut, test2, cchTestLength, &dwReadOrWritten, nullptr), L"Write the string.");
|
|
VERIFY_ARE_EQUAL(cchTestLength, dwReadOrWritten, L"Verify all characters reported as written.");
|
|
dwReadOrWritten = 0;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputCharacterA(hOut, chReadBack, 4, coordReadPos, &dwReadOrWritten), L"Read back buffer.");
|
|
Log::Comment(L"Verify previous lead and the trailing we just wrote formed a character. The final lead should have been consumed/stored and not yet displayed.");
|
|
VERIFY_ARE_EQUAL((unsigned char)test[0], (unsigned char)chReadBack[0]);
|
|
VERIFY_ARE_EQUAL((unsigned char)test2[0], (unsigned char)chReadBack[1]);
|
|
VERIFY_ARE_EQUAL((unsigned char)' ', (unsigned char)chReadBack[2]);
|
|
VERIFY_ARE_EQUAL((unsigned char)' ', (unsigned char)chReadBack[3]);
|
|
|
|
Log::Comment(L"3. Write trailing and finish string.");
|
|
cchTestLength = (DWORD)strlen(test3);
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleA(hOut, test3, cchTestLength, &dwReadOrWritten, nullptr), L"Write the string.");
|
|
VERIFY_ARE_EQUAL(cchTestLength, dwReadOrWritten, L"Verify all characters reported as written.");
|
|
dwReadOrWritten = 0;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputCharacterA(hOut, chReadBack, cchReadBack, coordReadPos, &dwReadOrWritten), L"Read back buffer.");
|
|
Log::Comment(L"Verify everything is displayed now that we've finished it off with the final trailing and rest of the string.");
|
|
VERIFY_ARE_EQUAL((unsigned char)test[0], (unsigned char)chReadBack[0]);
|
|
VERIFY_ARE_EQUAL((unsigned char)test2[0], (unsigned char)chReadBack[1]);
|
|
VERIFY_ARE_EQUAL((unsigned char)test2[1], (unsigned char)chReadBack[2]);
|
|
VERIFY_ARE_EQUAL((unsigned char)test3[0], (unsigned char)chReadBack[3]);
|
|
VERIFY_ARE_EQUAL((unsigned char)test3[1], (unsigned char)chReadBack[4]);
|
|
VERIFY_ARE_EQUAL((unsigned char)test3[2], (unsigned char)chReadBack[5]);
|
|
VERIFY_ARE_EQUAL((unsigned char)test3[3], (unsigned char)chReadBack[6]);
|
|
VERIFY_ARE_EQUAL((unsigned char)test3[4], (unsigned char)chReadBack[7]);
|
|
}
|
|
|
|
void DbcsTests::TestDbcsStdCoutScenario()
|
|
{
|
|
HANDLE const hOut = GetStdOutputHandle();
|
|
VERIFY_IS_NOT_NULL(hOut, L"Verify output handle is valid.");
|
|
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleOutputCP(936), L"Ensure output codepage is set to Simplified Chinese 936.");
|
|
|
|
// This is Unicode characters U+6D4B U+8BD5 U+4E2D U+6587 in Simplified Chinese Codepage 936.
|
|
// The English translation is "Test Chinese".
|
|
// We write the bytes in hex to prevent storage/interpretation issues by the source control and compiler.
|
|
char test[] = "\xb2\xe2\xca\xd4\xd6\xd0\xce\xc4";
|
|
Log::Comment(L"Write string using printf.");
|
|
printf("%s\n", test);
|
|
|
|
// Prepare structures for readback.
|
|
COORD coordReadPos = { 0 };
|
|
DWORD const cchReadBack = (DWORD)strlen(test);
|
|
wistd::unique_ptr<char[]> const psReadBack = wil::make_unique_failfast<char[]>(cchReadBack + 1);
|
|
DWORD dwRead = 0;
|
|
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputCharacterA(hOut, psReadBack.get(), cchReadBack, coordReadPos, &dwRead), L"Read back printf line.");
|
|
VERIFY_ARE_EQUAL(cchReadBack, dwRead, L"We should have read as many characters as we expected (length of original printed line.)");
|
|
VERIFY_ARE_EQUAL(String(test), String(psReadBack.get()), L"String should match what we wrote.");
|
|
|
|
// Clean up and move down a line for next test.
|
|
ZeroMemory(psReadBack.get(), cchReadBack);
|
|
dwRead = 0;
|
|
coordReadPos.Y++;
|
|
|
|
Log::Comment(L"Write string using std::cout.");
|
|
std::cout << test << std::endl;
|
|
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputCharacterA(hOut, psReadBack.get(), cchReadBack, coordReadPos, &dwRead), L"Read back std::cout line.");
|
|
VERIFY_ARE_EQUAL(cchReadBack, dwRead, L"We should have read as many characters as we expected (length of original printed line.)");
|
|
VERIFY_ARE_EQUAL(String(test), String(psReadBack.get()), L"String should match what we wrote.");
|
|
}
|