/* 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 : , ''" stderr lines. */ #include "scr_runtime.h" #include "scr_win_stats.h" #include #include #include #include #include #include #ifndef _WIN32 #include #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, "large Set uses sparse array tail"); 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 \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; }