mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-20 05:40:17 +08:00
A Map or Set header held six adapter pointers, the union key table and size_t counts: 168 bytes, 176 in its size class. The adapters and union key kinds now live in an immutable per-type ScrMapOps descriptor that the compiler emits once per collection type, a traced byte replaces the two trace-pointer tests, and the counts and capacities are 32-bit. The header is 88 bytes. The entries array starts with one slot and grows to four, then doubles, and maps of up to 8 entries scan their cached hashes instead of keeping a bucket table. Number-key maps still move to the direct index from four entries. One million maps held at once (RSS per map): empty 188 -> 100 bytes, 1 entry 286 -> 125, 4 entries 286 -> 198, 8 entries 450 -> 295.
330 lines
12 KiB
C
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);
|
|
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;
|
|
}
|