Files
Chris Tate 92032a1b11 Shrink Map and Set headers and small entry arrays
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.
2026-10-16 07:27:17 -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 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;
}