blob: 4d49106d98600c123cf11e290f205b27684416a7 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "textcon.h"
#include <sys/param.h>
#include <memory>
#include <gfx/gfx.h>
#include <zxtest/zxtest.h>
#include "vc.h"
// The graphics used for testing.
vc_gfx_t main_test_graphics = {};
void vc_attach_to_main_display(vc_t* vc) {
vc->graphics = &main_test_graphics;
vc_attach_gfx(vc);
}
// This is needed to satisfy a reference from vc_handle_device_control_keys
// in vc-input.cpp but the code path to that reference is dead in this test.
void vc_toggle_framebuffer() { __builtin_trap(); }
bool g_vc_owns_display = true;
namespace {
void invalidate_callback(void* cookie, int x, int y, int w, int h) {}
void movecursor_callback(void* cookie, int x, int y) {}
void push_scrollback_line_callback(void* cookie, int y) {}
void copy_lines_callback(void* cookie, int y_dest, int y_src, int line_count) {
auto* tc = reinterpret_cast<textcon_t*>(cookie);
tc_copy_lines(tc, y_dest, y_src, line_count);
}
void setparam_callback(void* cookie, int param, uint8_t* arg, size_t arglen) {}
// Helper for initializing and testing console instances. This actually
// creates two console instances:
//
// * A textcon_t (non-graphical), for testing character-level output.
// * A vc_t (graphical), for testing incremental updates to the
// gfx_surface.
//
// In principle, we could test the character-level output via the textcon_t
// that the vc_t creates internally. However, using our own
// separate textcon_t instance helps check that textcon_t can be used on
// its own, outside of vc_t.
class TextconHelper {
public:
TextconHelper(uint32_t size_x, uint32_t size_y,
const color_scheme_t* color_scheme = &color_schemes[kDefaultColorScheme])
: size_x(size_x), size_y(size_y) {
// Create a textcon_t.
textbuf = new vc_char_t[size_x * size_y];
textcon.cookie = &textcon;
textcon.invalidate = invalidate_callback;
textcon.movecursor = movecursor_callback;
textcon.push_scrollback_line = push_scrollback_line_callback;
textcon.copy_lines = copy_lines_callback;
textcon.setparam = setparam_callback;
tc_init(&textcon, size_x, size_y, textbuf, color_scheme->front, color_scheme->back, 0, 0);
// Initialize buffer contents, since this is currently done
// outside of textcon.cpp in vc-device.cpp.
for (size_t i = 0; i < size_x * size_y; ++i)
textbuf[i] = ' ';
// Create a vc_t with the same size in characters.
const gfx_font* font = vc_get_font();
int pixels_x = font->width * size_x;
int pixels_y = font->height * (size_y + 1); // Add 1 for status line.
// Add margins that aren't large enough to fit a whole column or
// row at the right and bottom. This tests incremental update of
// anything that might be displayed in the margins.
pixels_x += font->width - 1;
pixels_y += font->height - 1;
vc_surface = gfx_create_surface(nullptr, pixels_x, pixels_y, /* stride= */ pixels_x,
ZX_PIXEL_FORMAT_RGB_565, 0);
EXPECT_TRUE(vc_surface);
// This takes ownership of vc_surface.
EXPECT_OK(vc_init_gfx(&main_test_graphics, vc_surface));
EXPECT_OK(vc_alloc(&vc_dev, color_scheme));
EXPECT_EQ(vc_dev->columns, size_x);
EXPECT_EQ(vc_rows(vc_dev), static_cast<int>(size_y));
// Mark the console as active so that display updates get
// propagated to vc_surface.
vc_dev->active = true;
// Propagate the initial display contents to vc_surface.
vc_full_repaint(vc_dev);
vc_gfx_invalidate_all(&main_test_graphics, vc_dev);
}
~TextconHelper() {
delete[] textbuf;
vc_free(vc_dev);
vc_free_gfx(&main_test_graphics);
}
// Takes a snapshot of the vc_t's display.
class DisplaySnapshot {
public:
DisplaySnapshot(TextconHelper* helper)
: helper_(helper), snapshot_(new uint8_t[helper->vc_surface->len]) {
memcpy(snapshot_.get(), helper->vc_surface->ptr, helper->vc_surface->len);
}
// Returns whether the vc_t's display changed since the
// snapshot was taken.
bool ChangedSinceSnapshot() {
return memcmp(snapshot_.get(), helper_->vc_surface->ptr, helper_->vc_surface->len) != 0;
}
std::unique_ptr<char[]> ComparisonString() {
vc_t* vc_dev = helper_->vc_dev;
gfx_surface* vc_surface = helper_->vc_surface;
// Add 1 to these sizes to account for the margins.
uint32_t cmp_size_x = vc_dev->columns + 1;
uint32_t cmp_size_y = vc_dev->rows + 1;
uint32_t size_in_chars = cmp_size_x * cmp_size_y;
std::unique_ptr<bool[]> diffs(new bool[size_in_chars]);
for (uint32_t i = 0; i < size_in_chars; ++i)
diffs[i] = false;
for (uint32_t i = 0; i < vc_surface->len; ++i) {
if (static_cast<uint8_t*>(vc_surface->ptr)[i] != snapshot_[i]) {
uint32_t pixel_index = i / vc_surface->pixelsize;
uint32_t x_pixels = pixel_index % vc_surface->stride;
uint32_t y_pixels = pixel_index / vc_surface->stride;
uint32_t x_chars = x_pixels / vc_dev->charw;
uint32_t y_chars = y_pixels / vc_dev->charh;
EXPECT_LT(x_chars, cmp_size_x);
EXPECT_LT(y_chars, cmp_size_y);
diffs[x_chars + y_chars * cmp_size_x] = true;
}
}
// Build a string showing the differences. If we had
// std::string or equivalent, we'd use that here.
size_t string_size = (cmp_size_x + 3) * cmp_size_y + 1;
std::unique_ptr<char[]> string(new char[string_size]);
char* ptr = string.get();
for (uint32_t y = 0; y < cmp_size_y; ++y) {
*ptr++ = '|';
for (uint32_t x = 0; x < cmp_size_x; ++x) {
bool diff = diffs[x + y * cmp_size_x];
*ptr++ = diff ? 'D' : '-';
}
*ptr++ = '|';
*ptr++ = '\n';
}
*ptr++ = 0;
EXPECT_EQ(ptr, string.get() + string_size);
return string;
}
// Prints a representation of which characters in the vc_t's
// display changed since the snapshot was taken.
void PrintComparison() { printf("%s", ComparisonString().get()); }
private:
TextconHelper* helper_;
std::unique_ptr<uint8_t[]> snapshot_;
};
void InvalidateAllGraphics() {
vc_full_repaint(vc_dev);
vc_gfx_invalidate_all(&main_test_graphics, vc_dev);
}
void PutString(const char* str) {
for (const char* ptr = str; *ptr; ++ptr)
textcon.putc(&textcon, *ptr);
vc_write(vc_dev, str, strlen(str), 0);
// Test that the incremental update of the display was correct. We
// do that by refreshing the entire display, and checking that
// there was no change.
DisplaySnapshot copy(this);
InvalidateAllGraphics();
if (copy.ChangedSinceSnapshot()) {
copy.PrintComparison();
EXPECT_TRUE(false, "Display contents changed");
}
}
void AssertTextbufLineContains(vc_char_t* buf, int line_num, const char* str) {
size_t len = strlen(str);
EXPECT_LE(len, size_x);
for (size_t i = 0; i < len; ++i)
EXPECT_EQ(str[i], vc_char_get_char(buf[size_x * line_num + i]));
// The rest of the line should contain spaces.
for (size_t i = len; i < size_x; ++i)
EXPECT_EQ(' ', vc_char_get_char(buf[size_x * line_num + i]));
}
void AssertLineContains(int line_num, const char* str) {
AssertTextbufLineContains(textbuf, line_num, str);
AssertTextbufLineContains(vc_dev->text_buf, line_num, str);
}
uint32_t size_x;
uint32_t size_y;
vc_char_t* textbuf;
textcon_t textcon = {};
gfx_surface* vc_surface;
vc_t* vc_dev;
};
TEST(GfxConsoleTextbufTests, Simple) {
TextconHelper tc(10, 5);
tc.PutString("Hello");
tc.AssertLineContains(0, "Hello");
tc.AssertLineContains(1, "");
}
// This tests the DisplaySnapshot test helper above. If we write directly
// to vc_dev's text buffer without invalidating the display, the test
// machinery should detect which characters in the display were not updated
// properly.
TEST(GfxConsoleTextbufTests, DisplayUpdateComparison) {
TextconHelper tc(10, 3);
// Write some characters directly into the text buffer.
auto SetChar = [&](int x, int y, char ch) {
tc.vc_dev->text_buf[x + y * tc.size_x] = vc_char_make(ch, tc.textcon.fg, tc.textcon.bg);
};
SetChar(2, 1, 'x');
SetChar(3, 1, 'y');
SetChar(6, 1, 'z');
// Check that these characters in the display are detected as not
// properly updated.
TextconHelper::DisplaySnapshot snapshot(&tc);
tc.InvalidateAllGraphics();
EXPECT_TRUE(snapshot.ChangedSinceSnapshot());
const char* expected =
"|-----------|\n" // Console status line
"|-----------|\n" // Cursor at left was painted during tc init
"|--DD--D----|\n" // Chars set by SetChar() above
"|-----------|\n"
"|-----------|\n"; // Bottom margin
EXPECT_EQ(strcmp(snapshot.ComparisonString().get(), expected), 0);
}
// This tests updating the display with all of the different color schemes. This
// catches that the tc and the vcs set their color schemes correctly. If
// something goes wrong, either all of the chars will appear to be changed or
// none will be changed.
TEST(GfxConsoleTextbufTests, DisplayColorSchemes) {
int colors[] = {kDarkColorScheme, kLightColorScheme, kSpecialColorScheme};
for (size_t c = 0; c < countof(colors); c++) {
TextconHelper tc(10, 3, &color_schemes[colors[c]]);
// Write some characters directly into the text buffer.
auto SetChar = [&](int x, int y, char ch) {
tc.vc_dev->text_buf[x + y * tc.size_x] = vc_char_make(ch, tc.textcon.fg, tc.textcon.bg);
};
SetChar(2, 1, 'x');
SetChar(3, 1, 'y');
SetChar(6, 1, 'z');
// Check that these characters in the display are detected as not
// properly updated.
TextconHelper::DisplaySnapshot snapshot(&tc);
tc.InvalidateAllGraphics();
EXPECT_TRUE(snapshot.ChangedSinceSnapshot());
const char* expected =
"|-----------|\n" // Console status line
"|-----------|\n" // Cursor at left was painted during tc init
"|--DD--D----|\n" // Chars set by SetChar() above
"|-----------|\n"
"|-----------|\n"; // Bottom margin
EXPECT_EQ(strcmp(snapshot.ComparisonString().get(), expected), 0);
}
}
TEST(GfxConsoleTextbufTests, Wrapping) {
TextconHelper tc(10, 5);
tc.PutString("Hello world! More text here.");
tc.AssertLineContains(0, "Hello worl");
tc.AssertLineContains(1, "d! More te");
tc.AssertLineContains(2, "xt here.");
}
TEST(GfxConsoleTextbufTests, Tabs) {
TextconHelper tc(80, 40);
tc.PutString("\tA\n");
tc.PutString(" \tB\n");
tc.PutString(" \tC\n"); // 7 spaces
tc.PutString(" \tD\n"); // 8 spaces
tc.AssertLineContains(0, " A");
tc.AssertLineContains(1, " B");
tc.AssertLineContains(2, " C");
tc.AssertLineContains(3, " D");
}
TEST(GfxConsoleTextbufTests, BackspaceMovesCursor) {
TextconHelper tc(10, 5);
tc.PutString("ABCDEF\b\b\b\bxy");
// Backspace only moves the cursor and does not erase, so "EF" is left
// in place.
tc.AssertLineContains(0, "ABxyEF");
}
TEST(GfxConsoleTextbufTests, BackspaceAtStartOfLine) {
TextconHelper tc(10, 5);
tc.PutString("Foo\n\bBar");
// When the cursor is at the start of a line, backspace has no effect.
tc.AssertLineContains(0, "Foo");
tc.AssertLineContains(1, "Bar");
}
TEST(GfxConsoleTextbufTests, DeleteChars) {
TextconHelper tc(20, 10);
tc.PutString("123456");
// Move the cursor to be over the "3".
tc.PutString("\b\b\b\b");
// Delete 2 characters to the right of the cursor, using the "DCH"
// escape sequence. Any characters beyond that, on the right, are
// moved to the left by 2 characters. This escape sequence is a
// reduced test from a fuzzer-discovered example (see ZX-2936). This
// used to trigger an assertion failure.
tc.PutString("\x1b[2P");
tc.AssertLineContains(0, "1256");
}
TEST(GfxConsoleTextbufTests, DeleteCharsOverflow) {
TextconHelper tc(6, 10);
// Fill the width of the console, leaving the cursor on the next line.
tc.PutString("123456");
// Move the cursor to be over the "3", to position (y+1,x+1) = (1,3).
tc.PutString("\x1b[1;3H");
// Request deleting 5 characters to the right of the cursor, using
// the "DCH" escape sequence. This tests an overflow case,
// because there are only 4 characters to the right of the cursor.
tc.PutString("\x1b[5P");
tc.AssertLineContains(0, "12");
}
TEST(GfxConsoleTextbufTests, ScrollUp) {
TextconHelper tc(10, 4);
tc.PutString("AAA\nBBB\nCCC\nDDD\n");
tc.AssertLineContains(0, "BBB");
tc.AssertLineContains(1, "CCC");
tc.AssertLineContains(2, "DDD");
tc.AssertLineContains(3, "");
EXPECT_EQ(vc_get_scrollback_lines(tc.vc_dev), 1);
}
// Same as scroll_up(), but using ESC E (NEL) instead of "\n".
TEST(GfxConsoleTextbufTests, ScrollUpNel) {
TextconHelper tc(10, 4);
tc.PutString(
"AAA"
"\x1b"
"E"
"BBB"
"\x1b"
"E"
"CCC"
"\x1b"
"E"
"DDD"
"\x1b"
"E");
tc.AssertLineContains(0, "BBB");
tc.AssertLineContains(1, "CCC");
tc.AssertLineContains(2, "DDD");
tc.AssertLineContains(3, "");
EXPECT_EQ(vc_get_scrollback_lines(tc.vc_dev), 1);
}
TEST(GfxConsoleTextbufTests, InsertLines) {
TextconHelper tc(10, 5);
tc.PutString("AAA\nBBB\nCCC\nDDD\nEEE");
tc.PutString("\x1b[2A"); // Move the cursor up 2 lines
tc.PutString("\x1b[2L"); // Insert 2 lines
tc.PutString("Z"); // Output char to show where the cursor ends up
tc.AssertLineContains(0, "AAA");
tc.AssertLineContains(1, "BBB");
tc.AssertLineContains(2, " Z");
tc.AssertLineContains(3, "");
tc.AssertLineContains(4, "CCC");
EXPECT_EQ(vc_get_scrollback_lines(tc.vc_dev), 0);
}
TEST(GfxConsoleTextbufTests, DeleteLines) {
TextconHelper tc(10, 5);
tc.PutString("AAA\nBBB\nCCC\nDDD\nEEE");
tc.PutString("\x1b[2A"); // Move the cursor up 2 lines
tc.PutString("\x1b[2M"); // Delete 2 lines
tc.PutString("Z"); // Output char to show where the cursor ends up
tc.AssertLineContains(0, "AAA");
tc.AssertLineContains(1, "BBB");
tc.AssertLineContains(2, "EEEZ");
tc.AssertLineContains(3, "");
tc.AssertLineContains(4, "");
// TODO(mseaborn): We probably don't want to be adding the deleted
// lines to the scrollback in this case, because they are not from the
// top of the console.
EXPECT_EQ(vc_get_scrollback_lines(tc.vc_dev), 2);
}
// Test for a bug where this would cause an out-of-bounds array access.
TEST(GfxConsoleTextbufTests, InsertLinesMany) {
TextconHelper tc(10, 5);
tc.PutString("AAA\nBBB");
tc.PutString("\x1b[999L"); // Insert 999 lines
tc.PutString("Z"); // Output char to show where the cursor ends up
tc.AssertLineContains(0, "AAA");
tc.AssertLineContains(1, " Z");
}
// Test for a bug where this would cause an out-of-bounds array access.
TEST(GfxConsoleTextbufTests, DeleteLinesMany) {
TextconHelper tc(10, 5);
tc.PutString("AAA\nBBB");
tc.PutString("\x1b[999M"); // Delete 999 lines
tc.PutString("Z"); // Output char to show where the cursor ends up
tc.AssertLineContains(0, "AAA");
tc.AssertLineContains(1, " Z");
}
// Check that passing a huge parameter via "insert lines" completes in a
// reasonable amount of time. (We don't check the time here but we assume
// that someone will notice if this takes a long time.)
TEST(GfxConsoleTextbufTests, InsertLinesHuge) {
TextconHelper tc(10, 5);
tc.PutString("AAA\nBBB");
tc.PutString("\x1b[2000000000L"); // Insert lines
tc.PutString("Z"); // Output char to show where the cursor ends up
tc.AssertLineContains(0, "AAA");
tc.AssertLineContains(1, " Z");
}
// Check that passing a huge parameter via "delete lines" completes in a
// reasonable amount of time. (We don't check the time here but we assume
// that someone will notice if this takes a long time.)
TEST(GfxConsoleTextbufTests, DeleteLinesHuge) {
TextconHelper tc(10, 5);
tc.PutString("AAA\nBBB");
tc.PutString("\x1b[200000000M"); // Delete lines
tc.PutString("Z"); // Output char to show where the cursor ends up
tc.AssertLineContains(0, "AAA");
tc.AssertLineContains(1, " Z");
}
TEST(GfxConsoleTextbufTests, MoveCursorUpAndScroll) {
TextconHelper tc(10, 4);
tc.PutString("AAA\nBBB\nCCC\nDDD");
tc.PutString(
"\x1bM"
"1"); // Move cursor up; print char
tc.PutString(
"\x1bM"
"2"); // Move cursor up; print char
tc.PutString(
"\x1bM"
"3"); // Move cursor up; print char
tc.PutString(
"\x1bM"
"4"); // Move cursor up; print char
tc.AssertLineContains(0, " 4");
tc.AssertLineContains(1, "AAA 3");
tc.AssertLineContains(2, "BBB 2");
tc.AssertLineContains(3, "CCC1");
}
TEST(GfxConsoleTextbufTests, MoveCursorDownAndScroll) {
TextconHelper tc(10, 4);
tc.PutString(
"1"
"\x1b"
"D"); // Print char; move cursor down
tc.PutString(
"2"
"\x1b"
"D"); // Print char; move cursor down
tc.PutString(
"3"
"\x1b"
"D"); // Print char; move cursor down
tc.PutString(
"4"
"\x1b"
"D"); // Print char; move cursor down
tc.PutString("5");
tc.AssertLineContains(0, " 2");
tc.AssertLineContains(1, " 3");
tc.AssertLineContains(2, " 4");
tc.AssertLineContains(3, " 5");
}
TEST(GfxConsoleTextbufTests, CursorHideAndShow) {
TextconHelper tc(10, 4);
ASSERT_FALSE(tc.vc_dev->hide_cursor);
tc.PutString("\x1b[?25l"); // Hide cursor
ASSERT_TRUE(tc.vc_dev->hide_cursor);
tc.PutString("\x1b[?25h"); // Show cursor
ASSERT_FALSE(tc.vc_dev->hide_cursor);
}
// This tests for a bug: If the cursor was positioned over a character when
// we scroll up, that character would get erased.
TEST(GfxConsoleTextbufTests, CursorScrollBug) {
TextconHelper tc(10, 3);
// Move the cursor to the bottom line.
tc.PutString("\n\n\n");
// Scroll down when the cursor is over "C".
tc.PutString("ABCDE\b\b\b\n");
}
// Test for a bug where scrolling the console viewport by a large delta
// (e.g. going from the top to the bottom) can crash due to out-of-bounds
// memory accesses.
TEST(GfxConsoleTextbufTests, ScrollViewportByLargeDelta) {
TextconHelper tc(2, 2);
tc.PutString("\n");
for (int lines = 1; lines < 100; ++lines) {
tc.PutString("\n");
// Scroll up, to show older lines.
vc_scroll_viewport_top(tc.vc_dev);
EXPECT_EQ(tc.vc_dev->viewport_y, -lines);
// Scroll down, to show newer lines.
vc_scroll_viewport_bottom(tc.vc_dev);
EXPECT_EQ(tc.vc_dev->viewport_y, 0);
}
}
// When the console is displaying only the main console region (and no
// scrollback), the console should keep displaying that as new lines are
// outputted.
TEST(GfxConsoleTextbufTests, ViewportScrollingFollowsBottom) {
TextconHelper tc(1, 1);
for (unsigned i = 0; i < tc.vc_dev->scrollback_rows_max * 2; ++i) {
EXPECT_EQ(tc.vc_dev->viewport_y, 0);
tc.PutString("\n");
}
}
// When the console is displaying some of the scrollback buffer, then as
// new lines are outputted, the console should scroll the viewpoint to keep
// displaying the same point, unless we're at the top of the scrollback
// buffer.
TEST(GfxConsoleTextbufTests, ViewportScrollingFollowsScrollback) {
TextconHelper tc(1, 1);
// Add 3 lines to the scrollback buffer.
tc.PutString("\n\n\n");
vc_scroll_viewport(tc.vc_dev, -2);
EXPECT_EQ(tc.vc_dev->viewport_y, -2);
int limit = tc.vc_dev->scrollback_rows_max;
for (int line = 3; line < limit * 2; ++line) {
// Output different strings on each line in order to test that the
// display is updated consistently when the console starts dropping
// lines from the scrollback region.
char str[3] = {static_cast<char>('0' + (line % 10)), '\n', '\0'};
tc.PutString(str);
EXPECT_EQ(tc.vc_dev->viewport_y, -MIN(line, limit));
}
}
TEST(GfxConsoleTextbufTests, OutputWhenViewportScrolled) {
TextconHelper tc(10, 3);
// Line 1 will move into the scrollback region.
tc.PutString("1\n 2\n 3\n 4");
EXPECT_EQ(tc.vc_dev->viewport_y, 0);
vc_scroll_viewport_top(tc.vc_dev);
EXPECT_EQ(tc.vc_dev->viewport_y, -1);
// Check redrawing consistency.
tc.PutString("");
// Test that output updates the display correctly when the viewport is
// scrolled. Using two separate PutString() calls here was necessary
// for reproducing an incremental update bug.
tc.PutString("\x1b[1;1f"); // Move to top left
tc.PutString("Epilobium");
tc.AssertLineContains(0, "Epilobium");
tc.AssertLineContains(1, " 3");
tc.AssertLineContains(2, " 4");
// Test that erasing also updates the display correctly. This
// changes the console contents without moving the cursor.
tc.PutString("\b\b\b\b"); // Move cursor left 3 chars
tc.PutString("\x1b[1K"); // Erase to beginning of line
tc.AssertLineContains(0, " ium");
tc.AssertLineContains(1, " 3");
tc.AssertLineContains(2, " 4");
}
TEST(GfxConsoleTextbufTests, ScrollingWhenViewportScrolled) {
TextconHelper tc(10, 3);
// Line 1 will move into the scrollback region.
tc.PutString("1\n 2\n 3\n 4");
EXPECT_EQ(tc.vc_dev->viewport_y, 0);
vc_scroll_viewport_top(tc.vc_dev);
EXPECT_EQ(tc.vc_dev->viewport_y, -1);
// Check redrawing consistency.
tc.PutString("");
// Test that the display is updated correctly when we scroll.
tc.PutString("\n5");
tc.AssertLineContains(0, " 3");
tc.AssertLineContains(1, " 4");
tc.AssertLineContains(2, "5");
}
// Test that vc_get_scrollback_lines() gives the correct results.
TEST(GfxConsoleTextbufTests, ScrollbackLinesCount) {
TextconHelper tc(10, 3);
tc.PutString("\n\n");
// Reduce the scrollback limit to make the test faster.
const int kLimit = 20;
EXPECT_LE(kLimit, tc.vc_dev->scrollback_rows_max);
tc.vc_dev->scrollback_rows_max = kLimit;
for (int lines = 1; lines < kLimit * 4; ++lines) {
tc.PutString("\n");
EXPECT_EQ(MIN(lines, kLimit), vc_get_scrollback_lines(tc.vc_dev));
}
}
// Test that the scrollback lines have the correct contents.
TEST(GfxConsoleTextbufTests, ScrollbackLinesContents) {
// Use a 1-row-high console, which simplifies this test.
TextconHelper tc(3, 1);
// Reduce the scrollback limit to make the test faster.
const int kLimit = 20;
EXPECT_LE(kLimit, tc.vc_dev->scrollback_rows_max);
tc.vc_dev->scrollback_rows_max = kLimit;
vc_char_t test_val = 0;
for (int lines = 1; lines <= kLimit; ++lines) {
tc.vc_dev->text_buf[0] = test_val++;
tc.PutString("\n");
EXPECT_EQ(lines, vc_get_scrollback_lines(tc.vc_dev));
for (int i = 0; i < lines; ++i)
EXPECT_EQ(i, vc_get_scrollback_line_ptr(tc.vc_dev, i)[0]);
}
for (int lines = 0; lines < kLimit * 3; ++lines) {
tc.vc_dev->text_buf[0] = test_val++;
tc.PutString("\n");
EXPECT_EQ(kLimit, vc_get_scrollback_lines(tc.vc_dev));
for (int i = 0; i < kLimit; ++i) {
EXPECT_EQ(test_val + i - kLimit, vc_get_scrollback_line_ptr(tc.vc_dev, i)[0]);
}
}
}
} // namespace