// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "pch.h" #include "../TerminalControl/EventArgs.h" #include "../TerminalControl/ControlCore.h" #include "MockControlSettings.h" #include "MockConnection.h" #include "../UnitTests_TerminalCore/TestUtils.h" using namespace Microsoft::Console; using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace WEX::Common; using namespace winrt; using namespace winrt::Microsoft::Terminal; namespace ControlUnitTests { class ControlCoreTests { BEGIN_TEST_CLASS(ControlCoreTests) TEST_CLASS_PROPERTY(L"TestTimeout", L"0:0:10") // 10s timeout END_TEST_CLASS() TEST_METHOD(ComPtrSettings); TEST_METHOD(InstantiateCore); TEST_METHOD(TestInitialize); TEST_METHOD(TestAdjustAcrylic); TEST_METHOD(TestFreeAfterClose); TEST_METHOD(TestFontInitializedInCtor); TEST_METHOD(TestClearScrollback); TEST_METHOD(TestClearScreen); TEST_METHOD(TestClearAll); TEST_CLASS_SETUP(ModuleSetup) { winrt::init_apartment(winrt::apartment_type::single_threaded); return true; } TEST_CLASS_CLEANUP(ClassCleanup) { winrt::uninit_apartment(); return true; } std::tuple, winrt::com_ptr> _createSettingsAndConnection() { Log::Comment(L"Create settings object"); auto settings = winrt::make_self(); VERIFY_IS_NOT_NULL(settings); Log::Comment(L"Create connection object"); auto conn = winrt::make_self(); VERIFY_IS_NOT_NULL(conn); return { settings, conn }; } winrt::com_ptr createCore(Control::IControlSettings settings, TerminalConnection::ITerminalConnection conn) { Log::Comment(L"Create ControlCore object"); auto core = winrt::make_self(settings, conn); core->_inUnitTests = true; return core; } void _standardInit(winrt::com_ptr core) { // "Consolas" ends up with an actual size of 9x21 at 96DPI. So // let's just arbitrarily start with a 270x420px (30x20 chars) window core->Initialize(270, 420, 1.0); VERIFY_IS_TRUE(core->_initializedTerminal); VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height()); } }; void ControlCoreTests::ComPtrSettings() { Log::Comment(L"Just make sure we can instantiate a settings obj in a com_ptr"); auto settings = winrt::make_self(); Log::Comment(L"Verify literally any setting, it doesn't matter"); VERIFY_ARE_EQUAL(DEFAULT_FOREGROUND, settings->DefaultForeground()); } void ControlCoreTests::InstantiateCore() { auto [settings, conn] = _createSettingsAndConnection(); auto core = createCore(*settings, *conn); VERIFY_IS_NOT_NULL(core); } void ControlCoreTests::TestInitialize() { auto [settings, conn] = _createSettingsAndConnection(); auto core = createCore(*settings, *conn); VERIFY_IS_NOT_NULL(core); VERIFY_IS_FALSE(core->_initializedTerminal); // "Consolas" ends up with an actual size of 9x21 at 96DPI. So // let's just arbitrarily start with a 270x420px (30x20 chars) window core->Initialize(270, 420, 1.0); VERIFY_IS_TRUE(core->_initializedTerminal); VERIFY_ARE_EQUAL(30, core->_terminal->GetViewport().Width()); } void ControlCoreTests::TestAdjustAcrylic() { auto [settings, conn] = _createSettingsAndConnection(); settings->UseAcrylic(true); settings->Opacity(0.5f); auto core = createCore(*settings, *conn); VERIFY_IS_NOT_NULL(core); // A callback to make sure that we're raising TransparencyChanged events double expectedOpacity = 0.5; auto opacityCallback = [&](auto&&, Control::TransparencyChangedEventArgs args) mutable { VERIFY_ARE_EQUAL(expectedOpacity, args.Opacity()); VERIFY_ARE_EQUAL(expectedOpacity, settings->Opacity()); VERIFY_ARE_EQUAL(expectedOpacity, core->_settings.Opacity()); if (expectedOpacity < 1.0) { VERIFY_IS_TRUE(settings->UseAcrylic()); VERIFY_IS_TRUE(core->_settings.UseAcrylic()); } // GH#603: Adjusting opacity shouldn't change whether or not we // requested acrylic. VERIFY_IS_TRUE(settings->UseAcrylic()); VERIFY_IS_TRUE(core->_settings.UseAcrylic()); }; core->TransparencyChanged(opacityCallback); VERIFY_IS_FALSE(core->_initializedTerminal); // "Cascadia Mono" ends up with an actual size of 9x19 at 96DPI. So // let's just arbitrarily start with a 270x380px (30x20 chars) window core->Initialize(270, 380, 1.0); VERIFY_IS_TRUE(core->_initializedTerminal); Log::Comment(L"Increasing opacity till fully opaque"); expectedOpacity += 0.1; // = 0.6; core->AdjustOpacity(0.1); expectedOpacity += 0.1; // = 0.7; core->AdjustOpacity(0.1); expectedOpacity += 0.1; // = 0.8; core->AdjustOpacity(0.1); expectedOpacity += 0.1; // = 0.9; core->AdjustOpacity(0.1); expectedOpacity += 0.1; // = 1.0; // cast to float because floating point numbers are mean VERIFY_ARE_EQUAL(1.0f, base::saturated_cast(expectedOpacity)); core->AdjustOpacity(0.1); Log::Comment(L"Increasing opacity more doesn't actually change it to be >1.0"); expectedOpacity = 1.0; core->AdjustOpacity(0.1); Log::Comment(L"Decrease opacity"); expectedOpacity -= 0.25; // = 0.75; core->AdjustOpacity(-0.25); expectedOpacity -= 0.25; // = 0.5; core->AdjustOpacity(-0.25); expectedOpacity -= 0.25; // = 0.25; core->AdjustOpacity(-0.25); expectedOpacity -= 0.25; // = 0.05; // cast to float because floating point numbers are mean VERIFY_ARE_EQUAL(0.0f, base::saturated_cast(expectedOpacity)); core->AdjustOpacity(-0.25); Log::Comment(L"Decreasing opacity more doesn't actually change it to be < 0"); expectedOpacity = 0.0; core->AdjustOpacity(-0.25); } void ControlCoreTests::TestFreeAfterClose() { { auto [settings, conn] = _createSettingsAndConnection(); auto core = createCore(*settings, *conn); VERIFY_IS_NOT_NULL(core); Log::Comment(L"Close the Core, like a TermControl would"); core->Close(); } VERIFY_IS_TRUE(true, L"Make sure that the test didn't crash when the core when out of scope"); } void ControlCoreTests::TestFontInitializedInCtor() { // This is to catch a dumb programming mistake I made while working on // the core/control split. We want the font initialized in the ctor, // before we even get to Core::Initialize. auto [settings, conn] = _createSettingsAndConnection(); // Make sure to use something dumb like "Impact" as a font name here so // that you don't default to Cascadia* settings->FontFace(L"Impact"); auto core = createCore(*settings, *conn); VERIFY_IS_NOT_NULL(core); VERIFY_ARE_EQUAL(L"Impact", std::wstring_view{ core->_actualFont.GetFaceName() }); } void ControlCoreTests::TestClearScrollback() { auto [settings, conn] = _createSettingsAndConnection(); Log::Comment(L"Create ControlCore object"); auto core = createCore(*settings, *conn); VERIFY_IS_NOT_NULL(core); _standardInit(core); Log::Comment(L"Print 40 rows of 'Foo', and a single row of 'Bar' " L"(leaving the cursor afer 'Bar')"); for (int i = 0; i < 40; ++i) { conn->WriteInput(L"Foo\r\n"); } conn->WriteInput(L"Bar"); // We printed that 40 times, but the final \r\n bumped the view down one MORE row. Log::Comment(L"Check the buffer viewport before the clear"); VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height()); VERIFY_ARE_EQUAL(21, core->ScrollOffset()); VERIFY_ARE_EQUAL(20, core->ViewHeight()); VERIFY_ARE_EQUAL(41, core->BufferHeight()); Log::Comment(L"Clear the buffer"); core->ClearBuffer(Control::ClearBufferType::Scrollback); Log::Comment(L"Check the buffer after the clear"); VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height()); VERIFY_ARE_EQUAL(0, core->ScrollOffset()); VERIFY_ARE_EQUAL(20, core->ViewHeight()); VERIFY_ARE_EQUAL(20, core->BufferHeight()); // In this test, we can't actually check if we cleared the buffer // contents. ConPTY will handle the actual clearing of the buffer // contents. We can only ensure that the viewport moved when we did a // clear scrollback. // // The ConptyRoundtripTests test the actual clearing of the contents. } void ControlCoreTests::TestClearScreen() { auto [settings, conn] = _createSettingsAndConnection(); Log::Comment(L"Create ControlCore object"); auto core = createCore(*settings, *conn); VERIFY_IS_NOT_NULL(core); _standardInit(core); Log::Comment(L"Print 40 rows of 'Foo', and a single row of 'Bar' " L"(leaving the cursor afer 'Bar')"); for (int i = 0; i < 40; ++i) { conn->WriteInput(L"Foo\r\n"); } conn->WriteInput(L"Bar"); // We printed that 40 times, but the final \r\n bumped the view down one MORE row. Log::Comment(L"Check the buffer viewport before the clear"); VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height()); VERIFY_ARE_EQUAL(21, core->ScrollOffset()); VERIFY_ARE_EQUAL(20, core->ViewHeight()); VERIFY_ARE_EQUAL(41, core->BufferHeight()); Log::Comment(L"Clear the buffer"); core->ClearBuffer(Control::ClearBufferType::Screen); Log::Comment(L"Check the buffer after the clear"); VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height()); VERIFY_ARE_EQUAL(21, core->ScrollOffset()); VERIFY_ARE_EQUAL(20, core->ViewHeight()); VERIFY_ARE_EQUAL(41, core->BufferHeight()); // In this test, we can't actually check if we cleared the buffer // contents. ConPTY will handle the actual clearing of the buffer // contents. We can only ensure that the viewport moved when we did a // clear scrollback. // // The ConptyRoundtripTests test the actual clearing of the contents. } void ControlCoreTests::TestClearAll() { auto [settings, conn] = _createSettingsAndConnection(); Log::Comment(L"Create ControlCore object"); auto core = createCore(*settings, *conn); VERIFY_IS_NOT_NULL(core); _standardInit(core); Log::Comment(L"Print 40 rows of 'Foo', and a single row of 'Bar' " L"(leaving the cursor afer 'Bar')"); for (int i = 0; i < 40; ++i) { conn->WriteInput(L"Foo\r\n"); } conn->WriteInput(L"Bar"); // We printed that 40 times, but the final \r\n bumped the view down one MORE row. Log::Comment(L"Check the buffer viewport before the clear"); VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height()); VERIFY_ARE_EQUAL(21, core->ScrollOffset()); VERIFY_ARE_EQUAL(20, core->ViewHeight()); VERIFY_ARE_EQUAL(41, core->BufferHeight()); Log::Comment(L"Clear the buffer"); core->ClearBuffer(Control::ClearBufferType::All); Log::Comment(L"Check the buffer after the clear"); VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height()); VERIFY_ARE_EQUAL(0, core->ScrollOffset()); VERIFY_ARE_EQUAL(20, core->ViewHeight()); VERIFY_ARE_EQUAL(20, core->BufferHeight()); // In this test, we can't actually check if we cleared the buffer // contents. ConPTY will handle the actual clearing of the buffer // contents. We can only ensure that the viewport moved when we did a // clear scrollback. // // The ConptyRoundtripTests test the actual clearing of the contents. } }