#include "putty.h" #include "terminal.h" void modalfatalbox(const char *p, ...) { va_list ap; fprintf(stderr, "FATAL ERROR: "); va_start(ap, p); vfprintf(stderr, p, ap); va_end(ap); fputc('\n', stderr); exit(1); } const char *const appname = "test_lineedit"; char *platform_default_s(const char *name) { return NULL; } bool platform_default_b(const char *name, bool def) { return def; } int platform_default_i(const char *name, int def) { return def; } FontSpec *platform_default_fontspec(const char *name) { return fontspec_new_default(); } Filename *platform_default_filename(const char *name) { return filename_from_str(""); } const struct BackendVtable *const backends[] = { NULL }; typedef struct Mock { Terminal *term; Conf *conf; struct unicode_data ucsdata[1]; strbuf *context; bool any_test_failed; TermWin tw; } Mock; static bool mock_setup_draw_ctx(TermWin *win) { return false; } static void mock_draw_text(TermWin *win, int x, int y, wchar_t *text, int len, unsigned long attrs, int lattrs, truecolour tc) {} static void mock_draw_cursor(TermWin *win, int x, int y, wchar_t *text, int len, unsigned long attrs, int lattrs, truecolour tc) {} static void mock_set_raw_mouse_mode(TermWin *win, bool enable) {} static void mock_set_raw_mouse_mode_pointer(TermWin *win, bool enable) {} static void mock_palette_set(TermWin *win, unsigned start, unsigned ncolours, const rgb *colours) {} static void mock_palette_get_overrides(TermWin *tw, Terminal *term) {} static const TermWinVtable mock_termwin_vt = { .setup_draw_ctx = mock_setup_draw_ctx, .draw_text = mock_draw_text, .draw_cursor = mock_draw_cursor, .set_raw_mouse_mode = mock_set_raw_mouse_mode, .set_raw_mouse_mode_pointer = mock_set_raw_mouse_mode_pointer, .palette_set = mock_palette_set, .palette_get_overrides = mock_palette_get_overrides, }; static Mock *mock_new(void) { Mock *mk = snew(Mock); memset(mk, 0, sizeof(*mk)); mk->conf = conf_new(); do_defaults(NULL, mk->conf); init_ucs_generic(mk->conf, mk->ucsdata); mk->ucsdata->line_codepage = CP_ISO8859_1; mk->context = strbuf_new(); mk->tw.vt = &mock_termwin_vt; return mk; } static void mock_free(Mock *mk) { strbuf_free(mk->context); conf_free(mk->conf); term_free(mk->term); sfree(mk); } static void reset(Mock *mk) { term_pwron(mk->term, true); term_size(mk->term, 24, 80, 0); term_set_trust_status(mk->term, false); strbuf_clear(mk->context); } #if 0 static void test_context(Mock *mk, const char *fmt, ...) { strbuf_clear(mk->context); va_list ap; va_start(ap, fmt); put_fmtv(mk->context, fmt, ap); va_end(ap); } #endif static void report_fail(Mock *mk, const char *file, int line, const char *fmt, ...) { printf("%s:%d", file, line); if (mk->context->len) printf(" (%s)", mk->context->s); printf(": "); va_list ap; va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf("\n"); mk->any_test_failed = true; } static inline void check_iequal(Mock *mk, const char *file, int line, long long lhs, long long rhs) { if (lhs != rhs) report_fail(mk, file, line, "%lld != %lld / %#llx != %#llx", lhs, rhs, lhs, rhs); } #define IEQUAL(lhs, rhs) check_iequal(mk, __FILE__, __LINE__, lhs, rhs) static inline void term_datapl(Terminal *term, ptrlen pl) { term_data(term, pl.ptr, pl.len); } static struct termchar get_termchar(Terminal *term, int x, int y) { termline *tl = term_get_line(term, y); termchar tc; if (0 <= x && x < tl->cols) tc = tl->chars[x]; else tc = term->erase_char; term_release_line(tl); return tc; } static unsigned short get_lineattr(Terminal *term, int y) { termline *tl = term_get_line(term, y); unsigned short lattr = tl->lattr; term_release_line(tl); return lattr; } static void test_hello_world(Mock *mk) { /* A trivial test just to kick off this test framework */ mk->ucsdata->line_codepage = CP_ISO8859_1; reset(mk); term_datapl(mk->term, PTRLEN_LITERAL("hello, world")); IEQUAL(mk->term->curs.x, 12); IEQUAL(mk->term->curs.y, 0); IEQUAL(get_termchar(mk->term, 0, 0).chr, CSET_ASCII | 'h'); IEQUAL(get_termchar(mk->term, 1, 0).chr, CSET_ASCII | 'e'); IEQUAL(get_termchar(mk->term, 2, 0).chr, CSET_ASCII | 'l'); IEQUAL(get_termchar(mk->term, 3, 0).chr, CSET_ASCII | 'l'); IEQUAL(get_termchar(mk->term, 4, 0).chr, CSET_ASCII | 'o'); IEQUAL(get_termchar(mk->term, 5, 0).chr, CSET_ASCII | ','); IEQUAL(get_termchar(mk->term, 6, 0).chr, CSET_ASCII | ' '); IEQUAL(get_termchar(mk->term, 7, 0).chr, CSET_ASCII | 'w'); IEQUAL(get_termchar(mk->term, 8, 0).chr, CSET_ASCII | 'o'); IEQUAL(get_termchar(mk->term, 9, 0).chr, CSET_ASCII | 'r'); IEQUAL(get_termchar(mk->term, 10, 0).chr, CSET_ASCII | 'l'); IEQUAL(get_termchar(mk->term, 11, 0).chr, CSET_ASCII | 'd'); } static void test_wrap(Mock *mk) { /* Test behaviour when printing characters wrap to the next line */ mk->ucsdata->line_codepage = CP_UTF8; /* Print 'abc' without enough space for the c, in wrapping mode */ reset(mk); mk->term->curs.x = 78; mk->term->curs.y = 0; mk->term->wrap = true; /* The 'a' prints without anything unusual happening */ term_datapl(mk->term, PTRLEN_LITERAL("a")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); /* The 'b' prints, leaving the cursor where it is with wrapnext set */ term_datapl(mk->term, PTRLEN_LITERAL("b")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 1); IEQUAL(get_lineattr(mk->term, 0), 0); IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'b'); /* And now the 'c' causes a deferred wrap and goes to the next line */ term_datapl(mk->term, PTRLEN_LITERAL("c")); IEQUAL(mk->term->curs.x, 1); IEQUAL(mk->term->curs.y, 1); IEQUAL(mk->term->wrapnext, 0); IEQUAL(get_lineattr(mk->term, 0), LATTR_WRAPPED); IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'b'); IEQUAL(get_termchar(mk->term, 0, 1).chr, CSET_ASCII | 'c'); /* If we backspace once, the cursor moves back on to the c */ term_datapl(mk->term, PTRLEN_LITERAL("\b")); IEQUAL(mk->term->curs.x, 0); IEQUAL(mk->term->curs.y, 1); IEQUAL(mk->term->wrapnext, 0); /* Now backspace again, and the cursor returns to the b */ term_datapl(mk->term, PTRLEN_LITERAL("\b")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); /* Now try it with a double-width character in place of ab */ mk->term->curs.x = 78; mk->term->curs.y = 0; mk->term->wrap = true; /* The DW character goes directly to the wrapnext state */ term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 1); IEQUAL(get_termchar(mk->term, 78, 0).chr, 0xAC00); IEQUAL(get_termchar(mk->term, 79, 0).chr, UCSWIDE); /* And the 'c' causes a deferred wrap as before */ term_datapl(mk->term, PTRLEN_LITERAL("c")); IEQUAL(mk->term->curs.x, 1); IEQUAL(mk->term->curs.y, 1); IEQUAL(mk->term->wrapnext, 0); IEQUAL(get_lineattr(mk->term, 0), LATTR_WRAPPED); IEQUAL(get_termchar(mk->term, 78, 0).chr, 0xAC00); IEQUAL(get_termchar(mk->term, 79, 0).chr, UCSWIDE); IEQUAL(get_termchar(mk->term, 0, 1).chr, CSET_ASCII | 'c'); /* If we backspace once, the cursor moves back on to the c */ term_datapl(mk->term, PTRLEN_LITERAL("\b")); IEQUAL(mk->term->curs.x, 0); IEQUAL(mk->term->curs.y, 1); IEQUAL(mk->term->wrapnext, 0); /* Now backspace again, and the cursor goes to the RHS of the DW char */ term_datapl(mk->term, PTRLEN_LITERAL("\b")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); /* Now put the DW character in place of bc */ reset(mk); mk->term->curs.x = 78; mk->term->curs.y = 0; mk->term->wrap = true; /* The 'a' prints as before */ term_datapl(mk->term, PTRLEN_LITERAL("a")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); /* The DW character wraps, setting LATTR_WRAPPED2 */ term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); IEQUAL(mk->term->curs.x, 2); IEQUAL(mk->term->curs.y, 1); IEQUAL(mk->term->wrapnext, 0); IEQUAL(get_lineattr(mk->term, 0), LATTR_WRAPPED | LATTR_WRAPPED2); IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | ' '); IEQUAL(get_termchar(mk->term, 0, 1).chr, 0xAC00); IEQUAL(get_termchar(mk->term, 1, 1).chr, UCSWIDE); /* If we backspace once, cursor moves to the RHS of the DW char */ term_datapl(mk->term, PTRLEN_LITERAL("\b")); IEQUAL(mk->term->curs.x, 1); IEQUAL(mk->term->curs.y, 1); IEQUAL(mk->term->wrapnext, 0); /* Backspace again, and cursor moves from RHS to LHS of that char */ term_datapl(mk->term, PTRLEN_LITERAL("\b")); IEQUAL(mk->term->curs.x, 0); IEQUAL(mk->term->curs.y, 1); IEQUAL(mk->term->wrapnext, 0); /* Now backspace again, and the cursor skips the empty column so * that it can return to the previous logical character, to wit, the a */ term_datapl(mk->term, PTRLEN_LITERAL("\b")); IEQUAL(mk->term->curs.x, 78); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); /* Print 'ab' up to the rightmost column, and then backspace */ reset(mk); mk->term->curs.x = 78; mk->term->curs.y = 0; mk->term->wrap = true; /* As before, the 'ab' put us in the rightmost column with wrapnext set */ term_datapl(mk->term, PTRLEN_LITERAL("ab")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 1); IEQUAL(get_lineattr(mk->term, 0), 0); IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'b'); /* Backspacing just clears the wrapnext flag, so we're logically * back on the b again */ term_datapl(mk->term, PTRLEN_LITERAL("\b")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); /* For completeness, the easy case: just print 'a' then backspace */ reset(mk); mk->term->curs.x = 78; mk->term->curs.y = 0; mk->term->wrap = true; /* 'a' printed in column n-1 takes us to column n */ term_datapl(mk->term, PTRLEN_LITERAL("a")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); IEQUAL(get_lineattr(mk->term, 0), 0); IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); /* Backspacing moves us back a space on to the a */ term_datapl(mk->term, PTRLEN_LITERAL("\b")); IEQUAL(mk->term->curs.x, 78); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); /* * Now test the special cases that arise when the terminal is only * one column wide! */ reset(mk); term_size(mk->term, 24, 1, 0); mk->term->curs.x = 0; mk->term->curs.y = 0; mk->term->wrap = true; /* Printing a single-width character takes us into wrapnext immediately */ term_datapl(mk->term, PTRLEN_LITERAL("a")); IEQUAL(mk->term->curs.x, 0); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 1); IEQUAL(get_lineattr(mk->term, 0), 0); IEQUAL(get_termchar(mk->term, 0, 0).chr, CSET_ASCII | 'a'); /* Printing a second one wraps, and takes us _back_ to wrapnext */ term_datapl(mk->term, PTRLEN_LITERAL("b")); IEQUAL(mk->term->curs.x, 0); IEQUAL(mk->term->curs.y, 1); IEQUAL(mk->term->wrapnext, 1); IEQUAL(get_lineattr(mk->term, 0), LATTR_WRAPPED); IEQUAL(get_termchar(mk->term, 0, 0).chr, CSET_ASCII | 'a'); IEQUAL(get_termchar(mk->term, 0, 1).chr, CSET_ASCII | 'b'); /* Backspacing once clears the wrapnext flag, putting us on the b */ term_datapl(mk->term, PTRLEN_LITERAL("\b")); IEQUAL(mk->term->curs.x, 0); IEQUAL(mk->term->curs.y, 1); IEQUAL(mk->term->wrapnext, 0); /* Backspacing again returns to the previous line, putting us on the a */ term_datapl(mk->term, PTRLEN_LITERAL("\b")); IEQUAL(mk->term->curs.x, 0); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); /* And now try with a double-width character */ reset(mk); term_size(mk->term, 24, 1, 0); mk->term->curs.x = 0; mk->term->curs.y = 0; mk->term->wrap = true; /* DW character won't fit at all, so it transforms into U+FFFD * REPLACEMENT CHARACTER and then behaves like a SW char */ term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); IEQUAL(mk->term->curs.x, 0); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 1); IEQUAL(get_lineattr(mk->term, 0), 0); IEQUAL(get_termchar(mk->term, 0, 0).chr, 0xFFFD); } static void test_nonwrap(Mock *mk) { /* Test behaviour when printing characters hit end of line without wrap. * The wrapnext flag is never set in this mode. */ mk->ucsdata->line_codepage = CP_UTF8; /* Print 'abc' without enough space for the c */ reset(mk); mk->term->curs.x = 78; mk->term->curs.y = 0; mk->term->wrap = false; /* The 'a' prints without anything unusual happening */ term_datapl(mk->term, PTRLEN_LITERAL("a")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); /* The 'b' prints, leaving the cursor where it is */ term_datapl(mk->term, PTRLEN_LITERAL("b")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); IEQUAL(get_lineattr(mk->term, 0), 0); IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'b'); /* The 'c' overwrites the b */ term_datapl(mk->term, PTRLEN_LITERAL("c")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); IEQUAL(get_lineattr(mk->term, 0), 0); IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'c'); /* Since wrapnext was never set, backspacing returns us to the a */ term_datapl(mk->term, PTRLEN_LITERAL("\b")); IEQUAL(mk->term->curs.x, 78); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); /* Now try it with a double-width character in place of ab */ mk->term->curs.x = 78; mk->term->curs.y = 0; mk->term->wrap = false; /* The DW character occupies the rightmost two columns */ term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); IEQUAL(get_termchar(mk->term, 78, 0).chr, 0xAC00); IEQUAL(get_termchar(mk->term, 79, 0).chr, UCSWIDE); /* The 'c' must overprint the RHS of the DW char, clearing the LHS */ term_datapl(mk->term, PTRLEN_LITERAL("c")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); IEQUAL(get_lineattr(mk->term, 0), 0); IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | ' '); IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'c'); /* Now put the DW char in place of the bc */ reset(mk); mk->term->curs.x = 78; mk->term->curs.y = 0; mk->term->wrap = false; /* The 'a' prints as before */ term_datapl(mk->term, PTRLEN_LITERAL("a")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); /* The DW char won't fit, so turns into U+FFFD REPLACEMENT CHARACTER */ term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); IEQUAL(get_lineattr(mk->term, 0), 0); IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); IEQUAL(get_termchar(mk->term, 79, 0).chr, 0xFFFD); /* Just for completeness, try both of those together */ reset(mk); mk->term->curs.x = 78; mk->term->curs.y = 0; mk->term->wrap = false; /* First DW character occupies the rightmost columns */ term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); IEQUAL(get_termchar(mk->term, 78, 0).chr, 0xAC00); IEQUAL(get_termchar(mk->term, 79, 0).chr, UCSWIDE); /* Second DW char becomes U+FFFD, overwriting RHS of the first one */ term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x81")); IEQUAL(mk->term->curs.x, 79); IEQUAL(mk->term->curs.y, 0); IEQUAL(mk->term->wrapnext, 0); IEQUAL(get_lineattr(mk->term, 0), 0); IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | ' '); IEQUAL(get_termchar(mk->term, 79, 0).chr, 0xFFFD); } int main(void) { Mock *mk = mock_new(); mk->term = term_init(mk->conf, mk->ucsdata, &mk->tw); test_hello_world(mk); test_wrap(mk); test_nonwrap(mk); bool failed = mk->any_test_failed; mock_free(mk); if (failed) { printf("Test suite FAILED!\n"); return 1; } else { printf("Test suite passed\n"); return 0; } }