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

357 lines
10 KiB
C

// Dynamic test runner for every executable in the runner directory.
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <ntlite/syscall.h>
#define ALL_TESTS_INITIAL_CAPACITY 32u
typedef struct TEST_ENTRY {
char Name[SYS_DIR_NAME_MAX];
} TEST_ENTRY;
// Writes one NUL-terminated string to the console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SysWriteConsole(&req);
}
// Writes one unsigned integer to the console.
static void print_u64(uint64_t v) {
char buf[32];
int i = 0;
if (v == 0) {
print("0");
return;
}
while (v && i < (int)sizeof(buf)) {
buf[i++] = (char)('0' + (v % 10));
v /= 10;
}
while (i--) {
char c[2] = { buf[i], 0 };
print(c);
}
}
// Writes one signed integer to the console.
static void print_i32(int32_t v) {
if (v < 0) {
print("-");
print_u64((uint32_t)(-(v + 1)) + 1u);
return;
}
print_u64((uint32_t)v);
}
// Writes one 32-bit value as hexadecimal.
static void print_hex32(uint32_t v) {
static const char hex[] = "0123456789abcdef";
for (int i = 7; i >= 0; i--) {
char c[2] = { hex[(v >> (i * 4)) & 0xfu], 0 };
print(c);
}
}
// Exits the process without returning to the runtime.
static void exit_now(int code) {
for (;;) SysExitProcess(code);
}
// Converts ASCII to lowercase for stable case-insensitive comparisons.
static char ascii_lower(char c) {
if (c >= 'A' && c <= 'Z') return (char)(c - 'A' + 'a');
return c;
}
// Compares two names case-insensitively, with a case-sensitive tie-breaker.
static int compare_names(const char* a, const char* b) {
size_t i = 0;
for (;;) {
char ca = ascii_lower(a[i]);
char cb = ascii_lower(b[i]);
if (ca != cb) return (unsigned char)ca < (unsigned char)cb ? -1 : 1;
if (ca == '\0') break;
i++;
}
return strcmp(a, b);
}
// Returns whether one character is a path separator.
static bool is_separator(char c) {
return c == '/' || c == '\\';
}
// Copies a SYS_STRING_VIEW into a NUL-terminated fixed buffer.
static bool copy_view(char* out, size_t outSize, SYS_STRING_VIEW view) {
if (!out || outSize == 0 || !view.Data || view.Length + 1 > outSize) return false;
memcpy(out, view.Data, view.Length);
out[view.Length] = '\0';
return true;
}
// Splits an executable path into its directory and basename components.
static bool split_self_path(const char* path, char* dir, size_t dirSize, char* name, size_t nameSize) {
if (!path || !dir || !name || dirSize == 0 || nameSize == 0) return false;
size_t len = strlen(path);
size_t last = (size_t)-1;
for (size_t i = 0; i < len; i++) {
if (is_separator(path[i])) last = i;
}
const char* base = path;
size_t dirLen = 1;
if (last == (size_t)-1) {
if (dirSize < 2) return false;
dir[0] = '.';
dir[1] = '\0';
} else {
dirLen = last;
if (last == 0) dirLen = 1;
if (last >= 2 && path[1] == ':' && is_separator(path[2])) dirLen = last + 1;
if (dirLen + 1 > dirSize) return false;
memcpy(dir, path, dirLen);
dir[dirLen] = '\0';
base = path + last + 1;
}
size_t baseLen = strlen(base);
if (baseLen + 1 > nameSize) return false;
memcpy(name, base, baseLen + 1);
return true;
}
// Returns whether one filename ends in .exe, case-insensitively.
static bool has_exe_suffix(const char* name) {
size_t len = strlen(name);
if (len < 4) return false;
const char* ext = name + len - 4;
return ascii_lower(ext[0]) == '.' &&
ascii_lower(ext[1]) == 'e' &&
ascii_lower(ext[2]) == 'x' &&
ascii_lower(ext[3]) == 'e';
}
// Allocates a test-entry array from user VM.
static uint32_t alloc_entries(uint32_t capacity, TEST_ENTRY** entriesOut) {
void* base = 0;
size_t bytes = (size_t)capacity * sizeof(TEST_ENTRY);
size_t pages = (bytes + 4095u) / 4096u;
if (pages == 0) pages = 1;
SYS_ALLOC_VM_REQUEST req;
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.Protect = SYS_VM_READWRITE;
req.Flags = SYS_VM_FLAG_NONE;
req.PageCount = pages;
req.BaseOut = &base;
uint32_t status = SysAllocVm(&req);
if (status != NTLSTATUS_SUCCESS || !base) {
*entriesOut = 0;
return status ? status : NTLSTATUS_NO_MEMORY;
}
*entriesOut = (TEST_ENTRY*)base;
return NTLSTATUS_SUCCESS;
}
// Grows the test-entry array.
static uint32_t grow_entries(TEST_ENTRY** entries, uint32_t* capacity, uint32_t count) {
uint32_t oldCapacity = *capacity;
uint32_t newCapacity = oldCapacity ? oldCapacity * 2u : ALL_TESTS_INITIAL_CAPACITY;
TEST_ENTRY* next = 0;
uint32_t status = alloc_entries(newCapacity, &next);
if (status != NTLSTATUS_SUCCESS) return status;
if (*entries && count) memcpy(next, *entries, (size_t)count * sizeof(TEST_ENTRY));
if (*entries) SysFreeVm(*entries);
*entries = next;
*capacity = newCapacity;
return NTLSTATUS_SUCCESS;
}
// Sorts test entries in deterministic execution order.
static void sort_entries(TEST_ENTRY* entries, uint32_t count) {
for (uint32_t i = 1; i < count; i++) {
TEST_ENTRY current = entries[i];
uint32_t j = i;
while (j > 0 && compare_names(current.Name, entries[j - 1].Name) < 0) {
entries[j] = entries[j - 1];
j--;
}
entries[j] = current;
}
}
// Builds one child path from the runner directory and test filename.
static bool build_child_path(const char* dir, const char* name, char* out, size_t outSize) {
if (!dir || !name || !out || outSize == 0) return false;
size_t dirLen = strlen(dir);
size_t nameLen = strlen(name);
bool needSep = dirLen > 0 && !is_separator(dir[dirLen - 1]);
size_t total = dirLen + (needSep ? 1u : 0u) + nameLen;
if (total + 1 > outSize) return false;
memcpy(out, dir, dirLen);
size_t pos = dirLen;
if (needSep) out[pos++] = '\\';
memcpy(out + pos, name, nameLen);
out[total] = '\0';
return true;
}
// Collects executable tests from the runner directory.
static uint32_t collect_tests(const char* dir, const char* selfName, TEST_ENTRY** entriesOut, uint32_t* countOut) {
*entriesOut = 0;
*countOut = 0;
uint32_t hDir = 0;
SYS_PATH_HANDLE_REQUEST openReq = { SysSvFromCstr(dir), &hDir };
uint32_t status = SysOpenDir(&openReq);
if (status != NTLSTATUS_SUCCESS) return status;
TEST_ENTRY* entries = 0;
uint32_t count = 0;
uint32_t capacity = 0;
for (;;) {
SYS_DIR_ENTRY entry;
status = SysReadDir(hDir, &entry);
if (status != NTLSTATUS_SUCCESS) break;
if (!entry.Name[0]) continue;
if ((entry.Attr & 0x10u) != 0) continue;
if (!has_exe_suffix(entry.Name)) continue;
if (compare_names(entry.Name, selfName) == 0) continue;
if (count == capacity) {
status = grow_entries(&entries, &capacity, count);
if (status != NTLSTATUS_SUCCESS) {
SysCloseHandle(hDir);
if (entries) SysFreeVm(entries);
return status;
}
}
strncpy(entries[count].Name, entry.Name, sizeof(entries[count].Name) - 1);
entries[count].Name[sizeof(entries[count].Name) - 1] = '\0';
count++;
}
SysCloseHandle(hDir);
sort_entries(entries, count);
*entriesOut = entries;
*countOut = count;
return NTLSTATUS_SUCCESS;
}
// Runs one test process and returns the child's exit code through exitCodeOut.
static uint32_t run_test(const char* path, int32_t* exitCodeOut) {
uint32_t process = 0;
SYS_CREATE_PROCESS_REQUEST req;
req.Path = SysSvFromCstr(path);
req.CommandLine = SysSvFromCstr(path);
req.HandleOut = &process;
uint32_t status = SysCreateProcess(&req);
if (status != NTLSTATUS_SUCCESS) return status;
status = SysWait(process, SYS_WAIT_FLAG_NONE);
if (status != NTLSTATUS_SUCCESS) {
SysCloseHandle(process);
return status;
}
int32_t exitCode = 1;
status = SysGetProcessExitCode(process, &exitCode);
SysCloseHandle(process);
if (status != NTLSTATUS_SUCCESS) return status;
*exitCodeOut = exitCode;
return NTLSTATUS_SUCCESS;
}
// Program entry point.
int main(void) {
print("all_tests: start\n");
char selfPath[SYS_PATH_MAX];
char selfDir[SYS_PATH_MAX];
char selfName[SYS_DIR_NAME_MAX];
SYS_PEB* peb = SysGetPeb();
if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION ||
!copy_view(selfPath, sizeof(selfPath), peb->ImagePath) ||
!split_self_path(selfPath, selfDir, sizeof(selfDir), selfName, sizeof(selfName))) {
print("all_tests: failed to resolve runner path\n");
exit_now(1);
}
TEST_ENTRY* entries = 0;
uint32_t count = 0;
uint32_t status = collect_tests(selfDir, selfName, &entries, &count);
if (status != NTLSTATUS_SUCCESS) {
print("all_tests: failed to enumerate tests status=0x");
print_hex32(status);
print("\n");
exit_now(1);
}
if (count == 0) {
print("all_tests: no test executables found\n");
if (entries) SysFreeVm(entries);
exit_now(1);
}
for (uint32_t i = 0; i < count; i++) {
char childPath[SYS_PATH_MAX];
if (!build_child_path(selfDir, entries[i].Name, childPath, sizeof(childPath))) {
print("all_tests: child path too long: ");
print(entries[i].Name);
print("\n");
SysFreeVm(entries);
exit_now(1);
}
print("all_tests: RUN ");
print(childPath);
print("\n");
int32_t exitCode = 1;
status = run_test(childPath, &exitCode);
if (status != NTLSTATUS_SUCCESS) {
print("all_tests: FAIL ");
print(childPath);
print(" launch/wait status=0x");
print_hex32(status);
print("\n");
SysFreeVm(entries);
exit_now(1);
}
if (exitCode != 0) {
print("all_tests: FAIL ");
print(childPath);
print(" exit=");
print_i32(exitCode);
print("\n");
SysFreeVm(entries);
exit_now(exitCode ? exitCode : 1);
}
print("all_tests: PASS ");
print(childPath);
print("\n");
}
print("all_tests: PASS count=");
print_u64(count);
print("\n");
SysFreeVm(entries);
exit_now(0);
return 0;
}