/* Unit tests for the array runtime (scr_array.c). Built with ASan + * -DSCR_RC_AUDIT by array.test.ts, which also asserts the trap modes abort: * * (no args) run all assertions; prints "N/N cases passed" * --crash-get-oob read past the end → RangeError + abort() * --crash-get-frac read a fractional index → RangeError + abort() * --crash-hole-read read a hole → RangeError + abort() * --crash-pop-empty pop an empty array → RangeError + abort() * * The RC-recursion cases (array of strings, array of arrays of strings) * assert live counts directly: releasing the outer array must release * every reachable element exactly once. */ #include "../src/scr_runtime.h" #include #include #include #include #ifdef SCR_RC_AUDIT long scr_str_live_count(void); /* provided by scr_string.c */ long scr_arr_live_count(void); /* provided by scr_array.c */ #endif static long total = 0, failed = 0; static void check(bool ok, const char *what) { total++; if (!ok) { failed++; fprintf(stderr, "FAIL: %s\n", what); } } static void check_f64(double got, double want, const char *what) { check(got == want, what); if (got != want) fprintf(stderr, " got %g want %g\n", got, want); } static void test_f64_basics(void) { ScrArr *a = scr_arr_new(SCR_ELEM_F64, 0); check_f64(scr_arr_len(a), 0, "new array is empty"); check_f64(scr_arr_push_f64(a, 1.5), 1, "push returns new length"); check_f64(scr_arr_push_f64(a, -0.0), 2, "push returns new length (2)"); check_f64(scr_arr_get_f64(a, 0), 1.5, "get_f64[0]"); check(scr_arr_get_f64(a, 1) == 0 && signbit(scr_arr_get_f64(a, 1)), "-0 round-trips through a slot"); scr_arr_set_f64(a, 0, 7); check_f64(scr_arr_get_f64(a, 0), 7, "set_f64 replaces"); scr_arr_set_f64(a, 2, 9); /* i == len appends */ check_f64(scr_arr_len(a), 3, "set at len appends"); check_f64(scr_arr_get_f64(a, 2), 9, "appended value readable"); check_f64(scr_arr_pop_f64(a), 9, "pop returns last"); check_f64(scr_arr_len(a), 2, "pop shrinks"); /* NaN round-trips bit-exactly through the uint64_t slot. */ scr_arr_push_f64(a, 0.0 / 0.0); check(scr_arr_get_f64(a, 2) != scr_arr_get_f64(a, 2), "NaN round-trips"); /* growth across many appends */ for (double i = 0; i < 1000; i++) scr_arr_push_f64(a, i); check_f64(scr_arr_len(a), 1003, "1000 pushes grow"); check_f64(scr_arr_get_f64(a, 1002), 999, "last survives growth"); check_f64(scr_arr_get_f64(a, 0), 7, "first survives growth"); scr_arr_release(a); } static void test_bool(void) { ScrArr *a = scr_arr_new(SCR_ELEM_BOOL, 2); scr_arr_push_bool(a, true); scr_arr_push_bool(a, false); check(scr_arr_get_bool(a, 0) == true, "get_bool true"); check(scr_arr_get_bool(a, 1) == false, "get_bool false"); scr_arr_set_bool(a, 0, false); check(scr_arr_get_bool(a, 0) == false, "set_bool replaces"); check(scr_arr_pop_bool(a) == false, "pop_bool"); scr_arr_release(a); } static void test_unshift_reverse(void) { #ifdef SCR_RC_AUDIT long strings0 = scr_str_live_count(); long arrays0 = scr_arr_live_count(); #endif /* The emitter applies variadic unshift arguments right-to-left. */ ScrArr *a = scr_arr_new(SCR_ELEM_F64, 0); scr_arr_push_f64(a, 3); scr_arr_push_f64(a, 4); check_f64(scr_arr_unshift_f64(a, 2), 3, "unshift f64 grows"); check_f64(scr_arr_unshift_f64(a, 1), 4, "variadic-style unshift length"); check_f64(scr_arr_get_f64(a, 0), 1, "unshift preserves arg order [0]"); check_f64(scr_arr_get_f64(a, 1), 2, "unshift preserves arg order [1]"); ScrArr *same = scr_arr_reverse(a); check(same == a, "reverse returns receiver identity"); check_f64(scr_arr_get_f64(a, 0), 4, "reverse mutates first slot"); check_f64(scr_arr_get_f64(a, 3), 1, "reverse mutates last slot"); scr_arr_release(same); /* reverse's returned +1 */ ScrArr *front = scr_arr_new(SCR_ELEM_F64, 0); scr_arr_push_f64(front, 8); scr_arr_push_f64(front, 9); check_f64(scr_arr_unshift_spread(a, front), 6, "unshift spread length"); check_f64(scr_arr_get_f64(a, 0), 8, "unshift spread first"); check_f64(scr_arr_get_f64(a, 1), 9, "unshift spread second"); scr_arr_release(front); scr_arr_release(a); /* Self-spread snapshots the original block before front insertion. */ ScrArr *self = scr_arr_new(SCR_ELEM_BOOL, 0); scr_arr_push_bool(self, true); scr_arr_push_bool(self, false); check_f64(scr_arr_unshift_spread(self, self), 4, "unshift self-spread length"); check(scr_arr_get_bool(self, 0) && !scr_arr_get_bool(self, 1), "unshift self-spread copied prefix"); check(scr_arr_get_bool(self, 2) && !scr_arr_get_bool(self, 3), "unshift self-spread kept original tail"); scr_arr_release(self); /* Spread retains ref elements; either array may then die first. */ ScrArr *src = scr_arr_new(SCR_ELEM_STR, 0); scr_arr_push_ref(src, scr_str_new("front", 5)); ScrArr *dst = scr_arr_new(SCR_ELEM_STR, 0); scr_arr_push_ref(dst, scr_str_new("back", 4)); scr_arr_unshift_spread(dst, src); ScrStr *copied = (ScrStr *)scr_arr_get_ref(dst, 0); check(copied->rc == 3, "unshift spread retained ref element"); scr_str_release(copied); scr_arr_release(src); ScrStr *still = (ScrStr *)scr_arr_get_ref(dst, 0); check(strcmp(still->data, "front") == 0, "unshift spread ref survives source release"); scr_str_release(still); scr_arr_release(dst); #ifdef SCR_RC_AUDIT check(scr_str_live_count() == strings0, "unshift/reverse: no strings leaked"); check(scr_arr_live_count() == arrays0, "unshift/reverse: no arrays leaked"); #endif } static void test_str_rc(void) { #ifdef SCR_RC_AUDIT long strings0 = scr_str_live_count(); long arrays0 = scr_arr_live_count(); #endif ScrArr *a = scr_arr_new(SCR_ELEM_STR, 0); scr_arr_push_ref(a, scr_str_new("one", 3)); /* ownership moves in */ scr_arr_push_ref(a, scr_str_new("two", 3)); /* get_ref returns +1: the array and the caller each own a reference. */ ScrStr *s = (ScrStr *)scr_arr_get_ref(a, 0); check(s->rc == 2, "get_ref retained"); check(strcmp(s->data, "one") == 0, "get_ref content"); scr_str_release(s); /* set_ref releases the replaced element and owns the new one. */ scr_arr_set_ref(a, 0, scr_str_new("uno", 3)); ScrStr *r = (ScrStr *)scr_arr_get_ref(a, 0); check(strcmp(r->data, "uno") == 0, "set_ref replaced content"); scr_str_release(r); /* pop_ref transfers ownership out: rc unchanged, array no longer owns. */ ScrStr *popped = (ScrStr *)scr_arr_pop_ref(a); check(popped->rc == 1, "pop_ref transfers ownership"); check(strcmp(popped->data, "two") == 0, "pop_ref content"); scr_str_release(popped); /* immortal literals in arrays: retain/release must stay no-ops */ static struct { size_t rc; size_t len; size_t cap; char data[4]; } lit = {SIZE_MAX, 3, 3, "lit"}; scr_arr_push_ref(a, (ScrStr *)&lit); ScrStr *l = (ScrStr *)scr_arr_get_ref(a, 1); check(l->rc == SIZE_MAX, "immortal element stays immortal"); scr_arr_release(a); /* must release "uno" (and skip the literal) */ #ifdef SCR_RC_AUDIT check(scr_str_live_count() == strings0, "no strings leaked"); check(scr_arr_live_count() == arrays0, "no arrays leaked"); #endif } static void test_nested_rc(void) { #ifdef SCR_RC_AUDIT long strings0 = scr_str_live_count(); long arrays0 = scr_arr_live_count(); #endif /* [[ "a" ], [ "b", "c" ]] — releasing the outer array must cascade. */ ScrArr *outer = scr_arr_new(SCR_ELEM_ARR, 0); ScrArr *row0 = scr_arr_new(SCR_ELEM_STR, 0); scr_arr_push_ref(row0, scr_str_new("a", 1)); scr_arr_push_ref(outer, row0); ScrArr *row1 = scr_arr_new(SCR_ELEM_STR, 0); scr_arr_push_ref(row1, scr_str_new("b", 1)); scr_arr_push_ref(row1, scr_str_new("c", 1)); scr_arr_push_ref(outer, row1); ScrArr *row = (ScrArr *)scr_arr_get_ref(outer, 1); check(row->rc == 2, "nested get_ref retained"); check_f64(scr_arr_len(row), 2, "inner length"); scr_arr_release(row); scr_arr_release(outer); #ifdef SCR_RC_AUDIT check(scr_str_live_count() == strings0, "nested: no strings leaked"); check(scr_arr_live_count() == arrays0, "nested: no arrays leaked"); #endif } static void test_index_of_includes(void) { /* f64: indexOf is strict equality (NaN never matches, -0 == 0); * includes is SameValueZero (NaN matches NaN). */ ScrArr *a = scr_arr_new(SCR_ELEM_F64, 0); scr_arr_push_f64(a, 1); scr_arr_push_f64(a, 0.0 / 0.0); scr_arr_push_f64(a, -0.0); check_f64(scr_arr_index_of_f64(a, 1), 0, "indexOf f64 hit"); check_f64(scr_arr_index_of_f64(a, 2), -1, "indexOf f64 miss"); check_f64(scr_arr_index_of_f64(a, 0.0 / 0.0), -1, "indexOf NaN never matches"); check(scr_arr_includes_f64(a, 0.0 / 0.0), "includes NaN matches NaN"); check_f64(scr_arr_index_of_f64(a, 0.0), 2, "indexOf 0 matches -0"); check(scr_arr_includes_f64(a, 0.0), "includes 0 matches -0"); check(!scr_arr_includes_f64(a, 5), "includes miss"); scr_arr_release(a); /* bool by value. */ ScrArr *b = scr_arr_new(SCR_ELEM_BOOL, 0); scr_arr_push_bool(b, false); scr_arr_push_bool(b, true); check_f64(scr_arr_index_of_bool(b, true), 1, "indexOf bool"); check(scr_arr_includes_bool(b, false), "includes bool"); scr_arr_release(b); /* strings by CONTENT; needle borrowed (rc unchanged, released by us). */ ScrArr *s = scr_arr_new(SCR_ELEM_STR, 0); scr_arr_push_ref(s, scr_str_new("aa", 2)); scr_arr_push_ref(s, scr_str_new("bb", 2)); ScrStr *needle = scr_str_new("bb", 2); check_f64(scr_arr_index_of_ref(s, needle), 1, "indexOf string by content"); check(scr_arr_includes_ref(s, needle), "includes string by content"); check(needle->rc == 1, "indexOf/includes borrow the needle"); scr_str_release(needle); scr_arr_release(s); /* nested arrays by reference identity. */ ScrArr *outer = scr_arr_new(SCR_ELEM_ARR, 0); ScrArr *inner = scr_arr_new(SCR_ELEM_F64, 0); ScrArr *other = scr_arr_new(SCR_ELEM_F64, 0); scr_arr_push_ref(outer, scr_arr_retain(inner)); check_f64(scr_arr_index_of_ref(outer, inner), 0, "indexOf array by identity"); check_f64(scr_arr_index_of_ref(outer, other), -1, "structurally-equal array misses"); scr_arr_release(inner); scr_arr_release(other); scr_arr_release(outer); } /* ── SCR_ELEM_REF: a mock "record" the runtime cannot lay out ───────── * Acyclic flavor: 1-word rc header, retain/release via the stored fn ptrs * (the compiler's `_v` adapters stand in). Cyclic flavor: allocated with a * collector header whose trace visits an owner ARRAY slot — the record can * point back at the array holding it, the REF cycle case. */ typedef struct MockRec { size_t rc; int value; ScrArr *owner; /* cyclic flavor only: traced edge back at an array */ } MockRec; static long mock_live = 0; static void *mock_retain(void *p) { MockRec *r = (MockRec *)p; if (r->rc != SIZE_MAX) r->rc++; return r; } static void mock_release(void *p) { MockRec *r = (MockRec *)p; if (!r || r->rc == SIZE_MAX) return; if (--r->rc == 0) { mock_live--; free(r); } } static MockRec *mock_new(int value) { MockRec *r = calloc(1, sizeof *r); r->rc = 1; r->value = value; mock_live++; return r; } static void mock_trace(void *p, ScrTraceVisit visit, void *ctx) { MockRec *r = (MockRec *)p; if (r->owner) visit(r->owner, ctx); } static void mock_gcfree(void *p) { /* trace visits owner (headered); nothing else refcounted to release */ mock_live--; scr_cyc_free(p); } static void *mock_cyc_retain(void *p) { MockRec *r = (MockRec *)p; if (r->rc != SIZE_MAX) { r->rc++; scr_cyc_mark_live(r); } return r; } static void mock_cyc_release(void *p) { MockRec *r = (MockRec *)p; if (!r || r->rc == SIZE_MAX) return; if (--r->rc == 0) { scr_cyc_on_dead(r); if (r->owner) scr_arr_release(r->owner); mock_live--; scr_cyc_free(r); } else { scr_cyc_on_release(r); } } static MockRec *mock_cyc_new(int value) { MockRec *r = scr_cyc_alloc(sizeof *r, &mock_trace, &mock_gcfree); r->rc = 1; r->value = value; mock_live++; return r; } static void test_ref_elements(void) { #ifdef SCR_RC_AUDIT long arrays0 = scr_arr_live_count(); #endif ScrArr *a = scr_arr_new_ref(&mock_retain, &mock_release, NULL, 0); scr_arr_push_ref(a, mock_new(1)); /* ownership moves in */ scr_arr_push_ref(a, mock_new(2)); check_f64(scr_arr_len(a), 2, "ref push grows"); /* get_ref retains through the stored fn ptr. */ MockRec *r = (MockRec *)scr_arr_get_ref(a, 0); check(r->rc == 2, "ref get_ref retained via elem_retain"); check(r->value == 1, "ref get_ref content"); mock_release(r); /* set_ref releases the replaced element through elem_release. */ scr_arr_set_ref(a, 0, mock_new(10)); check(mock_live == 2, "ref set_ref released the old element"); MockRec *rr = (MockRec *)scr_arr_get_ref(a, 0); check(rr->value == 10, "ref set_ref replaced content"); mock_release(rr); /* indexOf/includes: POINTER identity, needle borrowed. */ MockRec *second = (MockRec *)scr_arr_get_ref(a, 1); check_f64(scr_arr_index_of_ref(a, second), 1, "ref indexOf by identity"); check(scr_arr_includes_ref(a, second), "ref includes by identity"); MockRec *stranger = mock_new(2); check_f64(scr_arr_index_of_ref(a, stranger), -1, "ref equal-value stranger misses"); check(second->rc == 2, "ref indexOf borrows the needle"); mock_release(stranger); /* pop_ref transfers ownership out. */ MockRec *popped = (MockRec *)scr_arr_pop_ref(a); check(popped == second, "ref pop_ref returns the element"); check(popped->rc == 2, "ref pop_ref transfers (our get_ref + the pop)"); mock_release(popped); mock_release(second); scr_arr_release(a); /* releases the remaining element */ check(mock_live == 0, "ref elements all released"); #ifdef SCR_RC_AUDIT check(scr_arr_live_count() == arrays0, "ref: no arrays leaked"); #endif } static void test_ref_cycle(void) { #ifdef SCR_RC_AUDIT long arrays0 = scr_arr_live_count(); #endif /* arr -> rec -> arr: drop the external references, then collect. */ ScrArr *arr = scr_arr_new_ref(&mock_cyc_retain, &mock_cyc_release, &mock_trace, 0); MockRec *rec = mock_cyc_new(7); rec->owner = (ScrArr *)scr_arr_retain(arr); /* rec points back at arr */ scr_arr_push_ref(arr, rec); /* arr owns rec */ check(mock_live == 1, "cycle: element alive"); scr_arr_release(arr); /* external edge gone; the cycle keeps both alive */ scr_collect_cycles(); check(mock_live == 0, "cycle collected through the array element"); #ifdef SCR_RC_AUDIT check(scr_arr_live_count() == arrays0, "cycle: no arrays leaked"); #endif } static void test_ref_truncate_cycle(void) { #ifdef SCR_RC_AUDIT long arrays0 = scr_arr_live_count(); #endif /* The truncation release runs while the record points back at the array. * The array edge must be detached before mock_cyc_release can trigger the * cycle collector. */ ScrArr *arr = scr_arr_new_ref(&mock_cyc_retain, &mock_cyc_release, &mock_trace, 0); MockRec *rec = mock_cyc_new(8); rec->owner = (ScrArr *)scr_arr_retain(arr); scr_arr_push_ref(arr, rec); scr_arr_set_len(arr, 0); scr_collect_cycles(); check(mock_live == 0, "truncation detaches cyclic ref before release"); scr_arr_release(arr); #ifdef SCR_RC_AUDIT check(scr_arr_live_count() == arrays0, "truncation cycle: no arrays leaked"); #endif } static void test_join(void) { #ifdef SCR_RC_AUDIT long strings0 = scr_str_live_count(); #endif ScrStr *sep = scr_str_new(",", 1); ScrArr *n = scr_arr_new(SCR_ELEM_F64, 0); ScrStr *empty = scr_arr_join(n, sep); check(empty->len == 0, "join of empty array is \"\""); scr_str_release(empty); scr_arr_push_f64(n, 1.5); scr_arr_push_f64(n, -0.0); scr_arr_push_f64(n, 0.0 / 0.0); ScrStr *nums = scr_arr_join(n, sep); check(strcmp(nums->data, "1.5,0,NaN") == 0, "join f64 (JS formatting, -0 -> \"0\")"); scr_str_release(nums); scr_arr_release(n); ScrArr *b = scr_arr_new(SCR_ELEM_BOOL, 0); scr_arr_push_bool(b, true); scr_arr_push_bool(b, false); ScrStr *bools = scr_arr_join(b, sep); check(strcmp(bools->data, "true,false") == 0, "join bool"); scr_str_release(bools); scr_arr_release(b); ScrArr *s = scr_arr_new(SCR_ELEM_STR, 0); scr_arr_push_ref(s, scr_str_new("a", 1)); scr_arr_push_ref(s, scr_str_new("", 0)); scr_arr_push_ref(s, scr_str_new("c", 1)); ScrStr *sep0 = scr_str_new("", 0); ScrStr *sepless = scr_arr_join(s, sep0); /* join borrows the separator */ check(strcmp(sepless->data, "ac") == 0, "join with empty separator"); scr_str_release(sepless); scr_str_release(sep0); ScrStr *sep2 = scr_str_new("--", 2); ScrStr *strs = scr_arr_join(s, sep2); check(strcmp(strs->data, "a----c") == 0, "join strings verbatim, empty kept"); scr_str_release(strs); scr_str_release(sep2); scr_arr_release(s); scr_str_release(sep); #ifdef SCR_RC_AUDIT check(scr_str_live_count() == strings0, "join: no strings leaked"); #endif } static void test_sparse_holes(void) { #ifdef SCR_RC_AUDIT long strings0 = scr_str_live_count(); long arrays0 = scr_arr_live_count(); #endif ScrArr *a = scr_arr_new(SCR_ELEM_F64, 0); scr_arr_set_f64(a, 3, 7); check_f64(scr_arr_len(a), 4, "far indexed write grows length"); check(!scr_arr_has(a, 0) && !scr_arr_has(a, 2), "growth leaves holes absent"); check(scr_arr_has(a, 3), "far indexed write is present"); check_f64(scr_arr_get_f64(a, 3), 7, "far indexed value is readable"); scr_arr_set_undefined(a, 2); check(scr_arr_state(a, 2) == SCR_ARR_UNDEFINED && scr_arr_has(a, 2), "explicit undefined is present and stateful"); check_f64(scr_arr_next_present(a, 0), 2, "next-present traversal finds explicit undefined"); check_f64(scr_arr_next_present(a, 3), 3, "next-present traversal finds dense values"); check_f64(scr_arr_next_present(a, 4), 4, "next-present traversal stops at length"); check(scr_arr_delete(a, 2) && scr_arr_state(a, 2) == SCR_ARR_HOLE && scr_arr_len(a) == 4, "delete removes a slot without shrinking length"); ScrStr *hole_sep = scr_str_new(",", 1); ScrStr *hole_join = scr_arr_join(a, hole_sep); check(strcmp(hole_join->data, ",,,7") == 0, "join preserves separators for holes"); scr_str_release(hole_join); scr_str_release(hole_sep); ScrArr *slice = scr_arr_slice(a, 0, 4); check_f64(scr_arr_len(slice), 4, "slice keeps sparse length"); check(!scr_arr_has(slice, 0) && scr_arr_has(slice, 3), "slice preserves hole presence"); scr_arr_release(slice); ScrArr *materialized = scr_arr_to_reversed(a); check(scr_arr_state(materialized, 1) == SCR_ARR_UNDEFINED, "toReversed materializes a hole as undefined"); scr_arr_release(materialized); ScrArr *concat = scr_arr_new(SCR_ELEM_F64, 0); scr_arr_concat_copy(concat, a); check(scr_arr_state(concat, 0) == SCR_ARR_HOLE, "concat copy preserves holes"); ScrArr *spread = scr_arr_new(SCR_ELEM_F64, 0); scr_arr_push_spread(spread, a); check(scr_arr_state(spread, 0) == SCR_ARR_UNDEFINED, "spread materializes holes as undefined"); scr_arr_release(concat); scr_arr_release(spread); ScrArr *rev = scr_arr_reverse(a); check(rev == a, "sparse reverse keeps identity"); check(scr_arr_has(a, 0) && !scr_arr_has(a, 3), "reverse moves presence with the value"); scr_arr_release(rev); scr_arr_release(a); ScrArr *high = scr_arr_new(SCR_ELEM_F64, 0); const double high_index = 4000000000.0; scr_arr_set_f64(high, high_index, 11); check(scr_arr_has(high, high_index), "high sparse index is present"); check(high->cap <= ((size_t)1 << 20), "high sparse index avoids dense allocation"); check(high->sparse_len == 1, "high sparse index uses side storage"); check_f64(scr_arr_next_present(high, 0), high_index, "next-present traversal jumps to side storage"); scr_arr_set_len(high, 0); check(high->sparse_len == 0 && scr_arr_len(high) == 0, "length shrink removes sparse entries"); scr_arr_release(high); ScrArr *refs = scr_arr_new(SCR_ELEM_STR, 0); scr_arr_set_ref(refs, high_index, scr_str_new("sparse", 6)); scr_arr_set_len(refs, 0); scr_arr_release(refs); ScrArr *props = scr_arr_new(SCR_ELEM_F64, 0); scr_arr_set_f64(props, -1, 21); scr_arr_set_f64(props, 0.5, 22); scr_arr_set_f64(props, 4294967295.0, 23); check_f64(scr_arr_len(props), 0, "ordinary numeric properties do not grow length"); check(scr_arr_has(props, -1) && scr_arr_has(props, 0.5) && scr_arr_has(props, 4294967295.0), "noncanonical numeric properties are present"); check_f64(scr_arr_get_f64(props, -1), 21, "negative numeric property round-trips"); check_f64(scr_arr_get_f64(props, 0.5), 22, "fractional numeric property round-trips"); check_f64(scr_arr_get_f64(props, 4294967295.0), 23, "max non-index numeric property round-trips"); scr_arr_set_undefined(props, -2); scr_arr_set_undefined(props, 1.5); scr_arr_set_undefined(props, 4294967295.0); check(scr_arr_state(props, -2) == SCR_ARR_UNDEFINED && scr_arr_state(props, 1.5) == SCR_ARR_UNDEFINED && scr_arr_state(props, 4294967295.0) == SCR_ARR_UNDEFINED, "ordinary undefined properties retain their state"); check(scr_arr_has(props, -2) && scr_arr_has(props, 1.5) && scr_arr_has(props, 4294967295.0), "ordinary undefined properties remain present"); scr_arr_set_len(props, 0); check(scr_arr_has(props, -1), "length truncation leaves ordinary properties"); scr_arr_release(props); #ifdef SCR_RC_AUDIT check(scr_str_live_count() == strings0, "sparse ref truncation releases values"); check(scr_arr_live_count() == arrays0, "sparse arrays do not leak"); #endif } int main(int argc, char **argv) { if (argc > 1) { ScrArr *a = scr_arr_new(SCR_ELEM_F64, 0); scr_arr_push_f64(a, 1); if (strcmp(argv[1], "--crash-get-oob") == 0) { scr_arr_get_f64(a, 1); /* len is 1 */ } else if (strcmp(argv[1], "--crash-get-frac") == 0) { scr_arr_get_f64(a, 0.5); } else if (strcmp(argv[1], "--crash-hole-read") == 0) { scr_arr_set_f64(a, 2, 9); scr_arr_get_f64(a, 1); /* present-length read of a hole */ } else if (strcmp(argv[1], "--crash-pop-empty") == 0) { scr_arr_pop_f64(a); scr_arr_pop_f64(a); /* now empty */ } else { fprintf(stderr, "unknown mode %s\n", argv[1]); return 2; } fprintf(stderr, "expected a trap, still alive\n"); return 2; } test_f64_basics(); test_bool(); test_unshift_reverse(); test_str_rc(); test_nested_rc(); test_index_of_includes(); test_ref_elements(); test_ref_cycle(); test_ref_truncate_cycle(); test_join(); test_sparse_holes(); fprintf(stderr, "%ld/%ld cases passed\n", total - failed, total); return failed == 0 ? 0 : 1; }