forked from microsoft/terminal
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathScrollTest.cpp
246 lines (208 loc) · 10.9 KB
/
ScrollTest.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include <WexTestClass.h>
#include <DefaultSettings.h>
#include "../renderer/inc/DummyRenderer.hpp"
#include "../renderer/base/Renderer.hpp"
#include "../renderer/dx/DxRenderer.hpp"
#include "../cascadia/TerminalCore/Terminal.hpp"
#include "MockTermSettings.h"
#include "consoletaeftemplates.hpp"
#include "../../inc/TestUtils.h"
using namespace winrt::Microsoft::Terminal::Core;
using namespace Microsoft::Terminal::Core;
using namespace Microsoft::Console::Render;
using namespace ::Microsoft::Console::Types;
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
namespace
{
class MockScrollRenderEngine final : public RenderEngineBase
{
public:
std::optional<til::point> TriggerScrollDelta() const
{
return _triggerScrollDelta;
}
void Reset()
{
_triggerScrollDelta.reset();
}
HRESULT StartPaint() noexcept { return S_OK; }
HRESULT EndPaint() noexcept { return S_OK; }
HRESULT Present() noexcept { return S_OK; }
HRESULT PrepareForTeardown(_Out_ bool* /*pForcePaint*/) noexcept { return S_OK; }
HRESULT ScrollFrame() noexcept { return S_OK; }
HRESULT Invalidate(const til::rect* /*psrRegion*/) noexcept { return S_OK; }
HRESULT InvalidateCursor(const til::rect* /*psrRegion*/) noexcept { return S_OK; }
HRESULT InvalidateSystem(const til::rect* /*prcDirtyClient*/) noexcept { return S_OK; }
HRESULT InvalidateSelection(const std::vector<til::rect>& /*rectangles*/) noexcept { return S_OK; }
HRESULT InvalidateScroll(const til::point* pcoordDelta) noexcept
{
_triggerScrollDelta = *pcoordDelta;
return S_OK;
}
HRESULT InvalidateAll() noexcept { return S_OK; }
HRESULT InvalidateCircling(_Out_ bool* /*pForcePaint*/) noexcept { return S_OK; }
HRESULT PaintBackground() noexcept { return S_OK; }
HRESULT PaintBufferLine(std::span<const Cluster> /*clusters*/, til::point /*coord*/, bool /*fTrimLeft*/, bool /*lineWrapped*/) noexcept { return S_OK; }
HRESULT PaintBufferGridLines(GridLineSet /*lines*/, COLORREF /*gridlineColor*/, COLORREF /*underlineColor*/, size_t /*cchLine*/, til::point /*coordTarget*/) noexcept { return S_OK; }
HRESULT PaintSelection(const til::rect& /*rect*/) noexcept { return S_OK; }
HRESULT PaintCursor(const CursorOptions& /*options*/) noexcept { return S_OK; }
HRESULT UpdateDrawingBrushes(const TextAttribute& /*textAttributes*/, const RenderSettings& /*renderSettings*/, gsl::not_null<IRenderData*> /*pData*/, bool /*usingSoftFont*/, bool /*isSettingDefaultBrushes*/) noexcept { return S_OK; }
HRESULT UpdateFont(const FontInfoDesired& /*FontInfoDesired*/, _Out_ FontInfo& /*FontInfo*/) noexcept { return S_OK; }
HRESULT UpdateDpi(int /*iDpi*/) noexcept { return S_OK; }
HRESULT UpdateViewport(const til::inclusive_rect& /*srNewViewport*/) noexcept { return S_OK; }
HRESULT GetProposedFont(const FontInfoDesired& /*FontInfoDesired*/, _Out_ FontInfo& /*FontInfo*/, int /*iDpi*/) noexcept { return S_OK; }
HRESULT GetDirtyArea(std::span<const til::rect>& /*area*/) noexcept { return S_OK; }
HRESULT GetFontSize(_Out_ til::size* /*pFontSize*/) noexcept { return S_OK; }
HRESULT IsGlyphWideByFont(std::wstring_view /*glyph*/, _Out_ bool* /*pResult*/) noexcept { return S_OK; }
protected:
HRESULT _DoUpdateTitle(const std::wstring_view /*newTitle*/) noexcept { return S_OK; }
private:
std::optional<til::point> _triggerScrollDelta;
};
struct ScrollBarNotification
{
int ViewportTop;
int ViewportHeight;
int BufferHeight;
};
}
namespace TerminalCoreUnitTests
{
class ScrollTest;
};
using namespace TerminalCoreUnitTests;
class TerminalCoreUnitTests::ScrollTest final
{
// !!! DANGER: Many tests in this class expect the Terminal buffer
// to be 80x32. If you change these, you'll probably inadvertently break a
// bunch of tests !!!
static const til::CoordType TerminalViewWidth = 80;
static const til::CoordType TerminalViewHeight = 32;
// For TestNotifyScrolling, it's important that this value is ~=9000.
// Something smaller like 100 won't cause the test to fail.
static const til::CoordType TerminalHistoryLength = 9001;
TEST_CLASS(ScrollTest);
TEST_METHOD(TestNotifyScrolling);
TEST_METHOD_SETUP(MethodSetup)
{
_term = std::make_unique<::Microsoft::Terminal::Core::Terminal>();
_scrollBarNotification = std::make_shared<std::optional<ScrollBarNotification>>();
_term->SetScrollPositionChangedCallback([scrollBarNotification = _scrollBarNotification](const int top, const int height, const int bottom) {
ScrollBarNotification tmp;
tmp.ViewportTop = top;
tmp.ViewportHeight = height;
tmp.BufferHeight = bottom;
*scrollBarNotification = { tmp };
});
_renderEngine = std::make_unique<MockScrollRenderEngine>();
_renderer = std::make_unique<DummyRenderer>(_term.get());
_renderer->AddRenderEngine(_renderEngine.get());
_term->Create({ TerminalViewWidth, TerminalViewHeight }, TerminalHistoryLength, *_renderer);
return true;
}
TEST_METHOD_CLEANUP(MethodCleanup)
{
_term = nullptr;
return true;
}
private:
std::unique_ptr<Terminal> _term;
std::unique_ptr<MockScrollRenderEngine> _renderEngine;
std::unique_ptr<DummyRenderer> _renderer;
std::shared_ptr<std::optional<ScrollBarNotification>> _scrollBarNotification;
};
void ScrollTest::TestNotifyScrolling()
{
// See https://github.com/microsoft/terminal/pull/5630
//
// This is a test for GH#5540, in the most bizarre way. The origin of that
// bug was that as newlines were emitted, we'd accumulate an enormous scroll
// delta into a selection region, to the point of overflowing a SHORT. When
// the overflow occurred, the Terminal would fail to send a NotifyScroll() to
// the TermControl hosting it.
//
// For this bug to repro, we need to:
// - Have a sufficiently large buffer, because each newline we'll accumulate
// a delta of (0, ~bufferHeight), so (bufferHeight^2 + bufferHeight) >
// SHRT_MAX
// - Have a selection
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:notifyOnCircling", L"{false, true}")
END_TEST_METHOD_PROPERTIES();
INIT_TEST_PROPERTY(bool, notifyOnCircling, L"Controls whether we should always request scroll notifications");
_term->AlwaysNotifyOnBufferRotation(notifyOnCircling);
Log::Comment(L"Watch out - this test takes a while to run, and won't "
L"output anything unless in encounters an error. This is expected.");
auto& termTb = *_term->_mainBuffer;
auto& termSm = *_term->_stateMachine;
const auto totalBufferSize = termTb.GetSize().Height();
auto currentRow = 0;
// We're outputting like 18000 lines of text here, so emitting 18000*4 lines
// of output to the console is actually quite unnecessary
WEX::TestExecution::SetVerifyOutput settings(WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures);
// Emit a bunch of newlines to test scrolling.
for (; currentRow < totalBufferSize * 2; currentRow++)
{
*_scrollBarNotification = std::nullopt;
_renderEngine->Reset();
termSm.ProcessString(L"X\r\n");
// When we're on TerminalViewHeight-1, we'll emit the newline that
// causes the first scroll event
auto scrolled = currentRow >= TerminalViewHeight - 1;
// When we circle the buffer, the scroll bar's position does not change.
// However, as of GH#14045, we will send a notification IF the control
// requested on (by setting AlwaysNotifyOnBufferRotation)
auto circledBuffer = currentRow >= totalBufferSize - 1;
auto expectScrollBarNotification = (scrolled && !circledBuffer) || // If we scrolled, but didn't circle the buffer OR
(circledBuffer && notifyOnCircling); // we circled AND we asked for notifications.
if (expectScrollBarNotification)
{
VERIFY_IS_TRUE(_scrollBarNotification->has_value(),
fmt::format(L"Expected a 'scroll bar position changed' notification for row {}", currentRow).c_str());
}
else
{
VERIFY_IS_FALSE(_scrollBarNotification->has_value(),
fmt::format(L"Expected to not see a 'scroll bar position changed' notification for row {}", currentRow).c_str());
}
// If we scrolled but it circled the buffer, then the terminal will
// call `TriggerScroll` with a delta to tell the renderer about it.
if (scrolled && circledBuffer)
{
VERIFY_IS_TRUE(_renderEngine->TriggerScrollDelta().has_value(),
fmt::format(L"Expected a 'trigger scroll' notification in Render Engine for row {}", currentRow).c_str());
til::point expectedDelta;
expectedDelta.x = 0;
expectedDelta.y = -1;
VERIFY_ARE_EQUAL(expectedDelta, _renderEngine->TriggerScrollDelta().value(), fmt::format(L"Wrong value in 'trigger scroll' notification in Render Engine for row {}", currentRow).c_str());
}
else
{
VERIFY_IS_FALSE(_renderEngine->TriggerScrollDelta().has_value(),
fmt::format(L"Expected to not see a 'trigger scroll' notification in Render Engine for row {}", currentRow).c_str());
}
if (_scrollBarNotification->has_value())
{
const auto tmp = _scrollBarNotification->value();
const auto expectedTop = std::clamp<int>(currentRow - TerminalViewHeight + 2,
0,
TerminalHistoryLength);
const int expectedHeight = TerminalViewHeight;
const auto expectedBottom = expectedTop + TerminalViewHeight;
if ((tmp.ViewportTop != expectedTop) ||
(tmp.ViewportHeight != expectedHeight) ||
(tmp.BufferHeight != expectedBottom))
{
Log::Comment(NoThrowString().Format(L"Expected viewport values did not match on line %d", currentRow));
}
VERIFY_ARE_EQUAL(tmp.ViewportTop, expectedTop);
VERIFY_ARE_EQUAL(tmp.ViewportHeight, expectedHeight);
VERIFY_ARE_EQUAL(tmp.BufferHeight, expectedBottom);
}
}
}