mirror of
https://github.com/microsoft/terminal.git
synced 2026-09-25 00:15:10 +00:00
Create tests that roundtrip output through a conpty to a Terminal (#4213)
## Summary of the Pull Request This PR adds two tests: * First, I started by writing a test where I could write output to the console host and inspect what output came out of conpty. This is the `ConptyOutputTests` in the host unit tests. * Then I got crazy and thought _"what if I could take that output and dump it straight into the `Terminal`"_? Hence, the `ConptyRoundtripTests` were born, into the TerminalCore unit tests. ## References Done in pursuit of #4200, but I felt this warranted it's own atomic PR ## PR Checklist * [x] Doesn't close anything on it's own. * [x] I work here * [x] you better believe this adds tests * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments From the comment in `ConptyRoundtripTests`: > This test class creates an in-proc conpty host as well as a Terminal, to > validate that strings written to the conpty create the same resopnse on the > terminal end. Tests can be written that validate both the contents of the > host buffer as well as the terminal buffer. Everytime that > `renderer.PaintFrame()` is called, the tests will validate the expected > output, and then flush the output of the VtEngine straight to th Also, some other bits had to be updated: * The renderer needed to be able to survive without a thread, so I hadded a simple check that it actually had a thread before calling `pThread->NotifyPaint` * Bits in `CommonState` used `NTSTATUS_FROM_HRESULT` which did _not_ work outside the host project. Since the `NTSTATUS` didn't seem that important, I replaced that with a `HRESULT` * `CommonState` likes to initialize the console to some _weird_ defaults. I added an optional param to let us just use the defaults.
This commit is contained in:
committed by
msftbot[bot]
parent
77dd51af39
commit
62765f152e
@@ -28,6 +28,11 @@ namespace Microsoft::Terminal::Core
|
||||
class Terminal;
|
||||
}
|
||||
|
||||
// fwdecl unittest classes
|
||||
#ifdef UNIT_TESTING
|
||||
class ConptyRoundtripTests;
|
||||
#endif
|
||||
|
||||
class Microsoft::Terminal::Core::Terminal final :
|
||||
public Microsoft::Terminal::Core::ITerminalApi,
|
||||
public Microsoft::Terminal::Core::ITerminalInput,
|
||||
@@ -245,4 +250,8 @@ private:
|
||||
SMALL_RECT _GetSelectionRow(const SHORT row, const COORD higherCoord, const COORD lowerCoord) const;
|
||||
void _ExpandSelectionRow(SMALL_RECT& selectionRow) const;
|
||||
#pragma endregion
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class ::ConptyRoundtripTests;
|
||||
#endif
|
||||
};
|
||||
|
||||
366
src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
Normal file
366
src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
Normal file
@@ -0,0 +1,366 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// This test class creates an in-proc conpty host as well as a Terminal, to
|
||||
// validate that strings written to the conpty create the same resopnse on the
|
||||
// terminal end. Tests can be written that validate both the contents of the
|
||||
// host buffer as well as the terminal buffer. Everytime that
|
||||
// `renderer.PaintFrame()` is called, the tests will validate the expected
|
||||
// output, and then flush the output of the VtEngine straight to the Terminal.
|
||||
|
||||
#include "precomp.h"
|
||||
#include <wextestclass.h>
|
||||
#include "../../inc/consoletaeftemplates.hpp"
|
||||
#include "../../types/inc/Viewport.hpp"
|
||||
#include "../../types/inc/convert.hpp"
|
||||
|
||||
#include "../renderer/inc/DummyRenderTarget.hpp"
|
||||
#include "../../renderer/base/Renderer.hpp"
|
||||
#include "../../renderer/vt/Xterm256Engine.hpp"
|
||||
#include "../../renderer/vt/XtermEngine.hpp"
|
||||
#include "../../renderer/vt/WinTelnetEngine.hpp"
|
||||
|
||||
class InputBuffer; // This for some reason needs to be fwd-decl'd
|
||||
#include "../host/inputBuffer.hpp"
|
||||
#include "../host/readDataCooked.hpp"
|
||||
#include "test/CommonState.hpp"
|
||||
|
||||
#include "../cascadia/TerminalCore/Terminal.hpp"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace Microsoft::Console::Types;
|
||||
using namespace Microsoft::Console::Interactivity;
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace Microsoft::Console::Render;
|
||||
using namespace Microsoft::Console::Types;
|
||||
|
||||
using namespace Microsoft::Terminal::Core;
|
||||
|
||||
class ConptyRoundtripTests
|
||||
{
|
||||
TEST_CLASS(ConptyRoundtripTests);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
m_state = std::make_unique<CommonState>();
|
||||
|
||||
m_state->InitEvents();
|
||||
m_state->PrepareGlobalFont();
|
||||
m_state->PrepareGlobalScreenBuffer();
|
||||
m_state->PrepareGlobalInputBuffer();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CLASS_CLEANUP(ClassCleanup)
|
||||
{
|
||||
m_state->CleanupGlobalScreenBuffer();
|
||||
m_state->CleanupGlobalFont();
|
||||
m_state->CleanupGlobalInputBuffer();
|
||||
|
||||
m_state.release();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_SETUP(MethodSetup)
|
||||
{
|
||||
// STEP 1: Set up the Terminal
|
||||
term = std::make_unique<Terminal>();
|
||||
term->Create({ CommonState::s_csBufferWidth, CommonState::s_csBufferHeight }, 0, emptyRT);
|
||||
|
||||
// STEP 2: Set up the Conpty
|
||||
|
||||
// Set up some sane defaults
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& gci = g.getConsoleInformation();
|
||||
gci.SetDefaultForegroundColor(INVALID_COLOR);
|
||||
gci.SetDefaultBackgroundColor(INVALID_COLOR);
|
||||
gci.SetFillAttribute(0x07); // DARK_WHITE on DARK_BLACK
|
||||
|
||||
m_state->PrepareNewTextBufferInfo(true);
|
||||
auto& currentBuffer = gci.GetActiveOutputBuffer();
|
||||
// Make sure a test hasn't left us in the alt buffer on accident
|
||||
VERIFY_IS_FALSE(currentBuffer._IsAltBuffer());
|
||||
VERIFY_SUCCEEDED(currentBuffer.SetViewportOrigin(true, { 0, 0 }, true));
|
||||
VERIFY_ARE_EQUAL(COORD({ 0, 0 }), currentBuffer.GetTextBuffer().GetCursor().GetPosition());
|
||||
|
||||
g.pRender = new Renderer(&gci.renderData, nullptr, 0, nullptr);
|
||||
|
||||
// Set up an xterm-256 renderer for conpty
|
||||
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
|
||||
Viewport initialViewport = currentBuffer.GetViewport();
|
||||
|
||||
_pVtRenderEngine = std::make_unique<Xterm256Engine>(std::move(hFile),
|
||||
gci,
|
||||
initialViewport,
|
||||
gci.GetColorTable(),
|
||||
static_cast<WORD>(gci.GetColorTableSize()));
|
||||
auto pfn = std::bind(&ConptyRoundtripTests::_writeCallback, this, std::placeholders::_1, std::placeholders::_2);
|
||||
_pVtRenderEngine->SetTestCallback(pfn);
|
||||
|
||||
g.pRender->AddRenderEngine(_pVtRenderEngine.get());
|
||||
gci.GetActiveOutputBuffer().SetTerminalConnection(_pVtRenderEngine.get());
|
||||
|
||||
expectedOutput.clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(MethodCleanup)
|
||||
{
|
||||
m_state->CleanupNewTextBufferInfo();
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
delete g.pRender;
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Tests should drain all the output they push into the expected output buffer.");
|
||||
|
||||
term = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD(ConptyOutputTestCanary);
|
||||
TEST_METHOD(SimpleWriteOutputTest);
|
||||
TEST_METHOD(WriteTwoLinesUsesNewline);
|
||||
TEST_METHOD(WriteAFewSimpleLines);
|
||||
|
||||
private:
|
||||
bool _writeCallback(const char* const pch, size_t const cch);
|
||||
void _flushFirstFrame();
|
||||
std::deque<std::string> expectedOutput;
|
||||
std::unique_ptr<Microsoft::Console::Render::VtEngine> _pVtRenderEngine;
|
||||
std::unique_ptr<CommonState> m_state;
|
||||
|
||||
DummyRenderTarget emptyRT;
|
||||
std::unique_ptr<Terminal> term;
|
||||
};
|
||||
|
||||
bool ConptyRoundtripTests::_writeCallback(const char* const pch, size_t const cch)
|
||||
{
|
||||
std::string actualString = std::string(pch, cch);
|
||||
VERIFY_IS_GREATER_THAN(expectedOutput.size(),
|
||||
static_cast<size_t>(0),
|
||||
NoThrowString().Format(L"writing=\"%hs\", expecting %u strings", actualString.c_str(), expectedOutput.size()));
|
||||
|
||||
std::string first = expectedOutput.front();
|
||||
expectedOutput.pop_front();
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Expected =\t\"%hs\"", first.c_str()));
|
||||
Log::Comment(NoThrowString().Format(L"Actual =\t\"%hs\"", actualString.c_str()));
|
||||
|
||||
VERIFY_ARE_EQUAL(first.length(), cch);
|
||||
VERIFY_ARE_EQUAL(first, actualString);
|
||||
|
||||
// Write the string back to our Terminal
|
||||
const auto converted = ConvertToW(CP_UTF8, actualString);
|
||||
term->Write(converted);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::_flushFirstFrame()
|
||||
{
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
|
||||
expectedOutput.push_back("\x1b[2J");
|
||||
expectedOutput.push_back("\x1b[m");
|
||||
expectedOutput.push_back("\x1b[H"); // Go Home
|
||||
expectedOutput.push_back("\x1b[?25h");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Helper function to validate that a number of characters in a row are all
|
||||
// the same. Validates that the next end-start characters are all equal to the
|
||||
// provided string. Will move the provided iterator as it validates. The
|
||||
// caller should ensure that `iter` starts where they would like to validate.
|
||||
// Arguments:
|
||||
// - expectedChar: The character (or characters) we're expecting
|
||||
// - iter: a iterator pointing to the cell we'd like to start validating at.
|
||||
// - start: the first index in the range we'd like to validate
|
||||
// - end: the last index in the range we'd like to validate
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void _verifySpanOfText(const wchar_t* const expectedChar,
|
||||
TextBufferCellIterator& iter,
|
||||
const int start,
|
||||
const int end)
|
||||
{
|
||||
for (int x = start; x < end; x++)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
if (iter->Chars() != expectedChar)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"character [%d] was mismatched", x));
|
||||
}
|
||||
VERIFY_ARE_EQUAL(expectedChar, (iter++)->Chars());
|
||||
}
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Successfully validated %d characters were '%s'", end - start, expectedChar));
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Helper function to validate that the next characters pointed to by `iter`
|
||||
// are the provided string. Will increment iter as it walks the provided
|
||||
// string of characters. It will leave `iter` on the first character after the
|
||||
// expectedString.
|
||||
// Arguments:
|
||||
// - expectedString: The characters we're expecting
|
||||
// - iter: a iterator pointing to the cell we'd like to start validating at.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void _verifyExpectedString(std::wstring_view expectedString,
|
||||
TextBufferCellIterator& iter)
|
||||
{
|
||||
for (const auto wch : expectedString)
|
||||
{
|
||||
wchar_t buffer[]{ wch, L'\0' };
|
||||
std::wstring_view view{ buffer, 1 };
|
||||
VERIFY_IS_TRUE(iter, L"Ensure iterator is still valid");
|
||||
VERIFY_ARE_EQUAL(view, (iter++)->Chars(), NoThrowString().Format(L"%s", view.data()));
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Helper function to validate that the next characters in the buffer at the
|
||||
// given location are the provided string. Will return an iterator on the
|
||||
// first character after the expectedString.
|
||||
// Arguments:
|
||||
// - tb: the buffer who's content we should check
|
||||
// - expectedString: The characters we're expecting
|
||||
// - pos: the starting position in the buffer to check the contents of
|
||||
// Return Value:
|
||||
// - an iterator on the first character after the expectedString.
|
||||
TextBufferCellIterator _verifyExpectedString(const TextBuffer& tb,
|
||||
std::wstring_view expectedString,
|
||||
const COORD pos)
|
||||
{
|
||||
auto iter = tb.GetCellDataAt(pos);
|
||||
_verifyExpectedString(expectedString, iter);
|
||||
return iter;
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::ConptyOutputTestCanary()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"This is a simple test to make sure that everything is working as expected."));
|
||||
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
|
||||
|
||||
_flushFirstFrame();
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::SimpleWriteOutputTest()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Write some simple output, and make sure it gets rendered largely "
|
||||
L"unmodified to the terminal"));
|
||||
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
auto& termTb = *term->_buffer;
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
expectedOutput.push_back("Hello World");
|
||||
hostSm.ProcessString(L"Hello World");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
_verifyExpectedString(termTb, L"Hello World ", { 0, 0 });
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::WriteTwoLinesUsesNewline()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Write two lines of output. We should use \r\n to move the cursor"));
|
||||
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
hostSm.ProcessString(L"AAA");
|
||||
hostSm.ProcessString(L"\x1b[2;1H");
|
||||
hostSm.ProcessString(L"BBB");
|
||||
|
||||
auto verifyData = [](TextBuffer& tb) {
|
||||
_verifyExpectedString(tb, L"AAA", { 0, 0 });
|
||||
_verifyExpectedString(tb, L"BBB", { 0, 1 });
|
||||
};
|
||||
|
||||
verifyData(hostTb);
|
||||
|
||||
expectedOutput.push_back("AAA");
|
||||
expectedOutput.push_back("\r\n");
|
||||
expectedOutput.push_back("BBB");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
verifyData(termTb);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::WriteAFewSimpleLines()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Write more lines of outout. We should use \r\n to move the cursor"));
|
||||
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
hostSm.ProcessString(L"AAA\n");
|
||||
hostSm.ProcessString(L"BBB\n");
|
||||
hostSm.ProcessString(L"\n");
|
||||
hostSm.ProcessString(L"CCC");
|
||||
auto verifyData = [](TextBuffer& tb) {
|
||||
_verifyExpectedString(tb, L"AAA", { 0, 0 });
|
||||
_verifyExpectedString(tb, L"BBB", { 0, 1 });
|
||||
_verifyExpectedString(tb, L" ", { 0, 2 });
|
||||
_verifyExpectedString(tb, L"CCC", { 0, 3 });
|
||||
};
|
||||
|
||||
verifyData(hostTb);
|
||||
|
||||
expectedOutput.push_back("AAA");
|
||||
expectedOutput.push_back("\r\n");
|
||||
expectedOutput.push_back("BBB");
|
||||
expectedOutput.push_back("\r\n");
|
||||
// Here, we're going to emit 3 spaces. The region that got invalidated was a
|
||||
// rectangle from 0,0 to 3,3, so the vt renderer will try to render the
|
||||
// region in between BBB and CCC as well, because it got included in the
|
||||
// rectangle Or() operation.
|
||||
// This behavior should not be seen as binding - if a future optimization
|
||||
// breaks this test, it wouldn't be the worst.
|
||||
expectedOutput.push_back(" ");
|
||||
expectedOutput.push_back("\r\n");
|
||||
expectedOutput.push_back("CCC");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
verifyData(termTb);
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TerminalApiTest.cpp" />
|
||||
<ClCompile Include="ConptyRoundtripTests.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\buffer\out\lib\bufferout.vcxproj">
|
||||
@@ -38,6 +39,42 @@
|
||||
<ProjectReference Include="..\TerminalCore\lib\TerminalCore-lib.vcxproj">
|
||||
<Project>{ca5cad1a-abcd-429c-b551-8562ec954746}</Project>
|
||||
</ProjectReference>
|
||||
|
||||
<!-- The following are all Console Host (host.lib) dependencies. We're
|
||||
including them for the ConptyRoundtripTests, which instantiate a console
|
||||
host, then user the output from conpty to dump directly into a Terminal,
|
||||
and make sure the buffer contents align. -->
|
||||
|
||||
<ProjectReference Include="..\..\renderer\vt\ut_lib\vt.unittest.vcxproj">
|
||||
<Project>{990F2657-8580-4828-943F-5DD657D11843}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\renderer\base\lib\base.vcxproj">
|
||||
<Project>{af0a096a-8b3a-4949-81ef-7df8f0fee91f}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(OpenConsoleDir)\src\host\ut_lib\host.unittest.vcxproj">
|
||||
<Project>{06ec74cb-9a12-429c-b551-8562ec954746}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\propslib\propslib.vcxproj">
|
||||
<Project>{345fd5a4-b32b-4f29-bd1c-b033bd2c35cc}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\interactivity\base\lib\InteractivityBase.vcxproj">
|
||||
<Project>{06ec74cb-9a12-429c-b551-8562ec964846}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\interactivity\win32\lib\win32.LIB.vcxproj">
|
||||
<Project>{06ec74cb-9a12-429c-b551-8532ec964726}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\tsf\tsf.vcxproj">
|
||||
<Project>{2fd12fbb-1ddb-46d8-b818-1023c624caca}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\server\lib\server.vcxproj">
|
||||
<Project>{18d09a24-8240-42d6-8cb6-236eee820262}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\terminal\adapter\lib\adapter.vcxproj">
|
||||
<Project>{dcf55140-ef6a-4736-a403-957e4f7430bb}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\internal\internal.vcxproj">
|
||||
<Project>{ef3e32a7-5ff6-42b4-b6e2-96cd7d033f00}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="MockTermSettings.h" />
|
||||
@@ -45,7 +82,7 @@
|
||||
</ItemGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalSettings\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalSettings\Generated Files";$(OpenConsoleDir)\src\host;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@@ -55,4 +92,4 @@
|
||||
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
|
||||
<Import Project="$(SolutionDir)src\common.build.post.props" />
|
||||
<Import Project="$(SolutionDir)src\common.build.tests.props" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -6,8 +6,10 @@ Module Name:
|
||||
- precomp.h
|
||||
|
||||
Abstract:
|
||||
- Contains external headers to include in the precompile phase of console build process.
|
||||
- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building).
|
||||
- Contains external headers to include in the precompile phase of console build
|
||||
process.
|
||||
- Avoid including internal project headers. Instead include them only in the
|
||||
classes that need them (helps with test project building).
|
||||
|
||||
Author(s):
|
||||
- Carlos Zamora (cazamor) April 2019
|
||||
@@ -15,6 +17,18 @@ Author(s):
|
||||
|
||||
#pragma once
|
||||
|
||||
// <Conhost includes>
|
||||
// This header and define are needed so that the console host code can build in
|
||||
// this test binary.
|
||||
|
||||
// Block minwindef.h min/max macros to prevent <algorithm> conflict
|
||||
#define NOMINMAX
|
||||
|
||||
// This includes a lot of common headers needed by both the host and the propsheet
|
||||
// including: windows.h, winuser, ntstatus, assert, and the DDK
|
||||
#include "HostAndPropsheetIncludes.h"
|
||||
// </Conhost Includes>
|
||||
|
||||
// This includes support libraries from the CRT, STL, WIL, and GSL
|
||||
#include "LibraryIncludes.h"
|
||||
|
||||
@@ -30,4 +44,4 @@ Author(s):
|
||||
#ifdef CON_BUILD_PUBLIC
|
||||
#define CON_USERPRIVAPI_INDIRECT
|
||||
#define CON_DPIAPI_INDIRECT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -307,5 +307,7 @@ private:
|
||||
friend class TextBufferIteratorTests;
|
||||
friend class ScreenBufferTests;
|
||||
friend class CommonState;
|
||||
friend class ConptyOutputTests;
|
||||
friend class ConptyRoundtripTests;
|
||||
#endif
|
||||
};
|
||||
|
||||
309
src/host/ut_host/ConptyOutputTests.cpp
Normal file
309
src/host/ut_host/ConptyOutputTests.cpp
Normal file
@@ -0,0 +1,309 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include <wextestclass.h>
|
||||
#include "../../inc/consoletaeftemplates.hpp"
|
||||
#include "../../types/inc/Viewport.hpp"
|
||||
|
||||
#include "../../renderer/base/Renderer.hpp"
|
||||
#include "../../renderer/vt/Xterm256Engine.hpp"
|
||||
#include "../../renderer/vt/XtermEngine.hpp"
|
||||
#include "../../renderer/vt/WinTelnetEngine.hpp"
|
||||
#include "../Settings.hpp"
|
||||
|
||||
#include "CommonState.hpp"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace Microsoft::Console::Types;
|
||||
using namespace Microsoft::Console::Interactivity;
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace Microsoft::Console::Render;
|
||||
using namespace Microsoft::Console::Types;
|
||||
|
||||
class ConptyOutputTests
|
||||
{
|
||||
TEST_CLASS(ConptyOutputTests);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
m_state = std::make_unique<CommonState>();
|
||||
|
||||
m_state->InitEvents();
|
||||
m_state->PrepareGlobalFont();
|
||||
m_state->PrepareGlobalScreenBuffer();
|
||||
m_state->PrepareGlobalInputBuffer();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CLASS_CLEANUP(ClassCleanup)
|
||||
{
|
||||
m_state->CleanupGlobalScreenBuffer();
|
||||
m_state->CleanupGlobalFont();
|
||||
m_state->CleanupGlobalInputBuffer();
|
||||
|
||||
m_state.release();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_SETUP(MethodSetup)
|
||||
{
|
||||
// Set up some sane defaults
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& gci = g.getConsoleInformation();
|
||||
gci.SetDefaultForegroundColor(INVALID_COLOR);
|
||||
gci.SetDefaultBackgroundColor(INVALID_COLOR);
|
||||
gci.SetFillAttribute(0x07); // DARK_WHITE on DARK_BLACK
|
||||
|
||||
m_state->PrepareNewTextBufferInfo(true);
|
||||
auto& currentBuffer = gci.GetActiveOutputBuffer();
|
||||
// Make sure a test hasn't left us in the alt buffer on accident
|
||||
VERIFY_IS_FALSE(currentBuffer._IsAltBuffer());
|
||||
VERIFY_SUCCEEDED(currentBuffer.SetViewportOrigin(true, { 0, 0 }, true));
|
||||
VERIFY_ARE_EQUAL(COORD({ 0, 0 }), currentBuffer.GetTextBuffer().GetCursor().GetPosition());
|
||||
|
||||
g.pRender = new Renderer(&gci.renderData, nullptr, 0, nullptr);
|
||||
|
||||
// Set up an xterm-256 renderer for conpty
|
||||
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
|
||||
Viewport initialViewport = currentBuffer.GetViewport();
|
||||
|
||||
_pVtRenderEngine = std::make_unique<Xterm256Engine>(std::move(hFile),
|
||||
gci,
|
||||
initialViewport,
|
||||
gci.GetColorTable(),
|
||||
static_cast<WORD>(gci.GetColorTableSize()));
|
||||
auto pfn = std::bind(&ConptyOutputTests::_writeCallback, this, std::placeholders::_1, std::placeholders::_2);
|
||||
_pVtRenderEngine->SetTestCallback(pfn);
|
||||
|
||||
g.pRender->AddRenderEngine(_pVtRenderEngine.get());
|
||||
gci.GetActiveOutputBuffer().SetTerminalConnection(_pVtRenderEngine.get());
|
||||
|
||||
expectedOutput.clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(MethodCleanup)
|
||||
{
|
||||
m_state->CleanupNewTextBufferInfo();
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
delete g.pRender;
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Tests should drain all the output they push into the expected output buffer.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD(ConptyOutputTestCanary);
|
||||
TEST_METHOD(SimpleWriteOutputTest);
|
||||
TEST_METHOD(WriteTwoLinesUsesNewline);
|
||||
TEST_METHOD(WriteAFewSimpleLines);
|
||||
|
||||
private:
|
||||
bool _writeCallback(const char* const pch, size_t const cch);
|
||||
void _flushFirstFrame();
|
||||
std::deque<std::string> expectedOutput;
|
||||
std::unique_ptr<Microsoft::Console::Render::VtEngine> _pVtRenderEngine;
|
||||
std::unique_ptr<CommonState> m_state;
|
||||
};
|
||||
|
||||
bool ConptyOutputTests::_writeCallback(const char* const pch, size_t const cch)
|
||||
{
|
||||
std::string actualString = std::string(pch, cch);
|
||||
VERIFY_IS_GREATER_THAN(expectedOutput.size(),
|
||||
static_cast<size_t>(0),
|
||||
NoThrowString().Format(L"writing=\"%hs\", expecting %u strings", actualString.c_str(), expectedOutput.size()));
|
||||
|
||||
std::string first = expectedOutput.front();
|
||||
expectedOutput.pop_front();
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Expected =\t\"%hs\"", first.c_str()));
|
||||
Log::Comment(NoThrowString().Format(L"Actual =\t\"%hs\"", actualString.c_str()));
|
||||
|
||||
VERIFY_ARE_EQUAL(first.length(), cch);
|
||||
VERIFY_ARE_EQUAL(first, actualString);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConptyOutputTests::_flushFirstFrame()
|
||||
{
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
|
||||
expectedOutput.push_back("\x1b[2J");
|
||||
expectedOutput.push_back("\x1b[m");
|
||||
expectedOutput.push_back("\x1b[H"); // Go Home
|
||||
expectedOutput.push_back("\x1b[?25h");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Helper function to validate that a number of characters in a row are all
|
||||
// the same. Validates that the next end-start characters are all equal to the
|
||||
// provided string. Will move the provided iterator as it validates. The
|
||||
// caller should ensure that `iter` starts where they would like to validate.
|
||||
// Arguments:
|
||||
// - expectedChar: The character (or characters) we're expecting
|
||||
// - iter: a iterator pointing to the cell we'd like to start validating at.
|
||||
// - start: the first index in the range we'd like to validate
|
||||
// - end: the last index in the range we'd like to validate
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void _verifySpanOfText(const wchar_t* const expectedChar,
|
||||
TextBufferCellIterator& iter,
|
||||
const int start,
|
||||
const int end)
|
||||
{
|
||||
for (int x = start; x < end; x++)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
if (iter->Chars() != expectedChar)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"character [%d] was mismatched", x));
|
||||
}
|
||||
VERIFY_ARE_EQUAL(expectedChar, (iter++)->Chars());
|
||||
}
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Successfully validated %d characters were '%s'", end - start, expectedChar));
|
||||
}
|
||||
|
||||
void ConptyOutputTests::ConptyOutputTestCanary()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"This is a simple test to make sure that everything is working as expected."));
|
||||
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
|
||||
|
||||
_flushFirstFrame();
|
||||
}
|
||||
|
||||
void ConptyOutputTests::SimpleWriteOutputTest()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Write some simple output, and make sure it gets rendered largely "
|
||||
L"unmodified to the terminal"));
|
||||
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& sm = si.GetStateMachine();
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
expectedOutput.push_back("Hello World");
|
||||
sm.ProcessString(L"Hello World");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
}
|
||||
|
||||
void ConptyOutputTests::WriteTwoLinesUsesNewline()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Write two lines of output. We should use \r\n to move the cursor"));
|
||||
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& sm = si.GetStateMachine();
|
||||
auto& tb = si.GetTextBuffer();
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
sm.ProcessString(L"AAA");
|
||||
sm.ProcessString(L"\x1b[2;1H");
|
||||
sm.ProcessString(L"BBB");
|
||||
|
||||
{
|
||||
auto iter = tb.GetCellDataAt({ 0, 0 });
|
||||
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
|
||||
}
|
||||
{
|
||||
auto iter = tb.GetCellDataAt({ 0, 1 });
|
||||
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
|
||||
}
|
||||
|
||||
expectedOutput.push_back("AAA");
|
||||
expectedOutput.push_back("\r\n");
|
||||
expectedOutput.push_back("BBB");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
}
|
||||
|
||||
void ConptyOutputTests::WriteAFewSimpleLines()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Write more lines of output. We should use \r\n to move the cursor"));
|
||||
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& sm = si.GetStateMachine();
|
||||
auto& tb = si.GetTextBuffer();
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
sm.ProcessString(L"AAA\n");
|
||||
sm.ProcessString(L"BBB\n");
|
||||
sm.ProcessString(L"\n");
|
||||
sm.ProcessString(L"CCC");
|
||||
|
||||
{
|
||||
auto iter = tb.GetCellDataAt({ 0, 0 });
|
||||
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
|
||||
}
|
||||
{
|
||||
auto iter = tb.GetCellDataAt({ 0, 1 });
|
||||
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
|
||||
}
|
||||
{
|
||||
auto iter = tb.GetCellDataAt({ 0, 2 });
|
||||
VERIFY_ARE_EQUAL(L" ", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L" ", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L" ", (iter++)->Chars());
|
||||
}
|
||||
{
|
||||
auto iter = tb.GetCellDataAt({ 0, 3 });
|
||||
VERIFY_ARE_EQUAL(L"C", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"C", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"C", (iter++)->Chars());
|
||||
}
|
||||
|
||||
expectedOutput.push_back("AAA");
|
||||
expectedOutput.push_back("\r\n");
|
||||
expectedOutput.push_back("BBB");
|
||||
expectedOutput.push_back("\r\n");
|
||||
// Here, we're going to emit 3 spaces. The region that got invalidated was a
|
||||
// rectangle from 0,0 to 3,3, so the vt renderer will try to render the
|
||||
// region in between BBB and CCC as well, because it got included in the
|
||||
// rectangle Or() operation.
|
||||
// This behavior should not be seen as binding - if a future optimization
|
||||
// breaks this test, it wouldn't be the worst.
|
||||
expectedOutput.push_back(" ");
|
||||
expectedOutput.push_back("\r\n");
|
||||
expectedOutput.push_back("CCC");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
}
|
||||
@@ -40,6 +40,7 @@
|
||||
<ClCompile Include="ViewportTests.cpp" />
|
||||
<ClCompile Include="VtIoTests.cpp" />
|
||||
<ClCompile Include="VtRendererTests.cpp" />
|
||||
<ClCompile Include="ConptyOutputTests.cpp" />
|
||||
<Clcompile Include="..\..\types\IInputEventStreams.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
|
||||
@@ -149,7 +149,7 @@ class TextBufferTests
|
||||
|
||||
void TextBufferTests::TestBufferCreate()
|
||||
{
|
||||
VERIFY_SUCCESS_NTSTATUS(m_state->GetTextBufferInfoInitResult());
|
||||
VERIFY_SUCCEEDED(m_state->GetTextBufferInfoInitResult());
|
||||
}
|
||||
|
||||
TextBuffer& TextBufferTests::GetTbi()
|
||||
|
||||
@@ -36,6 +36,7 @@ SOURCES = \
|
||||
InputBufferTests.cpp \
|
||||
VtIoTests.cpp \
|
||||
VtRendererTests.cpp \
|
||||
ConptyOutputTests.cpp \
|
||||
ViewportTests.cpp \
|
||||
ConsoleArgumentsTests.cpp \
|
||||
CommandLineTests.cpp \
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
|
||||
CommonState() :
|
||||
m_heap(GetProcessHeap()),
|
||||
m_ntstatusTextBufferInfo(STATUS_FAIL_CHECK),
|
||||
m_hrTextBufferInfo(E_FAIL),
|
||||
m_pFontInfo(nullptr),
|
||||
m_backupTextBufferInfo(),
|
||||
m_readHandle(nullptr)
|
||||
@@ -143,7 +143,7 @@ public:
|
||||
gci.SetCookedReadData(nullptr);
|
||||
}
|
||||
|
||||
void PrepareNewTextBufferInfo()
|
||||
void PrepareNewTextBufferInfo(const bool useDefaultAttributes = false)
|
||||
{
|
||||
CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
COORD coordScreenBufferSize;
|
||||
@@ -152,26 +152,29 @@ public:
|
||||
|
||||
UINT uiCursorSize = 12;
|
||||
|
||||
auto initialAttributes = useDefaultAttributes ? gci.GetDefaultAttributes() :
|
||||
TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY };
|
||||
|
||||
m_backupTextBufferInfo.swap(gci.pCurrentScreenBuffer->_textBuffer);
|
||||
try
|
||||
{
|
||||
std::unique_ptr<TextBuffer> textBuffer = std::make_unique<TextBuffer>(coordScreenBufferSize,
|
||||
TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY },
|
||||
initialAttributes,
|
||||
uiCursorSize,
|
||||
gci.pCurrentScreenBuffer->GetRenderTarget());
|
||||
if (textBuffer.get() == nullptr)
|
||||
{
|
||||
m_ntstatusTextBufferInfo = STATUS_NO_MEMORY;
|
||||
m_hrTextBufferInfo = E_OUTOFMEMORY;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ntstatusTextBufferInfo = STATUS_SUCCESS;
|
||||
m_hrTextBufferInfo = S_OK;
|
||||
}
|
||||
gci.pCurrentScreenBuffer->_textBuffer.swap(textBuffer);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
m_ntstatusTextBufferInfo = NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
|
||||
m_hrTextBufferInfo = wil::ResultFromCaughtException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,14 +224,14 @@ public:
|
||||
textBuffer.GetCursor().SetYPosition(cRowsToFill);
|
||||
}
|
||||
|
||||
[[nodiscard]] NTSTATUS GetTextBufferInfoInitResult()
|
||||
[[nodiscard]] HRESULT GetTextBufferInfoInitResult()
|
||||
{
|
||||
return m_ntstatusTextBufferInfo;
|
||||
return m_hrTextBufferInfo;
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE m_heap;
|
||||
NTSTATUS m_ntstatusTextBufferInfo;
|
||||
HRESULT m_hrTextBufferInfo;
|
||||
FontInfo* m_pFontInfo;
|
||||
std::unique_ptr<TextBuffer> m_backupTextBufferInfo;
|
||||
std::unique_ptr<INPUT_READ_HANDLE_DATA> m_readHandle;
|
||||
|
||||
@@ -151,8 +151,12 @@ Renderer::~Renderer()
|
||||
|
||||
void Renderer::_NotifyPaintFrame()
|
||||
{
|
||||
// The thread will provide throttling for us.
|
||||
_pThread->NotifyPaint();
|
||||
// If we're running in the unittests, we might not have a render thread.
|
||||
if (_pThread)
|
||||
{
|
||||
// The thread will provide throttling for us.
|
||||
_pThread->NotifyPaint();
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
||||
@@ -125,5 +125,9 @@ namespace Microsoft::Console::Render
|
||||
// Helper functions to diagnose issues with painting and layout.
|
||||
// These are only actually effective/on in Debug builds when the flag is set using an attached debugger.
|
||||
bool _fDebug = false;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class ConptyOutputTests;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace Microsoft::Console::Render
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class VtRendererTest;
|
||||
friend class ConptyOutputTests;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ namespace Microsoft::Console::Render
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class VtRendererTest;
|
||||
friend class ConptyOutputTests;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
@@ -80,8 +80,18 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
|
||||
#ifdef UNIT_TESTING
|
||||
if (_usingTestCallback)
|
||||
{
|
||||
RETURN_LAST_ERROR_IF(!_pfnTestCallback(str.data(), str.size()));
|
||||
return S_OK;
|
||||
// Try to get the last error. If that wasn't set, then the test probably
|
||||
// doesn't set last error. No matter. We'll just return with E_FAIL
|
||||
// then. This is a unit test, we don't particularily care.
|
||||
const auto succeeded = _pfnTestCallback(str.data(), str.size());
|
||||
auto hr = E_FAIL;
|
||||
if (!succeeded)
|
||||
{
|
||||
const auto err = ::GetLastError();
|
||||
// If there wasn't an error in GLE, just use E_FAIL
|
||||
hr = SUCCEEDED_WIN32(err) ? hr : HRESULT_FROM_WIN32(err);
|
||||
}
|
||||
return succeeded ? S_OK : hr;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -24,6 +24,11 @@ Author(s):
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
// fwdecl unittest classes
|
||||
#ifdef UNIT_TESTING
|
||||
class ConptyRoundtripTests;
|
||||
#endif
|
||||
|
||||
namespace Microsoft::Console::Render
|
||||
{
|
||||
class VtEngine : public RenderEngineBase, public Microsoft::Console::ITerminalOutputConnection
|
||||
@@ -220,6 +225,8 @@ namespace Microsoft::Console::Render
|
||||
bool _usingTestCallback;
|
||||
|
||||
friend class VtRendererTest;
|
||||
friend class ConptyOutputTests;
|
||||
friend class ConptyRoundtripTests;
|
||||
#endif
|
||||
|
||||
void SetTestCallback(_In_ std::function<bool(const char* const, size_t const)> pfn);
|
||||
|
||||
Reference in New Issue
Block a user