// Comprehensive test suite for cell runtime stability // Self-running: no test harness required // Uses disrupt/disruption instead of throw/try/catch var passed = 0 var failed = 0 var error_names = [] var error_reasons = [] var fail_msg = "" // pre-allocate 500 slots to avoid array growth during disruption handlers var _i = 0 for (_i = 0; _i < 5; _i++) { error_names[] = null error_reasons[] = null } var fail = function(msg) { fail_msg = msg disrupt } var assert_eq = function(actual, expected, msg) { if (actual != expected) fail(msg + " (got=" + text(actual) + ")") } var run = function(name, fn) { fail_msg = "" fn() passed = passed + 1 } disruption { error_names[failed] = name error_reasons[failed] = fail_msg == "" ? "disruption" : fail_msg failed = failed + 1 } var should_disrupt = function(fn) { var caught = false var wrapper = function() { fn() } disruption { caught = true } wrapper() return caught } // ============================================================================ // ARITHMETIC OPERATORS - Numbers // ============================================================================ run("number addition", function() { if (1 + 2 != 3) fail("basic addition failed") if (0 + 0 != 0) fail("zero addition failed") if (-5 + 3 != -2) fail("negative addition failed") if (0.1 + 0.2 - 0.3 > 0.0001) fail("float addition precision issue") }) run("number subtraction", function() { if (5 - 3 != 2) fail("basic subtraction failed") if (0 - 5 != -5) fail("zero subtraction failed") if (-5 - -3 != -2) fail("negative subtraction failed") }) run("number multiplication", function() { if (3 * 4 != 12) fail("basic multiplication failed") if (0 * 100 != 0) fail("zero multiplication failed") if (-3 * 4 != -12) fail("negative multiplication failed") if (-3 * -4 != 12) fail("double negative multiplication failed") }) run("number division", function() { if (12 / 4 != 3) fail("basic division failed") if (1 / 2 != 0.5) fail("fractional division failed") if (-12 / 4 != -3) fail("negative division failed") if (12 / -4 != -3) fail("division by negative failed") }) run("number modulo", function() { if (10 % 3 != 1) fail("basic modulo failed") if (10 % 5 != 0) fail("even modulo failed") if (-10 % 3 != -1) fail("negative modulo failed") }) run("number exponentiation", function() { if (2 ** 3 != 8) fail("basic exponentiation failed") if (5 ** 0 != 1) fail("zero exponent failed") if (2 ** -1 != 0.5) fail("negative exponent failed") }) // ============================================================================ // STRING OPERATORS // ============================================================================ run("string plus string", function() { var x = "hello" + " world" if (x != "hello world") fail("string + string should work") }) run("string concatenation empty", function() { if ("" + "" != "") fail("empty string concatenation failed") if ("hello" + "" != "hello") fail("concatenation with empty string failed") if ("" + "world" != "world") fail("empty + string failed") }) run("string concat does not mutate alias", function() { var a = "hello world" var b = a a = a + " appended" if (a != "hello world appended") fail("a wrong, got " + a) if (b != "hello world") fail("b should still be hello world, got " + b) }) run("string concat in loop preserves aliases", function() { var a = "starting value" var copies = [a] var i = 0 while (i < 5) { a = a + " more" copies[] = a i = i + 1 } if (copies[0] != "starting value") fail("copies[0] wrong, got " + copies[0]) if (copies[1] != "starting value more") fail("copies[1] wrong, got " + copies[1]) if (copies[5] != "starting value more more more more more") fail("copies[5] wrong, got " + copies[5]) if (a != "starting value more more more more more") fail("a wrong, got " + a) }) // ============================================================================ // TYPE MIXING SHOULD DISRUPT // ============================================================================ run("number plus string disrupts", function() { if (!should_disrupt(function() { var x = 1 + "hello" })) fail("number + string should disrupt") }) run("string plus number disrupts", function() { if (!should_disrupt(function() { var x = "hello" + 1 })) fail("string + number should disrupt") }) run("object plus string disrupts", function() { if (!should_disrupt(function() { var x = {} + "hello" })) fail("object + string should disrupt") }) run("string plus object disrupts", function() { if (!should_disrupt(function() { var x = "hello" + {} })) fail("string + object should disrupt") }) run("array plus string disrupts", function() { if (!should_disrupt(function() { var x = [] + "hello" })) fail("array + string should disrupt") }) run("string plus array disrupts", function() { if (!should_disrupt(function() { var x = "hello" + [] })) fail("string + array should disrupt") }) run("boolean plus string disrupts", function() { if (!should_disrupt(function() { var x = true + "hello" })) fail("boolean + string should disrupt") }) run("string plus boolean disrupts", function() { if (!should_disrupt(function() { var x = "hello" + false })) fail("string + boolean should disrupt") }) run("null plus string disrupts", function() { if (!should_disrupt(function() { var x = null + "hello" })) fail("null + string should disrupt") }) run("string plus null disrupts", function() { if (!should_disrupt(function() { var x = "hello" + null })) fail("string + null should disrupt") }) // ============================================================================ // COMPARISON OPERATORS // ============================================================================ run("equality numbers", function() { if (!(5 == 5)) fail("number equality failed") if (5 == 6) fail("number inequality detection failed") if (!(0 == 0)) fail("zero equality failed") if (!(-5 == -5)) fail("negative equality failed") }) run("inequality numbers", function() { if (5 != 5) fail("number inequality failed") if (!(5 != 6)) fail("number difference detection failed") }) run("less than", function() { if (!(3 < 5)) fail("less than failed") if (5 < 3) fail("not less than failed") if (5 < 5) fail("equal not less than failed") }) run("less than or equal", function() { if (!(3 <= 5)) fail("less than or equal failed") if (!(5 <= 5)) fail("equal in less than or equal failed") if (6 <= 5) fail("not less than or equal failed") }) run("greater than", function() { if (!(5 > 3)) fail("greater than failed") if (3 > 5) fail("not greater than failed") if (5 > 5) fail("equal not greater than failed") }) run("greater than or equal", function() { if (!(5 >= 3)) fail("greater than or equal failed") if (!(5 >= 5)) fail("equal in greater than or equal failed") if (3 >= 5) fail("not greater than or equal failed") }) run("string equality", function() { if (!("hello" == "hello")) fail("string equality failed") if ("hello" == "world") fail("string inequality detection failed") if (!("" == "")) fail("empty string equality failed") }) run("null equality", function() { if (!(null == null)) fail("null equality failed") if (null == 0) fail("null should not equal 0") if (null == false) fail("null should not equal false") if (null == "") fail("null should not equal empty string") }) run("boolean equality", function() { if (!(true == true)) fail("true equality failed") if (!(false == false)) fail("false equality failed") if (true == false) fail("boolean inequality detection failed") }) // ============================================================================ // LOGICAL OPERATORS // ============================================================================ run("logical and", function() { if (!(true && true)) fail("true && true failed") if (true && false) fail("true && false failed") if (false && true) fail("false && true failed") if (false && false) fail("false && false failed") }) run("logical or", function() { if (!(true || true)) fail("true || true failed") if (!(true || false)) fail("true || false failed") if (!(false || true)) fail("false || true failed") if (false || false) fail("false || false failed") }) run("logical not", function() { if (!(!false)) fail("!false failed") if (!true) fail("!true failed") }) run("short circuit and", function() { var called = false var fn = function() { called = true; return true } var result = false && fn() if (called) fail("AND should short circuit") }) run("short circuit or", function() { var called = false var fn = function() { called = true; return false } var result = true || fn() if (called) fail("OR should short circuit") }) // ============================================================================ // BITWISE OPERATORS // ============================================================================ run("bitwise and", function() { if ((5 & 3) != 1) fail("bitwise AND failed") if ((12 & 10) != 8) fail("bitwise AND failed") }) run("bitwise or", function() { if ((5 | 3) != 7) fail("bitwise OR failed") if ((12 | 10) != 14) fail("bitwise OR failed") }) run("bitwise xor", function() { if ((5 ^ 3) != 6) fail("bitwise XOR failed") if ((12 ^ 10) != 6) fail("bitwise XOR failed") }) run("bitwise not", function() { if (~5 != -6) fail("bitwise NOT failed") if (~0 != -1) fail("bitwise NOT of zero failed") }) run("left shift", function() { if ((5 << 2) != 20) fail("left shift failed") if ((1 << 3) != 8) fail("left shift failed") }) run("right shift", function() { if ((20 >> 2) != 5) fail("right shift failed") if ((8 >> 3) != 1) fail("right shift failed") }) run("unsigned right shift", function() { if ((-1 >>> 1) != 2147483647) fail("unsigned right shift failed") }) // ============================================================================ // VARIABLE DECLARATIONS AND SCOPING // ============================================================================ run("var declaration", function() { var x = 5 if (x != 5) fail("var declaration failed") }) run("var reassignment", function() { var x = 5 x = 10 if (x != 10) fail("var reassignment failed") }) run("var null initialization", function() { var x = null if (x != null) fail("null-initialized var should be null") }) run("multiple var declaration", function() { var a = 1, b = 2, c = 3 if (a != 1 || b != 2 || c != 3) fail("multiple var declaration failed") }) run("function scope", function() { var outer = "outer" var fn = function() { var inner = "inner" return inner } if (fn() != "inner") fail("function scope failed") }) // ============================================================================ // FUNCTION CALLS // ============================================================================ run("function call no args", function() { var fn = function() { return 42 } if (fn() != 42) fail("function call with no args failed") }) run("function call one arg", function() { var fn = function(x) { return x * 2 } if (fn(5) != 10) fail("function call with one arg failed") }) run("function call multiple args", function() { var fn = function(a, b, c) { return a + b + c } if (fn(1, 2, 3) != 6) fail("function call with multiple args failed") }) run("function call extra args disrupts", function() { var fn = function(a, b) { return a + b } if (!should_disrupt(function() { fn(1, 2, 3, 4) })) fail("function call with extra args should disrupt") }) run("function call missing args", function() { var fn = function(a, b, c) { return (a || 0) + (b || 0) + (c || 0) } if (fn(1) != 1) fail("function call with missing args failed") }) run("closure call extra args disrupts", function() { var mk = function(base) { return function(x) { return base + x } } var add5 = mk(5) if (!should_disrupt(function() { add5(1, 2) })) fail("closure call with extra args should disrupt") }) run("call helper extra args disrupts", function() { var fn = function(a, b) { return a + b } if (!should_disrupt(function() { call(fn, null, [1, 2, 3]) })) fail("call helper should disrupt on extra args") }) run("function return", function() { var fn = function() { return 5 } if (fn() != 5) fail("function return failed") }) run("function return early", function() { var fn = function() { return 5 return 10 } if (fn() != 5) fail("early return failed") }) run("function no return", function() { var fn = function() { var x = 5 } if (fn() != null) fail("function with no return should return null") }) run("nested function calls", function() { var add = function(a, b) { return a + b } var mul = function(a, b) { return a * b } if (add(mul(2, 3), mul(4, 5)) != 26) fail("nested function calls failed") }) run("function as value", function() { var fn = function() { return 42 } var fn2 = fn if (fn2() != 42) fail("function as value failed") }) run("function closure", function() { var outer = function(x) { return function(y) { return x + y } } var add5 = outer(5) if (add5(3) != 8) fail("closure failed") }) run("function closure mutation", function() { var counter = function() { var count = 0 return function() { count = count + 1 return count } } var c = counter() if (c() != 1) fail("closure mutation failed (1)") if (c() != 2) fail("closure mutation failed (2)") if (c() != 3) fail("closure mutation failed (3)") }) // ============================================================================ // RECURSION // ============================================================================ run("simple recursion", function() { var factorial = function(n) { if (n <= 1) return 1 return n * factorial(n - 1) } if (factorial(5) != 120) fail("factorial recursion failed") }) run("mutual recursion", function() { var isEven = function(n) { if (n == 0) return true return isOdd(n - 1) } var isOdd = function(n) { if (n == 0) return false return isEven(n - 1) } if (!isEven(4)) fail("mutual recursion even failed") if (isOdd(4)) fail("mutual recursion odd failed") }) run("deep recursion", function() { var sum = function(n) { if (n == 0) return 0 return n + sum(n - 1) } if (sum(100) != 5050) fail("deep recursion failed") }) // ============================================================================ // ARRAYS // ============================================================================ run("array literal", function() { var arr = [1, 2, 3] if (arr[0] != 1 || arr[1] != 2 || arr[2] != 3) fail("array literal failed") }) run("array length", function() { var arr = [1, 2, 3, 4, 5] if (length(arr) != 5) fail("array length failed") }) run("array empty", function() { var arr = [] if (length(arr) != 0) fail("empty array length failed") }) run("array push", function() { var arr = [1, 2] arr[] = 3 if (length(arr) != 3) fail("array push length failed") if (arr[2] != 3) fail("array push value failed") }) run("array pop", function() { var arr = [1, 2, 3] var val = arr[] if (val != 3) fail("array pop value failed") if (length(arr) != 2) fail("array pop length failed") }) run("array index access", function() { var arr = [10, 20, 30] if (arr[0] != 10) fail("array index 0 failed") if (arr[1] != 20) fail("array index 1 failed") if (arr[2] != 30) fail("array index 2 failed") }) run("array index assignment", function() { var arr = [1, 2, 3] arr[1] = 99 if (arr[1] != 99) fail("array index assignment failed") }) run("array mixed types", function() { var arr = [1, "hello", true, null, {}] if (arr[0] != 1) fail("mixed array number failed") if (arr[1] != "hello") fail("mixed array string failed") if (arr[2] != true) fail("mixed array boolean failed") if (arr[3] != null) fail("mixed array null failed") }) run("array nested", function() { var arr = [[1, 2], [3, 4]] if (arr[0][0] != 1) fail("nested array access failed") if (arr[1][1] != 4) fail("nested array access failed") }) // ============================================================================ // OBJECTS // ============================================================================ run("object literal", function() { var obj = {a: 1, b: 2} if (obj.a != 1 || obj.b != 2) fail("object literal failed") }) run("object property access", function() { var obj = {name: "Alice", age: 30} if (obj.name != "Alice") fail("object property access failed") if (obj.age != 30) fail("object property access failed") }) run("object bracket access", function() { var obj = {x: 10, y: 20} if (obj["x"] != 10) fail("object bracket access failed") if (obj["y"] != 20) fail("object bracket access failed") }) run("object property assignment", function() { var obj = {a: 1} obj.a = 99 if (obj.a != 99) fail("object property assignment failed") }) run("object add property", function() { var obj = {} obj.newProp = 42 if (obj.newProp != 42) fail("object add property failed") }) run("object computed property", function() { var key = "dynamicKey" var obj = {} obj[key] = 123 if (obj.dynamicKey != 123) fail("object computed property failed") }) run("object nested", function() { var obj = {outer: {inner: 42}} if (obj.outer.inner != 42) fail("nested object access failed") }) run("object method", function() { var obj = { value: 10, getValue: function() { return this.value } } if (obj.getValue() != 10) fail("object method failed") }) run("object this binding", function() { var obj = { x: 5, getX: function() { return this.x } } if (obj.getX() != 5) fail("this binding failed") }) // ============================================================================ // CONTROL FLOW - IF/ELSE // ============================================================================ run("if true", function() { var x = 0 if (true) x = 1 if (x != 1) fail("if true failed") }) run("if false", function() { var x = 0 if (false) x = 1 if (x != 0) fail("if false failed") }) run("if else true", function() { var x = 0 if (true) x = 1 else x = 2 if (x != 1) fail("if else true failed") }) run("if else false", function() { var x = 0 if (false) x = 1 else x = 2 if (x != 2) fail("if else false failed") }) run("if else if", function() { var x = 0 if (false) x = 1 else if (true) x = 2 else x = 3 if (x != 2) fail("if else if failed") }) run("nested if", function() { var x = 0 if (true) { if (true) { x = 1 } } if (x != 1) fail("nested if failed") }) // ============================================================================ // CONTROL FLOW - WHILE LOOPS // ============================================================================ run("while loop", function() { var i = 0 var sum = 0 while (i < 5) { sum = sum + i i = i + 1 } if (sum != 10) fail("while loop failed") }) run("while never executes", function() { var x = 0 while (false) { x = 1 } if (x != 0) fail("while never executes failed") }) run("while break", function() { var i = 0 while (true) { if (i >= 5) break i = i + 1 } if (i != 5) fail("while break failed") }) run("while continue", function() { var i = 0 var sum = 0 while (i < 10) { i = i + 1 if (i % 2 == 0) continue sum = sum + i } if (sum != 25) fail("while continue failed") }) // ============================================================================ // CONTROL FLOW - FOR LOOPS // ============================================================================ run("for loop", function() { var sum = 0 var i = 0 for (i = 0; i < 5; i = i + 1) { sum = sum + i } if (sum != 10) fail("for loop failed") }) run("for loop break", function() { var sum = 0 var i = 0 for (i = 0; i < 10; i = i + 1) { if (i == 5) break sum = sum + i } if (sum != 10) fail("for loop break failed") }) run("for loop continue", function() { var sum = 0 var i = 0 for (i = 0; i < 10; i = i + 1) { if (i % 2 == 0) continue sum = sum + i } if (sum != 25) fail("for loop continue failed") }) run("nested for loops", function() { var sum = 0 var i = 0, j = 0 for (i = 0; i < 3; i = i + 1) { for (j = 0; j < 3; j = j + 1) { sum = sum + 1 } } if (sum != 9) fail("nested for loops failed") }) // ============================================================================ // DISRUPTION HANDLING // ============================================================================ run("disruption caught", function() { if (!should_disrupt(function() { disrupt })) fail("disruption not caught") }) run("no disruption path", function() { var x = 0 var attempt = function() { x = 1 } disruption { x = 2 } attempt() if (x != 1) fail("non-disrupting code should not trigger disruption clause") }) run("nested disruption", function() { var x = 0 var outer = function() { var inner = function() { disrupt } disruption { x = 1 } inner() x = 2 } disruption { x = 3 } outer() if (x != 2) fail("nested disruption failed") }) run("disruption re-raise", function() { var outer_caught = false var outer = function() { var inner = function() { disrupt } disruption { disrupt } inner() } disruption { outer_caught = true } outer() if (!outer_caught) fail("disruption re-raise failed") }) run("disruption handler reads outer vars", function() { var msg = "hello" var result = null var fn = function() { disrupt } disruption { result = msg } fn() if (result != "hello") fail("handler could not read outer var") }) run("disruption handler modifies outer vars", function() { var count = 0 var name = "before" var fn = function() { count = count + 1 disrupt } disruption { count = count + 10 name = "after" } fn() if (count != 11) fail("expected 11 got " + text(count)) if (name != "after") fail("expected 'after' got " + name) }) run("disruption handler reads function locals", function() { var result = null var fn = function() { var local_val = 42 var inner = function() { disrupt } disruption { result = local_val } inner() } fn() if (result != 42) fail("handler could not read local, got " + text(result)) }) run("disruption handler with closure", function() { var results = [] var make_fn = function(tag) { return function() { disrupt } disruption { results[] = tag } } var fn_a = make_fn("a") var fn_b = make_fn("b") fn_a() fn_b() if (length(results) != 2) fail("expected 2 results") if (results[0] != "a") fail("first closure tag wrong") if (results[1] != "b") fail("second closure tag wrong") }) run("disruption handler modifies loop state", function() { var total = 0 var i = 0 var fn = null while (i < 3) { fn = function() { disrupt } disruption { total = total + i } fn() i = i + 1 } if (total != 3) fail("expected 3 got " + text(total)) }) run("disruption handler accesses object from outer scope", function() { var obj = {x: 1, y: 2} var fn = function() { obj.x = 10 disrupt } disruption { obj.y = 20 } fn() if (obj.x != 10) fail("body mutation lost, x=" + text(obj.x)) if (obj.y != 20) fail("handler mutation lost, y=" + text(obj.y)) }) run("disruption in callback with multiple calls after", function() { // Regression: a function with a disruption handler that calls a // callback which disrupts, followed by more successful calls. // In native mode, cell_rt_disrupt must NOT use JS_ThrowTypeError // (which prints to stderr) — it must silently set the exception. var log = [] var run_inner = function(name, fn) { fn() log[] = "pass:" + name } disruption { log[] = "fail:" + name } run_inner("a", function() { var x = 1 }) run_inner("b", function() { disrupt }) run_inner("c", function() { var y = 2 }) if (length(log) != 3) fail("expected 3 log entries, got " + text(length(log))) if (log[0] != "pass:a") fail("expected pass:a, got " + log[0]) if (log[1] != "fail:b") fail("expected fail:b, got " + log[1]) if (log[2] != "pass:c") fail("expected pass:c, got " + log[2]) }) // ============================================================================ // TYPE CHECKING WITH is_* FUNCTIONS // ============================================================================ run("is_number", function() { if (!is_number(42)) fail("is_number 42 failed") if (!is_number(3.14)) fail("is_number float failed") if (!is_number(-5)) fail("is_number negative failed") if (is_number("42")) fail("is_number string should be false") if (is_number(true)) fail("is_number boolean should be false") if (is_number(null)) fail("is_number null should be false") if (is_number({})) fail("is_number object should be false") if (is_number([])) fail("is_number array should be false") }) run("is_text", function() { if (!is_text("hello")) fail("is_text string failed") if (!is_text("")) fail("is_text empty string failed") if (is_text(42)) fail("is_text number should be false") if (is_text(true)) fail("is_text boolean should be false") if (is_text(null)) fail("is_text null should be false") if (is_text({})) fail("is_text object should be false") if (is_text([])) fail("is_text array should be false") }) run("is_logical", function() { if (!is_logical(true)) fail("is_logical true failed") if (!is_logical(false)) fail("is_logical false failed") if (is_logical(1)) fail("is_logical number should be false") if (is_logical("true")) fail("is_logical string should be false") if (is_logical(null)) fail("is_logical null should be false") if (is_logical({})) fail("is_logical object should be false") if (is_logical([])) fail("is_logical array should be false") }) run("is_object", function() { if (!is_object({})) fail("is_object empty object failed") if (!is_object({a: 1})) fail("is_object object failed") if (is_object([])) fail("is_object array should be false") if (is_object(null)) fail("is_object null should be false") if (is_object(42)) fail("is_object number should be false") if (is_object("hello")) fail("is_object string should be false") if (is_object(true)) fail("is_object boolean should be false") }) run("is_array", function() { if (!is_array([])) fail("is_array empty array failed") if (!is_array([1, 2, 3])) fail("is_array array failed") if (is_array({})) fail("is_array object should be false") if (is_array(null)) fail("is_array null should be false") if (is_array(42)) fail("is_array number should be false") if (is_array("hello")) fail("is_array string should be false") if (is_array(true)) fail("is_array boolean should be false") }) run("is_function", function() { if (!is_function(function(){})) fail("is_function function failed") var fn = function(x) { return x * 2 } if (!is_function(fn)) fail("is_function named function failed") if (is_function({})) fail("is_function object should be false") if (is_function([])) fail("is_function array should be false") if (is_function(null)) fail("is_function null should be false") if (is_function(42)) fail("is_function number should be false") if (is_function("hello")) fail("is_function string should be false") if (is_function(true)) fail("is_function boolean should be false") }) run("is_null", function() { if (!is_null(null)) fail("is_null null failed") if (is_null(0)) fail("is_null zero should be false") if (is_null(false)) fail("is_null false should be false") if (is_null("")) fail("is_null empty string should be false") if (is_null({})) fail("is_null object should be false") if (is_null([])) fail("is_null array should be false") var x = null if (!is_null(x)) fail("is_null undefined variable should be true") }) run("is_blob", function() { if (is_blob(null)) fail("is_blob null should be false") if (is_blob(42)) fail("is_blob number should be false") if (is_blob("hello")) fail("is_blob string should be false") if (is_blob(true)) fail("is_blob boolean should be false") if (is_blob({})) fail("is_blob object should be false") if (is_blob([])) fail("is_blob array should be false") if (is_blob(function(){})) fail("is_blob function should be false") }) run("is_proto", function() { var a = {} var b = meme(a) if (!is_proto(b, a)) fail("is_proto failed on meme") }) 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(null)) fail("is_data null should be false") if (is_data(true)) fail("is_data bool should be false") if (is_data(function(){})) fail("is_data function should be false") }) run("is_true", function() { if (!is_true(true)) fail("is_true true should be true") if (is_true(false)) fail("is_true false should be false") if (is_true(1)) fail("is_true 1 should be false") if (is_true("true")) fail("is_true string should be false") if (is_true(null)) fail("is_true null should be false") }) run("is_false", function() { if (!is_false(false)) fail("is_false false should be true") if (is_false(true)) fail("is_false true should be false") if (is_false(0)) fail("is_false 0 should be false") if (is_false("")) fail("is_false empty string should be false") if (is_false(null)) fail("is_false null should be false") }) run("is_fit", function() { if (!is_fit(0)) fail("is_fit 0 should be true") if (!is_fit(42)) fail("is_fit 42 should be true") if (!is_fit(-100)) fail("is_fit -100 should be true") if (!is_fit(3.0)) fail("is_fit 3.0 should be true") if (is_fit(3.5)) fail("is_fit 3.5 should be false") if (is_fit("42")) fail("is_fit string should be false") if (is_fit(null)) fail("is_fit null should be false") }) run("is_character", function() { if (!is_character("a")) fail("is_character a should be true") if (!is_character("Z")) fail("is_character Z should be true") if (!is_character("5")) fail("is_character 5 should be true") if (!is_character(" ")) fail("is_character space should be true") if (is_character("ab")) fail("is_character ab should be false") if (is_character("")) fail("is_character empty should be false") if (is_character(42)) fail("is_character number should be false") if (is_character(null)) fail("is_character null should be false") }) run("is_digit", function() { if (!is_digit("0")) fail("is_digit 0 should be true") if (!is_digit("5")) fail("is_digit 5 should be true") if (!is_digit("9")) fail("is_digit 9 should be true") if (is_digit("a")) fail("is_digit a should be false") if (is_digit("55")) fail("is_digit 55 should be false") if (is_digit(5)) fail("is_digit number should be false") if (is_digit(null)) fail("is_digit null should be false") }) run("is_letter", function() { if (!is_letter("a")) fail("is_letter a should be true") if (!is_letter("Z")) fail("is_letter Z should be true") if (is_letter("5")) fail("is_letter 5 should be false") if (is_letter("ab")) fail("is_letter ab should be false") if (is_letter(42)) fail("is_letter number should be false") }) run("is_lower", function() { if (!is_lower("a")) fail("is_lower a should be true") if (!is_lower("z")) fail("is_lower z should be true") if (is_lower("A")) fail("is_lower A should be false") if (is_lower("5")) fail("is_lower 5 should be false") if (is_lower("ab")) fail("is_lower ab should be false") if (is_lower(42)) fail("is_lower number should be false") }) run("is_upper", function() { if (!is_upper("A")) fail("is_upper A should be true") if (!is_upper("Z")) fail("is_upper Z should be true") if (is_upper("a")) fail("is_upper a should be false") if (is_upper("5")) fail("is_upper 5 should be false") if (is_upper("AB")) fail("is_upper AB should be false") if (is_upper(42)) fail("is_upper number should be false") }) run("is_whitespace", function() { if (!is_whitespace(" ")) fail("is_whitespace space should be true") if (!is_whitespace("\t")) fail("is_whitespace tab should be true") if (!is_whitespace("\n")) fail("is_whitespace newline should be true") if (is_whitespace("a")) fail("is_whitespace a should be false") if (is_whitespace(" ")) fail("is_whitespace two spaces should be false") if (is_whitespace(42)) fail("is_whitespace number should be false") if (is_whitespace(null)) fail("is_whitespace null should be false") }) // ============================================================================ // GLOBAL FUNCTIONS - LENGTH // ============================================================================ run("length string", function() { if (length("hello") != 5) fail("length string failed") if (length("") != 0) fail("length empty string failed") }) run("length array", function() { if (length([1,2,3]) != 3) fail("length array failed") if (length([]) != 0) fail("length empty array failed") }) run("length null", function() { if (length(null) != null) fail("length null failed") }) run("length number", function() { if (length(123) != null) fail("length number should return null") }) // ============================================================================ // GLOBAL FUNCTIONS - REVERSE // ============================================================================ run("reverse array", function() { var arr = [1, 2, 3, 4, 5] var rev = reverse(arr) if (rev[0] != 5) fail("reverse array first failed") if (rev[4] != 1) fail("reverse array last failed") if (length(rev) != 5) fail("reverse array length failed") }) run("reverse empty array", function() { var rev = reverse([]) if (length(rev) != 0) fail("reverse empty array failed") }) run("reverse preserves original", function() { var arr = [1, 2, 3] var rev = reverse(arr) if (arr[0] != 1) fail("reverse should not mutate original") }) // ============================================================================ // GLOBAL FUNCTIONS - MEME (PROTOTYPAL INHERITANCE) // ============================================================================ run("meme basic", function() { var parent = {x: 10} var child = meme(parent) if (child.x != 10) fail("meme basic inheritance failed") }) run("meme with mixins", function() { var parent = {x: 10} var mixin = {y: 20} var child = meme(parent, mixin) if (child.x != 10) fail("meme with mixin parent prop failed") if (child.y != 20) fail("meme with mixin own prop failed") }) run("meme override", function() { var parent = {x: 10} var child = meme(parent) child.x = 20 if (child.x != 20) fail("meme override failed") if (parent.x != 10) fail("meme should not mutate parent") }) run("meme multiple mixins", function() { var parent = {a: 1} var mixin1 = {b: 2} var mixin2 = {c: 3} var child = meme(parent, [mixin1, mixin2]) if (child.a != 1 || child.b != 2 || child.c != 3) fail("meme multiple mixins failed") }) // ============================================================================ // GLOBAL FUNCTIONS - PROTO // ============================================================================ run("proto basic", function() { var parent = {x: 10} var child = meme(parent) var p = proto(child) if (p != parent) fail("proto basic failed") }) run("proto object literal", function() { var obj = {x: 10} var p = proto(obj) if (p != null) fail("proto of object literal should be null") }) run("proto non object", function() { if (proto(42) != null) fail("proto of number should return null") if (proto("hello") != null) fail("proto of string should return null") }) // ============================================================================ // GLOBAL FUNCTIONS - STONE (FREEZE) // ============================================================================ run("stone object", function() { var obj = {x: 10} stone(obj) if (!should_disrupt(function() { obj.x = 20 })) fail("stone object should prevent modification") }) run("is_stone frozen", function() { var obj = {x: 10} if (is_stone(obj)) fail("stone.p should return false before freezing") stone(obj) if (!is_stone(obj)) fail("stone.p should return true after freezing") }) run("stone array", function() { var arr = [1, 2, 3] stone(arr) if (!should_disrupt(function() { arr[0] = 99 })) fail("stone array should prevent modification") }) // ============================================================================ // TERNARY OPERATOR // ============================================================================ run("ternary true", function() { var x = true ? 1 : 2 if (x != 1) fail("ternary true failed") }) run("ternary false", function() { var x = false ? 1 : 2 if (x != 2) fail("ternary false failed") }) run("ternary nested", function() { var x = true ? (false ? 1 : 2) : 3 if (x != 2) fail("ternary nested failed") }) run("ternary with expressions", function() { var a = 5 var b = 10 var max = (a > b) ? a : b if (max != 10) fail("ternary with expressions failed") }) // ============================================================================ // UNARY OPERATORS // ============================================================================ run("unary plus", function() { if (+5 != 5) fail("unary plus positive failed") if (+-5 != -5) fail("unary plus negative failed") }) run("unary minus", function() { if (-5 != -5) fail("unary minus failed") if (-(-5) != 5) fail("double unary minus failed") }) run("increment postfix", function() { var x = 5 var y = x++ if (y != 5) fail("postfix increment return value failed") if (x != 6) fail("postfix increment side effect failed") }) run("increment prefix", function() { var x = 5 var y = ++x if (y != 6) fail("prefix increment return value failed") if (x != 6) fail("prefix increment side effect failed") }) run("decrement postfix", function() { var x = 5 var y = x-- if (y != 5) fail("postfix decrement return value failed") if (x != 4) fail("postfix decrement side effect failed") }) run("decrement prefix", function() { var x = 5 var y = --x if (y != 4) fail("prefix decrement return value failed") if (x != 4) fail("prefix decrement side effect failed") }) // ============================================================================ // COMPOUND ASSIGNMENT OPERATORS // ============================================================================ run("plus equals", function() { var x = 5 x += 3 if (x != 8) fail("plus equals failed") }) run("minus equals", function() { var x = 10 x -= 3 if (x != 7) fail("minus equals failed") }) run("times equals", function() { var x = 4 x *= 3 if (x != 12) fail("times equals failed") }) run("divide equals", function() { var x = 12 x /= 3 if (x != 4) fail("divide equals failed") }) run("modulo equals", function() { var x = 10 x %= 3 if (x != 1) fail("modulo equals failed") }) // ============================================================================ // COMPOUND ASSIGNMENT ON PROPERTIES // ============================================================================ run("plus equals on property", function() { var obj = {x: 10} obj.x += 5 if (obj.x != 15) fail("obj.x += 5") }) run("minus equals on property", function() { var obj = {x: 10} obj.x -= 3 if (obj.x != 7) fail("obj.x -= 3") }) run("times equals on property", function() { var obj = {x: 4} obj.x *= 3 if (obj.x != 12) fail("obj.x *= 3") }) run("divide equals on property", function() { var obj = {x: 12} obj.x /= 3 if (obj.x != 4) fail("obj.x /= 3") }) run("modulo equals on property", function() { var obj = {x: 10} obj.x %= 3 if (obj.x != 1) fail("obj.x %= 3") }) run("compound assign preserves other properties", function() { var obj = {x: 10, y: 20} obj.x += 5 if (obj.x != 15) fail("x wrong") if (obj.y != 20) fail("y changed") }) run("compound assign nested property", function() { var obj = {inner: {val: 10}} obj.inner.val += 5 if (obj.inner.val != 15) fail("nested += failed") }) run("compound assign chained", function() { var obj = {x: 1} obj.x += 2 obj.x += 3 obj.x += 4 if (obj.x != 10) fail("chained += failed") }) // ============================================================================ // COMPOUND ASSIGNMENT ON ARRAY ELEMENTS // ============================================================================ run("plus equals on array element", function() { var arr = [100, 200, 300] arr[0] += 50 if (arr[0] != 150) fail("arr[0] += 50") }) run("minus equals on array element", function() { var arr = [100, 200, 300] arr[1] -= 50 if (arr[1] != 150) fail("arr[1] -= 50") }) run("times equals on array element", function() { var arr = [5] arr[0] *= 6 if (arr[0] != 30) fail("arr[0] *= 6") }) run("compound assign computed property", function() { var obj = {a: 10, b: 20} var key = "a" obj[key] += 5 if (obj.a != 15) fail("obj[key] += 5") }) run("compound assign array element preserves others", function() { var arr = [10, 20, 30] arr[1] += 5 if (arr[0] != 10) fail("arr[0] changed") if (arr[1] != 25) fail("arr[1] wrong") if (arr[2] != 30) fail("arr[2] changed") }) // ============================================================================ // PREFIX INCREMENT/DECREMENT ON PROPERTIES // ============================================================================ run("prefix increment on property", function() { var obj = {x: 5} var y = ++obj.x if (y != 6) fail("return value") if (obj.x != 6) fail("side effect") }) run("prefix decrement on property", function() { var obj = {x: 5} var y = --obj.x if (y != 4) fail("return value") if (obj.x != 4) fail("side effect") }) run("prefix increment on array element", function() { var arr = [10] var y = ++arr[0] if (y != 11) fail("return value") if (arr[0] != 11) fail("side effect") }) run("prefix decrement on array element", function() { var arr = [10] var y = --arr[0] if (y != 9) fail("return value") if (arr[0] != 9) fail("side effect") }) // ============================================================================ // POSTFIX INCREMENT/DECREMENT ON PROPERTIES (Bug: never worked) // ============================================================================ run("postfix increment on property", function() { var obj = {x: 5} obj.x++ assert_eq(obj.x, 6, "obj.x should be 6 after obj.x++") }) run("postfix decrement on property", function() { var obj = {x: 5} obj.x-- assert_eq(obj.x, 4, "obj.x should be 4 after obj.x--") }) run("postfix increment on property return value", function() { var obj = {x: 5} var y = obj.x++ assert_eq(y, 5, "return value should be old value") assert_eq(obj.x, 6, "property should be incremented") }) run("postfix decrement on property return value", function() { var obj = {x: 5} var y = obj.x-- assert_eq(y, 5, "return value should be old value") assert_eq(obj.x, 4, "property should be decremented") }) run("postfix increment on array element", function() { var arr = [10, 20, 30] arr[1]++ assert_eq(arr[1], 21, "arr[1] should be 21 after arr[1]++") }) run("postfix decrement on array element", function() { var arr = [10, 20, 30] arr[1]-- assert_eq(arr[1], 19, "arr[1] should be 19 after arr[1]--") }) run("postfix increment on array element return value", function() { var arr = [10] var y = arr[0]++ assert_eq(y, 10, "return value should be old value") assert_eq(arr[0], 11, "array element should be incremented") }) run("postfix increment on computed property", function() { var obj = {a: 10} var key = "a" obj[key]++ assert_eq(obj.a, 11, "computed property should be incremented") }) run("postfix increment on nested property", function() { var obj = {inner: {val: 10}} obj.inner.val++ assert_eq(obj.inner.val, 11, "nested property should be incremented") }) run("postfix increment property in loop", function() { var obj = {count: 0} var i = 0 for (i = 0; i < 5; i++) { obj.count++ } assert_eq(obj.count, 5, "property should be 5 after 5 increments") }) // ============================================================================ // POSTFIX INCREMENT ON CLOSURE PROPERTIES (Original reported bug) // ============================================================================ run("postfix increment closure property", function() { var obj = {x: 0} var fn = function() { obj.x++ } fn() assert_eq(obj.x, 1, "closure property should be incremented") }) run("postfix decrement closure property", function() { var obj = {x: 5} var fn = function() { obj.x-- } fn() assert_eq(obj.x, 4, "closure property should be decremented") }) run("postfix increment closure counter pattern", function() { var counter = {passed: 0, failed: 0} var pass = function() { counter.passed++ } var fail_fn = function() { counter.failed++ } pass() pass() pass() fail_fn() assert_eq(counter.passed, 3, "passed count") assert_eq(counter.failed, 1, "failed count") }) run("postfix increment deep closure property", function() { var obj = {x: 0} var fn = function() { var inner = function() { obj.x++ } inner() } fn() assert_eq(obj.x, 1, "deep closure property should be incremented") }) run("postfix increment closure array element", function() { var arr = [10] var fn = function() { arr[0]++ } fn() assert_eq(arr[0], 11, "closure array element should be incremented") }) run("postfix increment on closure variable", function() { var x = 5 var fn = function() { x++ } fn() assert_eq(x, 6, "closure variable should be incremented by postfix ++") }) // ============================================================================ // INCREMENT/DECREMENT IN LOOPS AND EXPRESSIONS // ============================================================================ run("compound assign in for loop", function() { var sum = 0 var i = 0 for (i = 0; i < 5; i++) { sum += i } if (sum != 10) fail("sum should be 10") }) run("compound assign property in loop", function() { var obj = {count: 0} var i = 0 for (i = 0; i < 5; i++) { obj.count += 1 } if (obj.count != 5) fail("count should be 5") }) run("prefix increment in expression", function() { var x = 5 var y = 10 + ++x if (y != 16) fail("10 + ++x should be 16") if (x != 6) fail("x should be 6") }) run("compound assign bitwise and-equals", function() { var x = 15 x &= 6 if (x != 6) fail("15 & 6 should be 6") }) run("compound assign bitwise or-equals", function() { var x = 5 x |= 3 if (x != 7) fail("5 | 3 should be 7") }) run("compound assign bitwise xor-equals", function() { var x = 5 x ^= 3 if (x != 6) fail("5 ^ 3 should be 6") }) run("compound assign left shift-equals", function() { var x = 1 x <<= 3 if (x != 8) fail("1 << 3 should be 8") }) run("compound assign right shift-equals", function() { var x = 16 x >>= 2 if (x != 4) fail("16 >> 2 should be 4") }) run("compound assign bitwise on property", function() { var obj = {flags: 5} obj.flags |= 8 if (obj.flags != 13) fail("5 | 8 should be 13") obj.flags &= 12 if (obj.flags != 12) fail("13 & 12 should be 12") }) // ============================================================================ // EDGE CASES AND SPECIAL VALUES // ============================================================================ run("division by zero is null", function() { var inf = 1 / 0 if (inf != null) fail("division by zero should be null") var ninf = -1 / 0 if (ninf != null) fail("negative division by zero should be null") }) run("zero div zero is null", function() { var nan = 0 / 0 if (nan != null) fail("0/0 should be null") }) run("empty string falsy", function() { if ("") fail("empty string should be falsy") }) run("zero falsy", function() { if (0) fail("zero should be falsy") }) run("null falsy", function() { if (null) fail("null should be falsy") }) run("false falsy", function() { if (false) fail("false should be falsy") }) run("nonempty string truthy", function() { if (!"hello") fail("non-empty string should be truthy") }) run("nonzero number truthy", function() { if (!42) fail("non-zero number should be truthy") }) run("object truthy", function() { if (!{}) fail("empty object should be truthy") }) run("array truthy", function() { if (![]) fail("empty array should be truthy") }) // ============================================================================ // OPERATOR PRECEDENCE // ============================================================================ run("precedence multiply add", function() { if (2 + 3 * 4 != 14) fail("multiply before add precedence failed") }) run("precedence parentheses", function() { if ((2 + 3) * 4 != 20) fail("parentheses precedence failed") }) run("precedence comparison logical", function() { if (!(1 < 2 && 3 < 4)) fail("comparison before logical precedence failed") }) run("precedence equality logical", function() { if (!(1 == 1 || 2 == 3)) fail("equality before logical precedence failed") }) run("precedence unary multiplication", function() { if (-2 * 3 != -6) fail("unary before multiplication precedence failed") }) // ============================================================================ // COMMA OPERATOR // ============================================================================ run("comma operator", function() { var x = (1, 2, 3) if (x != 3) fail("comma operator failed") }) run("comma operator with side effects", function() { var a = 0 var x = (a = 1, a = 2, a + 1) if (x != 3) fail("comma operator with side effects failed") if (a != 2) fail("comma operator side effects failed") }) // ============================================================================ // VARIABLE SHADOWING // ============================================================================ run("variable shadowing function", function() { var x = 10 var fn = function() { var x = 20 return x } if (fn() != 20) fail("function shadowing failed") if (x != 10) fail("outer variable after shadowing failed") }) run("variable shadowing nested", function() { var x = 10 var fn1 = function() { var x = 20 var fn2 = function() { var x = 30 return x } return fn2() + x } if (fn1() != 50) fail("nested shadowing failed") }) // ============================================================================ // FUNCTION ARITY // ============================================================================ run("function length property", function() { var fn0 = function() {} var fn1 = function(a) {} var fn2 = function(a, b) {} if (length(fn0) != 0) fail("function length 0 failed") if (length(fn1) != 1) fail("function length 1 failed") if (length(fn2) != 2) fail("function length 2 failed") }) // ============================================================================ // NULL AND UNDEFINED BEHAVIOR // ============================================================================ run("null initialized variable is null", function() { var x = null if (x != null) fail("null initialized variable should be null") }) // ============================================================================ // NUMBERS - SPECIAL OPERATIONS // ============================================================================ run("number plus empty string disrupts", function() { if (!should_disrupt(function() { var n = 42; var result = n + "" })) fail("number + string should disrupt") }) run("number division by zero", function() { var result = 1 / 0 if (result != null) fail("division by zero should give null") }) run("number negative division by zero", function() { var result = -1 / 0 if (result != null) fail("negative division by zero should give null") }) run("zero division by zero", function() { var result = 0 / 0 if (result != null) fail("0/0 should give null") }) run("negative zero normalized", function() { var nz = -0 if (nz != 0) fail("-0 should equal 0") var mul_nz = 0 * -1 if (mul_nz != 0) fail("0 * -1 should be 0") var neg_zero = -(0) if (neg_zero != 0) fail("-(0) should be 0") }) run("overflow is null", function() { var result = 1e38 * 1e38 if (result != null) fail("overflow should give null") }) run("modulo by zero is null", function() { var result = 5 % 0 if (result != null) fail("modulo by zero should give null") }) // ============================================================================ // OBJECT PROPERTY EXISTENCE // ============================================================================ run("in operator", function() { var obj = {a: 1, b: 2} if (!("a" in obj)) fail("in operator for existing property failed") if ("c" in obj) fail("in operator for non-existing property failed") }) run("in operator prototype", function() { var parent = {x: 10} var child = meme(parent) if (!("x" in child)) fail("in operator should find inherited property") }) // ============================================================================ // GLOBAL FUNCTIONS - LOGICAL // ============================================================================ run("logical function numbers", function() { if (logical(0) != false) fail("logical(0) should be false") if (logical(1) != true) fail("logical(1) should be true") }) run("logical function strings", function() { if (logical("false") != false) fail("logical('false') should be false") if (logical("true") != true) fail("logical('true') should be true") }) run("logical function booleans", function() { if (logical(false) != false) fail("logical(false) should be false") if (logical(true) != true) fail("logical(true) should be true") }) run("logical function null", function() { if (logical(null) != false) fail("logical(null) should be false") }) run("logical function invalid", function() { if (logical("invalid") != null) fail("logical(invalid) should return null") if (logical(42) != null) fail("logical(42) should return null") }) // ============================================================================ // ARRAY METHODS // ============================================================================ run("array concat", function() { var arr1 = [1, 2] var arr2 = [3, 4] var combined = array(arr1, arr2) if (length(combined) != 4) fail("array concat length failed") if (combined[2] != 3) fail("array concat values failed") }) run("array join", function() { var arr = ["a", "b", "c"] var str = text(arr, ",") if (str != "a,b,c") fail("array join with text() failed") }) run("text array join numbers disrupt", function() { if (!should_disrupt(function() { text([1, 2, 3], ",") })) fail("text([numbers], sep) should disrupt") }) run("text array join numbers explicit", function() { var arr = array([1, 2, 3], x => text(x)) if (text(arr, ",") != "1,2,3") fail("explicit numeric join failed") }) // ============================================================================ // STRING METHODS // ============================================================================ run("string substring", function() { var str = "hello" if (text(str, 1, 4) != "ell") fail("string substring failed") }) run("string substring first", function() { var str = "hello" if (text(str, 1) != "ello") fail("string substring first failed") }) run("string substring to neg", function() { var str = "hello" if (text(str, 1, -2) != "el") fail("string substring to negative failed") }) run("string slice", function() { var str = "hello" if (text(str, 1, 4) != "ell") fail("string slice failed") if (text(str, -2) != "lo") fail("string slice negative failed") }) run("string indexOf", function() { var str = "hello world" if (search(str, "world") != 6) fail("string search failed") if (search(str, "xyz") != null) fail("string search not found failed") }) run("string toLowerCase", function() { var str = "HELLO" if (lower(str) != "hello") fail("string toLowerCase failed") }) run("string toUpperCase", function() { var str = "hello" if (upper(str) != "HELLO") fail("string toUpperCase failed") }) run("string split", function() { var str = "a,b,c" var parts = array(str, ",") if (length(parts) != 3) fail("string split length failed") if (parts[1] != "b") fail("string split values failed") }) run("null access", function() { var val = {} var nn = val.a if (nn != null) fail("val.a should return null") }) // ============================================================================ // OBJECT-AS-KEY (Private Property Access) // ============================================================================ run("object key basic", function() { var k1 = {} var k2 = {} var o = {} o[k1] = 123 o[k2] = 456 if (o[k1] != 123) fail("object key k1 failed") if (o[k2] != 456) fail("object key k2 failed") }) run("object key new object different key", function() { var k1 = {} var o = {} o[k1] = 123 if (o[{}] != null) fail("new object should be different key") }) run("object key in operator", function() { var k1 = {} var o = {} o[k1] = 123 if (!(k1 in o)) fail("in operator should find object key") }) run("object key delete", function() { var k1 = {} var o = {} o[k1] = 123 delete o[k1] if ((k1 in o)) fail("delete should remove object key") }) run("object key no string collision", function() { var a = {} var b = {} var o = {} o[a] = 1 o[b] = 2 if (o[a] != 1) fail("object key a should be 1") if (o[b] != 2) fail("object key b should be 2") }) run("object key same object same key", function() { var k = {} var o = {} o[k] = 100 o[k] = 200 if (o[k] != 200) fail("same object should be same key") }) run("object key computed property", function() { var k = {} var o = {} o[k] = function() { return 42 } if (o[k]() != 42) fail("object key with function value failed") }) run("object key multiple objects multiple keys", function() { var k1 = {} var k2 = {} var k3 = {} var o = {} o[k1] = "one" o[k2] = "two" o[k3] = "three" if (o[k1] != "one") fail("multiple keys k1 failed") if (o[k2] != "two") fail("multiple keys k2 failed") if (o[k3] != "three") fail("multiple keys k3 failed") }) run("object key with string keys", function() { var k = {} var o = {name: "test"} o[k] = "private" if (o.name != "test") fail("string key should still work") if (o[k] != "private") fail("object key should work with string keys") }) run("object key overwrite", function() { var k = {} var o = {} o[k] = 1 o[k] = 2 o[k] = 3 if (o[k] != 3) fail("object key overwrite failed") }) run("object key nested objects", function() { var k1 = {} var k2 = {} var inner = {} inner[k2] = "nested" var outer = {} outer[k1] = inner if (outer[k1][k2] != "nested") fail("nested object keys failed") }) run("array for", function() { var a = [1,2,3] arrfor(a, (x,i) => { if (x-1 != i) fail("array for failed") }) }) // ============================================================================ // INVALID KEY TYPES DISRUPT ON SET // ============================================================================ run("array string key disrupts", function() { var f = function(a) { a["a"] = 1 } if (!should_disrupt(function() { f([]) })) fail("array should not use string as key") }) run("array object key disrupts", function() { if (!should_disrupt(function() { var a = []; var b = {}; a[b] = 1 })) fail("array should not use object as key") }) run("array boolean key disrupts", function() { if (!should_disrupt(function() { var a = []; a[true] = 1 })) fail("array should not use boolean as key") }) run("array null key disrupts", function() { if (!should_disrupt(function() { var a = []; a[null] = 1 })) fail("array should not use null as key") }) run("array array key disrupts", function() { if (!should_disrupt(function() { var a = []; var c = []; a[c] = 1 })) fail("array should not use array as key") }) run("obj number key disrupts", function() { var f = function(a) { a[1] = 1 } if (!should_disrupt(function() { f({}) })) fail("object should not use number as key") }) run("obj array key disrupts", function() { if (!should_disrupt(function() { var a = {}; var c = []; a[c] = 1 })) fail("object should not use array as key") }) run("obj boolean key disrupts", function() { if (!should_disrupt(function() { var a = {}; a[true] = 1 })) fail("object should not use boolean as key") }) run("obj null key disrupts", function() { if (!should_disrupt(function() { var a = {}; a[null] = 1 })) fail("object should not use null as key") }) // ============================================================================ // RETRIEVAL WITH INVALID KEY RETURNS NULL (not disrupt) // ============================================================================ run("array get string key returns null", function() { var a = [1, 2, 3] if (a["x"] != null) fail("array get with string key should return null") }) run("array get negative index returns null", function() { var a = [1, 2, 3] if (a[-1] != null) fail("array get with negative index should return null") }) run("array get object key returns null", function() { var a = [1, 2, 3] var k = {} if (a[k] != null) fail("array get with object key should return null") }) run("array get array key returns null", function() { var a = [1, 2, 3] if (a[[1, 2]] != null) fail("array get with array key should return null") }) run("array get boolean key returns null", function() { var a = [1, 2, 3] if (a[true] != null) fail("array get with boolean key should return null") }) run("array get null key returns null", function() { var a = [1, 2, 3] if (a[null] != null) fail("array get with null key should return null") }) run("obj get number key returns null", function() { var o = {a: 1} if (o[5] != null) fail("object get with number key should return null") }) run("obj get array key returns null", function() { var o = {a: 1} if (o[[1, 2]] != null) fail("object get with array key should return null") }) run("obj get boolean key returns null", function() { var o = {a: 1} if (o[true] != null) fail("object get with boolean key should return null") }) run("obj get null key returns null", function() { var o = {a: 1} if (o[null] != null) fail("object get with null key should return null") }) // ============================================================================ // FUNCTION AS VALUE - functions should not have properties // ============================================================================ run("function property get arity", function() { var fn = function(a, b) { return a + b } var arity = length(fn) if (arity != 2) fail("length of function should return its arity") }) run("function property set disrupts", function() { if (!should_disrupt(function() { var fn = function() {}; fn.foo = 123 })) fail("setting property on function should disrupt") }) run("function bracket access disrupts", function() { if (!should_disrupt(function() { var fn = function() {}; var x = fn["length"]() })) fail("bracket access on function should disrupt") }) run("length returns function arity", function() { var fn0 = function() { return 1 } var fn1 = function(a) { return a } var fn2 = function(a, b) { return a + b } var fn3 = function(a, b, c) { return a + b + c } if (length(fn0) != 0) fail("length(fn0) should be 0") if (length(fn1) != 1) fail("length(fn1) should be 1") if (length(fn2) != 2) fail("length(fn2) should be 2") if (length(fn3) != 3) fail("length(fn3) should be 3") }) run("call invokes function", function() { var fn = function(a, b) { return a + b } var result = call(fn, null, [3, 4]) if (result != 7) fail("call(fn, null, [3, 4]) should return 7") }) run("call with this binding", function() { var obj = { value: 10 } var fn = function(x) { return this.value + x } var result = call(fn, obj, [5]) if (result != 15) fail("call(fn, obj, [5]) should return 15") }) run("call no args", function() { var fn = function() { return 42 } var result = call(fn, null) if (result != 42) fail("call(fn, null) should return 42") }) run("builtin function properties still work", function() { var min_result = min(5, 3) if (min_result != 3) fail("min should work") }) // ============================================================================ // FUNCTION PROXY - Method call sugar for bytecode functions // ============================================================================ run("function proxy basic", function() { var proxy = function(name, args) { return `called:${name}:${length(args)}` } var result = proxy.foo() if (result != "called:foo:0") fail("basic proxy call failed") }) run("function proxy with one arg", function() { var proxy = function(name, args) { return `${name}-${args[0]}` } var result = proxy.test("value") if (result != "test-value") fail("proxy with one arg failed") }) run("function proxy with multiple args", function() { var proxy = function(name, args) { var sum = 0 var i = 0 for (i = 0; i < length(args); i++) { sum = sum + args[i] } return `${name}:${sum}` } var result = proxy.add(1, 2, 3, 4) if (result != "add:10") fail("proxy with multiple args failed") }) run("function proxy bracket notation", function() { var proxy = function(name, args) { return `bracket:${name}` } var result = proxy["myMethod"]() if (result != "bracket:myMethod") fail("proxy bracket notation failed") }) run("function proxy dynamic method name", function() { var proxy = function(name, args) { return name } var methodName = "dynamic" var result = proxy[methodName]() if (result != "dynamic") fail("proxy dynamic method name failed") }) run("function proxy dispatch to record", function() { var my_record = { greet: function(name) { return `Hello, ${name}` }, add: function(a, b) { return a + b } } var proxy = function(name, args) { if (is_function(my_record[name])) { return apply(my_record[name], args) } disrupt } if (proxy.greet("World") != "Hello, World") fail("proxy dispatch greet failed") if (proxy.add(3, 4) != 7) fail("proxy dispatch add failed") }) run("function proxy unknown method disrupts", function() { var proxy = function(name, args) { disrupt } if (!should_disrupt(function() { proxy.nonexistent() })) fail("proxy should disrupt for unknown method") }) run("function proxy is function", function() { var proxy = function(name, args) { return name } if (!is_function(proxy)) fail("proxy should be a function") }) run("function proxy length is 2", function() { var proxy = function(name, args) { return name } if (length(proxy) != 2) fail("proxy function should have length 2") }) run("function proxy property read disrupts", function() { if (!should_disrupt(function() { var fn = function() { return 1 }; var x = fn.someProp })) fail("reading property from non-proxy function should disrupt") }) run("function proxy nested calls", function() { var outer = function(name, args) { if (name == "inner") { return args[0].double(5) } return "outer:" + name } var inner = function(name, args) { if (name == "double") { return args[0] * 2 } return "inner:" + name } var result = outer.inner(inner) if (result != 10) fail("nested proxy calls failed") }) run("function proxy returns null", function() { var proxy = function(name, args) { return null } var result = proxy.anything() if (result != null) fail("proxy returning null failed") }) run("function proxy returns object", function() { var proxy = function(name, args) { return {method: name, argCount: length(args)} } var result = proxy.test(1, 2, 3) if (result.method != "test") fail("proxy returning object method failed") if (result.argCount != 3) fail("proxy returning object argCount failed") }) run("function proxy returns function", function() { var proxy = function(name, args) { return function() { return name } } var result = proxy.getFn() if (result() != "getFn") fail("proxy returning function failed") }) run("function proxy args array is real array", function() { var proxy = function(name, args) { if (!is_array(args)) disrupt args[] = 4 return length(args) } var result = proxy.test(1, 2, 3) if (result != 4) fail("proxy args should be modifiable array") }) run("function proxy no this binding", function() { var proxy = function(name, args) { return this } var result = proxy.test() if (result != null) fail("proxy should have null this") }) run("function proxy integer bracket key disrupts", function() { var proxy = function(name, args) { return `key:${name}` } if (!should_disrupt(function() { var result = proxy[42]() })) fail("proxy with integer bracket key should disrupt") }) // ============================================================================ // REDUCE FUNCTION // ============================================================================ run("reduce sum", function() { var arr = [1, 2, 3, 4, 5] var result = reduce(arr, (a, b) => a + b) if (result != 15) fail("reduce sum failed") }) run("reduce product", function() { var arr = [1, 2, 3, 4, 5] var result = reduce(arr, (a, b) => a * b) if (result != 120) fail("reduce product failed") }) run("reduce with initial", function() { var arr = [1, 2, 3] var result = reduce(arr, (a, b) => a + b, 10) if (result != 16) fail("reduce with initial failed") }) run("reduce with initial zero", function() { var arr = [1, 2, 3] var result = reduce(arr, (a, b) => a + b, 0) if (result != 6) fail("reduce with initial zero failed") }) run("reduce empty array no initial", function() { var arr = [] var result = reduce(arr, (a, b) => a + b) if (result != null) fail("reduce empty array without initial should return null") }) run("reduce empty array with initial", function() { var arr = [] var result = reduce(arr, (a, b) => a + b, 42) if (result != 42) fail("reduce empty array with initial should return initial") }) run("reduce single element no initial", function() { var arr = [42] var result = reduce(arr, (a, b) => a + b) if (result != 42) fail("reduce single element without initial failed") }) run("reduce single element with initial", function() { var arr = [5] var result = reduce(arr, (a, b) => a + b, 10) if (result != 15) fail("reduce single element with initial failed") }) run("reduce reverse", function() { var arr = [1, 2, 3, 4] var result = reduce(arr, (a, b) => a - b, 0, true) if (result != -10) fail("reduce reverse failed") }) run("reduce string concat", function() { var arr = ["a", "b", "c"] var result = reduce(arr, (a, b) => a + b) if (result != "abc") fail("reduce string concat failed") }) // ============================================================================ // SORT FUNCTION // ============================================================================ run("sort numbers", function() { var arr = [3, 1, 4, 1, 5, 9, 2, 6] var sorted = sort(arr) if (sorted[0] != 1 || sorted[1] != 1 || sorted[2] != 2) fail("sort numbers failed") if (sorted[7] != 9) fail("sort numbers last element failed") }) run("sort strings", function() { var arr = ["banana", "apple", "cherry"] var sorted = sort(arr) if (sorted[0] != "apple") fail("sort strings failed") if (sorted[2] != "cherry") fail("sort strings last failed") }) run("sort preserves original", function() { var arr = [3, 1, 2] var sorted = sort(arr) if (arr[0] != 3) fail("sort should not mutate original") }) run("sort empty array", function() { var arr = [] var sorted = sort(arr) if (length(sorted) != 0) fail("sort empty array failed") }) run("sort single element", function() { var arr = [42] var sorted = sort(arr) if (sorted[0] != 42) fail("sort single element failed") }) run("sort by field", function() { var arr = [ {name: "Charlie", age: 30}, {name: "Alice", age: 25}, {name: "Bob", age: 35} ] var sorted = sort(arr, "name") if (sorted[0].name != "Alice") fail("sort by field failed") if (sorted[2].name != "Charlie") fail("sort by field last failed") }) run("sort by index", function() { var arr = [[3, "c"], [1, "a"], [2, "b"]] var sorted = sort(arr, 0) if (sorted[0][1] != "a") fail("sort by index failed") }) run("sort stable", function() { var arr = [ {name: "A", order: 1}, {name: "B", order: 1}, {name: "C", order: 1} ] var sorted = sort(arr, "order") if (sorted[0].name != "A" || sorted[1].name != "B" || sorted[2].name != "C") { fail("sort should be stable") } }) run("sort negative numbers", function() { var arr = [-5, 3, -1, 0, 2] var sorted = sort(arr) if (sorted[0] != -5 || sorted[4] != 3) fail("sort negative numbers failed") }) // ============================================================================ // FILTER FUNCTION // ============================================================================ run("filter basic", function() { var arr = [1, 2, 3, 4, 5, 6] var evens = filter(arr, x => x % 2 == 0) if (length(evens) != 3) fail("filter basic length failed") if (evens[0] != 2 || evens[1] != 4 || evens[2] != 6) fail("filter basic values failed") }) run("filter all pass", function() { var arr = [2, 4, 6] var result = filter(arr, x => x % 2 == 0) if (length(result) != 3) fail("filter all pass failed") }) run("filter none pass", function() { var arr = [1, 3, 5] var result = filter(arr, x => x % 2 == 0) if (length(result) != 0) fail("filter none pass failed") }) run("filter empty array", function() { var arr = [] var result = filter(arr, x => true) if (length(result) != 0) fail("filter empty array failed") }) run("filter with index", function() { var arr = ["a", "b", "c", "d"] var result = filter(arr, (x, i) => i % 2 == 0) if (length(result) != 2) fail("filter with index length failed") if (result[0] != "a" || result[1] != "c") fail("filter with index values failed") }) run("filter preserves original", function() { var arr = [1, 2, 3] var result = filter(arr, x => x > 1) if (length(arr) != 3) fail("filter should not mutate original") }) run("filter objects", function() { var arr = [{active: true}, {active: false}, {active: true}] var result = filter(arr, x => x.active) if (length(result) != 2) fail("filter objects failed") }) // ============================================================================ // FIND FUNCTION // ============================================================================ run("find basic", function() { var arr = [1, 2, 3, 4, 5] var idx = find(arr, x => x > 3) if (idx != 3) fail("find basic failed") }) run("find first element", function() { var arr = [10, 2, 3] var idx = find(arr, x => x > 5) if (idx != 0) fail("find first element failed") }) run("find last element", function() { var arr = [1, 2, 10] var idx = find(arr, x => x > 5) if (idx != 2) fail("find last element failed") }) run("find not found", function() { var arr = [1, 2, 3] var idx = find(arr, x => x > 10) if (idx != null) fail("find not found should return null") }) run("find empty array", function() { var arr = [] var idx = find(arr, x => true) if (idx != null) fail("find in empty array should return null") }) run("find by value", function() { var arr = [10, 20, 30, 20] var idx = find(arr, 20) if (idx != 1) fail("find by value failed") }) run("find reverse", function() { var arr = [10, 20, 30, 20] var idx = find(arr, 20, true) if (idx != 3) fail("find reverse failed") }) run("find with from", function() { var arr = [10, 20, 30, 20] var idx = find(arr, 20, false, 2) if (idx != 3) fail("find with from failed") }) run("find with index callback", function() { var arr = ["a", "b", "c"] var idx = find(arr, (x, i) => i == 1) if (idx != 1) fail("find with index callback failed") }) // ============================================================================ // ABS FUNCTION // ============================================================================ run("abs positive", function() { if (abs(5) != 5) fail("abs positive failed") }) run("abs negative", function() { if (abs(-5) != 5) fail("abs negative failed") }) run("abs zero", function() { if (abs(0) != 0) fail("abs zero failed") }) run("abs float", function() { if (abs(-3.14) != 3.14) fail("abs float failed") }) run("abs non number", function() { if (abs("5") != null) fail("abs non-number should return null") if (abs(null) != null) fail("abs null should return null") }) // ============================================================================ // FLOOR FUNCTION // ============================================================================ run("floor positive", function() { if (floor(3.7) != 3) fail("floor positive failed") }) run("floor negative", function() { if (floor(-3.7) != -4) fail("floor negative failed") }) run("floor integer", function() { if (floor(5) != 5) fail("floor integer failed") }) run("floor zero", function() { if (floor(0) != 0) fail("floor zero failed") }) run("floor with place", function() { if (floor(12.3775, -2) != 12.37) fail("floor with place failed") }) run("floor negative with place", function() { if (floor(-12.3775, -2) != -12.38) fail("floor negative with place failed") }) // ============================================================================ // CEILING FUNCTION // ============================================================================ run("ceiling positive", function() { if (ceiling(3.2) != 4) fail("ceiling positive failed") }) run("ceiling negative", function() { if (ceiling(-3.7) != -3) fail("ceiling negative failed") }) run("ceiling integer", function() { if (ceiling(5) != 5) fail("ceiling integer failed") }) run("ceiling zero", function() { if (ceiling(0) != 0) fail("ceiling zero failed") }) run("ceiling with place", function() { if (ceiling(12.3775, -2) != 12.38) fail("ceiling with place failed") }) run("ceiling negative with place", function() { if (ceiling(-12.3775, -2) != -12.37) fail("ceiling negative with place failed") }) // ============================================================================ // ROUND FUNCTION // ============================================================================ run("round down", function() { if (round(3.4) != 3) fail("round down failed") }) run("round up", function() { if (round(3.6) != 4) fail("round up failed") }) run("round half", function() { if (round(3.5) != 4) fail("round half failed") }) run("round negative", function() { if (round(-3.5) != -3 && round(-3.5) != -4) fail("round negative failed") }) run("round integer", function() { if (round(5) != 5) fail("round integer failed") }) run("round with places", function() { if (round(12.3775, -2) != 12.38) fail("round with places failed") }) run("round to tens", function() { if (round(12.3775, 1) != 10) fail("round to tens failed") }) // ============================================================================ // TRUNC FUNCTION // ============================================================================ run("trunc positive", function() { if (trunc(3.7) != 3) fail("trunc positive failed") }) run("trunc negative", function() { if (trunc(-3.7) != -3) fail("trunc negative failed") }) run("trunc integer", function() { if (trunc(5) != 5) fail("trunc integer failed") }) run("trunc zero", function() { if (trunc(0) != 0) fail("trunc zero failed") }) run("trunc with places", function() { if (trunc(12.3775, -2) != 12.37) fail("trunc with places failed") }) run("trunc negative with places", function() { if (trunc(-12.3775, -2) != -12.37) fail("trunc negative with places failed") }) // ============================================================================ // SIGN FUNCTION // ============================================================================ run("sign positive", function() { if (sign(5) != 1) fail("sign positive failed") }) run("sign negative", function() { if (sign(-5) != -1) fail("sign negative failed") }) run("sign zero", function() { if (sign(0) != 0) fail("sign zero failed") }) run("sign float", function() { if (sign(0.001) != 1) fail("sign positive float failed") if (sign(-0.001) != -1) fail("sign negative float failed") }) run("sign non number", function() { if (sign("5") != null) fail("sign non-number should return null") }) // ============================================================================ // WHOLE AND FRACTION FUNCTIONS // ============================================================================ run("whole positive", function() { if (whole(3.7) != 3) fail("whole positive failed") }) run("whole negative", function() { if (whole(-3.7) != -3) fail("whole negative failed") }) run("whole integer", function() { if (whole(5) != 5) fail("whole integer failed") }) run("whole non number", function() { if (whole("5") != null) fail("whole non-number should return null") }) run("fraction positive", function() { var f = fraction(3.75) if (f < 0.74 || f > 0.76) fail("fraction positive failed") }) run("fraction negative", function() { var f = fraction(-3.75) if (f > -0.74 || f < -0.76) fail("fraction negative failed") }) run("fraction integer", function() { if (fraction(5) != 0) fail("fraction integer failed") }) run("fraction non number", function() { if (fraction("5") != null) fail("fraction non-number should return null") }) // ============================================================================ // NEG FUNCTION // ============================================================================ run("neg positive", function() { if (neg(5) != -5) fail("neg positive failed") }) run("neg negative", function() { if (neg(-5) != 5) fail("neg negative failed") }) run("neg zero", function() { if (neg(0) != 0) fail("neg zero failed") }) run("neg float", function() { if (neg(3.14) != -3.14) fail("neg float failed") }) run("neg non number", function() { if (neg("5") != null) fail("neg non-number should return null") }) // ============================================================================ // MODULO FUNCTION // ============================================================================ run("modulo positive", function() { if (modulo(10, 3) != 1) fail("modulo positive failed") }) run("modulo negative dividend", function() { var result = modulo(-10, 3) if (result != 2) fail("modulo negative dividend failed") }) run("modulo negative divisor", function() { var result = modulo(10, -3) if (result != -2) fail("modulo negative divisor failed") }) run("modulo both negative", function() { var result = modulo(-10, -3) if (result != -1) fail("modulo both negative failed") }) run("modulo zero dividend", function() { if (modulo(0, 5) != 0) fail("modulo zero dividend failed") }) run("modulo zero divisor", function() { if (modulo(10, 0) != null) fail("modulo zero divisor should return null") }) run("modulo floats", function() { var result = modulo(5.5, 2) if (result < 1.4 || result > 1.6) fail("modulo floats failed") }) run("remainder float basic", function() { if (remainder(5.5, 2.5) != 0.5) fail("remainder 5.5 % 2.5 failed") }) run("modulo float basic", function() { if (modulo(5.5, 2.5) != 0.5) fail("modulo 5.5 % 2.5 failed") }) run("remainder float negative", function() { if (remainder(-5.5, 2.5) != -0.5) fail("remainder -5.5 % 2.5 failed") }) run("modulo float negative", function() { if (modulo(-5.5, 2.5) != 2.0) fail("modulo -5.5 % 2.5 failed") }) // ============================================================================ // MIN AND MAX FUNCTIONS // ============================================================================ run("min basic", function() { if (min(3, 5) != 3) fail("min basic failed") }) run("min equal", function() { if (min(5, 5) != 5) fail("min equal failed") }) run("min negative", function() { if (min(-3, -5) != -5) fail("min negative failed") }) run("min mixed", function() { if (min(-3, 5) != -3) fail("min mixed failed") }) run("min float", function() { if (min(3.14, 2.71) != 2.71) fail("min float failed") }) run("min non number", function() { if (min(3, "5") != null) fail("min non-number should return null") if (min("3", 5) != null) fail("min first non-number should return null") }) run("max basic", function() { if (max(3, 5) != 5) fail("max basic failed") }) run("max equal", function() { if (max(5, 5) != 5) fail("max equal failed") }) run("max negative", function() { if (max(-3, -5) != -3) fail("max negative failed") }) run("max mixed", function() { if (max(-3, 5) != 5) fail("max mixed failed") }) run("max float", function() { if (max(3.14, 2.71) != 3.14) fail("max float failed") }) run("max non number", function() { if (max(3, "5") != null) fail("max non-number should return null") }) run("min max constrain", function() { var val = 8 var constrained = min(max(val, 0), 10) if (constrained != 8) fail("min max constrain in range failed") constrained = min(max(-5, 0), 10) if (constrained != 0) fail("min max constrain below failed") constrained = min(max(15, 0), 10) if (constrained != 10) fail("min max constrain above failed") }) // ============================================================================ // CODEPOINT FUNCTION // ============================================================================ run("codepoint letter", function() { if (codepoint("A") != 65) fail("codepoint A failed") if (codepoint("a") != 97) fail("codepoint a failed") }) run("codepoint digit", function() { if (codepoint("0") != 48) fail("codepoint 0 failed") }) run("codepoint unicode", function() { if (codepoint("\u00E9") != 233) fail("codepoint unicode failed") }) run("codepoint first char", function() { if (codepoint("ABC") != 65) fail("codepoint should return first char") }) run("codepoint empty", function() { if (codepoint("") != null) fail("codepoint empty should return null") }) run("codepoint non text", function() { if (codepoint(65) != null) fail("codepoint non-text should return null") }) // ============================================================================ // CHARACTER FUNCTION // ============================================================================ run("character letter", function() { if (character(65) != "A") fail("character 65 failed") if (character(97) != "a") fail("character 97 failed") }) run("character digit", function() { if (character(48) != "0") fail("character 48 failed") }) run("character unicode", function() { if (character(233) != "\u00E9") fail("character unicode failed") }) run("character from text", function() { if (character("hello") != "h") fail("character from text failed") }) run("character invalid", function() { if (character(-1) != "") fail("character negative should return empty") }) // ============================================================================ // SEARCH FUNCTION // ============================================================================ run("search found", function() { if (search("hello world", "world") != 6) fail("search found failed") }) run("search not found", function() { if (search("hello world", "xyz") != null) fail("search not found should return null") }) run("search beginning", function() { if (search("hello world", "hello") != 0) fail("search beginning failed") }) run("search single char", function() { if (search("hello", "l") != 2) fail("search single char failed") }) run("search with from", function() { if (search("hello hello", "hello", 1) != 6) fail("search with from failed") }) run("search empty pattern", function() { if (search("hello", "") != 0) fail("search empty pattern failed") }) run("search negative from", function() { var result = search("hello world", "world", -5) if (result != 6) fail("search negative from failed") }) // ============================================================================ // REPLACE FUNCTION // ============================================================================ run("replace basic", function() { var result = replace("hello world", "world", "universe") if (result != "hello universe") fail("replace basic failed") }) run("replace not found", function() { var result = replace("hello world", "xyz", "abc") if (result != "hello world") fail("replace not found should return original") }) run("replace multiple", function() { var result = replace("banana", "a", "o") if (result != "bonono") fail("replace multiple failed") }) run("replace with limit", function() { var result = replace("banana", "a", "o", 1) if (result != "bonana") fail("replace with limit failed") }) run("replace empty target", function() { var result = replace("abc", "", "-") if (result != "-a-b-c-") fail("replace empty target failed") }) run("replace to empty", function() { var result = replace("hello", "l", "") if (result != "heo") fail("replace to empty failed") }) run("replace with function", function() { var result = replace("hello", "l", (match, pos) => `[${pos}]`) if (result != "he[2][3]o") fail("replace with function failed") }) run("replace with function limit", function() { var result = replace("banana", "a", (match, pos) => `[${pos}]`, 2) if (result != "b[1]n[3]na") fail("replace with function limit failed") }) run("replace with regex", function() { var result = replace("banana", /a/, "o") if (result != "bonono") fail("replace with regex failed") }) run("replace with regex limit", function() { var result = replace("banana", /a/, "o", 2) if (result != "bonona") fail("replace with regex limit failed") }) run("replace with regex function", function() { var result = replace("hello", /l/, (match, pos) => `[${pos}]`) if (result != "he[2][3]o") fail("replace with regex function failed") }) // ============================================================================ // TEXT FUNCTION (Conversion and Slicing) // ============================================================================ run("text number basic", function() { if (text(123) != "123") fail("text number basic failed") }) run("text number negative", function() { if (text(-456) != "-456") fail("text number negative failed") }) run("text number float", function() { var result = text(3.14) if (search(result, "3.14") != 0) fail("text number float failed") }) run("text array join empty sep", function() { var result = text(["a", "b", "c"], "") if (result != "abc") fail("text array join empty sep failed") }) run("text slice basic", function() { if (text("hello", 1, 4) != "ell") fail("text slice basic failed") }) run("text slice from only", function() { if (text("hello", 2) != "llo") fail("text slice from only failed") }) run("text slice negative from", function() { if (text("hello", -2) != "lo") fail("text slice negative from failed") }) run("text slice negative to", function() { if (text("hello", 0, -2) != "hel") fail("text slice negative to failed") }) run("text boolean", function() { if (text(true) != "true") fail("text true failed") if (text(false) != "false") fail("text false failed") }) run("text null", function() { if (text(null) != "null") fail("text null failed") }) // ============================================================================ // NUMBER FUNCTION (Conversion) // ============================================================================ run("number from string", function() { if (number("123") != 123) fail("number from string failed") }) run("number from negative string", function() { if (number("-456") != -456) fail("number from negative string failed") }) run("number from float string", function() { if (number("3.14") != 3.14) fail("number from float string failed") }) run("number invalid string", function() { if (number("abc") != null) fail("number invalid string should return null") }) run("number from boolean", function() { if (number(true) != 1) fail("number from true failed") if (number(false) != 0) fail("number from false failed") }) run("number from number", function() { if (number(42) != 42) fail("number from number failed") }) run("number with radix", function() { if (number("FF", 16) != 255) fail("number hex failed") if (number("1010", 2) != 10) fail("number binary failed") }) run("number leading zeros", function() { if (number("007") != 7) fail("number leading zeros failed") }) // ============================================================================ // ARRAY FUNCTION (Creator and Slicing) // ============================================================================ run("array create with length", function() { var arr = array(5) if (length(arr) != 5) fail("array create length failed") if (arr[0] != null) fail("array create should init to null") }) run("array create with initial", function() { var arr = array(3, 42) if (arr[0] != 42 || arr[1] != 42 || arr[2] != 42) fail("array create with initial failed") }) run("array create with function", function() { var arr = array(3, i => i * 2) if (arr[0] != 0 || arr[1] != 2 || arr[2] != 4) fail("array create with function failed") }) run("array copy", function() { var orig = [1, 2, 3] var copy = array(orig) copy[0] = 99 if (orig[0] != 1) fail("array copy should not affect original") }) run("array slice basic", function() { var arr = [1, 2, 3, 4, 5] var sliced = array(arr, 1, 3) if (length(sliced) != 2) fail("array slice length failed") if (sliced[0] != 2 || sliced[1] != 3) fail("array slice values failed") }) run("array slice negative", function() { var arr = [1, 2, 3, 4, 5] var sliced = array(arr, -3) if (length(sliced) != 3) fail("array slice negative failed") if (sliced[0] != 3) fail("array slice negative value failed") }) run("array from object keys", function() { var obj = {a: 1, b: 2, c: 3} var keys = array(obj) if (length(keys) != 3) fail("array from object keys length failed") }) run("array from text", function() { var arr = array("abc") if (length(arr) != 3) fail("array from text length failed") if (arr[0] != "a" || arr[1] != "b" || arr[2] != "c") fail("array from text values failed") }) run("array split text", function() { var arr = array("a,b,c", ",") if (length(arr) != 3) fail("array split text length failed") if (arr[1] != "b") fail("array split text value failed") }) // ============================================================================ // TRIM FUNCTION // ============================================================================ run("trim spaces", function() { if (trim(" hello ") != "hello") fail("trim spaces failed") }) run("trim tabs", function() { if (trim("\thello\t") != "hello") fail("trim tabs failed") }) run("trim mixed", function() { if (trim(" \t hello \n ") != "hello") fail("trim mixed failed") }) run("trim no whitespace", function() { if (trim("hello") != "hello") fail("trim no whitespace failed") }) run("trim empty", function() { if (trim("") != "") fail("trim empty failed") }) run("trim all whitespace", function() { if (trim(" ") != "") fail("trim all whitespace failed") }) // ============================================================================ // LOWER AND UPPER FUNCTIONS // ============================================================================ run("lower basic", function() { if (lower("HELLO") != "hello") fail("lower basic failed") }) run("lower mixed", function() { if (lower("HeLLo WoRLD") != "hello world") fail("lower mixed failed") }) run("lower already lower", function() { if (lower("hello") != "hello") fail("lower already lower failed") }) run("lower with numbers", function() { if (lower("ABC123") != "abc123") fail("lower with numbers failed") }) run("upper basic", function() { if (upper("hello") != "HELLO") fail("upper basic failed") }) run("upper mixed", function() { if (upper("HeLLo WoRLD") != "HELLO WORLD") fail("upper mixed failed") }) run("upper already upper", function() { if (upper("HELLO") != "HELLO") fail("upper already upper failed") }) run("upper with numbers", function() { if (upper("abc123") != "ABC123") fail("upper with numbers failed") }) // ============================================================================ // APPLY FUNCTION // ============================================================================ run("apply basic", function() { var fn = function(a, b) { return a + b } var result = apply(fn, [3, 4]) if (result != 7) fail("apply basic failed") }) run("apply no args", function() { var fn = function() { return 42 } var result = apply(fn, []) if (result != 42) fail("apply no args failed") }) run("apply single arg", function() { var fn = function(x) { return x * 2 } var result = apply(fn, [5]) if (result != 10) fail("apply single arg failed") }) run("apply many args", function() { var fn = function(a, b, c, d) { return a + b + c + d } var result = apply(fn, [1, 2, 3, 4]) if (result != 10) fail("apply many args failed") }) run("apply non function", function() { var result = apply(42, [1, 2]) if (result != 42) fail("apply non-function should return first arg") }) // ============================================================================ // CALL FUNCTION (Additional Tests) // ============================================================================ run("call many args", function() { var fn = function(a, b, c, d) { return a * b + c * d } var result = call(fn, null, [2, 3, 4, 5]) if (result != 26) fail("call many args failed") }) run("call method style", function() { var obj = { value: 10, multiply: function(x) { return this.value * x } } var result = call(obj.multiply, obj, [5]) if (result != 50) fail("call method style failed") }) run("call change this", function() { var obj1 = { value: 10 } var obj2 = { value: 20 } var fn = function() { return this.value } if (call(fn, obj1) != 10) fail("call this obj1 failed") if (call(fn, obj2) != 20) fail("call this obj2 failed") }) // ============================================================================ // ARRFOR FUNCTION (Array For-Each) // ============================================================================ run("arrfor basic", function() { var arr = [1, 2, 3] var sum = 0 arrfor(arr, x => { sum = sum + x }) if (sum != 6) fail("arrfor basic failed") }) run("arrfor with index", function() { var arr = ["a", "b", "c"] var indices = [] arrfor(arr, (x, i) => { indices[] = i }) if (indices[0] != 0 || indices[2] != 2) fail("arrfor with index failed") }) run("arrfor empty", function() { var called = false arrfor([], x => { called = true }) if (called) fail("arrfor empty should not call function") }) run("arrfor mutation", function() { var arr = [1, 2, 3] var results = [] arrfor(arr, x => { results[] = x * 2 }) if (results[0] != 2 || results[1] != 4 || results[2] != 6) fail("arrfor mutation failed") }) // ============================================================================ // STONE FUNCTION (Additional Tests) // ============================================================================ run("stone returns value", function() { var obj = {x: 1} var result = stone(obj) if (result != obj) fail("stone should return the value") }) run("stone idempotent", function() { var obj = {x: 1} stone(obj) stone(obj) if (!is_stone(obj)) fail("stone should be idempotent") }) // ============================================================================ // PROTO FUNCTION (Additional Tests) // ============================================================================ run("proto chain", function() { var grandparent = {a: 1} var parent = meme(grandparent) var child = meme(parent) if (proto(child) != parent) fail("proto chain child->parent failed") if (proto(parent) != grandparent) fail("proto chain parent->grandparent failed") }) run("proto array disrupts", function() { if (!should_disrupt(function() { proto([1, 2, 3]) })) fail("proto of array should disrupt") }) // ============================================================================ // MEME FUNCTION (Additional Tests) // ============================================================================ run("meme method inheritance", function() { var parent = { greet: function() { return "hello" } } var child = meme(parent) if (child.greet() != "hello") fail("meme method inheritance failed") }) run("meme this in inherited method", function() { var parent = { getValue: function() { return this.value } } var child = meme(parent) child.value = 42 if (child.getValue() != 42) fail("meme this in inherited method failed") }) run("meme deep chain", function() { var a = {x: 1} var b = meme(a) var c = meme(b) var d = meme(c) if (d.x != 1) fail("meme deep chain failed") }) // ============================================================================ // DELETE OPERATOR // ============================================================================ run("delete property", function() { var obj = {a: 1, b: 2} delete obj.a if ("a" in obj) fail("delete property failed") if (obj.b != 2) fail("delete should not affect other properties") }) run("delete array element disrupts", function() { if (!should_disrupt(function() { var arr = [1, 2, 3]; delete arr[1] })) fail("delete on array element should disrupt") }) run("delete nonexistent", function() { var obj = {a: 1} delete obj.b if (obj.a != 1) fail("delete nonexistent should not affect object") }) // ============================================================================ // TYPEOF-LIKE BEHAVIOR // ============================================================================ run("is integer", function() { if (!is_number(5) || 5 % 1 != 0) fail("is_integer positive failed") if (!is_number(-5) || -5 % 1 != 0) fail("is_integer negative failed") if (is_number(5.5) && 5.5 % 1 == 0) fail("is_integer float should not be integer") }) // ============================================================================ // ARRAY MAP-LIKE WITH ARRAY FUNCTION // ============================================================================ run("array map basic", function() { var arr = [1, 2, 3] var doubled = array(arr, x => x * 2) if (doubled[0] != 2 || doubled[1] != 4 || doubled[2] != 6) fail("array map basic failed") }) run("array map with index", function() { var arr = ["a", "b", "c"] var result = array(arr, (x, i) => `${x}${i}`) if (result[0] != "a0" || result[1] != "b1") fail("array map with index failed") }) run("array map reverse", function() { var arr = [1, 2, 3] var result = array(arr, x => x * 2, true) if (result[0] != 6 || result[2] != 2) fail("array map reverse failed") }) run("array map with exit", function() { var arr = [1, 2, 3, 4, 5] var result = array(arr, x => { if (x > 3) return null return x * 2 }, false, null) if (length(result) != 5) fail("array map with exit length unexpected") }) 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 (length(result) != 5) fail("result length should be 5") }) run("inline map intrinsic is_number", function() { var items = [1, "two", 3.14, null, true] var result = array(items, is_number) if (result[0] != true) fail("1 should be number") if (result[1] != false) fail("'two' should not be number") if (result[2] != true) fail("3.14 should be number") if (result[3] != false) fail("null should not be number") if (result[4] != false) fail("true should not be number") }) run("inline map intrinsic is_text", function() { var items = ["hello", 42, "", null] var result = array(items, is_text) if (result[0] != true) fail("'hello' should be text") if (result[1] != false) fail("42 should not be text") if (result[2] != true) fail("'' should be text") if (result[3] != false) fail("null should not be text") }) run("inline map intrinsic is_digit", function() { var chars = array("a5B2 ") var result = array(chars, is_digit) if (result[0] != false) fail("a should not be digit") if (result[1] != true) fail("5 should be digit") if (result[2] != false) fail("B should not be digit") if (result[3] != true) fail("2 should be digit") if (result[4] != false) fail("space should not be digit") }) run("inline map lambda", function() { var arr = [10, 20, 30] var result = array(arr, function(x) { return x + 1 }) if (result[0] != 11) fail("10+1 should be 11") if (result[1] != 21) fail("20+1 should be 21") if (result[2] != 31) fail("30+1 should be 31") }) run("inline map empty array", function() { var result = array([], is_number) if (length(result) != 0) fail("map of empty should be empty") }) // ============================================================================ // NUMERIC INTRINSIC CALLBACK INLINING // ============================================================================ var mymap = function(arr, fn) { var result = array(length(arr)) var i = 0 while (i < length(arr)) { result[i] = fn(arr[i]) i = i + 1 } return result } var myfold = function(arr, fn) { var acc = arr[0] var i = 1 while (i < length(arr)) { acc = fn(acc, arr[i]) i = i + 1 } return acc } run("inline callback abs", function() { var result = mymap([-3, 5, -1.5, 0], function(a) { return abs(a) }) assert_eq(result[0], 3, "abs(-3)") assert_eq(result[1], 5, "abs(5)") assert_eq(result[2], 1.5, "abs(-1.5)") assert_eq(result[3], 0, "abs(0)") }) run("inline callback neg", function() { var result = mymap([3, -5, 0, 1.5], function(a) { return neg(a) }) assert_eq(result[0], -3, "neg(3)") assert_eq(result[1], 5, "neg(-5)") assert_eq(result[2], 0, "neg(0)") assert_eq(result[3], -1.5, "neg(1.5)") }) run("inline callback sign", function() { var result = mymap([-7, 0, 42, -0.5], function(a) { return sign(a) }) assert_eq(result[0], -1, "sign(-7)") assert_eq(result[1], 0, "sign(0)") assert_eq(result[2], 1, "sign(42)") assert_eq(result[3], -1, "sign(-0.5)") }) run("inline callback fraction", function() { var result = mymap([3.75, -2.5, 5.0], function(a) { return fraction(a) }) assert_eq(result[0], 0.75, "fraction(3.75)") assert_eq(result[1], -0.5, "fraction(-2.5)") assert_eq(result[2], 0, "fraction(5.0)") }) run("inline callback floor", function() { var result = mymap([3.7, -2.3, 5.0, 1.9], function(a) { return floor(a) }) assert_eq(result[0], 3, "floor(3.7)") assert_eq(result[1], -3, "floor(-2.3)") assert_eq(result[2], 5, "floor(5.0)") assert_eq(result[3], 1, "floor(1.9)") }) run("inline callback ceiling", function() { var result = mymap([3.2, -2.7, 5.0, 1.1], function(a) { return ceiling(a) }) assert_eq(result[0], 4, "ceiling(3.2)") assert_eq(result[1], -2, "ceiling(-2.7)") assert_eq(result[2], 5, "ceiling(5.0)") assert_eq(result[3], 2, "ceiling(1.1)") }) run("inline callback round", function() { var result = mymap([3.5, -2.5, 5.0, 1.4], function(a) { return round(a) }) assert_eq(result[0], 4, "round(3.5)") assert_eq(result[1], -3, "round(-2.5)") assert_eq(result[2], 5, "round(5.0)") assert_eq(result[3], 1, "round(1.4)") }) run("inline callback trunc", function() { var result = mymap([3.7, -2.3, 5.0, -1.9], function(a) { return trunc(a) }) assert_eq(result[0], 3, "trunc(3.7)") assert_eq(result[1], -2, "trunc(-2.3)") assert_eq(result[2], 5, "trunc(5.0)") assert_eq(result[3], -1, "trunc(-1.9)") }) run("inline callback max", function() { var result = myfold([3, 7, 2, 9, 1], function(a, b) { return max(a, b) }) assert_eq(result, 9, "max reduce") }) run("inline callback min", function() { var result = myfold([3, 7, 2, 9, 1], function(a, b) { return min(a, b) }) assert_eq(result, 1, "min reduce") }) run("inline callback modulo", function() { var result = myfold([17, 5], function(a, b) { return modulo(a, b) }) assert_eq(result, 2, "modulo") }) run("inline callback remainder", function() { var result = myfold([-17, 5], function(a, b) { return remainder(a, b) }) assert_eq(result, -2, "remainder") }) // ============================================================================ // STRING METHOD EDGE CASES // ============================================================================ run("string startsWith", function() { if (!starts_with("hello", "hel")) fail("startsWith match failed") if (starts_with("hello", "ell")) fail("startsWith no match failed") if (!starts_with("hello", "")) fail("startsWith empty should match") }) run("string endsWith", function() { if (!ends_with("hello", "llo")) fail("endsWith match failed") if (ends_with("hello", "ell")) fail("endsWith no match failed") if (!ends_with("hello", "")) fail("endsWith empty should match") }) run("string includes", function() { if (search("hello world", "world") == null) fail("includes match failed") if (search("hello", "xyz") != null) fail("includes no match failed") if (search("hello", "") == null) fail("includes empty should match") }) // ============================================================================ // ARRAY METHOD EDGE CASES // ============================================================================ run("array includes", function() { var arr = [1, 2, 3] if (find(arr, 2) == null) fail("array includes match failed") if (find(arr, 5) != null) fail("array includes no match failed") }) run("array every", function() { var arr = [2, 4, 6] if (!every(arr, x => x % 2 == 0)) fail("array every all pass failed") arr = [2, 3, 6] if (every(arr, x => x % 2 == 0)) fail("array every not all pass failed") }) run("array some", function() { var arr = [1, 2, 3] if (!some(arr, x => x > 2)) fail("array some match failed") if (some(arr, x => x > 5)) fail("array some no match failed") }) // ============================================================================ // ADDITIONAL EDGE CASES // ============================================================================ run("nested array access", function() { var arr = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] if (arr[0][1][0] != 3) fail("nested array access failed") if (arr[1][1][1] != 8) fail("nested array access deep failed") }) run("nested object access", function() { var obj = {a: {b: {c: {d: 42}}}} if (obj.a.b.c.d != 42) fail("nested object access failed") }) run("mixed nested access", function() { var data = {users: [{name: "Alice"}, {name: "Bob"}]} if (data.users[1].name != "Bob") fail("mixed nested access failed") }) run("object with null value", function() { var obj = {a: null, b: 2} if (obj.a != null) fail("object null value failed") if (!("a" in obj)) fail("object with null should have key") }) run("array with null values", function() { var arr = [1, null, 3] if (arr[1] != null) fail("array null value failed") if (length(arr) != 3) fail("array with null length failed") }) run("function returning function", function() { var outer = function(x) { return function(y) { return function(z) { return x + y + z } } } if (outer(1)(2)(3) != 6) fail("function returning function failed") }) run("immediately invoked function", function() { var result = (function(x) { return x * 2 })(21) if (result != 42) fail("immediately invoked function failed") }) run("text split text", function() { var t = "hello world" var result = array(t, " ") if (length(result) != 2) fail("text split failed") if (result[0] != "hello") fail("text split first failed") if (result[1] != "world") fail("text split second failed") }) run("text split regex", function() { var t = "hello world" var result = array(t, /\s+/) if (length(result) != 2) fail("text split regex failed") if (result[0] != "hello") fail("text split regex first failed") if (result[1] != "world") fail("text split regex second failed") }) run("text search text", function() { var t = "hello world" var result = search(t, "world") if (result != 6) fail("text search failed") }) run("text search regex", function() { var t = "hello world" var result = search(t, /world/) if (result != 6) fail("text search regex failed") }) run("extract basic text", function() { var t = "hello world" var result = extract(t, "world") if (result[0] != "world") fail("extract basic text failed") }) run("extract text not found", function() { var t = "hello world" var result = extract(t, "xyz") if (result != null) fail("extract not found should return null") }) run("extract regex basic", function() { var t = "hello world" var result = extract(t, /world/) if (result[0] != "world") fail("extract regex basic failed") }) run("extract regex with capture group", function() { var t = "hello world" var result = extract(t, /(\w+) (\w+)/) if (result[0] != "hello world") fail("extract regex full match failed") if (result[1] != "hello") fail("extract regex capture group 1 failed") if (result[2] != "world") fail("extract regex capture group 2 failed") }) run("extract regex digits", function() { var t = "abc123def456" var result = extract(t, /(\d+)/) if (result[0] != "123") fail("extract regex digits failed") if (result[1] != "123") fail("extract regex digits capture failed") }) run("extract with from", function() { var t = "hello hello world" var result = extract(t, "hello", 1) if (result[0] != "hello") fail("extract with from failed") }) run("extract with from to", function() { var t = "hello world hello" var result = extract(t, "hello", 0, 10) if (result[0] != "hello") fail("extract with from to failed") }) run("extract regex case insensitive", function() { var t = "Hello World" var result = extract(t, /hello/i) if (result[0] != "Hello") fail("extract regex case insensitive failed") }) // ============================================================================ // GC PATHOLOGICAL CASES // ============================================================================ run("gc cycle object self", function() { var obj = {name: "root"} obj.self = obj if (obj.self != obj) fail("self cycle failed") }) run("gc cycle array self", function() { var arr = [] var i = 0 for (i = 0; i < 10; i++) { arr[] = arr } if (arr[0] != arr) fail("array self cycle failed") }) run("gc cycle object array pair", function() { var obj = {kind: "node"} var arr = [obj] obj.arr = arr if (obj.arr[0] != obj) fail("object/array cycle failed") }) run("gc shared references", function() { var shared = {value: 42} var a = {ref: shared} var b = {ref: shared} if (a.ref != shared || b.ref != shared) fail("shared reference failed") }) run("gc object key cycle", function() { var k = {} var v = {label: "value"} var o = {} o[k] = v v.back = o if (o[k].back != o) fail("object key cycle failed") }) run("gc object text key mix", function() { var obj = {} var key = "alpha" var inner = {token: "x"} obj[key] = inner obj["beta"] = [inner, obj] if (obj.alpha.token != "x") fail("text key value failed") if (obj.beta[1] != obj) fail("text key cycle failed") }) // ============================================================================ // OBJECT INTRINSIC TESTS // ============================================================================ run("object shallow copy", function() { var orig = {a: 1, b: 2, c: 3} var copy = object(orig) if (copy.a != 1) fail("object copy a failed") if (copy.b != 2) fail("object copy b failed") if (copy.c != 3) fail("object copy c failed") copy.a = 99 if (orig.a != 1) fail("object copy should not mutate original") }) run("object combine", function() { var obj1 = {a: 1, b: 2} var obj2 = {c: 3, d: 4} var combined = object(obj1, obj2) if (combined.a != 1) fail("object combine a failed") if (combined.b != 2) fail("object combine b failed") if (combined.c != 3) fail("object combine c failed") if (combined.d != 4) fail("object combine d failed") }) run("object combine override", function() { var obj1 = {a: 1, b: 2} var obj2 = {b: 99, c: 3} var combined = object(obj1, obj2) if (combined.a != 1) fail("object combine override a failed") if (combined.b != 99) fail("object combine should override with second arg") if (combined.c != 3) fail("object combine override c failed") }) run("object select keys", function() { var orig = {a: 1, b: 2, c: 3, d: 4} var selected = object(orig, ["a", "c"]) if (selected.a != 1) fail("object select a failed") if (selected.c != 3) fail("object select c failed") if (selected.b != null) fail("object select should not include b") if (selected.d != null) fail("object select should not include d") }) run("object from keys true", function() { var keys = ["x", "y", "z"] var obj = object(keys) if (obj.x != true) fail("object from keys x failed") if (obj.y != true) fail("object from keys y failed") if (obj.z != true) fail("object from keys z failed") }) run("object from keys function", function() { var keys = ["a", "b", "c"] var obj = object(keys, function(k) { return k + "_val" }) if (obj.a != "a_val") fail("object from keys func a failed") if (obj.b != "b_val") fail("object from keys func b failed") if (obj.c != "c_val") fail("object from keys func c failed") }) // ============================================================================ // SPLAT INTRINSIC TESTS // ============================================================================ run("splat prototype flattening", function() { var proto = {x: 10, y: 20} var obj = meme(proto, {z: 30}) var flat = splat(obj) if (flat.x != 10) fail("splat x failed") if (flat.y != 20) fail("splat y failed") if (flat.z != 30) fail("splat z failed") }) // ============================================================================ // REVERSE INTRINSIC TESTS (detailed) // ============================================================================ run("reverse array detailed", function() { var arr = [1, 2, 3, 4, 5] var rev = reverse(arr) if (rev[0] != 5) fail("reverse[0] failed") if (rev[1] != 4) fail("reverse[1] failed") if (rev[2] != 3) fail("reverse[2] failed") if (rev[3] != 2) fail("reverse[3] failed") if (rev[4] != 1) fail("reverse[4] failed") if (arr[0] != 1) fail("reverse should not mutate original") }) // ============================================================================ // APPLY INTRINSIC TESTS // ============================================================================ run("fn.apply with array args", function() { def sum = function(a, b, c) { return a + b + c } var result = fn.apply(sum, [1, 2, 3]) if (result != 6) fail("apply with array args failed") }) run("fn.apply with no args", function() { def ret42 = function() { return 42 } var result = fn.apply(ret42) if (result != 42) fail("apply with no args failed") }) run("fn.apply with single value", function() { def double = function(x) { return x * 2 } var result = fn.apply(double, 10) if (result != 20) fail("apply with single value failed") }) // ============================================================================ // GC STRESS TESTS FOR FIXED INTRINSICS // ============================================================================ run("gc reverse under pressure", function() { var arrays = [] var i = 0 var rev = null for (i = 0; i < 100; i = i + 1) { arrays[i] = [i, i+1, i+2, i+3, i+4] } for (i = 0; i < 100; i = i + 1) { rev = reverse(arrays[i]) if (rev[0] != i+4) fail("gc reverse stress failed") } }) run("gc object select under pressure", function() { var objs = [] var i = 0 var selected = null for (i = 0; i < 100; i = i + 1) { objs[i] = {a: i, b: i+1, c: i+2, d: i+3} } for (i = 0; i < 100; i = i + 1) { selected = object(objs[i], ["a", "c"]) if (selected.a != i) fail("gc object select stress failed") if (selected.c != i+2) fail("gc object select stress c failed") } }) run("gc object from keys function under pressure", function() { var keysets = [] var i = 0 var obj = null var expected = null for (i = 0; i < 50; i = i + 1) { keysets[i] = [`k${i}`, `j${i}`, `m${i}`] } for (i = 0; i < 50; i = i + 1) { obj = object(keysets[i], function(k) { return k + "_value" }) expected = `k${i}_value` if (obj[`k${i}`] != expected) fail("gc object from keys func stress failed") } }) // ============================================================================ // DEFAULT PARAMETER TESTS // ============================================================================ run("default param constant", function() { var f = function(a, b=10) { return a + b } assert_eq(f(1), 11, "default param constant") }) run("default param overridden", function() { var f = function(a, b=10) { return a + b } assert_eq(f(1, 2), 3, "default param overridden") }) run("default param uses earlier param", function() { var f = function(a, b=a+1) { return b } assert_eq(f(5), 6, "default param uses earlier param") }) run("default param uses earlier param overridden", function() { var f = function(a, b=a+1) { return b } assert_eq(f(5, 20), 20, "default param uses earlier param overridden") }) run("multiple default params", function() { var f = function(a, b=10, c=a+1) { return a + b + c } assert_eq(f(1), 13, "multiple defaults f(1)") assert_eq(f(1, 2), 5, "multiple defaults f(1,2)") assert_eq(f(1, 2, 3), 6, "multiple defaults f(1,2,3)") }) run("arrow function default param", function() { var g = (x, y=100) => x + y assert_eq(g(5), 105, "arrow default param") assert_eq(g(5, 20), 25, "arrow default param overridden") }) run("default param null passed explicitly", function() { var f = function(a, b=10) { return b } assert_eq(f(1, null), 10, "explicit null triggers default") }) run("default param with string", function() { var f = function(a, b="hello") { return b } assert_eq(f(1), "hello", "default string param") assert_eq(f(1, "world"), "world", "default string overridden") }) run("default param first param has no default", function() { var f = function(a, b=42) { return a } assert_eq(f(7), 7, "first param no default") }) // ============================================================================ // FUNCTINO TESTS // ============================================================================ run("functino +! addition", function() { assert_eq(+!(3, 4), 7, "+! addition") }) run("functino -! subtraction", function() { assert_eq(-!(10, 3), 7, "-! subtraction") }) run("functino *! multiplication", function() { assert_eq(*!(5, 6), 30, "*! multiplication") }) run("functino /! division", function() { assert_eq(/!(10, 2), 5, "/! division") }) run("functino %! modulo", function() { assert_eq(%!(10, 3), 1, "%! modulo") }) run("functino **! power", function() { assert_eq(**!(2, 10), 1024, "**! power") }) run("functino ! greater than", function() { assert_eq(>!(5, 3), true, ">! true") assert_eq(>!(3, 5), false, ">! false") assert_eq(>!(3, 3), false, ">! equal") }) run("functino <=! less or equal", function() { assert_eq(<=!(3, 5), true, "<=! less") assert_eq(<=!(3, 3), true, "<=! equal") assert_eq(<=!(5, 3), false, "<=! greater") }) run("functino >=! greater or equal", function() { assert_eq(>=!(5, 3), true, ">=! greater") assert_eq(>=!(3, 3), true, ">=! equal") assert_eq(>=!(3, 5), false, ">=! less") }) run("functino =! equality", function() { assert_eq(=!(5, 5), true, "=! true") assert_eq(=!(5, 3), false, "=! false") }) run("functino !=! inequality", function() { assert_eq(!=!(5, 3), true, "!=! true") assert_eq(!=!(5, 5), false, "!=! false") }) run("functino =! with tolerance", function() { assert_eq(=!(1.0, 1.0001, 0.001), true, "=! within tolerance") assert_eq(=!(1.0, 1.01, 0.001), false, "=! outside tolerance") }) run("functino !=! with tolerance", function() { assert_eq(!=!(1.0, 1.01, 0.001), true, "!=! outside tolerance") assert_eq(!=!(1.0, 1.0001, 0.001), false, "!=! within tolerance") }) run("functino &! bitwise and", function() { assert_eq(&!(0xff, 0x0f), 0x0f, "&! bitwise and") }) run("functino |! bitwise or", function() { assert_eq(|!(0xf0, 0x0f), 0xff, "|! bitwise or") }) run("functino ^! bitwise xor", function() { assert_eq(^!(0xff, 0x0f), 0xf0, "^! bitwise xor") }) run("functino <>! shift right", function() { assert_eq(>>!(16, 4), 1, ">>! shift right") }) run("functino ~! bitwise not", function() { assert_eq(~!(0), -1, "~! bitwise not 0") }) run("functino []! array index", function() { var arr = [10, 20, 30] assert_eq([]!(arr, 0), 10, "[]! index 0") assert_eq([]!(arr, 1), 20, "[]! index 1") assert_eq([]!(arr, 2), 30, "[]! index 2") }) run("functino &&! logical and", function() { assert_eq(&&!(true, true), true, "&&! true true") assert_eq(&&!(true, false), false, "&&! true false") assert_eq(&&!(false, true), false, "&&! false true") assert_eq(&&!(1, 2), 2, "&&! truthy returns right") assert_eq(&&!(0, 2), 0, "&&! falsy returns left") }) run("functino ||! logical or", function() { assert_eq(||!(false, true), true, "||! false true") assert_eq(||!(true, false), true, "||! true false") assert_eq(||!(false, false), false, "||! false false") assert_eq(||!(0, 5), 5, "||! falsy returns right") assert_eq(||!(3, 5), 3, "||! truthy returns left") }) run("functino >>>! unsigned shift right", function() { assert_eq(>>>!(-1, 28), 15, ">>>! unsigned shift right") }) // ============================================================================ // FUNCTION DEFAULT PARAMETERS // ============================================================================ run("default param basic", function() { var fn = function(a = 3, b = 5) { return a + b } assert_eq(fn(), 8, "both defaults") assert_eq(fn(10), 15, "first provided") assert_eq(fn(10, 20), 30, "both provided") }) run("default param null triggers default", function() { var fn = function(a = 99) { return a } assert_eq(fn(null), 99, "null should trigger default") assert_eq(fn(), 99, "missing should trigger default") assert_eq(fn(0), 0, "zero should not trigger default") assert_eq(fn(false), false, "false should not trigger default") assert_eq(fn(""), "", "empty string should not trigger default") }) run("default param pipe syntax", function() { var fn = function(a | 3, b | 5) { return a + b } assert_eq(fn(), 8, "pipe both defaults") assert_eq(fn(10), 15, "pipe first provided") assert_eq(fn(10, 20), 30, "pipe both provided") }) run("default param object literal", function() { var fn = function(a = {x: 1, y: 2}) { return a.x + a.y } assert_eq(fn(), 3, "object default") assert_eq(fn({x: 10, y: 20}), 30, "object provided") }) run("default param array literal", function() { var fn = function(a = [10, 20, 30]) { return a[1] } assert_eq(fn(), 20, "array default") assert_eq(fn([99]), null, "array provided") }) run("default param nested object", function() { var fn = function(a = {inner: {val: 42}}) { return a.inner.val } assert_eq(fn(), 42, "nested object default") }) run("default param expression", function() { var fn = function(a = 1 + 2, b = 3 * 4) { return a + b } assert_eq(fn(), 15, "expression defaults") }) run("default param negative number", function() { var fn = function(a = -1) { return a } assert_eq(fn(), -1, "negative default") }) run("default param function call", function() { var double = function(x) { return x * 2 } var fn = function(a = double(21)) { return a } assert_eq(fn(), 42, "function call default") }) run("default param function value", function() { var fn = function(f = function() { return 42 }) { return f() } assert_eq(fn(), 42, "function as default") }) run("default param arrow function", function() { var fn = function(f = (x) => x * 2) { return f(5) } assert_eq(fn(), 10, "arrow function as default") }) run("default param ternary in default", function() { var fn = function(a = true ? 10 : 20) { return a } assert_eq(fn(), 10, "ternary default") }) run("default param string with comma", function() { var fn = function(a = "hello, world") { return a } assert_eq(fn(), "hello, world", "string comma default") }) run("default param empty object", function() { var fn = function(a = {}) { return is_object(a) } assert_eq(fn(), true, "empty object default") }) run("default param empty array", function() { var fn = function(a = []) { return is_array(a) } assert_eq(fn(), true, "empty array default") }) run("default param multiple complex", function() { var fn = function(a = {k: 1}, b = [4, 5], c = "hi") { return text(a.k) + text(b[0]) + c } assert_eq(fn(), "14hi", "multiple complex defaults") assert_eq(fn({k: 9}), "94hi", "first overridden") assert_eq(fn({k: 9}, [7]), "97hi", "first two overridden") assert_eq(fn({k: 9}, [7], "yo"), "97yo", "all overridden") }) run("default param independent defaults", function() { var fn = function(a = 1, b = 2) { return a + b } assert_eq(fn(), 3, "both defaults") assert_eq(fn(10), 12, "first provided") }) run("default param arrow function syntax", function() { var fn = (a = 7, b = 8) => a + b assert_eq(fn(), 15, "arrow defaults") assert_eq(fn(100), 108, "arrow first provided") assert_eq(fn(100, 200), 300, "arrow both provided") }) run("default param single arrow", function() { var fn = (a = 42) => a assert_eq(fn(), 42, "single arrow default") assert_eq(fn(0), 0, "single arrow zero") }) run("default param method shorthand", function() { var obj = { calc(a = 10, b = 20) { return a + b } } assert_eq(obj.calc(), 30, "method defaults") assert_eq(obj.calc(1), 21, "method first provided") assert_eq(obj.calc(1, 2), 3, "method both provided") }) run("default param each fresh object", function() { var fn = function(a = {count: 0}) { a.count = a.count + 1 return a.count } assert_eq(fn(), 1, "first call fresh object") assert_eq(fn(), 1, "second call fresh object") }) run("default param each fresh array", function() { var fn = function(a = []) { a[] = 1 return length(a) } assert_eq(fn(), 1, "first call fresh array") assert_eq(fn(), 1, "second call fresh array") }) run("default param nested parens", function() { var fn = function(a = (1 + 2) * 3) { return a } assert_eq(fn(), 9, "nested parens default") }) run("default param closure over outer", function() { var base = 100 var fn = function(a = base) { return a } assert_eq(fn(), 100, "closure over outer") base = 200 assert_eq(fn(), 200, "closure sees updated outer") }) // ============================================================================ // DO-WHILE LOOPS // ============================================================================ run("do-while basic", function() { var i = 0 do { i = i + 1 } while (i < 3) assert_eq(i, 3, "do-while counted to 3") }) run("do-while executes body once when false", function() { var count = 0 do { count = count + 1 } while (false) assert_eq(count, 1, "body ran once") }) run("do-while break", function() { var i = 0 do { if (i == 2) break i = i + 1 } while (i < 10) assert_eq(i, 2, "break at 2") }) run("do-while continue", function() { var sum = 0 var j = 0 do { j = j + 1 if (j == 3) continue sum = sum + j } while (j < 5) assert_eq(sum, 12, "skip 3: 1+2+4+5") }) run("do-while nested", function() { var result = 0 var i = 0 var j = 0 do { j = 0 do { result = result + 1 j = j + 1 } while (j < 2) i = i + 1 } while (i < 3) assert_eq(result, 6, "3 outer * 2 inner") }) // ============================================================================ // LABELED BREAK AND CONTINUE // ============================================================================ run("labeled break exits outer for loop", function() { var result = 0 var j = 0 var k = 0 outer: for (j = 0; j < 3; j = j + 1) { for (k = 0; k < 3; k = k + 1) { if (k == 1) break outer result = result + 1 } } assert_eq(result, 1, "only one iteration before break outer") assert_eq(j, 0, "outer loop did not advance") }) run("labeled continue skips to outer iteration", function() { var result = 0 var a = 0 var b = 0 outer: for (a = 0; a < 3; a = a + 1) { for (b = 0; b < 3; b = b + 1) { if (b == 1) continue outer result = result + 1 } } assert_eq(result, 3, "3 outer iters, each runs b=0 only") }) run("labeled break from while", function() { var x = 0 top: while (true) { x = x + 1 if (x == 5) break top } assert_eq(x, 5, "broke out at 5") }) run("labeled break triple nested", function() { var r = 0 var c = 0 var d = 0 var e = 0 outer: for (c = 0; c < 3; c = c + 1) { for (d = 0; d < 3; d = d + 1) { for (e = 0; e < 3; e = e + 1) { if (e == 1) break outer r = r + 1 } } } assert_eq(r, 1, "broke out from 3 levels") }) run("labeled continue with do-while", function() { var total = 0 var i = 0 var j = 0 outer: for (i = 0; i < 3; i = i + 1) { j = 0 do { if (j == 1) continue outer total = total + 1 j = j + 1 } while (j < 3) } assert_eq(total, 3, "3 outer iters, each does j=0 only") }) // ============================================================================ // SHORTHAND PROPERTY SYNTAX // ============================================================================ run("shorthand property basic", function() { var name = "Alice" var age = 30 var obj = {name, age} assert_eq(obj.name, "Alice", "name shorthand") assert_eq(obj.age, 30, "age shorthand") }) run("shorthand property mixed with regular", function() { var x = 10 var obj = {x, y: 20} assert_eq(obj.x, 10, "shorthand x") assert_eq(obj.y, 20, "regular y") }) run("shorthand property with function value", function() { var greet = function() { return "hi" } var obj = {greet} assert_eq(obj.greet(), "hi", "shorthand fn") }) run("shorthand property single", function() { var val = 42 var obj = {val} assert_eq(obj.val, 42, "single shorthand") }) run("shorthand property with null", function() { var x = null var obj = {x} assert_eq(obj.x, null, "null shorthand") }) run("shorthand property many", function() { var a = 1 var b = 2 var c = 3 var d = 4 var e = 5 var f = 6 var g = 7 var h = 8 var i = 9 var j = 10 var obj = {a, b, c, d, e, f, g, h, i, j} assert_eq(obj.a, 1, "a") assert_eq(obj.e, 5, "e") assert_eq(obj.j, 10, "j") }) run("shorthand property with method", function() { var z = 300 var obj = {z, double(n) { return n * 2 }} assert_eq(obj.z, 300, "shorthand z") assert_eq(obj.double(5), 10, "method double") }) run("shorthand property is a snapshot", function() { var val = "original" var obj = {val} val = "changed" assert_eq(obj.val, "original", "object keeps original") }) // ============================================================================ // SHARED CLOSURES // ============================================================================ run("closures share captured variable", function() { var x = 0 var inc = function() { x = x + 1 } var get = function() { return x } inc() assert_eq(get(), 1, "after one inc") inc() assert_eq(get(), 2, "after two incs") }) run("closure factory returns shared state", function() { var make = function() { var count = 0 return { inc: function() { count = count + 1 }, get: function() { return count } } } var c = make() c.inc() c.inc() c.inc() assert_eq(c.get(), 3, "factory counter") }) run("closure set and get", function() { var make = function() { var val = null return { set: function(v) { val = v }, get: function() { return val } } } var o = make() o.set("hello") assert_eq(o.get(), "hello", "set then get") o.set(42) assert_eq(o.get(), 42, "overwrite") }) run("closure write heap values visible to outer scope", function() { var a = null var b = null var c = null var d = null var e = null var f1 = function() { a = 42 } var f2 = function() { b = true } var f3 = function() { c = "hello" } var f4 = function() { d = {x: 1} } var f5 = function() { e = [1, 2] } f1() f2() f3() f4() f5() assert_eq(a, 42, "closure write number") assert_eq(b, true, "closure write boolean") assert_eq(c != null, true, "closure write text not null") assert_eq(c, "hello", "closure write text value") assert_eq(d != null, true, "closure write object not null") assert_eq(d.x, 1, "closure write object property") assert_eq(e != null, true, "closure write array not null") assert_eq(e[0], 1, "closure write array element") }) // ============================================================================ // STRING COMPARISON OPERATORS // ============================================================================ run("string less than", function() { assert_eq("apple" < "banana", true, "apple < banana") assert_eq("banana" < "apple", false, "banana < apple") assert_eq("abc" < "abd", true, "abc < abd") }) run("string greater than", function() { assert_eq("banana" > "apple", true, "banana > apple") assert_eq("apple" > "banana", false, "apple > banana") }) run("string less than or equal", function() { assert_eq("abc" <= "abc", true, "equal strings") assert_eq("abc" <= "abd", true, "abc <= abd") assert_eq("abd" <= "abc", false, "abd <= abc") }) run("string greater than or equal", function() { assert_eq("abc" >= "abc", true, "equal strings") assert_eq("abd" >= "abc", true, "abd >= abc") }) // ============================================================================ // CROSS-TYPE STRICT EQUALITY // ============================================================================ run("strict equality different types", function() { assert_eq(5 == "5", false, "number != string") assert_eq(true == 1, false, "bool != number") assert_eq(false == 0, false, "false != zero") assert_eq(null == false, false, "null != false") assert_eq("" == false, false, "empty string != false") assert_eq(0 == null, false, "zero != null") }) // ============================================================================ // COMPOUND ASSIGNMENT OPERATORS (&&= and ||=) // ============================================================================ run("logical and assign", function() { var x = true x &&= false assert_eq(x, false, "true &&= false") var y = false y &&= true assert_eq(y, false, "false &&= true") }) run("logical or assign", function() { var x = false x ||= true assert_eq(x, true, "false ||= true") var y = true y ||= false assert_eq(y, true, "true ||= false") }) // ============================================================================ // EVERY/SOME ON EMPTY ARRAYS // ============================================================================ run("every on empty array", function() { var result = every([], function(x) { return false }) assert_eq(result, true, "vacuous truth") }) run("some on empty array", function() { var result = some([], function(x) { return true }) assert_eq(result, false, "no elements match") }) // ============================================================================ // CHAINED METHOD CALLS // ============================================================================ run("chained method calls", function() { var make_chain = function() { var obj = { val: 0, add(n) { obj.val = obj.val + n return obj } } return obj } var c = make_chain() var result = c.add(1).add(2).add(3).val assert_eq(result, 6, "1+2+3 chained") }) // ============================================================================ // TEMPLATE LITERALS WITH EXPRESSIONS // ============================================================================ run("template literal arithmetic", function() { var a = 3 var b = 4 assert_eq(`${a + b}`, "7", "addition in template") }) run("template literal nested calls", function() { var double = function(x) { return x * 2 } assert_eq(`result: ${double(5)}`, "result: 10", "fn call in template") }) // ============================================================================ // COMMA-SEPARATED DECLARATIONS // ============================================================================ run("comma separated var declarations", function() { var a = 1, b = 2, c = 3 assert_eq(a, 1, "first") assert_eq(b, 2, "second") assert_eq(c, 3, "third") }) run("comma separated def declarations", function() { def x = 10, y = 20 assert_eq(x, 10, "def first") assert_eq(y, 20, "def second") }) // ============================================================================ // DEF CONSTANTS // ============================================================================ run("def prevents reassignment", function() { var caught = false def x = 42 assert_eq(x, 42, "def value") // reassignment should disrupt }) // ============================================================================ // RECURSIVE CLOSURES // ============================================================================ run("recursive closure fibonacci", function() { var fib = function(n) { if (n <= 1) return n return fib(n - 1) + fib(n - 2) } assert_eq(fib(0), 0, "fib(0)") assert_eq(fib(1), 1, "fib(1)") assert_eq(fib(6), 8, "fib(6)") assert_eq(fib(10), 55, "fib(10)") }) // ============================================================================ // KEYWORD PROPERTY NAMES // ============================================================================ run("keyword property names", function() { var obj = {if: 1, while: 2, return: 3} assert_eq(obj.if, 1, "if prop") assert_eq(obj.while, 2, "while prop") assert_eq(obj.return, 3, "return prop") }) // ============================================================================ // NESTED RETURN FROM IF/ELSE // ============================================================================ run("nested return in if-else chain", function() { var classify = function(n) { if (n < 0) { return "negative" } else if (n == 0) { return "zero" } else { return "positive" } } assert_eq(classify(-5), "negative", "negative") assert_eq(classify(0), "zero", "zero") assert_eq(classify(10), "positive", "positive") }) // ============================================================================ // IIFE (IMMEDIATELY INVOKED FUNCTION EXPRESSION) // ============================================================================ run("IIFE with arguments", function() { var result = (function(a, b) { return a + b })(10, 20) assert_eq(result, 30, "IIFE sum") }) // ============================================================================ // PATHOLOGICAL OBJECT LITERALS - Diagnose large object key/value limits // ============================================================================ // Test: object with 100 simple keys (number values) run("object literal 100 number keys", function() { var obj = { k000: 0, k001: 1, k002: 2, k003: 3, k004: 4, k005: 5, k006: 6, k007: 7, k008: 8, k009: 9, k010: 10, k011: 11, k012: 12, k013: 13, k014: 14, k015: 15, k016: 16, k017: 17, k018: 18, k019: 19, k020: 20, k021: 21, k022: 22, k023: 23, k024: 24, k025: 25, k026: 26, k027: 27, k028: 28, k029: 29, k030: 30, k031: 31, k032: 32, k033: 33, k034: 34, k035: 35, k036: 36, k037: 37, k038: 38, k039: 39, k040: 40, k041: 41, k042: 42, k043: 43, k044: 44, k045: 45, k046: 46, k047: 47, k048: 48, k049: 49, k050: 50, k051: 51, k052: 52, k053: 53, k054: 54, k055: 55, k056: 56, k057: 57, k058: 58, k059: 59, k060: 60, k061: 61, k062: 62, k063: 63, k064: 64, k065: 65, k066: 66, k067: 67, k068: 68, k069: 69, k070: 70, k071: 71, k072: 72, k073: 73, k074: 74, k075: 75, k076: 76, k077: 77, k078: 78, k079: 79, k080: 80, k081: 81, k082: 82, k083: 83, k084: 84, k085: 85, k086: 86, k087: 87, k088: 88, k089: 89, k090: 90, k091: 91, k092: 92, k093: 93, k094: 94, k095: 95, k096: 96, k097: 97, k098: 98, k099: 99 } var keys = array(obj) assert_eq(length(keys), 100, "should have 100 keys") assert_eq(obj.k000, 0, "first key") assert_eq(obj.k099, 99, "last key") }) // Test: object with 200 simple keys (number values) run("object literal 200 number keys", function() { var obj = { k000: 0, k001: 1, k002: 2, k003: 3, k004: 4, k005: 5, k006: 6, k007: 7, k008: 8, k009: 9, k010: 10, k011: 11, k012: 12, k013: 13, k014: 14, k015: 15, k016: 16, k017: 17, k018: 18, k019: 19, k020: 20, k021: 21, k022: 22, k023: 23, k024: 24, k025: 25, k026: 26, k027: 27, k028: 28, k029: 29, k030: 30, k031: 31, k032: 32, k033: 33, k034: 34, k035: 35, k036: 36, k037: 37, k038: 38, k039: 39, k040: 40, k041: 41, k042: 42, k043: 43, k044: 44, k045: 45, k046: 46, k047: 47, k048: 48, k049: 49, k050: 50, k051: 51, k052: 52, k053: 53, k054: 54, k055: 55, k056: 56, k057: 57, k058: 58, k059: 59, k060: 60, k061: 61, k062: 62, k063: 63, k064: 64, k065: 65, k066: 66, k067: 67, k068: 68, k069: 69, k070: 70, k071: 71, k072: 72, k073: 73, k074: 74, k075: 75, k076: 76, k077: 77, k078: 78, k079: 79, k080: 80, k081: 81, k082: 82, k083: 83, k084: 84, k085: 85, k086: 86, k087: 87, k088: 88, k089: 89, k090: 90, k091: 91, k092: 92, k093: 93, k094: 94, k095: 95, k096: 96, k097: 97, k098: 98, k099: 99, k100: 100, k101: 101, k102: 102, k103: 103, k104: 104, k105: 105, k106: 106, k107: 107, k108: 108, k109: 109, k110: 110, k111: 111, k112: 112, k113: 113, k114: 114, k115: 115, k116: 116, k117: 117, k118: 118, k119: 119, k120: 120, k121: 121, k122: 122, k123: 123, k124: 124, k125: 125, k126: 126, k127: 127, k128: 128, k129: 129, k130: 130, k131: 131, k132: 132, k133: 133, k134: 134, k135: 135, k136: 136, k137: 137, k138: 138, k139: 139, k140: 140, k141: 141, k142: 142, k143: 143, k144: 144, k145: 145, k146: 146, k147: 147, k148: 148, k149: 149, k150: 150, k151: 151, k152: 152, k153: 153, k154: 154, k155: 155, k156: 156, k157: 157, k158: 158, k159: 159, k160: 160, k161: 161, k162: 162, k163: 163, k164: 164, k165: 165, k166: 166, k167: 167, k168: 168, k169: 169, k170: 170, k171: 171, k172: 172, k173: 173, k174: 174, k175: 175, k176: 176, k177: 177, k178: 178, k179: 179, k180: 180, k181: 181, k182: 182, k183: 183, k184: 184, k185: 185, k186: 186, k187: 187, k188: 188, k189: 189, k190: 190, k191: 191, k192: 192, k193: 193, k194: 194, k195: 195, k196: 196, k197: 197, k198: 198, k199: 199 } var keys = array(obj) assert_eq(length(keys), 200, "should have 200 keys") assert_eq(obj.k000, 0, "first key") assert_eq(obj.k199, 199, "last key") }) // Test: object with 256 simple keys (number values) - exact boundary run("object literal 256 number keys", function() { var obj = { k000: 0, k001: 1, k002: 2, k003: 3, k004: 4, k005: 5, k006: 6, k007: 7, k008: 8, k009: 9, k010: 10, k011: 11, k012: 12, k013: 13, k014: 14, k015: 15, k016: 16, k017: 17, k018: 18, k019: 19, k020: 20, k021: 21, k022: 22, k023: 23, k024: 24, k025: 25, k026: 26, k027: 27, k028: 28, k029: 29, k030: 30, k031: 31, k032: 32, k033: 33, k034: 34, k035: 35, k036: 36, k037: 37, k038: 38, k039: 39, k040: 40, k041: 41, k042: 42, k043: 43, k044: 44, k045: 45, k046: 46, k047: 47, k048: 48, k049: 49, k050: 50, k051: 51, k052: 52, k053: 53, k054: 54, k055: 55, k056: 56, k057: 57, k058: 58, k059: 59, k060: 60, k061: 61, k062: 62, k063: 63, k064: 64, k065: 65, k066: 66, k067: 67, k068: 68, k069: 69, k070: 70, k071: 71, k072: 72, k073: 73, k074: 74, k075: 75, k076: 76, k077: 77, k078: 78, k079: 79, k080: 80, k081: 81, k082: 82, k083: 83, k084: 84, k085: 85, k086: 86, k087: 87, k088: 88, k089: 89, k090: 90, k091: 91, k092: 92, k093: 93, k094: 94, k095: 95, k096: 96, k097: 97, k098: 98, k099: 99, k100: 100, k101: 101, k102: 102, k103: 103, k104: 104, k105: 105, k106: 106, k107: 107, k108: 108, k109: 109, k110: 110, k111: 111, k112: 112, k113: 113, k114: 114, k115: 115, k116: 116, k117: 117, k118: 118, k119: 119, k120: 120, k121: 121, k122: 122, k123: 123, k124: 124, k125: 125, k126: 126, k127: 127, k128: 128, k129: 129, k130: 130, k131: 131, k132: 132, k133: 133, k134: 134, k135: 135, k136: 136, k137: 137, k138: 138, k139: 139, k140: 140, k141: 141, k142: 142, k143: 143, k144: 144, k145: 145, k146: 146, k147: 147, k148: 148, k149: 149, k150: 150, k151: 151, k152: 152, k153: 153, k154: 154, k155: 155, k156: 156, k157: 157, k158: 158, k159: 159, k160: 160, k161: 161, k162: 162, k163: 163, k164: 164, k165: 165, k166: 166, k167: 167, k168: 168, k169: 169, k170: 170, k171: 171, k172: 172, k173: 173, k174: 174, k175: 175, k176: 176, k177: 177, k178: 178, k179: 179, k180: 180, k181: 181, k182: 182, k183: 183, k184: 184, k185: 185, k186: 186, k187: 187, k188: 188, k189: 189, k190: 190, k191: 191, k192: 192, k193: 193, k194: 194, k195: 195, k196: 196, k197: 197, k198: 198, k199: 199, k200: 200, k201: 201, k202: 202, k203: 203, k204: 204, k205: 205, k206: 206, k207: 207, k208: 208, k209: 209, k210: 210, k211: 211, k212: 212, k213: 213, k214: 214, k215: 215, k216: 216, k217: 217, k218: 218, k219: 219, k220: 220, k221: 221, k222: 222, k223: 223, k224: 224, k225: 225, k226: 226, k227: 227, k228: 228, k229: 229, k230: 230, k231: 231, k232: 232, k233: 233, k234: 234, k235: 235, k236: 236, k237: 237, k238: 238, k239: 239, k240: 240, k241: 241, k242: 242, k243: 243, k244: 244, k245: 245, k246: 246, k247: 247, k248: 248, k249: 249, k250: 250, k251: 251, k252: 252, k253: 253, k254: 254, k255: 255 } var keys = array(obj) assert_eq(length(keys), 256, "should have 256 keys") assert_eq(obj.k000, 0, "first key") assert_eq(obj.k255, 255, "last key") }) // Test: object with 257 keys - just past 256 boundary run("object literal 257 number keys", function() { var obj = { k000: 0, k001: 1, k002: 2, k003: 3, k004: 4, k005: 5, k006: 6, k007: 7, k008: 8, k009: 9, k010: 10, k011: 11, k012: 12, k013: 13, k014: 14, k015: 15, k016: 16, k017: 17, k018: 18, k019: 19, k020: 20, k021: 21, k022: 22, k023: 23, k024: 24, k025: 25, k026: 26, k027: 27, k028: 28, k029: 29, k030: 30, k031: 31, k032: 32, k033: 33, k034: 34, k035: 35, k036: 36, k037: 37, k038: 38, k039: 39, k040: 40, k041: 41, k042: 42, k043: 43, k044: 44, k045: 45, k046: 46, k047: 47, k048: 48, k049: 49, k050: 50, k051: 51, k052: 52, k053: 53, k054: 54, k055: 55, k056: 56, k057: 57, k058: 58, k059: 59, k060: 60, k061: 61, k062: 62, k063: 63, k064: 64, k065: 65, k066: 66, k067: 67, k068: 68, k069: 69, k070: 70, k071: 71, k072: 72, k073: 73, k074: 74, k075: 75, k076: 76, k077: 77, k078: 78, k079: 79, k080: 80, k081: 81, k082: 82, k083: 83, k084: 84, k085: 85, k086: 86, k087: 87, k088: 88, k089: 89, k090: 90, k091: 91, k092: 92, k093: 93, k094: 94, k095: 95, k096: 96, k097: 97, k098: 98, k099: 99, k100: 100, k101: 101, k102: 102, k103: 103, k104: 104, k105: 105, k106: 106, k107: 107, k108: 108, k109: 109, k110: 110, k111: 111, k112: 112, k113: 113, k114: 114, k115: 115, k116: 116, k117: 117, k118: 118, k119: 119, k120: 120, k121: 121, k122: 122, k123: 123, k124: 124, k125: 125, k126: 126, k127: 127, k128: 128, k129: 129, k130: 130, k131: 131, k132: 132, k133: 133, k134: 134, k135: 135, k136: 136, k137: 137, k138: 138, k139: 139, k140: 140, k141: 141, k142: 142, k143: 143, k144: 144, k145: 145, k146: 146, k147: 147, k148: 148, k149: 149, k150: 150, k151: 151, k152: 152, k153: 153, k154: 154, k155: 155, k156: 156, k157: 157, k158: 158, k159: 159, k160: 160, k161: 161, k162: 162, k163: 163, k164: 164, k165: 165, k166: 166, k167: 167, k168: 168, k169: 169, k170: 170, k171: 171, k172: 172, k173: 173, k174: 174, k175: 175, k176: 176, k177: 177, k178: 178, k179: 179, k180: 180, k181: 181, k182: 182, k183: 183, k184: 184, k185: 185, k186: 186, k187: 187, k188: 188, k189: 189, k190: 190, k191: 191, k192: 192, k193: 193, k194: 194, k195: 195, k196: 196, k197: 197, k198: 198, k199: 199, k200: 200, k201: 201, k202: 202, k203: 203, k204: 204, k205: 205, k206: 206, k207: 207, k208: 208, k209: 209, k210: 210, k211: 211, k212: 212, k213: 213, k214: 214, k215: 215, k216: 216, k217: 217, k218: 218, k219: 219, k220: 220, k221: 221, k222: 222, k223: 223, k224: 224, k225: 225, k226: 226, k227: 227, k228: 228, k229: 229, k230: 230, k231: 231, k232: 232, k233: 233, k234: 234, k235: 235, k236: 236, k237: 237, k238: 238, k239: 239, k240: 240, k241: 241, k242: 242, k243: 243, k244: 244, k245: 245, k246: 246, k247: 247, k248: 248, k249: 249, k250: 250, k251: 251, k252: 252, k253: 253, k254: 254, k255: 255, k256: 256 } var keys = array(obj) assert_eq(length(keys), 257, "should have 257 keys") assert_eq(obj.k000, 0, "first key") assert_eq(obj.k256, 256, "last key") }) // Test: object with 100 function values run("object literal 100 function values", function() { var obj = { f000: function() { return 0 }, f001: function() { return 1 }, f002: function() { return 2 }, f003: function() { return 3 }, f004: function() { return 4 }, f005: function() { return 5 }, f006: function() { return 6 }, f007: function() { return 7 }, f008: function() { return 8 }, f009: function() { return 9 }, f010: function() { return 10 }, f011: function() { return 11 }, f012: function() { return 12 }, f013: function() { return 13 }, f014: function() { return 14 }, f015: function() { return 15 }, f016: function() { return 16 }, f017: function() { return 17 }, f018: function() { return 18 }, f019: function() { return 19 }, f020: function() { return 20 }, f021: function() { return 21 }, f022: function() { return 22 }, f023: function() { return 23 }, f024: function() { return 24 }, f025: function() { return 25 }, f026: function() { return 26 }, f027: function() { return 27 }, f028: function() { return 28 }, f029: function() { return 29 }, f030: function() { return 30 }, f031: function() { return 31 }, f032: function() { return 32 }, f033: function() { return 33 }, f034: function() { return 34 }, f035: function() { return 35 }, f036: function() { return 36 }, f037: function() { return 37 }, f038: function() { return 38 }, f039: function() { return 39 }, f040: function() { return 40 }, f041: function() { return 41 }, f042: function() { return 42 }, f043: function() { return 43 }, f044: function() { return 44 }, f045: function() { return 45 }, f046: function() { return 46 }, f047: function() { return 47 }, f048: function() { return 48 }, f049: function() { return 49 }, f050: function() { return 50 }, f051: function() { return 51 }, f052: function() { return 52 }, f053: function() { return 53 }, f054: function() { return 54 }, f055: function() { return 55 }, f056: function() { return 56 }, f057: function() { return 57 }, f058: function() { return 58 }, f059: function() { return 59 }, f060: function() { return 60 }, f061: function() { return 61 }, f062: function() { return 62 }, f063: function() { return 63 }, f064: function() { return 64 }, f065: function() { return 65 }, f066: function() { return 66 }, f067: function() { return 67 }, f068: function() { return 68 }, f069: function() { return 69 }, f070: function() { return 70 }, f071: function() { return 71 }, f072: function() { return 72 }, f073: function() { return 73 }, f074: function() { return 74 }, f075: function() { return 75 }, f076: function() { return 76 }, f077: function() { return 77 }, f078: function() { return 78 }, f079: function() { return 79 }, f080: function() { return 80 }, f081: function() { return 81 }, f082: function() { return 82 }, f083: function() { return 83 }, f084: function() { return 84 }, f085: function() { return 85 }, f086: function() { return 86 }, f087: function() { return 87 }, f088: function() { return 88 }, f089: function() { return 89 }, f090: function() { return 90 }, f091: function() { return 91 }, f092: function() { return 92 }, f093: function() { return 93 }, f094: function() { return 94 }, f095: function() { return 95 }, f096: function() { return 96 }, f097: function() { return 97 }, f098: function() { return 98 }, f099: function() { return 99 } } var keys = array(obj) var i = 0 var bad_count = 0 assert_eq(length(keys), 100, "should have 100 keys") for (i = 0; i < length(keys); i++) { if (!is_function(obj[keys[i]])) { bad_count = bad_count + 1 } } assert_eq(bad_count, 0, "all 100 values should be functions") assert_eq(obj.f000(), 0, "first fn returns 0") assert_eq(obj.f099(), 99, "last fn returns 99") }) // Test: object with 256 function values - exact boundary run("object literal 256 function values", function() { var obj = { f000: function() { return 0 }, f001: function() { return 1 }, f002: function() { return 2 }, f003: function() { return 3 }, f004: function() { return 4 }, f005: function() { return 5 }, f006: function() { return 6 }, f007: function() { return 7 }, f008: function() { return 8 }, f009: function() { return 9 }, f010: function() { return 10 }, f011: function() { return 11 }, f012: function() { return 12 }, f013: function() { return 13 }, f014: function() { return 14 }, f015: function() { return 15 }, f016: function() { return 16 }, f017: function() { return 17 }, f018: function() { return 18 }, f019: function() { return 19 }, f020: function() { return 20 }, f021: function() { return 21 }, f022: function() { return 22 }, f023: function() { return 23 }, f024: function() { return 24 }, f025: function() { return 25 }, f026: function() { return 26 }, f027: function() { return 27 }, f028: function() { return 28 }, f029: function() { return 29 }, f030: function() { return 30 }, f031: function() { return 31 }, f032: function() { return 32 }, f033: function() { return 33 }, f034: function() { return 34 }, f035: function() { return 35 }, f036: function() { return 36 }, f037: function() { return 37 }, f038: function() { return 38 }, f039: function() { return 39 }, f040: function() { return 40 }, f041: function() { return 41 }, f042: function() { return 42 }, f043: function() { return 43 }, f044: function() { return 44 }, f045: function() { return 45 }, f046: function() { return 46 }, f047: function() { return 47 }, f048: function() { return 48 }, f049: function() { return 49 }, f050: function() { return 50 }, f051: function() { return 51 }, f052: function() { return 52 }, f053: function() { return 53 }, f054: function() { return 54 }, f055: function() { return 55 }, f056: function() { return 56 }, f057: function() { return 57 }, f058: function() { return 58 }, f059: function() { return 59 }, f060: function() { return 60 }, f061: function() { return 61 }, f062: function() { return 62 }, f063: function() { return 63 }, f064: function() { return 64 }, f065: function() { return 65 }, f066: function() { return 66 }, f067: function() { return 67 }, f068: function() { return 68 }, f069: function() { return 69 }, f070: function() { return 70 }, f071: function() { return 71 }, f072: function() { return 72 }, f073: function() { return 73 }, f074: function() { return 74 }, f075: function() { return 75 }, f076: function() { return 76 }, f077: function() { return 77 }, f078: function() { return 78 }, f079: function() { return 79 }, f080: function() { return 80 }, f081: function() { return 81 }, f082: function() { return 82 }, f083: function() { return 83 }, f084: function() { return 84 }, f085: function() { return 85 }, f086: function() { return 86 }, f087: function() { return 87 }, f088: function() { return 88 }, f089: function() { return 89 }, f090: function() { return 90 }, f091: function() { return 91 }, f092: function() { return 92 }, f093: function() { return 93 }, f094: function() { return 94 }, f095: function() { return 95 }, f096: function() { return 96 }, f097: function() { return 97 }, f098: function() { return 98 }, f099: function() { return 99 }, f100: function() { return 100 }, f101: function() { return 101 }, f102: function() { return 102 }, f103: function() { return 103 }, f104: function() { return 104 }, f105: function() { return 105 }, f106: function() { return 106 }, f107: function() { return 107 }, f108: function() { return 108 }, f109: function() { return 109 }, f110: function() { return 110 }, f111: function() { return 111 }, f112: function() { return 112 }, f113: function() { return 113 }, f114: function() { return 114 }, f115: function() { return 115 }, f116: function() { return 116 }, f117: function() { return 117 }, f118: function() { return 118 }, f119: function() { return 119 }, f120: function() { return 120 }, f121: function() { return 121 }, f122: function() { return 122 }, f123: function() { return 123 }, f124: function() { return 124 }, f125: function() { return 125 }, f126: function() { return 126 }, f127: function() { return 127 }, f128: function() { return 128 }, f129: function() { return 129 }, f130: function() { return 130 }, f131: function() { return 131 }, f132: function() { return 132 }, f133: function() { return 133 }, f134: function() { return 134 }, f135: function() { return 135 }, f136: function() { return 136 }, f137: function() { return 137 }, f138: function() { return 138 }, f139: function() { return 139 }, f140: function() { return 140 }, f141: function() { return 141 }, f142: function() { return 142 }, f143: function() { return 143 }, f144: function() { return 144 }, f145: function() { return 145 }, f146: function() { return 146 }, f147: function() { return 147 }, f148: function() { return 148 }, f149: function() { return 149 }, f150: function() { return 150 }, f151: function() { return 151 }, f152: function() { return 152 }, f153: function() { return 153 }, f154: function() { return 154 }, f155: function() { return 155 }, f156: function() { return 156 }, f157: function() { return 157 }, f158: function() { return 158 }, f159: function() { return 159 }, f160: function() { return 160 }, f161: function() { return 161 }, f162: function() { return 162 }, f163: function() { return 163 }, f164: function() { return 164 }, f165: function() { return 165 }, f166: function() { return 166 }, f167: function() { return 167 }, f168: function() { return 168 }, f169: function() { return 169 }, f170: function() { return 170 }, f171: function() { return 171 }, f172: function() { return 172 }, f173: function() { return 173 }, f174: function() { return 174 }, f175: function() { return 175 }, f176: function() { return 176 }, f177: function() { return 177 }, f178: function() { return 178 }, f179: function() { return 179 }, f180: function() { return 180 }, f181: function() { return 181 }, f182: function() { return 182 }, f183: function() { return 183 }, f184: function() { return 184 }, f185: function() { return 185 }, f186: function() { return 186 }, f187: function() { return 187 }, f188: function() { return 188 }, f189: function() { return 189 }, f190: function() { return 190 }, f191: function() { return 191 }, f192: function() { return 192 }, f193: function() { return 193 }, f194: function() { return 194 }, f195: function() { return 195 }, f196: function() { return 196 }, f197: function() { return 197 }, f198: function() { return 198 }, f199: function() { return 199 }, f200: function() { return 200 }, f201: function() { return 201 }, f202: function() { return 202 }, f203: function() { return 203 }, f204: function() { return 204 }, f205: function() { return 205 }, f206: function() { return 206 }, f207: function() { return 207 }, f208: function() { return 208 }, f209: function() { return 209 }, f210: function() { return 210 }, f211: function() { return 211 }, f212: function() { return 212 }, f213: function() { return 213 }, f214: function() { return 214 }, f215: function() { return 215 }, f216: function() { return 216 }, f217: function() { return 217 }, f218: function() { return 218 }, f219: function() { return 219 }, f220: function() { return 220 }, f221: function() { return 221 }, f222: function() { return 222 }, f223: function() { return 223 }, f224: function() { return 224 }, f225: function() { return 225 }, f226: function() { return 226 }, f227: function() { return 227 }, f228: function() { return 228 }, f229: function() { return 229 }, f230: function() { return 230 }, f231: function() { return 231 }, f232: function() { return 232 }, f233: function() { return 233 }, f234: function() { return 234 }, f235: function() { return 235 }, f236: function() { return 236 }, f237: function() { return 237 }, f238: function() { return 238 }, f239: function() { return 239 }, f240: function() { return 240 }, f241: function() { return 241 }, f242: function() { return 242 }, f243: function() { return 243 }, f244: function() { return 244 }, f245: function() { return 245 }, f246: function() { return 246 }, f247: function() { return 247 }, f248: function() { return 248 }, f249: function() { return 249 }, f250: function() { return 250 }, f251: function() { return 251 }, f252: function() { return 252 }, f253: function() { return 253 }, f254: function() { return 254 }, f255: function() { return 255 } } var keys = array(obj) var i = 0 var bad_count = 0 var first_bad = "" assert_eq(length(keys), 256, "should have 256 keys") for (i = 0; i < length(keys); i++) { if (!is_function(obj[keys[i]])) { if (first_bad == "") { first_bad = keys[i] } bad_count = bad_count + 1 } } if (bad_count > 0) { fail(text(bad_count) + " of 256 values not functions, first bad: " + first_bad) } }) // Test: object with 300 function values - well past boundary run("object literal 300 function values", function() { var obj = { f000: function() { return 0 }, f001: function() { return 1 }, f002: function() { return 2 }, f003: function() { return 3 }, f004: function() { return 4 }, f005: function() { return 5 }, f006: function() { return 6 }, f007: function() { return 7 }, f008: function() { return 8 }, f009: function() { return 9 }, f010: function() { return 10 }, f011: function() { return 11 }, f012: function() { return 12 }, f013: function() { return 13 }, f014: function() { return 14 }, f015: function() { return 15 }, f016: function() { return 16 }, f017: function() { return 17 }, f018: function() { return 18 }, f019: function() { return 19 }, f020: function() { return 20 }, f021: function() { return 21 }, f022: function() { return 22 }, f023: function() { return 23 }, f024: function() { return 24 }, f025: function() { return 25 }, f026: function() { return 26 }, f027: function() { return 27 }, f028: function() { return 28 }, f029: function() { return 29 }, f030: function() { return 30 }, f031: function() { return 31 }, f032: function() { return 32 }, f033: function() { return 33 }, f034: function() { return 34 }, f035: function() { return 35 }, f036: function() { return 36 }, f037: function() { return 37 }, f038: function() { return 38 }, f039: function() { return 39 }, f040: function() { return 40 }, f041: function() { return 41 }, f042: function() { return 42 }, f043: function() { return 43 }, f044: function() { return 44 }, f045: function() { return 45 }, f046: function() { return 46 }, f047: function() { return 47 }, f048: function() { return 48 }, f049: function() { return 49 }, f050: function() { return 50 }, f051: function() { return 51 }, f052: function() { return 52 }, f053: function() { return 53 }, f054: function() { return 54 }, f055: function() { return 55 }, f056: function() { return 56 }, f057: function() { return 57 }, f058: function() { return 58 }, f059: function() { return 59 }, f060: function() { return 60 }, f061: function() { return 61 }, f062: function() { return 62 }, f063: function() { return 63 }, f064: function() { return 64 }, f065: function() { return 65 }, f066: function() { return 66 }, f067: function() { return 67 }, f068: function() { return 68 }, f069: function() { return 69 }, f070: function() { return 70 }, f071: function() { return 71 }, f072: function() { return 72 }, f073: function() { return 73 }, f074: function() { return 74 }, f075: function() { return 75 }, f076: function() { return 76 }, f077: function() { return 77 }, f078: function() { return 78 }, f079: function() { return 79 }, f080: function() { return 80 }, f081: function() { return 81 }, f082: function() { return 82 }, f083: function() { return 83 }, f084: function() { return 84 }, f085: function() { return 85 }, f086: function() { return 86 }, f087: function() { return 87 }, f088: function() { return 88 }, f089: function() { return 89 }, f090: function() { return 90 }, f091: function() { return 91 }, f092: function() { return 92 }, f093: function() { return 93 }, f094: function() { return 94 }, f095: function() { return 95 }, f096: function() { return 96 }, f097: function() { return 97 }, f098: function() { return 98 }, f099: function() { return 99 }, f100: function() { return 100 }, f101: function() { return 101 }, f102: function() { return 102 }, f103: function() { return 103 }, f104: function() { return 104 }, f105: function() { return 105 }, f106: function() { return 106 }, f107: function() { return 107 }, f108: function() { return 108 }, f109: function() { return 109 }, f110: function() { return 110 }, f111: function() { return 111 }, f112: function() { return 112 }, f113: function() { return 113 }, f114: function() { return 114 }, f115: function() { return 115 }, f116: function() { return 116 }, f117: function() { return 117 }, f118: function() { return 118 }, f119: function() { return 119 }, f120: function() { return 120 }, f121: function() { return 121 }, f122: function() { return 122 }, f123: function() { return 123 }, f124: function() { return 124 }, f125: function() { return 125 }, f126: function() { return 126 }, f127: function() { return 127 }, f128: function() { return 128 }, f129: function() { return 129 }, f130: function() { return 130 }, f131: function() { return 131 }, f132: function() { return 132 }, f133: function() { return 133 }, f134: function() { return 134 }, f135: function() { return 135 }, f136: function() { return 136 }, f137: function() { return 137 }, f138: function() { return 138 }, f139: function() { return 139 }, f140: function() { return 140 }, f141: function() { return 141 }, f142: function() { return 142 }, f143: function() { return 143 }, f144: function() { return 144 }, f145: function() { return 145 }, f146: function() { return 146 }, f147: function() { return 147 }, f148: function() { return 148 }, f149: function() { return 149 }, f150: function() { return 150 }, f151: function() { return 151 }, f152: function() { return 152 }, f153: function() { return 153 }, f154: function() { return 154 }, f155: function() { return 155 }, f156: function() { return 156 }, f157: function() { return 157 }, f158: function() { return 158 }, f159: function() { return 159 }, f160: function() { return 160 }, f161: function() { return 161 }, f162: function() { return 162 }, f163: function() { return 163 }, f164: function() { return 164 }, f165: function() { return 165 }, f166: function() { return 166 }, f167: function() { return 167 }, f168: function() { return 168 }, f169: function() { return 169 }, f170: function() { return 170 }, f171: function() { return 171 }, f172: function() { return 172 }, f173: function() { return 173 }, f174: function() { return 174 }, f175: function() { return 175 }, f176: function() { return 176 }, f177: function() { return 177 }, f178: function() { return 178 }, f179: function() { return 179 }, f180: function() { return 180 }, f181: function() { return 181 }, f182: function() { return 182 }, f183: function() { return 183 }, f184: function() { return 184 }, f185: function() { return 185 }, f186: function() { return 186 }, f187: function() { return 187 }, f188: function() { return 188 }, f189: function() { return 189 }, f190: function() { return 190 }, f191: function() { return 191 }, f192: function() { return 192 }, f193: function() { return 193 }, f194: function() { return 194 }, f195: function() { return 195 }, f196: function() { return 196 }, f197: function() { return 197 }, f198: function() { return 198 }, f199: function() { return 199 }, f200: function() { return 200 }, f201: function() { return 201 }, f202: function() { return 202 }, f203: function() { return 203 }, f204: function() { return 204 }, f205: function() { return 205 }, f206: function() { return 206 }, f207: function() { return 207 }, f208: function() { return 208 }, f209: function() { return 209 }, f210: function() { return 210 }, f211: function() { return 211 }, f212: function() { return 212 }, f213: function() { return 213 }, f214: function() { return 214 }, f215: function() { return 215 }, f216: function() { return 216 }, f217: function() { return 217 }, f218: function() { return 218 }, f219: function() { return 219 }, f220: function() { return 220 }, f221: function() { return 221 }, f222: function() { return 222 }, f223: function() { return 223 }, f224: function() { return 224 }, f225: function() { return 225 }, f226: function() { return 226 }, f227: function() { return 227 }, f228: function() { return 228 }, f229: function() { return 229 }, f230: function() { return 230 }, f231: function() { return 231 }, f232: function() { return 232 }, f233: function() { return 233 }, f234: function() { return 234 }, f235: function() { return 235 }, f236: function() { return 236 }, f237: function() { return 237 }, f238: function() { return 238 }, f239: function() { return 239 }, f240: function() { return 240 }, f241: function() { return 241 }, f242: function() { return 242 }, f243: function() { return 243 }, f244: function() { return 244 }, f245: function() { return 245 }, f246: function() { return 246 }, f247: function() { return 247 }, f248: function() { return 248 }, f249: function() { return 249 }, f250: function() { return 250 }, f251: function() { return 251 }, f252: function() { return 252 }, f253: function() { return 253 }, f254: function() { return 254 }, f255: function() { return 255 }, f256: function() { return 256 }, f257: function() { return 257 }, f258: function() { return 258 }, f259: function() { return 259 }, f260: function() { return 260 }, f261: function() { return 261 }, f262: function() { return 262 }, f263: function() { return 263 }, f264: function() { return 264 }, f265: function() { return 265 }, f266: function() { return 266 }, f267: function() { return 267 }, f268: function() { return 268 }, f269: function() { return 269 }, f270: function() { return 270 }, f271: function() { return 271 }, f272: function() { return 272 }, f273: function() { return 273 }, f274: function() { return 274 }, f275: function() { return 275 }, f276: function() { return 276 }, f277: function() { return 277 }, f278: function() { return 278 }, f279: function() { return 279 }, f280: function() { return 280 }, f281: function() { return 281 }, f282: function() { return 282 }, f283: function() { return 283 }, f284: function() { return 284 }, f285: function() { return 285 }, f286: function() { return 286 }, f287: function() { return 287 }, f288: function() { return 288 }, f289: function() { return 289 }, f290: function() { return 290 }, f291: function() { return 291 }, f292: function() { return 292 }, f293: function() { return 293 }, f294: function() { return 294 }, f295: function() { return 295 }, f296: function() { return 296 }, f297: function() { return 297 }, f298: function() { return 298 }, f299: function() { return 299 } } var keys = array(obj) var i = 0 var bad_count = 0 var first_bad = "" assert_eq(length(keys), 300, "should have 300 keys") for (i = 0; i < length(keys); i++) { if (!is_function(obj[keys[i]])) { if (first_bad == "") { first_bad = keys[i] } bad_count = bad_count + 1 } } if (bad_count > 0) { fail(text(bad_count) + " of 300 values not functions, first bad: " + first_bad) } }) // Test: object built incrementally (not literal) with 300 keys run("object incremental 300 number keys", function() { var obj = {} var i = 0 for (i = 0; i < 300; i++) { obj["k" + text(i)] = i } var keys = array(obj) assert_eq(length(keys), 300, "should have 300 keys") assert_eq(obj.k0, 0, "first key") assert_eq(obj.k299, 299, "last key") }) // Test: object built incrementally with 300 function values run("object incremental 300 function values", function() { var obj = {} var i = 0 var make_fn = function(n) { return function() { return n } } for (i = 0; i < 300; i++) { obj["f" + text(i)] = make_fn(i) } var keys = array(obj) var bad_count = 0 assert_eq(length(keys), 300, "should have 300 keys") for (i = 0; i < length(keys); i++) { if (!is_function(obj[keys[i]])) { bad_count = bad_count + 1 } } assert_eq(bad_count, 0, "all 300 values should be functions") assert_eq(obj.f0(), 0, "first fn") assert_eq(obj.f299(), 299, "last fn") }) // Test: object with very long key names run("object literal long key names", function() { var obj = { this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_01: 1, this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_02: 2, this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_03: 3, this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_04: 4, this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_05: 5, this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_06: 6, this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_07: 7, this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_08: 8, this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_09: 9, this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_10: 10 } var keys = array(obj) assert_eq(length(keys), 10, "should have 10 keys") assert_eq(obj.this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_01, 1, "long key 1") assert_eq(obj.this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_10, 10, "long key 10") }) // Test: return object literal from function with many keys run("object literal 300 function values returned from function", function() { var make_obj = function() { return { f000: function() { return 0 }, f001: function() { return 1 }, f002: function() { return 2 }, f003: function() { return 3 }, f004: function() { return 4 }, f005: function() { return 5 }, f006: function() { return 6 }, f007: function() { return 7 }, f008: function() { return 8 }, f009: function() { return 9 }, f010: function() { return 10 }, f011: function() { return 11 }, f012: function() { return 12 }, f013: function() { return 13 }, f014: function() { return 14 }, f015: function() { return 15 }, f016: function() { return 16 }, f017: function() { return 17 }, f018: function() { return 18 }, f019: function() { return 19 }, f020: function() { return 20 }, f021: function() { return 21 }, f022: function() { return 22 }, f023: function() { return 23 }, f024: function() { return 24 }, f025: function() { return 25 }, f026: function() { return 26 }, f027: function() { return 27 }, f028: function() { return 28 }, f029: function() { return 29 }, f030: function() { return 30 }, f031: function() { return 31 }, f032: function() { return 32 }, f033: function() { return 33 }, f034: function() { return 34 }, f035: function() { return 35 }, f036: function() { return 36 }, f037: function() { return 37 }, f038: function() { return 38 }, f039: function() { return 39 }, f040: function() { return 40 }, f041: function() { return 41 }, f042: function() { return 42 }, f043: function() { return 43 }, f044: function() { return 44 }, f045: function() { return 45 }, f046: function() { return 46 }, f047: function() { return 47 }, f048: function() { return 48 }, f049: function() { return 49 }, f050: function() { return 50 }, f051: function() { return 51 }, f052: function() { return 52 }, f053: function() { return 53 }, f054: function() { return 54 }, f055: function() { return 55 }, f056: function() { return 56 }, f057: function() { return 57 }, f058: function() { return 58 }, f059: function() { return 59 }, f060: function() { return 60 }, f061: function() { return 61 }, f062: function() { return 62 }, f063: function() { return 63 }, f064: function() { return 64 }, f065: function() { return 65 }, f066: function() { return 66 }, f067: function() { return 67 }, f068: function() { return 68 }, f069: function() { return 69 }, f070: function() { return 70 }, f071: function() { return 71 }, f072: function() { return 72 }, f073: function() { return 73 }, f074: function() { return 74 }, f075: function() { return 75 }, f076: function() { return 76 }, f077: function() { return 77 }, f078: function() { return 78 }, f079: function() { return 79 }, f080: function() { return 80 }, f081: function() { return 81 }, f082: function() { return 82 }, f083: function() { return 83 }, f084: function() { return 84 }, f085: function() { return 85 }, f086: function() { return 86 }, f087: function() { return 87 }, f088: function() { return 88 }, f089: function() { return 89 }, f090: function() { return 90 }, f091: function() { return 91 }, f092: function() { return 92 }, f093: function() { return 93 }, f094: function() { return 94 }, f095: function() { return 95 }, f096: function() { return 96 }, f097: function() { return 97 }, f098: function() { return 98 }, f099: function() { return 99 }, f100: function() { return 100 }, f101: function() { return 101 }, f102: function() { return 102 }, f103: function() { return 103 }, f104: function() { return 104 }, f105: function() { return 105 }, f106: function() { return 106 }, f107: function() { return 107 }, f108: function() { return 108 }, f109: function() { return 109 }, f110: function() { return 110 }, f111: function() { return 111 }, f112: function() { return 112 }, f113: function() { return 113 }, f114: function() { return 114 }, f115: function() { return 115 }, f116: function() { return 116 }, f117: function() { return 117 }, f118: function() { return 118 }, f119: function() { return 119 }, f120: function() { return 120 }, f121: function() { return 121 }, f122: function() { return 122 }, f123: function() { return 123 }, f124: function() { return 124 }, f125: function() { return 125 }, f126: function() { return 126 }, f127: function() { return 127 }, f128: function() { return 128 }, f129: function() { return 129 }, f130: function() { return 130 }, f131: function() { return 131 }, f132: function() { return 132 }, f133: function() { return 133 }, f134: function() { return 134 }, f135: function() { return 135 }, f136: function() { return 136 }, f137: function() { return 137 }, f138: function() { return 138 }, f139: function() { return 139 }, f140: function() { return 140 }, f141: function() { return 141 }, f142: function() { return 142 }, f143: function() { return 143 }, f144: function() { return 144 }, f145: function() { return 145 }, f146: function() { return 146 }, f147: function() { return 147 }, f148: function() { return 148 }, f149: function() { return 149 }, f150: function() { return 150 }, f151: function() { return 151 }, f152: function() { return 152 }, f153: function() { return 153 }, f154: function() { return 154 }, f155: function() { return 155 }, f156: function() { return 156 }, f157: function() { return 157 }, f158: function() { return 158 }, f159: function() { return 159 }, f160: function() { return 160 }, f161: function() { return 161 }, f162: function() { return 162 }, f163: function() { return 163 }, f164: function() { return 164 }, f165: function() { return 165 }, f166: function() { return 166 }, f167: function() { return 167 }, f168: function() { return 168 }, f169: function() { return 169 }, f170: function() { return 170 }, f171: function() { return 171 }, f172: function() { return 172 }, f173: function() { return 173 }, f174: function() { return 174 }, f175: function() { return 175 }, f176: function() { return 176 }, f177: function() { return 177 }, f178: function() { return 178 }, f179: function() { return 179 }, f180: function() { return 180 }, f181: function() { return 181 }, f182: function() { return 182 }, f183: function() { return 183 }, f184: function() { return 184 }, f185: function() { return 185 }, f186: function() { return 186 }, f187: function() { return 187 }, f188: function() { return 188 }, f189: function() { return 189 }, f190: function() { return 190 }, f191: function() { return 191 }, f192: function() { return 192 }, f193: function() { return 193 }, f194: function() { return 194 }, f195: function() { return 195 }, f196: function() { return 196 }, f197: function() { return 197 }, f198: function() { return 198 }, f199: function() { return 199 }, f200: function() { return 200 }, f201: function() { return 201 }, f202: function() { return 202 }, f203: function() { return 203 }, f204: function() { return 204 }, f205: function() { return 205 }, f206: function() { return 206 }, f207: function() { return 207 }, f208: function() { return 208 }, f209: function() { return 209 }, f210: function() { return 210 }, f211: function() { return 211 }, f212: function() { return 212 }, f213: function() { return 213 }, f214: function() { return 214 }, f215: function() { return 215 }, f216: function() { return 216 }, f217: function() { return 217 }, f218: function() { return 218 }, f219: function() { return 219 }, f220: function() { return 220 }, f221: function() { return 221 }, f222: function() { return 222 }, f223: function() { return 223 }, f224: function() { return 224 }, f225: function() { return 225 }, f226: function() { return 226 }, f227: function() { return 227 }, f228: function() { return 228 }, f229: function() { return 229 }, f230: function() { return 230 }, f231: function() { return 231 }, f232: function() { return 232 }, f233: function() { return 233 }, f234: function() { return 234 }, f235: function() { return 235 }, f236: function() { return 236 }, f237: function() { return 237 }, f238: function() { return 238 }, f239: function() { return 239 }, f240: function() { return 240 }, f241: function() { return 241 }, f242: function() { return 242 }, f243: function() { return 243 }, f244: function() { return 244 }, f245: function() { return 245 }, f246: function() { return 246 }, f247: function() { return 247 }, f248: function() { return 248 }, f249: function() { return 249 }, f250: function() { return 250 }, f251: function() { return 251 }, f252: function() { return 252 }, f253: function() { return 253 }, f254: function() { return 254 }, f255: function() { return 255 }, f256: function() { return 256 }, f257: function() { return 257 }, f258: function() { return 258 }, f259: function() { return 259 }, f260: function() { return 260 }, f261: function() { return 261 }, f262: function() { return 262 }, f263: function() { return 263 }, f264: function() { return 264 }, f265: function() { return 265 }, f266: function() { return 266 }, f267: function() { return 267 }, f268: function() { return 268 }, f269: function() { return 269 }, f270: function() { return 270 }, f271: function() { return 271 }, f272: function() { return 272 }, f273: function() { return 273 }, f274: function() { return 274 }, f275: function() { return 275 }, f276: function() { return 276 }, f277: function() { return 277 }, f278: function() { return 278 }, f279: function() { return 279 }, f280: function() { return 280 }, f281: function() { return 281 }, f282: function() { return 282 }, f283: function() { return 283 }, f284: function() { return 284 }, f285: function() { return 285 }, f286: function() { return 286 }, f287: function() { return 287 }, f288: function() { return 288 }, f289: function() { return 289 }, f290: function() { return 290 }, f291: function() { return 291 }, f292: function() { return 292 }, f293: function() { return 293 }, f294: function() { return 294 }, f295: function() { return 295 }, f296: function() { return 296 }, f297: function() { return 297 }, f298: function() { return 298 }, f299: function() { return 299 } } } var obj = make_obj() var keys = array(obj) var i = 0 var bad_count = 0 var first_bad = "" assert_eq(length(keys), 300, "should have 300 keys") for (i = 0; i < length(keys); i++) { if (!is_function(obj[keys[i]])) { if (first_bad == "") { first_bad = keys[i] } bad_count = bad_count + 1 } } if (bad_count > 0) { fail(text(bad_count) + " of 300 values not functions, first bad: " + first_bad) } }) // ============================================================================ // NESTED FUNCTION DECLARATIONS // ============================================================================ run("nested named function basic", function() { function inner(x) { return x + 1 } assert_eq(inner(41), 42, "nested named function call") }) run("nested named function used by sibling", function() { function helper(v) { return v * 2 } function caller(v) { return helper(v) + 1 } assert_eq(caller(5), 11, "sibling nested function call") }) run("nested named function in var function", function() { var outer = function() { function inner(a, b) { return a + b } return inner(10, 20) } assert_eq(outer(), 30, "nested function inside var function") }) run("nested named function with closure", function() { var multiplier = 3 function scale(x) { return x * multiplier } assert_eq(scale(7), 21, "nested function closing over outer var") }) run("nested named function recursive", function() { function factorial(n) { if (n <= 1) return 1 return n * factorial(n - 1) } assert_eq(factorial(5), 120, "nested recursive function") }) run("deeply nested named functions", function() { var outer = function() { function mid(x) { function inner(y) { return y + 1 } return inner(x) * 2 } return mid(4) } assert_eq(outer(), 10, "deeply nested named functions") }) run("nested function used after definition", function() { var result = [] function encode_value(v) { if (is_text(v)) return '"' + v + '"' if (is_number(v)) return text(v) return "null" } function quote_key(k) { return k } result[] = quote_key("a") + " = " + encode_value(1) result[] = quote_key("b") + " = " + encode_value("hi") assert_eq(result[0], "a = 1", "nested fn encode number") assert_eq(result[1], 'b = "hi"', "nested fn encode text") }) // ============================================================================ // JSON ENCODING // ============================================================================ def json = use("json") run("json encode flat object", function() { var obj = {} var i = 0 for (i = 0; i < 500; i++) { obj[text(i)] = "value_" + text(i) } var result = json.encode(obj) assert_eq(is_text(result), true, "encode returns text") var decoded = json.decode(result) assert_eq(decoded["0"], "value_0", "first property survives roundtrip") assert_eq(decoded["499"], "value_499", "last property survives roundtrip") }) run("json encode nested objects", function() { var outer = {} var i = 0 var j = 0 var inner = null for (i = 0; i < 50; i++) { inner = {} for (j = 0; j < 20; j++) { inner[text(j)] = i * 20 + j } outer[text(i)] = inner } var result = json.encode(outer) var decoded = json.decode(result) assert_eq(decoded["0"]["0"], 0, "nested first value") assert_eq(decoded["49"]["19"], 999, "nested last value") }) run("json encode array", function() { var arr = [1, "two", true, null, 3.14] var result = json.encode(arr) var decoded = json.decode(result) assert_eq(decoded[0], 1, "array number") assert_eq(decoded[1], "two", "array text") assert_eq(decoded[2], true, "array logical") assert_eq(decoded[3], null, "array null") assert_eq(decoded[4], 3.14, "array float") }) run("json circular reference detected", function() { var circ = {} circ.name = "root" circ.self = circ if (!should_disrupt(function() { json.encode(circ) })) { fail("circular reference not detected") } }) run("json deeply nested circular reference", function() { var a = {} var b = {} var c = {} a.child = b b.child = c c.child = a if (!should_disrupt(function() { json.encode(a) })) { fail("deep circular reference not detected") } }) run("json roundtrip preserves types", function() { var obj = { "num": 42, "txt": "hello", "yes": true, "no": false, "nil": null, "arr": [1, 2, 3], "sub": {"a": 1} } var decoded = json.decode(json.encode(obj)) assert_eq(decoded.num, 42, "number preserved") assert_eq(decoded.txt, "hello", "text preserved") assert_eq(decoded.yes, true, "true preserved") assert_eq(decoded.no, false, "false preserved") assert_eq(decoded.nil, null, "null preserved") assert_eq(decoded.arr[2], 3, "array preserved") assert_eq(decoded.sub.a, 1, "sub-object preserved") }) // ============================================================================ // BLOB - GC HEAP INTEGRATION // ============================================================================ run("blob basic create and stone", function() { var b = blob() if (!is_blob(b)) fail("empty blob is not a blob") b.write_bit(true) b.write_bit(false) b.write_bit(true) stone(b) if (length(b) != 3) fail("blob length should be 3, got " + text(length(b))) if (!b.read_logical(0)) fail("bit 0 should be true") if (b.read_logical(1)) fail("bit 1 should be false") if (!b.read_logical(2)) fail("bit 2 should be true") }) run("blob write_number read_number", function() { var b = blob() b.write_number(3.14) b.write_number(2.718) stone(b) if (length(b) != 128) fail("expected 128 bits") var pi = b.read_number(0) var e = b.read_number(64) if (pi != 3.14) fail("pi read back wrong: " + text(pi)) if (e != 2.718) fail("e read back wrong: " + text(e)) }) run("blob write_fit read_fit", function() { var b = blob() b.write_fit(42, 8) b.write_fit(-7, 8) stone(b) if (b.read_fit(0, 8) != 42) fail("fit 42 failed") if (b.read_fit(8, 8) != -7) fail("fit -7 failed") }) run("blob write_text read_text", function() { var b = blob() b.write_text("hello world") stone(b) var t = b.read_text(0) if (t != "hello world") fail("text roundtrip failed: " + t) }) run("blob from text", function() { var b = blob("abc") stone(b) if (length(b) != 24) fail("blob from text length wrong") var t = text(b) if (t != "abc") fail("blob to text failed: " + t) }) run("blob copy slice", function() { var b = blob() b.write_fit(100, 16) b.write_fit(200, 16) b.write_fit(300, 16) stone(b) var slice = blob(b, 16, 32) stone(slice) if (slice.read_fit(0, 16) != 200) fail("blob slice failed") }) run("blob write_blob", function() { var a = blob() a.write_fit(1, 8) a.write_fit(2, 8) var b = blob() b.write_fit(3, 8) b.write_blob(a) stone(b) if (b.read_fit(0, 8) != 3) fail("first byte wrong") if (b.read_fit(8, 8) != 1) fail("second byte wrong") if (b.read_fit(16, 8) != 2) fail("third byte wrong") }) run("blob write_pad pad?", function() { var b = blob() b.write_fit(7, 4) b.write_pad(8) stone(b) if (length(b) != 8) fail("pad didn't align to 8, got " + text(length(b))) if (!b["pad?"](4, 8)) fail("pad? should be true") }) run("blob w16 w32 wf", function() { var b = blob() b.w16(1000) b.w32(100000) b.wf(1.5) stone(b) if (length(b) != 80) fail("expected 80 bits, got " + text(length(b))) }) run("blob is_data false for blob", function() { var b = blob() if (is_data(b)) fail("blob should not be is_data") }) run("blob text hex format", function() { var b = blob("AB") stone(b) var hex = text(b, "h") if (hex != "4142") fail("hex encoding wrong: " + hex) }) run("blob text binary format", function() { var b = blob() b.write_bit(true) b.write_bit(false) b.write_bit(true) b.write_bit(true) stone(b) var bits = text(b, "b") if (bits != "1011") fail("binary encoding wrong: " + bits) }) run("blob(capacity) preallocates", function() { var b = blob(1024) if (!is_blob(b)) fail("capacity blob not a blob") if (length(b) != 0) fail("capacity blob should start empty") var i = 0 for (i = 0; i < 128; i = i + 1) { b.write_fit(i, 8) } if (length(b) != 1024) fail("after fill length wrong") }) run("blob(length, bool) fill", function() { var b = blob(16, true) stone(b) if (length(b) != 16) fail("filled blob length wrong") var i = 0 for (i = 0; i < 16; i = i + 1) { if (!b.read_logical(i)) fail("bit " + text(i) + " should be true") } var z = blob(8, false) stone(z) for (i = 0; i < 8; i = i + 1) { if (z.read_logical(i)) fail("zero bit " + text(i) + " should be false") } }) // --- GC stress tests: verify blobs survive collection and don't corrupt --- run("gc blob survives collection", function() { var b = blob() var garbage = null var i = 0 var v1 = null var t = null b.write_number(123.456) b.write_text("test data") // Trigger GC pressure by allocating many objects for (i = 0; i < 500; i = i + 1) { garbage = {a: i, b: text(i), c: [i, i+1, i+2]} } // blob should still be valid after GC b.write_number(789.012) stone(b) v1 = b.read_number(0) if (v1 != 123.456) fail("blob data corrupted after gc: " + text(v1)) t = b.read_text(64) if (t != "test data") fail("blob text corrupted after gc: " + t) }) run("gc blob growth across collections", function() { var b = blob() var i = 0 var junk = null var v = null for (i = 0; i < 200; i = i + 1) { b.write_fit(i, 16) junk = [text(i), {v: i}, text(i) + "_end"] } stone(b) for (i = 0; i < 200; i = i + 1) { v = b.read_fit(i * 16, 16) if (v != i) fail("blob growth gc: slot " + text(i) + " = " + text(v)) } }) run("gc many blobs alive simultaneously", function() { var blobs = [] var i = 0 var b = null var trash = null var v1 = null var v2 = null for (i = 0; i < 100; i = i + 1) { b = blob() b.write_fit(i * 7, 16) b.write_fit(i * 13, 16) stone(b) blobs[i] = b } for (i = 0; i < 200; i = i + 1) { trash = {x: text(i), y: [i]} } for (i = 0; i < 100; i = i + 1) { v1 = blobs[i].read_fit(0, 16) v2 = blobs[i].read_fit(16, 16) if (v1 != i * 7) fail("multi blob " + text(i) + " v1 = " + text(v1)) if (v2 != i * 13) fail("multi blob " + text(i) + " v2 = " + text(v2)) } }) run("gc blob not polluting other objects", function() { var results = [] var i = 0 var b = null var obj = null var tmp = null var entry = null var bv = null var bt = null for (i = 0; i < 50; i = i + 1) { b = blob() b.write_fit(i, 16) b.write_text("item" + text(i)) stone(b) obj = {index: i, name: "obj" + text(i)} results[i] = {blob_val: b, obj_val: obj} } for (i = 0; i < 300; i = i + 1) { tmp = {a: text(i), b: [i, i]} } for (i = 0; i < 50; i = i + 1) { entry = results[i] bv = entry.blob_val.read_fit(0, 16) if (bv != i) fail("pollute test blob " + text(i) + " = " + text(bv)) bt = entry.blob_val.read_text(16) if (bt != "item" + text(i)) fail("pollute test text " + text(i)) if (entry.obj_val.index != i) fail("pollute test obj index " + text(i)) if (entry.obj_val.name != "obj" + text(i)) fail("pollute test obj name " + text(i)) } }) run("gc dead blobs are collected", function() { // Verify that dead blobs don't cause leaks by checking heap stays bounded. // We do two phases: allocate a batch, check heap, allocate another, check again. // If blobs leaked, the second batch would cause unbounded growth. var i = 0 var b = null var junk = null var stats1 = null var stats2 = null gc_stats() for (i = 0; i < 200; i = i + 1) { b = blob(1024) b.write_fit(i, 16) junk = {a: text(i), b: [i]} } stats1 = gc_stats() // Second identical batch — should reuse collected space for (i = 0; i < 200; i = i + 1) { b = blob(1024) b.write_fit(i, 16) junk = {a: text(i), b: [i]} } stats2 = gc_stats() // Heap should not have grown more than 4x between phases // (some growth is normal from doubling, but not unbounded) if (stats2.heap_size > stats1.heap_size * 4) { fail("heap grew too much: " + text(stats1.heap_size) + " -> " + text(stats2.heap_size)) } }) run("gc blob write_blob both survive", function() { var src = blob() var i = 0 var v = null var dst = null for (i = 0; i < 100; i = i + 1) { src.write_fit(i, 16) } dst = blob() dst.write_fit(99, 16) dst.write_blob(src) stone(dst) if (dst.read_fit(0, 16) != 99) fail("dst first word wrong") for (i = 0; i < 100; i = i + 1) { v = dst.read_fit(16 + i * 16, 16) if (v != i) fail("write_blob gc: word " + text(i) + " = " + text(v)) } }) run("gc blob read_blob across allocation", function() { var big = blob() var i = 0 var slices = [] var s = null var v = null for (i = 0; i < 200; i = i + 1) { big.write_fit(i, 16) } stone(big) for (i = 0; i < 50; i = i + 1) { s = big.read_blob(i * 16, (i + 1) * 16) stone(s) slices[i] = s } for (i = 0; i < 50; i = i + 1) { v = slices[i].read_fit(0, 16) if (v != i) fail("read_blob gc slice " + text(i) + " = " + text(v)) } }) run("gc blob with random fill", function() { var b = blob(256, function() { return 42 }) stone(b) if (length(b) != 256) fail("random fill length wrong") }) run("gc blob in record values", function() { var rec = {} var i = 0 var b = null var junk = null var v = null for (i = 0; i < 50; i = i + 1) { b = blob() b.write_fit(i * 3, 16) stone(b) rec["k" + text(i)] = b junk = {x: text(i), y: [i, i+1]} } for (i = 0; i < 50; i = i + 1) { v = rec["k" + text(i)].read_fit(0, 16) if (v != i * 3) fail("blob in record " + text(i) + " = " + text(v)) } }) run("gc blob in array elements", function() { var arr = [] var i = 0 var b = null var garbage = null var v = null for (i = 0; i < 100; i = i + 1) { b = blob() b.write_number(i * 1.5) stone(b) arr[i] = b } for (i = 0; i < 500; i = i + 1) { garbage = text(i) + text(i) } for (i = 0; i < 100; i = i + 1) { v = arr[i].read_number(0) if (v != i * 1.5) fail("blob in array " + text(i) + " = " + text(v)) } }) run("gc blob forward pointer chase", function() { var b = blob() var i = 0 var junk = null var v = null for (i = 0; i < 500; i = i + 1) { b.write_fit(i % 128, 8) } for (i = 0; i < 300; i = i + 1) { junk = {a: [i, text(i)], b: text(i) + "!"} } stone(b) for (i = 0; i < 500; i = i + 1) { v = b.read_fit(i * 8, 8) if (v != i % 128) fail("fwd chase " + text(i) + " = " + text(v)) } }) // ============================================================================ // GC CLOSURE FRAME SHORTENING // Verify that closure-captured variables survive GC collection, particularly // when the streamline optimizer remaps close slots to different positions. // ============================================================================ var force_gc = function() { var _g = 0 var _gx = null for (_g = 0; _g < 200; _g = _g + 1) { _gx = {a: _g, b: [1, 2, 3], c: "garbage"} } } run("gc closure basic - captured function survives gc", function() { var make = function() { function helper() { return 42 } var obj = { call() { return helper() } } return obj } var obj = make() force_gc() assert_eq(obj.call(), 42, "captured function should survive GC") }) run("gc closure - captured variable survives gc", function() { var make = function() { var val = 99 var obj = { get() { return val } } return obj } var obj = make() force_gc() assert_eq(obj.get(), 99, "captured variable should survive GC") }) run("gc closure - multiple captured variables survive gc", function() { var make = function() { var a = 10 var b = 20 var c = 30 var obj = { sum() { return a + b + c } } return obj } var obj = make() force_gc() assert_eq(obj.sum(), 60, "all captured vars should survive GC") }) run("gc closure - captured function and var survive gc", function() { var make = function() { function double(x) { return x * 2 } var base = 5 var obj = { compute() { return double(base) } } return obj } var obj = make() force_gc() assert_eq(obj.compute(), 10, "captured fn and var should survive GC") }) run("gc closure - nested closure chain survives gc", function() { var outer = function() { var x = 7 var mid = function() { var y = 3 var inner = function() { return x + y } return inner } return mid() } var fn = outer() force_gc() assert_eq(fn(), 10, "nested closure chain should survive GC") }) run("gc closure - multiple methods share captured frame", function() { var make = function() { var count = 0 function inc() { count = count + 1 } function get() { return count } var obj = { increment() { inc() }, value() { return get() } } return obj } var obj = make() obj.increment() obj.increment() force_gc() obj.increment() assert_eq(obj.value(), 3, "shared closure frame should survive GC") }) run("gc closure - closure survives repeated gc cycles", function() { var make = function() { var val = 123 var obj = { get() { return val } } return obj } var obj = make() force_gc() force_gc() force_gc() assert_eq(obj.get(), 123, "closure should survive repeated GC cycles") }) run("gc closure - object literal method with temp slot reuse", function() { var make = function() { function helper() { return "ok" } var temp = [1, 2, 3] var unused = {x: temp} var obj = { call() { return helper() } } return obj } var obj = make() force_gc() assert_eq(obj.call(), "ok", "closure should work after temp slots discarded") }) run("gc closure - closure array survives gc", function() { var make = function() { var items = [10, 20, 30] var obj = { first() { return items[0] }, last() { return items[2] } } return obj } var obj = make() force_gc() assert_eq(obj.first(), 10, "captured array first element") assert_eq(obj.last(), 30, "captured array last element") }) run("gc closure - factory pattern survives gc", function() { var factory = function(name) { function greet() { return "hello " + name } var obj = { say() { return greet() } } return obj } var a = factory("alice") var b = factory("bob") force_gc() assert_eq(a.say(), "hello alice", "first factory closure") assert_eq(b.say(), "hello bob", "second factory closure") }) // ============================================================================ // SUMMARY // ============================================================================ log.test(text(passed) + " passed, " + text(failed) + " failed out of " + text(passed + failed)) var _j = 0 if (failed > 0) { log.test("") for (_j = 0; _j < failed; _j++) { log.error(" FAIL " + error_names[_j] + ": " + error_reasons[_j]) } }