2021 lines
68 KiB
C
2021 lines
68 KiB
C
/*
|
|
* Unit test suite for thread functions.
|
|
*
|
|
* Copyright 2002 Geoffrey Hausheer
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
/* Define _WIN32_WINNT to get SetThreadIdealProcessor on Windows */
|
|
#define _WIN32_WINNT 0x0600
|
|
|
|
#include <assert.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
|
|
/* the tests intentionally pass invalid pointers and need an exception handler */
|
|
#define WINE_NO_INLINE_STRING
|
|
|
|
#include <ntstatus.h>
|
|
#define WIN32_NO_STATUS
|
|
#include <windef.h>
|
|
#include <winbase.h>
|
|
#include <winnt.h>
|
|
#include <winerror.h>
|
|
#include <winnls.h>
|
|
#include <winternl.h>
|
|
#include "wine/test.h"
|
|
|
|
/* THREAD_ALL_ACCESS in Vista+ PSDKs is incompatible with older Windows versions */
|
|
#define THREAD_ALL_ACCESS_NT4 (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x3ff)
|
|
|
|
/* Specify the number of simultaneous threads to test */
|
|
#define NUM_THREADS 4
|
|
/* Specify whether to test the extended priorities for Win2k/XP */
|
|
#define USE_EXTENDED_PRIORITIES 0
|
|
/* Specify whether to test the stack allocation in CreateThread */
|
|
#define CHECK_STACK 0
|
|
|
|
/* Set CHECK_STACK to 1 if you want to try to test the stack-limit from
|
|
CreateThread. So far I have been unable to make this work, and
|
|
I am in doubt as to how portable it is. Also, according to MSDN,
|
|
you shouldn't mix C-run-time-libraries (i.e. alloca) with CreateThread.
|
|
Anyhow, the check is currently commented out
|
|
*/
|
|
#if CHECK_STACK
|
|
# ifdef __try
|
|
# define __TRY __try
|
|
# define __EXCEPT __except
|
|
# define __ENDTRY
|
|
# else
|
|
# include "wine/exception.h"
|
|
# endif
|
|
#endif
|
|
|
|
#ifdef __i386__
|
|
#define ARCH "x86"
|
|
#elif defined __x86_64__
|
|
#define ARCH "amd64"
|
|
#elif defined __arm__
|
|
#define ARCH "arm"
|
|
#elif defined __aarch64__
|
|
#define ARCH "arm64"
|
|
#else
|
|
#define ARCH "none"
|
|
#endif
|
|
|
|
static BOOL (WINAPI *pGetThreadPriorityBoost)(HANDLE,PBOOL);
|
|
static HANDLE (WINAPI *pOpenThread)(DWORD,BOOL,DWORD);
|
|
static BOOL (WINAPI *pQueueUserWorkItem)(LPTHREAD_START_ROUTINE,PVOID,ULONG);
|
|
static DWORD (WINAPI *pSetThreadIdealProcessor)(HANDLE,DWORD);
|
|
static BOOL (WINAPI *pSetThreadPriorityBoost)(HANDLE,BOOL);
|
|
static BOOL (WINAPI *pRegisterWaitForSingleObject)(PHANDLE,HANDLE,WAITORTIMERCALLBACK,PVOID,ULONG,ULONG);
|
|
static BOOL (WINAPI *pUnregisterWait)(HANDLE);
|
|
static BOOL (WINAPI *pIsWow64Process)(HANDLE,PBOOL);
|
|
static BOOL (WINAPI *pSetThreadErrorMode)(DWORD,PDWORD);
|
|
static DWORD (WINAPI *pGetThreadErrorMode)(void);
|
|
static DWORD (WINAPI *pRtlGetThreadErrorMode)(void);
|
|
static BOOL (WINAPI *pActivateActCtx)(HANDLE,ULONG_PTR*);
|
|
static HANDLE (WINAPI *pCreateActCtxW)(PCACTCTXW);
|
|
static BOOL (WINAPI *pDeactivateActCtx)(DWORD,ULONG_PTR);
|
|
static BOOL (WINAPI *pGetCurrentActCtx)(HANDLE *);
|
|
static void (WINAPI *pReleaseActCtx)(HANDLE);
|
|
static PTP_POOL (WINAPI *pCreateThreadpool)(PVOID);
|
|
static void (WINAPI *pCloseThreadpool)(PTP_POOL);
|
|
static PTP_WORK (WINAPI *pCreateThreadpoolWork)(PTP_WORK_CALLBACK,PVOID,PTP_CALLBACK_ENVIRON);
|
|
static void (WINAPI *pSubmitThreadpoolWork)(PTP_WORK);
|
|
static void (WINAPI *pWaitForThreadpoolWorkCallbacks)(PTP_WORK,BOOL);
|
|
static void (WINAPI *pCloseThreadpoolWork)(PTP_WORK);
|
|
static NTSTATUS (WINAPI *pNtQueryInformationThread)(HANDLE,THREADINFOCLASS,PVOID,ULONG,PULONG);
|
|
static BOOL (WINAPI *pGetThreadGroupAffinity)(HANDLE,GROUP_AFFINITY*);
|
|
static BOOL (WINAPI *pSetThreadGroupAffinity)(HANDLE,const GROUP_AFFINITY*,GROUP_AFFINITY*);
|
|
static NTSTATUS (WINAPI *pNtSetInformationThread)(HANDLE,THREADINFOCLASS,LPCVOID,ULONG);
|
|
|
|
static HANDLE create_target_process(const char *arg)
|
|
{
|
|
char **argv;
|
|
char cmdline[MAX_PATH];
|
|
PROCESS_INFORMATION pi;
|
|
BOOL ret;
|
|
STARTUPINFOA si = { 0 };
|
|
si.cb = sizeof(si);
|
|
|
|
winetest_get_mainargs( &argv );
|
|
sprintf(cmdline, "%s %s %s", argv[0], argv[1], arg);
|
|
ret = CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
|
|
ok(ret, "error: %u\n", GetLastError());
|
|
ret = CloseHandle(pi.hThread);
|
|
ok(ret, "error %u\n", GetLastError());
|
|
return pi.hProcess;
|
|
}
|
|
|
|
/* Functions not tested yet:
|
|
AttachThreadInput
|
|
SetThreadContext
|
|
SwitchToThread
|
|
|
|
In addition there are no checks that the inheritance works properly in
|
|
CreateThread
|
|
*/
|
|
|
|
/* Functions to ensure that from a group of threads, only one executes
|
|
certain chunks of code at a time, and we know which one is executing
|
|
it. It basically makes multithreaded execution linear, which defeats
|
|
the purpose of multiple threads, but makes testing easy. */
|
|
static HANDLE start_event, stop_event;
|
|
static LONG num_synced;
|
|
|
|
static void init_thread_sync_helpers(void)
|
|
{
|
|
start_event = CreateEventW(NULL, TRUE, FALSE, NULL);
|
|
ok(start_event != NULL, "CreateEvent failed\n");
|
|
stop_event = CreateEventW(NULL, TRUE, FALSE, NULL);
|
|
ok(stop_event != NULL, "CreateEvent failed\n");
|
|
num_synced = -1;
|
|
}
|
|
|
|
static BOOL sync_threads_and_run_one(DWORD sync_id, DWORD my_id)
|
|
{
|
|
LONG num = InterlockedIncrement(&num_synced);
|
|
assert(-1 <= num && num <= 1);
|
|
if (num == 1)
|
|
{
|
|
ResetEvent( stop_event );
|
|
SetEvent( start_event );
|
|
}
|
|
else
|
|
{
|
|
DWORD ret = WaitForSingleObject(start_event, 10000);
|
|
ok(ret == WAIT_OBJECT_0, "WaitForSingleObject failed %x\n",ret);
|
|
}
|
|
return sync_id == my_id;
|
|
}
|
|
|
|
static void resync_after_run(void)
|
|
{
|
|
LONG num = InterlockedDecrement(&num_synced);
|
|
assert(-1 <= num && num <= 1);
|
|
if (num == -1)
|
|
{
|
|
ResetEvent( start_event );
|
|
SetEvent( stop_event );
|
|
}
|
|
else
|
|
{
|
|
DWORD ret = WaitForSingleObject(stop_event, 10000);
|
|
ok(ret == WAIT_OBJECT_0, "WaitForSingleObject failed\n");
|
|
}
|
|
}
|
|
|
|
static void cleanup_thread_sync_helpers(void)
|
|
{
|
|
CloseHandle(start_event);
|
|
CloseHandle(stop_event);
|
|
}
|
|
|
|
static DWORD tlsIndex;
|
|
|
|
typedef struct {
|
|
int threadnum;
|
|
HANDLE *event;
|
|
DWORD *threadmem;
|
|
} t1Struct;
|
|
|
|
/* WinME supports OpenThread but doesn't know about access restrictions so
|
|
we require them to be either completely ignored or always obeyed.
|
|
*/
|
|
static INT obeying_ars = 0; /* -1 == no, 0 == dunno yet, 1 == yes */
|
|
#define obey_ar(x) \
|
|
(obeying_ars == 0 \
|
|
? ((x) \
|
|
? (obeying_ars = +1) \
|
|
: ((obeying_ars = -1), \
|
|
trace("not restricted, assuming consistent behaviour\n"))) \
|
|
: (obeying_ars < 0) \
|
|
? ok(!(x), "access restrictions obeyed\n") \
|
|
: ok( (x), "access restrictions not obeyed\n"))
|
|
|
|
/* Basic test that simultaneous threads can access shared memory,
|
|
that the thread local storage routines work correctly, and that
|
|
threads actually run concurrently
|
|
*/
|
|
static DWORD WINAPI threadFunc1(LPVOID p)
|
|
{
|
|
t1Struct *tstruct = p;
|
|
int i;
|
|
/* write our thread # into shared memory */
|
|
tstruct->threadmem[tstruct->threadnum]=GetCurrentThreadId();
|
|
ok(TlsSetValue(tlsIndex,(LPVOID)(INT_PTR)(tstruct->threadnum+1))!=0,
|
|
"TlsSetValue failed\n");
|
|
/* The threads synchronize before terminating. This is done by
|
|
Signaling an event, and waiting for all events to occur
|
|
*/
|
|
SetEvent(tstruct->event[tstruct->threadnum]);
|
|
WaitForMultipleObjects(NUM_THREADS,tstruct->event,TRUE,INFINITE);
|
|
/* Double check that all threads really did run by validating that
|
|
they have all written to the shared memory. There should be no race
|
|
here, since all threads were synchronized after the write.*/
|
|
for (i = 0; i < NUM_THREADS; i++)
|
|
ok(tstruct->threadmem[i] != 0, "expected threadmem[%d] != 0\n", i);
|
|
|
|
/* lstrlenA contains an exception handler so this makes sure exceptions work in threads */
|
|
ok( lstrlenA( (char *)0xdeadbeef ) == 0, "lstrlenA: unexpected success\n" );
|
|
|
|
/* Check that no one changed our tls memory */
|
|
ok((INT_PTR)TlsGetValue(tlsIndex)-1==tstruct->threadnum,
|
|
"TlsGetValue failed\n");
|
|
return NUM_THREADS+tstruct->threadnum;
|
|
}
|
|
|
|
static DWORD WINAPI threadFunc2(LPVOID p)
|
|
{
|
|
return 99;
|
|
}
|
|
|
|
static DWORD WINAPI threadFunc3(LPVOID p)
|
|
{
|
|
HANDLE thread;
|
|
thread=GetCurrentThread();
|
|
SuspendThread(thread);
|
|
return 99;
|
|
}
|
|
|
|
static DWORD WINAPI threadFunc4(LPVOID p)
|
|
{
|
|
HANDLE event = p;
|
|
if(event != NULL) {
|
|
SetEvent(event);
|
|
}
|
|
Sleep(99000);
|
|
return 0;
|
|
}
|
|
|
|
#if CHECK_STACK
|
|
static DWORD WINAPI threadFunc5(LPVOID p)
|
|
{
|
|
DWORD *exitCode = p;
|
|
SYSTEM_INFO sysInfo;
|
|
sysInfo.dwPageSize=0;
|
|
GetSystemInfo(&sysInfo);
|
|
*exitCode=0;
|
|
__TRY
|
|
{
|
|
alloca(2*sysInfo.dwPageSize);
|
|
}
|
|
__EXCEPT(1) {
|
|
*exitCode=1;
|
|
}
|
|
__ENDTRY
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static DWORD WINAPI threadFunc_SetEvent(LPVOID p)
|
|
{
|
|
SetEvent(p);
|
|
return 0;
|
|
}
|
|
|
|
static DWORD WINAPI threadFunc_CloseHandle(LPVOID p)
|
|
{
|
|
CloseHandle(p);
|
|
return 0;
|
|
}
|
|
|
|
struct thread_actctx_param
|
|
{
|
|
HANDLE thread_context;
|
|
HANDLE handle;
|
|
};
|
|
|
|
static DWORD WINAPI thread_actctx_func(void *p)
|
|
{
|
|
struct thread_actctx_param *param = (struct thread_actctx_param*)p;
|
|
HANDLE cur;
|
|
BOOL ret;
|
|
|
|
cur = (void*)0xdeadbeef;
|
|
ret = pGetCurrentActCtx(&cur);
|
|
ok(ret, "thread GetCurrentActCtx failed, %u\n", GetLastError());
|
|
ok(cur == param->handle, "got %p, expected %p\n", cur, param->handle);
|
|
param->thread_context = cur;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void create_function_addr_events(HANDLE events[2])
|
|
{
|
|
char buffer[256];
|
|
|
|
sprintf(buffer, "threadFunc_SetEvent %p", threadFunc_SetEvent);
|
|
events[0] = CreateEventA(NULL, FALSE, FALSE, buffer);
|
|
|
|
sprintf(buffer, "threadFunc_CloseHandle %p", threadFunc_CloseHandle);
|
|
events[1] = CreateEventA(NULL, FALSE, FALSE, buffer);
|
|
}
|
|
|
|
/* check CreateRemoteThread */
|
|
static VOID test_CreateRemoteThread(void)
|
|
{
|
|
HANDLE hProcess, hThread, hEvent, hRemoteEvent;
|
|
DWORD tid, ret, exitcode;
|
|
HANDLE hAddrEvents[2];
|
|
|
|
hProcess = create_target_process("sleep");
|
|
ok(hProcess != NULL, "Can't start process\n");
|
|
|
|
/* ensure threadFunc_SetEvent & threadFunc_CloseHandle are the same
|
|
* address as in the child process */
|
|
create_function_addr_events(hAddrEvents);
|
|
ret = WaitForMultipleObjects(2, hAddrEvents, TRUE, 5000);
|
|
if (ret == WAIT_TIMEOUT)
|
|
{
|
|
skip("child process wasn't mapped at same address, so can't do CreateRemoteThread tests.\n");
|
|
return;
|
|
}
|
|
ok(ret == WAIT_OBJECT_0 || broken(ret == WAIT_OBJECT_0+1 /* nt4,w2k */), "WaitForAllObjects 2 events %d\n", ret);
|
|
|
|
hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
|
|
ok(hEvent != NULL, "Can't create event, err=%u\n", GetLastError());
|
|
ret = DuplicateHandle(GetCurrentProcess(), hEvent, hProcess, &hRemoteEvent,
|
|
0, FALSE, DUPLICATE_SAME_ACCESS);
|
|
ok(ret != 0, "DuplicateHandle failed, err=%u\n", GetLastError());
|
|
|
|
/* create suspended remote thread with entry point SetEvent() */
|
|
SetLastError(0xdeadbeef);
|
|
hThread = CreateRemoteThread(hProcess, NULL, 0, threadFunc_SetEvent,
|
|
hRemoteEvent, CREATE_SUSPENDED, &tid);
|
|
if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED)
|
|
{
|
|
win_skip("CreateRemoteThread is not implemented\n");
|
|
goto cleanup;
|
|
}
|
|
ok(hThread != NULL, "CreateRemoteThread failed, err=%u\n", GetLastError());
|
|
ok(tid != 0, "null tid\n");
|
|
ret = SuspendThread(hThread);
|
|
ok(ret == 1, "ret=%u, err=%u\n", ret, GetLastError());
|
|
ret = ResumeThread(hThread);
|
|
ok(ret == 2, "ret=%u, err=%u\n", ret, GetLastError());
|
|
|
|
/* thread still suspended, so wait times out */
|
|
ret = WaitForSingleObject(hEvent, 1000);
|
|
ok(ret == WAIT_TIMEOUT, "wait did not time out, ret=%u\n", ret);
|
|
|
|
ret = ResumeThread(hThread);
|
|
ok(ret == 1, "ret=%u, err=%u\n", ret, GetLastError());
|
|
|
|
/* wait that doesn't time out */
|
|
ret = WaitForSingleObject(hEvent, 1000);
|
|
ok(ret == WAIT_OBJECT_0, "object not signaled, ret=%u\n", ret);
|
|
|
|
/* wait for thread end */
|
|
ret = WaitForSingleObject(hThread, 1000);
|
|
ok(ret == WAIT_OBJECT_0, "waiting for thread failed, ret=%u\n", ret);
|
|
CloseHandle(hThread);
|
|
|
|
/* create and wait for remote thread with entry point CloseHandle() */
|
|
hThread = CreateRemoteThread(hProcess, NULL, 0,
|
|
threadFunc_CloseHandle,
|
|
hRemoteEvent, 0, &tid);
|
|
ok(hThread != NULL, "CreateRemoteThread failed, err=%u\n", GetLastError());
|
|
ret = WaitForSingleObject(hThread, 1000);
|
|
ok(ret == WAIT_OBJECT_0, "waiting for thread failed, ret=%u\n", ret);
|
|
CloseHandle(hThread);
|
|
|
|
/* create remote thread with entry point SetEvent() */
|
|
hThread = CreateRemoteThread(hProcess, NULL, 0,
|
|
threadFunc_SetEvent,
|
|
hRemoteEvent, 0, &tid);
|
|
ok(hThread != NULL, "CreateRemoteThread failed, err=%u\n", GetLastError());
|
|
|
|
/* closed handle, so wait times out */
|
|
ret = WaitForSingleObject(hEvent, 1000);
|
|
ok(ret == WAIT_TIMEOUT, "wait did not time out, ret=%u\n", ret);
|
|
|
|
/* check that remote SetEvent() failed */
|
|
ret = GetExitCodeThread(hThread, &exitcode);
|
|
ok(ret != 0, "GetExitCodeThread failed, err=%u\n", GetLastError());
|
|
if (ret) ok(exitcode == 0, "SetEvent succeeded, expected to fail\n");
|
|
CloseHandle(hThread);
|
|
|
|
cleanup:
|
|
TerminateProcess(hProcess, 0);
|
|
CloseHandle(hEvent);
|
|
CloseHandle(hProcess);
|
|
}
|
|
|
|
/* Check basic functionality of CreateThread and Tls* functions */
|
|
static VOID test_CreateThread_basic(void)
|
|
{
|
|
HANDLE thread[NUM_THREADS],event[NUM_THREADS];
|
|
DWORD threadid[NUM_THREADS],curthreadId;
|
|
DWORD threadmem[NUM_THREADS];
|
|
DWORD exitCode;
|
|
t1Struct tstruct[NUM_THREADS];
|
|
int error;
|
|
DWORD i,j;
|
|
DWORD GLE, ret;
|
|
DWORD tid;
|
|
BOOL bRet;
|
|
|
|
/* lstrlenA contains an exception handler so this makes sure exceptions work in the main thread */
|
|
ok( lstrlenA( (char *)0xdeadbeef ) == 0, "lstrlenA: unexpected success\n" );
|
|
|
|
/* Retrieve current Thread ID for later comparisons */
|
|
curthreadId=GetCurrentThreadId();
|
|
/* Allocate some local storage */
|
|
ok((tlsIndex=TlsAlloc())!=TLS_OUT_OF_INDEXES,"TlsAlloc failed\n");
|
|
/* Create events for thread synchronization */
|
|
for(i=0;i<NUM_THREADS;i++) {
|
|
threadmem[i]=0;
|
|
/* Note that it doesn't matter what type of event we choose here. This
|
|
test isn't trying to thoroughly test events
|
|
*/
|
|
event[i]=CreateEventA(NULL,TRUE,FALSE,NULL);
|
|
tstruct[i].threadnum=i;
|
|
tstruct[i].threadmem=threadmem;
|
|
tstruct[i].event=event;
|
|
}
|
|
|
|
/* Test that passing arguments to threads works okay */
|
|
for(i=0;i<NUM_THREADS;i++) {
|
|
thread[i] = CreateThread(NULL,0,threadFunc1,
|
|
&tstruct[i],0,&threadid[i]);
|
|
ok(thread[i]!=NULL,"Create Thread failed\n");
|
|
}
|
|
/* Test that the threads actually complete */
|
|
for(i=0;i<NUM_THREADS;i++) {
|
|
error=WaitForSingleObject(thread[i],5000);
|
|
ok(error==WAIT_OBJECT_0, "Thread did not complete within timelimit\n");
|
|
if(error!=WAIT_OBJECT_0) {
|
|
TerminateThread(thread[i],i+NUM_THREADS);
|
|
}
|
|
ok(GetExitCodeThread(thread[i],&exitCode),"Could not retrieve ext code\n");
|
|
ok(exitCode==i+NUM_THREADS,"Thread returned an incorrect exit code\n");
|
|
}
|
|
/* Test that each thread executed in its parent's address space
|
|
(it was able to change threadmem and pass that change back to its parent)
|
|
and that each thread id was independent). Note that we prove that the
|
|
threads actually execute concurrently by having them block on each other
|
|
in threadFunc1
|
|
*/
|
|
for(i=0;i<NUM_THREADS;i++) {
|
|
error=0;
|
|
for(j=i+1;j<NUM_THREADS;j++) {
|
|
if (threadmem[i]==threadmem[j]) {
|
|
error=1;
|
|
}
|
|
}
|
|
ok(!error && threadmem[i]==threadid[i] && threadmem[i]!=curthreadId,
|
|
"Thread did not execute successfully\n");
|
|
ok(CloseHandle(thread[i])!=0,"CloseHandle failed\n");
|
|
}
|
|
|
|
SetLastError(0xCAFEF00D);
|
|
bRet = TlsFree(tlsIndex);
|
|
ok(bRet, "TlsFree failed: %08x\n", GetLastError());
|
|
ok(GetLastError()==0xCAFEF00D,
|
|
"GetLastError: expected 0xCAFEF00D, got %08x\n", GetLastError());
|
|
|
|
/* Test freeing an already freed TLS index */
|
|
SetLastError(0xCAFEF00D);
|
|
ok(TlsFree(tlsIndex)==0,"TlsFree succeeded\n");
|
|
ok(GetLastError()==ERROR_INVALID_PARAMETER,
|
|
"GetLastError: expected ERROR_INVALID_PARAMETER, got %08x\n", GetLastError());
|
|
|
|
/* Test how passing NULL as a pointer to threadid works */
|
|
SetLastError(0xFACEaBAD);
|
|
thread[0] = CreateThread(NULL,0,threadFunc2,NULL,0,&tid);
|
|
GLE = GetLastError();
|
|
if (thread[0]) { /* NT */
|
|
ok(GLE==0xFACEaBAD, "CreateThread set last error to %d, expected 4207848365\n", GLE);
|
|
ret = WaitForSingleObject(thread[0],100);
|
|
ok(ret==WAIT_OBJECT_0, "threadFunc2 did not exit during 100 ms\n");
|
|
ret = GetExitCodeThread(thread[0],&exitCode);
|
|
ok(ret!=0, "GetExitCodeThread returned %d (expected nonzero)\n", ret);
|
|
ok(exitCode==99, "threadFunc2 exited with code: %d (expected 99)\n", exitCode);
|
|
ok(CloseHandle(thread[0])!=0,"Error closing thread handle\n");
|
|
}
|
|
else { /* 9x */
|
|
ok(GLE==ERROR_INVALID_PARAMETER, "CreateThread set last error to %d, expected 87\n", GLE);
|
|
}
|
|
}
|
|
|
|
/* Check that using the CREATE_SUSPENDED flag works */
|
|
static VOID test_CreateThread_suspended(void)
|
|
{
|
|
HANDLE thread;
|
|
DWORD threadId;
|
|
DWORD suspend_count;
|
|
int error;
|
|
|
|
thread = CreateThread(NULL,0,threadFunc2,NULL,
|
|
CREATE_SUSPENDED,&threadId);
|
|
ok(thread!=NULL,"Create Thread failed\n");
|
|
/* Check that the thread is suspended */
|
|
ok(SuspendThread(thread)==1,"Thread did not start suspended\n");
|
|
ok(ResumeThread(thread)==2,"Resume thread returned an invalid value\n");
|
|
/* Check that resume thread didn't actually start the thread. I can't think
|
|
of a better way of checking this than just waiting. I am not sure if this
|
|
will work on slow computers.
|
|
*/
|
|
ok(WaitForSingleObject(thread,1000)==WAIT_TIMEOUT,
|
|
"ResumeThread should not have actually started the thread\n");
|
|
/* Now actually resume the thread and make sure that it actually completes*/
|
|
ok(ResumeThread(thread)==1,"Resume thread returned an invalid value\n");
|
|
ok((error=WaitForSingleObject(thread,1000))==WAIT_OBJECT_0,
|
|
"Thread did not resume\n");
|
|
if(error!=WAIT_OBJECT_0) {
|
|
TerminateThread(thread,1);
|
|
}
|
|
|
|
suspend_count = SuspendThread(thread);
|
|
ok(suspend_count == -1, "SuspendThread returned %d, expected -1\n", suspend_count);
|
|
|
|
suspend_count = ResumeThread(thread);
|
|
ok(suspend_count == 0 ||
|
|
broken(suspend_count == -1), /* win9x */
|
|
"ResumeThread returned %d, expected 0\n", suspend_count);
|
|
|
|
ok(CloseHandle(thread)!=0,"CloseHandle failed\n");
|
|
}
|
|
|
|
/* Check that SuspendThread and ResumeThread work */
|
|
static VOID test_SuspendThread(void)
|
|
{
|
|
HANDLE thread,access_thread;
|
|
DWORD threadId,exitCode,error;
|
|
int i;
|
|
|
|
thread = CreateThread(NULL,0,threadFunc3,NULL,
|
|
0,&threadId);
|
|
ok(thread!=NULL,"Create Thread failed\n");
|
|
/* Check that the thread is suspended */
|
|
/* Note that this is a polling method, and there is a race between
|
|
SuspendThread being called (in the child, and the loop below timing out,
|
|
so the test could fail on a heavily loaded or slow computer.
|
|
*/
|
|
error=0;
|
|
for(i=0;error==0 && i<100;i++) {
|
|
error=SuspendThread(thread);
|
|
ResumeThread(thread);
|
|
if(error==0) {
|
|
Sleep(50);
|
|
i++;
|
|
}
|
|
}
|
|
ok(error==1,"SuspendThread did not work\n");
|
|
/* check that access restrictions are obeyed */
|
|
if (pOpenThread) {
|
|
access_thread=pOpenThread(THREAD_ALL_ACCESS_NT4 & (~THREAD_SUSPEND_RESUME),
|
|
0,threadId);
|
|
ok(access_thread!=NULL,"OpenThread returned an invalid handle\n");
|
|
if (access_thread!=NULL) {
|
|
obey_ar(SuspendThread(access_thread)==~0U);
|
|
obey_ar(ResumeThread(access_thread)==~0U);
|
|
ok(CloseHandle(access_thread)!=0,"CloseHandle Failed\n");
|
|
}
|
|
}
|
|
/* Double check that the thread really is suspended */
|
|
ok((error=GetExitCodeThread(thread,&exitCode))!=0 && exitCode==STILL_ACTIVE,
|
|
"Thread did not really suspend\n");
|
|
/* Resume the thread, and make sure it actually completes */
|
|
ok(ResumeThread(thread)==1,"Resume thread returned an invalid value\n");
|
|
ok((error=WaitForSingleObject(thread,1000))==WAIT_OBJECT_0,
|
|
"Thread did not resume\n");
|
|
if(error!=WAIT_OBJECT_0) {
|
|
TerminateThread(thread,1);
|
|
}
|
|
/* Trying to suspend a terminated thread should fail */
|
|
error=SuspendThread(thread);
|
|
ok(error==~0U, "wrong return code: %d\n", error);
|
|
ok(GetLastError()==ERROR_ACCESS_DENIED || GetLastError()==ERROR_NO_MORE_ITEMS, "unexpected error code: %d\n", GetLastError());
|
|
|
|
ok(CloseHandle(thread)!=0,"CloseHandle Failed\n");
|
|
}
|
|
|
|
/* Check that TerminateThread works properly
|
|
*/
|
|
static VOID test_TerminateThread(void)
|
|
{
|
|
HANDLE thread,access_thread,event;
|
|
DWORD threadId,exitCode;
|
|
event=CreateEventA(NULL,TRUE,FALSE,NULL);
|
|
thread = CreateThread(NULL,0,threadFunc4,event,0,&threadId);
|
|
ok(thread!=NULL,"Create Thread failed\n");
|
|
/* TerminateThread has a race condition in Wine. If the thread is terminated
|
|
before it starts, it leaves a process behind. Therefore, we wait for the
|
|
thread to signal that it has started. There is no easy way to force the
|
|
race to occur, so we don't try to find it.
|
|
*/
|
|
ok(WaitForSingleObject(event,5000)==WAIT_OBJECT_0,
|
|
"TerminateThread didn't work\n");
|
|
/* check that access restrictions are obeyed */
|
|
if (pOpenThread) {
|
|
access_thread=pOpenThread(THREAD_ALL_ACCESS_NT4 & (~THREAD_TERMINATE),
|
|
0,threadId);
|
|
ok(access_thread!=NULL,"OpenThread returned an invalid handle\n");
|
|
if (access_thread!=NULL) {
|
|
obey_ar(TerminateThread(access_thread,99)==0);
|
|
ok(CloseHandle(access_thread)!=0,"CloseHandle Failed\n");
|
|
}
|
|
}
|
|
/* terminate a job and make sure it terminates */
|
|
ok(TerminateThread(thread,99)!=0,"TerminateThread failed\n");
|
|
ok(WaitForSingleObject(thread,5000)==WAIT_OBJECT_0,
|
|
"TerminateThread didn't work\n");
|
|
ok(GetExitCodeThread(thread,&exitCode)!=STILL_ACTIVE,
|
|
"TerminateThread should not leave the thread 'STILL_ACTIVE'\n");
|
|
ok(exitCode==99, "TerminateThread returned invalid exit code\n");
|
|
ok(CloseHandle(thread)!=0,"Error Closing thread handle\n");
|
|
}
|
|
|
|
/* Check if CreateThread obeys the specified stack size. This code does
|
|
not work properly, and is currently disabled
|
|
*/
|
|
static VOID test_CreateThread_stack(void)
|
|
{
|
|
#if CHECK_STACK
|
|
/* The only way I know of to test the stack size is to use alloca
|
|
and __try/__except. However, this is probably not portable,
|
|
and I couldn't get it to work under Wine anyhow. However, here
|
|
is the code which should allow for testing that CreateThread
|
|
respects the stack-size limit
|
|
*/
|
|
HANDLE thread;
|
|
DWORD threadId,exitCode;
|
|
|
|
SYSTEM_INFO sysInfo;
|
|
sysInfo.dwPageSize=0;
|
|
GetSystemInfo(&sysInfo);
|
|
ok(sysInfo.dwPageSize>0,"GetSystemInfo should return a valid page size\n");
|
|
thread = CreateThread(NULL,sysInfo.dwPageSize,
|
|
threadFunc5,&exitCode,
|
|
0,&threadId);
|
|
ok(WaitForSingleObject(thread,5000)==WAIT_OBJECT_0,
|
|
"TerminateThread didn't work\n");
|
|
ok(exitCode==1,"CreateThread did not obey stack-size-limit\n");
|
|
ok(CloseHandle(thread)!=0,"CloseHandle failed\n");
|
|
#endif
|
|
}
|
|
|
|
/* Check whether setting/retrieving thread priorities works */
|
|
static VOID test_thread_priority(void)
|
|
{
|
|
HANDLE curthread,access_thread;
|
|
DWORD curthreadId,exitCode;
|
|
int min_priority=-2,max_priority=2;
|
|
BOOL disabled,rc;
|
|
int i;
|
|
|
|
curthread=GetCurrentThread();
|
|
curthreadId=GetCurrentThreadId();
|
|
/* Check thread priority */
|
|
/* NOTE: on Win2k/XP priority can be from -7 to 6. All other platforms it
|
|
is -2 to 2. However, even on a real Win2k system, using thread
|
|
priorities beyond the -2 to 2 range does not work. If you want to try
|
|
anyway, enable USE_EXTENDED_PRIORITIES
|
|
*/
|
|
ok(GetThreadPriority(curthread)==THREAD_PRIORITY_NORMAL,
|
|
"GetThreadPriority Failed\n");
|
|
|
|
if (pOpenThread) {
|
|
/* check that access control is obeyed */
|
|
access_thread=pOpenThread(THREAD_ALL_ACCESS_NT4 &
|
|
(~THREAD_QUERY_INFORMATION) & (~THREAD_SET_INFORMATION),
|
|
0,curthreadId);
|
|
ok(access_thread!=NULL,"OpenThread returned an invalid handle\n");
|
|
if (access_thread!=NULL) {
|
|
obey_ar(SetThreadPriority(access_thread,1)==0);
|
|
obey_ar(GetThreadPriority(access_thread)==THREAD_PRIORITY_ERROR_RETURN);
|
|
obey_ar(GetExitCodeThread(access_thread,&exitCode)==0);
|
|
ok(CloseHandle(access_thread),"Error Closing thread handle\n");
|
|
}
|
|
}
|
|
#if USE_EXTENDED_PRIORITIES
|
|
min_priority=-7; max_priority=6;
|
|
#endif
|
|
for(i=min_priority;i<=max_priority;i++) {
|
|
ok(SetThreadPriority(curthread,i)!=0,
|
|
"SetThreadPriority Failed for priority: %d\n",i);
|
|
ok(GetThreadPriority(curthread)==i,
|
|
"GetThreadPriority Failed for priority: %d\n",i);
|
|
}
|
|
ok(SetThreadPriority(curthread,THREAD_PRIORITY_TIME_CRITICAL)!=0,
|
|
"SetThreadPriority Failed\n");
|
|
ok(GetThreadPriority(curthread)==THREAD_PRIORITY_TIME_CRITICAL,
|
|
"GetThreadPriority Failed\n");
|
|
ok(SetThreadPriority(curthread,THREAD_PRIORITY_IDLE)!=0,
|
|
"SetThreadPriority Failed\n");
|
|
ok(GetThreadPriority(curthread)==THREAD_PRIORITY_IDLE,
|
|
"GetThreadPriority Failed\n");
|
|
ok(SetThreadPriority(curthread,0)!=0,"SetThreadPriority Failed\n");
|
|
|
|
/* Check that the thread priority is not changed if SetThreadPriority
|
|
is called with a value outside of the max/min range */
|
|
SetThreadPriority(curthread,min_priority);
|
|
SetLastError(0xdeadbeef);
|
|
rc = SetThreadPriority(curthread,min_priority-1);
|
|
|
|
ok(rc == FALSE, "SetThreadPriority passed with a bad argument\n");
|
|
ok(GetLastError() == ERROR_INVALID_PARAMETER ||
|
|
GetLastError() == ERROR_INVALID_PRIORITY /* Win9x */,
|
|
"SetThreadPriority error %d, expected ERROR_INVALID_PARAMETER or ERROR_INVALID_PRIORITY\n",
|
|
GetLastError());
|
|
ok(GetThreadPriority(curthread)==min_priority,
|
|
"GetThreadPriority didn't return min_priority\n");
|
|
|
|
SetThreadPriority(curthread,max_priority);
|
|
SetLastError(0xdeadbeef);
|
|
rc = SetThreadPriority(curthread,max_priority+1);
|
|
|
|
ok(rc == FALSE, "SetThreadPriority passed with a bad argument\n");
|
|
ok(GetLastError() == ERROR_INVALID_PARAMETER ||
|
|
GetLastError() == ERROR_INVALID_PRIORITY /* Win9x */,
|
|
"SetThreadPriority error %d, expected ERROR_INVALID_PARAMETER or ERROR_INVALID_PRIORITY\n",
|
|
GetLastError());
|
|
ok(GetThreadPriority(curthread)==max_priority,
|
|
"GetThreadPriority didn't return max_priority\n");
|
|
|
|
/* Check thread priority boost */
|
|
if (!pGetThreadPriorityBoost || !pSetThreadPriorityBoost)
|
|
return; /* Win9x */
|
|
|
|
SetLastError(0xdeadbeef);
|
|
rc=pGetThreadPriorityBoost(curthread,&disabled);
|
|
if (rc==0 && GetLastError()==ERROR_CALL_NOT_IMPLEMENTED)
|
|
{
|
|
win_skip("GetThreadPriorityBoost is not implemented on WinME\n");
|
|
return;
|
|
}
|
|
|
|
ok(rc!=0,"error=%d\n",GetLastError());
|
|
|
|
if (pOpenThread) {
|
|
/* check that access control is obeyed */
|
|
access_thread=pOpenThread(THREAD_ALL_ACCESS_NT4 &
|
|
(~THREAD_QUERY_INFORMATION) & (~THREAD_SET_INFORMATION),
|
|
0,curthreadId);
|
|
ok(access_thread!=NULL,"OpenThread returned an invalid handle\n");
|
|
if (access_thread!=NULL) {
|
|
todo_wine obey_ar(pSetThreadPriorityBoost(access_thread,1)==0);
|
|
todo_wine obey_ar(pGetThreadPriorityBoost(access_thread,&disabled)==0);
|
|
ok(CloseHandle(access_thread),"Error Closing thread handle\n");
|
|
}
|
|
}
|
|
|
|
rc = pSetThreadPriorityBoost(curthread,1);
|
|
ok( rc != 0, "error=%d\n",GetLastError());
|
|
todo_wine {
|
|
rc=pGetThreadPriorityBoost(curthread,&disabled);
|
|
ok(rc!=0 && disabled==1,
|
|
"rc=%d error=%d disabled=%d\n",rc,GetLastError(),disabled);
|
|
}
|
|
|
|
rc = pSetThreadPriorityBoost(curthread,0);
|
|
ok( rc != 0, "error=%d\n",GetLastError());
|
|
rc=pGetThreadPriorityBoost(curthread,&disabled);
|
|
ok(rc!=0 && disabled==0,
|
|
"rc=%d error=%d disabled=%d\n",rc,GetLastError(),disabled);
|
|
}
|
|
|
|
/* check the GetThreadTimes function */
|
|
static VOID test_GetThreadTimes(void)
|
|
{
|
|
HANDLE thread,access_thread=NULL;
|
|
FILETIME creationTime,exitTime,kernelTime,userTime;
|
|
DWORD threadId;
|
|
int error;
|
|
|
|
thread = CreateThread(NULL,0,threadFunc2,NULL,
|
|
CREATE_SUSPENDED,&threadId);
|
|
|
|
ok(thread!=NULL,"Create Thread failed\n");
|
|
/* check that access control is obeyed */
|
|
if (pOpenThread) {
|
|
access_thread=pOpenThread(THREAD_ALL_ACCESS_NT4 &
|
|
(~THREAD_QUERY_INFORMATION), 0,threadId);
|
|
ok(access_thread!=NULL,
|
|
"OpenThread returned an invalid handle\n");
|
|
}
|
|
ok(ResumeThread(thread)==1,"Resume thread returned an invalid value\n");
|
|
ok(WaitForSingleObject(thread,5000)==WAIT_OBJECT_0,
|
|
"ResumeThread didn't work\n");
|
|
creationTime.dwLowDateTime=99; creationTime.dwHighDateTime=99;
|
|
exitTime.dwLowDateTime=99; exitTime.dwHighDateTime=99;
|
|
kernelTime.dwLowDateTime=99; kernelTime.dwHighDateTime=99;
|
|
userTime.dwLowDateTime=99; userTime.dwHighDateTime=99;
|
|
/* GetThreadTimes should set all of the parameters passed to it */
|
|
error=GetThreadTimes(thread,&creationTime,&exitTime,
|
|
&kernelTime,&userTime);
|
|
|
|
if (error == 0 && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED)
|
|
win_skip("GetThreadTimes is not implemented\n");
|
|
else {
|
|
ok(error!=0,"GetThreadTimes failed\n");
|
|
ok(creationTime.dwLowDateTime!=99 || creationTime.dwHighDateTime!=99,
|
|
"creationTime was invalid\n");
|
|
ok(exitTime.dwLowDateTime!=99 || exitTime.dwHighDateTime!=99,
|
|
"exitTime was invalid\n");
|
|
ok(kernelTime.dwLowDateTime!=99 || kernelTime.dwHighDateTime!=99,
|
|
"kernelTimewas invalid\n");
|
|
ok(userTime.dwLowDateTime!=99 || userTime.dwHighDateTime!=99,
|
|
"userTime was invalid\n");
|
|
ok(CloseHandle(thread)!=0,"CloseHandle failed\n");
|
|
if(access_thread!=NULL)
|
|
{
|
|
error=GetThreadTimes(access_thread,&creationTime,&exitTime,
|
|
&kernelTime,&userTime);
|
|
obey_ar(error==0);
|
|
}
|
|
}
|
|
if(access_thread!=NULL) {
|
|
ok(CloseHandle(access_thread)!=0,"CloseHandle Failed\n");
|
|
}
|
|
}
|
|
|
|
/* Check the processor affinity functions */
|
|
/* NOTE: These functions should also be checked that they obey access control
|
|
*/
|
|
static VOID test_thread_processor(void)
|
|
{
|
|
HANDLE curthread,curproc;
|
|
DWORD_PTR processMask,systemMask,retMask;
|
|
SYSTEM_INFO sysInfo;
|
|
int error=0;
|
|
BOOL is_wow64;
|
|
|
|
if (!pIsWow64Process || !pIsWow64Process( GetCurrentProcess(), &is_wow64 )) is_wow64 = FALSE;
|
|
|
|
sysInfo.dwNumberOfProcessors=0;
|
|
GetSystemInfo(&sysInfo);
|
|
ok(sysInfo.dwNumberOfProcessors>0,
|
|
"GetSystemInfo failed to return a valid # of processors\n");
|
|
/* Use the current Thread/process for all tests */
|
|
curthread=GetCurrentThread();
|
|
ok(curthread!=NULL,"GetCurrentThread failed\n");
|
|
curproc=GetCurrentProcess();
|
|
ok(curproc!=NULL,"GetCurrentProcess failed\n");
|
|
/* Check the Affinity Mask functions */
|
|
ok(GetProcessAffinityMask(curproc,&processMask,&systemMask)!=0,
|
|
"GetProcessAffinityMask failed\n");
|
|
ok(SetThreadAffinityMask(curthread,processMask)==processMask,
|
|
"SetThreadAffinityMask failed\n");
|
|
ok(SetThreadAffinityMask(curthread,processMask+1)==0,
|
|
"SetThreadAffinityMask passed for an illegal processor\n");
|
|
/* NOTE: Pre-Vista does not recognize the "all processors" flag (all bits set) */
|
|
retMask = SetThreadAffinityMask(curthread,~0);
|
|
ok(broken(retMask==0) || retMask==processMask,
|
|
"SetThreadAffinityMask(thread,-1) failed to request all processors.\n");
|
|
|
|
if (retMask == processMask)
|
|
{
|
|
/* Show that the "all processors" flag is handled in ntdll */
|
|
DWORD_PTR mask = ~0u;
|
|
NTSTATUS status = pNtSetInformationThread(curthread, ThreadAffinityMask, &mask, sizeof(mask));
|
|
ok(status == STATUS_SUCCESS, "Expected STATUS_SUCCESS in NtSetInformationThread, got %x\n", status);
|
|
}
|
|
|
|
if (retMask == processMask && sizeof(ULONG_PTR) > sizeof(ULONG))
|
|
{
|
|
/* only the low 32-bits matter */
|
|
retMask = SetThreadAffinityMask(curthread,~(ULONG_PTR)0);
|
|
ok(retMask == processMask, "SetThreadAffinityMask failed\n");
|
|
retMask = SetThreadAffinityMask(curthread,~(ULONG_PTR)0 >> 3);
|
|
ok(retMask == processMask, "SetThreadAffinityMask failed\n");
|
|
}
|
|
/* NOTE: This only works on WinNT/2000/XP) */
|
|
if (pSetThreadIdealProcessor)
|
|
{
|
|
SetLastError(0xdeadbeef);
|
|
error=pSetThreadIdealProcessor(curthread,0);
|
|
if (GetLastError() != ERROR_CALL_NOT_IMPLEMENTED)
|
|
{
|
|
ok(error!=-1, "SetThreadIdealProcessor failed\n");
|
|
|
|
if (is_wow64)
|
|
{
|
|
SetLastError(0xdeadbeef);
|
|
error=pSetThreadIdealProcessor(curthread,MAXIMUM_PROCESSORS+1);
|
|
todo_wine
|
|
ok(error!=-1, "SetThreadIdealProcessor failed for %u on Wow64\n", MAXIMUM_PROCESSORS+1);
|
|
|
|
SetLastError(0xdeadbeef);
|
|
error=pSetThreadIdealProcessor(curthread,65);
|
|
ok(error==-1, "SetThreadIdealProcessor succeeded with an illegal processor #\n");
|
|
ok(GetLastError()==ERROR_INVALID_PARAMETER,
|
|
"Expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError());
|
|
}
|
|
else
|
|
{
|
|
SetLastError(0xdeadbeef);
|
|
error=pSetThreadIdealProcessor(curthread,MAXIMUM_PROCESSORS+1);
|
|
ok(error==-1, "SetThreadIdealProcessor succeeded with an illegal processor #\n");
|
|
ok(GetLastError()==ERROR_INVALID_PARAMETER,
|
|
"Expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError());
|
|
}
|
|
|
|
error=pSetThreadIdealProcessor(curthread,MAXIMUM_PROCESSORS);
|
|
ok(error!=-1, "SetThreadIdealProcessor failed\n");
|
|
}
|
|
else
|
|
win_skip("SetThreadIdealProcessor is not implemented\n");
|
|
}
|
|
|
|
if (pGetThreadGroupAffinity && pSetThreadGroupAffinity)
|
|
{
|
|
GROUP_AFFINITY affinity, affinity_new;
|
|
NTSTATUS status;
|
|
|
|
memset(&affinity, 0, sizeof(affinity));
|
|
ok(pGetThreadGroupAffinity(curthread, &affinity), "GetThreadGroupAffinity failed\n");
|
|
|
|
SetLastError(0xdeadbeef);
|
|
ok(!pGetThreadGroupAffinity(curthread, NULL), "GetThreadGroupAffinity succeeded\n");
|
|
ok(GetLastError() == ERROR_INVALID_PARAMETER || broken(GetLastError() == ERROR_NOACCESS), /* Win 7 and 8 */
|
|
"Expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError());
|
|
ok(affinity.Group == 0, "Expected group 0 got %u\n", affinity.Group);
|
|
|
|
memset(&affinity_new, 0, sizeof(affinity_new));
|
|
affinity_new.Group = 0;
|
|
affinity_new.Mask = affinity.Mask;
|
|
ok(pSetThreadGroupAffinity(curthread, &affinity_new, &affinity), "SetThreadGroupAffinity failed\n");
|
|
ok(affinity_new.Mask == affinity.Mask, "Expected old affinity mask %lx, got %lx\n",
|
|
affinity_new.Mask, affinity.Mask);
|
|
|
|
/* show that the "all processors" flag is not supported for SetThreadGroupAffinity */
|
|
affinity_new.Group = 0;
|
|
affinity_new.Mask = ~0u;
|
|
SetLastError(0xdeadbeef);
|
|
ok(!pSetThreadGroupAffinity(curthread, &affinity_new, NULL), "SetThreadGroupAffinity succeeded\n");
|
|
ok(GetLastError() == ERROR_INVALID_PARAMETER,
|
|
"Expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError());
|
|
|
|
affinity_new.Group = 1; /* assumes that you have less than 64 logical processors */
|
|
affinity_new.Mask = 0x1;
|
|
SetLastError(0xdeadbeef);
|
|
ok(!pSetThreadGroupAffinity(curthread, &affinity_new, NULL), "SetThreadGroupAffinity succeeded\n");
|
|
ok(GetLastError() == ERROR_INVALID_PARAMETER,
|
|
"Expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError());
|
|
|
|
SetLastError(0xdeadbeef);
|
|
ok(!pSetThreadGroupAffinity(curthread, NULL, NULL), "SetThreadGroupAffinity succeeded\n");
|
|
ok(GetLastError() == ERROR_NOACCESS,
|
|
"Expected ERROR_NOACCESS, got %d\n", GetLastError());
|
|
|
|
/* show that the access violation was detected in ntdll */
|
|
status = pNtSetInformationThread(curthread, ThreadGroupInformation, NULL, sizeof(affinity_new));
|
|
ok(status == STATUS_ACCESS_VIOLATION,
|
|
"Expected STATUS_ACCESS_VIOLATION, got %08x\n", status);
|
|
|
|
/* restore original mask */
|
|
affinity_new.Group = 0;
|
|
affinity_new.Mask = affinity.Mask;
|
|
SetLastError(0xdeadbeef);
|
|
ok(pSetThreadGroupAffinity(curthread, &affinity_new, &affinity), "SetThreadGroupAffinity failed\n");
|
|
ok(affinity_new.Mask == affinity.Mask, "Expected old affinity mask %lx, got %lx\n",
|
|
affinity_new.Mask, affinity.Mask);
|
|
}
|
|
else
|
|
win_skip("Get/SetThreadGroupAffinity not available\n");
|
|
}
|
|
|
|
static VOID test_GetThreadExitCode(void)
|
|
{
|
|
DWORD exitCode, threadid;
|
|
DWORD GLE, ret;
|
|
HANDLE thread;
|
|
|
|
ret = GetExitCodeThread((HANDLE)0x2bad2bad,&exitCode);
|
|
ok(ret==0, "GetExitCodeThread returned non zero value: %d\n", ret);
|
|
GLE = GetLastError();
|
|
ok(GLE==ERROR_INVALID_HANDLE, "GetLastError returned %d (expected 6)\n", GLE);
|
|
|
|
thread = CreateThread(NULL,0,threadFunc2,NULL,0,&threadid);
|
|
ret = WaitForSingleObject(thread,100);
|
|
ok(ret==WAIT_OBJECT_0, "threadFunc2 did not exit during 100 ms\n");
|
|
ret = GetExitCodeThread(thread,&exitCode);
|
|
ok(ret==exitCode || ret==1,
|
|
"GetExitCodeThread returned %d (expected 1 or %d)\n", ret, exitCode);
|
|
ok(exitCode==99, "threadFunc2 exited with code %d (expected 99)\n", exitCode);
|
|
ok(CloseHandle(thread)!=0,"Error closing thread handle\n");
|
|
}
|
|
|
|
#ifdef __i386__
|
|
|
|
static int test_value = 0;
|
|
static HANDLE event;
|
|
|
|
static void WINAPI set_test_val( int val )
|
|
{
|
|
test_value += val;
|
|
ExitThread(0);
|
|
}
|
|
|
|
static DWORD WINAPI threadFunc6(LPVOID p)
|
|
{
|
|
SetEvent( event );
|
|
Sleep( 1000 );
|
|
test_value *= (int)p;
|
|
return 0;
|
|
}
|
|
|
|
static void test_SetThreadContext(void)
|
|
{
|
|
CONTEXT ctx;
|
|
int *stack;
|
|
HANDLE thread;
|
|
DWORD threadid;
|
|
DWORD prevcount;
|
|
BOOL ret;
|
|
|
|
SetLastError(0xdeadbeef);
|
|
event = CreateEventW( NULL, TRUE, FALSE, NULL );
|
|
thread = CreateThread( NULL, 0, threadFunc6, (void *)2, 0, &threadid );
|
|
ok( thread != NULL, "CreateThread failed : (%d)\n", GetLastError() );
|
|
if (!thread)
|
|
{
|
|
trace("Thread creation failed, skipping rest of test\n");
|
|
return;
|
|
}
|
|
WaitForSingleObject( event, INFINITE );
|
|
SuspendThread( thread );
|
|
CloseHandle( event );
|
|
|
|
ctx.ContextFlags = CONTEXT_FULL;
|
|
SetLastError(0xdeadbeef);
|
|
ret = GetThreadContext( thread, &ctx );
|
|
ok( ret, "GetThreadContext failed : (%u)\n", GetLastError() );
|
|
|
|
if (ret)
|
|
{
|
|
/* simulate a call to set_test_val(10) */
|
|
stack = (int *)ctx.Esp;
|
|
stack[-1] = 10;
|
|
stack[-2] = ctx.Eip;
|
|
ctx.Esp -= 2 * sizeof(int *);
|
|
ctx.Eip = (DWORD)set_test_val;
|
|
SetLastError(0xdeadbeef);
|
|
ret = SetThreadContext( thread, &ctx );
|
|
ok( ret, "SetThreadContext failed : (%d)\n", GetLastError() );
|
|
}
|
|
|
|
SetLastError(0xdeadbeef);
|
|
prevcount = ResumeThread( thread );
|
|
ok ( prevcount == 1, "Previous suspend count (%d) instead of 1, last error : (%d)\n",
|
|
prevcount, GetLastError() );
|
|
|
|
WaitForSingleObject( thread, INFINITE );
|
|
ok( test_value == 10, "test_value %d\n", test_value );
|
|
|
|
ctx.ContextFlags = CONTEXT_FULL;
|
|
SetLastError(0xdeadbeef);
|
|
ret = GetThreadContext( thread, &ctx );
|
|
ok( (!ret && (GetLastError() == ERROR_GEN_FAILURE)) ||
|
|
(!ret && broken(GetLastError() == ERROR_INVALID_HANDLE)) || /* win2k */
|
|
broken(ret), /* 32bit application on NT 5.x 64bit */
|
|
"got %d with %u (expected FALSE with ERROR_GEN_FAILURE)\n",
|
|
ret, GetLastError() );
|
|
|
|
SetLastError(0xdeadbeef);
|
|
ret = SetThreadContext( thread, &ctx );
|
|
ok( (!ret && ((GetLastError() == ERROR_GEN_FAILURE) || (GetLastError() == ERROR_ACCESS_DENIED))) ||
|
|
(!ret && broken(GetLastError() == ERROR_INVALID_HANDLE)) || /* win2k */
|
|
broken(ret), /* 32bit application on NT 5.x 64bit */
|
|
"got %d with %u (expected FALSE with ERROR_GEN_FAILURE or ERROR_ACCESS_DENIED)\n",
|
|
ret, GetLastError() );
|
|
|
|
CloseHandle( thread );
|
|
}
|
|
|
|
static void test_GetThreadSelectorEntry(void)
|
|
{
|
|
LDT_ENTRY entry;
|
|
CONTEXT ctx;
|
|
DWORD limit;
|
|
void *base;
|
|
BOOL ret;
|
|
|
|
memset(&ctx, 0x11, sizeof(ctx));
|
|
ctx.ContextFlags = CONTEXT_SEGMENTS | CONTEXT_CONTROL;
|
|
ret = GetThreadContext(GetCurrentThread(), &ctx);
|
|
ok(ret, "GetThreadContext error %u\n", GetLastError());
|
|
ok(!HIWORD(ctx.SegCs), "expected HIWORD(SegCs) == 0, got %u\n", ctx.SegCs);
|
|
ok(!HIWORD(ctx.SegDs), "expected HIWORD(SegDs) == 0, got %u\n", ctx.SegDs);
|
|
ok(!HIWORD(ctx.SegFs), "expected HIWORD(SegFs) == 0, got %u\n", ctx.SegFs);
|
|
|
|
ret = GetThreadSelectorEntry(GetCurrentThread(), ctx.SegCs, &entry);
|
|
ok(ret, "GetThreadSelectorEntry(SegCs) error %u\n", GetLastError());
|
|
ret = GetThreadSelectorEntry(GetCurrentThread(), ctx.SegDs, &entry);
|
|
ok(ret, "GetThreadSelectorEntry(SegDs) error %u\n", GetLastError());
|
|
|
|
memset(&entry, 0x11, sizeof(entry));
|
|
ret = GetThreadSelectorEntry(GetCurrentThread(), ctx.SegFs, &entry);
|
|
ok(ret, "GetThreadSelectorEntry(SegFs) error %u\n", GetLastError());
|
|
entry.HighWord.Bits.Type &= ~1; /* ignore accessed bit */
|
|
|
|
base = (void *)((entry.HighWord.Bits.BaseHi << 24) | (entry.HighWord.Bits.BaseMid << 16) | entry.BaseLow);
|
|
limit = (entry.HighWord.Bits.LimitHi << 16) | entry.LimitLow;
|
|
|
|
ok(base == NtCurrentTeb(), "expected %p, got %p\n", NtCurrentTeb(), base);
|
|
ok(limit == 0x0fff || limit == 0x4000, "expected 0x0fff or 0x4000, got %#x\n", limit);
|
|
ok(entry.HighWord.Bits.Type == 0x12, "expected 0x12, got %#x\n", entry.HighWord.Bits.Type);
|
|
ok(entry.HighWord.Bits.Dpl == 3, "expected 3, got %u\n", entry.HighWord.Bits.Dpl);
|
|
ok(entry.HighWord.Bits.Pres == 1, "expected 1, got %u\n", entry.HighWord.Bits.Pres);
|
|
ok(entry.HighWord.Bits.Sys == 0, "expected 0, got %u\n", entry.HighWord.Bits.Sys);
|
|
ok(entry.HighWord.Bits.Reserved_0 == 0, "expected 0, got %u\n", entry.HighWord.Bits.Reserved_0);
|
|
ok(entry.HighWord.Bits.Default_Big == 1, "expected 1, got %u\n", entry.HighWord.Bits.Default_Big);
|
|
ok(entry.HighWord.Bits.Granularity == 0, "expected 0, got %u\n", entry.HighWord.Bits.Granularity);
|
|
}
|
|
|
|
#endif /* __i386__ */
|
|
|
|
static HANDLE finish_event;
|
|
static LONG times_executed;
|
|
|
|
static DWORD CALLBACK work_function(void *p)
|
|
{
|
|
LONG executed = InterlockedIncrement(×_executed);
|
|
|
|
if (executed == 100)
|
|
SetEvent(finish_event);
|
|
return 0;
|
|
}
|
|
|
|
static void test_QueueUserWorkItem(void)
|
|
{
|
|
INT_PTR i;
|
|
DWORD wait_result;
|
|
DWORD before, after;
|
|
|
|
/* QueueUserWorkItem not present on win9x */
|
|
if (!pQueueUserWorkItem) return;
|
|
|
|
finish_event = CreateEventW(NULL, TRUE, FALSE, NULL);
|
|
|
|
before = GetTickCount();
|
|
|
|
for (i = 0; i < 100; i++)
|
|
{
|
|
BOOL ret = pQueueUserWorkItem(work_function, (void *)i, WT_EXECUTEDEFAULT);
|
|
ok(ret, "QueueUserWorkItem failed with error %d\n", GetLastError());
|
|
}
|
|
|
|
wait_result = WaitForSingleObject(finish_event, 10000);
|
|
|
|
after = GetTickCount();
|
|
trace("100 QueueUserWorkItem calls took %dms\n", after - before);
|
|
ok(wait_result == WAIT_OBJECT_0, "wait failed with error 0x%x\n", wait_result);
|
|
|
|
ok(times_executed == 100, "didn't execute all of the work items\n");
|
|
}
|
|
|
|
static void CALLBACK signaled_function(PVOID p, BOOLEAN TimerOrWaitFired)
|
|
{
|
|
HANDLE event = p;
|
|
SetEvent(event);
|
|
ok(!TimerOrWaitFired, "wait shouldn't have timed out\n");
|
|
}
|
|
|
|
static void CALLBACK timeout_function(PVOID p, BOOLEAN TimerOrWaitFired)
|
|
{
|
|
HANDLE event = p;
|
|
SetEvent(event);
|
|
ok(TimerOrWaitFired, "wait should have timed out\n");
|
|
}
|
|
|
|
static void test_RegisterWaitForSingleObject(void)
|
|
{
|
|
BOOL ret;
|
|
HANDLE wait_handle;
|
|
HANDLE handle;
|
|
HANDLE complete_event;
|
|
|
|
if (!pRegisterWaitForSingleObject || !pUnregisterWait)
|
|
{
|
|
win_skip("RegisterWaitForSingleObject or UnregisterWait not implemented\n");
|
|
return;
|
|
}
|
|
|
|
/* test signaled case */
|
|
|
|
handle = CreateEventW(NULL, TRUE, TRUE, NULL);
|
|
complete_event = CreateEventW(NULL, FALSE, FALSE, NULL);
|
|
|
|
ret = pRegisterWaitForSingleObject(&wait_handle, handle, signaled_function, complete_event, INFINITE, WT_EXECUTEONLYONCE);
|
|
ok(ret, "RegisterWaitForSingleObject failed with error %d\n", GetLastError());
|
|
|
|
WaitForSingleObject(complete_event, INFINITE);
|
|
/* give worker thread chance to complete */
|
|
Sleep(100);
|
|
|
|
ret = pUnregisterWait(wait_handle);
|
|
ok(ret, "UnregisterWait failed with error %d\n", GetLastError());
|
|
|
|
/* test cancel case */
|
|
|
|
ResetEvent(handle);
|
|
|
|
ret = pRegisterWaitForSingleObject(&wait_handle, handle, signaled_function, complete_event, INFINITE, WT_EXECUTEONLYONCE);
|
|
ok(ret, "RegisterWaitForSingleObject failed with error %d\n", GetLastError());
|
|
|
|
ret = pUnregisterWait(wait_handle);
|
|
ok(ret, "UnregisterWait failed with error %d\n", GetLastError());
|
|
|
|
/* test timeout case */
|
|
|
|
ret = pRegisterWaitForSingleObject(&wait_handle, handle, timeout_function, complete_event, 0, WT_EXECUTEONLYONCE);
|
|
ok(ret, "RegisterWaitForSingleObject failed with error %d\n", GetLastError());
|
|
|
|
WaitForSingleObject(complete_event, INFINITE);
|
|
/* give worker thread chance to complete */
|
|
Sleep(100);
|
|
|
|
ret = pUnregisterWait(wait_handle);
|
|
ok(ret, "UnregisterWait failed with error %d\n", GetLastError());
|
|
}
|
|
|
|
static DWORD TLS_main;
|
|
static DWORD TLS_index0, TLS_index1;
|
|
|
|
static DWORD WINAPI TLS_InheritanceProc(LPVOID p)
|
|
{
|
|
/* We should NOT inherit the TLS values from our parent or from the
|
|
main thread. */
|
|
LPVOID val;
|
|
|
|
val = TlsGetValue(TLS_main);
|
|
ok(val == NULL, "TLS inheritance failed\n");
|
|
|
|
val = TlsGetValue(TLS_index0);
|
|
ok(val == NULL, "TLS inheritance failed\n");
|
|
|
|
val = TlsGetValue(TLS_index1);
|
|
ok(val == NULL, "TLS inheritance failed\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Basic TLS usage test. Make sure we can create slots and the values we
|
|
store in them are separate among threads. Also test TLS value
|
|
inheritance with TLS_InheritanceProc. */
|
|
static DWORD WINAPI TLS_ThreadProc(LPVOID p)
|
|
{
|
|
LONG_PTR id = (LONG_PTR) p;
|
|
LPVOID val;
|
|
BOOL ret;
|
|
|
|
if (sync_threads_and_run_one(0, id))
|
|
{
|
|
TLS_index0 = TlsAlloc();
|
|
ok(TLS_index0 != TLS_OUT_OF_INDEXES, "TlsAlloc failed\n");
|
|
}
|
|
resync_after_run();
|
|
|
|
if (sync_threads_and_run_one(1, id))
|
|
{
|
|
TLS_index1 = TlsAlloc();
|
|
ok(TLS_index1 != TLS_OUT_OF_INDEXES, "TlsAlloc failed\n");
|
|
|
|
/* Slot indices should be different even if created in different
|
|
threads. */
|
|
ok(TLS_index0 != TLS_index1, "TlsAlloc failed\n");
|
|
|
|
/* Both slots should be initialized to NULL */
|
|
val = TlsGetValue(TLS_index0);
|
|
ok(GetLastError() == ERROR_SUCCESS, "TlsGetValue failed\n");
|
|
ok(val == NULL, "TLS slot not initialized correctly\n");
|
|
|
|
val = TlsGetValue(TLS_index1);
|
|
ok(GetLastError() == ERROR_SUCCESS, "TlsGetValue failed\n");
|
|
ok(val == NULL, "TLS slot not initialized correctly\n");
|
|
}
|
|
resync_after_run();
|
|
|
|
if (sync_threads_and_run_one(0, id))
|
|
{
|
|
val = TlsGetValue(TLS_index0);
|
|
ok(GetLastError() == ERROR_SUCCESS, "TlsGetValue failed\n");
|
|
ok(val == NULL, "TLS slot not initialized correctly\n");
|
|
|
|
val = TlsGetValue(TLS_index1);
|
|
ok(GetLastError() == ERROR_SUCCESS, "TlsGetValue failed\n");
|
|
ok(val == NULL, "TLS slot not initialized correctly\n");
|
|
|
|
ret = TlsSetValue(TLS_index0, (LPVOID) 1);
|
|
ok(ret, "TlsSetValue failed\n");
|
|
|
|
ret = TlsSetValue(TLS_index1, (LPVOID) 2);
|
|
ok(ret, "TlsSetValue failed\n");
|
|
|
|
val = TlsGetValue(TLS_index0);
|
|
ok(GetLastError() == ERROR_SUCCESS, "TlsGetValue failed\n");
|
|
ok(val == (LPVOID) 1, "TLS slot not initialized correctly\n");
|
|
|
|
val = TlsGetValue(TLS_index1);
|
|
ok(GetLastError() == ERROR_SUCCESS, "TlsGetValue failed\n");
|
|
ok(val == (LPVOID) 2, "TLS slot not initialized correctly\n");
|
|
}
|
|
resync_after_run();
|
|
|
|
if (sync_threads_and_run_one(1, id))
|
|
{
|
|
val = TlsGetValue(TLS_index0);
|
|
ok(GetLastError() == ERROR_SUCCESS, "TlsGetValue failed\n");
|
|
ok(val == NULL, "TLS slot not initialized correctly\n");
|
|
|
|
val = TlsGetValue(TLS_index1);
|
|
ok(GetLastError() == ERROR_SUCCESS, "TlsGetValue failed\n");
|
|
ok(val == NULL, "TLS slot not initialized correctly\n");
|
|
|
|
ret = TlsSetValue(TLS_index0, (LPVOID) 3);
|
|
ok(ret, "TlsSetValue failed\n");
|
|
|
|
ret = TlsSetValue(TLS_index1, (LPVOID) 4);
|
|
ok(ret, "TlsSetValue failed\n");
|
|
|
|
val = TlsGetValue(TLS_index0);
|
|
ok(GetLastError() == ERROR_SUCCESS, "TlsGetValue failed\n");
|
|
ok(val == (LPVOID) 3, "TLS slot not initialized correctly\n");
|
|
|
|
val = TlsGetValue(TLS_index1);
|
|
ok(GetLastError() == ERROR_SUCCESS, "TlsGetValue failed\n");
|
|
ok(val == (LPVOID) 4, "TLS slot not initialized correctly\n");
|
|
}
|
|
resync_after_run();
|
|
|
|
if (sync_threads_and_run_one(0, id))
|
|
{
|
|
HANDLE thread;
|
|
DWORD waitret, tid;
|
|
|
|
val = TlsGetValue(TLS_index0);
|
|
ok(GetLastError() == ERROR_SUCCESS, "TlsGetValue failed\n");
|
|
ok(val == (LPVOID) 1, "TLS slot not initialized correctly\n");
|
|
|
|
val = TlsGetValue(TLS_index1);
|
|
ok(GetLastError() == ERROR_SUCCESS, "TlsGetValue failed\n");
|
|
ok(val == (LPVOID) 2, "TLS slot not initialized correctly\n");
|
|
|
|
thread = CreateThread(NULL, 0, TLS_InheritanceProc, 0, 0, &tid);
|
|
ok(thread != NULL, "CreateThread failed\n");
|
|
waitret = WaitForSingleObject(thread, 60000);
|
|
ok(waitret == WAIT_OBJECT_0, "WaitForSingleObject failed\n");
|
|
CloseHandle(thread);
|
|
|
|
ret = TlsFree(TLS_index0);
|
|
ok(ret, "TlsFree failed\n");
|
|
}
|
|
resync_after_run();
|
|
|
|
if (sync_threads_and_run_one(1, id))
|
|
{
|
|
ret = TlsFree(TLS_index1);
|
|
ok(ret, "TlsFree failed\n");
|
|
}
|
|
resync_after_run();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void test_TLS(void)
|
|
{
|
|
HANDLE threads[2];
|
|
LONG_PTR i;
|
|
DWORD ret;
|
|
BOOL suc;
|
|
|
|
init_thread_sync_helpers();
|
|
|
|
/* Allocate a TLS slot in the main thread to test for inheritance. */
|
|
TLS_main = TlsAlloc();
|
|
ok(TLS_main != TLS_OUT_OF_INDEXES, "TlsAlloc failed\n");
|
|
suc = TlsSetValue(TLS_main, (LPVOID) 4114);
|
|
ok(suc, "TlsSetValue failed\n");
|
|
|
|
for (i = 0; i < 2; ++i)
|
|
{
|
|
DWORD tid;
|
|
|
|
threads[i] = CreateThread(NULL, 0, TLS_ThreadProc, (LPVOID) i, 0, &tid);
|
|
ok(threads[i] != NULL, "CreateThread failed\n");
|
|
}
|
|
|
|
ret = WaitForMultipleObjects(2, threads, TRUE, 60000);
|
|
ok(ret == WAIT_OBJECT_0 || broken(ret == WAIT_OBJECT_0+1 /* nt4,w2k */), "WaitForAllObjects 2 threads %d\n",ret);
|
|
|
|
for (i = 0; i < 2; ++i)
|
|
CloseHandle(threads[i]);
|
|
|
|
suc = TlsFree(TLS_main);
|
|
ok(suc, "TlsFree failed\n");
|
|
cleanup_thread_sync_helpers();
|
|
}
|
|
|
|
static void test_ThreadErrorMode(void)
|
|
{
|
|
DWORD oldmode;
|
|
DWORD mode;
|
|
DWORD rtlmode;
|
|
BOOL ret;
|
|
|
|
if (!pSetThreadErrorMode || !pGetThreadErrorMode)
|
|
{
|
|
win_skip("SetThreadErrorMode and/or GetThreadErrorMode unavailable (added in Windows 7)\n");
|
|
return;
|
|
}
|
|
|
|
if (!pRtlGetThreadErrorMode) {
|
|
win_skip("RtlGetThreadErrorMode not available\n");
|
|
return;
|
|
}
|
|
|
|
oldmode = pGetThreadErrorMode();
|
|
|
|
ret = pSetThreadErrorMode(0, &mode);
|
|
ok(ret, "SetThreadErrorMode failed\n");
|
|
ok(mode == oldmode,
|
|
"SetThreadErrorMode returned old mode 0x%x, expected 0x%x\n",
|
|
mode, oldmode);
|
|
mode = pGetThreadErrorMode();
|
|
ok(mode == 0, "GetThreadErrorMode returned mode 0x%x, expected 0\n", mode);
|
|
rtlmode = pRtlGetThreadErrorMode();
|
|
ok(rtlmode == 0,
|
|
"RtlGetThreadErrorMode returned mode 0x%x, expected 0\n", mode);
|
|
|
|
ret = pSetThreadErrorMode(SEM_FAILCRITICALERRORS, &mode);
|
|
ok(ret, "SetThreadErrorMode failed\n");
|
|
ok(mode == 0,
|
|
"SetThreadErrorMode returned old mode 0x%x, expected 0\n", mode);
|
|
mode = pGetThreadErrorMode();
|
|
ok(mode == SEM_FAILCRITICALERRORS,
|
|
"GetThreadErrorMode returned mode 0x%x, expected SEM_FAILCRITICALERRORS\n",
|
|
mode);
|
|
rtlmode = pRtlGetThreadErrorMode();
|
|
ok(rtlmode == 0x10,
|
|
"RtlGetThreadErrorMode returned mode 0x%x, expected 0x10\n", mode);
|
|
|
|
ret = pSetThreadErrorMode(SEM_NOGPFAULTERRORBOX, &mode);
|
|
ok(ret, "SetThreadErrorMode failed\n");
|
|
ok(mode == SEM_FAILCRITICALERRORS,
|
|
"SetThreadErrorMode returned old mode 0x%x, expected SEM_FAILCRITICALERRORS\n",
|
|
mode);
|
|
mode = pGetThreadErrorMode();
|
|
ok(mode == SEM_NOGPFAULTERRORBOX,
|
|
"GetThreadErrorMode returned mode 0x%x, expected SEM_NOGPFAULTERRORBOX\n",
|
|
mode);
|
|
rtlmode = pRtlGetThreadErrorMode();
|
|
ok(rtlmode == 0x20,
|
|
"RtlGetThreadErrorMode returned mode 0x%x, expected 0x20\n", mode);
|
|
|
|
ret = pSetThreadErrorMode(SEM_NOOPENFILEERRORBOX, NULL);
|
|
ok(ret, "SetThreadErrorMode failed\n");
|
|
mode = pGetThreadErrorMode();
|
|
ok(mode == SEM_NOOPENFILEERRORBOX,
|
|
"GetThreadErrorMode returned mode 0x%x, expected SEM_NOOPENFILEERRORBOX\n",
|
|
mode);
|
|
rtlmode = pRtlGetThreadErrorMode();
|
|
ok(rtlmode == 0x40,
|
|
"RtlGetThreadErrorMode returned mode 0x%x, expected 0x40\n", rtlmode);
|
|
|
|
for (mode = 1; mode; mode <<= 1)
|
|
{
|
|
ret = pSetThreadErrorMode(mode, NULL);
|
|
if (mode & (SEM_FAILCRITICALERRORS |
|
|
SEM_NOGPFAULTERRORBOX |
|
|
SEM_NOOPENFILEERRORBOX))
|
|
{
|
|
ok(ret,
|
|
"SetThreadErrorMode(0x%x,NULL) failed with error %d\n",
|
|
mode, GetLastError());
|
|
}
|
|
else
|
|
{
|
|
DWORD GLE = GetLastError();
|
|
ok(!ret,
|
|
"SetThreadErrorMode(0x%x,NULL) succeeded, expected failure\n",
|
|
mode);
|
|
ok(GLE == ERROR_INVALID_PARAMETER,
|
|
"SetThreadErrorMode(0x%x,NULL) failed with %d, "
|
|
"expected ERROR_INVALID_PARAMETER\n",
|
|
mode, GLE);
|
|
}
|
|
}
|
|
|
|
pSetThreadErrorMode(oldmode, NULL);
|
|
}
|
|
|
|
#if (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))) || (defined(_MSC_VER) && defined(__i386__))
|
|
static inline void set_fpu_cw(WORD cw)
|
|
{
|
|
#ifdef _MSC_VER
|
|
__asm { fnclex }
|
|
__asm { fldcw [cw] }
|
|
#else
|
|
__asm__ volatile ("fnclex; fldcw %0" : : "m" (cw));
|
|
#endif
|
|
}
|
|
|
|
static inline WORD get_fpu_cw(void)
|
|
{
|
|
WORD cw = 0;
|
|
#ifdef _MSC_VER
|
|
__asm { fnstcw [cw] }
|
|
#else
|
|
__asm__ volatile ("fnstcw %0" : "=m" (cw));
|
|
#endif
|
|
return cw;
|
|
}
|
|
|
|
struct fpu_thread_ctx
|
|
{
|
|
WORD cw;
|
|
HANDLE finished;
|
|
};
|
|
|
|
static DWORD WINAPI fpu_thread(void *param)
|
|
{
|
|
struct fpu_thread_ctx *ctx = param;
|
|
BOOL ret;
|
|
|
|
ctx->cw = get_fpu_cw();
|
|
|
|
ret = SetEvent(ctx->finished);
|
|
ok(ret, "SetEvent failed, last error %#x.\n", GetLastError());
|
|
|
|
return 0;
|
|
}
|
|
|
|
static WORD get_thread_fpu_cw(void)
|
|
{
|
|
struct fpu_thread_ctx ctx;
|
|
DWORD tid, res;
|
|
HANDLE thread;
|
|
|
|
ctx.finished = CreateEventW(NULL, FALSE, FALSE, NULL);
|
|
ok(!!ctx.finished, "Failed to create event, last error %#x.\n", GetLastError());
|
|
|
|
thread = CreateThread(NULL, 0, fpu_thread, &ctx, 0, &tid);
|
|
ok(!!thread, "Failed to create thread, last error %#x.\n", GetLastError());
|
|
|
|
res = WaitForSingleObject(ctx.finished, INFINITE);
|
|
ok(res == WAIT_OBJECT_0, "Wait failed (%#x), last error %#x.\n", res, GetLastError());
|
|
|
|
res = CloseHandle(ctx.finished);
|
|
ok(!!res, "Failed to close event handle, last error %#x.\n", GetLastError());
|
|
|
|
return ctx.cw;
|
|
}
|
|
|
|
static void test_thread_fpu_cw(void)
|
|
{
|
|
WORD initial_cw, cw;
|
|
|
|
initial_cw = get_fpu_cw();
|
|
ok(initial_cw == 0x27f, "Expected FPU control word 0x27f, got %#x.\n", initial_cw);
|
|
|
|
cw = get_thread_fpu_cw();
|
|
ok(cw == 0x27f, "Expected FPU control word 0x27f, got %#x.\n", cw);
|
|
|
|
set_fpu_cw(0xf60);
|
|
cw = get_fpu_cw();
|
|
ok(cw == 0xf60, "Expected FPU control word 0xf60, got %#x.\n", cw);
|
|
|
|
cw = get_thread_fpu_cw();
|
|
ok(cw == 0x27f, "Expected FPU control word 0x27f, got %#x.\n", cw);
|
|
|
|
cw = get_fpu_cw();
|
|
ok(cw == 0xf60, "Expected FPU control word 0xf60, got %#x.\n", cw);
|
|
|
|
set_fpu_cw(initial_cw);
|
|
cw = get_fpu_cw();
|
|
ok(cw == initial_cw, "Expected FPU control word %#x, got %#x.\n", initial_cw, cw);
|
|
}
|
|
#endif
|
|
|
|
static const char manifest_dep[] =
|
|
"<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">"
|
|
"<assemblyIdentity version=\"1.2.3.4\" name=\"testdep1\" type=\"win32\" processorArchitecture=\"" ARCH "\"/>"
|
|
" <file name=\"testdep.dll\" />"
|
|
"</assembly>";
|
|
|
|
static const char manifest_main[] =
|
|
"<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">"
|
|
"<assemblyIdentity version=\"1.2.3.4\" name=\"Wine.Test\" type=\"win32\" />"
|
|
"<dependency>"
|
|
" <dependentAssembly>"
|
|
" <assemblyIdentity type=\"win32\" name=\"testdep1\" version=\"1.2.3.4\" processorArchitecture=\"" ARCH "\" />"
|
|
" </dependentAssembly>"
|
|
"</dependency>"
|
|
"</assembly>";
|
|
|
|
static void create_manifest_file(const char *filename, const char *manifest)
|
|
{
|
|
WCHAR path[MAX_PATH];
|
|
HANDLE file;
|
|
DWORD size;
|
|
|
|
MultiByteToWideChar( CP_ACP, 0, filename, -1, path, MAX_PATH );
|
|
file = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
ok(file != INVALID_HANDLE_VALUE, "CreateFile failed: %u\n", GetLastError());
|
|
WriteFile(file, manifest, strlen(manifest), &size, NULL);
|
|
CloseHandle(file);
|
|
}
|
|
|
|
static HANDLE test_create(const char *file)
|
|
{
|
|
WCHAR path[MAX_PATH];
|
|
ACTCTXW actctx;
|
|
HANDLE handle;
|
|
|
|
MultiByteToWideChar(CP_ACP, 0, file, -1, path, MAX_PATH);
|
|
memset(&actctx, 0, sizeof(ACTCTXW));
|
|
actctx.cbSize = sizeof(ACTCTXW);
|
|
actctx.lpSource = path;
|
|
|
|
handle = pCreateActCtxW(&actctx);
|
|
ok(handle != INVALID_HANDLE_VALUE, "failed to create context, error %u\n", GetLastError());
|
|
|
|
ok(actctx.cbSize == sizeof(actctx), "cbSize=%d\n", actctx.cbSize);
|
|
ok(actctx.dwFlags == 0, "dwFlags=%d\n", actctx.dwFlags);
|
|
ok(actctx.lpSource == path, "lpSource=%p\n", actctx.lpSource);
|
|
ok(actctx.wProcessorArchitecture == 0, "wProcessorArchitecture=%d\n", actctx.wProcessorArchitecture);
|
|
ok(actctx.wLangId == 0, "wLangId=%d\n", actctx.wLangId);
|
|
ok(actctx.lpAssemblyDirectory == NULL, "lpAssemblyDirectory=%p\n", actctx.lpAssemblyDirectory);
|
|
ok(actctx.lpResourceName == NULL, "lpResourceName=%p\n", actctx.lpResourceName);
|
|
ok(actctx.lpApplicationName == NULL, "lpApplicationName=%p\n", actctx.lpApplicationName);
|
|
ok(actctx.hModule == NULL, "hModule=%p\n", actctx.hModule);
|
|
|
|
return handle;
|
|
}
|
|
|
|
static void test_thread_actctx(void)
|
|
{
|
|
struct thread_actctx_param param;
|
|
HANDLE thread, handle, context;
|
|
ULONG_PTR cookie;
|
|
DWORD tid, ret;
|
|
BOOL b;
|
|
|
|
if (!pActivateActCtx)
|
|
{
|
|
win_skip("skipping activation context tests\n");
|
|
return;
|
|
}
|
|
|
|
create_manifest_file("testdep1.manifest", manifest_dep);
|
|
create_manifest_file("main.manifest", manifest_main);
|
|
|
|
context = test_create("main.manifest");
|
|
DeleteFileA("testdep1.manifest");
|
|
DeleteFileA("main.manifest");
|
|
|
|
handle = (void*)0xdeadbeef;
|
|
b = pGetCurrentActCtx(&handle);
|
|
ok(b, "GetCurrentActCtx failed: %u\n", GetLastError());
|
|
ok(handle == 0, "active context %p\n", handle);
|
|
|
|
/* without active context */
|
|
param.thread_context = (void*)0xdeadbeef;
|
|
param.handle = NULL;
|
|
thread = CreateThread(NULL, 0, thread_actctx_func, ¶m, 0, &tid);
|
|
ok(thread != NULL, "failed, got %u\n", GetLastError());
|
|
|
|
ret = WaitForSingleObject(thread, 1000);
|
|
ok(ret == WAIT_OBJECT_0, "wait timeout\n");
|
|
ok(param.thread_context == NULL, "got wrong thread context %p\n", param.thread_context);
|
|
CloseHandle(thread);
|
|
|
|
b = pActivateActCtx(context, &cookie);
|
|
ok(b, "activation failed: %u\n", GetLastError());
|
|
|
|
handle = 0;
|
|
b = pGetCurrentActCtx(&handle);
|
|
ok(b, "GetCurrentActCtx failed: %u\n", GetLastError());
|
|
ok(handle != 0, "no active context\n");
|
|
pReleaseActCtx(handle);
|
|
|
|
param.handle = NULL;
|
|
b = pGetCurrentActCtx(¶m.handle);
|
|
ok(b && param.handle != NULL, "failed to get context, %u\n", GetLastError());
|
|
|
|
param.thread_context = (void*)0xdeadbeef;
|
|
thread = CreateThread(NULL, 0, thread_actctx_func, ¶m, 0, &tid);
|
|
ok(thread != NULL, "failed, got %u\n", GetLastError());
|
|
|
|
ret = WaitForSingleObject(thread, 1000);
|
|
ok(ret == WAIT_OBJECT_0, "wait timeout\n");
|
|
ok(param.thread_context == context, "got wrong thread context %p, %p\n", param.thread_context, context);
|
|
pReleaseActCtx(param.thread_context);
|
|
CloseHandle(thread);
|
|
|
|
/* similar test for CreateRemoteThread() */
|
|
param.thread_context = (void*)0xdeadbeef;
|
|
thread = CreateRemoteThread(GetCurrentProcess(), NULL, 0, thread_actctx_func, ¶m, 0, &tid);
|
|
ok(thread != NULL, "failed, got %u\n", GetLastError());
|
|
|
|
ret = WaitForSingleObject(thread, 1000);
|
|
ok(ret == WAIT_OBJECT_0, "wait timeout\n");
|
|
ok(param.thread_context == context, "got wrong thread context %p, %p\n", param.thread_context, context);
|
|
pReleaseActCtx(param.thread_context);
|
|
CloseHandle(thread);
|
|
|
|
pReleaseActCtx(param.handle);
|
|
|
|
b = pDeactivateActCtx(0, cookie);
|
|
ok(b, "DeactivateActCtx failed: %u\n", GetLastError());
|
|
pReleaseActCtx(context);
|
|
}
|
|
|
|
|
|
static void WINAPI threadpool_workcallback(PTP_CALLBACK_INSTANCE instance, void *context, PTP_WORK work) {
|
|
int *foo = (int*)context;
|
|
|
|
(*foo)++;
|
|
}
|
|
|
|
|
|
static void test_threadpool(void)
|
|
{
|
|
PTP_POOL pool;
|
|
PTP_WORK work;
|
|
int workcalled = 0;
|
|
|
|
if (!pCreateThreadpool) {
|
|
win_skip("thread pool apis not supported.\n");
|
|
return;
|
|
}
|
|
|
|
work = pCreateThreadpoolWork(threadpool_workcallback, &workcalled, NULL);
|
|
ok (work != NULL, "Error %d in CreateThreadpoolWork\n", GetLastError());
|
|
pSubmitThreadpoolWork(work);
|
|
pWaitForThreadpoolWorkCallbacks(work, FALSE);
|
|
pCloseThreadpoolWork(work);
|
|
|
|
ok (workcalled == 1, "expected work to be called once, got %d\n", workcalled);
|
|
|
|
pool = pCreateThreadpool(NULL);
|
|
ok (pool != NULL, "CreateThreadpool failed\n");
|
|
pCloseThreadpool(pool);
|
|
}
|
|
|
|
static void test_reserved_tls(void)
|
|
{
|
|
void *val;
|
|
DWORD tls;
|
|
BOOL ret;
|
|
|
|
/* This seems to be a WinXP SP2+ feature. */
|
|
if(!pIsWow64Process) {
|
|
win_skip("Skipping reserved TLS slot on too old Windows.\n");
|
|
return;
|
|
}
|
|
|
|
val = TlsGetValue(0);
|
|
ok(!val, "TlsGetValue(0) = %p\n", val);
|
|
|
|
/* Also make sure that there is a TLS allocated. */
|
|
tls = TlsAlloc();
|
|
ok(tls && tls != TLS_OUT_OF_INDEXES, "tls = %x\n", tls);
|
|
TlsSetValue(tls, (void*)1);
|
|
|
|
val = TlsGetValue(0);
|
|
ok(!val, "TlsGetValue(0) = %p\n", val);
|
|
|
|
TlsFree(tls);
|
|
|
|
/* The following is too ugly to be run by default */
|
|
if(0) {
|
|
/* Set TLS index 0 value and see that this works and doesn't cause problems
|
|
* for remaining tests. */
|
|
ret = TlsSetValue(0, (void*)1);
|
|
ok(ret, "TlsSetValue(0, 1) failed: %u\n", GetLastError());
|
|
|
|
val = TlsGetValue(0);
|
|
ok(val == (void*)1, "TlsGetValue(0) = %p\n", val);
|
|
}
|
|
}
|
|
|
|
static void test_thread_info(void)
|
|
{
|
|
char buf[4096];
|
|
static const ULONG info_size[] =
|
|
{
|
|
sizeof(THREAD_BASIC_INFORMATION), /* ThreadBasicInformation */
|
|
sizeof(KERNEL_USER_TIMES), /* ThreadTimes */
|
|
sizeof(ULONG), /* ThreadPriority */
|
|
sizeof(ULONG), /* ThreadBasePriority */
|
|
sizeof(ULONG_PTR), /* ThreadAffinityMask */
|
|
sizeof(HANDLE), /* ThreadImpersonationToken */
|
|
sizeof(THREAD_DESCRIPTOR_INFORMATION), /* ThreadDescriptorTableEntry */
|
|
sizeof(BOOLEAN), /* ThreadEnableAlignmentFaultFixup */
|
|
0, /* ThreadEventPair_Reusable */
|
|
sizeof(ULONG_PTR), /* ThreadQuerySetWin32StartAddress */
|
|
sizeof(ULONG), /* ThreadZeroTlsCell */
|
|
sizeof(LARGE_INTEGER), /* ThreadPerformanceCount */
|
|
sizeof(ULONG), /* ThreadAmILastThread */
|
|
sizeof(ULONG), /* ThreadIdealProcessor */
|
|
sizeof(ULONG), /* ThreadPriorityBoost */
|
|
sizeof(ULONG_PTR), /* ThreadSetTlsArrayAddress */
|
|
sizeof(ULONG), /* ThreadIsIoPending */
|
|
sizeof(BOOLEAN), /* ThreadHideFromDebugger */
|
|
/* FIXME: Add remaining classes */
|
|
};
|
|
HANDLE thread;
|
|
ULONG i, status, ret_len;
|
|
|
|
if (!pOpenThread)
|
|
{
|
|
win_skip("OpenThread is not available on this platform\n");
|
|
return;
|
|
}
|
|
|
|
if (!pNtQueryInformationThread)
|
|
{
|
|
win_skip("NtQueryInformationThread is not available on this platform\n");
|
|
return;
|
|
}
|
|
|
|
thread = pOpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, GetCurrentThreadId());
|
|
if (!thread)
|
|
{
|
|
win_skip("THREAD_QUERY_LIMITED_INFORMATION is not supported on this platform\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < sizeof(info_size)/sizeof(info_size[0]); i++)
|
|
{
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
#ifdef __i386__
|
|
if (i == ThreadDescriptorTableEntry)
|
|
{
|
|
CONTEXT ctx;
|
|
THREAD_DESCRIPTOR_INFORMATION *tdi = (void *)buf;
|
|
|
|
ctx.ContextFlags = CONTEXT_SEGMENTS;
|
|
GetThreadContext(GetCurrentThread(), &ctx);
|
|
tdi->Selector = ctx.SegDs;
|
|
}
|
|
#endif
|
|
ret_len = 0;
|
|
status = pNtQueryInformationThread(thread, i, buf, info_size[i], &ret_len);
|
|
if (status == STATUS_NOT_IMPLEMENTED) continue;
|
|
if (status == STATUS_INVALID_INFO_CLASS) continue;
|
|
if (status == STATUS_UNSUCCESSFUL) continue;
|
|
|
|
switch (i)
|
|
{
|
|
case ThreadBasicInformation:
|
|
case ThreadAmILastThread:
|
|
case ThreadPriorityBoost:
|
|
ok(status == STATUS_SUCCESS, "for info %u expected STATUS_SUCCESS, got %08x (ret_len %u)\n", i, status, ret_len);
|
|
break;
|
|
|
|
#ifdef __i386__
|
|
case ThreadDescriptorTableEntry:
|
|
ok(status == STATUS_SUCCESS || broken(status == STATUS_ACCESS_DENIED) /* testbot VM is broken */,
|
|
"for info %u expected STATUS_SUCCESS, got %08x (ret_len %u)\n", i, status, ret_len);
|
|
break;
|
|
#endif
|
|
|
|
case ThreadTimes:
|
|
todo_wine
|
|
ok(status == STATUS_SUCCESS, "for info %u expected STATUS_SUCCESS, got %08x (ret_len %u)\n", i, status, ret_len);
|
|
break;
|
|
|
|
case ThreadAffinityMask:
|
|
case ThreadQuerySetWin32StartAddress:
|
|
todo_wine
|
|
ok(status == STATUS_ACCESS_DENIED, "for info %u expected STATUS_ACCESS_DENIED, got %08x (ret_len %u)\n", i, status, ret_len);
|
|
break;
|
|
|
|
default:
|
|
ok(status == STATUS_ACCESS_DENIED, "for info %u expected STATUS_ACCESS_DENIED, got %08x (ret_len %u)\n", i, status, ret_len);
|
|
break;
|
|
}
|
|
}
|
|
|
|
CloseHandle(thread);
|
|
}
|
|
|
|
static void init_funcs(void)
|
|
{
|
|
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
|
|
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
|
|
|
|
/* Neither Cygwin nor mingW export OpenThread, so do a dynamic check
|
|
so that the compile passes */
|
|
|
|
#define X(f) p##f = (void*)GetProcAddress(hKernel32, #f)
|
|
X(GetThreadPriorityBoost);
|
|
X(OpenThread);
|
|
X(QueueUserWorkItem);
|
|
X(SetThreadIdealProcessor);
|
|
X(SetThreadPriorityBoost);
|
|
X(RegisterWaitForSingleObject);
|
|
X(UnregisterWait);
|
|
X(IsWow64Process);
|
|
X(SetThreadErrorMode);
|
|
X(GetThreadErrorMode);
|
|
X(ActivateActCtx);
|
|
X(CreateActCtxW);
|
|
X(DeactivateActCtx);
|
|
X(GetCurrentActCtx);
|
|
X(ReleaseActCtx);
|
|
|
|
X(CreateThreadpool);
|
|
X(CloseThreadpool);
|
|
X(CreateThreadpoolWork);
|
|
X(SubmitThreadpoolWork);
|
|
X(WaitForThreadpoolWorkCallbacks);
|
|
X(CloseThreadpoolWork);
|
|
|
|
X(GetThreadGroupAffinity);
|
|
X(SetThreadGroupAffinity);
|
|
#undef X
|
|
|
|
#define X(f) p##f = (void*)GetProcAddress(ntdll, #f)
|
|
if (ntdll)
|
|
{
|
|
X(NtQueryInformationThread);
|
|
X(RtlGetThreadErrorMode);
|
|
X(NtSetInformationThread);
|
|
}
|
|
#undef X
|
|
}
|
|
|
|
START_TEST(thread)
|
|
{
|
|
int argc;
|
|
char **argv;
|
|
argc = winetest_get_mainargs( &argv );
|
|
|
|
init_funcs();
|
|
|
|
if (argc >= 3)
|
|
{
|
|
if (!strcmp(argv[2], "sleep"))
|
|
{
|
|
HANDLE hAddrEvents[2];
|
|
create_function_addr_events(hAddrEvents);
|
|
SetEvent(hAddrEvents[0]);
|
|
SetEvent(hAddrEvents[1]);
|
|
Sleep(5000); /* spawned process runs for at most 5 seconds */
|
|
return;
|
|
}
|
|
while (1)
|
|
{
|
|
HANDLE hThread;
|
|
DWORD tid;
|
|
hThread = CreateThread(NULL, 0, threadFunc2, NULL, 0, &tid);
|
|
ok(hThread != NULL, "CreateThread failed, error %u\n",
|
|
GetLastError());
|
|
ok(WaitForSingleObject(hThread, 200) == WAIT_OBJECT_0,
|
|
"Thread did not exit in time\n");
|
|
if (hThread == NULL) break;
|
|
CloseHandle(hThread);
|
|
}
|
|
return;
|
|
}
|
|
|
|
test_thread_info();
|
|
test_reserved_tls();
|
|
test_CreateRemoteThread();
|
|
test_CreateThread_basic();
|
|
test_CreateThread_suspended();
|
|
test_SuspendThread();
|
|
test_TerminateThread();
|
|
test_CreateThread_stack();
|
|
test_thread_priority();
|
|
test_GetThreadTimes();
|
|
test_thread_processor();
|
|
test_GetThreadExitCode();
|
|
#ifdef __i386__
|
|
test_SetThreadContext();
|
|
test_GetThreadSelectorEntry();
|
|
#endif
|
|
test_QueueUserWorkItem();
|
|
test_RegisterWaitForSingleObject();
|
|
test_TLS();
|
|
test_ThreadErrorMode();
|
|
#if (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))) || (defined(_MSC_VER) && defined(__i386__))
|
|
test_thread_fpu_cw();
|
|
#endif
|
|
test_thread_actctx();
|
|
|
|
test_threadpool();
|
|
}
|