This commit is contained in:
2026-02-02 10:38:48 -06:00
parent ce74f726dd
commit a04bebd0d7
4 changed files with 664 additions and 49 deletions

View File

@@ -19,6 +19,9 @@
#include <unistd.h>
#include <sys/stat.h>
/* Test suite declaration */
int run_c_test_suite(JSContext *ctx);
cell_rt *root_cell = NULL;
static char *core_path = NULL;
@@ -196,12 +199,43 @@ static void signal_handler(int sig)
}
#endif
if (!str) return;
exit_handler();
}
/* Run the C test suite with minimal runtime setup */
static int run_test_suite(void)
{
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
return 1;
}
JSContext *ctx = JS_NewContextRaw(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
return 1;
}
JS_AddIntrinsicBaseObjects(ctx);
int result = run_c_test_suite(ctx);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return result;
}
int cell_init(int argc, char **argv)
{
/* Check for --test flag to run C test suite */
if (argc >= 2 && strcmp(argv[1], "--test") == 0) {
return run_test_suite();
}
int script_start = 1;
/* Find the cell shop at ~/.cell */

View File

@@ -2791,18 +2791,6 @@ static JSText *pretext_concat_value (JSContext *ctx, JSText *s, JSValue v) {
return s;
}
static JSText *pretext_concat_value_free (JSContext *ctx, JSText *s, JSValue v) {
JSText *p;
if (unlikely (JS_VALUE_GET_TAG (v) != JS_TAG_STRING)) {
v = JS_ToString (ctx, v);
if (JS_IsException (v)) return NULL;
}
p = JS_VALUE_GET_STRING (v);
s = pretext_concat (ctx, s, p, 0, (uint32_t)JSText_len (p));
return s;
}
/* Finalize a pretext into an immutable JSValue string */
static JSValue pretext_end (JSContext *ctx, JSText *s) {
if (!s) return JS_EXCEPTION;
@@ -2889,23 +2877,23 @@ JSValue JS_NewStringLen (JSContext *ctx, const char *buf, size_t buf_len) {
static JSValue JS_ConcatString3 (JSContext *ctx, const char *str1, JSValue str2, const char *str3) {
JSText *b;
int len1, len3;
JSText *p;
int len1, len3, str2_len;
if (unlikely (JS_VALUE_GET_TAG (str2) != JS_TAG_STRING)) {
if (!JS_IsText (str2)) {
str2 = JS_ToString (ctx, str2);
if (JS_IsException (str2)) goto fail;
}
p = JS_VALUE_GET_STRING (str2);
str2_len = js_string_value_len (str2);
len1 = strlen (str1);
len3 = strlen (str3);
b = pretext_init (ctx, len1 + (int)JSText_len (p) + len3);
b = pretext_init (ctx, len1 + str2_len + len3);
if (!b) goto fail;
b = pretext_write8 (ctx, b, (const uint8_t *)str1, len1);
if (!b) goto fail;
b = pretext_concat (ctx, b, p, 0, (uint32_t)JSText_len (p));
b = pretext_concat_value (ctx, b, str2);
if (!b) goto fail;
b = pretext_write8 (ctx, b, (const uint8_t *)str3, len3);
if (!b) goto fail;
@@ -2922,19 +2910,32 @@ fail:
* sequences */
const char *JS_ToCStringLen2 (JSContext *ctx, size_t *plen, JSValue val1, BOOL cesu8) {
JSValue val;
JSText *str;
char *q, *ret;
size_t size;
int i, len;
if (JS_VALUE_GET_TAG (val1) != JS_TAG_STRING) {
if (!JS_IsText (val1)) {
val = JS_ToString (ctx, val1);
if (JS_IsException (val)) goto fail;
} else {
val = val1;
}
str = JS_VALUE_GET_STRING (val);
/* Handle immediate ASCII strings */
if (MIST_IsImmediateASCII (val)) {
len = MIST_GetImmediateASCIILen (val);
ret = js_malloc (ctx, len + 1);
if (!ret) goto fail;
for (i = 0; i < len; i++) {
ret[i] = MIST_GetImmediateASCIIChar (val, i);
}
ret[len] = '\0';
if (plen) *plen = len;
return ret;
}
/* Handle heap strings (JSText) */
JSText *str = JS_VALUE_GET_STRING (val);
len = (int)JSText_len (str);
/* Calculate UTF-8 size */
@@ -4350,9 +4351,8 @@ static BOOL js_object_has_name (JSContext *ctx, JSValue obj) {
int slot = rec_find_slot (rec, name_key);
if (slot <= 0) return FALSE;
JSValue val = rec->slots[slot].val;
if (JS_VALUE_GET_TAG (val) != JS_TAG_STRING) return TRUE;
JSText *p = JS_VALUE_GET_STRING (val);
return (JSText_len (p) != 0);
if (!JS_IsText (val)) return TRUE; /* has name but it's not a string = truthy */
return (js_string_value_len (val) != 0);
}
static int JS_DefineObjectName (JSContext *ctx, JSValue obj, JSValue name) {
@@ -4942,7 +4942,7 @@ int JS_ToFloat64 (JSContext *ctx, double *pres, JSValue val) {
uint32_t tag;
tag = JS_VALUE_GET_TAG (val);
if (tag <= JS_TAG_NULL) {
if (tag == JS_TAG_INT) {
*pres = JS_VALUE_GET_INT (val);
return 0;
} else if (JS_TAG_IS_FLOAT64 (tag)) {
@@ -5344,16 +5344,8 @@ static void js_dump_char (JSPrintValueState *s, int c, int sep) {
}
static void js_print_string_rec (JSPrintValueState *s, JSValue val, int sep, uint32_t pos) {
if (JS_VALUE_GET_TAG (val) == JS_TAG_STRING) {
JSText *p = JS_VALUE_GET_STRING (val);
uint32_t i, len;
if (pos < s->options.max_string_length) {
len = min_uint32 ((uint32_t)JSText_len (p), s->options.max_string_length - pos);
for (i = 0; i < len; i++) {
js_dump_char (s, string_get (p, i), sep);
}
}
} else if (MIST_IsImmediateASCII (val)) {
if (MIST_IsImmediateASCII (val)) {
/* Immediate ASCII string */
int len = MIST_GetImmediateASCIILen (val);
if (pos < s->options.max_string_length) {
uint32_t i, l;
@@ -5362,6 +5354,16 @@ static void js_print_string_rec (JSPrintValueState *s, JSValue val, int sep, uin
js_dump_char (s, MIST_GetImmediateASCIIChar (val, i), sep);
}
}
} else if (JS_IsPtr (val) && objhdr_type (*(objhdr_t *)JS_VALUE_GET_PTR (val)) == OBJ_TEXT) {
/* Heap text (JSText) */
JSText *p = (JSText *)JS_VALUE_GET_PTR (val);
uint32_t i, len;
if (pos < s->options.max_string_length) {
len = min_uint32 ((uint32_t)JSText_len (p), s->options.max_string_length - pos);
for (i = 0; i < len; i++) {
js_dump_char (s, string_get (p, i), sep);
}
}
} else {
js_printf (s, "<invalid string tag %d>", (int)JS_VALUE_GET_TAG (val));
}
@@ -18944,9 +18946,8 @@ static JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern,
JSRecord *p;
JSRegExp *re;
/* sanity check */
if (JS_VALUE_GET_TAG (bc) != JS_TAG_STRING
|| JS_VALUE_GET_TAG (pattern) != JS_TAG_STRING) {
/* sanity check - need heap strings for pattern and bytecode */
if (!JS_IsText (bc) || !JS_IsText (pattern)) {
JS_ThrowTypeError (ctx, "string expected");
fail:
return JS_EXCEPTION;
@@ -18959,8 +18960,9 @@ static JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern,
re = js_malloc (ctx, sizeof(JSRegExp));
if (!re) goto fail;
REC_SET_OPAQUE(p, re);
re->pattern = JS_VALUE_GET_STRING (pattern);
re->bytecode = JS_VALUE_GET_STRING (bc);
/* Store pattern and bytecode - need to handle both immediate and heap strings */
re->pattern = MIST_IsImmediateASCII (pattern) ? NULL : (JSText *)JS_VALUE_GET_PTR (pattern);
re->bytecode = MIST_IsImmediateASCII (bc) ? NULL : (JSText *)JS_VALUE_GET_PTR (bc);
{
JSValue key = JS_KEY_STR (ctx, "lastIndex");
JS_SetPropertyInternal (ctx, obj, key, JS_NewInt32 (ctx, 0));
@@ -19873,7 +19875,7 @@ concat_primitive:
case JS_TAG_BOOL:
case JS_TAG_NULL:
concat_value:
jsc->b = pretext_concat_value_free (ctx, jsc->b, val);
jsc->b = pretext_concat_value (ctx, jsc->b, val);
return jsc->b ? 0 : -1;
default:
return 0;
@@ -20928,7 +20930,7 @@ static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue
JSValue item_str = JS_ToString (ctx, item);
if (JS_IsException (item_str)) goto array_fail;
b = pretext_concat_value_free (ctx, b, item_str);
b = pretext_concat_value (ctx, b, item_str);
if (!b) goto array_fail;
}
@@ -21120,7 +21122,7 @@ static JSValue js_cell_text_codepoint (JSContext *ctx, JSValue this_val, int arg
static JSText *pt_concat_value_to_string_free (JSContext *ctx, JSText *b, JSValue v) {
JSValue s = JS_ToString (ctx, v);
if (JS_IsException (s)) return NULL;
b = pretext_concat_value_free (ctx, b, s);
b = pretext_concat_value (ctx, b, s);
return b;
}
@@ -21239,7 +21241,7 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc,
if (boundary < len) {
JSValue ch = js_sub_string (ctx, sp, boundary, boundary + 1);
if (JS_IsException (ch)) goto fail_str_target;
b = pretext_concat_value_free (ctx, b, ch);
b = pretext_concat_value (ctx, b, ch);
if (!b) goto fail_str_target;
}
}
@@ -21264,7 +21266,7 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc,
if (found > pos) {
JSValue sub = js_sub_string (ctx, sp, pos, found);
if (JS_IsException (sub)) goto fail_str_target;
b = pretext_concat_value_free (ctx, b, sub);
b = pretext_concat_value (ctx, b, sub);
if (!b) goto fail_str_target;
}
@@ -21288,7 +21290,7 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc,
if (pos < len) {
JSValue sub = js_sub_string (ctx, sp, pos, len);
if (JS_IsException (sub)) goto fail_str_target;
b = pretext_concat_value_free (ctx, b, sub);
b = pretext_concat_value (ctx, b, sub);
if (!b) goto fail_str_target;
}
@@ -21360,7 +21362,7 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc,
if (found > pos) {
JSValue prefix = js_sub_string (ctx, sp, pos, found);
if (JS_IsException (prefix)) goto fail_rx;
b = pretext_concat_value_free (ctx, b, prefix);
b = pretext_concat_value (ctx, b, prefix);
if (!b) goto fail_rx;
}
@@ -21387,7 +21389,7 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc,
if (pos < len) {
JSValue tail = js_sub_string (ctx, sp, pos, len);
if (JS_IsException (tail)) goto fail_rx;
b = pretext_concat_value_free (ctx, b, tail);
b = pretext_concat_value (ctx, b, tail);
if (!b) goto fail_rx;
}

578
source/suite.c Normal file
View File

@@ -0,0 +1,578 @@
/*
* C-level test suite for cell runtime
* Tests core object representation using C API exclusively
* Bypasses parser/bytecode to verify low-level operations
*/
#include "quickjs.h"
#include <stdio.h>
#include <string.h>
#include <math.h>
static int tests_passed = 0;
static int tests_failed = 0;
#define TEST(name) static int test_##name(JSContext *ctx)
#define RUN_TEST(name) do { \
printf(" %-50s ", #name); \
if (test_##name(ctx)) { \
printf("PASS\n"); \
tests_passed++; \
} else { \
printf("FAIL\n"); \
tests_failed++; \
} \
} while(0)
#define ASSERT(cond) do { if (!(cond)) { printf("[line %d] ", __LINE__); return 0; } } while(0)
#define ASSERT_INT(v, expected) do { \
if (!JS_IsInt(v)) { printf("[line %d: not int] ", __LINE__); return 0; } \
if (JS_VALUE_GET_INT(v) != (expected)) { printf("[line %d: %d != %d] ", __LINE__, JS_VALUE_GET_INT(v), expected); return 0; } \
} while(0)
/* ============================================================================
NUMBER TESTS
============================================================================ */
TEST(int_creation) {
JSValue v = JS_NewInt32(ctx, 42);
ASSERT(JS_IsInt(v));
ASSERT(JS_VALUE_GET_INT(v) == 42);
return 1;
}
TEST(int_zero) {
JSValue v = JS_NewInt32(ctx, 0);
ASSERT(JS_IsInt(v));
ASSERT(JS_VALUE_GET_INT(v) == 0);
return 1;
}
TEST(int_negative) {
JSValue v = JS_NewInt32(ctx, -100);
ASSERT(JS_IsInt(v));
ASSERT(JS_VALUE_GET_INT(v) == -100);
return 1;
}
TEST(int_max) {
JSValue v = JS_NewInt32(ctx, INT32_MAX);
ASSERT(JS_IsInt(v));
ASSERT(JS_VALUE_GET_INT(v) == INT32_MAX);
return 1;
}
TEST(int_min) {
JSValue v = JS_NewInt32(ctx, INT32_MIN);
ASSERT(JS_IsInt(v));
ASSERT(JS_VALUE_GET_INT(v) == INT32_MIN);
return 1;
}
TEST(float_creation) {
JSValue v = JS_NewFloat64(ctx, 3.14159);
ASSERT(JS_IsNumber(v));
double d;
JS_ToFloat64(ctx, &d, v);
ASSERT(fabs(d - 3.14159) < 0.00001);
return 1;
}
TEST(float_zero_becomes_int) {
JSValue v = JS_NewFloat64(ctx, 0.0);
ASSERT(JS_IsInt(v));
ASSERT(JS_VALUE_GET_INT(v) == 0);
return 1;
}
TEST(float_whole_becomes_int) {
JSValue v = JS_NewFloat64(ctx, 42.0);
ASSERT(JS_IsInt(v));
ASSERT(JS_VALUE_GET_INT(v) == 42);
return 1;
}
TEST(float_negative) {
JSValue v = JS_NewFloat64(ctx, -2.5);
ASSERT(JS_IsNumber(v));
double d;
JS_ToFloat64(ctx, &d, v);
ASSERT(fabs(d - (-2.5)) < 0.00001);
return 1;
}
/* ============================================================================
BOOLEAN TESTS
============================================================================ */
TEST(bool_true) {
JSValue v = JS_TRUE;
ASSERT(JS_IsBool(v));
ASSERT(JS_VALUE_GET_BOOL(v) == 1);
return 1;
}
TEST(bool_false) {
JSValue v = JS_FALSE;
ASSERT(JS_IsBool(v));
ASSERT(JS_VALUE_GET_BOOL(v) == 0);
return 1;
}
TEST(bool_new_true) {
JSValue v = JS_NewBool(ctx, 1);
ASSERT(JS_IsBool(v));
ASSERT(JS_VALUE_GET_BOOL(v) == 1);
return 1;
}
TEST(bool_new_false) {
JSValue v = JS_NewBool(ctx, 0);
ASSERT(JS_IsBool(v));
ASSERT(JS_VALUE_GET_BOOL(v) == 0);
return 1;
}
/* ============================================================================
NULL TESTS
============================================================================ */
TEST(null_value) {
JSValue v = JS_NULL;
ASSERT(JS_IsNull(v));
ASSERT(!JS_IsBool(v));
ASSERT(!JS_IsNumber(v));
return 1;
}
/* ============================================================================
STRING TESTS - IMMEDIATE ASCII
============================================================================ */
TEST(string_immediate_short) {
JSValue v = JS_NewString(ctx, "hello");
ASSERT(JS_IsText(v));
ASSERT(MIST_IsImmediateASCII(v));
ASSERT(MIST_GetImmediateASCIILen(v) == 5);
ASSERT(MIST_GetImmediateASCIIChar(v, 0) == 'h');
ASSERT(MIST_GetImmediateASCIIChar(v, 4) == 'o');
return 1;
}
TEST(string_immediate_empty) {
JSValue v = JS_NewString(ctx, "");
ASSERT(JS_IsText(v));
ASSERT(MIST_IsImmediateASCII(v));
ASSERT(MIST_GetImmediateASCIILen(v) == 0);
return 1;
}
TEST(string_immediate_max) {
JSValue v = JS_NewString(ctx, "1234567"); /* 7 chars = max immediate */
ASSERT(JS_IsText(v));
ASSERT(MIST_IsImmediateASCII(v));
ASSERT(MIST_GetImmediateASCIILen(v) == 7);
return 1;
}
TEST(string_heap_long) {
JSValue v = JS_NewString(ctx, "12345678"); /* 8 chars = heap allocated */
ASSERT(JS_IsText(v));
ASSERT(!MIST_IsImmediateASCII(v));
return 1;
}
TEST(string_to_cstring) {
JSValue v = JS_NewString(ctx, "test");
const char *str = JS_ToCString(ctx, v);
ASSERT(str != NULL);
ASSERT(strcmp(str, "test") == 0);
JS_FreeCString(ctx, str);
return 1;
}
TEST(string_heap_to_cstring) {
JSValue v = JS_NewString(ctx, "this is a longer string");
const char *str = JS_ToCString(ctx, v);
ASSERT(str != NULL);
ASSERT(strcmp(str, "this is a longer string") == 0);
JS_FreeCString(ctx, str);
return 1;
}
/* ============================================================================
OBJECT/RECORD TESTS
============================================================================ */
TEST(object_create) {
JSValue obj = JS_NewObject(ctx);
ASSERT(JS_IsObject(obj));
ASSERT(JS_IsRecord(obj));
return 1;
}
TEST(object_set_get_property) {
JSValue obj = JS_NewObject(ctx);
int r = JS_SetPropertyStr(ctx, obj, "foo", JS_NewInt32(ctx, 42));
ASSERT(r >= 0);
JSValue val = JS_GetPropertyStr(ctx, obj, "foo");
ASSERT_INT(val, 42);
return 1;
}
TEST(object_multiple_properties) {
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "a", JS_NewInt32(ctx, 1));
JS_SetPropertyStr(ctx, obj, "b", JS_NewInt32(ctx, 2));
JS_SetPropertyStr(ctx, obj, "c", JS_NewInt32(ctx, 3));
JSValue a = JS_GetPropertyStr(ctx, obj, "a");
JSValue b = JS_GetPropertyStr(ctx, obj, "b");
JSValue c = JS_GetPropertyStr(ctx, obj, "c");
ASSERT_INT(a, 1);
ASSERT_INT(b, 2);
ASSERT_INT(c, 3);
return 1;
}
TEST(object_overwrite_property) {
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, 10));
JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, 20));
JSValue x = JS_GetPropertyStr(ctx, obj, "x");
ASSERT_INT(x, 20);
return 1;
}
TEST(object_missing_property_is_null) {
JSValue obj = JS_NewObject(ctx);
JSValue val = JS_GetPropertyStr(ctx, obj, "nonexistent");
ASSERT(JS_IsNull(val));
return 1;
}
TEST(object_string_property) {
JSValue obj = JS_NewObject(ctx);
JSValue str = JS_NewString(ctx, "hello");
JS_SetPropertyStr(ctx, obj, "msg", str);
JSValue val = JS_GetPropertyStr(ctx, obj, "msg");
ASSERT(JS_IsText(val));
const char *s = JS_ToCString(ctx, val);
ASSERT(strcmp(s, "hello") == 0);
JS_FreeCString(ctx, s);
return 1;
}
TEST(object_nested) {
JSValue outer = JS_NewObject(ctx);
JSValue inner = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, inner, "value", JS_NewInt32(ctx, 99));
JS_SetPropertyStr(ctx, outer, "inner", inner);
JSValue gotInner = JS_GetPropertyStr(ctx, outer, "inner");
ASSERT(JS_IsRecord(gotInner));
JSValue val = JS_GetPropertyStr(ctx, gotInner, "value");
ASSERT_INT(val, 99);
return 1;
}
TEST(object_many_properties_resize) {
JSValue obj = JS_NewObject(ctx);
/* Add many properties to trigger resize */
for (int i = 0; i < 100; i++) {
char key[16];
snprintf(key, sizeof(key), "prop%d", i);
JS_SetPropertyStr(ctx, obj, key, JS_NewInt32(ctx, i * 10));
}
/* Verify all properties */
for (int i = 0; i < 100; i++) {
char key[16];
snprintf(key, sizeof(key), "prop%d", i);
JSValue val = JS_GetPropertyStr(ctx, obj, key);
ASSERT_INT(val, i * 10);
}
return 1;
}
/* ============================================================================
ARRAY TESTS
============================================================================ */
TEST(array_create) {
JSValue arr = JS_NewArray(ctx);
ASSERT(JS_IsObject(arr));
ASSERT(JS_IsArray(arr));
return 1;
}
TEST(array_push_and_length) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 10));
JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 20));
JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 30));
int64_t len;
JS_GetLength(ctx, arr, &len);
ASSERT(len == 3);
return 1;
}
TEST(array_get_by_index) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 100));
JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 200));
JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 300));
JSValue v0 = JS_GetPropertyUint32(ctx, arr, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, arr, 1);
JSValue v2 = JS_GetPropertyUint32(ctx, arr, 2);
ASSERT_INT(v0, 100);
ASSERT_INT(v1, 200);
ASSERT_INT(v2, 300);
return 1;
}
TEST(array_set_by_index) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 0));
JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 0));
JS_SetPropertyUint32(ctx, arr, 0, JS_NewInt32(ctx, 55));
JS_SetPropertyUint32(ctx, arr, 1, JS_NewInt32(ctx, 66));
JSValue v0 = JS_GetPropertyUint32(ctx, arr, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, arr, 1);
ASSERT_INT(v0, 55);
ASSERT_INT(v1, 66);
return 1;
}
TEST(array_pop) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 1));
JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 2));
JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 3));
JSValue popped = JS_ArrayPop(ctx, arr);
ASSERT_INT(popped, 3);
int64_t len;
JS_GetLength(ctx, arr, &len);
ASSERT(len == 2);
return 1;
}
TEST(array_out_of_bounds_is_null) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 1));
JSValue val = JS_GetPropertyUint32(ctx, arr, 999);
ASSERT(JS_IsNull(val));
return 1;
}
TEST(array_mixed_types) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 42));
JS_ArrayPush(ctx, arr, JS_NewString(ctx, "hello"));
JS_ArrayPush(ctx, arr, JS_TRUE);
JS_ArrayPush(ctx, arr, JS_NULL);
JSValue v0 = JS_GetPropertyUint32(ctx, arr, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, arr, 1);
JSValue v2 = JS_GetPropertyUint32(ctx, arr, 2);
JSValue v3 = JS_GetPropertyUint32(ctx, arr, 3);
ASSERT(JS_IsInt(v0));
ASSERT(JS_IsText(v1));
ASSERT(JS_IsBool(v2));
ASSERT(JS_IsNull(v3));
return 1;
}
TEST(array_many_elements_resize) {
JSValue arr = JS_NewArray(ctx);
for (int i = 0; i < 1000; i++) {
JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, i));
}
int64_t len;
JS_GetLength(ctx, arr, &len);
ASSERT(len == 1000);
/* Verify some values */
JSValue v0 = JS_GetPropertyUint32(ctx, arr, 0);
JSValue v500 = JS_GetPropertyUint32(ctx, arr, 500);
JSValue v999 = JS_GetPropertyUint32(ctx, arr, 999);
ASSERT_INT(v0, 0);
ASSERT_INT(v500, 500);
ASSERT_INT(v999, 999);
return 1;
}
/* ============================================================================
TYPE CHECK TESTS
============================================================================ */
TEST(type_checks) {
JSValue num = JS_NewInt32(ctx, 42);
JSValue flt = JS_NewFloat64(ctx, 3.14);
JSValue str = JS_NewString(ctx, "test");
JSValue obj = JS_NewObject(ctx);
JSValue arr = JS_NewArray(ctx);
JSValue boo = JS_TRUE;
JSValue nul = JS_NULL;
ASSERT(JS_IsNumber(num));
ASSERT(JS_IsNumber(flt));
ASSERT(JS_IsText(str));
ASSERT(JS_IsRecord(obj));
ASSERT(JS_IsArray(arr));
ASSERT(JS_IsBool(boo));
ASSERT(JS_IsNull(nul));
ASSERT(!JS_IsText(num));
ASSERT(!JS_IsNumber(str));
ASSERT(!JS_IsArray(obj));
ASSERT(!JS_IsRecord(arr));
return 1;
}
/* ============================================================================
EQUALITY TESTS
============================================================================ */
TEST(strict_eq_ints) {
JSValue a = JS_NewInt32(ctx, 42);
JSValue b = JS_NewInt32(ctx, 42);
JSValue c = JS_NewInt32(ctx, 43);
ASSERT(JS_StrictEq(ctx, a, b));
ASSERT(!JS_StrictEq(ctx, a, c));
return 1;
}
TEST(strict_eq_strings) {
JSValue a = JS_NewString(ctx, "hello");
JSValue b = JS_NewString(ctx, "hello");
JSValue c = JS_NewString(ctx, "world");
ASSERT(JS_StrictEq(ctx, a, b));
ASSERT(!JS_StrictEq(ctx, a, c));
return 1;
}
TEST(strict_eq_null) {
JSValue a = JS_NULL;
JSValue b = JS_NULL;
JSValue c = JS_NewInt32(ctx, 0);
ASSERT(JS_StrictEq(ctx, a, b));
ASSERT(!JS_StrictEq(ctx, a, c));
return 1;
}
TEST(strict_eq_bool) {
ASSERT(JS_StrictEq(ctx, JS_TRUE, JS_TRUE));
ASSERT(JS_StrictEq(ctx, JS_FALSE, JS_FALSE));
ASSERT(!JS_StrictEq(ctx, JS_TRUE, JS_FALSE));
return 1;
}
/* ============================================================================
GLOBAL OBJECT TEST
============================================================================ */
TEST(global_object) {
JSValue global = JS_GetGlobalObject(ctx);
ASSERT(JS_IsRecord(global));
/* Set something on global */
JS_SetPropertyStr(ctx, global, "testGlobal", JS_NewInt32(ctx, 777));
JSValue val = JS_GetPropertyStr(ctx, global, "testGlobal");
ASSERT_INT(val, 777);
return 1;
}
/* ============================================================================
MAIN TEST RUNNER
============================================================================ */
int run_c_test_suite(JSContext *ctx)
{
printf("\n=== Cell Runtime C Test Suite ===\n\n");
printf("Numbers:\n");
RUN_TEST(int_creation);
RUN_TEST(int_zero);
RUN_TEST(int_negative);
RUN_TEST(int_max);
RUN_TEST(int_min);
RUN_TEST(float_creation);
RUN_TEST(float_zero_becomes_int);
RUN_TEST(float_whole_becomes_int);
RUN_TEST(float_negative);
printf("\nBooleans:\n");
RUN_TEST(bool_true);
RUN_TEST(bool_false);
RUN_TEST(bool_new_true);
RUN_TEST(bool_new_false);
printf("\nNull:\n");
RUN_TEST(null_value);
printf("\nStrings:\n");
RUN_TEST(string_immediate_short);
RUN_TEST(string_immediate_empty);
RUN_TEST(string_immediate_max);
RUN_TEST(string_heap_long);
RUN_TEST(string_to_cstring);
RUN_TEST(string_heap_to_cstring);
printf("\nObjects/Records:\n");
RUN_TEST(object_create);
RUN_TEST(object_set_get_property);
RUN_TEST(object_multiple_properties);
RUN_TEST(object_overwrite_property);
RUN_TEST(object_missing_property_is_null);
RUN_TEST(object_string_property);
RUN_TEST(object_nested);
RUN_TEST(object_many_properties_resize);
printf("\nArrays:\n");
RUN_TEST(array_create);
RUN_TEST(array_push_and_length);
RUN_TEST(array_get_by_index);
RUN_TEST(array_set_by_index);
RUN_TEST(array_pop);
RUN_TEST(array_out_of_bounds_is_null);
RUN_TEST(array_mixed_types);
RUN_TEST(array_many_elements_resize);
printf("\nType Checks:\n");
RUN_TEST(type_checks);
printf("\nEquality:\n");
RUN_TEST(strict_eq_ints);
RUN_TEST(strict_eq_strings);
RUN_TEST(strict_eq_null);
RUN_TEST(strict_eq_bool);
printf("\nGlobal Object:\n");
RUN_TEST(global_object);
printf("\n=================================\n");
printf("Results: %d passed, %d failed\n", tests_passed, tests_failed);
printf("=================================\n\n");
return tests_failed == 0 ? 0 : 1;
}