fix tests
This commit is contained in:
71
boot.ce
71
boot.ce
@@ -8,6 +8,8 @@
|
||||
|
||||
var shop = use('internal/shop')
|
||||
var fd = use('fd')
|
||||
var pkg_tools = use('package')
|
||||
var build = use('build')
|
||||
|
||||
var is_native = false
|
||||
var target_prog = null
|
||||
@@ -49,52 +51,7 @@ if (args && length(args) > 0) {
|
||||
|
||||
// Discover all transitive module dependencies for a file
|
||||
function discover_deps(file_path) {
|
||||
var visited = {}
|
||||
var scripts = []
|
||||
var c_packages = {}
|
||||
|
||||
function trace(fp) {
|
||||
if (visited[fp]) return
|
||||
visited[fp] = true
|
||||
|
||||
var fi = shop.file_info(fp)
|
||||
var file_pkg = fi.package
|
||||
var idx = null
|
||||
var j = 0
|
||||
var imp = null
|
||||
var mod_path = null
|
||||
var rinfo = null
|
||||
|
||||
// record this script (skip the root program itself)
|
||||
if (ends_with(fp, '.cm')) {
|
||||
scripts[] = {path: fp, package: file_pkg}
|
||||
}
|
||||
|
||||
var _trace = function() {
|
||||
idx = shop.index_file(fp)
|
||||
if (!idx || !idx.imports) return
|
||||
|
||||
j = 0
|
||||
while (j < length(idx.imports)) {
|
||||
imp = idx.imports[j]
|
||||
mod_path = imp.module_path
|
||||
rinfo = shop.resolve_import_info(mod_path, file_pkg)
|
||||
|
||||
if (rinfo) {
|
||||
if (rinfo.type == 'script' && rinfo.resolved_path) {
|
||||
trace(rinfo.resolved_path)
|
||||
} else if (rinfo.type == 'native' && rinfo.package) {
|
||||
c_packages[rinfo.package] = true
|
||||
}
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
} disruption {}
|
||||
_trace()
|
||||
}
|
||||
|
||||
trace(file_path)
|
||||
return {scripts: scripts, c_packages: array(c_packages)}
|
||||
return shop.trace_deps(file_path)
|
||||
}
|
||||
|
||||
// Filter out already-cached modules
|
||||
@@ -118,11 +75,21 @@ function filter_uncached(deps) {
|
||||
j = j + 1
|
||||
}
|
||||
|
||||
// C packages always included — build_dynamic handles its own caching
|
||||
// Expand C packages into individual files for parallel compilation
|
||||
var target = build.detect_host_target()
|
||||
var pkg = null
|
||||
var c_files = null
|
||||
var k = 0
|
||||
j = 0
|
||||
while (j < length(deps.c_packages)) {
|
||||
if (deps.c_packages[j] != 'core') {
|
||||
uncached[] = {type: 'c_package', package: deps.c_packages[j]}
|
||||
pkg = deps.c_packages[j]
|
||||
if (pkg != 'core') {
|
||||
c_files = pkg_tools.get_c_files(pkg, target, true)
|
||||
k = 0
|
||||
while (k < length(c_files)) {
|
||||
uncached[] = {type: 'c_file', package: pkg, file: c_files[k]}
|
||||
k = k + 1
|
||||
}
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
@@ -132,6 +99,7 @@ function filter_uncached(deps) {
|
||||
|
||||
function item_name(item) {
|
||||
if (item.path) return item.path
|
||||
if (item.file) return item.package + '/' + item.file
|
||||
return item.package
|
||||
}
|
||||
|
||||
@@ -147,7 +115,8 @@ function make_compile_requestor(item) {
|
||||
send(event.actor, {
|
||||
type: item.type,
|
||||
path: item.path,
|
||||
package: item.package
|
||||
package: item.package,
|
||||
file: item.file
|
||||
})
|
||||
}
|
||||
if (event.type == 'stop') {
|
||||
@@ -205,7 +174,7 @@ run_boot = function() {
|
||||
}
|
||||
|
||||
// Compile uncached modules in parallel using worker actors
|
||||
log.console('boot: compiling ' + text(length(uncached)) + ' modules...')
|
||||
log.console('boot: ' + text(length(uncached)) + ' modules to compile')
|
||||
requestors = array(uncached, make_compile_requestor)
|
||||
parallel(requestors)(function(results, reason) {
|
||||
if (reason) {
|
||||
|
||||
22
build.cm
22
build.cm
@@ -714,6 +714,28 @@ Build.build_module_dylib = function(pkg, file, target, opts) {
|
||||
return dylib_path
|
||||
}
|
||||
|
||||
// Compile a single C module file for a package (support objects + one dylib).
|
||||
// Used by parallel boot workers. No manifest writing — the runtime handles that.
|
||||
Build.compile_c_module = function(pkg, file, target, opts) {
|
||||
var _target = target || Build.detect_host_target()
|
||||
var _opts = opts || {}
|
||||
var _buildtype = _opts.buildtype || 'release'
|
||||
var pkg_dir = shop.get_package_dir(pkg)
|
||||
var cached_cflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'CFLAGS', _target), pkg_dir)
|
||||
|
||||
// Compile support sources to cached objects (content-addressed, safe for concurrent workers)
|
||||
var sources = pkg_tools.get_sources(pkg)
|
||||
var support_objects = []
|
||||
if (pkg != 'core') {
|
||||
arrfor(sources, function(src_file) {
|
||||
var obj = Build.compile_file(pkg, src_file, _target, {buildtype: _buildtype, cflags: cached_cflags})
|
||||
if (obj != null) push(support_objects, obj)
|
||||
})
|
||||
}
|
||||
|
||||
return Build.build_module_dylib(pkg, file, _target, {buildtype: _buildtype, extra_objects: support_objects, cflags: cached_cflags})
|
||||
}
|
||||
|
||||
// Build a dynamic library for a package (one dylib per C file)
|
||||
// Returns array of {file, symbol, dylib} for each module
|
||||
// Also writes a manifest mapping symbols to dylib paths
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// {type: 'script', path, package} — bytecode compile
|
||||
// {type: 'native_script', path, package} — native compile
|
||||
// {type: 'c_package', package} — C package build
|
||||
// {type: 'c_file', package, file} — single C module build
|
||||
//
|
||||
// Replies with {ok: true/false, path} and stops.
|
||||
|
||||
@@ -11,7 +12,7 @@ var shop = use('internal/shop')
|
||||
var build = use('build')
|
||||
|
||||
$receiver(function(msg) {
|
||||
var name = msg.path || msg.package
|
||||
var name = msg.path || (msg.file ? msg.package + '/' + msg.file : msg.package)
|
||||
var _work = function() {
|
||||
if (msg.type == 'script') {
|
||||
log.console('compile_worker: compiling ' + name)
|
||||
@@ -22,6 +23,9 @@ $receiver(function(msg) {
|
||||
} else if (msg.type == 'c_package') {
|
||||
log.console('compile_worker: building package ' + name)
|
||||
build.build_dynamic(msg.package, null, null, null)
|
||||
} else if (msg.type == 'c_file') {
|
||||
log.console('compile_worker: building ' + name)
|
||||
build.compile_c_module(msg.package, msg.file)
|
||||
}
|
||||
log.console('compile_worker: done ' + name)
|
||||
send(msg, {ok: true, path: name})
|
||||
|
||||
@@ -922,6 +922,7 @@ function load_log_config() {
|
||||
type: "console",
|
||||
format: "pretty",
|
||||
channels: ["*"],
|
||||
exclude: ["system", "shop", "build"],
|
||||
stack: ["error"]
|
||||
}
|
||||
}
|
||||
@@ -1845,11 +1846,44 @@ $_.clock(_ => {
|
||||
|
||||
// --- Auto-boot: pre-compile uncached deps before running ---
|
||||
// Only auto-boot for the root program (not child actors, not boot itself).
|
||||
// Delegates all discovery + compilation to boot.ce (separate actor/memory).
|
||||
// Quick-check deps inline; only spawn boot actor if something needs compiling.
|
||||
var _is_root_actor = !_cell.args.overling_id
|
||||
var _skip_boot = !_is_root_actor || prog == 'boot' || prog == 'compile_worker'
|
||||
|
||||
if (_skip_boot) {
|
||||
var _needs_boot = false
|
||||
var _boot_check = function() {
|
||||
var _deps = shop.trace_deps(prog_path)
|
||||
var _bi = 0
|
||||
var _bs = null
|
||||
while (_bi < length(_deps.scripts)) {
|
||||
_bs = _deps.scripts[_bi]
|
||||
if (native_mode) {
|
||||
if (!shop.is_native_cached(_bs.path, _bs.package)) {
|
||||
_needs_boot = true
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if (!shop.is_cached(_bs.path)) {
|
||||
_needs_boot = true
|
||||
return
|
||||
}
|
||||
}
|
||||
_bi = _bi + 1
|
||||
}
|
||||
_bi = 0
|
||||
while (_bi < length(_deps.c_packages)) {
|
||||
if (_deps.c_packages[_bi] != 'core' && !shop.has_c_manifest(_deps.c_packages[_bi])) {
|
||||
_needs_boot = true
|
||||
return
|
||||
}
|
||||
_bi = _bi + 1
|
||||
}
|
||||
} disruption {
|
||||
_needs_boot = true
|
||||
}
|
||||
if (!_skip_boot) _boot_check()
|
||||
|
||||
if (_skip_boot || !_needs_boot) {
|
||||
run_program()
|
||||
} else {
|
||||
$_.start(function(event) {
|
||||
|
||||
@@ -2288,6 +2288,52 @@ Shop.load_as_dylib = function(path, pkg) {
|
||||
return os.native_module_load_named(result._handle, result._sym, env)
|
||||
}
|
||||
|
||||
// Trace all transitive module dependencies for a file.
|
||||
// Returns {scripts: [{path, package}], c_packages: [string]}
|
||||
Shop.trace_deps = function(file_path) {
|
||||
var visited = {}
|
||||
var scripts = []
|
||||
var c_packages = {}
|
||||
|
||||
function trace(fp) {
|
||||
if (visited[fp]) return
|
||||
visited[fp] = true
|
||||
var fi = Shop.file_info(fp)
|
||||
var file_pkg = fi.package
|
||||
var idx = null
|
||||
var j = 0
|
||||
var imp = null
|
||||
var rinfo = null
|
||||
if (ends_with(fp, '.cm'))
|
||||
scripts[] = {path: fp, package: file_pkg}
|
||||
var _trace = function() {
|
||||
idx = Shop.index_file(fp)
|
||||
if (!idx || !idx.imports) return
|
||||
j = 0
|
||||
while (j < length(idx.imports)) {
|
||||
imp = idx.imports[j]
|
||||
rinfo = Shop.resolve_import_info(imp.module_path, file_pkg)
|
||||
if (rinfo) {
|
||||
if (rinfo.type == 'script' && rinfo.resolved_path)
|
||||
trace(rinfo.resolved_path)
|
||||
else if (rinfo.type == 'native' && rinfo.package)
|
||||
c_packages[rinfo.package] = true
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
} disruption {}
|
||||
_trace()
|
||||
}
|
||||
|
||||
trace(file_path)
|
||||
return {scripts: scripts, c_packages: array(c_packages)}
|
||||
}
|
||||
|
||||
// Check if a C package has a build manifest (was previously built)
|
||||
Shop.has_c_manifest = function(pkg) {
|
||||
return fd.is_file(dylib_manifest_path(pkg))
|
||||
}
|
||||
|
||||
// Check if a .cm file has a cached bytecode artifact (mach or mcode)
|
||||
Shop.is_cached = function(path) {
|
||||
if (!fd.is_file(path)) return false
|
||||
|
||||
11
mcode.cm
11
mcode.cm
@@ -1207,6 +1207,8 @@ var mcode = function(ast) {
|
||||
var f = alloc_slot()
|
||||
var val = alloc_slot()
|
||||
var skip = gen_label("filter_skip")
|
||||
var bail = gen_label("filter_bail")
|
||||
var bool_check = alloc_slot()
|
||||
var ctx = {fn: fn_slot, fn_arity: fn_arity, result: val, null_s: null_s,
|
||||
frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "filter"}
|
||||
var L = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
|
||||
@@ -1219,12 +1221,19 @@ var mcode = function(ast) {
|
||||
emit_2("length", fn_arity, fn_slot)
|
||||
emit_forward_loop(L, function(L) {
|
||||
emit_arity_call(ctx, [L.item, L.i], 2)
|
||||
emit_jump_cond("wary_false", val, skip)
|
||||
emit_2("is_bool", bool_check, val)
|
||||
emit_jump_cond("jump_false", bool_check, bail)
|
||||
emit_jump_cond("jump_false", val, skip)
|
||||
emit_2("push", result, L.item)
|
||||
emit_label(skip)
|
||||
return null
|
||||
})
|
||||
emit_2("move", dest, result)
|
||||
var end = gen_label("filter_end")
|
||||
emit_jump(end)
|
||||
emit_label(bail)
|
||||
emit_1("null", dest)
|
||||
emit_label(end)
|
||||
return dest
|
||||
}
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ typedef enum MachOpcode {
|
||||
MACH_LENGTH, /* R(A) = length(R(B)) — array/text/blob length */
|
||||
MACH_IS_PROXY, /* R(A) = is_function(R(B)) && R(B).length == 2 */
|
||||
MACH_IS_BLOB, /* R(A) = is_blob(R(B)) */
|
||||
MACH_IS_DATA, /* R(A) = is_data(R(B)) — plain record, not array/func/blob */
|
||||
MACH_IS_DATA, /* R(A) = is_data(R(B)) — not function or null */
|
||||
MACH_IS_TRUE, /* R(A) = (R(B) === true) */
|
||||
MACH_IS_FALSE, /* R(A) = (R(B) === false) */
|
||||
MACH_IS_FIT, /* R(A) = is_fit(R(B)) — safe integer */
|
||||
@@ -2398,10 +2398,8 @@ vm_dispatch:
|
||||
VM_BREAK();
|
||||
VM_CASE(MACH_IS_DATA): {
|
||||
JSValue v = frame->slots[b];
|
||||
int result = 0;
|
||||
if (mist_is_gc_object(v) && !mist_is_array(v)
|
||||
&& !mist_is_function(v) && !mist_is_blob(v))
|
||||
result = 1;
|
||||
/* data is text, number, logical, array, blob, or record — not function, null */
|
||||
int result = (v != JS_NULL && !mist_is_function(v));
|
||||
frame->slots[a] = JS_NewBool(ctx, result);
|
||||
VM_BREAK();
|
||||
}
|
||||
|
||||
@@ -8375,7 +8375,15 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
|
||||
/* Map - GC-safe: root result throughout, use rooted refs for func and array */
|
||||
int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (arg1_ref.val))->length;
|
||||
|
||||
int reverse = argc > 2 && JS_ToBool (ctx, argv[2]);
|
||||
int reverse = 0;
|
||||
if (argc > 2 && !JS_IsNull (argv[2])) {
|
||||
if (!JS_IsBool (argv[2])) {
|
||||
JS_PopGCRef (ctx, &arg1_ref);
|
||||
JS_PopGCRef (ctx, &arg0_ref);
|
||||
return JS_RaiseDisrupt (ctx, "array: reverse must be a logical");
|
||||
}
|
||||
reverse = JS_VALUE_GET_BOOL (argv[2]);
|
||||
}
|
||||
JSValue exit_val = argc > 3 ? argv[3] : JS_NULL;
|
||||
|
||||
JSGCRef result_ref;
|
||||
@@ -8505,7 +8513,7 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
|
||||
if (!JS_IsInteger (argv[1])) {
|
||||
JS_PopGCRef (ctx, &arg1_ref);
|
||||
JS_PopGCRef (ctx, &arg0_ref);
|
||||
return JS_NULL;
|
||||
return JS_RaiseDisrupt (ctx, "array slice: from must be an integer");
|
||||
}
|
||||
int from = JS_VALUE_GET_INT (argv[1]);
|
||||
int to;
|
||||
@@ -8513,7 +8521,7 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
|
||||
if (!JS_IsNumber (argv[2]) || !JS_IsInteger (argv[2])) {
|
||||
JS_PopGCRef (ctx, &arg1_ref);
|
||||
JS_PopGCRef (ctx, &arg0_ref);
|
||||
return JS_NULL;
|
||||
return JS_RaiseDisrupt (ctx, "array slice: to must be an integer");
|
||||
}
|
||||
to = JS_VALUE_GET_INT (argv[2]);
|
||||
} else {
|
||||
@@ -8550,7 +8558,7 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
|
||||
|
||||
JS_PopGCRef (ctx, &arg1_ref);
|
||||
JS_PopGCRef (ctx, &arg0_ref);
|
||||
return JS_NULL;
|
||||
return JS_RaiseDisrupt (ctx, "array: invalid argument combination");
|
||||
}
|
||||
|
||||
/* array(object) - keys */
|
||||
@@ -8822,7 +8830,12 @@ static JSValue js_cell_array_reduce (JSContext *ctx, JSValue this_val, int argc,
|
||||
word_t len = arr->len;
|
||||
JSValue fn = argv[1];
|
||||
|
||||
int reverse = argc > 3 && JS_ToBool (ctx, argv[3]);
|
||||
int reverse = 0;
|
||||
if (argc > 3 && !JS_IsNull (argv[3])) {
|
||||
if (!JS_IsBool (argv[3]))
|
||||
return JS_RaiseDisrupt (ctx, "reduce: reverse must be a logical");
|
||||
reverse = JS_VALUE_GET_BOOL (argv[3]);
|
||||
}
|
||||
JSGCRef acc_ref;
|
||||
JSValue acc;
|
||||
|
||||
@@ -8898,7 +8911,12 @@ static JSValue js_cell_array_for (JSContext *ctx, JSValue this_val, int argc, JS
|
||||
word_t len = arr->len;
|
||||
if (len == 0) return JS_NULL;
|
||||
|
||||
int reverse = argc > 2 && JS_ToBool (ctx, argv[2]);
|
||||
int reverse = 0;
|
||||
if (argc > 2 && !JS_IsNull (argv[2])) {
|
||||
if (!JS_IsBool (argv[2]))
|
||||
return JS_RaiseDisrupt (ctx, "arrfor: reverse must be a logical");
|
||||
reverse = JS_VALUE_GET_BOOL (argv[2]);
|
||||
}
|
||||
JSValue exit_val = argc > 3 ? argv[3] : JS_NULL;
|
||||
|
||||
if (reverse) {
|
||||
@@ -8938,7 +8956,12 @@ static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, J
|
||||
JSArray *arr = JS_VALUE_GET_ARRAY (argv[0]);
|
||||
word_t len = arr->len;
|
||||
|
||||
int reverse = argc > 2 && JS_ToBool (ctx, argv[2]);
|
||||
int reverse = 0;
|
||||
if (argc > 2 && !JS_IsNull (argv[2])) {
|
||||
if (!JS_IsBool (argv[2]))
|
||||
return JS_RaiseDisrupt (ctx, "find: reverse must be a logical");
|
||||
reverse = JS_VALUE_GET_BOOL (argv[2]);
|
||||
}
|
||||
int32_t from;
|
||||
if (argc > 3 && !JS_IsNull (argv[3])) {
|
||||
if (JS_ToInt32 (ctx, &from, argv[3])) return JS_NULL;
|
||||
@@ -9416,7 +9439,7 @@ static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSVal
|
||||
/* Use text directly as key */
|
||||
JSValue prop_key = js_key_from_string (ctx, key);
|
||||
JSValue val;
|
||||
if (argc < 2 || JS_IsNull (func_ref.val)) {
|
||||
if (argc < 2) {
|
||||
val = JS_TRUE;
|
||||
} else if (is_func) {
|
||||
JSValue arg_key = key;
|
||||
@@ -11336,11 +11359,9 @@ static JSValue js_cell_is_blob (JSContext *ctx, JSValue this_val, int argc, JSVa
|
||||
static JSValue js_cell_is_data (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||
if (argc < 1) return JS_FALSE;
|
||||
JSValue val = argv[0];
|
||||
if (!mist_is_gc_object (val)) return JS_FALSE;
|
||||
if (JS_IsArray (val)) return JS_FALSE;
|
||||
/* data is text, number, logical, array, blob, or record — not function, null */
|
||||
if (JS_IsNull (val)) return JS_FALSE;
|
||||
if (JS_IsFunction (val)) return JS_FALSE;
|
||||
if (mist_is_blob (val)) return JS_FALSE;
|
||||
/* Check if it's a plain object (prototype is Object.prototype or null) */
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
|
||||
@@ -1323,6 +1323,10 @@ TEST(cell_not) {
|
||||
CELL CORE FUNCTION TESTS
|
||||
============================================================================ */
|
||||
|
||||
static JSValue cfunc_returns_99(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||
return JS_NewInt32(ctx, 99);
|
||||
}
|
||||
|
||||
TEST(cell_length_array) {
|
||||
JSGCRef arr_ref;
|
||||
JS_PushGCRef(ctx, &arr_ref);
|
||||
@@ -1343,6 +1347,81 @@ TEST(cell_length_string) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(cell_length_blob) {
|
||||
uint8_t data[] = {0xAA, 0xBB, 0xCC};
|
||||
JSValue blob = js_new_blob_stoned_copy(ctx, data, 3);
|
||||
JSValue result = JS_CellLength(ctx, blob);
|
||||
/* blob length is in bits: 3 bytes = 24 bits */
|
||||
ASSERT_INT(result, 24);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(cell_length_null) {
|
||||
JSValue result = JS_CellLength(ctx, JS_NULL);
|
||||
ASSERT(JS_IsNull(result));
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(cell_length_logical) {
|
||||
JSValue result_t = JS_CellLength(ctx, JS_TRUE);
|
||||
JSValue result_f = JS_CellLength(ctx, JS_FALSE);
|
||||
ASSERT(JS_IsNull(result_t));
|
||||
ASSERT(JS_IsNull(result_f));
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(cell_length_number) {
|
||||
JSValue result = JS_CellLength(ctx, JS_NewInt32(ctx, 42));
|
||||
ASSERT(JS_IsNull(result));
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(cell_length_function_arity) {
|
||||
JSGCRef func_ref;
|
||||
JS_PushGCRef(ctx, &func_ref);
|
||||
func_ref.val = JS_NewCFunction(ctx, cfunc_add, "add", 2);
|
||||
JSValue result = JS_CellLength(ctx, func_ref.val);
|
||||
JS_PopGCRef(ctx, &func_ref);
|
||||
ASSERT_INT(result, 2);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(cell_length_object_no_length) {
|
||||
JSGCRef obj_ref;
|
||||
JS_PushGCRef(ctx, &obj_ref);
|
||||
obj_ref.val = JS_NewObject(ctx);
|
||||
JS_SetPropertyStr(ctx, obj_ref.val, "x", JS_NewInt32(ctx, 10));
|
||||
JSValue result = JS_CellLength(ctx, obj_ref.val);
|
||||
JS_PopGCRef(ctx, &obj_ref);
|
||||
ASSERT(JS_IsNull(result));
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(cell_length_object_number) {
|
||||
JSGCRef obj_ref;
|
||||
JS_PushGCRef(ctx, &obj_ref);
|
||||
obj_ref.val = JS_NewObject(ctx);
|
||||
JS_SetPropertyStr(ctx, obj_ref.val, "length", JS_NewInt32(ctx, 7));
|
||||
JSValue result = JS_CellLength(ctx, obj_ref.val);
|
||||
JS_PopGCRef(ctx, &obj_ref);
|
||||
ASSERT_INT(result, 7);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(cell_length_object_function) {
|
||||
JSGCRef obj_ref, func_ref;
|
||||
JS_PushGCRef(ctx, &obj_ref);
|
||||
JS_PushGCRef(ctx, &func_ref);
|
||||
obj_ref.val = JS_NewObject(ctx);
|
||||
func_ref.val = JS_NewCFunction(ctx, cfunc_returns_99, "length", 0);
|
||||
JS_SetPropertyStr(ctx, obj_ref.val, "length", func_ref.val);
|
||||
JSValue result = JS_CellLength(ctx, obj_ref.val);
|
||||
JS_PopGCRef(ctx, &func_ref);
|
||||
JS_PopGCRef(ctx, &obj_ref);
|
||||
ASSERT_INT(result, 99);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(cell_reverse_array) {
|
||||
JSGCRef arr_ref;
|
||||
JS_PushGCRef(ctx, &arr_ref);
|
||||
@@ -2245,6 +2324,14 @@ int run_c_test_suite(JSContext *ctx)
|
||||
printf("\nCell Core Functions:\n");
|
||||
RUN_TEST(cell_length_array);
|
||||
RUN_TEST(cell_length_string);
|
||||
RUN_TEST(cell_length_blob);
|
||||
RUN_TEST(cell_length_null);
|
||||
RUN_TEST(cell_length_logical);
|
||||
RUN_TEST(cell_length_number);
|
||||
RUN_TEST(cell_length_function_arity);
|
||||
RUN_TEST(cell_length_object_no_length);
|
||||
RUN_TEST(cell_length_object_number);
|
||||
RUN_TEST(cell_length_object_function);
|
||||
RUN_TEST(cell_reverse_array);
|
||||
RUN_TEST(cell_reverse_string);
|
||||
RUN_TEST(cell_meme_shallow);
|
||||
|
||||
941
vm_suite.ce
941
vm_suite.ce
@@ -984,11 +984,11 @@ run("is_proto", function() {
|
||||
|
||||
run("is_data", function() {
|
||||
if (!is_data({})) fail("is_data {} should be true")
|
||||
if (is_data([])) fail("is_data [] should be false")
|
||||
if (is_data(42)) fail("is_data number should be false")
|
||||
if (is_data("hello")) fail("is_data string should be false")
|
||||
if (!is_data([])) fail("is_data [] should be true")
|
||||
if (!is_data(42)) fail("is_data number should be true")
|
||||
if (!is_data("hello")) fail("is_data string should be true")
|
||||
if (is_data(null)) fail("is_data null should be false")
|
||||
if (is_data(true)) fail("is_data bool should be false")
|
||||
if (!is_data(true)) fail("is_data bool should be true")
|
||||
if (is_data(function(){})) fail("is_data function should be false")
|
||||
})
|
||||
|
||||
@@ -1097,6 +1097,26 @@ run("length number", function() {
|
||||
if (length(123) != null) fail("length number should return null")
|
||||
})
|
||||
|
||||
run("length logical", function() {
|
||||
if (length(true) != null) fail("length true should return null")
|
||||
if (length(false) != null) fail("length false should return null")
|
||||
})
|
||||
|
||||
run("length object no length", function() {
|
||||
var obj = {x: 1, y: 2}
|
||||
if (length(obj) != null) fail("length of object without length should return null")
|
||||
})
|
||||
|
||||
run("length object number", function() {
|
||||
var obj = {length: 7}
|
||||
if (length(obj) != 7) fail("length of object with number length should return 7")
|
||||
})
|
||||
|
||||
run("length object function", function() {
|
||||
var obj = {length: function() { return 42 }}
|
||||
if (length(obj) != 42) fail("length of object with function length should call it")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// GLOBAL FUNCTIONS - REVERSE
|
||||
// ============================================================================
|
||||
@@ -3530,10 +3550,10 @@ run("inline map intrinsic is_data", function() {
|
||||
var items = [{}, [], "hello", 42, true]
|
||||
var result = array(items, is_data)
|
||||
if (result[0] != true) fail("is_data {} should be true")
|
||||
if (result[1] != false) fail("is_data [] should be false")
|
||||
if (result[2] != false) fail("is_data string should be false")
|
||||
if (result[3] != false) fail("is_data number should be false")
|
||||
if (result[4] != false) fail("is_data bool should be false")
|
||||
if (result[1] != true) fail("is_data [] should be true")
|
||||
if (result[2] != true) fail("is_data string should be true")
|
||||
if (result[3] != true) fail("is_data number should be true")
|
||||
if (result[4] != true) fail("is_data bool should be true")
|
||||
if (length(result) != 5) fail("result length should be 5")
|
||||
})
|
||||
|
||||
@@ -5577,9 +5597,9 @@ run("blob w16 w32 wf", function() {
|
||||
if (length(b) != 80) fail("expected 80 bits, got " + text(length(b)))
|
||||
})
|
||||
|
||||
run("blob is_data false for blob", function() {
|
||||
run("blob is_data true for blob", function() {
|
||||
var b = blob()
|
||||
if (is_data(b)) fail("blob should not be is_data")
|
||||
if (!is_data(b)) fail("blob should be is_data")
|
||||
})
|
||||
|
||||
run("blob text hex format", function() {
|
||||
@@ -7232,30 +7252,36 @@ run("reverse does not mutate", function() {
|
||||
// Tests that inlined loops correctly handle truthy/falsy for all types.
|
||||
// ============================================================================
|
||||
|
||||
run("filter inline - string truthiness", function() {
|
||||
run("filter inline - non-empty strings", function() {
|
||||
var items = ["hello", "", "world", "", "ok"]
|
||||
var result = filter(items, function(x) { return x })
|
||||
assert_eq(length(result), 3, "filter truthy strings count")
|
||||
assert_eq(result[0], "hello", "filter truthy [0]")
|
||||
assert_eq(result[1], "world", "filter truthy [1]")
|
||||
assert_eq(result[2], "ok", "filter truthy [2]")
|
||||
var result = filter(items, function(x) { return length(x) > 0 })
|
||||
assert_eq(length(result), 3, "filter non-empty strings count")
|
||||
assert_eq(result[0], "hello", "filter non-empty [0]")
|
||||
assert_eq(result[1], "world", "filter non-empty [1]")
|
||||
assert_eq(result[2], "ok", "filter non-empty [2]")
|
||||
})
|
||||
|
||||
run("filter inline - number truthiness", function() {
|
||||
run("filter inline - nonzero numbers", function() {
|
||||
var items = [0, 1, 0, 2, 0, 3]
|
||||
var result = filter(items, function(x) { return x })
|
||||
assert_eq(length(result), 3, "filter truthy numbers count")
|
||||
assert_eq(result[0], 1, "filter truthy num [0]")
|
||||
assert_eq(result[1], 2, "filter truthy num [1]")
|
||||
assert_eq(result[2], 3, "filter truthy num [2]")
|
||||
var result = filter(items, function(x) { return x != 0 })
|
||||
assert_eq(length(result), 3, "filter nonzero numbers count")
|
||||
assert_eq(result[0], 1, "filter nonzero [0]")
|
||||
assert_eq(result[1], 2, "filter nonzero [1]")
|
||||
assert_eq(result[2], 3, "filter nonzero [2]")
|
||||
})
|
||||
|
||||
run("filter inline - null truthiness", function() {
|
||||
run("filter inline - non-null values", function() {
|
||||
var items = [1, null, 2, null, 3]
|
||||
var result = filter(items, function(x) { return x })
|
||||
var result = filter(items, function(x) { return !is_null(x) })
|
||||
assert_eq(length(result), 3, "filter non-null count")
|
||||
})
|
||||
|
||||
run("filter non-boolean callback returns null", function() {
|
||||
// Callbacks that return non-boolean values should cause filter to return null
|
||||
var result = filter([1, 2, 3], function(x) { return x })
|
||||
assert_eq(result, null, "filter truthy-return is null")
|
||||
})
|
||||
|
||||
run("filter inline - negated predicate", function() {
|
||||
var items = ["hello", "", "world", ""]
|
||||
var result = filter(items, function(x) { return !x })
|
||||
@@ -7717,6 +7743,873 @@ run("function explicit return before trailing expr", function() {
|
||||
assert_eq(fn(), 7, "explicit return should take precedence")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// COMPREHENSIVE EDGE CASE TESTS
|
||||
// ============================================================================
|
||||
|
||||
// --- array() edge cases ---
|
||||
|
||||
run("array(number) negative disrupts", function() {
|
||||
var result = array(-1)
|
||||
assert_eq(result, null, "array(-1) returns null")
|
||||
})
|
||||
|
||||
run("array(float) disrupts", function() {
|
||||
if (!should_disrupt(function() { array(3.5) })) fail("array(3.5) should disrupt")
|
||||
})
|
||||
|
||||
run("array(number, value) all initialized", function() {
|
||||
var a = array(4, "x")
|
||||
assert_eq(a[0], "x", "init [0]")
|
||||
assert_eq(a[3], "x", "init [3]")
|
||||
assert_eq(length(a), 4, "init length")
|
||||
})
|
||||
|
||||
run("array(number, null) all null", function() {
|
||||
var a = array(3, null)
|
||||
assert_eq(a[0], null, "null init [0]")
|
||||
assert_eq(a[2], null, "null init [2]")
|
||||
})
|
||||
|
||||
run("array(number, function) index passed", function() {
|
||||
var a = array(4, function(i) { return i * i })
|
||||
assert_eq(a[0], 0, "fn [0]")
|
||||
assert_eq(a[1], 1, "fn [1]")
|
||||
assert_eq(a[2], 4, "fn [2]")
|
||||
assert_eq(a[3], 9, "fn [3]")
|
||||
})
|
||||
|
||||
run("array(array, function) basic map", function() {
|
||||
var result = array([1, 2, 3], function(x) { return x + 10 })
|
||||
assert_eq(result[0], 11, "map [0]")
|
||||
assert_eq(result[2], 13, "map [2]")
|
||||
})
|
||||
|
||||
run("array(array, function, true) reverse map", function() {
|
||||
var order = []
|
||||
var result = array([10, 20, 30], function(x) {
|
||||
order[] = x
|
||||
return x
|
||||
}, true)
|
||||
assert_eq(order[0], 30, "reverse order [0]")
|
||||
assert_eq(order[1], 20, "reverse order [1]")
|
||||
assert_eq(order[2], 10, "reverse order [2]")
|
||||
assert_eq(length(result), 3, "reverse map length")
|
||||
})
|
||||
|
||||
run("array(array, function, false) forward map", function() {
|
||||
var order = []
|
||||
array([1, 2, 3], function(x) {
|
||||
order[] = x
|
||||
return x
|
||||
}, false)
|
||||
assert_eq(order[0], 1, "forward order [0]")
|
||||
assert_eq(order[2], 3, "forward order [2]")
|
||||
})
|
||||
|
||||
run("array(array, function, reverse) non-boolean reverse disrupts", function() {
|
||||
if (!should_disrupt(function() { array([1, 2, 3], function(x) { return x }, 42) }))
|
||||
fail("array map with integer reverse should disrupt")
|
||||
})
|
||||
|
||||
run("array(array, function, reverse) string reverse disrupts", function() {
|
||||
if (!should_disrupt(function() { array([1, 2, 3], function(x) { return x }, "yes") }))
|
||||
fail("array map with string reverse should disrupt")
|
||||
})
|
||||
|
||||
run("array(array, function, reverse) null reverse ok", function() {
|
||||
// null is treated as absent — forward iteration
|
||||
var order = []
|
||||
array([1, 2, 3], function(x) {
|
||||
order[] = x
|
||||
return x
|
||||
}, null)
|
||||
assert_eq(order[0], 1, "null reverse forward")
|
||||
})
|
||||
|
||||
run("array(array, function, true, exit) early exit", function() {
|
||||
var result = array([1, 2, 3, 4, 5], function(x) {
|
||||
if (x == 4) return "stop"
|
||||
return x * 10
|
||||
}, false, "stop")
|
||||
assert_eq(length(result), 3, "exit length")
|
||||
assert_eq(result[0], 10, "exit [0]")
|
||||
assert_eq(result[2], 30, "exit [2]")
|
||||
})
|
||||
|
||||
run("array(array, function, true, exit) reverse early exit", function() {
|
||||
var result = array([1, 2, 3, 4, 5], function(x) {
|
||||
if (x == 2) return "stop"
|
||||
return x * 10
|
||||
}, true, "stop")
|
||||
// reverse: processes 5,4,3,2 -> stops at 2
|
||||
assert_eq(result[0], 50, "rev exit [0]")
|
||||
assert_eq(result[1], 40, "rev exit [1]")
|
||||
assert_eq(result[2], 30, "rev exit [2]")
|
||||
assert_eq(length(result), 3, "rev exit length")
|
||||
})
|
||||
|
||||
run("array(array, from, to) wrong type for to disrupts", function() {
|
||||
if (!should_disrupt(function() { array([1, 2, 3, 4, 5], 1, true) }))
|
||||
fail("array slice with boolean to should disrupt")
|
||||
})
|
||||
|
||||
run("array(array, from, to) wrong type for from disrupts", function() {
|
||||
if (!should_disrupt(function() { array([1, 2, 3, 4, 5], true) }))
|
||||
fail("array with boolean from should disrupt")
|
||||
})
|
||||
|
||||
run("array(array, from, to) float from disrupts", function() {
|
||||
if (!should_disrupt(function() { array([1, 2, 3, 4, 5], 1.5, 3) }))
|
||||
fail("array slice with float from should disrupt")
|
||||
})
|
||||
|
||||
run("array(array, from, to) from > to returns null", function() {
|
||||
var result = array([1, 2, 3, 4, 5], 3, 1)
|
||||
assert_eq(result, null, "slice from > to returns null")
|
||||
})
|
||||
|
||||
run("array(array, from, to) from == to empty", function() {
|
||||
var result = array([1, 2, 3], 2, 2)
|
||||
assert_eq(length(result), 0, "slice from==to empty")
|
||||
})
|
||||
|
||||
run("array(array, from, to) full range", function() {
|
||||
var result = array([10, 20, 30], 0, 3)
|
||||
assert_eq(length(result), 3, "full range length")
|
||||
assert_eq(result[0], 10, "full range [0]")
|
||||
assert_eq(result[2], 30, "full range [2]")
|
||||
})
|
||||
|
||||
run("array(array, from) to defaults to end", function() {
|
||||
var result = array([10, 20, 30, 40], 2)
|
||||
assert_eq(length(result), 2, "default to length")
|
||||
assert_eq(result[0], 30, "default to [0]")
|
||||
assert_eq(result[1], 40, "default to [1]")
|
||||
})
|
||||
|
||||
run("array(array, array) concat both empty", function() {
|
||||
var result = array([], [])
|
||||
assert_eq(length(result), 0, "concat both empty")
|
||||
})
|
||||
|
||||
run("array copy empty", function() {
|
||||
var copy = array([])
|
||||
assert_eq(length(copy), 0, "copy empty length")
|
||||
})
|
||||
|
||||
run("array from empty record", function() {
|
||||
var keys = array({})
|
||||
assert_eq(length(keys), 0, "empty record keys")
|
||||
})
|
||||
|
||||
run("array from text single char", function() {
|
||||
var chars = array("x")
|
||||
assert_eq(length(chars), 1, "single char length")
|
||||
assert_eq(chars[0], "x", "single char value")
|
||||
})
|
||||
|
||||
run("array(text, length) dice exact", function() {
|
||||
var result = array("abcdef", 3)
|
||||
assert_eq(length(result), 2, "dice exact length")
|
||||
assert_eq(result[0], "abc", "dice exact [0]")
|
||||
assert_eq(result[1], "def", "dice exact [1]")
|
||||
})
|
||||
|
||||
// --- text() edge cases ---
|
||||
|
||||
run("text(text, from, to) from > to returns null", function() {
|
||||
assert_eq(text("hello", 3, 1), null, "text from>to null")
|
||||
})
|
||||
|
||||
run("text(text, from, to) empty range", function() {
|
||||
assert_eq(text("hello", 2, 2), "", "text from==to empty")
|
||||
})
|
||||
|
||||
run("text(text, from) from at length", function() {
|
||||
assert_eq(text("hello", 5), "", "text from==len empty")
|
||||
})
|
||||
|
||||
run("text(text, from) from beyond length", function() {
|
||||
var result = text("hello", 6)
|
||||
// from > len should clamp
|
||||
assert_eq(result, "", "text from>len")
|
||||
})
|
||||
|
||||
run("text(text, from, to) full range", function() {
|
||||
assert_eq(text("hello", 0, 5), "hello", "text full range")
|
||||
})
|
||||
|
||||
run("text(text, negative from, negative to)", function() {
|
||||
assert_eq(text("hello", -3, -1), "ll", "text neg from neg to")
|
||||
})
|
||||
|
||||
run("text from number format string", function() {
|
||||
assert_eq(text(255, "h"), "FF", "text hex format")
|
||||
assert_eq(text(10, "b"), "1010", "text binary format")
|
||||
assert_eq(text(8, "o"), "10", "text octal format")
|
||||
})
|
||||
|
||||
run("text from array disrupts on non-text element", function() {
|
||||
if (!should_disrupt(function() { text([1, 2, 3]) }))
|
||||
fail("text(array of numbers) should disrupt")
|
||||
})
|
||||
|
||||
run("text from array disrupts on mixed", function() {
|
||||
if (!should_disrupt(function() { text(["a", 1, "b"]) }))
|
||||
fail("text(array with number) should disrupt")
|
||||
})
|
||||
|
||||
run("text from logical", function() {
|
||||
assert_eq(text(true), "true", "text(true)")
|
||||
assert_eq(text(false), "false", "text(false)")
|
||||
})
|
||||
|
||||
run("text from null", function() {
|
||||
assert_eq(text(null), "null", "text(null)")
|
||||
})
|
||||
|
||||
// --- number() edge cases ---
|
||||
|
||||
run("number from invalid text returns null", function() {
|
||||
assert_eq(number("abc"), null, "number('abc')")
|
||||
assert_eq(number(""), null, "number('')")
|
||||
})
|
||||
|
||||
run("number from null returns null", function() {
|
||||
assert_eq(number(null), null, "number(null)")
|
||||
})
|
||||
|
||||
run("number radix boundaries", function() {
|
||||
assert_eq(number("10", 2), 2, "number base 2")
|
||||
assert_eq(number("10", 36), 36, "number base 36")
|
||||
assert_eq(number("z", 36), 35, "number z base 36")
|
||||
})
|
||||
|
||||
run("number invalid for radix returns null", function() {
|
||||
assert_eq(number("g", 16), null, "number('g',16)")
|
||||
assert_eq(number("2", 2), null, "number('2',2)")
|
||||
})
|
||||
|
||||
// --- object() edge cases ---
|
||||
|
||||
run("object copy empty", function() {
|
||||
var copy = object({})
|
||||
assert_eq(length(array(copy)), 0, "object copy empty")
|
||||
})
|
||||
|
||||
run("object merge empty into filled", function() {
|
||||
var result = object({a: 1}, {})
|
||||
assert_eq(result.a, 1, "merge empty keeps a")
|
||||
})
|
||||
|
||||
run("object merge filled into empty", function() {
|
||||
var result = object({}, {b: 2})
|
||||
assert_eq(result.b, 2, "merge into empty gets b")
|
||||
})
|
||||
|
||||
run("object select missing key", function() {
|
||||
var result = object({a: 1, b: 2}, ["a", "z"])
|
||||
assert_eq(result.a, 1, "select present key")
|
||||
assert_eq(result.z, null, "select missing key null")
|
||||
})
|
||||
|
||||
run("object from empty keys", function() {
|
||||
var r = object([])
|
||||
assert_eq(length(array(r)), 0, "empty keys")
|
||||
})
|
||||
|
||||
run("object from keys with null value", function() {
|
||||
var r = object(["a", "b"], null)
|
||||
assert_eq(r.a, null, "null value a")
|
||||
assert_eq(r.b, null, "null value b")
|
||||
})
|
||||
|
||||
// --- filter() edge cases ---
|
||||
|
||||
run("filter non-boolean return returns null", function() {
|
||||
var result = filter([1, 2, 3], function(x) { return x })
|
||||
assert_eq(result, null, "filter non-boolean returns null")
|
||||
})
|
||||
|
||||
// filter with non-function: statically rejected by compiler (invoking int)
|
||||
|
||||
run("filter on number disrupts", function() {
|
||||
if (!should_disrupt(function() { filter(42, function(x) { return true }) }))
|
||||
fail("filter on number should disrupt")
|
||||
})
|
||||
|
||||
run("filter with index callback", function() {
|
||||
var result = filter([10, 20, 30, 40], function(el, i) { return i < 2 })
|
||||
assert_eq(length(result), 2, "filter index length")
|
||||
assert_eq(result[0], 10, "filter index [0]")
|
||||
assert_eq(result[1], 20, "filter index [1]")
|
||||
})
|
||||
|
||||
// --- find() edge cases ---
|
||||
|
||||
run("find value search exact match", function() {
|
||||
assert_eq(find([10, 20, 30], 20), 1, "find value 20")
|
||||
})
|
||||
|
||||
run("find value not present", function() {
|
||||
assert_eq(find([10, 20, 30], 99), null, "find value missing")
|
||||
})
|
||||
|
||||
run("find reverse value", function() {
|
||||
assert_eq(find([1, 2, 1, 2], 1, true), 2, "find reverse value")
|
||||
})
|
||||
|
||||
run("find with from index", function() {
|
||||
assert_eq(find([1, 2, 3, 2, 1], 2, false, 2), 3, "find from index")
|
||||
})
|
||||
|
||||
run("find predicate with index", function() {
|
||||
assert_eq(find(["a", "bb", "ccc"], function(el, i) { return length(el) == 2 }), 1, "find predicate idx")
|
||||
})
|
||||
|
||||
run("find empty array", function() {
|
||||
assert_eq(find([], 1), null, "find empty")
|
||||
assert_eq(find([], function(x) { return true }), null, "find empty fn")
|
||||
})
|
||||
|
||||
run("find on number disrupts", function() {
|
||||
if (!should_disrupt(function() { find(42, 4) }))
|
||||
fail("find on number should disrupt")
|
||||
})
|
||||
|
||||
// --- reduce() edge cases ---
|
||||
|
||||
run("reduce empty no initial returns null", function() {
|
||||
assert_eq(reduce([], function(a, b) { return a + b }), null, "reduce empty")
|
||||
})
|
||||
|
||||
run("reduce empty with initial returns initial", function() {
|
||||
assert_eq(reduce([], function(a, b) { return a + b }, 99), 99, "reduce empty initial")
|
||||
})
|
||||
|
||||
run("reduce single no initial returns element", function() {
|
||||
assert_eq(reduce([42], function(a, b) { return a + b }), 42, "reduce single")
|
||||
})
|
||||
|
||||
run("reduce single with initial", function() {
|
||||
assert_eq(reduce([5], function(a, b) { return a + b }, 10), 15, "reduce single initial")
|
||||
})
|
||||
|
||||
run("reduce reverse", function() {
|
||||
var result = reduce([1, 2, 3], function(a, b) { return a - b }, 0, true)
|
||||
// reverse: 0 - 3 = -3, -3 - 2 = -5, -5 - 1 = -6
|
||||
assert_eq(result, -6, "reduce reverse")
|
||||
})
|
||||
|
||||
// reduce with non-function: statically rejected by compiler (invoking int)
|
||||
|
||||
run("reduce on number disrupts", function() {
|
||||
if (!should_disrupt(function() { reduce(42, function(a, b) { return a + b }) }))
|
||||
fail("reduce on number should disrupt")
|
||||
})
|
||||
|
||||
// --- sort() edge cases ---
|
||||
|
||||
run("sort empty array", function() {
|
||||
var result = sort([])
|
||||
assert_eq(length(result), 0, "sort empty")
|
||||
})
|
||||
|
||||
run("sort single element", function() {
|
||||
var result = sort([42])
|
||||
assert_eq(result[0], 42, "sort single")
|
||||
})
|
||||
|
||||
run("sort already sorted", function() {
|
||||
var result = sort([1, 2, 3])
|
||||
assert_eq(result[0], 1, "sort sorted [0]")
|
||||
assert_eq(result[2], 3, "sort sorted [2]")
|
||||
})
|
||||
|
||||
run("sort descending input", function() {
|
||||
var result = sort([5, 4, 3, 2, 1])
|
||||
assert_eq(result[0], 1, "sort desc [0]")
|
||||
assert_eq(result[4], 5, "sort desc [4]")
|
||||
})
|
||||
|
||||
run("sort strings", function() {
|
||||
var result = sort(["cherry", "apple", "banana"])
|
||||
assert_eq(result[0], "apple", "sort str [0]")
|
||||
assert_eq(result[2], "cherry", "sort str [2]")
|
||||
})
|
||||
|
||||
run("sort by field name", function() {
|
||||
var items = [{n: "c", v: 3}, {n: "a", v: 1}, {n: "b", v: 2}]
|
||||
var result = sort(items, "n")
|
||||
assert_eq(result[0].n, "a", "sort field [0]")
|
||||
assert_eq(result[2].n, "c", "sort field [2]")
|
||||
})
|
||||
|
||||
run("sort by array index", function() {
|
||||
var items = [[3, "c"], [1, "a"], [2, "b"]]
|
||||
var result = sort(items, 0)
|
||||
assert_eq(result[0][0], 1, "sort idx [0]")
|
||||
assert_eq(result[2][0], 3, "sort idx [2]")
|
||||
})
|
||||
|
||||
run("sort does not mutate original", function() {
|
||||
var arr = [3, 1, 2]
|
||||
var result = sort(arr)
|
||||
assert_eq(arr[0], 3, "sort no mutate")
|
||||
})
|
||||
|
||||
run("sort negative numbers", function() {
|
||||
var result = sort([-5, 3, -1, 0, 2])
|
||||
assert_eq(result[0], -5, "sort neg [0]")
|
||||
assert_eq(result[4], 3, "sort neg [4]")
|
||||
})
|
||||
|
||||
run("sort stable", function() {
|
||||
var items = [{k: 1, v: "a"}, {k: 2, v: "b"}, {k: 1, v: "c"}]
|
||||
var result = sort(items, "k")
|
||||
assert_eq(result[0].v, "a", "sort stable [0]")
|
||||
assert_eq(result[1].v, "c", "sort stable [1]")
|
||||
assert_eq(result[2].v, "b", "sort stable [2]")
|
||||
})
|
||||
|
||||
// --- reverse() edge cases ---
|
||||
|
||||
run("reverse single element", function() {
|
||||
var result = reverse([42])
|
||||
assert_eq(result[0], 42, "reverse single")
|
||||
assert_eq(length(result), 1, "reverse single length")
|
||||
})
|
||||
|
||||
run("reverse does not mutate", function() {
|
||||
var arr = [1, 2, 3]
|
||||
var rev = reverse(arr)
|
||||
assert_eq(arr[0], 1, "reverse no mutate")
|
||||
assert_eq(rev[0], 3, "reverse result [0]")
|
||||
})
|
||||
|
||||
run("reverse text", function() {
|
||||
var result = reverse("abc")
|
||||
assert_eq(result, "cba", "reverse text")
|
||||
})
|
||||
|
||||
run("reverse empty text", function() {
|
||||
var result = reverse("")
|
||||
assert_eq(result, "", "reverse empty text")
|
||||
})
|
||||
|
||||
// --- every() / some() edge cases ---
|
||||
|
||||
run("every empty returns true", function() {
|
||||
assert_eq(every([], function(x) { return false }), true, "every empty")
|
||||
})
|
||||
|
||||
run("every all pass", function() {
|
||||
assert_eq(every([2, 4, 6], function(x) { return x % 2 == 0 }), true, "every all")
|
||||
})
|
||||
|
||||
run("every one fails", function() {
|
||||
assert_eq(every([2, 3, 6], function(x) { return x % 2 == 0 }), false, "every one fail")
|
||||
})
|
||||
|
||||
run("some empty returns false", function() {
|
||||
assert_eq(some([], function(x) { return true }), false, "some empty")
|
||||
})
|
||||
|
||||
run("some one match", function() {
|
||||
assert_eq(some([1, 3, 5], function(x) { return x == 3 }), true, "some one match")
|
||||
})
|
||||
|
||||
run("some no match", function() {
|
||||
assert_eq(some([1, 3, 5], function(x) { return x > 10 }), false, "some no match")
|
||||
})
|
||||
|
||||
// --- apply() edge cases ---
|
||||
|
||||
run("apply basic", function() {
|
||||
var fn = function(a, b) { return a + b }
|
||||
assert_eq(apply(fn, [3, 4]), 7, "apply basic")
|
||||
})
|
||||
|
||||
run("apply no args", function() {
|
||||
var fn = function() { return 42 }
|
||||
assert_eq(apply(fn, []), 42, "apply no args")
|
||||
})
|
||||
|
||||
run("apply non-function returns value", function() {
|
||||
assert_eq(apply(42, [1, 2]), 42, "apply non-fn")
|
||||
})
|
||||
|
||||
run("apply single arg", function() {
|
||||
var fn = function(x) { return x * 2 }
|
||||
assert_eq(apply(fn, [5]), 10, "apply single")
|
||||
})
|
||||
|
||||
run("apply too many args disrupts", function() {
|
||||
var fn = function(a) { return a }
|
||||
if (!should_disrupt(function() { apply(fn, [1, 2, 3]) }))
|
||||
fail("apply with too many args should disrupt")
|
||||
})
|
||||
|
||||
// --- abs() edge cases ---
|
||||
|
||||
run("abs non-number returns null", function() {
|
||||
assert_eq(abs("5"), null, "abs string")
|
||||
assert_eq(abs(null), null, "abs null")
|
||||
assert_eq(abs(true), null, "abs bool")
|
||||
})
|
||||
|
||||
run("abs zero", function() {
|
||||
assert_eq(abs(0), 0, "abs zero")
|
||||
})
|
||||
|
||||
// --- sign() edge cases ---
|
||||
|
||||
run("sign non-number returns null", function() {
|
||||
assert_eq(sign("5"), null, "sign string")
|
||||
assert_eq(sign(null), null, "sign null")
|
||||
})
|
||||
|
||||
// --- neg() edge cases ---
|
||||
|
||||
run("neg non-number returns null", function() {
|
||||
assert_eq(neg("5"), null, "neg string")
|
||||
assert_eq(neg(null), null, "neg null")
|
||||
})
|
||||
|
||||
// --- not() edge cases ---
|
||||
|
||||
run("not non-logical returns null", function() {
|
||||
assert_eq(not(0), null, "not 0")
|
||||
assert_eq(not(1), null, "not 1")
|
||||
assert_eq(not("true"), null, "not string")
|
||||
assert_eq(not(null), null, "not null")
|
||||
})
|
||||
|
||||
// --- min/max edge cases ---
|
||||
|
||||
run("min non-number returns null", function() {
|
||||
assert_eq(min("3", 5), null, "min string")
|
||||
assert_eq(min(3, "5"), null, "min string 2")
|
||||
assert_eq(min(null, 5), null, "min null")
|
||||
})
|
||||
|
||||
run("max non-number returns null", function() {
|
||||
assert_eq(max("3", 5), null, "max string")
|
||||
assert_eq(max(3, null), null, "max null")
|
||||
})
|
||||
|
||||
// --- modulo() edge cases ---
|
||||
|
||||
run("modulo zero divisor returns null", function() {
|
||||
assert_eq(modulo(10, 0), null, "modulo div 0")
|
||||
})
|
||||
|
||||
run("modulo non-number returns null", function() {
|
||||
assert_eq(modulo("10", 3), null, "modulo string")
|
||||
assert_eq(modulo(10, "3"), null, "modulo string 2")
|
||||
})
|
||||
|
||||
run("modulo zero dividend", function() {
|
||||
assert_eq(modulo(0, 5), 0, "modulo 0 dividend")
|
||||
})
|
||||
|
||||
// --- remainder() edge cases ---
|
||||
|
||||
run("remainder basic", function() {
|
||||
assert_eq(remainder(7, 3), 1, "remainder 7,3")
|
||||
assert_eq(remainder(-7, 3), -1, "remainder -7,3")
|
||||
})
|
||||
|
||||
// --- floor/ceiling/round/trunc edge cases ---
|
||||
|
||||
run("floor integer passthrough", function() {
|
||||
assert_eq(floor(5), 5, "floor int")
|
||||
})
|
||||
|
||||
run("floor with place", function() {
|
||||
assert_eq(floor(12.3775, -2), 12.37, "floor place -2")
|
||||
assert_eq(floor(-12.3775, -2), -12.38, "floor neg place -2")
|
||||
})
|
||||
|
||||
run("ceiling integer passthrough", function() {
|
||||
assert_eq(ceiling(5), 5, "ceiling int")
|
||||
})
|
||||
|
||||
run("ceiling with place", function() {
|
||||
assert_eq(ceiling(12.3775, -2), 12.38, "ceiling place -2")
|
||||
assert_eq(ceiling(-12.3775, -2), -12.37, "ceiling neg place -2")
|
||||
})
|
||||
|
||||
run("round with place", function() {
|
||||
assert_eq(round(12.3775, -2), 12.38, "round place -2")
|
||||
})
|
||||
|
||||
run("trunc toward zero", function() {
|
||||
assert_eq(trunc(3.7), 3, "trunc pos")
|
||||
assert_eq(trunc(-3.7), -3, "trunc neg")
|
||||
})
|
||||
|
||||
run("trunc with place", function() {
|
||||
assert_eq(trunc(12.3775, -2), 12.37, "trunc place -2")
|
||||
assert_eq(trunc(-12.3775, -2), -12.37, "trunc neg place -2")
|
||||
})
|
||||
|
||||
// --- whole/fraction edge cases ---
|
||||
|
||||
run("whole non-number returns null", function() {
|
||||
assert_eq(whole("5"), null, "whole string")
|
||||
assert_eq(whole(null), null, "whole null")
|
||||
})
|
||||
|
||||
run("fraction non-number returns null", function() {
|
||||
assert_eq(fraction("5"), null, "fraction string")
|
||||
assert_eq(fraction(null), null, "fraction null")
|
||||
})
|
||||
|
||||
run("whole integer", function() {
|
||||
assert_eq(whole(5), 5, "whole int")
|
||||
assert_eq(whole(-5), -5, "whole neg int")
|
||||
})
|
||||
|
||||
run("fraction integer returns zero", function() {
|
||||
assert_eq(fraction(5), 0, "fraction int")
|
||||
})
|
||||
|
||||
// --- lower/upper edge cases ---
|
||||
|
||||
run("lower empty string", function() {
|
||||
assert_eq(lower(""), "", "lower empty")
|
||||
})
|
||||
|
||||
run("upper empty string", function() {
|
||||
assert_eq(upper(""), "", "upper empty")
|
||||
})
|
||||
|
||||
// --- trim edge cases ---
|
||||
|
||||
run("trim with custom reject", function() {
|
||||
assert_eq(trim("xxhelloxx", "x"), "hello", "trim custom reject")
|
||||
})
|
||||
|
||||
// --- search() edge cases ---
|
||||
|
||||
run("search not found", function() {
|
||||
assert_eq(search("hello", "xyz"), null, "search not found")
|
||||
})
|
||||
|
||||
run("search empty text in empty text", function() {
|
||||
assert_eq(search("", ""), 0, "search empty in empty")
|
||||
})
|
||||
|
||||
run("search from negative index", function() {
|
||||
assert_eq(search("hello world", "world", -5), 6, "search neg from")
|
||||
})
|
||||
|
||||
// --- replace() edge cases ---
|
||||
|
||||
run("replace not found", function() {
|
||||
assert_eq(replace("hello", "xyz", "abc"), "hello", "replace not found")
|
||||
})
|
||||
|
||||
run("replace with limit 0", function() {
|
||||
assert_eq(replace("aaa", "a", "b", 0), "aaa", "replace limit 0")
|
||||
})
|
||||
|
||||
run("replace to empty", function() {
|
||||
assert_eq(replace("hello", "l", ""), "heo", "replace to empty")
|
||||
})
|
||||
|
||||
run("replace with function", function() {
|
||||
var result = replace("abc", "b", function(match, pos) { return text(pos) })
|
||||
assert_eq(result, "a1c", "replace fn")
|
||||
})
|
||||
|
||||
// --- starts_with / ends_with edge cases ---
|
||||
|
||||
run("starts_with full match", function() {
|
||||
assert_eq(starts_with("hello", "hello"), true, "starts_with full")
|
||||
})
|
||||
|
||||
run("starts_with empty", function() {
|
||||
assert_eq(starts_with("hello", ""), true, "starts_with empty")
|
||||
})
|
||||
|
||||
run("ends_with full match", function() {
|
||||
assert_eq(ends_with("hello", "hello"), true, "ends_with full")
|
||||
})
|
||||
|
||||
run("ends_with empty", function() {
|
||||
assert_eq(ends_with("hello", ""), true, "ends_with empty")
|
||||
})
|
||||
|
||||
// --- stone() edge cases ---
|
||||
|
||||
run("stone array prevents modification", function() {
|
||||
var arr = [1, 2, 3]
|
||||
stone(arr)
|
||||
if (!should_disrupt(function() { arr[0] = 99 }))
|
||||
fail("modifying stone array should disrupt")
|
||||
})
|
||||
|
||||
run("stone returns the value", function() {
|
||||
var arr = [1, 2, 3]
|
||||
var result = stone(arr)
|
||||
assert_eq(result[0], 1, "stone returns value")
|
||||
assert_eq(is_stone(result), true, "stone result is stone")
|
||||
})
|
||||
|
||||
run("stone idempotent", function() {
|
||||
var arr = [1, 2, 3]
|
||||
stone(arr)
|
||||
stone(arr)
|
||||
assert_eq(is_stone(arr), true, "double stone ok")
|
||||
})
|
||||
|
||||
run("stone primitives already stone", function() {
|
||||
assert_eq(is_stone(42), true, "number is stone")
|
||||
assert_eq(is_stone("hello"), true, "text is stone")
|
||||
assert_eq(is_stone(true), true, "bool is stone")
|
||||
assert_eq(is_stone(null), true, "null is stone")
|
||||
})
|
||||
|
||||
// --- codepoint / character edge cases ---
|
||||
|
||||
run("codepoint non-text returns null", function() {
|
||||
assert_eq(codepoint(65), null, "codepoint number")
|
||||
assert_eq(codepoint(null), null, "codepoint null")
|
||||
})
|
||||
|
||||
run("codepoint empty returns null", function() {
|
||||
assert_eq(codepoint(""), null, "codepoint empty")
|
||||
})
|
||||
|
||||
run("character negative returns empty", function() {
|
||||
assert_eq(character(-1), "", "character -1")
|
||||
})
|
||||
|
||||
run("character text returns first char", function() {
|
||||
assert_eq(character("hello"), "h", "character text")
|
||||
})
|
||||
|
||||
run("character roundtrip", function() {
|
||||
assert_eq(codepoint(character(65)), 65, "roundtrip 65")
|
||||
assert_eq(character(codepoint("A")), "A", "roundtrip A")
|
||||
})
|
||||
|
||||
// --- sensory function edge cases ---
|
||||
|
||||
run("is_integer floats that are whole", function() {
|
||||
assert_eq(is_integer(3.0), true, "is_integer 3.0")
|
||||
assert_eq(is_integer(-5.0), true, "is_integer -5.0")
|
||||
})
|
||||
|
||||
run("is_integer actual floats", function() {
|
||||
assert_eq(is_integer(3.5), false, "is_integer 3.5")
|
||||
assert_eq(is_integer(0.1), false, "is_integer 0.1")
|
||||
})
|
||||
|
||||
run("is_integer non-numbers", function() {
|
||||
assert_eq(is_integer("3"), false, "is_integer string")
|
||||
assert_eq(is_integer(null), false, "is_integer null")
|
||||
assert_eq(is_integer(true), false, "is_integer bool")
|
||||
})
|
||||
|
||||
run("is_character edge cases", function() {
|
||||
assert_eq(is_character(""), false, "is_character empty")
|
||||
assert_eq(is_character("ab"), false, "is_character multi")
|
||||
assert_eq(is_character("x"), true, "is_character single")
|
||||
assert_eq(is_character(65), false, "is_character number")
|
||||
})
|
||||
|
||||
run("is_digit edge cases", function() {
|
||||
assert_eq(is_digit("0"), true, "is_digit 0")
|
||||
assert_eq(is_digit("9"), true, "is_digit 9")
|
||||
assert_eq(is_digit("a"), false, "is_digit a")
|
||||
assert_eq(is_digit(""), false, "is_digit empty")
|
||||
assert_eq(is_digit("10"), false, "is_digit multi")
|
||||
})
|
||||
|
||||
run("is_letter edge cases", function() {
|
||||
assert_eq(is_letter("a"), true, "is_letter a")
|
||||
assert_eq(is_letter("Z"), true, "is_letter Z")
|
||||
assert_eq(is_letter("0"), false, "is_letter 0")
|
||||
assert_eq(is_letter(""), false, "is_letter empty")
|
||||
assert_eq(is_letter("ab"), false, "is_letter multi")
|
||||
})
|
||||
|
||||
run("is_lower edge cases", function() {
|
||||
assert_eq(is_lower("a"), true, "is_lower a")
|
||||
assert_eq(is_lower("A"), false, "is_lower A")
|
||||
assert_eq(is_lower("1"), false, "is_lower 1")
|
||||
assert_eq(is_lower(""), false, "is_lower empty")
|
||||
})
|
||||
|
||||
run("is_upper edge cases", function() {
|
||||
assert_eq(is_upper("A"), true, "is_upper A")
|
||||
assert_eq(is_upper("a"), false, "is_upper a")
|
||||
assert_eq(is_upper("1"), false, "is_upper 1")
|
||||
assert_eq(is_upper(""), false, "is_upper empty")
|
||||
})
|
||||
|
||||
run("is_whitespace edge cases", function() {
|
||||
assert_eq(is_whitespace(" "), true, "is_whitespace space")
|
||||
assert_eq(is_whitespace("\t"), true, "is_whitespace tab")
|
||||
assert_eq(is_whitespace("\n"), true, "is_whitespace newline")
|
||||
assert_eq(is_whitespace(""), false, "is_whitespace empty")
|
||||
assert_eq(is_whitespace("a"), false, "is_whitespace letter")
|
||||
assert_eq(is_whitespace(32), false, "is_whitespace number")
|
||||
})
|
||||
|
||||
run("is_data edge cases", function() {
|
||||
// is_data: true for text, number, logical, array, blob, record
|
||||
// false for function and null
|
||||
assert_eq(is_data({}), true, "is_data record")
|
||||
assert_eq(is_data("hello"), true, "is_data text")
|
||||
assert_eq(is_data(42), true, "is_data number")
|
||||
assert_eq(is_data(true), true, "is_data bool")
|
||||
assert_eq(is_data([]), true, "is_data array")
|
||||
assert_eq(is_data(null), false, "is_data null")
|
||||
assert_eq(is_data(function() {}), false, "is_data function")
|
||||
})
|
||||
|
||||
// --- format() edge cases ---
|
||||
|
||||
run("format basic array", function() {
|
||||
var result = format("{0} in {1}!", ["Malmborg", "Plano"])
|
||||
assert_eq(result, "Malmborg in Plano!", "format basic")
|
||||
})
|
||||
|
||||
run("format record", function() {
|
||||
var result = format("{name} is {age}", {name: "Alice", age: "30"})
|
||||
assert_eq(result, "Alice is 30", "format record")
|
||||
})
|
||||
|
||||
run("format missing key unchanged", function() {
|
||||
var result = format("{0} and {1}", ["hello"])
|
||||
assert_eq(starts_with(result, "hello"), true, "format missing keeps prefix")
|
||||
})
|
||||
|
||||
// --- arrfor edge cases ---
|
||||
|
||||
run("arrfor reverse with exit", function() {
|
||||
var visited = []
|
||||
var result = arrfor([10, 20, 30], function(x) {
|
||||
visited[] = x
|
||||
if (x == 20) return true
|
||||
return null
|
||||
}, true, true)
|
||||
assert_eq(result, true, "arrfor rev exit returns exit value")
|
||||
assert_eq(visited[0], 30, "arrfor rev exit started from end")
|
||||
})
|
||||
|
||||
run("arrfor with index", function() {
|
||||
var indices = []
|
||||
arrfor([10, 20, 30], function(x, i) { indices[] = i })
|
||||
assert_eq(indices[0], 0, "arrfor idx [0]")
|
||||
assert_eq(indices[2], 2, "arrfor idx [2]")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// SUMMARY
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user