terminal/src/terminal/parser/ut_parser/OutputEngineTest.cpp
pythias 2c1ab620bf Tab to spaces (#578)
* tab to spaces

* change tab size to 4.
2019-05-13 18:06:36 -07:00

1651 lines
61 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include <wextestclass.h>
#include "../../inc/consoletaeftemplates.hpp"
#include "stateMachine.hpp"
#include "OutputStateMachineEngine.hpp"
#include "ascii.hpp"
using namespace Microsoft::Console::VirtualTerminal;
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
namespace Microsoft
{
namespace Console
{
namespace VirtualTerminal
{
class OutputEngineTest;
}
}
}
// From VT100.net...
// 9999-10000 is the classic boundary for most parsers parameter values.
// 16383-16384 is the boundary for DECSR commands according to EK-VT520-RM section 4.3.3.2
// 32767-32768 is our boundary SHORT_MAX for the Windows console
#define PARAM_VALUES L"{0, 1, 2, 1000, 9999, 10000, 16383, 16384, 32767, 32768, 50000, 999999999}"
class DummyDispatch final : public TermDispatch
{
public:
virtual void Execute(const wchar_t /*wchControl*/) override
{
}
virtual void Print(const wchar_t /*wchPrintable*/) override
{
}
virtual void PrintString(const wchar_t* const /*rgwch*/, const size_t /*cch*/) override
{
}
};
class Microsoft::Console::VirtualTerminal::OutputEngineTest final
{
TEST_CLASS(OutputEngineTest);
TEST_METHOD(TestEscapePath)
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:uiTest", L"{0,1,2,3,4,5,6,7,8,9,10,11}") // one value for each type of state test below.
END_TEST_METHOD_PROPERTIES()
unsigned int uiTest;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiTest", uiTest));
StateMachine mach(new OutputStateMachineEngine(new DummyDispatch));
// The OscString state shouldn't escape out after an ESC.
bool shouldEscapeOut = true;
switch (uiTest)
{
case 0:
{
Log::Comment(L"Escape from Ground.");
mach._state = StateMachine::VTStates::Ground;
break;
}
case 1:
{
Log::Comment(L"Escape from Escape.");
mach._state = StateMachine::VTStates::Escape;
break;
}
case 2:
{
Log::Comment(L"Escape from Escape Intermediate");
mach._state = StateMachine::VTStates::EscapeIntermediate;
break;
}
case 3:
{
Log::Comment(L"Escape from CsiEntry");
mach._state = StateMachine::VTStates::CsiEntry;
break;
}
case 4:
{
Log::Comment(L"Escape from CsiIgnore");
mach._state = StateMachine::VTStates::CsiIgnore;
break;
}
case 5:
{
Log::Comment(L"Escape from CsiParam");
mach._state = StateMachine::VTStates::CsiParam;
break;
}
case 6:
{
Log::Comment(L"Escape from CsiIntermediate");
mach._state = StateMachine::VTStates::CsiIntermediate;
break;
}
case 7:
{
Log::Comment(L"Escape from OscParam");
mach._state = StateMachine::VTStates::OscParam;
break;
}
case 8:
{
Log::Comment(L"Escape from OscString");
shouldEscapeOut = false;
mach._state = StateMachine::VTStates::OscString;
break;
}
case 9:
{
Log::Comment(L"Escape from OscTermination");
mach._state = StateMachine::VTStates::OscTermination;
break;
}
case 10:
{
Log::Comment(L"Escape from Ss3Entry");
mach._state = StateMachine::VTStates::Ss3Entry;
break;
}
case 11:
{
Log::Comment(L"Escape from Ss3Param");
mach._state = StateMachine::VTStates::Ss3Param;
break;
}
}
mach.ProcessCharacter(AsciiChars::ESC);
if(shouldEscapeOut)
{
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
}
}
TEST_METHOD(TestEscapeImmediatePath)
{
StateMachine mach(new OutputStateMachineEngine(new DummyDispatch));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'#');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::EscapeIntermediate);
mach.ProcessCharacter(L'(');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::EscapeIntermediate);
mach.ProcessCharacter(L')');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::EscapeIntermediate);
mach.ProcessCharacter(L'#');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::EscapeIntermediate);
mach.ProcessCharacter(L'6');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestEscapeThenC0Path)
{
StateMachine mach(new OutputStateMachineEngine(new DummyDispatch));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
// When we see a C0 control char in the escape state, the Output engine
// should execute it, without interrupting the sequence it's currently
// processing
mach.ProcessCharacter(L'\x03');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'[');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
mach.ProcessCharacter(L'3');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
mach.ProcessCharacter(L'1');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
mach.ProcessCharacter(L'm');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestGroundPrint)
{
StateMachine mach(new OutputStateMachineEngine(new DummyDispatch));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(L'a');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestCsiEntry)
{
StateMachine mach(new OutputStateMachineEngine(new DummyDispatch));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'[');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
mach.ProcessCharacter(L'm');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestC1CsiEntry)
{
StateMachine mach(new OutputStateMachineEngine(new DummyDispatch));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(L'\x9b');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
mach.ProcessCharacter(L'm');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestCsiImmediate)
{
StateMachine mach(new OutputStateMachineEngine(new DummyDispatch));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'[');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
mach.ProcessCharacter(L'$');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIntermediate);
mach.ProcessCharacter(L'#');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIntermediate);
mach.ProcessCharacter(L'%');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIntermediate);
mach.ProcessCharacter(L'v');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestCsiParam)
{
StateMachine mach(new OutputStateMachineEngine(new DummyDispatch));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'[');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
mach.ProcessCharacter(L'3');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
mach.ProcessCharacter(L'2');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
mach.ProcessCharacter(L'4');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
mach.ProcessCharacter(L'8');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
mach.ProcessCharacter(L'J');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestCsiIgnore)
{
StateMachine mach(new OutputStateMachineEngine(new DummyDispatch));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'[');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
mach.ProcessCharacter(L':');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIgnore);
mach.ProcessCharacter(L'3');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIgnore);
mach.ProcessCharacter(L'q');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'[');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
mach.ProcessCharacter(L'4');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
mach.ProcessCharacter(L':');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIgnore);
mach.ProcessCharacter(L'8');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIgnore);
mach.ProcessCharacter(L'J');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'[');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
mach.ProcessCharacter(L'4');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
mach.ProcessCharacter(L'#');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIntermediate);
mach.ProcessCharacter(L':');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIgnore);
mach.ProcessCharacter(L'8');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIgnore);
mach.ProcessCharacter(L'J');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestOscStringSimple)
{
StateMachine mach(new OutputStateMachineEngine(new DummyDispatch));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L']');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
mach.ProcessCharacter(L'0');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L's');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L'o');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L'm');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L'e');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L' ');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L't');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L'e');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L'x');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L't');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(AsciiChars::BEL);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L']');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
mach.ProcessCharacter(L'0');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L's');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L'o');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L'm');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L'e');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L' ');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L't');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L'e');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L'x');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L't');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscTermination);
mach.ProcessCharacter(L'\\');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestLongOscString)
{
StateMachine mach(new OutputStateMachineEngine(new DummyDispatch));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L']');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
mach.ProcessCharacter(L'0');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
mach.ProcessCharacter(L';');
for (int i = 0; i < MAX_PATH; i++) // The buffer is only 256 long, so any longer value should work :P
{
mach.ProcessCharacter(L's');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
}
VERIFY_ARE_EQUAL(mach._sOscNextChar, mach.s_cOscStringMaxLength - 1);
mach.ProcessCharacter(AsciiChars::BEL);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(NormalTestOscParam)
{
StateMachine mach(new OutputStateMachineEngine(new DummyDispatch));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L']');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
for (int i = 0; i < 5; i++) // We're only expecting to be able to keep 5 digits max
{
mach.ProcessCharacter((wchar_t)(L'1' + i));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
}
VERIFY_ARE_EQUAL(mach._sOscParam, 12345);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L's');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(AsciiChars::BEL);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestLongOscParam)
{
StateMachine mach(new OutputStateMachineEngine(new DummyDispatch));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L']');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
for (int i = 0; i < 6; i++) // We're only expecting to be able to keep 5 digits max
{
mach.ProcessCharacter((wchar_t)(L'1' + i));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
}
VERIFY_ARE_EQUAL(mach._sOscParam, SHORT_MAX);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L's');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(AsciiChars::BEL);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
Log::Comment(L"Make sure we cap the param value to SHORT_MAX");
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L']');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
for (int i = 0; i < 5; i++) // We're only expecting to be able to keep 5 digits max
{
mach.ProcessCharacter((wchar_t)(L'4' + i)); // 45678 > (SHORT_MAX===32767)
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
}
VERIFY_ARE_EQUAL(mach._sOscParam, SHORT_MAX);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L's');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(AsciiChars::BEL);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestSs3Entry)
{
StateMachine mach(new OutputStateMachineEngine(new DummyDispatch));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L'm');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestSs3Immediate)
{
// Intermediates aren't supported by Ss3 - they just get dispatched
StateMachine mach(new OutputStateMachineEngine(new DummyDispatch));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L'$');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L'#');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L'%');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L'?');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestSs3Param)
{
StateMachine mach(new OutputStateMachineEngine(new DummyDispatch));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L'3');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L'2');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L'4');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L'8');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L'J');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
};
class StatefulDispatch final : public TermDispatch
{
public:
virtual void Execute(const wchar_t /*wchControl*/) override
{
}
virtual void Print(const wchar_t /*wchPrintable*/) override
{
}
virtual void PrintString(const wchar_t* const /*rgwch*/, const size_t /*cch*/) override
{
}
StatefulDispatch() :
_uiCursorDistance{ 0 },
_uiLine{ 0 },
_uiColumn{ 0 },
_fCursorUp{ false },
_fCursorDown{ false },
_fCursorBackward{ false },
_fCursorForward{ false },
_fCursorNextLine{ false },
_fCursorPreviousLine{ false },
_fCursorHorizontalPositionAbsolute{ false },
_fVerticalLinePositionAbsolute{ false },
_fCursorPosition{ false },
_fCursorSave{ false },
_fCursorLoad{ false },
_fCursorVisible{ true },
_fEraseDisplay{ false },
_fEraseLine{ false },
_fInsertCharacter{ false },
_fDeleteCharacter{ false },
_eraseType{ (DispatchTypes::EraseType)-1 },
_fSetGraphics{ false },
_statusReportType{ (DispatchTypes::AnsiStatusType)-1 },
_fDeviceStatusReport{ false },
_fDeviceAttributes{ false },
_cOptions{ 0 },
_fIsAltBuffer{ false },
_fCursorKeysMode{ false },
_fCursorBlinking{ true },
_uiWindowWidth{ 80 }
{
memset(_rgOptions, s_uiGraphicsCleared, sizeof(_rgOptions));
}
void ClearState()
{
StatefulDispatch dispatch;
*this = dispatch;
}
bool CursorUp(_In_ unsigned int const uiDistance) override
{
_fCursorUp = true;
_uiCursorDistance = uiDistance;
return true;
}
bool CursorDown(_In_ unsigned int const uiDistance) override
{
_fCursorDown = true;
_uiCursorDistance = uiDistance;
return true;
}
bool CursorBackward(_In_ unsigned int const uiDistance) override
{
_fCursorBackward = true;
_uiCursorDistance = uiDistance;
return true;
}
bool CursorForward(_In_ unsigned int const uiDistance) override
{
_fCursorForward = true;
_uiCursorDistance = uiDistance;
return true;
}
bool CursorNextLine(_In_ unsigned int const uiDistance) override
{
_fCursorNextLine = true;
_uiCursorDistance = uiDistance;
return true;
}
bool CursorPrevLine(_In_ unsigned int const uiDistance) override
{
_fCursorPreviousLine = true;
_uiCursorDistance = uiDistance;
return true;
}
bool CursorHorizontalPositionAbsolute(_In_ unsigned int const uiPosition) override
{
_fCursorHorizontalPositionAbsolute = true;
_uiCursorDistance = uiPosition;
return true;
}
bool VerticalLinePositionAbsolute(_In_ unsigned int const uiPosition) override
{
_fVerticalLinePositionAbsolute = true;
_uiCursorDistance = uiPosition;
return true;
}
bool CursorPosition(_In_ unsigned int const uiLine, _In_ unsigned int const uiColumn) override
{
_fCursorPosition = true;
_uiLine = uiLine;
_uiColumn = uiColumn;
return true;
}
bool CursorSavePosition() override
{
_fCursorSave = true;
return true;
}
bool CursorRestorePosition() override
{
_fCursorLoad = true;
return true;
}
bool EraseInDisplay(const DispatchTypes::EraseType eraseType) override
{
_fEraseDisplay = true;
_eraseType = eraseType;
return true;
}
bool EraseInLine(const DispatchTypes::EraseType eraseType) override
{
_fEraseLine = true;
_eraseType = eraseType;
return true;
}
bool InsertCharacter(_In_ unsigned int const uiCount) override
{
_fInsertCharacter = true;
_uiCursorDistance = uiCount;
return true;
}
bool DeleteCharacter(_In_ unsigned int const uiCount) override
{
_fDeleteCharacter = true;
_uiCursorDistance = uiCount;
return true;
}
bool CursorVisibility(const bool fIsVisible) override
{
_fCursorVisible = fIsVisible;
return true;
}
bool SetGraphicsRendition(_In_reads_(cOptions) const DispatchTypes::GraphicsOptions* const rgOptions, const size_t cOptions) override
{
size_t cCopyLength = std::min(cOptions, s_cMaxOptions); // whichever is smaller, our buffer size or the number given
_cOptions = cCopyLength;
memcpy(_rgOptions, rgOptions, _cOptions * sizeof(DispatchTypes::GraphicsOptions));
_fSetGraphics = true;
return true;
}
bool DeviceStatusReport(const DispatchTypes::AnsiStatusType statusType) override
{
_fDeviceStatusReport = true;
_statusReportType = statusType;
return true;
}
bool DeviceAttributes() override
{
_fDeviceAttributes = true;
return true;
}
bool _PrivateModeParamsHelper(_In_ DispatchTypes::PrivateModeParams const param, const bool fEnable)
{
bool fSuccess = false;
switch(param)
{
case DispatchTypes::PrivateModeParams::DECCKM_CursorKeysMode:
// set - Enable Application Mode, reset - Numeric/normal mode
fSuccess = SetVirtualTerminalInputMode(fEnable);
break;
case DispatchTypes::PrivateModeParams::DECCOLM_SetNumberOfColumns:
fSuccess = SetColumns(static_cast<unsigned int>(fEnable ? DispatchTypes::s_sDECCOLMSetColumns : DispatchTypes::s_sDECCOLMResetColumns));
break;
case DispatchTypes::PrivateModeParams::ATT610_StartCursorBlink:
fSuccess = EnableCursorBlinking(fEnable);
break;
case DispatchTypes::PrivateModeParams::DECTCEM_TextCursorEnableMode:
fSuccess = CursorVisibility(fEnable);
break;
case DispatchTypes::PrivateModeParams::ASB_AlternateScreenBuffer:
fSuccess = fEnable? UseAlternateScreenBuffer() : UseMainScreenBuffer();
break;
default:
// If no functions to call, overall dispatch was a failure.
fSuccess = false;
break;
}
return fSuccess;
}
bool _SetResetPrivateModesHelper(_In_reads_(cParams) const DispatchTypes::PrivateModeParams* const rParams,
const size_t cParams,
const bool fEnable)
{
size_t cFailures = 0;
for (size_t i = 0; i < cParams; i++)
{
cFailures += _PrivateModeParamsHelper(rParams[i], fEnable)? 0 : 1; // increment the number of failures if we fail.
}
return cFailures == 0;
}
bool SetPrivateModes(_In_reads_(cParams) const DispatchTypes::PrivateModeParams* const rParams, const size_t cParams) override
{
return _SetResetPrivateModesHelper(rParams, cParams, true);
}
bool ResetPrivateModes(_In_reads_(cParams) const DispatchTypes::PrivateModeParams* const rParams, const size_t cParams) override
{
return _SetResetPrivateModesHelper(rParams, cParams, false);
}
bool SetColumns(_In_ unsigned int const uiColumns) override
{
_uiWindowWidth = uiColumns;
return true;
}
bool SetVirtualTerminalInputMode(const bool fApplicationMode)
{
_fCursorKeysMode = fApplicationMode;
return true;
}
bool EnableCursorBlinking(const bool bEnable) override
{
_fCursorBlinking = bEnable;
return true;
}
bool UseAlternateScreenBuffer() override
{
_fIsAltBuffer = true;
return true;
}
bool UseMainScreenBuffer() override
{
_fIsAltBuffer = false;
return true;
}
unsigned int _uiCursorDistance;
unsigned int _uiLine;
unsigned int _uiColumn;
bool _fCursorUp;
bool _fCursorDown;
bool _fCursorBackward;
bool _fCursorForward;
bool _fCursorNextLine;
bool _fCursorPreviousLine;
bool _fCursorHorizontalPositionAbsolute;
bool _fVerticalLinePositionAbsolute;
bool _fCursorPosition;
bool _fCursorSave;
bool _fCursorLoad;
bool _fCursorVisible;
bool _fEraseDisplay;
bool _fEraseLine;
bool _fInsertCharacter;
bool _fDeleteCharacter;
DispatchTypes::EraseType _eraseType;
bool _fSetGraphics;
DispatchTypes::AnsiStatusType _statusReportType;
bool _fDeviceStatusReport;
bool _fDeviceAttributes;
bool _fIsAltBuffer;
bool _fCursorKeysMode;
bool _fCursorBlinking;
unsigned int _uiWindowWidth;
static const size_t s_cMaxOptions = 16;
static const unsigned int s_uiGraphicsCleared = UINT_MAX;
DispatchTypes::GraphicsOptions _rgOptions[s_cMaxOptions];
size_t _cOptions;
};
class StateMachineExternalTest final
{
TEST_CLASS(StateMachineExternalTest);
TEST_METHOD_SETUP(SetupState)
{
return true;
}
void TestEscCursorMovement(wchar_t const wchCommand,
const bool* const pfFlag,
StateMachine& mach,
StatefulDispatch& dispatch)
{
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(wchCommand);
VERIFY_IS_TRUE(*pfFlag);
VERIFY_ARE_EQUAL(dispatch._uiCursorDistance, 1u);
}
TEST_METHOD(TestEscCursorMovement)
{
StatefulDispatch* pDispatch = new StatefulDispatch;
VERIFY_IS_NOT_NULL(pDispatch);
StateMachine mach(new OutputStateMachineEngine(pDispatch));
TestEscCursorMovement(L'A', &pDispatch->_fCursorUp, mach, *pDispatch);
TestEscCursorMovement(L'B', &pDispatch->_fCursorDown, mach, *pDispatch);
TestEscCursorMovement(L'C', &pDispatch->_fCursorForward, mach, *pDispatch);
TestEscCursorMovement(L'D', &pDispatch->_fCursorBackward, mach, *pDispatch);
}
void InsertNumberToMachine(StateMachine* const pMachine, unsigned int uiNumber)
{
static const size_t cchBufferMax = 20;
wchar_t pwszDistance[cchBufferMax];
int cchDistance = swprintf_s(pwszDistance, cchBufferMax, L"%d", uiNumber);
if (cchDistance > 0 && cchDistance < cchBufferMax)
{
for (int i = 0; i < cchDistance; i++)
{
pMachine->ProcessCharacter(pwszDistance[i]);
}
}
}
void ApplyParameterBoundary(unsigned int* uiExpected, unsigned int uiGiven)
{
// 0 and 1 should be 1. Use the preset value.
// 1-SHORT_MAX should be what we set.
// > SHORT_MAX should be SHORT_MAX.
if (uiGiven <= 1)
{
*uiExpected = 1u;
}
else if (uiGiven > 1 && uiGiven <= SHORT_MAX)
{
*uiExpected = uiGiven;
}
else if (uiGiven > SHORT_MAX)
{
*uiExpected = SHORT_MAX; // 16383 is our max value.
}
}
void TestCsiCursorMovement(wchar_t const wchCommand, unsigned int const uiDistance,
const bool fUseDistance,
const bool* const pfFlag,
StateMachine& mach,
StatefulDispatch& dispatch)
{
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
if (fUseDistance)
{
InsertNumberToMachine(&mach, uiDistance);
}
mach.ProcessCharacter(wchCommand);
VERIFY_IS_TRUE(*pfFlag);
unsigned int uiExpectedDistance = 1u;
if (fUseDistance)
{
ApplyParameterBoundary(&uiExpectedDistance, uiDistance);
}
VERIFY_ARE_EQUAL(dispatch._uiCursorDistance, uiExpectedDistance);
}
TEST_METHOD(TestCsiCursorMovementWithValues)
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:uiDistance", PARAM_VALUES)
END_TEST_METHOD_PROPERTIES()
unsigned int uiDistance;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiDistance", uiDistance));
StatefulDispatch* pDispatch = new StatefulDispatch;
VERIFY_IS_NOT_NULL(pDispatch);
StateMachine mach(new OutputStateMachineEngine(pDispatch));
TestCsiCursorMovement(L'A', uiDistance, true, &pDispatch->_fCursorUp, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'B', uiDistance, true, &pDispatch->_fCursorDown, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'C', uiDistance, true, &pDispatch->_fCursorForward, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'D', uiDistance, true, &pDispatch->_fCursorBackward, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'E', uiDistance, true, &pDispatch->_fCursorNextLine, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'F', uiDistance, true, &pDispatch->_fCursorPreviousLine, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'G', uiDistance, true, &pDispatch->_fCursorHorizontalPositionAbsolute, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'd', uiDistance, true, &pDispatch->_fVerticalLinePositionAbsolute, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'@', uiDistance, true, &pDispatch->_fInsertCharacter, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'P', uiDistance, true, &pDispatch->_fDeleteCharacter, mach, *pDispatch);
}
TEST_METHOD(TestCsiCursorMovementWithoutValues)
{
StatefulDispatch* pDispatch = new StatefulDispatch;
VERIFY_IS_NOT_NULL(pDispatch);
StateMachine mach(new OutputStateMachineEngine(pDispatch));
unsigned int uiDistance = 9999; // this value should be ignored with the false below.
TestCsiCursorMovement(L'A', uiDistance, false, &pDispatch->_fCursorUp, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'B', uiDistance, false, &pDispatch->_fCursorDown, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'C', uiDistance, false, &pDispatch->_fCursorForward, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'D', uiDistance, false, &pDispatch->_fCursorBackward, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'E', uiDistance, false, &pDispatch->_fCursorNextLine, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'F', uiDistance, false, &pDispatch->_fCursorPreviousLine, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'G', uiDistance, false, &pDispatch->_fCursorHorizontalPositionAbsolute, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'd', uiDistance, false, &pDispatch->_fVerticalLinePositionAbsolute, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'@', uiDistance, false, &pDispatch->_fInsertCharacter, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'P', uiDistance, false, &pDispatch->_fDeleteCharacter, mach, *pDispatch);
}
TEST_METHOD(TestCsiCursorPosition)
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:uiRow", PARAM_VALUES)
TEST_METHOD_PROPERTY(L"Data:uiCol", PARAM_VALUES)
END_TEST_METHOD_PROPERTIES()
unsigned int uiRow;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiRow", uiRow));
unsigned int uiCol;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiCol", uiCol));
StatefulDispatch* pDispatch = new StatefulDispatch;
VERIFY_IS_NOT_NULL(pDispatch);
StateMachine mach(new OutputStateMachineEngine(pDispatch));
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
InsertNumberToMachine(&mach, uiRow);
mach.ProcessCharacter(L';');
InsertNumberToMachine(&mach, uiCol);
mach.ProcessCharacter(L'H');
// bound the row/col values by the max we expect
ApplyParameterBoundary(&uiRow, uiRow);
ApplyParameterBoundary(&uiCol, uiCol);
VERIFY_IS_TRUE(pDispatch->_fCursorPosition);
VERIFY_ARE_EQUAL(pDispatch->_uiLine, uiRow);
VERIFY_ARE_EQUAL(pDispatch->_uiColumn, uiCol);
}
TEST_METHOD(TestCsiCursorPositionWithOnlyRow)
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:uiRow", PARAM_VALUES)
END_TEST_METHOD_PROPERTIES()
unsigned int uiRow;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiRow", uiRow));
StatefulDispatch* pDispatch = new StatefulDispatch;
VERIFY_IS_NOT_NULL(pDispatch);
StateMachine mach(new OutputStateMachineEngine(pDispatch));
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
InsertNumberToMachine(&mach, uiRow);
mach.ProcessCharacter(L'H');
// bound the row/col values by the max we expect
ApplyParameterBoundary(&uiRow, uiRow);
VERIFY_IS_TRUE(pDispatch->_fCursorPosition);
VERIFY_ARE_EQUAL(pDispatch->_uiLine, uiRow);
VERIFY_ARE_EQUAL(pDispatch->_uiColumn, (unsigned int)1); // Without the second param, the column should always be the default
}
TEST_METHOD(TestCursorSaveLoad)
{
StatefulDispatch* pDispatch = new StatefulDispatch;
VERIFY_IS_NOT_NULL(pDispatch);
StateMachine mach(new OutputStateMachineEngine(pDispatch));
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'7');
VERIFY_IS_TRUE(pDispatch->_fCursorSave);
pDispatch->ClearState();
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'8');
VERIFY_IS_TRUE(pDispatch->_fCursorLoad);
pDispatch->ClearState();
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L's');
VERIFY_IS_TRUE(pDispatch->_fCursorSave);
pDispatch->ClearState();
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'u');
VERIFY_IS_TRUE(pDispatch->_fCursorLoad);
pDispatch->ClearState();
}
TEST_METHOD(TestCursorKeysMode)
{
StatefulDispatch* pDispatch = new StatefulDispatch;
VERIFY_IS_NOT_NULL(pDispatch);
StateMachine mach(new OutputStateMachineEngine(pDispatch));
mach.ProcessString(L"\x1b[?1h", 5);
VERIFY_IS_TRUE(pDispatch->_fCursorKeysMode);
pDispatch->ClearState();
mach.ProcessString(L"\x1b[?1l", 5);
VERIFY_IS_FALSE(pDispatch->_fCursorKeysMode);
pDispatch->ClearState();
}
TEST_METHOD(TestSetNumberOfColumns)
{
StatefulDispatch* pDispatch = new StatefulDispatch;
VERIFY_IS_NOT_NULL(pDispatch);
StateMachine mach(new OutputStateMachineEngine(pDispatch));
mach.ProcessString(L"\x1b[?3h", 5);
VERIFY_ARE_EQUAL(pDispatch->_uiWindowWidth, static_cast<unsigned int>(DispatchTypes::s_sDECCOLMSetColumns));
pDispatch->ClearState();
mach.ProcessString(L"\x1b[?3l", 5);
VERIFY_ARE_EQUAL(pDispatch->_uiWindowWidth, static_cast<unsigned int>(DispatchTypes::s_sDECCOLMResetColumns));
pDispatch->ClearState();
}
TEST_METHOD(TestCursorBlinking)
{
StatefulDispatch* pDispatch = new StatefulDispatch;
VERIFY_IS_NOT_NULL(pDispatch);
StateMachine mach(new OutputStateMachineEngine(pDispatch));
mach.ProcessString(L"\x1b[?12h", 6);
VERIFY_IS_TRUE(pDispatch->_fCursorBlinking);
pDispatch->ClearState();
mach.ProcessString(L"\x1b[?12l", 6);
VERIFY_IS_FALSE(pDispatch->_fCursorBlinking);
pDispatch->ClearState();
}
TEST_METHOD(TestCursorVisibility)
{
StatefulDispatch* pDispatch = new StatefulDispatch;
VERIFY_IS_NOT_NULL(pDispatch);
StateMachine mach(new OutputStateMachineEngine(pDispatch));
mach.ProcessString(L"\x1b[?25h", 6);
VERIFY_IS_TRUE(pDispatch->_fCursorVisible);
pDispatch->ClearState();
mach.ProcessString(L"\x1b[?25l", 6);
VERIFY_IS_FALSE(pDispatch->_fCursorVisible);
pDispatch->ClearState();
}
TEST_METHOD(TestAltBufferSwapping)
{
StatefulDispatch* pDispatch = new StatefulDispatch;
VERIFY_IS_NOT_NULL(pDispatch);
StateMachine mach(new OutputStateMachineEngine(pDispatch));
mach.ProcessString(L"\x1b[?1049h", 8);
VERIFY_IS_TRUE(pDispatch->_fIsAltBuffer);
pDispatch->ClearState();
mach.ProcessString(L"\x1b[?1049h", 8);
VERIFY_IS_TRUE(pDispatch->_fIsAltBuffer);
mach.ProcessString(L"\x1b[?1049h", 8);
VERIFY_IS_TRUE(pDispatch->_fIsAltBuffer);
pDispatch->ClearState();
mach.ProcessString(L"\x1b[?1049l", 8);
VERIFY_IS_FALSE(pDispatch->_fIsAltBuffer);
pDispatch->ClearState();
mach.ProcessString(L"\x1b[?1049h", 8);
VERIFY_IS_TRUE(pDispatch->_fIsAltBuffer);
mach.ProcessString(L"\x1b[?1049l", 8);
VERIFY_IS_FALSE(pDispatch->_fIsAltBuffer);
pDispatch->ClearState();
mach.ProcessString(L"\x1b[?1049l", 8);
VERIFY_IS_FALSE(pDispatch->_fIsAltBuffer);
mach.ProcessString(L"\x1b[?1049l", 8);
VERIFY_IS_FALSE(pDispatch->_fIsAltBuffer);
pDispatch->ClearState();
}
TEST_METHOD(TestErase)
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:uiEraseOperation", L"{0, 1}") // for "display" and "line" type erase operations
TEST_METHOD_PROPERTY(L"Data:uiDispatchTypes::EraseType", L"{0, 1, 2, 10}") // maps to DispatchTypes::EraseType enum class options.
END_TEST_METHOD_PROPERTIES()
unsigned int uiEraseOperation;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiEraseOperation", uiEraseOperation));
unsigned int uiDispatchTypes;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiDispatchTypes::EraseType", uiDispatchTypes));
WCHAR wchOp = L'\0';
bool* pfOperationCallback = nullptr;
StatefulDispatch* pDispatch = new StatefulDispatch;
VERIFY_IS_NOT_NULL(pDispatch);
StateMachine mach(new OutputStateMachineEngine(pDispatch));
switch (uiEraseOperation)
{
case 0:
wchOp = L'J';
pfOperationCallback = &pDispatch->_fEraseDisplay;
break;
case 1:
wchOp = L'K';
pfOperationCallback = &pDispatch->_fEraseLine;
break;
default:
VERIFY_FAIL(L"Unknown erase operation permutation.");
}
VERIFY_IS_NOT_NULL(wchOp);
VERIFY_IS_NOT_NULL(pfOperationCallback);
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
DispatchTypes::EraseType expectedDispatchTypes;
switch (uiDispatchTypes)
{
case 0:
expectedDispatchTypes = DispatchTypes::EraseType::ToEnd;
InsertNumberToMachine(&mach, uiDispatchTypes);
break;
case 1:
expectedDispatchTypes = DispatchTypes::EraseType::FromBeginning;
InsertNumberToMachine(&mach, uiDispatchTypes);
break;
case 2:
expectedDispatchTypes = DispatchTypes::EraseType::All;
InsertNumberToMachine(&mach, uiDispatchTypes);
break;
case 10:
// Do nothing. Default case of 10 should be like a 0 to the end.
expectedDispatchTypes = DispatchTypes::EraseType::ToEnd;
break;
}
mach.ProcessCharacter(wchOp);
VERIFY_IS_TRUE(*pfOperationCallback);
VERIFY_ARE_EQUAL(expectedDispatchTypes, pDispatch->_eraseType);
}
void VerifyDispatchTypes(_In_reads_(cExpectedOptions) const DispatchTypes::GraphicsOptions* const rgExpectedOptions,
const size_t cExpectedOptions,
const StatefulDispatch& dispatch)
{
VERIFY_ARE_EQUAL(cExpectedOptions, dispatch._cOptions);
bool fOptionsValid = true;
for (size_t i = 0; i < dispatch.s_cMaxOptions; i++)
{
auto expectedOption = (DispatchTypes::GraphicsOptions)dispatch.s_uiGraphicsCleared;
if (i < cExpectedOptions)
{
expectedOption = rgExpectedOptions[i];
}
fOptionsValid = expectedOption == dispatch._rgOptions[i];
if (!fOptionsValid)
{
Log::Comment(NoThrowString().Format(L"Graphics option match failed, index [%zu]. Expected: '%d' Actual: '%d'", i, expectedOption, dispatch._rgOptions[i]));
break;
}
}
VERIFY_IS_TRUE(fOptionsValid);
}
TEST_METHOD(TestSetGraphicsRendition)
{
StatefulDispatch* pDispatch = new StatefulDispatch;
VERIFY_IS_NOT_NULL(pDispatch);
StateMachine mach(new OutputStateMachineEngine(pDispatch));
DispatchTypes::GraphicsOptions rgExpected[16];
Log::Comment(L"Test 1: Check default case.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'm');
VERIFY_IS_TRUE(pDispatch->_fSetGraphics);
rgExpected[0] = DispatchTypes::GraphicsOptions::Off;
VerifyDispatchTypes(rgExpected, 1, *pDispatch);
pDispatch->ClearState();
Log::Comment(L"Test 2: Check clear/0 case.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'0');
mach.ProcessCharacter(L'm');
VERIFY_IS_TRUE(pDispatch->_fSetGraphics);
rgExpected[0] = DispatchTypes::GraphicsOptions::Off;
VerifyDispatchTypes(rgExpected, 1, *pDispatch);
pDispatch->ClearState();
Log::Comment(L"Test 3: Check 'handful of options' case.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'1');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'4');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'7');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'3');
mach.ProcessCharacter(L'0');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'4');
mach.ProcessCharacter(L'5');
mach.ProcessCharacter(L'm');
VERIFY_IS_TRUE(pDispatch->_fSetGraphics);
rgExpected[0] = DispatchTypes::GraphicsOptions::BoldBright;
rgExpected[1] = DispatchTypes::GraphicsOptions::Underline;
rgExpected[2] = DispatchTypes::GraphicsOptions::Negative;
rgExpected[3] = DispatchTypes::GraphicsOptions::ForegroundBlack;
rgExpected[4] = DispatchTypes::GraphicsOptions::BackgroundMagenta;
VerifyDispatchTypes(rgExpected, 5, *pDispatch);
pDispatch->ClearState();
Log::Comment(L"Test 4: Check 'too many options' (>16) case.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'1');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'4');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'1');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'4');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'1');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'4');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'1');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'4');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'1');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'4');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'1');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'4');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'1');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'4');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'1');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'4');
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'1');
mach.ProcessCharacter(L'm');
VERIFY_IS_TRUE(pDispatch->_fSetGraphics);
rgExpected[0] = DispatchTypes::GraphicsOptions::BoldBright;
rgExpected[1] = DispatchTypes::GraphicsOptions::Underline;
rgExpected[2] = DispatchTypes::GraphicsOptions::BoldBright;
rgExpected[3] = DispatchTypes::GraphicsOptions::Underline;
rgExpected[4] = DispatchTypes::GraphicsOptions::BoldBright;
rgExpected[5] = DispatchTypes::GraphicsOptions::Underline;
rgExpected[6] = DispatchTypes::GraphicsOptions::BoldBright;
rgExpected[7] = DispatchTypes::GraphicsOptions::Underline;
rgExpected[8] = DispatchTypes::GraphicsOptions::BoldBright;
rgExpected[9] = DispatchTypes::GraphicsOptions::Underline;
rgExpected[10] = DispatchTypes::GraphicsOptions::BoldBright;
rgExpected[11] = DispatchTypes::GraphicsOptions::Underline;
rgExpected[12] = DispatchTypes::GraphicsOptions::BoldBright;
rgExpected[13] = DispatchTypes::GraphicsOptions::Underline;
rgExpected[14] = DispatchTypes::GraphicsOptions::BoldBright;
rgExpected[15] = DispatchTypes::GraphicsOptions::Underline;
VerifyDispatchTypes(rgExpected, 16, *pDispatch);
pDispatch->ClearState();
Log::Comment(L"Test 5.a: Test an empty param at the end of a sequence");
std::wstring sequence = L"\x1b[1;m";
mach.ProcessString(&sequence[0], sequence.length());
VERIFY_IS_TRUE(pDispatch->_fSetGraphics);
rgExpected[0] = DispatchTypes::GraphicsOptions::BoldBright;
rgExpected[1] = DispatchTypes::GraphicsOptions::Off;
VerifyDispatchTypes(rgExpected, 2, *pDispatch);
pDispatch->ClearState();
Log::Comment(L"Test 5.b: Test an empty param in the middle of a sequence");
sequence = L"\x1b[1;;1m";
mach.ProcessString(&sequence[0], sequence.length());
VERIFY_IS_TRUE(pDispatch->_fSetGraphics);
rgExpected[0] = DispatchTypes::GraphicsOptions::BoldBright;
rgExpected[1] = DispatchTypes::GraphicsOptions::Off;
rgExpected[2] = DispatchTypes::GraphicsOptions::BoldBright;
VerifyDispatchTypes(rgExpected, 3, *pDispatch);
pDispatch->ClearState();
Log::Comment(L"Test 5.c: Test an empty param at the start of a sequence");
sequence = L"\x1b[;31;1m";
mach.ProcessString(&sequence[0], sequence.length());
VERIFY_IS_TRUE(pDispatch->_fSetGraphics);
rgExpected[0] = DispatchTypes::GraphicsOptions::Off;
rgExpected[1] = DispatchTypes::GraphicsOptions::ForegroundRed;
rgExpected[2] = DispatchTypes::GraphicsOptions::BoldBright;
VerifyDispatchTypes(rgExpected, 3, *pDispatch);
pDispatch->ClearState();
}
TEST_METHOD(TestDeviceStatusReport)
{
StatefulDispatch* pDispatch = new StatefulDispatch;
VERIFY_IS_NOT_NULL(pDispatch);
StateMachine mach(new OutputStateMachineEngine(pDispatch));
Log::Comment(L"Test 1: Check empty case. Should fail.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'n');
VERIFY_IS_FALSE(pDispatch->_fDeviceStatusReport);
pDispatch->ClearState();
Log::Comment(L"Test 2: Check CSR (cursor position command) case 6. Should succeed.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'6');
mach.ProcessCharacter(L'n');
VERIFY_IS_TRUE(pDispatch->_fDeviceStatusReport);
VERIFY_ARE_EQUAL(DispatchTypes::AnsiStatusType::CPR_CursorPositionReport, pDispatch->_statusReportType);
pDispatch->ClearState();
Log::Comment(L"Test 3: Check unimplemented case 1. Should fail.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'1');
mach.ProcessCharacter(L'n');
VERIFY_IS_FALSE(pDispatch->_fDeviceStatusReport);
pDispatch->ClearState();
}
TEST_METHOD(TestDeviceAttributes)
{
StatefulDispatch* pDispatch = new StatefulDispatch;
VERIFY_IS_NOT_NULL(pDispatch);
StateMachine mach(new OutputStateMachineEngine(pDispatch));
Log::Comment(L"Test 1: Check default case, no params.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'c');
VERIFY_IS_TRUE(pDispatch->_fDeviceAttributes);
pDispatch->ClearState();
Log::Comment(L"Test 2: Check default case, 0 param.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'0');
mach.ProcessCharacter(L'c');
VERIFY_IS_TRUE(pDispatch->_fDeviceAttributes);
pDispatch->ClearState();
Log::Comment(L"Test 3: Check fail case, 1 (or any other) param.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'1');
mach.ProcessCharacter(L'c');
VERIFY_IS_FALSE(pDispatch->_fDeviceAttributes);
pDispatch->ClearState();
}
TEST_METHOD(TestStrings)
{
StatefulDispatch* pDispatch = new StatefulDispatch;
VERIFY_IS_NOT_NULL(pDispatch);
StateMachine mach(new OutputStateMachineEngine(pDispatch));
DispatchTypes::GraphicsOptions rgExpected[16];
DispatchTypes::EraseType expectedDispatchTypes;
///////////////////////////////////////////////////////////////////////
Log::Comment(L"Test 1: Basic String processing. One sequence in a string.");
mach.ProcessString(L"\x1b[0m", 4);
VERIFY_IS_TRUE(pDispatch->_fSetGraphics);
pDispatch->ClearState();
///////////////////////////////////////////////////////////////////////
Log::Comment(L"Test 2: A couple of sequences all in one string");
mach.ProcessString(L"\x1b[1;4;7;30;45m\x1b[2J", 18);
VERIFY_IS_TRUE(pDispatch->_fSetGraphics);
VERIFY_IS_TRUE(pDispatch->_fEraseDisplay);
rgExpected[0] = DispatchTypes::GraphicsOptions::BoldBright;
rgExpected[1] = DispatchTypes::GraphicsOptions::Underline;
rgExpected[2] = DispatchTypes::GraphicsOptions::Negative;
rgExpected[3] = DispatchTypes::GraphicsOptions::ForegroundBlack;
rgExpected[4] = DispatchTypes::GraphicsOptions::BackgroundMagenta;
expectedDispatchTypes = DispatchTypes::EraseType::All;
VerifyDispatchTypes(rgExpected, 5, *pDispatch);
VERIFY_ARE_EQUAL(expectedDispatchTypes, pDispatch->_eraseType);
pDispatch->ClearState();
///////////////////////////////////////////////////////////////////////
Log::Comment(L"Test 3: Two sequences seperated by a non-sequence of characters");
mach.ProcessString(L"\x1b[1;30mHello World\x1b[2J", 22);
rgExpected[0] = DispatchTypes::GraphicsOptions::BoldBright;
rgExpected[1] = DispatchTypes::GraphicsOptions::ForegroundBlack;
expectedDispatchTypes = DispatchTypes::EraseType::All;
VERIFY_IS_TRUE(pDispatch->_fSetGraphics);
VERIFY_IS_TRUE(pDispatch->_fEraseDisplay);
VerifyDispatchTypes(rgExpected, 2, *pDispatch);
VERIFY_ARE_EQUAL(expectedDispatchTypes, pDispatch->_eraseType);
pDispatch->ClearState();
///////////////////////////////////////////////////////////////////////
Log::Comment(L"Test 4: An entire sequence broke into multiple strings");
mach.ProcessString(L"\x1b[1;", 4);
VERIFY_IS_FALSE(pDispatch->_fSetGraphics);
VERIFY_IS_FALSE(pDispatch->_fEraseDisplay);
mach.ProcessString(L"30mHello World\x1b[2J", 18);
rgExpected[0] = DispatchTypes::GraphicsOptions::BoldBright;
rgExpected[1] = DispatchTypes::GraphicsOptions::ForegroundBlack;
expectedDispatchTypes = DispatchTypes::EraseType::All;
VERIFY_IS_TRUE(pDispatch->_fSetGraphics);
VERIFY_IS_TRUE(pDispatch->_fEraseDisplay);
VerifyDispatchTypes(rgExpected, 2, *pDispatch);
VERIFY_ARE_EQUAL(expectedDispatchTypes, pDispatch->_eraseType);
pDispatch->ClearState();
///////////////////////////////////////////////////////////////////////
Log::Comment(L"Test 5: A sequence with mixed ProcessCharacter and ProcessString calls");
rgExpected[0] = DispatchTypes::GraphicsOptions::BoldBright;
rgExpected[1] = DispatchTypes::GraphicsOptions::ForegroundBlack;
mach.ProcessString(L"\x1b[1;", 4);
VERIFY_IS_FALSE(pDispatch->_fSetGraphics);
VERIFY_IS_FALSE(pDispatch->_fEraseDisplay);
mach.ProcessCharacter(L'3');
VERIFY_IS_FALSE(pDispatch->_fSetGraphics);
VERIFY_IS_FALSE(pDispatch->_fEraseDisplay);
mach.ProcessCharacter(L'0');
VERIFY_IS_FALSE(pDispatch->_fSetGraphics);
VERIFY_IS_FALSE(pDispatch->_fEraseDisplay);
mach.ProcessCharacter(L'm');
VERIFY_IS_TRUE(pDispatch->_fSetGraphics);
VERIFY_IS_FALSE(pDispatch->_fEraseDisplay);
VerifyDispatchTypes(rgExpected, 2, *pDispatch);
mach.ProcessString(L"Hello World\x1b[2J", 15);
expectedDispatchTypes = DispatchTypes::EraseType::All;
VERIFY_IS_TRUE(pDispatch->_fEraseDisplay);
VERIFY_ARE_EQUAL(expectedDispatchTypes, pDispatch->_eraseType);
pDispatch->ClearState();
}
};