Files
scriptc/packages/runtime/test/test_lib.c
T
Malte Ubl 1a14263b5f Move rarely used array storage out of line
Every ScrArr carried sixteen words, seven of them (sparse slots, their
length and capacity, noncanonical numeric properties with length and
capacity, and match-result metadata) used only by sparse arrays, arrays
with numeric non-index properties and regex match results. They now live in
a ScrArrExt the array points at, allocated on first use: a dense array
shrinks from 128 to 80 bytes, and a cycle-capable one (with its 16-byte
collector header) from 144 to 96.

Runtime readers go through a shared empty ext; writers allocate it. The
backend mirror loses the seven fields, tagged-template arrays are emitted
with a NULL ext, live stream commits swap the ext pointer (which swapped all
seven fields before), and the cached typed-view refresh swaps sparse slots
and properties through scr_arr_swap_indexed_ext, keeping each array's
metadata as before. The program-object ABI marker moves to
scr_runtime_abi_v11.

A corpus program (4610-array-ext-dyn-refresh.ts, with its order baseline) pins numeric non-index properties of a dynamically typed array through its refreshed typed view, sparse slots written directly and through a live reference, truncation, and regex match metadata read directly, through `any` and after a copy.
2026-10-11 09:38:27 -07: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 a->ext ? a->ext->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, NULL, 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;
}