diff --git a/rostests/apitests/kernel32/CMakeLists.txt b/rostests/apitests/kernel32/CMakeLists.txt index cad5e854d77..e699ce58cc7 100644 --- a/rostests/apitests/kernel32/CMakeLists.txt +++ b/rostests/apitests/kernel32/CMakeLists.txt @@ -10,6 +10,7 @@ list(APPEND SOURCE MultiByteToWideChar.c SetCurrentDirectory.c SetUnhandledExceptionFilter.c + TerminateProcess.c testlist.c) add_executable(kernel32_apitest ${SOURCE}) diff --git a/rostests/apitests/kernel32/TerminateProcess.c b/rostests/apitests/kernel32/TerminateProcess.c new file mode 100644 index 00000000000..5483e11303c --- /dev/null +++ b/rostests/apitests/kernel32/TerminateProcess.c @@ -0,0 +1,255 @@ +/* + * PROJECT: ReactOS api tests + * LICENSE: LGPLv2.1+ - See COPYING.LIB in the top level directory + * PURPOSE: Test for TerminateProcess + * PROGRAMMER: Thomas Faber + */ + +#include + +#include +#include + +static +HANDLE +StartChild( + _In_ PCWSTR Argument, + _In_ DWORD Flags, + _Out_opt_ PDWORD ProcessId) +{ + BOOL Success; + WCHAR FileName[MAX_PATH]; + WCHAR CommandLine[MAX_PATH]; + STARTUPINFOW StartupInfo; + PROCESS_INFORMATION ProcessInfo; + + GetModuleFileNameW(NULL, FileName, _countof(FileName)); + StringCbPrintfW(CommandLine, + sizeof(CommandLine), + L"\"%ls\" TerminateProcess %ls", + FileName, + Argument); + + RtlZeroMemory(&StartupInfo, sizeof(StartupInfo)); + StartupInfo.cb = sizeof(StartupInfo); + /* HACK: running the test under rosautotest seems to keep another reference + * to the child process around until the test finishes (on both ROS and + * Windows)... I'm too lazy to investigate very much so let's just redirect + * the child std handles to nowhere. ok() is useless in half the child + * processes anyway. + */ + StartupInfo.dwFlags = STARTF_USESTDHANDLES; + + Success = CreateProcessW(FileName, + CommandLine, + NULL, + NULL, + FALSE, + Flags, + NULL, + NULL, + &StartupInfo, + &ProcessInfo); + if (!Success) + { + skip("CreateProcess failed with %lu\n", GetLastError()); + if (ProcessId) + *ProcessId = 0; + return NULL; + } + CloseHandle(ProcessInfo.hThread); + if (ProcessId) + *ProcessId = ProcessInfo.dwProcessId; + return ProcessInfo.hProcess; +} + +static +VOID +TraceHandleCount_( + _In_ HANDLE hObject, + _In_ PCSTR File, + _In_ INT Line) +{ + NTSTATUS Status; + OBJECT_BASIC_INFORMATION BasicInfo; + + Status = NtQueryObject(hObject, + ObjectBasicInformation, + &BasicInfo, + sizeof(BasicInfo), + NULL); + if (!NT_SUCCESS(Status)) + { + ok_(File, Line)(0, "NtQueryObject failed with status 0x%lx\n", Status); + return; + } + ok_(File, Line)(0, "Handle %p still has %lu open handles, %lu references\n", hObject, BasicInfo.HandleCount, BasicInfo.PointerCount); +} + +#define WaitExpectSuccess(h, ms) WaitExpect_(h, ms, WAIT_OBJECT_0, __FILE__, __LINE__) +#define WaitExpectTimeout(h, ms) WaitExpect_(h, ms, WAIT_TIMEOUT, __FILE__, __LINE__) +static +VOID +WaitExpect_( + _In_ HANDLE hWait, + _In_ DWORD Milliseconds, + _In_ DWORD ExpectedError, + _In_ PCSTR File, + _In_ INT Line) +{ + DWORD Error; + + Error = WaitForSingleObject(hWait, Milliseconds); + ok_(File, Line)(Error == ExpectedError, "Wait for %p return %lu\n", hWait, Error); +} + +#define CloseProcessAndVerify(hp, pid, code) CloseProcessAndVerify_(hp, pid, code, __FILE__, __LINE__) +static +VOID +CloseProcessAndVerify_( + _In_ HANDLE hProcess, + _In_ DWORD ProcessId, + _In_ UINT ExpectedExitCode, + _In_ PCSTR File, + _In_ INT Line) +{ + int i = 0; + DWORD Error; + DWORD ExitCode; + BOOL Success; + + WaitExpect_(hProcess, 0, WAIT_OBJECT_0, File, Line); + Success = GetExitCodeProcess(hProcess, &ExitCode); + ok_(File, Line)(Success, "GetExitCodeProcess failed with %lu\n", GetLastError()); + CloseHandle(hProcess); + while ((hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, ProcessId)) != NULL) + { + if (++i >= 100) + { + TraceHandleCount_(hProcess, File, Line); + CloseHandle(hProcess); + break; + } + CloseHandle(hProcess); + Sleep(100); + } + Error = GetLastError(); + ok_(File, Line)(hProcess == NULL, "OpenProcess succeeded unexpectedly for pid 0x%lx\n", ProcessId); + ok_(File, Line)(Error == ERROR_INVALID_PARAMETER, "Error = %lu\n", Error); + ok_(File, Line)(ExitCode == ExpectedExitCode, "Exit code is %lu but expected %lu\n", ExitCode, ExpectedExitCode); +} + +static +VOID +TestTerminateProcess( + _In_ HANDLE hEvent) +{ + HANDLE hProcess; + DWORD ProcessId; + + /* Regular child process that returns from the test function */ + /* HACK: These two tests don't work if stdout is a pipe. See StartChild */ + ResetEvent(hEvent); + hProcess = StartChild(L"child", 0, &ProcessId); + WaitExpectSuccess(hEvent, 5000); + WaitExpectSuccess(hProcess, 5000); + CloseProcessAndVerify(hProcess, ProcessId, 0); + + ResetEvent(hEvent); + hProcess = StartChild(L"child", 0, &ProcessId); + WaitExpectSuccess(hProcess, 5000); + WaitExpectSuccess(hEvent, 0); + CloseProcessAndVerify(hProcess, ProcessId, 0); + + /* Suspended process -- never gets a chance to initialize */ + ResetEvent(hEvent); + hProcess = StartChild(L"child", CREATE_SUSPENDED, &ProcessId); + WaitExpectTimeout(hEvent, 100); + WaitExpectTimeout(hProcess, 100); + TerminateProcess(hProcess, 123); + WaitExpectSuccess(hProcess, 5000); + CloseProcessAndVerify(hProcess, ProcessId, 123); + + /* Waiting process -- we have to terminate it */ + ResetEvent(hEvent); + hProcess = StartChild(L"wait", 0, &ProcessId); + WaitExpectTimeout(hProcess, 100); + TerminateProcess(hProcess, 123); + WaitExpectSuccess(hProcess, 5000); + CloseProcessAndVerify(hProcess, ProcessId, 123); + + /* Process calls ExitProcess */ + ResetEvent(hEvent); + hProcess = StartChild(L"child exit 456", 0, &ProcessId); + WaitExpectSuccess(hEvent, 5000); + WaitExpectSuccess(hProcess, 5000); + CloseProcessAndVerify(hProcess, ProcessId, 456); + + /* Process calls TerminateProcess with GetCurrentProcess */ + ResetEvent(hEvent); + hProcess = StartChild(L"child terminate 456", 0, &ProcessId); + WaitExpectSuccess(hEvent, 5000); + WaitExpectSuccess(hProcess, 5000); + CloseProcessAndVerify(hProcess, ProcessId, 456); + + /* Process calls TerminateProcess with real handle to itself */ + ResetEvent(hEvent); + hProcess = StartChild(L"child terminate2 456", 0, &ProcessId); + WaitExpectSuccess(hEvent, 5000); + WaitExpectSuccess(hProcess, 5000); + CloseProcessAndVerify(hProcess, ProcessId, 456); +} + +START_TEST(TerminateProcess) +{ + HANDLE hEvent; + BOOL Success; + DWORD Error; + int argc; + char **argv; + + hEvent = CreateEventW(NULL, TRUE, FALSE, L"kernel32_apitest_TerminateProcess_event"); + Error = GetLastError(); + if (!hEvent) + { + skip("CreateEvent failed with error %lu\n", Error); + return; + } + argc = winetest_get_mainargs(&argv); + if (argc >= 3) + { + ok(Error == ERROR_ALREADY_EXISTS, "Error = %lu\n", Error); + if (!strcmp(argv[2], "wait")) + { + WaitExpectSuccess(hEvent, 30000); + } + else + { + Success = SetEvent(hEvent); + ok(Success, "SetEvent failed with return %d, error %lu\n", Success, GetLastError()); + } + } + else + { + ok(Error == NO_ERROR, "Error = %lu\n", Error); + TestTerminateProcess(hEvent); + } + CloseHandle(hEvent); + if (argc >= 5) + { + UINT ExitCode = strtol(argv[4], NULL, 10); + + fflush(stdout); + if (!strcmp(argv[3], "exit")) + ExitProcess(ExitCode); + else if (!strcmp(argv[3], "terminate")) + TerminateProcess(GetCurrentProcess(), ExitCode); + else if (!strcmp(argv[3], "terminate2")) + { + HANDLE hProcess; + hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, GetCurrentProcessId()); + TerminateProcess(hProcess, ExitCode); + } + ok(0, "Should have terminated\n"); + } +} diff --git a/rostests/apitests/kernel32/testlist.c b/rostests/apitests/kernel32/testlist.c index e6836acaf50..850ccff5894 100644 --- a/rostests/apitests/kernel32/testlist.c +++ b/rostests/apitests/kernel32/testlist.c @@ -13,6 +13,7 @@ extern void func_lstrcpynW(void); extern void func_MultiByteToWideChar(void); extern void func_SetCurrentDirectory(void); extern void func_SetUnhandledExceptionFilter(void); +extern void func_TerminateProcess(void); const struct test winetest_testlist[] = { @@ -25,7 +26,8 @@ const struct test winetest_testlist[] = { "lstrcpynW", func_lstrcpynW }, { "MultiByteToWideChar", func_MultiByteToWideChar }, { "SetCurrentDirectory", func_SetCurrentDirectory }, - { "SetUnhandledExceptionFilter", func_SetUnhandledExceptionFilter}, + { "SetUnhandledExceptionFilter", func_SetUnhandledExceptionFilter }, + { "TerminateProcess", func_TerminateProcess }, { 0, 0 } };