Files
scriptc/packages/runtime/test/test_lib.c
T
Chris Tate 96f31cbc18 Shrink the array header to 32 bytes
An array's element ops are a registry id in `spare` instead of a pointer
field: descriptors get their id on their first array (emitted descriptors
are writable for it), and a program with more element types than the id
holds keeps the rest in the array's out-of-line storage.

The out-of-line storage (sparse indices, noncanonical numeric properties,
match metadata) is no longer a header field. An array that has it keeps
its pointer in the 8 bytes before its dense allocation and sets
SCR_ARR_HAS_EXT; such an array always has a dense allocation of its own
(inline slots move out, an empty array gets the prefix alone), and every
allocation, reallocation and free of the dense storage accounts for the
prefix.

Loops over elements look the ops up once, and the state lookup below the
dense capacity reads no out-of-line storage.
2026-10-21 13:04:07 -05:00

330 lines
12 KiB
C

/* Oracle test for scr_lib.c (process + sync fs). Run by lib.test.ts with a
* fresh scratch directory as argv[1]; built with ASan + the RC audit, so a
* clean exit also proves the library's ownership contract (borrowed args,
* +1 results, interned argv/platform released by the atexit cleanup).
*
* Error-message fidelity is checked HERE because compiled programs cannot
* observe it: the supported catch form is bindingless and uncaught stderr
* is not compared by the differential harness. Each expected failure is
* routed through scr_exc_print_uncaught(), and lib.test.ts asserts the
* exact "Uncaught <ERRNO>: <text>, <syscall> '<path>'" stderr lines.
*/
#include "scr_runtime.h"
#include "scr_win_stats.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#ifndef _WIN32
#include <sys/wait.h>
/* Sparse entries live in the array's out-of-line storage. */
static size_t arr_sparse_len(const ScrArr *a) { return scr_arr_ext_of(a) ? scr_arr_ext_of(a)->sparse_len : 0; }
#endif
static int checks = 0;
static int failures = 0;
static void check(bool ok, const char *name) {
checks++;
if (!ok) {
failures++;
printf("FAIL %s\n", name);
}
}
static ScrStr *S(const char *s) { return scr_str_new(s, strlen(s)); }
static bool str_is(const ScrStr *s, const char *want) {
return s && s->len == strlen(want) && memcmp(s->data, want, s->len) == 0;
}
/* Perform-op helpers so every expected failure follows one shape: op runs,
* the exception must be pending, and the payload prints to stderr (which
* releases it and clears the cell). */
static void expect_pending(const char *name) {
check(scr_exc_pending(), name);
if (scr_exc_pending()) scr_exc_print_uncaught();
}
static void test_large_set_materialization(void) {
const size_t count = ((size_t)1 << 20) + 3;
ScrMap *set = scr_map_new(SCR_MAP_KEY_F64, SCR_MAP_VAL_F64, NULL);
for (size_t i = 0; i < count; i++) scr_map_set_f64_f64(set, (double)i, 0);
scr_map_delete_f64(set, 1);
ScrArr *values = scr_set_to_arr_f64(set);
check(values->len == count - 1 && values->cap >= values->len && arr_sparse_len(values) == 0,
"large Set materializes into dense storage past the 2^20 cutoff");
check(scr_arr_get_f64(values, 0) == 0 && scr_arr_get_f64(values, 1) == 2,
"large Set materialization skips tombstones in order");
check(scr_arr_get_f64(values, (double)count - 2) == (double)count - 1,
"large Set preserves its final element");
scr_map_release(set);
scr_arr_release(values);
}
static void test_read_storage(void) {
uint8_t *owned = malloc(3);
memcpy(owned, "abc", 3);
ScrBytes *bytes = scr_bytes_take_data(owned, 3);
check(bytes->data == owned && bytes->backing == NULL && !bytes->external,
"adopted bytes keep their owned allocation");
ScrBytes *view = scr_bytes_subarray(bytes, 1, 3);
scr_bytes_release(bytes);
check(view->len == 2 && memcmp(view->data, "bc", 2) == 0, "view retains adopted storage");
scr_bytes_release(view);
bytes = scr_bytes_take_data(NULL, 0);
view = scr_bytes_subarray(bytes, 0, 0);
check(bytes->data != NULL && view->len == 0, "empty owned bytes support views");
scr_bytes_release(bytes);
scr_bytes_release(view);
#ifndef _WIN32
/* A pipe has no useful size hint and short reads need not mean EOF.
* The producer exceeds pipe capacity, exercising repeated growth. */
for (int text = 0; text < 2; text++) {
int fds[2];
if (pipe(fds) != 0) { check(false, "pipe setup"); return; }
pid_t child = fork();
if (child == 0) {
close(fds[0]);
char chunk[997];
memset(chunk, 'x', sizeof chunk);
size_t sent = 0;
while (sent < 70001) {
size_t count = 70001 - sent;
if (count > sizeof chunk) count = sizeof chunk;
ssize_t written = write(fds[1], chunk, count);
if (written <= 0) _exit(2);
sent += (size_t)written;
}
close(fds[1]);
_exit(0);
}
close(fds[1]);
if (child < 0) { close(fds[0]); check(false, "pipe producer"); return; }
size_t len;
const uint8_t *data;
ScrStr *string = NULL;
bytes = NULL;
if (text) {
string = scr_fs_read_fd((double)fds[0]);
len = string ? string->len : 0;
data = string ? (uint8_t *)string->data : NULL;
check(string && string->data[string->len] == '\0', "pipe string terminates");
} else {
bytes = scr_fs_read_fd_bytes((double)fds[0]);
len = bytes ? bytes->len : 0;
data = bytes ? bytes->data : NULL;
}
bool matches = len == 70001;
for (size_t i = 0; i < len; i++) if (data[i] != 'x') matches = false;
check(matches && !scr_exc_pending(), "pipe reads all chunks into final storage");
char extra;
check(read(fds[0], &extra, 1) == 0, "caller still owns pipe descriptor");
close(fds[0]);
int status = 0;
check(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0,
"pipe producer completed");
scr_str_release(string);
scr_bytes_release(bytes);
}
#endif
}
int main(int argc, char **argv) {
scr_init();
scr_lib_init(argc, argv);
if (argc >= 3 && strcmp(argv[argc - 1], "--argv-collision-probe") == 0) {
ScrArr *args = scr_process_argv();
double n = scr_arr_len(args);
printf("%.0f\n", n);
for (double i = 2; i < n; i++) {
ScrStr *arg = scr_arr_get_ref(args, i);
printf("%s\n", arg->data);
scr_str_release(arg);
}
scr_arr_release(args);
return 0;
}
if (argc < 2) {
fputs("usage: test_lib <scratch-dir>\n", stderr);
return 2;
}
test_large_set_materialization();
test_read_storage();
const char *dir = argv[1];
char pb[4096];
ScrStr *p; /* current path operand */
ScrStr *t;
/* IO_REPARSE_TAG_MOUNT_POINT is shared by directory junctions and volume
* mount points. Pin the libuv-compatible payload distinction without
* requiring this portable unit test to provision a Windows volume. */
static const uint16_t junction_root[] = {'\\', '?', '?', '\\', 'C', ':'};
static const uint16_t junction_path[] = {
'\\', '?', '?', '\\', 'd', ':', '\\', 'w', 'o', 'r', 'k'
};
static const uint16_t volume_mount[] = {
'\\', '?', '?', '\\', 'V', 'o', 'l', 'u', 'm', 'e', '{', '1', '}', '\\'
};
static const uint16_t drive_relative[] = {
'\\', '?', '?', '\\', 'C', ':', 'r', 'e', 'l'
};
static const uint16_t unc_mount[] = {
'\\', '?', '?', '\\', 'U', 'N', 'C', '\\', 's', 'h', 'a', 'r', 'e'
};
check(scr_win_stats_mount_target_is_junction(
junction_root, sizeof junction_root / sizeof junction_root[0]),
"drive-root mount payload is a junction");
check(scr_win_stats_mount_target_is_junction(
junction_path, sizeof junction_path / sizeof junction_path[0]),
"drive-path mount payload is a junction");
check(!scr_win_stats_mount_target_is_junction(
volume_mount, sizeof volume_mount / sizeof volume_mount[0]),
"volume mount payload is followed");
check(!scr_win_stats_mount_target_is_junction(
drive_relative, sizeof drive_relative / sizeof drive_relative[0]),
"drive-relative mount payload is followed");
check(!scr_win_stats_mount_target_is_junction(
unc_mount, sizeof unc_mount / sizeof unc_mount[0]),
"UNC mount payload is followed");
/* process.argv: one interned array — identity, shape, contents. */
ScrArr *a1 = scr_process_argv();
ScrArr *a2 = scr_process_argv();
check(a1 == a2, "argv interned identity");
check(scr_arr_len(a1) == (double)argc + 1, "argv length = argc + 1");
t = scr_arr_get_ref(a1, 0);
check(str_is(t, "scriptc"), "argv[0] is 'scriptc'");
scr_str_release(t);
t = scr_arr_get_ref(a1, 2);
check(str_is(t, dir), "argv[2] is the first real argument");
scr_str_release(t);
scr_arr_release(a1);
scr_arr_release(a2);
/* process.platform: interned, matches the compile-time target. */
ScrStr *pl1 = scr_process_platform();
ScrStr *pl2 = scr_process_platform();
check(pl1 == pl2, "platform interned identity");
#if defined(__APPLE__)
check(str_is(pl1, "darwin"), "platform is darwin");
#elif defined(__linux__)
check(str_is(pl1, "linux"), "platform is linux");
#endif
scr_str_release(pl1);
scr_str_release(pl2);
/* Historical local-mean offsets include seconds in the timezone data,
* while Date#getTimezoneOffset exposes whole minutes. */
setenv("TZ", "America/Chicago", 1);
tzset();
double historical_offset = scr_date_get_timezone_offset(-5364662400000.0);
check(historical_offset == trunc(historical_offset),
"historical timezone offset truncates to whole minutes");
/* process.cwd: fresh absolute path. */
t = scr_process_cwd();
check(t->len > 0 && t->data[0] == '/', "cwd is absolute");
scr_str_release(t);
/* process.env: +1 fresh string when set, NULL when absent, never a
* throw (the compiler builds the string|undefined union from this). */
setenv("SCR_TEST_ENV_VAR", "libtest", 1);
p = S("SCR_TEST_ENV_VAR");
t = scr_env_get(p);
check(str_is(t, "libtest"), "env get returns the set value");
scr_str_release(t);
scr_str_release(p);
p = S("SCR_TEST_ENV_VAR_DEFINITELY_NOT_SET");
check(scr_env_get(p) == NULL, "absent env var is NULL");
check(!scr_exc_pending(), "env get never throws");
scr_str_release(p);
/* write → exists → read roundtrip (unicode bytes), then append. */
snprintf(pb, sizeof pb, "%s/a.txt", dir);
p = S(pb);
ScrStr *content = S("h\xc3\xa9llo \xf0\x9f\x8c\x8d\n"); /* "héllo 🌍\n" */
scr_fs_write_file(p, content);
check(!scr_exc_pending(), "write succeeds");
check(scr_fs_exists(p), "exists after write");
t = scr_fs_read_file(p);
check(!scr_exc_pending(), "read succeeds");
check(t && t->len == content->len && memcmp(t->data, content->data, t->len) == 0,
"read returns written bytes");
scr_str_release(t);
ScrStr *more = S("more");
scr_fs_append_file(p, more);
check(!scr_exc_pending(), "append succeeds");
t = scr_fs_read_file(p);
check(t && t->len == content->len + 4, "append extends the file");
scr_str_release(t);
scr_str_release(more);
scr_str_release(content);
/* readdir membership, then rm the file. */
ScrStr *dirs = S(dir);
ScrArr *names = scr_fs_readdir(dirs);
check(!scr_exc_pending(), "readdir succeeds");
t = S("a.txt");
check(scr_arr_includes_ref(names, t), "readdir lists a.txt");
scr_str_release(t);
scr_arr_release(names);
scr_fs_rm(p);
check(!scr_exc_pending(), "rm succeeds");
check(!scr_fs_exists(p), "gone after rm");
scr_str_release(p);
/* mkdir → rmdir roundtrip. */
snprintf(pb, sizeof pb, "%s/sub", dir);
p = S(pb);
scr_fs_mkdir(p);
check(!scr_exc_pending(), "mkdir succeeds");
check(scr_fs_exists(p), "dir exists");
/* Expected failures, in a fixed order lib.test.ts asserts on stderr. */
/* 1: read of a missing file → ENOENT open */
snprintf(pb, sizeof pb, "%s/missing.txt", dir);
t = S(pb);
check(!scr_fs_exists(t), "existsSync false, no throw");
check(!scr_exc_pending(), "existsSync never throws");
ScrStr *dummy = scr_fs_read_file(t);
check(dummy == NULL, "failed read returns dummy NULL");
expect_pending("read missing throws");
/* 2: rm of a missing path → ENOENT lstat */
scr_fs_rm(t);
expect_pending("rm missing throws");
/* 3: rmdir of a missing path → ENOENT rmdir */
scr_fs_rmdir(t);
expect_pending("rmdir missing throws");
/* 4: readdir of a missing path → ENOENT scandir */
names = scr_fs_readdir(t);
check(names == NULL, "failed readdir returns dummy NULL");
expect_pending("readdir missing throws");
scr_str_release(t);
/* 5: mkdir of an existing path → EEXIST mkdir */
scr_fs_mkdir(p);
expect_pending("mkdir existing throws");
/* 6: mkdir under a missing parent → ENOENT mkdir */
snprintf(pb, sizeof pb, "%s/nope/deep", dir);
t = S(pb);
scr_fs_mkdir(t);
expect_pending("mkdir missing parent throws");
scr_str_release(t);
/* 7: rm of a directory → EISDIR rm */
scr_fs_rm(p);
expect_pending("rm of a directory throws");
scr_fs_rmdir(p);
check(!scr_exc_pending(), "rmdir succeeds");
scr_str_release(p);
scr_str_release(dirs);
printf("%d/%d checks passed\n", checks - failures, checks);
return failures ? 1 : 0;
}