1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <gfx/gfx.h>
6#include <fbl/unique_ptr.h>
7#include <sys/param.h>
8#include <unittest/unittest.h>
9
10#include "textcon.h"
11#include "vc.h"
12
13// This is needed to satisfy a reference from vc_handle_device_control_keys
14// in vc-input.cpp but the code path to that reference is dead in this test.
15void vc_toggle_framebuffer() {
16    __builtin_trap();
17}
18
19bool g_vc_owns_display = true;
20
21namespace {
22
23void invalidate_callback(void* cookie, int x, int y, int w, int h) {
24}
25
26void movecursor_callback(void* cookie, int x, int y) {
27}
28
29void push_scrollback_line_callback(void* cookie, int y) {
30}
31
32void copy_lines_callback(void* cookie, int y_dest, int y_src, int line_count) {
33    auto* tc = reinterpret_cast<textcon_t*>(cookie);
34    tc_copy_lines(tc, y_dest, y_src, line_count);
35}
36
37void setparam_callback(void* cookie, int param, uint8_t* arg, size_t arglen) {
38}
39
40// Helper for initializing and testing console instances.  This actually
41// creates two console instances:
42//
43//  * A textcon_t (non-graphical), for testing character-level output.
44//  * A vc_t (graphical), for testing incremental updates to the
45//    gfx_surface.
46//
47// In principle, we could test the character-level output via the textcon_t
48// that the vc_t creates internally.  However, using our own
49// separate textcon_t instance helps check that textcon_t can be used on
50// its own, outside of vc_t.
51class TextconHelper {
52public:
53    TextconHelper(uint32_t size_x, uint32_t size_y) : size_x(size_x),
54                                                      size_y(size_y) {
55        // Create a textcon_t.
56        textbuf = new vc_char_t[size_x * size_y];
57        textcon.cookie = &textcon;
58        textcon.invalidate = invalidate_callback;
59        textcon.movecursor = movecursor_callback;
60        textcon.push_scrollback_line = push_scrollback_line_callback;
61        textcon.copy_lines = copy_lines_callback;
62        textcon.setparam = setparam_callback;
63        tc_init(&textcon, size_x, size_y, textbuf, 0, 0, 0, 0);
64        // Initialize buffer contents, since this is currently done
65        // outside of textcon.cpp in vc-device.cpp.
66        for (size_t i = 0; i < size_x * size_y; ++i)
67            textbuf[i] = ' ';
68
69        // Create a vc_t with the same size in characters.
70        const gfx_font* font = vc_get_font();
71        int pixels_x = font->width * size_x;
72        int pixels_y = font->height * (size_y + 1); // Add 1 for status line.
73        // Add margins that aren't large enough to fit a whole column or
74        // row at the right and bottom.  This tests incremental update of
75        // anything that might be displayed in the margins.
76        pixels_x += font->width - 1;
77        pixels_y += font->height - 1;
78        vc_surface = gfx_create_surface(
79            nullptr, pixels_x, pixels_y, /* stride= */ pixels_x,
80            ZX_PIXEL_FORMAT_RGB_565, 0);
81        EXPECT_TRUE(vc_surface, "");
82        // This takes ownership of vc_surface.
83        EXPECT_EQ(vc_init_gfx(vc_surface), ZX_OK, "");
84        EXPECT_EQ(vc_alloc(&vc_dev, false), ZX_OK, "");
85        EXPECT_EQ(vc_dev->columns, size_x, "");
86        EXPECT_EQ(vc_rows(vc_dev), static_cast<int>(size_y), "");
87        // Mark the console as active so that display updates get
88        // propagated to vc_surface.
89        vc_dev->active = true;
90        // Propagate the initial display contents to vc_surface.
91        vc_full_repaint(vc_dev);
92        vc_gfx_invalidate_all(vc_dev);
93    }
94
95    ~TextconHelper() {
96        delete[] textbuf;
97        vc_free(vc_dev);
98    }
99
100    // Takes a snapshot of the vc_t's display.
101    class DisplaySnapshot {
102    public:
103        DisplaySnapshot(TextconHelper* helper)
104            : helper_(helper),
105              snapshot_(new uint8_t[helper->vc_surface->len]) {
106            memcpy(snapshot_.get(), helper->vc_surface->ptr,
107                   helper->vc_surface->len);
108        }
109
110        // Returns whether the vc_t's display changed since the
111        // snapshot was taken.
112        bool ChangedSinceSnapshot() {
113            return memcmp(snapshot_.get(), helper_->vc_surface->ptr,
114                          helper_->vc_surface->len) != 0;
115        }
116
117        fbl::unique_ptr<char[]> ComparisonString() {
118            vc_t* vc_dev = helper_->vc_dev;
119            gfx_surface* vc_surface = helper_->vc_surface;
120            // Add 1 to these sizes to account for the margins.
121            uint32_t cmp_size_x = vc_dev->columns + 1;
122            uint32_t cmp_size_y = vc_dev->rows + 1;
123            uint32_t size_in_chars = cmp_size_x * cmp_size_y;
124
125            fbl::unique_ptr<bool[]> diffs(new bool[size_in_chars]);
126            for (uint32_t i = 0; i < size_in_chars; ++i)
127                diffs[i] = false;
128
129            for (uint32_t i = 0; i < vc_surface->len; ++i) {
130                if (static_cast<uint8_t*>(vc_surface->ptr)[i] != snapshot_[i]) {
131                    uint32_t pixel_index = i / vc_surface->pixelsize;
132                    uint32_t x_pixels = pixel_index % vc_surface->stride;
133                    uint32_t y_pixels = pixel_index / vc_surface->stride;
134                    uint32_t x_chars = x_pixels / vc_dev->charw;
135                    uint32_t y_chars = y_pixels / vc_dev->charh;
136                    EXPECT_LT(x_chars, cmp_size_x, "");
137                    EXPECT_LT(y_chars, cmp_size_y, "");
138                    diffs[x_chars + y_chars * cmp_size_x] = true;
139                }
140            }
141
142            // Build a string showing the differences.  If we had
143            // std::string or equivalent, we'd use that here.
144            size_t string_size = (cmp_size_x + 3) * cmp_size_y + 1;
145            fbl::unique_ptr<char[]> string(new char[string_size]);
146            char* ptr = string.get();
147            for (uint32_t y = 0; y < cmp_size_y; ++y) {
148                *ptr++ = '|';
149                for (uint32_t x = 0; x < cmp_size_x; ++x) {
150                    bool diff = diffs[x + y * cmp_size_x];
151                    *ptr++ = diff ? 'D' : '-';
152                }
153                *ptr++ = '|';
154                *ptr++ = '\n';
155            }
156            *ptr++ = 0;
157            EXPECT_EQ(ptr, string.get() + string_size, "");
158            return string;
159        }
160
161        // Prints a representation of which characters in the vc_t's
162        // display changed since the snapshot was taken.
163        void PrintComparison() {
164            printf("%s", ComparisonString().get());
165        }
166
167    private:
168        TextconHelper* helper_;
169        fbl::unique_ptr<uint8_t[]> snapshot_;
170    };
171
172    void InvalidateAllGraphics() {
173        vc_full_repaint(vc_dev);
174        vc_gfx_invalidate_all(vc_dev);
175    }
176
177    void PutString(const char* str) {
178        for (const char* ptr = str; *ptr; ++ptr)
179            textcon.putc(&textcon, *ptr);
180
181        vc_write(vc_dev, str, strlen(str), 0);
182        // Test that the incremental update of the display was correct.  We
183        // do that by refreshing the entire display, and checking that
184        // there was no change.
185        DisplaySnapshot copy(this);
186        InvalidateAllGraphics();
187        if (copy.ChangedSinceSnapshot()) {
188            copy.PrintComparison();
189            EXPECT_TRUE(false, "Display contents changed");
190        }
191    }
192
193    void AssertTextbufLineContains(vc_char_t* buf, int line_num,
194                                   const char* str) {
195        size_t len = strlen(str);
196        EXPECT_LE(len, size_x, "");
197        for (size_t i = 0; i < len; ++i)
198            EXPECT_EQ(str[i], vc_char_get_char(buf[size_x * line_num + i]), "");
199        // The rest of the line should contain spaces.
200        for (size_t i = len; i < size_x; ++i)
201            EXPECT_EQ(' ', vc_char_get_char(buf[size_x * line_num + i]), "");
202    }
203
204    void AssertLineContains(int line_num, const char* str) {
205        AssertTextbufLineContains(textbuf, line_num, str);
206        AssertTextbufLineContains(vc_dev->text_buf, line_num, str);
207    }
208
209    uint32_t size_x;
210    uint32_t size_y;
211
212    vc_char_t* textbuf;
213    textcon_t textcon = {};
214
215    gfx_surface* vc_surface;
216    vc_t* vc_dev;
217};
218
219bool test_simple() {
220    BEGIN_TEST;
221
222    TextconHelper tc(10, 5);
223    tc.PutString("Hello");
224    tc.AssertLineContains(0, "Hello");
225    tc.AssertLineContains(1, "");
226
227    END_TEST;
228}
229
230// This tests the DisplaySnapshot test helper above.  If we write directly
231// to vc_dev's text buffer without invalidating the display, the test
232// machinery should detect which characters in the display were not updated
233// properly.
234bool test_display_update_comparison() {
235    BEGIN_TEST;
236
237    TextconHelper tc(10, 3);
238    // Write some characters directly into the text buffer.
239    auto SetChar = [&](int x, int y, char ch) {
240        tc.vc_dev->text_buf[x + y * tc.size_x] =
241            vc_char_make(ch, tc.textcon.fg, tc.textcon.bg);
242    };
243    SetChar(2, 1, 'x');
244    SetChar(3, 1, 'y');
245    SetChar(6, 1, 'z');
246
247    // Check that these characters in the display are detected as not
248    // properly updated.
249    TextconHelper::DisplaySnapshot snapshot(&tc);
250    tc.InvalidateAllGraphics();
251    EXPECT_TRUE(snapshot.ChangedSinceSnapshot(), "");
252    const char *expected =
253        "|-----------|\n"  // Console status line
254        "|-----------|\n"  // Cursor at left was painted during tc init
255        "|--DD--D----|\n"  // Chars set by SetChar() above
256        "|-----------|\n"
257        "|-----------|\n"; // Bottom margin
258    EXPECT_EQ(strcmp(snapshot.ComparisonString().get(), expected), 0, "");
259
260    END_TEST;
261}
262
263bool test_wrapping() {
264    BEGIN_TEST;
265
266    TextconHelper tc(10, 5);
267    tc.PutString("Hello world! More text here.");
268    tc.AssertLineContains(0, "Hello worl");
269    tc.AssertLineContains(1, "d! More te");
270    tc.AssertLineContains(2, "xt here.");
271
272    END_TEST;
273}
274
275bool test_tabs() {
276    BEGIN_TEST;
277
278    TextconHelper tc(80, 40);
279    tc.PutString("\tA\n");
280    tc.PutString(" \tB\n");
281    tc.PutString("       \tC\n"); // 7 spaces
282    tc.PutString("        \tD\n"); // 8 spaces
283    tc.AssertLineContains(0, "        A");
284    tc.AssertLineContains(1, "        B");
285    tc.AssertLineContains(2, "        C");
286    tc.AssertLineContains(3, "                D");
287
288    END_TEST;
289}
290
291bool test_backspace_moves_cursor() {
292    BEGIN_TEST;
293
294    TextconHelper tc(10, 5);
295    tc.PutString("ABCDEF\b\b\b\bxy");
296    // Backspace only moves the cursor and does not erase, so "EF" is left
297    // in place.
298    tc.AssertLineContains(0, "ABxyEF");
299
300    END_TEST;
301}
302
303bool test_backspace_at_start_of_line() {
304    BEGIN_TEST;
305
306    TextconHelper tc(10, 5);
307    tc.PutString("Foo\n\bBar");
308    // When the cursor is at the start of a line, backspace has no effect.
309    tc.AssertLineContains(0, "Foo");
310    tc.AssertLineContains(1, "Bar");
311
312    END_TEST;
313}
314
315bool test_scroll_up() {
316    BEGIN_TEST;
317
318    TextconHelper tc(10, 4);
319    tc.PutString("AAA\nBBB\nCCC\nDDD\n");
320    tc.AssertLineContains(0, "BBB");
321    tc.AssertLineContains(1, "CCC");
322    tc.AssertLineContains(2, "DDD");
323    tc.AssertLineContains(3, "");
324    EXPECT_EQ(vc_get_scrollback_lines(tc.vc_dev), 1, "");
325
326    END_TEST;
327}
328
329// Same as scroll_up(), but using ESC E (NEL) instead of "\n".
330bool test_scroll_up_nel() {
331    BEGIN_TEST;
332
333    TextconHelper tc(10, 4);
334    tc.PutString("AAA" "\x1b" "E"
335                 "BBB" "\x1b" "E"
336                 "CCC" "\x1b" "E"
337                 "DDD" "\x1b" "E");
338    tc.AssertLineContains(0, "BBB");
339    tc.AssertLineContains(1, "CCC");
340    tc.AssertLineContains(2, "DDD");
341    tc.AssertLineContains(3, "");
342    EXPECT_EQ(vc_get_scrollback_lines(tc.vc_dev), 1, "");
343
344    END_TEST;
345}
346
347bool test_insert_lines() {
348    BEGIN_TEST;
349
350    TextconHelper tc(10, 5);
351    tc.PutString("AAA\nBBB\nCCC\nDDD\nEEE");
352    tc.PutString("\x1b[2A"); // Move the cursor up 2 lines
353    tc.PutString("\x1b[2L"); // Insert 2 lines
354    tc.PutString("Z"); // Output char to show where the cursor ends up
355    tc.AssertLineContains(0, "AAA");
356    tc.AssertLineContains(1, "BBB");
357    tc.AssertLineContains(2, "   Z");
358    tc.AssertLineContains(3, "");
359    tc.AssertLineContains(4, "CCC");
360    EXPECT_EQ(vc_get_scrollback_lines(tc.vc_dev), 0, "");
361
362    END_TEST;
363}
364
365bool test_delete_lines() {
366    BEGIN_TEST;
367
368    TextconHelper tc(10, 5);
369    tc.PutString("AAA\nBBB\nCCC\nDDD\nEEE");
370    tc.PutString("\x1b[2A"); // Move the cursor up 2 lines
371    tc.PutString("\x1b[2M"); // Delete 2 lines
372    tc.PutString("Z"); // Output char to show where the cursor ends up
373    tc.AssertLineContains(0, "AAA");
374    tc.AssertLineContains(1, "BBB");
375    tc.AssertLineContains(2, "EEEZ");
376    tc.AssertLineContains(3, "");
377    tc.AssertLineContains(4, "");
378    // TODO(mseaborn): We probably don't want to be adding the deleted
379    // lines to the scrollback in this case, because they are not from the
380    // top of the console.
381    EXPECT_EQ(vc_get_scrollback_lines(tc.vc_dev), 2, "");
382
383    END_TEST;
384}
385
386// Test for a bug where this would cause an out-of-bounds array access.
387bool test_insert_lines_many() {
388    BEGIN_TEST;
389
390    TextconHelper tc(10, 5);
391    tc.PutString("AAA\nBBB");
392    tc.PutString("\x1b[999L"); // Insert 999 lines
393    tc.PutString("Z"); // Output char to show where the cursor ends up
394    tc.AssertLineContains(0, "AAA");
395    tc.AssertLineContains(1, "   Z");
396
397    END_TEST;
398}
399
400// Test for a bug where this would cause an out-of-bounds array access.
401bool test_delete_lines_many() {
402    BEGIN_TEST;
403
404    TextconHelper tc(10, 5);
405    tc.PutString("AAA\nBBB");
406    tc.PutString("\x1b[999M"); // Delete 999 lines
407    tc.PutString("Z"); // Output char to show where the cursor ends up
408    tc.AssertLineContains(0, "AAA");
409    tc.AssertLineContains(1, "   Z");
410
411    END_TEST;
412}
413
414
415// Check that passing a huge parameter via "insert lines" completes in a
416// reasonable amount of time.  (We don't check the time here but we assume
417// that someone will notice if this takes a long time.)
418bool test_insert_lines_huge() {
419    BEGIN_TEST;
420
421    TextconHelper tc(10, 5);
422    tc.PutString("AAA\nBBB");
423    tc.PutString("\x1b[2000000000L"); // Insert lines
424    tc.PutString("Z"); // Output char to show where the cursor ends up
425    tc.AssertLineContains(0, "AAA");
426    tc.AssertLineContains(1, "   Z");
427
428    END_TEST;
429}
430
431// Check that passing a huge parameter via "delete lines" completes in a
432// reasonable amount of time.  (We don't check the time here but we assume
433// that someone will notice if this takes a long time.)
434bool test_delete_lines_huge() {
435    BEGIN_TEST;
436
437    TextconHelper tc(10, 5);
438    tc.PutString("AAA\nBBB");
439    tc.PutString("\x1b[200000000M"); // Delete lines
440    tc.PutString("Z"); // Output char to show where the cursor ends up
441    tc.AssertLineContains(0, "AAA");
442    tc.AssertLineContains(1, "   Z");
443
444    END_TEST;
445}
446
447bool test_move_cursor_up_and_scroll() {
448    BEGIN_TEST;
449
450    TextconHelper tc(10, 4);
451    tc.PutString("AAA\nBBB\nCCC\nDDD");
452    tc.PutString("\x1bM" "1"); // Move cursor up; print char
453    tc.PutString("\x1bM" "2"); // Move cursor up; print char
454    tc.PutString("\x1bM" "3"); // Move cursor up; print char
455    tc.PutString("\x1bM" "4"); // Move cursor up; print char
456    tc.AssertLineContains(0, "      4");
457    tc.AssertLineContains(1, "AAA  3");
458    tc.AssertLineContains(2, "BBB 2");
459    tc.AssertLineContains(3, "CCC1");
460
461    END_TEST;
462}
463
464bool test_move_cursor_down_and_scroll() {
465    BEGIN_TEST;
466
467    TextconHelper tc(10, 4);
468    tc.PutString("1" "\x1b" "D"); // Print char; move cursor down
469    tc.PutString("2" "\x1b" "D"); // Print char; move cursor down
470    tc.PutString("3" "\x1b" "D"); // Print char; move cursor down
471    tc.PutString("4" "\x1b" "D"); // Print char; move cursor down
472    tc.PutString("5");
473    tc.AssertLineContains(0, " 2");
474    tc.AssertLineContains(1, "  3");
475    tc.AssertLineContains(2, "   4");
476    tc.AssertLineContains(3, "    5");
477
478    END_TEST;
479}
480
481bool test_cursor_hide_and_show() {
482    BEGIN_TEST;
483
484    TextconHelper tc(10, 4);
485    ASSERT_FALSE(tc.vc_dev->hide_cursor, "");
486    tc.PutString("\x1b[?25l"); // Hide cursor
487    ASSERT_TRUE(tc.vc_dev->hide_cursor, "");
488    tc.PutString("\x1b[?25h"); // Show cursor
489    ASSERT_FALSE(tc.vc_dev->hide_cursor, "");
490
491    END_TEST;
492}
493
494// This tests for a bug: If the cursor was positioned over a character when
495// we scroll up, that character would get erased.
496bool test_cursor_scroll_bug() {
497    BEGIN_TEST;
498
499    TextconHelper tc(10, 3);
500    // Move the cursor to the bottom line.
501    tc.PutString("\n\n\n");
502    // Scroll down when the cursor is over "C".
503    tc.PutString("ABCDE\b\b\b\n");
504
505    END_TEST;
506}
507
508// Test for a bug where scrolling the console viewport by a large delta
509// (e.g. going from the top to the bottom) can crash due to out-of-bounds
510// memory accesses.
511bool test_scroll_viewport_by_large_delta() {
512    BEGIN_TEST;
513
514    TextconHelper tc(2, 2);
515    tc.PutString("\n");
516    for (int lines = 1; lines < 100; ++lines) {
517        tc.PutString("\n");
518
519        // Scroll up, to show older lines.
520        vc_scroll_viewport_top(tc.vc_dev);
521        EXPECT_EQ(tc.vc_dev->viewport_y, -lines, "");
522
523        // Scroll down, to show newer lines.
524        vc_scroll_viewport_bottom(tc.vc_dev);
525        EXPECT_EQ(tc.vc_dev->viewport_y, 0, "");
526    }
527
528    END_TEST;
529}
530
531// When the console is displaying only the main console region (and no
532// scrollback), the console should keep displaying that as new lines are
533// outputted.
534bool test_viewport_scrolling_follows_bottom() {
535    BEGIN_TEST;
536
537    TextconHelper tc(1, 1);
538    for (unsigned i = 0; i < tc.vc_dev->scrollback_rows_max * 2; ++i) {
539        EXPECT_EQ(tc.vc_dev->viewport_y, 0, "");
540        tc.PutString("\n");
541    }
542
543    END_TEST;
544}
545
546// When the console is displaying some of the scrollback buffer, then as
547// new lines are outputted, the console should scroll the viewpoint to keep
548// displaying the same point, unless we're at the top of the scrollback
549// buffer.
550bool test_viewport_scrolling_follows_scrollback() {
551    BEGIN_TEST;
552
553    TextconHelper tc(1, 1);
554    // Add 3 lines to the scrollback buffer.
555    tc.PutString("\n\n\n");
556    vc_scroll_viewport(tc.vc_dev, -2);
557
558    EXPECT_EQ(tc.vc_dev->viewport_y, -2, "");
559    int limit = tc.vc_dev->scrollback_rows_max;
560    for (int line = 3; line < limit * 2; ++line) {
561        // Output different strings on each line in order to test that the
562        // display is updated consistently when the console starts dropping
563        // lines from the scrollback region.
564        char str[3] = { static_cast<char>('0' + (line % 10)), '\n', '\0' };
565        tc.PutString(str);
566        EXPECT_EQ(tc.vc_dev->viewport_y, -MIN(line, limit), "");
567    }
568
569    END_TEST;
570}
571
572bool test_output_when_viewport_scrolled() {
573    BEGIN_TEST;
574
575    TextconHelper tc(10, 3);
576    // Line 1 will move into the scrollback region.
577    tc.PutString("1\n 2\n  3\n   4");
578    EXPECT_EQ(tc.vc_dev->viewport_y, 0, "");
579    vc_scroll_viewport_top(tc.vc_dev);
580
581    EXPECT_EQ(tc.vc_dev->viewport_y, -1, "");
582    // Check redrawing consistency.
583    tc.PutString("");
584
585    // Test that output updates the display correctly when the viewport is
586    // scrolled.  Using two separate PutString() calls here was necessary
587    // for reproducing an incremental update bug.
588    tc.PutString("\x1b[1;1f"); // Move to top left
589    tc.PutString("Epilobium");
590    tc.AssertLineContains(0, "Epilobium");
591    tc.AssertLineContains(1, "  3");
592    tc.AssertLineContains(2, "   4");
593
594    // Test that erasing also updates the display correctly.  This
595    // changes the console contents without moving the cursor.
596    tc.PutString("\b\b\b\b"); // Move cursor left 3 chars
597    tc.PutString("\x1b[1K"); // Erase to beginning of line
598    tc.AssertLineContains(0, "      ium");
599    tc.AssertLineContains(1, "  3");
600    tc.AssertLineContains(2, "   4");
601
602    END_TEST;
603}
604
605bool test_scrolling_when_viewport_scrolled() {
606    BEGIN_TEST;
607
608    TextconHelper tc(10, 3);
609    // Line 1 will move into the scrollback region.
610    tc.PutString("1\n 2\n  3\n   4");
611    EXPECT_EQ(tc.vc_dev->viewport_y, 0, "");
612    vc_scroll_viewport_top(tc.vc_dev);
613    EXPECT_EQ(tc.vc_dev->viewport_y, -1, "");
614    // Check redrawing consistency.
615    tc.PutString("");
616
617    // Test that the display is updated correctly when we scroll.
618    tc.PutString("\n5");
619    tc.AssertLineContains(0, "  3");
620    tc.AssertLineContains(1, "   4");
621    tc.AssertLineContains(2, "5");
622
623    END_TEST;
624}
625
626// Test that vc_get_scrollback_lines() gives the correct results.
627bool test_scrollback_lines_count() {
628    BEGIN_TEST;
629
630    TextconHelper tc(10, 3);
631    tc.PutString("\n\n");
632
633    // Reduce the scrollback limit to make the test faster.
634    const int kLimit = 20;
635    EXPECT_LE(kLimit, tc.vc_dev->scrollback_rows_max, "");
636    tc.vc_dev->scrollback_rows_max = kLimit;
637
638    for (int lines = 1; lines < kLimit * 4; ++lines) {
639        tc.PutString("\n");
640        EXPECT_EQ(MIN(lines, kLimit),
641                  vc_get_scrollback_lines(tc.vc_dev), "");
642    }
643
644    END_TEST;
645}
646
647// Test that the scrollback lines have the correct contents.
648bool test_scrollback_lines_contents() {
649    BEGIN_TEST;
650
651    // Use a 1-row-high console, which simplifies this test.
652    TextconHelper tc(3, 1);
653
654    // Reduce the scrollback limit to make the test faster.
655    const int kLimit = 20;
656    EXPECT_LE(kLimit, tc.vc_dev->scrollback_rows_max, "");
657    tc.vc_dev->scrollback_rows_max = kLimit;
658
659    vc_char_t test_val = 0;
660    for (int lines = 1; lines <= kLimit; ++lines) {
661        tc.vc_dev->text_buf[0] = test_val++;
662        tc.PutString("\n");
663
664        EXPECT_EQ(lines, vc_get_scrollback_lines(tc.vc_dev), "");
665        for (int i = 0; i < lines; ++i)
666            EXPECT_EQ(i, vc_get_scrollback_line_ptr(tc.vc_dev, i)[0], "");
667    }
668    for (int lines = 0; lines < kLimit * 3; ++lines) {
669        tc.vc_dev->text_buf[0] = test_val++;
670        tc.PutString("\n");
671
672        EXPECT_EQ(kLimit, vc_get_scrollback_lines(tc.vc_dev), "");
673        for (int i = 0; i < kLimit; ++i) {
674            EXPECT_EQ(test_val + i - kLimit,
675                      vc_get_scrollback_line_ptr(tc.vc_dev, i)[0], "");
676        }
677    }
678
679    END_TEST;
680}
681
682BEGIN_TEST_CASE(gfxconsole_textbuf_tests)
683RUN_TEST(test_simple)
684RUN_TEST(test_display_update_comparison)
685RUN_TEST(test_wrapping)
686RUN_TEST(test_tabs)
687RUN_TEST(test_backspace_moves_cursor)
688RUN_TEST(test_backspace_at_start_of_line)
689RUN_TEST(test_scroll_up)
690RUN_TEST(test_scroll_up_nel)
691RUN_TEST(test_insert_lines)
692RUN_TEST(test_delete_lines)
693RUN_TEST(test_insert_lines_many)
694RUN_TEST(test_delete_lines_many)
695RUN_TEST(test_insert_lines_huge)
696RUN_TEST(test_delete_lines_huge)
697RUN_TEST(test_move_cursor_up_and_scroll)
698RUN_TEST(test_move_cursor_down_and_scroll)
699RUN_TEST(test_cursor_hide_and_show)
700RUN_TEST(test_cursor_scroll_bug)
701RUN_TEST(test_scroll_viewport_by_large_delta)
702RUN_TEST(test_viewport_scrolling_follows_bottom)
703RUN_TEST(test_viewport_scrolling_follows_scrollback)
704RUN_TEST(test_output_when_viewport_scrolled)
705RUN_TEST(test_scrolling_when_viewport_scrolled)
706RUN_TEST(test_scrollback_lines_count)
707RUN_TEST(test_scrollback_lines_contents)
708END_TEST_CASE(gfxconsole_textbuf_tests)
709
710}
711
712#ifndef BUILD_COMBINED_TESTS
713int main(int argc, char** argv) {
714    return unittest_run_all_tests(argc, argv) ? 0 : -1;
715}
716#endif
717