Files
nt-lite/code/user/tests/thread_name_smoke/thread_name_smoke.c
T

169 lines
6.0 KiB
C

// Native thread-description smoke test.
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/status.h>
#define TEST_NAME_MAIN "thread-name-main"
#define TEST_NAME_WORKER "thread-name-worker"
#define TEST_MAX_THREADS 16u
#define TEST_EVENT_AUTO_RESET 0u
// Writes one NUL-terminated string to the console.
static void print(const char* text) {
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) };
(void)SysWriteConsole(&request);
}
// Terminates the test immediately.
static void exit_now(int32_t code) {
for (;;) SysExitProcess(code);
}
// Fails the test with one diagnostic.
static void fail(const char* text) {
print("thread_name_smoke: ");
print(text);
print("\n");
exit_now(1);
}
// Sets a description on the supplied native thread handle.
static KSTATUS set_name(SYS_HANDLE thread, const char* name) {
SYS_SET_THREAD_NAME_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.ThreadHandle = thread;
request.Name = SysSvFromCstr(name);
return SysSetThreadName(&request);
}
// A suspended worker only needs to prove its named handle remains runnable.
static void worker(uintptr_t argument) {
(void)argument;
SysExitThread(0);
}
// A detached worker proves running threads do not need a returned handle or TID.
static void detached_worker(uintptr_t argument) {
(void)SysSignalEvent((SYS_HANDLE)argument);
SysExitThread(0);
}
// Runs the thread-description ABI smoke test.
int main(void) {
print("thread_name_smoke: start\n");
if (set_name(SYS_HANDLE_INVALID, TEST_NAME_MAIN) != NTLSTATUS_SUCCESS)
fail("set current name failed");
// Native thread descriptions accept well-formed UTF-8 and reject malformed bytes.
static const char unicodeName[] = "thread-\xC3\xA9";
if (set_name(SYS_HANDLE_INVALID, unicodeName) != NTLSTATUS_SUCCESS)
fail("valid UTF-8 thread name rejected");
static const char invalidName[] = { 'b', (char)0xC0, (char)0xAF, 0 };
if (set_name(SYS_HANDLE_INVALID, invalidName) != NTLSTATUS_INVALID_PARAMETER)
fail("overlong UTF-8 thread name accepted");
SYS_SET_THREAD_NAME_REQUEST nulRequest;
memset(&nulRequest, 0, sizeof(nulRequest));
nulRequest.Size = sizeof(nulRequest);
nulRequest.ThreadHandle = SYS_HANDLE_INVALID;
static const char nulName[] = { 'a', 0, 'b' };
nulRequest.Name = SysSvFromParts(nulName, sizeof(nulName));
if (SysSetThreadName(&nulRequest) != NTLSTATUS_INVALID_PARAMETER)
fail("embedded NUL thread name accepted");
if (set_name(SYS_HANDLE_INVALID, TEST_NAME_MAIN) != NTLSTATUS_SUCCESS)
fail("restore current thread name failed");
char name[SYS_THREAD_NAME_MAX];
size_t length = 0;
KSTATUS status = SysQueryThreadName(SYS_HANDLE_INVALID, name, sizeof(name), &length);
if (status != NTLSTATUS_SUCCESS || length != sizeof(TEST_NAME_MAIN) - 1u ||
strcmp(name, TEST_NAME_MAIN) != 0)
fail("query current name mismatch");
size_t required = 0;
status = SysQueryThreadName(SYS_HANDLE_INVALID, (char*)0, 0, &required);
if (status != NTLSTATUS_BUFFER_TOO_SMALL || required != sizeof(TEST_NAME_MAIN) - 1u)
fail("length-only query contract mismatch");
SYS_HANDLE thread = SYS_HANDLE_INVALID;
uint32_t tid = 0;
SYS_CREATE_THREAD_REQUEST create;
memset(&create, 0, sizeof(create));
create.Size = sizeof(create);
create.Entry = (uintptr_t)worker;
create.Priority = 1u;
create.Flags = SYS_CREATE_THREAD_FLAG_SUSPENDED;
create.ThreadHandleOut = &thread;
create.ThreadIdOut = &tid;
status = SysCreateThread(&create);
if (status != NTLSTATUS_SUCCESS || thread == SYS_HANDLE_INVALID || tid == 0)
fail("create suspended worker failed");
status = set_name(thread, TEST_NAME_WORKER);
if (status != NTLSTATUS_SUCCESS)
fail("set worker name failed");
memset(name, 0, sizeof(name));
length = 0;
status = SysQueryThreadName(thread, name, sizeof(name), &length);
if (status != NTLSTATUS_SUCCESS || length != sizeof(TEST_NAME_WORKER) - 1u ||
strcmp(name, TEST_NAME_WORKER) != 0)
fail("query worker name mismatch");
SYS_THREAD_INFO threads[TEST_MAX_THREADS];
size_t threadCount = 0;
status = SysGetThreadList(SysGetCurrentPid(), threads, TEST_MAX_THREADS, &threadCount);
if (status != NTLSTATUS_SUCCESS)
fail("thread enumeration failed");
bool foundWorker = false;
size_t visible = threadCount < TEST_MAX_THREADS ? threadCount : TEST_MAX_THREADS;
for (size_t i = 0; i < visible; ++i) {
if (threads[i].ThreadId == tid) {
if (strcmp(threads[i].Name, TEST_NAME_WORKER) != 0)
fail("thread-list name mismatch");
foundWorker = true;
break;
}
}
if (!foundWorker)
fail("worker missing from thread list");
uint32_t previousSuspendCount = 0;
status = SysResumeThreadAndWait(thread, &previousSuspendCount);
if (status != NTLSTATUS_SUCCESS || previousSuspendCount != 1u)
fail("resume-and-wait worker failed");
(void)SysCloseHandle(thread);
SYS_HANDLE detachedReady = SYS_HANDLE_INVALID;
status = SysCreateEvent(TEST_EVENT_AUTO_RESET, 0, &detachedReady);
if (status != NTLSTATUS_SUCCESS)
fail("create detached-ready event failed");
memset(&create, 0, sizeof(create));
create.Size = sizeof(create);
create.Entry = (uintptr_t)detached_worker;
create.Argument = (uintptr_t)detachedReady;
create.Priority = 1u;
create.Flags = SYS_CREATE_THREAD_FLAG_NONE;
status = SysCreateThread(&create);
if (status != NTLSTATUS_SUCCESS)
fail("create detached worker failed");
status = SysWait(detachedReady, SYS_WAIT_FLAG_NONE);
if (status != NTLSTATUS_SUCCESS)
fail("detached worker did not run");
(void)SysCloseHandle(detachedReady);
print("thread_name_smoke: PASS\n");
exit_now(0);
return 0;
}