[PATCH v3 3/4] programs/conhost/tests: add tests for ReadConsoleW with control

Eric Pouech eric.pouech at gmail.com
Tue Mar 1 02:26:08 CST 2022


Signed-off-by: Eric Pouech <eric.pouech at gmail.com>

---
 programs/conhost/tests/tty.c |  179 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 175 insertions(+), 4 deletions(-)

diff --git a/programs/conhost/tests/tty.c b/programs/conhost/tests/tty.c
index 1857cdd4aad..849986a1c29 100644
--- a/programs/conhost/tests/tty.c
+++ b/programs/conhost/tests/tty.c
@@ -153,6 +153,7 @@ enum req_type
     REQ_READ_CONSOLE,
     REQ_READ_CONSOLE_A,
     REQ_READ_CONSOLE_FILE,
+    REQ_READ_CONSOLE_CONTROL,
     REQ_SCROLL,
     REQ_SET_ACTIVE,
     REQ_SET_CURSOR,
@@ -201,6 +202,12 @@ struct pseudoconsole_req
             DWORD count;
             COORD coord;
         } fill;
+        struct
+        {
+            size_t size;
+            DWORD mask;
+            WCHAR initial[1];
+        } control;
     } u;
 };
 
@@ -415,6 +422,21 @@ static void child_read_console_file(HANDLE pipe, size_t size)
     ok(ret, "WriteFile failed: %lu\n", GetLastError());
 }
 
+static void child_read_console_control(HANDLE pipe, size_t size, DWORD control, const WCHAR* recall)
+{
+    char tmp[4096];
+    struct pseudoconsole_req *req = (void *)tmp;
+    DWORD count;
+    BOOL ret;
+
+    req->type = REQ_READ_CONSOLE_CONTROL;
+    req->u.control.size = size;
+    req->u.control.mask = control;
+    wcscpy(req->u.control.initial, recall);
+    ret = WriteFile(pipe, req, sizeof(*req) + wcslen(recall) * sizeof(WCHAR), &count, NULL);
+    ok(ret, "WriteFile failed: %lu\n", GetLastError());
+}
+
 #define child_expect_read_result(a,b) child_expect_read_result_(__LINE__,a,b)
 static void child_expect_read_result_(unsigned int line, HANDLE pipe, const WCHAR *expect)
 {
@@ -431,6 +453,25 @@ static void child_expect_read_result_(unsigned int line, HANDLE pipe, const WCHA
     ok_(__FILE__,line)(!memcmp(expect, buf, count), "unexpected data %s\n", wine_dbgstr_w(buf));
 }
 
+#define child_expect_read_control_result(a,b,c) child_expect_read_control_result_(__LINE__,a,b,c)
+static void child_expect_read_control_result_(unsigned int line, HANDLE pipe, const WCHAR *expect, DWORD state)
+{
+    size_t exlen = wcslen(expect);
+    WCHAR buf[4096];
+    WCHAR *ptr = (void *)((char *)buf + sizeof(DWORD));
+    DWORD count;
+    BOOL ret;
+
+    ret = ReadFile(pipe, buf, sizeof(buf), &count, NULL);
+    ok_(__FILE__,line)(ret, "ReadFile failed: %lu\n", GetLastError());
+    ok_(__FILE__,line)(count == sizeof(DWORD) + exlen * sizeof(WCHAR), "got %lu, expected %Iu\n",
+                       count, sizeof(DWORD) + exlen * sizeof(WCHAR));
+    buf[count / sizeof(WCHAR)] = 0;
+    todo_wine_if(*(DWORD *)buf != state && *(DWORD *)buf == 0)
+    ok_(__FILE__,line)(*(DWORD *)buf == state, "keyboard state: got %lx, expected %lx\n", *(DWORD *)buf, state);
+    ok_(__FILE__,line)(!memcmp(expect, ptr, count - sizeof(DWORD)), "unexpected data %s %s\n", wine_dbgstr_w(ptr), wine_dbgstr_w(expect));
+}
+
 #define child_expect_read_result_a(a,b) child_expect_read_result_a_(__LINE__,a,b)
 static void child_expect_read_result_a_(unsigned int line, HANDLE pipe, const char *expect)
 {
@@ -552,19 +593,25 @@ static void expect_char_key_(unsigned int line, WCHAR ch)
     expect_key_pressed_(line, ch, ch, vk, ctrl);
 }
 
-#define test_cursor_pos(a,b) _test_cursor_pos(__LINE__,a,b)
-static void _test_cursor_pos(unsigned line, int expect_x, int expect_y)
+static void fetch_child_sb_info(CONSOLE_SCREEN_BUFFER_INFO *info)
 {
     struct pseudoconsole_req req = { REQ_GET_SB_INFO };
-    CONSOLE_SCREEN_BUFFER_INFO info;
     DWORD read;
     BOOL ret;
 
     ret = WriteFile(child_pipe, &req, sizeof(req), &read, NULL);
     ok(ret, "WriteFile failed: %lu\n", GetLastError());
 
-    ret = ReadFile(child_pipe, &info, sizeof(info), &read, NULL);
+    ret = ReadFile(child_pipe, info, sizeof(*info), &read, NULL);
     ok(ret, "ReadFile failed: %lu\n", GetLastError());
+}
+
+#define test_cursor_pos(a,b) _test_cursor_pos(__LINE__,a,b)
+static void _test_cursor_pos(unsigned line, int expect_x, int expect_y)
+{
+    CONSOLE_SCREEN_BUFFER_INFO info;
+
+    fetch_child_sb_info(&info);
 
     ok_(__FILE__,line)(info.dwCursorPosition.X == expect_x, "dwCursorPosition.X = %u, expected %u\n",
                        info.dwCursorPosition.X, expect_x);
@@ -1276,6 +1323,108 @@ static void test_read_console(void)
     expect_empty_output();
 }
 
+static void test_read_console_control(void)
+{
+    CONSOLE_SCREEN_BUFFER_INFO info1, info2;
+    char ctrl;
+    char buf[16];
+    WCHAR bufw[16];
+
+    child_set_input_mode(child_pipe, ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT |
+                         ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT | ENABLE_INSERT_MODE |
+                         ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS | ENABLE_AUTO_POSITION);
+
+    /* test simple behavior */
+    for (ctrl = 0; ctrl < ' '; ctrl++)
+    {
+        /* don't play with fire */
+        if (ctrl == 0 || ctrl == 3 || ctrl == '\n' || ctrl == 27) continue;
+
+        /* Simulate initial characters
+         * Note: as ReadConsole with CONTROL structure will need to back up the cursor
+         * up to the length of the initial characters, all the following tests ensure that
+         * this backup doesn't imply backing up to the previous line.
+         * This is the "regular" behavior when using completion.
+         */
+        child_string_request(REQ_WRITE_CONSOLE, L"\rabc");
+        skip_sequence("\x1b[25l");  /* broken hide cursor */
+        skip_sequence("\x1b[?25l"); /* hide cursor */
+        skip_sequence("\x1b[H");
+        skip_sequence("\r");
+        expect_output_sequence("abc");
+        skip_sequence("\x1b[?25h"); /* show cursor */
+
+        fetch_child_sb_info(&info1);
+        child_read_console_control(child_pipe, 100, 1ul << ctrl, L"abc");
+        strcpy(buf, "def."); buf[3] = ctrl;
+        write_console_pipe(buf);
+        wcscpy(bufw, L"abcdef."); bufw[6] = (WCHAR)ctrl;
+        child_expect_read_control_result(child_pipe, bufw,
+                                         (ctrl == '\t' || ctrl == '\r') ? 0
+                                         : ((ctrl == 30 || ctrl == 31) ? (LEFT_CTRL_PRESSED | SHIFT_PRESSED)
+                                            : LEFT_CTRL_PRESSED));
+        skip_sequence("\x1b[?25l"); /* hide cursor */
+        expect_output_sequence("def");
+        skip_sequence("\x1b[?25h"); /* show cursor */
+        fetch_child_sb_info(&info2);
+        ok(info1.dwCursorPosition.X + 3 == info2.dwCursorPosition.X,
+           "Bad x-position: expected %u => %u but got => %u instead\n",
+           info1.dwCursorPosition.X, info1.dwCursorPosition.X + 3, info2.dwCursorPosition.X);
+        ok(info1.dwCursorPosition.Y == info2.dwCursorPosition.Y, "Cursor shouldn't have changed line\n");
+    }
+
+    /* test two different control characters in input */
+    fetch_child_sb_info(&info1);
+    child_read_console_control(child_pipe, 100, 1ul << '\x01', L"abc");
+    write_console_pipe("d\x02""ef\x01");
+    child_expect_read_control_result(child_pipe, L"abcd\x02""ef\x01", LEFT_CTRL_PRESSED);
+    skip_sequence("\x1b[?25l"); /* hide cursor */
+    expect_output_sequence("d^Bef");
+    skip_sequence("\x1b[?25h"); /* show cursor */
+    fetch_child_sb_info(&info2);
+    ok(info1.dwCursorPosition.X + 5 == info2.dwCursorPosition.X,
+       "Bad x-position: expected %u => %u but got => %u instead\n",
+       info1.dwCursorPosition.X, info1.dwCursorPosition.X + 5, info2.dwCursorPosition.X);
+    ok(info1.dwCursorPosition.Y == info2.dwCursorPosition.Y, "Cursor shouldn't have changed line\n");
+
+    /* test that ctrl character not in mask is handled as without ctrl mask */
+    child_read_console_control(child_pipe, 100, 1ul << '\x01', L"abc");
+    write_console_pipe("d\ref\x01");
+    child_expect_read_control_result(child_pipe, L"abcd\r\n", 0);
+    skip_sequence("\x1b[?25l"); /* hide cursor */
+    expect_output_sequence("d\r\n");
+    skip_sequence("\x1b[?25h"); /* show cursor */
+    expect_empty_output();
+    /* see note above... ditto */
+    child_string_request(REQ_WRITE_CONSOLE, L"abc");
+    skip_sequence("\x1b[?25l"); /* hide cursor */
+    expect_output_sequence("abc");
+    skip_sequence("\x1b[?25h"); /* show cursor */
+
+    child_read_console_control(child_pipe, 100, 1ul << '\x01', L"abc");
+    child_expect_read_control_result(child_pipe, L"abcef\x01", LEFT_CTRL_PRESSED);
+    skip_sequence("\x1b[?25l"); /* hide cursor */
+    expect_output_sequence("ef");
+    skip_sequence("\x1b[?25h"); /* show cursor */
+
+    /* test when output buffer becomes full before control event */
+    child_read_console_control(child_pipe, 4, 1ul << '\x01', L"abc");
+    write_console_pipe("def\x01");
+    child_expect_read_control_result(child_pipe, L"abcd", LEFT_CTRL_PRESSED);
+    skip_sequence("\x1b[?25l"); /* hide cursor */
+    expect_output_sequence("def");
+    skip_sequence("\x1b[?25h"); /* show cursor */
+    expect_empty_output();
+    child_read_console_control(child_pipe, 20, 1ul << '\x01', L"abc");
+    child_expect_read_control_result(child_pipe, L"ef\x01", 0);
+
+    /* TODO: add tests:
+     * - when initial characters go back to previous line
+     * - edition inside initial characters can occur
+     */
+    expect_empty_output();
+}
+
 static void test_tty_input(void)
 {
     INPUT_RECORD ir;
@@ -1481,6 +1630,27 @@ static void child_process(HANDLE pipe)
             ok(ret, "WriteFile failed: %lu\n", GetLastError());
             break;
 
+        case REQ_READ_CONSOLE_CONTROL:
+            {
+                CONSOLE_READCONSOLE_CONTROL crc;
+                WCHAR result[1024];
+                WCHAR *ptr = (void *)((char *)result + sizeof(DWORD));
+
+                count = req->u.control.size;
+                memset(result, 0xcc, sizeof(result));
+                crc.nLength = sizeof(crc);
+                crc.dwCtrlWakeupMask = req->u.control.mask;
+                crc.nInitialChars = wcslen(req->u.control.initial);
+                crc.dwConsoleKeyState = 0xa5;
+                memcpy(ptr, req->u.control.initial, crc.nInitialChars * sizeof(WCHAR));
+                ret = ReadConsoleW(input, ptr, count, &count, &crc);
+                ok(ret, "ReadConsoleW failed: %lu\n", GetLastError());
+                *(DWORD *)result = crc.dwConsoleKeyState;
+                ret = WriteFile(pipe, result, sizeof(DWORD) + count * sizeof(WCHAR), NULL, NULL);
+                ok(ret, "WriteFile failed: %lu\n", GetLastError());
+            }
+            break;
+
         case REQ_SCROLL:
             ret = ScrollConsoleScreenBufferW(output, &req->u.scroll.rect, NULL, req->u.scroll.dst, &req->u.scroll.fill);
             ok(ret, "ScrollConsoleScreenBuffer failed: %lu\n", GetLastError());
@@ -1641,6 +1811,7 @@ static void test_pseudoconsole(void)
     if (!broken_version)
     {
         test_tty_output();
+        test_read_console_control();
         test_read_console();
         test_tty_input();
     }




More information about the wine-devel mailing list