Author: tfaber Date: Fri Feb 20 10:29:47 2015 New Revision: 66367
URL: http://svn.reactos.org/svn/reactos?rev=66367&view=rev Log: [KERNEL32_APITEST] - Add a test for process termination behavior. This shows that CreateProcess with CREATE_SUSPENDED followed by TerminateProcess causes a handle leak. CORE-9234
Added: trunk/rostests/apitests/kernel32/TerminateProcess.c (with props) Modified: trunk/rostests/apitests/kernel32/CMakeLists.txt trunk/rostests/apitests/kernel32/testlist.c
Modified: trunk/rostests/apitests/kernel32/CMakeLists.txt URL: http://svn.reactos.org/svn/reactos/trunk/rostests/apitests/kernel32/CMakeLis... ============================================================================== --- trunk/rostests/apitests/kernel32/CMakeLists.txt [iso-8859-1] (original) +++ trunk/rostests/apitests/kernel32/CMakeLists.txt [iso-8859-1] Fri Feb 20 10:29:47 2015 @@ -10,6 +10,7 @@ MultiByteToWideChar.c SetCurrentDirectory.c SetUnhandledExceptionFilter.c + TerminateProcess.c testlist.c)
add_executable(kernel32_apitest ${SOURCE})
Added: trunk/rostests/apitests/kernel32/TerminateProcess.c URL: http://svn.reactos.org/svn/reactos/trunk/rostests/apitests/kernel32/Terminat... ============================================================================== --- trunk/rostests/apitests/kernel32/TerminateProcess.c (added) +++ trunk/rostests/apitests/kernel32/TerminateProcess.c [iso-8859-1] Fri Feb 20 10:29:47 2015 @@ -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 thomas.faber@reactos.org + */ + +#include <apitest.h> + +#include <ndk/obfuncs.h> +#include <strsafe.h> + +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"); + } +}
Propchange: trunk/rostests/apitests/kernel32/TerminateProcess.c ------------------------------------------------------------------------------ svn:eol-style = native
Modified: trunk/rostests/apitests/kernel32/testlist.c URL: http://svn.reactos.org/svn/reactos/trunk/rostests/apitests/kernel32/testlist... ============================================================================== --- trunk/rostests/apitests/kernel32/testlist.c [iso-8859-1] (original) +++ trunk/rostests/apitests/kernel32/testlist.c [iso-8859-1] Fri Feb 20 10:29:47 2015 @@ -13,6 +13,7 @@ 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 @@ { "lstrcpynW", func_lstrcpynW }, { "MultiByteToWideChar", func_MultiByteToWideChar }, { "SetCurrentDirectory", func_SetCurrentDirectory }, - { "SetUnhandledExceptionFilter", func_SetUnhandledExceptionFilter}, + { "SetUnhandledExceptionFilter", func_SetUnhandledExceptionFilter }, + { "TerminateProcess", func_TerminateProcess }, { 0, 0 } };