1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-10 01:48:00 +00:00
putty-source/test/test_terminal.c
Simon Tatham 259de04636 Run test_lineedit and test_terminal in the main build.
These seem likely to carry on being useful, so let's make sure they
pass before allowing any build to complete successfully. I've added
code to both test programs to return a sensible exit status indicating
pass/fail, and added runs of both to Buildscr.
2023-03-05 10:26:45 +00:00

509 lines
18 KiB
C

#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 */
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 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');
/* The 'c' overwrites the b, leaving wrapnext still set */
term_datapl(mk->term, PTRLEN_LITERAL("c"));
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 | 'c');
/* So backspacing clears wrapnext, leaving us on the c */
term_datapl(mk->term, PTRLEN_LITERAL("\b"));
IEQUAL(mk->term->curs.x, 79);
IEQUAL(mk->term->curs.y, 0);
IEQUAL(mk->term->wrapnext, 0);
/* And backspacing again returns the cursor 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 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);
/* 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, 1);
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, 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, 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 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);
/* 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, 1);
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;
}
}