terminal/src/til/ut_til/SizeTests.cpp
Mike Griese 7612044363
Implement a pair of shims for cls, Clear-Host in conpty mode (#5627)
## Summary of the Pull Request

This PR implements a pair of shims for `cmd` and `powershell`, so that their `cls` and `Clear-Host` functions will clear the entire terminal buffer (like they do in conhost), instead of just the viewport. With the conpty viewport and buffer being the same size, there's effectively no way to know if an application is calling these API's in this way with the intention of clearing the buffer or the viewport. We absolutely have to guess. 

Each of these shims checks to see if the way that the API is being called exactly matches the way `cmd` or `powershell` would call these APIs. If it does, we manually write a `^[[3J` to the connected terminal, to get he Terminal to clear it's own scrollback.

~~_⚠️ If another application were trying to clear the **viewport** with an exactly similar API call, this would also cause the terminal scrollback to get cleared ⚠️_~~

* [x] Should these shims be restricted to when the process that's calling them is actually `cmd.exe` or `powershell.exe`? Can I even do this? I think we've done such a good job of isolating the client process information from the rest of the host code that I can't figure out how to do this.
  - YES, this can be done, and I did it.
* [ ] **TODO**: _While I'm here_, should I have `DoSrvPrivateEraseAll` (the implementation for `^[[2J`, in `getset.cpp`) also manually trigger a EraseAll in the terminal in conpty mode?

## PR Checklist
* [x] Closes #3126
* [x] Actually closes #1305 too, which is really the same thing, but probably deserves a callout
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Validation Steps Performed
* ran tests
* checked `cls` in the Terminal
* checked `Clear-Host` in the Terminal
* Checked running `powershell clear-host` from `cmd.exe`
2020-04-30 21:53:31 +00:00

729 lines
23 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "til/size.h"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
class SizeTests
{
TEST_CLASS(SizeTests);
TEST_METHOD(DefaultConstruct)
{
const til::size sz;
VERIFY_ARE_EQUAL(0, sz._width);
VERIFY_ARE_EQUAL(0, sz._height);
}
TEST_METHOD(RawConstruct)
{
const til::size sz{ 5, 10 };
VERIFY_ARE_EQUAL(5, sz._width);
VERIFY_ARE_EQUAL(10, sz._height);
}
TEST_METHOD(RawFloatingConstruct)
{
const til::size sz{ til::math::rounding, 3.2f, 7.8f };
VERIFY_ARE_EQUAL(3, sz._width);
VERIFY_ARE_EQUAL(8, sz._height);
}
TEST_METHOD(UnsignedConstruct)
{
Log::Comment(L"0.) Normal unsigned construct.");
{
const size_t width = 5;
const size_t height = 10;
const til::size sz{ width, height };
VERIFY_ARE_EQUAL(5, sz._width);
VERIFY_ARE_EQUAL(10, sz._height);
}
Log::Comment(L"1.) Unsigned construct overflow on width.");
{
constexpr size_t width = std::numeric_limits<size_t>().max();
const size_t height = 10;
auto fn = [&]() {
til::size sz{ width, height };
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
Log::Comment(L"2.) Unsigned construct overflow on height.");
{
constexpr size_t height = std::numeric_limits<size_t>().max();
const size_t width = 10;
auto fn = [&]() {
til::size sz{ width, height };
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
}
TEST_METHOD(SignedConstruct)
{
const ptrdiff_t width = -5;
const ptrdiff_t height = -10;
const til::size sz{ width, height };
VERIFY_ARE_EQUAL(width, sz._width);
VERIFY_ARE_EQUAL(height, sz._height);
}
TEST_METHOD(MixedRawTypeConstruct)
{
const ptrdiff_t a = -5;
const int b = -10;
Log::Comment(L"Case 1: ptrdiff_t/int");
{
const til::size sz{ a, b };
VERIFY_ARE_EQUAL(a, sz._width);
VERIFY_ARE_EQUAL(b, sz._height);
}
Log::Comment(L"Case 2: int/ptrdiff_t");
{
const til::size sz{ b, a };
VERIFY_ARE_EQUAL(b, sz._width);
VERIFY_ARE_EQUAL(a, sz._height);
}
}
TEST_METHOD(CoordConstruct)
{
COORD coord{ -5, 10 };
const til::size sz{ coord };
VERIFY_ARE_EQUAL(coord.X, sz._width);
VERIFY_ARE_EQUAL(coord.Y, sz._height);
}
TEST_METHOD(SizeConstruct)
{
SIZE size{ 5, -10 };
const til::size sz{ size };
VERIFY_ARE_EQUAL(size.cx, sz._width);
VERIFY_ARE_EQUAL(size.cy, sz._height);
}
TEST_METHOD(Equality)
{
Log::Comment(L"0.) Equal.");
{
const til::size s1{ 5, 10 };
const til::size s2{ 5, 10 };
VERIFY_IS_TRUE(s1 == s2);
}
Log::Comment(L"1.) Left Width changed.");
{
const til::size s1{ 4, 10 };
const til::size s2{ 5, 10 };
VERIFY_IS_FALSE(s1 == s2);
}
Log::Comment(L"2.) Right Width changed.");
{
const til::size s1{ 5, 10 };
const til::size s2{ 6, 10 };
VERIFY_IS_FALSE(s1 == s2);
}
Log::Comment(L"3.) Left Height changed.");
{
const til::size s1{ 5, 9 };
const til::size s2{ 5, 10 };
VERIFY_IS_FALSE(s1 == s2);
}
Log::Comment(L"4.) Right Height changed.");
{
const til::size s1{ 5, 10 };
const til::size s2{ 5, 11 };
VERIFY_IS_FALSE(s1 == s2);
}
}
TEST_METHOD(Inequality)
{
Log::Comment(L"0.) Equal.");
{
const til::size s1{ 5, 10 };
const til::size s2{ 5, 10 };
VERIFY_IS_FALSE(s1 != s2);
}
Log::Comment(L"1.) Left Width changed.");
{
const til::size s1{ 4, 10 };
const til::size s2{ 5, 10 };
VERIFY_IS_TRUE(s1 != s2);
}
Log::Comment(L"2.) Right Width changed.");
{
const til::size s1{ 5, 10 };
const til::size s2{ 6, 10 };
VERIFY_IS_TRUE(s1 != s2);
}
Log::Comment(L"3.) Left Height changed.");
{
const til::size s1{ 5, 9 };
const til::size s2{ 5, 10 };
VERIFY_IS_TRUE(s1 != s2);
}
Log::Comment(L"4.) Right Height changed.");
{
const til::size s1{ 5, 10 };
const til::size s2{ 5, 11 };
VERIFY_IS_TRUE(s1 != s2);
}
}
TEST_METHOD(Boolean)
{
const til::size empty;
VERIFY_IS_FALSE(!!empty);
const til::size yOnly{ 0, 10 };
VERIFY_IS_FALSE(!!yOnly);
const til::size xOnly{ 10, 0 };
VERIFY_IS_FALSE(!!xOnly);
const til::size both{ 10, 10 };
VERIFY_IS_TRUE(!!both);
const til::size yNegative{ 10, -10 };
VERIFY_IS_FALSE(!!yNegative);
const til::size xNegative{ -10, 10 };
VERIFY_IS_FALSE(!!xNegative);
const til::size bothNegative{ -10, -10 };
VERIFY_IS_FALSE(!!bothNegative);
}
TEST_METHOD(Addition)
{
Log::Comment(L"0.) Addition of two things that should be in bounds.");
{
const til::size sz{ 5, 10 };
const til::size sz2{ 23, 47 };
const til::size expected{ sz.width() + sz2.width(), sz.height() + sz2.height() };
VERIFY_ARE_EQUAL(expected, sz + sz2);
}
Log::Comment(L"1.) Addition results in value that is too large (width).");
{
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
const til::size sz{ bigSize, static_cast<ptrdiff_t>(0) };
const til::size sz2{ 1, 1 };
auto fn = [&]() {
sz + sz2;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
Log::Comment(L"2.) Addition results in value that is too large (height).");
{
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
const til::size sz{ static_cast<ptrdiff_t>(0), bigSize };
const til::size sz2{ 1, 1 };
auto fn = [&]() {
sz + sz2;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
}
TEST_METHOD(Subtraction)
{
Log::Comment(L"0.) Subtraction of two things that should be in bounds.");
{
const til::size sz{ 5, 10 };
const til::size sz2{ 23, 47 };
const til::size expected{ sz.width() - sz2.width(), sz.height() - sz2.height() };
VERIFY_ARE_EQUAL(expected, sz - sz2);
}
Log::Comment(L"1.) Subtraction results in value that is too small (width).");
{
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
const til::size sz{ bigSize, static_cast<ptrdiff_t>(0) };
const til::size sz2{ -2, -2 };
auto fn = [&]() {
sz2 - sz;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
Log::Comment(L"2.) Subtraction results in value that is too small (height).");
{
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
const til::size sz{ static_cast<ptrdiff_t>(0), bigSize };
const til::size sz2{ -2, -2 };
auto fn = [&]() {
sz2 - sz;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
}
TEST_METHOD(Multiplication)
{
Log::Comment(L"0.) Multiplication of two things that should be in bounds.");
{
const til::size sz{ 5, 10 };
const til::size sz2{ 23, 47 };
const til::size expected{ sz.width() * sz2.width(), sz.height() * sz2.height() };
VERIFY_ARE_EQUAL(expected, sz * sz2);
}
Log::Comment(L"1.) Multiplication results in value that is too large (width).");
{
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
const til::size sz{ bigSize, static_cast<ptrdiff_t>(0) };
const til::size sz2{ 10, 10 };
auto fn = [&]() {
sz* sz2;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
Log::Comment(L"2.) Multiplication results in value that is too large (height).");
{
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
const til::size sz{ static_cast<ptrdiff_t>(0), bigSize };
const til::size sz2{ 10, 10 };
auto fn = [&]() {
sz* sz2;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
}
TEST_METHOD(ScaleByFloat)
{
Log::Comment(L"0.) Scale that should be in bounds.");
{
const til::size sz{ 5, 10 };
const float scale = 1.783f;
const til::size expected{ static_cast<ptrdiff_t>(ceil(5 * scale)), static_cast<ptrdiff_t>(ceil(10 * scale)) };
const auto actual = sz.scale(til::math::ceiling, scale);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"1.) Scale results in value that is too large.");
{
const til::size sz{ 5, 10 };
constexpr float scale = std::numeric_limits<float>().max();
auto fn = [&]() {
sz.scale(til::math::ceiling, scale);
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
}
TEST_METHOD(Division)
{
Log::Comment(L"0.) Division of two things that should be in bounds.");
{
const til::size sz{ 555, 510 };
const til::size sz2{ 23, 47 };
const til::size expected{ sz.width() / sz2.width(), sz.height() / sz2.height() };
VERIFY_ARE_EQUAL(expected, sz / sz2);
}
Log::Comment(L"1.) Division by zero");
{
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
const til::size sz{ bigSize, static_cast<ptrdiff_t>(0) };
const til::size sz2{ 1, 1 };
auto fn = [&]() {
sz2 / sz;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
}
TEST_METHOD(DivisionRoundingUp)
{
Log::Comment(L"1.) Division rounding up with positive result.");
{
const til::size sz{ 10, 5 };
const til::size divisor{ 3, 2 };
// 10 / 3 is 3.333, rounded up is 4.
// 5 / 2 is 2.5, rounded up is 3.
const til::size expected{ 4, 3 };
VERIFY_ARE_EQUAL(expected, sz.divide_ceil(divisor));
}
Log::Comment(L"2.) Division rounding larger(up) with negative result.");
{
const til::size sz{ -10, -5 };
const til::size divisor{ 3, 2 };
// -10 / 3 is -3.333, rounded up is -4.
// -5 / 2 is -2.5, rounded up is -3.
const til::size expected{ -4, -3 };
VERIFY_ARE_EQUAL(expected, sz.divide_ceil(divisor));
}
}
TEST_METHOD(Width)
{
const til::size sz{ 5, 10 };
VERIFY_ARE_EQUAL(sz._width, sz.width());
}
TEST_METHOD(WidthCast)
{
const til::size sz{ 5, 10 };
VERIFY_ARE_EQUAL(static_cast<SHORT>(sz._width), sz.width<SHORT>());
}
TEST_METHOD(Height)
{
const til::size sz{ 5, 10 };
VERIFY_ARE_EQUAL(sz._height, sz.height());
}
TEST_METHOD(HeightCast)
{
const til::size sz{ 5, 10 };
VERIFY_ARE_EQUAL(static_cast<SHORT>(sz._height), sz.height<SHORT>());
}
TEST_METHOD(Area)
{
Log::Comment(L"0.) Area of two things that should be in bounds.");
{
const til::size sz{ 5, 10 };
VERIFY_ARE_EQUAL(sz._width * sz._height, sz.area());
}
Log::Comment(L"1.) Area is out of bounds on multiplication.");
{
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
const til::size sz{ bigSize, bigSize };
auto fn = [&]() {
sz.area();
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
}
TEST_METHOD(AreaCast)
{
Log::Comment(L"0.) Area of two things that should be in bounds.");
{
const til::size sz{ 5, 10 };
VERIFY_ARE_EQUAL(static_cast<SHORT>(sz.area()), sz.area<SHORT>());
}
Log::Comment(L"1.) Area is out of bounds on multiplication.");
{
constexpr ptrdiff_t bigSize = std::numeric_limits<SHORT>().max();
const til::size sz{ bigSize, bigSize };
auto fn = [&]() {
sz.area<SHORT>();
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
}
TEST_METHOD(CastToCoord)
{
Log::Comment(L"0.) Typical situation.");
{
const til::size sz{ 5, 10 };
COORD val = sz;
VERIFY_ARE_EQUAL(5, val.X);
VERIFY_ARE_EQUAL(10, val.Y);
}
Log::Comment(L"1.) Overflow on width.");
{
constexpr ptrdiff_t width = std::numeric_limits<ptrdiff_t>().max();
const ptrdiff_t height = 10;
const til::size sz{ width, height };
auto fn = [&]() {
COORD val = sz;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
Log::Comment(L"2.) Overflow on height.");
{
constexpr ptrdiff_t height = std::numeric_limits<ptrdiff_t>().max();
const ptrdiff_t width = 10;
const til::size sz{ width, height };
auto fn = [&]() {
COORD val = sz;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
}
TEST_METHOD(CastToSize)
{
Log::Comment(L"0.) Typical situation.");
{
const til::size sz{ 5, 10 };
SIZE val = sz;
VERIFY_ARE_EQUAL(5, val.cx);
VERIFY_ARE_EQUAL(10, val.cy);
}
Log::Comment(L"1.) Fit max width into SIZE (may overflow).");
{
constexpr ptrdiff_t width = std::numeric_limits<ptrdiff_t>().max();
const ptrdiff_t height = 10;
const til::size sz{ width, height };
// On some platforms, ptrdiff_t will fit inside cx/cy
const bool overflowExpected = width > std::numeric_limits<decltype(SIZE::cx)>().max();
if (overflowExpected)
{
auto fn = [&]() {
SIZE val = sz;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
else
{
SIZE val = sz;
VERIFY_ARE_EQUAL(width, val.cx);
}
}
Log::Comment(L"2.) Fit max height into SIZE (may overflow).");
{
constexpr ptrdiff_t height = std::numeric_limits<ptrdiff_t>().max();
const ptrdiff_t width = 10;
const til::size sz{ width, height };
// On some platforms, ptrdiff_t will fit inside cx/cy
const bool overflowExpected = height > std::numeric_limits<decltype(SIZE::cy)>().max();
if (overflowExpected)
{
auto fn = [&]() {
SIZE val = sz;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
else
{
SIZE val = sz;
VERIFY_ARE_EQUAL(height, val.cy);
}
}
}
TEST_METHOD(CastToD2D1SizeF)
{
Log::Comment(L"0.) Typical situation.");
{
const til::size sz{ 5, 10 };
D2D1_SIZE_F val = sz;
VERIFY_ARE_EQUAL(5, val.width);
VERIFY_ARE_EQUAL(10, val.height);
}
// All ptrdiff_ts fit into a float, so there's no exception tests.
}
template<typename T>
struct SizeTypeWith_XY
{
T X, Y;
};
template<typename T>
struct SizeTypeWith_cxcy
{
T cx, cy;
};
template<typename T>
struct SizeTypeWith_WidthHeight
{
T Width, Height;
};
TEST_METHOD(CastFromFloatWithMathTypes)
{
SizeTypeWith_XY<float> XYFloatIntegral{ 1.f, 2.f };
SizeTypeWith_XY<float> XYFloat{ 1.6f, 2.4f };
SizeTypeWith_cxcy<double> cxcyDoubleIntegral{ 3., 4. };
SizeTypeWith_cxcy<double> cxcyDouble{ 3.6, 4.4 };
SizeTypeWith_WidthHeight<double> WHDoubleIntegral{ 5., 6. };
SizeTypeWith_WidthHeight<double> WHDouble{ 5.6, 6.4 };
Log::Comment(L"0.) Ceiling");
{
{
til::size converted{ til::math::ceiling, XYFloatIntegral };
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
}
{
til::size converted{ til::math::ceiling, XYFloat };
VERIFY_ARE_EQUAL((til::size{ 2, 3 }), converted);
}
{
til::size converted{ til::math::ceiling, cxcyDoubleIntegral };
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
}
{
til::size converted{ til::math::ceiling, cxcyDouble };
VERIFY_ARE_EQUAL((til::size{ 4, 5 }), converted);
}
{
til::size converted{ til::math::ceiling, WHDoubleIntegral };
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
}
{
til::size converted{ til::math::ceiling, WHDouble };
VERIFY_ARE_EQUAL((til::size{ 6, 7 }), converted);
}
}
Log::Comment(L"1.) Flooring");
{
{
til::size converted{ til::math::flooring, XYFloatIntegral };
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
}
{
til::size converted{ til::math::flooring, XYFloat };
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
}
{
til::size converted{ til::math::flooring, cxcyDoubleIntegral };
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
}
{
til::size converted{ til::math::flooring, cxcyDouble };
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
}
{
til::size converted{ til::math::flooring, WHDoubleIntegral };
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
}
{
til::size converted{ til::math::flooring, WHDouble };
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
}
}
Log::Comment(L"2.) Rounding");
{
{
til::size converted{ til::math::rounding, XYFloatIntegral };
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
}
{
til::size converted{ til::math::rounding, XYFloat };
VERIFY_ARE_EQUAL((til::size{ 2, 2 }), converted);
}
{
til::size converted{ til::math::rounding, cxcyDoubleIntegral };
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
}
{
til::size converted{ til::math::rounding, cxcyDouble };
VERIFY_ARE_EQUAL((til::size{ 4, 4 }), converted);
}
{
til::size converted{ til::math::rounding, WHDoubleIntegral };
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
}
{
til::size converted{ til::math::rounding, WHDouble };
VERIFY_ARE_EQUAL((til::size{ 6, 6 }), converted);
}
}
Log::Comment(L"3.) Truncating");
{
{
til::size converted{ til::math::truncating, XYFloatIntegral };
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
}
{
til::size converted{ til::math::truncating, XYFloat };
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
}
{
til::size converted{ til::math::truncating, cxcyDoubleIntegral };
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
}
{
til::size converted{ til::math::truncating, cxcyDouble };
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
}
{
til::size converted{ til::math::truncating, WHDoubleIntegral };
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
}
{
til::size converted{ til::math::truncating, WHDouble };
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
}
}
}
};