[KERNEL32_APITEST]
authorThomas Faber <thomas.faber@reactos.org>
Fri, 20 Feb 2015 10:29:47 +0000 (10:29 +0000)
committerThomas Faber <thomas.faber@reactos.org>
Fri, 20 Feb 2015 10:29:47 +0000 (10:29 +0000)
- Add a test for process termination behavior. This shows that CreateProcess with CREATE_SUSPENDED followed by TerminateProcess causes a handle leak.
CORE-9234

svn path=/trunk/; revision=66367

rostests/apitests/kernel32/CMakeLists.txt
rostests/apitests/kernel32/TerminateProcess.c [new file with mode: 0644]
rostests/apitests/kernel32/testlist.c

index cad5e85..e699ce5 100644 (file)
@@ -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 (file)
index 0000000..5483e11
--- /dev/null
@@ -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");
+    }
+}
index e6836ac..850ccff 100644 (file)
@@ -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 }
 };