diff options
Diffstat (limited to 'src/pal/tests/palsuite/threading')
399 files changed, 26394 insertions, 0 deletions
diff --git a/src/pal/tests/palsuite/threading/CMakeLists.txt b/src/pal/tests/palsuite/threading/CMakeLists.txt new file mode 100644 index 0000000000..bd31d17579 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(CreateEventA) +add_subdirectory(CreateEventW) +add_subdirectory(CreateMutexA_ReleaseMutex) +add_subdirectory(CreateMutexW_ReleaseMutex) +add_subdirectory(CreateProcessA) +add_subdirectory(CreateProcessW) +add_subdirectory(CreateSemaphoreA_ReleaseSemaphore) +add_subdirectory(CreateSemaphoreW_ReleaseSemaphore) +add_subdirectory(CreateThread) +add_subdirectory(CriticalSectionFunctions) +add_subdirectory(DuplicateHandle) +add_subdirectory(ExitProcess) +add_subdirectory(ExitThread) +add_subdirectory(GetCurrentProcess) +add_subdirectory(GetCurrentProcessId) +add_subdirectory(GetCurrentThread) +add_subdirectory(GetCurrentThreadId) +add_subdirectory(GetExitCodeProcess) +add_subdirectory(GetProcessTimes) +add_subdirectory(GetThreadTimes) +add_subdirectory(NamedMutex) +add_subdirectory(OpenEventW) +add_subdirectory(OpenProcess) +add_subdirectory(QueryThreadCycleTime) +add_subdirectory(QueueUserAPC) +add_subdirectory(ReleaseMutex) +add_subdirectory(releasesemaphore) +add_subdirectory(ResetEvent) +add_subdirectory(ResumeThread) +add_subdirectory(SetErrorMode) +add_subdirectory(SetEvent) +add_subdirectory(Sleep) +add_subdirectory(SleepEx) +add_subdirectory(SwitchToThread) +add_subdirectory(TerminateProcess) +add_subdirectory(ThreadPriority) +add_subdirectory(TLS) +add_subdirectory(WaitForMultipleObjects) +add_subdirectory(WaitForMultipleObjectsEx) +add_subdirectory(WaitForSingleObject) +add_subdirectory(YieldProcessor) + diff --git a/src/pal/tests/palsuite/threading/CreateEventA/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateEventA/CMakeLists.txt new file mode 100644 index 0000000000..1962ade358 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventA/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) +add_subdirectory(test3) + diff --git a/src/pal/tests/palsuite/threading/CreateEventA/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateEventA/test1/CMakeLists.txt new file mode 100644 index 0000000000..da1232eea4 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventA/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_createeventa_test1 + ${SOURCES} +) + +add_dependencies(paltest_createeventa_test1 coreclrpal) + +target_link_libraries(paltest_createeventa_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateEventA/test1/test1.c b/src/pal/tests/palsuite/threading/CreateEventA/test1/test1.c new file mode 100644 index 0000000000..d8ef0f58a3 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventA/test1/test1.c @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test1.c +** +** Purpose: Test for CreateEvent. Create an event, ensure the +** HANDLE is valid. Then check to ensure that the object is in the +** signaled state. Close the HANDLE and done. +** +** +**=========================================================*/ + +/* + Note: From the rotor_pal documentation: + + lpEventAttributes will always be NULL, bManualReset can be either + TRUE or FALSE, bInitialState can be either TRUE or FALSE, the lpName + may be non-NULL + +*/ + + +#include <palsuite.h> + +BOOL CreateEventTest() +{ + BOOL bRet = FALSE; + DWORD dwRet = 0; + + LPSECURITY_ATTRIBUTES lpEventAttributes = NULL; + BOOL bManualReset = TRUE; + BOOL bInitialState = TRUE; + + /* Call CreateEvent, and check to ensure the returned HANDLE is a + valid event HANDLE + */ + + HANDLE hEvent = CreateEvent( lpEventAttributes, + bManualReset, + bInitialState, + NULL); + + if (hEvent != NULL) + { + /* Wait for the Object (for 0 time) and ensure that it returns + the value indicating that the event is signaled. + */ + dwRet = WaitForSingleObject(hEvent,0); + + if (dwRet != WAIT_OBJECT_0) + { + Trace("CreateEventTest:WaitForSingleObject failed (%x)\n", GetLastError()); + } + else + { + /* If we make it here, and CloseHandle succeeds, then the + entire test has passed. Otherwise bRet will still show + failure + */ + bRet = CloseHandle(hEvent); + + if (!bRet) + { + Trace("CreateEventTest:CloseHandle failed (%x)\n", GetLastError()); + } + } + } + else + { + Trace("CreateEventTest:CreateEvent failed (%x)\n", GetLastError()); + } + + return bRet; +} + +int __cdecl main(int argc, char **argv) +{ + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + if(!CreateEventTest()) + { + Fail ("Test failed\n"); + } + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/CreateEventA/test1/testinfo.dat b/src/pal/tests/palsuite/threading/CreateEventA/test1/testinfo.dat new file mode 100644 index 0000000000..c0d169ccdc --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventA/test1/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateEvent +Name = Positive Test for CreateEvent +TYPE = DEFAULT +EXE1 = test1 +Description += Test for CreateEvent. Create an event, ensure the += HANDLE is valid. Then check to ensure that the object is in the += signaled state. Close the HANDLE and done. diff --git a/src/pal/tests/palsuite/threading/CreateEventA/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateEventA/test2/CMakeLists.txt new file mode 100644 index 0000000000..f87e5bea87 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventA/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test2.c +) + +add_executable(paltest_createeventa_test2 + ${SOURCES} +) + +add_dependencies(paltest_createeventa_test2 coreclrpal) + +target_link_libraries(paltest_createeventa_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateEventA/test2/test2.c b/src/pal/tests/palsuite/threading/CreateEventA/test2/test2.c new file mode 100644 index 0000000000..a24d20eeea --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventA/test2/test2.c @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test2.c +** +** Purpose: Test for CreateEvent. Create the event with the +** initial state being not signaled. Check to ensure that it +** times out when the event is triggered. +** +** +**=========================================================*/ + +#include <palsuite.h> + +BOOL CreateEventTest() +{ + BOOL bRet = FALSE; + DWORD dwRet = 0; + + LPSECURITY_ATTRIBUTES lpEventAttributes = 0; + BOOL bManualReset = TRUE; + BOOL bInitialState = FALSE; + + /* Create an event with the Initial State set to FALSE */ + + HANDLE hEvent = CreateEvent( lpEventAttributes, + bManualReset, + bInitialState, + NULL); + + if (hEvent != NULL) + { + /* This should ensure that the object is reset, or + non-signaled. + */ + + dwRet = WaitForSingleObject(hEvent,0); + + if (dwRet != WAIT_TIMEOUT) + { + Trace("CloseEventTest:WaitForSingleObject failed (%x)\n", GetLastError()); + } + else + { + /* At this point, we've tested the function with success. + So long as the HANDLE can be closed, this test should + pass. + */ + + bRet = CloseHandle(hEvent); + + if (!bRet) + { + Trace("CloseEventTest:CloseHandle failed (%x)\n", GetLastError()); + } + } + } + else + { + Trace("CloseEventTest:CreateEvent failed (%x)\n", GetLastError()); + } + + return bRet; +} + +int __cdecl main(int argc, char **argv) +{ + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + if(!CreateEventTest()) + { + Fail ("Test failed\n"); + } + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/CreateEventA/test2/testinfo.dat b/src/pal/tests/palsuite/threading/CreateEventA/test2/testinfo.dat new file mode 100644 index 0000000000..4af65f18d8 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventA/test2/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateEvent +Name = Positive Test for CreateEvent +TYPE = DEFAULT +EXE1 = test2 +Description += Test for CreateEvent. Create the event with the += initial state being not signaled. Check to ensure that it += times out when the event is triggered. diff --git a/src/pal/tests/palsuite/threading/CreateEventA/test3/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateEventA/test3/CMakeLists.txt new file mode 100644 index 0000000000..e33c404a79 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventA/test3/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test3.c +) + +add_executable(paltest_createeventa_test3 + ${SOURCES} +) + +add_dependencies(paltest_createeventa_test3 coreclrpal) + +target_link_libraries(paltest_createeventa_test3 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateEventA/test3/test3.c b/src/pal/tests/palsuite/threading/CreateEventA/test3/test3.c new file mode 100644 index 0000000000..56d107b22d --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventA/test3/test3.c @@ -0,0 +1,219 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test3.c +** +** Purpose: Tests for CreateEventA. Create an event with an empty name, +** create an event with the same name as an already created event +** object. +** +** +**=========================================================*/ + +#include <palsuite.h> + +#define SWAPPTR ((VOID *)(-1)) + +struct testCase +{ + LPSECURITY_ATTRIBUTES lpEventAttributes; + BOOL bManualReset; + BOOL bInitialState; + char lpName[MAX_PATH + 2]; + DWORD dwNameLen; + DWORD lastError; + BOOL bResult; +}; + +struct testCase testCases[]= +{ + {0, TRUE, FALSE, "", 0, ERROR_SUCCESS, PASS}, + {0, TRUE, FALSE, "", 5, ERROR_SUCCESS, PASS}, + {0, TRUE, FALSE, "", 5, ERROR_ALREADY_EXISTS, PASS}, + {0, TRUE, FALSE, "", 6, ERROR_INVALID_HANDLE, PASS}, + {0, TRUE, FALSE, "", MAX_PATH - 1 - 60, ERROR_SUCCESS, PASS}, + {0, TRUE, FALSE, "", MAX_PATH + 1, ERROR_FILENAME_EXCED_RANGE, PASS} +}; + +static HANDLE hEvent[sizeof(testCases)/sizeof(struct testCase)]; + +DWORD result[sizeof(testCases)/sizeof(struct testCase)]; + +int __cdecl main(int argc, char **argv) +{ + + BOOL bRet = TRUE; + const char *nonEventName = "aaaaaa"; + HANDLE hUnnamedEvent; + HANDLE hFMap; + DWORD dwRet; + int i; + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + hUnnamedEvent = CreateEventA(0, TRUE, FALSE, NULL); + + if ( NULL == hUnnamedEvent ) + { + bRet = FALSE; + Trace ( "PALSUITE ERROR: CreateEventA (%d, %d, %d, NULL) call " + "returned NULL.\nGetLastError returned %u.\n", 0, TRUE, FALSE, + GetLastError()); + } + + if (!CloseHandle(hUnnamedEvent)) + { + bRet = FALSE; + Trace("PALSUITE ERROR: CreateEventA: CloseHandle(%lp); call " + "failed\nGetLastError returned '%u'.\n", hUnnamedEvent, + GetLastError()); + } + + /* Create non-event with the same name as one of the testCases */ + hFMap = CreateFileMappingA( SWAPPTR, NULL, PAGE_READONLY, 0, 1, + nonEventName ); + + if ( NULL == hFMap ) + { + bRet = FALSE; + Trace ( "PALSUITE ERROR: CreateFileMapping (%p, %p, %d, %d, %d, %s)" + " call returned NULL.\nGetLastError returned %u.\n", + SWAPPTR, NULL, PAGE_READONLY, 0, 0, nonEventName, + GetLastError()); + goto done; + } + + /* Create Events */ + for (i = 0; i < sizeof(testCases)/sizeof(struct testCase); i++) + { + /* create name */ + memset (testCases[i].lpName, '\0', (MAX_PATH + 2)); + memset (testCases[i].lpName, 'a', testCases[i].dwNameLen ); + + SetLastError(ERROR_SUCCESS); + + hEvent[i] = CreateEventA( testCases[i].lpEventAttributes, + testCases[i].bManualReset, + testCases[i].bInitialState, + testCases[i].lpName); + + if (hEvent[i] != INVALID_HANDLE_VALUE) + { + DWORD dwError = GetLastError(); + + if (dwError != testCases[i].lastError) + { + bRet = FALSE; + Trace ("PALSUITE ERROR:\nCreateEventA(%lp, %d, %d, %s)" + "\nGetLastError returned '%u', it should have returned" + "'%d' at index '%d'.\n", testCases[i].lpEventAttributes, + testCases[i].bManualReset, testCases[i].bInitialState, + testCases[i].lpName, dwError, + testCases[i].lastError, i); + } + if ( ERROR_FILENAME_EXCED_RANGE == testCases[i].lastError ) + { + result [i] = 1; + } + if ( ERROR_INVALID_HANDLE == testCases[i].lastError ) + { + result [i] = 1; + } + /* + * If we expected the testcase to FAIL and it passed, + * report an error. + */ + if (testCases[i].bResult == FAIL) + { + bRet = FALSE; + Trace ("PALSUITE ERROR:\nCreateEventA(%lp, %d, %d, %s)" + "\nShould have returned INVALID_HANDLE_VALUE but " + "didn't at index '%d'.\n", + testCases[i].lpEventAttributes, + testCases[i].bManualReset, + testCases[i].bInitialState, + testCases[i].lpName, i); + } + /* + * If result hasn't been set already set it to 0 so all the + * resources will be freed. + */ + if (!result[i] ) + { + result[i] = 0; + } + } + else + { + /* + * If we get an INVALID_HANDLE_VALUE and we expected the + * test case to pass, report an error. + */ + result[i] = 1; + + if (testCases[i].bResult == PASS) + { + bRet = FALSE; + Trace ("PALSUITE ERROR:\nCreateEventA(%lp, %d, %d, %s);" + "\nReturned INVALID_HANDLE_VALUE at index '%d'.\n", + testCases[i].lpEventAttributes, + testCases[i].bManualReset, testCases[i].bInitialState, + testCases[i].lpName, i); + } + } + } + + /* cleanup */ + for (i = 0; i < sizeof(testCases)/sizeof(struct testCase); i++) + { + if (result[i]) + { + continue; + } + dwRet = WaitForSingleObject ( hEvent[i], 0 ); + + if (dwRet != WAIT_TIMEOUT) + { + bRet = FALSE; + Trace("PALSUITE ERROR: CreateEventA:\nWaitForSingleObject (%lp, " + "%d) call failed at index %d .\nGetLastError returned " + "'%u'.\n", hEvent[i], 0, i, GetLastError()); + } + + if (!CloseHandle(hEvent[i])) + { + bRet = FALSE; + Trace("PALSUITE ERROR: CreateEventA: CloseHandle(%lp) call " + "failed at index %d\nGetLastError returned '%u'.\n", + hEvent[i], i, GetLastError()); + } + } + +done: + if (!CloseHandle(hFMap)) + { + bRet = FALSE; + Trace("PALSUITE ERROR: CreateEventW: CloseHandle(%p) call " + "failed\nGetLastError returned '%u'.\n", hFMap, + GetLastError()); + } + + if (FALSE == bRet) + { + bRet = FAIL; + } + else + { + bRet = PASS; + } + + PAL_TerminateEx(bRet); + + return(bRet); +} diff --git a/src/pal/tests/palsuite/threading/CreateEventA/test3/testinfo.dat b/src/pal/tests/palsuite/threading/CreateEventA/test3/testinfo.dat new file mode 100644 index 0000000000..c3a344a2d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventA/test3/testinfo.dat @@ -0,0 +1,16 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateEventA +Name = Positive Test for CreateEventA +TYPE = DEFAULT +EXE1 = test3 +Description += Tests for CreateEventA. Create an unnamed event, create += an event with an empty name, create an event with a name longer than += MAX_PATH, MAX_PATH + 1, create an event with a name already taken += by a non-event object, create an event with a name already taken += by an event object. diff --git a/src/pal/tests/palsuite/threading/CreateEventW/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateEventW/CMakeLists.txt new file mode 100644 index 0000000000..1962ade358 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventW/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) +add_subdirectory(test3) + diff --git a/src/pal/tests/palsuite/threading/CreateEventW/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateEventW/test1/CMakeLists.txt new file mode 100644 index 0000000000..0e8ba5bc2a --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventW/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_createeventw_test1 + ${SOURCES} +) + +add_dependencies(paltest_createeventw_test1 coreclrpal) + +target_link_libraries(paltest_createeventw_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateEventW/test1/test1.c b/src/pal/tests/palsuite/threading/CreateEventW/test1/test1.c new file mode 100644 index 0000000000..8d99e41934 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventW/test1/test1.c @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test1.c +** +** Purpose: Test for CreateEventW +** +** +**=========================================================*/ + +/* + * Note: From the rotor_pal documentation: lpEventAttributes will + * always be NULL, bManualReset can be either TRUE or FALSE, + * bInitialState can be either TRUE or FALSE, the lpName may be + * non-NULL. +*/ +#define UNICODE +#include <palsuite.h> + +BOOL CreateEventTest() +{ + BOOL bRet = FALSE; + DWORD dwRet = 0; + + LPSECURITY_ATTRIBUTES lpEventAttributes = NULL; + BOOL bManualReset = TRUE; + BOOL bInitialState = TRUE; + + /* + * Call CreateEvent, and check to ensure the returned HANDLE is a + * valid event HANDLE + */ + + HANDLE hEvent = CreateEventW(lpEventAttributes, + bManualReset, + bInitialState, + NULL); + + if (hEvent != NULL) + { + /* + * Wait for the Object (for 0 time) and ensure that it returns + * the value indicating that the event is signaled. + */ + dwRet = WaitForSingleObject(hEvent,0); + + if (dwRet != WAIT_OBJECT_0) + { + Trace("CreateEventTest:WaitForSingleObject failed (%x)\n", GetLastError()); + } + else + { + /* + * If we make it here, and CloseHandle succeeds, then the + * entire test has passed. Otherwise bRet will still show + * failure + */ + bRet = CloseHandle(hEvent); + + if (!bRet) + { + Trace("CreateEventTest:CloseHandle failed (%x)\n", GetLastError()); + } + } + } + else + { + Trace("CreateEventTest:CreateEvent failed (%x)\n", GetLastError()); + } + + return bRet; +} + +int __cdecl main(int argc, char **argv) +{ + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + if(!CreateEventTest()) + { + Fail ("Test failed\n"); + } + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/CreateEventW/test1/testinfo.dat b/src/pal/tests/palsuite/threading/CreateEventW/test1/testinfo.dat new file mode 100644 index 0000000000..204ad1f4d4 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventW/test1/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateEventW +Name = Positive Test for CreateEventW +TYPE = DEFAULT +EXE1 = test1 +Description += Test for CreateEventW. Create an event, ensure the += HANDLE is valid. Then check to ensure that the object is in the += signaled state. Close the HANDLE and done. diff --git a/src/pal/tests/palsuite/threading/CreateEventW/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateEventW/test2/CMakeLists.txt new file mode 100644 index 0000000000..f624377474 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventW/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test2.c +) + +add_executable(paltest_createeventw_test2 + ${SOURCES} +) + +add_dependencies(paltest_createeventw_test2 coreclrpal) + +target_link_libraries(paltest_createeventw_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateEventW/test2/test2.c b/src/pal/tests/palsuite/threading/CreateEventW/test2/test2.c new file mode 100644 index 0000000000..4df218995a --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventW/test2/test2.c @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test2.c +** +** Purpose: Test for CreateEventW. Create the event with the +** initial state being not signaled. Check to ensure that it +** times out when the event is triggered. +** +** +**=========================================================*/ +#define UNICODE +#include <palsuite.h> + +BOOL CreateEventTest() +{ + BOOL bRet = FALSE; + DWORD dwRet = 0; + + LPSECURITY_ATTRIBUTES lpEventAttributes = 0; + BOOL bManualReset = TRUE; + BOOL bInitialState = FALSE; + + + /* Create an event with the Initial State set to FALSE */ + + HANDLE hEvent = CreateEventW(lpEventAttributes, + bManualReset, + bInitialState, + NULL); + + if (hEvent != NULL) + { + /* This should ensure that the object is reset, or + non-signaled. + */ + + dwRet = WaitForSingleObject(hEvent,0); + + if (dwRet != WAIT_TIMEOUT) + { + Trace("CloseEventTest:WaitForSingleObject failed (%x)\n", GetLastError()); + } + else + { + /* At this point, we've tested the function with success. + So long as the HANDLE can be closed, this test should + pass. + */ + + bRet = CloseHandle(hEvent); + + if (!bRet) + { + Trace("CloseEventTest:CloseHandle failed (%x)\n", GetLastError()); + } + } + } + else + { + Trace("CloseEventTest:CreateEvent failed (%x)\n", GetLastError()); + } + + return bRet; +} + +int __cdecl main(int argc, char **argv) +{ + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + if(!CreateEventTest()) + { + Fail ("Test failed\n"); + } + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/CreateEventW/test2/testinfo.dat b/src/pal/tests/palsuite/threading/CreateEventW/test2/testinfo.dat new file mode 100644 index 0000000000..01f7519ae3 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventW/test2/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateEventW +Name = Positive Test for CreateEventW +TYPE = DEFAULT +EXE1 = test2 +Description += Test for CreateEvent. Create the event with the += initial state being not signaled. Check to ensure that it += times out when the event is triggered. diff --git a/src/pal/tests/palsuite/threading/CreateEventW/test3/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateEventW/test3/CMakeLists.txt new file mode 100644 index 0000000000..4493ba3872 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventW/test3/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test3.c +) + +add_executable(paltest_createeventw_test3 + ${SOURCES} +) + +add_dependencies(paltest_createeventw_test3 coreclrpal) + +target_link_libraries(paltest_createeventw_test3 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateEventW/test3/test3.c b/src/pal/tests/palsuite/threading/CreateEventW/test3/test3.c new file mode 100644 index 0000000000..22f0fcfc49 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventW/test3/test3.c @@ -0,0 +1,233 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test3.c +** +** Purpose: Tests for CreateEvent. Create an unnamed event, create +** an event with an empty name, create an event with a name longer than +** MAX_PATH, MAX_PATH + 1, create an event with a name already taken +** by a non-event object, create an event with a name already taken +** by an event object. +** +** +**=========================================================*/ +#include <palsuite.h> + +#define SWAPPTR ((VOID *) (-1)) + +struct testCase +{ + LPSECURITY_ATTRIBUTES lpEventAttributes; + BOOL bManualReset; + BOOL bInitialState; + WCHAR lpName[MAX_PATH + 2]; + DWORD dwNameLen; + DWORD lastError; + BOOL bResult; +}; + +struct testCase testCases[]= +{ + {0, TRUE, FALSE, {'\0'}, 0, ERROR_SUCCESS, PASS}, + {0, TRUE, FALSE, {'\0'}, 5, ERROR_SUCCESS, PASS}, + {0, TRUE, FALSE, {'\0'}, 5, ERROR_ALREADY_EXISTS, PASS}, + {0, TRUE, FALSE, {'\0'}, 6, ERROR_INVALID_HANDLE, PASS}, + {0, TRUE, FALSE, {'\0'}, MAX_PATH - 1 - 60, ERROR_SUCCESS, PASS}, + {0, TRUE, FALSE, {'\0'}, MAX_PATH - 60, ERROR_SUCCESS, PASS}, +}; + +static HANDLE hEvent[sizeof(testCases)/sizeof(struct testCase)]; + +DWORD result[sizeof(testCases)/sizeof(struct testCase)]; + +int __cdecl main(int argc, char **argv) +{ + + BOOL bRet = TRUE; + WCHAR nonEventName[] = {'a','a','a','a','a','a','\0'}; + char name[MAX_PATH + 2]; + WCHAR *wName; + HANDLE hFMap = NULL; + HANDLE hUnnamedEvent; + DWORD dwRet; + int i; + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + hUnnamedEvent = CreateEventW(0, TRUE, FALSE, NULL); + + if ( NULL == hUnnamedEvent ) + { + bRet = FALSE; + Trace ( "PALSUITE ERROR: CreateEventW (%d, %d, %d, NULL) call " + "returned NULL.\nGetLastError returned %u.\n", 0, TRUE, FALSE, + GetLastError()); + goto done; + } + + if (!CloseHandle(hUnnamedEvent)) + { + bRet = FALSE; + Trace("PALSUITE ERROR: CreateEventW: CloseHandle(%lp); call " + "failed\nGetLastError returned '%u'.\n", hUnnamedEvent, + GetLastError()); + } + + /* Create non-event with the same name as one of the testCases */ + hFMap = CreateFileMappingW( SWAPPTR, NULL, PAGE_READONLY, 0, 1, + nonEventName ); + + if ( NULL == hFMap ) + { + bRet = FALSE; + Trace ( "PALSUITE ERROR: CreateFileMapping (%p, %p, %d, %d, %d, %S)" + " call returned NULL.\nGetLastError returned %u\n", + SWAPPTR, NULL, PAGE_READONLY, 0, 0, nonEventName, + GetLastError()); + } + + /* Create Events */ + for (i = 0; i < sizeof(testCases)/sizeof(struct testCase); i++) + { + /* create name */ + memset (name, '\0', MAX_PATH + 2); + memset (name, 'a', testCases[i].dwNameLen ); + + wName = convert(name); + + wcsncpy(testCases[i].lpName, wName, + testCases[i].dwNameLen); + + free(wName); + + SetLastError(ERROR_SUCCESS); + + hEvent[i] = CreateEventW( testCases[i].lpEventAttributes, + testCases[i].bManualReset, + testCases[i].bInitialState, + testCases[i].lpName); + + if (hEvent[i] != INVALID_HANDLE_VALUE) + { + DWORD dwError = GetLastError(); + + if (dwError != testCases[i].lastError) + { + bRet = FALSE; + Trace ("PALSUITE ERROR:\nCreateEvent(%lp, %d, %d, %S)" + "\nGetLastError returned '%u', it should have returned" + "'%d' at index '%d'.\n", testCases[i].lpEventAttributes, + testCases[i].bManualReset, testCases[i].bInitialState, + testCases[i].lpName, dwError, + testCases[i].lastError, i); + } + if ( ERROR_FILENAME_EXCED_RANGE == testCases[i].lastError ) + { + result [i] = 1; + } + if ( ERROR_INVALID_HANDLE == testCases[i].lastError ) + { + result [i] = 1; + } + /* + * If we expected the testcase to FAIL and it passed, + * report an error. + */ + if (testCases[i].bResult == FAIL) + { + bRet = FALSE; + Trace ("PALSUITE ERROR:\nCreateEvent(%lp, %d, %d, %S)" + "\nShould have returned INVALID_HANDLE_VALUE but " + "didn't at index '%d'.\n", + testCases[i].lpEventAttributes, + testCases[i].bManualReset, + testCases[i].bInitialState, + testCases[i].lpName, i); + } + /* + * If result hasn't been set already set it to 0 so all the + * resources will be freed. + */ + if (!result[i]) + { + result[i] = 0; + } + } + else + { + /* + * If we get an INVALID_HANDLE_VALUE and we expected the + * test case to pass, report an error. + */ + result[i] = 1; + + if (testCases[i].bResult == PASS) + { + bRet = FALSE; + Trace ("PALSUITE ERROR:\nCreateEvent(%lp, %d, %d, %S);" + "\nReturned INVALID_HANDLE_VALUE at index '%d'.\n", + testCases[i].lpEventAttributes, + testCases[i].bManualReset, testCases[i].bInitialState, + testCases[i].lpName, i); + } + } + } + + /* cleanup */ + for (i = 0; i < sizeof(testCases)/sizeof(struct testCase); i++) + { + if (result[i]) + { + continue; + } + dwRet = WaitForSingleObject ( hEvent[i], 0 ); + + if (dwRet != WAIT_TIMEOUT) + { + bRet = FALSE; + Trace("PALSUITE ERROR: CreateEventW:\nWaitForSingleObject (%lp, " + "%d) call failed at index %d .\nGetLastError returned " + "'%u'.\n", hEvent[i], 0, i, GetLastError()); + } + + if (!CloseHandle(hEvent[i])) + { + bRet = FALSE; + Trace("PALSUITE ERROR: CreateEventW: CloseHandle(%lp) call " + "failed at index %d\nGetLastError returned '%u'.\n", + hEvent[i], i, GetLastError()); + } + } + +done: + if (hFMap != NULL && !CloseHandle(hFMap)) + { + bRet = FALSE; + Trace("PALSUITE ERROR: CreateEventW: CloseHandle(%p) call " + "failed\nGetLastError returned '%u'.\n", hFMap, + GetLastError()); + } + + if (FALSE == bRet) + { + bRet = FAIL; + } + else + { + bRet = PASS; + } + + PAL_TerminateEx(bRet); + + return(bRet); + +} + + + diff --git a/src/pal/tests/palsuite/threading/CreateEventW/test3/testinfo.dat b/src/pal/tests/palsuite/threading/CreateEventW/test3/testinfo.dat new file mode 100644 index 0000000000..4776ed239f --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateEventW/test3/testinfo.dat @@ -0,0 +1,16 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateEventW +Name = Positive Test for CreateEventW +TYPE = DEFAULT +EXE1 = test3 +Description += Tests for CreateEventW. Create an unnamed event, create += an event with an empty name, create an event with a name longer than += MAX_PATH, MAX_PATH + 1, create an event with a name already taken += by a non-event object, create an event with a name already taken += by an event object. diff --git a/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/CMakeLists.txt new file mode 100644 index 0000000000..ef14ea5352 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) + diff --git a/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test1/CMakeLists.txt new file mode 100644 index 0000000000..ffdf13228e --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + CreateMutexA.c +) + +add_executable(paltest_createmutexa_releasemutex_test1 + ${SOURCES} +) + +add_dependencies(paltest_createmutexa_releasemutex_test1 coreclrpal) + +target_link_libraries(paltest_createmutexa_releasemutex_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test1/CreateMutexA.c b/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test1/CreateMutexA.c new file mode 100644 index 0000000000..52bab351fb --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test1/CreateMutexA.c @@ -0,0 +1,345 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: CreateMutexA_ReleaseMutex/test1/CreateMutexA.c +** +** Purpose: This test case tests whether a Mutex object created +** with CreateMutex really works by mutually excluding +** threads from accessing a data structure at the same +** time. Here we have a buffer that can be filled or +** emptied, we use a Mutex object to ensure that one +** operation cannot be started until the other is +** finished. If one operation detects that the other +** has not finished, it fails. There is a Producer +** thread which will try to fill the buffer 25 times, +** and a consumer thread which try to empty the buffer +** 25 times. If either the fill or empty operations +** fails because the Mutex failed to mutually exclude +** them, the corresponding thread will set an error +** flag and return. This will cause the test case to +** fail. +** +** To increase the probability of identifying problems, +** the Fill opeartion has been slowed down with a call +** to Sleep. This ensures that one operation will try +** to access the shared buffer while the other is in +** progress. +** +** NOTE: this test case also serves as a test case for +** WaitForSingleObject. +** +** +** Dependencies: CreateThread +** ReleaseMutex +** WaitForSingleObject +** WaitForMultipleObjects +** Sleep +** memset +** + +** +**=========================================================*/ + +#define UNICODE +#include <palsuite.h> + +/* Define some values that we will using many times */ +#define MAIN_BUF_SIZE 40 +#define NUM_OF_CYCLES 40 + +/* Buffer Operation return codes */ +#define OP_OK 0 +#define OP_ERR 1 +#define OP_NONE 2 + + +HANDLE hMutex; /* handle to mutex */ + +BOOL bProdErr; /* Producer error Flag */ +BOOL bConErr; /* Consumer error Flag */ + +/* Test Buffer */ +char Buffer[MAIN_BUF_SIZE]; + +/* + * EmptyBuffer implements the empty operation for test buffer. + */ +int +EmptyBuffer() +{ + int i; + + if ( WaitForSingleObject(hMutex, INFINITE) == WAIT_FAILED) + { + Fail("ERROR: WaitForSingleObject failed.\n"); + } + + /* Check to see if the buffer is already completely empty */ + for (i=0; i<MAIN_BUF_SIZE && Buffer[i] == 0; i++); + if (i == MAIN_BUF_SIZE) + { + /* Its empty so just return */ + if (ReleaseMutex(hMutex) == FALSE) + { + Fail("ERROR: ReleaseMutex Failed.\n"); + } + return OP_NONE; + } + + /* Its not empty so we must empty it. */ + for (i=0; i<MAIN_BUF_SIZE; i++) + { + /* Check for empty slots if we find one then the */ + /* fill operation did no finish. return an error */ + if (Buffer[i] == 0) + { + if (ReleaseMutex(hMutex) == FALSE) + { + Fail("ERROR: ReleaseMutex Failed.\n"); + } + return OP_ERR; + } + + Buffer[i] = 0; + } + + if (ReleaseMutex(hMutex) == FALSE) + { + Fail("ERROR: ReleaseMutex Failed.\n"); + } + return OP_OK; +} + +/* + * FillBuffer implements the fill operation for test buffer. + */ +int +FillBuffer() +{ + int i; + + if ( WaitForSingleObject(hMutex, INFINITE) == WAIT_FAILED) + { + Fail("ERROR: WaitForSingleObject failed.\n"); + } + + /* Check to see if the buffer is already completely full */ + for (i=0; i<MAIN_BUF_SIZE && Buffer[i] != 0; i++); + if (i == MAIN_BUF_SIZE) + { + /* Its full so just return */ + if (ReleaseMutex(hMutex) == FALSE) + { + Fail("ERROR: ReleaseMutex Failed.\n"); + } + return OP_NONE; + } + + /* Its not full so we must fill it. */ + for (i=0; i<MAIN_BUF_SIZE; i++) + { + /* Check for filled slots if we find one then the */ + /* empty operation did not finish. return an error */ + if (Buffer[i] == 1) + { + if (ReleaseMutex(hMutex) == FALSE) + { + Fail("ERROR: ReleaseMutex Failed.\n"); + } + return OP_ERR; + } + + Buffer[i] = 1; + Sleep(10); + } + + if (ReleaseMutex(hMutex) == FALSE) + { + Fail("ERROR: ReleaseMutex Failed.\n"); + } + return OP_OK; +} + + + + +/* + * Producer thread function. + */ +DWORD +Producer(LPVOID lpParam) +{ + int n = 0; + int ret; + + while (n < NUM_OF_CYCLES) + { + if (bConErr == TRUE) + { + /* The consumer ran into an error so we'll stop */ + return 0; + } + + ret = FillBuffer(); + + if (ret == OP_OK) + { + n++; + } + else if (ret == OP_ERR) + { + bProdErr = TRUE; + return 0; + } + } + + return 0; +} + +/* + * Consumer thread function. + */ +DWORD Consumer( LPVOID lpParam ) +{ + int n = 0; + int ret; + + while (n < NUM_OF_CYCLES) + { + if (bProdErr == TRUE) + { + /* The consumer ran into an error so we'll stop */ + return 0; + } + + ret = EmptyBuffer(); + + if (ret == OP_OK) + { + n++; + } + else if (ret == OP_ERR) + { + bConErr = TRUE; + return 0; + } + } + + return 0; +} + + +int __cdecl main (int argc, char **argv) +{ + DWORD dwThreadId; + DWORD dwWaitRet; + + HANDLE hThread1; /* handle to consumer thread */ + HANDLE hThread2; /* handle to producer thread */ + HANDLE handleArray[2]; + + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + /* Initialize our error flags */ + bProdErr = FALSE; + bConErr = FALSE; + + /* + * Initialize the Buffer to be empty + */ + memset(Buffer, 0, MAIN_BUF_SIZE); + + /* + * Create Mutex + */ + hMutex = CreateMutexA (NULL, FALSE, NULL); + + if (NULL == hMutex) + { + Fail("hMutex = CreateMutexA() - returned NULL\n" + "Failing Test.\nGetLastError returned %u\n", GetLastError()); + } + + + /* + * Create the Producer thread + */ + hThread1 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Producer, + 0, 0, &dwThreadId); + + if ( NULL == hThread1 ) + { + CloseHandle(hMutex); + + Fail("CreateThread() returned NULL. Failing test.\n" + "GetLastError returned %u\n", GetLastError()); + } + + /* + * Create the Consumer thread + */ + hThread2 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Consumer, + 0, 0, &dwThreadId); + + if ( NULL == hThread2 ) + { + CloseHandle(hMutex); + + /* Set the error flag and give thread1 some time to exit */ + bConErr = FALSE; + Sleep(250); + + Fail("CreateThread() returned NULL. Failing test.\n" + "GetLastError returned %u\n", GetLastError()); + } + + /* + * Wait for both threads to complete (Max 45 Seconds) + */ + handleArray[0] = hThread1; + handleArray[1] = hThread2; + dwWaitRet = WaitForMultipleObjects (2, handleArray, TRUE, 450000); + if (dwWaitRet == WAIT_FAILED) + { + Fail("ERROR: WaitForMultipleObjects failed.\n"); + } + else if (dwWaitRet == WAIT_TIMEOUT) + { + /* Set the error flags and give the threads some time to exit */ + bProdErr = FALSE; + bConErr = FALSE; + Sleep(250); + + Fail("ERROR: Timeout interval exceeded.\n"); + } + + /* + * Clean up + */ + if (CloseHandle(hThread1) == FALSE || + CloseHandle(hThread2) == FALSE || + CloseHandle(hMutex) == FALSE) + { + Fail("ERROR: CloseHandle failed.\n"); + } + + + /* + * Check our error flags + */ + if (bProdErr == TRUE || bConErr == TRUE) + { + Fail("ERROR: A collision occurred, so the mutex failed.\n"); + } + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test1/testinfo.dat b/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test1/testinfo.dat new file mode 100644 index 0000000000..829b7159ac --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test1/testinfo.dat @@ -0,0 +1,33 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateMutexA / ReleaseMutex +Name = Positive Test for CreateMutexA and ReleaseMutex +TYPE = DEFAULT +EXE1 = createmutexa +Description += This test cases test whether a Mutex object created += with CreateMutexA really works by mutually excluding += threads from accessing a data structure at the same += time. Here we have a buffer that can be filled or += emptied, we use a Mutex object to ensure that one += operation cannot be started until the other is += finished. If one operation detects that the other += has not finished, it fails. There is a Producer += thread which will try to fill the buffer 25 times, += and a consumer thread which try to empty the buffer += 25 times. If either the fill or empty operations += fails because the Mutex failed to mutually exclude += then, the corresponding thread will set an error += flag and return. This will cause the test case to += fail. += To increase the probability of identifying problems, += the Fill opeartion has been slowed dowm with a call += to Sleep. This ensures that one operation will try += to access the shared buffer while the other is in += progress. += NOTE: this test case also serves as a test case for += WaitForSingleObject. diff --git a/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test2/CMakeLists.txt new file mode 100644 index 0000000000..925c5d41f6 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + CreateMutexA.c +) + +add_executable(paltest_createmutexa_releasemutex_test2 + ${SOURCES} +) + +add_dependencies(paltest_createmutexa_releasemutex_test2 coreclrpal) + +target_link_libraries(paltest_createmutexa_releasemutex_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test2/CreateMutexA.c b/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test2/CreateMutexA.c new file mode 100644 index 0000000000..3b83ba9674 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test2/CreateMutexA.c @@ -0,0 +1,331 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: CreateMutexA_ReleaseMutex/test2/CreateMutexA.c +** +** Purpose: This test case tests the following things +** - Creation of named Mutexes +** - Creating multiple handles to a single named Mutex +** - Ensuring that these handles work interchangeably +** - Setting bInitialOwnerFlag to TRUE will cause the +** initial call to a Wait function on the same Mutex +** to actually wait. +** - Waiting on a Mutex that a thread already owns does +** not block. +** - Create Named mutex with empty string ("") +** - Create Named mutex with string of MAX_LONGPATH length +** - Calling RelaseMutex with invalid Mutex handles and +** valid but unowned Mutexes. +** +** Dependencies: CreateThread +** ReleaseMutex +** WaitForSingleObject +** CloseHandle +** Sleep +** memset +** + +** +**=========================================================*/ + +#define UNICODE +#include <palsuite.h> + +const char *szMutex = "MyMutex"; +const char *szEmpty = ""; + +/* Function Prototypes */ +BOOL TestNamedMutex(const char *szMutexName); +DWORD NamedMutexThread(LPVOID lpParam); +BOOL NegativeReleaseMutexTests(); + +struct ThreadData +{ + HANDLE hMutex; + BOOL bReturnCode; +}; +typedef struct ThreadData THREADDATA; + + +int __cdecl main (int argc, char **argv) +{ + BOOL bFailures = FALSE; + char *szMaxPath; + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + + /* + * Test named Mutexes with ordinary string + */ + + if (!TestNamedMutex(szMutex)) + { + bFailures = TRUE; + } + + + /* + * Test named Mutexes with empty ("") string + */ + + if (!TestNamedMutex(szEmpty)) + { + bFailures = TRUE; + } + + + /* + * Test named Mutexes with string of length MAX_LONGPATHPATH + */ + + szMaxPath = (char *)malloc(MAX_LONGPATH+2); + memset(szMaxPath, 'A', MAX_LONGPATH-60); + szMaxPath[MAX_LONGPATH-60] = 0; + + if (!TestNamedMutex(szMaxPath)) + { + bFailures = TRUE; + } + + free(szMaxPath); + + + /* + * Run some negative tests on ReleaseMutex + */ + + if (!NegativeReleaseMutexTests()) + { + bFailures = TRUE; + } + + + /* + * If there were any failures, then abort with a call to Fail + */ + + if (bFailures == TRUE) + { + Fail("ERROR: There some failures in the Mutex tests.\n"); + } + + PAL_Terminate(); + return ( PASS ); +} + + +/* + * Testing Function + * + * Try to get multiple handles to a named Mutex and test + * to make sure they actually refer to same Mutex object. + */ +BOOL TestNamedMutex(const char *szMutexName) +{ + DWORD dwData; + HANDLE hMutex1; + HANDLE hMutex2; + HANDLE hThread; + THREADDATA threadData; + + /* Create a mutex and take ownership immediately */ + hMutex1 = CreateMutexA (NULL, TRUE, szMutexName); + + if (NULL == hMutex1) + { + Trace("ERROR: CreateMutexA #1 failed. GetLastError returned %u\n", + GetLastError()); + return FALSE; + } + + /* Try to wait on the Mutex we just created. We should not block. */ + if (WaitForSingleObject(hMutex1, 1000) == WAIT_TIMEOUT) + { + Trace("WaitForSingleObject blocked on a Mutex that we owned.\n"); + return FALSE; + } + /* We have to call ReleaseMutex here because of the Wait */ + if (ReleaseMutex(hMutex1) == FALSE) + { + Trace("ReleaseMutex Failed.\n"); + return FALSE; + } + + /* Get a second handle to the same mutex */ + hMutex2 = CreateMutexA (NULL, FALSE, szMutexName); + + if (NULL == hMutex2) + { + Trace("ERROR: CreateMutex #2 failed. GetLastError returned %u\n", + GetLastError()); + free(szMutexName); + return FALSE; + } + + /* + * Create a thread that will Wait on the second handle. + */ + threadData.hMutex = hMutex2; + hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)NamedMutexThread, + (LPVOID)&threadData, 0, &dwData); + + if (NULL == hThread) + { + Trace("ERROR: CreateThread failed. GetLastError returned %u\n", + GetLastError()); + return FALSE; + } + + /* Give the thread a little time to execute & wait*/ + Sleep(500); + + /* Signal the the first handle */ + if (ReleaseMutex(hMutex1) == FALSE) + { + Trace("ReleaseMutex Failed.\n"); + return FALSE; + } + + /* Give the thread some time to finish */ + Sleep(2000); + + /* Clean Up */ + if (CloseHandle(hMutex1) == FALSE || + CloseHandle(hMutex2) == FALSE || + CloseHandle(hThread) == FALSE) + { + Trace("ERROR: CloseHandle failed.\n"); + return FALSE; + } + + /* Check the return code to see if signalling the first */ + /* Mutex handle woke up the thread which was Waiting on */ + /* the second handle. */ + if (threadData.bReturnCode != FALSE) + { + Trace("ERROR: The handles did not refer to the same Mutex object.\n"); + return FALSE; + } + + return TRUE; +} + + +/* + * Thread function used with above testing function. + */ +DWORD NamedMutexThread(LPVOID lpParam) +{ + BOOL bTimedOut = FALSE; + THREADDATA *lpThreadData = (THREADDATA *)lpParam; + + /* Wait on the Mutex that was passed to us */ + if (WaitForSingleObject(lpThreadData->hMutex, 10000) == WAIT_TIMEOUT) + { + /* The Mutex was not signaled in the allotted time */ + bTimedOut = TRUE; + } + if (ReleaseMutex(lpThreadData->hMutex) == FALSE) + { + Trace("ERROR: ReleaseMutex failed.\n"); + lpThreadData->bReturnCode = FALSE; + return 0; + } + + /* Indicate whether we timed out Waiting on the Mutex */ + lpThreadData->bReturnCode = bTimedOut; + + return 0; +} + + +/* + * Testing Function + * + * Try some negative tests on ReleaseMutex + */ +BOOL NegativeReleaseMutexTests() +{ + HANDLE hMutex; + BOOL bRet; + BOOL bResults = TRUE; + + + /* + * Try calling ReleaseMutex on a null handle + */ + hMutex = 0; + bRet = ReleaseMutex(hMutex); + + if (bRet != 0) + { + Trace("Error: ReleaseMutex accepted null handle.\n"); + bResults = FALSE; + } + + + /* + * Try calling ReleaseMutex on an handle that we don't own + */ + hMutex = CreateMutexA (NULL, TRUE, NULL); + if (hMutex == 0) + { + Trace("Error: CreateMutex failed.\n"); + bResults = FALSE; + } + + bRet = ReleaseMutex(hMutex); + bRet = ReleaseMutex(hMutex); + + if (bRet != FALSE) + { + Trace("Error: ReleaseMutex accepted unowned handle.\n"); + bResults = FALSE; + } + + if (CloseHandle(hMutex) == FALSE) + { + Trace("Error: CloseHandle failed.\n"); + bResults = FALSE; + } + + + + /* + * Try calling ReleaseMutex on an handle that has been closed + */ + hMutex = CreateMutexA (NULL, TRUE, NULL); + if (hMutex == 0) + { + Trace("Error: CreateMutex failed.\n"); + bResults = FALSE; + } + + if (ReleaseMutex(hMutex) == FALSE) + { + Trace("Error: ReleaseMutex failed.\n"); + bResults = FALSE; + } + if (CloseHandle(hMutex) == FALSE) + { + Trace("Error: CloseHandle failed.\n"); + bResults = FALSE; + } + + bRet = ReleaseMutex(hMutex); + + if (bRet != FALSE) + { + Trace("Error: ReleaseMutex accepted invalid handle.\n"); + bResults = FALSE; + } + + return bResults; +} diff --git a/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test2/testinfo.dat b/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test2/testinfo.dat new file mode 100644 index 0000000000..7e37528c15 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateMutexA_ReleaseMutex/test2/testinfo.dat @@ -0,0 +1,24 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateMutexA / ReleaseMutex +Name = Basic validity Tests for CreateMutexA / ReleaseMutex +TYPE = DEFAULT +EXE1 = createmutexa +Description += This test case tests the following things += - Creation of named Mutexes += - Creating multiple handles to a single named Mutex += - Ensuring that these handles work interchangeably += - Setting bInitialOwnerFlag to TRUE will cause the += initial call to a Wait function on the same Mutex += to actually wait. += - Waiting on a Mutex that a thread already owns should += not block. += - Create Named mutex with empty string ("") += - Create Named mutex with string of MAX_PATH length += - Calling RelaseMutex with invalid Mutex handles and += valid but unowned Mutexes. diff --git a/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/CMakeLists.txt new file mode 100644 index 0000000000..ef14ea5352 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) + diff --git a/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test1/CMakeLists.txt new file mode 100644 index 0000000000..a73ee045a3 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + CreateMutexW.c +) + +add_executable(paltest_createmutexw_releasemutex_test1 + ${SOURCES} +) + +add_dependencies(paltest_createmutexw_releasemutex_test1 coreclrpal) + +target_link_libraries(paltest_createmutexw_releasemutex_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test1/CreateMutexW.c b/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test1/CreateMutexW.c new file mode 100644 index 0000000000..c21bfb6a50 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test1/CreateMutexW.c @@ -0,0 +1,345 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: CreateMutexW_ReleaseMutex/test1/CreateMutexW.c +** +** Purpose: This test case tests whether a Mutex object created +** with CreateMutex really works by mutually excluding +** threads from accessing a data structure at the same +** time. Here we have a buffer that can be filled or +** emptied, we use a Mutex object to ensure that one +** operation cannot be started until the other is +** finished. If one operation detects that the other +** has not finished, it fails. There is a Producer +** thread which will try to fill the buffer 25 times, +** and a consumer thread which try to empty the buffer +** 25 times. If either the fill or empty operations +** fails because the Mutex failed to mutually exclude +** them, the corresponding thread will set an error +** flag and return. This will cause the test case to +** fail. +** +** To increase the probability of identifying problems, +** the Fill opeartion has been slowed dowm with a call +** to Sleep. This ensures that one operation will try +** to access the shared buffer while the other is in +** progress. +** +** NOTE: this test case also serves as a test case for +** WaitForSingleObject. +** +** +** Dependencies: CreateThread +** ReleaseMutex +** WaitForSingleObject +** WaitForMultipleObjects +** Sleep +** memset +** + +** +**=========================================================*/ + +#define UNICODE +#include <palsuite.h> + +/* Define some values that we will using many times */ +#define MAIN_BUF_SIZE 40 +#define NUM_OF_CYCLES 40 + +/* Buffer Operation return codes */ +#define OP_OK 0 +#define OP_ERR 1 +#define OP_NONE 2 + + +HANDLE hMutex; /* handle to mutex */ + +BOOL bProdErr; /* Producer error Flag */ +BOOL bConErr; /* Consumer error Flag */ + +/* Test Buffer */ +char Buffer[MAIN_BUF_SIZE]; + +/* + * EmptyBuffer implements the empty operation for test buffer. + */ +int +EmptyBuffer() +{ + int i; + + if ( WaitForSingleObject(hMutex, INFINITE) == WAIT_FAILED) + { + Fail("ERROR: WaitForSingleObject failed.\n"); + } + + /* Check to see if the buffer is already completely empty */ + for (i=0; i<MAIN_BUF_SIZE && Buffer[i] == 0; i++); + if (i == MAIN_BUF_SIZE) + { + /* Its empty so just return */ + if (ReleaseMutex(hMutex) == FALSE) + { + Fail("ERROR: ReleaseMutex Failed.\n"); + } + return OP_NONE; + } + + /* Its not empty so we must empty it. */ + for (i=0; i<MAIN_BUF_SIZE; i++) + { + /* Check for empty slots if we find one then the */ + /* fill operation did no finish. return an error */ + if (Buffer[i] == 0) + { + if (ReleaseMutex(hMutex) == FALSE) + { + Fail("ERROR: ReleaseMutex Failed.\n"); + } + return OP_ERR; + } + + Buffer[i] = 0; + } + + if (ReleaseMutex(hMutex) == FALSE) + { + Fail("ERROR: ReleaseMutex Failed.\n"); + } + return OP_OK; +} + +/* + * FillBuffer implements the fill operation for test buffer. + */ +int +FillBuffer() +{ + int i; + + if ( WaitForSingleObject(hMutex, INFINITE) == WAIT_FAILED) + { + Fail("ERROR: WaitForSingleObject failed.\n"); + } + + /* Check to see if the buffer is already completely full */ + for (i=0; i<MAIN_BUF_SIZE && Buffer[i] != 0; i++); + if (i == MAIN_BUF_SIZE) + { + /* Its full so just return */ + if (ReleaseMutex(hMutex) == FALSE) + { + Fail("ERROR: ReleaseMutex Failed.\n"); + } + return OP_NONE; + } + + /* Its not full so we must fill it. */ + for (i=0; i<MAIN_BUF_SIZE; i++) + { + /* Check for filled slots if we find one then the */ + /* empty operation did not finish. return an error */ + if (Buffer[i] == 1) + { + if (ReleaseMutex(hMutex) == FALSE) + { + Fail("ERROR: ReleaseMutex Failed.\n"); + } + return OP_ERR; + } + + Buffer[i] = 1; + Sleep(10); + } + + if (ReleaseMutex(hMutex) == FALSE) + { + Fail("ERROR: ReleaseMutex Failed.\n"); + } + return OP_OK; +} + + + + +/* + * Producer thread function. + */ +DWORD +Producer(LPVOID lpParam) +{ + int n = 0; + int ret; + + while (n < NUM_OF_CYCLES) + { + if (bConErr == TRUE) + { + /* The consumer ran into an error so we'll stop */ + return 0; + } + + ret = FillBuffer(); + + if (ret == OP_OK) + { + n++; + } + else if (ret == OP_ERR) + { + bProdErr = TRUE; + return 0; + } + } + + return 0; +} + +/* + * Consumer thread function. + */ +DWORD Consumer( LPVOID lpParam ) +{ + int n = 0; + int ret; + + while (n < NUM_OF_CYCLES) + { + if (bProdErr == TRUE) + { + /* The consumer ran into an error so we'll stop */ + return 0; + } + + ret = EmptyBuffer(); + + if (ret == OP_OK) + { + n++; + } + else if (ret == OP_ERR) + { + bConErr = TRUE; + return 0; + } + } + + return 0; +} + + +int __cdecl main (int argc, char **argv) +{ + DWORD dwThreadId; + DWORD dwWaitRet; + + HANDLE hThread1; /* handle to consumer thread */ + HANDLE hThread2; /* handle to producer thread */ + HANDLE handleArray[2]; + + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + /* Initialize our error flags */ + bProdErr = FALSE; + bConErr = FALSE; + + /* + * Initialize the Buffer to be empty + */ + memset(Buffer, 0, MAIN_BUF_SIZE); + + /* + * Create Mutex + */ + hMutex = CreateMutexW (NULL, FALSE, NULL); + + if (NULL == hMutex) + { + Fail("hMutex = CreateMutexW() - returned NULL\n" + "Failing Test.\nGetLastError returned %u\n", GetLastError()); + } + + + /* + * Create the Producer thread + */ + hThread1 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Producer, + 0, 0, &dwThreadId); + + if ( NULL == hThread1 ) + { + CloseHandle(hMutex); + + Fail("CreateThread() returned NULL. Failing test.\n" + "GetLastError returned %u\n", GetLastError()); + } + + /* + * Create the Consumer thread + */ + hThread2 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Consumer, + 0, 0, &dwThreadId); + + if ( NULL == hThread2 ) + { + CloseHandle(hMutex); + + /* Set the error flag and give thread1 some time to exit */ + bConErr = FALSE; + Sleep(250); + + Fail("CreateThread() returned NULL. Failing test.\n" + "GetLastError returned %u\n", GetLastError()); + } + + /* + * Wait for both threads to complete (Max 45 Seconds) + */ + handleArray[0] = hThread1; + handleArray[1] = hThread2; + dwWaitRet = WaitForMultipleObjects (2, handleArray, TRUE, 450000); + if (dwWaitRet == WAIT_FAILED) + { + Fail("ERROR: WaitForMultipleObjects failed.\n"); + } + else if (dwWaitRet == WAIT_TIMEOUT) + { + /* Set the error flags and give the threads some time to exit */ + bProdErr = FALSE; + bConErr = FALSE; + Sleep(250); + + Fail("ERROR: Timeout interval exceeded.\n"); + } + + /* + * Clean up + */ + if (CloseHandle(hThread1) == FALSE || + CloseHandle(hThread2) == FALSE || + CloseHandle(hMutex) == FALSE) + { + Fail("ERROR: CloseHandle failed.\n"); + } + + + /* + * Check our error flags + */ + if (bProdErr == TRUE || bConErr == TRUE) + { + Fail("ERROR: A collision occurred, so the mutex failed.\n"); + } + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test1/testinfo.dat b/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test1/testinfo.dat new file mode 100644 index 0000000000..19ea934dff --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test1/testinfo.dat @@ -0,0 +1,33 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateMutexW / ReleaseMutex +Name = Positive Test for CreateMutexW and ReleaseMutex +TYPE = DEFAULT +EXE1 = createmutexw +Description += This test cases test whether a Mutex object created += with CreateMutexW really works by mutually excluding += threads from accessing a data structure at the same += time. Here we have a buffer that can be filled or += emptied, we use a Mutex object to ensure that one += operation cannot be started until the other is += finished. If one operation detects that the other += has not finished, it fails. There is a Producer += thread which will try to fill the buffer 25 times, += and a consumer thread which try to empty the buffer += 25 times. If either the fill or empty operations += fails because the Mutex failed to mutually exclude += then, the corresponding thread will set an error += flag and return. This will cause the test case to += fail. += To increase the probability of identifying problems, += the Fill opeartion has been slowed dowm with a call += to Sleep. This ensures that one operation will try += to access the shared buffer while the other is in += progress. += NOTE: this test case also serves as a test case for += WaitForSingleObject. diff --git a/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test2/CMakeLists.txt new file mode 100644 index 0000000000..b40569b3f5 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + CreateMutexW.c +) + +add_executable(paltest_createmutexw_releasemutex_test2 + ${SOURCES} +) + +add_dependencies(paltest_createmutexw_releasemutex_test2 coreclrpal) + +target_link_libraries(paltest_createmutexw_releasemutex_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test2/CreateMutexW.c b/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test2/CreateMutexW.c new file mode 100644 index 0000000000..41b7798a6e --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test2/CreateMutexW.c @@ -0,0 +1,340 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: CreateMutexW_ReleaseMutex/test2/CreateMutexW.c +** +** Purpose: This test case tests the following things +** - Creation of named Mutexes +** - Creating multiple handles to a single named Mutex +** - Ensuring that these handles work interchangeably +** - Setting bInitialOwnerFlag to TRUE will cause the +** initial call to a Wait function on the same Mutex +** to actually wait. +** - Waiting on a Mutex that a thread already owns does +** not block. +** - Create Named mutex with empty string ("") +** - Create Named mutex with string of MAX_LONGPATH length +** - Calling RelaseMutex with invalid Mutex handles and +** valid but unowned Mutexes. +** +** Dependencies: CreateThread +** ReleaseMutex +** WaitForSingleObject +** CloseHandle +** Sleep +** memset +** + +** +**=========================================================*/ + +#define UNICODE +#include <palsuite.h> + +const char *szMutex = "MyMutex"; +const char *szEmpty = ""; + +/* Function Prototypes */ +BOOL TestNamedMutex(const char *szMutexName); +DWORD NamedMutexThread(LPVOID lpParam); +BOOL NegativeReleaseMutexTests(); + +struct ThreadData +{ + HANDLE hMutex; + BOOL bReturnCode; +}; +typedef struct ThreadData THREADDATA; + + +int __cdecl main (int argc, char **argv) +{ + BOOL bFailures = FALSE; + char *szMaxPath; + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + + /* + * Test named Mutexes with ordinary string + */ + + if (!TestNamedMutex(szMutex)) + { + bFailures = TRUE; + } + + + /* + * Test named Mutexes with empty ("") string + */ + + if (!TestNamedMutex(szEmpty)) + { + bFailures = TRUE; + } + + + /* + * Test named Mutexes with string of length MAX_LONGPATH + */ + + szMaxPath = (char *)malloc(MAX_LONGPATH+2); + memset(szMaxPath, 'A', MAX_LONGPATH-60); + szMaxPath[MAX_LONGPATH-60] = 0; + + if (!TestNamedMutex(szMaxPath)) + { + bFailures = TRUE; + } + + free(szMaxPath); + + + /* + * Run some negative tests on ReleaseMutex + */ + + if (!NegativeReleaseMutexTests()) + { + bFailures = TRUE; + } + + + /* + * If there were any failures, then abort with a call to Fail + */ + + if (bFailures == TRUE) + { + Fail("ERROR: There some failures in the Mutex tests.\n"); + } + + PAL_Terminate(); + return ( PASS ); +} + + +/* + * Testing Function + * + * Try to get multiple handles to a named Mutex and test + * to make sure they actually refer to same Mutex object. + */ +BOOL TestNamedMutex(const char *szMutexName) +{ + DWORD dwData; + HANDLE hMutex1; + HANDLE hMutex2; + HANDLE hThread; + WCHAR *swzMutexName; + THREADDATA threadData; + + /* Convert the Mutex name to wide characters */ + swzMutexName = convert((char *)szMutexName); + + /* Create a mutex and take ownership immediately */ + hMutex1 = CreateMutexW (NULL, TRUE, swzMutexName); + + if (NULL == hMutex1) + { + Trace("ERROR: CreateMutex #1 failed. GetLastError returned %u\n", + GetLastError()); + free(swzMutexName); + return FALSE; + } + + /* Try to wait on the Mutex we just created. We should not block. */ + if (WaitForSingleObject(hMutex1, 1000) == WAIT_TIMEOUT) + { + Trace("WaitForSingleObject blocked on a Mutex that we owned.\n"); + free(swzMutexName); + return FALSE; + } + /* We have to call ReleaseMutex here because of the Wait */ + if (ReleaseMutex(hMutex1) == FALSE) + { + Trace("ReleaseMutex Failed.\n"); + return FALSE; + } + + /* Get a second handle to the same mutex */ + hMutex2 = CreateMutexW (NULL, FALSE, swzMutexName); + + if (NULL == hMutex2) + { + Trace("ERROR: CreateMutex #2 failed. GetLastError returned %u\n", + GetLastError()); + free(swzMutexName); + return FALSE; + } + + /* Get rid of the wide character string */ + free(swzMutexName); + + /* + * Create a thread that will Wait on the second handle. + */ + threadData.hMutex = hMutex2; + hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)NamedMutexThread, + (LPVOID)&threadData, 0, &dwData); + + if (NULL == hThread) + { + Trace("ERROR: CreateThread failed. GetLastError returned %u\n", + GetLastError()); + return FALSE; + } + + /* Give the thread a little time to execute & wait*/ + Sleep(500); + + /* Signal the the first handle */ + if (ReleaseMutex(hMutex1) == FALSE) + { + Trace("ReleaseMutex Failed.\n"); + return FALSE; + } + + /* Give the thread some time to finish */ + Sleep(2000); + + /* Clean Up */ + if (CloseHandle(hMutex1) == FALSE || + CloseHandle(hMutex2) == FALSE || + CloseHandle(hThread) == FALSE) + { + Trace("ERROR: CloseHandle failed.\n"); + return FALSE; + } + + /* Check the return code to see if signalling the first */ + /* Mutex handle woke up the thread which was Waiting on */ + /* the second handle. */ + if (threadData.bReturnCode != FALSE) + { + Trace("ERROR: The handles did not refer to the same Mutex object.\n"); + return FALSE; + } + + return TRUE; +} + + +/* + * Thread function used with above testing function. + */ +DWORD NamedMutexThread(LPVOID lpParam) +{ + BOOL bTimedOut = FALSE; + THREADDATA *lpThreadData = (THREADDATA *)lpParam; + + /* Wait on the Mutex that was passed to us */ + if (WaitForSingleObject(lpThreadData->hMutex, 10000) == WAIT_TIMEOUT) + { + /* The Mutex was not signaled in the allotted time */ + bTimedOut = TRUE; + } + if (ReleaseMutex(lpThreadData->hMutex) == FALSE) + { + Trace("ERROR: ReleaseMutex failed.\n"); + lpThreadData->bReturnCode = FALSE; + return 0; + } + + /* Indicate whether we timed out Waiting on the Mutex */ + lpThreadData->bReturnCode = bTimedOut; + + return 0; +} + + +/* + * Testing Function + * + * Try some negative tests on ReleaseMutex + */ +BOOL NegativeReleaseMutexTests() +{ + HANDLE hMutex; + BOOL bRet; + BOOL bResults = TRUE; + + + /* + * Try calling ReleaseMutex on a null handle + */ + hMutex = 0; + bRet = ReleaseMutex(hMutex); + + if (bRet != 0) + { + Trace("Error: ReleaseMutex accepted null handle.\n"); + bResults = FALSE; + } + + + /* + * Try calling ReleaseMutex on an handle that we don't own + */ + hMutex = CreateMutexW (NULL, TRUE, NULL); + if (hMutex == 0) + { + Trace("Error: CreateMutex failed.\n"); + bResults = FALSE; + } + + bRet = ReleaseMutex(hMutex); + bRet = ReleaseMutex(hMutex); + + if (bRet != FALSE) + { + Trace("Error: ReleaseMutex accepted unowned handle.\n"); + bResults = FALSE; + } + + if (CloseHandle(hMutex) == FALSE) + { + Trace("Error: CloseHandle failed.\n"); + bResults = FALSE; + } + + + + /* + * Try calling ReleaseMutex on an handle that has been closed + */ + hMutex = CreateMutexW (NULL, TRUE, NULL); + if (hMutex == 0) + { + Trace("Error: CreateMutex failed.\n"); + bResults = FALSE; + } + + if (ReleaseMutex(hMutex) == FALSE) + { + Trace("Error: ReleaseMutex failed.\n"); + bResults = FALSE; + } + if (CloseHandle(hMutex) == FALSE) + { + Trace("Error: CloseHandle failed.\n"); + bResults = FALSE; + } + + bRet = ReleaseMutex(hMutex); + + if (bRet != FALSE) + { + Trace("Error: ReleaseMutex accepted invalid handle.\n"); + bResults = FALSE; + } + + return bResults; +} diff --git a/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test2/testinfo.dat b/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test2/testinfo.dat new file mode 100644 index 0000000000..c5769e3ad3 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test2/testinfo.dat @@ -0,0 +1,24 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateMutexW / ReleaseMutex +Name = Basic validity Tests for CreateMutexW / ReleaseMutex +TYPE = DEFAULT +EXE1 = createmutexw +Description += This test case tests the following things += - Creation of named Mutexes += - Creating multiple handles to a single named Mutex += - Ensuring that these handles work interchangeably += - Setting bInitialOwnerFlag to TRUE will cause the += initial call to a Wait function on the same Mutex += to actually wait. += - Waiting on a Mutex that a thread already owns should += not block. += - Create Named mutex with empty string ("") += - Create Named mutex with string of MAX_PATH length += - Calling RelaseMutex with invalid Mutex handles and += valid but unowned Mutexes. diff --git a/src/pal/tests/palsuite/threading/CreateProcessA/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateProcessA/CMakeLists.txt new file mode 100644 index 0000000000..ef14ea5352 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessA/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) + diff --git a/src/pal/tests/palsuite/threading/CreateProcessA/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateProcessA/test1/CMakeLists.txt new file mode 100644 index 0000000000..67e53edccd --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessA/test1/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(TESTSOURCES + parentProcess.c +) + +add_executable(paltest_createprocessa_test1 + ${TESTSOURCES} +) + +add_dependencies(paltest_createprocessa_test1 coreclrpal) + +target_link_libraries(paltest_createprocessa_test1 + pthread + m + coreclrpal +) + + +set(HELPERSOURCES + childProcess.c +) + +add_executable(paltest_createprocessa_test1_child + ${HELPERSOURCES} +) + +add_dependencies(paltest_createprocessa_test1_child coreclrpal) + +target_link_libraries(paltest_createprocessa_test1_child + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateProcessA/test1/childProcess.c b/src/pal/tests/palsuite/threading/CreateProcessA/test1/childProcess.c new file mode 100644 index 0000000000..ccbb050c04 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessA/test1/childProcess.c @@ -0,0 +1,131 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: CreateProcessA/test1/childprocess.c +** +** Purpose: Test to ensure CreateProcessA starts a new process. This test +** launches a child process, and examines a file written by the child. +** This code is the child code. +** +** Dependencies: GetCurrentDirectory +** strlen +** fopen +** fclose +** fprintf +** + +** +**=========================================================*/ + +#include <palsuite.h> + +const char *szCommonFileA = "childdata.tmp"; + +const char *szPathDelimA = "\\"; + +const char *szCommonStringA = "058d2d057111a313aa82401c2e856002\0"; + +/* + * Take two wide strings representing file and directory names + * (dirName, fileName), join the strings with the appropriate path + * delimiter and populate a wide character buffer (absPathName) with + * the resulting string. + * + * Returns: The number of wide characters in the resulting string. + * 0 is returned on Error. + */ +int +mkAbsoluteFilenameA ( + LPSTR dirName, + DWORD dwDirLength, + LPCSTR fileName, + DWORD dwFileLength, + LPSTR absPathName ) +{ + extern const char *szPathDelimA; + + DWORD sizeDN, sizeFN, sizeAPN; + + sizeDN = strlen( dirName ); + sizeFN = strlen( fileName ); + sizeAPN = (sizeDN + 1 + sizeFN + 1); + + /* insure ((dirName + DELIM + fileName + \0) =< _MAX_PATH ) */ + if ( sizeAPN > _MAX_PATH ) + { + return ( 0 ); + } + + strncpy(absPathName, dirName, dwDirLength +1); + strncpy(absPathName, szPathDelimA, 2); + strncpy(absPathName, fileName, dwFileLength +1); + + return (sizeAPN); + +} + +int __cdecl main( int argc, char **argv ) +{ + + static FILE * fp; + + DWORD dwFileLength; + DWORD dwDirLength; + DWORD dwSize; + + char szDirNameA[_MAX_DIR]; + char szAbsPathNameA[_MAX_PATH]; + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + dwDirLength = GetCurrentDirectory( _MAX_PATH, szDirNameA ); + + if (0 == dwDirLength) + { + Fail ("GetCurrentDirectory call failed. Could not get " + "current working directory\n. Exiting.\n"); + } + + dwFileLength = strlen( szCommonFileA ); + + dwSize = mkAbsoluteFilenameA( szDirNameA, dwDirLength, szCommonFileA, + dwFileLength, szAbsPathNameA ); + + if (0 == dwSize) + { + Fail ("Palsuite Code: mkAbsoluteFilename() call failed. Could " + "not build absolute path name to file\n. Exiting.\n"); + } + + if ( NULL == ( fp = fopen ( szAbsPathNameA , "w+" ) ) ) + { + /* + * A return value of NULL indicates an error condition or an + * EOF condition + */ + Fail ("%s unable to open %s for writing. Exiting.\n", argv[0] + , szAbsPathNameA ); + } + + if ( 0 >= ( fprintf ( fp, "%s", szCommonStringA ))) + { + Fail("%s unable to write to %s. Exiting.\n", argv[0] + , szAbsPathNameA ); + } + + if (0 != (fclose ( fp ))) + { + Fail ("%s unable to close file %s. Pid may not be " + "written to file. Exiting.\n", argv[0], szAbsPathNameA ); + } + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/CreateProcessA/test1/parentProcess.c b/src/pal/tests/palsuite/threading/CreateProcessA/test1/parentProcess.c new file mode 100644 index 0000000000..b0c5808a7e --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessA/test1/parentProcess.c @@ -0,0 +1,201 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: CreateProcessA/test1/parentprocess.c +** +** Purpose: Test to ensure CreateProcessA starts a new process. This test +** launches a child process, and examines a file written by the child. +** This process (the parent process) reads the file created by the child and +** compares the value the child wrote to the file. (a const char *) +** +** Dependencies: GetCurrentDirectory +** strlen +** WaitForSingleObject +** fopen +** fclose +** Fail +** + +** +**=========================================================*/ + +#include <palsuite.h> + +const char *szCommonFileA = "childdata.tmp"; + +const char *szChildFileA = "paltest_createprocessa_test1_child"; + +const char *szPathDelimA = "\\"; + +const char *szCommonStringA = "058d2d057111a313aa82401c2e856002\0"; + +/* + * Take two wide strings representing file and directory names + * (dirName, fileName), join the strings with the appropriate path + * delimiter and populate a wide character buffer (absPathName) with + * the resulting string. + * + * Returns: The number of wide characters in the resulting string. + * 0 is returned on Error. + */ +int +mkAbsoluteFilenameA ( + LPSTR dirName, + DWORD dwDirLength, + LPCSTR fileName, + DWORD dwFileLength, + LPSTR absPathName ) +{ + extern const char *szPathDelimA; + + DWORD sizeDN, sizeFN, sizeAPN; + + sizeDN = strlen( dirName ); + sizeFN = strlen( fileName ); + sizeAPN = (sizeDN + 1 + sizeFN + 1); + + /* insure ((dirName + DELIM + fileName + \0) =< _MAX_PATH ) */ + if ( sizeAPN > _MAX_PATH ) + { + return ( 0 ); + } + + strncpy(absPathName, dirName, dwDirLength +1); + strncpy(absPathName, szPathDelimA, 2); + strncpy(absPathName, fileName, dwFileLength +1); + + return (sizeAPN); + +} + +int __cdecl main( int argc, char **argv ) + +{ + + STARTUPINFO si; + PROCESS_INFORMATION pi; + + static FILE * fp; + + DWORD dwFileLength; + DWORD dwDirLength; + DWORD dwSize; + + size_t cslen; + + char szReadStringA[256]; + + char szDirNameA[_MAX_DIR]; + char absPathBuf[_MAX_PATH]; + char *szAbsPathNameA; + + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + ZeroMemory ( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory ( &pi, sizeof(pi) ); + + szAbsPathNameA=&absPathBuf[0]; + dwFileLength = strlen( szChildFileA ); + + dwDirLength = GetCurrentDirectory(_MAX_PATH, szDirNameA); + + if (0 == dwDirLength) + { + Fail ("GetCurrentDirectory call failed. Could not get " + "current working directory\n. Exiting.\n"); + } + + dwSize = mkAbsoluteFilenameA( szDirNameA, dwDirLength, szChildFileA, + dwFileLength, szAbsPathNameA ); + + if (0 == dwSize) + { + Fail ("Palsuite Code: mkAbsoluteFilename() call failed. Could " + "not build absolute path name to file\n. Exiting.\n"); + } + + if ( !CreateProcessA ( NULL, + szAbsPathNameA, + NULL, + NULL, + FALSE, + CREATE_NEW_CONSOLE, + NULL, + NULL, + &si, + &pi ) + ) + { + Fail ( "CreateProcess call failed. GetLastError returned %d\n", + GetLastError() ); + } + + WaitForSingleObject ( pi.hProcess, INFINITE ); + + szAbsPathNameA=&absPathBuf[0]; + + dwFileLength = strlen( szCommonFileA ); + + dwSize = mkAbsoluteFilenameA( szDirNameA, dwDirLength, szCommonFileA, + dwFileLength, szAbsPathNameA ); + + /* set the string length for the open call*/ + + if (0 == dwSize) + { + Fail ("Palsuite Code: mkAbsoluteFilename() call failed. Could " + "not build absolute path name to file\n. Exiting.\n"); + } + + if ( NULL == ( fp = fopen ( szAbsPathNameA , "r" ) ) ) + { + Fail ("%s\nunable to open %s\nfor reading. Exiting.\n", argv[0], + szAbsPathNameA ); + } + + cslen = strlen ( szCommonStringA ); + + if ( NULL == fgets( szReadStringA, (cslen + 1), fp )) + { + /* + * A return value of NULL indicates an error condition or an + * EOF condition + */ + Fail ("%s\nunable to read file\n%s\nszReadStringA is %s\n" + "Exiting.\n", argv[0], szAbsPathNameA, + szReadStringA ); + + } + if ( 0 != strncmp( szReadStringA, szCommonStringA, cslen )) + { + Fail ("string comparison failed.\n szReadStringA is %s and\n" + "szCommonStringA is %s\n", szReadStringA, + szCommonStringA ); + } + else + { + Trace ("string comparison passed.\n"); + } + + if (0 != (fclose ( fp ))) + { + Trace ("%s unable to close file %s. This may cause a file pointer " + "leak. Continuing.\n", argv[0], szAbsPathNameA ); + } + + /* Close process and thread handle */ + CloseHandle ( pi.hProcess ); + CloseHandle ( pi.hThread ); + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/CreateProcessA/test1/testinfo.dat b/src/pal/tests/palsuite/threading/CreateProcessA/test1/testinfo.dat new file mode 100644 index 0000000000..02c25444fe --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessA/test1/testinfo.dat @@ -0,0 +1,17 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateProcessA +Name = Positive Test for CreateProcessA +TYPE = DEFAULT +EXE1 = parentprocess +EXE2 = childprocess +Description += Test the CreateProcessA function. The test executes the childprocess += program. The childprocess program launches and writes a const char string += to a file childdata. The parent waits for the completion of childprocess += and then reads the string from the childdata file. If the string in the += file matches it's copy of the const char string, then the test succeeds. diff --git a/src/pal/tests/palsuite/threading/CreateProcessA/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateProcessA/test2/CMakeLists.txt new file mode 100644 index 0000000000..b81ea2e978 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessA/test2/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(TESTSOURCES + parentprocess.c +) + +add_executable(paltest_createprocessa_test2 + ${TESTSOURCES} +) + +add_dependencies(paltest_createprocessa_test2 coreclrpal) + +target_link_libraries(paltest_createprocessa_test2 + pthread + m + coreclrpal +) + + +set(HELPERSOURCES + childprocess.c +) + +add_executable(paltest_createprocessa_test2_child + ${HELPERSOURCES} +) + +add_dependencies(paltest_createprocessa_test2_child coreclrpal) + +target_link_libraries(paltest_createprocessa_test2_child + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateProcessA/test2/childprocess.c b/src/pal/tests/palsuite/threading/CreateProcessA/test2/childprocess.c new file mode 100644 index 0000000000..baa20c2d3c --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessA/test2/childprocess.c @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: createprocessa/test2/childprocess.c +** +** Purpose: This child process reads a string from stdin +** and writes it out to stdout & stderr +** +** Dependencies: memset +** fgets +** gputs +** + +** +**=========================================================*/ + +#include <palsuite.h> +#include "test2.h" + + + +int __cdecl main( int argc, char **argv ) +{ + int iRetCode = EXIT_OK_CODE; /* preset exit code to OK */ + char szBuf[BUF_LEN]; + + + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + if (argc != 4) + { + return EXIT_ERR_CODE3; + } + + if (strcmp(argv[1], szArg1) != 0 + || strcmp(argv[2], szArg2) != 0 + || strcmp(argv[3], szArg3) != 0) + { + return EXIT_ERR_CODE4; + } + + + memset(szBuf, 0, BUF_LEN); + + /* Read the string that was written by the parent */ + if (fgets(szBuf, BUF_LEN, stdin) == NULL) + { + return EXIT_ERR_CODE1; + } + + /* Write the string out to the stdout & stderr pipes */ + if (fputs(szBuf, stdout) == EOF + || fputs(szBuf, stderr) == EOF) + { + return EXIT_ERR_CODE2; + } + + /* The exit code will indicate success or failure */ + PAL_TerminateEx(iRetCode); + + /* Return special exit code to indicate success or failure */ + return iRetCode; +} diff --git a/src/pal/tests/palsuite/threading/CreateProcessA/test2/parentprocess.c b/src/pal/tests/palsuite/threading/CreateProcessA/test2/parentprocess.c new file mode 100644 index 0000000000..ef3340c5d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessA/test2/parentprocess.c @@ -0,0 +1,243 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: createprocessa/test2/parentprocess.c +** +** Purpose: Test the following features of CreateProcessA: +** - Check to see if hProcess & hThread are set in +** return PROCESS_INFORMATION structure +** - Check to see if stdin, stdout, & stderr handles +** are used when STARTF_USESTDHANDLES is specified +** in STARUPINFO flags and bInheritHandles = TRUE +** - Check to see that proper arguments are passed to +** child process +** +** Dependencies: CreatePipe +** strcpy, strlen, strncmp, memset +** WaitForSingleObject +** WriteFile, ReadFile +** GetExitCodeProcess +** + +** +**=========================================================*/ + +#include <palsuite.h> +#include "test2.h" + + + +int __cdecl main( int argc, char **argv ) +{ + + /******************************************* + * Declarations + *******************************************/ + STARTUPINFO si; + PROCESS_INFORMATION pi; + + HANDLE hTestStdInR = NULL; + HANDLE hTestStdInW = NULL; + HANDLE hTestStdOutR = NULL; + HANDLE hTestStdOutW = NULL; + HANDLE hTestStdErrR = NULL; + HANDLE hTestStdErrW = NULL; + + BOOL bRetVal = FALSE; + DWORD dwBytesWritten = 0; + DWORD dwBytesRead = 0; + DWORD dwExitCode = 0; + + SECURITY_ATTRIBUTES pipeAttributes; + + char szStdOutBuf[BUF_LEN]; + char szStdErrBuf[BUF_LEN]; + char szFullPathNameA[_MAX_PATH]; + + + /******************************************* + * Initialization + *******************************************/ + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + /*Setup SECURITY_ATTRIBUTES structure for CreatePipe*/ + pipeAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); + pipeAttributes.lpSecurityDescriptor = NULL; + pipeAttributes.bInheritHandle = TRUE; + + + /*Create a StdIn pipe for child*/ + bRetVal = CreatePipe(&hTestStdInR, /* read handle*/ + &hTestStdInW, /* write handle */ + &pipeAttributes, /* security attributes*/ + 1024); /* pipe size*/ + + if (bRetVal == FALSE) + { + Fail("ERROR: %ld :Unable to create stdin pipe\n", GetLastError()); + } + + + /*Create a StdOut pipe for child*/ + bRetVal = CreatePipe(&hTestStdOutR, /* read handle*/ + &hTestStdOutW, /* write handle */ + &pipeAttributes, /* security attributes*/ + 0); /* pipe size*/ + + if (bRetVal == FALSE) + { + Fail("ERROR: %ld :Unable to create stdout pipe\n", GetLastError()); + } + + + /*Create a StdErr pipe for child*/ + bRetVal = CreatePipe(&hTestStdErrR, /* read handle*/ + &hTestStdErrW, /* write handle */ + &pipeAttributes, /* security attributes*/ + 0); /* pipe size*/ + + if (bRetVal == FALSE) + { + Fail("ERROR: %ld :Unable to create stderr pipe\n", GetLastError()); + } + + /* Zero the data structure space */ + ZeroMemory ( &pi, sizeof(pi) ); + ZeroMemory ( &si, sizeof(si) ); + + /* Set the process flags and standard io handles */ + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = hTestStdInR; + si.hStdOutput = hTestStdOutW; + si.hStdError = hTestStdErrW; + + strcpy(szFullPathNameA, szChildFileA); + strcat(szFullPathNameA, szArgs); + + /******************************************* + * Start Testing + *******************************************/ + + /* Launch the child */ + if ( !CreateProcessA (NULL, szFullPathNameA, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi )) + { + Fail("ERROR: CreateProcess call failed. GetLastError returned %d\n", + GetLastError() ); + } + + /* Check the returned process information for validity */ + if (pi.hProcess == 0 || pi.hThread == 0) + { + Fail("ERROR: CreateProcess Error: Process Handle = %u, Thread Handle = %u\n", + pi.hProcess, pi.hThread); + } + + /* Write the Constructed string to stdin pipe for the child process */ + if (WriteFile(hTestStdInW, szTestString, strlen(szTestString), &dwBytesWritten, NULL) == FALSE + || WriteFile(hTestStdInW, "\n", strlen("\n"), &dwBytesWritten, NULL) == FALSE) + { + Fail("ERROR: %ld :unable to write to write pipe handle " + "hTestStdInW=0x%lx\n", GetLastError(), hTestStdInW); + } + + /* Wait for the child to finish, Max 20 seconds */ + dwExitCode = WaitForSingleObject(pi.hProcess, 20000); + + /* If the child failed then whole thing fails */ + if (dwExitCode != WAIT_OBJECT_0) + { + TerminateProcess(pi.hProcess, 0); + Fail("ERROR: The child failed to run properly.\n"); + } + + /* Check for problems in the child process */ + if (GetExitCodeProcess(pi.hProcess, &dwExitCode) == FALSE) + { + Fail("ERROR: Call to GetExitCodeProcess failed.\n"); + } + else if (dwExitCode == EXIT_ERR_CODE1) + { + Fail("ERROR: The Child process could not reead the string " + "written to the stdin pipe.\n"); + } + else if (dwExitCode == EXIT_ERR_CODE2) + { + Fail("ERROR: The Child process could not write the string " + "the stdout pipe or stderr pipe.\n"); + } + else if (dwExitCode == EXIT_ERR_CODE3) + { + Fail("ERROR: The Child received the wrong number of " + "command line arguments.\n"); + } + else if (dwExitCode == EXIT_ERR_CODE4) + { + Fail("ERROR: The Child received the wrong " + "command line arguments.\n"); + } + else if (dwExitCode != EXIT_OK_CODE) + { + Fail("ERROR: Unexpected exit code returned: %u. Child process " + "did not complete its part of the test.\n", dwExitCode); + } + + + /* The child ran ok, so check to see if we received the proper */ + /* strings through the pipes. */ + + /* clear our buffers */ + memset(szStdOutBuf, 0, BUF_LEN); + memset(szStdErrBuf, 0, BUF_LEN); + + /* Read the data back from the child process stdout */ + bRetVal = ReadFile(hTestStdOutR, /* handle to read pipe*/ + szStdOutBuf, /* buffer to write to*/ + BUF_LEN, /* number of bytes to read*/ + &dwBytesRead, /* number of bytes read*/ + NULL); /* overlapped buffer*/ + + /*Read the data back from the child process stderr */ + bRetVal = ReadFile(hTestStdErrR, /* handle to read pipe*/ + szStdErrBuf, /* buffer to write to*/ + BUF_LEN, /* number of bytes to read*/ + &dwBytesRead, /* number of bytes read*/ + NULL); /* overlapped buffer*/ + + + /* Confirm that we recieved the same string that we originally */ + /* wrote to the child and was received on both stdout & stderr.*/ + if (strncmp(szTestString, szStdOutBuf, strlen(szTestString)) != 0 + || strncmp(szTestString, szStdErrBuf, strlen(szTestString)) != 0) + { + Fail("ERROR: The data read back from child does not match " + "what was written. STDOUT: %s STDERR: %s\n", + szStdOutBuf, szStdErrBuf); + } + + + /******************************************* + * Clean Up + *******************************************/ + + /* Close process and thread handle */ + CloseHandle ( pi.hProcess ); + CloseHandle ( pi.hThread ); + + CloseHandle(hTestStdInR); + CloseHandle(hTestStdInW); + CloseHandle(hTestStdOutR); + CloseHandle(hTestStdOutW); + CloseHandle(hTestStdErrR); + CloseHandle(hTestStdErrW); + + PAL_Terminate(); + return ( PASS ); +} diff --git a/src/pal/tests/palsuite/threading/CreateProcessA/test2/test2.h b/src/pal/tests/palsuite/threading/CreateProcessA/test2/test2.h new file mode 100644 index 0000000000..8cdff3b939 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessA/test2/test2.h @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test2.h +** + +** +**===========================================================*/ + + +const char *szChildFileA = "paltest_createprocessa_test2_child"; +const char *szArgs = " A B C"; +const char *szArg1 = "A"; +const char *szArg2 = "B"; +const char *szArg3 = "C"; + +const char *szPathDelimA = "\\"; + +const char *szTestString = "Copyright (c) Microsoft"; + +const DWORD EXIT_OK_CODE = 100; +const DWORD EXIT_ERR_CODE1 = 101; +const DWORD EXIT_ERR_CODE2 = 102; +const DWORD EXIT_ERR_CODE3 = 103; +const DWORD EXIT_ERR_CODE4 = 104; +const DWORD EXIT_ERR_CODE5 = 105; + +#define BUF_LEN 64 + +/* + * Take two wide strings representing file and directory names + * (dirName, fileName), join the strings with the appropriate path + * delimiter and populate a wide character buffer (absPathName) with + * the resulting string. + * + * Returns: The number of wide characters in the resulting string. + * 0 is returned on Error. + */ +int +mkAbsoluteFilenameA ( + LPSTR dirName, + DWORD dwDirLength, + LPCSTR fileName, + DWORD dwFileLength, + LPSTR absPathName ) +{ + extern const char *szPathDelimA; + + DWORD sizeDN; + DWORD sizeFN; + DWORD sizeAPN; + + sizeDN = strlen( dirName ); + sizeFN = strlen( fileName ); + sizeAPN = (sizeDN + 1 + sizeFN + 1); + + /* insure ((dirName + DELIM + fileName + \0) =< _MAX_PATH ) */ + if ( sizeAPN > _MAX_PATH ) + { + return ( 0 ); + } + + strncpy(absPathName, dirName, dwDirLength +1); + strcat(absPathName, szPathDelimA); + strcat(absPathName, fileName); + + return (sizeAPN); + +} diff --git a/src/pal/tests/palsuite/threading/CreateProcessA/test2/testinfo.dat b/src/pal/tests/palsuite/threading/CreateProcessA/test2/testinfo.dat new file mode 100644 index 0000000000..23fcdf93ae --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessA/test2/testinfo.dat @@ -0,0 +1,20 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateProcessA +Name = PROCESS_INFORMATION and HANDLE Inheritance +TYPE = DEFAULT +EXE1 = parentprocess +EXE2 = childprocess +Description += Test the following features of CreateProcessA: += - Check to see if hProcess & hThread are set in += return PROCESS_INFORMATION structure += - Check to see if stdin, stdout, & stderr handles += are used when STARTF_USESTDHANDLES is specified += in STARUPINFO flags and bInheritHandles = TRUE += - Check to see that proper arguments are passed to += child process diff --git a/src/pal/tests/palsuite/threading/CreateProcessW/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateProcessW/CMakeLists.txt new file mode 100644 index 0000000000..ef14ea5352 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessW/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) + diff --git a/src/pal/tests/palsuite/threading/CreateProcessW/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateProcessW/test1/CMakeLists.txt new file mode 100644 index 0000000000..394b124526 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessW/test1/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(TESTSOURCES + parentProcess.c +) + +add_executable(paltest_createprocessw_test1 + ${TESTSOURCES} +) + +add_dependencies(paltest_createprocessw_test1 coreclrpal) + +target_link_libraries(paltest_createprocessw_test1 + pthread + m + coreclrpal +) + + +set(HELPERSOURCES + childProcess.c +) + +add_executable(paltest_createprocessw_test1_child + ${HELPERSOURCES} +) + +add_dependencies(paltest_createprocessw_test1_child coreclrpal) + +target_link_libraries(paltest_createprocessw_test1_child + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateProcessW/test1/childProcess.c b/src/pal/tests/palsuite/threading/CreateProcessW/test1/childProcess.c new file mode 100644 index 0000000000..c71f967b65 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessW/test1/childProcess.c @@ -0,0 +1,150 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: CreateProcessW/test1/childprocess.c +** +** Purpose: Test to ensure CreateProcessW starts a new process. This test +** launches a child process, and examines a file written by the child. +** This code is the child code. +** +** Dependencies: GetCurrentDirectory +** MultiByteToWideChar +** wcslen +** strlen +** WideCharToMultiByte +** fopen +** fclose +** fprintf +** + +** +**=========================================================*/ + +#define UNICODE +#include <palsuite.h> + +const WCHAR szCommonFileW[] = + {'c','h','i','l','d','d','a','t','a','.','t','m','p','\0'}; + +const WCHAR szPathDelimW[] = {'\\','\0'}; + +const char *szCommonStringA = "058d2d057111a313aa82401c2e856002\0"; + +/* + * Take two wide strings representing file and directory names + * (dirName, fileName), join the strings with the appropriate path + * delimiter and populate a wide character buffer (absPathName) with + * the resulting string. + * + * Returns: The number of wide characters in the resulting string. + * 0 is returned on Error. + */ +int +mkAbsoluteFilenameW ( + LPWSTR dirName, + DWORD dwDirLength, + LPCWSTR fileName, + DWORD dwFileLength, + LPWSTR absPathName ) +{ + extern const WCHAR szPathDelimW[]; + + DWORD sizeDN, sizeFN, sizeAPN; + + sizeDN = wcslen( dirName ); + sizeFN = wcslen( fileName ); + sizeAPN = (sizeDN + 1 + sizeFN + 1); + + /* insure ((dirName + DELIM + fileName + \0) =< _MAX_PATH ) */ + if ( sizeAPN > _MAX_PATH ) + { + return ( 0 ); + } + + wcsncpy(absPathName, dirName, dwDirLength +1); + wcsncpy(absPathName, szPathDelimW, 2); + wcsncpy(absPathName, fileName, dwFileLength +1); + + return (sizeAPN); + +} + +int __cdecl main( int argc, char **argv ) +{ + + static FILE * fp; + + DWORD dwFileLength; + DWORD dwDirLength; + DWORD dwSize; + + char *szAbsPathNameA; + WCHAR szDirNameW[_MAX_DIR]; + WCHAR szAbsPathNameW[_MAX_PATH]; + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + dwDirLength = GetCurrentDirectory( _MAX_PATH, szDirNameW ); + + if (0 == dwDirLength) + { + Fail ("GetCurrentDirectory call failed. Could not get " + "current working directory\n. Exiting.\n"); + } + + dwFileLength = wcslen( szCommonFileW ); + + dwSize = mkAbsoluteFilenameW( szDirNameW, dwDirLength, szCommonFileW, + dwFileLength, szAbsPathNameW ); + + if (0 == dwSize) + { + Fail ("Palsuite Code: mkAbsoluteFilename() call failed. Could " + "not build absolute path name to file\n. Exiting.\n"); + } + + /* set the string length for the open call */ + szAbsPathNameA = malloc (dwSize +1); + + if (NULL == szAbsPathNameA) + { + Fail ("Unable to malloc (%d) bytes. Exiting\n", (dwSize +1) ); + } + + WideCharToMultiByte (CP_ACP, 0, szAbsPathNameW, -1, szAbsPathNameA, + (dwSize + 1), NULL, NULL); + + if ( NULL == ( fp = fopen ( szAbsPathNameA , "w+" ) ) ) + { + /* + * A return value of NULL indicates an error condition or an + * EOF condition + */ + Fail ("%s unable to open %s for writing. Exiting.\n", argv[0] + , szAbsPathNameA ); + } + + free (szAbsPathNameA); + + if ( 0 >= ( fprintf ( fp, "%s", szCommonStringA ))) + { + Fail("%s unable to write to %s. Exiting.\n", argv[0] + , szAbsPathNameA ); + } + + if (0 != (fclose ( fp ))) + { + Fail ("%s unable to close file %s. Pid may not be " + "written to file. Exiting.\n", argv[0], szAbsPathNameA ); + } + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/CreateProcessW/test1/parentProcess.c b/src/pal/tests/palsuite/threading/CreateProcessW/test1/parentProcess.c new file mode 100644 index 0000000000..db1fb6356d --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessW/test1/parentProcess.c @@ -0,0 +1,210 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: CreateProcessW/test1/parentprocess.c +** +** Purpose: Test to ensure CreateProcessW starts a new process. This test +** launches a child process, and examines a file written by the child. +** This process (the parent process) reads the file created by the child and +** compares the value the child wrote to the file. (a const char *) +** +** Dependencies: GetCurrentDirectory +** MultiByteToWideChar +** wcslen +** strlen +** WideCharToMultiByte +** WaitForSingleObject +** fopen +** fclose +** Fail +** + +** +**=========================================================*/ + +#define UNICODE +#include <palsuite.h> + +const WCHAR szCommonFileW[] = + {'c','h','i','l','d','d','a','t','a','.','t','m','p','\0'}; + +const WCHAR szChildFileW[] = u"paltest_createprocessw_test1_child"; + +const WCHAR szPathDelimW[] = {'\\','\0'}; + +const char *szCommonStringA = "058d2d057111a313aa82401c2e856002\0"; + +/* + * Take two wide strings representing file and directory names + * (dirName, fileName), join the strings with the appropriate path + * delimiter and populate a wide character buffer (absPathName) with + * the resulting string. + * + * Returns: The number of wide characters in the resulting string. + * 0 is returned on Error. + */ +int +mkAbsoluteFilenameW ( + LPWSTR dirName, + DWORD dwDirLength, + LPCWSTR fileName, + DWORD dwFileLength, + LPWSTR absPathName ) +{ + extern const WCHAR szPathDelimW[]; + + DWORD sizeDN, sizeFN, sizeAPN; + + sizeDN = wcslen( dirName ); + sizeFN = wcslen( fileName ); + sizeAPN = (sizeDN + 1 + sizeFN + 1); + + /* insure ((dirName + DELIM + fileName + \0) =< _MAX_PATH ) */ + if ( sizeAPN > _MAX_PATH ) + { + return ( 0 ); + } + + wcsncpy(absPathName, dirName, dwDirLength +1); + wcsncpy(absPathName, szPathDelimW, 2); + wcsncpy(absPathName, fileName, dwFileLength +1); + + return (sizeAPN); + +} + +int __cdecl main( int argc, char **argv ) + +{ + + STARTUPINFOW si; + PROCESS_INFORMATION pi; + + static FILE * fp; + + DWORD dwFileLength; + DWORD dwDirLength; + DWORD dwSize; + + size_t cslen; + + char szReadStringA[256]; + + char szAbsPathNameA[_MAX_PATH]; + WCHAR szDirNameW[_MAX_DIR]; + WCHAR absPathBuf[_MAX_PATH]; + WCHAR *szAbsPathNameW; + + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + ZeroMemory ( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory ( &pi, sizeof(pi) ); + + szAbsPathNameW=&absPathBuf[0]; + dwFileLength = wcslen( szChildFileW ); + + dwDirLength = GetCurrentDirectory(_MAX_PATH, szDirNameW); + + if (0 == dwDirLength) + { + Fail ("GetCurrentDirectory call failed. Could not get " + "current working directory\n. Exiting.\n"); + } + + dwSize = mkAbsoluteFilenameW( szDirNameW, dwDirLength, szChildFileW, + dwFileLength, szAbsPathNameW ); + + if (0 == dwSize) + { + Fail ("Palsuite Code: mkAbsoluteFilename() call failed. Could " + "not build absolute path name to file\n. Exiting.\n"); + } + + if ( !CreateProcessW ( NULL, + szAbsPathNameW, + NULL, + NULL, + FALSE, + CREATE_NEW_CONSOLE, + NULL, + NULL, + &si, + &pi ) + ) + { + Fail ( "CreateProcess call failed. GetLastError returned %d\n", + GetLastError() ); + } + + WaitForSingleObject ( pi.hProcess, INFINITE ); + + szAbsPathNameW=&absPathBuf[0]; + + dwFileLength = wcslen( szCommonFileW ); + + dwSize = mkAbsoluteFilenameW( szDirNameW, dwDirLength, szCommonFileW, + dwFileLength, szAbsPathNameW ); + + /* set the string length for the open call*/ + + if (0 == dwSize) + { + Fail ("Palsuite Code: mkAbsoluteFilename() call failed. Could " + "not build absolute path name to file\n. Exiting.\n"); + } + + WideCharToMultiByte (CP_ACP, 0, szAbsPathNameW, -1, szAbsPathNameA, + (dwSize + 1), NULL, NULL); + + if ( NULL == ( fp = fopen ( szAbsPathNameA , "r" ) ) ) + { + Fail ("%s\nunable to open %s\nfor reading. Exiting.\n", argv[0], + szAbsPathNameA ); + } + + cslen = strlen ( szCommonStringA ); + + if ( NULL == fgets( szReadStringA, (cslen + 1), fp )) + { + /* + * A return value of NULL indicates an error condition or an + * EOF condition + */ + Fail ("%s\nunable to read file\n%s\nszReadStringA is %s\n" + "Exiting.\n", argv[0], szAbsPathNameA, + szReadStringA ); + } + + if ( 0 != strncmp( szReadStringA, szCommonStringA, cslen )) + { + Fail ("string comparison failed.\n szReadStringA is %s and\n" + "szCommonStringA is %s\n", szReadStringA, + szCommonStringA ); + } + else + { + Trace ("string comparison passed.\n"); + } + + if (0 != (fclose ( fp ))) + { + Trace ("%s unable to close file %s. This may cause a file pointer " + "leak. Continuing.\n", argv[0], szAbsPathNameA ); + } + + /* Close process and thread handle */ + CloseHandle ( pi.hProcess ); + CloseHandle ( pi.hThread ); + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/CreateProcessW/test1/testinfo.dat b/src/pal/tests/palsuite/threading/CreateProcessW/test1/testinfo.dat new file mode 100644 index 0000000000..2acf2c9289 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessW/test1/testinfo.dat @@ -0,0 +1,17 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateProcessW +Name = Positive Test for CreateProcessW +TYPE = DEFAULT +EXE1 = parentprocess +EXE2 = childprocess +Description += Test the CreateProcessW function. The test executes the childprocess += program. The childprocess program launches and writes a const char string += to a file childdata. The parent waits for the completion of childprocess += and then reads the string from the childdata file. If the string in the += file matches it's copy of the const char string, then the test succeeds. diff --git a/src/pal/tests/palsuite/threading/CreateProcessW/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateProcessW/test2/CMakeLists.txt new file mode 100644 index 0000000000..3feef213c4 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessW/test2/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(TESTSOURCES + parentprocess.c +) + +add_executable(paltest_createprocessw_test2 + ${TESTSOURCES} +) + +add_dependencies(paltest_createprocessw_test2 coreclrpal) + +target_link_libraries(paltest_createprocessw_test2 + pthread + m + coreclrpal +) + + +set(HELPERSOURCES + childprocess.c +) + +add_executable(paltest_createprocessw_test2_child + ${HELPERSOURCES} +) + +add_dependencies(paltest_createprocessw_test2_child coreclrpal) + +target_link_libraries(paltest_createprocessw_test2_child + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateProcessW/test2/childprocess.c b/src/pal/tests/palsuite/threading/CreateProcessW/test2/childprocess.c new file mode 100644 index 0000000000..b4ab9366d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessW/test2/childprocess.c @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: createprocessw/test2/childprocess.c +** +** Purpose: This child process reads a string from stdin +** and writes it out to stdout & stderr +** +** Dependencies: memset +** fgets +** gputs +** + +** +**=========================================================*/ + +#define UNICODE +#include <palsuite.h> +#include "test2.h" + + +int __cdecl main( int argc, char **argv ) +{ + int iRetCode = EXIT_OK_CODE; /* preset exit code to OK */ + char szBuf[BUF_LEN]; + + WCHAR *swzParam1, *swzParam2, *swzParam3 = NULL; + + + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + if (argc != 4) + { + return EXIT_ERR_CODE3; + } + + swzParam1 = convert(argv[1]); + swzParam2 = convert(argv[2]); + swzParam3 = convert(argv[3]); + + if (wcscmp(swzParam1, szArg1) != 0 + || wcscmp(swzParam2, szArg2) != 0 + || wcscmp(swzParam3, szArg3) != 0) + { + return EXIT_ERR_CODE4; + } + + free(swzParam1); + free(swzParam2); + free(swzParam3); + + memset(szBuf, 0, BUF_LEN); + + /* Read the string that was written by the parent */ + if (fgets(szBuf, BUF_LEN, stdin) == NULL) + { + return EXIT_ERR_CODE1; + } + + + /* Write the string out to the stdout & stderr pipes */ + if (fputs(szBuf, stdout) == EOF + || fputs(szBuf, stderr) == EOF) + { + return EXIT_ERR_CODE2; + } + + /* The exit code will indicate success or failure */ + PAL_TerminateEx(iRetCode); + + /* Return special exit code to indicate success or failure */ + return iRetCode; +} diff --git a/src/pal/tests/palsuite/threading/CreateProcessW/test2/parentprocess.c b/src/pal/tests/palsuite/threading/CreateProcessW/test2/parentprocess.c new file mode 100644 index 0000000000..439b7b5eef --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessW/test2/parentprocess.c @@ -0,0 +1,245 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: createprocessw/test2/parentprocess.c +** +** Purpose: Test the following features of CreateProcessW: +** - Check to see if hProcess & hThread are set in +** return PROCESS_INFORMATION structure +** - Check to see if stdin, stdout, & stderr handles +** are used when STARTF_USESTDHANDLES is specified +** in STARUPINFO flags and bInheritHandles = TRUE +** - Check to see that proper arguments are passed to +** child process +** +** Dependencies: CreatePipe +** strcpy, strlen, strncmp, memset +** WaitForSingleObject +** WriteFile, ReadFile +** GetExitCodeProcess +** + +** +**=========================================================*/ + +#define UNICODE +#include <palsuite.h> +#include "test2.h" + + + +int __cdecl main( int argc, char **argv ) +{ + + /******************************************* + * Declarations + *******************************************/ + STARTUPINFO si; + PROCESS_INFORMATION pi; + + HANDLE hTestStdInR = NULL; + HANDLE hTestStdInW = NULL; + HANDLE hTestStdOutR = NULL; + HANDLE hTestStdOutW = NULL; + HANDLE hTestStdErrR = NULL; + HANDLE hTestStdErrW = NULL; + + BOOL bRetVal = FALSE; + DWORD dwBytesWritten = 0; + DWORD dwBytesRead = 0; + DWORD dwExitCode = 0; + + SECURITY_ATTRIBUTES pipeAttributes; + + char szStdOutBuf[BUF_LEN]; + char szStdErrBuf[BUF_LEN]; + WCHAR szFullPathNameW[_MAX_PATH]; + + + /******************************************* + * Initialization + *******************************************/ + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + /*Setup SECURITY_ATTRIBUTES structure for CreatePipe*/ + pipeAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); + pipeAttributes.lpSecurityDescriptor = NULL; + pipeAttributes.bInheritHandle = TRUE; + + + /*Create a StdIn pipe for child*/ + bRetVal = CreatePipe(&hTestStdInR, /* read handle*/ + &hTestStdInW, /* write handle */ + &pipeAttributes, /* security attributes*/ + 1024); /* pipe size*/ + + if (bRetVal == FALSE) + { + Fail("ERROR: %ld :Unable to create stdin pipe\n", GetLastError()); + } + + + /*Create a StdOut pipe for child*/ + bRetVal = CreatePipe(&hTestStdOutR, /* read handle*/ + &hTestStdOutW, /* write handle */ + &pipeAttributes, /* security attributes*/ + 0); /* pipe size*/ + + if (bRetVal == FALSE) + { + Fail("ERROR: %ld :Unable to create stdout pipe\n", GetLastError()); + } + + + /*Create a StdErr pipe for child*/ + bRetVal = CreatePipe(&hTestStdErrR, /* read handle*/ + &hTestStdErrW, /* write handle */ + &pipeAttributes, /* security attributes*/ + 0); /* pipe size*/ + + if (bRetVal == FALSE) + { + Fail("ERROR: %ld :Unable to create stderr pipe\n", GetLastError()); + } + + /* Zero the data structure space */ + ZeroMemory ( &pi, sizeof(pi) ); + ZeroMemory ( &si, sizeof(si) ); + + /* Set the process flags and standard io handles */ + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = hTestStdInR; + si.hStdOutput = hTestStdOutW; + si.hStdError = hTestStdErrW; + + wcscpy(szFullPathNameW, szChildFileW); + wcscat(szFullPathNameW, szArgs); + + /******************************************* + * Start Testing + *******************************************/ + + /* Launch the child */ + if ( !CreateProcess (NULL, szFullPathNameW, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi )) + { + Fail("ERROR: CreateProcess call failed. GetLastError returned %d\n", + GetLastError() ); + } + + /* Check the returned process information for validity */ + if (pi.hProcess == 0 || pi.hThread == 0) + { + Fail("ERROR: CreateProcess Error: Process Handle = %u, Thread Handle = %u\n", + pi.hProcess, pi.hThread); + } + + + /* Write the Constructed string to stdin pipe for the child process */ + if (WriteFile(hTestStdInW, szTestString, strlen(szTestString), &dwBytesWritten, NULL) == FALSE + || WriteFile(hTestStdInW, "\n", strlen("\n"), &dwBytesWritten, NULL) == FALSE) + { + Fail("ERROR: %ld :unable to write to write pipe handle " + "hTestStdInW=0x%lx\n", GetLastError(), hTestStdInW); + } + + /* Wait for the child to finish, Max 20 seconds */ + dwExitCode = WaitForSingleObject(pi.hProcess, 20000); + + /* If the child failed then whole thing fails */ + if (dwExitCode != WAIT_OBJECT_0) + { + TerminateProcess(pi.hProcess, 0); + Fail("ERROR: The child failed to run properly.\n"); + } + + /* Check for problems in the child process */ + if (GetExitCodeProcess(pi.hProcess, &dwExitCode) == FALSE) + { + Fail("ERROR: Call to GetExitCodeProcess failed.\n"); + } + else if (dwExitCode == EXIT_ERR_CODE1) + { + Fail("ERROR: The Child process could not reead the string " + "written to the stdin pipe.\n"); + } + else if (dwExitCode == EXIT_ERR_CODE2) + { + Fail("ERROR: The Child process could not write the string " + "the stdout pipe or stderr pipe.\n"); + } + else if (dwExitCode == EXIT_ERR_CODE3) + { + Fail("ERROR: The Child received the wrong number of " + "command line arguments.\n"); + } + else if (dwExitCode == EXIT_ERR_CODE4) + { + Fail("ERROR: The Child received the wrong " + "command line arguments.\n"); + } + else if (dwExitCode != EXIT_OK_CODE) + { + Fail("ERROR: Unexpected exit code returned: %u. Child process " + "did not complete its part of the test.\n", dwExitCode); + } + + + /* The child ran ok, so check to see if we received the proper */ + /* strings through the pipes. */ + + /* clear our buffers */ + memset(szStdOutBuf, 0, BUF_LEN); + memset(szStdErrBuf, 0, BUF_LEN); + + /* Read the data back from the child process stdout */ + bRetVal = ReadFile(hTestStdOutR, /* handle to read pipe*/ + szStdOutBuf, /* buffer to write to*/ + BUF_LEN, /* number of bytes to read*/ + &dwBytesRead, /* number of bytes read*/ + NULL); /* overlapped buffer*/ + + /*Read the data back from the child process stderr */ + bRetVal = ReadFile(hTestStdErrR, /* handle to read pipe*/ + szStdErrBuf, /* buffer to write to*/ + BUF_LEN, /* number of bytes to read*/ + &dwBytesRead, /* number of bytes read*/ + NULL); /* overlapped buffer*/ + + + /* Confirm that we recieved the same string that we originally */ + /* wrote to the child and was received on both stdout & stderr.*/ + if (strncmp(szTestString, szStdOutBuf, strlen(szTestString)) != 0 + || strncmp(szTestString, szStdErrBuf, strlen(szTestString)) != 0) + { + Fail("ERROR: The data read back from child does not match " + "what was written. STDOUT: %s STDERR: %s\n", + szStdOutBuf, szStdErrBuf); + } + + + /******************************************* + * Clean Up + *******************************************/ + + /* Close process and thread handle */ + CloseHandle ( pi.hProcess ); + CloseHandle ( pi.hThread ); + + CloseHandle(hTestStdInR); + CloseHandle(hTestStdInW); + CloseHandle(hTestStdOutR); + CloseHandle(hTestStdOutW); + CloseHandle(hTestStdErrR); + CloseHandle(hTestStdErrW); + + PAL_Terminate(); + return ( PASS ); +} diff --git a/src/pal/tests/palsuite/threading/CreateProcessW/test2/test2.h b/src/pal/tests/palsuite/threading/CreateProcessW/test2/test2.h new file mode 100644 index 0000000000..07d40b8942 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessW/test2/test2.h @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test2.h +** +** + +** +**=========================================================*/ + + +const WCHAR szChildFileW[] = u"paltest_createprocessw_test2_child"; +const WCHAR szArgs[] = {' ',0x41,' ','B',' ','C','\0'}; +const WCHAR szArg1[] = {0x41,'\0'}; +const WCHAR szArg2[] = {'B','\0'}; +const WCHAR szArg3[] = {'C','\0'}; + +const char *szTestString = "An uninteresting test string (it works though)"; + +const DWORD EXIT_OK_CODE = 100; +const DWORD EXIT_ERR_CODE1 = 101; +const DWORD EXIT_ERR_CODE2 = 102; +const DWORD EXIT_ERR_CODE3 = 103; +const DWORD EXIT_ERR_CODE4 = 104; +const DWORD EXIT_ERR_CODE5 = 105; + +#define BUF_LEN 128 + diff --git a/src/pal/tests/palsuite/threading/CreateProcessW/test2/testinfo.dat b/src/pal/tests/palsuite/threading/CreateProcessW/test2/testinfo.dat new file mode 100644 index 0000000000..d16ae593f2 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateProcessW/test2/testinfo.dat @@ -0,0 +1,20 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateProcessW +Name = PROCESS_INFORMATION and HANDLE Inheritance +TYPE = DEFAULT +EXE1 = parentprocess +EXE2 = childprocess +Description += Test the following features of CreateProcessW: += - Check to see if hProcess & hThread are set in += return PROCESS_INFORMATION structure += - Check to see if stdin, stdout, & stderr handles += are used when STARTF_USESTDHANDLES is specified += in STARUPINFO flags and bInheritHandles = TRUE += - Check to see that proper arguments are passed to += child process diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/CMakeLists.txt new file mode 100644 index 0000000000..1962ade358 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) +add_subdirectory(test3) + diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test1/CMakeLists.txt new file mode 100644 index 0000000000..f89e150a32 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + CreateSemaphore.c +) + +add_executable(paltest_createsemaphorea_releasesemaphore_test1 + ${SOURCES} +) + +add_dependencies(paltest_createsemaphorea_releasesemaphore_test1 coreclrpal) + +target_link_libraries(paltest_createsemaphorea_releasesemaphore_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test1/CreateSemaphore.c b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test1/CreateSemaphore.c new file mode 100644 index 0000000000..342b15ec29 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test1/CreateSemaphore.c @@ -0,0 +1,322 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: createsemaphorea_releasesemaphore/test1/createsemaphore.c +** +** Purpose: Test Semaphore operation using classic IPC problem: +** "Producer-Consumer Problem". +** +** Dependencies: CreateThread +** ReleaseSemaphore +** WaitForSingleObject +** Sleep +** fflush +** + +** +**=========================================================*/ + +#include <palsuite.h> + +#define PRODUCTION_TOTAL 26 + +#define _BUF_SIZE 10 + +DWORD dwThreadId; /* consumer thread identifier */ + +HANDLE hThread; /* handle to consumer thread */ + +HANDLE hSemaphoreM; /* handle to mutual exclusion semaphore */ + +HANDLE hSemaphoreE; /* handle to semaphore that counts empty buffer slots */ + +HANDLE hSemaphoreF; /* handle to semaphore that counts full buffer slots */ + +typedef struct Buffer +{ + short readIndex; + short writeIndex; + CHAR message[_BUF_SIZE]; + +} BufferStructure; + +CHAR producerItems[PRODUCTION_TOTAL + 1]; + +CHAR consumerItems[PRODUCTION_TOTAL + 1]; + +/* + * Read next message from the Buffer into provided pointer. + * Returns: 0 on failure, 1 on success. + */ +int +readBuf(BufferStructure *Buffer, char *c) +{ + if( Buffer -> writeIndex == Buffer -> readIndex ) + { + return 0; + } + *c = Buffer -> message[Buffer -> readIndex++]; + Buffer -> readIndex %= _BUF_SIZE; + return 1; +} + +/* + * Write message generated by the producer to Buffer. + * Returns: 0 on failure, 1 on success. + */ +int +writeBuf(BufferStructure *Buffer, CHAR c) +{ + if( ( ((Buffer -> writeIndex) + 1) % _BUF_SIZE) == + (Buffer -> readIndex) ) + { + return 0; + } + Buffer -> message[Buffer -> writeIndex++] = c; + Buffer -> writeIndex %= _BUF_SIZE; + return 1; +} + +/* + * Atomic decrement of semaphore value. + */ +VOID +down(HANDLE hSemaphore) +{ + switch ( (WaitForSingleObject ( + hSemaphore, + 10000))) /* Wait 10 seconds */ + { + case WAIT_OBJECT_0: /* + * Semaphore was signaled. OK to access + * semaphore. + */ + break; + case WAIT_ABANDONED: /* + * Object was mutex object whose owning + * thread has terminated. Shouldn't occur. + */ + Fail("WaitForSingleObject call returned 'WAIT_ABANDONED'.\n" + "Failing Test.\n"); + break; + case WAIT_FAILED: /* WaitForSingleObject function failed */ + Fail("WaitForSingleObject call returned 'WAIT_FAILED'.\n" + "GetLastError returned %d\nFailing Test.\n",GetLastError()); + break; + default: + Fail("WaitForSingleObject call returned an unexpected value.\n" + "Failing Test.\n"); + break; + } + +} + +/* + * Atomic increment of semaphore value. + */ +VOID +up(HANDLE hSemaphore) +{ + if (!ReleaseSemaphore ( + hSemaphore, + 1, + NULL) + ) + { + Fail("ReleaseSemaphore call failed. GetLastError returned %d\n", + GetLastError()); + } +} + +/* + * Sleep 500 milleseconds. + */ +VOID +consumerSleep(VOID) +{ + Sleep(500); +} + +/* + * Sleep between 10 milleseconds. + */ +VOID +producerSleep(VOID) +{ + Sleep(10); +} + +/* + * Produce a message and write the message to Buffer. + */ +VOID +producer(BufferStructure *Buffer) +{ + + int n = 0; + char c; + + while (n < PRODUCTION_TOTAL) + { + c = 'A' + n ; /* Produce Item */ + + down(hSemaphoreE); + down(hSemaphoreM); + + if (writeBuf(Buffer, c)) + { + Trace("Producer produces %c.\n", c); + fflush(stdout); + producerItems[n++] = c; + } + + up(hSemaphoreM); + up(hSemaphoreF); + + producerSleep(); + } + + return; +} + +/* + * Read and "Consume" the messages in Buffer. + */ +DWORD +PALAPI +consumer( LPVOID lpParam ) +{ + int n = 0; + char c; + + consumerSleep(); + + while (n < PRODUCTION_TOTAL) + { + + down(hSemaphoreF); + down(hSemaphoreM); + + if (readBuf((BufferStructure*)lpParam, &c)) + { + Trace("\tConsumer consumes %c.\n", c); + fflush(stdout); + consumerItems[n++] = c; + } + + up(hSemaphoreM); + up(hSemaphoreE); + + consumerSleep(); + } + + return 0; +} + +int __cdecl main (int argc, char **argv) +{ + + BufferStructure Buffer, *pBuffer; + + pBuffer = &Buffer; + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + /* + * Create Semaphores + */ + hSemaphoreM = CreateSemaphoreA ( + NULL, + 1, + 1, + NULL); + + if ( NULL == hSemaphoreM ) + { + Fail ( "hSemaphoreM = CreateSemaphoreA () - returned NULL\n" + "Failing Test.\nGetLastError returned %d\n", GetLastError()); + } + + + hSemaphoreE = CreateSemaphoreA ( + NULL, + _BUF_SIZE , + _BUF_SIZE , + NULL); + + if ( NULL == hSemaphoreE ) + { + Fail ( "hSemaphoreE = CreateSemaphoreA () - returned NULL\n" + "Failing Test.\nGetLastError returned %d\n", GetLastError()); + } + + hSemaphoreF = CreateSemaphoreA ( + NULL, + 0, + _BUF_SIZE , + NULL); + + if ( NULL == hSemaphoreF ) + { + Fail ( "hSemaphoreF = CreateSemaphoreA () - returned NULL\n" + "Failing Test.\nGetLastError returned %d\n", GetLastError()); + } + + + /* + * Initialize Buffer + */ + pBuffer->writeIndex = pBuffer->readIndex = 0; + + /* + * Create Consumer + */ + hThread = CreateThread( + NULL, + 0, + consumer, + &Buffer, + 0, + &dwThreadId); + + if ( NULL == hThread ) + { + Fail ( "CreateThread() returned NULL. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + } + + /* + * Start producing + */ + producer(pBuffer); + + /* + * Wait for consumer to complete + */ + WaitForSingleObject (hThread, INFINITE); + + /* + * Compare items produced vs. items consumed + */ + if ( 0 != strncmp (producerItems, consumerItems, PRODUCTION_TOTAL) ) + { + Fail("The producerItems string %s\n and the consumerItems string " + "%s\ndo not match. This could be a problem with the strncmp()" + " function\n FailingTest\nGetLastError() returned %d\n", + producerItems, consumerItems, GetLastError()); + } + + Trace ("producerItems and consumerItems arrays match. All %d\nitems " + "were produced and consumed in order.\nTest passed.\n", + PRODUCTION_TOTAL); + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test1/testinfo.dat b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test1/testinfo.dat new file mode 100644 index 0000000000..880746e43e --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test1/testinfo.dat @@ -0,0 +1,18 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateSemaphoreA / ReleaseSemaphore +Name = Positive Test for CreateSemaphoreA and ReleaseSemaphore +TYPE = DEFAULT +EXE1 = createsemaphore +Description += Implementation of Producer / Consumer IPC problem using CreateSemaphoreA += and ReleaseSemaphore functions. This test case exercises CreateSemaphoreA += , ReleaseSemaphore, CreateThread and WaitForSingleObject functions. += Since there is no way to currently create "pseudo" random events in the += pal, this example does not behave as classic bounded buffers would. This += test case is designed to starve the consumer and have the producer fill += the buffer. diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test2/CMakeLists.txt new file mode 100644 index 0000000000..f3e01749b8 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + CreateSemaphore.c +) + +add_executable(paltest_createsemaphorea_releasesemaphore_test2 + ${SOURCES} +) + +add_dependencies(paltest_createsemaphorea_releasesemaphore_test2 coreclrpal) + +target_link_libraries(paltest_createsemaphorea_releasesemaphore_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test2/CreateSemaphore.c b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test2/CreateSemaphore.c new file mode 100644 index 0000000000..bff5b51c33 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test2/CreateSemaphore.c @@ -0,0 +1,313 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: CreateSemaphoreA_ReleaseSemaphore/test2/createsemaphore.c +** +** Purpose: Test Semaphore operation using classic IPC problem: +** "Producer-Consumer Problem". +** +** Dependencies: CreateThread +** ReleaseSemaphore +** WaitForSingleObject +** Sleep +** fflush +** + +** +**=========================================================*/ + +#include <palsuite.h> + +#define PRODUCTION_TOTAL 26 + +#define _BUF_SIZE 10 + +DWORD dwThreadId; /* consumer thread identifier */ + +HANDLE hThread; /* handle to consumer thread */ + +HANDLE hSemaphoreM; /* handle to mutual exclusion semaphore */ + +HANDLE hSemaphoreE; /* handle to semaphore that counts empty buffer slots */ + +HANDLE hSemaphoreF; /* handle to semaphore that counts full buffer slots */ + +typedef struct Buffer +{ + short readIndex; + short writeIndex; + CHAR message[_BUF_SIZE]; + +} BufferStructure; + +CHAR producerItems[PRODUCTION_TOTAL + 1]; + +CHAR consumerItems[PRODUCTION_TOTAL + 1]; + +/* + * Read next message from the Buffer into provided pointer. + * Returns: 0 on failure, 1 on success. + */ +int +readBuf(BufferStructure *Buffer, char *c) +{ + if( Buffer -> writeIndex == Buffer -> readIndex ) + { + return 0; + } + *c = Buffer -> message[Buffer -> readIndex++]; + Buffer -> readIndex %= _BUF_SIZE; + return 1; +} + +/* + * Write message generated by the producer to Buffer. + * Returns: 0 on failure, 1 on success. + */ +int +writeBuf(BufferStructure *Buffer, CHAR c) +{ + if( ( ((Buffer -> writeIndex) + 1) % _BUF_SIZE) == + (Buffer -> readIndex) ) + { + return 0; + } + Buffer -> message[Buffer -> writeIndex++] = c; + Buffer -> writeIndex %= _BUF_SIZE; + return 1; +} + +/* + * Atomic decrement of semaphore value. + */ +VOID +down(HANDLE hSemaphore) +{ + switch ( (WaitForSingleObject ( + hSemaphore, + 10000))) + { + case WAIT_OBJECT_0: /* + * Semaphore was signaled. OK to access semaphore. + */ + break; + case WAIT_ABANDONED: /* + * Object was mutex object whose owning + * thread has terminated. Shouldn't occur. + */ + Fail("WaitForSingleObject call returned 'WAIT_ABANDONED'.\n" + "Failing Test.\n"); + break; + case WAIT_FAILED: /* WaitForSingleObject function failed */ + Fail("WaitForSingleObject call returned 'WAIT_FAILED'.\n" + "GetLastError returned %d\nFailing Test.\n",GetLastError()); + break; + default: + Fail("WaitForSingleObject call returned an unexpected value.\n" + "Failing Test.\n"); + break; + } + +} + +/* + * Atomic increment of semaphore value. + */ +VOID +up(HANDLE hSemaphore) +{ + if (!ReleaseSemaphore ( + hSemaphore, + 1, + NULL) + ) + { + Fail("ReleaseSemaphore call failed. GetLastError returned %d\n", + GetLastError()); + } +} + +/* + * Sleep 10 milleseconds. + */ +VOID +consumerSleep(VOID) +{ + Sleep(10); +} + +/* + * Sleep 500 milleseconds. + */ +VOID +producerSleep(VOID) +{ + Sleep(500); +} + +/* + * Produce a message and write the message to Buffer. + */ +VOID +producer(BufferStructure *Buffer) +{ + + int n = 0; + char c; + + while (n < PRODUCTION_TOTAL) + { + c = 'A' + n ; /* Produce Item */ + + down(hSemaphoreE); + down(hSemaphoreM); + + if (writeBuf(Buffer, c)) + { + Trace("Producer produces %c.\n", c); + fflush(stdout); + producerItems[n++] = c; + } + + up(hSemaphoreM); + up(hSemaphoreF); + + producerSleep(); + } + return; +} + +/* + * Read and "Consume" the messages in Buffer. + */ +DWORD +PALAPI +consumer( LPVOID lpParam ) +{ + int n = 0; + char c; + + consumerSleep(); + + while (n < PRODUCTION_TOTAL) + { + + down(hSemaphoreF); + down(hSemaphoreM); + + if (readBuf((BufferStructure*)lpParam, &c)) + { + Trace("\tConsumer consumes %c.\n", c); + fflush(stdout); + consumerItems[n++] = c; + } + + up(hSemaphoreM); + up(hSemaphoreE); + + consumerSleep(); + } + return 0; +} + +int __cdecl main (int argc, char **argv) +{ + + BufferStructure Buffer, *pBuffer; + + pBuffer = &Buffer; + + if(0 != (PAL_Initialize(argc, argv))) + { + return (FAIL); + } + + /* + * Create Semaphores + */ + hSemaphoreM = CreateSemaphoreA ( + NULL, + 1, + 1, + NULL); + + if ( NULL == hSemaphoreM ) + { + Fail ( "hSemaphoreM = CreateSemaphoreA () - returned NULL\n" + "Failing Test.\n"); + } + + hSemaphoreE = CreateSemaphoreA ( + NULL, + _BUF_SIZE , + _BUF_SIZE , + NULL); + + if ( NULL == hSemaphoreE ) + { + Fail ( "hSemaphoreE = CreateSemaphoreA () - returned NULL\n" + "Failing Test.\n"); + } + + hSemaphoreF = CreateSemaphoreA ( + NULL, + 0, + _BUF_SIZE , + NULL); + + if ( NULL == hSemaphoreF ) + { + Fail ( "hSemaphoreF = CreateSemaphoreA () - returned NULL\n" + "Failing Test.\n"); + } + + /* + * Initialize Buffer + */ + pBuffer->writeIndex = pBuffer->readIndex = 0; + + /* + * Create Consumer + */ + hThread = CreateThread( + NULL, + 0, + consumer, + &Buffer, + 0, + &dwThreadId); + + if ( NULL == hThread ) + { + Fail ( "CreateThread() returned NULL. Failing test.\n"); + } + + /* + * Start producing + */ + producer(pBuffer); + + /* + * Wait for consumer to complete + */ + WaitForSingleObject (hThread, INFINITE); + + if ( 0 != strncmp (producerItems, consumerItems, PRODUCTION_TOTAL) ) + { + Fail("The producerItems string %s\n and the consumerItems string " + "%s\ndo not match. This could be a problem with the strncmp()" + " function\n FailingTest\nGetLastError() returned %d\n", + producerItems, consumerItems, GetLastError()); + } + + Trace ("producerItems and consumerItems arrays match. All %d\nitems " + "were produced and consumed in order.\nTest passed.\n", + PRODUCTION_TOTAL); + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test2/testinfo.dat b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test2/testinfo.dat new file mode 100644 index 0000000000..5a6c2e7909 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test2/testinfo.dat @@ -0,0 +1,18 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateSemaphoreA / ReleaseSemaphore +Name = Positive Test for CreateSemaphoreA and ReleaseSemaphore +TYPE = DEFAULT +EXE1 = createsemaphore +Description += Implementation of Producer / Consumer IPC problem using CreateSemaphoreA += and ReleaseSemaphore functions. This test case exercises CreateSemaphoreA += , ReleaseSemaphore, CreateThread and WaitForSingleObject functions. += Since there is no way to currently create "pseudo" random events in the += pal, this example does not behave as classic bounded buffers would. This += test case is designed to starve the producer and have the consumer fill += the buffer. diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test3/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test3/CMakeLists.txt new file mode 100644 index 0000000000..0c604d6ced --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test3/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + createsemaphore.c +) + +add_executable(paltest_createsemaphorea_releasesemaphore_test3 + ${SOURCES} +) + +add_dependencies(paltest_createsemaphorea_releasesemaphore_test3 coreclrpal) + +target_link_libraries(paltest_createsemaphorea_releasesemaphore_test3 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test3/createsemaphore.c b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test3/createsemaphore.c new file mode 100644 index 0000000000..7c6db6b055 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test3/createsemaphore.c @@ -0,0 +1,200 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================ +** +** Source: createsemaphorea_releasesemaphore/test3/createsemaphore.c +** +** Purpose: Test attributes of CreateSemaphoreA and ReleaseSemaphore. +** Insure for CreateSemaphore that lInitialCount and lMaximumCount +** constraints are respected. Validate that CreateSemaphore rejects +** conditions where initial count and / or maximum count are negative +** and conditions where the initial count is greater than the maximum +** count. For ReleaseSemaphore validate that lpPreviousCount gets set +** to the previous semaphore count and lpPreviousCount can be NULL. +** Also establish ReleaseSemaphore fails when called in a semaphore +** with count equal to lMaximumCount. +** +** +**==========================================================================*/ + +#include <palsuite.h> + +struct testcase +{ + LPSECURITY_ATTRIBUTES lpSemaphoreAttributes; + LONG lInitialCount; + LONG lMaximumCount; + LPCTSTR lpName; + BOOL bNegativeTest; +}; + +struct testcase testCases[] = +{ + {NULL, -1, 1, NULL, TRUE}, + {NULL, 1, -1, NULL, TRUE}, + {NULL, -1, -1, NULL, TRUE}, + {NULL, 2, 1, NULL, TRUE}, + {NULL, 1, 2, NULL, FALSE}, + {NULL, 0, 10, NULL, FALSE}, + {NULL, INT_MAX - 1, INT_MAX, NULL, FALSE}, + {NULL, INT_MAX, INT_MAX, NULL, FALSE} +}; + +HANDLE hSemaphore[sizeof(testCases)/sizeof(struct testcase)]; + +BOOL cleanup(int index) +{ + int i; + BOOL bRet = TRUE; + for (i = 0; i < index; i++) + { + if (!CloseHandle(hSemaphore[i])) + { + bRet = FALSE; + Trace("PALSUITE ERROR: CloseHandle(%p) call failed for index %d\n", + hSemaphore[i], i); + } + } + return(bRet); +} + +int __cdecl main (int argc, char **argv) +{ + int i; + int j; + + if(0 != (PAL_Initialize(argc, argv))) + { + return (FAIL); + } + /* create semaphores */ + for (i = 0; i < sizeof(testCases)/sizeof(struct testcase); i++) + { + hSemaphore[i] = CreateSemaphoreA (testCases[i].lpSemaphoreAttributes, + testCases[i].lInitialCount, + testCases[i].lMaximumCount, + testCases[i].lpName); + + if (NULL == hSemaphore[i]) + { + if (!testCases[i].bNegativeTest) + { + Trace("PALSUITE ERROR: CreateSemaphoreA('%p' '%ld' '%ld' " + "'%p') returned NULL at index %d.\nGetLastError " + "returned %d.\n", testCases[i].lpSemaphoreAttributes, + testCases[i].lInitialCount, testCases[i].lMaximumCount, + testCases[i].lpName, i, GetLastError()); + if (i > 0) + { + cleanup(i - 1); + } + Fail(""); + } + else + { + continue; + } + } + + /* increment semaphore count to lMaximumCount */ + for (j = testCases[i].lInitialCount; (ULONG)j <= (ULONG)testCases[i].lMaximumCount; + j++) + { + if (testCases[i].lMaximumCount == j) + { + /* Call ReleaseSemaphore once more to ensure ReleaseSemaphore + fails */ + if(ReleaseSemaphore(hSemaphore[i], 1, NULL)) + { + Trace("PALSUITE ERROR: ReleaseSemaphore('%p' '%ld' '%p') " + "call returned %d\nwhen it should have returned " + "%d.\nThe semaphore's count was %d.\nGetLastError " + "returned %d.\n", hSemaphore[i], 1, NULL, TRUE, + FALSE, j, GetLastError()); + cleanup(i); + Fail(""); + } + } + else + { + int previous; + BOOL bRet = ReleaseSemaphore(hSemaphore[i], 1, &previous); + DWORD dwError = GetLastError(); + + if(!bRet) + { + Trace("PALSUITE ERROR: ReleaseSemaphore('%p' '%ld' '%p') " + "call returned %d\nwhen it should have returned " + "%d.\nThe semaphore count was %d and it's " + "lMaxCount was %d.\nGetLastError returned %d.\n", + hSemaphore[i], 1, &previous, bRet, TRUE, j, + testCases[i].lMaximumCount, dwError); + cleanup(i); + Fail(""); + } + if (previous != j) + { + Trace("PALSUITE ERROR: ReleaseSemaphore('%p' '%ld' '%p') " + "call set %p to %d instead of %d.\n The semaphore " + "count was %d and GetLastError returned %d.\n", + hSemaphore[i], 1, &previous, &previous, previous, + j, j, dwError); + cleanup(i); + Fail(""); + } + } + } + + // Skip exhaustive decrement tests for too large an initial count + if(testCases[i].lInitialCount >= INT_MAX - 1) + { + continue; + } + + /* decrement semaphore count to 0 */ + for (j = testCases[i].lMaximumCount; j >= 0; j--) + { + DWORD dwRet = WaitForSingleObject(hSemaphore[i], 0); + DWORD dwError = GetLastError(); + + if (0 == j) + { + /* WaitForSingleObject should report that the + semaphore is nonsignaled */ + if (WAIT_TIMEOUT != dwRet) + { + Trace("PALSUITE ERROR: WaitForSingleObject('%p' '%u') " + "call returned %d\nwhen it should have returned " + "%d.\nThe semaphore's count was %d.\nGetLastError " + "returned %d.\n", hSemaphore[i], 0, dwRet, + WAIT_TIMEOUT, j, dwError); + cleanup(i); + Fail(""); + } + } + else + { + /* WaitForSingleObject should report that the + semaphore is signaled */ + if (WAIT_OBJECT_0 != dwRet) + { + Trace("PALSUITE ERROR: WaitForSingleObject('%p' '%u') " + "call returned %d\nwhen it should have returned " + "%d.\nThe semaphore's count was %d.\nGetLastError " + "returned %d.\n", hSemaphore[i], 0, dwRet, + WAIT_OBJECT_0, j, dwError); + cleanup(i); + Fail(""); + } + } + } + } + PAL_Terminate(); + return (PASS); +} + + + + diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test3/testinfo.dat b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test3/testinfo.dat new file mode 100644 index 0000000000..d8cd590923 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreA_ReleaseSemaphore/test3/testinfo.dat @@ -0,0 +1,20 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateSemaphoreA / ReleaseSemaphore +Name = Positive Test for CreateSemaphoreA and ReleaseSemaphore +TYPE = DEFAULT +EXE1 = createsemaphore +Description += Test attributes of CreateSemaphoreA and ReleaseSemaphore. += Insure for CreateSemaphore that lInitialCount and lMaximumCount += constraints are respected. Validate that CreateSemaphore rejects += conditions where, initial count and / or maximum count are negative += and conditions where the initial count is greater than the maximum += count. For ReleaseSemaphore validate that lpPreviousCount gets set += to the previous semaphore count and lpPreviousCount can be NULL. += Also establish ReleaseSemaphore fails when called in a semaphore += with count equal to lMaximumCount. diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/CMakeLists.txt new file mode 100644 index 0000000000..1962ade358 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) +add_subdirectory(test3) + diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test1/CMakeLists.txt new file mode 100644 index 0000000000..b40f2695bc --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + CreateSemaphore.c +) + +add_executable(paltest_createsemaphorew_releasesemaphore_test1 + ${SOURCES} +) + +add_dependencies(paltest_createsemaphorew_releasesemaphore_test1 coreclrpal) + +target_link_libraries(paltest_createsemaphorew_releasesemaphore_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test1/CreateSemaphore.c b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test1/CreateSemaphore.c new file mode 100644 index 0000000000..854d16d0ef --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test1/CreateSemaphore.c @@ -0,0 +1,323 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: CreateSemaphoreW_ReleaseSemaphore/test1/CreateSemaphore.c +** +** Purpose: Test Semaphore operation using classic IPC problem: +** "Producer-Consumer Problem". +** +** Dependencies: CreateThread +** ReleaseSemaphore +** WaitForSingleObject +** Sleep +** fflush +** + +** +**=========================================================*/ + +#define UNICODE +#include <palsuite.h> + +#define PRODUCTION_TOTAL 26 + +#define _BUF_SIZE 10 + +DWORD dwThreadId; /* consumer thread identifier */ + +HANDLE hThread; /* handle to consumer thread */ + +HANDLE hSemaphoreM; /* handle to mutual exclusion semaphore */ + +HANDLE hSemaphoreE; /* handle to semaphore that counts empty buffer slots */ + +HANDLE hSemaphoreF; /* handle to semaphore that counts full buffer slots */ + +typedef struct Buffer +{ + short readIndex; + short writeIndex; + CHAR message[_BUF_SIZE]; + +} BufferStructure; + +CHAR producerItems[PRODUCTION_TOTAL + 1]; + +CHAR consumerItems[PRODUCTION_TOTAL + 1]; + +/* + * Read next message from the Buffer into provided pointer. + * Returns: 0 on failure, 1 on success. + */ +int +readBuf(BufferStructure *Buffer, char *c) +{ + if( Buffer -> writeIndex == Buffer -> readIndex ) + { + return 0; + } + *c = Buffer -> message[Buffer -> readIndex++]; + Buffer -> readIndex %= _BUF_SIZE; + return 1; +} + +/* + * Write message generated by the producer to Buffer. + * Returns: 0 on failure, 1 on success. + */ +int +writeBuf(BufferStructure *Buffer, CHAR c) +{ + if( ( ((Buffer -> writeIndex) + 1) % _BUF_SIZE) == + (Buffer -> readIndex) ) + { + return 0; + } + Buffer -> message[Buffer -> writeIndex++] = c; + Buffer -> writeIndex %= _BUF_SIZE; + return 1; +} + +/* + * Atomic decrement of semaphore value. + */ +VOID +down(HANDLE hSemaphore) +{ + switch ( (WaitForSingleObject ( + hSemaphore, + 10000))) /* Wait 10 seconds */ + { + case WAIT_OBJECT_0: /* + * Semaphore was signaled. OK to access + * semaphore. + */ + break; + case WAIT_ABANDONED: /* + * Object was mutex object whose owning + * thread has terminated. Shouldn't occur. + */ + Fail("WaitForSingleObject call returned 'WAIT_ABANDONED'.\n" + "Failing Test.\n"); + break; + case WAIT_FAILED: /* WaitForSingleObject function failed */ + Fail("WaitForSingleObject call returned 'WAIT_FAILED'.\n" + "GetLastError returned %d\nFailing Test.\n",GetLastError()); + break; + default: + Fail("WaitForSingleObject call returned an unexpected value.\n" + "Failing Test.\n"); + break; + } + +} + +/* + * Atomic increment of semaphore value. + */ +VOID +up(HANDLE hSemaphore) +{ + if (!ReleaseSemaphore ( + hSemaphore, + 1, + NULL) + ) + { + Fail("ReleaseSemaphore call failed. GetLastError returned %d\n", + GetLastError()); + } +} + +/* + * Sleep 500 milleseconds. + */ +VOID +consumerSleep(VOID) +{ + Sleep(500); +} + +/* + * Sleep between 10 milleseconds. + */ +VOID +producerSleep(VOID) +{ + Sleep(10); +} + +/* + * Produce a message and write the message to Buffer. + */ +VOID +producer(BufferStructure *Buffer) +{ + + int n = 0; + char c; + + while (n < PRODUCTION_TOTAL) + { + c = 'A' + n ; /* Produce Item */ + + down(hSemaphoreE); + down(hSemaphoreM); + + if (writeBuf(Buffer, c)) + { + Trace("Producer produces %c.\n", c); + fflush(stdout); + producerItems[n++] = c; + } + + up(hSemaphoreM); + up(hSemaphoreF); + + producerSleep(); + } + + return; +} + +/* + * Read and "Consume" the messages in Buffer. + */ +DWORD +PALAPI +consumer( LPVOID lpParam ) +{ + int n = 0; + char c; + + consumerSleep(); + + while (n < PRODUCTION_TOTAL) + { + + down(hSemaphoreF); + down(hSemaphoreM); + + if (readBuf((BufferStructure*)lpParam, &c)) + { + Trace("\tConsumer consumes %c.\n", c); + fflush(stdout); + consumerItems[n++] = c; + } + + up(hSemaphoreM); + up(hSemaphoreE); + + consumerSleep(); + } + + return 0; +} + +int __cdecl main (int argc, char **argv) +{ + + BufferStructure Buffer, *pBuffer; + + pBuffer = &Buffer; + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + /* + * Create Semaphores + */ + hSemaphoreM = CreateSemaphoreW ( + NULL, + 1, + 1, + NULL); + + if ( NULL == hSemaphoreM ) + { + Fail ( "hSemaphoreM = CreateSemaphoreW () - returned NULL\n" + "Failing Test.\nGetLastError returned %d\n", GetLastError()); + } + + + hSemaphoreE = CreateSemaphoreW ( + NULL, + _BUF_SIZE , + _BUF_SIZE , + NULL); + + if ( NULL == hSemaphoreE ) + { + Fail ( "hSemaphoreE = CreateSemaphoreW () - returned NULL\n" + "Failing Test.\nGetLastError returned %d\n", GetLastError()); + } + + hSemaphoreF = CreateSemaphoreW ( + NULL, + 0, + _BUF_SIZE , + NULL); + + if ( NULL == hSemaphoreF ) + { + Fail ( "hSemaphoreF = CreateSemaphoreW () - returned NULL\n" + "Failing Test.\nGetLastError returned %d\n", GetLastError()); + } + + + /* + * Initialize Buffer + */ + pBuffer->writeIndex = pBuffer->readIndex = 0; + + /* + * Create Consumer + */ + hThread = CreateThread( + NULL, + 0, + consumer, + &Buffer, + 0, + &dwThreadId); + + if ( NULL == hThread ) + { + Fail ( "CreateThread() returned NULL. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + } + + /* + * Start producing + */ + producer(pBuffer); + + /* + * Wait for consumer to complete + */ + WaitForSingleObject (hThread, INFINITE); + + /* + * Compare items produced vs. items consumed + */ + if ( 0 != strncmp (producerItems, consumerItems, PRODUCTION_TOTAL) ) + { + Fail("The producerItems string %s\n and the consumerItems string " + "%s\ndo not match. This could be a problem with the strncmp()" + " function\n FailingTest\nGetLastError() returned %d\n", + producerItems, consumerItems, GetLastError()); + } + + Trace ("producerItems and consumerItems arrays match. All %d\nitems " + "were produced and consumed in order.\nTest passed.\n", + PRODUCTION_TOTAL); + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test1/testinfo.dat b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test1/testinfo.dat new file mode 100644 index 0000000000..9127589333 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test1/testinfo.dat @@ -0,0 +1,18 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateSemaphoreW / ReleaseSemaphore +Name = Positive Test for CreateSemaphoreW and ReleaseSemaphore +TYPE = DEFAULT +EXE1 = createsemaphore +Description += Implementation of Producer / Consumer IPC problem using CreateSemaphoreW += and ReleaseSemaphore functions. This test case exercises CreateSemaphoreW += , ReleaseSemaphore, CreateThread and WaitForSingleObject functions. += Since there is no way to currently create "pseudo" random events in the += pal, this example does not behave as classic bounded buffers would. This += test case is designed to starve the consumer and have the producer fill += the buffer. diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test2/CMakeLists.txt new file mode 100644 index 0000000000..b14284d08f --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + CreateSemaphore.c +) + +add_executable(paltest_createsemaphorew_releasesemaphore_test2 + ${SOURCES} +) + +add_dependencies(paltest_createsemaphorew_releasesemaphore_test2 coreclrpal) + +target_link_libraries(paltest_createsemaphorew_releasesemaphore_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test2/CreateSemaphore.c b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test2/CreateSemaphore.c new file mode 100644 index 0000000000..62532737ac --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test2/CreateSemaphore.c @@ -0,0 +1,314 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: CreateSemaphoreW_ReleaseSemaphore/test1/CreateSemaphore.c +** +** Purpose: Test Semaphore operation using classic IPC problem: +** "Producer-Consumer Problem". +** +** Dependencies: CreateThread +** ReleaseSemaphore +** WaitForSingleObject +** Sleep +** fflush +** + +** +**=========================================================*/ + +#define UNICODE +#include <palsuite.h> + +#define PRODUCTION_TOTAL 26 + +#define _BUF_SIZE 10 + +DWORD dwThreadId; /* consumer thread identifier */ + +HANDLE hThread; /* handle to consumer thread */ + +HANDLE hSemaphoreM; /* handle to mutual exclusion semaphore */ + +HANDLE hSemaphoreE; /* handle to semaphore that counts empty buffer slots */ + +HANDLE hSemaphoreF; /* handle to semaphore that counts full buffer slots */ + +typedef struct Buffer +{ + short readIndex; + short writeIndex; + CHAR message[_BUF_SIZE]; + +} BufferStructure; + +CHAR producerItems[PRODUCTION_TOTAL + 1]; + +CHAR consumerItems[PRODUCTION_TOTAL + 1]; + +/* + * Read next message from the Buffer into provided pointer. + * Returns: 0 on failure, 1 on success. + */ +int +readBuf(BufferStructure *Buffer, char *c) +{ + if( Buffer -> writeIndex == Buffer -> readIndex ) + { + return 0; + } + *c = Buffer -> message[Buffer -> readIndex++]; + Buffer -> readIndex %= _BUF_SIZE; + return 1; +} + +/* + * Write message generated by the producer to Buffer. + * Returns: 0 on failure, 1 on success. + */ +int +writeBuf(BufferStructure *Buffer, CHAR c) +{ + if( ( ((Buffer -> writeIndex) + 1) % _BUF_SIZE) == + (Buffer -> readIndex) ) + { + return 0; + } + Buffer -> message[Buffer -> writeIndex++] = c; + Buffer -> writeIndex %= _BUF_SIZE; + return 1; +} + +/* + * Atomic decrement of semaphore value. + */ +VOID +down(HANDLE hSemaphore) +{ + switch ( (WaitForSingleObject ( + hSemaphore, + 10000))) + { + case WAIT_OBJECT_0: /* + * Semaphore was signaled. OK to access semaphore. + */ + break; + case WAIT_ABANDONED: /* + * Object was mutex object whose owning + * thread has terminated. Shouldn't occur. + */ + Fail("WaitForSingleObject call returned 'WAIT_ABANDONED'.\n" + "Failing Test.\n"); + break; + case WAIT_FAILED: /* WaitForSingleObject function failed */ + Fail("WaitForSingleObject call returned 'WAIT_FAILED'.\n" + "GetLastError returned %d\nFailing Test.\n",GetLastError()); + break; + default: + Fail("WaitForSingleObject call returned an unexpected value.\n" + "Failing Test.\n"); + break; + } + +} + +/* + * Atomic increment of semaphore value. + */ +VOID +up(HANDLE hSemaphore) +{ + if (!ReleaseSemaphore ( + hSemaphore, + 1, + NULL) + ) + { + Fail("ReleaseSemaphore call failed. GetLastError returned %d\n", + GetLastError()); + } +} + +/* + * Sleep 10 milleseconds. + */ +VOID +consumerSleep(VOID) +{ + Sleep(10); +} + +/* + * Sleep 500 milleseconds. + */ +VOID +producerSleep(VOID) +{ + Sleep(500); +} + +/* + * Produce a message and write the message to Buffer. + */ +VOID +producer(BufferStructure *Buffer) +{ + + int n = 0; + char c; + + while (n < PRODUCTION_TOTAL) + { + c = 'A' + n ; /* Produce Item */ + + down(hSemaphoreE); + down(hSemaphoreM); + + if (writeBuf(Buffer, c)) + { + Trace("Producer produces %c.\n", c); + fflush(stdout); + producerItems[n++] = c; + } + + up(hSemaphoreM); + up(hSemaphoreF); + + producerSleep(); + } + return; +} + +/* + * Read and "Consume" the messages in Buffer. + */ +DWORD +PALAPI +consumer( LPVOID lpParam ) +{ + int n = 0; + char c; + + consumerSleep(); + + while (n < PRODUCTION_TOTAL) + { + + down(hSemaphoreF); + down(hSemaphoreM); + + if (readBuf((BufferStructure*)lpParam, &c)) + { + Trace("\tConsumer consumes %c.\n", c); + fflush(stdout); + consumerItems[n++] = c; + } + + up(hSemaphoreM); + up(hSemaphoreE); + + consumerSleep(); + } + return 0; +} + +int __cdecl main (int argc, char **argv) +{ + + BufferStructure Buffer, *pBuffer; + + pBuffer = &Buffer; + + if(0 != (PAL_Initialize(argc, argv))) + { + return (FAIL); + } + + /* + * Create Semaphores + */ + hSemaphoreM = CreateSemaphoreW ( + NULL, + 1, + 1, + NULL); + + if ( NULL == hSemaphoreM ) + { + Fail ( "hSemaphoreM = CreateSemaphoreW () - returned NULL\n" + "Failing Test.\n"); + } + + hSemaphoreE = CreateSemaphoreW ( + NULL, + _BUF_SIZE , + _BUF_SIZE , + NULL); + + if ( NULL == hSemaphoreE ) + { + Fail ( "hSemaphoreE = CreateSemaphoreW () - returned NULL\n" + "Failing Test.\n"); + } + + hSemaphoreF = CreateSemaphoreW ( + NULL, + 0, + _BUF_SIZE , + NULL); + + if ( NULL == hSemaphoreF ) + { + Fail ( "hSemaphoreF = CreateSemaphoreW () - returned NULL\n" + "Failing Test.\n"); + } + + /* + * Initialize Buffer + */ + pBuffer->writeIndex = pBuffer->readIndex = 0; + + /* + * Create Consumer + */ + hThread = CreateThread( + NULL, + 0, + consumer, + &Buffer, + 0, + &dwThreadId); + + if ( NULL == hThread ) + { + Fail ( "CreateThread() returned NULL. Failing test.\n"); + } + + /* + * Start producing + */ + producer(pBuffer); + + /* + * Wait for consumer to complete + */ + WaitForSingleObject (hThread, INFINITE); + + if ( 0 != strncmp (producerItems, consumerItems, PRODUCTION_TOTAL) ) + { + Fail("The producerItems string %s\n and the consumerItems string " + "%s\ndo not match. This could be a problem with the strncmp()" + " function\n FailingTest\nGetLastError() returned %d\n", + producerItems, consumerItems, GetLastError()); + } + + Trace ("producerItems and consumerItems arrays match. All %d\nitems " + "were produced and consumed in order.\nTest passed.\n", + PRODUCTION_TOTAL); + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test2/testinfo.dat b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test2/testinfo.dat new file mode 100644 index 0000000000..32b107fd9e --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test2/testinfo.dat @@ -0,0 +1,18 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateSemaphoreW / ReleaseSemaphore +Name = Positive Test for CreateSemaphoreW and ReleaseSemaphore +TYPE = DEFAULT +EXE1 = createsemaphore +Description += Implementation of Producer / Consumer IPC problem using CreateSemaphoreW += and ReleaseSemaphore functions. This test case exercises CreateSemaphoreW += , ReleaseSemaphore, CreateThread and WaitForSingleObject functions. += Since there is no way to currently create "pseudo" random events in the += pal, this example does not behave as classic bounded buffers would. This += test case is designed to starve the producer and have the consumer fill += the buffer. diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test3/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test3/CMakeLists.txt new file mode 100644 index 0000000000..f7f0905761 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test3/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + createsemaphore.c +) + +add_executable(paltest_createsemaphorew_releasesemaphore_test3 + ${SOURCES} +) + +add_dependencies(paltest_createsemaphorew_releasesemaphore_test3 coreclrpal) + +target_link_libraries(paltest_createsemaphorew_releasesemaphore_test3 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test3/createsemaphore.c b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test3/createsemaphore.c new file mode 100644 index 0000000000..ea0a5e0846 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test3/createsemaphore.c @@ -0,0 +1,201 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================ +** +** Source: createsemaphorew_releasesemaphore/test3/createsemaphore.c +** +** Purpose: Test attributes of CreateSemaphoreW and ReleaseSemaphore. +** Insure for CreateSemaphore that lInitialCount and lMaximumCount +** constraints are respected. Validate that CreateSemaphore rejects +** conditions where initial count and / or maximum count are negative +** and conditions where the initial count is greater than the maximum +** count. For ReleaseSemaphore validate that lpPreviousCount gets set +** to the previous semaphore count and lpPreviousCount can be NULL. +** Also establish ReleaseSemaphore fails when called in a semaphore +** with count equal to lMaximumCount. +** +** +**==========================================================================*/ + +#include <palsuite.h> + +struct testcase +{ + LPSECURITY_ATTRIBUTES lpSemaphoreAttributes; + LONG lInitialCount; + LONG lMaximumCount; + LPCWSTR lpName; + BOOL bNegativeTest; +}; + +struct testcase testCases[] = +{ + {NULL, -1, 1, NULL, TRUE}, + {NULL, 1, -1, NULL, TRUE}, + {NULL, -1, -1, NULL, TRUE}, + {NULL, 2, 1, NULL, TRUE}, + {NULL, 1, 2, NULL, FALSE}, + {NULL, 0, 10, NULL, FALSE}, + {NULL, INT_MAX - 1, INT_MAX, NULL, FALSE}, + {NULL, INT_MAX, INT_MAX, NULL, FALSE} +}; + +HANDLE hSemaphore[sizeof(testCases)/sizeof(struct testcase)]; + +BOOL cleanup(int index) +{ + int i; + BOOL bRet = TRUE; + for (i = 0; i < index; i++) + { + if (!CloseHandle(hSemaphore[i])) + { + bRet = FALSE; + Trace("PALSUITE ERROR: CloseHandle(%p) call failed for index %d\n", + hSemaphore[i], i); + } + } + return(bRet); +} + +int __cdecl main (int argc, char **argv) +{ + int i; + int j; + + if(0 != (PAL_Initialize(argc, argv))) + { + return (FAIL); + } + /* create semaphores */ + for (i = 0; i < sizeof(testCases)/sizeof(struct testcase); i++) + { + hSemaphore[i] = CreateSemaphoreW (testCases[i].lpSemaphoreAttributes, + testCases[i].lInitialCount, + testCases[i].lMaximumCount, + testCases[i].lpName); + + if (NULL == hSemaphore[i]) + { + if (!testCases[i].bNegativeTest) + { + Trace("PALSUITE ERROR: CreateSemaphoreW('%p' '%ld' '%ld' " + "'%p') returned NULL at index %d.\nGetLastError " + "returned %d.\n", testCases[i].lpSemaphoreAttributes, + testCases[i].lInitialCount, testCases[i].lMaximumCount, + testCases[i].lpName, i, GetLastError()); + if (i > 0) + { + cleanup(i - 1); + } + Fail(""); + } + else + { + continue; + } + } + + /* increment semaphore count to lMaximumCount */ + for (j = testCases[i].lInitialCount; (ULONG)j <= (ULONG)testCases[i].lMaximumCount; + j++) + { + if (testCases[i].lMaximumCount == j) + { + /* Call ReleaseSemaphore once more to ensure ReleaseSemaphore + fails */ + if(ReleaseSemaphore(hSemaphore[i], 1, NULL)) + { + Trace("PALSUITE ERROR: ReleaseSemaphore('%p' '%ld' '%p') " + "call returned %d\nwhen it should have returned " + "%d.\nThe semaphore's count was %d.\nGetLastError " + "returned %d.\n", hSemaphore[i], 1, NULL, TRUE, + FALSE, j, GetLastError()); + cleanup(i); + Fail(""); + } + } + else + { + int previous; + BOOL bRet = ReleaseSemaphore(hSemaphore[i], 1, &previous); + DWORD dwError = GetLastError(); + + if(!bRet) + { + Trace("PALSUITE ERROR: ReleaseSemaphore('%p' '%ld' '%p') " + "call returned %d\nwhen it should have returned " + "%d.\nThe semaphore count was %d and it's " + "lMaxCount was %d.\nGetLastError returned %d.\n", + hSemaphore[i], 1, &previous, bRet, TRUE, j, + testCases[i].lMaximumCount, dwError); + cleanup(i); + Fail(""); + } + if (previous != j) + { + Trace("PALSUITE ERROR: ReleaseSemaphore('%p' '%ld' '%p') " + "call set %p to %d instead of %d.\n The semaphore " + "count was %d and GetLastError returned %d.\n", + hSemaphore[i], 1, &previous, &previous, previous, + j, j, dwError); + cleanup(i); + Fail(""); + } + } + } + + // Skip exhaustive decrement tests for too large an initial count + if(testCases[i].lInitialCount >= INT_MAX - 1) + { + continue; + } + + /* decrement semaphore count to 0 */ + for (j = testCases[i].lMaximumCount; j >= 0; j--) + { + DWORD dwRet = WaitForSingleObject(hSemaphore[i], 0); + DWORD dwError = GetLastError(); + + if (0 == j) + { + /* WaitForSingleObject should report that the + semaphore is nonsignaled */ + if (WAIT_TIMEOUT != dwRet) + { + Trace("PALSUITE ERROR: WaitForSingleObject('%p' '%u') " + "call returned %d\nwhen it should have returned " + "%d.\nThe semaphore's count was %d.\nGetLastError " + "returned %d.\n", hSemaphore[i], 0, dwRet, + WAIT_TIMEOUT, j, dwError); + cleanup(i); + Fail(""); + } + } + else + { + /* WaitForSingleObject should report that the + semaphore is signaled */ + if (WAIT_OBJECT_0 != dwRet) + { + Trace("PALSUITE ERROR: WaitForSingleObject('%p' '%u') " + "call returned %d\nwhen it should have returned " + "%d.\nThe semaphore's count was %d.\nGetLastError " + "returned %d.\n", hSemaphore[i], 0, dwRet, + WAIT_OBJECT_0, j, dwError); + cleanup(i); + Fail(""); + } + } + } + } + PAL_Terminate(); + return (PASS); +} + + + + + diff --git a/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test3/testinfo.dat b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test3/testinfo.dat new file mode 100644 index 0000000000..beaac95f97 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test3/testinfo.dat @@ -0,0 +1,20 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateSemaphoreW / ReleaseSemaphore +Name = Positive Test for CreateSemaphoreW and ReleaseSemaphore +TYPE = DEFAULT +EXE1 = createsemaphore +Description += Test attributes of CreateSemaphoreW and ReleaseSemaphore. += Insure for CreateSemaphore that lInitialCount and lMaximumCount += constraints are respected. Validate that CreateSemaphore rejects += conditions where, initial count and / or maximum count are negative += and conditions where the initial count is greater than the maximum += count. For ReleaseSemaphore validate that lpPreviousCount gets set += to the previous semaphore count and lpPreviousCount can be NULL. += Also establish ReleaseSemaphore fails when called in a semaphore += with count equal to lMaximumCount. diff --git a/src/pal/tests/palsuite/threading/CreateThread/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateThread/CMakeLists.txt new file mode 100644 index 0000000000..1962ade358 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateThread/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) +add_subdirectory(test3) + diff --git a/src/pal/tests/palsuite/threading/CreateThread/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateThread/test1/CMakeLists.txt new file mode 100644 index 0000000000..d3921c4409 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateThread/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_createthread_test1 + ${SOURCES} +) + +add_dependencies(paltest_createthread_test1 coreclrpal) + +target_link_libraries(paltest_createthread_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateThread/test1/test1.c b/src/pal/tests/palsuite/threading/CreateThread/test1/test1.c new file mode 100644 index 0000000000..2dd2f6acb6 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateThread/test1/test1.c @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test1.c +** +** Purpose: Test for CreateThread. Call CreateThread and ensure +** that it succeeds. Also check to ensure the paramater is passed +** properly. +** +** +**=========================================================*/ + +#include <palsuite.h> + +#ifndef PLATFORM_UNIX +#define LLFORMAT "%I64u" +#else +#define LLFORMAT "%llu" +#endif + +ULONGLONG dwCreateThreadTestParameter = 0; + +DWORD PALAPI CreateThreadTestThread( LPVOID lpParameter) +{ + DWORD dwRet = 0; + + /* save parameter for test */ + dwCreateThreadTestParameter = (ULONGLONG)lpParameter; + + return dwRet; +} + +BOOL CreateThreadTest() +{ + BOOL bRet = FALSE; + DWORD dwRet = 0; + + LPSECURITY_ATTRIBUTES lpThreadAttributes = NULL; + DWORD dwStackSize = 0; + LPTHREAD_START_ROUTINE lpStartAddress = &CreateThreadTestThread; + LPVOID lpParameter = lpStartAddress; + DWORD dwCreationFlags = 0; /* run immediately */ + DWORD dwThreadId = 0; + + HANDLE hThread = 0; + + dwCreateThreadTestParameter = 0; + + /* Create a thread, passing the appropriate paramaters as declared + above. + */ + + hThread = CreateThread( lpThreadAttributes, + dwStackSize, + lpStartAddress, + lpParameter, + dwCreationFlags, + &dwThreadId ); + + /* Ensure that the HANDLE is not invalid! */ + if (hThread != INVALID_HANDLE_VALUE) + { + dwRet = WaitForSingleObject(hThread,INFINITE); + + if (dwRet != WAIT_OBJECT_0) + { + Trace("CreateThreadTest:WaitForSingleObject " + "failed (%x)\n",GetLastError()); + } + else + { + /* Check to ensure that the parameter passed to the thread + function is the same in the function as what we passed. + */ + + if (dwCreateThreadTestParameter != (ULONGLONG)lpParameter) + { + Trace("CreateThreadTest:parameter error. The " + "parameter passed should have been " LLFORMAT " but when " + "passed to the Thread function it was " LLFORMAT " . GetLastError[%x]\n", + dwCreateThreadTestParameter,lpParameter, GetLastError()); + } + else + { + bRet = TRUE; + } + + } + CloseHandle(hThread); + } + else + { + Trace("CreateThreadTest:CreateThread failed (%x)\n",GetLastError()); + } + + return bRet; +} + + +int __cdecl main(int argc, char **argv) +{ + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + if(!CreateThreadTest()) + { + Fail ("Test failed\n"); + } + + Trace("Test Passed\n"); + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/CreateThread/test1/testinfo.dat b/src/pal/tests/palsuite/threading/CreateThread/test1/testinfo.dat new file mode 100644 index 0000000000..3ae70625c2 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateThread/test1/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateThread +Name = Positive Test for CreateThread +TYPE = DEFAULT +EXE1 = test1 +Description += Test for CreateThread. Call CreateThread and ensure += that it succeeds. Also check to ensure the paramater is passed += properly. diff --git a/src/pal/tests/palsuite/threading/CreateThread/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateThread/test2/CMakeLists.txt new file mode 100644 index 0000000000..350cf66e9b --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateThread/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test2.c +) + +add_executable(paltest_createthread_test2 + ${SOURCES} +) + +add_dependencies(paltest_createthread_test2 coreclrpal) + +target_link_libraries(paltest_createthread_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateThread/test2/test2.c b/src/pal/tests/palsuite/threading/CreateThread/test2/test2.c new file mode 100644 index 0000000000..f64c32ea87 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateThread/test2/test2.c @@ -0,0 +1,184 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*=========================================================== +** +** Source: test2.c +** +** Purpose: Test that lpThreadId is assigned the correct +** threadId value and that lpThreadId can be NULL. +** +** +**=========================================================*/ + +#include <palsuite.h> + +#define NUM_TESTS 3 + +HANDLE hThread[NUM_TESTS]; +DWORD dwThreadId[NUM_TESTS]; +volatile BOOL bResult[NUM_TESTS]; +volatile DWORD dwThreadId1[NUM_TESTS]; + +DWORD PALAPI Thread( LPVOID lpParameter) +{ + dwThreadId1[(DWORD) lpParameter] = GetCurrentThreadId(); + bResult[(DWORD) lpParameter] = TRUE; + return (DWORD) lpParameter; +} + +struct testCase +{ + LPSECURITY_ATTRIBUTES lpThreadAttributes; + DWORD dwStackSize; + LPTHREAD_START_ROUTINE lpStartAddress; + DWORD dwCreationFlags; + LPVOID lpThreadId; +}; + +struct testCase testCases[]= +{ + {NULL, 0, &Thread, 0, NULL}, + {NULL, 0, &Thread, CREATE_SUSPENDED, NULL}, + {NULL, 0, &Thread, 0, (LPVOID) 1} +}; + +/* + * close handles + */ +BOOL cleanup(int index) +{ + int i; + BOOL bRet = TRUE; + + for (i = 0; i < index; i++) + { + if (!CloseHandle(hThread[i])) + { + bRet = FALSE; + Trace("PALSUITE ERROR: CloseHandle(%p) call failed for index %d\n", + hThread[i], i); + } + } + + return(bRet); +} + +int __cdecl main(int argc, char **argv) +{ + SIZE_T i; + DWORD dwRetWFSO; + DWORD dwRetRT; + BOOL bRet = TRUE; + + if(0 != (PAL_Initialize(argc, argv))) + { + return (FAIL); + } + + /* set results array to FALSE */ + for (i = 0; i < NUM_TESTS; i++) + { + bResult[i]=FALSE; + dwThreadId[i]=0; + } + + for (i = 0; i < NUM_TESTS; i++) + { + if (NULL != testCases[i].lpThreadId) + { + testCases[i].lpThreadId = &dwThreadId[i]; + } + /* pass the index as the thread argument */ + hThread[i] = CreateThread( testCases[i].lpThreadAttributes, + testCases[i].dwStackSize, + testCases[i].lpStartAddress, + (LPVOID) i, + testCases[i].dwCreationFlags, + testCases[i].lpThreadId); + if (hThread[i] == NULL) + { + Trace("PALSUITE ERROR: CreateThread('%p' '%d' '%p' '%p' '%d' " + "'%p') call failed.\nGetLastError returned '%u'.\n", + testCases[i].lpThreadAttributes, testCases[i].dwStackSize, + testCases[i].lpStartAddress, (LPVOID) i, + testCases[i].dwCreationFlags, + testCases[i].lpThreadId, GetLastError()); + cleanup(i - 1); + Fail(""); + } + + /* Resume suspended threads */ + if (testCases[i].dwCreationFlags == CREATE_SUSPENDED) + { + dwRetRT = ResumeThread (hThread[i]); + if (dwRetRT != 1) + { + Trace ("PALSUITE ERROR: ResumeThread(%p) " + "call returned %d it should have returned %d.\n" + "GetLastError returned %u.\n", hThread[i], dwRetRT, + 1, GetLastError()); + cleanup(i); + Fail(""); + } + } + } + + /* cleanup */ + for (i = 0; i < NUM_TESTS; i++) + { + dwRetWFSO = WaitForSingleObject(hThread[i], 10000); + if (dwRetWFSO != WAIT_OBJECT_0) + { + Trace ("PALSUITE ERROR: WaitForSingleObject('%p' '%d') " + "call returned %d instead of WAIT_OBJECT_0 ('%d').\n" + "GetLastError returned %u.\n", hThread[i], 10000, + dwRetWFSO, WAIT_OBJECT_0, GetLastError()); + cleanup(i); + Fail(""); + } + } + if(!cleanup(NUM_TESTS)) + { + Fail(""); + } + + for (i = 0; i < NUM_TESTS; i++) + { + /* + * check to see that all threads were created and were passed + * the array index as an argument. + */ + if (FALSE == bResult[i]) + { + bRet = FALSE; + Trace("PALSUITE ERROR: result[%d]=%d. It should be %d\n", i, + FALSE, TRUE); + } + /* + * check to see that lpThreadId received the correct value. + */ + if (0 != dwThreadId[i]) + { + if (dwThreadId[i] != dwThreadId1[i]) + { + bRet = FALSE; + Trace("PALSUITE ERROR: dwThreadId[%d]=%p and dwThreadId1[%d]" + "=%p\nThese values should be identical.\n", i, + dwThreadId[i], i, dwThreadId1[i]); + } + } + } + if (!bRet) + { + cleanup(NUM_TESTS); + Fail(""); + } + + PAL_Terminate(); + return (PASS); +} + + + diff --git a/src/pal/tests/palsuite/threading/CreateThread/test2/testinfo.dat b/src/pal/tests/palsuite/threading/CreateThread/test2/testinfo.dat new file mode 100644 index 0000000000..0333beb360 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateThread/test2/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateThread +Name = Positive Test for CreateThread +TYPE = DEFAULT +EXE1 = test2 +Description += Test that lpThreadId is assigned the correct threadId value and += that lpThreadId can be NULL. diff --git a/src/pal/tests/palsuite/threading/CreateThread/test3/CMakeLists.txt b/src/pal/tests/palsuite/threading/CreateThread/test3/CMakeLists.txt new file mode 100644 index 0000000000..923d7a72ab --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateThread/test3/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test3.c +) + +add_executable(paltest_createthread_test3 + ${SOURCES} +) + +add_dependencies(paltest_createthread_test3 coreclrpal) + +target_link_libraries(paltest_createthread_test3 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CreateThread/test3/test3.c b/src/pal/tests/palsuite/threading/CreateThread/test3/test3.c new file mode 100644 index 0000000000..0c44d1fdd0 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateThread/test3/test3.c @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*=========================================================== +** +** Source: test3.c +** +** Purpose: Check to see that the handle CreateThread returns +** can be closed while the thread is still running. +** +** +**=========================================================*/ + +#include <palsuite.h> + +HANDLE hThread; +HANDLE hEvent; + +DWORD PALAPI Thread( LPVOID lpParameter) +{ + DWORD dwRet; + dwRet = WaitForSingleObject(hEvent, INFINITE); + /* if this thread continues beyond here, fail */ + Fail(""); + + return 0; +} + +int __cdecl main(int argc, char **argv) +{ + DWORD dwThreadId; + DWORD dwRet; + + if(0 != (PAL_Initialize(argc, argv))) + { + return (FAIL); + } + + hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (hEvent == NULL) + { + Fail("PALSUITE ERROR: CreateEvent call #0 failed. GetLastError " + "returned %u.\n", GetLastError()); + } + + /* pass the index as the thread argument */ + hThread = CreateThread( NULL, + 0, + &Thread, + (LPVOID) 0, + 0, + &dwThreadId); + if (hThread == NULL) + { + Trace("PALSUITE ERROR: CreateThread('%p' '%d' '%p' '%p' '%d' '%p') " + "call failed.\nGetLastError returned '%u'.\n", NULL, + 0, &Thread, (LPVOID) 0, 0, &dwThreadId, GetLastError()); + if (0 == CloseHandle(hEvent)) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%u'.\n", hEvent); + } + Fail(""); + } + + dwRet = WaitForSingleObject(hThread, 10000); + if (dwRet != WAIT_TIMEOUT) + { + Trace ("PALSUITE ERROR: WaitForSingleObject('%p' '%d') " + "call returned %d instead of WAIT_TIMEOUT ('%d').\n" + "GetLastError returned '%u'.\n", hThread, 10000, + dwRet, WAIT_TIMEOUT, GetLastError()); + Fail(""); + } + + if (0 == CloseHandle(hThread)) + { + Trace("PALSUITE ERROR: Unable to CloseHandle(%p) on a running thread." + "\nGetLastError returned '%u'.\n", hThread, GetLastError()); + if (0 == CloseHandle(hEvent)) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "cleanup.\nGetLastError returned '%u'.\n", hEvent, + GetLastError()); + } + Fail(""); + } + if (0 == CloseHandle(hEvent)) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "cleanup.\nGetLastError returned '%u'.\n", hEvent, + GetLastError()); + Fail(""); + } + + PAL_Terminate(); + return (PASS); +} + diff --git a/src/pal/tests/palsuite/threading/CreateThread/test3/testinfo.dat b/src/pal/tests/palsuite/threading/CreateThread/test3/testinfo.dat new file mode 100644 index 0000000000..712c3a6652 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CreateThread/test3/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CreateThread +Name = Positive Test for CreateThread +TYPE = DEFAULT +EXE1 = test3 +Description += Check to see that the handle CreateThread returns can be closed while += the thread is still running. + diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/CMakeLists.txt b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/CMakeLists.txt new file mode 100644 index 0000000000..b7c6e394b0 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) +add_subdirectory(test3) +add_subdirectory(test4) +add_subdirectory(test5) +add_subdirectory(test6) +add_subdirectory(test7) +add_subdirectory(test8) + diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test1/CMakeLists.txt new file mode 100644 index 0000000000..5ba04fd801 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + InitializeCriticalSection.c +) + +add_executable(paltest_criticalsectionfunctions_test1 + ${SOURCES} +) + +add_dependencies(paltest_criticalsectionfunctions_test1 coreclrpal) + +target_link_libraries(paltest_criticalsectionfunctions_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test1/InitializeCriticalSection.c b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test1/InitializeCriticalSection.c new file mode 100644 index 0000000000..f294cea472 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test1/InitializeCriticalSection.c @@ -0,0 +1,235 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: criticalsectionfunctions/test1/initializecriticalsection.c +** +** Purpose: Test Semaphore operation using classic IPC problem: +** "Producer-Consumer Problem". +** +** Dependencies: CreateThread +** InitializeCriticalSection +** EnterCriticalSection +** LeaveCriticalSection +** DeleteCriticalSection +** WaitForSingleObject +** Sleep +** + +** +**=========================================================*/ + +#include <palsuite.h> + +#define PRODUCTION_TOTAL 26 + +#define _BUF_SIZE 10 + +DWORD dwThreadId; /* consumer thread identifier */ + +HANDLE hThread; /* handle to consumer thread */ + +CRITICAL_SECTION CriticalSectionM; /* Critical Section Object (used as mutex) */ + +typedef struct Buffer +{ + short readIndex; + short writeIndex; + CHAR message[_BUF_SIZE]; + +} BufferStructure; + +CHAR producerItems[PRODUCTION_TOTAL + 1]; + +CHAR consumerItems[PRODUCTION_TOTAL + 1]; + +/* + * Read next message from the Buffer into provided pointer. + * Returns: 0 on failure, 1 on success. + */ +int +readBuf(BufferStructure *Buffer, char *c) +{ + if( Buffer -> writeIndex == Buffer -> readIndex ) + { + return 0; + } + *c = Buffer -> message[Buffer -> readIndex++]; + Buffer -> readIndex %= _BUF_SIZE; + return 1; +} + +/* + * Write message generated by the producer to Buffer. + * Returns: 0 on failure, 1 on success. + */ +int +writeBuf(BufferStructure *Buffer, CHAR c) +{ + if( ( ((Buffer -> writeIndex) + 1) % _BUF_SIZE) == + (Buffer -> readIndex) ) + { + return 0; + } + Buffer -> message[Buffer -> writeIndex++] = c; + Buffer -> writeIndex %= _BUF_SIZE; + return 1; +} + +/* + * Sleep 500 milleseconds. + */ +VOID +consumerSleep(VOID) +{ + Sleep(500); +} + +/* + * Sleep between 10 milleseconds. + */ +VOID +producerSleep(VOID) +{ + Sleep(10); +} + +/* + * Produce a message and write the message to Buffer. + */ +VOID +producer(BufferStructure *Buffer) +{ + + int n = 0; + char c; + + while (n < PRODUCTION_TOTAL) + { + c = 'A' + n ; /* Produce Item */ + + EnterCriticalSection(&CriticalSectionM); + + if (writeBuf(Buffer, c)) + { + printf("Producer produces %c.\n", c); + producerItems[n++] = c; + } + + LeaveCriticalSection(&CriticalSectionM); + + producerSleep(); + } + + return; +} + +/* + * Read and "Consume" the messages in Buffer. + */ +DWORD +PALAPI +consumer( LPVOID lpParam ) +{ + int n = 0; + char c; + + consumerSleep(); + + while (n < PRODUCTION_TOTAL) + { + + EnterCriticalSection(&CriticalSectionM); + + if (readBuf((BufferStructure*)lpParam, &c)) + { + printf("\tConsumer consumes %c.\n", c); + consumerItems[n++] = c; + } + + LeaveCriticalSection(&CriticalSectionM); + + consumerSleep(); + } + + return 0; +} + +int __cdecl main (int argc, char **argv) +{ + + BufferStructure Buffer, *pBuffer; + + pBuffer = &Buffer; + + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + /* + * Create mutual exclusion mechanisms + */ + + InitializeCriticalSection ( &CriticalSectionM ); + + /* + * Initialize Buffer + */ + pBuffer->writeIndex = pBuffer->readIndex = 0; + + + + /* + * Create Consumer + */ + hThread = CreateThread( + NULL, + 0, + consumer, + &Buffer, + 0, + &dwThreadId); + + if ( NULL == hThread ) + { + Fail ( "CreateThread() returned NULL. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + } + + /* + * Start producing + */ + producer(pBuffer); + + /* + * Wait for consumer to complete + */ + WaitForSingleObject (hThread, INFINITE); + + /* + * Compare items produced vs. items consumed + */ + if ( 0 != strncmp (producerItems, consumerItems, PRODUCTION_TOTAL) ) + { + Fail("The producerItems string %s\n and the consumerItems string " + "%s\ndo not match. This could be a problem with the strncmp()" + " function\n FailingTest\nGetLastError() returned %d\n", + producerItems, consumerItems, GetLastError()); + } + + /* + * Clean up Critical Section object + */ + DeleteCriticalSection(&CriticalSectionM); + + Trace("producerItems and consumerItems arrays match. All %d\nitems " + "were produced and consumed in order.\nTest passed.\n", + PRODUCTION_TOTAL); + + PAL_Terminate(); + return (PASS); + +} diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test1/testinfo.dat b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test1/testinfo.dat new file mode 100644 index 0000000000..494b899b90 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test1/testinfo.dat @@ -0,0 +1,21 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = InitializeCriticalSection / EnterCriticalSection / LeaveCriticalSection / DeleteCriticalSection +Name = Positive Test for InitializeCriticalSection, EnterCriticalSection, LeaveCriticalSection and DeleteCriticalSection +TYPE = DEFAULT +EXE1 = initializecriticalsection +Description += Implementation of Producer / Consumer IPC problem using the += "CriticalSection" functions to provide a mutual exclusion mechanism. += This test case exercises InitializeCriticalSection, EnterCriticalSection, += LeaveCriticalSection, DeleteCriticalSection, and WaitForSingleObject += functions. += This case doesn't work with more than one producer and one consumer. += The producer thread and consumer thread each take turns blocking on += the CriticalSection object and do not have any other synchronization += mechanisms. This prevents adding producers or consumers as there are += no mechanisms to block them once the buffer is full. diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test2/CMakeLists.txt new file mode 100644 index 0000000000..d26658cf5d --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test2.c +) + +add_executable(paltest_criticalsectionfunctions_test2 + ${SOURCES} +) + +add_dependencies(paltest_criticalsectionfunctions_test2 coreclrpal) + +target_link_libraries(paltest_criticalsectionfunctions_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test2/test2.c b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test2/test2.c new file mode 100644 index 0000000000..47659a1c18 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test2/test2.c @@ -0,0 +1,226 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: CriticalSectionFunctions/test2/test2.c +** +** Purpose: Test that we are able to nest critical section calls. +** The initial thread makes a call to EnterCriticalSection once, +** blocking on a CRITICAL_SECTION object and creates a new thread. +** The newly created thread blocks on the same CRITICAL_SECTION object. +** The first thread now makes a call to LeaveCriticalSection. +** Test to see that the new thread doesn't get unblocked. +** +** Dependencies: CreateThread +** InitializeCriticalSection +** EnterCriticalSection +** LeaveCriticalSection +** DeleteCriticalSection +** WaitForSingleObject +** + +** +**=========================================================*/ + +#include <palsuite.h> + +CRITICAL_SECTION CriticalSection; + +volatile BOOL t0_tflag = FAIL; /* thread 0 timeout flag */ +volatile BOOL t1_aflag = FAIL; /* thread 1 access flag */ +volatile BOOL t1_cflag = FAIL; /* thread 1 critical section flag */ +volatile BOOL bTestResult = FAIL; + +DWORD PALAPI Thread(LPVOID lpParam) +{ + t1_aflag = PASS; + EnterCriticalSection(&CriticalSection); + t1_cflag = PASS; + LeaveCriticalSection(&CriticalSection); + return 0; +} + +int __cdecl main (int argc, char **argv) +{ + HANDLE hThread; + DWORD dwThreadId; + DWORD dwRet; + + if(0 != (PAL_Initialize(argc, argv))) + { + return (bTestResult); + } + + /* + * Create critical section object and enter it + */ + InitializeCriticalSection ( &CriticalSection ); + EnterCriticalSection(&CriticalSection); + + /* + * Create a suspended thread + */ + hThread = CreateThread(NULL, + 0, + &Thread, + (LPVOID) NULL, + CREATE_SUSPENDED, + &dwThreadId); + + if (hThread == NULL) + { + Trace("PALSUITE ERROR: CreateThread call failed. GetLastError " + "returned %d.\n", GetLastError()); + LeaveCriticalSection(&CriticalSection); + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + + EnterCriticalSection(&CriticalSection); + /* + * Set priority of the thread to greater than that of the currently + * running thread so it is guaranteed to run. + */ + dwRet = (DWORD) SetThreadPriority(hThread, THREAD_PRIORITY_ABOVE_NORMAL); + + if (0 == dwRet) + { + Trace("PALSUITE ERROR: SetThreadPriority (%p, %d) call failed.\n" + "GetLastError returned %d.\n", hThread, + THREAD_PRIORITY_NORMAL, GetLastError()); + LeaveCriticalSection(&CriticalSection); + CloseHandle(hThread); + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + + dwRet = ResumeThread(hThread); + + if (-1 == dwRet) + { + Trace("PALSUITE ERROR: ResumeThread(%p) call failed.\nGetLastError " + "returned %d.\n", hThread, GetLastError()); + LeaveCriticalSection(&CriticalSection); + CloseHandle(hThread); + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + /* + * Sleep until we know the thread has been invoked. This sleep in + * combination with the higher priority of the other thread should + * guarantee both threads block on the critical section. + */ + while (t1_aflag == FAIL) + { + Sleep(1); + } + + LeaveCriticalSection(&CriticalSection); + + switch ((WaitForSingleObject( + hThread, + 10000))) /* Wait 10 seconds */ + { + case WAIT_OBJECT_0: + /* Object (thread) is signaled */ + LeaveCriticalSection(&CriticalSection); + CloseHandle(hThread); + DeleteCriticalSection(&CriticalSection); + Fail("PALSUITE ERROR: WaitForSingleObject(%p,%d) should have " + "returned\nWAIT_TIMEOUT ('%d'), instead it returned " + "WAIT_OBJECT_0 ('%d').\nA nested LeaveCriticalSection(%p) " + "call released both threads that were waiting on it!\n", + hThread, 10000, WAIT_TIMEOUT, WAIT_OBJECT_0, &CriticalSection); + break; + case WAIT_ABANDONED: + /* + * Object was mutex object whose owning + * thread has terminated. Shouldn't occur. + */ + Trace("PALSUITE ERROR: WaitForSingleObject(%p,%d) should have " + "returned\nWAIT_TIMEOUT ('%d'), instead it returned " + "WAIT_ABANDONED ('%d').\nGetLastError returned '%d'\n", + hThread, 10000, WAIT_TIMEOUT, WAIT_ABANDONED, GetLastError()); + LeaveCriticalSection(&CriticalSection); + CloseHandle(hThread); + DeleteCriticalSection(&CriticalSection); + Fail(""); + break; + case WAIT_FAILED: /* WaitForSingleObject function failed */ + Trace("PALSUITE ERROR: WaitForSingleObject(%p,%d) should have " + "returned\nWAIT_TIMEOUT ('%d'), instead it returned " + "WAIT_FAILED ('%d').\nGetLastError returned '%d'\n", + hThread, 10000, WAIT_TIMEOUT, WAIT_FAILED, GetLastError()); + LeaveCriticalSection(&CriticalSection); + CloseHandle(hThread); + DeleteCriticalSection(&CriticalSection); + Fail(""); + break; + case WAIT_TIMEOUT: + /* + * We expect this thread to timeout waiting for the + * critical section object to become available. + */ + t0_tflag = PASS; + break; + } + + LeaveCriticalSection(&CriticalSection); + + if (WAIT_OBJECT_0 != WaitForSingleObject (hThread, 10000)) + { + if (0 == CloseHandle(hThread)) + { + Trace("PALSUITE ERROR: CloseHandle(%p) call failed.\n" + "WaitForSingleObject(%p,%d) should have returned " + "WAIT_OBJECT_0 ('%d').\nBoth calls failed. " + "Deleted CRITICAL_SECTION object which likely means\n" + "thread %p is now in an undefined state. GetLastError " + "returned '%d'.\n", hThread, hThread, 10000, WAIT_OBJECT_0, + hThread, GetLastError()); + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + else + { + Trace("PALSUITE ERROR: WaitForSingleObject(%p,%d) should have " + "returned WAIT_OBJECT_0 ('%d').\n GetLastError returned " + "'%d'.\n", hThread, hThread, 10000, WAIT_OBJECT_0, + hThread, GetLastError()); + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + } + + if (0 == CloseHandle(hThread)) + { + Trace("PALSUITE ERROR: CloseHandle(%p) call failed.\n" + "Deleted CRITICAL_SECTION object which likely means\n" + "thread %p is now in an undefined state. GetLastError " + "returned '%d'.\n", hThread, hThread, GetLastError()); + DeleteCriticalSection(&CriticalSection); + Fail(""); + + } + DeleteCriticalSection(&CriticalSection); + /* + * Ensure both thread 0 experienced a wait timeout and thread 1 + * accessed the critical section or fail the test, otherwise pass it. + */ + if ((t0_tflag == FAIL) || (t1_cflag == FAIL)) + { + Trace("PALSUITE ERROR: Thread 0 returned %d when %d was expected.\n" + "Thread 1 returned %d when %d was expected.\n", t0_tflag, + PASS, t1_cflag, PASS); + bTestResult=FAIL; + } + else + { + bTestResult=PASS; + } + + PAL_TerminateEx(bTestResult); + return (bTestResult); +} diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test2/testinfo.dat b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test2/testinfo.dat new file mode 100644 index 0000000000..06842124b9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test2/testinfo.dat @@ -0,0 +1,18 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CriticalSectionFunctions +Name = Positive test to ensure CRITICAL_SECTION objects can be nested +TYPE = DEFAULT +EXE1 = test2 +Description += Test that we are able to nest critical section calls. += The initial thread makes a call to EnterCriticalSection once, += blocking on a CRITICAL_SECTION object and creates a new thread. += The newly created thread blocks on the same CRITICAL_SECTION object. += The first thread now makes a call to LeaveCriticalSection. += Test to see that the new thread doesn't get unblocked. + diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test3/CMakeLists.txt b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test3/CMakeLists.txt new file mode 100644 index 0000000000..8bd2a72878 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test3/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test3.c +) + +add_executable(paltest_criticalsectionfunctions_test3 + ${SOURCES} +) + +add_dependencies(paltest_criticalsectionfunctions_test3 coreclrpal) + +target_link_libraries(paltest_criticalsectionfunctions_test3 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test3/test3.c b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test3/test3.c new file mode 100644 index 0000000000..d5911267b2 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test3/test3.c @@ -0,0 +1,376 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: CriticalSectionFunctions/test3/test3.c +** +** Purpose: Create two threads to exercise TryEnterCriticalSection +** and EnterCriticalSection. TryEnterCriticalSection acquires +** and holds a CRITICAL_SECTION object. Another call to +** TryEnterCriticalSection is made from a different thread, at +** this time, to establish a call to TryEnterCriticalSection +** will return immediatly and to establish +** TryEnterCriticalSection returns the proper value when it +** attempts to lock a CRITICAL_SECTION that is already owned +** by another thread. The CRITICAL_SECTION object is then +** released and held by a call to EnterCriticalSection. A new +** thread is invoked and attempts to acquire the held +** CRITICAL_SECTION with a call to TryEnterCriticalSection. +** TryEnterCriticalSection returns immediatly and returns +** with the value that states the CRITICAL_SECTION object is +** held by another thread. This establishes +** TryEnterCriticalSection behaves the same way with +** CriticalSections locked by TryEnterCriticalSection and +** EnterCriticalSection. +** +** +**===================================================================*/ +#include <palsuite.h> + +#define NUM_THREADS 2 + +HANDLE hThread[NUM_THREADS]; +HANDLE hEvent[NUM_THREADS]; +CRITICAL_SECTION CriticalSection; +BOOL bRet = FAIL; + +DWORD PALAPI Thread(LPVOID lpParam) +{ + DWORD dwRet; + + if (0 == TryEnterCriticalSection(&CriticalSection)) + { + dwRet = WaitForMultipleObjects(NUM_THREADS, hEvent, TRUE, 10000); + if ((WAIT_OBJECT_0 > dwRet) || + ((WAIT_OBJECT_0 + NUM_THREADS - 1) < dwRet)) + { +#if 0 + if (0 == CloseHandle(hThread[1])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) " + "during clean up.\nGetLastError returned '%d'.\n", + hThread[1], GetLastError()); + } +#endif + Trace("PALSUITE ERROR: WaitForMultipleObjects(%d, %p, %d, %d) call" + "returned an unexpected value, '%d'.\nGetLastError returned " + "%d.\n", NUM_THREADS, hEvent, TRUE, 10000, dwRet, + GetLastError()); + } + else + { + bRet = PASS; + } + } + else + { + /* signal thread 0 */ + if (0 == SetEvent(hEvent[0])) + { + Trace("PALSUITE ERROR: Unable to execute SetEvent(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hEvent[0], + GetLastError()); + LeaveCriticalSection(&CriticalSection); + if (0 == CloseHandle(hThread[0])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) " + "during clean up\nGetLastError returned '%d'.\n", + hThread[0], GetLastError()); + } + if (0 == CloseHandle(hEvent[0])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) " + "during clean up\nGetLastError returned '%d'.\n", + hEvent[0], GetLastError()); + } + if (0 == CloseHandle(hEvent[1])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) " + "during clean up\nGetLastError returned '%d'.\n", + hEvent[1], GetLastError()); + } + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + + /* wait to be signaled */ + dwRet = WaitForSingleObject(hEvent[1], 10000); + if (WAIT_OBJECT_0 != dwRet) + { + Trace("PALSUITE ERROR: WaitForSingleObject(%p,%d) should have " + "returned\nWAIT_OBJECT_0 ('%d'), instead it returned " + "('%d').\nGetLastError returned '%d'.\n", + hEvent[0], 10000, WAIT_OBJECT_0, dwRet, GetLastError()); + if (0 == CloseHandle(hThread[0])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) " + "during clean up.\nGetLastError returned '%d'.\n", + hThread[0], GetLastError()); + } + if (0 == CloseHandle(hEvent[0])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) " + "during clean up.\nGetLastError returned '%d'.\n", + hEvent[0], GetLastError()); + } + if (0 == CloseHandle(hEvent[1])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) " + "during clean up.\nGetLastError returned '%d.'\n", + hEvent[1], GetLastError()); + } + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + LeaveCriticalSection(&CriticalSection); + } + return FAIL; +} + +int __cdecl main(int argc, char **argv) +{ + HANDLE hThread[NUM_THREADS]; + DWORD dwThreadId[NUM_THREADS]; + DWORD dwRet; + + if ((PAL_Initialize(argc,argv)) != 0) + { + return(bRet); + } + + /* thread 0 event */ + hEvent[0] = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (hEvent[0] == NULL) + { + Fail("PALSUITE ERROR: CreateEvent call #0 failed. GetLastError " + "returned %d.\n", GetLastError()); + } + + /* thread 1 event */ + hEvent[1] = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (hEvent[1] == NULL) + { + Trace("PALSUITE ERROR: CreateEvent call #1 failed. GetLastError " + "returned %d.\n", GetLastError()); + if (0 == CloseHandle(hEvent[0])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hEvent[0]); + } + Fail(""); + } + + InitializeCriticalSection ( &CriticalSection ); + + hThread[0] = CreateThread(NULL, + 0, + &Thread, + (LPVOID) NULL, + 0, + &dwThreadId[0]); + + if (hThread[0] == NULL) + { + Trace("PALSUITE ERROR: CreateThread call #0 failed. GetLastError " + "returned %d.\n", GetLastError()); + if (0 == CloseHandle(hEvent[0])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hEvent[0], + GetLastError()); + } + if (0 == CloseHandle(hEvent[1])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hEvent[1], + GetLastError()); + } + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + + + /* wait for thread 0 to be signaled */ + dwRet = WaitForSingleObject(hEvent[0], 10000); + if (WAIT_OBJECT_0 != dwRet) + { + Trace("PALSUITE ERROR: WaitForSingleObject(%p,%d) should have " + "returned\nWAIT_OBJECT_0 ('%d'), instead it returned " + "('%d').\nGetLastError returned '%d'.\n", hEvent[0], 10000, + WAIT_OBJECT_0, dwRet, GetLastError()); + if (0 == CloseHandle(hThread[0])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hThread[0], + GetLastError()); + } + if (0 == CloseHandle(hEvent[0])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hEvent[0], + GetLastError()); + } + if (0 == CloseHandle(hEvent[1])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hEvent[1], + GetLastError()); + } + Fail(""); + } + + /* + * Attempting to enter CRITICAL_SECTION object owned by the + * created thread and locked with TryEnterCriticalSection + */ + if (0 == TryEnterCriticalSection(&CriticalSection)) + { + /* signal thread 1 */ + if (0 == SetEvent(hEvent[1])) + { + Trace("PALSUITE ERROR: Unable to execute SetEvent(%p) call.\n" + "GetLastError returned '%d'.\n", hEvent[1], + GetLastError()); + goto done; + } + } + else + { + Trace("PALSUITE_ERROR: TryEnterCriticalSection was able to grab a" + " CRITICAL_SECTION object\nwhich was already owned.\n"); + LeaveCriticalSection(&CriticalSection); + if (0 == CloseHandle(hThread[0])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hThread[0], + GetLastError()); + } + if (0 == CloseHandle(hEvent[0])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hEvent[0], + GetLastError()); + } + if (0 == CloseHandle(hEvent[1])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hEvent[1], + GetLastError()); + } + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + /* + * Enter the CRITICAL_SECTION and launch another thread to attempt + * to access the CRITICAL_SECTION with a call to TryEnterCriticalSection. + */ + EnterCriticalSection(&CriticalSection); + + hThread[1] = CreateThread(NULL, + 0, + &Thread, + (LPVOID) NULL, + 0, + &dwThreadId[1]); + + if (hThread[1] == NULL) + { + Trace("PALSUITE ERROR: CreateThread call #1 failed. GetLastError " + "returned %d.\n", GetLastError()); + LeaveCriticalSection(&CriticalSection); + if (0 == CloseHandle(hThread[0])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hThread[0], + GetLastError()); + } + if (0 == CloseHandle(hEvent[0])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hEvent[0], + GetLastError()); + } + if (0 == CloseHandle(hEvent[1])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hEvent[1], + GetLastError()); + } + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + + dwRet = WaitForMultipleObjects(NUM_THREADS, hThread, TRUE, 10000); + if ((WAIT_OBJECT_0 > dwRet) || + ((WAIT_OBJECT_0 + NUM_THREADS - 1) < dwRet)) + { + Trace("PALSUITE ERROR: WaitForMultipleObjects(%d, %p, %d, %d) call " + "returned an unexpected value, '%d'.\nGetLastError returned " + "%d.\n", NUM_THREADS, hThread, TRUE, 10000, dwRet, + GetLastError()); + LeaveCriticalSection(&CriticalSection); + if (0 == CloseHandle(hThread[0])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hThread[0], + GetLastError()); + } + if (0 == CloseHandle(hThread[1])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hThread[1], + GetLastError()); + } + if (0 == CloseHandle(hEvent[0])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hEvent[0], + GetLastError()); + } + if (0 == CloseHandle(hEvent[1])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hEvent[1], + GetLastError()); + } + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + + LeaveCriticalSection(&CriticalSection); + if (0 == CloseHandle(hThread[1])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hThread[1], + GetLastError()); + } +done: + if (0 == CloseHandle(hThread[0])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hThread[0], + GetLastError()); + } + if (0 == CloseHandle(hEvent[0])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hEvent[0], + GetLastError()); + } + if (0 == CloseHandle(hEvent[1])) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%d'.\n", hEvent[1], + GetLastError()); + } + DeleteCriticalSection(&CriticalSection); + + PAL_TerminateEx(bRet); + + return (bRet); +} + diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test3/testinfo.dat b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test3/testinfo.dat new file mode 100644 index 0000000000..818b4870a2 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test3/testinfo.dat @@ -0,0 +1,29 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CriticalSectionFunctions +Name = Positive Test for TryEnterCriticalSection +TYPE = DEFAULT +EXE1 = test3 +Description += Create two threads to exercise TryEnterCriticalSection += and EnterCriticalSection. TryEnterCriticalSection acquires += and holds a CRITICAL_SECTION object. Another call to += TryEnterCriticalSection is made from a different thread, at += this time, to establish a call to TryEnterCriticalSection += will return immediatly and to establish += TryEnterCriticalSection returns the proper value when it += attempts to lock a CRITICAL_SECTION that is already owned += by another thread. The CRITICAL_SECTION object is then += released and held by a call to EnterCriticalSection. A new += thread is invoked and attempts to acquire the held += CRITICAL_SECTION with a call to TryEnterCriticalSection. += TryEnterCriticalSection returns immediatly and returns += with the value that states the CRITICAL_SECTION object is += held by another thread. This establishes += TryEnterCriticalSection behaves the same way with += CriticalSections locked by TryEnterCriticalSection and += EnterCriticalSection. diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test4/CMakeLists.txt b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test4/CMakeLists.txt new file mode 100644 index 0000000000..0626631640 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test4/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test4.c +) + +add_executable(paltest_criticalsectionfunctions_test4 + ${SOURCES} +) + +add_dependencies(paltest_criticalsectionfunctions_test4 coreclrpal) + +target_link_libraries(paltest_criticalsectionfunctions_test4 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test4/test4.c b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test4/test4.c new file mode 100644 index 0000000000..8a245a4776 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test4/test4.c @@ -0,0 +1,241 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: criticalsectionfunctions/test4/test4.c +** +** Purpose: Test to see if threads blocked on a CRITICAL_SECTION object will +** be released in an orderly manner. This case looks at the following +** scenario. If one thread owns a CRITICAL_SECTION object and two threads +** block in EnterCriticalSection, trying to hold the already owned +** CRITICAL_SECTION object, when the first thread releases the CRITICAL_SECTION +** object, will one and only one of the waiters get unblocked? +** +** Dependencies: CreateThread +** InitializeCriticalSection +** EnterCriticalSection +** LeaveCriticalSection +** DeleteCriticalSection +** Sleep +** WaitForSingleObject +** + +** +**=========================================================*/ + +#include <palsuite.h> + +#define NUM_BLOCKING_THREADS 2 + +BOOL bTestResult; +CRITICAL_SECTION CriticalSection; +HANDLE hThread[NUM_BLOCKING_THREADS]; +HANDLE hEvent; +DWORD dwThreadId[NUM_BLOCKING_THREADS]; +volatile int flags[NUM_BLOCKING_THREADS] = {0,0}; + +DWORD PALAPI ThreadTest1(LPVOID lpParam) +{ + + EnterCriticalSection ( &CriticalSection ); + + flags[0] = 1; + + return 0; + +} + +DWORD PALAPI ThreadTest2(LPVOID lpParam) +{ + + EnterCriticalSection ( &CriticalSection ); + + flags[1] = 1; + + return 0; + +} + +int __cdecl main(int argc, char **argv) +{ + + DWORD dwRet; + DWORD dwRet1; + bTestResult = FAIL; + + if ((PAL_Initialize(argc,argv)) != 0) + { + return(bTestResult); + } + + /* + * Create Critical Section Object + */ + InitializeCriticalSection ( &CriticalSection ); + + EnterCriticalSection ( &CriticalSection ); + + hThread[0] = CreateThread(NULL, + 0, + &ThreadTest1, + (LPVOID) 0, + CREATE_SUSPENDED, + &dwThreadId[0]); + if (hThread[0] == NULL) + { + Trace("PALSUITE ERROR: CreateThread(%p, %d, %p, %p, %d, %p) call " + "failed.\nGetLastError returned %d.\n", NULL, 0, &ThreadTest1, + (LPVOID) 0, CREATE_SUSPENDED, &dwThreadId[0], GetLastError()); + LeaveCriticalSection(&CriticalSection); + DeleteCriticalSection ( &CriticalSection ); + Fail(""); + } + + hThread[1] = CreateThread(NULL, + 0, + &ThreadTest2, + (LPVOID) 0, + CREATE_SUSPENDED, + &dwThreadId[1]); + if (hThread[1] == NULL) + { + Trace("PALSUITE ERROR: CreateThread(%p, %d, %p, %p, %d, %p) call " + "failed.\nGetLastError returned %d.\n", NULL, 0, &ThreadTest2, + (LPVOID) 0, CREATE_SUSPENDED, &dwThreadId[1], GetLastError()); + LeaveCriticalSection(&CriticalSection); + + dwRet = ResumeThread(hThread[0]); + if (-1 == dwRet) + { + Trace("PALSUITE ERROR: ResumeThread(%p) call failed.\n" + "GetLastError returned '%d'.\n", hThread[0], + GetLastError()); + } + + dwRet = WaitForSingleObject(hThread[0], 10000); + if (WAIT_OBJECT_0 == dwRet) + { + Trace("PALSUITE ERROR: WaitForSingleObject(%p, %d) call " + "failed. '%d' was returned instead of the expected '%d'.\n" + "GetLastError returned '%d'.\n", hThread[0], 10000, dwRet, + WAIT_OBJECT_0, GetLastError()); + } + + if (0 == CloseHandle(hThread[0])) + { + Trace("PALSUITE NOTIFICATION: CloseHandle(%p) call failed.\n" + "GetLastError returned %d. Not failing tests.\n", + hThread[0], GetLastError()); + } + + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + + /* + * Set other thread priorities to be higher than ours & Sleep to ensure + * we give up the processor. + */ + dwRet = (DWORD) SetThreadPriority(hThread[0], + THREAD_PRIORITY_ABOVE_NORMAL); + if (0 == dwRet) + { + Trace("PALSUITE ERROR: SetThreadPriority(%p, %d) call failed.\n" + "GetLastError returned %d", hThread[0], + THREAD_PRIORITY_ABOVE_NORMAL, GetLastError()); + } + + dwRet = (DWORD) SetThreadPriority(hThread[1], + THREAD_PRIORITY_ABOVE_NORMAL); + if (0 == dwRet) + { + Trace("PALSUITE ERROR: SetThreadPriority(%p, %d) call failed.\n" + "GetLastError returned %d", hThread[1], + THREAD_PRIORITY_ABOVE_NORMAL, GetLastError()); + } + + dwRet = ResumeThread(hThread[0]); + if (-1 == dwRet) + { + Trace("PALSUITE ERROR: ResumeThread(%p, %d) call failed.\n" + "GetLastError returned %d", hThread[0], + GetLastError() ); + } + + dwRet = ResumeThread(hThread[1]); + if (-1 == dwRet) + { + Trace("PALSUITE ERROR: ResumeThread(%p, %d) call failed.\n" + "GetLastError returned %d", hThread[0], + GetLastError()); + } + + Sleep (0); + + LeaveCriticalSection (&CriticalSection); + + dwRet = WaitForSingleObject(hThread[0], 10000); + dwRet1 = WaitForSingleObject(hThread[1], 10000); + + if ((WAIT_OBJECT_0 == dwRet) || + (WAIT_OBJECT_0 == dwRet1)) + { + if ((1 == flags[0] && 0 == flags[1]) || + (0 == flags[0] && 1 == flags[1])) + { + bTestResult = PASS; + } + else + { + bTestResult = FAIL; + Trace ("PALSUITE ERROR: flags[%d] = {%d,%d}. These values are" + "inconsistent.\nCriticalSection test failed.\n", + NUM_BLOCKING_THREADS, flags[0], flags[1]); + } + + /* Fail the test if both threads returned WAIT_OBJECT_0 */ + if ((WAIT_OBJECT_0 == dwRet) && (WAIT_OBJECT_0 == dwRet1)) + { + bTestResult = FAIL; + Trace ("PALSUITE ERROR: WaitForSingleObject(%p, %d) and " + "WaitForSingleObject(%p, %d)\nboth returned dwRet = '%d'\n" + "One should have returned WAIT_TIMEOUT ('%d').\n", + hThread[0], 10000, hThread[1], 10000, dwRet, WAIT_TIMEOUT); + } + } + else + { + bTestResult = FAIL; + Trace ("PALSUITE ERROR: WaitForSingleObject(%p, %d) and " + "WaitForSingleObject(%p, %d)\nReturned dwRet = '%d' and\n" + "dwRet1 = '%d' respectively.\n", hThread[0], 10000, hThread[1], + 10000, dwRet, dwRet1); + } + + if (WAIT_OBJECT_0 == dwRet) + { + if (0 == CloseHandle(hThread[0])) + { + Trace("PALSUITE NOTIFICATION: CloseHandle(%p) call failed.\n" + "GetLastError returned %d. Not failing tests.\n", + hThread[0], GetLastError()); + } + } + if (WAIT_OBJECT_0 == dwRet1) + { + if (0 == CloseHandle(hThread[1])) + { + Trace("PALSUITE NOTIFICATION: CloseHandle(%p) call failed.\n" + "GetLastError returned %d. Not failing tests.\n", + hThread[1], GetLastError()); + } + } + + /* Leaking the CS on purpose, since there is still a thread + waiting on it */ + + PAL_TerminateEx(bTestResult); + return (bTestResult); +} diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test4/testinfo.dat b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test4/testinfo.dat new file mode 100644 index 0000000000..9c07c24113 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test4/testinfo.dat @@ -0,0 +1,17 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = LeaveCriticalSection +Name = Positive test to ensure CRITICAL_SECTION objects are released properly +TYPE = DEFAULT +EXE1 = test4 +Description += Test to see if threads blocked on a CRITICAL_SECTION object will += be released in an orderly manner. This case looks at the following += scenario. If one thread owns a CRITICAL_SECTION object and two threads += block in EnterCriticalSection, trying to hold the already owned += CRITICAL_SECTION object, when the first thread releases the CRITICAL_SECTION += object, will one and only one of the waiters get unblocked? diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test5/CMakeLists.txt b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test5/CMakeLists.txt new file mode 100644 index 0000000000..37d709f121 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test5/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test5.c +) + +add_executable(paltest_criticalsectionfunctions_test5 + ${SOURCES} +) + +add_dependencies(paltest_criticalsectionfunctions_test5 coreclrpal) + +target_link_libraries(paltest_criticalsectionfunctions_test5 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test5/test5.c b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test5/test5.c new file mode 100644 index 0000000000..8dfa4f5f3d --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test5/test5.c @@ -0,0 +1,187 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: CriticalSectionFunctions/test5/test5.c +** +** Purpose: Attempt to delete a critical section owned by another +** thread. +** +** +**===================================================================*/ +#include <palsuite.h> + +/* + * Tokens 0 and 1 are events. Token 2 is the thread. + */ +#define NUM_TOKENS 3 + +HANDLE hToken[NUM_TOKENS]; +CRITICAL_SECTION CriticalSection; + +BOOL CleanupHelper (HANDLE *hArray, DWORD dwIndex) +{ + BOOL bCHRet; + + bCHRet = CloseHandle(hArray[dwIndex]); + if (!bCHRet) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%u'.\n", hArray[dwIndex], + GetLastError()); + } + + return (bCHRet); +} + +BOOL Cleanup(HANDLE *hArray, DWORD dwIndex) +{ + BOOL bCRet; + BOOL bCHRet = FALSE; + + while (--dwIndex > 0) + { + bCHRet = CleanupHelper(&hArray[0], dwIndex); + } + + bCRet = CloseHandle(hArray[0]); + if (!bCRet) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%u'.\n", hArray[dwIndex], + GetLastError()); + } + + return (bCRet&&bCHRet); +} + +DWORD PALAPI Thread(LPVOID lpParam) +{ + DWORD dwTRet; + + EnterCriticalSection(&CriticalSection); + + /* signal thread 0 */ + if (0 == SetEvent(hToken[0])) + { + Trace("PALSUITE ERROR: Unable to execute SetEvent(%p) during " + "clean up.\nGetLastError returned '%u'.\n", hToken[0], + GetLastError()); + LeaveCriticalSection(&CriticalSection); + Cleanup (&hToken[0], NUM_TOKENS); + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + + /* wait to be signaled */ + dwTRet = WaitForSingleObject(hToken[1], 10000); + if (WAIT_OBJECT_0 != dwTRet) + + { + Trace("PALSUITE ERROR: WaitForSingleObject(%p,%d) should have " + "returned\nWAIT_OBJECT_0 ('%d'), instead it returned " + "('%d').\nGetLastError returned '%u'.\n", + hToken[1], 10000, WAIT_OBJECT_0, dwTRet, GetLastError()); + LeaveCriticalSection(&CriticalSection); + Cleanup (&hToken[0], NUM_TOKENS); + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + + LeaveCriticalSection(&CriticalSection); + return 0; +} + +int __cdecl main(int argc, char **argv) +{ + DWORD dwThreadId; + DWORD dwMRet; + + if ((PAL_Initialize(argc,argv)) != 0) + { + return(FAIL); + } + + /* thread 0 event */ + hToken[0] = CreateEvent(NULL, TRUE, FALSE, NULL); + if (NULL == hToken[0]) + { + Fail("PALSUITE ERROR: CreateEvent call #0 failed. GetLastError " + "returned %u.\n", GetLastError()); + } + + /* thread 1 event */ + hToken[1] = CreateEvent(NULL, TRUE, FALSE, NULL); + if (NULL == hToken[1]) + { + Trace("PALSUITE ERROR: CreateEvent call #1 failed. GetLastError " + "returned %u.\n", GetLastError()); + Cleanup(&hToken[0], (NUM_TOKENS - 2)); + Fail(""); + } + + InitializeCriticalSection(&CriticalSection); + + hToken[2] = CreateThread(NULL, + 0, + &Thread, + (LPVOID) NULL, + 0, + &dwThreadId); + if (hToken[2] == NULL) + { + Trace("PALSUITE ERROR: CreateThread call #0 failed. GetLastError " + "returned %u.\n", GetLastError()); + Cleanup(&hToken[0], (NUM_TOKENS - 1)); + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + + /* wait for thread 0 to be signaled */ + dwMRet = WaitForSingleObject(hToken[0], 10000); + if (WAIT_OBJECT_0 != dwMRet) + { + Trace("PALSUITE ERROR: WaitForSingleObject(%p,%d) should have " + "returned\nWAIT_OBJECT_0 ('%d'), instead it returned " + "('%d').\nGetLastError returned '%u'.\n", hToken[0], 10000, + WAIT_OBJECT_0, dwMRet, GetLastError()); + Cleanup(&hToken[0], NUM_TOKENS); + Fail(""); + } + + /* + * Attempt to do delete CriticalSection object owned by other thread + */ + DeleteCriticalSection(&CriticalSection); + + /* signal thread 1 */ + if (0 == SetEvent(hToken[1])) + { + Trace("PALSUITE ERROR: Unable to execute SetEvent(%p) call.\n" + "GetLastError returned '%u'.\n", hToken[1], + GetLastError()); + Cleanup(&hToken[0], NUM_TOKENS); + Fail(""); + } + + dwMRet = WaitForSingleObject(hToken[2], 10000); + if (WAIT_OBJECT_0 != dwMRet) + { + Trace("PALSUITE ERROR: WaitForSingleObject(%p, %d) call " + "returned an unexpected value '%d'.\nGetLastError returned " + "%u.\n", hToken[2], 10000, dwMRet, GetLastError()); + Cleanup(&hToken[0], NUM_TOKENS); + Fail(""); + } + + if (!Cleanup(&hToken[0], NUM_TOKENS)) + { + Fail(""); + } + + PAL_Terminate(); + + return (PASS); +} diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test5/testinfo.dat b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test5/testinfo.dat new file mode 100644 index 0000000000..aa1124925b --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test5/testinfo.dat @@ -0,0 +1,12 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CriticalSectionFunctions +Name = Positive test for DeleteCriticalSection +TYPE = DEFAULT +EXE1 = test5 +Description += Attempt to delete a critical section owned by another thread. diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test6/CMakeLists.txt b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test6/CMakeLists.txt new file mode 100644 index 0000000000..c580fdbd6b --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test6/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test6.c +) + +add_executable(paltest_criticalsectionfunctions_test6 + ${SOURCES} +) + +add_dependencies(paltest_criticalsectionfunctions_test6 coreclrpal) + +target_link_libraries(paltest_criticalsectionfunctions_test6 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test6/test6.c b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test6/test6.c new file mode 100644 index 0000000000..c27db86e5b --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test6/test6.c @@ -0,0 +1,190 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: CriticalSectionFunctions/test6/test6.c +** +** Purpose: Attempt to leave a critical section which is owned by +** another thread. +** +** +**===================================================================*/ +#include <palsuite.h> + +/* + * Tokens 0 and 1 are events. Token 2 is the thread. + */ +#define NUM_TOKENS 3 + +HANDLE hToken[NUM_TOKENS]; +CRITICAL_SECTION CriticalSection; + +BOOL CleanupHelper (HANDLE *hArray, DWORD dwIndex) +{ + BOOL bCHRet; + + bCHRet = CloseHandle(hArray[dwIndex]); + if (!bCHRet) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%u'.\n", hArray[dwIndex], + GetLastError()); + } + + return (bCHRet); +} + +BOOL Cleanup(HANDLE *hArray, DWORD dwIndex) +{ + BOOL bCRet; + BOOL bCHRet; + + while (--dwIndex > 0) + { + bCHRet = CleanupHelper(&hArray[0], dwIndex); + } + + bCRet = CloseHandle(hArray[0]); + if (!bCRet) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%u'.\n", hArray[dwIndex], + GetLastError()); + } + + return (bCRet&&bCHRet); +} + +DWORD PALAPI Thread(LPVOID lpParam) +{ + DWORD dwTRet; + + EnterCriticalSection(&CriticalSection); + + /* signal thread 0 */ + if (0 == SetEvent(hToken[0])) + { + Trace("PALSUITE ERROR: Unable to execute SetEvent(%p) during " + "clean up.\nGetLastError returned '%u'.\n", hToken[0], + GetLastError()); + LeaveCriticalSection(&CriticalSection); + Cleanup (&hToken[0], NUM_TOKENS); + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + + /* wait to be signaled */ + dwTRet = WaitForSingleObject(hToken[1], 10000); + if (WAIT_OBJECT_0 != dwTRet) + { + Trace("PALSUITE ERROR: WaitForSingleObject(%p,%d) should have " + "returned\nWAIT_OBJECT_0 ('%d'), instead it returned " + "('%d').\nGetLastError returned '%u'.\n", + hToken[1], 10000, WAIT_OBJECT_0, dwTRet, GetLastError()); + LeaveCriticalSection(&CriticalSection); + Cleanup (&hToken[0], NUM_TOKENS); + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + + LeaveCriticalSection(&CriticalSection); + + return 0; +} + +int __cdecl main(int argc, char **argv) +{ + DWORD dwThreadId; + DWORD dwMRet; + + if ((PAL_Initialize(argc,argv)) != 0) + { + return(FAIL); + } + + /* thread 0 event */ + hToken[0] = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (hToken[0] == NULL) + { + Fail("PALSUITE ERROR: CreateEvent call #0 failed. GetLastError " + "returned %u.\n", GetLastError()); + } + + /* thread 1 event */ + hToken[1] = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (hToken[1] == NULL) + { + Trace("PALSUITE ERROR: CreateEvent call #1 failed. GetLastError " + "returned %u.\n", GetLastError()); + Cleanup(&hToken[0], (NUM_TOKENS - 2)); + Fail(""); + } + + InitializeCriticalSection(&CriticalSection); + + hToken[2] = CreateThread(NULL, + 0, + &Thread, + (LPVOID) NULL, + 0, + &dwThreadId); + + if (hToken[2] == NULL) + { + Trace("PALSUITE ERROR: CreateThread call #0 failed. GetLastError " + "returned %u.\n", GetLastError()); + Cleanup(&hToken[0], (NUM_TOKENS - 1)); + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + + /* wait for thread 0 to be signaled */ + dwMRet = WaitForSingleObject(hToken[0], 10000); + if (WAIT_OBJECT_0 != dwMRet) + { + Trace("PALSUITE ERROR: WaitForSingleObject(%p,%d) should have " + "returned\nWAIT_OBJECT_0 ('%d'), instead it returned " + "('%d').\nGetLastError returned '%u'.\n", hToken[0], 10000, + WAIT_OBJECT_0, dwMRet, GetLastError()); + Cleanup(&hToken[0], NUM_TOKENS); + Fail(""); + } + + /* + * Attempt to leave critical section which is owned by the other thread. + */ + LeaveCriticalSection(&CriticalSection); + + /* signal thread 1 */ + if (0 == SetEvent(hToken[1])) + { + Trace("PALSUITE ERROR: Unable to execute SetEvent(%p) call.\n" + "GetLastError returned '%u'.\n", hToken[1], + GetLastError()); + Cleanup(&hToken[0], NUM_TOKENS); + Fail(""); + } + + dwMRet = WaitForSingleObject(hToken[2], 10000); + if (WAIT_OBJECT_0 != dwMRet) + { + Trace("PALSUITE ERROR: WaitForSingleObject(%p, %d) call " + "returned an unexpected value '%d'.\nGetLastError returned " + "%u.\n", hToken[2], 10000, dwMRet, GetLastError()); + Cleanup(&hToken[0], NUM_TOKENS); + Fail(""); + } + + if (!Cleanup(&hToken[0], NUM_TOKENS)) + { + Fail(""); + } + + PAL_Terminate(); + + return(PASS); +} diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test6/testinfo.dat b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test6/testinfo.dat new file mode 100644 index 0000000000..151e1ed4d0 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test6/testinfo.dat @@ -0,0 +1,12 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CriticalSectionFunctions +Name = Positive Test for LeaveCriticalSection +TYPE = DEFAULT +EXE1 = test6 +Description += Attempt to leave a critical section which is owned by another thread. diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test7/CMakeLists.txt b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test7/CMakeLists.txt new file mode 100644 index 0000000000..0a12bfe3ef --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test7/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test7.c +) + +add_executable(paltest_criticalsectionfunctions_test7 + ${SOURCES} +) + +add_dependencies(paltest_criticalsectionfunctions_test7 coreclrpal) + +target_link_libraries(paltest_criticalsectionfunctions_test7 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test7/test7.c b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test7/test7.c new file mode 100644 index 0000000000..1c030d3c03 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test7/test7.c @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: CriticalSectionFunctions/test7/test7.c +** +** Purpose: Attempt to delete a critical section owned by the current +** thread. +** +** +**===================================================================*/ +#include <palsuite.h> + +/* + * Tokens 0 and 1 are events. Token 2 is the thread. + */ +#define NUM_TOKENS 3 + +HANDLE hToken[NUM_TOKENS]; +CRITICAL_SECTION CriticalSection; + +BOOL CleanupHelper (HANDLE *hArray, DWORD dwIndex) +{ + BOOL bCHRet; + + bCHRet = CloseHandle(hArray[dwIndex]); + if (!bCHRet) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%u'.\n", hArray[dwIndex], + GetLastError()); + } + + return (bCHRet); +} + +BOOL Cleanup(HANDLE *hArray, DWORD dwIndex) +{ + BOOL bCRet; + BOOL bCHRet = 0; + + while (--dwIndex > 0) + { + bCHRet = CleanupHelper(&hArray[0], dwIndex); + } + + bCRet = CloseHandle(hArray[0]); + if (!bCRet) + { + Trace("PALSUITE ERROR: Unable to execute CloseHandle(%p) during " + "clean up.\nGetLastError returned '%u'.\n", hArray[dwIndex], + GetLastError()); + } + + return (bCRet&&bCHRet); +} + +DWORD PALAPI Thread(LPVOID lpParam) +{ + DWORD dwTRet; + + EnterCriticalSection(&CriticalSection); + + /* signal thread 0 */ + if (0 == SetEvent(hToken[0])) + { + Trace("PALSUITE ERROR: Unable to execute SetEvent(%p) during " + "clean up.\nGetLastError returned '%u'.\n", hToken[0], + GetLastError()); + LeaveCriticalSection(&CriticalSection); + Cleanup (&hToken[0], NUM_TOKENS); + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + + /* wait to be signaled */ + dwTRet = WaitForSingleObject(hToken[1], 10000); + if (WAIT_OBJECT_0 != dwTRet) + { + Trace("PALSUITE ERROR: WaitForSingleObject(%p,%d) should have " + "returned\nWAIT_OBJECT_0 ('%d'), instead it returned " + "('%d').\nGetLastError returned '%u'.\n", + hToken[0], 10000, WAIT_OBJECT_0, dwTRet, GetLastError()); + LeaveCriticalSection(&CriticalSection); + Cleanup (&hToken[0], NUM_TOKENS); + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + + DeleteCriticalSection(&CriticalSection); + + return 0; +} + +int __cdecl main(int argc, char **argv) +{ + DWORD dwThreadId; + DWORD dwMRet; + + if ((PAL_Initialize(argc,argv)) != 0) + { + return(FAIL); + } + + /* thread 0 event */ + hToken[0] = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (hToken[0] == NULL) + { + Fail("PALSUITE ERROR: CreateEvent call #0 failed. GetLastError " + "returned %u.\n", GetLastError()); + } + + /* thread 1 event */ + hToken[1] = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (hToken[1] == NULL) + { + Trace("PALSUITE ERROR: CreateEvent call #1 failed. GetLastError " + "returned %u.\n", GetLastError()); + Cleanup (&hToken[0], (NUM_TOKENS - 2)); + Fail(""); + } + + InitializeCriticalSection(&CriticalSection); + + hToken[2] = CreateThread(NULL, + 0, + &Thread, + (LPVOID) NULL, + 0, + &dwThreadId); + + if (hToken[2] == NULL) + { + Trace("PALSUITE ERROR: CreateThread call #0 failed. GetLastError " + "returned %u.\n", GetLastError()); + Cleanup (&hToken[0], (NUM_TOKENS - 1)); + DeleteCriticalSection(&CriticalSection); + Fail(""); + } + + /* wait for thread 0 to be signaled */ + dwMRet = WaitForSingleObject(hToken[0], 10000); + if (WAIT_OBJECT_0 != dwMRet) + { + Trace("PALSUITE ERROR: WaitForSingleObject(%p,%d) should have " + "returned\nWAIT_OBJECT_0 ('%d'), instead it returned " + "('%d').\nGetLastError returned '%u'.\n", hToken[0], 10000, + WAIT_OBJECT_0, dwMRet, GetLastError()); + Cleanup (&hToken[0], NUM_TOKENS); + Fail(""); + } + + /* signal thread 1 */ + if (0 == SetEvent(hToken[1])) + { + Trace("PALSUITE ERROR: Unable to execute SetEvent(%p) call.\n" + "GetLastError returned '%u'.\n", hToken[1], + GetLastError()); + Cleanup (&hToken[0], NUM_TOKENS); + Fail(""); + } + + dwMRet = WaitForSingleObject(hToken[2], 10000); + if (WAIT_OBJECT_0 != dwMRet) + { + Trace("PALSUITE ERROR: WaitForSingleObject(%p, %d) call " + "returned an unexpected value '%d'.\nGetLastError returned " + "%u.\n", hToken[2], 10000, dwMRet, GetLastError()); + Cleanup (&hToken[0], NUM_TOKENS); + Fail(""); + } + + if (!Cleanup(&hToken[0], NUM_TOKENS)) + { + Fail(""); + } + + PAL_Terminate(); + + return (PASS); +} + + + diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test7/testinfo.dat b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test7/testinfo.dat new file mode 100644 index 0000000000..5cf5809622 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test7/testinfo.dat @@ -0,0 +1,12 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = CriticalSectionFunctions +Name = Positive test for DeleteCriticalSection +TYPE = DEFAULT +EXE1 = test7 +Description += Attempt to delete a critical section owned by the current thread. diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test8/CMakeLists.txt b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test8/CMakeLists.txt new file mode 100644 index 0000000000..0d95a95410 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test8/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test8.c +) + +add_executable(paltest_criticalsectionfunctions_test8 + ${SOURCES} +) + +add_dependencies(paltest_criticalsectionfunctions_test8 coreclrpal) + +target_link_libraries(paltest_criticalsectionfunctions_test8 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test8/test8.c b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test8/test8.c new file mode 100644 index 0000000000..7f0c58cd26 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test8/test8.c @@ -0,0 +1,217 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: CriticalSectionFunctions/test8/test8.c +** +** Pyrpose: Ensure critical section functionality is working by +** having multiple threads racing on a CS under different +** scenarios +** +** +**===================================================================*/ +#include <palsuite.h> + +#define MAX_THREAD_COUNT 128 +#define DEFAULT_THREAD_COUNT 10 +#define DEFAULT_LOOP_COUNT 1000 + +#ifndef MIN +#define MIN(a,b) (((a)<(b)) ? (a) : (b)) +#endif + +int g_iThreadCount = DEFAULT_THREAD_COUNT; +int g_iLoopCount = DEFAULT_LOOP_COUNT; +volatile LONG g_lCriticalCount = 0; +HANDLE g_hEvStart = NULL; + +CRITICAL_SECTION g_cs; +DWORD PALAPI Thread(LPVOID lpParam) +{ + int i, j, iLpCnt; + DWORD dwRet = 0; + DWORD dwTid = GetCurrentThreadId(); + LONG lRet; + BOOL bSleepInside; + BOOL bSleepOutside; + + Trace("[tid=%u] Thread starting\n", dwTid); + + dwRet = WaitForSingleObject(g_hEvStart, INFINITE); + if (WAIT_OBJECT_0 != dwRet) + { + Fail("WaitForSingleObject returned unexpected %u [GetLastError()=%u]\n", + dwRet, GetLastError()); + } + + for (j=0;j<8;j++) + { + bSleepInside = 2 & j; + bSleepOutside = 4 & j; + + iLpCnt = g_iLoopCount; + if (bSleepInside || bSleepOutside) + { + iLpCnt /= 10; + } + + for (i=0;i<iLpCnt;i++) + { + EnterCriticalSection(&g_cs); + if (1 & i) + { + // Simple increment on odd iterations + lRet = (g_lCriticalCount += 1); + } + else + { + // Interlocked increment on even iterations + lRet = InterlockedIncrement(&g_lCriticalCount); + } + + if (1 != lRet || 1 != g_lCriticalCount) + { + Fail("Detected %d threads in area protected by critical section " + "[expected: 1 thread]\n", g_lCriticalCount); + } + if (bSleepInside) + { + Sleep(rand()%10); + } + if (1 != g_lCriticalCount) + { + Fail("Detected %d threads inside area protected by critical section " + "[expected: 1 thread]\n", (int)g_lCriticalCount); + } + + if (1 & i) + { + // Simple decrement on odd iterations + lRet = (g_lCriticalCount -= 1); + } + else + { + // Interlocked decrement on even iterations + lRet = InterlockedDecrement(&g_lCriticalCount); + } + LeaveCriticalSection(&g_cs); + + if (bSleepOutside) + { + Sleep(rand()%10); + } + } + } + + Trace("[tid=%u] Thread done\n", dwTid); + + return 0; +} + +int __cdecl main(int argc, char **argv) +{ + DWORD dwThreadId; + DWORD dwRet; + HANDLE hThreads[MAX_THREAD_COUNT] = { 0 }; + int iThreadCount = 0; + int i, iVal; + + if ((PAL_Initialize(argc,argv)) != 0) + { + return(FAIL); + } + + srand(time(NULL)); + + for (i=1; i<argc; i++) + { + if ('-' == *argv[i]) + { + switch(*(argv[i]+1)) + { + case 'n': + if (i < argc-1) + { + i += 1; + iVal = atoi(argv[i]); + if (0 < iVal) + { + g_iLoopCount = iVal; + } + } + break; + case 't': + if (i < argc-1) + { + i += 1; + iVal = atoi(argv[i]); + if (0 < iVal && MAX_THREAD_COUNT >= iVal) + { + g_iThreadCount = iVal; + } + } + break; + default: + break; + } + } + } + + Trace ("Iterations:\t%d\n", g_iLoopCount); + Trace ("Threads:\t%d\n", g_iThreadCount); + + g_hEvStart = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (g_hEvStart == NULL) + { + Fail("CreateEvent call failed. GetLastError " + "returned %u.\n", GetLastError()); + } + + InitializeCriticalSection(&g_cs); + + for (i=0;i<g_iThreadCount;i++) + { + hThreads[iThreadCount] = CreateThread(NULL, + 0, + &Thread, + (LPVOID) NULL, + 0, + &dwThreadId); + if (NULL != hThreads[iThreadCount]) + { + iThreadCount++; + } + } + + Sleep(100); + + Trace("Created %d client threads\n", g_iThreadCount); + + if (2 > iThreadCount) + { + Fail("Failed to create minimum number if threads, i.e. 2\n"); + } + + if (!SetEvent(g_hEvStart)) + { + Fail("SetEvent failed [GetLastError()=%u]\n", GetLastError()); + } + + for (i=0; i<iThreadCount; i+=64) + { + dwRet = WaitForMultipleObjects(MIN(iThreadCount-i,64), + hThreads+i, + TRUE, + INFINITE); + if (WAIT_OBJECT_0 != dwRet) + { + Fail("Wait for all threads failed\n"); + } + } + + PAL_Terminate(); + return (PASS); +} diff --git a/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test8/thistest.dat b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test8/thistest.dat new file mode 100644 index 0000000000..1057eca5c5 --- /dev/null +++ b/src/pal/tests/palsuite/threading/CriticalSectionFunctions/test8/thistest.dat @@ -0,0 +1,2 @@ + +PAL,threading,palsuite\threading\criticalsectionfunctions\test8,Test8forCriticalSectionFunctionalities=test8.c, diff --git a/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test1/test1.c b/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test1/test1.c new file mode 100644 index 0000000000..b64fd0c7d2 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test1/test1.c @@ -0,0 +1,145 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test1.c +** +** Purpose: Tests that DisableThreadLibraryCalls actually stops thread +** attach/detach notifications to a library. Also tests how it +** handles an invalid module handle. +** +** +**===================================================================*/ + +#include <palsuite.h> + + +/* SHLEXT is defined only for Unix variants */ + +#if defined(SHLEXT) +#define LibName "testlib"SHLEXT +#define GETCALLCOUNT "GetCallCount" +#else +#define LibName "testlib" +#define GETCALLCOUNT "_GetCallCount@0" +#endif + +DWORD __stdcall ThreadFunc(LPVOID lpParam); +int RunTest(int DisableThreadCalls); + +int __cdecl main(int argc, char **argv) +{ + int ret; + + if ((PAL_Initialize(argc,argv)) != 0) + { + return (FAIL); + } + + + /* + * Although MSDN says that DisableThreadLibraryCalls will fail if passed + * an invalid handle, it actually returns success! + */ + ret = DisableThreadLibraryCalls(NULL); + if (!ret) + { + Fail("DisableThreadLibraryCalls failed on an invalid module " + "handle (it actually should pass)!\n"); + } + + + /* + * Test once without calling DisableThreadLibraryCalls and make sure we + * get expected results. + */ + ret = RunTest(0); + if (ret != 2) + { + Fail("Expected to get 2 thread library calls, got %d!\n", ret); + } + + + /* + * Test again, this time calling DisableThreadLibraryCalls. + */ + ret = RunTest(1); + if (ret != 0) + { + Fail("Expected to get 0 thread library calls, got %d!\n", ret); + } + + PAL_Terminate(); + return PASS; +} + +/* + * Thread entry point. Doesn't do anything. + */ +DWORD __stdcall ThreadFunc(LPVOID lpParam) +{ + return 0; +} + + +int RunTest(int DisableThreadCalls) +{ + HMODULE LibMod; + HANDLE hThread; + DWORD threadID; + DWORD WaitRet; + int (*GetCallCount)(); + int count; + + LibMod = LoadLibrary(LibName); + if (LibMod == NULL) + { + Fail("Unable to load test library!\nGetLastError returned %d\n", + GetLastError()); + } + + GetCallCount = (int(*)())GetProcAddress(LibMod, GETCALLCOUNT); + if (GetCallCount == NULL) + { + Fail("Unable to get function GetCallCount from library!\n" + "GetLastError returned %d\n", GetLastError()); + } + + if (DisableThreadCalls) + { + if (!DisableThreadLibraryCalls(LibMod)) + { + Fail("DisabledThreadLibraryCalls failed!\n" + "GetLastError returned %d!\n", GetLastError()); + } + } + + hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) ThreadFunc, + NULL, 0, &threadID); + + if (hThread == NULL) + { + Fail("Unable to create a thread!\n"); + } + + WaitRet = WaitForSingleObject(hThread, INFINITE); + if (WaitRet == WAIT_FAILED) + { + Fail("Unable to wait on thread!\nGetLastError returned %d\n", + GetLastError()); + } + + count = GetCallCount(); + + CloseHandle(hThread); + + if (!FreeLibrary(LibMod)) + { + Fail("Failed freeing library!\nGetLastError returned %d\n", + GetLastError()); + } + + return count; +} diff --git a/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test1/testinfo.dat b/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test1/testinfo.dat new file mode 100644 index 0000000000..6d32f3591a --- /dev/null +++ b/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test1/testinfo.dat @@ -0,0 +1,15 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Threading +Function = DisableThreadLibraryCalls +Name = Positive Test #1 for DisableThreadLibraryCalls +TYPE = DEFAULT +EXE1 = test1 +LIB1 = testlib +Description +=Tests that DisableThreadLibraryCalls actually stops thread +=attach/detach notifications to a library. Also tests how it +=handles an invalid module handle. diff --git a/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test1/testlib.c b/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test1/testlib.c new file mode 100644 index 0000000000..53b66d1357 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test1/testlib.c @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: testlib.c +** +** Purpose: Simple library that counts thread attach/detach notifications +** +** +**===================================================================*/ + +#include <palsuite.h> + +static int Count; + +BOOL __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + + if (fdwReason == DLL_PROCESS_ATTACH) + { + Count = 0; + } + else if (fdwReason == DLL_THREAD_ATTACH || + fdwReason == DLL_THREAD_DETACH) + { + Count++; + } + + return TRUE; +} + +#ifdef WIN32 +BOOL __stdcall _DllMainCRTStartup(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + return DllMain(hinstDLL, fdwReason, lpvReserved); +} +#endif + +#ifdef WIN32 +__declspec(dllexport) +#endif +int __stdcall GetCallCount() +{ + return Count; +} diff --git a/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test2/dllmain1.c b/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test2/dllmain1.c new file mode 100644 index 0000000000..5010a27665 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test2/dllmain1.c @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: dllmain1.c +** +** Purpose: Test to ensure DllMain() is called with THREAD_ATTACH +** when a thread in the application is started. +** +** Dependencies: none +** +** +**===========================================================================*/ + +#include <palsuite.h> + +/* count of the number of times DllMain() was called with THREAD_DETACH */ +static int g_attachCount = 0; + + +/* standard DllMain() */ +BOOL PALAPI DllMain(HINSTANCE hinstDLL, DWORD reason, LPVOID lpvReserved) +{ + BOOL bResult = TRUE; + + switch( reason ) + { + case DLL_PROCESS_ATTACH: + { + break; + } + + case DLL_PROCESS_DETACH: + { + break; + } + + case DLL_THREAD_ATTACH: + /* increment g_attachCount */ + g_attachCount++; + break; + + case DLL_THREAD_DETACH: + break; + } + return bResult; +} + + +#ifdef WIN32 +BOOL __stdcall _DllMainCRTStartup(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + return DllMain(hinstDLL, fdwReason, lpvReserved); +} +#endif + +/* function to return the current attach count */ +#ifdef WIN32 +__declspec(dllexport) +#endif +int PALAPI GetAttachCount( void ) +{ + return g_attachCount; +} diff --git a/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test2/dllmain2.c b/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test2/dllmain2.c new file mode 100644 index 0000000000..4e3f8862a4 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test2/dllmain2.c @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: dllmain2.c +** +** Purpose: Test to ensure DllMain() is called with THREAD_ATTACH +** when a thread in the application is started. +** +** Dependencies: none +** +** +**===========================================================================*/ + +#include <palsuite.h> + +/* count of the number of times DllMain() was called with THREAD_DETACH */ +static int g_attachCount = 0; + + +/* standard DllMain() */ +BOOL PALAPI DllMain(HINSTANCE hinstDLL, DWORD reason, LPVOID lpvReserved) +{ + BOOL bResult = TRUE; + + switch( reason ) + { + case DLL_PROCESS_ATTACH: + { + break; + } + + case DLL_PROCESS_DETACH: + { + break; + } + + case DLL_THREAD_ATTACH: + /* increment g_attachCount */ + g_attachCount++; + break; + + case DLL_THREAD_DETACH: + break; + } + return bResult; +} + + +#ifdef WIN32 +BOOL __stdcall _DllMainCRTStartup(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + return DllMain(hinstDLL, fdwReason, lpvReserved); +} +#endif + +/* function to return the current attach count */ +#ifdef WIN32 +__declspec(dllexport) +#endif +int PALAPI GetAttachCount( void ) +{ + return g_attachCount; +} diff --git a/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test2/test2.c b/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test2/test2.c new file mode 100644 index 0000000000..5fb694ea14 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test2/test2.c @@ -0,0 +1,237 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test2.c +** +** Purpose: Test to ensure DisableThreadLibraryCalls() called for one +** library will not disrupt THREAD_ATTACH notifications etc. by +** other loaded modules. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** Fail +** CreateThread +** ResumeThread +** LoadLibrary +** FreeLibrary +** GetProcAddress +** WaitForSingleObject +** GetLastError +** +** +**===========================================================================*/ +#include <palsuite.h> + +/* SHLEXT is defined only for Unix variants */ + +#if defined(SHLEXT) +#define rgchLibraryFile1 "dllmain1"SHLEXT +#define rgchLibraryFile2 "dllmain2"SHLEXT +#define szFunction "GetAttachCount" +#else +#define rgchLibraryFile1 "dllmain1" +#define rgchLibraryFile2 "dllmain2" +#define szFunction "_GetAttachCount@0" +#endif + +/* define our test function type */ +typedef int ( PALAPI *LPTESTFUNC )( void ); + + + +/** + * ThreadFunc + * + * Dummy thread function for causing DLL thread notifications. + */ +DWORD PALAPI ThreadFunc( LPVOID param ) +{ + /* simulate some brief "work" */ + int i; + for( i=0; i<100000; i++ ) + { + } + + return 0; +} + + +/* main program entry point */ +int __cdecl main( int argc, char **argv ) + +{ + /* local variables */ + + HANDLE hLib1 = NULL; + HANDLE hLib2 = NULL; + LPTESTFUNC pFunc1; + LPTESTFUNC pFunc2; + int attachCount1a = 0; + int attachCount1b = 0; + int attachCount2a = 0; + int attachCount2b = 0; + + HANDLE hThread = NULL; + DWORD IDThread; + DWORD dwRet; + + BOOL bResult = FAIL; + + /* initialize the PAL */ + if( PAL_Initialize(argc, argv) != 0 ) + { + return( FAIL ); + } + + /* Load the first test library */ + hLib1 = LoadLibrary( rgchLibraryFile1 ); + if(hLib1 == NULL) + { + Fail( "ERROR:%lu:LoadLibrary() call failed for %s\n", + GetLastError(), + rgchLibraryFile1 ); + } + + /* Load the second test library */ + hLib2 = LoadLibrary( rgchLibraryFile2 ); + if(hLib2 == NULL) + { + Trace( "ERROR:%lu:LoadLibrary() call failed for %s\n", + GetLastError(), + rgchLibraryFile2 ); + if( ! FreeLibrary( hLib1 ) ) + { + Trace( "ERROR:%lu:FreeLibrary() call failed\n", GetLastError() ); + } + Fail( "test failed\n" ); + } + + + /* Get the addresses of our test functions in the dlls */ + pFunc1 = (LPTESTFUNC)GetProcAddress( hLib1, szFunction ); + if( pFunc1 == NULL ) + { + Trace( "ERROR:%lu%:Unable to find \"%s\" in library \"%s\"\n", + GetLastError(), + szFunction, + rgchLibraryFile1 ); + goto cleanup; + } + + pFunc2 = (LPTESTFUNC)GetProcAddress( hLib2, szFunction ); + if( pFunc1 == NULL ) + { + Trace( "ERROR:%lu%:Unable to find \"%s\" in library \"%s\"\n", + GetLastError(), + szFunction, + rgchLibraryFile2 ); + goto cleanup; + } + + /* disable thread library calls for the first library */ + if( ! DisableThreadLibraryCalls( (HMODULE)hLib1 ) ) + { + Trace( "ERROR:%lu:DisableThreadLibraryCalls() call failed\n", + GetLastError() ); + goto cleanup; + } + + /* Execute the test function to get the attach count */ + attachCount1a = pFunc1(); + attachCount2a = pFunc2(); + + /* run another dummy thread to cause notification of the libraries */ + hThread = CreateThread( NULL, /* no security attributes */ + 0, /* use default stack size */ + (LPTHREAD_START_ROUTINE) ThreadFunc, /* thread function */ + (LPVOID) NULL, /* pass thread index as */ + /* function argument */ + CREATE_SUSPENDED, /* create suspended */ + &IDThread ); /* returns thread id */ + + /* Check the return value for success. */ + if( hThread == NULL ) + { + /* error creating thread */ + Trace( "ERROR:%lu:CreateThread() call failed\n", + GetLastError() ); + goto cleanup; + } + + /* Resume the suspended thread */ + if( ResumeThread( hThread ) == -1 ) + { + Trace( "ERROR:%lu:ResumeThread() call failed\n", GetLastError() ); + goto cleanup; + } + + + /* wait for the thread to complete */ + dwRet = WaitForSingleObject( hThread, INFINITE ); + if( dwRet != WAIT_OBJECT_0 ) + { + Trace( "ERROR: WaitForSingleObject returned %lu, " + "expected WAIT_OBJECT_0\n", + dwRet ); + goto cleanup; + } + + + /* Execute the test function to get the new detach count */ + attachCount1b = pFunc1(); + attachCount2b = pFunc2(); + + /* validate the result */ + if( attachCount1b != attachCount1a ) + { + Trace( "FAIL: unexpected DLL attach count %d, expected %d\n", + attachCount1b, + attachCount1a ); + goto cleanup; + } + + /* validate the result */ + if( attachCount2b != (attachCount2a + 1) ) + { + Trace( "FAIL: unexpected DLL attach count %d, expected %d\n", + attachCount2b, + (attachCount2a + 1) ); + goto cleanup; + } + + bResult = PASS; + +cleanup: + /* Unload the test libraries */ + if( !FreeLibrary( hLib1 ) ) + { + Trace( "ERROR:%u:FreeLibrary() failed on library \"%s\"\n", + GetLastError(), + rgchLibraryFile1 ); + bResult = FAIL; + } + + if( !FreeLibrary( hLib2 ) ) + { + Trace( "ERROR:%u:FreeLibrary() failed on library \"%s\"\n", + GetLastError(), + rgchLibraryFile2 ); + bResult = FAIL; + } + + /* check for failure */ + if( bResult == FAIL ) + { + Fail( "test failed\n" ); + } + + /* terminate the PAL */ + PAL_Terminate(); + + /* return success */ + return PASS; +} + diff --git a/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test2/testinfo.dat b/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test2/testinfo.dat new file mode 100644 index 0000000000..6d28595628 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DisableThreadLibraryCalls/test2/testinfo.dat @@ -0,0 +1,18 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = DisableThreadLibraryCalls +Name = Test for DisableThreadLibraryCalls +TYPE = DEFAULT +EXE1 = test2 +LIB1 = dllmain1 +LIB2 = dllmain2 +Description += Test to ensure proper operation of the DisableThreadLibraryCalls() += API. This tests to make sure the DisableThreadLibraryCalls() called += for one DLL loaded by a process takes effect *only* for that DLL += and not for any others, by checking the THREAD_ATTACH notifications += in each. diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/CMakeLists.txt b/src/pal/tests/palsuite/threading/DuplicateHandle/CMakeLists.txt new file mode 100644 index 0000000000..b908c1246b --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test10) +add_subdirectory(test11) +add_subdirectory(test12) +add_subdirectory(test2) +add_subdirectory(test3) +add_subdirectory(test4) +add_subdirectory(test5) +add_subdirectory(test6) +add_subdirectory(test7) +add_subdirectory(test8) +add_subdirectory(test9) + diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/DuplicateHandle/test1/CMakeLists.txt new file mode 100644 index 0000000000..04588b75fe --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_duplicatehandle_test1 + ${SOURCES} +) + +add_dependencies(paltest_duplicatehandle_test1 coreclrpal) + +target_link_libraries(paltest_duplicatehandle_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test1/test1.c b/src/pal/tests/palsuite/threading/DuplicateHandle/test1/test1.c new file mode 100644 index 0000000000..e080e98ae8 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test1/test1.c @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test1.c (DuplicateHandle) +** +** Purpose: Tests the PAL implementation of the DuplicateHandle function. +** This test will create two handles to file, one to write and +** one to read what was written. Test on a closed handle and a +** NULL handle, both should fail. +** +** +**===================================================================*/ +#include <palsuite.h> + +int __cdecl main(int argc, char **argv) +{ + HANDLE hFile; + HANDLE hDupFile; + char buf[256]; + char teststr[] = "A uNiQuE tEsT sTrInG"; + char lpFileName[] = "testfile.txt"; + DWORD dwBytesWritten; + DWORD dwBytesRead; + BOOL bRetVal; + + /*Initalize the PAL*/ + if ((PAL_Initialize(argc,argv)) != 0) + { + return (FAIL); + } + + /*Create a file handle with CreateFile*/ + hFile = CreateFile(lpFileName, + GENERIC_WRITE|GENERIC_READ, + FILE_SHARE_WRITE|FILE_SHARE_READ, + NULL, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (hFile == INVALID_HANDLE_VALUE) + { + Fail("ERROR: %u :unable to create file \"%s\".\n", + GetLastError(), + lpFileName); + } + + /*Write test string to the file.*/ + bRetVal = WriteFile(hFile, // handle to file + teststr, // data buffer + strlen(teststr), // number of bytes to write + &dwBytesWritten, // number of bytes written + NULL); // overlapped buffer + + if (bRetVal == FALSE) + { + Trace("ERROR: %u : unable to write to file handle " + "hFile=0x%lx\n", + GetLastError(), + hFile); + CloseHandle(hFile); + Fail(""); + } + + /*Create a duplicate handle with DuplicateHandle.*/ + if (!(DuplicateHandle( + GetCurrentProcess(), + hFile, + GetCurrentProcess(), + &hDupFile, + GENERIC_READ|GENERIC_WRITE, + FALSE, + DUPLICATE_SAME_ACCESS))) + { + Trace("ERROR: %u : Fail to create the duplicate handle" + " to hFile=0x%lx\n", + GetLastError(), + hFile); + CloseHandle(hFile); + Fail(""); + } + + memset(buf, 0, 256); + + /*Read from the Duplicated handle.*/ + bRetVal = ReadFile(hDupFile, + buf, + 256, + &dwBytesRead, + NULL); + if (bRetVal == FALSE) + { + Trace("ERROR: %u :unable to read from file handle " + "hFile=0x%lx\n", + GetLastError(), + hFile); + CloseHandle(hFile); + CloseHandle(hDupFile); + Fail(""); + } + + /*Compare what was written to what was read.*/ + if (memcmp(teststr, buf, dwBytesRead) != 0) + { + Trace("ERROR: expected %s, got %s\n", teststr, buf); + CloseHandle(hFile); + CloseHandle(hDupFile); + Fail(""); + } + + /*Close the handles*/ + CloseHandle(hFile); + CloseHandle(hDupFile); + + /*Failure test: Create DuplicateHandle to a closed handle*/ + if ((DuplicateHandle( + GetCurrentProcess(), + hFile, + GetCurrentProcess(), + &hDupFile, + GENERIC_READ|GENERIC_WRITE, + FALSE, + DUPLICATE_SAME_ACCESS))) + { + Fail("ERROR: %u :Created a duplicate handle to" + " a closed handle hFile=0x%lx\n", + GetLastError(), + hFile); + } + + /*Failure test: Create DuplicateHandle to a NULL handle*/ + hFile = NULL; + if ((DuplicateHandle( + GetCurrentProcess(), + hFile, + GetCurrentProcess(), + &hDupFile, + GENERIC_READ|GENERIC_WRITE, + FALSE, + DUPLICATE_SAME_ACCESS))) + { + Fail("ERROR: %u :Created a duplicate handle to " + " a NULL handle hFile=0x%lx\n", + GetLastError(), + hFile); + } + + PAL_Terminate(); + return (PASS); +} diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test1/testinfo.dat b/src/pal/tests/palsuite/threading/DuplicateHandle/test1/testinfo.dat new file mode 100644 index 0000000000..e22b0bea6a --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test1/testinfo.dat @@ -0,0 +1,15 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Threading +Function = DuplicateHandle +Name = Positive Test for DuplicateHandle +TYPE = DEFAULT +EXE1 = test1 +Description += Tests the PAL implementation of the DuplicateHandle function. += This test will create two handles to file, one to write and += one to read what was written. Test on a closed handle and a += NULL handle, both should fail. diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test10/CMakeLists.txt b/src/pal/tests/palsuite/threading/DuplicateHandle/test10/CMakeLists.txt new file mode 100644 index 0000000000..ba16252cb4 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test10/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test10.c +) + +add_executable(paltest_duplicatehandle_test10 + ${SOURCES} +) + +add_dependencies(paltest_duplicatehandle_test10 coreclrpal) + +target_link_libraries(paltest_duplicatehandle_test10 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test10/test10.c b/src/pal/tests/palsuite/threading/DuplicateHandle/test10/test10.c new file mode 100644 index 0000000000..108d748de6 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test10/test10.c @@ -0,0 +1,239 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test10.c (DuplicateHandle) +** +** Purpose: Tests the PAL implementation of the DuplicateHandle function. +** This tests the operation of a duplicated Semaphore handle +** +** +**===================================================================*/ + +#include <palsuite.h> + +enum wait_results +{ + WR_WAITING, + WR_GOT_MUTEX, + WR_TIMED_OUT, + WR_RELEASED +}; + + +volatile int t1_result=WR_WAITING; +volatile int t2_result=WR_WAITING; + + +DWORD PALAPI ThreadTest1(LPVOID lpParam) +{ + DWORD dwWait; + + dwWait = WaitForSingleObject((HANDLE)lpParam, 0); + if (dwWait == WAIT_OBJECT_0) + { + /* tell the main thread we got the mutex */ + t1_result=WR_GOT_MUTEX; + + /* wait for main thread to tell us to release the mutex */ + while(WR_GOT_MUTEX == t1_result) + Sleep(1); + ReleaseSemaphore((HANDLE)lpParam, 1, NULL); + + /* tell the main thread we released the mutex */ + t1_result = WR_RELEASED; + } + else + { + t1_result = WR_TIMED_OUT; + } + return 0; +} + +DWORD PALAPI ThreadTest2(LPVOID lpParam) +{ + DWORD dwWait; + + dwWait = WaitForSingleObject((HANDLE)lpParam, 0 ); + if (dwWait == WAIT_OBJECT_0) + { + ReleaseSemaphore((HANDLE)lpParam, 1, NULL); + t2_result = WR_GOT_MUTEX; + } + else + { + t2_result = WR_TIMED_OUT; + } + + return 0; +} + + +int __cdecl main(int argc, char **argv) +{ + + HANDLE hDupSemaphore; + HANDLE hSemaphore; + HANDLE hThread; + HANDLE hThread2; + BOOL bDupHandle=FALSE; + DWORD dwThreadId = 0; + + if ((PAL_Initialize(argc,argv)) != 0) + { + return(FAIL); + } + + hSemaphore = CreateSemaphoreW( NULL, + 1, + 1, + NULL); + if (hSemaphore == NULL) + { + Fail("PALSUITE ERROR:%u: Unable to create mutex\n", + GetLastError()); + } + + /*Create Duplicate of the Semaphore above*/ + bDupHandle = DuplicateHandle(GetCurrentProcess(), + hSemaphore, + GetCurrentProcess(), + &hDupSemaphore, + GENERIC_READ|GENERIC_WRITE, + FALSE, + DUPLICATE_SAME_ACCESS); + if (!bDupHandle) + { + Trace("PALSUITE ERROR:%u: Created the duplicate handle to " + "closed event handle hSemaphore=0x%lx\n", + GetLastError(), + hSemaphore); + CloseHandle(hSemaphore); + Fail(""); + } + + /*Create a thread to test the Semaphore*/ + hThread = CreateThread(NULL, + 0, + &ThreadTest1, + hSemaphore, + 0, + &dwThreadId); + if (hThread == NULL) + { + Trace("PALSUITE ERROR:%u: unable to create thread\n", + GetLastError()); + CloseHandle(hSemaphore); + CloseHandle(hDupSemaphore); + Fail(""); + } + + /* wait until thread has taken the mutex */ + while (WR_WAITING == t1_result) + Sleep(1); + + if(WR_TIMED_OUT == t1_result) + { + Trace("PALSUITE ERROR: %u: thread couldn't acquire the semaphore\n", + GetLastError()); + CloseHandle(hSemaphore); + CloseHandle(hDupSemaphore); + CloseHandle(hThread); + Fail(""); + } + + /*Create a second thread to use the Semaphore's duplicate handle*/ + /*This thread should block since the Semaphore is owned by another + thread*/ + hThread2 = CreateThread(NULL, + 0, + &ThreadTest2, + hDupSemaphore, + 0, + &dwThreadId); + + if (hThread2 == NULL) + { + Trace("PALSUITE ERROR:%u: unable to create thread\n", + GetLastError()); + CloseHandle(hSemaphore); + CloseHandle(hDupSemaphore); + CloseHandle(hThread); + Fail(""); + } + + /* wait until thread has tried to take the mutex */ + while (WR_WAITING == t2_result) + Sleep(1); + + if (WR_TIMED_OUT != t2_result ) + { + Trace("PALSUITE ERROR:%u: Able to take mutex %#x while its " + "duplicate %#x is held\n", GetLastError(), hDupSemaphore, + hSemaphore); + CloseHandle(hSemaphore); + CloseHandle(hDupSemaphore); + CloseHandle(hThread); + CloseHandle(hThread2); + Fail(""); + } + + /* reset second thread status */ + t2_result = WR_WAITING; + + /* tell thread 1 to release the mutex */ + t1_result = WR_WAITING; + + /* wait for thread 1 to release the mutex */ + while (WR_WAITING == t1_result) + Sleep(1); + + CloseHandle(hThread2); + + /*Re-Create the second thread to reuse the duplicated Semaphore*/ + /*Since the Semaphore has since been released, the thread should + put WR_GOT_MUTEX into t2_result */ + hThread2 = CreateThread(NULL, + 0, + &ThreadTest2, + hDupSemaphore, + 0, + &dwThreadId); + + if (hThread2 == NULL) + { + Trace("PALSUITE ERROR:%u: unable to create thread\n", + GetLastError()); + CloseHandle(hSemaphore); + CloseHandle(hDupSemaphore); + CloseHandle(hThread); + Fail(""); + } + + /* wait until thread has taken the semaphore */ + while (WR_WAITING == t2_result) + Sleep(1); + + if (WR_GOT_MUTEX != t2_result ) + { + Trace("PALSUITE ERROR:%u: Unable to take semaphore %#x after its" + " duplicate %#x was released\n", GetLastError(), hDupSemaphore, + hSemaphore); + CloseHandle(hSemaphore); + CloseHandle(hDupSemaphore); + CloseHandle(hThread); + CloseHandle(hThread2); + Fail(""); + } + + /*Cleanup.*/ + CloseHandle(hSemaphore); + CloseHandle(hDupSemaphore); + CloseHandle(hThread); + CloseHandle(hThread2); + + PAL_Terminate(); + return (PASS); +} diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test10/testinfo.dat b/src/pal/tests/palsuite/threading/DuplicateHandle/test10/testinfo.dat new file mode 100644 index 0000000000..674c71c2b3 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test10/testinfo.dat @@ -0,0 +1,17 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Threading +Function = DuplicateHandle +Name = Test for DuplicateHandle (CreateSemaphore) +TYPE = DEFAULT +EXE1 = test10 +Description += Tests the PAL implementation of the DuplicateHandle function. += This test duplication of a Semaphore handle. The test will += create a Semaphore and duplicate a handle to it. += Then two subthreads will be used to verify that they can selectively += block each other with different handles that refer to the += same Semaphore diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test11/CMakeLists.txt b/src/pal/tests/palsuite/threading/DuplicateHandle/test11/CMakeLists.txt new file mode 100644 index 0000000000..68ce7b23fb --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test11/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(TESTSOURCES + test11.c +) + +add_executable(paltest_duplicatehandle_test11 + ${TESTSOURCES} +) + +add_dependencies(paltest_duplicatehandle_test11 coreclrpal) + +target_link_libraries(paltest_duplicatehandle_test11 + pthread + m + coreclrpal +) + + +set(HELPERSOURCES + childprocess.c +) + +add_executable(paltest_duplicatehandle_test11_child + ${HELPERSOURCES} +) + +add_dependencies(paltest_duplicatehandle_test11_child coreclrpal) + +target_link_libraries(paltest_duplicatehandle_test11_child + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test11/childprocess.c b/src/pal/tests/palsuite/threading/DuplicateHandle/test11/childprocess.c new file mode 100644 index 0000000000..d5b310e46c --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test11/childprocess.c @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: childprocess.c +** +** Purpose: Test to ensure DuplicateHandle works properly. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** CreateMutexW +** WaitForSingleObject +** CloseHandle +** +** +**=========================================================*/ + +#include <palsuite.h> +#include "myexitcode.h" + + +int __cdecl main( int argc, char **argv ) +{ + HANDLE hMutex; + WCHAR wszMutexName[] = { 'T','E','S','T','1','1','\0' }; + DWORD dwRet; + int i; + + /* initialize the PAL */ + if( PAL_Initialize(argc, argv) != 0 ) + { + return( FAIL ); + } + + /* open a mutex to synchronize with the parent process */ + hMutex = CreateMutexW( NULL, FALSE, wszMutexName ); + if( hMutex == NULL ) + { + Fail( "ERROR:%lu:CreateMutex() call failed\r\n", GetLastError() ); + } + + /* acquire the mutex lock */ + dwRet = WaitForSingleObject( hMutex, 10000 ); + if( dwRet != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject() returned %lu, " + "expected WAIT_OBJECT_0", + dwRet ); + if( ! CloseHandle( hMutex ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + Fail( "test failed\n" ); + } + + + /* simulate some activity */ + for( i=0; i<50000; i++ ) + ; + + /* close our mutex handle */ + if( ! CloseHandle( hMutex ) ) + { + Fail( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + + /* terminate the PAL */ + PAL_Terminate(); + + /* return the predefined exit code */ + return TEST_EXIT_CODE; +} diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test11/myexitcode.h b/src/pal/tests/palsuite/threading/DuplicateHandle/test11/myexitcode.h new file mode 100644 index 0000000000..84801cbb54 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test11/myexitcode.h @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: duplicatehandle/test11/myexitcode.h +** +** Purpose: Define an exit code constant. +** +** +**=========================================================*/ +#define TEST_EXIT_CODE 31 diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test11/test11.c b/src/pal/tests/palsuite/threading/DuplicateHandle/test11/test11.c new file mode 100644 index 0000000000..b05244c4b8 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test11/test11.c @@ -0,0 +1,364 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test11.c +** +** Purpose: +** +** Test to ensure proper operation of the DuplicateHandle API. +** The test launches a trivial child process, then opens +** a handle to it using OpenProcess. It then duplicates that +** handle and uses it to wait for the child process to terminate, +** and then checks the exit code of the child process in order to +** verify that it was in fact a handle to the correct +** process. The test tries to duplicate the handle again after +** the process has been closed, to verify that failure ensues. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** Fail +** ZeroMemory +** GetCurrentDirectoryW +** CreateProcessW +** WaitForSingleObject +** CreateMutexW +** ReleaseMutex +** CloseHandle +** GetLastError +** strlen +** strncpy +** +** +**===========================================================================*/ +#include <palsuite.h> +#include "myexitcode.h" + + +static const char* rgchPathDelim = "\\"; + + +int +mkAbsoluteFilename( LPSTR dirName, + DWORD dwDirLength, + LPCSTR fileName, + DWORD dwFileLength, + LPSTR absPathName ) +{ + DWORD sizeDN, sizeFN, sizeAPN; + + sizeDN = strlen( dirName ); + sizeFN = strlen( fileName ); + sizeAPN = (sizeDN + 1 + sizeFN + 1); + + /* ensure ((dirName + DELIM + fileName + \0) =< _MAX_PATH ) */ + if( sizeAPN > _MAX_PATH ) + { + return ( 0 ); + } + + strncpy( absPathName, dirName, dwDirLength +1 ); + strncpy( absPathName, rgchPathDelim, 2 ); + strncpy( absPathName, fileName, dwFileLength +1 ); + + return (sizeAPN); + +} + + +int __cdecl main( int argc, char **argv ) + +{ + const char* rgchChildFile = "childprocess"; + + STARTUPINFO si; + PROCESS_INFORMATION pi; + + DWORD dwError; + DWORD dwExitCode; + DWORD dwFileLength; + DWORD dwDirLength; + DWORD dwSize; + DWORD dwRet; + + HANDLE hMutex; + HANDLE hChildProcess; + HANDLE hDupChildProcess; + + char rgchDirName[_MAX_DIR]; + char absPathBuf[_MAX_PATH]; + char* rgchAbsPathName; + + BOOL ret = FAIL; + BOOL bChildDone = FALSE; + WCHAR wszMutexName[] = { 'T','E','S','T','1','1','\0' }; + + /* initialize the PAL */ + if( PAL_Initialize(argc, argv) != 0 ) + { + return( FAIL ); + } + + /* create a mutex to synchronize with the child process */ + hMutex = CreateMutexW( NULL, TRUE, wszMutexName ); + if( hMutex == NULL ) + { + Fail( "ERROR:%lu:CreateMutex() call failed\r\n", GetLastError() ); + } + + /* zero our process and startup info structures */ + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof( si ); + ZeroMemory( &pi, sizeof(pi) ); + + /* build the absolute path to the child process */ + rgchAbsPathName = &absPathBuf[0]; + dwFileLength = strlen( rgchChildFile ); + + dwDirLength = GetCurrentDirectory( _MAX_PATH, rgchDirName ); + if( dwDirLength == 0 ) + { + dwError = GetLastError(); + if( ReleaseMutex( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); + } + if( CloseHandle( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + Fail( "GetCurrentDirectory call failed with error code %d\n", + dwError ); + } + + dwSize = mkAbsoluteFilename( rgchDirName, + dwDirLength, + rgchChildFile, + dwFileLength, + rgchAbsPathName ); + if( dwSize == 0 ) + { + if( ReleaseMutex( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); + } + if( CloseHandle( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + Fail( "Palsuite Code: mkAbsoluteFilename() call failed. Could ", + "not build absolute path name to file\n. Exiting.\n" ); + } + + /* launch the child process */ + if( !CreateProcess( NULL, /* module name to execute */ + rgchAbsPathName, /* command line */ + NULL, /* process handle not */ + /* inheritable */ + NULL, /* thread handle not */ + /*inheritable */ + FALSE, /* handle inheritance */ + CREATE_NEW_CONSOLE, /* dwCreationFlags */ + NULL, /* use parent's environment */ + NULL, /* use parent's starting */ + /* directory */ + &si, /* startup info struct */ + &pi ) /* process info struct */ + ) + { + dwError = GetLastError(); + if( ReleaseMutex( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); + } + if( CloseHandle( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + Fail( "CreateProcess call failed with error code %d\n", + dwError ); + } + + /* open another handle to the child process */ + hChildProcess = OpenProcess( PROCESS_ALL_ACCESS, /* access */ + FALSE, /* inheritable */ + pi.dwProcessId /* process id */ + ); + if( hChildProcess == NULL ) + { + dwError = GetLastError(); + if( ReleaseMutex( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); + } + Trace( "ERROR:%lu:OpenProcess call failed\n", dwError ); + goto cleanup3; + } + + /* duplicate the child process handle */ + if( ! DuplicateHandle( GetCurrentProcess(), + hChildProcess, + GetCurrentProcess(), + &hDupChildProcess, + GENERIC_READ|GENERIC_WRITE, + FALSE, + DUPLICATE_SAME_ACCESS) ) + { + Trace( "ERROR:%lu:DuplicateHandle() call failed\n", GetLastError() ); + if( ReleaseMutex( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); + } + goto cleanup2; + } + + /* release the mutex so the child can proceed */ + if( ReleaseMutex( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); + goto cleanup1; + } + + /* wait for the child process to complete, using the new handle */ + dwRet = WaitForSingleObject( hDupChildProcess, 10000 ); + if( dwRet != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject call returned %lu, " + "expected WAIT_OBJECT_0", + dwRet ); + goto cleanup1; + } + + /* remember that we waited until the child was finished */ + bChildDone = TRUE; + + /* check the exit code from the process -- this is a bit of an */ + /* extra verification that we opened the correct process handle */ + if( ! GetExitCodeProcess( hDupChildProcess, &dwExitCode ) ) + { + Trace( "ERROR:%lu:GetExitCodeProcess call failed\n", GetLastError() ); + goto cleanup1; + } + + /* verification */ + if( (dwExitCode & 0xFF) != (TEST_EXIT_CODE & 0xFF) ) + { + Trace( "GetExitCodeProcess returned an incorrect exit code %d, " + "expected value is %d\n", + (dwExitCode & 0xFF), + (TEST_EXIT_CODE & 0xFF)); + goto cleanup1; + } + + /* close the duplicate handle */ + if( ! CloseHandle( hDupChildProcess ) ) + { + Trace( "ERROR:%lu:CloseHandle call failed\n", GetLastError() ); + goto cleanup2; + } + + /* close the child process handle */ + if( ! CloseHandle ( hChildProcess ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + goto cleanup3; + } + + /* try to call duplicate handle on the closed child process handle */ + if( DuplicateHandle( GetCurrentProcess(), + hChildProcess, + GetCurrentProcess(), + &hDupChildProcess, + GENERIC_READ|GENERIC_WRITE, + FALSE, + DUPLICATE_SAME_ACCESS) ) + { + Trace( "ERROR:%lu:DuplicateHandle call succeeded on " + "a closed process handle, expected ERROR_INVALID_HANDLE\n" ); + if( ! CloseHandle( hDupChildProcess ) ) + { + Trace( "ERROR:%lu:CloseHandle call failed\n", GetLastError() ); + } + goto cleanup3; + } + + /* verify that the last error was ERROR_INVALID_HANDLE */ + dwRet = GetLastError(); + if( dwRet != ERROR_INVALID_HANDLE ) + { + Trace( "ERROR:DuplicateHandle returned %lu, " + "expected ERROR_INVALID_HANDLE\n", + dwRet ); + goto cleanup3; + } + + + /* success if we get here */ + ret = PASS; + + /* skip the cleanup stuff that's already done */ + goto cleanup3; + + +cleanup1: + /* close our duplicate handle */ + if( ! CloseHandle( hDupChildProcess ) ) + { + Trace( "ERROR:%lu:CloseHandle call failed\n", GetLastError() ); + ret = FAIL; + } + +cleanup2: + /* wait on the child process to complete if necessary */ + if( ! bChildDone ) + { + dwRet = WaitForSingleObject( hChildProcess, 10000 ); + if( dwRet != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject call returned %lu, " + "expected WAIT_OBJECT_0", + dwRet ); + ret = FAIL; + } + } + + /* close our child process handle */ + if( CloseHandle ( hChildProcess ) == 0 ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + ret = FAIL; + } + +cleanup3: + /* close all our other handles */ + if( CloseHandle ( pi.hProcess ) == 0 ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + ret = FAIL; + } + if( CloseHandle ( pi.hThread ) == 0 ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + ret = FAIL; + } + if( CloseHandle( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + ret = FAIL; + } + + if( ret == FAIL ) + { + Fail( "test failed\n" ); + } + + + + /* terminate the PAL */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test11/testinfo.dat b/src/pal/tests/palsuite/threading/DuplicateHandle/test11/testinfo.dat new file mode 100644 index 0000000000..1937877880 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test11/testinfo.dat @@ -0,0 +1,19 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = DuplicateHandle +Name = Test for DuplicateHandle +TYPE = DEFAULT +EXE1 = test11 +EXE2 = childprocess +Description += Test to ensure proper operation of the DuplicateHandle API. += The test launches a trivial child process, then opens += a handle to it using OpenProcess. It then duplicates that += handle and uses it to wait for the child process to terminate, += and then checks the exit code of the child process in order to += verify that it was in fact a handle to the correct= process. The test tries to duplicate the handle again after += the process has been closed, to verify that failure ensues. diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test12/CMakeLists.txt b/src/pal/tests/palsuite/threading/DuplicateHandle/test12/CMakeLists.txt new file mode 100644 index 0000000000..961a9c64e6 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test12/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test12.c +) + +add_executable(paltest_duplicatehandle_test12 + ${SOURCES} +) + +add_dependencies(paltest_duplicatehandle_test12 coreclrpal) + +target_link_libraries(paltest_duplicatehandle_test12 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test12/test12.c b/src/pal/tests/palsuite/threading/DuplicateHandle/test12/test12.c new file mode 100644 index 0000000000..519194bc3a --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test12/test12.c @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test12.c (DuplicateHandle) +** +** Purpose: Tests the PAL implementation of the DuplicateHandle function. +** This test will create handle to file (to write) and close it, +** then call duplicate handle to read what was written. +** +** +**===================================================================*/ +#include <palsuite.h> + +int __cdecl main(int argc, char **argv) +{ + HANDLE hFile; + HANDLE hDupFile; + char buf[256]; + char teststr[] = "A uNiQuE tEsT sTrInG"; + char lpFileName[] = "testfile.txt"; + DWORD dwBytesWritten; + DWORD dwBytesRead; + BOOL bRetVal; + + /*Initalize the PAL*/ + if ((PAL_Initialize(argc,argv)) != 0) + { + return (FAIL); + } + + /*Create a file handle with CreateFile*/ + hFile = CreateFile(lpFileName, + GENERIC_WRITE|GENERIC_READ, + FILE_SHARE_WRITE|FILE_SHARE_READ, + NULL, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (hFile == INVALID_HANDLE_VALUE) + { + Fail("ERROR: %u :unable to create file \"%s\".\n", + GetLastError(), + lpFileName); + } + + /*Write test string to the file.*/ + bRetVal = WriteFile(hFile, // handle to file + teststr, // data buffer + strlen(teststr), // number of bytes to write + &dwBytesWritten, // number of bytes written + NULL); // overlapped buffer + + if (bRetVal == FALSE) + { + Trace("ERROR: %u : unable to write to file handle " + "hFile=0x%lx\n", + GetLastError(), + hFile); + CloseHandle(hFile); + Fail(""); + } + + /*Create a duplicate handle with DuplicateHandle.*/ + if (!(DuplicateHandle( + GetCurrentProcess(), + hFile, + GetCurrentProcess(), + &hDupFile, + GENERIC_READ|GENERIC_WRITE, + FALSE, + DUPLICATE_SAME_ACCESS))) + { + Trace("ERROR: %u : Fail to create the duplicate handle" + " to hFile=0x%lx\n", + GetLastError(), + hFile); + CloseHandle(hFile); + Fail(""); + } + + if( !CloseHandle(hFile) ) + { + Fail("Duplicate Handle:Unable to close original file: Error[%u]\n", GetLastError()); + } + + memset(buf, 0, 256); + + /*Read from the Duplicated handle.*/ + bRetVal = ReadFile(hDupFile, + buf, + 256, + &dwBytesRead, + NULL); + if (bRetVal == FALSE) + { + Trace("ERROR: %u :unable to read from file handle " + "hFile=0x%lx\n", + GetLastError(), + hDupFile); + CloseHandle(hDupFile); + Fail(""); + } + + /*Compare what was written to what was read.*/ + if (memcmp(teststr, buf, dwBytesRead) != 0) + { + Trace("ERROR: expected %s, got %s\n", teststr, buf); + CloseHandle(hDupFile); + Fail(""); + } + + /*Close the handles*/ + CloseHandle(hDupFile); + + bRetVal = DeleteFileA(lpFileName); + if (bRetVal != TRUE) + { + Trace("Error:%u: DuplicateHandle, DeleteFileA: Couldn't delete DeleteFileA's" + " %s\n", GetLastError(), lpFileName); + Fail(""); + } + + + PAL_Terminate(); + return (PASS); +} diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test12/testinfo.dat b/src/pal/tests/palsuite/threading/DuplicateHandle/test12/testinfo.dat new file mode 100644 index 0000000000..3d73362eb3 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test12/testinfo.dat @@ -0,0 +1,15 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Threading +Function = DuplicateHandle +Name = Positive Test for DuplicateHandle +TYPE = DEFAULT +EXE1 = test1 +Description += Tests the PAL implementation of the DuplicateHandle function. += This test will create handle to file (to write), += then call duplicate handle, and close the original handle += and then use duplicated handle to read what was written. diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/DuplicateHandle/test2/CMakeLists.txt new file mode 100644 index 0000000000..06529a6204 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test2.c +) + +add_executable(paltest_duplicatehandle_test2 + ${SOURCES} +) + +add_dependencies(paltest_duplicatehandle_test2 coreclrpal) + +target_link_libraries(paltest_duplicatehandle_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test2/test2.c b/src/pal/tests/palsuite/threading/DuplicateHandle/test2/test2.c new file mode 100644 index 0000000000..d1411e62d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test2/test2.c @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test2.c (DuplicateHandle) +** +** Purpose: Tests the PAL implementation of the DuplicateHandle function. +** This will test duplication of an CreateEvent handle. Test an +** event in a signaled state to wait, and then set the duplicate +** to nonsignaled state and perform the wait again. The wait on +** the event should fail. Test the duplication of closed and NULL +** events, these should fail. +** +** +**===================================================================*/ +#include <palsuite.h> + +int __cdecl main(int argc, char **argv) +{ + HANDLE hEvent; + HANDLE hDupEvent; + + /*Initalize the PAL.*/ + if ((PAL_Initialize(argc,argv)) != 0) + { + return (FAIL); + } + + /*Create an Event, and set it in the signaled state.*/ + hEvent = CreateEvent(0, TRUE, TRUE, 0); + if (hEvent == NULL) + { + Fail("ERROR: %u :unable to create event\n", + GetLastError()); + } + + /*Create a duplicate Event handle.*/ + if (!(DuplicateHandle(GetCurrentProcess(), + hEvent,GetCurrentProcess(), + &hDupEvent, + GENERIC_READ|GENERIC_WRITE, + FALSE, DUPLICATE_SAME_ACCESS))) + { + Trace("ERROR: %u :Fail to create the duplicate handle" + " to hEvent=0x%lx\n", + GetLastError(), + hEvent); + CloseHandle(hEvent); + Fail(""); + } + + /*Perform wait on Event that is in signaled state.*/ + if ((WaitForSingleObject(hEvent, 1000)) != WAIT_OBJECT_0) + { + Trace("ERROR: %u :WaitForSignalObject on Event=0x%lx set to " + " signaled state failed", + GetLastError(), + hEvent); + CloseHandle(hEvent); + CloseHandle(hDupEvent); + Fail(""); + } + + /*Set the Duplicate Event handle to nonsignaled state.*/ + if ((ResetEvent(hDupEvent)) == 0) + { + Trace("ERROR: %u :unable to reset dup event\n", + GetLastError()); + CloseHandle(hEvent); + CloseHandle(hDupEvent); + Fail(""); + } + + /*Perform wait on Event that is in signaled state.*/ + if ((WaitForSingleObject(hEvent, 1000)) == WAIT_OBJECT_0) + { + Trace("ERROR: %u: WaitForSignalObject succeeded on Event=0x%lx " + " when Duplicate Event=0x%lx set to nonsignaled state" + " succeeded\n", + GetLastError(), + hEvent, + hDupEvent); + CloseHandle(hEvent); + CloseHandle(hDupEvent); + Fail(""); + } + + /*Close handles to events.*/ + CloseHandle(hEvent); + CloseHandle(hDupEvent); + + PAL_Terminate(); + return (PASS); +} diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test2/testinfo.dat b/src/pal/tests/palsuite/threading/DuplicateHandle/test2/testinfo.dat new file mode 100644 index 0000000000..273440804e --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test2/testinfo.dat @@ -0,0 +1,17 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Threading +Function = DuplicateHandle +Name = Test for DuplicateHandle +TYPE = DEFAULT +EXE1 = test2 +Description += Tests the PAL implementation of the DuplicateHandle function. += This will test duplication of an CreateEvent handle. Test an += event in a signaled state to wait, and then set the duplicate += to nonsignaled state and perform the wait again. The wait on += the event should fail. Test the duplication of closed and NULL += events, these should fail. diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test3/CMakeLists.txt b/src/pal/tests/palsuite/threading/DuplicateHandle/test3/CMakeLists.txt new file mode 100644 index 0000000000..7f961c2213 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test3/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test3.c +) + +add_executable(paltest_duplicatehandle_test3 + ${SOURCES} +) + +add_dependencies(paltest_duplicatehandle_test3 coreclrpal) + +target_link_libraries(paltest_duplicatehandle_test3 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test3/test3.c b/src/pal/tests/palsuite/threading/DuplicateHandle/test3/test3.c new file mode 100644 index 0000000000..fc91b5ec22 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test3/test3.c @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test3.c (DuplicateHandle) +** +** Purpose: Tests the PAL implementation of the DuplicateHandle function. +** This will test duplication of an OpenEvent handle. Test an +** event in a signaled state to wait, and then set the duplicate +** to nonsignaled state and perform the wait again. The wait on +** the event should fail. Test the duplication of closed and NULL +** events, these should fail. +** +** +**===================================================================*/ +#include <palsuite.h> + +int __cdecl main(int argc, char **argv) +{ + HANDLE hCreateEvent; + HANDLE hOpenEvent; + HANDLE hDupEvent; + WCHAR lpEventName[]={'E','v','e','n','t','\0'}; + + /*Initialize the PAL.*/ + if ((PAL_Initialize(argc,argv)) != 0) + { + return (FAIL); + } + + /*Create an Event, and set it in the signaled state.*/ + hCreateEvent = CreateEventW(0, + TRUE, + TRUE, + lpEventName); + if (hCreateEvent == NULL) + { + Fail("ERROR: %u :unable to create event %s\n", + GetLastError(), + lpEventName); + } + + /*Open another handle to hCreateHandle with OpenEvent*/ + hOpenEvent = OpenEventW(EVENT_ALL_ACCESS, + TRUE, + lpEventName); + if (hOpenEvent == NULL) + { + Trace("ERROR: %u :unable to create handle with OpenEvent to %s\n", + GetLastError(), + lpEventName); + CloseHandle(hCreateEvent); + Fail(""); + } + + /*Create a duplicate Event handle*/ + if (!(DuplicateHandle(GetCurrentProcess(), + hOpenEvent, + GetCurrentProcess(), + &hDupEvent, + GENERIC_READ|GENERIC_WRITE, + FALSE, + DUPLICATE_SAME_ACCESS))) + { + Trace("ERROR: %u :Fail to create the duplicate handle" + " to hCreateEvent=0x%lx\n", + GetLastError(), + hCreateEvent); + CloseHandle(hCreateEvent); + CloseHandle(hOpenEvent); + Fail(""); + } + + /*Perform wait on Event that is in signaled state*/ + if ((WaitForSingleObject(hOpenEvent, 1000)) != WAIT_OBJECT_0) + { + Trace("ERROR: %u :WaitForSignalObject on hOpenEvent=0x%lx set to " + " signaled state failed\n", + GetLastError(), + hOpenEvent); + CloseHandle(hCreateEvent); + CloseHandle(hOpenEvent); + CloseHandle(hDupEvent); + Fail(""); + } + + /*Set the Duplicate Event handle to nonsignaled state*/ + if ((ResetEvent(hDupEvent)) == 0) + { + Trace("ERROR: %u: unable to reset hDupEvent=0x%lx\n", + GetLastError(), + hDupEvent); + CloseHandle(hCreateEvent); + CloseHandle(hOpenEvent); + CloseHandle(hDupEvent); + Fail(""); + } + + /*Perform wait on Event that is in signaled state*/ + if ((WaitForSingleObject(hOpenEvent, 1000)) == WAIT_OBJECT_0) + { + Trace("ERROR: %u :WaitForSignalObject succeeded on hOpenEvent=0x%lx " + " when Duplicate hDupEvent=0x%lx set to nonsignaled state" + " succeeded\n", + GetLastError(), + hOpenEvent, + hDupEvent); + CloseHandle(hCreateEvent); + CloseHandle(hOpenEvent); + CloseHandle(hDupEvent); + Fail(""); + } + + /*Close handles the events*/ + CloseHandle(hCreateEvent); + CloseHandle(hOpenEvent); + CloseHandle(hDupEvent); + + PAL_Terminate(); + return (PASS); +} diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test3/testinfo.dat b/src/pal/tests/palsuite/threading/DuplicateHandle/test3/testinfo.dat new file mode 100644 index 0000000000..a10adb9a8e --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test3/testinfo.dat @@ -0,0 +1,17 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Threading +Function = DuplicateHandle +Name = Test for DuplicateHandle +TYPE = DEFAULT +EXE1 = test3 +Description += Tests the PAL implementation of the DuplicateHandle function. += This will test duplication of an OpenEvent handle. Test an += event in a signaled state to wait, and then set the duplicate += to nonsignaled state and perform the wait again. The wait on += the event should fail. Test the duplication of closed and NULL += events, these should fail. diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test4/CMakeLists.txt b/src/pal/tests/palsuite/threading/DuplicateHandle/test4/CMakeLists.txt new file mode 100644 index 0000000000..c3040d09ec --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test4/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test4.c +) + +add_executable(paltest_duplicatehandle_test4 + ${SOURCES} +) + +add_dependencies(paltest_duplicatehandle_test4 coreclrpal) + +target_link_libraries(paltest_duplicatehandle_test4 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test4/test4.c b/src/pal/tests/palsuite/threading/DuplicateHandle/test4/test4.c new file mode 100644 index 0000000000..14a72db461 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test4/test4.c @@ -0,0 +1,239 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test4.c (DuplicateHandle) +** +** Purpose: Tests the PAL implementation of the DuplicateHandle function. +** This test duplication of a Mutex handle. The test will comprise +** of creating a Mutex and its duplicate and create a thread that +** will get ownership. Another thread will be create that will +** attempt to get ownership of the duplicate Mutex, this will +** fail, since the Mutex is owned by another thread. The Mutex +** will be released and then the thread will attempt to get +** ownership of the duplicate Mutex, this will succeed. +** +** +**===================================================================*/ +#include <palsuite.h> + +enum wait_results +{ + WR_WAITING, + WR_GOT_MUTEX, + WR_TIMED_OUT, + WR_RELEASED +}; + + +volatile int t1_result=WR_WAITING; +volatile int t2_result=WR_WAITING; + + +DWORD PALAPI ThreadTest1(LPVOID lpParam) +{ + DWORD dwWait; + + dwWait = WaitForSingleObject((HANDLE)lpParam, 0); + if (dwWait == WAIT_OBJECT_0) + { + /* tell the main thread we got the mutex */ + t1_result=WR_GOT_MUTEX; + + /* wait for main thread to tell us to release the mutex */ + while(WR_GOT_MUTEX == t1_result) + Sleep(1); + ReleaseMutex((HANDLE)lpParam); + + /* tell the main thread we released the mutex */ + t1_result = WR_RELEASED; + } + else + { + t1_result = WR_TIMED_OUT; + } + return 0; +} + +DWORD PALAPI ThreadTest2(LPVOID lpParam) +{ + DWORD dwWait; + + dwWait = WaitForSingleObject((HANDLE)lpParam, 0 ); + if (dwWait == WAIT_OBJECT_0) + { + ReleaseMutex((HANDLE)lpParam); + t2_result = WR_GOT_MUTEX; + } + else + { + t2_result = WR_TIMED_OUT; + } + + return 0; +} + + +int __cdecl main(int argc, char **argv) +{ + + HANDLE hDupMutex; + HANDLE hMutex; + HANDLE hThread; + HANDLE hThread2; + BOOL bDupHandle=FALSE; + DWORD dwThreadId = 0; + + if ((PAL_Initialize(argc,argv)) != 0) + { + return(FAIL); + } + + /*Create Mutex without ownership*/ + hMutex = CreateMutexW(NULL, // no security attributes + FALSE, // initially not owned + NULL); // name of mutex + if (hMutex == NULL) + { + Fail("ERROR:%u: Unable to create mutex\n", + GetLastError()); + } + + /*Create Duplicate of the Mutex above*/ + bDupHandle = DuplicateHandle(GetCurrentProcess(), + hMutex, + GetCurrentProcess(), + &hDupMutex, + GENERIC_READ|GENERIC_WRITE, + FALSE, + DUPLICATE_SAME_ACCESS); + if (!bDupHandle) + { + Trace("ERROR:%u: Created the duplicate handle to " + "closed event handle hMutex=0x%lx\n", + GetLastError(), + hMutex); + CloseHandle(hMutex); + Fail(""); + } + + /*Create a thread to test the Mutex*/ + hThread = CreateThread(NULL, + 0, + &ThreadTest1, + hMutex, + 0, + &dwThreadId); + if (hThread == NULL) + { + Trace("ERROR:%u: unable to create thread\n", + GetLastError()); + CloseHandle(hMutex); + CloseHandle(hDupMutex); + Fail(""); + } + + /* wait until thread has taken the mutex */ + while (WR_WAITING == t1_result) + Sleep(1); + + if(WR_TIMED_OUT == t1_result) + { + Trace("ERROR: %u: thread 1 couldn't acquire the mutex\n"); + CloseHandle(hMutex); + CloseHandle(hDupMutex); + CloseHandle(hThread); + Fail(""); + } + + /*Create a second thread to use the duplicate Mutex*/ + /*This should fail since the Mutex is owned hThread*/ + hThread2 = CreateThread(NULL, + 0, + &ThreadTest2, + hDupMutex, + 0, + &dwThreadId); + + if (hThread2 == NULL) + { + Trace("ERROR:%u: unable to create thread\n", + GetLastError()); + CloseHandle(hMutex); + CloseHandle(hDupMutex); + CloseHandle(hThread); + Fail(""); + } + + /* wait until thread has tried to take the mutex */ + while (WR_WAITING == t2_result) + Sleep(1); + + if (WR_TIMED_OUT != t2_result ) + { + Trace("ERROR:%u: Able to take mutex %#x while its duplicate %#x is " + "held\n", hDupMutex, hMutex); + CloseHandle(hMutex); + CloseHandle(hDupMutex); + CloseHandle(hThread); + CloseHandle(hThread2); + Fail(""); + } + + /* reset second thread status */ + t2_result = WR_WAITING; + + /* tell thread 1 to release the mutex */ + t1_result = WR_WAITING; + + /* wait for thread 1 to release the mutex */ + while (WR_WAITING == t1_result) + Sleep(1); + + CloseHandle(hThread2); + + /*Re-Create the second thread to reuse the duplicated Mutex*/ + /*This test should pass, the Mutex has since been released*/ + hThread2 = CreateThread(NULL, + 0, + &ThreadTest2, + hDupMutex, + 0, + &dwThreadId); + + if (hThread2 == NULL) + { + Trace("ERROR:%u: unable to create thread\n", + GetLastError()); + CloseHandle(hMutex); + CloseHandle(hDupMutex); + CloseHandle(hThread); + Fail(""); + } + + /* wait until thread has taken the mutex */ + while (WR_WAITING == t2_result) + Sleep(1); + + if (WR_GOT_MUTEX != t2_result ) + { + Trace("ERROR:%u: Unable to take mutex %#x after its duplicate %#x was " + "released\n", hDupMutex, hMutex); + CloseHandle(hMutex); + CloseHandle(hDupMutex); + CloseHandle(hThread); + CloseHandle(hThread2); + Fail(""); + } + + /*Cleanup.*/ + CloseHandle(hMutex); + CloseHandle(hDupMutex); + CloseHandle(hThread); + CloseHandle(hThread2); + + PAL_Terminate(); + return (PASS); +} diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test4/testinfo.dat b/src/pal/tests/palsuite/threading/DuplicateHandle/test4/testinfo.dat new file mode 100644 index 0000000000..64842f8713 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test4/testinfo.dat @@ -0,0 +1,19 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Threading +Function = DuplicateHandle +Name = Test for DuplicateHandle (CreateMutex) +TYPE = DEFAULT +EXE1 = test4 +Description += Tests the PAL implementation of the DuplicateHandle function. += This test duplication of a Mutex handle. The test will comprise += of creating a Mutex and its duplicate and create a thread that will += get ownership. Another thread will be create that will attempt to += get ownership of the duplicate Mutex, this will fail, since the += Mutex is owned by another thread. The Mutex will be released and += then the thread will attempt to get ownership of the duplicate += Mutex, this will succeed. diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test5/CMakeLists.txt b/src/pal/tests/palsuite/threading/DuplicateHandle/test5/CMakeLists.txt new file mode 100644 index 0000000000..bc468a4a75 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test5/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test5.c +) + +add_executable(paltest_duplicatehandle_test5 + ${SOURCES} +) + +add_dependencies(paltest_duplicatehandle_test5 coreclrpal) + +target_link_libraries(paltest_duplicatehandle_test5 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test5/test5.c b/src/pal/tests/palsuite/threading/DuplicateHandle/test5/test5.c new file mode 100644 index 0000000000..a588928f00 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test5/test5.c @@ -0,0 +1,145 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test5.c (DuplicateHandle) +** +** Purpose: Tests the PAL implementation of the DuplicateHandle function, +** with CreatePipe. This test will create a pipe and write to it, +** the duplicate the read handle and read what was written. +** +** Depends: WriteFile +** ReadFile +** memcmp +** CloseHandle +** +** +**===================================================================*/ + +#include <palsuite.h> + +const char* cTestString = "one fish, two fish, red fish, blue fish."; + +int __cdecl main(int argc, char **argv) +{ + HANDLE hReadPipe = NULL; + HANDLE hWritePipe = NULL; + HANDLE hDupPipe = NULL; + BOOL bRetVal = FALSE; + DWORD dwBytesWritten; + DWORD dwBytesRead; + char buffer[256]; + + SECURITY_ATTRIBUTES lpPipeAttributes; + + /*Initialize the PAL*/ + if ((PAL_Initialize(argc, argv)) != 0) + { + return (FAIL); + } + + /*Setup SECURITY_ATTRIBUTES structure for CreatePipe*/ + lpPipeAttributes.nLength = sizeof(lpPipeAttributes); + lpPipeAttributes.lpSecurityDescriptor = NULL; + lpPipeAttributes.bInheritHandle = TRUE; + + /*Create a Pipe*/ + bRetVal = CreatePipe(&hReadPipe, /* read handle*/ + &hWritePipe, /* write handle */ + &lpPipeAttributes,/* security attributes*/ + 0); /* pipe size*/ + if (bRetVal == FALSE) + { + Fail("ERROR:%u:Unable to create pipe\n", GetLastError()); + } + + /*Write to the write pipe handle*/ + bRetVal = WriteFile(hWritePipe, /* handle to write pipe*/ + cTestString, /* buffer to write*/ + strlen(cTestString),/* number of bytes to write*/ + &dwBytesWritten, /* number of bytes written*/ + NULL); /* overlapped buffer*/ + if (bRetVal == FALSE) + { + Trace("ERROR:%u:unable to write to write pipe handle " + "hWritePipe=0x%lx\n", GetLastError(), hWritePipe); + CloseHandle(hReadPipe); + CloseHandle(hWritePipe); + Fail(""); + } + + /*Duplicate the pipe handle*/ + if (!(DuplicateHandle(GetCurrentProcess(), /* source handle process*/ + hReadPipe, /* handle to duplicate*/ + GetCurrentProcess(), /* target process handle*/ + &hDupPipe, /* duplicate handle*/ + GENERIC_READ|GENERIC_WRITE,/* requested access*/ + FALSE, /* handle inheritance*/ + DUPLICATE_SAME_ACCESS))) /* optional actions*/ + { + Trace("ERROR:%u:Fail to create the duplicate handle" + " to hReadPipe=0x%lx", + GetLastError(), + hReadPipe); + CloseHandle(hReadPipe); + CloseHandle(hWritePipe); + Fail(""); + } + + /*Read from the duplicated handle, 256 bytes, more bytes + than actually written. This will allow us to use the + value that ReadFile returns for comparision.*/ + bRetVal = ReadFile(hDupPipe, /* handle to read pipe*/ + buffer, /* buffer to write to*/ + 256, /* number of bytes to read*/ + &dwBytesRead, /* number of bytes read*/ + NULL); /* overlapped buffer*/ + if (bRetVal == FALSE) + { + Trace("ERROR:%u:unable read from the duplicated pipe " + "hDupPipe=0x%lx\n", + GetLastError(), + hDupPipe); + CloseHandle(hReadPipe); + CloseHandle(hWritePipe); + CloseHandle(hDupPipe); + Fail(""); + } + + /*Compare what was read with what was written.*/ + if ((memcmp(cTestString, buffer, dwBytesRead)) != 0) + { + Trace("ERROR:%u: read \"%s\" expected \"%s\" \n", + GetLastError(), + buffer, + cTestString); + CloseHandle(hReadPipe); + CloseHandle(hWritePipe); + CloseHandle(hDupPipe); + Fail(""); + } + + /*Compare values returned from WriteFile and ReadFile.*/ + if (dwBytesWritten != dwBytesRead) + { + Trace("ERROR:%u: WriteFile wrote \"%s\", but ReadFile read \"%s\"," + " these should be the same\n", + GetLastError(), + buffer, + cTestString); + CloseHandle(hReadPipe); + CloseHandle(hWritePipe); + CloseHandle(hDupPipe); + Fail(""); + } + + /*Cleanup.*/ + CloseHandle(hWritePipe); + CloseHandle(hReadPipe); + CloseHandle(hDupPipe); + + PAL_Terminate(); + return (PASS); +} diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test5/testinfo.dat b/src/pal/tests/palsuite/threading/DuplicateHandle/test5/testinfo.dat new file mode 100644 index 0000000000..97e42a9787 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test5/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Threading +Function = DuplicateHandle +Name = Test for DuplicateHandle (CreatePipe) +TYPE = DEFAULT +EXE1 = test5 +Description += Tests the PAL implementation of the DuplicateHandle function, += with CreatePipe. This test will create a pipe and write to it, += then duplicate the read handle and read what was written. diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test6/CMakeLists.txt b/src/pal/tests/palsuite/threading/DuplicateHandle/test6/CMakeLists.txt new file mode 100644 index 0000000000..20f7822b1e --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test6/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test6.c +) + +add_executable(paltest_duplicatehandle_test6 + ${SOURCES} +) + +add_dependencies(paltest_duplicatehandle_test6 coreclrpal) + +target_link_libraries(paltest_duplicatehandle_test6 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test6/test6.c b/src/pal/tests/palsuite/threading/DuplicateHandle/test6/test6.c new file mode 100644 index 0000000000..026f315a44 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test6/test6.c @@ -0,0 +1,146 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test6.c (DuplicateHandle) +** +** Purpose: Tests the PAL implementation of the DuplicateHandle function, +** with CreatePipe. This test will create a pipe, then duplicate +** the write handle, write to the handle, and use the read to +** verify. +** +** Depends: WriteFile +** ReadFile +** memcmp +** CloseHandle +** +** +**===================================================================*/ + +#include <palsuite.h> + +const char* cTestString = "one fish, two fish, red fish, blue fish."; + +int __cdecl main(int argc, char **argv) +{ + HANDLE hReadPipe = NULL; + HANDLE hWritePipe = NULL; + HANDLE hDupPipe = NULL; + BOOL bRetVal = FALSE; + DWORD dwBytesWritten; + DWORD dwBytesRead; + char buffer[256]; + + SECURITY_ATTRIBUTES lpPipeAttributes; + + /*Initialize the PAL*/ + if ((PAL_Initialize(argc, argv)) != 0) + { + return (FAIL); + } + + /*Setup SECURITY_ATTRIBUTES structure for CreatePipe*/ + lpPipeAttributes.nLength = sizeof(lpPipeAttributes); + lpPipeAttributes.lpSecurityDescriptor = NULL; + lpPipeAttributes.bInheritHandle = TRUE; + + /*Create a Pipe*/ + bRetVal = CreatePipe(&hReadPipe, /* read handle*/ + &hWritePipe, /* write handle */ + &lpPipeAttributes,/* security attributes*/ + 0); /* pipe size*/ + if (bRetVal == FALSE) + { + Fail("ERROR: %ld :Unable to create pipe\n", GetLastError()); + } + + /*Duplicate the pipe handle*/ + if (!(DuplicateHandle(GetCurrentProcess(), /* source handle process*/ + hWritePipe, /* handle to duplicate*/ + GetCurrentProcess(), /* target process handle*/ + &hDupPipe, /* duplicate handle*/ + GENERIC_READ|GENERIC_WRITE,/* requested access*/ + FALSE, /* handle inheritance*/ + DUPLICATE_SAME_ACCESS))) /* optional actions*/ + { + Trace("ERROR: %ld :Fail to create the duplicate handle" + " to hWritePipe=0x%lx", + GetLastError(), + hWritePipe); + CloseHandle(hReadPipe); + CloseHandle(hWritePipe); + Fail(""); + } + + /*Write to the duplicate write pipe handle*/ + bRetVal = WriteFile(hDupPipe, /* handle to write pipe*/ + cTestString, /* buffer to write*/ + strlen(cTestString),/* number of bytes to write*/ + &dwBytesWritten, /* number of bytes written*/ + NULL); /* overlapped buffer*/ + if (bRetVal == FALSE) + { + Trace("ERROR: %ld :unable to write to duplicate write pipe handle " + "hDupPipe=0x%lx\n", + GetLastError(), + hDupPipe); + CloseHandle(hReadPipe); + CloseHandle(hWritePipe); + CloseHandle(hDupPipe); + Fail(""); + } + + /*Read from the read handle, 256 bytes, more bytes + then actually written. This will give allow us to use + the value that ReadFile returns for comparision.*/ + bRetVal = ReadFile(hReadPipe, /* handle to read pipe*/ + buffer, /* buffer to write to*/ + 256, /* number of bytes to read*/ + &dwBytesRead, /* number of bytes read*/ + NULL); /* overlapped buffer*/ + if (bRetVal == FALSE) + { + Trace("ERROR: %ld : unable read hReadPipe=0x%lx\n", + GetLastError(), hReadPipe); + CloseHandle(hReadPipe); + CloseHandle(hWritePipe); + CloseHandle(hDupPipe); + Fail(""); + } + + /*Compare what was read with what was written.*/ + if ((memcmp(cTestString, buffer, dwBytesRead)) != 0) + { + Trace("ERROR: read \"%s\" expected \"%s\" \n", + buffer, + cTestString); + CloseHandle(hReadPipe); + CloseHandle(hWritePipe); + CloseHandle(hDupPipe); + Fail(""); + } + + /*Compare values returned from WriteFile and ReadFile.*/ + if (dwBytesWritten != dwBytesRead) + { + Trace("ERROR: WriteFile wrote \"%s\", but ReadFile read \"%s\"," + " these should be the same\n", + buffer, + cTestString); + CloseHandle(hReadPipe); + CloseHandle(hWritePipe); + CloseHandle(hDupPipe); + Fail(""); + } + + /*Cleanup.*/ + CloseHandle(hReadPipe); + CloseHandle(hWritePipe); + CloseHandle(hDupPipe); + + + PAL_Terminate(); + return (PASS); +} diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test6/testinfo.dat b/src/pal/tests/palsuite/threading/DuplicateHandle/test6/testinfo.dat new file mode 100644 index 0000000000..6c49d64f89 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test6/testinfo.dat @@ -0,0 +1,15 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Threading +Function = DuplicateHandle +Name = Test for DuplicateHandle (CreatePipe) +TYPE = DEFAULT +EXE1 = test6 +Description += Tests the PAL implementation of the DuplicateHandle function, += with CreatePipe. This test will create a pipe, then duplicate += the write handle, write to the handle, and use the read to += verify. diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test7/CMakeLists.txt b/src/pal/tests/palsuite/threading/DuplicateHandle/test7/CMakeLists.txt new file mode 100644 index 0000000000..df3fdf9ae0 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test7/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test7.c +) + +add_executable(paltest_duplicatehandle_test7 + ${SOURCES} +) + +add_dependencies(paltest_duplicatehandle_test7 coreclrpal) + +target_link_libraries(paltest_duplicatehandle_test7 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test7/test7.c b/src/pal/tests/palsuite/threading/DuplicateHandle/test7/test7.c new file mode 100644 index 0000000000..0477291922 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test7/test7.c @@ -0,0 +1,149 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test7.c (DuplicateHandle) +** +** Purpose: Tests the PAL implementation of the DuplicateHandle function, +** with a handle from CreateThread. The test will create a thread +** handle and its duplicate. Then get the priorities of the threads, +** set the priority of one and the change should be seen in the +** other. +** +** +**===================================================================*/ + +#include <palsuite.h> + +DWORD PALAPI CreateTestThread(LPVOID lpParam); + +int __cdecl main(int argc, char* argv[]) +{ + HANDLE hThread; + HANDLE hDupThread; + DWORD dwThreadId = 0; + LPTHREAD_START_ROUTINE lpStartAddress = &CreateTestThread; + + int threadPriority; + int duplicatePriority; + int finalPriority; + + /* Initialize the PAL.*/ + if ((PAL_Initialize(argc, argv)) != 0) + { + return (FAIL); + } + + /* Create a thread.*/ + hThread = CreateThread(NULL, /* SD*/ + (DWORD)0, /* initial stack size*/ + lpStartAddress, /* thread function*/ + NULL, /* thread argument*/ + (DWORD)0, /* creation option*/ + &dwThreadId); /* thread identifier*/ + if (hThread == NULL) + { + Fail("ERROR:%u: Unable to create thread.\n", + GetLastError()); + } + + /* Duplicate the thread handle.*/ + if (!(DuplicateHandle(GetCurrentProcess(), /* source handle process*/ + hThread, /* handle to duplicate*/ + GetCurrentProcess(), /* target process handle*/ + &hDupThread, /* duplicate handle*/ + (DWORD)0, /* requested access*/ + FALSE, /* handle inheritance*/ + DUPLICATE_SAME_ACCESS))) /* optional actions*/ + { + Trace("ERROR: %ld :Fail to create the duplicate handle" + " to hThread=0x%lx", + GetLastError(), + hThread); + CloseHandle(hThread); + Fail(""); + } + + /* Get the priority of the thread.*/ + threadPriority = GetThreadPriority(hThread); + if(threadPriority != 0) + { + Trace("ERROR: Thread priority of hThread=0x%lx should be " + "set to normal THREAD_PRIORITY_NORMAL=%d\n", + hThread, + THREAD_PRIORITY_NORMAL); + CloseHandle(hThread); + CloseHandle(hDupThread); + Fail(""); + } + + /* Get the priority of the duplicated handle, and compare it to + * the priority of the original thread. Should be the same.*/ + duplicatePriority = GetThreadPriority(hThread); + if(duplicatePriority != threadPriority) + { + Trace("ERROR: Expected priority of hThread=0x%lx and hDupThread=0x%lx" + " to be the same. Priorities:hThread=\"%d\":hDupThread=\"%d\"\n", + hThread, + hDupThread, + threadPriority, + duplicatePriority); + CloseHandle(hThread); + CloseHandle(hDupThread); + Fail(""); + } + + /* Set the priority of the duplicate thread.*/ + if(!SetThreadPriority (hDupThread,THREAD_PRIORITY_HIGHEST)) + { + Trace("ERROR:%u: SetThreadPriority failed on hThread=0x%lx\n", + GetLastError(), + hDupThread); + CloseHandle(hThread); + CloseHandle(hDupThread); + Fail(""); + } + + /* Get the priority of the origianl thread, and + * compare it to what the duplicate was set to.*/ + finalPriority = GetThreadPriority(hThread); + if (finalPriority != THREAD_PRIORITY_HIGHEST) + { + Trace("ERROR: Expected priority of hThread=0x%lw and " + "hDupThread=0x%lw to be set the same. Priorities:" + "hThread=\"%d\":hDupThread=\"%d\".\n", + hThread, + hDupThread, + threadPriority, + duplicatePriority); + CloseHandle(hThread); + CloseHandle(hDupThread); + Fail(""); + } + + /* Wait on the original thread.*/ + if((WaitForSingleObject(hThread, 100)) != WAIT_OBJECT_0) + { + Trace("ERROR:%u: hThread=0x%lx is in a non-signalled " + "mode, yet created signalled.\n", + GetLastError(), + hThread); + CloseHandle(hThread); + CloseHandle(hDupThread); + Fail(""); + } + + /* Clean-up thread and Terminate the PAL.*/ + CloseHandle(hThread); + CloseHandle(hDupThread); + PAL_Terminate(); + return PASS; +} + +/*Thread testing function, only return '0'*/ +DWORD PALAPI CreateTestThread(LPVOID lpParam) +{ + return (DWORD)0; +} diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test7/testinfo.dat b/src/pal/tests/palsuite/threading/DuplicateHandle/test7/testinfo.dat new file mode 100644 index 0000000000..b8092d6152 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test7/testinfo.dat @@ -0,0 +1,16 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Threading +Function = DuplicateHandle +Name = Test for DuplicateHandle (CreateThread) +TYPE = DEFAULT +EXE1 = test7 +Description += Tests the PAL implementation of the DuplicateHandle function, += with a handle from CreateThread. The test will create a thread += handle and its duplicate. Then get the priorities of the threads, += set the priority of one and the change should be seen in the += other. diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test8/CMakeLists.txt b/src/pal/tests/palsuite/threading/DuplicateHandle/test8/CMakeLists.txt new file mode 100644 index 0000000000..15ec23d272 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test8/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test8.c +) + +add_executable(paltest_duplicatehandle_test8 + ${SOURCES} +) + +add_dependencies(paltest_duplicatehandle_test8 coreclrpal) + +target_link_libraries(paltest_duplicatehandle_test8 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test8/test8.c b/src/pal/tests/palsuite/threading/DuplicateHandle/test8/test8.c new file mode 100644 index 0000000000..6748c5dffd --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test8/test8.c @@ -0,0 +1,164 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test8.c (DuplicateHandle) +** +** Purpose: Tests the PAL implementation of the DuplicateHandle function, +** with a handle from GetCurrentThread. The test will create a thread +** handle, get the current thread and its duplicate. Then get the +** priorities of the threads, set the priority of one and the change +** should be seen in the other. +** +** +**===================================================================*/ + +#include <palsuite.h> + +DWORD PALAPI CreateTestThread(LPVOID lpParam); + +int __cdecl main(int argc, char* argv[]) +{ + HANDLE hThread; + HANDLE hCurrentThread; + HANDLE hDupThread; + DWORD dwThreadId = 0; + LPTHREAD_START_ROUTINE lpStartAddress = &CreateTestThread; + + int threadPriority; + int duplicatePriority; + int finalPriority; + + /* Initialize the PAL.*/ + if ((PAL_Initialize(argc, argv)) != 0) + { + return (FAIL); + } + +#if !HAVE_SCHED_OTHER_ASSIGNABLE + /* Defining thread priority for SCHED_OTHER is implementation defined. + Some platforms like NetBSD cannot reassign it as they are dynamic. + */ + printf("paltest_duplicatehandle_test8 has been disabled on this platform\n"); +#else + + /* Create a thread.*/ + hThread = CreateThread(NULL, /* SD*/ + (DWORD)0, /* initial stack size*/ + lpStartAddress, /* thread function*/ + NULL, /* thread argument*/ + (DWORD)0, /* creation option*/ + &dwThreadId); /* thread identifier*/ + if (hThread == NULL) + { + Fail("ERROR:%u: Unable to create thread.\n", + GetLastError()); + } + + /*Get a psuedo handle to the current thread.*/ + hCurrentThread = GetCurrentThread(); + + /* Duplicate the psuedo thread handle.*/ + if (!(DuplicateHandle(GetCurrentProcess(), /* source handle process*/ + hCurrentThread, /* handle to duplicate*/ + GetCurrentProcess(), /* target process handle*/ + &hDupThread, /* duplicate handle*/ + (DWORD)0, /* requested access*/ + FALSE, /* handle inheritance*/ + DUPLICATE_SAME_ACCESS))) /* optional actions*/ + { + Trace("ERROR: %ld :Fail to create the duplicate handle" + " to hThread=0x%lx", + GetLastError(), + hThread); + CloseHandle(hThread); + Fail(""); + } + + /* Get the priority of the thread.*/ + threadPriority = GetThreadPriority(hCurrentThread); + if(threadPriority != 0) + { + Trace("ERROR: Thread priority of hCurrentThread=0x%lx should be " + "set to normal THREAD_PRIORITY_NORMAL=%d\n", + hCurrentThread, + THREAD_PRIORITY_NORMAL); + CloseHandle(hThread); + CloseHandle(hDupThread); + Fail(""); + } + + /* Get the priority of the duplicated handle, and compare it to + * the priority of the original thread. Should be the same.*/ + duplicatePriority = GetThreadPriority(hCurrentThread); + if(duplicatePriority != threadPriority) + { + Trace("ERROR: Expected priority of hCurrentThread=0x%lx and " + "hDupThread=0x%lx to be the same. Priorities:hThread=" + "\"%d\":hDupThread=\"%d\"\n", + hCurrentThread, + hDupThread, + threadPriority, + duplicatePriority); + CloseHandle(hThread); + CloseHandle(hDupThread); + Fail(""); + } + + /* Set the priority of the original thread.*/ + if(!SetThreadPriority (hCurrentThread,THREAD_PRIORITY_HIGHEST)) + { + Trace("ERROR:%u: SetThreadPriority failed on hCurrentThread=0x%lx\n", + GetLastError(), + hCurrentThread); + CloseHandle(hThread); + CloseHandle(hDupThread); + Fail(""); + } + + /* Get the priority of the duplicate thread, and + * compare it to what the original was set to.*/ + finalPriority = GetThreadPriority(hDupThread); + if (finalPriority != THREAD_PRIORITY_HIGHEST) + { + Trace("ERROR: Expected priority of hCurrentThread=0x%lw and " + "hDupThread=0x%lw to be set the same. Priorities:" + "hCurrentThread=\"%d\":hDupThread=\"%d\".\n", + hCurrentThread, + hDupThread, + threadPriority, + duplicatePriority); + CloseHandle(hThread); + CloseHandle(hDupThread); + Fail(""); + } + + /* Wait on the original thread.*/ + if((WaitForSingleObject(hThread, 100)) != WAIT_OBJECT_0) + { + Trace("ERROR:%u: hCurrentThread=0x%lx is in a non-signalled " + "mode, yet created signalled.\n", + GetLastError(), + hThread); + CloseHandle(hThread); + CloseHandle(hDupThread); + Fail(""); + } + + /* Clean-up thread and Terminate the PAL.*/ + CloseHandle(hThread); + CloseHandle(hDupThread); + +#endif + + PAL_Terminate(); + return PASS; +} + +/*Thread testing function, only return '0'*/ +DWORD PALAPI CreateTestThread(LPVOID lpParam) +{ + return (DWORD)0; +} diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test8/testinfo.dat b/src/pal/tests/palsuite/threading/DuplicateHandle/test8/testinfo.dat new file mode 100644 index 0000000000..ae1353af18 --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test8/testinfo.dat @@ -0,0 +1,16 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Threading +Function = DuplicateHandle +Name = Test for DuplicateHandle (GetCurrentThread) +TYPE = DEFAULT +EXE1 = test8 +Description +=Tests the PAL implementation of the DuplicateHandle function, +=with a handle from GetCurrentThread. The test will create a thread +=handle, get the current thread and its duplicate. Then get the +=priorities of the threads, set the priority of one and the change +=should be seen in the other. diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test9/CMakeLists.txt b/src/pal/tests/palsuite/threading/DuplicateHandle/test9/CMakeLists.txt new file mode 100644 index 0000000000..e4442e327c --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test9/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test9.c +) + +add_executable(paltest_duplicatehandle_test9 + ${SOURCES} +) + +add_dependencies(paltest_duplicatehandle_test9 coreclrpal) + +target_link_libraries(paltest_duplicatehandle_test9 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test9/test9.c b/src/pal/tests/palsuite/threading/DuplicateHandle/test9/test9.c new file mode 100644 index 0000000000..f15871c57d --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test9/test9.c @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test9.c (DuplicateHandle) +** +** Purpose: Tests the PAL implementation of the DuplicateHandle function, +** with a handle from GetCurrentProcess. The test will create a +** process, duplicate it, then using ReadProcessMemory will +** read from the memory location of the CreateProcess process +** memory and the DuplicateHandle process memory. If the +** duplication is correct the memory will be the same for both. +** +** +**===================================================================*/ + +#include <palsuite.h> + +int __cdecl main(int argc, char* argv[]) +{ + HANDLE hProcess; + HANDLE hDupProcess; + char lpBuffer[64]; + char lpDupBuffer[64]; + SIZE_T lpNumberOfBytesRead; + SIZE_T lpDupNumberOfBytesRead; + char lpTestBuffer[] = "abcdefghijklmnopqrstuvwxyz"; + + /* Initalize the PAL. + */ + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + /* Initalize the buffers. + */ + ZeroMemory( &lpBuffer, sizeof(lpBuffer) ); + ZeroMemory( &lpDupBuffer, sizeof(lpDupBuffer) ); + + /* Get current proces, this will be duplicated. + */ + hProcess = GetCurrentProcess(); + if(hProcess == NULL) + { + Fail("ERROR: Unable to get the current process\n"); + } + + /* Duplicate the current process handle. + */ + if (!(DuplicateHandle(GetCurrentProcess(), /* source handle process*/ + hProcess, /* handle to duplicate*/ + GetCurrentProcess(), /* target process handle*/ + &hDupProcess, /* duplicate handle*/ + (DWORD)0, /* requested access*/ + FALSE, /* handle inheritance*/ + DUPLICATE_SAME_ACCESS))) /* optional actions*/ + { + Trace("ERROR:%u: Failed to create the duplicate handle" + " to hProcess=0x%lx", + GetLastError(), + hProcess); + CloseHandle(hProcess); + Fail(""); + } + + /* Get memory read of the current process. + */ + if ((ReadProcessMemory(hDupProcess, &lpTestBuffer, + lpDupBuffer, sizeof(lpDupBuffer), &lpDupNumberOfBytesRead)) == 0) + { + Trace("ERROR:%u: Unable to read the process memory of " + "hDupProcess=0x%lx.\n", + GetLastError(), + hDupProcess); + CloseHandle(hProcess); + CloseHandle(hDupProcess); + Fail(""); + } + + /* Get read memory of the created process. + */ + if ((ReadProcessMemory(hProcess, &lpTestBuffer, + lpBuffer, sizeof(lpBuffer), &lpNumberOfBytesRead)) == 0) + { + Trace("ERROR:%u: Unable to read the process memory of " + "hProcess=0x%lx.\n", + GetLastError(), + hProcess); + CloseHandle(hProcess); + CloseHandle(hDupProcess); + Fail(""); + } + + /* Compare the number of bytes that were read by each + * ReadProcessMemory.*/ + if (lpDupNumberOfBytesRead != lpNumberOfBytesRead) + { + Trace("ERROR: ReadProcessMemory read different numbers of bytes " + "from duplicate process handles.\n"); + CloseHandle(hProcess); + CloseHandle(hDupProcess); + Fail(""); + } + + /* Compare the two buffers to make sure they are equal. + */ + if ((strcmp(lpBuffer, lpDupBuffer)) != 0) + { + Trace("ERROR: ReadProcessMemory read different numbers of bytes " + "from duplicate process handles. hProcess read \"%s\" and " + "hDupProcess read \"%s\"\n", + lpBuffer, + lpDupBuffer); + CloseHandle(hProcess); + CloseHandle(hDupProcess); + Fail(""); + } + + /* Clean-up thread and Terminate the PAL.*/ + CloseHandle(hProcess); + CloseHandle(hDupProcess); + PAL_Terminate(); + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/DuplicateHandle/test9/testinfo.dat b/src/pal/tests/palsuite/threading/DuplicateHandle/test9/testinfo.dat new file mode 100644 index 0000000000..c7122908fd --- /dev/null +++ b/src/pal/tests/palsuite/threading/DuplicateHandle/test9/testinfo.dat @@ -0,0 +1,17 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Threading +Function = DuplicateHandle +Name = Test for DuplicateHandle (GetCurrentProcess) +TYPE = DEFAULT +EXE1 = test9 +Description += Tests the PAL implementation of the DuplicateHandle function, += with a handle from GetCurrentProcess. The test will create a += process, duplicate it, then using ReadProcessMemory will += read from the memory location of the CreateProcess process += memory and the DuplicateHandle process memory. If the += duplication is correct the memory will be the same for both. diff --git a/src/pal/tests/palsuite/threading/ExitProcess/CMakeLists.txt b/src/pal/tests/palsuite/threading/ExitProcess/CMakeLists.txt new file mode 100644 index 0000000000..1962ade358 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitProcess/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) +add_subdirectory(test3) + diff --git a/src/pal/tests/palsuite/threading/ExitProcess/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/ExitProcess/test1/CMakeLists.txt new file mode 100644 index 0000000000..decddb8512 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitProcess/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + ExitProcess.c +) + +add_executable(paltest_exitprocess_test1 + ${SOURCES} +) + +add_dependencies(paltest_exitprocess_test1 coreclrpal) + +target_link_libraries(paltest_exitprocess_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/ExitProcess/test1/ExitProcess.c b/src/pal/tests/palsuite/threading/ExitProcess/test1/ExitProcess.c new file mode 100644 index 0000000000..2b089a0b83 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitProcess/test1/ExitProcess.c @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: exitprocess/test1/exitprocess.c +** +** Purpose: Test to ensure ExitProcess returns the argument given +** to it. +** +** +**=========================================================*/ + +#include <palsuite.h> + +int __cdecl main( int argc, char **argv ) + +{ + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + ExitProcess(PASS); + + Fail ("ExitProcess(0) failed to exit.\n Test Failed.\n"); + + return ( FAIL); + +} diff --git a/src/pal/tests/palsuite/threading/ExitProcess/test1/testinfo.dat b/src/pal/tests/palsuite/threading/ExitProcess/test1/testinfo.dat new file mode 100644 index 0000000000..d8b85abad6 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitProcess/test1/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = ExitProcess +Name = Positive Test for ExitProcess +TYPE = DEFAULT +EXE1 = exitprocess +Description += Test the ExitProcess function. The test runs the ExitProcess function += with the TEST_RETURN enumeration value PASS diff --git a/src/pal/tests/palsuite/threading/ExitProcess/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/ExitProcess/test2/CMakeLists.txt new file mode 100644 index 0000000000..881c7d0eee --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitProcess/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test2.c +) + +add_executable(paltest_exitprocess_test2 + ${SOURCES} +) + +add_dependencies(paltest_exitprocess_test2 coreclrpal) + +target_link_libraries(paltest_exitprocess_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/ExitProcess/test2/test2.c b/src/pal/tests/palsuite/threading/ExitProcess/test2/test2.c new file mode 100644 index 0000000000..8023ad7bab --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitProcess/test2/test2.c @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test2.c +** +** Purpose: Positive test for ExitProcess. +** +** Dependencies: none +** + +** +**===========================================================================*/ +#include <palsuite.h> + + + +int __cdecl main( int argc, char **argv ) + +{ + /* call ExitProcess() -- should work without PAL_Initialize() */ + ExitProcess(PASS); + + + /* return failure if we reach here -- note no attempt at */ + /* meaningful output because we never called PAL_Initialize(). */ + return FAIL; +} diff --git a/src/pal/tests/palsuite/threading/ExitProcess/test2/testinfo.dat b/src/pal/tests/palsuite/threading/ExitProcess/test2/testinfo.dat new file mode 100644 index 0000000000..0aa07eb15a --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitProcess/test2/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = ExitProcess +Name = Positive test for ExitProcess +TYPE = DEFAULT +EXE1 = test2 +Description += Test to ensure proper operation of the ExitProcess() += API by ensuring it works before PAL_Initialize() is += called. diff --git a/src/pal/tests/palsuite/threading/ExitProcess/test3/CMakeLists.txt b/src/pal/tests/palsuite/threading/ExitProcess/test3/CMakeLists.txt new file mode 100644 index 0000000000..638408e986 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitProcess/test3/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test3.c +) + +add_executable(paltest_exitprocess_test3 + ${SOURCES} +) + +add_dependencies(paltest_exitprocess_test3 coreclrpal) + +target_link_libraries(paltest_exitprocess_test3 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/ExitProcess/test3/test3.c b/src/pal/tests/palsuite/threading/ExitProcess/test3/test3.c new file mode 100644 index 0000000000..aea485e5e3 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitProcess/test3/test3.c @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test3.c +** +** Purpose: Positive test for ExitProcess. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** + +** +**===========================================================================*/ +#include <palsuite.h> + + + +int __cdecl main( int argc, char **argv ) + +{ + /* initialize the PAL */ + if( PAL_Initialize(argc, argv) != 0 ) + { + return( FAIL ); + } + + /* terminate the PAL */ + PAL_Terminate(); + + /* call ExitProcess() -- should work after PAL_Terminate() */ + ExitProcess( PASS ); + + + /* return failure if we reach here -- note no attempt at */ + /* meaningful output because we've called PAL_Terminte(). */ + return FAIL; +} diff --git a/src/pal/tests/palsuite/threading/ExitProcess/test3/testinfo.dat b/src/pal/tests/palsuite/threading/ExitProcess/test3/testinfo.dat new file mode 100644 index 0000000000..c857d885cc --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitProcess/test3/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = ExitProcess +Name = Positive test for ExitProcess +TYPE = DEFAULT +EXE1 = test3 +Description += Test to ensure proper operation of the ExitProcess() += API by ensuring it works after PAL_Terminate() is += called. diff --git a/src/pal/tests/palsuite/threading/ExitThread/CMakeLists.txt b/src/pal/tests/palsuite/threading/ExitThread/CMakeLists.txt new file mode 100644 index 0000000000..ef14ea5352 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitThread/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) + diff --git a/src/pal/tests/palsuite/threading/ExitThread/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/ExitThread/test1/CMakeLists.txt new file mode 100644 index 0000000000..15e0c1b62d --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitThread/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_exitthread_test1 + ${SOURCES} +) + +add_dependencies(paltest_exitthread_test1 coreclrpal) + +target_link_libraries(paltest_exitthread_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/ExitThread/test1/test1.c b/src/pal/tests/palsuite/threading/ExitThread/test1/test1.c new file mode 100644 index 0000000000..2963745bef --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitThread/test1/test1.c @@ -0,0 +1,114 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test1.c +** +** Purpose: Test for ExitThread. Create a thread and then call +** exit thread within the threading function. Ensure that it exits +** immediatly. +** +** +**=========================================================*/ + +#include <palsuite.h> + +DWORD dwExitThreadTestParameter = 0; + +DWORD PALAPI ExitThreadTestThread( LPVOID lpParameter) +{ + DWORD dwRet = 0; + + /* Save parameter for test */ + dwExitThreadTestParameter = (DWORD)lpParameter; + + /* Call the ExitThread function */ + ExitThread(dwRet); + + /* If we didn't exit, get caught in this loop. But, the + program will exit. + */ + while (!dwRet) + { + Fail("ERROR: Entered an infinite loop because ExitThread " + "failed to exit from the thread. Forcing exit from " + "the test now."); + } + + return dwRet; +} + +BOOL ExitThreadTest() +{ + BOOL bRet = FALSE; + DWORD dwRet = 0; + + LPSECURITY_ATTRIBUTES lpThreadAttributes = NULL; + DWORD dwStackSize = 0; + LPTHREAD_START_ROUTINE lpStartAddress = &ExitThreadTestThread; + LPVOID lpParameter = lpStartAddress; + DWORD dwCreationFlags = 0; //run immediately + DWORD dwThreadId = 0; + + HANDLE hThread = 0; + + dwExitThreadTestParameter = 0; + + /* Create a Thread. We'll need this to test that we're able + to exit the thread. + */ + hThread = CreateThread( lpThreadAttributes, + dwStackSize, lpStartAddress, lpParameter, + dwCreationFlags, &dwThreadId ); + + if (hThread != INVALID_HANDLE_VALUE) + { + dwRet = WaitForSingleObject(hThread,INFINITE); + + if (dwRet != WAIT_OBJECT_0) + { + Trace("ExitThreadTest:WaitForSingleObject failed " + "(%x)\n",GetLastError()); + } + else + { + /* Check to ensure that the parameter set in the Thread + function is correct. + */ + if (dwExitThreadTestParameter != (DWORD)lpParameter) + { + Trace("ERROR: The paramater passed should have been " + "%d but turned up as %d.", + dwExitThreadTestParameter, lpParameter); + } + else + { + bRet = TRUE; + } + } + } + else + { + Trace("ExitThreadTest:CreateThread failed (%x)\n",GetLastError()); + } + + return bRet; +} + +int __cdecl main(int argc, char **argv) +{ + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + if(!ExitThreadTest()) + { + Fail ("Test failed\n"); + } + + PAL_Terminate(); + return ( PASS ); +} diff --git a/src/pal/tests/palsuite/threading/ExitThread/test1/testinfo.dat b/src/pal/tests/palsuite/threading/ExitThread/test1/testinfo.dat new file mode 100644 index 0000000000..a526f8e127 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitThread/test1/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = ExitThread +Name = Positive Test for ExitThread +TYPE = DEFAULT +EXE1 = test1 +Description += Test for ExitThread. Create a thread and then call += exit thread within the threading function. Ensure that it exits += immediatly. diff --git a/src/pal/tests/palsuite/threading/ExitThread/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/ExitThread/test2/CMakeLists.txt new file mode 100644 index 0000000000..aa05382feb --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitThread/test2/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(TESTSOURCES + test2.c +) + +add_executable(paltest_exitthread_test2 + ${TESTSOURCES} +) + +add_dependencies(paltest_exitthread_test2 coreclrpal) + +target_link_libraries(paltest_exitthread_test2 + pthread + m + coreclrpal +) + + +set(HELPERSOURCES + childprocess.c +) + +add_executable(paltest_exitthread_test2_child + ${HELPERSOURCES} +) + +add_dependencies(paltest_exitthread_test2_child coreclrpal) + +target_link_libraries(paltest_exitthread_test2_child + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/ExitThread/test2/childprocess.c b/src/pal/tests/palsuite/threading/ExitThread/test2/childprocess.c new file mode 100644 index 0000000000..7fbe208f91 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitThread/test2/childprocess.c @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: childprocess.c +** +** Purpose: Test to ensure ExitThread returns the right +** value when shutting down the last thread of a process. +** All this program does is call ExitThread() with a predefined +** value. +** +** Dependencies: none +** + +** +**=========================================================*/ + +#include <palsuite.h> +#include "myexitcode.h" + +int __cdecl main( int argc, char **argv ) +{ + /* initialize the PAL */ + if( PAL_Initialize(argc, argv) != 0 ) + { + return( FAIL ); + } + + /* exit the current thread with a magic test value -- it should */ + /* terminate the process and return that test value from this */ + /* program. */ + ExitThread( TEST_EXIT_CODE ); + + /* technically we should never get here */ + PAL_Terminate(); + + /* return failure */ + return FAIL; +} diff --git a/src/pal/tests/palsuite/threading/ExitThread/test2/myexitcode.h b/src/pal/tests/palsuite/threading/ExitThread/test2/myexitcode.h new file mode 100644 index 0000000000..566becb9a0 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitThread/test2/myexitcode.h @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================ +** +** Source: myexitcode.h +** +** Purpose: Define an exit code. +** +** +**==========================================================================*/ + +#define TEST_EXIT_CODE 316 diff --git a/src/pal/tests/palsuite/threading/ExitThread/test2/test2.c b/src/pal/tests/palsuite/threading/ExitThread/test2/test2.c new file mode 100644 index 0000000000..c31af8a079 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitThread/test2/test2.c @@ -0,0 +1,168 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test2.c +** +** Purpose: Test to ensure ExitThread() called from the last thread of +** a process shuts down that process and returns the proper +** exit code as specified in the ExitThread() call. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** Fail +** ZeroMemory +** GetCurrentDirectoryW +** CreateProcessW +** WaitForSingleObject +** GetLastError +** strlen +** strncpy +** + +** +**===========================================================================*/ +#include <palsuite.h> +#include "myexitcode.h" + + +static const char* rgchPathDelim = "\\"; + + +int +mkAbsoluteFilename( LPSTR dirName, + DWORD dwDirLength, + LPCSTR fileName, + DWORD dwFileLength, + LPSTR absPathName ) +{ + DWORD sizeDN, sizeFN, sizeAPN; + + sizeDN = strlen( dirName ); + sizeFN = strlen( fileName ); + sizeAPN = (sizeDN + 1 + sizeFN + 1); + + /* ensure ((dirName + DELIM + fileName + \0) =< _MAX_PATH ) */ + if( sizeAPN > _MAX_PATH ) + { + return ( 0 ); + } + + strncpy( absPathName, dirName, dwDirLength +1 ); + strncpy( absPathName, rgchPathDelim, 2 ); + strncpy( absPathName, fileName, dwFileLength +1 ); + + return (sizeAPN); + +} + + +int __cdecl main( int argc, char **argv ) + +{ + const char* rgchChildFile = "childprocess"; + + STARTUPINFO si; + PROCESS_INFORMATION pi; + + DWORD dwError; + DWORD dwExitCode; + DWORD dwFileLength; + DWORD dwDirLength; + DWORD dwSize; + DWORD dwExpected = TEST_EXIT_CODE; + + char rgchDirName[_MAX_DIR]; + char absPathBuf[_MAX_PATH]; + char* rgchAbsPathName; + + /* initialize the PAL */ + if( PAL_Initialize(argc, argv) != 0 ) + { + return( FAIL ); + } + + /* zero our process and startup info structures */ + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof( si ); + ZeroMemory( &pi, sizeof(pi) ); + + /* build the absolute path to the child process */ + rgchAbsPathName = &absPathBuf[0]; + dwFileLength = strlen( rgchChildFile ); + + dwDirLength = GetCurrentDirectory( _MAX_PATH, rgchDirName ); + if( dwDirLength == 0 ) + { + dwError = GetLastError(); + Fail( "GetCurrentDirectory call failed with error code %d\n", + dwError ); + } + + dwSize = mkAbsoluteFilename( rgchDirName, + dwDirLength, + rgchChildFile, + dwFileLength, + rgchAbsPathName ); + if( dwSize == 0 ) + { + Fail( "Palsuite Code: mkAbsoluteFilename() call failed. Could ", + "not build absolute path name to file\n. Exiting.\n" ); + } + + /* launch the child process */ + if( !CreateProcess( NULL, /* module name to execute */ + rgchAbsPathName, /* command line */ + NULL, /* process handle not */ + /* inheritable */ + NULL, /* thread handle not */ + /* inheritable */ + FALSE, /* handle inheritance */ + CREATE_NEW_CONSOLE, /* dwCreationFlags */ + NULL, /* use parent's environment */ + NULL, /* use parent's starting */ + /* directory */ + &si, /* startup info struct */ + &pi ) /* process info struct */ + ) + { + dwError = GetLastError(); + Fail( "CreateProcess call failed with error code %d\n", + dwError ); + } + + /* wait for the child process to complete */ + WaitForSingleObject ( pi.hProcess, INFINITE ); + + /* check the exit code from the process */ + if( ! GetExitCodeProcess( pi.hProcess, &dwExitCode ) ) + { + dwError = GetLastError(); + CloseHandle ( pi.hProcess ); + CloseHandle ( pi.hThread ); + Fail( "GetExitCodeProcess call failed with error code %d\n", + dwError ); + } + + /* close process and thread handle */ + CloseHandle ( pi.hProcess ); + CloseHandle ( pi.hThread ); + + /* check for the expected exit code */ + /* exit code for some systems is as small as a char, so that's all */ + /* we'll compare for checking success */ + if( LOBYTE(LOWORD(dwExitCode)) != LOBYTE(LOWORD(dwExpected)) ) + { + Fail( "GetExitCodeProcess returned an incorrect exit code %d, " + "expected value is %d\n", + LOWORD(dwExitCode), dwExpected ); + } + + /* terminate the PAL */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/ExitThread/test2/testinfo.dat b/src/pal/tests/palsuite/threading/ExitThread/test2/testinfo.dat new file mode 100644 index 0000000000..4b5bdc2ac6 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitThread/test2/testinfo.dat @@ -0,0 +1,19 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = ExitThread +Name = Test for ExitThread +TYPE = DEFAULT +EXE1 = test2 +EXE2 = childprocess +Description += Test to ensure proper operation of the ExitThread += API. This test launches a simple child process that exits += by calling ExitThread() with a known value, and checks += that the correct value is returned to the parent process. += This verifies that when the last thread of a process exits += via ExitThread, the process exits with the proper return += code. diff --git a/src/pal/tests/palsuite/threading/ExitThread/test3/dllmain.c b/src/pal/tests/palsuite/threading/ExitThread/test3/dllmain.c new file mode 100644 index 0000000000..923650d1d3 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitThread/test3/dllmain.c @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: dllmain.c +** +** Purpose: Test to ensure DllMain() is called with THREAD_DETACH +** when a thread in the application calls ExitThread(). +** +** Dependencies: none +** + +** +**===========================================================================*/ + +#include <palsuite.h> + +/* count of the number of times DllMain() was called with THREAD_DETACH */ +static int g_detachCount = 0; + + +/* standard DllMain() */ +BOOL PALAPI DllMain(HINSTANCE hinstDLL, DWORD reason, LPVOID lpvReserved) +{ + switch( reason ) + { + case DLL_PROCESS_ATTACH: + { + break; + } + + case DLL_PROCESS_DETACH: + { + break; + } + + case DLL_THREAD_ATTACH: + break; + + case DLL_THREAD_DETACH: + /* increment g_detachCount */ + g_detachCount++; + break; + } + return TRUE; +} + +#ifdef WIN32 +BOOL __stdcall _DllMainCRTStartup(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + return DllMain(hinstDLL, fdwReason, lpvReserved); +} +#endif + + +/* function to return the current detach count */ +#ifdef WIN32 +__declspec(dllexport) +#endif +int PALAPI GetDetachCount( void ) +{ + return g_detachCount; +} diff --git a/src/pal/tests/palsuite/threading/ExitThread/test3/test3.c b/src/pal/tests/palsuite/threading/ExitThread/test3/test3.c new file mode 100644 index 0000000000..8a71c7cc99 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitThread/test3/test3.c @@ -0,0 +1,162 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test3.c +** +** Purpose: Test to ensure ExitThread() results in any loaded dynamic +** libraries having their entry point called with a THREAD_DETACH +** notification. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** Fail +** GetCurrentDirectoryW +** CreateThread +** ResumeThread +** LoadLibrary +** FreeLibrary +** GetProcAddress +** WaitForSingleObject +** GetLastError +** strlen +** strncpy +** + +** +**===========================================================================*/ +#include <palsuite.h> + +/* SHLEXT is defined only for Unix variants */ + +#if defined(SHLEXT) +#define rgchLibraryFile "dllmain"SHLEXT +#define szFunction "GetDetachCount" +#else +#define rgchLibraryFile "dllmain" +#define szFunction "_GetDetachCount@0" +#endif + +/* define our test function type */ +typedef int ( PALAPI *LPTESTFUNC )( void ); + + +/** + * ThreadFunc + * + * Dummy thread function for causing DLL thread notifications. + */ +DWORD PALAPI ThreadFunc( LPVOID param ) +{ + /* simulate some brief "work" */ + int i; + for( i=0; i<100000; i++ ) + ; + + ExitThread( 0 ); + return (0); +} + + +/* main program entry point */ +int __cdecl main( int argc, char **argv ) + +{ + /* local variables */ + + HANDLE hLib = NULL; + LPTESTFUNC pFunc; + int detachCount1 = 0; + int detachCount2 = 0; + + HANDLE hThread = NULL; + DWORD IDThread; + + /* initialize the PAL */ + if( PAL_Initialize(argc, argv) != 0 ) + { + return( FAIL ); + } + + /* Load the test library */ + hLib = LoadLibrary( rgchLibraryFile ); + if(hLib == NULL) + { + Fail("ERROR: Unable to load library %s\n", rgchLibraryFile ); + } + + + /* Get the address of our test function in the dll */ + pFunc = (LPTESTFUNC)GetProcAddress( hLib, szFunction ); + if( pFunc == NULL ) + { + Trace( "ERROR:%lu%:Unable to load function \"%s\" library \"%s\"\n", + GetLastError(), + szFunction, + rgchLibraryFile ); + if( ! FreeLibrary( hLib ) ) { + Trace( "FreeLibrary() failed with error code %lu\n", + GetLastError() ); + } + Fail( "Exiting\n" ); + } + + /* Execute the test function to get the detach count */ + detachCount1 = pFunc(); + + /* run another dummy thread to cause notification of the library */ + hThread = CreateThread( NULL, /* no security attributes */ + 0, /* use default stack size */ + (LPTHREAD_START_ROUTINE) ThreadFunc, /* thread function */ + (LPVOID) NULL, /* pass thread index as */ + /* function argument */ + CREATE_SUSPENDED, /* create suspended */ + &IDThread ); /* returns thread id */ + + /* Check the return value for success. */ + if( hThread == NULL ) + { + /* error creating thread */ + Trace( "Unexpected CreateThread error %d\n", + GetLastError() ); + if( ! FreeLibrary( hLib ) ) { + Trace( "FreeLibrary() failed with error code %lu\n", + GetLastError() ); + } + Fail( "Exiting\n" ); + } + + /* Resume the suspended thread */ + ResumeThread( hThread ); + + /* wait for the thread to complete */ + WaitForSingleObject( hThread, INFINITE ); + + /* Execute the test function to get the new detach count */ + detachCount2 = pFunc(); + + /* Unload the test library */ + if( !FreeLibrary( hLib ) ) + { + Fail( "ERROR:%u: Unable to free library \"%s\"\n", + GetLastError(), + rgchLibraryFile ); + } + + /* validate the result */ + if( detachCount2 != (detachCount1 + 1) ) + { + Fail( "FAIL: unexpected DLL detach count %d, expected %d\n", + detachCount2, + (detachCount1 + 1) ); + } + + + /* terminate the PAL */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/ExitThread/test3/testinfo.dat b/src/pal/tests/palsuite/threading/ExitThread/test3/testinfo.dat new file mode 100644 index 0000000000..1c9e8c7567 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ExitThread/test3/testinfo.dat @@ -0,0 +1,16 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = ExitThread +Name = Test for ExitThread +TYPE = DEFAULT +EXE1 = test3 +LIB1 = dllmain +Description += Test to ensure proper operation of the ExitThread += API. This tests to make sure ExitThread() results += in any loaded dynamic libraries having their entry += point called with a THREAD_DETACH notification. diff --git a/src/pal/tests/palsuite/threading/GetCurrentProcess/CMakeLists.txt b/src/pal/tests/palsuite/threading/GetCurrentProcess/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentProcess/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/threading/GetCurrentProcess/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/GetCurrentProcess/test1/CMakeLists.txt new file mode 100644 index 0000000000..44105348be --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentProcess/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + process.c +) + +add_executable(paltest_getcurrentprocess_test1 + ${SOURCES} +) + +add_dependencies(paltest_getcurrentprocess_test1 coreclrpal) + +target_link_libraries(paltest_getcurrentprocess_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/GetCurrentProcess/test1/process.c b/src/pal/tests/palsuite/threading/GetCurrentProcess/test1/process.c new file mode 100644 index 0000000000..17d9af6282 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentProcess/test1/process.c @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: GetCurrentProcess/test1/process.c +** +** Purpose: Test for to see if the process GetCurrentProcess +** returns a handle to the current process or not. +** +** Dependencies: TerminateProcess +** +** +**=========================================================*/ + +#include <palsuite.h> + +INT __cdecl main( int argc, char **argv ) +{ + + HANDLE hProcess; + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + hProcess = GetCurrentProcess(); + Trace ("Testing the handle returned by GetCurrentProcess\n"); + if ( 0 == ( TerminateProcess ( hProcess, PASS ) ) ) + { + Fail ("Testing GetCurrentProcess, the TerminateProcess function " + "failed.\n"); + } + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/GetCurrentProcess/test1/testinfo.dat b/src/pal/tests/palsuite/threading/GetCurrentProcess/test1/testinfo.dat new file mode 100644 index 0000000000..8eb2759fb9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentProcess/test1/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = GetCurrentProcess +Name = Positive Test for GetCurrentProcess +TYPE = DEFAULT +EXE1 = process +Description += Test to see if the function GetCurrentProcess returns a handle to the += current process or not. diff --git a/src/pal/tests/palsuite/threading/GetCurrentProcessId/CMakeLists.txt b/src/pal/tests/palsuite/threading/GetCurrentProcessId/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentProcessId/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/threading/GetCurrentProcessId/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/GetCurrentProcessId/test1/CMakeLists.txt new file mode 100644 index 0000000000..e5c31e6af2 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentProcessId/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + processId.c +) + +add_executable(paltest_getcurrentprocessid_test1 + ${SOURCES} +) + +add_dependencies(paltest_getcurrentprocessid_test1 coreclrpal) + +target_link_libraries(paltest_getcurrentprocessid_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/GetCurrentProcessId/test1/processId.c b/src/pal/tests/palsuite/threading/GetCurrentProcessId/test1/processId.c new file mode 100644 index 0000000000..cc689b3f8b --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentProcessId/test1/processId.c @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: getcurrentprocessid/test1/processid.c +** +** Purpose: Test to ensure GetCurrentProcessId returns the current +** process id number. This test compares the result of +** GetCurrentProcessId to getpid. +** +** +**=========================================================*/ + +#include <palsuite.h> + +INT __cdecl main( int argc, char **argv ) +{ + + DWORD dwProcessId; + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + dwProcessId = GetCurrentProcessId(); + + if ( 0 >= dwProcessId ) + { + Fail ("%s has dwProcessId has id value %d\n", argv[0], + dwProcessId ); + } + Trace ("%s has dwProcessId %d\nPassing test as dwProcessId is > 0\n" + , argv[0], dwProcessId); + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/GetCurrentProcessId/test1/testinfo.dat b/src/pal/tests/palsuite/threading/GetCurrentProcessId/test1/testinfo.dat new file mode 100644 index 0000000000..db615c0bf9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentProcessId/test1/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = GetCurrentProcessId +Name = Positive Test for GetCurrentProcessId +TYPE = DEFAULT +EXE1 = processid +Description += Test to ensure GetCurrentProcessId returns the current process id number. += This test compares the result of GetCurrentProcessId to getpid. diff --git a/src/pal/tests/palsuite/threading/GetCurrentThread/CMakeLists.txt b/src/pal/tests/palsuite/threading/GetCurrentThread/CMakeLists.txt new file mode 100644 index 0000000000..ef14ea5352 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentThread/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) + diff --git a/src/pal/tests/palsuite/threading/GetCurrentThread/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/GetCurrentThread/test1/CMakeLists.txt new file mode 100644 index 0000000000..84cbccbca0 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentThread/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + thread.c +) + +add_executable(paltest_getcurrentthread_test1 + ${SOURCES} +) + +add_dependencies(paltest_getcurrentthread_test1 coreclrpal) + +target_link_libraries(paltest_getcurrentthread_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/GetCurrentThread/test1/testinfo.dat b/src/pal/tests/palsuite/threading/GetCurrentThread/test1/testinfo.dat new file mode 100644 index 0000000000..29c9767ed0 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentThread/test1/testinfo.dat @@ -0,0 +1,12 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = GetCurrentThread +Name = Positive Test for GetCurrentThread +TYPE = DEFAULT +EXE1 = thread +Description += Test to ensure GetCurrentThread returns a handle to the current thread. diff --git a/src/pal/tests/palsuite/threading/GetCurrentThread/test1/thread.c b/src/pal/tests/palsuite/threading/GetCurrentThread/test1/thread.c new file mode 100644 index 0000000000..b2bb97fd67 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentThread/test1/thread.c @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: GetCurrentThread/test1/thread.c +** +** Purpose: Test to ensure GetCurrentThread returns a handle to +** the current thread. +** +** Dependencies: GetThreadPriority +** SetThreadPriority +** Fail +** Trace +** + +** +**=========================================================*/ + +#include <palsuite.h> + +int __cdecl main( int argc, char **argv ) +{ + + HANDLE hThread; + int nPriority; + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + +#if !HAVE_SCHED_OTHER_ASSIGNABLE + /* Defining thread priority for SCHED_OTHER is implementation defined. + Some platforms like NetBSD cannot reassign it as they are dynamic. + */ + printf("paltest_getcurrentthread_test1 has been disabled on this platform\n"); +#else + hThread = GetCurrentThread(); + + nPriority = GetThreadPriority(hThread); + + if ( THREAD_PRIORITY_NORMAL != nPriority ) + { + if ( THREAD_PRIORITY_ERROR_RETURN == nPriority ) + { + Fail ("GetThreadPriority function call failed for %s\n" + "GetLastError returned %d\n", argv[0], GetLastError()); + } + else + { + Fail ("GetThreadPriority function call failed for %s\n" + "The priority returned was %d\n", argv[0], nPriority); + } + } + else + { + nPriority = 0; + + if (0 == SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST)) + { + Fail ("Unable to set thread priority. Either handle doesn't" + " point to current thread \nor SetThreadPriority " + "function failed. Failing test.\n"); + } + + nPriority = GetThreadPriority(hThread); + + if ( THREAD_PRIORITY_ERROR_RETURN == nPriority ) + { + Fail ("GetThreadPriority function call failed for %s\n" + "GetLastError returned %d\n", argv[0], GetLastError()); + } + else if ( THREAD_PRIORITY_HIGHEST == nPriority ) + { + Trace ("GetCurrentThread returns handle to the current " + "thread.\n"); + exit ( PASS ); + } + else + { + Fail ("Unable to set thread priority. Either handle doesn't" + " point to current thread \nor SetThreadPriority " + "function failed. Failing test.\n"); + } + } +#endif + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/GetCurrentThread/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/GetCurrentThread/test2/CMakeLists.txt new file mode 100644 index 0000000000..3fc3c3e7c6 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentThread/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test2.c +) + +add_executable(paltest_getcurrentthread_test2 + ${SOURCES} +) + +add_dependencies(paltest_getcurrentthread_test2 coreclrpal) + +target_link_libraries(paltest_getcurrentthread_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/GetCurrentThread/test2/test2.c b/src/pal/tests/palsuite/threading/GetCurrentThread/test2/test2.c new file mode 100644 index 0000000000..beeb5ec241 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentThread/test2/test2.c @@ -0,0 +1,144 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test2.c +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** Fail +** CreateThread +** SetThreadPriority +** GetThreadPriority +** ResumeThread +** WaitForSingleObject +** GetLastError +** +** Purpose: +** +** Test to ensure proper operation of the GetCurrentThread() +** API. The test launches a thread in suspended mode, and sets +** its priority to a non-default value using the handle returned +** by CreateThread(). The new thread calls GetCurrentThred() to +** retrieve a handle to itself, and calls GetThreadPriority() +** to verify that its priority matches what it was set to on +** the main execution thread. +** + +** +**===========================================================================*/ +#include <palsuite.h> + + +/* we store the return code from the child thread here because */ +/* we're missing the GetExitCodeThread() API */ + +static int g_priority = 0; + +/** + * ThreadFunc + * + * Thread function that calls GetCurrentThread() to get a pseudo-handle + * to itself, then checks its priority and exits with that value. + */ +DWORD PALAPI ThreadFunc( LPVOID param ) +{ + int priority; + HANDLE hThread; + + /* call GetCurrentThread() to get a pseudo-handle to */ + /* the current thread */ + hThread = GetCurrentThread(); + if( hThread == NULL ) + { + Fail( "GetCurrentThread() call failed\n" ); + } + + + /* get the current thread priority */ + priority = GetThreadPriority( hThread ); + if( priority == THREAD_PRIORITY_ERROR_RETURN ) + { + /* GetThreadPriority call failed */ + Fail( "ERROR:%lu:GetThreadPriority() call failed\n", GetLastError() ); + } + + /* store this globally because we don't have GetExitCodeThread() */ + g_priority = priority; + return (DWORD)priority; +} + + +/** + * main + * + * executable entry point + */ +INT __cdecl main( INT argc, CHAR **argv ) +{ + HANDLE hThread = NULL; + DWORD IDThread; + DWORD dwRet; + + SIZE_T i = 0; + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return( FAIL ); + } + +#if !HAVE_SCHED_OTHER_ASSIGNABLE + /* Defining thread priority for SCHED_OTHER is implementation defined. + Some platforms like NetBSD cannot reassign it as they are dynamic. + */ + printf("paltest_getcurrentthread_test2 has been disabled on this platform\n"); +#else + /* Create multiple threads. */ + hThread = CreateThread( NULL, /* no security attributes */ + 0, /* use default stack size */ + (LPTHREAD_START_ROUTINE) ThreadFunc, /* thread function */ + (LPVOID) i, /* pass thread index as */ + /* function argument */ + CREATE_SUSPENDED, /* create suspended */ + &IDThread ); /* returns thread identifier */ + + /* Check the return value for success. */ + if( hThread == NULL ) + { + /* ERROR */ + Fail( "ERROR:%lu:CreateThread failed\n", GetLastError() ); + } + + /* set the thread priority of the new thread to the highest value */ + if( ! SetThreadPriority( hThread, THREAD_PRIORITY_TIME_CRITICAL) ) + { + Fail( "ERROR:%lu:SetThreadPriority() call failed\n", GetLastError() ); + } + + /* let the child thread run now */ + ResumeThread( hThread ); + + + /* wait for the thread to finish */ + dwRet = WaitForSingleObject( hThread, INFINITE ); + if( dwRet == WAIT_FAILED ) + { + /* ERROR */ + Fail( "ERROR:%lu:WaitForSingleObject call failed\n", GetLastError() ); + } + + /* validate the thread's exit code */ + if( g_priority != THREAD_PRIORITY_TIME_CRITICAL ) + { + /* ERROR */ + Fail( "FAIL:Unexpected thread priority %d returned, expected %d\n", + g_priority, THREAD_PRIORITY_TIME_CRITICAL ); + } +#endif + + PAL_Terminate(); + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/GetCurrentThread/test2/testinfo.dat b/src/pal/tests/palsuite/threading/GetCurrentThread/test2/testinfo.dat new file mode 100644 index 0000000000..96a6d403bb --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentThread/test2/testinfo.dat @@ -0,0 +1,18 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = GetCurrentThread +Name = Test for GetCurrentThread +TYPE = DEFAULT +EXE1 = test2 +Description += Test to ensure proper operation of the GetCurrentThread() += API. The test launches a thread in suspended mode, and sets += its priority to a non-default value using the handle returned += by CreateThread(). The new thread calls GetCurrentThred() to += retrieve a handle to itself, and calls GetThreadPriority() += to verify that its priority matches what it was set to on += the main execution thread. diff --git a/src/pal/tests/palsuite/threading/GetCurrentThreadId/CMakeLists.txt b/src/pal/tests/palsuite/threading/GetCurrentThreadId/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentThreadId/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/threading/GetCurrentThreadId/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/GetCurrentThreadId/test1/CMakeLists.txt new file mode 100644 index 0000000000..490bff8f5f --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentThreadId/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + threadId.c +) + +add_executable(paltest_getcurrentthreadid_test1 + ${SOURCES} +) + +add_dependencies(paltest_getcurrentthreadid_test1 coreclrpal) + +target_link_libraries(paltest_getcurrentthreadid_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/GetCurrentThreadId/test1/testinfo.dat b/src/pal/tests/palsuite/threading/GetCurrentThreadId/test1/testinfo.dat new file mode 100644 index 0000000000..4d1e056b71 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentThreadId/test1/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = GetCurrentThreadId +Name = Positive Test for GetCurrentThreadId +TYPE = DEFAULT +EXE1 = threadid +Description += Test to ensure GetCurrentThreadId returns the threadId of the current += thread. diff --git a/src/pal/tests/palsuite/threading/GetCurrentThreadId/test1/threadId.c b/src/pal/tests/palsuite/threading/GetCurrentThreadId/test1/threadId.c new file mode 100644 index 0000000000..acbb1ff373 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetCurrentThreadId/test1/threadId.c @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: getcurrentthreadid/test1/threadid.c +** +** Purpose: Test to ensure GetCurrentThreadId returns the threadId of the +** current thread. +** +** Dependencies: CloseHandle +** WaitForSingleObject +** CreateThread +** + +** +**=========================================================*/ + + +#include <palsuite.h> + +DWORD dwThreadIdTF; + +DWORD PALAPI ThreadFunction ( LPVOID lpParam ) +{ + Trace ("thread code executed\n"); + dwThreadIdTF = GetCurrentThreadId(); + return 0; +} + +int __cdecl main( int argc, char **argv ) +{ + extern DWORD dwThreadIdTF; + DWORD dwThreadIdCT; + HANDLE hThread; + DWORD dwThreadParam = 1; + DWORD dwThreadWait; + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + hThread = CreateThread( + NULL, + 0, + ThreadFunction, + &dwThreadParam, + 0, + &dwThreadIdCT); + + if ( NULL == hThread ) + { + Fail ( "CreateThread() call failed - returned NULL"); + } + else + { + dwThreadWait = WaitForSingleObject( hThread, INFINITE ); + + Trace ("dwThreadWait returned %d\n", dwThreadWait ); + + if ( dwThreadIdCT == dwThreadIdTF ) + { + Trace ( "ThreadId numbers match - GetCurrentThreadId" + " works. dwThreadIdCT == dwThreadIdTF == %d\n", + dwThreadIdTF ); + PAL_Terminate(); + return ( PASS ); + } + else + { + Fail ( "ThreadId numbers don't match - " + "GetCurrentThreadId fails dwThreadIdCT = %d " + "and dwThreadIdTF = %d\n", dwThreadIdCT, dwThreadIdTF); + } + } + + PAL_TerminateEx(FAIL); + return (FAIL); + +} diff --git a/src/pal/tests/palsuite/threading/GetExitCodeProcess/CMakeLists.txt b/src/pal/tests/palsuite/threading/GetExitCodeProcess/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetExitCodeProcess/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/threading/GetExitCodeProcess/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/GetExitCodeProcess/test1/CMakeLists.txt new file mode 100644 index 0000000000..adddd97e6d --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetExitCodeProcess/test1/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(TESTSOURCES + test1.c +) + +add_executable(paltest_getexitcodeprocess_test1 + ${TESTSOURCES} +) + +add_dependencies(paltest_getexitcodeprocess_test1 coreclrpal) + +target_link_libraries(paltest_getexitcodeprocess_test1 + pthread + m + coreclrpal +) + + +set(HELPERSOURCES + childProcess.c +) + +add_executable(paltest_getexitcodeprocess_test1_child + ${HELPERSOURCES} +) + +add_dependencies(paltest_getexitcodeprocess_test1_child coreclrpal) + +target_link_libraries(paltest_getexitcodeprocess_test1_child + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/GetExitCodeProcess/test1/childProcess.c b/src/pal/tests/palsuite/threading/GetExitCodeProcess/test1/childProcess.c new file mode 100644 index 0000000000..fe1b38fb31 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetExitCodeProcess/test1/childProcess.c @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: childprocess.c +** +** Purpose: Test to ensure GetExitCodeProcess returns the right +** value. All this program does is return a predefined value. +** +** Dependencies: none +** + +** +**=========================================================*/ + +#include <pal.h> +#include "myexitcode.h" + +int __cdecl main( int argc, char **argv ) +{ + int i; + + // simulate some activity + for( i=0; i<10000; i++ ) + ; + + // return the predefined exit code + return TEST_EXIT_CODE; +} diff --git a/src/pal/tests/palsuite/threading/GetExitCodeProcess/test1/myexitcode.h b/src/pal/tests/palsuite/threading/GetExitCodeProcess/test1/myexitcode.h new file mode 100644 index 0000000000..60a140d1f3 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetExitCodeProcess/test1/myexitcode.h @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================ +** +** Source: myexitcode.h +** +** Purpose: Define an exit code. +** +** +**==========================================================================*/ + +#define TEST_EXIT_CODE 104 diff --git a/src/pal/tests/palsuite/threading/GetExitCodeProcess/test1/test1.c b/src/pal/tests/palsuite/threading/GetExitCodeProcess/test1/test1.c new file mode 100644 index 0000000000..0f98cf8f57 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetExitCodeProcess/test1/test1.c @@ -0,0 +1,163 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test1.c +** +** Purpose: Test to ensure GetExitCodeProcess works properly. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** Fail +** ZeroMemory +** GetCurrentDirectoryW +** CreateProcessW +** WaitForSingleObject +** GetLastError +** strlen +** strncpy +** + +** +**===========================================================================*/ +#include <palsuite.h> +#include "myexitcode.h" + + +static const char* rgchPathDelim = "\\"; + + +int +mkAbsoluteFilename( LPSTR dirName, + DWORD dwDirLength, + LPCSTR fileName, + DWORD dwFileLength, + LPSTR absPathName ) +{ + DWORD sizeDN, sizeFN, sizeAPN; + + sizeDN = strlen( dirName ); + sizeFN = strlen( fileName ); + sizeAPN = (sizeDN + 1 + sizeFN + 1); + + /* ensure ((dirName + DELIM + fileName + \0) =< _MAX_PATH ) */ + if( sizeAPN > _MAX_PATH ) + { + return ( 0 ); + } + + strncpy( absPathName, dirName, dwDirLength +1 ); + strncpy( absPathName, rgchPathDelim, 2 ); + strncpy( absPathName, fileName, dwFileLength +1 ); + + return (sizeAPN); + +} + + +int __cdecl main( int argc, char **argv ) + +{ + const char* rgchChildFile = "childprocess"; + + STARTUPINFO si; + PROCESS_INFORMATION pi; + + DWORD dwError; + DWORD dwExitCode; + DWORD dwFileLength; + DWORD dwDirLength; + DWORD dwSize; + + char rgchDirName[_MAX_DIR]; + char absPathBuf[_MAX_PATH]; + char* rgchAbsPathName; + + /* initialize the PAL */ + if( PAL_Initialize(argc, argv) != 0 ) + { + return( FAIL ); + } + + /* zero our process and startup info structures */ + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof( si ); + ZeroMemory( &pi, sizeof(pi) ); + + /* build the absolute path to the child process */ + rgchAbsPathName = &absPathBuf[0]; + dwFileLength = strlen( rgchChildFile ); + + dwDirLength = GetCurrentDirectory( _MAX_PATH, rgchDirName ); + if( dwDirLength == 0 ) + { + dwError = GetLastError(); + Fail( "GetCurrentDirectory call failed with error code %d\n", + dwError ); + } + + dwSize = mkAbsoluteFilename( rgchDirName, + dwDirLength, + rgchChildFile, + dwFileLength, + rgchAbsPathName ); + if( dwSize == 0 ) + { + Fail( "Palsuite Code: mkAbsoluteFilename() call failed. Could ", + "not build absolute path name to file\n. Exiting.\n" ); + } + + /* launch the child process */ + if( !CreateProcess( NULL, /* module name to execute */ + rgchAbsPathName, /* command line */ + NULL, /* process handle not */ + /* inheritable */ + NULL, /* thread handle not */ + /* inheritable */ + FALSE, /* handle inheritance */ + CREATE_NEW_CONSOLE, /* dwCreationFlags */ + NULL, /* use parent's environment */ + NULL, /* use parent's starting */ + /* directory */ + &si, /* startup info struct */ + &pi ) /* process info struct */ + ) + { + dwError = GetLastError(); + Fail( "CreateProcess call failed with error code %d\n", + dwError ); + } + + /* wait for the child process to complete */ + WaitForSingleObject ( pi.hProcess, INFINITE ); + + /* check the exit code from the process */ + if( ! GetExitCodeProcess( pi.hProcess, &dwExitCode ) ) + { + dwError = GetLastError(); + CloseHandle ( pi.hProcess ); + CloseHandle ( pi.hThread ); + Fail( "GetExitCodeProcess call failed with error code %d\n", + dwError ); + } + + /* close process and thread handle */ + CloseHandle ( pi.hProcess ); + CloseHandle ( pi.hThread ); + + /* check for the expected exit code */ + if( dwExitCode != TEST_EXIT_CODE ) + { + Fail( "GetExitCodeProcess returned an incorrect exit code %d, " + "expected value is %d\n", + dwExitCode, TEST_EXIT_CODE ); + } + + /* terminate the PAL */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/GetExitCodeProcess/test1/testinfo.dat b/src/pal/tests/palsuite/threading/GetExitCodeProcess/test1/testinfo.dat new file mode 100644 index 0000000000..d06719f0b0 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetExitCodeProcess/test1/testinfo.dat @@ -0,0 +1,16 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = GetExitCodeProcess +Name = Test for GetExitCodeProcess +TYPE = DEFAULT +EXE1 = test1 +EXE2 = childprocess +Description += Test to ensure proper operation of the GetExitCodeProcess += API. This test launches a simple child process that exits += with a known value, and checks that the correct value is += returned by the function. diff --git a/src/pal/tests/palsuite/threading/GetProcessTimes/CMakeLists.txt b/src/pal/tests/palsuite/threading/GetProcessTimes/CMakeLists.txt new file mode 100644 index 0000000000..f4796dc1d3 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetProcessTimes/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test2) + diff --git a/src/pal/tests/palsuite/threading/GetProcessTimes/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/GetProcessTimes/test2/CMakeLists.txt new file mode 100644 index 0000000000..0ef7260204 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetProcessTimes/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test2.c +) + +add_executable(paltest_getprocesstimes_test2 + ${SOURCES} +) + +add_dependencies(paltest_getprocesstimes_test2 coreclrpal) + +target_link_libraries(paltest_getprocesstimes_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/GetProcessTimes/test2/test2.c b/src/pal/tests/palsuite/threading/GetProcessTimes/test2/test2.c new file mode 100644 index 0000000000..687facc584 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetProcessTimes/test2/test2.c @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test2.c +** +** Purpose: Test to ensure GetProcessTimes works properly. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** Fail +** ZeroMemory +** CompareFileTime +** GetLastError +** + +** +**===========================================================================*/ +#include <palsuite.h> + + +int __cdecl main( int argc, char **argv ) + +{ + int i, j, k; + int *total; + + HANDLE hProcess; + FILETIME createTime; + FILETIME exitTime; + FILETIME kernelTime1; + FILETIME userTime1; + FILETIME kernelTime2; + FILETIME userTime2; + + DWORD dwError; + + /* initialize the PAL */ + if( PAL_Initialize(argc, argv) != 0 ) + { + return( FAIL ); + } + + /* get our own process handle */ + hProcess = GetCurrentProcess(); + if( hProcess == NULL ) + { + Fail( "GetCurrentProcess() returned a NULL handle.\n" ); + } + + /* zero our time structures */ + ZeroMemory( &createTime, sizeof(createTime) ); + ZeroMemory( &exitTime, sizeof(exitTime) ); + ZeroMemory( &kernelTime1, sizeof(kernelTime1) ); + ZeroMemory( &userTime1, sizeof(userTime1) ); + ZeroMemory( &kernelTime2, sizeof(kernelTime2) ); + ZeroMemory( &userTime2, sizeof(userTime2) ); + + /* check the process times for the child process */ + if( ! GetProcessTimes( hProcess, + &createTime, + &exitTime, + &kernelTime1, + &userTime1 ) ) + { + dwError = GetLastError(); + Fail( "GetProcessTimes() call failed with error code %d\n", + dwError ); + } + + + /* simulate some activity */ + for( i=0; i<1000; i++ ) + { + for( j=0; j<1000; j++ ) + { + /* do kernel work to increase system usage counters */ + total = malloc(1024 * 1024); + + *total = j * i; + for( k=0; k<1000; k++ ) + { + *total += k + i; + } + + free(total); + } + } + + /* check the process times for the child process */ + if( ! GetProcessTimes( hProcess, + &createTime, + &exitTime, + &kernelTime2, + &userTime2 ) ) + { + dwError = GetLastError(); + Fail( "GetProcessTimes() call failed with error code %d\n", + dwError ); + } + + + /* very simple logical checking of the results */ + if( CompareFileTime( &kernelTime1, &kernelTime2 ) > 0 ) + { + Fail( "Unexpected kernel time value reported.\n" ); + } + + if( CompareFileTime( &userTime1, &userTime2 ) > 0 ) + { + Fail( "Unexpected user time value reported.\n" ); + } + + + /* terminate the PAL */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/GetProcessTimes/test2/testinfo.dat b/src/pal/tests/palsuite/threading/GetProcessTimes/test2/testinfo.dat new file mode 100644 index 0000000000..d0d3b75f06 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetProcessTimes/test2/testinfo.dat @@ -0,0 +1,15 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = GetProcessTimes +Name = Test for GetProcessTimes +TYPE = DEFAULT +EXE1 = test2 +Description += Test to ensure proper operation of the GetProcessTimes() += API. This test simply checks the kernel/user times for the += the current process, which is the only thing supported += for this function under the PAL. diff --git a/src/pal/tests/palsuite/threading/GetThreadTimes/CMakeLists.txt b/src/pal/tests/palsuite/threading/GetThreadTimes/CMakeLists.txt new file mode 100644 index 0000000000..5e1ef7f28b --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetThreadTimes/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) diff --git a/src/pal/tests/palsuite/threading/GetThreadTimes/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/GetThreadTimes/test1/CMakeLists.txt new file mode 100644 index 0000000000..d7e2eb2a88 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetThreadTimes/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_getthreadtimes_test1 + ${SOURCES} +) + +add_dependencies(paltest_getthreadtimes_test1 coreclrpal) + +target_link_libraries(paltest_getthreadtimes_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/GetThreadTimes/test1/test1.c b/src/pal/tests/palsuite/threading/GetThreadTimes/test1/test1.c new file mode 100644 index 0000000000..6b62c05ec7 --- /dev/null +++ b/src/pal/tests/palsuite/threading/GetThreadTimes/test1/test1.c @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: +** +** Source : test1.c +** +** Purpose: Test for GetThreadTimes() function +** +** +**=========================================================*/ + +#include <palsuite.h> + +int __cdecl main(int argc, char *argv[]) { + int ret = FAIL; + + //Test is failing unreliably, so for now we always return pass. + if (TRUE){ + ret = PASS; + goto EXIT; + } + + FILETIME kernelTime1, userTime1, kernelTime2, userTime2; + /* Delta = .01 sec */ + LONG64 Actual, Expected, Delta = 850000000; + Actual = 0; + Expected = 0; + const ULONG64 MSEC_TO_NSEC = 1000000; + + /* + * Initialize the PAL and return FAILURE if this fails + */ + + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + HANDLE cThread = GetCurrentThread(); + + int i; + /* Take 2000 tiny measurements */ + for (i = 0; i < 2000; i++){ + ULONG64 Time1, Time2; + + Sleep(1); + + /* Grab a FirstCount, then loop for a bit to make the clock increase */ + if (!GetThreadTimes(cThread, NULL, NULL, &kernelTime1, &userTime1)) + { + Fail("ERROR: GetThreadTimes returned failure.\n"); + } + LONG64 x, Init; + /* Init is in milliseconds, so we will convert later */ + Init = (ULONG64)GetTickCount(); + /* Spin for < 1 Quantum so we don't get interrupted */ + x = Init + 3; + volatile int counter; + do { + for (counter = 0; counter < 100000; counter++) + { + // spin to consume CPU time + } + + } while (x > GetTickCount()); + Expected += (GetTickCount() - Init) * MSEC_TO_NSEC; + /* Get a second count */ + if (!GetThreadTimes(cThread, NULL, NULL, &kernelTime2, &userTime2)) + { + Fail("ERROR: GetThreadTimes returned failure.\n"); + } + + Time1 = ((ULONG64)kernelTime1.dwHighDateTime << 32); + Time1 += (ULONG64)kernelTime1.dwLowDateTime; + Time1 += ((ULONG64)userTime1.dwHighDateTime << 32); + Time1 += (ULONG64)userTime1.dwLowDateTime; + + Time2 = ((ULONG64)kernelTime2.dwHighDateTime << 32); + Time2 += (ULONG64)kernelTime2.dwLowDateTime; + Time2 += ((ULONG64)userTime2.dwHighDateTime << 32); + Time2 += (ULONG64)userTime2.dwLowDateTime; + + Actual += (Time2 - Time1) * 100; + } + + if(labs(Expected - Actual) > Delta) + { + Fail("ERROR: The measured time (%llu millisecs) was not within Delta %llu " + "of the expected time (%llu millisecs).\n", + (Actual / MSEC_TO_NSEC), (Delta / MSEC_TO_NSEC), (Expected / MSEC_TO_NSEC)); + } + //printf("%llu, %llu\n", Expected, Actual); + PAL_Terminate(); + ret = PASS; + +EXIT: + return ret; +} diff --git a/src/pal/tests/palsuite/threading/NamedMutex/CMakeLists.txt b/src/pal/tests/palsuite/threading/NamedMutex/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/NamedMutex/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/threading/NamedMutex/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/NamedMutex/test1/CMakeLists.txt new file mode 100644 index 0000000000..315144e1e8 --- /dev/null +++ b/src/pal/tests/palsuite/threading/NamedMutex/test1/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + namedmutex.cpp + nopal.cpp +) + +add_executable(paltest_namedmutex_test1 + ${SOURCES} +) + +add_dependencies(paltest_namedmutex_test1 coreclrpal) + +target_link_libraries(paltest_namedmutex_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/NamedMutex/test1/namedmutex.cpp b/src/pal/tests/palsuite/threading/NamedMutex/test1/namedmutex.cpp new file mode 100644 index 0000000000..3726214701 --- /dev/null +++ b/src/pal/tests/palsuite/threading/NamedMutex/test1/namedmutex.cpp @@ -0,0 +1,1058 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// These test cases test named mutexes, including positive +// and negative cases, cross - thread and cross - process, mutual +// exclusion, abandon detection, etc. + +#include <palsuite.h> + +#ifndef _countof +#define _countof(a) (sizeof(a) / sizeof(a[0])) +#endif // !_countof + +const char *const SessionPrefix = "Local\\"; +const char *const GlobalPrefix = "Global\\"; + +const char *const NamePrefix = "paltest_namedmutex_test1_"; +const char *const TempNamePrefix = "paltest_namedmutex_test1_temp_"; +const char *const InvalidNamePrefix0 = "paltest\\namedmutex_"; +const char *const InvalidNamePrefix1 = "paltest/namedmutex_"; +const char *const ParentEventNamePrefix0 = "paltest_namedmutex_test1_pe0_"; +const char *const ParentEventNamePrefix1 = "paltest_namedmutex_test1_pe1_"; +const char *const ChildEventNamePrefix0 = "paltest_namedmutex_test1_ce0_"; +const char *const ChildEventNamePrefix1 = "paltest_namedmutex_test1_ce1_"; +const char *const ChildRunningEventNamePrefix = "paltest_namedmutex_test1_cr_"; + +const char *const GlobalShmFilePathPrefix = "/tmp/.dotnet/shm/global/"; + +#define MaxPathSize (200) +const DWORD PollLoopSleepMilliseconds = 100; +const DWORD FailTimeoutMilliseconds = 30000; +DWORD g_expectedTimeoutMilliseconds = 500; + +bool g_isParent = true; +bool g_isStress = false; +char g_processPath[4096], g_processCommandLinePath[4096]; +DWORD g_parentPid = static_cast<DWORD>(-1); + +extern char *(*test_strcpy)(char *dest, const char *src); +extern int (*test_strcmp)(const char *s1, const char *s2); +extern size_t (*test_strlen)(const char *s); +extern int (*test_sprintf)(char *str, const char *format, ...); +extern int (*test_sscanf)(const char *str, const char *format, ...); +extern int(*test_close)(int fd); +extern int (*test_unlink)(const char *pathname); +extern unsigned int test_getpid(); +extern int test_kill(unsigned int pid); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Test helpers + +extern bool TestFileExists(const char *path); +extern bool WriteHeaderInfo(const char *path, char sharedMemoryType, char version, int *fdRef); + +#define TestAssert(expression) \ + do \ + { \ + if (!(expression)) \ + { \ + if (!g_isParent) \ + { \ + Trace("Child process: "); \ + } \ + Trace("'paltest_namedmutex_test1' failed at line %u. Expression: " #expression "\n", __LINE__); \ + fflush(stdout); \ + return false; \ + } \ + } while(false) + +char *BuildName(const char *testName, char *buffer, const char *prefix0, const char *prefix1 = nullptr) +{ + size_t nameLength = 0; + const char *prefixes[] = {prefix0, prefix1}; + for (int i = 0; i < 2; ++i) + { + const char *prefix = prefixes[i]; + if (prefix == nullptr) + { + break; + } + test_strcpy(&buffer[nameLength], prefix); + nameLength += test_strlen(prefix); + } + + if (g_isStress) + { + // Append the test name so that tests can run in parallel + nameLength += test_sprintf(&buffer[nameLength], "%s", testName); + buffer[nameLength++] = '_'; + } + + nameLength += test_sprintf(&buffer[nameLength], "%u", g_parentPid); + return buffer; +} + +char *BuildGlobalShmFilePath(const char *testName, char *buffer, const char *namePrefix) +{ + size_t pathLength = 0; + test_strcpy(&buffer[pathLength], GlobalShmFilePathPrefix); + pathLength += test_strlen(GlobalShmFilePathPrefix); + test_strcpy(&buffer[pathLength], namePrefix); + pathLength += test_strlen(namePrefix); + + if (g_isStress) + { + // Append the test name so that tests can run in parallel + pathLength += test_sprintf(&buffer[pathLength], "%s", testName); + buffer[pathLength++] = '_'; + } + + pathLength += test_sprintf(&buffer[pathLength], "%u", g_parentPid); + return buffer; +} + +class AutoCloseMutexHandle +{ +private: + HANDLE m_handle; + +public: + AutoCloseMutexHandle(HANDLE handle = nullptr) : m_handle(handle) + { + } + + ~AutoCloseMutexHandle() + { + Close(); + } + +public: + HANDLE GetHandle() const + { + return m_handle; + } + + bool Release() + { + return !!ReleaseMutex(m_handle); + } + + void Close() + { + if (m_handle != nullptr) + { + CloseHandle(m_handle); + m_handle = nullptr; + } + } + + void Abandon() + { + // Don't close the handle + m_handle = nullptr; + } + + AutoCloseMutexHandle &operator =(HANDLE handle) + { + Close(); + m_handle = handle; + return *this; + } + + operator HANDLE() const + { + return m_handle; + } + +private: + AutoCloseMutexHandle(const AutoCloseMutexHandle &other); + AutoCloseMutexHandle(AutoCloseMutexHandle &&other); + AutoCloseMutexHandle &operator =(const AutoCloseMutexHandle &other); +}; + +void TestCreateMutex(AutoCloseMutexHandle &m, const char *name, bool initiallyOwned = false) +{ + m.Close(); + m = CreateMutexA(nullptr, initiallyOwned, name); +} + +HANDLE TestOpenMutex(const char *name) +{ + return OpenMutexA(SYNCHRONIZE, false, name); +} + +bool StartProcess(const char *funcName) +{ + // Command line format: <processPath> <parentPid> <testFunctionName> [stress] + + size_t processCommandLinePathLength = 0; + g_processCommandLinePath[processCommandLinePathLength++] = '\"'; + test_strcpy(&g_processCommandLinePath[processCommandLinePathLength], g_processPath); + processCommandLinePathLength += test_strlen(g_processPath); + g_processCommandLinePath[processCommandLinePathLength++] = '\"'; + g_processCommandLinePath[processCommandLinePathLength++] = ' '; + processCommandLinePathLength += test_sprintf(&g_processCommandLinePath[processCommandLinePathLength], "%u", g_parentPid); + g_processCommandLinePath[processCommandLinePathLength++] = ' '; + test_strcpy(&g_processCommandLinePath[processCommandLinePathLength], funcName); + processCommandLinePathLength += test_strlen(funcName); + + if (g_isStress) + { + test_strcpy(&g_processCommandLinePath[processCommandLinePathLength], " stress"); + processCommandLinePathLength += _countof("stress") - 1; + } + + STARTUPINFO si; + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + PROCESS_INFORMATION pi; + memset(&pi, 0, sizeof(pi)); + if (!CreateProcessA(nullptr, g_processCommandLinePath, nullptr, nullptr, false, 0, nullptr, nullptr, &si, &pi)) + { + return false; + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return true; +} + +bool StartThread(LPTHREAD_START_ROUTINE func, void *arg = nullptr, HANDLE *threadHandleRef = nullptr) +{ + DWORD threadId; + HANDLE handle = CreateThread(nullptr, 0, func, arg, 0, &threadId); + if (handle != nullptr) + { + if (threadHandleRef == nullptr) + { + CloseHandle(handle); + } + else + { + *threadHandleRef = handle; + } + return true; + } + return false; +} + +bool WaitForMutexToBeCreated(const char *testName, AutoCloseMutexHandle &m, const char *eventNamePrefix) +{ + char eventName[MaxPathSize]; + BuildName(testName, eventName, GlobalPrefix, eventNamePrefix); + DWORD startTime = GetTickCount(); + while (true) + { + m = TestOpenMutex(eventName); + if (m != nullptr) + { + return true; + } + if (GetTickCount() - startTime >= FailTimeoutMilliseconds) + { + return false; + } + Sleep(PollLoopSleepMilliseconds); + } +} + +// The following functions are used for parent/child tests, where the child runs in a separate thread or process. The tests are +// organized such that one the parent or child is ever running code, and they yield control and wait for the other. Since the +// named mutex is the only type of cross-process sync object available, they are used as events to synchronize. The parent and +// child have a pair of event mutexes each, which they own initially. To release the other waiting thread/process, the +// thread/process releases one of its mutexes, which the other thread/process would be waiting on. To wait, the thread/process +// waits on one of the other thread/process' mutexes. All the while, they ping-pong between the two mutexes. YieldToChild() and +// YieldToParent() below control the releasing, waiting, and ping-ponging, to help create a deterministic path through the +// parent and child tests while both are running concurrently. + +bool AcquireChildRunningEvent(const char *testName, AutoCloseMutexHandle &childRunningEvent) +{ + char name[MaxPathSize]; + TestCreateMutex(childRunningEvent, BuildName(testName, name, GlobalPrefix, ChildRunningEventNamePrefix)); + TestAssert(WaitForSingleObject(childRunningEvent, FailTimeoutMilliseconds) == WAIT_OBJECT_0); + return true; +} + +bool InitializeParent(const char *testName, AutoCloseMutexHandle parentEvents[2], AutoCloseMutexHandle childEvents[2]) +{ + // Create parent events + char name[MaxPathSize]; + for (int i = 0; i < 2; ++i) + { + TestCreateMutex( + parentEvents[i], + BuildName(testName, name, GlobalPrefix, i == 0 ? ParentEventNamePrefix0 : ParentEventNamePrefix1), + true); + TestAssert(parentEvents[i] != nullptr); + TestAssert(GetLastError() != ERROR_ALREADY_EXISTS); + } + + // Wait for the child to create and acquire locks on its events so that the parent can wait on them + TestAssert(WaitForMutexToBeCreated(testName, childEvents[0], ChildEventNamePrefix0)); + TestAssert(WaitForMutexToBeCreated(testName, childEvents[1], ChildEventNamePrefix1)); + return true; +} + +bool UninitializeParent(const char *testName, AutoCloseMutexHandle parentEvents[2], bool releaseParentEvents = true) +{ + if (releaseParentEvents) + { + TestAssert(parentEvents[0].Release()); + TestAssert(parentEvents[1].Release()); + } + + // Wait for the child to finish its test. Child tests will release and close 'childEvents' before releasing + // 'childRunningEvent', so after this wait, the parent process can freely start another child that will deterministically + // recreate the 'childEvents', which the next parent test will wait on, upon its initialization. + AutoCloseMutexHandle childRunningEvent; + TestAssert(AcquireChildRunningEvent(testName, childRunningEvent)); + TestAssert(childRunningEvent.Release()); + return true; +} + +bool InitializeChild( + const char *testName, + AutoCloseMutexHandle &childRunningEvent, + AutoCloseMutexHandle parentEvents[2], + AutoCloseMutexHandle childEvents[2]) +{ + TestAssert(AcquireChildRunningEvent(testName, childRunningEvent)); + + // Create child events + char name[MaxPathSize]; + for (int i = 0; i < 2; ++i) + { + TestCreateMutex( + childEvents[i], + BuildName(testName, name, GlobalPrefix, i == 0 ? ChildEventNamePrefix0 : ChildEventNamePrefix1), + true); + TestAssert(childEvents[i] != nullptr); + TestAssert(GetLastError() != ERROR_ALREADY_EXISTS); + } + + // Wait for the parent to create and acquire locks on its events so that the child can wait on them + TestAssert(WaitForMutexToBeCreated(testName, parentEvents[0], ParentEventNamePrefix0)); + TestAssert(WaitForMutexToBeCreated(testName, parentEvents[1], ParentEventNamePrefix1)); + + // Parent/child tests start with the parent, so after initialization, wait for the parent to tell the child test to start + TestAssert(WaitForSingleObject(parentEvents[0], FailTimeoutMilliseconds) == WAIT_OBJECT_0); + TestAssert(parentEvents[0].Release()); + return true; +} + +bool UninitializeChild( + AutoCloseMutexHandle &childRunningEvent, + AutoCloseMutexHandle parentEvents[2], + AutoCloseMutexHandle childEvents[2]) +{ + // Release and close 'parentEvents' and 'childEvents' before releasing 'childRunningEvent' to avoid races, see + // UnitializeParent() for more info + TestAssert(childEvents[0].Release()); + TestAssert(childEvents[1].Release()); + childEvents[0].Close(); + childEvents[1].Close(); + parentEvents[0].Close(); + parentEvents[1].Close(); + TestAssert(childRunningEvent.Release()); + return true; +} + +bool YieldToChild(AutoCloseMutexHandle parentEvents[2], AutoCloseMutexHandle childEvents[2], int &ei) +{ + TestAssert(parentEvents[ei].Release()); + TestAssert(WaitForSingleObject(childEvents[ei], FailTimeoutMilliseconds) == WAIT_OBJECT_0); + TestAssert(childEvents[ei].Release()); + TestAssert(WaitForSingleObject(parentEvents[ei], 0) == WAIT_OBJECT_0); + ei = 1 - ei; + return true; +} + +bool YieldToParent(AutoCloseMutexHandle parentEvents[2], AutoCloseMutexHandle childEvents[2], int &ei) +{ + TestAssert(childEvents[ei].Release()); + ei = 1 - ei; + TestAssert(WaitForSingleObject(parentEvents[ei], FailTimeoutMilliseconds) == WAIT_OBJECT_0); + TestAssert(parentEvents[ei].Release()); + TestAssert(WaitForSingleObject(childEvents[1 - ei], 0) == WAIT_OBJECT_0); + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Tests + +bool NameTests() +{ + const char *testName = "NameTests"; + + AutoCloseMutexHandle m; + char name[MaxPathSize]; + + // Empty name + TestCreateMutex(m, ""); + TestAssert(m != nullptr); + + // Normal name + TestCreateMutex(m, BuildName(testName, name, NamePrefix)); + TestAssert(m != nullptr); + TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, NamePrefix))) != nullptr); + TestCreateMutex(m, BuildName(testName, name, SessionPrefix, NamePrefix)); + TestAssert(m != nullptr); + TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, SessionPrefix, NamePrefix))) != nullptr); + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); + TestAssert(m != nullptr); + TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, GlobalPrefix, NamePrefix))) != nullptr); + + // Name too long. The maximum allowed length depends on the file system, so we're not checking for that. + { + char name[257]; + memset(name, 'a', _countof(name) - 1); + name[_countof(name) - 1] = '\0'; + TestCreateMutex(m, name); + TestAssert(m == nullptr); + TestAssert(GetLastError() == ERROR_FILENAME_EXCED_RANGE); + TestAssert(AutoCloseMutexHandle(TestOpenMutex(name)) == nullptr); + TestAssert(GetLastError() == ERROR_FILENAME_EXCED_RANGE); + } + + // Invalid characters in name + TestCreateMutex(m, BuildName(testName, name, InvalidNamePrefix0)); + TestAssert(m == nullptr); + TestAssert(GetLastError() == ERROR_INVALID_NAME); + TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, InvalidNamePrefix0))) == nullptr); + TestAssert(GetLastError() == ERROR_INVALID_NAME); + TestCreateMutex(m, BuildName(testName, name, InvalidNamePrefix1)); + TestAssert(m == nullptr); + TestAssert(GetLastError() == ERROR_INVALID_NAME); + TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, InvalidNamePrefix1))) == nullptr); + TestAssert(GetLastError() == ERROR_INVALID_NAME); + TestCreateMutex(m, BuildName(testName, name, SessionPrefix, InvalidNamePrefix0)); + TestAssert(m == nullptr); + TestAssert(GetLastError() == ERROR_INVALID_NAME); + TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, SessionPrefix, InvalidNamePrefix0))) == nullptr); + TestAssert(GetLastError() == ERROR_INVALID_NAME); + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, InvalidNamePrefix1)); + TestAssert(m == nullptr); + TestAssert(GetLastError() == ERROR_INVALID_NAME); + TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, GlobalPrefix, InvalidNamePrefix1))) == nullptr); + TestAssert(GetLastError() == ERROR_INVALID_NAME); + + // Creating a second reference to the same named mutex yields an error indicating that it was opened, not created + { + TestCreateMutex(m, BuildName(testName, name, NamePrefix)); + TestAssert(m != nullptr); + AutoCloseMutexHandle m2; + TestCreateMutex(m2, BuildName(testName, name, NamePrefix)); + TestAssert(m2 != nullptr); + TestAssert(GetLastError() == ERROR_ALREADY_EXISTS); + } + + return true; +} + +bool HeaderMismatchTests() +{ + const char *testName = "HeaderMismatchTests"; + + AutoCloseMutexHandle m, m2; + char name[MaxPathSize]; + int fd; + + // Create and hold onto a mutex during this test to create the shared memory directory + TestCreateMutex(m2, BuildName(testName, name, GlobalPrefix, TempNamePrefix)); + TestAssert(m2 != nullptr); + + // Unknown shared memory type + TestAssert(WriteHeaderInfo(BuildGlobalShmFilePath(testName, name, NamePrefix), -1, 1, &fd)); + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); + TestAssert(m == nullptr); + TestAssert(GetLastError() == ERROR_INVALID_HANDLE); + TestAssert(test_close(fd) == 0); + TestAssert(test_unlink(BuildGlobalShmFilePath(testName, name, NamePrefix)) == 0); + + // Mismatched version + TestAssert(WriteHeaderInfo(BuildGlobalShmFilePath(testName, name, NamePrefix), 0, -1, &fd)); + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); + TestAssert(m == nullptr); + TestAssert(GetLastError() == ERROR_INVALID_HANDLE); + TestAssert(test_close(fd) == 0); + TestAssert(test_unlink(BuildGlobalShmFilePath(testName, name, NamePrefix)) == 0); + + return true; +} + +bool MutualExclusionTests_Parent() +{ + const char *testName = "MutualExclusionTests"; + + AutoCloseMutexHandle parentEvents[2], childEvents[2]; + TestAssert(InitializeParent(testName, parentEvents, childEvents)); + int ei = 0; + char name[MaxPathSize]; + AutoCloseMutexHandle m; + + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); + TestAssert(m != nullptr); + + // Recursive locking with various timeouts + TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); + TestAssert(WaitForSingleObject(m, FailTimeoutMilliseconds) == WAIT_OBJECT_0); + TestAssert(WaitForSingleObject(m, static_cast<DWORD>(-1)) == WAIT_OBJECT_0); + TestAssert(m.Release()); + TestAssert(m.Release()); + TestAssert(m.Release()); + TestAssert(!m.Release()); // try to release the lock while nobody owns it, and verify recursive lock counting + TestAssert(GetLastError() == ERROR_NOT_OWNER); + + TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child takes the lock + + TestAssert(WaitForSingleObject(m, 0) == WAIT_TIMEOUT); // try to lock the mutex without waiting + TestAssert(WaitForSingleObject(m, g_expectedTimeoutMilliseconds) == WAIT_TIMEOUT); // try to lock the mutex with a timeout + TestAssert(!m.Release()); // try to release the lock while another thread owns it + TestAssert(GetLastError() == ERROR_NOT_OWNER); + + TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child releases the lock + + TestAssert(WaitForSingleObject(m, static_cast<DWORD>(-1)) == WAIT_OBJECT_0); // lock the mutex with no timeout and release + TestAssert(m.Release()); + + UninitializeParent(testName, parentEvents); + return true; +} + +DWORD MutualExclusionTests_Child(void *arg = nullptr) +{ + const char *testName = "MutualExclusionTests"; + + AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; + TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); + int ei = 0; + + { + char name[MaxPathSize]; + AutoCloseMutexHandle m; + + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); + TestAssert(m != nullptr); + TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); // lock the mutex + YieldToParent(parentEvents, childEvents, ei); // parent attempts to lock/release, and fails + TestAssert(m.Release()); // release the lock + } + + UninitializeChild(childRunningEvent, parentEvents, childEvents); + return 0; +} + +bool MutualExclusionTests() +{ + const char *testName = "MutualExclusionTests"; + + { + AutoCloseMutexHandle m; + char name[MaxPathSize]; + + // Releasing a lock that is not owned by any thread fails + TestCreateMutex(m, BuildName(testName, name, NamePrefix)); + TestAssert(m != nullptr); + TestAssert(!m.Release()); + TestAssert(GetLastError() == ERROR_NOT_OWNER); + + // Acquire a lock during upon creation, and release + TestCreateMutex(m, BuildName(testName, name, NamePrefix), true); + TestAssert(m != nullptr); + TestAssert(m.Release()); + + // Multi-waits including a named mutex are not supported + AutoCloseMutexHandle m2; + TestCreateMutex(m2, nullptr); + TestAssert(m2 != nullptr); + HANDLE waitHandles[] = {m2.GetHandle(), m.GetHandle()}; + TestAssert( + WaitForMultipleObjects( + _countof(waitHandles), + waitHandles, + false /* waitAll */, + FailTimeoutMilliseconds) == + WAIT_FAILED); + TestAssert(GetLastError() == ERROR_NOT_SUPPORTED); + TestAssert( + WaitForMultipleObjects( + _countof(waitHandles), + waitHandles, + true /* waitAll */, + FailTimeoutMilliseconds) == + WAIT_FAILED); + TestAssert(GetLastError() == ERROR_NOT_SUPPORTED); + } + + // When another thread or process owns the lock, this process should not be able to acquire a lock, and the converse + TestAssert(StartThread(MutualExclusionTests_Child)); + TestAssert(MutualExclusionTests_Parent()); + TestAssert(StartProcess("MutualExclusionTests_Child")); + TestAssert(MutualExclusionTests_Parent()); + + return true; +} + +bool LifetimeTests_Parent() +{ + const char *testName = "LifetimeTests"; + + AutoCloseMutexHandle parentEvents[2], childEvents[2]; + TestAssert(InitializeParent(testName, parentEvents, childEvents)); + int ei = 0; + char name[MaxPathSize]; + AutoCloseMutexHandle m; + + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); // create first reference to mutex + TestAssert(m != nullptr); + TestAssert(TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))); + TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child creates second reference to mutex using CreateMutex + m.Close(); // close first reference + TestAssert(TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))); + TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child closes second reference + TestAssert(!TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))); + + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); // create first reference to mutex + TestAssert(m != nullptr); + TestAssert(TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))); + TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child creates second reference to mutex using OpenMutex + m.Close(); // close first reference + TestAssert(TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))); + TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child closes second reference + TestAssert(!TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))); + + UninitializeParent(testName, parentEvents); + return true; +} + +DWORD LifetimeTests_Child(void *arg = nullptr) +{ + const char *testName = "LifetimeTests"; + + AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; + TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); + int ei = 0; + + { + char name[MaxPathSize]; + AutoCloseMutexHandle m; + + // ... parent creates first reference to mutex + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); // create second reference to mutex using CreateMutex + TestAssert(m != nullptr); + TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent closes first reference + m.Close(); // close second reference + + TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent verifies, and creates first reference to mutex again + m = TestOpenMutex(BuildName(testName, name, GlobalPrefix, NamePrefix)); // create second reference to mutex using OpenMutex + TestAssert(m != nullptr); + TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent closes first reference + m.Close(); // close second reference + + TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent verifies + } + + UninitializeChild(childRunningEvent, parentEvents, childEvents); + return 0; +} + +bool LifetimeTests() +{ + const char *testName = "LifetimeTests"; + + { + AutoCloseMutexHandle m; + char name[MaxPathSize]; + + // Shm file should be created and deleted + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); + TestAssert(m != nullptr); + TestAssert(TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))); + m.Close(); + TestAssert(!TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))); + } + + // Shm file should not be deleted until last reference is released + TestAssert(StartThread(LifetimeTests_Child)); + TestAssert(LifetimeTests_Parent()); + TestAssert(StartProcess("LifetimeTests_Child")); + TestAssert(LifetimeTests_Parent()); + + return true; +} + +DWORD AbandonTests_Child_TryLock(void *arg = nullptr); + +bool AbandonTests_Parent() +{ + const char *testName = "AbandonTests"; + + char name[MaxPathSize]; + AutoCloseMutexHandle m; + { + AutoCloseMutexHandle parentEvents[2], childEvents[2]; + TestAssert(InitializeParent(testName, parentEvents, childEvents)); + int ei = 0; + + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); + TestAssert(m != nullptr); + TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child locks mutex + TestAssert(parentEvents[0].Release()); + TestAssert(parentEvents[1].Release()); // child sleeps for short duration and abandons the mutex + TestAssert(WaitForSingleObject(m, FailTimeoutMilliseconds) == WAIT_ABANDONED_0); // attempt to lock and see abandoned mutex + + UninitializeParent(testName, parentEvents, false /* releaseParentEvents */); // parent events are released above + } + + // Verify that the mutex lock is owned by this thread, by starting a new thread and trying to lock it + StartThread(AbandonTests_Child_TryLock); + { + AutoCloseMutexHandle parentEvents[2], childEvents[2]; + TestAssert(InitializeParent(testName, parentEvents, childEvents)); + int ei = 0; + + TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child tries to lock mutex + + UninitializeParent(testName, parentEvents); + } + + // Verify that the mutex lock is owned by this thread, by starting a new process and trying to lock it + StartProcess("AbandonTests_Child_TryLock"); + AutoCloseMutexHandle parentEvents[2], childEvents[2]; + TestAssert(InitializeParent(testName, parentEvents, childEvents)); + int ei = 0; + + TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child tries to lock mutex + + // Continue verification + TestAssert(m.Release()); + TestAssert(WaitForSingleObject(m, FailTimeoutMilliseconds) == WAIT_OBJECT_0); // lock again to see it's not abandoned anymore + TestAssert(m.Release()); + + UninitializeParent(testName, parentEvents, false /* releaseParentEvents */); // parent events are released above + + // Since the child abandons the mutex, and a child process may not release the file lock on the shared memory file before + // indicating completion to the parent, make sure to delete the shared memory file by repeatedly opening/closing the mutex + // until the parent process becomes the last process to reference the mutex and closing it deletes the file. + DWORD startTime = GetTickCount(); + while (true) + { + m.Close(); + if (!TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))) + { + break; + } + + TestAssert(GetTickCount() - startTime < FailTimeoutMilliseconds); + m = TestOpenMutex(BuildName(testName, name, GlobalPrefix, NamePrefix)); + } + + return true; +} + +DWORD AbandonTests_Child_GracefulExit_Close(void *arg = nullptr) +{ + const char *testName = "AbandonTests"; + + AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; + TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); + int ei = 0; + + { + char name[MaxPathSize]; + AutoCloseMutexHandle m; + + // ... parent waits for child to lock mutex + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); + TestAssert(m != nullptr); + TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); + TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent waits on mutex + Sleep(g_expectedTimeoutMilliseconds); // wait for parent to wait on mutex + m.Close(); // close mutex without releasing lock + } + + UninitializeChild(childRunningEvent, parentEvents, childEvents); + return 0; +} + +DWORD AbandonTests_Child_GracefulExit_NoClose(void *arg = nullptr) +{ + const char *testName = "AbandonTests"; + + // This test needs to run in a separate process because it does not close the mutex handle. Running it in a separate thread + // causes the mutex object to retain a reference until the process terminates. + TestAssert(test_getpid() != g_parentPid); + + AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; + TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); + int ei = 0; + + { + char name[MaxPathSize]; + AutoCloseMutexHandle m; + + // ... parent waits for child to lock mutex + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); + TestAssert(m != nullptr); + TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); + TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent waits on mutex + Sleep(g_expectedTimeoutMilliseconds); // wait for parent to wait on mutex + m.Abandon(); // don't close the mutex + } + + UninitializeChild(childRunningEvent, parentEvents, childEvents); + return 0; +} + +DWORD AbandonTests_Child_AbruptExit(void *arg = nullptr) +{ + const char *testName = "AbandonTests"; + + DWORD currentPid = test_getpid(); + TestAssert(currentPid != g_parentPid); // this test needs to run in a separate process + + { + AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; + TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); + int ei = 0; + + { + char name[MaxPathSize]; + AutoCloseMutexHandle m; + + // ... parent waits for child to lock mutex + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); + TestAssert(m != nullptr); + TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); + TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent waits on mutex + Sleep(g_expectedTimeoutMilliseconds); // wait for parent to wait on mutex + m.Abandon(); // don't close the mutex + } + + UninitializeChild(childRunningEvent, parentEvents, childEvents); + } + + TestAssert(test_kill(currentPid) == 0); // abandon the mutex abruptly + return 0; +} + +DWORD AbandonTests_Child_TryLock(void *arg) +{ + const char *testName = "AbandonTests"; + + AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; + TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); + int ei = 0; + + { + char name[MaxPathSize]; + AutoCloseMutexHandle m; + + // ... parent waits for child to lock mutex + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); + TestAssert(m != nullptr); + TestAssert(WaitForSingleObject(m, 0) == WAIT_TIMEOUT); // try to lock the mutex while the parent holds the lock + TestAssert(WaitForSingleObject(m, g_expectedTimeoutMilliseconds) == WAIT_TIMEOUT); + } + + UninitializeChild(childRunningEvent, parentEvents, childEvents); + return 0; +} + +bool AbandonTests() +{ + const char *testName = "AbandonTests"; + + // Abandon by graceful exit where the lock owner closes the mutex before releasing it, unblocks a waiter + TestAssert(StartThread(AbandonTests_Child_GracefulExit_Close)); + TestAssert(AbandonTests_Parent()); + TestAssert(StartProcess("AbandonTests_Child_GracefulExit_Close")); + TestAssert(AbandonTests_Parent()); + + // Abandon by graceful exit without closing the mutex unblocks a waiter + TestAssert(StartProcess("AbandonTests_Child_GracefulExit_NoClose")); + TestAssert(AbandonTests_Parent()); + + // Abandon by abrupt exit unblocks a waiter + TestAssert(StartProcess("AbandonTests_Child_AbruptExit")); + TestAssert(AbandonTests_Parent()); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Test harness + +bool (*const (TestList[]))() = +{ + NameTests, + HeaderMismatchTests, + MutualExclusionTests, + LifetimeTests, + AbandonTests +}; + +bool RunTests() +{ + bool allPassed = true; + for (SIZE_T i = 0; i < _countof(TestList); ++i) + { + if (!TestList[i]()) + { + allPassed = false; + } + } + return allPassed; +} + +DWORD g_stressDurationMilliseconds = 0; +LONG g_stressTestCounts[_countof(TestList)] = {0}; +LONG g_stressResult = true; + +DWORD StressTest(void *arg) +{ + // Run the specified test continuously for the stress duration + SIZE_T testIndex = reinterpret_cast<SIZE_T>(arg); + DWORD startTime = GetTickCount(); + do + { + ++g_stressTestCounts[testIndex]; + if (!TestList[testIndex]()) + { + InterlockedExchange(&g_stressResult, false); + break; + } + } while ( + InterlockedCompareExchange(&g_stressResult, false, false) == true && + GetTickCount() - startTime < g_stressDurationMilliseconds); + return 0; +} + +bool StressTests(DWORD durationMinutes) +{ + g_isStress = true; + g_expectedTimeoutMilliseconds = 1; + g_stressDurationMilliseconds = durationMinutes * (60 * 1000); + + // Start a thread for each test + HANDLE threadHandles[_countof(TestList)]; + for (SIZE_T i = 0; i < _countof(threadHandles); ++i) + { + TestAssert(StartThread(StressTest, reinterpret_cast<void *>(i), &threadHandles[i])); + } + + while (true) + { + DWORD waitResult = + WaitForMultipleObjects(_countof(threadHandles), threadHandles, true /* bWaitAll */, 10 * 1000 /* dwMilliseconds */); + TestAssert(waitResult == WAIT_OBJECT_0 || waitResult == WAIT_TIMEOUT); + if (waitResult == WAIT_OBJECT_0) + { + break; + } + + Trace("'paltest_namedmutex_test1' stress test counts: "); + for (SIZE_T i = 0; i < _countof(g_stressTestCounts); ++i) + { + if (i != 0) + { + Trace(", "); + } + Trace("%u", g_stressTestCounts[i]); + } + Trace("\n"); + fflush(stdout); + } + + for (SIZE_T i = 0; i < _countof(threadHandles); ++i) + { + CloseHandle(threadHandles[i]); + } + return static_cast<bool>(g_stressResult); +} + +int __cdecl main(int argc, char **argv) +{ + if (argc < 1 || argc > 4) + { + return FAIL; + } + + if (PAL_Initialize(argc, argv) != 0) + { + return FAIL; + } + + test_strcpy(g_processPath, argv[0]); + + if (argc == 1) + { + // Unit test arguments: <processPath> + + g_parentPid = test_getpid(); + int result = RunTests() ? PASS : FAIL; + ExitProcess(result); + return result; + } + + if (test_strcmp(argv[1], "stress") == 0) + { + // Stress test arguments: <processPath> stress [durationMinutes] + + DWORD durationMinutes = 1; + if (argc >= 3 && test_sscanf(argv[2], "%u", &durationMinutes) != 1) + { + ExitProcess(FAIL); + return FAIL; + } + + g_parentPid = test_getpid(); + int result = StressTests(durationMinutes) ? PASS : FAIL; + ExitProcess(result); + return result; + } + + // Child test process arguments: <processPath> <parentPid> <testFunctionName> [stress] + + g_isParent = false; + + // Get parent process' ID from argument + if (test_sscanf(argv[1], "%u", &g_parentPid) != 1) + { + ExitProcess(FAIL); + return FAIL; + } + + if (argc >= 4 && test_strcmp(argv[3], "stress") == 0) + { + g_isStress = true; + } + + if (test_strcmp(argv[2], "MutualExclusionTests_Child") == 0) + { + MutualExclusionTests_Child(); + } + else if (test_strcmp(argv[2], "LifetimeTests_Child") == 0) + { + LifetimeTests_Child(); + } + else if (test_strcmp(argv[2], "AbandonTests_Child_GracefulExit_Close") == 0) + { + AbandonTests_Child_GracefulExit_Close(); + } + else if (test_strcmp(argv[2], "AbandonTests_Child_GracefulExit_NoClose") == 0) + { + AbandonTests_Child_GracefulExit_NoClose(); + } + else if (test_strcmp(argv[2], "AbandonTests_Child_AbruptExit") == 0) + { + AbandonTests_Child_AbruptExit(); + } + else if (test_strcmp(argv[2], "AbandonTests_Child_TryLock") == 0) + { + AbandonTests_Child_TryLock(); + } + ExitProcess(PASS); + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/NamedMutex/test1/nopal.cpp b/src/pal/tests/palsuite/threading/NamedMutex/test1/nopal.cpp new file mode 100644 index 0000000000..46a5d87341 --- /dev/null +++ b/src/pal/tests/palsuite/threading/NamedMutex/test1/nopal.cpp @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// Contains wrappers for functions whose required headers conflict with the PAL + +#include <sys/file.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#define _countof(a) (sizeof(a) / sizeof(a[0])) + +#define PAGE_SIZE (4096) + +auto test_strcpy = strcpy; +auto test_strcmp = strcmp; +auto test_strlen = strlen; +auto test_sprintf = sprintf; +auto test_sscanf = sscanf; +auto test_close = close; +auto test_unlink = unlink; + +unsigned int test_getpid() +{ + return getpid(); +} + +int test_kill(unsigned int pid) +{ + return kill(pid, SIGKILL); +} + +bool TestFileExists(const char *path) +{ + int fd = open(path, O_RDWR); + if (fd == -1) + return false; + close(fd); + return true; +} + +bool WriteHeaderInfo(const char *path, char sharedMemoryType, char version, int *fdRef) +{ + int fd = open(path, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (fd == -1) + return false; + *fdRef = fd; + if (ftruncate(fd, PAGE_SIZE) != 0) + return false; + if (lseek(fd, 0, SEEK_SET) != 0) + return false; + + // See SharedMemorySharedDataHeader for format + char buffer[] = {sharedMemoryType, version}; + if (write(fd, buffer, _countof(buffer)) != _countof(buffer)) + return false; + + return flock(fd, LOCK_SH | LOCK_NB) == 0; +} diff --git a/src/pal/tests/palsuite/threading/NamedMutex/test1/testinfo.dat b/src/pal/tests/palsuite/threading/NamedMutex/test1/testinfo.dat new file mode 100644 index 0000000000..e3090093ea --- /dev/null +++ b/src/pal/tests/palsuite/threading/NamedMutex/test1/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = Named mutex +Name = Tests for named mutexes +TYPE = DEFAULT +EXE1 = namedmutex +Description += These test cases test named mutexes, including positive += and negative cases, cross-thread and cross-process, mutual += exclusion, abandon detection, etc. diff --git a/src/pal/tests/palsuite/threading/OpenEventW/CMakeLists.txt b/src/pal/tests/palsuite/threading/OpenEventW/CMakeLists.txt new file mode 100644 index 0000000000..8083faf655 --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) +add_subdirectory(test3) +add_subdirectory(test4) +add_subdirectory(test5) + diff --git a/src/pal/tests/palsuite/threading/OpenEventW/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/OpenEventW/test1/CMakeLists.txt new file mode 100644 index 0000000000..2bb61e3044 --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_openeventw_test1 + ${SOURCES} +) + +add_dependencies(paltest_openeventw_test1 coreclrpal) + +target_link_libraries(paltest_openeventw_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/OpenEventW/test1/test1.c b/src/pal/tests/palsuite/threading/OpenEventW/test1/test1.c new file mode 100644 index 0000000000..9dcb3a4a68 --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/test1/test1.c @@ -0,0 +1,134 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================ +** +** Source: test1.c +** +** Purpose: Test for OpenEventW. This test creates an event, +** opens a handle to the same event, then waits on both handles +** in both a signalled and non-signalled state to verify they're. +** pointing to the same event object. +** +** +**==========================================================================*/ +#include <palsuite.h> + +int __cdecl main(int argc, char **argv) +{ + BOOL bRet = FAIL; + DWORD dwRet; + HANDLE hEvent; + HANDLE hOpenEvent; + WCHAR theName[] = {'E','v','e','n','t','\0'}; + LPCWSTR lpName = theName; + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return( FAIL ); + } + + /* Create an event (with a 0 intial state!) and ensure that the + HANDLE is valid + */ + hEvent = CreateEventW( NULL, TRUE, FALSE, lpName ); + if( hEvent == NULL ) + { + Fail( "ERROR:%lu:CreateEvent call failed\n", GetLastError() ); + } + + + /* Call OpenEventW to get another HANDLE on + this event. Ensure the HANDLE is valid. + */ + hOpenEvent = OpenEventW( EVENT_ALL_ACCESS, TRUE, lpName ); + if( hOpenEvent == NULL ) + { + Trace( "ERROR:%lu:OpenEventW call failed\n", GetLastError() ); + goto cleanup2; + } + + /* wait on the original event to verify that it's not signalled */ + dwRet = WaitForSingleObject( hEvent, 0 ); + if( dwRet != WAIT_TIMEOUT ) + { + Trace( "ERROR:WaitForSingleObject returned %lu, " + "expected WAIT_TIMEOUT\n", + dwRet ); + goto cleanup; + } + + /* wait on the opened event to verify that it's not signalled either */ + dwRet = WaitForSingleObject( hOpenEvent, 0 ); + if( dwRet != WAIT_TIMEOUT ) + { + Trace( "ERROR:WaitForSingleObject returned %lu, " + "expected WAIT_TIMEOUT\n", + dwRet ); + goto cleanup; + } + + + /* Set this opened HANDLE */ + if( ! SetEvent( hOpenEvent ) ) + { + Trace( "ERROR:%lu:SetEvent call failed\n", GetLastError() ); + goto cleanup; + } + + /* wait on the original event to verify that it's signalled */ + dwRet = WaitForSingleObject( hEvent, 0 ); + if( dwRet != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject returned %lu, " + "expected WAIT_OBJECT_0\n", + dwRet ); + goto cleanup; + } + + /* wait on the opened event to verify that it's signalled too */ + dwRet = WaitForSingleObject( hOpenEvent, 0 ); + if( dwRet != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject returned %lu, " + "expected WAIT_OBJECT_0\n", + dwRet ); + goto cleanup; + } + + /* success if we get here */ + bRet = PASS; + +cleanup: + /* close the opened handle */ + if( ! CloseHandle( hOpenEvent ) ) + { + Trace( "ERROR:%lu:CloseHandle call failed\n", GetLastError() ); + bRet = FAIL; + } + +cleanup2: + /* close the original event handle */ + if( ! CloseHandle( hEvent ) ) + { + Trace( "ERROR:%lu:CloseHandle call failed\n", GetLastError() ); + bRet = FAIL; + } + + /* check for failure */ + if( bRet == FAIL ) + { + Fail( "test failed\n" ); + } + + + /* terminate the PAL */ + PAL_Terminate(); + + /* return success */ + return ( PASS ); + +} + diff --git a/src/pal/tests/palsuite/threading/OpenEventW/test1/testinfo.dat b/src/pal/tests/palsuite/threading/OpenEventW/test1/testinfo.dat new file mode 100644 index 0000000000..cc9be71042 --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/test1/testinfo.dat @@ -0,0 +1,15 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = OpenEventW +Name = Positive Test for OpenEventW +TYPE = DEFAULT +EXE1 = test1 +Description += Purpose: Test for OpenEventW. This test creates an event, += opens a handle to the same event, then waits on both handles += in both a signalled and non-signalled state to verify they're += pointing to the same event object. diff --git a/src/pal/tests/palsuite/threading/OpenEventW/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/OpenEventW/test2/CMakeLists.txt new file mode 100644 index 0000000000..48fa1f2c4b --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test2.c +) + +add_executable(paltest_openeventw_test2 + ${SOURCES} +) + +add_dependencies(paltest_openeventw_test2 coreclrpal) + +target_link_libraries(paltest_openeventw_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/OpenEventW/test2/test2.c b/src/pal/tests/palsuite/threading/OpenEventW/test2/test2.c new file mode 100644 index 0000000000..9cbf872b95 --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/test2/test2.c @@ -0,0 +1,194 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test2.c +** +** Purpose: Positive test for OpenEventW. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** CreateEvent +** CloseHandle +** WaitForSingleObject +** +** Purpose: +** +** Test to ensure proper operation of the OpenEventW() +** API by creating a new named event and verifying that +** it can be used interchangeably by setting the event +** with the original handle and waiting on it with the +** new one, then resetting it with the new one and waiting +** on it with the original one. +** +** +**===========================================================================*/ +#include <palsuite.h> + + + +int __cdecl main( int argc, char **argv ) + +{ + /* local variables */ + BOOL ret = FAIL; + DWORD dwRet = 0; + HANDLE hEvent = NULL; + HANDLE hTestEvent = NULL; + LPSECURITY_ATTRIBUTES lpEventAttributes = NULL; + BOOL bManualReset = TRUE; + BOOL bInitialState = FALSE; + WCHAR wcName[] = {'W','o','o','B','a','b','y','\0'}; + LPWSTR lpName = wcName; + + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return( FAIL ); + } + + + /* create an event which we can use with SetEvent */ + hEvent = CreateEventW( lpEventAttributes, + bManualReset, + bInitialState, + lpName ); + + if( hEvent == NULL ) + { + /* ERROR */ + Fail( "ERROR:%lu:CreateEvent() call failed\n", GetLastError() ); + } + + /* open a new handle to our event */ + hTestEvent = OpenEventW(EVENT_ALL_ACCESS, /* we want all rights */ + FALSE, /* no inherit */ + lpName ); + + if( hTestEvent == NULL ) + { + /* ERROR */ + Trace( "ERROR:%lu:OpenEventW() call failed\n", GetLastError() ); + goto cleanup; + } + + /* verify that the event isn't signalled yet by waiting on both */ + /* handles to the event object */ + dwRet = WaitForSingleObject( hEvent, 0 ); + if( dwRet != WAIT_TIMEOUT ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_TIMEOUT\n", + dwRet ); + goto cleanup; + } + + dwRet = WaitForSingleObject( hTestEvent, 0 ); + if( dwRet != WAIT_TIMEOUT ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_TIMEOUT\n", + dwRet ); + goto cleanup; + } + + + /* set the event using the original handle */ + if( ! SetEvent( hEvent ) ) + { + /* ERROR */ + Trace( "ERROR:%lu:SetEvent() call failed\n", GetLastError() ); + goto cleanup; + } + + /* verify that the event is signalled by waiting on both handles */ + dwRet = WaitForSingleObject( hEvent, 0 ); + if( dwRet != WAIT_OBJECT_0 ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_OBJECT_0\n", + dwRet ); + goto cleanup; + } + + dwRet = WaitForSingleObject( hTestEvent, 0 ); + if( dwRet != WAIT_OBJECT_0 ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_OBJECT_0\n", + dwRet ); + goto cleanup; + } + + /* reset the event using the new handle */ + if( ! ResetEvent( hTestEvent ) ) + { + /* ERROR */ + Trace( "ERROR:%lu:ResetEvent() call failed\n", GetLastError() ); + goto cleanup; + } + + /* verify that the event isn't signalled by waiting on both handles */ + dwRet = WaitForSingleObject( hEvent, 0 ); + if( dwRet != WAIT_TIMEOUT ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_TIMEOUT\n", + dwRet ); + goto cleanup; + } + + dwRet = WaitForSingleObject( hTestEvent, 0 ); + if( dwRet != WAIT_TIMEOUT ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_TIMEOUT\n", + dwRet ); + goto cleanup; + } + + + /* test was successful */ + ret = PASS; + + +cleanup: + /* close the new event handle */ + if( hTestEvent != NULL ) + { + if( ! CloseHandle( hTestEvent ) ) + { + ret = FAIL; + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + } + + /* close the original event handle */ + if( ! CloseHandle( hEvent ) ) + { + ret = FAIL; + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + + /* failure message */ + if( ret != PASS ) + { + Fail( "Test failed\n" ); + } + + + /* PAL termination */ + PAL_Terminate(); + + /* return success or failure */ + return ret; +} diff --git a/src/pal/tests/palsuite/threading/OpenEventW/test2/testinfo.dat b/src/pal/tests/palsuite/threading/OpenEventW/test2/testinfo.dat new file mode 100644 index 0000000000..ad3f22eea0 --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/test2/testinfo.dat @@ -0,0 +1,17 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = OpenEventW +Name = Positive test for OpenEventW +TYPE = DEFAULT +EXE1 = test2 +Description += Test to ensure proper operation of the OpenEventW() += API by creating a new named event and verifying that += it can be used interchangeably by setting the event += with the original handle and waiting on it with the += new one, then resetting it with the new one and waiting += on it with the original one. diff --git a/src/pal/tests/palsuite/threading/OpenEventW/test3/CMakeLists.txt b/src/pal/tests/palsuite/threading/OpenEventW/test3/CMakeLists.txt new file mode 100644 index 0000000000..433a3a255c --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/test3/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(TESTSOURCES + test3.c +) + +add_executable(paltest_openeventw_test3 + ${TESTSOURCES} +) + +add_dependencies(paltest_openeventw_test3 coreclrpal) + +target_link_libraries(paltest_openeventw_test3 + pthread + m + coreclrpal +) + + +set(HELPERSOURCES + childprocess.c +) + +add_executable(paltest_openeventw_test3_child + ${HELPERSOURCES} +) + +add_dependencies(paltest_openeventw_test3_child coreclrpal) + +target_link_libraries(paltest_openeventw_test3_child + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/OpenEventW/test3/childprocess.c b/src/pal/tests/palsuite/threading/OpenEventW/test3/childprocess.c new file mode 100644 index 0000000000..b5149e006f --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/test3/childprocess.c @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: childprocess.c +** +** Purpose: Test to ensure that OpenEventW() works when +** opening an event created by another process. The test +** program launches this program as a child, which creates +** a named, initially-unset event. The child waits up to +** 10 seconds for the parent process to open that event +** and set it, and returns PASS if the event was set or FAIL +** otherwise. The parent process checks the return value +** from the child to verify that the opened event was +** properly used across processes. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** CreateEventW +** WaitForSingleObject +** CloseHandle +** +** +**=========================================================*/ + +#include <palsuite.h> + +int __cdecl main( int argc, char **argv ) +{ + /* local variables */ + HANDLE hEvent = NULL; + WCHAR wcName[] = {'P','A','L','R','o','c','k','s','\0'}; + LPWSTR lpName = wcName; + + int result = PASS; + + /* initialize the PAL */ + if( PAL_Initialize(argc, argv) != 0 ) + { + return( FAIL ); + } + + + /* open a handle to the event created in the child process */ + hEvent = OpenEventW( EVENT_ALL_ACCESS, /* we want all rights */ + FALSE, /* no inherit */ + lpName ); + + if( hEvent == NULL ) + { + /* ERROR */ + Trace( "ERROR:%lu:OpenEventW() call failed\n", GetLastError() ); + result = FAIL; + goto parentwait; + } + + /* set the event -- should take effect in the child process */ + if( ! SetEvent( hEvent ) ) + { + /* ERROR */ + Trace( "ERROR:%lu:SetEvent() call failed\n", GetLastError() ); + result = FAIL; + } + +parentwait: + /* close the event handle */ + if( ! CloseHandle( hEvent ) ) + { + /* ERROR */ + Fail( "ERROR:%lu:CloseHandle() call failed in child\n", + GetLastError()); + } + + /* terminate the PAL */ + PAL_TerminateEx(result); + + /* return success or failure */ + return result; +} diff --git a/src/pal/tests/palsuite/threading/OpenEventW/test3/test3.c b/src/pal/tests/palsuite/threading/OpenEventW/test3/test3.c new file mode 100644 index 0000000000..10a678a107 --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/test3/test3.c @@ -0,0 +1,187 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test3.c +** +** Purpose: Test to ensure that OpenEventW() works when +** opening an event created by another process. This test +** program launches a child process which creates a +** named, initially-unset event. The child waits up to +** 10 seconds for the parent process to open that event +** and set it, and returns PASS if the event was set or FAIL +** otherwise. The parent process checks the return value +** from the child to verify that the opened event was +** properly used across processes. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** Fail +** ZeroMemory +** GetCurrentDirectoryW +** CreateProcessW +** WaitForSingleObject +** GetExitCodeProcess +** GetLastError +** strlen +** strncpy +** +** +**===========================================================================*/ +#include <palsuite.h> + +#define TIMEOUT 60000 + +int __cdecl main( int argc, char **argv ) +{ + BOOL ret = FAIL; + LPSECURITY_ATTRIBUTES lpEventAttributes = NULL; + + STARTUPINFO si; + PROCESS_INFORMATION pi; + + DWORD dwExitCode; + + DWORD dwRet = 0; + HANDLE hEvent = NULL; + WCHAR wcName[] = {'P','A','L','R','o','c','k','s','\0'}; + LPWSTR lpName = wcName; + char lpCommandLine[MAX_PATH] = ""; + + /* initialize the PAL */ + if( PAL_Initialize(argc, argv) != 0 ) + { + return( FAIL ); + } + + /* zero our process and startup info structures */ + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof( si ); + ZeroMemory( &pi, sizeof(pi) ); + + /* create an event which we can use with SetEvent */ + hEvent = CreateEventW( lpEventAttributes, + TRUE, /* manual reset */ + FALSE, /* unsignalled */ + lpName ); + + if( hEvent == NULL ) + { + /* ERROR */ + Fail( "ERROR:%lu:CreateEventW() call failed in child\n", + GetLastError()); + } + + ZeroMemory( lpCommandLine, MAX_PATH ); + if ( _snprintf( lpCommandLine, MAX_PATH-1, "childprocess ") < 0 ) + { + Fail ("Error: Insufficient lpCommandline for\n"); + } + + /* launch the child process */ + if( !CreateProcess( NULL, /* module name to execute */ + lpCommandLine, /* command line */ + NULL, /* process handle not */ + /* inheritable */ + NULL, /* thread handle not */ + /* inheritable */ + FALSE, /* handle inheritance */ + CREATE_NEW_CONSOLE, /* dwCreationFlags */ + NULL, /* use parent's environment */ + NULL, /* use parent's starting */ + /* directory */ + &si, /* startup info struct */ + &pi ) /* process info struct */ + ) + { + Fail( "ERROR:%lu:CreateProcess call failed\n", + GetLastError() ); + } + + /* verify that the event is signalled by the child process */ + dwRet = WaitForSingleObject( hEvent, TIMEOUT ); + if( dwRet != WAIT_OBJECT_0 ) + { + ret = FAIL; + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_TIMEOUT\n", + "expected WAIT_OBJECT_0\n", + dwRet ); + + goto cleanup; + + if( !CloseHandle( hEvent ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed in child\n", + GetLastError()); + } + goto cleanup; + } + + /* wait for the child process to complete */ + dwRet = WaitForSingleObject ( pi.hProcess, TIMEOUT ); + if( dwRet != WAIT_OBJECT_0 ) + { + ret = FAIL; + Trace( "ERROR:WaitForSingleObject() returned %lu, " + "expected %lu\n", + dwRet, + WAIT_OBJECT_0 ); + goto cleanup; + } + + /* check the exit code from the process */ + if( ! GetExitCodeProcess( pi.hProcess, &dwExitCode ) ) + { + ret = FAIL; + Trace( "ERROR:%lu:GetExitCodeProcess call failed\n", + GetLastError() ); + goto cleanup; + } + + /* check for success */ + ret = (dwExitCode == PASS) ? PASS : FAIL; + +cleanup: + if( hEvent != NULL ) + { + if( ! CloseHandle ( hEvent ) ) + { + Trace( "ERROR:%lu:CloseHandle call failed on event handle\n", + GetLastError() ); + ret = FAIL; + } + } + + + /* close process and thread handle */ + if( ! CloseHandle ( pi.hProcess ) ) + { + Trace( "ERROR:%lu:CloseHandle call failed on process handle\n", + GetLastError() ); + ret = FAIL; + } + + if( ! CloseHandle ( pi.hThread ) ) + { + Trace( "ERROR:%lu:CloseHandle call failed on thread handle\n", + GetLastError() ); + ret = FAIL; + } + + /* output a convenient error message and exit if we failed */ + if( ret == FAIL ) + { + Fail( "test failed\n" ); + } + + + /* terminate the PAL */ + PAL_Terminate(); + + /* return success */ + return ret; +} diff --git a/src/pal/tests/palsuite/threading/OpenEventW/test3/testinfo.dat b/src/pal/tests/palsuite/threading/OpenEventW/test3/testinfo.dat new file mode 100644 index 0000000000..96b2c06644 --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/test3/testinfo.dat @@ -0,0 +1,21 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = OpenEventW +Name = Test for OpenEventW +TYPE = DEFAULT +EXE1 = test3 +EXE2 = childprocess +Description += Purpose: Test to ensure that OpenEventW() works when += opening an event created by another process. This test += program launches a child process which creates a += named, initially-unset event. The child waits up to += 10 seconds for the parent process to open that event += and set it, and returns PASS if the event was set or FAIL += otherwise. The parent process checks the return value += from the child to verify that the opened event was += properly used across processes. diff --git a/src/pal/tests/palsuite/threading/OpenEventW/test4/CMakeLists.txt b/src/pal/tests/palsuite/threading/OpenEventW/test4/CMakeLists.txt new file mode 100644 index 0000000000..ff17fea23b --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/test4/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test4.c +) + +add_executable(paltest_openeventw_test4 + ${SOURCES} +) + +add_dependencies(paltest_openeventw_test4 coreclrpal) + +target_link_libraries(paltest_openeventw_test4 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/OpenEventW/test4/test4.c b/src/pal/tests/palsuite/threading/OpenEventW/test4/test4.c new file mode 100644 index 0000000000..ae657a0511 --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/test4/test4.c @@ -0,0 +1,112 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test4.c +** +** Purpose: Positive test for OpenEventW. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** CreateEvent +** CloseHandle +** WaitForSingleObject +** +** Purpose: +** +** Test to ensure proper operation of the OpenEventW() +** API by trying to open an event with a name that is +** already taken by a non-event object. +** +** +**===========================================================================*/ +#include <palsuite.h> + + + +int __cdecl main( int argc, char **argv ) + +{ + /* local variables */ + BOOL bRet = PASS; + DWORD dwLastError = 0; + HANDLE hMutex = NULL; + HANDLE hTestEvent = NULL; + LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL; + BOOL bInitialState = TRUE; + WCHAR wcName[] = {'I','m','A','M','u','t','e','x','\0'}; + LPWSTR lpName = wcName; + + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return( FAIL ); + } + + /* create a mutex object */ + hMutex = CreateMutexW( lpSecurityAttributes, + bInitialState, + lpName ); + + if( hMutex == NULL ) + { + /* ERROR */ + Fail( "ERROR:%lu:CreateMutexW() call failed\n", GetLastError() ); + } + + /* open a new handle to our event */ + hTestEvent = OpenEventW(EVENT_ALL_ACCESS, /* we want all rights */ + FALSE, /* no inherit */ + lpName ); + + if( hTestEvent != NULL ) + { + /* ERROR */ + Trace( "ERROR:OpenEventW() call succeeded against a named " + "mutex, should have returned NULL\n" ); + if( ! CloseHandle( hTestEvent ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed \n", GetLastError() ); + } + bRet = FAIL; + } + else + { + dwLastError = GetLastError(); + if( dwLastError != ERROR_INVALID_HANDLE ) + { + /* ERROR */ + Trace( "ERROR:OpenEventW() call failed against a named " + "mutex, but returned an unexpected result: %lu\n", + dwLastError ); + bRet = FAIL; + } + } + + + /* close the mutex handle */ + if( ! CloseHandle( hMutex ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed \n", GetLastError() ); + bRet = FAIL; + } + + + /* fail here if we weren't successful */ + if( bRet == FAIL ) + { + Fail( "" ); + } + + + /* PAL termination */ + PAL_Terminate(); + + /* return success or failure */ + return PASS; +} + + diff --git a/src/pal/tests/palsuite/threading/OpenEventW/test4/testinfo.dat b/src/pal/tests/palsuite/threading/OpenEventW/test4/testinfo.dat new file mode 100644 index 0000000000..1b3f2d83c6 --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/test4/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = OpenEventW +Name = Negative test for OpenEventW +TYPE = DEFAULT +EXE1 = test4 +Description += Test to ensure proper operation of the OpenEventW() += API by trying to open an event with a name that is += already taken by a non-event object. diff --git a/src/pal/tests/palsuite/threading/OpenEventW/test5/CMakeLists.txt b/src/pal/tests/palsuite/threading/OpenEventW/test5/CMakeLists.txt new file mode 100644 index 0000000000..b78d7fbfc0 --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/test5/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test5.c +) + +add_executable(paltest_openeventw_test5 + ${SOURCES} +) + +add_dependencies(paltest_openeventw_test5 coreclrpal) + +target_link_libraries(paltest_openeventw_test5 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/OpenEventW/test5/test5.c b/src/pal/tests/palsuite/threading/OpenEventW/test5/test5.c new file mode 100644 index 0000000000..43b585765c --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/test5/test5.c @@ -0,0 +1,197 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test5.c +** +** Purpose: Positive test for OpenEventW. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** CreateEvent +** CloseHandle +** WaitForSingleObject +** +** Purpose: +** +** Test to ensure proper operation of the OpenEventW() +** API by creating a new named event with CreateEventA() +** and verifying that it can be opened with OpenEventW(). +** It should be possible to use the event handles +** interchangeably, we test by setting the event with the +** original handle and waiting on it with the new one, +** then resetting it with the new one and waiting +** on it with the original one. +** +** +**===========================================================================*/ +#include <palsuite.h> + + + +int __cdecl main( int argc, char **argv ) + +{ + /* local variables */ + BOOL ret = FAIL; + DWORD dwRet = 0; + HANDLE hEvent = NULL; + HANDLE hTestEvent = NULL; + LPSECURITY_ATTRIBUTES lpEventAttributes = NULL; + BOOL bManualReset = TRUE; + BOOL bInitialState = FALSE; + LPSTR lpNameA = "ShakeIt"; + WCHAR wcName[] = {'S','h','a','k','e','I','t','\0'}; + LPWSTR lpNameW = wcName; + + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return( FAIL ); + } + + + /* create an event which we can use with SetEvent */ + hEvent = CreateEventA( lpEventAttributes, + bManualReset, + bInitialState, + lpNameA ); + + if( hEvent == NULL ) + { + /* ERROR */ + Fail( "ERROR:%lu:CreateEvent() call failed\n", GetLastError() ); + } + + /* open a new handle to our event */ + hTestEvent = OpenEventW(EVENT_ALL_ACCESS, /* we want all rights */ + FALSE, /* no inherit */ + lpNameW ); + + if( hTestEvent == NULL ) + { + /* ERROR */ + Trace( "ERROR:%lu:OpenEventW() call failed\n", GetLastError() ); + goto cleanup; + } + + /* verify that the event isn't signalled yet by waiting on both */ + /* handles to the event object */ + dwRet = WaitForSingleObject( hEvent, 0 ); + if( dwRet != WAIT_TIMEOUT ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_TIMEOUT\n", + dwRet ); + goto cleanup; + } + + dwRet = WaitForSingleObject( hTestEvent, 0 ); + if( dwRet != WAIT_TIMEOUT ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_TIMEOUT\n", + dwRet ); + goto cleanup; + } + + + /* set the event using the original handle */ + if( ! SetEvent( hEvent ) ) + { + /* ERROR */ + Trace( "ERROR:%lu:SetEvent() call failed\n", GetLastError() ); + goto cleanup; + } + + /* verify that the event is signalled by waiting on both handles */ + dwRet = WaitForSingleObject( hEvent, 0 ); + if( dwRet != WAIT_OBJECT_0 ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_OBJECT_0\n", + dwRet ); + goto cleanup; + } + + dwRet = WaitForSingleObject( hTestEvent, 0 ); + if( dwRet != WAIT_OBJECT_0 ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_OBJECT_0\n", + dwRet ); + goto cleanup; + } + + /* reset the event using the new handle */ + if( ! ResetEvent( hTestEvent ) ) + { + /* ERROR */ + Trace( "ERROR:%lu:ResetEvent() call failed\n", GetLastError() ); + goto cleanup; + } + + /* verify that the event isn't signalled by waiting on both handles */ + dwRet = WaitForSingleObject( hEvent, 0 ); + if( dwRet != WAIT_TIMEOUT ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_TIMEOUT\n", + dwRet ); + goto cleanup; + } + + dwRet = WaitForSingleObject( hTestEvent, 0 ); + if( dwRet != WAIT_TIMEOUT ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_TIMEOUT\n", + dwRet ); + goto cleanup; + } + + + /* test was successful */ + ret = PASS; + + +cleanup: + /* close the new event handle */ + if( hTestEvent != NULL ) + { + if( ! CloseHandle( hTestEvent ) ) + { + ret = FAIL; + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + } + + /* close the original event handle */ + if( ! CloseHandle( hEvent ) ) + { + ret = FAIL; + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + + /* failure message */ + if( ret != PASS ) + { + Fail( "Test failed\n" ); + } + + + /* PAL termination */ + PAL_Terminate(); + + /* return success or failure */ + return ret; +} diff --git a/src/pal/tests/palsuite/threading/OpenEventW/test5/testinfo.dat b/src/pal/tests/palsuite/threading/OpenEventW/test5/testinfo.dat new file mode 100644 index 0000000000..f5af943a7c --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenEventW/test5/testinfo.dat @@ -0,0 +1,19 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = OpenEventW +Name = Positive test for OpenEventW +TYPE = DEFAULT +EXE1 = test5 +Description += Test to ensure proper operation of the OpenEventW() += API by creating a new named event with CreateEventA() += and verifying that it can be opened with OpenEventW(). += It should be possible to use the event handles += interchangeably, we test by setting the event with the += original handle and waiting on it with the new one, += then resetting it with the new one and waiting += on it with the original one. diff --git a/src/pal/tests/palsuite/threading/OpenProcess/CMakeLists.txt b/src/pal/tests/palsuite/threading/OpenProcess/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenProcess/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/threading/OpenProcess/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/OpenProcess/test1/CMakeLists.txt new file mode 100644 index 0000000000..26d30547a9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenProcess/test1/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(TESTSOURCES + test1.c +) + +add_executable(paltest_openprocess_test1 + ${TESTSOURCES} +) + +add_dependencies(paltest_openprocess_test1 coreclrpal) + +target_link_libraries(paltest_openprocess_test1 + pthread + m + coreclrpal +) + + +set(HELPERSOURCES + childProcess.c +) + +add_executable(paltest_openprocess_test1_child + ${HELPERSOURCES} +) + +add_dependencies(paltest_openprocess_test1_child coreclrpal) + +target_link_libraries(paltest_openprocess_test1_child + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/OpenProcess/test1/childProcess.c b/src/pal/tests/palsuite/threading/OpenProcess/test1/childProcess.c new file mode 100644 index 0000000000..9ef07433fd --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenProcess/test1/childProcess.c @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: childprocess.c +** +** Purpose: Test to ensure OpenProcess works properly. +** All this program does is return a predefined value. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** CreateMutexW +** WaitForSingleObject +** CloseHandle +** +** +**=========================================================*/ + +#include <palsuite.h> +#include "myexitcode.h" + + +int __cdecl main( int argc, char **argv ) +{ + HANDLE hMutex; + WCHAR wszMutexName[] = { 'T','E','S','T','1','\0' }; + DWORD dwRet; + int i; + + /* initialize the PAL */ + if( PAL_Initialize(argc, argv) != 0 ) + { + return( FAIL ); + } + + /* open a mutex to synchronize with the parent process */ + hMutex = CreateMutexW( NULL, FALSE, wszMutexName ); + if( hMutex == NULL ) + { + Fail( "ERROR:%lu:CreateMutex() call failed\r\n", GetLastError() ); + } + + /* acquire the mutex lock */ + dwRet = WaitForSingleObject( hMutex, 10000 ); + if( dwRet != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject() returned %lu, " + "expected WAIT_OBJECT_0", + dwRet ); + if( ! CloseHandle( hMutex ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + Fail( "test failed\n" ); + } + + + /* simulate some activity */ + for( i=0; i<50000; i++ ) + ; + + /* close our mutex handle */ + if( ! CloseHandle( hMutex ) ) + { + Fail( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + + /* terminate the PAL */ + PAL_Terminate(); + + /* return the predefined exit code */ + return TEST_EXIT_CODE; +} diff --git a/src/pal/tests/palsuite/threading/OpenProcess/test1/myexitcode.h b/src/pal/tests/palsuite/threading/OpenProcess/test1/myexitcode.h new file mode 100644 index 0000000000..66b8f43a97 --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenProcess/test1/myexitcode.h @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================ +** +** Source: myexitcode.h +** +** Purpose: Define an exit code. +** +** +**==========================================================================*/ + +#define TEST_EXIT_CODE 317 diff --git a/src/pal/tests/palsuite/threading/OpenProcess/test1/test1.c b/src/pal/tests/palsuite/threading/OpenProcess/test1/test1.c new file mode 100644 index 0000000000..d0f9019646 --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenProcess/test1/test1.c @@ -0,0 +1,282 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test1.c +** +** Purpose: Test to ensure OpenProcess works properly. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** Fail +** ZeroMemory +** GetCurrentDirectoryW +** CreateProcessW +** WaitForSingleObject +** CreateMutexW +** ReleaseMutex +** CloseHandle +** GetLastError +** strlen +** strncpy +** +** +**===========================================================================*/ +#include <palsuite.h> +#include "myexitcode.h" + + +static const char* rgchPathDelim = "\\"; + + +int +mkAbsoluteFilename( LPSTR dirName, + DWORD dwDirLength, + LPCSTR fileName, + DWORD dwFileLength, + LPSTR absPathName ) +{ + DWORD sizeDN, sizeFN, sizeAPN; + + sizeDN = strlen( dirName ); + sizeFN = strlen( fileName ); + sizeAPN = (sizeDN + 1 + sizeFN + 1); + + /* ensure ((dirName + DELIM + fileName + \0) =< _MAX_PATH ) */ + if( sizeAPN > _MAX_PATH ) + { + return ( 0 ); + } + + strncpy( absPathName, dirName, dwDirLength +1 ); + strncpy( absPathName, rgchPathDelim, 2 ); + strncpy( absPathName, fileName, dwFileLength +1 ); + + return (sizeAPN); + +} + + +int __cdecl main( int argc, char **argv ) + +{ + const char* rgchChildFile = "childprocess"; + + STARTUPINFO si; + PROCESS_INFORMATION pi; + + DWORD dwError; + DWORD dwExitCode; + DWORD dwFileLength; + DWORD dwDirLength; + DWORD dwSize; + DWORD dwRet; + + HANDLE hMutex; + HANDLE hChildProcess; + + char rgchDirName[_MAX_DIR]; + char absPathBuf[_MAX_PATH]; + char* rgchAbsPathName; + + BOOL ret = FAIL; + BOOL bChildDone = FALSE; + WCHAR wszMutexName[] = { 'T','E','S','T','1','\0' }; + + /* initialize the PAL */ + if( PAL_Initialize(argc, argv) != 0 ) + { + return( FAIL ); + } + + /* create a mutex to synchronize with the child process */ + hMutex = CreateMutexW( NULL, TRUE, wszMutexName ); + if( hMutex == NULL ) + { + Fail( "ERROR:%lu:CreateMutex() call failed\r\n", GetLastError() ); + } + + /* zero our process and startup info structures */ + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof( si ); + ZeroMemory( &pi, sizeof(pi) ); + + /* build the absolute path to the child process */ + rgchAbsPathName = &absPathBuf[0]; + dwFileLength = strlen( rgchChildFile ); + + dwDirLength = GetCurrentDirectory( _MAX_PATH, rgchDirName ); + if( dwDirLength == 0 ) + { + dwError = GetLastError(); + if( ReleaseMutex( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); + } + if( CloseHandle( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + Fail( "GetCurrentDirectory call failed with error code %d\n", + dwError ); + } + + dwSize = mkAbsoluteFilename( rgchDirName, + dwDirLength, + rgchChildFile, + dwFileLength, + rgchAbsPathName ); + if( dwSize == 0 ) + { + if( ReleaseMutex( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); + } + if( CloseHandle( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + Fail( "Palsuite Code: mkAbsoluteFilename() call failed. Could ", + "not build absolute path name to file\n. Exiting.\n" ); + } + + /* launch the child process */ + if( !CreateProcess( NULL, /* module name to execute */ + rgchAbsPathName, /* command line */ + NULL, /* process handle not */ + /* inheritable */ + NULL, /* thread handle not */ + /*inheritable */ + FALSE, /* handle inheritance */ + CREATE_NEW_CONSOLE, /* dwCreationFlags */ + NULL, /* use parent's environment */ + NULL, /* use parent's starting */ + /* directory */ + &si, /* startup info struct */ + &pi ) /* process info struct */ + ) + { + dwError = GetLastError(); + if( ReleaseMutex( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); + } + if( CloseHandle( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + Fail( "CreateProcess call failed with error code %d\n", + dwError ); + } + + /* open another handle to the child process */ + hChildProcess = OpenProcess( PROCESS_ALL_ACCESS, /* access */ + FALSE, /* inheritable */ + pi.dwProcessId /* process id */ + ); + if( hChildProcess == NULL ) + { + dwError = GetLastError(); + if( ReleaseMutex( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); + } + Trace( "ERROR:%lu:OpenProcess call failed\n", dwError ); + goto cleanup2; + } + + /* release the mutex so the child can proceed */ + if( ReleaseMutex( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); + goto cleanup; + } + + /* wait for the child process to complete, using the new handle */ + dwRet = WaitForSingleObject( hChildProcess, 10000 ); + if( dwRet != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject call returned %lu, " + "expected WAIT_OBJECT_0", + dwRet ); + goto cleanup; + } + + /* remember that we waited until the child was finished */ + bChildDone = TRUE; + + /* check the exit code from the process -- this is a bit of an */ + /* extra verification that we opened the correct process handle */ + if( ! GetExitCodeProcess( hChildProcess, &dwExitCode ) ) + { + Trace( "ERROR:%lu:GetExitCodeProcess call failed\n", GetLastError() ); + goto cleanup; + } + + /* verification */ + if( (dwExitCode & 0xFF) != (TEST_EXIT_CODE & 0xFF) ) + { + Trace( "GetExitCodeProcess returned an incorrect exit code %d, " + "expected value is %d\n", + (dwExitCode & 0xFF), + (TEST_EXIT_CODE & 0xFF)); + goto cleanup; + } + + /* success if we get here */ + ret = PASS; + + +cleanup: + /* wait on the child process to complete if necessary */ + if( ! bChildDone ) + { + dwRet = WaitForSingleObject( hChildProcess, 10000 ); + if( dwRet != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject call returned %lu, " + "expected WAIT_OBJECT_0", + dwRet ); + ret = FAIL; + } + } + + /* close all our handles */ + if( CloseHandle ( hChildProcess ) == 0 ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + ret = FAIL; + } + +cleanup2: + if( CloseHandle ( pi.hProcess ) == 0 ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + ret = FAIL; + } + if( CloseHandle ( pi.hThread ) == 0 ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + ret = FAIL; + } + if( CloseHandle( hMutex ) == 0 ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + ret = FAIL; + } + + if( ret == FAIL ) + { + Fail( "test failed\n" ); + } + + + + /* terminate the PAL */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/OpenProcess/test1/testinfo.dat b/src/pal/tests/palsuite/threading/OpenProcess/test1/testinfo.dat new file mode 100644 index 0000000000..dd6b2c0ffe --- /dev/null +++ b/src/pal/tests/palsuite/threading/OpenProcess/test1/testinfo.dat @@ -0,0 +1,19 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = OpenProcess +Name = Test for OpenProcess +TYPE = DEFAULT +EXE1 = test1 +EXE2 = childprocess +Description += Test to ensure proper operation of the OpenProcess API. += The test launches a trivial child process, then opens += a handle to it using OpenProcess. It uses that handle += to wait for the child process to terminate, and then += checks the exit code of the child process in order to += verify that it was in fact a handle to the correct += process. diff --git a/src/pal/tests/palsuite/threading/QueryThreadCycleTime/CMakeLists.txt b/src/pal/tests/palsuite/threading/QueryThreadCycleTime/CMakeLists.txt new file mode 100644 index 0000000000..5e1ef7f28b --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueryThreadCycleTime/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) diff --git a/src/pal/tests/palsuite/threading/QueryThreadCycleTime/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/QueryThreadCycleTime/test1/CMakeLists.txt new file mode 100644 index 0000000000..ad3eec1a45 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueryThreadCycleTime/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_querythreadcycletime_test1 + ${SOURCES} +) + +add_dependencies(paltest_querythreadcycletime_test1 coreclrpal) + +target_link_libraries(paltest_querythreadcycletime_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/QueryThreadCycleTime/test1/test1.c b/src/pal/tests/palsuite/threading/QueryThreadCycleTime/test1/test1.c new file mode 100644 index 0000000000..54f0116bd0 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueryThreadCycleTime/test1/test1.c @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: +** +** Source : test1.c +** +** Purpose: Test for QueryThreadCycleTime() function +** +** +**=========================================================*/ + +#include <palsuite.h> + +int __cdecl main(int argc, char *argv[]) { + int ret = FAIL; + + //Test is failing unreliably, so for now we always return pass. + if (TRUE){ + ret = PASS; + goto EXIT; + } + + LONG64 Actual, Expected, Delta = 850000000; + Actual = 0; + Expected = 0; + const LONG64 MSEC_TO_NSEC = 1000000; + + /* + * Initialize the PAL and return FAILURE if this fails + */ + + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + HANDLE cThread = GetCurrentThread(); + + int i; + /* Take 2000 tiny measurements */ + for (i = 0; i < 2000; i++){ + ULONG64 FirstCount, SecondCount; + LONG64 Init; + + Sleep(1); + + /* Grab a FirstCount, then loop for a bit to make the clock increase */ + if (!QueryThreadCycleTime(cThread, (PULONG64)&FirstCount)) + { + Fail("ERROR: QueryThreadCycleTime returned failure.\n"); + } + + LONG64 x; + /* Init is in milliseconds, so we will convert later */ + Init = (LONG64)GetTickCount(); + x = Init + 3; + volatile int counter; + do { + for (counter = 0; counter < 100000; counter++) + { + // spin to consume CPU time + } + + } while (x > GetTickCount()); + Expected += (GetTickCount() - Init) * MSEC_TO_NSEC; + /* Get a second count */ + if (!QueryThreadCycleTime(cThread, (PULONG64)&SecondCount)) + { + Fail("ERROR: QueryThreadCycleTime returned failure.\n"); + } + + LONG64 trial = (LONG64)SecondCount - (LONG64)FirstCount; + if (trial < 0){ + printf("Negative value %llu measured", trial); + } + Actual += (trial); + + } + + + + if(labs(Expected - Actual) > Delta) + { + Fail("ERROR: The measured time (%llu millisecs) was not within Delta %llu " + "of the expected time (%llu millisecs).\n", + (Actual / MSEC_TO_NSEC), (Delta / MSEC_TO_NSEC), (Expected / MSEC_TO_NSEC)); + } + //printf("%llu, %llu\n", Expected, Actual); + PAL_Terminate(); + ret = PASS; + +EXIT: + return ret; +} diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/CMakeLists.txt b/src/pal/tests/palsuite/threading/QueueUserAPC/CMakeLists.txt new file mode 100644 index 0000000000..19ee487a6a --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) +add_subdirectory(test3) +add_subdirectory(test4) +add_subdirectory(test5) +add_subdirectory(test6) +add_subdirectory(test7) + diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/QueueUserAPC/test1/CMakeLists.txt new file mode 100644 index 0000000000..54a4d03719 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_queueuserapc_test1 + ${SOURCES} +) + +add_dependencies(paltest_queueuserapc_test1 coreclrpal) + +target_link_libraries(paltest_queueuserapc_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test1/test1.c b/src/pal/tests/palsuite/threading/QueueUserAPC/test1/test1.c new file mode 100644 index 0000000000..3637897ba7 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test1/test1.c @@ -0,0 +1,313 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test1.c +** +** Purpose: Tests that APCs sent to a thread in an alertable state via +** QueueUserAPC are executed in FIFO order. Also tests that the APC +** function is executed within the context of the correct thread and +** that the dwData parameter gets sent correctly. +** +** +**===================================================================*/ + +#include <palsuite.h> + +const int ChildThreadSleepTime = 2000; +const int InterruptTime = 1000; + +VOID PALAPI APCFuncA(ULONG_PTR dwParam); +VOID PALAPI APCFuncB(ULONG_PTR dwParam); +VOID PALAPI APCFuncC(ULONG_PTR dwParam); +VOID PALAPI APCFuncD(ULONG_PTR dwParam); +DWORD PALAPI SleeperProc(LPVOID lpParameter); + +const char *ExpectedResults = "A0B0C0D0A1B1C1D1A2B2C2D2A3B3C3D3"; +char ResultBuffer[256]; +char *ResultPtr; +DWORD ChildThread; + +/* synchronization events */ +static HANDLE hSyncEvent1 = NULL; +static HANDLE hSyncEvent2 = NULL; + +/* thread result because we have no GetExitCodeThread() API */ +BOOL bThreadResult = FAIL; + +int __cdecl main (int argc, char **argv) +{ + HANDLE hThread = NULL; + int ret; + int i,j; + BOOL bResult = FAIL; + + PAPCFUNC APCFuncs[] = + { + APCFuncA, + APCFuncB, + APCFuncC, + APCFuncD, + }; + + /* initialize the PAL */ + if (0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + ResultPtr = ResultBuffer; + + /* create a pair of synchronization events to coordinate our threads */ + hSyncEvent1 = CreateEvent( NULL, FALSE, FALSE, NULL ); + if( hSyncEvent1 == NULL ) + { + Trace( "ERROR:%lu:CreateEvent() call failed\n", GetLastError() ); + goto cleanup; + } + + hSyncEvent2 = CreateEvent( NULL, FALSE, FALSE, NULL ); + if( hSyncEvent2 == NULL ) + { + Trace( "ERROR:%lu:CreateEvent() call failed\n", GetLastError() ); + goto cleanup; + } + + /* create a child thread which will call SleepEx */ + hThread = CreateThread( NULL, + 0, + (LPTHREAD_START_ROUTINE)SleeperProc, + 0, + 0, + &ChildThread); + + if( hThread == NULL ) + { + Trace( "ERROR:%lu:CreateThread() call failed\n", + GetLastError()); + goto cleanup; + } + + + /* wait on our synchronization event to ensure the thread is running */ + ret = WaitForSingleObject( hSyncEvent1, 20000 ); + if( ret != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject() returned %lu, " + "expected WAIT_OBJECT_0\n", + ret ); + goto cleanup; + } + + + /* queue our user APC functions on the thread */ + for (i=0; i<4; i++) + { + for (j=0; j<sizeof(APCFuncs)/sizeof(APCFuncs[0]); j++) + { + ret = QueueUserAPC(APCFuncs[j], hThread, '0' + i); + if (ret == 0) + { + Trace( "ERROR:%lu:QueueUserAPC() call failed\n", + GetLastError()); + goto cleanup; + } + } + } + + /* signal the child thread to continue */ + if( ! SetEvent( hSyncEvent2 ) ) + { + Trace( "ERROR:%lu:SetEvent() call failed\n", GetLastError() ); + goto cleanup; + } + + + /* wait on our synchronization event to ensure the other thread is done */ + ret = WaitForSingleObject( hSyncEvent1, 20000 ); + if( ret != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject() returned %lu, " + "expected WAIT_OBJECT_0\n", + ret ); + goto cleanup; + } + + + /* check that the thread executed successfully */ + if( bThreadResult == FAIL ) + { + goto cleanup; + } + + + /* check the result buffer */ + if (strcmp(ExpectedResults, ResultBuffer) != 0) + { + Trace( "FAIL:Expected the APC function calls to produce a result of " + " \"%s\", got \"%s\"\n", + ExpectedResults, + ResultBuffer ); + goto cleanup; + } + + /* success if we get here */ + bResult = PASS; + +cleanup: + /* wait for the other thread to finish */ + if( hThread != NULL ) + { + ret = WaitForSingleObject( hThread, INFINITE ); + if (ret == WAIT_FAILED) + { + Trace( "ERROR:%lu:WaitForSingleObject() returned %lu, " + "expected WAIT_OBJECT_0\n", + ret ); + bResult = FAIL; + } + } + + /* close our synchronization handles */ + if( hSyncEvent1 != NULL ) + { + if( ! CloseHandle( hSyncEvent1 ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + bResult = FAIL; + } + } + + if( hSyncEvent2 != NULL ) + { + if( ! CloseHandle( hSyncEvent2 ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + bResult = FAIL; + } + } + + if( bResult == FAIL ) + { + Fail( "test failed\n" ); + } + + /* terminate the PAL */ + PAL_Terminate(); + + /* return success */ + return PASS; +} + +VOID PALAPI APCFuncA(ULONG_PTR dwParam) +{ + char val = (int) dwParam; + + if (GetCurrentThreadId() != ChildThread) + { + Fail("Executing APC in thread %d, should be in %d!\n", + GetCurrentThreadId(), ChildThread); + } + + *ResultPtr++ = 'A'; + *ResultPtr++ = val; + *ResultPtr = 0; +} + +VOID PALAPI APCFuncB(ULONG_PTR dwParam) +{ + char val = (int) dwParam; + + if (GetCurrentThreadId() != ChildThread) + { + Fail("Executing APC in thread %d, should be in %d!\n", + GetCurrentThreadId(), ChildThread); + } + + *ResultPtr++ = 'B'; + *ResultPtr++ = val; + *ResultPtr = 0; +} + +VOID PALAPI APCFuncC(ULONG_PTR dwParam) +{ + char val = (int) dwParam; + + if (GetCurrentThreadId() != ChildThread) + { + Fail("Executing APC in thread %d, should be in %d!\n", + GetCurrentThreadId(), ChildThread); + } + + *ResultPtr++ = 'C'; + *ResultPtr++ = val; + *ResultPtr = 0; +} + +VOID PALAPI APCFuncD(ULONG_PTR dwParam) +{ + char val = (int) dwParam; + + if (GetCurrentThreadId() != ChildThread) + { + Fail("Executing APC in thread %d, should be in %d!\n", + GetCurrentThreadId(), ChildThread); + } + + *ResultPtr++ = 'D'; + *ResultPtr++ = val; + *ResultPtr = 0; +} + +/* Entry Point for child thread. All it does is call SleepEx. */ +DWORD PALAPI SleeperProc(LPVOID lpParameter) +{ + DWORD ret; + + /* signal the main thread that we're ready to proceed */ + if( ! SetEvent( hSyncEvent1 ) ) + { + Trace( "ERROR:%lu:SetEvent() call failed\n", GetLastError() ); + bThreadResult = FAIL; + goto done; + } + + /* wait for notification from the main thread */ + ret = WaitForSingleObject( hSyncEvent2, 20000 ); + if( ret != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject() returned %lu, " + "expected WAIT_OBJECT_0\n", + ret ); + bThreadResult = FAIL; + goto done; + } + + /* call SleepEx to activate any queued APCs */ + ret = SleepEx(ChildThreadSleepTime, TRUE); + if (ret != WAIT_IO_COMPLETION) + { + Trace( "ERROR:SleepEx() call returned %lu, " + "expected WAIT_IO_COMPLETION\n", + ret ); + bThreadResult = FAIL; + goto done; + } + + /* everything passed here */ + bThreadResult = PASS; + + +done: + /* signal the main thread that we're finished */ + if( ! SetEvent( hSyncEvent1 ) ) + { + Trace( "ERROR:%lu:SetEvent() call failed\n", GetLastError() ); + bThreadResult = FAIL; + } + + /* return success or failure */ + return bThreadResult; +} diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test1/testinfo.dat b/src/pal/tests/palsuite/threading/QueueUserAPC/test1/testinfo.dat new file mode 100644 index 0000000000..fbe8343d81 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test1/testinfo.dat @@ -0,0 +1,15 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = QueueUserAPC +Name = Test #1 for QueueUserAPC +TYPE = DEFAULT +EXE1 = test1 +Description +=Tests that APCs sent to a thread in an alertable state via +=QueueUserAPC are executed in FIFO order. Also tests that the APC +=function is executed within the context of the correct thread and +=that the dwData parameter gets sent correctly. diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/QueueUserAPC/test2/CMakeLists.txt new file mode 100644 index 0000000000..88cda5f7b1 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test2.c +) + +add_executable(paltest_queueuserapc_test2 + ${SOURCES} +) + +add_dependencies(paltest_queueuserapc_test2 coreclrpal) + +target_link_libraries(paltest_queueuserapc_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test2/test2.c b/src/pal/tests/palsuite/threading/QueueUserAPC/test2/test2.c new file mode 100644 index 0000000000..dc2bfdb173 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test2/test2.c @@ -0,0 +1,224 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test2.c +** +** Purpose: Tests that APCs are not executed if a thread never enters an +** alertable state after they are queued. +** +** +**===================================================================*/ + +#include <palsuite.h> + +const int ChildThreadSleepTime = 2000; +const int InterruptTime = 1000; + +DWORD ChildThread; +BOOL InAPC; + +/* synchronization events */ +static HANDLE hSyncEvent1 = NULL; +static HANDLE hSyncEvent2 = NULL; + +/* thread result because we have no GetExitCodeThread() API */ +static BOOL bThreadResult = FAIL; + + +VOID PALAPI APCFunc(ULONG_PTR dwParam) +{ + InAPC = TRUE; +} + +DWORD PALAPI SleeperProc(LPVOID lpParameter) +{ + DWORD ret; + + /* signal the main thread that we're ready to proceed */ + if( ! SetEvent( hSyncEvent1 ) ) + { + Trace( "ERROR:%lu:SetEvent() call failed\n", GetLastError() ); + bThreadResult = FAIL; + goto done; + } + + /* wait for notification from the main thread */ + ret = WaitForSingleObject( hSyncEvent2, 20000 ); + if( ret != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject() returned %lu, " + "expected WAIT_OBJECT_0\n", + ret ); + bThreadResult = FAIL; + goto done; + } + + /* call our sleep function */ + Sleep( ChildThreadSleepTime ); + + /* success if we reach here */ + bThreadResult = PASS; + + +done: + + /* signal the main thread that we're finished */ + if( ! SetEvent( hSyncEvent1 ) ) + { + Trace( "ERROR:%lu:SetEvent() call failed\n", GetLastError() ); + bThreadResult = FAIL; + } + + /* return success or failure */ + return bThreadResult; +} + + +int __cdecl main (int argc, char **argv) +{ + /* local variables */ + HANDLE hThread = 0; + int ret; + BOOL bResult = FAIL; + + /* initialize the PAL */ + if (0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + InAPC = FALSE; + + /* create a pair of synchronization events to coordinate our threads */ + hSyncEvent1 = CreateEvent( NULL, FALSE, FALSE, NULL ); + if( hSyncEvent1 == NULL ) + { + Trace( "ERROR:%lu:CreateEvent() call failed\n", GetLastError() ); + goto cleanup; + } + + hSyncEvent2 = CreateEvent( NULL, FALSE, FALSE, NULL ); + if( hSyncEvent2 == NULL ) + { + Trace( "ERROR:%lu:CreateEvent() call failed\n", GetLastError() ); + goto cleanup; + } + + /* create a child thread */ + hThread = CreateThread( NULL, + 0, + (LPTHREAD_START_ROUTINE)SleeperProc, + 0, + 0, + &ChildThread); + + if (hThread == NULL) + { + Trace( "ERROR:%lu:CreateThread() call failed\n", + GetLastError()); + goto cleanup; + } + + + /* wait on our synchronization event to ensure the thread is running */ + ret = WaitForSingleObject( hSyncEvent1, 20000 ); + if( ret != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject() returned %lu, " + "expected WAIT_OBJECT_0\n", + ret ); + goto cleanup; + } + + /* queue a user APC on the child thread */ + ret = QueueUserAPC(APCFunc, hThread, 0); + if (ret == 0) + { + Trace( "ERROR:%lu:QueueUserAPC() call failed\n", + GetLastError()); + goto cleanup; + } + + /* signal the child thread to continue */ + if( ! SetEvent( hSyncEvent2 ) ) + { + Trace( "ERROR:%lu:SetEvent() call failed\n", GetLastError() ); + goto cleanup; + } + + /* wait on our synchronization event to ensure the other thread is done */ + ret = WaitForSingleObject( hSyncEvent1, 20000 ); + if( ret != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject() returned %lu, " + "expected WAIT_OBJECT_0\n", + ret ); + goto cleanup; + } + + /* check that the thread executed successfully */ + if( bThreadResult == FAIL ) + { + goto cleanup; + } + + + /* check whether the APC function was executed */ + if( InAPC ) + { + Trace( "FAIL:APC function was executed but shouldn't have been\n" ); + goto cleanup; + } + + /* success if we reach here */ + bResult = PASS; + + +cleanup: + /* wait for the other thread to finish */ + if( hThread != NULL ) + { + ret = WaitForSingleObject( hThread, INFINITE ); + if (ret == WAIT_FAILED) + { + Trace( "ERROR:%lu:WaitForSingleObject() returned %lu, " + "expected WAIT_OBJECT_0\n", + ret ); + bResult = FAIL; + } + } + + /* close our synchronization handles */ + if( hSyncEvent1 != NULL ) + { + if( ! CloseHandle( hSyncEvent1 ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + bResult = FAIL; + } + } + + if( hSyncEvent2 != NULL ) + { + if( ! CloseHandle( hSyncEvent2 ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + bResult = FAIL; + } + } + + if( bResult == FAIL ) + { + Fail( "test failed\n" ); + } + + + /* terminate the PAL */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test2/testinfo.dat b/src/pal/tests/palsuite/threading/QueueUserAPC/test2/testinfo.dat new file mode 100644 index 0000000000..42d942df36 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test2/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = QueueUserAPC +Name = Test #2 for QueueUserAPC +TYPE = DEFAULT +EXE1 = test2 +Description +=Tests that APCs are not executed if a thread never enters an +=alertable state after they are queued. diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test3/CMakeLists.txt b/src/pal/tests/palsuite/threading/QueueUserAPC/test3/CMakeLists.txt new file mode 100644 index 0000000000..8475dcb03e --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test3/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test3.c +) + +add_executable(paltest_queueuserapc_test3 + ${SOURCES} +) + +add_dependencies(paltest_queueuserapc_test3 coreclrpal) + +target_link_libraries(paltest_queueuserapc_test3 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test3/test3.c b/src/pal/tests/palsuite/threading/QueueUserAPC/test3/test3.c new file mode 100644 index 0000000000..933f41a5b4 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test3/test3.c @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test3.c +** +** Purpose: Tests how QueueUserAPC handles an invalid thread. +** +** +**===================================================================*/ + +#include <palsuite.h> + +int __cdecl main (int argc, char **argv) +{ + int ret; + + if (0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + ret = QueueUserAPC(NULL, NULL, 0); + if (ret != 0) + { + Fail("QueueUserAPC passed with an invalid thread!\n"); + } + + PAL_Terminate(); + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test3/testinfo.dat b/src/pal/tests/palsuite/threading/QueueUserAPC/test3/testinfo.dat new file mode 100644 index 0000000000..0b96349f15 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test3/testinfo.dat @@ -0,0 +1,12 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = QueueUserAPC +Name = Test #3 for QueueUserAPC +TYPE = DEFAULT +EXE1 = test3 +Description +=Tests how QueueUserAPC handles an invalid thread. diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test4/CMakeLists.txt b/src/pal/tests/palsuite/threading/QueueUserAPC/test4/CMakeLists.txt new file mode 100644 index 0000000000..2552738b50 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test4/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test4.c +) + +add_executable(paltest_queueuserapc_test4 + ${SOURCES} +) + +add_dependencies(paltest_queueuserapc_test4 coreclrpal) + +target_link_libraries(paltest_queueuserapc_test4 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test4/test4.c b/src/pal/tests/palsuite/threading/QueueUserAPC/test4/test4.c new file mode 100644 index 0000000000..c28709db81 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test4/test4.c @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test4.c +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** GetCurrentThread +** SleepEx +** +** Purpose: +** +** Test to ensure proper operation of the QueueUserAPC() +** API by trying to queue APC functions on the current +** thread. +** +** +**===========================================================================*/ +#include <palsuite.h> + + +static BOOL bAPCExecuted = FALSE; + +VOID PALAPI APCFunc( ULONG_PTR dwParam ) +{ + bAPCExecuted = TRUE; +} + +int __cdecl main( int argc, char **argv ) + +{ + /* local variables */ + HANDLE hThread = NULL; + DWORD ret; + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return( FAIL ); + } + + /* get the current thread */ + hThread = GetCurrentThread(); + ret = QueueUserAPC( APCFunc, hThread, 0 ); + if( ret == 0 ) + { + Fail( "ERROR:%lu:QueueUserAPC call failed\n", GetLastError() ); + } + + /* call SleepEx() to put the thread in an alertable state */ + ret = SleepEx( 2000, TRUE ); + if( ret != WAIT_IO_COMPLETION ) + { + Fail( "ERROR:Expected sleep to return WAIT_IO_COMPLETION, got %lu\n", + ret ); + } + + /* check that the APC function was executed */ + if( bAPCExecuted == FALSE ) + { + Fail( "ERROR:APC function was not executed\n" ); + } + + /* PAL termination */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test4/testinfo.dat b/src/pal/tests/palsuite/threading/QueueUserAPC/test4/testinfo.dat new file mode 100644 index 0000000000..cd7b7c2f21 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test4/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = QueueUserAPC +Name = Positive test for QueueUserAPC +TYPE = DEFAULT +EXE1 = test4 +Description += Test to ensure proper operation of the QueueUserAPC() += API by trying to queue APC functions on the current += thread. diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test5/CMakeLists.txt b/src/pal/tests/palsuite/threading/QueueUserAPC/test5/CMakeLists.txt new file mode 100644 index 0000000000..c2fa10de12 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test5/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test5.c +) + +add_executable(paltest_queueuserapc_test5 + ${SOURCES} +) + +add_dependencies(paltest_queueuserapc_test5 coreclrpal) + +target_link_libraries(paltest_queueuserapc_test5 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test5/test5.c b/src/pal/tests/palsuite/threading/QueueUserAPC/test5/test5.c new file mode 100644 index 0000000000..3d26a55f59 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test5/test5.c @@ -0,0 +1,200 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test5.c +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** CreateEvent +** SetEvent +** CreateThread +** ResumeThread +** WaitForSingleObject +** CloseHandle +** +** Purpose: +** +** Test to ensure proper operation of the QueueUserAPC() +** API by trying to queue and activate APC functions on +** a thread that was created suspended, prior to resuming +** it. We're verifying the following behavior: +** +** "If an application queues an APC before the thread begins +** running, the thread begins by calling the APC function. +** After the thread calls an APC function, it calls the APC +** functions for all APCs in its APC queue." +** +** +**===========================================================================*/ +#include <palsuite.h> + + +static HANDLE hEvent = NULL; +static BOOL bAPCExecuted = FALSE; + +VOID PALAPI APCFunc( ULONG_PTR dwParam ) +{ + bAPCExecuted = TRUE; +} + +/** + * ThreadFunc + * + * Dummy thread function for APC queuing. + */ +DWORD PALAPI ThreadFunc( LPVOID param ) +{ + DWORD ret = 0; + + /* alertable wait until the global event is signalled */ + ret = WaitForSingleObject( hEvent, INFINITE ); + if( ret != WAIT_OBJECT_0 ) + { + Fail( "ERROR:WaitForSingleObject() returned %lu, " + "expected WAIT_OBJECT_0\n", + ret ); + } + + return 0; +} + + +int __cdecl main( int argc, char **argv ) + +{ + /* local variables */ + HANDLE hThread = NULL; + DWORD IDThread; + DWORD ret; + BOOL bResult = FALSE; + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return( FAIL ); + } + + /* create an event for the other thread to wait on */ + hEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); + if( hEvent == NULL ) + { + Fail( "ERROR:%lu:CreateEvent() call failed\n", GetLastError() ); + } + + /* run another dummy thread to cause notification of the library */ + hThread = CreateThread( NULL, /* no security attributes */ + 0, /* use default stack size */ + (LPTHREAD_START_ROUTINE) ThreadFunc, /* thread function */ + (LPVOID) NULL, /* pass thread index as */ + /* function argument */ + CREATE_SUSPENDED, /* create suspended */ + &IDThread ); /* returns thread id */ + + /* Check the return value for success. */ + if( hThread == NULL ) + { + /* error creating thread */ + Trace( "ERROR:%lu:CreateThread call failed\n", GetLastError() ); + if( ! CloseHandle( hEvent ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + Fail( "test failed\n" ); + } + + /* queue our APC on the suspended thread */ + ret = QueueUserAPC( APCFunc, hThread, 0 ); + if( ret == 0 ) + { + Fail( "ERROR:%lu:QueueUserAPC call failed\n", GetLastError() ); + } + + /* wait on the suspended thread */ + ret = WaitForSingleObject( hThread, 2000 ); + if( ret != WAIT_TIMEOUT ) + { + Trace( "ERROR:WaitForSingleObject() returned %lu, " + "expected WAIT_TIMEOUT\n", + ret ); + goto cleanup; + } + + /* verify that the APC function was not executed */ + if( bAPCExecuted == TRUE ) + { + Trace( "ERROR:APC function was executed for a suspended thread\n" ); + goto cleanup; + } + + /* Resume the suspended thread */ + ResumeThread( hThread ); + + /* do another wait on the resumed thread */ + ret = WaitForSingleObject( hThread, 2000 ); + + /* verify that we got a WAIT_TIMEOUT result */ + if( ret != WAIT_TIMEOUT ) + { + Trace( "ERROR:WaitForSingleObject() returned %lu, " + "expected WAIT_TIMEOUT\n", + ret ); + goto cleanup; + } + + /* check that the APC function was actually executed */ + if( bAPCExecuted == FALSE ) + { + Trace( "ERROR:APC function was not executed\n" ); + goto cleanup; + } + + /* set the success flag */ + bResult = PASS; + +cleanup: + /* signal the event so the other thread will exit */ + if( ! SetEvent( hEvent ) ) + { + Trace( "ERROR:%lu:SetEvent() call failed\n", GetLastError() ); + bResult = FAIL; + } + + /* close the global event handle */ + if( ! CloseHandle( hEvent ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + bResult = FAIL; + } + + /* wait on the other thread to complete */ + ret = WaitForSingleObject( hThread, 2000 ); + if( ret != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject() returned %lu, " + "expected WAIT_OBJECT_0\n", + ret ); + bResult = FAIL; + } + + /* close the thread handle */ + if( ! CloseHandle( hThread ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + bResult = FAIL; + } + + /* output final failure result for failure case */ + if( bResult == FAIL ) + { + Fail( "test failed\n" ); + } + + /* PAL termination */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test5/testinfo.dat b/src/pal/tests/palsuite/threading/QueueUserAPC/test5/testinfo.dat new file mode 100644 index 0000000000..f1775aabe8 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test5/testinfo.dat @@ -0,0 +1,20 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = QueueUserAPC +Name = Positive test for QueueUserAPC +TYPE = DEFAULT +EXE1 = test5 +Description += Test to ensure proper operation of the QueueUserAPC() += API by trying to queue and activate APC functions on += a thread that was created suspended, prior to resuming += it. We're verifying the following behavior: += += "If an application queues an APC before the thread begins += running, the thread begins by calling the APC function. += After the thread calls an APC function, it calls the APC += functions for all APCs in its APC queue." diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test6/CMakeLists.txt b/src/pal/tests/palsuite/threading/QueueUserAPC/test6/CMakeLists.txt new file mode 100644 index 0000000000..3fd1b1a08e --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test6/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test6.c +) + +add_executable(paltest_queueuserapc_test6 + ${SOURCES} +) + +add_dependencies(paltest_queueuserapc_test6 coreclrpal) + +target_link_libraries(paltest_queueuserapc_test6 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test6/test6.c b/src/pal/tests/palsuite/threading/QueueUserAPC/test6/test6.c new file mode 100644 index 0000000000..e2e2464726 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test6/test6.c @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test6.c +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** CreateEvent +** SetEvent +** CreateThread +** ResumeThread +** WaitForMultipleObjectsEx +** CloseHandle +** +** Purpose: +** +** Test to ensure proper operation of the QueueUserAPC() +** API by trying to queue APC functions on a thread that +** has already terminated. +** +** +**===========================================================================*/ +#include <palsuite.h> + + +static BOOL bAPCExecuted = FALSE; + +VOID PALAPI APCFunc( ULONG_PTR dwParam ) +{ + bAPCExecuted = TRUE; +} + +/** + * ThreadFunc + * + * Dummy thread function for APC queuing. + */ +DWORD PALAPI ThreadFunc( LPVOID param ) +{ + int i; + + /* simulate some activity */ + for( i=0; i<250000; i++ ) + ; + + return 0; +} + + +int __cdecl main( int argc, char **argv ) + +{ + /* local variables */ + HANDLE hThread = NULL; + DWORD IDThread; + DWORD ret; + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return( FAIL ); + } + + /* run another dummy thread to cause notification of the library */ + hThread = CreateThread( NULL, /* no security attributes */ + 0, /* use default stack size */ + (LPTHREAD_START_ROUTINE) ThreadFunc, /* thread function */ + (LPVOID) NULL, /* pass thread index as */ + /* function argument */ + CREATE_SUSPENDED, /* create suspended */ + &IDThread ); /* returns thread id */ + + /* Check the return value for success. */ + if( hThread == NULL ) + { + /* error creating thread */ + Fail( "ERROR:%lu:CreateThread call failed\n", GetLastError() ); + } + + /* Resume the suspended thread */ + ResumeThread( hThread ); + + /* wait on the other thread to complete */ + ret = WaitForSingleObject( hThread, INFINITE ); + if( ret != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject() returned %lu, " + "expected WAIT_OBJECT_0\n", + ret ); + if( ! CloseHandle( hThread ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + Fail( "test failed\n" ); + } + + /* queue our APC on the finished thread */ + ret = QueueUserAPC( APCFunc, hThread, 0 ); + if( ret != 0 ) + { + Trace( "ERROR:QueueUserAPC call succeeded on a terminated thread\n" ); + if( ! CloseHandle( hThread ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + Fail( "test failed\n" ); + } + + if( ! CloseHandle( hThread ) ) + { + Fail( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + + /* dummy check that the APC function wasn't actually executed */ + if( bAPCExecuted != FALSE ) + { + Fail( "ERROR:APC function was executed\n" ); + } + + + /* PAL termination */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test6/testinfo.dat b/src/pal/tests/palsuite/threading/QueueUserAPC/test6/testinfo.dat new file mode 100644 index 0000000000..4d806184ee --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test6/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = QueueUserAPC +Name = Negative test for QueueUserAPC +TYPE = DEFAULT +EXE1 = test6 +Description += Test to ensure proper operation of the QueueUserAPC() += API by trying to queue APC functions on a thread that += has already terminated. diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test7/CMakeLists.txt b/src/pal/tests/palsuite/threading/QueueUserAPC/test7/CMakeLists.txt new file mode 100644 index 0000000000..751f3894f4 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test7/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test7.c +) + +add_executable(paltest_queueuserapc_test7 + ${SOURCES} +) + +add_dependencies(paltest_queueuserapc_test7 coreclrpal) + +target_link_libraries(paltest_queueuserapc_test7 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test7/test7.c b/src/pal/tests/palsuite/threading/QueueUserAPC/test7/test7.c new file mode 100644 index 0000000000..54a63982fe --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test7/test7.c @@ -0,0 +1,253 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test7.c +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** CreateEvent +** SetEvent +** CreateThread +** ResumeThread +** WaitForMultipleObjectsEx +** CloseHandle +** +** Purpose: +** +** Test to ensure proper operation of the QueueUserAPC() +** API by trying to queue an APC function on a thread and +** activating it with WaitForMultipleObjectsEx. +** +** +**===========================================================================*/ +#include <palsuite.h> + + +static HANDLE hSyncEvent = NULL; +static HANDLE hTestEvent = NULL; +static int nAPCExecuted = 0; +static BOOL bThreadResult = FALSE; + +VOID PALAPI APCFunc( ULONG_PTR dwParam ) +{ + ++nAPCExecuted; +} + +/** + * ThreadFunc + * + * Dummy thread function for APC queuing. + */ +DWORD PALAPI ThreadFunc( LPVOID param ) +{ + DWORD ret = 0; + + /* pessimism */ + bThreadResult = FALSE; + + /* set the sync event to notify the main thread */ + if( ! SetEvent( hSyncEvent ) ) + { + Trace( "ERROR:%lu:SetEvent() call failed\n", GetLastError() ); + goto done; + } + + /* wait until the test event is signalled */ + ret = WaitForSingleObject( hTestEvent, INFINITE ); + if( ret != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject() returned %lu, " + "expected WAIT_OBJECT_0\n", + ret ); + goto done; + } + + /* now do an alertable wait on the same event, which is now + in an unsignalled state */ + ret = WaitForMultipleObjectsEx( 1, &hTestEvent, TRUE, 2000, TRUE ); + + /* verify that we got a WAIT_IO_COMPLETION result */ + if( ret != WAIT_IO_COMPLETION ) + { + Trace( "ERROR:WaitForMultipleObjectsEx returned %lu, " + "expected WAIT_IO_COMPLETION\n", + ret ); + goto done; + } + + /* set the event again */ + if( ! SetEvent( hTestEvent ) ) + { + Trace( "ERROR:%lu:SetEvent() call failed\n", GetLastError() ); + goto done; + } + + /* do a non-alertable wait on the same event */ + ret = WaitForMultipleObjectsEx( 1, &hTestEvent, TRUE, INFINITE, FALSE ); + + /* verify that we got a WAIT_OBJECT_0 result */ + if( ret != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForMultipleObjectsEx returned %lu, " + "expected WAIT_OBJECT_0\n", + ret ); + goto done; + } + + /* success at this point */ + bThreadResult = TRUE; + + +done: + return bThreadResult; +} + + +int __cdecl main( int argc, char **argv ) + +{ + /* local variables */ + HANDLE hThread = NULL; + DWORD IDThread; + DWORD ret; + BOOL bResult = FALSE; + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return( FAIL ); + } + + /* create an auto-reset event for the other thread to wait on */ + hTestEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if( hTestEvent == NULL ) + { + Fail( "ERROR:%lu:CreateEvent() call failed\n", GetLastError() ); + } + + /* create an auto-reset event for synchronization */ + hSyncEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if( hSyncEvent == NULL ) + { + Trace( "ERROR:%lu:CreateEvent() call failed\n", GetLastError() ); + if( ! CloseHandle( hTestEvent ) ) + { + Trace( "ERROR:%lu:CreateEvent() call failed\n", GetLastError() ); + } + Fail( "test failed\n" ); + } + + /* run another dummy thread to cause notification of the library */ + hThread = CreateThread( NULL, /* no security attributes */ + 0, /* use default stack size */ + (LPTHREAD_START_ROUTINE) ThreadFunc, /* thread function */ + (LPVOID) NULL, /* pass thread index as */ + /* function argument */ + CREATE_SUSPENDED, /* create suspended */ + &IDThread ); /* returns thread id */ + + /* Check the return value for success. */ + if( hThread == NULL ) + { + /* error creating thread */ + Trace( "ERROR:%lu:CreateThread call failed\n", GetLastError() ); + if( ! CloseHandle( hTestEvent ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + Fail( "test failed\n" ); + } + + /* Resume the suspended thread */ + ResumeThread( hThread ); + + /* wait until the other thread is ready to proceed */ + ret = WaitForSingleObject( hSyncEvent, 10000 ); + if( ret != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject returned %lu, " + "expected WAIT_OBJECT_0\n", + ret ); + goto cleanup; + } + + + /* now queue our APC on the test thread */ + ret = QueueUserAPC( APCFunc, hThread, 0 ); + if( ret == 0 ) + { + Trace( "ERROR:%lu:QueueUserAPC call failed\n", GetLastError() ); + goto cleanup; + } + + /* signal the test event so the other thread will proceed */ + if( ! SetEvent( hTestEvent ) ) + { + Trace( "ERROR:%lu:SetEvent() call failed\n", GetLastError() ); + goto cleanup; + } + + /* wait on the other thread to complete */ + ret = WaitForSingleObject( hThread, INFINITE ); + if( ret != WAIT_OBJECT_0 ) + { + Trace( "ERROR:WaitForSingleObject() returned %lu, " + "expected WAIT_OBJECT_0\n", + ret ); + goto cleanup; + } + + /* check the result of the other thread */ + if( bThreadResult == FALSE ) + { + goto cleanup; + } + + /* check that the APC function was actually executed exactly one time */ + if( nAPCExecuted != 1 ) + { + Trace( "ERROR:APC function was executed %d times, " + "expected once\n", nAPCExecuted ); + goto cleanup; + } + + /* set the success flag */ + bResult = PASS; + + +cleanup: + /* close the global event handles */ + if( ! CloseHandle( hTestEvent ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + bResult = FAIL; + } + + if( ! CloseHandle( hSyncEvent ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + bResult = FAIL; + } + + /* close the thread handle */ + if( ! CloseHandle( hThread ) ) + { + Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + bResult = FAIL; + } + + /* output final failure result for failure case */ + if( bResult == FAIL ) + { + Fail( "test failed\n" ); + } + + /* PAL termination */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/QueueUserAPC/test7/testinfo.dat b/src/pal/tests/palsuite/threading/QueueUserAPC/test7/testinfo.dat new file mode 100644 index 0000000000..d92d9496d7 --- /dev/null +++ b/src/pal/tests/palsuite/threading/QueueUserAPC/test7/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = QueueUserAPC +Name = Positive test for QueueUserAPC +TYPE = DEFAULT +EXE1 = test7 +Description += Test to ensure proper operation of the QueueUserAPC() += API by trying to queue an APC function on a thread and += activating it with WaitForMultipleObjectsEx. diff --git a/src/pal/tests/palsuite/threading/ReleaseMutex/CMakeLists.txt b/src/pal/tests/palsuite/threading/ReleaseMutex/CMakeLists.txt new file mode 100644 index 0000000000..f9524196e0 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ReleaseMutex/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test3) + diff --git a/src/pal/tests/palsuite/threading/ReleaseMutex/test3/CMakeLists.txt b/src/pal/tests/palsuite/threading/ReleaseMutex/test3/CMakeLists.txt new file mode 100644 index 0000000000..8e64c28242 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ReleaseMutex/test3/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + ReleaseMutex.c +) + +add_executable(paltest_releasemutex_test3 + ${SOURCES} +) + +add_dependencies(paltest_releasemutex_test3 coreclrpal) + +target_link_libraries(paltest_releasemutex_test3 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/ReleaseMutex/test3/ReleaseMutex.c b/src/pal/tests/palsuite/threading/ReleaseMutex/test3/ReleaseMutex.c new file mode 100644 index 0000000000..5f6adb0419 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ReleaseMutex/test3/ReleaseMutex.c @@ -0,0 +1,103 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: ReleaseMutex/test3/ReleaseMutex.c +** +** Purpose: Test failure code for ReleaseMutex. +** +** Dependencies: CreateMutex +** ReleaseMutex +** CreateThread +** + +** +**=========================================================*/ + +#include <palsuite.h> + +DWORD dwTestResult; /* global for test result */ + +DWORD dwThreadId; /* consumer thread identifier */ + +HANDLE hMutex; /* handle to mutex */ + +HANDLE hThread; /* handle to thread */ + +/* + * Thread function. + */ +DWORD +PALAPI +ThreadFunction( LPVOID lpNoArg ) +{ + + dwTestResult = ReleaseMutex(hMutex); + + return 0; +} + +int __cdecl main (int argc, char **argv) +{ + + if(0 != (PAL_Initialize(argc, argv))) + { + return (FAIL); + } + + /* + * set dwTestResult so test fails even if ReleaseMutex is not called + */ + dwTestResult = 1; + + /* + * Create mutex + */ + hMutex = CreateMutexW ( + NULL, + TRUE, + NULL); + + if ( NULL == hMutex ) + { + Fail ( "hMutex = CreateMutex () - returned NULL\n" + "Failing Test.\nGetLastError returned %d\n", GetLastError()); + } + + /* + * Create ThreadFunction + */ + hThread = CreateThread( + NULL, + 0, + ThreadFunction, + NULL, + 0, + &dwThreadId); + + if ( NULL == hThread ) + { + + Fail ( "CreateThread() returned NULL. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + } + + /* + * Wait for ThreadFunction to complete + */ + WaitForSingleObject (hThread, INFINITE); + + if (dwTestResult) + { + Fail ("ReleaseMutex() test was expected to return 0.\n" + "It returned %d. Failing test.\n", dwTestResult ); + } + + Trace ("ReleaseMutex() test returned 0.\nTest passed.\n"); + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/ReleaseMutex/test3/testinfo.dat b/src/pal/tests/palsuite/threading/ReleaseMutex/test3/testinfo.dat new file mode 100644 index 0000000000..3f06757eb6 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ReleaseMutex/test3/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = ReleaseMutex +Name = Positive Test for ReleaseMutex +TYPE = DEFAULT +EXE1 = releasemutex +Description += Calls ReleaseMutex from a thread which should not have ownership of the += mutex. If ReleaseMutex fails correctly this test will succeed. diff --git a/src/pal/tests/palsuite/threading/ResetEvent/CMakeLists.txt b/src/pal/tests/palsuite/threading/ResetEvent/CMakeLists.txt new file mode 100644 index 0000000000..a3847f8ca9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResetEvent/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) +add_subdirectory(test3) +add_subdirectory(test4) + diff --git a/src/pal/tests/palsuite/threading/ResetEvent/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/ResetEvent/test1/CMakeLists.txt new file mode 100644 index 0000000000..78e84da2b9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResetEvent/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_resetevent_test1 + ${SOURCES} +) + +add_dependencies(paltest_resetevent_test1 coreclrpal) + +target_link_libraries(paltest_resetevent_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/ResetEvent/test1/test1.c b/src/pal/tests/palsuite/threading/ResetEvent/test1/test1.c new file mode 100644 index 0000000000..20a0d5dffa --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResetEvent/test1/test1.c @@ -0,0 +1,99 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test1.c +** +** Purpose: Test for ResetEvent. Create an event with an intial +** state signaled. Then reset that signal, and check to see that +** the event is now not signaled. +** +** +**=========================================================*/ + +#include <palsuite.h> + +BOOL ResetEventTest() +{ + BOOL bRet = FALSE; + DWORD dwRet = 0; + + LPSECURITY_ATTRIBUTES lpEventAttributes = 0; + BOOL bManualReset = TRUE; + BOOL bInitialState = TRUE; + + /* Create an Event, ensure it is valid */ + HANDLE hEvent = CreateEvent( lpEventAttributes, + bManualReset, bInitialState, NULL); + + if (hEvent != INVALID_HANDLE_VALUE) + { + /* Check that WaitFor returns WAIT_OBJECT_0, indicating that + the event is signaled. + */ + + dwRet = WaitForSingleObject(hEvent,0); + + if (dwRet != WAIT_OBJECT_0) + { + Fail("ResetEventTest:WaitForSingleObject failed (%x)\n", GetLastError()); + } + else + { + /* Call ResetEvent, which will reset the signal */ + bRet = ResetEvent(hEvent); + + if (!bRet) + { + Fail("ResetEventTest:ResetEvent failed (%x)\n", GetLastError()); + } + else + { + /* Call WaitFor again, and since it has been reset, + the return value should now be WAIT_TIMEOUT + */ + dwRet = WaitForSingleObject(hEvent,0); + + if (dwRet != WAIT_TIMEOUT) + { + Fail("ResetEventTest:WaitForSingleObject %s failed (%x)\n", GetLastError()); + } + else + { + bRet = CloseHandle(hEvent); + + if (!bRet) + { + Fail("ResetEventTest:CloseHandle failed (%x)\n", GetLastError()); + } + } + } + } + } + else + { + Fail("ResetEventTest:CreateEvent failed (%x)\n", GetLastError()); + } + + return bRet; +} + +int __cdecl main(int argc, char **argv) +{ + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + if(!ResetEventTest()) + { + Fail ("Test failed\n"); + } + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/ResetEvent/test1/testinfo.dat b/src/pal/tests/palsuite/threading/ResetEvent/test1/testinfo.dat new file mode 100644 index 0000000000..ed27f13dba --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResetEvent/test1/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = ResetEvent +Name = Positive Test for ResetEvent +TYPE = DEFAULT +EXE1 = test1 +Description += Test for ResetEvent. Create an event with an intial += state signaled. Then reset that signal, and check to see that += the event is now not signaled. diff --git a/src/pal/tests/palsuite/threading/ResetEvent/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/ResetEvent/test2/CMakeLists.txt new file mode 100644 index 0000000000..ad0fe9b4a1 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResetEvent/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test2.c +) + +add_executable(paltest_resetevent_test2 + ${SOURCES} +) + +add_dependencies(paltest_resetevent_test2 coreclrpal) + +target_link_libraries(paltest_resetevent_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/ResetEvent/test2/test2.c b/src/pal/tests/palsuite/threading/ResetEvent/test2/test2.c new file mode 100644 index 0000000000..8117f44353 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResetEvent/test2/test2.c @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test2.c +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** CreateEvent +** CloseHandle +** WaitForSingleObject +** +** Purpose: Test to ensure proper operation of the ResetEvent() +** API by calling it on an event handle that's already +** unsignalled. +** + +** +**===========================================================================*/ +#include <palsuite.h> + + + +int __cdecl main( int argc, char **argv ) + +{ + /* local variables */ + DWORD dwRet = 0; + HANDLE hEvent = NULL; + LPSECURITY_ATTRIBUTES lpEventAttributes = NULL; + BOOL bManualReset = TRUE; + BOOL bInitialState = FALSE; + + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return( FAIL ); + } + + + /* create an unsignalled event which we can use with ResetEvent */ + hEvent = CreateEvent( lpEventAttributes, + bManualReset, + bInitialState, + NULL ); + + if( hEvent == INVALID_HANDLE_VALUE ) + { + /* ERROR */ + Fail( "ERROR:%lu:CreateEvent() call failed\n", GetLastError() ); + } + + /* verify that the event isn't signalled yet */ + dwRet = WaitForSingleObject( hEvent, 0 ); + if( dwRet != WAIT_TIMEOUT ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_TIMEOUT\n", + dwRet ); + CloseHandle( hEvent ); + Fail( "Test failed\n" ); + } + + /* try to reset the event */ + if( ! ResetEvent( hEvent ) ) + { + /* ERROR */ + Trace( "FAIL:%lu:ResetEvent() call failed\n", GetLastError() ); + CloseHandle( hEvent ); + Fail( "Test failed\n" ); + } + + /* close the event handle */ + if( ! CloseHandle( hEvent ) ) + { + Fail( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + + + /* PAL termination */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/ResetEvent/test2/testinfo.dat b/src/pal/tests/palsuite/threading/ResetEvent/test2/testinfo.dat new file mode 100644 index 0000000000..4af1769cd4 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResetEvent/test2/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = ResetEvent +Name = Positive test for ResetEvent +TYPE = DEFAULT +EXE1 = test2 +Description += Test to ensure proper operation of the ResetEvent() += API by calling it on an event handle that's already += unsignalled. diff --git a/src/pal/tests/palsuite/threading/ResetEvent/test3/CMakeLists.txt b/src/pal/tests/palsuite/threading/ResetEvent/test3/CMakeLists.txt new file mode 100644 index 0000000000..3c4ea1f249 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResetEvent/test3/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test3.c +) + +add_executable(paltest_resetevent_test3 + ${SOURCES} +) + +add_dependencies(paltest_resetevent_test3 coreclrpal) + +target_link_libraries(paltest_resetevent_test3 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/ResetEvent/test3/test3.c b/src/pal/tests/palsuite/threading/ResetEvent/test3/test3.c new file mode 100644 index 0000000000..9bc068ea72 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResetEvent/test3/test3.c @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test3.c +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** CreateEvent +** CloseHandle +** +** Purpose: +** +** Test to ensure proper operation of the ResetEvent() +** API by calling it on an event handle that's been +** closed. We expect it to return an appropriate error +** result. +** +** + +** +**===========================================================================*/ +#include <palsuite.h> + + + +int __cdecl main( int argc, char **argv ) + +{ + /* local variables */ + HANDLE hEvent = NULL; + LPSECURITY_ATTRIBUTES lpEventAttributes = NULL; + BOOL bManualReset = TRUE; + BOOL bInitialState = FALSE; + + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return( FAIL ); + } + + + /* create an event which we can use with ResetEvent */ + hEvent = CreateEvent( lpEventAttributes, + bManualReset, + bInitialState, + NULL ); + + if( hEvent == INVALID_HANDLE_VALUE ) + { + /* ERROR */ + Fail( "ERROR:%lu:CreateEvent() call failed\n", GetLastError() ); + } + + /* close the event handle */ + if( ! CloseHandle( hEvent ) ) + { + Fail( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + + /* try to reset the event */ + if( ResetEvent( hEvent ) ) + { + /* ERROR */ + Fail( "FAIL:ResetEvent() call succeeded on a closed event handle\n" ); + } + + /* verify the result of GetLastError() */ + if( GetLastError() != ERROR_INVALID_HANDLE ) + { + /* ERROR */ + Fail( "FAIL:ResetEvent() call failed on a closed event handle " + "but returned an unexpected error result %lu\n" ); + } + + + + /* PAL termination */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/ResetEvent/test3/testinfo.dat b/src/pal/tests/palsuite/threading/ResetEvent/test3/testinfo.dat new file mode 100644 index 0000000000..4abeeded00 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResetEvent/test3/testinfo.dat @@ -0,0 +1,15 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = ResetEvent +Name = Negative test for ResetEvent +TYPE = DEFAULT +EXE1 = test3 +Description += Test to ensure proper operation of the ResetEvent() += API by calling it on an event handle that's been += closed. We expect it to return an appropriate error += result. diff --git a/src/pal/tests/palsuite/threading/ResetEvent/test4/CMakeLists.txt b/src/pal/tests/palsuite/threading/ResetEvent/test4/CMakeLists.txt new file mode 100644 index 0000000000..68ee20bc95 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResetEvent/test4/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test4.c +) + +add_executable(paltest_resetevent_test4 + ${SOURCES} +) + +add_dependencies(paltest_resetevent_test4 coreclrpal) + +target_link_libraries(paltest_resetevent_test4 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/ResetEvent/test4/test4.c b/src/pal/tests/palsuite/threading/ResetEvent/test4/test4.c new file mode 100644 index 0000000000..0cc68fd9aa --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResetEvent/test4/test4.c @@ -0,0 +1,161 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test4.c +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** CreateEvent +** CloseHandle +** WaitForSingleObject +** DuplicateHandle +** GetCurrentProcess +** +** Purpose: +** +** Test to ensure proper operation of the ResetEvent() +** API by calling it on an event handle that's the +** result of a DuplicateHandle() call on another event +** handle. +** + +** +**===========================================================================*/ +#include <palsuite.h> + + + +int __cdecl main( int argc, char **argv ) + +{ + /* local variables */ + DWORD dwRet = 0; + HANDLE hEvent = NULL; + HANDLE hDupEvent = NULL; + LPSECURITY_ATTRIBUTES lpEventAttributes = NULL; + BOOL bManualReset = TRUE; + BOOL bInitialState = TRUE; + + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return( FAIL ); + } + + + /* create an event which we can use with ResetEvent */ + hEvent = CreateEvent( lpEventAttributes, + bManualReset, + bInitialState, + NULL ); + + if( hEvent == INVALID_HANDLE_VALUE ) + { + /* ERROR */ + Fail( "ERROR:%lu:CreateEvent() call failed\n", GetLastError() ); + } + + /* verify that the event is signalled already */ + dwRet = WaitForSingleObject( hEvent, 0 ); + if( dwRet != WAIT_OBJECT_0 ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_OBJECT_0\n", + dwRet ); + CloseHandle( hEvent ); + Fail( "Test failed\n" ); + } + + + /* duplicate the event handle */ + if( ! (DuplicateHandle( + GetCurrentProcess(), + hEvent, + GetCurrentProcess(), + &hDupEvent, + GENERIC_READ|GENERIC_WRITE, /* ignored in PAL */ + FALSE, + DUPLICATE_SAME_ACCESS ) ) ) + { + Trace("ERROR:%u:DuplicateHandle() call failed\n", + GetLastError() ); + CloseHandle( hEvent ); + Fail("Test failed\n"); + } + + /* verify that the event is signalled with the duplicate handle */ + dwRet = WaitForSingleObject( hDupEvent, 0 ); + if( dwRet != WAIT_OBJECT_0 ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_OBJECT_0\n", + dwRet ); + CloseHandle( hEvent ); + CloseHandle( hDupEvent ); + Fail( "Test failed\n" ); + } + + + /* reset the event using the duplicate handle */ + if( ! ResetEvent( hDupEvent ) ) + { + /* ERROR */ + Trace( "ERROR:%lu:ResetEvent() call failed\n", GetLastError() ); + CloseHandle( hEvent ); + CloseHandle( hDupEvent ); + Fail( "Test failed\n" ); + } + + /* verify that the event isn't signalled using the duplicate handle*/ + dwRet = WaitForSingleObject( hDupEvent, 0 ); + if( dwRet != WAIT_TIMEOUT ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_TIMEOUT\n", + dwRet ); + CloseHandle( hEvent ); + Fail( "Test failed\n" ); + } + + /* check that the event isn't signalled using the original event handle */ + dwRet = WaitForSingleObject( hEvent, 0 ); + if( dwRet != WAIT_TIMEOUT ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_TIMEOUT\n", + dwRet ); + CloseHandle( hEvent ); + Fail( "Test failed\n" ); + } + + + /* close the duplicate event handle */ + if( ! CloseHandle( hDupEvent ) ) + { + Fail( "ERROR:%lu:CloseHandle() call failed for duplicate handle\n", + GetLastError() ); + } + + + /* close the event handle */ + if( ! CloseHandle( hEvent ) ) + { + Fail( "ERROR:%lu:CloseHandle() call failed for original handle\n", + GetLastError() ); + } + + + /* PAL termination */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/ResetEvent/test4/testinfo.dat b/src/pal/tests/palsuite/threading/ResetEvent/test4/testinfo.dat new file mode 100644 index 0000000000..0223246c6f --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResetEvent/test4/testinfo.dat @@ -0,0 +1,15 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = ResetEvent +Name = Positive test for ResetEvent +TYPE = DEFAULT +EXE1 = test4 +Description += Test to ensure proper operation of the ResetEvent() += API by calling it on an event handle that's the += result of a DuplicateHandle() call on another event += handle. diff --git a/src/pal/tests/palsuite/threading/ResumeThread/CMakeLists.txt b/src/pal/tests/palsuite/threading/ResumeThread/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResumeThread/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/threading/ResumeThread/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/ResumeThread/test1/CMakeLists.txt new file mode 100644 index 0000000000..2c83dfd3d5 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResumeThread/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_resumethread_test1 + ${SOURCES} +) + +add_dependencies(paltest_resumethread_test1 coreclrpal) + +target_link_libraries(paltest_resumethread_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/ResumeThread/test1/test1.c b/src/pal/tests/palsuite/threading/ResumeThread/test1/test1.c new file mode 100644 index 0000000000..037f79f7a3 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResumeThread/test1/test1.c @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test1.c +** +** Purpose: Test for ResumeThread. Create a suspended Thread. +** First, ensure that it is indeed suspended. Then call resumethread +** and check to ensure that the function has now run. +** +** +**=========================================================*/ + +#include <palsuite.h> + +DWORD dwResumeThreadTestParameter = 0; + +DWORD PALAPI ResumeThreadTestThread( LPVOID lpParameter) +{ + DWORD dwRet = 0; + + /* Save parameter so we can check and ensure this function ran + properly. + */ + + dwResumeThreadTestParameter = (DWORD)lpParameter; + + return dwRet; +} + +BOOL ResumeThreadTest() +{ + BOOL bRet = FALSE; + DWORD dwRet = 0; + + LPSECURITY_ATTRIBUTES lpThreadAttributes = NULL; + DWORD dwStackSize = 0; + LPTHREAD_START_ROUTINE lpStartAddress = &ResumeThreadTestThread; + LPVOID lpParameter = lpStartAddress; + DWORD dwCreationFlags = CREATE_SUSPENDED; + DWORD dwThreadId = 0; + + HANDLE hThread = 0; + + dwResumeThreadTestParameter = 0; + + /* Create a thread, with CREATE_SUSPENDED, so we can resume it! */ + + hThread = CreateThread( lpThreadAttributes, + dwStackSize, lpStartAddress, lpParameter, + dwCreationFlags, &dwThreadId ); + + if (hThread != INVALID_HANDLE_VALUE) + { + /* Wait for one second. This should return WAIT_TIMEOUT */ + dwRet = WaitForSingleObject(hThread,1000); + + if (dwRet != WAIT_TIMEOUT) + { + Trace("ResumeThreadTest:WaitForSingleObject " + "failed (%x)\n",GetLastError()); + } + else + { + /* Check to ensure the parameter hasn't changed. The + function shouldn't have occurred yet. + */ + if (dwResumeThreadTestParameter != 0) + { + Trace("ResumeThreadTest:parameter error\n"); + } + else + { + /* Call ResumeThread and ensure the return value is + correct. + */ + + dwRet = ResumeThread(hThread); + + if (dwRet != 1) + { + Trace("ResumeThreadTest:ResumeThread " + "failed (%x)\n",GetLastError()); + } + else + { + /* Wait again, now that the thread has been + resumed, and the return should be WAIT_OBJECT_0 + */ + dwRet = WaitForSingleObject(hThread,INFINITE); + + if (dwRet != WAIT_OBJECT_0) + { + Trace("ResumeThreadTest:WaitForSingleObject " + "failed (%x)\n",GetLastError()); + } + else + { + /* Check the param now and it should have been + set. + */ + if (dwResumeThreadTestParameter != (DWORD)lpParameter) + { + Trace("ResumeThreadTest:parameter error\n"); + } + else + { + bRet = TRUE; + } + } + } + } + } + } + else + { + Trace("ResumeThreadTest:CreateThread failed (%x)\n",GetLastError()); + } + + return bRet; +} + +int __cdecl main(int argc, char **argv) +{ + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + if(!ResumeThreadTest()) + { + Fail("Test Failed\n"); + } + + PAL_Terminate(); + return (PASS); + +} diff --git a/src/pal/tests/palsuite/threading/ResumeThread/test1/testinfo.dat b/src/pal/tests/palsuite/threading/ResumeThread/test1/testinfo.dat new file mode 100644 index 0000000000..8472165d5d --- /dev/null +++ b/src/pal/tests/palsuite/threading/ResumeThread/test1/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = ResumeThread +Name = Positive Test for ResumeThread +TYPE = DEFAULT +EXE1 = test1 +Description += Test for ResumeThread. Create a suspended Thread. += First, ensure that it is indeed suspended. Then call resumethread += and check to ensure that the function has now run. diff --git a/src/pal/tests/palsuite/threading/SetErrorMode/CMakeLists.txt b/src/pal/tests/palsuite/threading/SetErrorMode/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetErrorMode/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/threading/SetErrorMode/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/SetErrorMode/test1/CMakeLists.txt new file mode 100644 index 0000000000..cfdeb31950 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetErrorMode/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_seterrormode_test1 + ${SOURCES} +) + +add_dependencies(paltest_seterrormode_test1 coreclrpal) + +target_link_libraries(paltest_seterrormode_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/SetErrorMode/test1/test1.c b/src/pal/tests/palsuite/threading/SetErrorMode/test1/test1.c new file mode 100644 index 0000000000..238cec4421 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetErrorMode/test1/test1.c @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test1.c (SetErrorMode) +** +** Purpose: Tests the PAL implementation of the SetErrorMode function. +** This test will set the error mode and then read the error +** set with GetLastError(). +** +** +**===================================================================*/ + +#include <palsuite.h> + +int __cdecl main(int argc, char **argv) +{ + DWORD dErrorReturn; + UINT dErrorModes[] = {SEM_NOOPENFILEERRORBOX, SEM_FAILCRITICALERRORS, 0}; + int i; + + /* + * Initialize the Pal + */ + if ((PAL_Initialize(argc,argv)) != 0) + { + return (FAIL); + } + + /* + * Loop through the supported Error Modes and verify + * that GetLastError() returns the correct Error Mode + */ + for (i=0; i < (sizeof(dErrorModes) / sizeof(UINT)); i++) + { + SetLastError(dErrorModes[i]); + if ((dErrorReturn = GetLastError()) != dErrorModes[i]) + { + Fail("ERROR: SetLastError was set to 0x%4.4x but," + " GetLastError returned 0x%4.4x\n", + dErrorModes[i], + dErrorReturn); + } + } + + PAL_Terminate(); + return (PASS); +} diff --git a/src/pal/tests/palsuite/threading/SetErrorMode/test1/testinfo.dat b/src/pal/tests/palsuite/threading/SetErrorMode/test1/testinfo.dat new file mode 100644 index 0000000000..875ac2a223 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetErrorMode/test1/testinfo.dat @@ -0,0 +1,16 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Threading +Function = SetErrorMode +Name = Positive Test for SetErrorMode +TYPE = DEFAULT +EXE1 = test1 +Description += Tests the PAL implementation of the SetErrorMode function. += This test will set the error mode and then read the error += set with GetLastError(). + + diff --git a/src/pal/tests/palsuite/threading/SetEvent/CMakeLists.txt b/src/pal/tests/palsuite/threading/SetEvent/CMakeLists.txt new file mode 100644 index 0000000000..a3847f8ca9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetEvent/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) +add_subdirectory(test3) +add_subdirectory(test4) + diff --git a/src/pal/tests/palsuite/threading/SetEvent/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/SetEvent/test1/CMakeLists.txt new file mode 100644 index 0000000000..9be8d5a421 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetEvent/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_setevent_test1 + ${SOURCES} +) + +add_dependencies(paltest_setevent_test1 coreclrpal) + +target_link_libraries(paltest_setevent_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/SetEvent/test1/test1.c b/src/pal/tests/palsuite/threading/SetEvent/test1/test1.c new file mode 100644 index 0000000000..d5a29ce3f3 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetEvent/test1/test1.c @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test1.c +** +** Purpose: Test for SetEvent. Create an Event and then set +** this event, checking the return value. Ensure that it returns +** positive. +** +** +**=========================================================*/ + +#include <palsuite.h> + +BOOL SetEventTest() +{ + int bRet = 0; + DWORD dwRet = 0; + + LPSECURITY_ATTRIBUTES lpEventAttributes = 0; + BOOL bManualReset = TRUE; + BOOL bInitialState = FALSE; + + /* Create an event which we can use with SetEvent */ + HANDLE hEvent = CreateEvent( lpEventAttributes, + bManualReset, bInitialState, NULL); + + if (hEvent != INVALID_HANDLE_VALUE) + { + dwRet = WaitForSingleObject(hEvent,0); + + if (dwRet != WAIT_TIMEOUT) + { + Trace("SetEventTest:WaitForSingleObject failed (%x)\n", GetLastError()); + } + else + { + /* Set the event to the previously created event and check + the return value. + */ + bRet = SetEvent(hEvent); + + if (!bRet) + { + Trace("SetEventTest:SetEvent failed (%x)\n", GetLastError()); + } + else + { + dwRet = WaitForSingleObject(hEvent,0); + + if (dwRet != WAIT_OBJECT_0) + { + Trace("SetEventTest:WaitForSingleObject failed (%x)\n", GetLastError()); + } + else + { + dwRet = CloseHandle(hEvent); + + if (!dwRet) + { + Trace("SetEventTest:CloseHandle failed (%x)\n", GetLastError()); + } + } + } + } + } + else + { + Trace("SetEventTest:CreateEvent failed (%x)\n", GetLastError()); + } + + return bRet; +} + + +int __cdecl main(int argc, char **argv) +{ + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + if(SetEventTest() == 0) + { + Fail ("Test failed\n"); + } + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/SetEvent/test1/testinfo.dat b/src/pal/tests/palsuite/threading/SetEvent/test1/testinfo.dat new file mode 100644 index 0000000000..9bfd80829c --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetEvent/test1/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = SetEvent +Name = Positive Test for SetEvent +TYPE = DEFAULT +EXE1 = test1 +Description +=Test for SetEvent. Create an Event and then set +=this event, checking the return value. Ensure that it returns +=positive. diff --git a/src/pal/tests/palsuite/threading/SetEvent/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/SetEvent/test2/CMakeLists.txt new file mode 100644 index 0000000000..87522b3ffc --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetEvent/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test2.c +) + +add_executable(paltest_setevent_test2 + ${SOURCES} +) + +add_dependencies(paltest_setevent_test2 coreclrpal) + +target_link_libraries(paltest_setevent_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/SetEvent/test2/test2.c b/src/pal/tests/palsuite/threading/SetEvent/test2/test2.c new file mode 100644 index 0000000000..5fd2833957 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetEvent/test2/test2.c @@ -0,0 +1,125 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test2.c +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** CreateEvent +** CloseHandle +** WaitForSingleObject +** +** Purpose: +** +** Test to ensure proper operation of the SetEvent() +** API by calling it on an event handle that's already set. +** + +** +**===========================================================================*/ +#include <palsuite.h> + + + +int __cdecl main( int argc, char **argv ) + +{ + /* local variables */ + DWORD dwRet = 0; + HANDLE hEvent = NULL; + LPSECURITY_ATTRIBUTES lpEventAttributes = NULL; + BOOL bManualReset = TRUE; + BOOL bInitialState = FALSE; + + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return( FAIL ); + } + + + /* create an event which we can use with SetEvent */ + hEvent = CreateEvent( lpEventAttributes, + bManualReset, + bInitialState, + NULL ); + + if( hEvent == INVALID_HANDLE_VALUE ) + { + /* ERROR */ + Fail( "ERROR:%lu:CreateEvent() call failed\n", GetLastError() ); + } + + /* verify that the event isn't signalled yet */ + dwRet = WaitForSingleObject( hEvent, 0 ); + if( dwRet != WAIT_TIMEOUT ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_TIMEOUT\n", + dwRet ); + CloseHandle( hEvent ); + Fail( "Test failed\n" ); + } + + /* set the event */ + if( ! SetEvent( hEvent ) ) + { + /* ERROR */ + Trace( "ERROR:%lu:SetEvent() call failed\n", GetLastError() ); + CloseHandle( hEvent ); + Fail( "Test failed\n" ); + } + + /* verify that the event is signalled */ + dwRet = WaitForSingleObject( hEvent, 0 ); + if( dwRet != WAIT_OBJECT_0 ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_OBJECT_0\n", + dwRet ); + CloseHandle( hEvent ); + Fail( "Test failed\n" ); + } + + /* try to set the event again */ + if( ! SetEvent( hEvent ) ) + { + /* ERROR */ + Trace( "FAIL:%lu:SetEvent() call failed on signalled event\n", + GetLastError() ); + CloseHandle( hEvent ); + Fail( "Test failed\n" ); + } + + /* verify that the event is still signalled */ + dwRet = WaitForSingleObject( hEvent, 0 ); + if( dwRet != WAIT_OBJECT_0 ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_OBJECT_0\n", + dwRet ); + CloseHandle( hEvent ); + Fail( "Test failed\n" ); + } + + + /* close the event handle */ + if( ! CloseHandle( hEvent ) ) + { + Fail( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + + + /* PAL termination */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/SetEvent/test2/testinfo.dat b/src/pal/tests/palsuite/threading/SetEvent/test2/testinfo.dat new file mode 100644 index 0000000000..f2153052bb --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetEvent/test2/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = SetEvent +Name = Positive test for SetEvent +TYPE = DEFAULT +EXE1 = test2 +Description += Test to ensure proper operation of the SetEvent() += API by calling it on an event handle that's already set. diff --git a/src/pal/tests/palsuite/threading/SetEvent/test3/CMakeLists.txt b/src/pal/tests/palsuite/threading/SetEvent/test3/CMakeLists.txt new file mode 100644 index 0000000000..d09239b3e4 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetEvent/test3/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test3.c +) + +add_executable(paltest_setevent_test3 + ${SOURCES} +) + +add_dependencies(paltest_setevent_test3 coreclrpal) + +target_link_libraries(paltest_setevent_test3 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/SetEvent/test3/test3.c b/src/pal/tests/palsuite/threading/SetEvent/test3/test3.c new file mode 100644 index 0000000000..21601f00b8 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetEvent/test3/test3.c @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test3.c +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** CreateEvent +** CloseHandle +** +** Purpose: +** +** Test to ensure proper operation of the SetEvent() +** API by calling it on an event handle that's been +** closed. We expect it to return an appropriate error +** result. +** + +** +**===========================================================================*/ +#include <palsuite.h> + + + +int __cdecl main( int argc, char **argv ) + +{ + /* local variables */ + HANDLE hEvent = NULL; + LPSECURITY_ATTRIBUTES lpEventAttributes = NULL; + BOOL bManualReset = TRUE; + BOOL bInitialState = FALSE; + + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return( FAIL ); + } + + + /* create an event which we can use with SetEvent */ + hEvent = CreateEvent( lpEventAttributes, + bManualReset, + bInitialState, + NULL ); + + if( hEvent == INVALID_HANDLE_VALUE ) + { + /* ERROR */ + Fail( "ERROR:%lu:CreateEvent() call failed\n", GetLastError() ); + } + + /* close the event handle */ + if( ! CloseHandle( hEvent ) ) + { + Fail( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); + } + + /* try to set the event */ + if( SetEvent( hEvent ) ) + { + /* ERROR */ + Fail( "FAIL:SetEvent() call succeeded on a closed event handle\n" ); + } + + /* verify the result of GetLastError() */ + if( GetLastError() != ERROR_INVALID_HANDLE ) + { + /* ERROR */ + Fail( "FAIL:SetEvent() call failed on a closed event handle" + "but returned an unexpected error result %lu\n" ); + } + + + + /* PAL termination */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/SetEvent/test3/testinfo.dat b/src/pal/tests/palsuite/threading/SetEvent/test3/testinfo.dat new file mode 100644 index 0000000000..7b8f43013a --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetEvent/test3/testinfo.dat @@ -0,0 +1,15 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = SetEvent +Name = Negative test for SetEvent +TYPE = DEFAULT +EXE1 = test3 +Description += Test to ensure proper operation of the SetEvent() += API by calling it on an event handle that's been += closed. We expect it to return an appropriate error += result. diff --git a/src/pal/tests/palsuite/threading/SetEvent/test4/CMakeLists.txt b/src/pal/tests/palsuite/threading/SetEvent/test4/CMakeLists.txt new file mode 100644 index 0000000000..3f1d344292 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetEvent/test4/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test4.c +) + +add_executable(paltest_setevent_test4 + ${SOURCES} +) + +add_dependencies(paltest_setevent_test4 coreclrpal) + +target_link_libraries(paltest_setevent_test4 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/SetEvent/test4/test4.c b/src/pal/tests/palsuite/threading/SetEvent/test4/test4.c new file mode 100644 index 0000000000..7a79a9d708 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetEvent/test4/test4.c @@ -0,0 +1,161 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test4.c +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** CreateEvent +** CloseHandle +** WaitForSingleObject +** DuplicateHandle +** GetCurrentProcess +** +** Purpose: +** +** Test to ensure proper operation of the SetEvent() +** API by calling it on an event handle that's the +** result of a DuplicateHandle() call on another event +** handle. +** + +** +**===========================================================================*/ +#include <palsuite.h> + + + +int __cdecl main( int argc, char **argv ) + +{ + /* local variables */ + DWORD dwRet = 0; + HANDLE hEvent = NULL; + HANDLE hDupEvent = NULL; + LPSECURITY_ATTRIBUTES lpEventAttributes = NULL; + BOOL bManualReset = TRUE; + BOOL bInitialState = FALSE; + + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return( FAIL ); + } + + + /* create an event which we can use with SetEvent */ + hEvent = CreateEvent( lpEventAttributes, + bManualReset, + bInitialState, + NULL ); + + if( hEvent == INVALID_HANDLE_VALUE ) + { + /* ERROR */ + Fail( "ERROR:%lu:CreateEvent() call failed\n", GetLastError() ); + } + + /* verify that the event isn't signalled yet */ + dwRet = WaitForSingleObject( hEvent, 0 ); + if( dwRet != WAIT_TIMEOUT ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_TIMEOUT\n", + dwRet ); + CloseHandle( hEvent ); + Fail( "Test failed\n" ); + } + + + /* duplicate the event handle */ + if( ! (DuplicateHandle( + GetCurrentProcess(), + hEvent, + GetCurrentProcess(), + &hDupEvent, + GENERIC_READ|GENERIC_WRITE, /* ignored in PAL */ + FALSE, + DUPLICATE_SAME_ACCESS ) ) ) + { + Trace("ERROR:%u:DuplicateHandle() call failed\n", + GetLastError() ); + CloseHandle( hEvent ); + Fail("Test failed\n"); + } + + /* verify that the event isn't signalled yet with the duplicate handle */ + dwRet = WaitForSingleObject( hDupEvent, 0 ); + if( dwRet != WAIT_TIMEOUT ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_TIMEOUT\n", + dwRet ); + CloseHandle( hEvent ); + CloseHandle( hDupEvent ); + Fail( "Test failed\n" ); + } + + + /* set the event using the duplicate handle */ + if( ! SetEvent( hDupEvent ) ) + { + /* ERROR */ + Trace( "ERROR:%lu:SetEvent() call failed\n", GetLastError() ); + CloseHandle( hEvent ); + CloseHandle( hDupEvent ); + Fail( "Test failed\n" ); + } + + /* verify that the event is signalled using the duplicate handle*/ + dwRet = WaitForSingleObject( hDupEvent, 0 ); + if( dwRet != WAIT_OBJECT_0 ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_OBJECT_0\n", + dwRet ); + CloseHandle( hEvent ); + Fail( "Test failed\n" ); + } + + /* verify that the event is signalled using the original event handle */ + dwRet = WaitForSingleObject( hEvent, 0 ); + if( dwRet != WAIT_OBJECT_0 ) + { + /* ERROR */ + Trace( "ERROR:WaitForSingleObject() call returned %lu, " + "expected WAIT_OBJECT_0\n", + dwRet ); + CloseHandle( hEvent ); + Fail( "Test failed\n" ); + } + + + /* close the duplicate event handle */ + if( ! CloseHandle( hDupEvent ) ) + { + Fail( "ERROR:%lu:CloseHandle() call failed for duplicate handle\n", + GetLastError() ); + } + + + /* close the event handle */ + if( ! CloseHandle( hEvent ) ) + { + Fail( "ERROR:%lu:CloseHandle() call failed for original handle\n", + GetLastError() ); + } + + + /* PAL termination */ + PAL_Terminate(); + + /* return success */ + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/SetEvent/test4/testinfo.dat b/src/pal/tests/palsuite/threading/SetEvent/test4/testinfo.dat new file mode 100644 index 0000000000..9a7f7ddb3b --- /dev/null +++ b/src/pal/tests/palsuite/threading/SetEvent/test4/testinfo.dat @@ -0,0 +1,15 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = SetEvent +Name = Positive test for SetEvent +TYPE = DEFAULT +EXE1 = test4 +Description += Test to ensure proper operation of the SetEvent() += API by calling it on an event handle that's the += result of a DuplicateHandle() call on another event += handle. diff --git a/src/pal/tests/palsuite/threading/Sleep/CMakeLists.txt b/src/pal/tests/palsuite/threading/Sleep/CMakeLists.txt new file mode 100644 index 0000000000..ef14ea5352 --- /dev/null +++ b/src/pal/tests/palsuite/threading/Sleep/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) + diff --git a/src/pal/tests/palsuite/threading/Sleep/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/Sleep/test1/CMakeLists.txt new file mode 100644 index 0000000000..b0d8db28a2 --- /dev/null +++ b/src/pal/tests/palsuite/threading/Sleep/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + Sleep.c +) + +add_executable(paltest_sleep_test1 + ${SOURCES} +) + +add_dependencies(paltest_sleep_test1 coreclrpal) + +target_link_libraries(paltest_sleep_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/Sleep/test1/Sleep.c b/src/pal/tests/palsuite/threading/Sleep/test1/Sleep.c new file mode 100644 index 0000000000..f7f7c91730 --- /dev/null +++ b/src/pal/tests/palsuite/threading/Sleep/test1/Sleep.c @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: Sleep.c +** +** Purpose: Test to establish whether the Sleep function stops the thread from +** executing for the specified times. +** +** Dependencies: GetSystemTime +** Fail +** Trace +** + +** +**=========================================================*/ + +#include <palsuite.h> + +DWORD SleepTimes[] = +{ + 0, + 50, + 100, + 500, + 2000 +}; + +/* Milliseconds of error which are acceptable Function execution time, etc. */ +DWORD AcceptableTimeError = 150; + +int __cdecl main( int argc, char **argv ) +{ + UINT64 OldTimeStamp; + UINT64 NewTimeStamp; + DWORD MaxDelta; + DWORD TimeDelta; + DWORD i; + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + LARGE_INTEGER performanceFrequency; + if (!QueryPerformanceFrequency(&performanceFrequency)) + { + return FAIL; + } + + for( i = 0; i < sizeof(SleepTimes) / sizeof(DWORD); i++) + { + OldTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency); + Sleep(SleepTimes[i]); + NewTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency); + + TimeDelta = NewTimeStamp - OldTimeStamp; + + /* For longer intervals use a 10 percent tolerance */ + if ((SleepTimes[i] * 0.1) > AcceptableTimeError) + { + MaxDelta = SleepTimes[i] + (DWORD)(SleepTimes[i] * 0.1); + } + else + { + MaxDelta = SleepTimes[i] + AcceptableTimeError; + } + + if ( TimeDelta<SleepTimes[i] || TimeDelta>MaxDelta ) + { + Fail("The sleep function slept for %d ms when it should have " + "slept for %d ms\n", TimeDelta, SleepTimes[i]); + } + } + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/Sleep/test1/testinfo.dat b/src/pal/tests/palsuite/threading/Sleep/test1/testinfo.dat new file mode 100644 index 0000000000..433a061f2c --- /dev/null +++ b/src/pal/tests/palsuite/threading/Sleep/test1/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = Sleep +Name = Positive Test for Sleep +TYPE = DEFAULT +EXE1 = sleep +Description += Test to see if the Sleep function stops the thread from executing for the += specified amount of time. diff --git a/src/pal/tests/palsuite/threading/Sleep/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/Sleep/test2/CMakeLists.txt new file mode 100644 index 0000000000..832e8ab42d --- /dev/null +++ b/src/pal/tests/palsuite/threading/Sleep/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + sleep.c +) + +add_executable(paltest_sleep_test2 + ${SOURCES} +) + +add_dependencies(paltest_sleep_test2 coreclrpal) + +target_link_libraries(paltest_sleep_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/Sleep/test2/sleep.c b/src/pal/tests/palsuite/threading/Sleep/test2/sleep.c new file mode 100644 index 0000000000..eb30e34f2f --- /dev/null +++ b/src/pal/tests/palsuite/threading/Sleep/test2/sleep.c @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: Sleep.c +** +** Purpose: Test to establish whether the Sleep function stops the thread from +** executing for the specified times. +** +** Dependencies: GetTickCount +** + +** +**=========================================================*/ + +#include <palsuite.h> + +/* + * times in 10^(-3) seconds + */ + +DWORD SleepTimes[] = +{ + 60000, + 300000, + 1800000, + 3200000 +}; + +/* Milliseconds of error which are acceptable Function execution time, etc. */ +DWORD AcceptableTimeError = 150; + +int __cdecl main( int argc, char **argv ) +{ + UINT64 OldTimeStamp; + UINT64 NewTimeStamp; + DWORD MaxDelta; + DWORD TimeDelta; + DWORD i; + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + LARGE_INTEGER performanceFrequency; + if (!QueryPerformanceFrequency(&performanceFrequency)) + { + return FAIL; + } + + for( i = 0; i < sizeof(SleepTimes) / sizeof(DWORD); i++) + { + OldTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency); + Sleep(SleepTimes[i]); + NewTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency); + + TimeDelta = NewTimeStamp - OldTimeStamp; + + MaxDelta = SleepTimes[i] + AcceptableTimeError; + + if ( TimeDelta<SleepTimes[i] || TimeDelta>MaxDelta ) + { + Fail("The sleep function slept for %u ms when it should have " + "slept for %u ms\n", TimeDelta, SleepTimes[i]); + } + } + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/Sleep/test2/testinfo.dat b/src/pal/tests/palsuite/threading/Sleep/test2/testinfo.dat new file mode 100644 index 0000000000..433a061f2c --- /dev/null +++ b/src/pal/tests/palsuite/threading/Sleep/test2/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = Sleep +Name = Positive Test for Sleep +TYPE = DEFAULT +EXE1 = sleep +Description += Test to see if the Sleep function stops the thread from executing for the += specified amount of time. diff --git a/src/pal/tests/palsuite/threading/SleepEx/CMakeLists.txt b/src/pal/tests/palsuite/threading/SleepEx/CMakeLists.txt new file mode 100644 index 0000000000..ef14ea5352 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SleepEx/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) + diff --git a/src/pal/tests/palsuite/threading/SleepEx/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/SleepEx/test1/CMakeLists.txt new file mode 100644 index 0000000000..de562755fc --- /dev/null +++ b/src/pal/tests/palsuite/threading/SleepEx/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_sleepex_test1 + ${SOURCES} +) + +add_dependencies(paltest_sleepex_test1 coreclrpal) + +target_link_libraries(paltest_sleepex_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/SleepEx/test1/test1.c b/src/pal/tests/palsuite/threading/SleepEx/test1/test1.c new file mode 100644 index 0000000000..7ccfe0ce87 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SleepEx/test1/test1.c @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test1.c +** +** Purpose: Tests that SleepEx correctly sleeps for a given amount of time, +** regardless of the alertable flag. +** +** +**===================================================================*/ + +#include <palsuite.h> + +typedef struct +{ + DWORD SleepTime; + BOOL Alertable; +} testCase; + +testCase testCases[] = +{ + {0, FALSE}, + {50, FALSE}, + {100, FALSE}, + {500, FALSE}, + {2000, FALSE}, + + {0, TRUE}, + {50, TRUE}, + {100, TRUE}, + {500, TRUE}, + {2000, TRUE}, +}; + +/* Milliseconds of error which are acceptable Function execution time, etc. */ +DWORD AcceptableTimeError = 150; + +int __cdecl main( int argc, char **argv ) +{ + UINT64 OldTimeStamp; + UINT64 NewTimeStamp; + DWORD MaxDelta; + DWORD TimeDelta; + DWORD i; + + if (0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + LARGE_INTEGER performanceFrequency; + if (!QueryPerformanceFrequency(&performanceFrequency)) + { + return FAIL; + } + + for (i = 0; i<sizeof(testCases) / sizeof(testCases[0]); i++) + { + OldTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency); + + SleepEx(testCases[i].SleepTime, testCases[i].Alertable); + + NewTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency); + + TimeDelta = NewTimeStamp - OldTimeStamp; + + /* For longer intervals use a 10 percent tolerance */ + if ((testCases[i].SleepTime * 0.1) > AcceptableTimeError) + { + MaxDelta = testCases[i].SleepTime + (DWORD)(testCases[i].SleepTime * 0.1); + } + else + { + MaxDelta = testCases[i].SleepTime + AcceptableTimeError; + } + + if (TimeDelta < testCases[i].SleepTime || TimeDelta > MaxDelta) + { + Fail("The sleep function slept for %d ms when it should have " + "slept for %d ms\n", TimeDelta, testCases[i].SleepTime); + } + } + + PAL_Terminate(); + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/SleepEx/test1/testinfo.dat b/src/pal/tests/palsuite/threading/SleepEx/test1/testinfo.dat new file mode 100644 index 0000000000..1242768743 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SleepEx/test1/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = SleepEx +Name = Test #1 for SleepEx +TYPE = DEFAULT +EXE1 = test1 +Description +=Tests that SleepEx correctly sleeps for a given amount of time, +=regardless of the alertable flag. diff --git a/src/pal/tests/palsuite/threading/SleepEx/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/SleepEx/test2/CMakeLists.txt new file mode 100644 index 0000000000..0ea4a44449 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SleepEx/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test2.c +) + +add_executable(paltest_sleepex_test2 + ${SOURCES} +) + +add_dependencies(paltest_sleepex_test2 coreclrpal) + +target_link_libraries(paltest_sleepex_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/SleepEx/test2/test2.c b/src/pal/tests/palsuite/threading/SleepEx/test2/test2.c new file mode 100644 index 0000000000..c2ba4e704d --- /dev/null +++ b/src/pal/tests/palsuite/threading/SleepEx/test2/test2.c @@ -0,0 +1,183 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test2.c +** +** Purpose: Tests that a child thread in the middle of a SleepEx call will be +** interrupted by QueueUserAPC if the alert flag was set. +** +** +**===================================================================*/ + +#include <palsuite.h> + +const int ChildThreadSleepTime = 2000; +const int InterruptTime = 1000; +/* We need to keep in mind that BSD has a timer resolution of 10ms, so + we need to adjust our delta to keep that in mind. Besides we need some + tolerance to account for different scheduling strategies, heavy load + scenarios, etc. + + Real-world data also tells us we can expect a big difference between + values when run on real iron vs run in a hypervisor. + + Thread-interruption times when run on bare metal will typically yield + around 0ms on Linux and between 0 and 16ms on FreeBSD. However, when run + in a hypervisor (like VMWare ESXi) we may get values around an order of + magnitude higher, up to 110 ms for some tests. +*/ +const DWORD AcceptableDelta = 150; + +const int Iterations = 5; + +void RunTest(BOOL AlertThread); +VOID PALAPI APCFunc(ULONG_PTR dwParam); +DWORD PALAPI SleeperProc(LPVOID lpParameter); + +DWORD ThreadSleepDelta; + +int __cdecl main( int argc, char **argv ) +{ + int i; + DWORD dwAvgDelta; + + if (0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + /* + On some platforms (e.g. FreeBSD 4.9) the first call to some synch objects + (such as conditions) involves some pthread internal initialization that + can make the first wait slighty longer, potentially going above the + acceptable delta for this test. Let's add a dummy wait to preinitialize + internal structures + */ + Sleep(100); + + /* + * Check that Queueing an APC in the middle of a sleep does interrupt + * it, if it's in an alertable state. + */ + dwAvgDelta = 0; + for (i=0;i<Iterations;i++) + { + RunTest(TRUE); + dwAvgDelta += ThreadSleepDelta - InterruptTime; + } + dwAvgDelta /= Iterations; + + if (dwAvgDelta > AcceptableDelta) + { + Fail("Expected thread to sleep for %d ms (and get interrupted).\n" + "Average delta: %u ms, acceptable delta: %u\n", + InterruptTime, dwAvgDelta, AcceptableDelta); + } + + /* + * Check that Queueing an APC in the middle of a sleep does NOT interrupt + * it, if it is not in an alertable state. + */ + dwAvgDelta = 0; + for (i=0;i<Iterations;i++) + { + RunTest(FALSE); + dwAvgDelta += ThreadSleepDelta - ChildThreadSleepTime; + } + dwAvgDelta /= Iterations; + + if (dwAvgDelta > AcceptableDelta) + { + Fail("Expected thread to sleep for %d ms (and not be interrupted).\n" + "Average delta: %u ms, acceptable delta: %u\n", + ChildThreadSleepTime, dwAvgDelta, AcceptableDelta); + } + + PAL_Terminate(); + return PASS; +} + +void RunTest(BOOL AlertThread) +{ + HANDLE hThread = 0; + DWORD dwThreadId = 0; + int ret; + + hThread = CreateThread( NULL, + 0, + (LPTHREAD_START_ROUTINE)SleeperProc, + (LPVOID) AlertThread, + 0, + &dwThreadId); + + if (hThread == NULL) + { + Fail("ERROR: Was not able to create the thread to test SleepEx!\n" + "GetLastError returned %d\n", GetLastError()); + } + + if (SleepEx(InterruptTime, FALSE) != 0) + { + Fail("The creating thread did not sleep!\n"); + } + + ret = QueueUserAPC(APCFunc, hThread, 0); + if (ret == 0) + { + Fail("QueueUserAPC failed! GetLastError returned %d\n", GetLastError()); + } + + ret = WaitForSingleObject(hThread, INFINITE); + if (ret == WAIT_FAILED) + { + Fail("Unable to wait on child thread!\nGetLastError returned %d.", + GetLastError()); + } +} + +/* Function doesn't do anything, just needed to interrupt SleepEx */ +VOID PALAPI APCFunc(ULONG_PTR dwParam) +{ + +} + +/* Entry Point for child thread. */ +DWORD PALAPI SleeperProc(LPVOID lpParameter) +{ + UINT64 OldTimeStamp; + UINT64 NewTimeStamp; + BOOL Alertable; + DWORD ret; + + Alertable = (BOOL) lpParameter; + + LARGE_INTEGER performanceFrequency; + if (!QueryPerformanceFrequency(&performanceFrequency)) + { + return FAIL; + } + + OldTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency); + + ret = SleepEx(ChildThreadSleepTime, Alertable); + + NewTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency); + + if (Alertable && ret != WAIT_IO_COMPLETION) + { + Fail("Expected the interrupted sleep to return WAIT_IO_COMPLETION.\n" + "Got %d\n", ret); + } + else if (!Alertable && ret != 0) + { + Fail("Sleep did not timeout. Expected return of 0, got %d.\n", ret); + } + + + ThreadSleepDelta = NewTimeStamp - OldTimeStamp; + + return 0; +} diff --git a/src/pal/tests/palsuite/threading/SleepEx/test2/testinfo.dat b/src/pal/tests/palsuite/threading/SleepEx/test2/testinfo.dat new file mode 100644 index 0000000000..52f3ce0af6 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SleepEx/test2/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = SleepEx +Name = Test #2 for SleepEx +TYPE = DEFAULT +EXE1 = test2 +Description +=Tests that a child thread in the middle of a SleepEx call will be +=interrupted by QueueUserAPC if the alert flag was set. + diff --git a/src/pal/tests/palsuite/threading/SwitchToThread/CMakeLists.txt b/src/pal/tests/palsuite/threading/SwitchToThread/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SwitchToThread/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/threading/SwitchToThread/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/SwitchToThread/test1/CMakeLists.txt new file mode 100644 index 0000000000..40ecc910b0 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SwitchToThread/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_switchtothread_test1 + ${SOURCES} +) + +add_dependencies(paltest_switchtothread_test1 coreclrpal) + +target_link_libraries(paltest_switchtothread_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/SwitchToThread/test1/test1.c b/src/pal/tests/palsuite/threading/SwitchToThread/test1/test1.c new file mode 100644 index 0000000000..76ecdd3572 --- /dev/null +++ b/src/pal/tests/palsuite/threading/SwitchToThread/test1/test1.c @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test1.c +** +** Purpose: Test to ensure SwitchToThread works, without +** causing test to hang +** +** Dependencies: PAL_Initialize +** Fail +** SwitchToThread +** WaitForMultipleObject +** CreateThread +** GetLastError +** + +** +**===========================================================================*/ + + +#include <palsuite.h> +#define THREAD_COUNT 10 +#define REPEAT_COUNT 1000 +#define TIMEOUT 60000 +void PALAPI Run_Thread(LPVOID lpParam); + +/** + * main + * + * executable entry point + */ +INT __cdecl main( INT argc, CHAR **argv ) +{ + DWORD dwParam; + HANDLE hThread[THREAD_COUNT]; + DWORD threadId[THREAD_COUNT]; + + int i = 0; + int returnCode = 0; + + /*PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return FAIL; + } + + + for( i = 0; i < THREAD_COUNT; i++ ) + { + dwParam = (int) i; + //Create thread + hThread[i] = CreateThread( + NULL, /* no security attributes */ + 0, /* use default stack size */ + (LPTHREAD_START_ROUTINE)Run_Thread,/* thread function */ + (LPVOID)dwParam, /* argument to thread function */ + 0, /* use default creation flags */ + &threadId[i] /* returns the thread identifier*/ + ); + + if(hThread[i] == NULL) + { + Fail("Create Thread failed for iteration %d GetLastError value is %d\n", i, GetLastError()); + } + + } + + + returnCode = WaitForMultipleObjects(THREAD_COUNT, hThread, TRUE, TIMEOUT); + if( WAIT_OBJECT_0 != returnCode ) + { + Trace("Wait for Object(s) returned %d, expected value is %d, and GetLastError value is %d\n", returnCode, WAIT_OBJECT_0, GetLastError()); + } + + PAL_Terminate(); + return PASS; + +} + +void PALAPI Run_Thread (LPVOID lpParam) +{ + int i = 0; + int Id=(int)lpParam; + + for(i=0; i < REPEAT_COUNT; i++ ) + { + // No Last Error is set.. + if(!SwitchToThread()) + { + Trace( "The operating system did not switch execution to another thread," + "for thread id[%d], iteration [%d]\n", Id, i ); + } + } +} diff --git a/src/pal/tests/palsuite/threading/SwitchToThread/test1/testinfo.dat b/src/pal/tests/palsuite/threading/SwitchToThread/test1/testinfo.dat new file mode 100644 index 0000000000..15ee8d4d4e --- /dev/null +++ b/src/pal/tests/palsuite/threading/SwitchToThread/test1/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = SwitchToThread +Name = Test for SwitchToThread +TYPE = DEFAULT +EXE1 = test +Description += Purpose: Test to ensure SwitchToThread is += working properly on supported platforms diff --git a/src/pal/tests/palsuite/threading/TLS/CMakeLists.txt b/src/pal/tests/palsuite/threading/TLS/CMakeLists.txt new file mode 100644 index 0000000000..bffdf7f714 --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) +add_subdirectory(test3) +add_subdirectory(test4) +add_subdirectory(test5) + + diff --git a/src/pal/tests/palsuite/threading/TLS/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/TLS/test1/CMakeLists.txt new file mode 100644 index 0000000000..ff1a866eb9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + TLS.c +) + +add_executable(paltest_tls_test1 + ${SOURCES} +) + +add_dependencies(paltest_tls_test1 coreclrpal) + +target_link_libraries(paltest_tls_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/TLS/test1/TLS.c b/src/pal/tests/palsuite/threading/TLS/test1/TLS.c new file mode 100644 index 0000000000..4300c3f98b --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test1/TLS.c @@ -0,0 +1,182 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: tls.c +** +** Purpose: Test to ensure TlsAlloc, TlsGetValue, TlsSetValue +** and TlsFree are working properly together. +** +** Dependencies: PAL_Initialize +** Fail +** Sleep +** LocalAlloc +** LocalFree +** WaitForSingleObject +** CreateThread +** GetLastError +** + +** +**===========================================================================*/ +#include <palsuite.h> + +#define NUM_OF_THREADS 10 + +DWORD dwTlsIndex; /* TLS index */ + +/** + * CommonFunction + * + * Helper function that calls TlsGetValue + */ +VOID CommonFunction(VOID) +{ + LPVOID lpvData; + DWORD dwError; + + /* Retrieve a data pointer for the current thread. */ + lpvData = TlsGetValue(dwTlsIndex); + + if ( (lpvData == 0) && + ((dwError = GetLastError()) != NO_ERROR) ) + {/*ERROR */ + Fail("TlsGetValue(%d) returned 0 with error %d\n", + dwTlsIndex, + dwError); + } + + Sleep(5000); +} + +/** + * ThreadFunc + * + * Thread function that stores a value in the thread's tls slot + * for the predefined tls index + */ +DWORD PALAPI ThreadFunc(LPVOID lpThreadParameter) +{ + LPVOID lpvData; + DWORD dwError; + + /* Initialize the TLS index for this thread.*/ + lpvData = (LPVOID) LocalAlloc(0, 256); + + if( lpvData == NULL ) + {/*ERROR */ + dwError = GetLastError(); + Fail("Unexpected LocalAlloc(0, 256) failure with error %d\n", + dwError); + } + + + if ( TlsSetValue(dwTlsIndex, lpvData) == 0 ) + {/*ERROR */ + dwError = GetLastError(); + Fail("TlsSetValue(%d, %x) returned 0 with error %d\n", + dwTlsIndex, + lpvData, + dwError); + } + + CommonFunction(); + + /* Release the dynamic memory. */ + lpvData = TlsGetValue(dwTlsIndex); + + if ( (lpvData == 0) && + ((dwError = GetLastError()) != NO_ERROR) ) + {/*ERROR */ + Fail("TlsGetValue(%d) returned 0 with error %d\n", + dwTlsIndex, + dwError); + } + else + { + if( LocalFree((HLOCAL) lpvData) != NULL ) + { + dwError = GetLastError(); + Fail("Unexpected LocalFree(%x) failure with error %d\n", + lpvData, + dwError); + } + } + + return PASS; +} + +/** + * main + * + * executable entry point + */ +INT __cdecl main( INT argc, CHAR **argv ) +{ + DWORD IDThread; + HANDLE hThread[NUM_OF_THREADS]; + int i; + + /*PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return FAIL; + } + + /*Allocate a TLS index. */ + if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES) + {/*RROR*/ + DWORD dwError = GetLastError(); + Fail("TlsAlloc() returned error %d\n", + dwError); + } + + /*Create multiple threads.*/ + + for (i = 0; i < NUM_OF_THREADS; i++) + { + hThread[i] = CreateThread(NULL, /* no security attributes*/ + 0, /* use default stack size */ + ThreadFunc, /* thread function */ + NULL, /* no thread function argument */ + 0, /* use default creation flags */ + &IDThread); /* returns thread identifier */ + + /* Check the return value for success. */ + if (hThread[i] == NULL) + {/* ERROR */ + DWORD dwError = GetLastError(); + Fail("Unexpected CreateThread error %d\n", + dwError); + } + } + + /* Wait for all threads to finish */ + for (i = 0; i < NUM_OF_THREADS; i++) + { + DWORD dwRet; + + dwRet = WaitForSingleObject(hThread[i], INFINITE); + + if( dwRet == WAIT_FAILED ) + {/* ERROR */ + DWORD dwError = GetLastError(); + Fail("Unexpected WaitForSingleObject error %d\n", + dwError); + } + } + + /* Release the TLS index */ + if( TlsFree( dwTlsIndex ) == 0 ) + {/* ERROR */ + DWORD dwError = GetLastError(); + Fail("TlsFree() returned 0 with error %d\n", + dwError); + } + + PAL_Terminate(); + return PASS; +} + diff --git a/src/pal/tests/palsuite/threading/TLS/test1/testinfo.dat b/src/pal/tests/palsuite/threading/TLS/test1/testinfo.dat new file mode 100644 index 0000000000..544e391266 --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test1/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = TLS +Name = Test for TlsAlloc, TlsGetValue, TlsSetValue and TlsFree +TYPE = DEFAULT +EXE1 = tls +Description += Test to ensure TlsAlloc, TlsGetValue, TlsSetValue += and TlsFree are working properly together. diff --git a/src/pal/tests/palsuite/threading/TLS/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/TLS/test2/CMakeLists.txt new file mode 100644 index 0000000000..5afe82b4a6 --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + TLS.c +) + +add_executable(paltest_tls_test2 + ${SOURCES} +) + +add_dependencies(paltest_tls_test2 coreclrpal) + +target_link_libraries(paltest_tls_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/TLS/test2/TLS.c b/src/pal/tests/palsuite/threading/TLS/test2/TLS.c new file mode 100644 index 0000000000..96a6011f96 --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test2/TLS.c @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: tls.c +** +** Purpose: Test to ensure TlsAlloc and TlsFree are working when we try +** to allocate the guaranted minimum number of indicies. +** + +** +**===========================================================================*/ +#include <palsuite.h> + +#define NUM_OF_INDEX 64 +/* Minimum guaranteed is at least 64 for all systems.*/ + +/** + * main + * + * executable entry point + */ +INT __cdecl main( INT argc, CHAR **argv ) +{ + DWORD dwIndexes[NUM_OF_INDEX]; + int i,j; + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return FAIL; + } + + /* Allocate a bunch of TLS indexes. */ + for( i = 0; i < NUM_OF_INDEX; i++ ) + { + if( (dwIndexes[i] = TlsAlloc()) == TLS_OUT_OF_INDEXES ) + {/*ERROR */ + DWORD dwError = GetLastError(); + Fail("TlsAlloc() returned -1 with error %d" + "when trying to allocate %d index\n", + dwError, + i); + } + } + + /* Free the TLS indexes.*/ + for( j = 0; j < NUM_OF_INDEX; j++ ) + { + if( TlsFree(dwIndexes[j]) == 0 ) + {/* ERROR */ + DWORD dwError = GetLastError(); + Fail("TlsFree() returned 0 with error %d" + "when trying to free %d index\n", + dwError, + i); + } + } + + PAL_Terminate(); + + return PASS; +} + diff --git a/src/pal/tests/palsuite/threading/TLS/test2/testinfo.dat b/src/pal/tests/palsuite/threading/TLS/test2/testinfo.dat new file mode 100644 index 0000000000..3a672a5f38 --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test2/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = TLS +Name = Test for TlsAlloc and TlsFree +TYPE = DEFAULT +EXE1 = tls +Description += Test to ensure TlsAlloc and TlsFree are working when we try += to allocate the guaranted minimum number of index. diff --git a/src/pal/tests/palsuite/threading/TLS/test3/CMakeLists.txt b/src/pal/tests/palsuite/threading/TLS/test3/CMakeLists.txt new file mode 100644 index 0000000000..0964d33d2c --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test3/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + TLS.c +) + +add_executable(paltest_tls_test3 + ${SOURCES} +) + +add_dependencies(paltest_tls_test3 coreclrpal) + +target_link_libraries(paltest_tls_test3 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/TLS/test3/TLS.c b/src/pal/tests/palsuite/threading/TLS/test3/TLS.c new file mode 100644 index 0000000000..4acaef5020 --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test3/TLS.c @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: tls.c +** +** Purpose: Test to ensure TlsGetValue, TlsSetValue and TlsFree +** are not working with an invalid index +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** LocalAlloc +** LocalFree +** + +** +**===========================================================================*/ +#include <palsuite.h> + +DWORD dwTlsIndex; /* TLS index */ + +/** + * main + * + * executable entry point + */ +INT __cdecl main( INT argc, CHAR **argv ) +{ + CHAR lpstrData[256] = ""; + LPVOID lpvData = NULL; + BOOL bRet; + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return FAIL; + } + + /* Invalid TLS index */ + dwTlsIndex = -1; + + /* + * Set some data in the invalid TLS index + *Should return 0 and an error + */ + bRet = TlsSetValue(dwTlsIndex, (LPVOID)lpstrData); + + if ( bRet != 0) + {/*ERROR */ + Fail("TlsSetValue(%d, %x) returned %d " + "when it should have returned 0 and an error\n", + dwTlsIndex, + lpvData, + bRet); + } + + /* + * Get the data at the invalid index + * Should return 0 and an error + */ + lpvData = TlsGetValue(dwTlsIndex); + + if ( lpvData != 0 ) + {/* ERROR */ + Fail("TlsGetValue(%d) returned %d " + "when it should have returned 0 and an error\n", + dwTlsIndex, + lpvData); + } + + /* + * Release the invalid TLS index + * Should return 0 and an error + */ + bRet = TlsFree( dwTlsIndex ); + + if( bRet != 0 ) + {/* ERROR */ + Fail("TlsFree() returned %d " + "when it should have returned 0 and an error\n", + bRet); + } + + PAL_Terminate(); + return PASS; +} + + diff --git a/src/pal/tests/palsuite/threading/TLS/test3/testinfo.dat b/src/pal/tests/palsuite/threading/TLS/test3/testinfo.dat new file mode 100644 index 0000000000..63ce59f351 --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test3/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = TLS +Name = TlsGetValue, TlsSetValue and TlsFree +TYPE = DEFAULT +EXE1 = tls +Description += Test to ensure TlsGetValue, TlsSetValue and TlsFree += are not working with an invalid index diff --git a/src/pal/tests/palsuite/threading/TLS/test4/CMakeLists.txt b/src/pal/tests/palsuite/threading/TLS/test4/CMakeLists.txt new file mode 100644 index 0000000000..7e7b47786a --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test4/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test4.c +) + +add_executable(paltest_tls_test4 + ${SOURCES} +) + +add_dependencies(paltest_tls_test4 coreclrpal) + +target_link_libraries(paltest_tls_test4 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/TLS/test4/test4.c b/src/pal/tests/palsuite/threading/TLS/test4/test4.c new file mode 100644 index 0000000000..8c3603cdb0 --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test4/test4.c @@ -0,0 +1,137 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test4.c (threading/tls) +** +** Purpose: Test to ensure that upon key creation, the value NULL +** is associated with the new key in all active threads. +** Upon thread creation, the value NULL is associated +** with all defined keys in the new thread. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** LocalAlloc +** LocalFree +** + +** +**===========================================================================*/ +#include <palsuite.h> + +#define NUM_OF_THREADS 10 + +DWORD dwTlsIndex; /* TLS index */ + +/** + * ThreadFunc + * + * Thread function that checks that NULL is associated with the tls index + */ +DWORD PALAPI ThreadFunc(VOID) +{ + LPVOID lpvData; + DWORD dwError; + + /* Retrieve a data pointer for the current thread. + The return value should be NULL since no data has been + set in the index */ + lpvData = TlsGetValue(dwTlsIndex); + + if ( (lpvData != NULL) && + ((dwError = GetLastError()) == NO_ERROR) ) + {/*ERROR */ + Fail("TlsGetValue(%d) returned data " + "even if no data was associated with the index\n", + dwTlsIndex); + } + + return PASS; +} + +/** + * main + * + * executable entry point + */ +INT __cdecl main( INT argc, CHAR **argv ) +{ + DWORD IDThread; + LPVOID lpvData; + DWORD dwError; + HANDLE hThread[NUM_OF_THREADS]; + int i; + + /*PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return FAIL; + } + + /*Allocate a TLS index. */ + if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES) + {/*ERROR*/ + DWORD dwError = GetLastError(); + Fail("TlsAlloc() returned error %d\n", + dwError); + } + + /*Check that the value associated with the tls index is NULL*/ + lpvData = TlsGetValue(dwTlsIndex); + + if ( (lpvData != NULL) && + ((dwError = GetLastError()) == NO_ERROR) ) + {/*ERROR */ + Fail("TlsGetValue(%d) returned non-null data " + "even if no data was associated with the index\n", + dwTlsIndex); + } + + /*Create multiple threads.*/ + for (i = 0; i < NUM_OF_THREADS; i++) + { + hThread[i] = CreateThread(NULL, /* no security attributes*/ + 0, /* use default stack size */ + (LPTHREAD_START_ROUTINE) ThreadFunc, /* thread function */ + NULL, /* no thread function argument */ + 0, /* use default creation flags */ + &IDThread); /* returns thread identifier */ + + /* Check the return value for success. */ + if (hThread[i] == NULL) + {/* ERROR */ + DWORD dwError = GetLastError(); + Fail("Unexpected CreateThread error %d\n", + dwError); + } + } + + /* Wait for all threads to finish */ + for (i = 0; i < NUM_OF_THREADS; i++) + { + DWORD dwRet; + + dwRet = WaitForSingleObject(hThread[i], INFINITE); + + if( dwRet == WAIT_FAILED ) + {/* ERROR */ + DWORD dwError = GetLastError(); + Fail("Unexpected WaitForSingleObject error %d\n", + dwError); + } + } + + /* Release the TLS index */ + if( TlsFree( dwTlsIndex ) == 0 ) + {/* ERROR */ + DWORD dwError = GetLastError(); + Fail("TlsFree() returned 0 with error %d\n", + dwError); + } + + PAL_Terminate(); + return PASS; +} + diff --git a/src/pal/tests/palsuite/threading/TLS/test4/testinfo.dat b/src/pal/tests/palsuite/threading/TLS/test4/testinfo.dat new file mode 100644 index 0000000000..6001770642 --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test4/testinfo.dat @@ -0,0 +1,15 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = TLS +Name = TlsAlloc, TlsGetValue +TYPE = DEFAULT +EXE1 = test4 +Description +=Test to ensure that upon key creation, the value NULL +=is associated with the new key in all active threads. +=Upon thread creation, the value NULL is associated +=with all defined keys in the new thread. diff --git a/src/pal/tests/palsuite/threading/TLS/test5/CMakeLists.txt b/src/pal/tests/palsuite/threading/TLS/test5/CMakeLists.txt new file mode 100644 index 0000000000..5fb5c9ddfd --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test5/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test5.c +) + +add_executable(paltest_tls_test5 + ${SOURCES} +) + +add_dependencies(paltest_tls_test5 coreclrpal) + +target_link_libraries(paltest_tls_test5 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/TLS/test5/test5.c b/src/pal/tests/palsuite/threading/TLS/test5/test5.c new file mode 100644 index 0000000000..c1cd132937 --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test5/test5.c @@ -0,0 +1,108 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test5.c (threading/tls) +** +** Purpose: Test that creates a key, sets its value, deletes the key, +** creates a new key, and gets its value to make sure its NULL. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** LocalAlloc +** LocalFree +** + +** +**===========================================================================*/ +#include <palsuite.h> + +DWORD dwTlsIndex; /* TLS index */ + +/** + * main + * + * executable entry point + */ +INT __cdecl main( INT argc, CHAR **argv ) +{ + LPVOID lpvData; + DWORD dwError; + + /*PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return FAIL; + } + + /** + * create a key, set its value, delete the key + */ + + /*Allocate a TLS index. */ + if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES) + {/*ERROR*/ + DWORD dwError = GetLastError(); + Fail("TlsAlloc() returned error %d\n", + dwError); + } + + /* Initialize the TLS index for this thread.*/ + lpvData = (LPVOID) LocalAlloc(0, 256); + + if( lpvData == NULL ) + {/*ERROR */ + dwError = GetLastError(); + Fail("Unexpected LocalAlloc(0, 256) failure with error %d\n", + dwError); + } + + if ( TlsSetValue(dwTlsIndex, lpvData) == 0 ) + {/*ERROR */ + dwError = GetLastError(); + Fail("TlsSetValue(%d, %x) returned 0 with error %d\n", + dwTlsIndex, + lpvData, + dwError); + } + + /* Release the TLS index */ + if( TlsFree( dwTlsIndex ) == 0 ) + {/* ERROR */ + DWORD dwError = GetLastError(); + Fail("TlsFree() returned 0 with error %d\n", + dwError); + } + + + /** + * create a new key, and get its value + */ + + /*Allocate a TLS index. */ + if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES) + {/*ERROR*/ + DWORD dwError = GetLastError(); + Fail("TlsAlloc() returned error %d\n", + dwError); + } + + /* Retrieve a data pointer for the current thread. + The return value should be NULL since no data has been + set in the index */ + lpvData = TlsGetValue(dwTlsIndex); + + if ( (lpvData != NULL) && + ((dwError = GetLastError()) == NO_ERROR) ) + {/*ERROR */ + Fail("TlsGetValue(%d) returned data " + "even if no data was associated with the index\n", + dwTlsIndex); + } + + PAL_Terminate(); + return PASS; +} + diff --git a/src/pal/tests/palsuite/threading/TLS/test5/testinfo.dat b/src/pal/tests/palsuite/threading/TLS/test5/testinfo.dat new file mode 100644 index 0000000000..4b3e2a64c5 --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test5/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = TLS +Name = TlsAlloc and TlsGetValue +TYPE = DEFAULT +EXE1 = test5 +Description += Test that creates a key, sets its value, deletes the key, += creates a new key, and gets its value to make sure its NULL. diff --git a/src/pal/tests/palsuite/threading/TLS/test6_optimizedtls/CMakeLists.txt b/src/pal/tests/palsuite/threading/TLS/test6_optimizedtls/CMakeLists.txt new file mode 100644 index 0000000000..8b3a4ed64a --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test6_optimizedtls/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test.c +) + +add_executable(paltest_tls_test6_optimizedtls + ${SOURCES} +) + +add_dependencies(paltest_tls_test6_optimizedtls coreclrpal) + +target_link_libraries(paltest_tls_test6_optimizedtls + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/TLS/test6_optimizedtls/test.c b/src/pal/tests/palsuite/threading/TLS/test6_optimizedtls/test.c new file mode 100644 index 0000000000..02419dc90c --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test6_optimizedtls/test.c @@ -0,0 +1,190 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test.c +** +** Purpose: Test to ensure TlsAlloc, PAL_MakeOptimizedTlsGetter, +** PAL_FreeOptimizedTlsGetter and TlsFree are working properly +** on supported platforms +** +** Dependencies: PAL_Initialize +** Fail +** Sleep +** LocalAlloc +** LocalFree +** WaitForSingleObject +** CreateThread +** GetLastError +** + +** +**===========================================================================*/ + + +#include <palsuite.h> +#define THREAD_COUNT 5 +DWORD dwTlsIndex; /* TLS index */ + +void PALAPI Run_Thread(LPVOID lpParam); + +/** + * main + * + * executable entry point + */ +INT __cdecl main( INT argc, CHAR **argv ) +{ + DWORD dwParam; + DWORD dwError; + HANDLE hThread[THREAD_COUNT]; + DWORD threadId[THREAD_COUNT]; + + int i = 0; + int returnCode = 0; + + /*PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return FAIL; + } + + /*Allocate a TLS index. */ + if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES) + { + /*ERROR*/ + dwError = GetLastError(); + Fail("TlsAlloc() returned error %d\n", + dwError); + } + + + for( i = 0; i < THREAD_COUNT; i++ ) + { + dwParam = (int) i; + //Create thread + hThread[i] = CreateThread( + NULL, /* no security attributes */ + 0, /* use default stack size */ + (LPTHREAD_START_ROUTINE)Run_Thread,/* thread function */ + (LPVOID)dwParam, /* argument to thread function */ + 0, /* use default creation flags */ + &threadId[i] /* returns the thread identifier*/ + ); + + if(hThread[i] == NULL) + { + Fail("Create Thread failed for iteration %d GetLastError value is %d\n", i, GetLastError()); + } + + } + + + returnCode = WaitForMultipleObjects(THREAD_COUNT, hThread, TRUE, INFINITE); + if( WAIT_OBJECT_0 != returnCode ) + { + Trace("Wait for Object(s) returned %d, expected value is %d, and GetLastError value is %d\n", returnCode, WAIT_OBJECT_0, GetLastError()); + } + + /* Release the TLS index */ + if( TlsFree( dwTlsIndex ) == 0 ) + { + /* ERROR */ + dwError = GetLastError(); + Fail("TlsFree() returned 0 with error %d\n", + dwError); + } + + PAL_Terminate(); + return PASS; + +} + +void PALAPI Run_Thread (LPVOID lpParam) +{ + unsigned int i = 0; + + LPVOID lpvData; + DWORD dwError; + PAL_POPTIMIZEDTLSGETTER ptrOptimizedTlsGetter; + + int Id=(int)lpParam; + + + lpvData = TlsGetValue(dwTlsIndex); + if ( (lpvData != NULL) && + ((dwError = GetLastError()) == NO_ERROR) ) + { + /*ERROR */ + Fail("Error:%d:TlsGetValue(%d) returned data " + "even if data was not associated with the index, for thread id [%d]\n", + dwError, dwTlsIndex, Id); + } + + + /* Initialize the TLS index for this thread.*/ + lpvData = (LPVOID) LocalAlloc(0, 256); + + if( lpvData == NULL ) + { + /*ERROR */ + dwError = GetLastError(); + Fail("Unexpected LocalAlloc(0, 256) failure with error %d\n", + dwError); + } + + if ( TlsSetValue(dwTlsIndex, lpvData) == 0 ) + { + /*ERROR */ + dwError = GetLastError(); + Fail("TlsSetValue(%d, %x) returned 0 with error %d\n", + dwTlsIndex, + lpvData, + dwError); + } + + ptrOptimizedTlsGetter = PAL_MakeOptimizedTlsGetter(dwTlsIndex); + if( ptrOptimizedTlsGetter == NULL ) + { + /* Retrieve a data pointer for the current thread. + The return value should be NULL since no data has been + set in the index */ + lpvData = TlsGetValue(dwTlsIndex); + Trace("Not Inside the optimizer loop for thread [%d]\n", Id); + + if ( (lpvData == NULL) && + ((dwError = GetLastError()) == NO_ERROR) ) + { + /*ERROR */ + Fail("Error:%d:TlsGetValue(%d) returned data " + "as NULL even if data was associated with the index, for thread id [%d]\n", + dwError, dwTlsIndex, Id); + } + } + else + { + /* Retrieve a data pointer for the current thread. + The return value should be NULL since no data has been + set in the index */ + lpvData = ptrOptimizedTlsGetter(); + + if ( (lpvData == NULL) && + ((dwError = GetLastError()) == NO_ERROR) ) + { + /*ERROR */ + Fail(" Error:%d: MakeOptimizedTlsGetter for dwTlsIndex (%d) returned data " + "as NULL even if no data was associated with the index, for thread id [%d]\n", + dwError, dwTlsIndex, Id); + } + + Trace("Inside the optimizer loop for thread [%d]\n", Id); + PAL_FreeOptimizedTlsGetter(ptrOptimizedTlsGetter); + } + + + + +} + diff --git a/src/pal/tests/palsuite/threading/TLS/test6_optimizedtls/testinfo.dat b/src/pal/tests/palsuite/threading/TLS/test6_optimizedtls/testinfo.dat new file mode 100644 index 0000000000..2193edcab3 --- /dev/null +++ b/src/pal/tests/palsuite/threading/TLS/test6_optimizedtls/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = PAL_MakeOptimizedTlsGetter and PAL_FreeOptimizedTlsGetter +Name = Test for PAL_MakeOptimizedTlsGetterandPAL_FreeOptimizedTlsGetter +TYPE = DEFAULT +EXE1 = test +Description += Purpose: Test to ensure TlsAlloc, PAL_MakeOptimizedTlsGetter, += PAL_FreeOptimizedTlsGetter and TlsFree are working properly += on supported platforms diff --git a/src/pal/tests/palsuite/threading/TerminateProcess/CMakeLists.txt b/src/pal/tests/palsuite/threading/TerminateProcess/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/TerminateProcess/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/threading/TerminateProcess/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/TerminateProcess/test1/CMakeLists.txt new file mode 100644 index 0000000000..37d575952f --- /dev/null +++ b/src/pal/tests/palsuite/threading/TerminateProcess/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + TerminateProcess.c +) + +add_executable(paltest_terminateprocess_test1 + ${SOURCES} +) + +add_dependencies(paltest_terminateprocess_test1 coreclrpal) + +target_link_libraries(paltest_terminateprocess_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/TerminateProcess/test1/TerminateProcess.c b/src/pal/tests/palsuite/threading/TerminateProcess/test1/TerminateProcess.c new file mode 100644 index 0000000000..6feedfce76 --- /dev/null +++ b/src/pal/tests/palsuite/threading/TerminateProcess/test1/TerminateProcess.c @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: terminateprocess/test1/terminateprocess.c +** +** Purpose: Test to see if TerminateProcess will +** terminate the current process. +** +** Dependencies: GetCurrentProcess +** +** +**=========================================================*/ + +#include <palsuite.h> + +INT __cdecl main( int argc, char **argv ) +{ + + HANDLE hProcess; + + if(0 != (PAL_Initialize(argc, argv))) + { + return (FAIL); + } + + hProcess = GetCurrentProcess(); + + Trace ("Testing TerminateProcess function.\n"); + + if ( 0 == ( TerminateProcess ( hProcess, PASS ) ) ) + { + Fail ("TerminateProcess failed.\n"); + } + + PAL_TerminateEx(FAIL); + return (FAIL); + +} diff --git a/src/pal/tests/palsuite/threading/TerminateProcess/test1/testinfo.dat b/src/pal/tests/palsuite/threading/TerminateProcess/test1/testinfo.dat new file mode 100644 index 0000000000..7ee69bfa29 --- /dev/null +++ b/src/pal/tests/palsuite/threading/TerminateProcess/test1/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = TerminateProcess +Name = Positive Test for TerminateProcess +TYPE = DEFAULT +EXE1 = terminateprocess +Description += Test to see if the function TerminateProcess terminates the currently += running process and passes the appropriate value through uExitCode. + diff --git a/src/pal/tests/palsuite/threading/ThreadPriority/CMakeLists.txt b/src/pal/tests/palsuite/threading/ThreadPriority/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ThreadPriority/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/threading/ThreadPriority/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/ThreadPriority/test1/CMakeLists.txt new file mode 100644 index 0000000000..e341ca7435 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ThreadPriority/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + ThreadPriority.c +) + +add_executable(paltest_threadpriority_test1 + ${SOURCES} +) + +add_dependencies(paltest_threadpriority_test1 coreclrpal) + +target_link_libraries(paltest_threadpriority_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/ThreadPriority/test1/ThreadPriority.c b/src/pal/tests/palsuite/threading/ThreadPriority/test1/ThreadPriority.c new file mode 100644 index 0000000000..95bcdac52a --- /dev/null +++ b/src/pal/tests/palsuite/threading/ThreadPriority/test1/ThreadPriority.c @@ -0,0 +1,83 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: threadpriority.c +** +** Purpose: Test to ensure GetThreadPriority works properly. +** +** Dependencies: PAL_Initialize +** PAL_Terminate +** Fail +** CreateThread +** WaitForSingleObject +** GetLastError +** time() +** + +** +**===========================================================================*/ +#include <palsuite.h> + +/** + * CheckThreadPriority + * + * Helper function that checks the current thread priority + * against an expected value. + */ +static VOID CheckThreadPriority( HANDLE hThread, int expectedPriority ) +{ + int priority; + DWORD dwError = 0; + + /* get the current thread priority */ + priority = GetThreadPriority( hThread ); + if( priority == THREAD_PRIORITY_ERROR_RETURN ) + { + /* GetThreadPriority call failed */ + dwError = GetLastError(); + Fail( "Unexpected GetThreadPriority() failure " + "with error %d\n", dwError ); + } + else if( priority != expectedPriority ) + { + /* unexpected thread priority detected */ + Fail( "Unexpected initial thread priority value %d reported\n", + priority ); + } +} + + +/** + * main + * + * executable entry point + */ +INT __cdecl main( INT argc, CHAR **argv ) +{ + + /* PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return( FAIL ); + } + + /* set the thread priority of the main to the highest possible value + this will give the chance to the main thread to create all the + other threads */ + if(!SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_NORMAL)) + { + DWORD dwError; + + dwError = GetLastError(); + Fail( "Unexpected SetThreadPriority() failure with error %d\n", + dwError ); + } + + CheckThreadPriority( GetCurrentThread(), THREAD_PRIORITY_NORMAL ); + //Add verification of timing out here.. + PAL_Terminate(); + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/ThreadPriority/test1/testinfo.dat b/src/pal/tests/palsuite/threading/ThreadPriority/test1/testinfo.dat new file mode 100644 index 0000000000..0abd9c1e06 --- /dev/null +++ b/src/pal/tests/palsuite/threading/ThreadPriority/test1/testinfo.dat @@ -0,0 +1,17 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = ThreadPriority +Name = Test for GetThreadPriority and SetThreadPriority +TYPE = DEFAULT +EXE1 = threadpriority +Description += Test to ensure proper operation of the GetThreadPriority += and SetThreadPriority APIs. The test launches several threads += of varying priorities, and verifies that the correct priority += is reported for each. It also verifies that the processing += time for each test thread is consistent with the priority += that's set for it. diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjects/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForMultipleObjects/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjects/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjects/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForMultipleObjects/test1/CMakeLists.txt new file mode 100644 index 0000000000..2b1f946b26 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjects/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_waitformultipleobjects_test1 + ${SOURCES} +) + +add_dependencies(paltest_waitformultipleobjects_test1 coreclrpal) + +target_link_libraries(paltest_waitformultipleobjects_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjects/test1/test1.c b/src/pal/tests/palsuite/threading/WaitForMultipleObjects/test1/test1.c new file mode 100644 index 0000000000..8249c38d9d --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjects/test1/test1.c @@ -0,0 +1,224 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test1.c +** +** Purpose: Test for WaitForMultipleObjects. Call the function +** on an array of 4 events, and ensure that it returns correct +** results when we do so. +** +** +**=========================================================*/ + +#include <palsuite.h> + +/* Number of events in array */ +#define MAX_EVENTS 4 + +BOOL WaitForMultipleObjectsTest() +{ + BOOL bRet = TRUE; + DWORD dwRet = 0; + + DWORD i = 0, j = 0; + + LPSECURITY_ATTRIBUTES lpEventAttributes = NULL; + BOOL bManualReset = TRUE; + BOOL bInitialState = TRUE; + + HANDLE hEvent[MAX_EVENTS]; + + /* Run through this for loop and create 4 events */ + for (i = 0; i < MAX_EVENTS; i++) + { + hEvent[i] = CreateEvent( lpEventAttributes, + bManualReset, bInitialState, NULL); + + if (hEvent[i] == INVALID_HANDLE_VALUE) + { + Trace("WaitForMultipleObjectsTest:CreateEvent %u failed (%x)\n", i, GetLastError()); + bRet = FALSE; + break; + } + + /* Set the current event */ + bRet = SetEvent(hEvent[i]); + + if (!bRet) + { + Trace("WaitForMultipleObjectsTest:SetEvent %u failed (%x)\n", i, GetLastError()); + bRet = FALSE; + break; + } + + /* Ensure that this returns the correct value */ + dwRet = WaitForSingleObject(hEvent[i],0); + + if (dwRet != WAIT_OBJECT_0) + { + Trace("WaitForMultipleObjectsTest:WaitForSingleObject %u failed (%x)\n", i, GetLastError()); + bRet = FALSE; + break; + } + + /* Reset the event, and again ensure that the return value of + WaitForSingle is correct. + */ + bRet = ResetEvent(hEvent[i]); + + if (!bRet) + { + Trace("WaitForMultipleObjectsTest:ResetEvent %u failed (%x)\n", i, GetLastError()); + bRet = FALSE; + break; + } + + dwRet = WaitForSingleObject(hEvent[i],0); + + if (dwRet != WAIT_TIMEOUT) + { + Trace("WaitForMultipleObjectsTest:WaitForSingleObject %u failed (%x)\n", i, GetLastError()); + bRet = FALSE; + break; + } + } + + /* + * If the first section of the test passed, move on to the + * second. + */ + + if (bRet) + { + BOOL bWaitAll = TRUE; + DWORD nCount = MAX_EVENTS; + CONST HANDLE *lpHandles = &hEvent[0]; + + /* Call WaitForMultipleOjbects on all the events, the return + should be WAIT_TIMEOUT + */ + dwRet = WaitForMultipleObjects( nCount, + lpHandles, + bWaitAll, + 0); + + if (dwRet != WAIT_TIMEOUT) + { + Trace("WaitForMultipleObjectsTest:WaitForMultipleObjects failed (%x)\n", GetLastError()); + } + else + { + /* Step through each event and one at a time, set the + currect test, while reseting all the other tests + */ + + for (i = 0; i < MAX_EVENTS; i++) + { + for (j = 0; j < MAX_EVENTS; j++) + { + if (j == i) + { + + bRet = SetEvent(hEvent[j]); + + if (!bRet) + { + Trace("WaitForMultipleObjectsTest:SetEvent %u failed (%x)\n", j, GetLastError()); + break; + } + } + else + { + bRet = ResetEvent(hEvent[j]); + + if (!bRet) + { + Trace("WaitForMultipleObjectsTest:ResetEvent %u failed (%x)\n", j, GetLastError()); + } + } + } + + bWaitAll = FALSE; + + /* Check that WaitFor returns WAIT_OBJECT + i */ + dwRet = WaitForMultipleObjects( nCount, + lpHandles, bWaitAll, 0); + + if (dwRet != WAIT_OBJECT_0+i) + { + Trace("WaitForMultipleObjectsTest:WaitForMultipleObjects failed (%x)\n", GetLastError()); + bRet = FALSE; + break; + } + } + } + + for (i = 0; i < MAX_EVENTS; i++) + { + bRet = CloseHandle(hEvent[i]); + + if (!bRet) + { + Trace("WaitForMultipleObjectsTest:CloseHandle %u failed (%x)\n", i, GetLastError()); + } + } + } + + return bRet; +} + +BOOL WaitMultipleDuplicateHandleTest() +{ + BOOL testResult = TRUE; + const HANDLE eventHandle = CreateEvent(NULL, TRUE, TRUE, NULL); + HANDLE eventHandles[] = {eventHandle, eventHandle}; + + // WaitAny - Wait for any of the events (no error expected) + DWORD result = WaitForMultipleObjects(sizeof(eventHandles) / sizeof(eventHandles[0]), eventHandles, FALSE, 0); + if (result != WAIT_OBJECT_0) + { + Trace("WaitMultipleDuplicateHandleTest:WaitAny failed (%x)\n", GetLastError()); + testResult = FALSE; + } + + // WaitAll - Wait for all of the events (error expected) + result = WaitForMultipleObjects(sizeof(eventHandles) / sizeof(eventHandles[0]), eventHandles, TRUE, 0); + if (result != WAIT_FAILED) + { + Trace("WaitMultipleDuplicateHandleTest:WaitAll failed: call unexpectedly succeeded\n"); + testResult = FALSE; + } + else if (GetLastError() != ERROR_INVALID_PARAMETER) + { + Trace("WaitMultipleDuplicateHandleTest:WaitAll failed: unexpected last error (%x)\n"); + testResult = FALSE; + } + + return testResult; +} + +int __cdecl main(int argc, char **argv) +{ + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + if(!WaitForMultipleObjectsTest()) + { + Fail ("Test failed\n"); + } + + if (!WaitMultipleDuplicateHandleTest()) + { + Fail("Test failed\n"); + } + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjects/test1/testinfo.dat b/src/pal/tests/palsuite/threading/WaitForMultipleObjects/test1/testinfo.dat new file mode 100644 index 0000000000..38bd350d64 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjects/test1/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = WaitForMultipleObjects +Name = Positive Test for WaitForMultipleObjects +TYPE = DEFAULT +EXE1 = test1 +Description += Test for WaitForMultipleObjects. Call the function += on an array of 4 events, and ensure that it returns correct += results when we do so. diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/CMakeLists.txt new file mode 100644 index 0000000000..7c20179353 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test2) +add_subdirectory(test3) +add_subdirectory(test4) +add_subdirectory(test5) +add_subdirectory(test6) + diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test1/CMakeLists.txt new file mode 100644 index 0000000000..d035021bb9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_waitformultipleobjectsex_test1 + ${SOURCES} +) + +add_dependencies(paltest_waitformultipleobjectsex_test1 coreclrpal) + +target_link_libraries(paltest_waitformultipleobjectsex_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test1/test1.c b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test1/test1.c new file mode 100644 index 0000000000..df3233fa50 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test1/test1.c @@ -0,0 +1,226 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test1.c +** +** Purpose: Test for WaitForMultipleObjectsEx. Call the function +** on an array of 4 events, and ensure that it returns correct +** results when we do so. +** +** +**=========================================================*/ + +#include <palsuite.h> + +/* Originally written as WaitForMultipleObjects/test1 */ + + +/* Number of events in array */ +#define MAX_EVENTS 4 + +BOOL WaitForMultipleObjectsExTest() +{ + BOOL bRet = TRUE; + DWORD dwRet = 0; + DWORD i = 0, j = 0; + + LPSECURITY_ATTRIBUTES lpEventAttributes = NULL; + BOOL bManualReset = TRUE; + BOOL bInitialState = TRUE; + + HANDLE hEvent[MAX_EVENTS]; + + /* Run through this for loop and create 4 events */ + for (i = 0; i < MAX_EVENTS; i++) + { + hEvent[i] = CreateEvent( lpEventAttributes, + bManualReset, bInitialState, NULL); + + if (hEvent[i] == INVALID_HANDLE_VALUE) + { + Trace("WaitForMultipleObjectsExTest:CreateEvent %u failed (%x)\n", i, GetLastError()); + bRet = FALSE; + break; + } + + /* Set the current event */ + bRet = SetEvent(hEvent[i]); + + if (!bRet) + { + Trace("WaitForMultipleObjectsExTest:SetEvent %u failed (%x)\n", i, GetLastError()); + bRet = FALSE; + break; + } + + /* Ensure that this returns the correct value */ + dwRet = WaitForSingleObject(hEvent[i],0); + + if (dwRet != WAIT_OBJECT_0) + { + Trace("WaitForMultipleObjectsExTest:WaitForSingleObject %u failed (%x)\n", i, GetLastError()); + bRet = FALSE; + break; + } + + /* Reset the event, and again ensure that the return value of + WaitForSingle is correct. + */ + bRet = ResetEvent(hEvent[i]); + + if (!bRet) + { + Trace("WaitForMultipleObjectsExTest:ResetEvent %u failed (%x)\n", i, GetLastError()); + bRet = FALSE; + break; + } + + dwRet = WaitForSingleObject(hEvent[i],0); + + if (dwRet != WAIT_TIMEOUT) + { + Trace("WaitForMultipleObjectsExTest:WaitForSingleObject %u failed (%x)\n", i, GetLastError()); + bRet = FALSE; + break; + } + } + + /* + * If the first section of the test passed, move on to the + * second. + */ + + if (bRet) + { + BOOL bWaitAll = TRUE; + DWORD nCount = MAX_EVENTS; + CONST HANDLE *lpHandles = &hEvent[0]; + + /* Call WaitForMultipleObjectsEx on all the events, the return + should be WAIT_TIMEOUT + */ + dwRet = WaitForMultipleObjectsEx(nCount, + lpHandles, + bWaitAll, + 0, + FALSE); + + if (dwRet != WAIT_TIMEOUT) + { + Trace("WaitForMultipleObjectsExTest: WaitForMultipleObjectsEx failed (%x)\n", GetLastError()); + } + else + { + /* Step through each event and one at a time, set the + currect test, while reseting all the other tests + */ + + for (i = 0; i < MAX_EVENTS; i++) + { + for (j = 0; j < MAX_EVENTS; j++) + { + if (j == i) + { + + bRet = SetEvent(hEvent[j]); + + if (!bRet) + { + Trace("WaitForMultipleObjectsExTest:SetEvent %j failed (%x)\n", j, GetLastError()); + break; + } + } + else + { + bRet = ResetEvent(hEvent[j]); + + if (!bRet) + { + Trace("WaitForMultipleObjectsExTest:ResetEvent %u failed (%x)\n", j, GetLastError()); + } + } + } + + bWaitAll = FALSE; + + /* Check that WaitFor returns WAIT_OBJECT + i */ + dwRet = WaitForMultipleObjectsEx( nCount, + lpHandles, bWaitAll, 0, FALSE); + + if (dwRet != WAIT_OBJECT_0+i) + { + Trace("WaitForMultipleObjectsExTest: WaitForMultipleObjectsEx failed (%x)\n", GetLastError()); + bRet = FALSE; + break; + } + } + } + + for (i = 0; i < MAX_EVENTS; i++) + { + bRet = CloseHandle(hEvent[i]); + + if (!bRet) + { + Trace("WaitForMultipleObjectsExTest:CloseHandle %u failed (%x)\n", i, GetLastError()); + } + } + } + + return bRet; +} + +BOOL WaitMultipleDuplicateHandleTest() +{ + BOOL testResult = TRUE; + const HANDLE eventHandle = CreateEvent(NULL, TRUE, TRUE, NULL); + HANDLE eventHandles[] = {eventHandle, eventHandle}; + + // WaitAny - Wait for any of the events (no error expected) + DWORD result = WaitForMultipleObjects(sizeof(eventHandles) / sizeof(eventHandles[0]), eventHandles, FALSE, 0); + if (result != WAIT_OBJECT_0) + { + Trace("WaitMultipleDuplicateHandleTest:WaitAny failed (%x)\n", GetLastError()); + testResult = FALSE; + } + + // WaitAll - Wait for all of the events (error expected) + result = WaitForMultipleObjects(sizeof(eventHandles) / sizeof(eventHandles[0]), eventHandles, TRUE, 0); + if (result != WAIT_FAILED) + { + Trace("WaitMultipleDuplicateHandleTest:WaitAll failed: call unexpectedly succeeded\n"); + testResult = FALSE; + } + else if (GetLastError() != ERROR_INVALID_PARAMETER) + { + Trace("WaitMultipleDuplicateHandleTest:WaitAll failed: unexpected last error (%x)\n"); + testResult = FALSE; + } + + return testResult; +} + +int __cdecl main(int argc, char **argv) +{ + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + if(!WaitForMultipleObjectsExTest()) + { + Fail ("Test failed\n"); + } + + if (!WaitMultipleDuplicateHandleTest()) + { + Fail("Test failed\n"); + } + + PAL_Terminate(); + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test1/testinfo.dat b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test1/testinfo.dat new file mode 100644 index 0000000000..596c4bbf32 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test1/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = WaitForMultipleObjectsEx +Name = Test #1 for WaitForMultipleObjectsEx +TYPE = DEFAULT +EXE1 = test1 +Description += Test for WaitForMultipleObjectsEx. Call the function += on an array of 4 events, and ensure that it returns correct += results when we do so. diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test2/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test2/CMakeLists.txt new file mode 100644 index 0000000000..ab47c6f2e7 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test2/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test2.c +) + +add_executable(paltest_waitformultipleobjectsex_test2 + ${SOURCES} +) + +add_dependencies(paltest_waitformultipleobjectsex_test2 coreclrpal) + +target_link_libraries(paltest_waitformultipleobjectsex_test2 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test2/test2.c b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test2/test2.c new file mode 100644 index 0000000000..b2bc301128 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test2/test2.c @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test2.c +** +** Purpose: Tests that a child thread in the middle of a +** WaitForMultipleObjectsEx call will be interrupted by QueueUserAPC +** if the alert flag was set. +** +** +**===================================================================*/ + +#include <palsuite.h> + +/* Based on SleepEx/test2 */ + +const unsigned int ChildThreadWaitTime = 1000; +const unsigned int InterruptTime = 500; + +#define TOLERANCE 10 + +void RunTest(BOOL AlertThread); +VOID PALAPI APCFunc(ULONG_PTR dwParam); +DWORD PALAPI WaiterProc(LPVOID lpParameter); + +DWORD ThreadWaitDelta; + +int __cdecl main( int argc, char **argv ) +{ + + DWORD delta = 0; + + if (0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + /* + On some platforms (e.g. FreeBSD 4.9) the first call to some synch objects + (such as conditions) involves some pthread internal initialization that + can make the first wait slighty longer, potentially going above the + acceptable delta for this test. Let's add a dummy wait to preinitialize + internal structures + */ + Sleep(100); + + + /* + * Check that Queueing an APC in the middle of a wait does interrupt + * it, if it's in an alertable state. + */ + RunTest(TRUE); + // Make sure that the wait returns in time greater than interrupt and less than + // wait timeout + if ( + ((ThreadWaitDelta >= ChildThreadWaitTime) && (ThreadWaitDelta - ChildThreadWaitTime) > TOLERANCE) + || (( ThreadWaitDelta < InterruptTime) && (ThreadWaitDelta - InterruptTime) > TOLERANCE) + ) + { + Fail("Expected thread to wait for %d ms (and get interrupted).\n" + "Interrupt Time: %d ms, ThreadWaitDelta %u\n", + ChildThreadWaitTime, InterruptTime, ThreadWaitDelta); + } + + /* + * Check that Queueing an APC in the middle of a wait does NOT interrupt + * it, if it is not in an alertable state. + */ + RunTest(FALSE); + + // Make sure that time taken for thread to return from wait is more than interrupt + // and also not less than the complete child thread wait time + + delta = ThreadWaitDelta - ChildThreadWaitTime; + if( (ThreadWaitDelta < ChildThreadWaitTime) && ( delta > TOLERANCE) ) + { + Fail("Expected thread to wait for %d ms (and not get interrupted).\n" + "Interrupt Time: %d ms, ThreadWaitDelta %u\n", + ChildThreadWaitTime, InterruptTime, ThreadWaitDelta); + } + + + PAL_Terminate(); + return PASS; +} + +void RunTest(BOOL AlertThread) +{ + HANDLE hThread = 0; + DWORD dwThreadId = 0; + int ret; + + hThread = CreateThread( NULL, + 0, + (LPTHREAD_START_ROUTINE)WaiterProc, + (LPVOID) AlertThread, + 0, + &dwThreadId); + + if (hThread == NULL) + { + Fail("ERROR: Was not able to create the thread to test!\n" + "GetLastError returned %d\n", GetLastError()); + } + + Sleep(InterruptTime); + + ret = QueueUserAPC(APCFunc, hThread, 0); + if (ret == 0) + { + Fail("QueueUserAPC failed! GetLastError returned %d\n", + GetLastError()); + } + + ret = WaitForSingleObject(hThread, INFINITE); + if (ret == WAIT_FAILED) + { + Fail("Unable to wait on child thread!\nGetLastError returned %d.\n", + GetLastError()); + } +} + +/* Function doesn't do anything, just needed to interrupt the wait*/ +VOID PALAPI APCFunc(ULONG_PTR dwParam) +{ +} + +/* Entry Point for child thread. */ +DWORD PALAPI WaiterProc(LPVOID lpParameter) +{ + HANDLE Semaphore; + UINT64 OldTimeStamp; + UINT64 NewTimeStamp; + BOOL Alertable; + DWORD ret; + + /* Create a semaphore that is not in the signalled state */ + Semaphore = CreateSemaphoreW(NULL, 0, 1, NULL); + + if (Semaphore == NULL) + { + Fail("Failed to create semaphore! GetLastError returned %d.\n", + GetLastError()); + } + + Alertable = (BOOL) lpParameter; + + LARGE_INTEGER performanceFrequency; + if (!QueryPerformanceFrequency(&performanceFrequency)) + { + Fail("Failed to query performance frequency!"); + } + + OldTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency); + + ret = WaitForMultipleObjectsEx(1, &Semaphore, FALSE, ChildThreadWaitTime, + Alertable); + + NewTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency); + + + if (Alertable && ret != WAIT_IO_COMPLETION) + { + Fail("Expected the interrupted wait to return WAIT_IO_COMPLETION.\n" + "Got %d\n", ret); + } + else if (!Alertable && ret != WAIT_TIMEOUT) + { + Fail("WaitForMultipleObjectsEx did not timeout.\n" + "Expected return of WAIT_TIMEOUT, got %d.\n", ret); + } + + ThreadWaitDelta = NewTimeStamp - OldTimeStamp; + + ret = CloseHandle(Semaphore); + if (!ret) + { + Fail("Unable to close handle to semaphore!\n" + "GetLastError returned %d\n", GetLastError()); + } + + return 0; +} + + diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test2/testinfo.dat b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test2/testinfo.dat new file mode 100644 index 0000000000..e8e781a7f2 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test2/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = WaitForMultipleObjectsEx +Name = Test #2 for WaitForMultipleObjectsEx +TYPE = DEFAULT +EXE1 = test2 +Description +=Tests that a child thread in the middle of a +=WaitForMultipleObjectsEx call will be interrupted by QueueUserAPC +=if the alert flag was set. diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test3/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test3/CMakeLists.txt new file mode 100644 index 0000000000..645b86719d --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test3/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test3.c +) + +add_executable(paltest_waitformultipleobjectsex_test3 + ${SOURCES} +) + +add_dependencies(paltest_waitformultipleobjectsex_test3 coreclrpal) + +target_link_libraries(paltest_waitformultipleobjectsex_test3 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test3/test3.c b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test3/test3.c new file mode 100644 index 0000000000..b78b0540dc --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test3/test3.c @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test3.c +** +** Purpose: Tests that waiting on an open mutex will a return +** WAIT_OBJECT_0. Does this by creating a child thread that +** acquires the mutex, releases it, and exits. +** +** +**===================================================================*/ + +#include <palsuite.h> + + +const int ChildThreadWaitTime = 1000; +const int ParentDelayTime = 2000; + +DWORD PALAPI AcquiringProc(LPVOID lpParameter); + +int __cdecl main( int argc, char **argv) +{ + HANDLE Mutex; + HANDLE hThread = 0; + DWORD dwThreadId = 0; + int ret; + + if (0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + Mutex = CreateMutexW(NULL, FALSE, NULL); + if (Mutex == NULL) + { + Fail("Unable to create the mutex. GetLastError returned %d\n", + GetLastError()); + } + + hThread = CreateThread( NULL, + 0, + (LPTHREAD_START_ROUTINE)AcquiringProc, + (LPVOID) Mutex, + 0, + &dwThreadId); + + if (hThread == NULL) + { + Fail("ERROR: Was not able to create the thread to test!\n" + "GetLastError returned %d\n", GetLastError()); + } + + Sleep(ParentDelayTime); + + ret = WaitForMultipleObjectsEx(1, &Mutex, FALSE, INFINITE, FALSE); + if (ret != WAIT_OBJECT_0) + { + Fail("Expected WaitForMultipleObjectsEx to return WAIT_OBJECT_0\n" + "Got %d\n", ret); + } + + if (!CloseHandle(Mutex)) + { + Fail("CloseHandle on the mutex failed!\n"); + } + + if (!CloseHandle(hThread)) + { + Fail("CloseHandle on the thread failed!\n"); + } + + PAL_Terminate(); + return PASS; +} + +/* + * Entry Point for child thread. Acquries a mutex, releases it, and exits. + */ +DWORD PALAPI AcquiringProc(LPVOID lpParameter) +{ + HANDLE Mutex; + DWORD ret; + + Mutex = (HANDLE) lpParameter; + + Sleep(ChildThreadWaitTime); + + ret = WaitForSingleObject(Mutex, 0); + if (ret != WAIT_OBJECT_0) + { + Fail("Expected the WaitForSingleObject call on the mutex to succeed\n" + "Expected return of WAIT_OBJECT_0, got %d\n", ret); + } + + ret = ReleaseMutex(Mutex); + if (!ret) + { + Fail("Unable to release mutex! GetLastError returned %d\n", + GetLastError()); + } + + return 0; +} diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test3/testinfo.dat b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test3/testinfo.dat new file mode 100644 index 0000000000..991b93489d --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test3/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = WaitForMultipleObjectsEx +Name = Test #3 for WaitForMultipleObjectsEx +TYPE = DEFAULT +EXE1 = test3 +Description +=Tests that waiting on an open mutex will a return +=WAIT_OBJECT_0. Does this by creating a child thread that +=acquires the mutex, releases it, and exits. diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test4/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test4/CMakeLists.txt new file mode 100644 index 0000000000..3cd7f72b98 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test4/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test4.c +) + +add_executable(paltest_waitformultipleobjectsex_test4 + ${SOURCES} +) + +add_dependencies(paltest_waitformultipleobjectsex_test4 coreclrpal) + +target_link_libraries(paltest_waitformultipleobjectsex_test4 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test4/test4.c b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test4/test4.c new file mode 100644 index 0000000000..15d0a386d1 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test4/test4.c @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: test4.c +** +** Purpose: Tests that waiting on an abandonded mutex will a return +** WAIT_ABANDONED_0. Does this by creating a child thread that +** acquires the mutex and exits. +** +** +**===================================================================*/ + +#include <palsuite.h> + + +const int ChildThreadWaitTime = 1000; +const int ParentDelayTime = 2000; + +DWORD PALAPI AbandoningProc(LPVOID lpParameter); + +int __cdecl main( int argc, char **argv ) +{ + HANDLE Mutex; + HANDLE hThread = 0; + DWORD dwThreadId = 0; + int ret; + + if (0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + Mutex = CreateMutexW(NULL, FALSE, NULL); + if (Mutex == NULL) + { + Fail("Unable to create the mutex. GetLastError returned %d\n", + GetLastError()); + } + + hThread = CreateThread( NULL, + 0, + (LPTHREAD_START_ROUTINE)AbandoningProc, + (LPVOID) Mutex, + 0, + &dwThreadId); + + if (hThread == NULL) + { + Fail("ERROR: Was not able to create the thread to test!\n" + "GetLastError returned %d\n", GetLastError()); + } + + Sleep(ParentDelayTime); + + ret = WaitForMultipleObjectsEx(1, &Mutex, FALSE, INFINITE, FALSE); + if (ret != WAIT_ABANDONED_0) + { + Fail("Expected WaitForMultipleObjectsEx to return WAIT_ABANDONED_0\n" + "Got %d\n", ret); + } + + ReleaseMutex(Mutex); + if (!CloseHandle(Mutex)) + { + Fail("CloseHandle on the mutex failed!\n"); + } + + if (!CloseHandle(hThread)) + { + Fail("CloseHandle on the thread failed!\n"); + } + + PAL_Terminate(); + return PASS; +} + +/* + * Entry Point for child thread. Acquries a mutex and exit's without + * releasing it. + */ +DWORD PALAPI AbandoningProc(LPVOID lpParameter) +{ + HANDLE Mutex; + DWORD ret; + + Mutex = (HANDLE) lpParameter; + + Sleep(ChildThreadWaitTime); + + ret = WaitForSingleObject(Mutex, 0); + if (ret != WAIT_OBJECT_0) + { + Fail("Expected the WaitForSingleObject call on the mutex to succeed\n" + "Expected return of WAIT_OBJECT_0, got %d\n", ret); + } + + return 0; +} diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test4/testinfo.dat b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test4/testinfo.dat new file mode 100644 index 0000000000..16f3468ac2 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test4/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = WaitForMultipleObjectsEx +Name = Test #4 for WaitForMultipleObjectsEx +TYPE = DEFAULT +EXE1 = test4 +Description +=Tests that waiting on an abandonded mutex will a return +=WAIT_ABANDONED_0. Does this by creating a child thread that +=acquires the mutex and exits. diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test5/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test5/CMakeLists.txt new file mode 100644 index 0000000000..50147e9bf3 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test5/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(TESTSOURCES + test5.c +) + +add_executable(paltest_waitformultipleobjectsex_test5 + ${TESTSOURCES} +) + +add_dependencies(paltest_waitformultipleobjectsex_test5 coreclrpal) + +target_link_libraries(paltest_waitformultipleobjectsex_test5 + pthread + m + coreclrpal +) + + +set(HELPERSOURCES + helper.c +) + +add_executable(paltest_waitformultipleobjectsex_test5_helper + ${HELPERSOURCES} +) + +add_dependencies(paltest_waitformultipleobjectsex_test5_helper coreclrpal) + +target_link_libraries(paltest_waitformultipleobjectsex_test5_helper + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test5/commonconsts.h b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test5/commonconsts.h new file mode 100644 index 0000000000..b746616b58 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test5/commonconsts.h @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================= +** +** Source: commonconsts.h +** +** +**============================================================*/ + +#ifndef _COMMONCONSTS_H_ +#define _COMMONCONSTS_H_ + +#include <pal.h> + +const int TIMEOUT = 60 * 5 * 1000; + +char *szcHelperProcessStartEvName = "start"; +char *szcHelperProcessReadyEvName = "ready"; +char *szcHelperProcessFinishEvName = "finish"; + +/* PEDANTIC and PEDANTIC0 is a helper macro that just grumps about any + * zero return codes in a generic way. with little typing */ +#define PEDANTIC(function, parameters) \ +{ \ + if (! (function parameters) ) \ + { \ + Trace("%s: NonFatal failure of %s%s for reasons %u and %u\n", \ + __FILE__, #function, #parameters, GetLastError(), errno); \ + } \ +} +#define PEDANTIC1(function, parameters) \ +{ \ + if ( (function parameters) ) \ + { \ + Trace("%s: NonFatal failure of %s%s for reasons %u and %u\n", \ + __FILE__, #function, #parameters, GetLastError(), errno); \ + } \ +} + +#endif diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test5/helper.c b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test5/helper.c new file mode 100644 index 0000000000..274780a216 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test5/helper.c @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================= +** +** Source: helper.c +** +** Purpose: This helper process sets up signals to communicate +** with the test thread in the parent process, and let the test +** thread signal this process when to exit. +** +** +**============================================================*/ + +#include "commonconsts.h" + +#include <palsuite.h> + +HANDLE hProcessStartEvent; +HANDLE hProcessReadyEvent; +HANDLE hProcessFinishEvent; +HANDLE hProcessCleanupEvent; + + +int __cdecl main(int argc, char *argv[]) +{ + + BOOL success = TRUE; /* assume success */ + DWORD dwRet; + DWORD dwProcessId; + char szEventName[MAX_LONGPATH]; + PWCHAR uniString; + + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + /* Open the event to let test thread tell us to get started. */ + uniString = convert(szcHelperProcessStartEvName); + hProcessStartEvent = OpenEventW(EVENT_ALL_ACCESS, 0, uniString); + free(uniString); + if (!hProcessStartEvent) + { + Fail("helper.main: OpenEvent of '%S' failed (%u). " + "(the event should already exist!)\n", + szcHelperProcessStartEvName, GetLastError()); + } + + /* Wait for signal from test thread. */ + dwRet = WaitForSingleObject(hProcessStartEvent, TIMEOUT); + if (dwRet != WAIT_OBJECT_0) + { + Fail("helper.main: WaitForSingleObject '%s' failed\n" + "LastError:(%u)\n", szcHelperProcessStartEvName, GetLastError()); + } + + dwProcessId = GetCurrentProcessId(); + + if ( 0 >= dwProcessId ) + { + Fail ("helper.main: %s has invalid pid %d\n", argv[0], dwProcessId ); + } + + /* Open the event to tell test thread we are ready. */ + if (_snprintf(szEventName, MAX_LONGPATH-1, "%s%d", szcHelperProcessReadyEvName, dwProcessId) < 0) + { + Fail ("helper.main: Insufficient event name string length for pid=%d\n", dwProcessId); + } + + uniString = convert(szEventName); + + hProcessReadyEvent = OpenEventW(EVENT_ALL_ACCESS, 0, uniString); + free(uniString); + if (!hProcessReadyEvent) + { + Fail("helper.main: OpenEvent of '%s' failed (%u). " + "(the event should already exist!)\n", + szEventName, GetLastError()); + } + + /* Open the event to let test thread tell us to exit. */ + if (_snprintf(szEventName, MAX_LONGPATH-1, "%s%d", szcHelperProcessFinishEvName, dwProcessId) < 0) + { + Fail ("helper.main: Insufficient event name string length for pid=%d\n", dwProcessId); + } + + uniString = convert(szEventName); + + hProcessFinishEvent = OpenEventW(EVENT_ALL_ACCESS, 0, uniString); + free(uniString); + if (!hProcessFinishEvent) + { + Fail("helper.main: OpenEvent of '%s' failed LastError:(%u).\n", + szEventName, GetLastError()); + } + + /* Tell the test thread we are ready. */ + if (!SetEvent(hProcessReadyEvent)) + { + Fail("helper.main: SetEvent '%s' failed LastError:(%u)\n", + hProcessReadyEvent, GetLastError()); + } + + /* Wait for signal from test thread before exit. */ + dwRet = WaitForSingleObject(hProcessFinishEvent, TIMEOUT); + if (WAIT_OBJECT_0 != dwRet) + { + Fail("helper.main: WaitForSingleObject '%s' failed pid=%d\n" + "LastError:(%u)\n", + szcHelperProcessFinishEvName, dwProcessId, GetLastError()); + } + + PEDANTIC(CloseHandle, (hProcessStartEvent)); + PEDANTIC(CloseHandle, (hProcessReadyEvent)); + PEDANTIC(CloseHandle, (hProcessFinishEvent)); + + PAL_Terminate(); + + return success ? PASS : FAIL; +} diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test5/test5.c b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test5/test5.c new file mode 100644 index 0000000000..d19978c103 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test5/test5.c @@ -0,0 +1,506 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================= +** +** Source: test5.c +** +** Purpose: Test the functionality of simultaneously waiting +** on multiple processes. Create the same number of helper +** processes and helper threads. +** Helper threads wait on helper processes to finish. +** Helper processes wait on the event signal from test +** thread before exit. +** The test thread can wake up one helper +** thread at a time by signaling the corresponding helper +** process to finish. +** The test thread can also wake up all helper threads at once +** by signaling help process 0 to exit. +** +** +**============================================================*/ + +#define UNICODE + +#include "commonconsts.h" + +#include <palsuite.h> + +/* The maximum number of objects a thread can wait is MAXIMUM_WAIT_OBJECTS. + The last helper thread in this test case will wait on all helper processes + plus a thread finish event so the maximum number of helper processes + can be created in this test case is (MAXIMUM_WAIT_OBJECTS-1). */ +#define MAX_HELPER_PROCESS (MAXIMUM_WAIT_OBJECTS-1) + +int MaxNumHelperProcess = MAX_HELPER_PROCESS; + +/* indicate how the test thread wake up helper thread. */ +typedef enum _TestCaseType { + WakeUpOneThread, /* wake up one helper thread at a time. */ + WakeUpAllThread /* wake up all helper threads at once */ +} TestCaseType; + +TestCaseType TestCase = WakeUpOneThread; + +/* When the test thread wakes up one thread at a time, + ThreadIndexOfThreadFinishEvent specifies the index of the thread that + should be waked up using hThreadFinishEvent instead of helper process. */ +DWORD ThreadIndexOfThreadFinishEvent = 0; + +struct helper_process_t +{ + PROCESS_INFORMATION pi; + HANDLE hProcessReadyEvent; + HANDLE hProcessFinishEvent; +} helper_process[MAX_HELPER_PROCESS]; + +HANDLE hProcessStartEvent; + +struct helper_thread_t +{ + HANDLE hThread; + DWORD dwThreadId; + HANDLE hThreadReadyEvent; + HANDLE hThreadFinishEvent; +} helper_thread[MAX_HELPER_PROCESS]; + +/* + * Entry Point for helper thread. + */ +DWORD PALAPI WaitForProcess(LPVOID lpParameter) +{ + DWORD index, i; + DWORD dwRet; + HANDLE handles[MAX_HELPER_PROCESS+1]; + + index = (DWORD) lpParameter; + + /* The helper thread 0 will wait for helper process 0, helper thread 1 will + wait for helper process 0 and 1, helper thread 2 will wait for helper + process 0, 1, and 2, and so on ..., and the last helper thread will wait + on all helper processes. + Each helper thread also waits on hThreadFinishEvent so that + it can exit without waiting on any process to finish. */ + + for (i = 0; i <= index; i++) + { + handles[i] = helper_process[i].pi.hProcess; + } + + handles[index+1] = helper_thread[index].hThreadFinishEvent; + + if(!SetEvent(helper_thread[index].hThreadReadyEvent)) + { + Fail("test5.WaitProcess: SetEvent of hThreadReadyEvent failed for thread %d. " + "GetLastError() returned %d.\n", index, + GetLastError()); + } + + dwRet = WaitForMultipleObjectsEx(index+2, &handles[0], FALSE, TIMEOUT, TRUE); + if (WakeUpAllThread == TestCase) + { + /* If the test thread signals helper process 0 to exit, all threads will be waked up, + and the return value must be (WAIT_OBJECT_0+0) because the handle of helper process 0 + is in handle[0]. */ + if (dwRet != (WAIT_OBJECT_0+0)) + { + Fail("test5.WaitForProcess: invalid return value %d for WakupAllThread from WaitForMultipleObjectsEx for thread %d\n" + "LastError:(%u)\n", + dwRet, index, + GetLastError()); + } + } + else if (WakeUpOneThread == TestCase) + { + /* If the test thread wakes up one helper thread at a time, + the return value must be either (WAIT_OBJECT_0+index) if the helper thread + wakes up because the corresponding help process exits, + or (index+1) if the helper thread wakes up because of hThreadReadyEvent. */ + if ((index != ThreadIndexOfThreadFinishEvent && dwRet != (WAIT_OBJECT_0+index)) || + (index == ThreadIndexOfThreadFinishEvent && dwRet != (index+1))) + { + Fail("test5.WaitForProcess: invalid return value %d for WakupOneThread from WaitForMultipleObjectsEx for thread %d\n" + "LastError:(%u)\n", + dwRet, index, + GetLastError()); + } + } + else + { + Fail("Unknown TestCase %d\n", TestCase); + } + return 0; +} + +/* + * Setup the helper processes and helper threads. + */ +void +Setup() +{ + + STARTUPINFO si; + DWORD dwRet; + int i; + + char szEventName[MAX_PATH]; + PWCHAR uniStringHelper; + PWCHAR uniString; + + /* Create the event to start helper process after it was created. */ + uniString = convert(szcHelperProcessStartEvName); + hProcessStartEvent = CreateEvent(NULL, TRUE, FALSE, uniString); + free(uniString); + if (!hProcessStartEvent) + { + Fail("test5.Setup: CreateEvent of '%s' failed. " + "GetLastError() returned %d.\n", szcHelperProcessStartEvName, + GetLastError()); + } + + /* Create the helper processes. */ + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + uniStringHelper = convert("helper"); + for (i = 0; i < MaxNumHelperProcess; i++) + { + ZeroMemory( &helper_process[i].pi, sizeof(PROCESS_INFORMATION)); + + if(!CreateProcess( NULL, uniStringHelper, NULL, NULL, + FALSE, 0, NULL, NULL, &si, &helper_process[i].pi)) + { + Fail("test5.Setup: CreateProcess failed to load executable for helper process %d. " + "GetLastError() returned %u.\n", + i, GetLastError()); + } + + /* Create the event to let helper process tell us it is ready. */ + if (_snprintf(szEventName, MAX_PATH-1, "%s%d", + szcHelperProcessReadyEvName, helper_process[i].pi.dwProcessId) < 0) + { + Fail ("test5.Setup: Insufficient event name string length for %s\n", szcHelperProcessReadyEvName); + } + + uniString = convert(szEventName); + + helper_process[i].hProcessReadyEvent = CreateEvent(NULL, FALSE, FALSE, uniString); + free(uniString); + if (!helper_process[i].hProcessReadyEvent) + { + Fail("test5.Setup: CreateEvent of '%s' failed. " + "GetLastError() returned %d.\n", szEventName, + GetLastError()); + } + + /* Create the event to tell helper process to exit. */ + if (_snprintf(szEventName, MAX_PATH-1, "%s%d", + szcHelperProcessFinishEvName, helper_process[i].pi.dwProcessId) < 0) + { + Fail ("test5.Setup: Insufficient event name string length for %s\n", szcHelperProcessFinishEvName); + } + + uniString = convert(szEventName); + + helper_process[i].hProcessFinishEvent = CreateEvent(NULL, TRUE, FALSE, uniString); + free(uniString); + if (!helper_process[i].hProcessFinishEvent) + { + Fail("test5.Setup: CreateEvent of '%s' failed. " + "GetLastError() returned %d.\n", szEventName, + GetLastError()); + } + + } + free(uniStringHelper); + + /* Signal all helper processes to start. */ + if (!SetEvent(hProcessStartEvent)) + { + Fail("test5.Setup: SetEvent '%s' failed\n", + "LastError:(%u)\n", + szcHelperProcessStartEvName, GetLastError()); + } + + /* Wait for ready signals from all helper processes. */ + for (i = 0; i < MaxNumHelperProcess; i++) + { + dwRet = WaitForSingleObject(helper_process[i].hProcessReadyEvent, TIMEOUT); + if (dwRet != WAIT_OBJECT_0) + { + Fail("test5.Setup: WaitForSingleObject %s failed for helper process %d\n" + "LastError:(%u)\n", + szcHelperProcessReadyEvName, i, GetLastError()); + } + } + + /* Create the same number of helper threads as helper processes. */ + for (i = 0; i < MaxNumHelperProcess; i++) + { + /* Create the event to let helper thread tell us it is ready. */ + helper_thread[i].hThreadReadyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!helper_thread[i].hThreadReadyEvent) + { + Fail("test5.Setup: CreateEvent of hThreadReadyEvent failed for thread %d\n" + "LastError:(%u)\n", i, GetLastError()); + } + + /* Create the event to tell helper thread to exit without waiting for helper process. */ + helper_thread[i].hThreadFinishEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!helper_thread[i].hThreadFinishEvent) + { + Fail("test5.Setup: CreateEvent of hThreadFinishEvent failed for thread %d\n" + "LastError:(%u)\n", i, GetLastError()); + } + + /* Create the helper thread. */ + helper_thread[i].hThread = CreateThread( NULL, + 0, + (LPTHREAD_START_ROUTINE)WaitForProcess, + (LPVOID)i, + 0, + &helper_thread[i].dwThreadId); + if (NULL == helper_thread[i].hThread) + { + Fail("test5.Setup: Unable to create the helper thread %d\n" + "LastError:(%u)\n", i, GetLastError()); + } + } + + /* Wait for ready signals from all helper threads. */ + for (i = 0; i < MaxNumHelperProcess; i++) + { + dwRet = WaitForSingleObject(helper_thread[i].hThreadReadyEvent, TIMEOUT); + if (dwRet != WAIT_OBJECT_0) + { + Fail("test5.Setup: WaitForSingleObject hThreadReadyEvent for thread %d\n" + "LastError:(%u)\n", i, GetLastError()); + } + } +} + +/* + * Cleanup the helper processes and helper threads. + */ +DWORD +Cleanup() +{ + DWORD dwExitCode; + DWORD dwRet; + int i; + + /* Wait for all helper process to finish and close their handles + and associated events. */ + for (i = 0; i < MaxNumHelperProcess; i++) + { + + /* wait for the child process to complete */ + dwRet = WaitForSingleObject ( helper_process[i].pi.hProcess, TIMEOUT ); + if (WAIT_OBJECT_0 != dwRet) + { + Fail("test5.Cleanup: WaitForSingleObject hThreadReadyEvent failed for thread %d\n" + "LastError:(%u)\n", i, GetLastError()); + } + + /* check the exit code from the process */ + if (!GetExitCodeProcess(helper_process[i].pi.hProcess, &dwExitCode)) + { + Trace( "test5.Cleanup: GetExitCodeProcess %d call failed LastError:(%u)\n", + i, GetLastError()); + dwExitCode = FAIL; + } + PEDANTIC(CloseHandle, (helper_process[i].pi.hThread)); + PEDANTIC(CloseHandle, (helper_process[i].pi.hProcess)); + PEDANTIC(CloseHandle, (helper_process[i].hProcessReadyEvent)); + PEDANTIC(CloseHandle, (helper_process[i].hProcessFinishEvent)); + } + + /* Close all helper threads' handles */ + for (i = 0; i < MaxNumHelperProcess; i++) + { + PEDANTIC(CloseHandle, (helper_thread[i].hThread)); + PEDANTIC(CloseHandle, (helper_thread[i].hThreadReadyEvent)); + PEDANTIC(CloseHandle, (helper_thread[i].hThreadFinishEvent)); + } + + /* Close all process start event. */ + PEDANTIC(CloseHandle, (hProcessStartEvent)); + + return dwExitCode; +} + +/* + * In this test case, the test thread will signal one helper + * process to exit at a time starting from the last helper + * process and then wait for the corresponding helper thread to exit. + * The ThreadIndexOfThreadFinishEvent specifies the index of the thread that + * should be waked up using hThreadFinishEvent instead of helper process. + */ +void +TestWakeupOneThread() +{ + DWORD dwRet; + int i; + + TestCase = WakeUpOneThread; + + if (((LONG)ThreadIndexOfThreadFinishEvent) < 0 || + ThreadIndexOfThreadFinishEvent >= MAX_HELPER_PROCESS) + Fail("test5.TestWaitOnOneThread: Invalid ThreadIndexOfThreadFinishEvent %d\n", ThreadIndexOfThreadFinishEvent); + + /* Since helper thread 0 waits on helper process 0, + thread 1 waits on on process 0, and 1, + thread 2 waits on process 0, 1, and 2, and so on ..., + and the last helper thread will wait on all helper processes, + the helper thread can be waked up one at a time by + waking up the help process one at a time starting from the + last helper process. */ + for (i = MaxNumHelperProcess-1; i >= 0; i--) + { + /* make sure the helper thread has not exited yet. */ + dwRet = WaitForSingleObject(helper_thread[i].hThread, 0); + if (WAIT_TIMEOUT != dwRet) + { + Fail("test5.TestWaitOnOneThread: helper thread %d already exited %d\n", i); + } + + /* Decide how to wakeup the helper thread: + using event or using helper process. */ + if (i == ThreadIndexOfThreadFinishEvent) + { + if (!SetEvent(helper_thread[i].hThreadFinishEvent)) + { + Fail("test5.TestWaitOnOneThread: SetEvent hThreadFinishEvent failed for thread %d\n", + "LastError:(%u)\n", i, GetLastError()); + } + } + else + { + if (!SetEvent(helper_process[i].hProcessFinishEvent)) + { + Fail("test5.TestWaitOnOneThread: SetEvent %s%d failed for helper process %d\n", + "LastError:(%u)\n", + szcHelperProcessFinishEvName, helper_process[i].pi.dwProcessId, i, + GetLastError()); + } + } + + dwRet = WaitForSingleObject(helper_thread[i].hThread, TIMEOUT); + if (WAIT_OBJECT_0 != dwRet) + { + Fail("test5.TestWaitOnOneThread: WaitForSingleObject helper thread %d" + "LastError:(%u)\n", + i, GetLastError()); + } + } + + /* Finally, need to wake up the helper process which the test thread + skips waking up in the last loop. */ + if (!SetEvent(helper_process[ThreadIndexOfThreadFinishEvent].hProcessFinishEvent)) + { + Fail("test5.TestWaitOnOneThread: SetEvent %s%d failed\n", + "LastError:(%u)\n", + szcHelperProcessFinishEvName, helper_process[ThreadIndexOfThreadFinishEvent].pi.dwProcessId, + GetLastError()); + } +} + +/* + * In this test case, the test thread will signal the helper + * process 0 to exit. Since all helper threads wait on process 0, + * all helper threads will wake up and exit, and the test thread + * will wait for all of them to exit. + */ +void +TestWakeupAllThread() +{ + DWORD dwRet; + int i; + + TestCase = WakeUpAllThread; + + /* make sure none of the helper thread exits. */ + for (i = 0; i < MaxNumHelperProcess; i++) + { + dwRet = WaitForSingleObject(helper_thread[i].hThread, 0); + if (WAIT_TIMEOUT != dwRet) + { + Fail("test5.TestWaitOnAllThread: helper thread %d already exited %d\n", i); + } + } + + /* Signal helper process 0 to exit. */ + if (!SetEvent(helper_process[0].hProcessFinishEvent)) + { + Fail("test5.TestWaitOnAllThread: SetEvent %s%d failed\n", + "LastError:(%u)\n", + szcHelperProcessFinishEvName, helper_process[0].pi.dwProcessId, + GetLastError()); + } + + /* Wait for all helper threads to exit. */ + for (i = 0; i < MaxNumHelperProcess; i++) + { + + dwRet = WaitForSingleObject(helper_thread[i].hThread, TIMEOUT); + if (WAIT_OBJECT_0 != dwRet) + { + Fail("test5.TestWaitOnAllThread: WaitForSingleObject failed for helper thread %d\n" + "LastError:(%u)\n", + i, GetLastError()); + } + } + + /* Signal the rest of helper processes to exit. */ + for (i = 1; i < MaxNumHelperProcess; i++) + { + if (!SetEvent(helper_process[i].hProcessFinishEvent)) + { + Fail("test5.TestWaitOnAllThread: SetEvent %s%d failed\n", + "LastError:(%u)\n", + szcHelperProcessFinishEvName, helper_process[i].pi.dwProcessId, + GetLastError()); + } + } +} + +int __cdecl main(int argc, char *argv[]) +{ + DWORD dwExitCode; + + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + switch (argc) + { + case 1: + MaxNumHelperProcess = MAX_HELPER_PROCESS; + break; + case 2: + MaxNumHelperProcess = atol(argv[1]); + break; + default: + Fail("Invalid number of arguments\n"); + } + + if (MaxNumHelperProcess < 1 || + MaxNumHelperProcess > MAX_HELPER_PROCESS) + Fail("test5.main: Invalid MaxNumHelperProcess %d\n", MaxNumHelperProcess); + + Setup(); + ThreadIndexOfThreadFinishEvent = 3; + TestWakeupOneThread(); + dwExitCode = Cleanup(); + + if (PASS == dwExitCode) + { + Setup(); + TestWakeupAllThread(); + dwExitCode = Cleanup(); + } + + PAL_TerminateEx(dwExitCode); + return dwExitCode; +} diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test5/testinfo.dat b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test5/testinfo.dat new file mode 100644 index 0000000000..5efc75f77e --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test5/testinfo.dat @@ -0,0 +1,18 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = WaitForMultipleObjectsEx +Name = Check simultaneously waiting on multiple processes. +TYPE = DEFAULT +EXE1 = test5 +EXE2 = helper +Description += Create a number of helper processes and helper threads. += Helper threads wait on helper processes to finish. += Helper processes wait on the event signal from test += thread before exit. The test thread then selectively += signals helper process to finish and then wait on the += selected helper thread to finish. diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/CMakeLists.txt new file mode 100644 index 0000000000..ce75cadbff --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(TESTSOURCES + test6.c +) + +add_executable(paltest_waitformultipleobjectsex_test6 + ${TESTSOURCES} +) + +add_dependencies(paltest_waitformultipleobjectsex_test6 coreclrpal) + +target_link_libraries(paltest_waitformultipleobjectsex_test6 + pthread + m + coreclrpal +) + + +set(HELPERSOURCES + child6.c +) + +add_executable(paltest_waitformultipleobjectsex_test6_child + ${HELPERSOURCES} +) + +add_dependencies(paltest_waitformultipleobjectsex_test6_child coreclrpal) + +target_link_libraries(paltest_waitformultipleobjectsex_test6_child + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/child6.c b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/child6.c new file mode 100644 index 0000000000..1a95815ddf --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/child6.c @@ -0,0 +1,211 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: child6.c +** +** Purpose: Test for WaitForMultipleObjectsEx in multiple +** scenarios - child process +** +** +**=========================================================*/ + +#include <palsuite.h> + +int __cdecl main(int argc, char **argv) +{ + int i, iRet; + BOOL bRet; + BOOL bNamedEvent = 0; + BOOL bMutex = 0; + BOOL bMutexAndNamedEvent = 0; + BOOL bSemaphore = 0; + DWORD dwRet; + HANDLE hNamedEvent; + HANDLE hMutex; + char szTestName[256]; + WCHAR wszTestName[256] = { 0 }; + char szEventName[128] = { 0 }; + char szMutexName[128] = { 0 }; + char szSemName[128] = { 0 }; + WCHAR wszEventName[128]; + WCHAR wszMutexName[128]; + WCHAR wszSemName[128]; + DWORD iExitCode = 0; + HANDLE hSemaphore; + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + Trace("[child] Starting\n"); + + for (i=1; i<argc; i++) + { + if (0 == strcmp(argv[i],"-event")) + { + bNamedEvent = 1; + } + else if (0 == strcmp(argv[i],"-mutex")) + { + bMutex = 1; + } + else if (0 == strcmp(argv[i],"-mutex_and_named_event")) + { + bMutexAndNamedEvent = 1; + } + else if (0 == strcmp(argv[i],"-semaphore")) + { + bSemaphore = 1; + } + else if (0 == strcmp(argv[i],"-exitcode") && i < argc-1 ) + { + i++; + iExitCode = atoi(argv[i]); + Trace("[child] My exit code is %d\n", iExitCode); + } + + else if ('-' != *argv[i]) + { + strncpy(szTestName, argv[i], 256); + szTestName[255] = 0; + iRet = MultiByteToWideChar(CP_ACP, 0, szTestName, strlen(szTestName)+1, wszTestName, 256); + if (0 == iRet) + { + Fail("Failed to convert test string\n"); + } + } + } + + _snprintf(szEventName, 128, "%s_Event", szTestName); + szEventName[127] = 0; + _snprintf(szMutexName, 128, "%s_Mutex", szTestName); + szMutexName[127] = 0; + _snprintf(szSemName, 128, "%s_Semaphore", szTestName); + szSemName[127] = 0; + + iRet = MultiByteToWideChar(CP_ACP, 0, szEventName, strlen(szEventName)+1, wszEventName, 128); + iRet &= MultiByteToWideChar(CP_ACP, 0, szMutexName, strlen(szMutexName)+1, wszMutexName, 128); + iRet &= MultiByteToWideChar(CP_ACP, 0, szSemName, strlen(szSemName)+1, wszSemName, 128); + if (0 == iRet) + { + Fail("[child] Failed to convert strings\n"); + } + + Trace("[child] TestName=%s Event: %S, Mutex: %S, Semaphore = %S\n", + szTestName, wszEventName, wszMutexName, wszSemName); + + hNamedEvent = OpenEventW(0, FALSE, wszEventName); + if (NULL == hNamedEvent) + { + Fail("[child] OpenEventW failed [szEventName=%s GetLastError()=%u]\n", + szEventName, GetLastError()); + } + hMutex = OpenMutexW(0, FALSE, wszMutexName); + if (NULL == hMutex) + { + Fail("[child] OpenMutexW failed [GetLastError()=%u]\n", + GetLastError()); + } + hSemaphore = CreateSemaphoreW(NULL, 0, 256, wszSemName); + if (NULL == hSemaphore) + { + Fail("[child] CreateSemaphore failed [GetLastError()=%u]\n", + GetLastError()); + } + + + if (bMutex) + { + Trace("[child] Going to wait on mutex %s\n", szMutexName); + dwRet = WaitForSingleObject(hMutex, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("[child] WaitForMultipleObjects failed [GetLastError()=%u]\n", + GetLastError()); + } + + Trace("[child] Setting event %s\n", szEventName); + bRet = SetEvent(hNamedEvent); + if (FALSE == bRet) + { + Fail("[child] SetEvent failed [GetLastError()=%u]\n", + GetLastError()); + } + + // mutex will be abandoned + } + else if (bMutexAndNamedEvent) + { + dwRet = WaitForSingleObject(hMutex, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("[child] WaitForMultipleObjects failed [GetLastError()=%u]\n", + GetLastError()); + } + + Sleep(2000); + + bRet = ReleaseMutex(hMutex); + if (FALSE == bRet) + { + Fail("[child] ReleaseMutex failed [GetLastError()=%u]\n", + GetLastError()); + } + + Sleep(1000); + + bRet = SetEvent(hNamedEvent); + if (FALSE == bRet) + { + Fail("[child] SetEvent failed [GetLastError()=%u]\n", + GetLastError()); + } + } + else if (bSemaphore) + { + LONG lPrevCount = 42; + + + Trace("[child] Going to wait on event %s\n", szEventName); + dwRet = WaitForSingleObject(hNamedEvent, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("[child] WaitForMultipleObjects failed [GetLastError()=%u]\n", + GetLastError()); + } + + Trace("[child] Releasing semaphore %s\n", szSemName); + bRet = ReleaseSemaphore(hSemaphore, 10, &lPrevCount); + if (FALSE == bRet) + { + Fail("ReleaseMutex failed [GetLastError()=%u]\n", + GetLastError()); + } + if (0 != lPrevCount) + { + Fail("Previous count from semaphore=%d, expected 0\n", lPrevCount); + } + } + else if (bNamedEvent) + { + Sleep(1000); + + bRet = SetEvent(hNamedEvent); + if (FALSE == bRet) + { + Fail("[child] SetEvent failed [GetLastError()=%u]\n", + GetLastError()); + } + } + + Sleep(1000); + + Trace("[child] Done\n"); + + PAL_TerminateEx(iExitCode); + return iExitCode; +} diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/test6.c b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/test6.c new file mode 100644 index 0000000000..399d2c3bff --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/test6.c @@ -0,0 +1,709 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test6.c +** +** Purpose: Test for WaitForMultipleObjectsEx in multiple +** scenarios +** +** +**=========================================================*/ + +#include <palsuite.h> + +#define MAX_COUNT 10000 +#define MAX_THREADS 256 + +BOOL g_bMutex = 0; +BOOL g_bEvent = 0; +BOOL g_bNamedEvent = 0; +BOOL g_bSemaphore = 0; +BOOL g_bProcess = 0; +BOOL g_bLocalWaitAll = 0; +BOOL g_bRemoteWaitAll = 0; +BOOL g_bRandom = 0; + +int iCount = 1; +int iThreads = 1; +HANDLE hThreads[MAX_THREADS]; + +#ifndef MIN +#define MIN(a,b) (((a)<(b)) ? (a) : (b)) +#endif + +DWORD PALAPI EventTestThread(PVOID pArg) +{ + BOOL bRet; + DWORD dwRet; + HANDLE hEvent[2]; + HANDLE (*prgHandles)[] = (HANDLE (*)[])pArg; + + Trace("[EventTestThread] Starting\n"); + + bRet = DuplicateHandle(GetCurrentProcess(), (*prgHandles)[0], GetCurrentProcess(), + &hEvent[0], 0, FALSE, DUPLICATE_SAME_ACCESS); + bRet &= DuplicateHandle(GetCurrentProcess(), (*prgHandles)[1], GetCurrentProcess(), + &hEvent[1], 0, FALSE, DUPLICATE_SAME_ACCESS); + if (FALSE == bRet) + { + Fail("[EventTestThread] Failed to duplicate handles\n"); + } + + Sleep(1000); + bRet = SetEvent(hEvent[1]); + if (FALSE == bRet) + { + Fail("SetEvent failed\n"); + Fail("[EventTestThread] SetEvent failed [GetLastError()=%u]\n", + GetLastError()); + } + + dwRet = WaitForSingleObject(hEvent[1], INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("[EventTestThread] WaitForMultipleObjects failed [GetLastError()=%u]\n", + GetLastError()); + } + + Sleep(1000); + bRet = SetEvent(hEvent[0]); + if (FALSE == bRet) + { + Fail("[EventTestThread] SetEvent failed [GetLastError()=%u]\n", + GetLastError()); + } + + Sleep(1000); + bRet = SetEvent(hEvent[1]); + if (FALSE == bRet) + { + Fail("[EventTestThread] SetEvent failed [GetLastError()=%u]\n", + GetLastError()); + } + + CloseHandle(hEvent[0]); + CloseHandle(hEvent[1]); + + Trace("[EventTestThread] Done\n"); + return 0; +} + +DWORD PALAPI MutexTestThread(PVOID pArg) +{ + BOOL bRet; + DWORD dwRet; + HANDLE hMutex; + + Trace("[MutexTestThread] Starting\n"); + + bRet = DuplicateHandle(GetCurrentProcess(), (HANDLE)pArg, GetCurrentProcess(), &hMutex, + 0, FALSE, DUPLICATE_SAME_ACCESS); + if (FALSE == bRet) + { + Fail("[EventTestThread] DuplicateHandle failed [GetLastError()=%u]\n", + GetLastError()); + } + + dwRet = WaitForSingleObject(hMutex, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("[EventTestThread] WaitForMultipleObjects failed [GetLastError()=%u]\n", + GetLastError()); + } + + Sleep(1000); + CloseHandle(hMutex); + + Trace("[MutexTestThread] Done\n"); + + return 0; +} + +DWORD PALAPI TestThread(PVOID pArg) +{ + BOOL bRet; + DWORD dwRet; + PROCESS_INFORMATION pi; + STARTUPINFO si; + HANDLE hNamedEvent; + HANDLE hEvent[2] = { 0, 0 }; + HANDLE hMutex = 0; + HANDLE hSemaphore = 0; + HANDLE hObjs[2]; + DWORD dwThreadNum; + DWORD dwSlaveThreadTid = 0; + HANDLE hThread; + int i, iCnt, iRet; + char szTestName[128]; + char szCmd[128]; + char szEventName[128] = { 0 }; + char szMutexName[128] = { 0 }; + char szSemName[128] = { 0 }; + WCHAR wszEventName[128] = { 0 }; + WCHAR wszMutexName[128] = { 0 }; + WCHAR wszSemName[128] = { 0 }; + BOOL bMutex = g_bMutex; + BOOL bEvent = g_bEvent; + BOOL bNamedEvent = g_bNamedEvent; + BOOL bSemaphore = g_bSemaphore; + BOOL bProcess = g_bProcess; + BOOL bLocalWaitAll = g_bLocalWaitAll; + BOOL bRemoteWaitAll = g_bRemoteWaitAll; + int iDesiredExitCode; + + dwThreadNum = (DWORD)pArg; + + _snprintf (szTestName, 128, "Test6_%u", dwThreadNum); + szTestName[127] = 0; + + _snprintf(szEventName, 128, "%s_Event", szTestName); + szEventName[127] = 0; + _snprintf(szMutexName, 128, "%s_Mutex", szTestName); + szMutexName[127] = 0; + _snprintf(szSemName, 128, "%s_Semaphore", szTestName); + szSemName[127] = 0; + + iRet = MultiByteToWideChar(CP_ACP, 0, szEventName, strlen(szEventName)+1, wszEventName, 128); + iRet &= MultiByteToWideChar(CP_ACP, 0, szMutexName, strlen(szMutexName)+1, wszMutexName, 128); + iRet &= MultiByteToWideChar(CP_ACP, 0, szSemName, strlen(szSemName)+1, wszSemName, 128); + + if (0 == iRet) + { + Fail("[TestThread] Failed to convert strings\n"); + } + + Trace("[TestThread] TestName=%s Event: %S, Mutex: %S, Semaphore = %S\n", + szTestName, wszEventName, wszMutexName, wszSemName); + + hEvent[0] = CreateEventA(NULL, FALSE, FALSE, NULL); + hEvent[1] = CreateEventA(NULL, FALSE, FALSE, NULL); + + hNamedEvent = CreateEventW(NULL, FALSE, FALSE, wszEventName); + hMutex = CreateMutexW(NULL, FALSE, wszMutexName); + hSemaphore = CreateSemaphoreW(NULL, 0, 256, wszSemName); + + if (NULL == hEvent[0] || NULL == hEvent[1] || NULL == hMutex || + NULL == hNamedEvent || NULL == hSemaphore) + { + Fail("[TestThread] Failed to create objects " + "[hNamedEvent=%p hMutex=%p hSemaphore=%p]\n", + (VOID*)hNamedEvent, (VOID*)hMutex, (VOID*)hSemaphore); + } + + for (iCnt=0; iCnt<iCount; iCnt++) + { + if (g_bRandom) + { + int iRnd; + + bMutex = 0; + bEvent = 0; + bNamedEvent = 0; + bSemaphore = 0; + bProcess = 0; + bLocalWaitAll = 0; + bRemoteWaitAll = 0; + + iRnd = rand() % 7; + switch(iRnd) + { + case 0: + bMutex = 1; + break; + case 1: + bEvent = 1; + break; + case 2: + bNamedEvent = 1; + break; + case 3: + bSemaphore = 1; + break; + case 4: + bProcess = 1; + break; + case 5: + bLocalWaitAll = 1; + break; + case 6: + bRemoteWaitAll = 1; + break; + } + } + + if (bEvent) + { + Trace("======================================================================\n"); + Trace("Local unnamed event test\n"); + Trace("----------------------------------------\n"); + hThread = CreateThread(NULL, 0, EventTestThread, (PVOID)hEvent, 0, &dwSlaveThreadTid); + if (NULL == hThread) + { + Fail("Failed to create thread\n"); + } + + hObjs[0] = hEvent[0]; + dwRet = WaitForMultipleObjects(1, hObjs, FALSE, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("WaitForMultipleObjects failed\n"); + } + + hObjs[0] = hThread; + dwRet = WaitForMultipleObjects(1, hObjs, FALSE, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("WaitForMultipleObjects failed\n"); + } + + CloseHandle(hThread); + Trace("Local unnamed event test done \n"); + Trace("======================================================================\n"); + } + + if (bMutex) + { + Trace("======================================================================\n"); + Trace("Mutex with remote thread awakening test\n"); + Trace("----------------------------------------\n"); + + hThread = CreateThread(NULL, 0, MutexTestThread, (PVOID)hMutex, 0, &dwSlaveThreadTid); + if (NULL == hThread) + { + Fail("Failed to create thread\n"); + } + + Sleep(1000); + + hObjs[0] = hMutex; + + for (i=0;i<10;i++) + { + dwRet = WaitForMultipleObjects(1, hObjs, FALSE, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("WaitForMultipleObjects failed [dwRet=%x GetLastError()=%d\n", + dwRet, GetLastError()); + } + } + + hObjs[0] = hThread; + dwRet = WaitForMultipleObjects(1, hObjs, FALSE, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("WaitForMultipleObjects failed [GetLastError()=%u]\n", + GetLastError()); + } + + for (i=0;i<10;i++) + { + bRet = ReleaseMutex(hMutex); + if (FALSE == bRet) + { + Fail("ReleaseMutex failed [GetLastError()=%u]\n", + GetLastError()); + } + } + + CloseHandle(hThread); + Trace("Mutex with remote thread awakening test done\n"); + Trace("======================================================================\n"); + } + + if (bNamedEvent) + { + Trace("======================================================================\n"); + Trace("Named event with remote thread awakening test\n"); + Trace("----------------------------------------\n"); + + ZeroMemory ( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory ( &pi, sizeof(pi) ); + + _snprintf (szCmd, 128, "child6 -event %s", szTestName); + szCmd[127] = 0; + + bRet = CreateProcessA(NULL, szCmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); + if (FALSE == bRet) + { + Fail("CreateProcess failed [GetLastError()=%u]\n", + GetLastError()); + } + + hObjs[0] = pi.hProcess; + hObjs[1] = hNamedEvent; + + dwRet = WaitForMultipleObjects(2, hObjs, FALSE, INFINITE); + if (1 != dwRet) + { + Fail("WaitForMultipleObjects failed [dwRet=%u GetLastError()=%u]\n", + dwRet, GetLastError()); + } + + dwRet = WaitForSingleObject(pi.hProcess, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("WaitForMultipleObjects failed [GetLastError()=%u]\n", + GetLastError()); + } + Trace("Named event with remote thread awakening test done\n"); + Trace("======================================================================\n"); + } + + if (bSemaphore) + { + Trace("======================================================================\n"); + Trace("Semaphore with remote thread awakening test\n"); + Trace("----------------------------------------\n"); + + ZeroMemory ( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory ( &pi, sizeof(pi) ); + + _snprintf (szCmd, 128, "child6 -semaphore %s", szTestName); + szCmd[127] = 0; + + bRet = CreateProcessA(NULL, szCmd, NULL, NULL, FALSE, + 0, NULL, NULL, &si, &pi); + if (FALSE == bRet) + { + Fail("CreateProcessA failed [GetLastError()=%u]\n", + GetLastError()); + } + + Trace("Setting event %s\n", szEventName); + bRet = SetEvent(hNamedEvent); + if (FALSE == bRet) + { + Fail("[child] SetEvent failed [GetLastError()=%u]\n", + GetLastError()); + } + + Trace("Going to wait on semaphore %s\n", szSemName); + + + hObjs[0] = pi.hProcess; + hObjs[0] = hEvent[0]; + hObjs[1] = hSemaphore; + for (i=0;i<10;i++) + { + dwRet = WaitForMultipleObjects(2, hObjs, FALSE, INFINITE); + if (1 != dwRet) + { + Trace("WaitForMultipleObjects failed [tid=%u dwRet=%u GetLastError()=%u]\n", + GetCurrentThreadId(), dwRet, GetLastError()); + DebugBreak(); + } + } + + dwRet = WaitForSingleObject(pi.hProcess, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("WaitForMultipleObjects failed [GetLastError()=%u]\n", + GetLastError()); + } + Trace("Semaphore with remote thread awakening test done\n"); + Trace("======================================================================\n"); + } + + if (bProcess) + { + DWORD dwExitCode; + + Trace("======================================================================\n"); + Trace("Process wait test\n"); + Trace("----------------------------------------\n"); + + iDesiredExitCode = rand() % 0xFF; + + ZeroMemory ( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory ( &pi, sizeof(pi) ); + + _snprintf (szCmd, 128, "child6 -mutex %s -exitcode %d", szTestName, iDesiredExitCode); + szCmd[127] = 0; + + bRet = CreateProcessA(NULL, szCmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); + if (FALSE == bRet) + { + Fail("CreateProcess failed [GetLastError()=%u]\n", + GetLastError()); + } + + Trace("Going to wait on event %s\n", szEventName); + dwRet = WaitForSingleObject(hNamedEvent, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("WaitForMultipleObjects failed [GetLastError()=%u]\n", + GetLastError()); + } + + hObjs[0] = hEvent[0]; // dummy, this is a local event + hObjs[1] = hMutex; + + dwRet = WaitForMultipleObjects(2, hObjs, FALSE, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("WaitForMultipleObjects failed [GetLastError()=%u]\n", + GetLastError()); + } + if (1 == dwRet || (1 + WAIT_ABANDONED_0) == dwRet) + { + bRet = ReleaseMutex(hMutex); + if (FALSE == bRet) + { + Fail("ReleaseMutex failed [GetLastError()=%u]\n", + GetLastError()); + } + } + + dwRet = WaitForSingleObject(pi.hProcess, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("WaitForMultipleObjects failed [GetLastError()=%u]\n", + GetLastError()); + } + + if (!GetExitCodeProcess(pi.hProcess, &dwExitCode)) + { + Trace("GetExitCodeProcess call failed LastError:(%u)\n", + GetLastError()); + dwExitCode = FAIL; + } + + if (iDesiredExitCode != dwExitCode) + { + Fail("Wrong return code: %u [%d]\n", dwExitCode, iDesiredExitCode); + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + Trace("Process wait test done\n"); + Trace("======================================================================\n"); + } + + if (bLocalWaitAll) + { + Trace("======================================================================\n"); + Trace("WaitAll with local thread awakening test\n"); + Trace("----------------------------------------\n"); + + hThread = CreateThread(NULL, 0, EventTestThread, (PVOID)hEvent, 0, &dwSlaveThreadTid); + if (NULL == hThread) + { + Fail("CreateThread failed [GetLastError()=%u]\n", + GetLastError()); + } + + dwRet = WaitForMultipleObjects(2, hEvent, TRUE, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("WaitForMultipleObjects failed [GetLastError()=%u]\n", + GetLastError()); + } + + hObjs[0] = hThread; + dwRet = WaitForMultipleObjects(1, hObjs, FALSE, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("WaitForMultipleObjects failed [GetLastError()=%u]\n", + GetLastError()); + } + + CloseHandle(hThread); + Trace("WaitAll with local thread awakening test done\n"); + Trace("======================================================================\n"); + } + + if (bRemoteWaitAll) + { + Trace("======================================================================\n"); + Trace("WaitAll with remote thread awakening test\n"); + Trace("----------------------------------------\n"); + + ZeroMemory ( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory ( &pi, sizeof(pi) ); + + _snprintf (szCmd, 128, "child6 -mutex_and_named_event %s", szTestName); + szCmd[127] = 0; + + bRet = CreateProcessA(NULL, szCmd, NULL, NULL, FALSE, + 0, NULL, NULL, &si, &pi); + if (FALSE == bRet) + { + Fail("CreateProcess failed [GetLastError()=%u]\n", + GetLastError()); + } + + Sleep(1000); + + hObjs[0] = hMutex; + hObjs[1] = hNamedEvent; + + dwRet = WaitForMultipleObjects(2, hObjs, TRUE, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("WaitForMultipleObjects failed [GetLastError()=%u]\n", + GetLastError()); + } + + bRet = ReleaseMutex(hMutex); + if (FALSE == bRet) + { + Fail("ReleaseMutex failed [GetLastError()=%u]\n", + GetLastError()); + } + + dwRet = WaitForSingleObject(pi.hProcess, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("WaitForMultipleObjects failed [GetLastError()=%u]\n", + GetLastError()); + } + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + Trace("WaitAll with remote thread awakening test done\n"); + Trace("======================================================================\n"); + } + } + + return 0; +} + +int __cdecl main(int argc, char **argv) +{ + DWORD dwRet; + DWORD dwSlaveThreadTid = 0; + int i, iCnt; + + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + srand(time(NULL) * GetCurrentProcessId()); + + if (argc == 1) + { + g_bMutex = 1; + g_bEvent = 1; + g_bNamedEvent = 1; + g_bSemaphore = 1; + g_bProcess = 1; + g_bLocalWaitAll = 1; + g_bRemoteWaitAll = 1; + } + else + { + for (i=1;i<argc;i++) + { + if (0 == strcmp(argv[i], "-mutex")) + { + g_bMutex = 1; + } + else if (0 == strcmp(argv[i], "-event")) + { + g_bEvent = 1; + } + else if (0 == strcmp(argv[i], "-namedevent")) + { + g_bNamedEvent = 1; + } + else if (0 == strcmp(argv[i], "-semaphore")) + { + g_bSemaphore = 1; + } + else if (0 == strcmp(argv[i], "-process")) + { + g_bProcess = 1; + } + else if (0 == strcmp(argv[i], "-localwaitall")) + { + g_bLocalWaitAll = 1; + } + else if (0 == strcmp(argv[i], "-remotewaitall")) + { + g_bRemoteWaitAll = 1; + } + else if (0 == strcmp(argv[i], "-all")) + { + g_bMutex = 1; + g_bEvent = 1; + g_bNamedEvent = 1; + g_bSemaphore = 1; + g_bProcess = 1; + g_bLocalWaitAll = 1; + g_bRemoteWaitAll = 1; + } + else if (0 == strcmp(argv[i], "-random")) + { + g_bRandom = 1; + } + else if ((0 == strcmp(argv[i], "-count")) && (argc > i+1)) + { + i++; + iCnt = atoi(argv[i]); + if (iCnt > 0 && iCnt < MAX_COUNT) + { + iCount = iCnt; + } + } + else if ((0 == strcmp(argv[i], "-threads")) && (argc > i+1)) + { + i++; + iCnt = atoi(argv[i]); + if (iCnt > 0 && iCnt <= MAX_THREADS) + { + iThreads = iCnt; + } + } + else + { + Trace("Unknown option %s ignored\n", argv[i]); + } + } + } + + + iCnt = 0; + for (i=0;i<iThreads;i++) + { + hThreads[iCnt] = CreateThread(NULL, 0, TestThread, (VOID*)iCnt, 0, &dwSlaveThreadTid); + if (NULL == hThreads[iCnt]) + { + Trace("Failed to create thread\n"); + } + else + { + iCnt++; + } + } + + if (0 == iCnt) + { + Fail("Can't create any thread\n"); + } + + for (i=0; i<iCnt; i+=64) + { + dwRet = WaitForMultipleObjects(MIN(64, iCnt-i), &hThreads[i], TRUE, INFINITE); + if (WAIT_FAILED == dwRet) + { + Fail("WaitForMultipleObjects failed [dwRet=%u GetLastError()=%u iCnt=%d i=%d]\n", + dwRet, GetLastError(), iCnt, i); + } + } + + + for (i=0; i<iCnt; i++) + { + CloseHandle(hThreads[i]); + } + + PAL_Terminate(); + return PASS; +} diff --git a/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/thistest.dat b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/thistest.dat new file mode 100644 index 0000000000..8d36162752 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/thistest.dat @@ -0,0 +1,2 @@ + +PAL,threading,palsuite\threading\waitformultipleobjectsex\test6,Test6forWaitForMultipleObjectsEx=test6.c child6.c,<SUPPORTEXE> diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForSingleObject/CMakeLists.txt new file mode 100644 index 0000000000..94bd51caa6 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(WFSOExMutexTest) +add_subdirectory(WFSOExSemaphoreTest) +add_subdirectory(WFSOExThreadTest) +add_subdirectory(WFSOMutexTest) +add_subdirectory(WFSOProcessTest) +add_subdirectory(WFSOSemaphoreTest) +add_subdirectory(WFSOThreadTest) + diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExMutexTest/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExMutexTest/CMakeLists.txt new file mode 100644 index 0000000000..65fa0abe14 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExMutexTest/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + WFSOExMutexTest.c +) + +add_executable(paltest_waitforsingleobject_wfsoexmutextest + ${SOURCES} +) + +add_dependencies(paltest_waitforsingleobject_wfsoexmutextest coreclrpal) + +target_link_libraries(paltest_waitforsingleobject_wfsoexmutextest + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExMutexTest/WFSOExMutexTest.c b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExMutexTest/WFSOExMutexTest.c new file mode 100644 index 0000000000..3737f9cc10 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExMutexTest/WFSOExMutexTest.c @@ -0,0 +1,214 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: WFSOExMutex.c +** +** Purpose: Tests a child thread in the middle of a +** WaitForSingleObjectEx call will be interrupted by QueueUserAPC +** if the alert flag was set. +** +** +**===================================================================*/ + +#include <palsuite.h> + +/*Based on SleepEx/test2 */ + +const int ChildThreadWaitTime = 4000; +const int InterruptTime = 2000; +const DWORD AcceptableDelta = 300; + +void RunTest(BOOL AlertThread); +VOID PALAPI APCFunc(ULONG_PTR dwParam); +DWORD PALAPI WaiterProc(LPVOID lpParameter); + +DWORD ThreadWaitDelta; +HANDLE hMutex; + + + +int __cdecl main( int argc, char **argv ) +{ + int ret=0; + + if (0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + /* + On some platforms (e.g. FreeBSD 4.9) the first call to some synch objects + (such as conditions) involves some pthread internal initialization that + can make the first wait slighty longer, potentially going above the + acceptable delta for this test. Let's add a dummy wait to preinitialize + internal structures + */ + Sleep(100); + + /* + The state of a mutex object is signaled when it is not owned by any thread. + The creating thread can use the bInitialOwner flag to request immediate ownership + of the mutex. Otherwise, a thread must use one of the wait functions to request + ownership. When the mutex's state is signaled, one waiting thread is granted + ownership, the mutex's state changes to nonsignaled, and the wait function returns. + Only one thread can own a mutex at any given time. The owning thread uses the + ReleaseMutex function to release its ownership. + */ + + /* Create a mutex that is not in the signalled state */ + hMutex = CreateMutex(NULL, //No security attributes + TRUE, //Iniitally owned + NULL); //Name of mutex + + if (hMutex == NULL) + { + Fail("Failed to create mutex! GetLastError returned %d.\n", + GetLastError()); + } + /* + * Check that Queueing an APC in the middle of a wait does interrupt + * it, if it's in an alertable state. + */ + + RunTest(TRUE); + if ((ThreadWaitDelta - InterruptTime) > AcceptableDelta) + { + Fail("Expected thread to wait for %d ms (and get interrupted).\n" + "Thread waited for %d ms! (Acceptable delta: %d)\n", + InterruptTime, ThreadWaitDelta, AcceptableDelta); + } + + + /* + * Check that Queueing an APC in the middle of a wait does NOT interrupt + * it, if it is not in an alertable state. + */ + RunTest(FALSE); + if ((ThreadWaitDelta - ChildThreadWaitTime) > AcceptableDelta) + { + Fail("Expected thread to wait for %d ms (and not be interrupted).\n" + "Thread waited for %d ms! (Acceptable delta: %d)\n", + ChildThreadWaitTime, ThreadWaitDelta, AcceptableDelta); + } + + + + //Release Mutex + ret = ReleaseMutex(hMutex); + if (0==ret) + { + Fail("Unable to Release Mutex!\n" + "GetLastError returned %d\n", GetLastError()); + } + + //Close Mutex Handle + ret = CloseHandle(hMutex); + if (!ret) + { + Fail("Unable to close handle to Mutex!\n" + "GetLastError returned %d\n", GetLastError()); + } + + PAL_Terminate(); + return PASS; +} + +void RunTest(BOOL AlertThread) +{ + + HANDLE hThread = 0; + DWORD dwThreadId = 0; + + int ret=0; + + hThread = CreateThread( NULL, + 0, + (LPTHREAD_START_ROUTINE)WaiterProc, + (LPVOID) AlertThread, + 0, + &dwThreadId); + + if (hThread == NULL) + { + Fail("ERROR: Was not able to create the thread to test!\n" + "GetLastError returned %d\n", GetLastError()); + } + + + + Sleep(InterruptTime); + + ret = QueueUserAPC(APCFunc, hThread, 0); + + if (ret == 0) + { + Fail("QueueUserAPC failed! GetLastError returned %d\n", + GetLastError()); + } + + ret = WaitForSingleObject(hThread, INFINITE); + + if (ret == WAIT_FAILED) + { + Fail("Unable to wait on child thread!\nGetLastError returned %d.\n", + GetLastError()); + } + + + if (0==CloseHandle(hThread)) + { + Trace("Could not close Thread handle\n"); + Fail ( "GetLastError returned %d\n", GetLastError()); + } +} + +/* Function doesn't do anything, just needed to interrupt the wait*/ +VOID PALAPI APCFunc(ULONG_PTR dwParam) +{ +} + +/* Entry Point for child thread. */ +DWORD PALAPI WaiterProc(LPVOID lpParameter) +{ + UINT64 OldTimeStamp; + UINT64 NewTimeStamp; + BOOL Alertable; + DWORD ret; + + Alertable = (BOOL) lpParameter; + + LARGE_INTEGER performanceFrequency; + if (!QueryPerformanceFrequency(&performanceFrequency)) + { + Fail("Failed to query performance frequency!"); + } + + OldTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency); + + ret = WaitForSingleObjectEx( hMutex, + ChildThreadWaitTime, + Alertable); + + NewTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency); + + if (Alertable && ret != WAIT_IO_COMPLETION) + { + Fail("Expected the interrupted wait to return WAIT_IO_COMPLETION.\n" + "Got %d\n", ret); + } + else if (!Alertable && ret != WAIT_TIMEOUT) + { + Fail("WaitForSingleObjectEx did not timeout.\n" + "Expected return of WAIT_TIMEOUT, got %d.\n", ret); + } + + ThreadWaitDelta = NewTimeStamp - OldTimeStamp; + + return 0; +} + + + diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExSemaphoreTest/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExSemaphoreTest/CMakeLists.txt new file mode 100644 index 0000000000..12517e31e0 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExSemaphoreTest/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + WFSOExSemaphoreTest.c +) + +add_executable(paltest_waitforsingleobject_wfsoexsemaphoretest + ${SOURCES} +) + +add_dependencies(paltest_waitforsingleobject_wfsoexsemaphoretest coreclrpal) + +target_link_libraries(paltest_waitforsingleobject_wfsoexsemaphoretest + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExSemaphoreTest/WFSOExSemaphoreTest.c b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExSemaphoreTest/WFSOExSemaphoreTest.c new file mode 100644 index 0000000000..793c50995c --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExSemaphoreTest/WFSOExSemaphoreTest.c @@ -0,0 +1,184 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: WFSOExSemaphore.c +** +** Purpose: Tests a child thread in the middle of a +** WaitForSingleObjectEx call will be interrupted by QueueUserAPC +** if the alert flag was set. +** +** +**===================================================================*/ + +#include <palsuite.h> + +/*Based on SleepEx/test2 */ + +const int ChildThreadWaitTime = 4000; +const int InterruptTime = 2000; +const DWORD AcceptableDelta = 300; + +void RunTest(BOOL AlertThread); +VOID PALAPI APCFunc(ULONG_PTR dwParam); +DWORD PALAPI WaiterProc(LPVOID lpParameter); + +DWORD ThreadWaitDelta; + +int __cdecl main( int argc, char **argv ) +{ + if (0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + /* + On some platforms (e.g. FreeBSD 4.9) the first call to some synch objects + (such as conditions) involves some pthread internal initialization that + can make the first wait slighty longer, potentially going above the + acceptable delta for this test. Let's add a dummy wait to preinitialize + internal structures + */ + Sleep(100); + + /* + * Check that Queueing an APC in the middle of a wait does interrupt + * it, if it's in an alertable state. + */ + + RunTest(TRUE); + if ((ThreadWaitDelta - InterruptTime) > AcceptableDelta) + { + Fail("Expected thread to wait for %d ms (and get interrupted).\n" + "Thread waited for %d ms! (Acceptable delta: %d)\n", + InterruptTime, ThreadWaitDelta, AcceptableDelta); + } + + + /* + * Check that Queueing an APC in the middle of a wait does NOT interrupt + * it, if it is not in an alertable state. + */ + RunTest(FALSE); + if ((ThreadWaitDelta - ChildThreadWaitTime) > AcceptableDelta) + { + Fail("Expected thread to wait for %d ms (and not be interrupted).\n" + "Thread waited for %d ms! (Acceptable delta: %d)\n", + ChildThreadWaitTime, ThreadWaitDelta, AcceptableDelta); + } + + + PAL_Terminate(); + return PASS; +} + +void RunTest(BOOL AlertThread) +{ + HANDLE hThread = 0; + DWORD dwThreadId = 0; + int ret; + + hThread = CreateThread( NULL, + 0, + (LPTHREAD_START_ROUTINE)WaiterProc, + (LPVOID) AlertThread, + 0, + &dwThreadId); + + if (hThread == NULL) + { + Fail("ERROR: Was not able to create the thread to test!\n" + "GetLastError returned %d\n", GetLastError()); + } + + Sleep(InterruptTime); + + ret = QueueUserAPC(APCFunc, hThread, 0); + if (ret == 0) + { + Fail("QueueUserAPC failed! GetLastError returned %d\n", + GetLastError()); + } + + ret = WaitForSingleObject(hThread, INFINITE); + if (ret == WAIT_FAILED) + { + Fail("Unable to wait on child thread!\nGetLastError returned %d.\n", + GetLastError()); + } + + if (0==CloseHandle(hThread)) + { + Trace("Could not close Thread handle\n"); + Fail ( "GetLastError returned %d\n", GetLastError()); + } +} + +/* Function doesn't do anything, just needed to interrupt the wait*/ +VOID PALAPI APCFunc(ULONG_PTR dwParam) +{ +} + +/* Entry Point for child thread. */ +DWORD PALAPI WaiterProc(LPVOID lpParameter) +{ + HANDLE hSemaphore; + UINT64 OldTimeStamp; + UINT64 NewTimeStamp; + BOOL Alertable; + DWORD ret; + + LARGE_INTEGER performanceFrequency; + if (!QueryPerformanceFrequency(&performanceFrequency)) + { + Fail("Failed to query performance frequency!"); + } + + /* Create a semaphore that is not in the signalled state */ + hSemaphore = CreateSemaphoreW(NULL, 0, 1, NULL); + + if (hSemaphore == NULL) + { + Fail("Failed to create semaphore! GetLastError returned %d.\n", + GetLastError()); + } + + Alertable = (BOOL) lpParameter; + + OldTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency); + + ret = WaitForSingleObjectEx( hSemaphore, + ChildThreadWaitTime, + Alertable); + + NewTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency); + + + if (Alertable && ret != WAIT_IO_COMPLETION) + { + Fail("Expected the interrupted wait to return WAIT_IO_COMPLETION.\n" + "Got %d\n", ret); + } + else if (!Alertable && ret != WAIT_TIMEOUT) + { + Fail("WaitForSingleObjectEx did not timeout.\n" + "Expected return of WAIT_TIMEOUT, got %d.\n", ret); + } + + + ThreadWaitDelta = NewTimeStamp - OldTimeStamp; + + ret = CloseHandle(hSemaphore); + if (!ret) + { + Fail("Unable to close handle to semaphore!\n" + "GetLastError returned %d\n", GetLastError()); + } + + return 0; +} + + + diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExThreadTest/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExThreadTest/CMakeLists.txt new file mode 100644 index 0000000000..f3d868f082 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExThreadTest/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + WFSOExThreadTest.c +) + +add_executable(paltest_waitforsingleobject_wfsoexthreadtest + ${SOURCES} +) + +add_dependencies(paltest_waitforsingleobject_wfsoexthreadtest coreclrpal) + +target_link_libraries(paltest_waitforsingleobject_wfsoexthreadtest + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExThreadTest/WFSOExThreadTest.c b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExThreadTest/WFSOExThreadTest.c new file mode 100644 index 0000000000..894d2804aa --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExThreadTest/WFSOExThreadTest.c @@ -0,0 +1,204 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*===================================================================== +** +** Source: WFSOExThreadTest.c +** +** Purpose: Tests a child thread in the middle of a +** WaitForSingleObjectEx call will be interrupted by QueueUserAPC +** if the alert flag was set. +** +** +**===================================================================*/ + +#include <palsuite.h> + +/*Based on SleepEx/test2 */ + +const int ChildThreadWaitTime = 4000; +const int InterruptTime = 2000; +const DWORD AcceptableDelta = 300; + +void RunTest(BOOL AlertThread); +VOID PALAPI APCFunc(ULONG_PTR dwParam); +DWORD PALAPI WaiterProc(LPVOID lpParameter); +void WorkerThread(void); + +int ThreadWaitDelta; + +int __cdecl main( int argc, char **argv ) +{ + if (0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + /* + On some platforms (e.g. FreeBSD 4.9) the first call to some synch objects + (such as conditions) involves some pthread internal initialization that + can make the first wait slighty longer, potentially going above the + acceptable delta for this test. Let's add a dummy wait to preinitialize + internal structures + */ + Sleep(100); + + /* + * Check that Queueing an APC in the middle of a wait does interrupt + * it, if it's in an alertable state. + */ + + RunTest(TRUE); + if (abs(ThreadWaitDelta - InterruptTime) > AcceptableDelta) + { + Fail("Expected thread to wait for %d ms (and get interrupted).\n" + "Thread waited for %d ms! (Acceptable delta: %d)\n", + InterruptTime, ThreadWaitDelta, AcceptableDelta); + } + + + /* + * Check that Queueing an APC in the middle of a wait does NOT interrupt + * it, if it is not in an alertable state. + */ + RunTest(FALSE); + if (abs(ThreadWaitDelta - ChildThreadWaitTime) > AcceptableDelta) + { + Fail("Expected thread to wait for %d ms (and not be interrupted).\n" + "Thread waited for %d ms! (Acceptable delta: %d)\n", + ChildThreadWaitTime, ThreadWaitDelta, AcceptableDelta); + } + + + PAL_Terminate(); + return PASS; +} + +void RunTest(BOOL AlertThread) +{ + HANDLE hThread = 0; + DWORD dwThreadId = 0; + int ret; + + //Create thread + hThread = CreateThread( NULL, + 0, + (LPTHREAD_START_ROUTINE)WaiterProc, + (LPVOID) AlertThread, + 0, + &dwThreadId); + + if (hThread == NULL) + { + Fail("ERROR: Was not able to create the thread to test!\n" + "GetLastError returned %d\n", GetLastError()); + } + + Sleep(InterruptTime); + + ret = QueueUserAPC(APCFunc, hThread, 0); + if (ret == 0) + { + Fail("QueueUserAPC failed! GetLastError returned %d\n", + GetLastError()); + } + + + ret = WaitForSingleObject(hThread, INFINITE); + if (ret == WAIT_FAILED) + { + Fail("Unable to wait on child thread!\nGetLastError returned %d.\n", + GetLastError()); + } + + if (0==CloseHandle(hThread)) + { + Trace("Could not close Thread handle\n"); + Fail ( "GetLastError returned %d\n", GetLastError()); + } +} + +/* Function doesn't do anything, just needed to interrupt the wait*/ +VOID PALAPI APCFunc(ULONG_PTR dwParam) +{ +} + +/* Entry Point for child thread. */ +DWORD PALAPI WaiterProc(LPVOID lpParameter) +{ + HANDLE hWaitThread; + UINT64 OldTimeStamp; + UINT64 NewTimeStamp; + BOOL Alertable; + DWORD ret; + DWORD dwThreadId = 0; + +/* +When a thread terminates, the thread object attains a signaled state, +satisfying any threads that were waiting on the object. +*/ + +/* Create a thread that does not return immediately to maintain a non signaled test*/ + hWaitThread = CreateThread( NULL, + 0, + (LPTHREAD_START_ROUTINE)WorkerThread, + NULL, + 0, + &dwThreadId); + + if (hWaitThread == NULL) + { + Fail("ERROR: Was not able to create worker thread to wait on!\n" + "GetLastError returned %d\n", GetLastError()); + } + + Alertable = (BOOL) lpParameter; + + LARGE_INTEGER performanceFrequency; + if (!QueryPerformanceFrequency(&performanceFrequency)) + { + Fail("Failed to query performance frequency!"); + } + + OldTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency); + + ret = WaitForSingleObjectEx( hWaitThread, + ChildThreadWaitTime, + Alertable); + + NewTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency); + + + if (Alertable && ret != WAIT_IO_COMPLETION) + { + Fail("Expected the interrupted wait to return WAIT_IO_COMPLETION.\n" + "Got %d\n", ret); + } + else if (!Alertable && ret != WAIT_TIMEOUT) + { + Fail("WaitForSingleObjectEx did not timeout.\n" + "Expected return of WAIT_TIMEOUT, got %d.\n", ret); + } + + ThreadWaitDelta = NewTimeStamp - OldTimeStamp; + + ret = CloseHandle(hWaitThread); + if (!ret) + { + Fail("Unable to close handle to Thread!\n" + "GetLastError returned %d\n", GetLastError()); + } + + return 0; +} + + +void WorkerThread(void) +{ + + //Make the worker thread sleep to test WFSOEx Functionality + + Sleep(2*ChildThreadWaitTime); +} + diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOMutexTest/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOMutexTest/CMakeLists.txt new file mode 100644 index 0000000000..374880e90f --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOMutexTest/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + WFSOMutexTest.c +) + +add_executable(paltest_waitforsingleobject_wfsomutextest + ${SOURCES} +) + +add_dependencies(paltest_waitforsingleobject_wfsomutextest coreclrpal) + +target_link_libraries(paltest_waitforsingleobject_wfsomutextest + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOMutexTest/WFSOMutexTest.c b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOMutexTest/WFSOMutexTest.c new file mode 100644 index 0000000000..5ecf517c2a --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOMutexTest/WFSOMutexTest.c @@ -0,0 +1,184 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: WFSOMutexTest.c +** +** Purpose: Test for WaitForSingleObjectTest. +** Create Mutex Object +** Create Two Threads, Each Threads does WFSO for the Mutex Object +** Increments Counter +** Releases Mutex +** Test Passes if the above operations are successful +** +** +** +**=========================================================*/ + + + +#include <palsuite.h> + + +#define NUMBER_OF_WORKER_THREADS 2 + +//Declaring Variables +HANDLE hMutex = NULL; +unsigned int globalcounter =0; +int testReturnCode = PASS; + +//Declaring Function Prototypes +DWORD WFSOMutexTest(LPVOID params); +void incrementCounter(void); + + + +int __cdecl main(int argc, char **argv) +{ + + //Declare local variables + int i =0; + + // 2 dimensional array to hold thread handles for each worker thread + HANDLE hThread[NUMBER_OF_WORKER_THREADS]; + DWORD dwThreadId=0; + int returnCode = 0; + + //Initialize PAL + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + //Create Mutex + hMutex = CreateMutex(NULL, // no security attributes + FALSE, // initially not owned + NULL); // name of mutex + + //Check for Mutex Creation + + if (hMutex == NULL) + { + Fail("Create Mutex Failed, GetLastError: %d\n", GetLastError()); + } + + + //Spawn 2 worker threads + for (i=0;i<NUMBER_OF_WORKER_THREADS;i++) + { + //Create Thread + + hThread[i] = CreateThread( + NULL, + 0, + WFSOMutexTest, + NULL, + 0, + &dwThreadId); + + if ( NULL == hThread[i] ) + { + Fail ( "CreateThread() returned NULL. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + } + + } + + /* Test running */ + returnCode = WaitForMultipleObjects( NUMBER_OF_WORKER_THREADS, hThread, TRUE, 5000); + if( WAIT_OBJECT_0 != returnCode ) + { + Trace("Wait for Object(s) returned %d, and GetLastError value is %d\n", returnCode, GetLastError()); + testReturnCode = FAIL; + } + +//Close thread handles +for (i=0;i<NUMBER_OF_WORKER_THREADS;i++) + { + + if (0==CloseHandle(hThread[i])) + { + Trace("Could not Close thread handle\n"); + Fail ( "GetLastError returned %d\n", GetLastError()); + } + } + +//Close Mutex Handle +if (0==CloseHandle(hMutex)) + { + Trace("Could not close mutex handle\n"); + Fail ( "GetLastError returned %d\n", GetLastError()); + } + + +PAL_TerminateEx(testReturnCode); +return ( testReturnCode ); + +} + + +void incrementCounter(void) +{ + if (INT_MAX == globalcounter) + { + globalcounter = 0; + } + + globalcounter++; + Trace("Global Counter Value: %d \n", globalcounter); +} + + +DWORD WFSOMutexTest(LPVOID params) +{ + + DWORD dwWaitResult; + + // Request ownership of mutex. + + dwWaitResult = WaitForSingleObject( + hMutex, // handle to mutex + 5000L); // five-second time-out interval + + switch (dwWaitResult) + { + // The thread got mutex ownership. + case WAIT_OBJECT_0: + { + + incrementCounter(); + + //Release ownership of the mutex object. + if (! ReleaseMutex(hMutex)) + { + Fail ( "ReleaseMutex() returned NULL. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + } + + break; + } + + // Cannot get mutex ownership due to time-out. + case WAIT_TIMEOUT: + { + Fail ( "Cannot get mutex ownership due to time-out. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + return FALSE; + } + + // Got ownership of the abandoned mutex object. + case WAIT_ABANDONED: + { + Fail ( "Got ownership of the abandoned mutex object. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + return FALSE; + } + } + + return 1; +} + + + diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOProcessTest/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOProcessTest/CMakeLists.txt new file mode 100644 index 0000000000..a70b101988 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOProcessTest/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(TESTSOURCES + WFSOProcessTest.c +) + +add_executable(paltest_waitforsingleobject_wfsoprocesstest + ${TESTSOURCES} +) + +add_dependencies(paltest_waitforsingleobject_wfsoprocesstest coreclrpal) + +target_link_libraries(paltest_waitforsingleobject_wfsoprocesstest + pthread + m + coreclrpal +) + + +set(HELPERSOURCES + ChildProcess.c +) + +add_executable(paltest_waitforsingleobject_wfsoprocesstest_child + ${HELPERSOURCES} +) + +add_dependencies(paltest_waitforsingleobject_wfsoprocesstest_child coreclrpal) + +target_link_libraries(paltest_waitforsingleobject_wfsoprocesstest_child + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOProcessTest/ChildProcess.c b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOProcessTest/ChildProcess.c new file mode 100644 index 0000000000..91c24d87bb --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOProcessTest/ChildProcess.c @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: ChildProcess.c +** +** Purpose: Dummy Process which does some work on which the Main Test case waits +** +** + +** +**=========================================================*/ + + + +#include <palsuite.h> + +int __cdecl main(int argc, char **argv) +{ + +//Declare local variables +int i =0; + + + +//Initialize PAL +if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + +//Do some work +for (i=0; i<100000; i++); + +Trace("Counter Value was incremented to %d \n ",i); + +PAL_Terminate(); +return ( PASS ); + +} + + + + + + + + diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOProcessTest/WFSOProcessTest.c b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOProcessTest/WFSOProcessTest.c new file mode 100644 index 0000000000..2711e26c29 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOProcessTest/WFSOProcessTest.c @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: WFSOProcessTest.c +** +** Purpose: Test for WaitForSingleObjectTest. +** Create One Process and do some work +** Use WFSO For the Process to finish +** +** Test Passes if the above operations are successful +** +** +** +**=========================================================*/ + + + +#include <palsuite.h> + +int __cdecl main(int argc, char **argv) +{ + +//Declare local variables +STARTUPINFO si; +PROCESS_INFORMATION pi; + +DWORD dwWaitResult=0; + +//Initialize PAL +if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + +ZeroMemory( &si, sizeof(si) ); +si.cb = sizeof(si); +ZeroMemory( &pi, sizeof(pi) ); + +// Start the child process. +if( !CreateProcess( NULL, // No module name (use command line). + "childprocess", // Command line. + NULL, // Process handle not inheritable. + NULL, // Thread handle not inheritable. + FALSE, // Set handle inheritance to FALSE. + 0, // No creation flags. + NULL, // Use parent's environment block. + NULL, // Use parent's starting directory. + &si, // Pointer to STARTUPINFO structure. + &pi ) // Pointer to PROCESS_INFORMATION structure. +) + +{ +Fail ( "Create Process Failed. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); +} + +// Wait until child process exits. + dwWaitResult = WaitForSingleObject( pi.hProcess, INFINITE ); +switch (dwWaitResult) + { + // The Process wait was successful + case WAIT_OBJECT_0: + { + + Trace("Wait for Process was successful\n"); + break; + } + + // Time-out. + case WAIT_TIMEOUT: + { + Fail ( "Time -out. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + return FALSE; + } + + // Got ownership of the abandoned process object. + case WAIT_ABANDONED: + { + Fail ( "Got ownership of the abandoned Process object. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + return FALSE; + } + + //Error condition + case WAIT_FAILED: + { + Fail ( "Wait for Process Failed. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + return FALSE; + } + +} + + + +// Close process handle +if (0==CloseHandle(pi.hProcess)) + { + Trace("Could not close process handle\n"); + Fail ( "GetLastError returned %d\n", GetLastError()); + } + + +PAL_Terminate(); +return ( PASS ); + +} + + + + + + + diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOSemaphoreTest/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOSemaphoreTest/CMakeLists.txt new file mode 100644 index 0000000000..171e0583e5 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOSemaphoreTest/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + WFSOSemaphoreTest.c +) + +add_executable(paltest_waitforsingleobject_wfsosemaphoretest + ${SOURCES} +) + +add_dependencies(paltest_waitforsingleobject_wfsosemaphoretest coreclrpal) + +target_link_libraries(paltest_waitforsingleobject_wfsosemaphoretest + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOSemaphoreTest/WFSOSemaphoreTest.c b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOSemaphoreTest/WFSOSemaphoreTest.c new file mode 100644 index 0000000000..9902d448cd --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOSemaphoreTest/WFSOSemaphoreTest.c @@ -0,0 +1,183 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: WFSOMutexTest.c +** +** Purpose: Test for WaitForSingleObjectTest. +** Create Semaphore Object +** Create Two Threads, Each Threads does WFSO for the Semaphore Object +** Increments Counter +** Releases Semaphore +** Test Passes if the above operations are successful +** +** +** +**=========================================================*/ + + + +#include <palsuite.h> + + +#define NUMBER_OF_WORKER_THREADS 2 + + +//Declaring Variables +HANDLE hSemaphore = NULL; +unsigned int globalcounter =0; +int testReturnCode = PASS; + +//Declaring Function Prototypes +DWORD WFSOSemaphoreTest(LPVOID params); +void incrementCounter(void); + +int __cdecl main(int argc, char **argv) +{ + + //Declare local variables + int i =0; + int cMax = 2; + + int returnCode = 0; + + // 2 dimensional array to hold thread handles for each worker thread + HANDLE hThread[NUMBER_OF_WORKER_THREADS]; + DWORD dwThreadId=0; + + //Initialize PAL + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + //Create Semaphore + hSemaphore = CreateSemaphore( + NULL, // no security attributes + cMax, // initial count + cMax, // maximum count + NULL); // unnamed semaphore + + if (hSemaphore == NULL) + { + // Check for error. + Fail("Create Semaphore Failed, GetLastError: %d\n", GetLastError()); + } + + + + //Spawn 2 worker threads + for (i=0;i<NUMBER_OF_WORKER_THREADS;i++) + { + //Create Thread + + hThread[i] = CreateThread( + NULL, + 0, + WFSOSemaphoreTest, + NULL, + 0, + &dwThreadId); + + if ( NULL == hThread[i] ) + { + Fail ( "CreateThread() returned NULL. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + } + + } + + + /* Test running */ + returnCode = WaitForMultipleObjects( NUMBER_OF_WORKER_THREADS, hThread, TRUE, 5000); + if( WAIT_OBJECT_0 != returnCode ) + { + Trace("Wait for Object(s) returned %d, and GetLastError value is %d\n", returnCode, GetLastError()); + testReturnCode = FAIL; + } + +//Close thread handles +for (i=0;i<NUMBER_OF_WORKER_THREADS;i++) + { + + if (0==CloseHandle(hThread[i])) + { + Trace("Could not Close thread handle\n"); + Fail ( "GetLastError returned %d\n", GetLastError()); + } + } + +//Close Semaphore Handle +if (0==CloseHandle(hSemaphore)) + { + Trace("Could not close semaphore handle\n"); + Fail ( "GetLastError returned %d\n", GetLastError()); + } + +PAL_TerminateEx(testReturnCode); +return ( testReturnCode ); + +} + + +void incrementCounter(void) +{ + if (INT_MAX == globalcounter) + { + globalcounter = 0; + } + + globalcounter++; + Trace("Global Counter Value: %d \n", globalcounter); +} + + +DWORD WFSOSemaphoreTest(LPVOID params) +{ + + DWORD dwWaitResult; + + // Request ownership of Semaphore + + dwWaitResult = WaitForSingleObject( + hSemaphore, // handle to semaphore + 0L); // zero-second time-out interval + + + switch (dwWaitResult) + { + // The semaphore object was signaled. + case WAIT_OBJECT_0: + { + + incrementCounter(); + // Increment the count of the semaphore. + + if (!ReleaseSemaphore( + hSemaphore, // handle to semaphore + 1, // increase count by one + NULL) ) // not interested in previous count + { + Fail ( "ReleaseSemaphore() returned NULL. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + } + + break; + } + + // Semaphore was nonsignaled, so a time-out occurred. + case WAIT_TIMEOUT: + { + Fail ( "Semaphore was nonsignaled, so a time-out occurred. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + return FALSE; + } + } + + return 1; +} + + + diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOThreadTest/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOThreadTest/CMakeLists.txt new file mode 100644 index 0000000000..18701a9da5 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOThreadTest/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + WFSOThreadTest.c +) + +add_executable(paltest_waitforsingleobject_wfsothreadtest + ${SOURCES} +) + +add_dependencies(paltest_waitforsingleobject_wfsothreadtest coreclrpal) + +target_link_libraries(paltest_waitforsingleobject_wfsothreadtest + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOThreadTest/WFSOThreadTest.c b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOThreadTest/WFSOThreadTest.c new file mode 100644 index 0000000000..378350671a --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/WFSOThreadTest/WFSOThreadTest.c @@ -0,0 +1,179 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: WFSOThreadTest.c +** +** Purpose: Test for WaitForSingleObjectTest. +** Create One Thread and do some work +** Use WFSO For the Thread to finish +** +** Test Passes if the above operations are successful +** +** +** +**=========================================================*/ + + + +#include <palsuite.h> + + +//Declaring Variables +HANDLE hThread = NULL; +HANDLE hEvent = NULL; + +unsigned int globalcounter =0; + +//Declaring Function Prototypes +DWORD incrementCounter(LPVOID params); + +int __cdecl main(int argc, char **argv) +{ + + //Declare local variables + DWORD dwThreadId=0; + DWORD dwWaitResult=0; + + //Initialize PAL + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + + //Create Event + hEvent = CreateEvent(NULL,TRUE,FALSE, NULL); + if(hEvent == NULL) + { + Fail("Create Event Failed\n" + "GetLastError returned %d\n", GetLastError()); + } + + + //Create Thread + hThread = CreateThread( + NULL, + 0, + incrementCounter, + NULL, + 0, + &dwThreadId); + + if ( NULL == hThread ) + { + Fail ( "CreateThread() returned NULL. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + } + + + //Wait For Thread to signal start + dwWaitResult = WaitForSingleObject(hEvent,INFINITE); + + switch (dwWaitResult) + { + // The thread wait was successful + case WAIT_OBJECT_0: + { + + Trace ("Wait for Single Object (hEvent) was successful.\n"); + break; + } + + // Time-out. + case WAIT_TIMEOUT: + { + Fail ( "Time -out. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + return FALSE; + } + + // Got ownership of the abandoned event object. + case WAIT_ABANDONED: + { + Fail ( "Got ownership of the abandoned event object. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + return FALSE; + } + + } + + + //Wait for Thread to finish + dwWaitResult = WaitForSingleObject( + hThread, //handle to thread + 5000L); //Wait Indefinitely + + + switch (dwWaitResult) + { + // The thread wait was successful + case WAIT_OBJECT_0: + { + + Trace("Wait for thread was successful\n"); + + break; + } + + // Time-out. + case WAIT_TIMEOUT: + { + Fail ( "Time -out. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + return FALSE; + } + + // Got ownership of the abandoned thread object. + case WAIT_ABANDONED: + { + Fail ( "Got ownership of the abandoned thread object. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + return FALSE; + } + + } + + +//Close Handles +if (0==CloseHandle(hEvent)) + { + Trace("Could not Close event handle\n"); + Fail ( "GetLastError returned %d\n", GetLastError()); + } +if (0==CloseHandle(hThread)) + { + Trace("Could not Close thread handle\n"); + Fail ( "GetLastError returned %d\n", GetLastError()); + } + +PAL_Terminate(); +return ( PASS ); + +} + +DWORD incrementCounter(LPVOID params) +{ + + //Signal Event so that main thread can start to wait for thread object + if (0==SetEvent(hEvent)) + { + Fail ( "SetEvent returned Zero. Failing test.\n" + "GetLastError returned %d\n", GetLastError()); + } + + for (globalcounter=0;globalcounter<100000;globalcounter++); + + //Sleep(5000); + + Trace("Global Counter Value: %d \n", globalcounter); + return 0; +} + + + + + + diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/WaitForSingleObject/test1/CMakeLists.txt new file mode 100644 index 0000000000..0a71330ba1 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_waitforsingleobject_test1 + ${SOURCES} +) + +add_dependencies(paltest_waitforsingleobject_test1 coreclrpal) + +target_link_libraries(paltest_waitforsingleobject_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/test1/test1.c b/src/pal/tests/palsuite/threading/WaitForSingleObject/test1/test1.c new file mode 100644 index 0000000000..2af80df677 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/test1/test1.c @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Source: test1.c +** +** Purpose: Test for WaitForSingleObjectTest. Create two events, one +** with a TRUE and one with FALSE intial state. Ensure that WaitForSingle +** returns correct values for each of these. +** +** +**=========================================================*/ + +#include <palsuite.h> + +BOOL WaitForSingleObjectTest() +{ + + BOOL bRet = FALSE; + DWORD dwRet = 0; + + LPSECURITY_ATTRIBUTES lpEventAttributes = 0; + BOOL bManualReset = TRUE; + BOOL bInitialState = TRUE; + + HANDLE hEvent; + + /* Create an event, and ensure the HANDLE is valid */ + hEvent = CreateEvent(lpEventAttributes, bManualReset, + bInitialState, NULL); + + if (hEvent != INVALID_HANDLE_VALUE) + { + + /* Call WaitForSingleObject with 0 time on the event. It + should return WAIT_OBJECT_0 + */ + + dwRet = WaitForSingleObject(hEvent,0); + + if (dwRet != WAIT_OBJECT_0) + { + Trace("WaitForSingleObjectTest:WaitForSingleObject failed (%x)\n", GetLastError()); + } + else + { + bRet = CloseHandle(hEvent); + + if (!bRet) + { + Trace("WaitForSingleObjectTest:CloseHandle failed (%x)\n", GetLastError()); + } + } + } + else + { + Trace("WaitForSingleObjectTest:CreateEvent failed (%x)\n", GetLastError()); + } + + /* If the first section passed, Create another event, with the + intial state being FALSE this time. + */ + + if (bRet) + { + bRet = FALSE; + + bInitialState = FALSE; + + hEvent = CreateEvent( lpEventAttributes, + bManualReset, bInitialState, NULL); + + if (hEvent != INVALID_HANDLE_VALUE) + { + + /* Test WaitForSingleObject and ensure that it returns + WAIT_TIMEOUT in this case. + */ + + dwRet = WaitForSingleObject(hEvent,0); + + if (dwRet != WAIT_TIMEOUT) + { + Trace("WaitForSingleObjectTest:WaitForSingleObject failed (%x)\n", GetLastError()); + } + else + { + bRet = CloseHandle(hEvent); + + if (!bRet) + { + Trace("WaitForSingleObjectTest:CloseHandle failed (%x)\n", GetLastError()); + } + } + } + else + { + Trace("WaitForSingleObjectTest::CreateEvent failed (%x)\n", GetLastError()); + } + } + return bRet; +} + +int __cdecl main(int argc, char **argv) +{ + if(0 != (PAL_Initialize(argc, argv))) + { + return ( FAIL ); + } + + if(!WaitForSingleObjectTest()) + { + Fail ("Test failed\n"); + } + + PAL_Terminate(); + return ( PASS ); + +} diff --git a/src/pal/tests/palsuite/threading/WaitForSingleObject/test1/testinfo.dat b/src/pal/tests/palsuite/threading/WaitForSingleObject/test1/testinfo.dat new file mode 100644 index 0000000000..89193d1c20 --- /dev/null +++ b/src/pal/tests/palsuite/threading/WaitForSingleObject/test1/testinfo.dat @@ -0,0 +1,14 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = WaitForSingleObject +Name = Positive Test for WaitForSingleObject +TYPE = DEFAULT +EXE1 = test1 +Description += Test for WaitForSingleObject. Create two events, one += with a TRUE and one with FALSE intial state. Ensure that WaitForSingle += returns correct values for each of these. diff --git a/src/pal/tests/palsuite/threading/YieldProcessor/CMakeLists.txt b/src/pal/tests/palsuite/threading/YieldProcessor/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/YieldProcessor/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/threading/YieldProcessor/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/YieldProcessor/test1/CMakeLists.txt new file mode 100644 index 0000000000..5002bf763d --- /dev/null +++ b/src/pal/tests/palsuite/threading/YieldProcessor/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_yieldprocessor_test1 + ${SOURCES} +) + +add_dependencies(paltest_yieldprocessor_test1 coreclrpal) + +target_link_libraries(paltest_yieldprocessor_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/YieldProcessor/test1/test1.c b/src/pal/tests/palsuite/threading/YieldProcessor/test1/test1.c new file mode 100644 index 0000000000..6adbe989c2 --- /dev/null +++ b/src/pal/tests/palsuite/threading/YieldProcessor/test1/test1.c @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** Source: test1.c +** +** Purpose: Test to ensure YieldProcessor works, without +** causing test to hang +** +** Dependencies: PAL_Initialize +** Fail +** YieldProcessor +** WaitForMultipleObject +** CreateThread +** GetLastError +** + +** +**===========================================================================*/ + + +#include <palsuite.h> +#define THREAD_COUNT 10 +#define REPEAT_COUNT 1000 +#define TIMEOUT 60000 +void PALAPI Run_Thread(LPVOID lpParam); + +/** + * main + * + * executable entry point + */ +INT __cdecl main( INT argc, CHAR **argv ) +{ + DWORD dwParam; + HANDLE hThread[THREAD_COUNT]; + DWORD threadId[THREAD_COUNT]; + + int i = 0; + int returnCode = 0; + + /*PAL initialization */ + if( (PAL_Initialize(argc, argv)) != 0 ) + { + return FAIL; + } + + + for( i = 0; i < THREAD_COUNT; i++ ) + { + dwParam = (int) i; + //Create thread + hThread[i] = CreateThread( + NULL, /* no security attributes */ + 0, /* use default stack size */ + (LPTHREAD_START_ROUTINE)Run_Thread,/* thread function */ + (LPVOID)dwParam, /* argument to thread function */ + 0, /* use default creation flags */ + &threadId[i] /* returns the thread identifier*/ + ); + + if(hThread[i] == NULL) + { + Fail("Create Thread failed for iteration %d GetLastError value is %d\n", i, GetLastError()); + } + + } + + + returnCode = WaitForMultipleObjects(THREAD_COUNT, hThread, TRUE, TIMEOUT); + if( WAIT_OBJECT_0 != returnCode ) + { + Trace("Wait for Object(s) returned %d, expected value is %d, and GetLastError value is %d\n", returnCode, WAIT_OBJECT_0, GetLastError()); + } + + PAL_Terminate(); + return PASS; + +} + +void PALAPI Run_Thread (LPVOID lpParam) +{ + int i = 0; + + for(i=0; i < REPEAT_COUNT; i++ ) + { + // No error code set nor does it have any return code + YieldProcessor(); + } +} diff --git a/src/pal/tests/palsuite/threading/YieldProcessor/test1/testinfo.dat b/src/pal/tests/palsuite/threading/YieldProcessor/test1/testinfo.dat new file mode 100644 index 0000000000..6d12110c34 --- /dev/null +++ b/src/pal/tests/palsuite/threading/YieldProcessor/test1/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = YieldProcessor +Name = Test for YieldProcessor +TYPE = DEFAULT +EXE1 = test +Description += Purpose: Test to ensure YieldProcessor is += working properly on supported platforms diff --git a/src/pal/tests/palsuite/threading/releasesemaphore/CMakeLists.txt b/src/pal/tests/palsuite/threading/releasesemaphore/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/threading/releasesemaphore/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/threading/releasesemaphore/test1/CMakeLists.txt b/src/pal/tests/palsuite/threading/releasesemaphore/test1/CMakeLists.txt new file mode 100644 index 0000000000..e415f91fb2 --- /dev/null +++ b/src/pal/tests/palsuite/threading/releasesemaphore/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test.c +) + +add_executable(paltest_releasesemaphore_test1 + ${SOURCES} +) + +add_dependencies(paltest_releasesemaphore_test1 coreclrpal) + +target_link_libraries(paltest_releasesemaphore_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/threading/releasesemaphore/test1/test.c b/src/pal/tests/palsuite/threading/releasesemaphore/test1/test.c new file mode 100644 index 0000000000..4d736b7d9a --- /dev/null +++ b/src/pal/tests/palsuite/threading/releasesemaphore/test1/test.c @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================ +** +** Source: releasesemaphore/test1/createsemaphore.c +** +** Purpose: Check that ReleaseSemaphore fails when using a semaphore handle +** which has been closed by a call to CloseHandle. Check that +** ReleaseSemaphore fails when using a ReleaseCount of zero or less than +** zero. +** +** +**==========================================================================*/ + +#include <palsuite.h> + +HANDLE hSemaphore; + +int __cdecl main (int argc, char **argv) +{ + if(0 != (PAL_Initialize(argc, argv))) + { + return (FAIL); + } + hSemaphore = CreateSemaphoreA (NULL, 1, 2, NULL); + + if (NULL == hSemaphore) + { + Fail("PALSUITE ERROR: CreateSemaphoreA ('%p' '%ld' '%ld' " + "'%p') returned NULL.\nGetLastError returned %d.\n", + NULL, 1, 2, NULL, GetLastError()); + } + + if(ReleaseSemaphore(hSemaphore, 0, NULL)) + { + Fail("PALSUITE ERROR: ReleaseSemaphore('%p' '%ld' '%p') " + "call returned %d\nwhen it should have returned " + "%d.\nGetLastError returned %d.\n", + hSemaphore, 0, NULL, FALSE, TRUE, GetLastError()); + } + + if(ReleaseSemaphore(hSemaphore, -1, NULL)) + { + Fail("PALSUITE ERROR: ReleaseSemaphore('%p' '%ld' '%p') " + "call returned %d\nwhen it should have returned " + "%d.\nGetLastError returned %d.\n", + hSemaphore, -1, NULL, TRUE, FALSE, GetLastError()); + } + + if(!CloseHandle(hSemaphore)) + { + Fail("PALSUITE ERROR: CloseHandle(%p) call failed. GetLastError " + "returned %d.\n", hSemaphore, GetLastError()); + } + + if(ReleaseSemaphore(hSemaphore, 1, NULL)) + { + Fail("PALSUITE ERROR: ReleaseSemaphore('%p' '%ld' '%p') " + "call incremented semaphore %p count\nafter the handle " + "was closed by a call to CloseHandle.\n GetLastError returned " + "%d.\n", hSemaphore, -1, NULL, hSemaphore, GetLastError()); + } + + PAL_Terminate(); + return (PASS); +} diff --git a/src/pal/tests/palsuite/threading/releasesemaphore/test1/testinfo.dat b/src/pal/tests/palsuite/threading/releasesemaphore/test1/testinfo.dat new file mode 100644 index 0000000000..b4d647a592 --- /dev/null +++ b/src/pal/tests/palsuite/threading/releasesemaphore/test1/testinfo.dat @@ -0,0 +1,15 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = threading +Function = ReleaseSemaphore +Name = Positive Test for ReleaseSemaphore +TYPE = DEFAULT +EXE1 = test +Description += Check that ReleaseSemaphore fails when using a semaphore handle += which has been closed by a call to CloseHandle. Check that += ReleaseSemaphore fails when using a ReleaseCount of zero or less than += zero. |