// 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 namespace WEX::Logging;
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.
    auto 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.
    auto 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 affected = region;
    affected.Left += bufferSize.X;
    affected.Right += bufferSize.X;
    affected.Top += bufferSize.Y;
    affected.Bottom += bufferSize.Y;
    auto expected = affected;
    expected.Right = expected.Left - 1;
    expected.Bottom = expected.Top - 1;
    VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(consoleOutputHandle, buffer.data(), regionDimensions, regionOrigin, &affected));
    VERIFY_ARE_EQUAL(expected, 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++)
        {
            auto 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++)
        {
            auto 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++)
        {
            auto 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::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 auto size = width * height;
    auto attrs = std::make_unique<WORD[]>(size);

    std::generate(attrs.get(), attrs.get() + size, [=]() {
        static auto i = 0;
        return i++ % 2 == 0 ? red : green;
    });

    // write text
    const COORD coord{ 0, 0 };
    DWORD charsWritten = 0;
    auto 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
    auto 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
    auto 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
    auto 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
    auto 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(til::wrap_small_rect(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++)
        {
            auto 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(til::wrap_small_rect(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++)
        {
            auto 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(til::wrap_small_rect(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
    auto 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++)
        {
            auto 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 auto hwnd = GetConsoleWindow();
    const auto 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 auto 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;
    }
}
