// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "precomp.h" #include "WexTestClass.h" #include "../../inc/consoletaeftemplates.hpp" #include "CommonState.hpp" #include "globals.h" #include "../buffer/out/textBuffer.hpp" #include "../buffer/out/textBufferCellIterator.hpp" #include "../buffer/out/textBufferTextIterator.hpp" #include "../buffer/out/CharRow.hpp" #include "input.h" #include "../interactivity/inc/ServiceLocator.hpp" using namespace WEX::Common; using namespace WEX::Logging; using namespace WEX::TestExecution; using Microsoft::Console::Interactivity::ServiceLocator; template T GetIterator() { } template T GetIteratorAt(COORD at) { } template T GetIteratorWithAdvance() { } template<> TextBufferCellIterator GetIteratorAt(COORD at) { const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto& outputBuffer = gci.GetActiveOutputBuffer(); return outputBuffer.GetCellDataAt(at); } template<> TextBufferCellIterator GetIterator() { return GetIteratorAt({ 0 }); } template<> TextBufferCellIterator GetIteratorWithAdvance() { return GetIteratorAt({ 5, 5 }); } template<> TextBufferTextIterator GetIteratorAt(COORD at) { const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto& outputBuffer = gci.GetActiveOutputBuffer(); return outputBuffer.GetTextDataAt(at); } template<> TextBufferTextIterator GetIterator() { return GetIteratorAt({ 0 }); } template<> TextBufferTextIterator GetIteratorWithAdvance() { return GetIteratorAt({ 5, 5 }); } class TextBufferIteratorTests { CommonState* m_state; TEST_CLASS(TextBufferIteratorTests); TEST_CLASS_SETUP(ClassSetup) { m_state = new CommonState(); m_state->PrepareGlobalFont(); m_state->PrepareGlobalScreenBuffer(); return true; } TEST_CLASS_CLEANUP(ClassCleanup) { m_state->CleanupGlobalScreenBuffer(); m_state->CleanupGlobalFont(); delete m_state; return true; } TEST_METHOD_SETUP(MethodSetup) { m_state->PrepareNewTextBufferInfo(); return true; } TEST_METHOD_CLEANUP(MethodCleanup) { m_state->CleanupNewTextBufferInfo(); return true; } template void BoolOperatorTestHelper() { const auto it = GetIterator(); VERIFY_IS_TRUE(it); const auto& outputBuffer = ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer(); const auto size = outputBuffer.GetBufferSize().Dimensions(); T itInvalidPos(it); itInvalidPos._exceeded = true; VERIFY_IS_FALSE(itInvalidPos); } TEST_METHOD(BoolOperatorText); TEST_METHOD(BoolOperatorCell); template void EqualsOperatorTestHelper() { const auto it = GetIterator(); const auto it2 = GetIterator(); VERIFY_ARE_EQUAL(it, it2); } TEST_METHOD(EqualsOperatorText); TEST_METHOD(EqualsOperatorCell); template void NotEqualsOperatorTestHelper() { const auto it = GetIterator(); COORD oneOff = it._pos; oneOff.X++; const auto it2 = GetIteratorAt(oneOff); VERIFY_ARE_NOT_EQUAL(it, it2); } TEST_METHOD(NotEqualsOperatorText); TEST_METHOD(NotEqualsOperatorCell); template void PlusEqualsOperatorTestHelper() { auto it = GetIterator(); ptrdiff_t diffUnit = 3; COORD expectedPos = it._pos; expectedPos.X += gsl::narrow(diffUnit); const auto itExpected = GetIteratorAt(expectedPos); it += diffUnit; VERIFY_ARE_EQUAL(itExpected, it); } TEST_METHOD(PlusEqualsOperatorText); TEST_METHOD(PlusEqualsOperatorCell); template void MinusEqualsOperatorTestHelper() { auto itExpected = GetIteratorWithAdvance(); ptrdiff_t diffUnit = 3; COORD pos = itExpected._pos; pos.X += gsl::narrow(diffUnit); auto itOffset = GetIteratorAt(pos); itOffset -= diffUnit; VERIFY_ARE_EQUAL(itExpected, itOffset); } TEST_METHOD(MinusEqualsOperatorText); TEST_METHOD(MinusEqualsOperatorCell); template void PrefixPlusPlusOperatorTestHelper() { auto itActual = GetIterator(); COORD expectedPos = itActual._pos; expectedPos.X++; const auto itExpected = GetIteratorAt(expectedPos); ++itActual; VERIFY_ARE_EQUAL(itExpected, itActual); } TEST_METHOD(PrefixPlusPlusOperatorText); TEST_METHOD(PrefixPlusPlusOperatorCell); template void PrefixMinusMinusOperatorTestHelper() { const auto itExpected = GetIteratorWithAdvance(); COORD pos = itExpected._pos; pos.X++; auto itActual = GetIteratorAt(pos); --itActual; VERIFY_ARE_EQUAL(itExpected, itActual); } TEST_METHOD(PrefixMinusMinusOperatorText); TEST_METHOD(PrefixMinusMinusOperatorCell); template void PostfixPlusPlusOperatorTestHelper() { auto it = GetIterator(); COORD expectedPos = it._pos; expectedPos.X++; const auto itExpected = GetIteratorAt(expectedPos); ++it; VERIFY_ARE_EQUAL(itExpected, it); } TEST_METHOD(PostfixPlusPlusOperatorText); TEST_METHOD(PostfixPlusPlusOperatorCell); template void PostfixMinusMinusOperatorTestHelper() { const auto itExpected = GetIteratorWithAdvance(); COORD pos = itExpected._pos; pos.X++; auto itActual = GetIteratorAt(pos); itActual--; VERIFY_ARE_EQUAL(itExpected, itActual); } TEST_METHOD(PostfixMinusMinusOperatorText); TEST_METHOD(PostfixMinusMinusOperatorCell); template void PlusOperatorTestHelper() { auto it = GetIterator(); ptrdiff_t diffUnit = 3; COORD expectedPos = it._pos; expectedPos.X += gsl::narrow(diffUnit); const auto itExpected = GetIteratorAt(expectedPos); const auto itActual = it + diffUnit; VERIFY_ARE_EQUAL(itExpected, itActual); } TEST_METHOD(PlusOperatorText); TEST_METHOD(PlusOperatorCell); template void MinusOperatorTestHelper() { auto itExpected = GetIteratorWithAdvance(); ptrdiff_t diffUnit = 3; COORD pos = itExpected._pos; pos.X += gsl::narrow(diffUnit); auto itOffset = GetIteratorAt(pos); const auto itActual = itOffset - diffUnit; VERIFY_ARE_EQUAL(itExpected, itActual); } TEST_METHOD(MinusOperatorText); TEST_METHOD(MinusOperatorCell); template void DifferenceOperatorTestHelper() { const ptrdiff_t expected(3); auto it = GetIterator(); auto it2 = it + expected; const ptrdiff_t actual = it2 - it; VERIFY_ARE_EQUAL(expected, actual); } TEST_METHOD(DifferenceOperatorText); TEST_METHOD(DifferenceOperatorCell); TEST_METHOD(AsCharInfoCell); TEST_METHOD(DereferenceOperatorText); TEST_METHOD(DereferenceOperatorCell); TEST_METHOD(ConstructedNoLimit); TEST_METHOD(ConstructedLimits); }; void TextBufferIteratorTests::BoolOperatorText() { BoolOperatorTestHelper(); } void TextBufferIteratorTests::BoolOperatorCell() { BoolOperatorTestHelper(); Log::Comment(L"For cells, also check incrementing past the end."); const auto& outputBuffer = ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer(); const auto size = outputBuffer.GetBufferSize().Dimensions(); TextBufferCellIterator it(outputBuffer.GetTextBuffer(), { size.X - 1, size.Y - 1 }); VERIFY_IS_TRUE(it); it++; VERIFY_IS_FALSE(it); } void TextBufferIteratorTests::EqualsOperatorText() { EqualsOperatorTestHelper(); } void TextBufferIteratorTests::EqualsOperatorCell() { EqualsOperatorTestHelper(); } void TextBufferIteratorTests::NotEqualsOperatorText() { NotEqualsOperatorTestHelper(); } void TextBufferIteratorTests::NotEqualsOperatorCell() { NotEqualsOperatorTestHelper(); } void TextBufferIteratorTests::PlusEqualsOperatorText() { PlusEqualsOperatorTestHelper(); } void TextBufferIteratorTests::PlusEqualsOperatorCell() { PlusEqualsOperatorTestHelper(); } void TextBufferIteratorTests::MinusEqualsOperatorText() { MinusEqualsOperatorTestHelper(); } void TextBufferIteratorTests::MinusEqualsOperatorCell() { MinusEqualsOperatorTestHelper(); } void TextBufferIteratorTests::PrefixPlusPlusOperatorText() { PrefixPlusPlusOperatorTestHelper(); } void TextBufferIteratorTests::PrefixPlusPlusOperatorCell() { PrefixPlusPlusOperatorTestHelper(); } void TextBufferIteratorTests::PrefixMinusMinusOperatorText() { PrefixMinusMinusOperatorTestHelper(); } void TextBufferIteratorTests::PrefixMinusMinusOperatorCell() { PrefixMinusMinusOperatorTestHelper(); } void TextBufferIteratorTests::PostfixPlusPlusOperatorText() { PostfixPlusPlusOperatorTestHelper(); } void TextBufferIteratorTests::PostfixPlusPlusOperatorCell() { PostfixPlusPlusOperatorTestHelper(); } void TextBufferIteratorTests::PostfixMinusMinusOperatorText() { PostfixMinusMinusOperatorTestHelper(); } void TextBufferIteratorTests::PostfixMinusMinusOperatorCell() { PostfixMinusMinusOperatorTestHelper(); } void TextBufferIteratorTests::PlusOperatorText() { PlusOperatorTestHelper(); } void TextBufferIteratorTests::PlusOperatorCell() { PlusOperatorTestHelper(); } void TextBufferIteratorTests::MinusOperatorText() { MinusOperatorTestHelper(); } void TextBufferIteratorTests::MinusOperatorCell() { MinusOperatorTestHelper(); } void TextBufferIteratorTests::DifferenceOperatorText() { DifferenceOperatorTestHelper(); } void TextBufferIteratorTests::DifferenceOperatorCell() { DifferenceOperatorTestHelper(); } void TextBufferIteratorTests::AsCharInfoCell() { m_state->FillTextBuffer(); const auto it = GetIterator(); const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto& outputBuffer = gci.GetActiveOutputBuffer(); const auto& row = outputBuffer._textBuffer->GetRowByOffset(it._pos.Y); const auto wcharExpected = *row.GetCharRow().GlyphAt(it._pos.X).begin(); const auto attrExpected = row.GetAttrRow().GetAttrByColumn(it._pos.X); const auto cellActual = gci.AsCharInfo(*it); const auto wcharActual = cellActual.Char.UnicodeChar; const auto attrActual = it->TextAttr(); VERIFY_ARE_EQUAL(wcharExpected, wcharActual); VERIFY_ARE_EQUAL(attrExpected, attrActual); } void TextBufferIteratorTests::DereferenceOperatorText() { m_state->FillTextBuffer(); const auto it = GetIterator(); const auto& outputBuffer = ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer(); const auto& row = outputBuffer._textBuffer->GetRowByOffset(it._pos.Y); const auto wcharExpected = row.GetCharRow().GlyphAt(it._pos.X); const auto wcharActual = *it; VERIFY_ARE_EQUAL(*wcharExpected.begin(), *wcharActual.begin()); } void TextBufferIteratorTests::DereferenceOperatorCell() { m_state->FillTextBuffer(); const auto it = GetIterator(); const auto& outputBuffer = ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer(); const auto& row = outputBuffer._textBuffer->GetRowByOffset(it._pos.Y); const auto textExpected = (std::wstring_view)row.GetCharRow().GlyphAt(it._pos.X); const auto dbcsExpected = row.GetCharRow().DbcsAttrAt(it._pos.X); const auto attrExpected = row.GetAttrRow().GetAttrByColumn(it._pos.X); const auto cellActual = *it; const auto textActual = cellActual.Chars(); const auto dbcsActual = cellActual.DbcsAttr(); const auto attrActual = cellActual.TextAttr(); VERIFY_ARE_EQUAL(String(textExpected.data(), (int)textExpected.size()), String(textActual.data(), (int)textActual.size())); VERIFY_ARE_EQUAL(dbcsExpected, dbcsActual); VERIFY_ARE_EQUAL(attrExpected, attrActual); } void TextBufferIteratorTests::ConstructedNoLimit() { m_state->FillTextBuffer(); const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto& outputBuffer = gci.GetActiveOutputBuffer(); const auto& textBuffer = outputBuffer.GetTextBuffer(); const auto& bufferSize = textBuffer.GetSize(); TextBufferCellIterator it(textBuffer, { 0 }); VERIFY_IS_TRUE(it, L"Iterator is valid."); VERIFY_ARE_EQUAL(bufferSize, it._bounds, L"Bounds match the bounds of the text buffer."); const auto totalBufferDistance = bufferSize.Width() * bufferSize.Height(); // Advance buffer to one before the end. it += (totalBufferDistance - 1); VERIFY_IS_TRUE(it, L"Iterator is still valid."); // Advance over the end. it++; VERIFY_IS_FALSE(it, L"Iterator invalid now."); // Verify throws for out of range. VERIFY_THROWS_SPECIFIC(TextBufferCellIterator(textBuffer, { -1, -1 }), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; }); } void TextBufferIteratorTests::ConstructedLimits() { m_state->FillTextBuffer(); const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto& outputBuffer = gci.GetActiveOutputBuffer(); const auto& textBuffer = outputBuffer.GetTextBuffer(); SMALL_RECT limits; limits.Top = 1; limits.Bottom = 1; limits.Left = 3; limits.Right = 5; const auto viewport = Microsoft::Console::Types::Viewport::FromInclusive(limits); COORD pos; pos.X = limits.Left; pos.Y = limits.Top; TextBufferCellIterator it(textBuffer, pos, viewport); VERIFY_IS_TRUE(it, L"Iterator is valid."); VERIFY_ARE_EQUAL(viewport, it._bounds, L"Bounds match the bounds given."); const auto totalBufferDistance = viewport.Width() * viewport.Height(); // Advance buffer to one before the end. it += (totalBufferDistance - 1); VERIFY_IS_TRUE(it, L"Iterator is still valid."); // Advance over the end. it++; VERIFY_IS_FALSE(it, L"Iterator invalid now."); // Verify throws for out of range. VERIFY_THROWS_SPECIFIC(TextBufferCellIterator(textBuffer, { 0 }, viewport), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; }); // Verify throws for limit not inside buffer const auto bufferSize = textBuffer.GetSize(); VERIFY_THROWS_SPECIFIC(TextBufferCellIterator(textBuffer, pos, Microsoft::Console::Types::Viewport::FromInclusive(bufferSize.ToExclusive())), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; }); }