mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 09:27:59 +00:00
Add a test rig for ldisc's local line editing.
I'm about to rewrite it completely, so the first thing I need to do is to write tests for as much of the functionality as possible, so that I can check the new implementation behaves in the same ways.
This commit is contained in:
parent
fd43ff6e27
commit
7a48837471
735
test/test_lineedit.c
Normal file
735
test/test_lineedit.c
Normal file
@ -0,0 +1,735 @@
|
||||
#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(""); }
|
||||
|
||||
struct SpecialRecord {
|
||||
SessionSpecialCode code;
|
||||
int arg;
|
||||
};
|
||||
|
||||
typedef struct Mock {
|
||||
Terminal *term;
|
||||
Ldisc *ldisc;
|
||||
Conf *conf;
|
||||
struct unicode_data ucsdata[1];
|
||||
|
||||
bool echo, edit;
|
||||
strbuf *to_terminal, *to_backend;
|
||||
|
||||
struct SpecialRecord *specials;
|
||||
size_t nspecials, specialsize;
|
||||
|
||||
strbuf *context; /* for printing in failed tests */
|
||||
|
||||
TermWin tw;
|
||||
Seat seat;
|
||||
Backend backend;
|
||||
} Mock;
|
||||
|
||||
static size_t mock_output(Seat *seat, SeatOutputType type,
|
||||
const void *data, size_t len)
|
||||
{
|
||||
Mock *mk = container_of(seat, Mock, seat);
|
||||
put_data(mk->to_terminal, data, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mock_send(Backend *be, const char *buf, size_t len)
|
||||
{
|
||||
Mock *mk = container_of(be, Mock, backend);
|
||||
put_data(mk->to_backend, buf, len);
|
||||
}
|
||||
|
||||
static void mock_special(Backend *be, SessionSpecialCode code, int arg)
|
||||
{
|
||||
Mock *mk = container_of(be, Mock, backend);
|
||||
sgrowarray(mk->specials, mk->specialsize, mk->nspecials);
|
||||
mk->specials[mk->nspecials].code = code;
|
||||
mk->specials[mk->nspecials].arg = arg;
|
||||
mk->nspecials++;
|
||||
}
|
||||
|
||||
static bool mock_ldisc_option_state(Backend *be, int option)
|
||||
{
|
||||
Mock *mk = container_of(be, Mock, backend);
|
||||
switch (option) {
|
||||
case LD_ECHO:
|
||||
return mk->echo;
|
||||
case LD_EDIT:
|
||||
return mk->edit;
|
||||
default:
|
||||
unreachable("bad ldisc option");
|
||||
}
|
||||
}
|
||||
|
||||
static void mock_provide_ldisc(Backend *be, Ldisc *ldisc)
|
||||
{
|
||||
Mock *mk = container_of(be, Mock, backend);
|
||||
mk->ldisc = ldisc;
|
||||
}
|
||||
|
||||
static bool mock_sendok(Backend *be)
|
||||
{
|
||||
Mock *mk = container_of(be, Mock, backend);
|
||||
(void)mk;
|
||||
/* FIXME: perhaps make this settable, to test the input_queue system? */
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mock_set_raw_mouse_mode(TermWin *win, bool enable) {}
|
||||
static void mock_palette_get_overrides(TermWin *tw, Terminal *term) {}
|
||||
|
||||
static const TermWinVtable mock_termwin_vt = {
|
||||
.set_raw_mouse_mode = mock_set_raw_mouse_mode,
|
||||
.palette_get_overrides = mock_palette_get_overrides,
|
||||
};
|
||||
|
||||
static const SeatVtable mock_seat_vt = {
|
||||
.output = mock_output,
|
||||
.echoedit_update = nullseat_echoedit_update,
|
||||
};
|
||||
|
||||
static const BackendVtable mock_backend_vt = {
|
||||
.sendok = mock_sendok,
|
||||
.send = mock_send,
|
||||
.special = mock_special,
|
||||
.ldisc_option_state = mock_ldisc_option_state,
|
||||
.provide_ldisc = mock_provide_ldisc,
|
||||
.id = "mock",
|
||||
};
|
||||
|
||||
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_437;
|
||||
|
||||
mk->context = strbuf_new();
|
||||
mk->to_terminal = strbuf_new();
|
||||
mk->to_backend = strbuf_new();
|
||||
|
||||
mk->tw.vt = &mock_termwin_vt;
|
||||
mk->seat.vt = &mock_seat_vt;
|
||||
mk->backend.vt = &mock_backend_vt;
|
||||
|
||||
return mk;
|
||||
}
|
||||
|
||||
static void mock_free(Mock *mk)
|
||||
{
|
||||
strbuf_free(mk->context);
|
||||
strbuf_free(mk->to_terminal);
|
||||
strbuf_free(mk->to_backend);
|
||||
conf_free(mk->conf);
|
||||
term_free(mk->term);
|
||||
sfree(mk->specials);
|
||||
sfree(mk);
|
||||
}
|
||||
|
||||
static void reset(Mock *mk)
|
||||
{
|
||||
strbuf_clear(mk->context);
|
||||
strbuf_clear(mk->to_terminal);
|
||||
strbuf_clear(mk->to_backend);
|
||||
mk->nspecials = 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);
|
||||
}
|
||||
|
||||
static void print_context(Mock *mk, const char *file, int line)
|
||||
{
|
||||
printf("%s:%d", file, line);
|
||||
if (mk->context->len)
|
||||
printf(" (%s)", mk->context->s);
|
||||
printf(": ");
|
||||
}
|
||||
|
||||
#define EXPECT(mk, what, ...) \
|
||||
expect_ ## what(mk, __FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
static void expect_backend(Mock *mk, const char *file, int line,
|
||||
ptrlen expected)
|
||||
{
|
||||
ptrlen actual = ptrlen_from_strbuf(mk->to_backend);
|
||||
if (!ptrlen_eq_ptrlen(expected, actual)) {
|
||||
print_context(mk, file, line);
|
||||
printf("expected backend output \"");
|
||||
write_c_string_literal(stdout, expected);
|
||||
printf("\", got \"");
|
||||
write_c_string_literal(stdout, actual);
|
||||
printf("\"\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void expect_terminal(Mock *mk, const char *file, int line,
|
||||
ptrlen expected)
|
||||
{
|
||||
ptrlen actual = ptrlen_from_strbuf(mk->to_terminal);
|
||||
if (!ptrlen_eq_ptrlen(expected, actual)) {
|
||||
print_context(mk, file, line);
|
||||
printf("expected terminal output \"");
|
||||
write_c_string_literal(stdout, expected);
|
||||
printf("\", got \"");
|
||||
write_c_string_literal(stdout, actual);
|
||||
printf("\"\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void expect_specials(Mock *mk, const char *file, int line,
|
||||
size_t nspecials, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
static const char *const special_names[] = {
|
||||
#define SPECIAL(x) #x,
|
||||
#include "specials.h"
|
||||
#undef SPECIAL
|
||||
};
|
||||
|
||||
bool match;
|
||||
if (nspecials != mk->nspecials) {
|
||||
match = false;
|
||||
} else {
|
||||
match = true;
|
||||
va_start(ap, nspecials);
|
||||
for (size_t i = 0; i < nspecials; i++) {
|
||||
SessionSpecialCode code = va_arg(ap, SessionSpecialCode);
|
||||
int arg = va_arg(ap, int);
|
||||
if (code != mk->specials[i].code || arg != mk->specials[i].arg)
|
||||
match = false;
|
||||
}
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
print_context(mk, file, line);
|
||||
printf("expected specials [");
|
||||
va_start(ap, nspecials);
|
||||
for (size_t i = 0; i < nspecials; i++) {
|
||||
SessionSpecialCode code = va_arg(ap, SessionSpecialCode);
|
||||
int arg = va_arg(ap, int);
|
||||
printf(" %s.%d", special_names[code], arg);
|
||||
}
|
||||
va_end(ap);
|
||||
printf(" ], got [");
|
||||
for (size_t i = 0; i < mk->nspecials; i++) {
|
||||
printf(" %s.%d", special_names[mk->specials[i].code],
|
||||
mk->specials[i].arg);
|
||||
}
|
||||
printf(" ]\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void test_noedit(Mock *mk)
|
||||
{
|
||||
mk->edit = false;
|
||||
mk->echo = false;
|
||||
|
||||
/*
|
||||
* In non-echo and non-edit mode, the channel is 8-bit clean
|
||||
*/
|
||||
for (unsigned c = 0; c < 256; c++) {
|
||||
char buf[1];
|
||||
|
||||
test_context(mk, "c=%02x", c);
|
||||
buf[0] = c;
|
||||
ldisc_send(mk->ldisc, buf, 1, false);
|
||||
EXPECT(mk, backend, make_ptrlen(buf, 1));
|
||||
reset(mk);
|
||||
}
|
||||
/* ... regardless of the 'interactive' flag */
|
||||
for (unsigned c = 0; c < 256; c++) {
|
||||
char buf[1];
|
||||
|
||||
test_context(mk, "c=%02x", c);
|
||||
buf[0] = c;
|
||||
ldisc_send(mk->ldisc, buf, 1, true);
|
||||
EXPECT(mk, backend, make_ptrlen(buf, 1));
|
||||
reset(mk);
|
||||
}
|
||||
/* ... and any nonzero character does the same thing even if sent
|
||||
* with the magic -1 length flag */
|
||||
for (unsigned c = 1; c < 256; c++) {
|
||||
char buf[2];
|
||||
|
||||
test_context(mk, "c=%02x", c);
|
||||
buf[0] = c;
|
||||
buf[1] = '\0';
|
||||
ldisc_send(mk->ldisc, buf, -1, true);
|
||||
EXPECT(mk, backend, make_ptrlen(buf, 1));
|
||||
reset(mk);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test the special-character cases for Telnet.
|
||||
*/
|
||||
conf_set_int(mk->conf, CONF_protocol, PROT_TELNET);
|
||||
conf_set_bool(mk->conf, CONF_telnet_newline, false);
|
||||
conf_set_bool(mk->conf, CONF_telnet_keyboard, false);
|
||||
ldisc_configure(mk->ldisc, mk->conf);
|
||||
|
||||
/* Without telnet_newline or telnet_keyboard, these all do the
|
||||
* normal thing */
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, true);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("\x0D"));
|
||||
reset(mk);
|
||||
ldisc_send(mk->ldisc, "\x08", -1, true);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("\x08"));
|
||||
reset(mk);
|
||||
ldisc_send(mk->ldisc, "\x7F", -1, true);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("\x7F"));
|
||||
reset(mk);
|
||||
ldisc_send(mk->ldisc, "\x03", -1, true);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("\x03"));
|
||||
reset(mk);
|
||||
ldisc_send(mk->ldisc, "\x1A", -1, true);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("\x1A"));
|
||||
reset(mk);
|
||||
|
||||
/* telnet_newline controls translation of CR into SS_EOL */
|
||||
conf_set_bool(mk->conf, CONF_telnet_newline, true);
|
||||
ldisc_configure(mk->ldisc, mk->conf);
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, true);
|
||||
EXPECT(mk, specials, 1, SS_EOL, 0);
|
||||
reset(mk);
|
||||
|
||||
/* And telnet_keyboard controls the others */
|
||||
conf_set_bool(mk->conf, CONF_telnet_newline, false);
|
||||
conf_set_bool(mk->conf, CONF_telnet_keyboard, true);
|
||||
ldisc_configure(mk->ldisc, mk->conf);
|
||||
ldisc_send(mk->ldisc, "\x08", -1, true);
|
||||
EXPECT(mk, specials, 1, SS_EC, 0);
|
||||
reset(mk);
|
||||
ldisc_send(mk->ldisc, "\x7F", -1, true);
|
||||
EXPECT(mk, specials, 1, SS_EC, 0);
|
||||
reset(mk);
|
||||
ldisc_send(mk->ldisc, "\x03", -1, true);
|
||||
EXPECT(mk, specials, 1, SS_IP, 0);
|
||||
reset(mk);
|
||||
ldisc_send(mk->ldisc, "\x1A", -1, true);
|
||||
EXPECT(mk, specials, 1, SS_SUSP, 0);
|
||||
reset(mk);
|
||||
|
||||
/*
|
||||
* In echo-but-no-edit mode, we also expect that every character
|
||||
* is echoed back to the display as a side effect, including when
|
||||
* it's sent as a special -1 keystroke.
|
||||
*
|
||||
* This state only comes up in Telnet, because that has protocol
|
||||
* options to independently configure echo and edit. Telnet is
|
||||
* also the most complicated of the protocols because of the above
|
||||
* special cases, so we stay in Telnet mode for this test.
|
||||
*/
|
||||
mk->echo = true;
|
||||
for (unsigned c = 0; c < 256; c++) {
|
||||
char buf[1];
|
||||
|
||||
test_context(mk, "c=%02x", c);
|
||||
buf[0] = c;
|
||||
ldisc_send(mk->ldisc, buf, 1, false);
|
||||
EXPECT(mk, terminal, make_ptrlen(buf, 1));
|
||||
reset(mk);
|
||||
}
|
||||
for (unsigned c = 1; c < 256; c++) {
|
||||
char buf[2];
|
||||
|
||||
test_context(mk, "c=%02x", c);
|
||||
buf[0] = c;
|
||||
buf[1] = '\0';
|
||||
ldisc_send(mk->ldisc, buf, -1, true);
|
||||
EXPECT(mk, terminal, make_ptrlen(buf, 1));
|
||||
reset(mk);
|
||||
}
|
||||
|
||||
do_defaults(NULL, mk->conf);
|
||||
ldisc_configure(mk->ldisc, mk->conf);
|
||||
}
|
||||
|
||||
static void test_edit(Mock *mk, bool echo)
|
||||
{
|
||||
static const char *const ctls = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_";
|
||||
|
||||
mk->edit = true;
|
||||
mk->echo = echo;
|
||||
|
||||
#define EXPECT_TERMINAL(mk, val) do { \
|
||||
if (!echo) EXPECT(mk, terminal, PTRLEN_LITERAL("")); \
|
||||
else EXPECT(mk, terminal, val); \
|
||||
} while (0)
|
||||
|
||||
/* ASCII printing characters all print when entered, but don't go
|
||||
* to the terminal until Return is pressed */
|
||||
for (unsigned c = 0x20; c < 0x7F; c++) {
|
||||
char buf[3];
|
||||
|
||||
test_context(mk, "c=%02x", c);
|
||||
buf[0] = c;
|
||||
ldisc_send(mk->ldisc, buf, 1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, make_ptrlen(buf, 1));
|
||||
ldisc_send(mk->ldisc, "\015", 1, false);
|
||||
buf[1] = '\015';
|
||||
buf[2] = '\012';
|
||||
EXPECT(mk, backend, make_ptrlen(buf, 3));
|
||||
EXPECT_TERMINAL(mk, make_ptrlen(buf, 3));
|
||||
reset(mk);
|
||||
}
|
||||
|
||||
/* C0 control characters mostly show up as ^X or similar */
|
||||
for (unsigned c = 0; c < 0x1F; c++) {
|
||||
char backbuf[3];
|
||||
char termbuf[4];
|
||||
|
||||
switch (ctls[c]) {
|
||||
case 'D': continue; /* ^D sends EOF */
|
||||
case 'M': continue; /* ^M is Return */
|
||||
case 'R': continue; /* ^R redisplays line */
|
||||
case 'U': continue; /* ^U deletes the line */
|
||||
case 'V': continue; /* ^V makes the next char literal */
|
||||
case 'W': continue; /* ^W deletes a word */
|
||||
/*
|
||||
* ^H / ^? are not included here. Those are treated
|
||||
* literally if sent as plain input bytes. Only sending
|
||||
* them as special via length==-1 causes them to act as
|
||||
* backspace, which I think was simply because there _is_
|
||||
* a dedicated key that can do that function, so there's
|
||||
* no need to also eat the Ctrl+thing combo.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Also, ^C, ^Z and ^\ self-insert (when not in Telnet
|
||||
* mode) but have the side effect of erasing the line
|
||||
* buffer so far. In this loop, that doesn't show up,
|
||||
* because the line buffer is empty already. However, I
|
||||
* don't test that, because it's silly, and probably
|
||||
* doesn't want to keep happening!
|
||||
*/
|
||||
}
|
||||
|
||||
test_context(mk, "c=%02x", c);
|
||||
backbuf[0] = c;
|
||||
ldisc_send(mk->ldisc, backbuf, 1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
termbuf[0] = '^';
|
||||
termbuf[1] = ctls[c];
|
||||
EXPECT_TERMINAL(mk, make_ptrlen(termbuf, 2));
|
||||
ldisc_send(mk->ldisc, "\015", 1, false);
|
||||
backbuf[1] = '\015';
|
||||
backbuf[2] = '\012';
|
||||
EXPECT(mk, backend, make_ptrlen(backbuf, 3));
|
||||
termbuf[2] = '\015';
|
||||
termbuf[3] = '\012';
|
||||
EXPECT_TERMINAL(mk, make_ptrlen(termbuf, 4));
|
||||
reset(mk);
|
||||
}
|
||||
|
||||
/* Prefixed with ^V, the same is true of _all_ C0 controls */
|
||||
for (unsigned c = 0; c < 0x1F; c++) {
|
||||
char backbuf[3];
|
||||
char termbuf[4];
|
||||
|
||||
test_context(mk, "c=%02x", c);
|
||||
backbuf[0] = 'V' & 0x1F;
|
||||
ldisc_send(mk->ldisc, backbuf, 1, false);
|
||||
backbuf[0] = c;
|
||||
ldisc_send(mk->ldisc, backbuf, 1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
termbuf[0] = '^';
|
||||
termbuf[1] = ctls[c];
|
||||
EXPECT_TERMINAL(mk, make_ptrlen(termbuf, 2));
|
||||
ldisc_send(mk->ldisc, "\015", 1, false);
|
||||
backbuf[1] = '\015';
|
||||
backbuf[2] = '\012';
|
||||
EXPECT(mk, backend, make_ptrlen(backbuf, 3));
|
||||
termbuf[2] = '\015';
|
||||
termbuf[3] = '\012';
|
||||
EXPECT_TERMINAL(mk, make_ptrlen(termbuf, 4));
|
||||
reset(mk);
|
||||
}
|
||||
|
||||
/* Deleting an ASCII character sends a single BSB and deletes just
|
||||
* that byte from the buffer */
|
||||
ldisc_send(mk->ldisc, "ab", 2, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("ab"));
|
||||
ldisc_send(mk->ldisc, "\x08", -1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("ab\x08 \x08"));
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("a\x0D\x0A"));
|
||||
reset(mk);
|
||||
|
||||
/* Deleting a character written as a ^X code sends two BSBs to
|
||||
* wipe out the two-character display sequence */
|
||||
ldisc_send(mk->ldisc, "a\x02", 2, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^B"));
|
||||
ldisc_send(mk->ldisc, "\x7F", -1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^B\x08 \x08\x08 \x08"));
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("a\x0D\x0A"));
|
||||
reset(mk);
|
||||
|
||||
/* ^D sends the line editing buffer without a trailing Return, if
|
||||
* it's non-empty */
|
||||
ldisc_send(mk->ldisc, "abc\x04", 4, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("abc"));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc"));
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("abc\x0D\x0A"));
|
||||
reset(mk);
|
||||
|
||||
/* But if the buffer is empty, ^D sends SS_EOF */
|
||||
ldisc_send(mk->ldisc, "\x04", 1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL(""));
|
||||
EXPECT(mk, specials, 1, SS_EOF, 0);
|
||||
reset(mk);
|
||||
|
||||
/* ^R redraws the current line, after printing "^R" at the end of
|
||||
* the previous attempt to make it clear that that's what
|
||||
* happened */
|
||||
ldisc_send(mk->ldisc, "a\x01", 2, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^A"));
|
||||
ldisc_send(mk->ldisc, "\x12", 1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^A^R\x0D\x0A" "a^A"));
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
reset(mk);
|
||||
|
||||
/* ^U deletes the whole line */
|
||||
ldisc_send(mk->ldisc, "a b c", 5, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a b c"));
|
||||
ldisc_send(mk->ldisc, "\x15", 1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(
|
||||
mk, PTRLEN_LITERAL(
|
||||
"a b c\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08"));
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("\x0D\x0A"));
|
||||
EXPECT_TERMINAL(
|
||||
mk, PTRLEN_LITERAL(
|
||||
"a b c\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x0D\x0A"));
|
||||
reset(mk);
|
||||
/* And it still knows that a control character written as ^X takes
|
||||
* two BSBs to delete */
|
||||
ldisc_send(mk->ldisc, "a\x02" "c", 3, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^Bc"));
|
||||
ldisc_send(mk->ldisc, "\x15", 1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(
|
||||
mk, PTRLEN_LITERAL("a^Bc\x08 \x08\x08 \x08\x08 \x08\x08 \x08"));
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
reset(mk);
|
||||
|
||||
/* ^W deletes a word, which means that it deletes to the most
|
||||
* recent boundary with a space on the left and a nonspace on the
|
||||
* right. (Or the beginning of the string, whichever comes first.) */
|
||||
ldisc_send(mk->ldisc, "hello, world\x17", 13, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(
|
||||
mk, PTRLEN_LITERAL(
|
||||
"hello, world\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08"));
|
||||
ldisc_send(mk->ldisc, "\x0D", 1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("hello, \x0D\x0A"));
|
||||
reset(mk);
|
||||
ldisc_send(mk->ldisc, "hello, world \x17", 14, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(
|
||||
mk, PTRLEN_LITERAL(
|
||||
"hello, world "
|
||||
"\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08"));
|
||||
ldisc_send(mk->ldisc, "\x0D", 1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("hello, \x0D\x0A"));
|
||||
reset(mk);
|
||||
ldisc_send(mk->ldisc, " hello \x17", 8, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(
|
||||
mk, PTRLEN_LITERAL(
|
||||
" hello \x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08"));
|
||||
ldisc_send(mk->ldisc, "\x0D", 1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(" \x0D\x0A"));
|
||||
reset(mk);
|
||||
ldisc_send(mk->ldisc, "hello \x17", 7, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(
|
||||
mk, PTRLEN_LITERAL(
|
||||
"hello \x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08"));
|
||||
ldisc_send(mk->ldisc, "\x0D", 1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("\x0D\x0A"));
|
||||
reset(mk);
|
||||
/* And this too knows that a control character written as ^X takes
|
||||
* two BSBs to delete */
|
||||
ldisc_send(mk->ldisc, "a\x02" "c", 3, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^Bc"));
|
||||
ldisc_send(mk->ldisc, "\x17", 1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(
|
||||
mk, PTRLEN_LITERAL("a^Bc\x08 \x08\x08 \x08\x08 \x08\x08 \x08"));
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
reset(mk);
|
||||
|
||||
/* Test handling of ^C and friends in non-telnet_keyboard mode */
|
||||
ldisc_send(mk->ldisc, "abc\x03", 4, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08^C"));
|
||||
EXPECT(mk, specials, 1, SS_EL, 0);
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
reset(mk);
|
||||
ldisc_send(mk->ldisc, "abc\x1a", 4, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08^Z"));
|
||||
EXPECT(mk, specials, 1, SS_EL, 0);
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
reset(mk);
|
||||
ldisc_send(mk->ldisc, "abc\x1c", 4, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08^\\"));
|
||||
EXPECT(mk, specials, 1, SS_EL, 0);
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
reset(mk);
|
||||
|
||||
/* And in telnet_keyboard mode */
|
||||
conf_set_bool(mk->conf, CONF_telnet_keyboard, true);
|
||||
ldisc_configure(mk->ldisc, mk->conf);
|
||||
|
||||
/* FIXME: should we _really_ be sending EL before each of these? */
|
||||
ldisc_send(mk->ldisc, "abc\x03", 4, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08"));
|
||||
EXPECT(mk, specials, 2, SS_EL, 0, SS_IP, 0);
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
reset(mk);
|
||||
ldisc_send(mk->ldisc, "abc\x1a", 4, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08"));
|
||||
EXPECT(mk, specials, 2, SS_EL, 0, SS_SUSP, 0);
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
reset(mk);
|
||||
ldisc_send(mk->ldisc, "abc\x1c", 4, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08"));
|
||||
EXPECT(mk, specials, 2, SS_EL, 0, SS_ABORT, 0);
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
reset(mk);
|
||||
|
||||
conf_set_bool(mk->conf, CONF_telnet_keyboard, false);
|
||||
ldisc_configure(mk->ldisc, mk->conf);
|
||||
|
||||
/* Test UTF-8 characters of various lengths and ensure deleting one
|
||||
* deletes the whole character from the buffer (by pressing Return and
|
||||
* seeing what gets sent) but only sends one BSB */
|
||||
mk->term->utf = true;
|
||||
|
||||
ldisc_send(mk->ldisc, "\xC2\xA0\xC2\xA1", 4, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xC2\xA0\xC2\xA1"));
|
||||
ldisc_send(mk->ldisc, "\x08", -1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xC2\xA0\xC2\xA1\x08 \x08"));
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("\xC2\xA0\x0D\x0A"));
|
||||
reset(mk);
|
||||
|
||||
ldisc_send(mk->ldisc, "\xE2\xA0\x80\xE2\xA0\x81", 6, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xE2\xA0\x80\xE2\xA0\x81"));
|
||||
ldisc_send(mk->ldisc, "\x08", -1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xE2\xA0\x80\xE2\xA0\x81\x08 \x08"));
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("\xE2\xA0\x80\x0D\x0A"));
|
||||
reset(mk);
|
||||
|
||||
ldisc_send(mk->ldisc, "\xF0\x90\x80\x80\xF0\x90\x80\x81", 8, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xF0\x90\x80\x80\xF0\x90\x80\x81"));
|
||||
ldisc_send(mk->ldisc, "\x08", -1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xF0\x90\x80\x80\xF0\x90\x80\x81"
|
||||
"\x08 \x08"));
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("\xF0\x90\x80\x80\x0D\x0A"));
|
||||
reset(mk);
|
||||
|
||||
/* And reset back to non-UTF-8 mode and expect high-bit-set bytes
|
||||
* to be treated individually, as characters in a single-byte
|
||||
* charset. (In our case, given the test config, that will be
|
||||
* CP437, but it makes no difference to the editing behaviour.) */
|
||||
mk->term->utf = false;
|
||||
ldisc_send(mk->ldisc, "\xC2\xA0\xC2\xA1", 4, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xC2\xA0\xC2\xA1"));
|
||||
ldisc_send(mk->ldisc, "\x08", -1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL(""));
|
||||
EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xC2\xA0\xC2\xA1\x08 \x08"));
|
||||
ldisc_send(mk->ldisc, "\x0D", -1, false);
|
||||
EXPECT(mk, backend, PTRLEN_LITERAL("\xC2\xA0\xC2\x0D\x0A"));
|
||||
reset(mk);
|
||||
|
||||
/* Make sure we flush all the terminal contents at the end of this
|
||||
* function */
|
||||
ldisc_send(mk->ldisc, "\x0D", 1, false);
|
||||
reset(mk);
|
||||
|
||||
#undef EXPECT_TERMINAL
|
||||
|
||||
}
|
||||
|
||||
const struct BackendVtable *const backends[] = { &mock_backend_vt, NULL };
|
||||
|
||||
int main(void)
|
||||
{
|
||||
Mock *mk = mock_new();
|
||||
mk->term = term_init(mk->conf, mk->ucsdata, &mk->tw);
|
||||
Ldisc *ldisc = ldisc_create(mk->conf, mk->term, &mk->backend, &mk->seat);
|
||||
term_size(mk->term, 80, 24, 0);
|
||||
|
||||
test_noedit(mk);
|
||||
test_edit(mk, true);
|
||||
test_edit(mk, false);
|
||||
|
||||
ldisc_free(ldisc);
|
||||
mock_free(mk);
|
||||
return 0;
|
||||
}
|
@ -226,3 +226,13 @@ target_link_libraries(pageant
|
||||
eventloop console agent settings network crypto utils
|
||||
${pageant_libs})
|
||||
installed_program(pageant)
|
||||
|
||||
add_executable(test_lineedit
|
||||
${CMAKE_SOURCE_DIR}/test/test_lineedit.c
|
||||
${CMAKE_SOURCE_DIR}/stubs/no-gss.c
|
||||
${CMAKE_SOURCE_DIR}/stubs/no-logging.c
|
||||
${CMAKE_SOURCE_DIR}/stubs/no-printing.c
|
||||
${CMAKE_SOURCE_DIR}/stubs/no-storage.c
|
||||
${CMAKE_SOURCE_DIR}/stubs/no-timing.c)
|
||||
target_link_libraries(test_lineedit
|
||||
guiterminal settings eventloop charset utils ${platform_libraries})
|
||||
|
@ -332,6 +332,7 @@ void gtk_setup_config_box(
|
||||
*/
|
||||
#define DEFAULT_CODEPAGE 0xFFFF
|
||||
#define CP_UTF8 CS_UTF8 /* from libcharset */
|
||||
#define CP_437 CS_CP437 /* used for test suites */
|
||||
|
||||
#define strnicmp strncasecmp
|
||||
#define stricmp strcasecmp
|
||||
|
@ -188,3 +188,14 @@ target_link_libraries(test_split_into_argv utils ${platform_libraries})
|
||||
add_executable(test_screenshot
|
||||
test_screenshot.c)
|
||||
target_link_libraries(test_screenshot utils ${platform_libraries})
|
||||
|
||||
add_executable(test_lineedit
|
||||
${CMAKE_SOURCE_DIR}/test/test_lineedit.c
|
||||
${CMAKE_SOURCE_DIR}/stubs/no-gss.c
|
||||
${CMAKE_SOURCE_DIR}/stubs/no-logging.c
|
||||
${CMAKE_SOURCE_DIR}/stubs/no-printing.c
|
||||
${CMAKE_SOURCE_DIR}/stubs/no-storage.c
|
||||
${CMAKE_SOURCE_DIR}/stubs/no-timing.c
|
||||
no-jump-list.c)
|
||||
target_link_libraries(test_lineedit
|
||||
guiterminal settings eventloop utils ${platform_libraries})
|
||||
|
@ -195,6 +195,7 @@ void centre_window(HWND hwnd);
|
||||
#define DEFAULT_CODEPAGE CP_ACP
|
||||
#define USES_VTLINE_HACK
|
||||
#define CP_UTF8 65001
|
||||
#define CP_437 437 /* used for test suites */
|
||||
|
||||
#ifndef NO_GSSAPI
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user