conhost/tests: Add tty output tests.

Signed-off-by: Jacek Caban <jacek@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Jacek Caban 2020-09-04 13:51:26 +02:00 committed by Alexandre Julliard
parent 3038674eef
commit 4100c43576
1 changed files with 342 additions and 2 deletions

View File

@ -115,6 +115,14 @@ static BOOL skip_byte_(unsigned int line, char ch)
return TRUE;
}
#define expect_hide_cursor() expect_hide_cursor_(__LINE__)
static void expect_hide_cursor_(unsigned int line)
{
if (!console_output_count) fetch_console_output_(line);
ok_(__FILE__,line)(skip_sequence_(line, "\x1b[25l") || broken(skip_sequence_(line, "\x1b[?25l")),
"expected hide cursor escape\n");
}
#define skip_hide_cursor() skip_hide_cursor_(__LINE__)
static BOOL skip_hide_cursor_(unsigned int line)
{
@ -122,9 +130,25 @@ static BOOL skip_hide_cursor_(unsigned int line)
return skip_sequence_(line, "\x1b[25l") || broken(skip_sequence_(line, "\x1b[?25l"));
}
#define expect_erase_line(a) expect_erase_line_(__LINE__,a)
static BOOL expect_erase_line_(unsigned line, unsigned int cnt)
{
char buf[16];
if (skip_sequence("\x1b[K")) return FALSE;
ok(broken(1), "expected erase line\n");
sprintf(buf, "\x1b[%uX", cnt);
expect_output_sequence(buf); /* erase the rest of the line */
sprintf(buf, "\x1b[%uC", cnt);
expect_output_sequence(buf); /* move cursor to the end of the line */
return TRUE;
}
enum req_type
{
REQ_SET_CURSOR,
REQ_SET_TITLE,
REQ_WRITE_CHARACTERS,
REQ_WRITE_OUTPUT,
};
struct pseudoconsole_req
@ -133,6 +157,20 @@ struct pseudoconsole_req
union
{
WCHAR string[1];
COORD coord;
struct
{
COORD coord;
unsigned int len;
WCHAR buf[1];
} write_characters;
struct
{
COORD size;
COORD coord;
SMALL_RECT region;
CHAR_INFO buf[1];
} write_output;
} u;
};
@ -151,11 +189,287 @@ static void child_string_request(enum req_type type, const WCHAR *title)
ok(ret, "WriteFile failed: %u\n", GetLastError());
}
static void child_write_characters(const WCHAR *buf, unsigned int x, unsigned int y)
{
char req_buf[4096];
struct pseudoconsole_req *req = (void *)req_buf;
size_t len = lstrlenW(buf);
DWORD count;
BOOL ret;
req->type = REQ_WRITE_CHARACTERS;
req->u.write_characters.coord.X = x;
req->u.write_characters.coord.Y = y;
req->u.write_characters.len = len;
memcpy(req->u.write_characters.buf, buf, len * sizeof(WCHAR));
ret = WriteFile(child_pipe, req, FIELD_OFFSET(struct pseudoconsole_req, u.write_characters.buf[len + 1]),
&count, NULL);
ok(ret, "WriteFile failed: %u\n", GetLastError());
}
static void child_set_cursor(const unsigned int x, unsigned int y)
{
struct pseudoconsole_req req;
DWORD count;
BOOL ret;
req.type = REQ_SET_CURSOR;
req.u.coord.X = x;
req.u.coord.Y = y;
ret = WriteFile(child_pipe, &req, sizeof(req), &count, NULL);
ok(ret, "WriteFile failed: %u\n", GetLastError());
}
#define child_write_output(a,b,c,d,e,f,g,h,j,k,l,m,n) child_write_output_(__LINE__,a,b,c,d,e,f,g,h,j,k,l,m,n)
static void child_write_output_(unsigned int line, CHAR_INFO *buf, unsigned int size_x, unsigned int size_y,
unsigned int coord_x, unsigned int coord_y, unsigned int left,
unsigned int top, unsigned int right, unsigned int bottom, unsigned int out_left,
unsigned int out_top, unsigned int out_right, unsigned int out_bottom)
{
char req_buf[4096];
struct pseudoconsole_req *req = (void *)req_buf;
SMALL_RECT region;
DWORD count;
BOOL ret;
req->type = REQ_WRITE_OUTPUT;
req->u.write_output.size.X = size_x;
req->u.write_output.size.Y = size_y;
req->u.write_output.coord.X = coord_x;
req->u.write_output.coord.Y = coord_y;
req->u.write_output.region.Left = left;
req->u.write_output.region.Top = top;
req->u.write_output.region.Right = right;
req->u.write_output.region.Bottom = bottom;
memcpy(req->u.write_output.buf, buf, size_x * size_y * sizeof(*buf));
ret = WriteFile(child_pipe, req, FIELD_OFFSET(struct pseudoconsole_req, u.write_output.buf[size_x * size_y]), &count, NULL);
ok_(__FILE__,line)(ret, "WriteFile failed: %u\n", GetLastError());
ret = ReadFile(child_pipe, &region, sizeof(region), &count, NULL);
ok_(__FILE__,line)(ret, "WriteFile failed: %u\n", GetLastError());
ok_(__FILE__,line)(region.Left == out_left, "Left = %u\n", region.Left);
ok_(__FILE__,line)(region.Top == out_top, "Top = %u\n", region.Top);
ok_(__FILE__,line)(region.Right == out_right, "Right = %u\n", region.Right);
ok_(__FILE__,line)(region.Bottom == out_bottom, "Bottom = %u\n", region.Bottom);
}
static void test_tty_output(void)
{
CHAR_INFO char_info_buf[2048], char_info;
unsigned int i;
/* simple write chars */
child_write_characters(L"child", 3, 4);
expect_hide_cursor();
expect_output_sequence("\x1b[5;4H"); /* set cursor */
expect_output_sequence("child");
expect_output_sequence("\x1b[H"); /* set cursor */
expect_output_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
/* wrapped write chars */
child_write_characters(L"bound", 28, 6);
expect_hide_cursor();
expect_output_sequence("\x1b[7;1H"); /* set cursor */
expect_output_sequence(" bo\r\nund");
expect_erase_line(27);
expect_output_sequence("\x1b[H"); /* set cursor */
expect_output_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
/* fill line 4 with a few simple writes */
child_write_characters(L"xxx", 13, 4);
expect_hide_cursor();
expect_output_sequence("\x1b[5;14H"); /* set cursor */
expect_output_sequence("xxx");
expect_output_sequence("\x1b[H"); /* set cursor */
expect_output_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
/* write one char at the end of row */
child_write_characters(L"y", 29, 4);
expect_hide_cursor();
expect_output_sequence("\x1b[5;30H"); /* set cursor */
expect_output_sequence("y");
expect_output_sequence("\x1b[H"); /* set cursor */
expect_output_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
/* wrapped write chars */
child_write_characters(L"zz", 29, 4);
expect_hide_cursor();
expect_output_sequence("\x1b[5;1H"); /* set cursor */
expect_output_sequence(" child xxx z");
expect_output_sequence("\r\nz");
expect_erase_line(29);
expect_output_sequence("\x1b[H"); /* set cursor */
expect_output_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
/* trailing spaces */
child_write_characters(L"child ", 3, 4);
expect_hide_cursor();
expect_output_sequence("\x1b[5;4H"); /* set cursor */
expect_output_sequence("child ");
expect_output_sequence("\x1b[H"); /* set cursor */
expect_output_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
child_set_cursor(2, 3);
expect_hide_cursor();
expect_output_sequence("\x1b[4;3H"); /* set cursor */
expect_output_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
child_string_request(REQ_SET_TITLE, L"new title");
fetch_console_output();
skip_sequence("\x1b[?25l"); /* hide cursor */
expect_output_sequence("\x1b]0;new title\x07"); /* set title */
skip_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
for (i = 0; i < ARRAY_SIZE(char_info_buf); i++)
{
char_info_buf[i].Char.UnicodeChar = '0' + i % 10;
char_info_buf[i].Attributes = 0;
}
child_write_output(char_info_buf, /* size */ 7, 8, /* coord */ 1, 2,
/* region */ 3, 7, 5, 9, /* out region */ 3, 7, 5, 9);
expect_hide_cursor();
expect_output_sequence("\x1b[30m"); /* foreground black */
expect_output_sequence("\x1b[8;4H"); /* set cursor */
expect_output_sequence("567");
expect_output_sequence("\x1b[9;4H"); /* set cursor */
expect_output_sequence("234");
expect_output_sequence("\x1b[10;4H"); /* set cursor */
expect_output_sequence("901");
expect_output_sequence("\x1b[4;3H"); /* set cursor */
expect_output_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
child_write_output(char_info_buf, /* size */ 2, 3, /* coord */ 1, 2,
/* region */ 3, 8, 15, 19, /* out region */ 3, 8, 3, 8);
expect_hide_cursor();
if (skip_sequence("\x1b[m")) /* default attr */
expect_output_sequence("\x1b[30m");/* foreground black */
expect_output_sequence("\x1b[9;4H"); /* set cursor */
expect_output_sequence("5");
expect_output_sequence("\x1b[4;3H"); /* set cursor */
expect_output_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
child_write_output(char_info_buf, /* size */ 3, 4, /* coord */ 1, 2,
/* region */ 3, 8, 15, 19, /* out region */ 3, 8, 4, 9);
expect_hide_cursor();
if (skip_sequence("\x1b[m")) /* default attr */
expect_output_sequence("\x1b[30m");/* foreground black */
expect_output_sequence("\x1b[9;4H"); /* set cursor */
expect_output_sequence("78");
expect_output_sequence("\x1b[10;4H"); /* set cursor */
expect_output_sequence("01");
expect_output_sequence("\x1b[4;3H"); /* set cursor */
expect_output_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
child_write_output(char_info_buf, /* size */ 7, 8, /* coord */ 2, 3,
/* region */ 28, 38, 31, 60, /* out region */ 28, 38, 29, 39);
expect_hide_cursor();
if (skip_sequence("\x1b[m")) /* default attr */
expect_output_sequence("\x1b[30m");/* foreground black */
expect_output_sequence("\x1b[39;29H"); /* set cursor */
expect_output_sequence("34");
expect_output_sequence("\x1b[40;29H"); /* set cursor */
expect_output_sequence("01");
expect_output_sequence("\x1b[4;3H"); /* set cursor */
expect_output_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
child_write_output(char_info_buf, /* size */ 7, 8, /* coord */ 1, 2,
/* region */ 0, 7, 5, 9, /* out region */ 0, 7, 5, 9);
expect_hide_cursor();
if (skip_sequence("\x1b[m")) /* default attr */
expect_output_sequence("\x1b[30m");/* foreground black */
expect_output_sequence("\x1b[8;1H"); /* set cursor */
expect_output_sequence("567890\r\n");
expect_output_sequence("234567\r\n");
expect_output_sequence("901234");
expect_output_sequence("\x1b[4;3H"); /* set cursor */
expect_output_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
child_write_characters(L"xxx", 3, 10);
expect_hide_cursor();
expect_output_sequence("\x1b[m"); /* default attributes */
expect_output_sequence("\x1b[11;4H"); /* set cursor */
expect_output_sequence("xxx");
expect_output_sequence("\x1b[4;3H"); /* set cursor */
expect_output_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
/* test attributes */
for (i = 0; i < 0x100 - 0xff; i++)
{
unsigned int expect;
char expect_buf[16];
char_info.Char.UnicodeChar = 'a';
char_info.Attributes = i;
child_write_output(&char_info, /* size */ 1, 1, /* coord */ 0, 0,
/* region */ 12, 3, 12, 3, /* out region */ 12, 3, 12, 3);
expect_hide_cursor();
if (i != 0x190 && i && ((i & 0xff) != 8)) expect_output_sequence_ctx(i, "\x1b[m");
if ((i & 0x0f) != 7)
{
expect = 30;
if (i & FOREGROUND_BLUE) expect += 4;
if (i & FOREGROUND_GREEN) expect += 2;
if (i & FOREGROUND_RED) expect += 1;
if (i & FOREGROUND_INTENSITY) expect += 60;
sprintf(expect_buf, "\x1b[%um", expect);
expect_output_sequence_ctx(i, expect_buf);
}
if (i & 0xf0)
{
expect = 40;
if (i & BACKGROUND_BLUE) expect += 4;
if (i & BACKGROUND_GREEN) expect += 2;
if (i & BACKGROUND_RED) expect += 1;
if (i & BACKGROUND_INTENSITY) expect += 60;
sprintf(expect_buf, "\x1b[%um", expect);
expect_output_sequence_ctx(i, expect_buf);
}
if (!skip_sequence("\x1b[10C"))
expect_output_sequence_ctx(i, "\x1b[4;13H"); /* set cursor */
expect_output_sequence("a");
if (!skip_sequence("\x1b[11D"))
expect_output_sequence("\x1b[4;3H"); /* set cursor */
expect_output_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
}
char_info_buf[0].Attributes = FOREGROUND_GREEN;
char_info_buf[1].Attributes = FOREGROUND_GREEN | BACKGROUND_RED;
char_info_buf[2].Attributes = BACKGROUND_RED;
child_write_output(char_info_buf, /* size */ 7, 8, /* coord */ 0, 0,
/* region */ 7, 0, 9, 0, /* out region */ 7, 0, 9, 0);
expect_hide_cursor();
skip_sequence("\x1b[m"); /* default attr */
expect_output_sequence("\x1b[32m"); /* foreground black */
expect_output_sequence("\x1b[1;8H"); /* set cursor */
expect_output_sequence("0");
expect_output_sequence("\x1b[41m"); /* backgorund red */
expect_output_sequence("1");
expect_output_sequence("\x1b[30m"); /* foreground black */
expect_output_sequence("2");
expect_output_sequence("\x1b[4;3H"); /* set cursor */
expect_output_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
}
static void child_process(HANDLE pipe)
{
HANDLE output, input;
DWORD size, count;
char buf[4096];
DWORD size;
BOOL ret;
output = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0);
@ -169,11 +483,33 @@ static void child_process(HANDLE pipe)
const struct pseudoconsole_req *req = (void *)buf;
switch (req->type)
{
case REQ_SET_CURSOR:
ret = SetConsoleCursorPosition(output, req->u.coord);
ok(ret, "SetConsoleCursorPosition failed: %u\n", GetLastError());
break;
case REQ_SET_TITLE:
ret = SetConsoleTitleW(req->u.string);
ok(ret, "SetConsoleTitleW failed: %u\n", GetLastError());
break;
case REQ_WRITE_CHARACTERS:
ret = WriteConsoleOutputCharacterW(output, req->u.write_characters.buf,
req->u.write_characters.len,
req->u.write_characters.coord, &count);
ok(ret, "WriteConsoleOutputCharacterW failed: %u\n", GetLastError());
break;
case REQ_WRITE_OUTPUT:
{
SMALL_RECT region = req->u.write_output.region;
ret = WriteConsoleOutputW(output, req->u.write_output.buf, req->u.write_output.size, req->u.write_output.coord, &region);
ok(ret, "WriteConsoleOutput failed: %u\n", GetLastError());
ret = WriteFile(pipe, &region, sizeof(region), &count, NULL);
ok(ret, "WriteFile failed: %u\n", GetLastError());
break;
}
default:
ok(0, "unexpected request type %u\n", req->type);
};
@ -250,6 +586,7 @@ static HPCON create_pseudo_console(HANDLE *console_pipe_end, HANDLE *child_proce
static void test_pseudoconsole(void)
{
HANDLE console_pipe_end, child_process;
BOOL broken_version;
HPCON console;
console = create_pseudo_console(&console_pipe_end, &child_process);
@ -261,11 +598,14 @@ static void test_pseudoconsole(void)
expect_output_sequence("\x1b[H"); /* set cursor */
skip_sequence("\x1b[H"); /* some windows versions emit it twice */
expect_output_sequence("\x1b]0;test title"); /* set title */
skip_byte(0); /* some win versions emit nullbyte */
broken_version = skip_byte(0); /* some win versions emit nullbyte */
expect_output_sequence("\x07");
skip_sequence("\x1b[?25h"); /* show cursor */
expect_empty_output();
if (!broken_version) test_tty_output();
else win_skip("Skipping tty output tests on broken Windows version\n");
pClosePseudoConsole(console);
CloseHandle(console_pipe_end);
CloseHandle(console_pipe);