Windows 10 and 11 introduce the timeout.exe command. This is a similar program with same argument options
-- v18: timeout: add minimal test suite
From: Michele Dionisio michele.dionisio@powersoft.com
--- configure.ac | 1 + programs/timeout/Makefile.in | 8 ++ programs/timeout/main.c | 161 +++++++++++++++++++++++++++++++++++ programs/timeout/timeout.h | 27 ++++++ programs/timeout/timeout.rc | 32 +++++++ 5 files changed, 229 insertions(+) create mode 100644 programs/timeout/Makefile.in create mode 100644 programs/timeout/main.c create mode 100644 programs/timeout/timeout.h create mode 100644 programs/timeout/timeout.rc
diff --git a/configure.ac b/configure.ac index 3f8be71bd21..0f2bfd93b9b 100644 --- a/configure.ac +++ b/configure.ac @@ -3514,6 +3514,7 @@ WINE_CONFIG_MAKEFILE(programs/tasklist) WINE_CONFIG_MAKEFILE(programs/tasklist/tests) WINE_CONFIG_MAKEFILE(programs/taskmgr) WINE_CONFIG_MAKEFILE(programs/termsv) +WINE_CONFIG_MAKEFILE(programs/timeout) WINE_CONFIG_MAKEFILE(programs/uninstaller) WINE_CONFIG_MAKEFILE(programs/unlodctr) WINE_CONFIG_MAKEFILE(programs/view) diff --git a/programs/timeout/Makefile.in b/programs/timeout/Makefile.in new file mode 100644 index 00000000000..708cca355ff --- /dev/null +++ b/programs/timeout/Makefile.in @@ -0,0 +1,8 @@ +MODULE = timeout.exe +IMPORTS = user32 + +EXTRADLLFLAGS = -mconsole -municode + +SOURCES = \ + timeout.rc \ + main.c diff --git a/programs/timeout/main.c b/programs/timeout/main.c new file mode 100644 index 00000000000..cc54c172def --- /dev/null +++ b/programs/timeout/main.c @@ -0,0 +1,161 @@ +/* + * Copyright 2024 Michele Dionisio michele.dionisio@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <windows.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <synchapi.h> +#include <stdbool.h> +#include <wchar.h> +#include <conio.h> +#include "timeout.h" + +static int WINAPIV timeout_error_wprintf(int msg, ...) +{ + WCHAR msg_buffer[MAXSTRING]; + va_list va_args; + int ret; + + LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer, ARRAY_SIZE(msg_buffer)); + va_start(va_args, msg); + ret = vfwprintf(stderr, msg_buffer, va_args); + va_end(va_args); + return ret; +} + +static int WINAPIV timeout_message(int msg) +{ + WCHAR msg_buffer[MAXSTRING]; + + LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer, ARRAY_SIZE(msg_buffer)); + return fwprintf(stdout, msg_buffer); +} + +static int WINAPIV timeout_wprintf(int msg, ...) +{ + WCHAR msg_buffer[MAXSTRING]; + va_list va_args; + int ret; + + LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer, ARRAY_SIZE(msg_buffer)); + va_start(va_args, msg); + ret = vfwprintf(stdout, msg_buffer, va_args); + va_end(va_args); + return ret; +} + +static volatile bool stop = false; + +static BOOL WINAPI ctrl_c_handler(DWORD dwCtrlType) +{ + if (dwCtrlType == CTRL_C_EVENT) + { + stop = true; + return TRUE; + } + return FALSE; +} + +int __cdecl wmain(int argc, WCHAR *argv[]) +{ + int wait_time = 0; + int wait_time_valid = 0; + int nobreak = 0; + + if (argc <= 1) + { + timeout_error_wprintf(STRING_BAD_COMMAND_LINE); + return 1; + } + + for (int i = 1; i < argc; i++) + { + if (wcscmp(argv[i], L"/?") == 0) + { + timeout_message(STRING_USAGE); + return 0; + } + else if (wcsicmp(argv[i], L"/t") == 0) + { + if ((i + 1) < argc) + { + WCHAR *endptr = NULL; + long l_wait_time; + i++; + l_wait_time = wcstol(argv[i], &endptr, 10); + if (*endptr != L'\0') { + timeout_error_wprintf(STRING_BAD_COMMAND_LINE); + return 1; + } + if ((l_wait_time < -1) || (l_wait_time > 99999)) + { + timeout_error_wprintf(STRING_BAD_COMMAND_LINE); + return 1; + } + wait_time = (int)l_wait_time; + wait_time_valid = 1; + } + else + { + timeout_error_wprintf(STRING_BAD_COMMAND_LINE); + return 1; + } + } + else if (wcsicmp(argv[i], L"/nobreak") == 0) + { + nobreak = 1; + } + else + { + timeout_error_wprintf(STRING_BAD_COMMAND_LINE); + return 1; + } + } + + if (wait_time_valid == 0) + { + timeout_error_wprintf(STRING_BAD_COMMAND_LINE); + return 1; + } + + SetConsoleCtrlHandler(ctrl_c_handler, TRUE); + + for (int i = 0; (wait_time < 0) || (i < wait_time); i++) + { + timeout_wprintf(STRING_WAITING_SINCE, i); + timeout_message((nobreak == 0) ? STRING_PRESS_KEY : STRING_PRESS_CRTLC); + if (stop) + { + break; + } + if ((nobreak == 0) && _kbhit()) + { + break; + } + Sleep(1000); + } + putc('\n', stdout); + + if (stop) + { + return STATUS_CONTROL_C_EXIT; + } + + return 0; +} diff --git a/programs/timeout/timeout.h b/programs/timeout/timeout.h new file mode 100644 index 00000000000..8b14e7da086 --- /dev/null +++ b/programs/timeout/timeout.h @@ -0,0 +1,27 @@ +/* + * Copyright 2024 Michele Dionisio michele.dionisio@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <windef.h> + +#define MAXSTRING 8192 + +#define STRING_USAGE 101 +#define STRING_BAD_COMMAND_LINE 102 +#define STRING_WAITING_SINCE 103 +#define STRING_PRESS_CRTLC 104 +#define STRING_PRESS_KEY 105 diff --git a/programs/timeout/timeout.rc b/programs/timeout/timeout.rc new file mode 100644 index 00000000000..53922cd7ca0 --- /dev/null +++ b/programs/timeout/timeout.rc @@ -0,0 +1,32 @@ +/* + * Copyright 2024 Michele Dionisio michele.dionisio@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "timeout.h" + +#pragma makedep po + +LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT + +STRINGTABLE +{ + STRING_USAGE, "Usage: timeout /T <timeout> [</NOBREAK>]\n" + STRING_BAD_COMMAND_LINE, "TIMEOUT: Bad command line\n" + STRING_WAITING_SINCE, "waiting since %d sec. " + STRING_PRESS_CRTLC, "press CTRL+C...\r" + STRING_PRESS_KEY, "press a key to continue...\r" +}
From: Michele Dionisio michele.dionisio@powersoft.com
--- configure.ac | 1 + programs/timeout/tests/Makefile.in | 4 + programs/timeout/tests/timeout.c | 137 +++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 programs/timeout/tests/Makefile.in create mode 100644 programs/timeout/tests/timeout.c
diff --git a/configure.ac b/configure.ac index 0f2bfd93b9b..5955eb89c78 100644 --- a/configure.ac +++ b/configure.ac @@ -3515,6 +3515,7 @@ WINE_CONFIG_MAKEFILE(programs/tasklist/tests) WINE_CONFIG_MAKEFILE(programs/taskmgr) WINE_CONFIG_MAKEFILE(programs/termsv) WINE_CONFIG_MAKEFILE(programs/timeout) +WINE_CONFIG_MAKEFILE(programs/timeout/tests) WINE_CONFIG_MAKEFILE(programs/uninstaller) WINE_CONFIG_MAKEFILE(programs/unlodctr) WINE_CONFIG_MAKEFILE(programs/view) diff --git a/programs/timeout/tests/Makefile.in b/programs/timeout/tests/Makefile.in new file mode 100644 index 00000000000..a50cdee3062 --- /dev/null +++ b/programs/timeout/tests/Makefile.in @@ -0,0 +1,4 @@ +TESTDLL = timeout.exe + +SOURCES = \ + timeout.c diff --git a/programs/timeout/tests/timeout.c b/programs/timeout/tests/timeout.c new file mode 100644 index 00000000000..5ed1752a176 --- /dev/null +++ b/programs/timeout/tests/timeout.c @@ -0,0 +1,137 @@ +/* + * Copyright 2024 Michele Dionisio michele.dionisio@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <windows.h> +#include "wine/test.h" + +#define run_timeout_stdin(a, b) _run_timeout_stdin(__FILE__, __LINE__, a, b) +static void _run_timeout_stdin(const char *file, int line, const char *commandline, int exitcode_expected) +{ + PROCESS_INFORMATION process_info = {0}; + STARTUPINFOA startup_info; + char cmd[4096]; + DWORD exitcode; + + memset(&startup_info, 0, sizeof(startup_info)); + startup_info.cb = sizeof(startup_info); + startup_info.dwFlags = STARTF_USESTDHANDLES; + startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startup_info.hStdOutput = NULL; + startup_info.hStdError = NULL; + + sprintf(cmd, "timeout.exe %s", commandline); + + trace("Running %s\n", cmd); + + CreateProcessA(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &startup_info, &process_info); + + WaitForSingleObject(process_info.hProcess, INFINITE); + GetExitCodeProcess(process_info.hProcess, &exitcode); + CloseHandle(process_info.hProcess); + CloseHandle(process_info.hThread); + ok_(file, line)(exitcode == exitcode_expected, "Expected exitcode %d, got %ld\n", + exitcode_expected, exitcode); +} + +static void test_basic(void) +{ + /* No options */ + run_timeout_stdin("", 1); + + /* /? */ + run_timeout_stdin("/?", 0); + + /* /T 1 /NOBREAK */ + run_timeout_stdin("/T 1 /NOBREAK", 0); + + /* 1 /NOBREAK */ + run_timeout_stdin("/T 1 /NOBREAK", 0); + + /* /T 1ab /NOBREAK */ + run_timeout_stdin("/T 1ab /NOBREAK", 1); + + /* /T -3 /NOBREAK */ + run_timeout_stdin("/T -3 /NOBREAK", 1); + + /* /T 10000000 /NOBREAK */ + run_timeout_stdin("/T 10000000 /NOBREAK", 1); +} + +#define run_timeout_ctrlc(a, b) _run_timeout_ctrlc(__FILE__, __LINE__, a, b) +static void _run_timeout_ctrlc(const char *file, int line, const char *option, DWORD exitcode_expected) +{ + PROCESS_INFORMATION process_info = {0}; + STARTUPINFOA startup_info; + char cmd[4096]; + DWORD status, exitcode; + DWORD64 tick_count; + BOOL ret; + + memset(&startup_info, 0, sizeof(startup_info)); + startup_info.cb = sizeof(startup_info); + startup_info.dwFlags = STARTF_USESTDHANDLES; + startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startup_info.hStdOutput = NULL; + startup_info.hStdError = NULL; + + SetConsoleCtrlHandler(NULL, FALSE); + sprintf(cmd, "timeout.exe /T 8 %s", option); + ret = CreateProcessA(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &startup_info, &process_info); + ok(ret, "CreateProcessA failed: %lu\n", GetLastError()); + + /* wait for process to be started */ + status = WaitForSingleObject(process_info.hProcess, 2000); + ok_(file, line)(status == WAIT_TIMEOUT, "WaitForSingleObject returned %#lx (expecting WAIT_TIMEOUT)\n", status); + + tick_count = GetTickCount64(); + + SetConsoleCtrlHandler(NULL, TRUE); + ret = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); + ok_(file, line)(ret, "GenerateConsoleCtrlEvent failed: %lu\n", GetLastError()); + + status = WaitForSingleObject(process_info.hProcess, INFINITE); + ok_(file, line)(status == WAIT_OBJECT_0, "WaitForSingleObject returned %#lx (expecting WAIT_OBJECT_0)\n", status); + + tick_count = GetTickCount64() - tick_count; + ok_(file, line)(tick_count < 2000, "Process has not been stopped by ctrl-c\n"); + + ret = GetExitCodeProcess(process_info.hProcess, &exitcode); + ok_(file, line)(ret, "GetExitCodeProcess failed\n"); + + CloseHandle(process_info.hProcess); + CloseHandle(process_info.hThread); + ok_(file, line)(exitcode == exitcode_expected, "Expected exitcode %ld, got %lx\n", + exitcode_expected, exitcode); +} + +static void test_ctrlc(void) +{ + /* don't temper with winetest (and its parent) */ + FreeConsole(); + AllocConsole(); + + run_timeout_ctrlc("", STATUS_CONTROL_C_EXIT); + run_timeout_ctrlc("/nobreak", 1); +} + +START_TEST(timeout) +{ + test_basic(); + /* must be last in list (changes current console) */ + test_ctrlc(); +}
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=149886
Your paranoid android.
=== debian11 (32 bit report) ===
timeout.exe: timeout.c:129: Test failed: Expected exitcode 1, got c000013a
=== debian11 (32 bit ar:MA report) ===
timeout.exe: timeout.c:129: Test failed: Expected exitcode 1, got c000013a
=== debian11 (32 bit de report) ===
timeout.exe: timeout.c:129: Test failed: Expected exitcode 1, got c000013a
=== debian11 (32 bit fr report) ===
timeout.exe: timeout.c:129: Test failed: Expected exitcode 1, got c000013a
=== debian11 (32 bit he:IL report) ===
timeout.exe: timeout.c:129: Test failed: Expected exitcode 1, got c000013a
=== debian11 (32 bit hi:IN report) ===
timeout.exe: timeout.c:129: Test failed: Expected exitcode 1, got c000013a
=== debian11 (32 bit ja:JP report) ===
timeout.exe: timeout.c:129: Test failed: Expected exitcode 1, got c000013a
=== debian11 (32 bit zh:CN report) ===
timeout.exe: timeout.c:129: Test failed: Expected exitcode 1, got c000013a
=== debian11b (32 bit WoW report) ===
timeout.exe: timeout.c:129: Test failed: Expected exitcode 1, got c000013a
=== debian11b (64 bit WoW report) ===
timeout.exe: timeout.c:129: Test failed: Expected exitcode 1, got c000013a
user32: input.c:4305: Test succeeded inside todo block: button_down_hwnd_todo 1: got MSG_TEST_WIN hwnd 0000000000A200DC, msg WM_LBUTTONDOWN, wparam 0x1, lparam 0x320032 win.c:4070: Test failed: Expected active window 0000000004330158, got 0000000000000000. win.c:4071: Test failed: Expected focus window 0000000004330158, got 0000000000000000.
On Fri Nov 22 17:11:47 2024 +0000, eric pouech wrote:
a couple of more comments:
- the /T on command line is optional (added test for that, see below)
- the test that you commented out, is a left over from earlier version
of the patch where the input handle was a pipe. it does work properly now that you pass console handle as input handle
- so you should test after validating the command line arguments that
the input handle is really a console handle (VerifyConsoleIoHandle(GetStdHandle(STD_INPUT_HANDLE)) (as windows does.... "timeout < nul" in cmd prompt shows the relevant error)
- I believe it's better in run_timeout_stdin to redirect output/error
handle to NULL as we don't care for the actual output (otherwise it cripples the output)
- I toyed a bit the MR and added tests for ctrl-c handling (see attached
file, also includes improvements for above items). Feel free to include into your MR [patch](/uploads/fe2608693e811eeec8e2f42c2cc9ce87/patch)
- Actually, the STATUS_CONTROL_C_EXIT exit code is not generated by
timeout.exe but by the default handlers in kernelbase. Wine doesn't do this properly yet (I'll send another MR to fix that)
- So I recommend only setting the ctrl_c hander when the /NOBREAK is present
thank's for the patch. I apply that but unfortunatly I not add any console verification with ``` if (VerifyConsoleIoHandle(GetStdHandle(STD_INPUT_HANDLE)) ... ```
because it work under wine but not under windows. And If I do ``` if (!_isatty(_fileno(stdin))) ```
it works under windows but not under wine