terminal/src/host/ft_host/API_OutputTests.cpp

1065 lines
42 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "../types/inc/viewport.hpp"
#include <thread>
#include <vector>
#include <algorithm>
using namespace Microsoft::Console::Types;
using namespace WEX::TestExecution;
using WEX::Logging::Log;
using namespace WEX::Common;
class OutputTests
{
BEGIN_TEST_CLASS(OutputTests)
TEST_CLASS_PROPERTY(L"IsolationLevel", L"Method")
END_TEST_CLASS()
TEST_CLASS_SETUP(TestSetup);
TEST_CLASS_CLEANUP(TestCleanup);
TEST_METHOD(BasicReadConsoleOutputATest);
TEST_METHOD(BasicReadConsoleOutputWTest);
TEST_METHOD(BasicWriteConsoleOutputWTest);
TEST_METHOD(BasicWriteConsoleOutputATest);
TEST_METHOD(WriteConsoleOutputWOutsideBuffer);
TEST_METHOD(WriteConsoleOutputWWithClipping);
TEST_METHOD(WriteConsoleOutputWNegativePositions);
TEST_METHOD(ReadConsoleOutputWOutsideBuffer);
TEST_METHOD(ReadConsoleOutputWWithClipping);
TEST_METHOD(ReadConsoleOutputWNegativePositions);
TEST_METHOD(ReadConsoleOutputWPartialUserBuffer);
TEST_METHOD(WriteConsoleOutputCharacterWRunoff);
TEST_METHOD(WriteConsoleOutputAttributeSimpleTest);
TEST_METHOD(WriteConsoleOutputAttributeCheckerTest);
TEST_METHOD(WriteBackspaceTest);
TEST_METHOD(WinPtyWrite);
};
bool OutputTests::TestSetup()
{
return Common::TestBufferSetup();
}
bool OutputTests::TestCleanup()
{
return Common::TestBufferCleanup();
}
void OutputTests::BasicWriteConsoleOutputWTest()
{
// Get output buffer information.
const auto consoleOutputHandle = GetStdOutputHandle();
SetConsoleActiveScreenBuffer(consoleOutputHandle);
CONSOLE_SCREEN_BUFFER_INFOEX sbiex{ 0 };
sbiex.cbSize = sizeof(sbiex);
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(consoleOutputHandle, &sbiex));
const auto bufferSize = sbiex.dwSize;
// Establish a writing region that is the width of the buffer and half the height.
const SMALL_RECT region{ 0, 0, bufferSize.X - 1, bufferSize.Y / 2 };
const COORD regionDimensions{ region.Right - region.Left + 1, region.Bottom - region.Top + 1 };
const auto regionSize = regionDimensions.X * regionDimensions.Y;
const COORD regionOrigin{ 0, 0 };
// Make a test value and fill an array (via a vector) full of it.
CHAR_INFO testValue;
testValue.Attributes = 0x3e;
testValue.Char.UnicodeChar = L' ';
std::vector<CHAR_INFO> buffer(regionSize, testValue);
// Call the API and confirm results.
SMALL_RECT affected = region;
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(consoleOutputHandle, buffer.data(), regionDimensions, regionOrigin, &affected));
VERIFY_ARE_EQUAL(region, affected);
}
void OutputTests::BasicWriteConsoleOutputATest()
{
// Get output buffer information.
const auto consoleOutputHandle = GetStdOutputHandle();
SetConsoleActiveScreenBuffer(consoleOutputHandle);
CONSOLE_SCREEN_BUFFER_INFOEX sbiex{ 0 };
sbiex.cbSize = sizeof(sbiex);
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(consoleOutputHandle, &sbiex));
const auto bufferSize = sbiex.dwSize;
// Establish a writing region that is the width of the buffer and half the height.
const SMALL_RECT region{ 0, 0, bufferSize.X - 1, bufferSize.Y / 2 };
const COORD regionDimensions{ region.Right - region.Left + 1, region.Bottom - region.Top + 1 };
const auto regionSize = regionDimensions.X * regionDimensions.Y;
const COORD regionOrigin{ 0, 0 };
// Make a test value and fill an array (via a vector) full of it.
CHAR_INFO testValue;
testValue.Attributes = 0x3e;
testValue.Char.AsciiChar = ' ';
std::vector<CHAR_INFO> buffer(regionSize, testValue);
// Call the API and confirm results.
SMALL_RECT affected = region;
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputA(consoleOutputHandle, buffer.data(), regionDimensions, regionOrigin, &affected));
VERIFY_ARE_EQUAL(region, affected);
}
void OutputTests::WriteConsoleOutputWOutsideBuffer()
{
SetVerifyOutput vf(VerifyOutputSettings::LogOnlyFailures);
// Get output buffer information.
const auto consoleOutputHandle = GetStdOutputHandle();
// OneCore systems can't adjust the window/buffer size, so we'll skip making it smaller.
// On Desktop systems, make it smaller so the test runs faster.
if (OneCoreDelay::IsIsWindowPresent())
{
SMALL_RECT window = { 0 };
window.Right = 5;
window.Bottom = 5;
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleWindowInfo(consoleOutputHandle, true, &window));
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleScreenBufferSize(consoleOutputHandle, { 20, 20 }));
}
CONSOLE_SCREEN_BUFFER_INFOEX sbiex{ 0 };
sbiex.cbSize = sizeof(sbiex);
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(consoleOutputHandle, &sbiex));
const auto bufferSize = sbiex.dwSize;
const SMALL_RECT region{ 0, 0, bufferSize.X - 1, bufferSize.Y / 2 };
const COORD regionDimensions{ region.Right - region.Left + 1, region.Bottom - region.Top + 1 };
const auto regionSize = regionDimensions.X * regionDimensions.Y;
const COORD regionOrigin{ 0, 0 };
// Make a test value and fill an array (via a vector) full of it.
CHAR_INFO testValue;
testValue.Attributes = 0x3e;
testValue.Char.UnicodeChar = L'A';
std::vector<CHAR_INFO> buffer(regionSize, testValue);
// Call the API and confirm results.
// move outside in X and Y directions
auto shiftedRegion = region;
shiftedRegion.Left += bufferSize.X;
shiftedRegion.Right += bufferSize.X;
shiftedRegion.Top += bufferSize.Y;
shiftedRegion.Bottom += bufferSize.Y;
auto affected = shiftedRegion;
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(consoleOutputHandle, buffer.data(), regionDimensions, regionOrigin, &affected));
VERIFY_ARE_EQUAL(shiftedRegion, affected);
// Read the entire buffer back and validate that we didn't write anything anywhere
const auto readBack = std::make_unique<CHAR_INFO[]>(sbiex.dwSize.X * sbiex.dwSize.Y);
SMALL_RECT readRegion = { 0 };
readRegion.Bottom = sbiex.dwSize.Y - 1;
readRegion.Right = sbiex.dwSize.X - 1;
const auto readBefore = readRegion;
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(consoleOutputHandle, readBack.get(), sbiex.dwSize, { 0, 0 }, &readRegion));
VERIFY_ARE_EQUAL(readBefore, readRegion);
for (auto row = 0; row < sbiex.dwSize.Y; row++)
{
for (auto col = 0; col < sbiex.dwSize.X; col++)
{
CHAR_INFO readItem = *(readBack.get() + (row * sbiex.dwSize.X) + col);
CHAR_INFO expectedItem;
expectedItem.Char.UnicodeChar = L' ';
expectedItem.Attributes = sbiex.wAttributes;
VERIFY_ARE_EQUAL(expectedItem, readItem);
}
}
}
void OutputTests::WriteConsoleOutputWWithClipping()
{
SetVerifyOutput vf(VerifyOutputSettings::LogOnlyFailures);
// Get output buffer information.
const auto consoleOutputHandle = GetStdOutputHandle();
// OneCore systems can't adjust the window/buffer size, so we'll skip making it smaller.
// On Desktop systems, make it smaller so the test runs faster.
if (OneCoreDelay::IsIsWindowPresent())
{
SMALL_RECT window = { 0 };
window.Right = 5;
window.Bottom = 5;
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleWindowInfo(consoleOutputHandle, true, &window));
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleScreenBufferSize(consoleOutputHandle, { 20, 20 }));
}
CONSOLE_SCREEN_BUFFER_INFOEX sbiex{ 0 };
sbiex.cbSize = sizeof(sbiex);
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(consoleOutputHandle, &sbiex));
const auto bufferSize = sbiex.dwSize;
const SMALL_RECT region{ 0, 0, bufferSize.X - 1, bufferSize.Y / 2 };
const COORD regionDimensions{ region.Right - region.Left + 1, region.Bottom - region.Top + 1 };
const auto regionSize = regionDimensions.X * regionDimensions.Y;
const COORD regionOrigin{ 0, 0 };
// Make a test value and fill an array (via a vector) full of it.
CHAR_INFO testValue;
testValue.Attributes = 0x3e;
testValue.Char.UnicodeChar = L'A';
std::vector<CHAR_INFO> buffer(regionSize, testValue);
// Move the write region to get clipped in the X and the Y dimension.
auto adjustedRegion = region;
adjustedRegion.Left += 5;
adjustedRegion.Right += 5;
adjustedRegion.Top += bufferSize.Y / 2;
adjustedRegion.Bottom += bufferSize.Y / 2;
auto expectedRegion = adjustedRegion;
expectedRegion.Left = std::max<SHORT>(0, adjustedRegion.Left);
expectedRegion.Top = std::max<SHORT>(0, adjustedRegion.Top);
expectedRegion.Right = std::min<SHORT>(bufferSize.X - 1, adjustedRegion.Right);
expectedRegion.Bottom = std::min<SHORT>(bufferSize.Y - 1, adjustedRegion.Bottom);
// Call the API and confirm results.
auto affected = adjustedRegion;
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(consoleOutputHandle, buffer.data(), regionDimensions, regionOrigin, &affected));
VERIFY_ARE_EQUAL(expectedRegion, affected);
// Read the entire buffer back and validate that we only wrote where we expected to write
const auto readBack = std::make_unique<CHAR_INFO[]>(sbiex.dwSize.X * sbiex.dwSize.Y);
SMALL_RECT readRegion = { 0 };
readRegion.Bottom = sbiex.dwSize.Y - 1;
readRegion.Right = sbiex.dwSize.X - 1;
const auto readBefore = readRegion;
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(consoleOutputHandle, readBack.get(), sbiex.dwSize, { 0, 0 }, &readRegion));
VERIFY_ARE_EQUAL(readBefore, readRegion);
for (auto row = 0; row < sbiex.dwSize.Y; row++)
{
for (auto col = 0; col < sbiex.dwSize.X; col++)
{
CHAR_INFO readItem = *(readBack.get() + (row * sbiex.dwSize.X) + col);
CHAR_INFO expectedItem;
if (affected.Top <= row && affected.Bottom >= row && affected.Left <= col && affected.Right >= col)
{
expectedItem = testValue;
}
else
{
expectedItem.Char.UnicodeChar = L' ';
expectedItem.Attributes = sbiex.wAttributes;
}
VERIFY_ARE_EQUAL(expectedItem, readItem);
}
}
}
void OutputTests::WriteConsoleOutputWNegativePositions()
{
SetVerifyOutput vf(VerifyOutputSettings::LogOnlyFailures);
// Get output buffer information.
const auto consoleOutputHandle = GetStdOutputHandle();
// OneCore systems can't adjust the window/buffer size, so we'll skip making it smaller.
// On Desktop systems, make it smaller so the test runs faster.
if (OneCoreDelay::IsIsWindowPresent())
{
SMALL_RECT window = { 0 };
window.Right = 5;
window.Bottom = 5;
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleWindowInfo(consoleOutputHandle, true, &window));
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleScreenBufferSize(consoleOutputHandle, { 20, 20 }));
}
CONSOLE_SCREEN_BUFFER_INFOEX sbiex{ 0 };
sbiex.cbSize = sizeof(sbiex);
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(consoleOutputHandle, &sbiex));
const auto bufferSize = sbiex.dwSize;
const SMALL_RECT region{ 0, 0, bufferSize.X - 1, bufferSize.Y / 2 };
const COORD regionDimensions{ region.Right - region.Left + 1, region.Bottom - region.Top + 1 };
const auto regionSize = regionDimensions.X * regionDimensions.Y;
const COORD regionOrigin{ 0, 0 };
// Make a test value and fill an array (via a vector) full of it.
CHAR_INFO testValue;
testValue.Attributes = 0x3e;
testValue.Char.UnicodeChar = L'A';
std::vector<CHAR_INFO> buffer(regionSize, testValue);
// Call the API and confirm results.
// Move the write region to negative values in the X and Y dimension
auto adjustedRegion = region;
adjustedRegion.Left -= 3;
adjustedRegion.Right -= 3;
adjustedRegion.Top -= 10;
adjustedRegion.Bottom -= 10;
auto expectedRegion = adjustedRegion;
expectedRegion.Left = std::max<SHORT>(0, adjustedRegion.Left);
expectedRegion.Top = std::max<SHORT>(0, adjustedRegion.Top);
expectedRegion.Right = std::min<SHORT>(bufferSize.X - 1, adjustedRegion.Right);
expectedRegion.Bottom = std::min<SHORT>(bufferSize.Y - 1, adjustedRegion.Bottom);
// Call the API and confirm results.
auto affected = adjustedRegion;
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(consoleOutputHandle, buffer.data(), regionDimensions, regionOrigin, &affected));
VERIFY_ARE_EQUAL(expectedRegion, affected);
// Read the entire buffer back and validate that we only wrote where we expected to write
const auto readBack = std::make_unique<CHAR_INFO[]>(sbiex.dwSize.X * sbiex.dwSize.Y);
SMALL_RECT readRegion = { 0 };
readRegion.Bottom = sbiex.dwSize.Y - 1;
readRegion.Right = sbiex.dwSize.X - 1;
const auto readBefore = readRegion;
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(consoleOutputHandle, readBack.get(), sbiex.dwSize, { 0, 0 }, &readRegion));
VERIFY_ARE_EQUAL(readBefore, readRegion);
for (auto row = 0; row < sbiex.dwSize.Y; row++)
{
for (auto col = 0; col < sbiex.dwSize.X; col++)
{
CHAR_INFO readItem = *(readBack.get() + (row * sbiex.dwSize.X) + col);
CHAR_INFO expectedItem;
if (affected.Top <= row && affected.Bottom >= row && affected.Left <= col && affected.Right >= col)
{
expectedItem = testValue;
}
else
{
expectedItem.Char.UnicodeChar = L' ';
expectedItem.Attributes = sbiex.wAttributes;
}
VERIFY_ARE_EQUAL(expectedItem, readItem);
}
}
// Set the region so the left will end up past the right
adjustedRegion = region;
adjustedRegion.Left = -(adjustedRegion.Right + 1);
affected = adjustedRegion;
VERIFY_WIN32_BOOL_FAILED(WriteConsoleOutputW(consoleOutputHandle, buffer.data(), regionDimensions, regionOrigin, &affected));
}
void OutputTests::WriteConsoleOutputCharacterWRunoff()
{
// writes text that will not all fit on the screen to verify reported size is correct
const auto consoleOutputHandle = GetStdOutputHandle();
SetConsoleActiveScreenBuffer(consoleOutputHandle);
CONSOLE_SCREEN_BUFFER_INFOEX sbiex{ 0 };
sbiex.cbSize = sizeof(sbiex);
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(consoleOutputHandle, &sbiex));
const auto bufferSize = sbiex.dwSize;
COORD target{ bufferSize.X - 1, bufferSize.Y - 1 };
const std::wstring text = L"hello";
DWORD charsWritten = 0;
VERIFY_SUCCEEDED(WriteConsoleOutputCharacterW(consoleOutputHandle,
text.c_str(),
gsl::narrow<DWORD>(text.size()),
target,
&charsWritten));
VERIFY_ARE_EQUAL(charsWritten, 1u);
}
void OutputTests::WriteConsoleOutputAttributeSimpleTest()
{
// Get output buffer information.
const auto consoleOutputHandle = GetStdOutputHandle();
SetConsoleActiveScreenBuffer(consoleOutputHandle);
const DWORD size = 500;
const WORD setAttr = FOREGROUND_BLUE | BACKGROUND_RED;
const COORD coord{ 0, 0 };
DWORD attrsWritten = 0;
WORD attributes[size];
std::fill_n(attributes, size, setAttr);
// write some attribute changes
VERIFY_SUCCEEDED(WriteConsoleOutputAttribute(consoleOutputHandle, attributes, size, coord, &attrsWritten));
VERIFY_ARE_EQUAL(attrsWritten, size);
// confirm change happened
WORD resultAttrs[size];
DWORD attrsRead = 0;
VERIFY_SUCCEEDED(ReadConsoleOutputAttribute(consoleOutputHandle, resultAttrs, size, coord, &attrsRead));
VERIFY_ARE_EQUAL(attrsRead, size);
for (size_t i = 0; i < size; ++i)
{
VERIFY_ARE_EQUAL(attributes[i], resultAttrs[i]);
}
}
void OutputTests::WriteConsoleOutputAttributeCheckerTest()
{
// writes a red/green checkerboard pattern on top of some text and makes sure that the text and color attr
// changes roundtrip properly through the API
// Get output buffer information.
const auto consoleOutputHandle = GetStdOutputHandle();
SetConsoleActiveScreenBuffer(consoleOutputHandle);
CONSOLE_SCREEN_BUFFER_INFOEX sbiex{ 0 };
sbiex.cbSize = sizeof(sbiex);
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(consoleOutputHandle, &sbiex));
const auto bufferSize = sbiex.dwSize;
const WORD red = BACKGROUND_RED;
const WORD green = BACKGROUND_GREEN;
const DWORD height = 8;
const DWORD width = bufferSize.X;
// todo verify less than or equal to buffer size ^^^
const DWORD size = width * height;
std::unique_ptr<WORD[]> attrs = std::make_unique<WORD[]>(size);
std::generate(attrs.get(), attrs.get() + size, [=]() {
static int i = 0;
return i++ % 2 == 0 ? red : green;
});
// write text
const COORD coord{ 0, 0 };
DWORD charsWritten = 0;
std::unique_ptr<wchar_t[]> wchs = std::make_unique<wchar_t[]>(size);
std::fill_n(wchs.get(), size, L'*');
VERIFY_SUCCEEDED(WriteConsoleOutputCharacter(consoleOutputHandle, wchs.get(), size, coord, &charsWritten));
VERIFY_ARE_EQUAL(charsWritten, size);
// write attribute changes
DWORD attrsWritten = 0;
VERIFY_SUCCEEDED(WriteConsoleOutputAttribute(consoleOutputHandle, attrs.get(), size, coord, &attrsWritten));
VERIFY_ARE_EQUAL(attrsWritten, size);
// get changed attributes
std::unique_ptr<WORD[]> resultAttrs = std::make_unique<WORD[]>(size);
DWORD attrsRead = 0;
VERIFY_SUCCEEDED(ReadConsoleOutputAttribute(consoleOutputHandle, resultAttrs.get(), size, coord, &attrsRead));
VERIFY_ARE_EQUAL(attrsRead, size);
// get text
std::unique_ptr<wchar_t[]> resultWchs = std::make_unique<wchar_t[]>(size);
DWORD charsRead = 0;
VERIFY_SUCCEEDED(ReadConsoleOutputCharacter(consoleOutputHandle, resultWchs.get(), size, coord, &charsRead));
VERIFY_ARE_EQUAL(charsRead, size);
// confirm that attributes were set without affecting text
for (size_t i = 0; i < size; ++i)
{
VERIFY_ARE_EQUAL(attrs[i], resultAttrs[i]);
VERIFY_ARE_EQUAL(wchs[i], resultWchs[i]);
}
}
void OutputTests::WriteBackspaceTest()
{
// Get output buffer information.
const auto hOut = GetStdOutputHandle();
Log::Comment(NoThrowString().Format(
L"Outputting \"\\b \\b\" should behave the same as \"\b\", \" \", \"\b\" in separate WriteConsoleW calls."));
DWORD n = 0;
CONSOLE_SCREEN_BUFFER_INFO csbi = { 0 };
COORD c = { 0, 0 };
VERIFY_SUCCEEDED(SetConsoleCursorPosition(hOut, c));
VERIFY_SUCCEEDED(WriteConsoleW(hOut, L"GoodX", 5, &n, nullptr));
VERIFY_SUCCEEDED(GetConsoleScreenBufferInfo(hOut, &csbi));
VERIFY_ARE_EQUAL(csbi.dwCursorPosition.X, 5);
VERIFY_ARE_EQUAL(csbi.dwCursorPosition.Y, 0);
VERIFY_SUCCEEDED(WriteConsoleW(hOut, L"\b", 1, &n, nullptr));
VERIFY_SUCCEEDED(WriteConsoleW(hOut, L" ", 1, &n, nullptr));
VERIFY_SUCCEEDED(WriteConsoleW(hOut, L"\b", 1, &n, nullptr));
VERIFY_SUCCEEDED(GetConsoleScreenBufferInfo(hOut, &csbi));
VERIFY_ARE_EQUAL(csbi.dwCursorPosition.X, 4);
VERIFY_ARE_EQUAL(csbi.dwCursorPosition.Y, 0);
VERIFY_SUCCEEDED(WriteConsoleW(hOut, L"\n", 1, &n, nullptr));
VERIFY_SUCCEEDED(GetConsoleScreenBufferInfo(hOut, &csbi));
VERIFY_ARE_EQUAL(csbi.dwCursorPosition.X, 0);
VERIFY_ARE_EQUAL(csbi.dwCursorPosition.Y, 1);
VERIFY_SUCCEEDED(WriteConsoleW(hOut, L"badX", 4, &n, nullptr));
VERIFY_SUCCEEDED(GetConsoleScreenBufferInfo(hOut, &csbi));
VERIFY_ARE_EQUAL(csbi.dwCursorPosition.X, 4);
VERIFY_ARE_EQUAL(csbi.dwCursorPosition.Y, 1);
VERIFY_SUCCEEDED(WriteConsoleW(hOut, L"\b \b", 3, &n, nullptr));
VERIFY_SUCCEEDED(GetConsoleScreenBufferInfo(hOut, &csbi));
VERIFY_ARE_EQUAL(csbi.dwCursorPosition.X, 3);
VERIFY_ARE_EQUAL(csbi.dwCursorPosition.Y, 1);
}
void OutputTests::BasicReadConsoleOutputATest()
{
SetVerifyOutput vf(VerifyOutputSettings::LogOnlyFailures);
// Get output buffer information.
const auto consoleOutputHandle = GetStdOutputHandle();
SetConsoleActiveScreenBuffer(consoleOutputHandle);
CONSOLE_SCREEN_BUFFER_INFOEX sbiex{ 0 };
sbiex.cbSize = sizeof(sbiex);
// Get buffer information
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(consoleOutputHandle, &sbiex));
const auto bufferSize = sbiex.dwSize;
const auto bufferLength = bufferSize.X * bufferSize.Y;
// Establish a reading region that is the width of the buffer and half the height.
const SMALL_RECT region{ 0, 0, bufferSize.X - 1, bufferSize.Y / 2 };
const COORD regionDimensions{ region.Right - region.Left + 1, region.Bottom - region.Top + 1 };
const auto regionSize = regionDimensions.X * regionDimensions.Y;
const COORD regionOrigin{ 0, 0 };
// Fill buffer with some data to read back.
CHAR_INFO ciFill = { 0 };
ciFill.Char.AsciiChar = 'A';
ciFill.Attributes = FOREGROUND_RED;
DWORD written = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputCharacterA(consoleOutputHandle, ciFill.Char.AsciiChar, bufferLength, { 0, 0 }, &written));
VERIFY_ARE_EQUAL(static_cast<DWORD>(bufferLength), written);
written = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputAttribute(consoleOutputHandle, ciFill.Attributes, bufferLength, { 0, 0 }, &written));
VERIFY_ARE_EQUAL(static_cast<DWORD>(bufferLength), written);
// Make an array that can hold the output
std::vector<CHAR_INFO> buffer(regionSize);
// Call the API and confirm results
SMALL_RECT affected = region;
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputA(consoleOutputHandle, buffer.data(), regionDimensions, regionOrigin, &affected));
VERIFY_ARE_EQUAL(region, affected);
// Verify that all the data read matches what was expected.
for (const auto& ci : buffer)
{
VERIFY_ARE_EQUAL(ciFill, ci);
}
}
void OutputTests::BasicReadConsoleOutputWTest()
{
SetVerifyOutput vf(VerifyOutputSettings::LogOnlyFailures);
// Get output buffer information.
const auto consoleOutputHandle = GetStdOutputHandle();
SetConsoleActiveScreenBuffer(consoleOutputHandle);
CONSOLE_SCREEN_BUFFER_INFOEX sbiex{ 0 };
sbiex.cbSize = sizeof(sbiex);
// Get buffer information
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(consoleOutputHandle, &sbiex));
const auto bufferSize = sbiex.dwSize;
const auto bufferLength = bufferSize.X * bufferSize.Y;
// Establish a reading region that is the width of the buffer and half the height.
const SMALL_RECT region{ 0, 0, bufferSize.X - 1, bufferSize.Y / 2 };
const COORD regionDimensions{ region.Right - region.Left + 1, region.Bottom - region.Top + 1 };
const auto regionSize = regionDimensions.X * regionDimensions.Y;
const COORD regionOrigin{ 0, 0 };
// Fill buffer with some data to read back.
CHAR_INFO ciFill = { 0 };
ciFill.Char.UnicodeChar = L'Z';
ciFill.Attributes = FOREGROUND_RED;
DWORD written = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputCharacterW(consoleOutputHandle, ciFill.Char.AsciiChar, bufferLength, { 0, 0 }, &written));
VERIFY_ARE_EQUAL(static_cast<DWORD>(bufferLength), written);
written = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputAttribute(consoleOutputHandle, ciFill.Attributes, bufferLength, { 0, 0 }, &written));
VERIFY_ARE_EQUAL(static_cast<DWORD>(bufferLength), written);
// Make an array that can hold the output
std::vector<CHAR_INFO> buffer(regionSize);
// Call the API and confirm results
SMALL_RECT affected = region;
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(consoleOutputHandle, buffer.data(), regionDimensions, regionOrigin, &affected));
VERIFY_ARE_EQUAL(region, affected);
// Verify that all the data read matches what was expected.
for (const auto& ci : buffer)
{
VERIFY_ARE_EQUAL(ciFill, ci);
}
}
void OutputTests::ReadConsoleOutputWOutsideBuffer()
{
SetVerifyOutput vf(VerifyOutputSettings::LogOnlyFailures);
// Get output buffer information.
const auto consoleOutputHandle = GetStdOutputHandle();
SetConsoleActiveScreenBuffer(consoleOutputHandle);
// OneCore systems can't adjust the window/buffer size, so we'll skip making it smaller.
// On Desktop systems, make it smaller so the test runs faster.
if (OneCoreDelay::IsIsWindowPresent())
{
SMALL_RECT window = { 0 };
window.Right = 5;
window.Bottom = 5;
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleWindowInfo(consoleOutputHandle, true, &window));
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleScreenBufferSize(consoleOutputHandle, { 20, 20 }));
}
CONSOLE_SCREEN_BUFFER_INFOEX sbiex{ 0 };
sbiex.cbSize = sizeof(sbiex);
// Get buffer information
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(consoleOutputHandle, &sbiex));
const auto bufferSize = sbiex.dwSize;
const auto bufferLength = bufferSize.X * bufferSize.Y;
// Establish a reading region that is the width of the buffer and half the height.
const SMALL_RECT region{ 0, 0, bufferSize.X - 1, bufferSize.Y / 2 };
const COORD regionDimensions{ region.Right - region.Left + 1, region.Bottom - region.Top + 1 };
const auto regionSize = regionDimensions.X * regionDimensions.Y;
const COORD regionOrigin{ 0, 0 };
// Fill buffer with some data to read back.
CHAR_INFO ciFill = { 0 };
ciFill.Char.UnicodeChar = L'Z';
ciFill.Attributes = FOREGROUND_RED;
DWORD written = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputCharacterW(consoleOutputHandle, ciFill.Char.AsciiChar, bufferLength, { 0, 0 }, &written));
VERIFY_ARE_EQUAL(static_cast<DWORD>(bufferLength), written);
written = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputAttribute(consoleOutputHandle, ciFill.Attributes, bufferLength, { 0, 0 }, &written));
VERIFY_ARE_EQUAL(static_cast<DWORD>(bufferLength), written);
// Make a buffer to hold the read data
const CHAR_INFO ciEmpty = { 0 };
std::vector<CHAR_INFO> buffer(regionSize, ciEmpty);
// Try to read completely outside the buffer.
auto shiftedRegion = region;
shiftedRegion.Left += bufferSize.X;
shiftedRegion.Right += bufferSize.X;
shiftedRegion.Top += bufferSize.Y;
shiftedRegion.Bottom += bufferSize.Y;
auto expectedRegion = shiftedRegion;
expectedRegion.Right = expectedRegion.Left - 1;
expectedRegion.Bottom = expectedRegion.Top - 1;
auto affected = shiftedRegion;
VERIFY_WIN32_BOOL_FAILED(ReadConsoleOutputW(consoleOutputHandle, buffer.data(), regionDimensions, regionOrigin, &affected));
VERIFY_ARE_EQUAL(expectedRegion, affected);
// Verify that all the data read matches what was expected.
for (const auto& ci : buffer)
{
VERIFY_ARE_EQUAL(ciEmpty, ci);
}
}
void OutputTests::ReadConsoleOutputWWithClipping()
{
SetVerifyOutput vf(VerifyOutputSettings::LogOnlyFailures);
// Get output buffer information.
const auto consoleOutputHandle = GetStdOutputHandle();
SetConsoleActiveScreenBuffer(consoleOutputHandle);
// OneCore systems can't adjust the window/buffer size, so we'll skip making it smaller.
// On Desktop systems, make it smaller so the test runs faster.
if (OneCoreDelay::IsIsWindowPresent())
{
SMALL_RECT window = { 0 };
window.Right = 5;
window.Bottom = 5;
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleWindowInfo(consoleOutputHandle, true, &window));
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleScreenBufferSize(consoleOutputHandle, { 20, 20 }));
}
CONSOLE_SCREEN_BUFFER_INFOEX sbiex{ 0 };
sbiex.cbSize = sizeof(sbiex);
// Get buffer information
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(consoleOutputHandle, &sbiex));
const auto bufferSize = sbiex.dwSize;
const auto bufferLength = bufferSize.X * bufferSize.Y;
// Establish a reading region that is the width of the buffer and half the height.
const SMALL_RECT region{ 0, 0, bufferSize.X - 1, bufferSize.Y / 2 };
const COORD regionDimensions{ region.Right - region.Left + 1, region.Bottom - region.Top + 1 };
const auto regionSize = regionDimensions.X * regionDimensions.Y;
const COORD regionOrigin{ 0, 0 };
// Fill buffer with some data to read back.
CHAR_INFO ciFill = { 0 };
ciFill.Char.UnicodeChar = L'Z';
ciFill.Attributes = FOREGROUND_RED;
DWORD written = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputCharacterW(consoleOutputHandle, ciFill.Char.AsciiChar, bufferLength, { 0, 0 }, &written));
VERIFY_ARE_EQUAL(static_cast<DWORD>(bufferLength), written);
written = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputAttribute(consoleOutputHandle, ciFill.Attributes, bufferLength, { 0, 0 }, &written));
VERIFY_ARE_EQUAL(static_cast<DWORD>(bufferLength), written);
// Make a buffer to hold the read data
CHAR_INFO ciEmpty;
ciEmpty.Char.UnicodeChar = L'A';
ciEmpty.Attributes = BACKGROUND_BLUE;
std::vector<CHAR_INFO> buffer(regionSize, ciEmpty);
// Move the write region to get clipped in the X and the Y dimension.
auto adjustedRegion = region;
adjustedRegion.Left += 5;
adjustedRegion.Right += 5;
adjustedRegion.Top += bufferSize.Y / 2;
adjustedRegion.Bottom += bufferSize.Y / 2;
auto expectedRegion = adjustedRegion;
expectedRegion.Left = std::max<SHORT>(0, adjustedRegion.Left);
expectedRegion.Top = std::max<SHORT>(0, adjustedRegion.Top);
expectedRegion.Right = std::min<SHORT>(bufferSize.X - 1, adjustedRegion.Right);
expectedRegion.Bottom = std::min<SHORT>(bufferSize.Y - 1, adjustedRegion.Bottom);
// Call the API and confirm results.
// NOTE: We expect this to be broken for v1. It's always been wrong there (returning a clipped count of bytes instead of the whole rectangle).
auto affected = adjustedRegion;
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(consoleOutputHandle, buffer.data(), regionDimensions, regionOrigin, &affected));
VERIFY_ARE_EQUAL(expectedRegion, affected);
const auto affectedViewport = Viewport::FromInclusive(affected);
const auto filledBuffer = Viewport::FromDimensions({ 0, 0 }, affectedViewport.Dimensions());
for (SHORT row = 0; row < regionDimensions.Y; row++)
{
for (SHORT col = 0; col < regionDimensions.X; col++)
{
CHAR_INFO bufferItem = *(buffer.begin() + (row * regionDimensions.X) + col);
CHAR_INFO expectedItem;
if (filledBuffer.IsInBounds({ col, row }))
{
expectedItem = ciFill;
}
else
{
expectedItem = ciEmpty;
}
VERIFY_ARE_EQUAL(expectedItem, bufferItem);
}
}
}
void OutputTests::ReadConsoleOutputWNegativePositions()
{
SetVerifyOutput vf(VerifyOutputSettings::LogOnlyFailures);
// Get output buffer information.
const auto consoleOutputHandle = GetStdOutputHandle();
SetConsoleActiveScreenBuffer(consoleOutputHandle);
// OneCore systems can't adjust the window/buffer size, so we'll skip making it smaller.
// On Desktop systems, make it smaller so the test runs faster.
if (OneCoreDelay::IsIsWindowPresent())
{
SMALL_RECT window = { 0 };
window.Right = 5;
window.Bottom = 5;
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleWindowInfo(consoleOutputHandle, true, &window));
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleScreenBufferSize(consoleOutputHandle, { 20, 20 }));
}
CONSOLE_SCREEN_BUFFER_INFOEX sbiex{ 0 };
sbiex.cbSize = sizeof(sbiex);
// Get buffer information
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(consoleOutputHandle, &sbiex));
const auto bufferSize = sbiex.dwSize;
const auto bufferLength = bufferSize.X * bufferSize.Y;
// Establish a reading region that is the width of the buffer and half the height.
const SMALL_RECT region{ 0, 0, bufferSize.X - 1, bufferSize.Y / 2 };
const COORD regionDimensions{ region.Right - region.Left + 1, region.Bottom - region.Top + 1 };
const auto regionSize = regionDimensions.X * regionDimensions.Y;
const COORD regionOrigin{ 0, 0 };
// Fill buffer with some data to read back.
CHAR_INFO ciFill = { 0 };
ciFill.Char.UnicodeChar = L'Z';
ciFill.Attributes = FOREGROUND_RED;
DWORD written = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputCharacterW(consoleOutputHandle, ciFill.Char.AsciiChar, bufferLength, { 0, 0 }, &written));
VERIFY_ARE_EQUAL(static_cast<DWORD>(bufferLength), written);
written = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputAttribute(consoleOutputHandle, ciFill.Attributes, bufferLength, { 0, 0 }, &written));
VERIFY_ARE_EQUAL(static_cast<DWORD>(bufferLength), written);
// Make a buffer to hold the read data
CHAR_INFO ciEmpty;
ciEmpty.Char.UnicodeChar = L'A';
ciEmpty.Attributes = BACKGROUND_BLUE;
std::vector<CHAR_INFO> buffer(regionSize, ciEmpty);
// Move the read region to negative values in the X and Y dimension
auto adjustedRegion = region;
adjustedRegion.Left -= 3;
adjustedRegion.Right -= 3;
adjustedRegion.Top -= 10;
adjustedRegion.Bottom -= 10;
auto expectedRegion = adjustedRegion;
expectedRegion.Left = std::max<SHORT>(0, adjustedRegion.Left);
expectedRegion.Top = std::max<SHORT>(0, adjustedRegion.Top);
expectedRegion.Right = std::min<SHORT>(bufferSize.X - 1, adjustedRegion.Right);
expectedRegion.Bottom = std::min<SHORT>(bufferSize.Y - 1, adjustedRegion.Bottom);
// Call the API
// NOTE: Due to the same reason as the ReadConsoleOutputWWithClipping test (the v1 buffer told the driver the wrong return buffer byte length)
// we expect the test to fail on the v1 console. V2 reports the correct buffer byte length to the driver for the return payload.
auto affected = adjustedRegion;
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(consoleOutputHandle, buffer.data(), regionDimensions, regionOrigin, &affected));
VERIFY_ARE_EQUAL(expectedRegion, affected);
// Verify the data read affected only the expected area
const auto affectedViewport = Viewport::FromInclusive(affected);
// Because of the negative origin, the API will report that it filled starting at the 0 coordinate, but it believed
// the original buffer's origin was at -3, -10. This means we have to read at that offset into the buffer we provided
// for the data we requested.
const auto filledBuffer = Viewport::FromDimensions({ 0, 0 }, affectedViewport.Dimensions());
auto adjustedBuffer = Viewport::Offset(filledBuffer, { -adjustedRegion.Left, -adjustedRegion.Top });
for (SHORT row = 0; row < regionDimensions.Y; row++)
{
for (SHORT col = 0; col < regionDimensions.X; col++)
{
CHAR_INFO bufferItem = *(buffer.begin() + (row * regionDimensions.X) + col);
CHAR_INFO expectedItem;
if (adjustedBuffer.IsInBounds({ col, row }))
{
expectedItem = ciFill;
}
else
{
expectedItem = ciEmpty;
}
VERIFY_ARE_EQUAL(expectedItem, bufferItem);
}
}
}
void OutputTests::ReadConsoleOutputWPartialUserBuffer()
{
SetVerifyOutput vf(VerifyOutputSettings::LogOnlyFailures);
// Get output buffer information.
const auto consoleOutputHandle = GetStdOutputHandle();
SetConsoleActiveScreenBuffer(consoleOutputHandle);
// OneCore systems can't adjust the window/buffer size, so we'll skip making it smaller.
// On Desktop systems, make it smaller so the test runs faster.
if (OneCoreDelay::IsIsWindowPresent())
{
SMALL_RECT window = { 0 };
window.Right = 5;
window.Bottom = 5;
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleWindowInfo(consoleOutputHandle, true, &window));
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleScreenBufferSize(consoleOutputHandle, { 20, 20 }));
}
CONSOLE_SCREEN_BUFFER_INFOEX sbiex{ 0 };
sbiex.cbSize = sizeof(sbiex);
// Get buffer information
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(consoleOutputHandle, &sbiex));
const auto bufferSize = sbiex.dwSize;
const auto bufferLength = bufferSize.X * bufferSize.Y;
// Establish a reading region that is the width of the buffer and half the height.
const SMALL_RECT region{ 0, 0, bufferSize.X - 1, bufferSize.Y / 2 };
const COORD regionDimensions{ region.Right - region.Left + 1, region.Bottom - region.Top + 1 };
const auto regionSize = regionDimensions.X * regionDimensions.Y;
// Fill buffer with some data to read back.
CHAR_INFO ciFill = { 0 };
ciFill.Char.UnicodeChar = L'Z';
ciFill.Attributes = FOREGROUND_RED;
DWORD written = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputCharacterW(consoleOutputHandle, ciFill.Char.AsciiChar, bufferLength, { 0, 0 }, &written));
VERIFY_ARE_EQUAL(static_cast<DWORD>(bufferLength), written);
written = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputAttribute(consoleOutputHandle, ciFill.Attributes, bufferLength, { 0, 0 }, &written));
VERIFY_ARE_EQUAL(static_cast<DWORD>(bufferLength), written);
// Make an array that can hold the output prefilled with some data so we can confirm it is untouched
CHAR_INFO ciEmpty;
ciEmpty.Char.UnicodeChar = L'A';
ciEmpty.Attributes = BACKGROUND_BLUE;
std::vector<CHAR_INFO> buffer(regionSize, ciEmpty);
// Only fill up a small portion of the region we allocated.
// We're going to set the origin to the middle and say we only want to read into/out of the bottom right corner.
const COORD regionOrigin{ regionDimensions.X / 2, regionDimensions.Y / 2 };
// Create the area that we expect to be filled with data.
SMALL_RECT expected;
expected.Left = regionOrigin.X;
expected.Right = regionDimensions.X - 1;
expected.Top = regionOrigin.Y;
expected.Bottom = regionDimensions.Y - 1;
const auto filledExpected = Viewport::FromInclusive(expected);
// translate the expected region into the origin at 0,0 because that's what the API will report.
expected.Right -= expected.Left;
expected.Left -= expected.Left;
expected.Bottom -= expected.Top;
expected.Top -= expected.Top;
// Call the API and confirm results
SMALL_RECT affected = region;
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(consoleOutputHandle, buffer.data(), regionDimensions, regionOrigin, &affected));
VERIFY_ARE_EQUAL(expected, affected);
// Verify that all the data read matches what was expected.
for (SHORT row = 0; row < regionDimensions.Y; row++)
{
for (SHORT col = 0; col < regionDimensions.X; col++)
{
CHAR_INFO bufferItem = *(buffer.begin() + (row * regionDimensions.X) + col);
CHAR_INFO expectedItem;
if (filledExpected.IsInBounds({ col, row }))
{
expectedItem = ciFill;
}
else
{
expectedItem = ciEmpty;
}
VERIFY_ARE_EQUAL(expectedItem, bufferItem);
}
}
}
// Send "Select All", then spawn a thread to hit ESC a moment later.
static void WinPtyTestStartSelection()
{
const HWND hwnd = GetConsoleWindow();
const int SC_CONSOLE_SELECT_ALL = 0xFFF5;
SendMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_SELECT_ALL, 0);
auto press_escape = std::thread([=]() {
Sleep(500);
SendMessage(hwnd, WM_CHAR, 27, 0x00010001); // 0x00010001 is the repeat count (1) and scan code (1)
});
press_escape.detach();
}
template<typename T>
static void WinPtyDoWriteTest(
const wchar_t* api_name,
T* api_ptr,
bool use_selection)
{
if (use_selection)
WinPtyTestStartSelection();
char buf[] = "1234567890567890567890567890\n";
DWORD actual = 0;
const BOOL ret = api_ptr(
GetStdHandle(STD_OUTPUT_HANDLE),
buf,
static_cast<DWORD>(strlen(buf)),
&actual,
NULL);
const DWORD last_error = GetLastError();
VERIFY_IS_TRUE(ret && actual == strlen(buf),
String().Format(L"%s: %s returned %d: actual=%u LastError=%u (%s)\n",
((ret && actual == strlen(buf)) ? L"SUCCESS" : L"ERROR"),
api_name,
ret,
actual,
last_error,
use_selection ? L"select" : L"no-select"));
}
void OutputTests::WinPtyWrite()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:method", L"{0, 1}")
TEST_METHOD_PROPERTY(L"Data:selection", L"{true, false}")
END_TEST_METHOD_PROPERTIES();
if (!OneCoreDelay::IsIsWindowPresent())
{
Log::Comment(L"Scenario requiring window message triggers can't be checked on platform without classic window operations.");
Log::Result(WEX::Logging::TestResults::Skipped);
return;
}
DWORD method;
bool selection;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"method", method), L"Get which function mode we should use");
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"selection", selection), L"Get whether we should use selection.");
switch (method)
{
case 0:
WinPtyDoWriteTest(L"WriteConsoleA", WriteConsoleA, selection);
break;
case 1:
WinPtyDoWriteTest(L"WriteFile", WriteConsoleA, selection);
break;
default:
VERIFY_FAIL(L"Unknown test type.");
break;
}
}