@mikrojs/firmware 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +30 -0
- package/bin/idf.py +7 -0
- package/chips.json +3 -0
- package/cmake.js +9 -0
- package/components/mikrojs/CMakeLists.txt +187 -0
- package/components/mikrojs/Kconfig +55 -0
- package/components/mikrojs/idf_component.yml +6 -0
- package/components/mikrojs/include/mem.h +3 -0
- package/components/mikrojs/include/mik_color.h +3 -0
- package/components/mikrojs/include/mik_http_internal.h +77 -0
- package/components/mikrojs/include/mikrojs.h +5 -0
- package/components/mikrojs/include/mikrojs_esp32.h +65 -0
- package/components/mikrojs/include/private.h +10 -0
- package/components/mikrojs/include/utils.h +3 -0
- package/components/mikrojs/mik_ble.cpp +1588 -0
- package/components/mikrojs/mik_ble_c_shim.c +61 -0
- package/components/mikrojs/mik_ble_c_shim.h +37 -0
- package/components/mikrojs/mik_config.cpp +167 -0
- package/components/mikrojs/mik_deploy.cpp +584 -0
- package/components/mikrojs/mik_http.cpp +916 -0
- package/components/mikrojs/mik_i2c.cpp +364 -0
- package/components/mikrojs/mik_main.cpp +542 -0
- package/components/mikrojs/mik_neopixel.cpp +437 -0
- package/components/mikrojs/mik_nvs_kv.cpp +219 -0
- package/components/mikrojs/mik_pin.cpp +195 -0
- package/components/mikrojs/mik_pwm.cpp +525 -0
- package/components/mikrojs/mik_recovery.cpp +86 -0
- package/components/mikrojs/mik_rtc.cpp +305 -0
- package/components/mikrojs/mik_serial_io.cpp +362 -0
- package/components/mikrojs/mik_sleep.cpp +226 -0
- package/components/mikrojs/mik_sntp.cpp +275 -0
- package/components/mikrojs/mik_spi.cpp +330 -0
- package/components/mikrojs/mik_uart.cpp +497 -0
- package/components/mikrojs/mik_wifi.cpp +1434 -0
- package/components/mikrojs/platform_esp32.cpp +192 -0
- package/components/mikrojs/test/CMakeLists.txt +32 -0
- package/components/mikrojs/test/abort_test.cpp +254 -0
- package/components/mikrojs/test/ble_test.cpp +714 -0
- package/components/mikrojs/test/fs_js_test.cpp +458 -0
- package/components/mikrojs/test/fs_pub_test.cpp +312 -0
- package/components/mikrojs/test/http_test.cpp +475 -0
- package/components/mikrojs/test/i2c_test.cpp +138 -0
- package/components/mikrojs/test/modules_extended_test.cpp +137 -0
- package/components/mikrojs/test/modules_test.cpp +131 -0
- package/components/mikrojs/test/pins_test.cpp +47 -0
- package/components/mikrojs/test/pwm_test.cpp +166 -0
- package/components/mikrojs/test/repl_protocol_test.cpp +405 -0
- package/components/mikrojs/test/rtc_test.cpp +331 -0
- package/components/mikrojs/test/runtime_test.cpp +89 -0
- package/components/mikrojs/test/sleep_test.cpp +222 -0
- package/components/mikrojs/test/sntp_test.cpp +249 -0
- package/components/mikrojs/test/stdio_test.cpp +449 -0
- package/components/mikrojs/test/sys_test.cpp +165 -0
- package/components/mikrojs/test/text_encoding_test.cpp +224 -0
- package/components/mikrojs/test/timers_js_test.cpp +244 -0
- package/components/mikrojs/test/timers_test.cpp +79 -0
- package/components/mikrojs/test/wifi_test.cpp +599 -0
- package/default-app/main/CMakeLists.txt +3 -0
- package/default-app/main/main.cpp +5 -0
- package/discover.js +77 -0
- package/index.d.ts +7 -0
- package/index.js +20 -0
- package/package.json +61 -0
- package/partitions.csv +5 -0
- package/prebuilds/esp32/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32/flasher_args.json +24 -0
- package/prebuilds/esp32/mikrojs.bin +0 -0
- package/prebuilds/esp32/partition_table/partition-table.bin +0 -0
- package/prebuilds/esp32c3/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32c3/flasher_args.json +24 -0
- package/prebuilds/esp32c3/mikrojs.bin +0 -0
- package/prebuilds/esp32c3/partition_table/partition-table.bin +0 -0
- package/prebuilds/esp32c6/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32c6/flasher_args.json +24 -0
- package/prebuilds/esp32c6/mikrojs.bin +0 -0
- package/prebuilds/esp32c6/partition_table/partition-table.bin +0 -0
- package/prebuilds/esp32s3/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32s3/flasher_args.json +24 -0
- package/prebuilds/esp32s3/mikrojs.bin +0 -0
- package/prebuilds/esp32s3/partition_table/partition-table.bin +0 -0
- package/project.cmake +101 -0
- package/resolve.js +54 -0
- package/sdkconfig.defaults +127 -0
- package/sdkconfig.defaults.esp32 +8 -0
- package/sdkconfig.defaults.esp32c3 +15 -0
- package/sdkconfig.defaults.esp32c6 +26 -0
- package/sdkconfig.defaults.esp32s3 +22 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#include "mikrojs.h"
|
|
2
|
+
#include "private.h"
|
|
3
|
+
#include "quickjs.h"
|
|
4
|
+
#include "unity.h"
|
|
5
|
+
|
|
6
|
+
static MIKRuntime* rt;
|
|
7
|
+
static JSContext* ctx;
|
|
8
|
+
|
|
9
|
+
static JSValue eval_module(const char* code) {
|
|
10
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikrojs/test", code, strlen(code));
|
|
11
|
+
if (!JS_IsException(ret)) {
|
|
12
|
+
JS_FreeValue(ctx, ret);
|
|
13
|
+
mik__execute_jobs(ctx);
|
|
14
|
+
}
|
|
15
|
+
return ret;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static void setup() {
|
|
19
|
+
rt = MIK_NewRuntime();
|
|
20
|
+
ctx = MIK_GetJSContext(rt);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static void teardown() { MIK_FreeRuntime(rt); }
|
|
24
|
+
|
|
25
|
+
TEST_CASE("native:sys evalScript evaluates global code", "[modules]") {
|
|
26
|
+
setup();
|
|
27
|
+
|
|
28
|
+
JSValue ret = eval_module(R"(
|
|
29
|
+
import { evalScript } from "native:sys";
|
|
30
|
+
globalThis.__evalResult = evalScript("2 + 2");
|
|
31
|
+
)");
|
|
32
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
33
|
+
|
|
34
|
+
// evalScript returns a promise (due to JS_EVAL_FLAG_ASYNC), execute jobs
|
|
35
|
+
mik__execute_jobs(ctx);
|
|
36
|
+
|
|
37
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
38
|
+
JSValue result = JS_GetPropertyStr(ctx, global, "__evalResult");
|
|
39
|
+
|
|
40
|
+
// The result is a Promise, we need to check if it resolved
|
|
41
|
+
// For simple expressions, after executing jobs it should be available
|
|
42
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsUndefined(result), "evalScript should produce a result");
|
|
43
|
+
JS_FreeValue(ctx, result);
|
|
44
|
+
JS_FreeValue(ctx, global);
|
|
45
|
+
|
|
46
|
+
teardown();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
TEST_CASE("native:sys memoryUsage returns object with heapTotal and heapUsed", "[modules]") {
|
|
50
|
+
setup();
|
|
51
|
+
|
|
52
|
+
JSValue ret = eval_module(R"(
|
|
53
|
+
import { memoryUsage } from "native:sys";
|
|
54
|
+
const mem = memoryUsage();
|
|
55
|
+
globalThis.__hasTotal = "heapTotal" in mem;
|
|
56
|
+
globalThis.__hasUsed = "heapUsed" in mem;
|
|
57
|
+
globalThis.__totalIsNum = typeof mem.heapTotal === "number";
|
|
58
|
+
globalThis.__usedIsNum = typeof mem.heapUsed === "number";
|
|
59
|
+
globalThis.__totalGtZero = mem.heapTotal > 0;
|
|
60
|
+
globalThis.__usedGtZero = mem.heapUsed > 0;
|
|
61
|
+
)");
|
|
62
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
63
|
+
|
|
64
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
65
|
+
|
|
66
|
+
JSValue v;
|
|
67
|
+
v = JS_GetPropertyStr(ctx, global, "__hasTotal");
|
|
68
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "memoryUsage should have heapTotal");
|
|
69
|
+
JS_FreeValue(ctx, v);
|
|
70
|
+
|
|
71
|
+
v = JS_GetPropertyStr(ctx, global, "__hasUsed");
|
|
72
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "memoryUsage should have heapUsed");
|
|
73
|
+
JS_FreeValue(ctx, v);
|
|
74
|
+
|
|
75
|
+
v = JS_GetPropertyStr(ctx, global, "__totalIsNum");
|
|
76
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "heapTotal should be a number");
|
|
77
|
+
JS_FreeValue(ctx, v);
|
|
78
|
+
|
|
79
|
+
v = JS_GetPropertyStr(ctx, global, "__usedIsNum");
|
|
80
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "heapUsed should be a number");
|
|
81
|
+
JS_FreeValue(ctx, v);
|
|
82
|
+
|
|
83
|
+
v = JS_GetPropertyStr(ctx, global, "__totalGtZero");
|
|
84
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "heapTotal should be > 0");
|
|
85
|
+
JS_FreeValue(ctx, v);
|
|
86
|
+
|
|
87
|
+
v = JS_GetPropertyStr(ctx, global, "__usedGtZero");
|
|
88
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "heapUsed should be > 0");
|
|
89
|
+
JS_FreeValue(ctx, v);
|
|
90
|
+
|
|
91
|
+
JS_FreeValue(ctx, global);
|
|
92
|
+
|
|
93
|
+
teardown();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
TEST_CASE("native:sys exports are not uninitialized", "[modules]") {
|
|
97
|
+
setup();
|
|
98
|
+
|
|
99
|
+
JSValue ret = eval_module(R"(
|
|
100
|
+
import { evalScript, memoryUsage, gc, setTime, uptime, restart } from "native:sys";
|
|
101
|
+
globalThis.__allFunctions =
|
|
102
|
+
typeof evalScript === "function" &&
|
|
103
|
+
typeof memoryUsage === "function" &&
|
|
104
|
+
typeof gc === "function" &&
|
|
105
|
+
typeof setTime === "function" &&
|
|
106
|
+
typeof uptime === "function" &&
|
|
107
|
+
typeof restart === "function";
|
|
108
|
+
)");
|
|
109
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Importing native:sys should not throw");
|
|
110
|
+
|
|
111
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
112
|
+
JSValue v = JS_GetPropertyStr(ctx, global, "__allFunctions");
|
|
113
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v),
|
|
114
|
+
"All native:sys exports should be functions, not uninitialized");
|
|
115
|
+
JS_FreeValue(ctx, v);
|
|
116
|
+
JS_FreeValue(ctx, global);
|
|
117
|
+
|
|
118
|
+
teardown();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
TEST_CASE("native:sys uptime returns a positive number", "[modules]") {
|
|
122
|
+
setup();
|
|
123
|
+
|
|
124
|
+
JSValue ret = eval_module(R"(
|
|
125
|
+
import { uptime } from "native:sys";
|
|
126
|
+
const t = uptime();
|
|
127
|
+
globalThis.__isObj = typeof t === "object" && t !== null;
|
|
128
|
+
globalThis.__isPositive = typeof t.boot === "number" && t.boot >= 0;
|
|
129
|
+
)");
|
|
130
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "uptime() should not throw");
|
|
131
|
+
|
|
132
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
133
|
+
|
|
134
|
+
JSValue v;
|
|
135
|
+
v = JS_GetPropertyStr(ctx, global, "__isObj");
|
|
136
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "uptime should return an object");
|
|
137
|
+
JS_FreeValue(ctx, v);
|
|
138
|
+
|
|
139
|
+
v = JS_GetPropertyStr(ctx, global, "__isPositive");
|
|
140
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "uptime().boot should be a positive number");
|
|
141
|
+
JS_FreeValue(ctx, v);
|
|
142
|
+
|
|
143
|
+
JS_FreeValue(ctx, global);
|
|
144
|
+
|
|
145
|
+
teardown();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
TEST_CASE("native:sys gc does not crash", "[modules]") {
|
|
149
|
+
setup();
|
|
150
|
+
|
|
151
|
+
JSValue ret = eval_module(R"(
|
|
152
|
+
import { gc } from "native:sys";
|
|
153
|
+
gc();
|
|
154
|
+
globalThis.__gcDone = true;
|
|
155
|
+
)");
|
|
156
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "gc() should not throw");
|
|
157
|
+
|
|
158
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
159
|
+
JSValue done = JS_GetPropertyStr(ctx, global, "__gcDone");
|
|
160
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, done), "gc should complete without error");
|
|
161
|
+
JS_FreeValue(ctx, done);
|
|
162
|
+
JS_FreeValue(ctx, global);
|
|
163
|
+
|
|
164
|
+
teardown();
|
|
165
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#include "mikrojs.h"
|
|
2
|
+
#include "quickjs.h"
|
|
3
|
+
#include "unity.h"
|
|
4
|
+
|
|
5
|
+
static MIKRuntime* rt;
|
|
6
|
+
static JSContext* ctx;
|
|
7
|
+
|
|
8
|
+
static JSValue eval(const char* code) {
|
|
9
|
+
return JS_Eval(ctx, code, strlen(code), "main.js", JS_EVAL_TYPE_GLOBAL);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static void setup() {
|
|
13
|
+
rt = MIK_NewRuntime();
|
|
14
|
+
ctx = MIK_GetJSContext(rt);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static void teardown() { MIK_FreeRuntime(rt); }
|
|
18
|
+
|
|
19
|
+
TEST_CASE("TextEncoder is a constructor", "[runtime]") {
|
|
20
|
+
setup();
|
|
21
|
+
|
|
22
|
+
JSValue r = eval("typeof TextEncoder");
|
|
23
|
+
const char* type = JS_ToCString(ctx, r);
|
|
24
|
+
TEST_ASSERT_EQUAL_STRING("function", type);
|
|
25
|
+
JS_FreeCString(ctx, type);
|
|
26
|
+
JS_FreeValue(ctx, r);
|
|
27
|
+
|
|
28
|
+
teardown();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
TEST_CASE("TextDecoder is a constructor", "[runtime]") {
|
|
32
|
+
setup();
|
|
33
|
+
|
|
34
|
+
JSValue r = eval("typeof TextDecoder");
|
|
35
|
+
const char* type = JS_ToCString(ctx, r);
|
|
36
|
+
TEST_ASSERT_EQUAL_STRING("function", type);
|
|
37
|
+
JS_FreeCString(ctx, type);
|
|
38
|
+
JS_FreeValue(ctx, r);
|
|
39
|
+
|
|
40
|
+
teardown();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
TEST_CASE("TextEncoder.encode produces Uint8Array with correct bytes", "[runtime]") {
|
|
44
|
+
setup();
|
|
45
|
+
|
|
46
|
+
JSValue r = eval(R"(
|
|
47
|
+
const enc = new TextEncoder();
|
|
48
|
+
const arr = enc.encode("hello");
|
|
49
|
+
globalThis.__len = arr.length;
|
|
50
|
+
globalThis.__b0 = arr[0];
|
|
51
|
+
globalThis.__b4 = arr[4];
|
|
52
|
+
arr instanceof Uint8Array;
|
|
53
|
+
)");
|
|
54
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(r), "encode should not throw");
|
|
55
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, r), "Result should be a Uint8Array");
|
|
56
|
+
JS_FreeValue(ctx, r);
|
|
57
|
+
|
|
58
|
+
int32_t len, b0, b4;
|
|
59
|
+
JSValue v;
|
|
60
|
+
|
|
61
|
+
v = eval("globalThis.__len");
|
|
62
|
+
JS_ToInt32(ctx, &len, v);
|
|
63
|
+
JS_FreeValue(ctx, v);
|
|
64
|
+
TEST_ASSERT_EQUAL_INT32(5, len);
|
|
65
|
+
|
|
66
|
+
v = eval("globalThis.__b0");
|
|
67
|
+
JS_ToInt32(ctx, &b0, v);
|
|
68
|
+
JS_FreeValue(ctx, v);
|
|
69
|
+
TEST_ASSERT_EQUAL_INT32('h', b0);
|
|
70
|
+
|
|
71
|
+
v = eval("globalThis.__b4");
|
|
72
|
+
JS_ToInt32(ctx, &b4, v);
|
|
73
|
+
JS_FreeValue(ctx, v);
|
|
74
|
+
TEST_ASSERT_EQUAL_INT32('o', b4);
|
|
75
|
+
|
|
76
|
+
teardown();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
TEST_CASE("TextDecoder.decode converts Uint8Array to string", "[runtime]") {
|
|
80
|
+
setup();
|
|
81
|
+
|
|
82
|
+
JSValue r = eval(R"(
|
|
83
|
+
const dec = new TextDecoder();
|
|
84
|
+
const arr = new Uint8Array([72, 101, 108, 108, 111]);
|
|
85
|
+
dec.decode(arr);
|
|
86
|
+
)");
|
|
87
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(r), "decode should not throw");
|
|
88
|
+
const char* str = JS_ToCString(ctx, r);
|
|
89
|
+
TEST_ASSERT_EQUAL_STRING("Hello", str);
|
|
90
|
+
JS_FreeCString(ctx, str);
|
|
91
|
+
JS_FreeValue(ctx, r);
|
|
92
|
+
|
|
93
|
+
teardown();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
TEST_CASE("TextEncoder/TextDecoder roundtrip", "[runtime]") {
|
|
97
|
+
setup();
|
|
98
|
+
|
|
99
|
+
JSValue r = eval(R"(
|
|
100
|
+
const enc = new TextEncoder();
|
|
101
|
+
const dec = new TextDecoder();
|
|
102
|
+
dec.decode(enc.encode("mikrojs rocks"));
|
|
103
|
+
)");
|
|
104
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(r), "roundtrip should not throw");
|
|
105
|
+
const char* str = JS_ToCString(ctx, r);
|
|
106
|
+
TEST_ASSERT_EQUAL_STRING("mikrojs rocks", str);
|
|
107
|
+
JS_FreeCString(ctx, str);
|
|
108
|
+
JS_FreeValue(ctx, r);
|
|
109
|
+
|
|
110
|
+
teardown();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
TEST_CASE("TextDecoder.decode rejects non-Uint8Array", "[runtime]") {
|
|
114
|
+
setup();
|
|
115
|
+
|
|
116
|
+
JSValue r = eval(R"(
|
|
117
|
+
try {
|
|
118
|
+
const dec = new TextDecoder();
|
|
119
|
+
dec.decode("not a typed array");
|
|
120
|
+
false;
|
|
121
|
+
} catch (e) {
|
|
122
|
+
e instanceof TypeError;
|
|
123
|
+
}
|
|
124
|
+
)");
|
|
125
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(r), "Should not throw at top level");
|
|
126
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, r), "Should have caught a TypeError");
|
|
127
|
+
JS_FreeValue(ctx, r);
|
|
128
|
+
|
|
129
|
+
teardown();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* ---- btoa / atob ---- */
|
|
133
|
+
|
|
134
|
+
TEST_CASE("btoa encodes ASCII string", "[runtime]") {
|
|
135
|
+
setup();
|
|
136
|
+
JSValue r = eval("btoa('Hello')");
|
|
137
|
+
const char* str = JS_ToCString(ctx, r);
|
|
138
|
+
TEST_ASSERT_EQUAL_STRING("SGVsbG8=", str);
|
|
139
|
+
JS_FreeCString(ctx, str);
|
|
140
|
+
JS_FreeValue(ctx, r);
|
|
141
|
+
teardown();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
TEST_CASE("atob decodes base64 string", "[runtime]") {
|
|
145
|
+
setup();
|
|
146
|
+
JSValue r = eval("atob('SGVsbG8=')");
|
|
147
|
+
const char* str = JS_ToCString(ctx, r);
|
|
148
|
+
TEST_ASSERT_EQUAL_STRING("Hello", str);
|
|
149
|
+
JS_FreeCString(ctx, str);
|
|
150
|
+
JS_FreeValue(ctx, r);
|
|
151
|
+
teardown();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
TEST_CASE("btoa/atob roundtrip", "[runtime]") {
|
|
155
|
+
setup();
|
|
156
|
+
JSValue r = eval("atob(btoa('mikrojs'))");
|
|
157
|
+
const char* str = JS_ToCString(ctx, r);
|
|
158
|
+
TEST_ASSERT_EQUAL_STRING("mikrojs", str);
|
|
159
|
+
JS_FreeCString(ctx, str);
|
|
160
|
+
JS_FreeValue(ctx, r);
|
|
161
|
+
teardown();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
TEST_CASE("btoa handles padding correctly", "[runtime]") {
|
|
165
|
+
setup();
|
|
166
|
+
JSValue r1 = eval("btoa('a')");
|
|
167
|
+
const char* s1 = JS_ToCString(ctx, r1);
|
|
168
|
+
TEST_ASSERT_EQUAL_STRING("YQ==", s1);
|
|
169
|
+
JS_FreeCString(ctx, s1);
|
|
170
|
+
JS_FreeValue(ctx, r1);
|
|
171
|
+
|
|
172
|
+
JSValue r2 = eval("btoa('ab')");
|
|
173
|
+
const char* s2 = JS_ToCString(ctx, r2);
|
|
174
|
+
TEST_ASSERT_EQUAL_STRING("YWI=", s2);
|
|
175
|
+
JS_FreeCString(ctx, s2);
|
|
176
|
+
JS_FreeValue(ctx, r2);
|
|
177
|
+
teardown();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
TEST_CASE("atob throws on invalid input", "[runtime]") {
|
|
181
|
+
setup();
|
|
182
|
+
JSValue r = eval(R"(
|
|
183
|
+
try { atob('!!!'); false; }
|
|
184
|
+
catch (e) { e instanceof SyntaxError; }
|
|
185
|
+
)");
|
|
186
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, r), "Should throw SyntaxError on invalid base64");
|
|
187
|
+
JS_FreeValue(ctx, r);
|
|
188
|
+
teardown();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
TEST_CASE("TextEncoder.encode handles multibyte UTF-8", "[runtime]") {
|
|
192
|
+
setup();
|
|
193
|
+
|
|
194
|
+
// "é" is 2 bytes in UTF-8 (0xC3, 0xA9)
|
|
195
|
+
JSValue r = eval(R"(
|
|
196
|
+
const enc = new TextEncoder();
|
|
197
|
+
const arr = enc.encode("\u00e9");
|
|
198
|
+
globalThis.__mbLen = arr.length;
|
|
199
|
+
globalThis.__mb0 = arr[0];
|
|
200
|
+
globalThis.__mb1 = arr[1];
|
|
201
|
+
)");
|
|
202
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(r), "encode multibyte should not throw");
|
|
203
|
+
JS_FreeValue(ctx, r);
|
|
204
|
+
|
|
205
|
+
int32_t len, b0, b1;
|
|
206
|
+
JSValue v;
|
|
207
|
+
|
|
208
|
+
v = eval("globalThis.__mbLen");
|
|
209
|
+
JS_ToInt32(ctx, &len, v);
|
|
210
|
+
JS_FreeValue(ctx, v);
|
|
211
|
+
TEST_ASSERT_EQUAL_INT32(2, len);
|
|
212
|
+
|
|
213
|
+
v = eval("globalThis.__mb0");
|
|
214
|
+
JS_ToInt32(ctx, &b0, v);
|
|
215
|
+
JS_FreeValue(ctx, v);
|
|
216
|
+
TEST_ASSERT_EQUAL_INT32(0xC3, b0);
|
|
217
|
+
|
|
218
|
+
v = eval("globalThis.__mb1");
|
|
219
|
+
JS_ToInt32(ctx, &b1, v);
|
|
220
|
+
JS_FreeValue(ctx, v);
|
|
221
|
+
TEST_ASSERT_EQUAL_INT32(0xA9, b1);
|
|
222
|
+
|
|
223
|
+
teardown();
|
|
224
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#include "mikrojs.h"
|
|
2
|
+
#include "quickjs.h"
|
|
3
|
+
#include "unity.h"
|
|
4
|
+
|
|
5
|
+
static MIKRuntime* rt;
|
|
6
|
+
static JSContext* ctx;
|
|
7
|
+
|
|
8
|
+
static JSValue eval(const char* code) {
|
|
9
|
+
return JS_Eval(ctx, code, strlen(code), "main.js", JS_EVAL_TYPE_GLOBAL);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static int32_t eval_int(const char* code) {
|
|
13
|
+
int32_t num;
|
|
14
|
+
JSValue val = eval(code);
|
|
15
|
+
JS_ToInt32(ctx, &num, val);
|
|
16
|
+
JS_FreeValue(ctx, val);
|
|
17
|
+
return num;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static void setup() {
|
|
21
|
+
rt = MIK_NewRuntime();
|
|
22
|
+
ctx = MIK_GetJSContext(rt);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static void teardown() { MIK_FreeRuntime(rt); }
|
|
26
|
+
|
|
27
|
+
TEST_CASE("setTimeout fires callback", "[timers]") {
|
|
28
|
+
setup();
|
|
29
|
+
|
|
30
|
+
JSValue r = eval(R"(
|
|
31
|
+
globalThis.fired = false;
|
|
32
|
+
setTimeout(() => { globalThis.fired = true; }, 0);
|
|
33
|
+
)");
|
|
34
|
+
JS_FreeValue(ctx, r);
|
|
35
|
+
|
|
36
|
+
MIK_Loop(rt);
|
|
37
|
+
|
|
38
|
+
JSValue fired = eval("globalThis.fired");
|
|
39
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, fired), "setTimeout callback should have fired");
|
|
40
|
+
JS_FreeValue(ctx, fired);
|
|
41
|
+
|
|
42
|
+
teardown();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
TEST_CASE("clearTimeout cancels a pending timer", "[timers]") {
|
|
46
|
+
setup();
|
|
47
|
+
|
|
48
|
+
JSValue r = eval(R"(
|
|
49
|
+
globalThis.fired = false;
|
|
50
|
+
const id = setTimeout(() => { globalThis.fired = true; }, 0);
|
|
51
|
+
clearTimeout(id);
|
|
52
|
+
)");
|
|
53
|
+
JS_FreeValue(ctx, r);
|
|
54
|
+
|
|
55
|
+
MIK_Loop(rt);
|
|
56
|
+
|
|
57
|
+
JSValue fired = eval("globalThis.fired");
|
|
58
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_ToBool(ctx, fired),
|
|
59
|
+
"Callback should not fire after clearTimeout");
|
|
60
|
+
JS_FreeValue(ctx, fired);
|
|
61
|
+
|
|
62
|
+
teardown();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
TEST_CASE("setInterval fires repeatedly", "[timers]") {
|
|
66
|
+
setup();
|
|
67
|
+
|
|
68
|
+
JSValue r = eval(R"(
|
|
69
|
+
globalThis.count = 0;
|
|
70
|
+
setInterval(() => { globalThis.count++; }, 0);
|
|
71
|
+
)");
|
|
72
|
+
JS_FreeValue(ctx, r);
|
|
73
|
+
|
|
74
|
+
for (int i = 0; i < 5; i++) {
|
|
75
|
+
MIK_Loop(rt);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
int32_t count = eval_int("globalThis.count");
|
|
79
|
+
TEST_ASSERT_GREATER_OR_EQUAL_INT32_MESSAGE(5, count,
|
|
80
|
+
"setInterval should fire on each loop iteration");
|
|
81
|
+
|
|
82
|
+
teardown();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
TEST_CASE("clearInterval stops an interval", "[timers]") {
|
|
86
|
+
setup();
|
|
87
|
+
|
|
88
|
+
JSValue r = eval(R"(
|
|
89
|
+
globalThis.count = 0;
|
|
90
|
+
globalThis.intervalId = setInterval(() => { globalThis.count++; }, 0);
|
|
91
|
+
)");
|
|
92
|
+
JS_FreeValue(ctx, r);
|
|
93
|
+
|
|
94
|
+
// Run a few loops to accumulate some ticks
|
|
95
|
+
for (int i = 0; i < 3; i++) {
|
|
96
|
+
MIK_Loop(rt);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
JSValue r2 = eval("clearInterval(globalThis.intervalId);");
|
|
100
|
+
JS_FreeValue(ctx, r2);
|
|
101
|
+
|
|
102
|
+
int32_t count_after_clear = eval_int("globalThis.count");
|
|
103
|
+
|
|
104
|
+
// Run more loops — count should not increase
|
|
105
|
+
for (int i = 0; i < 3; i++) {
|
|
106
|
+
MIK_Loop(rt);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
int32_t count_final = eval_int("globalThis.count");
|
|
110
|
+
TEST_ASSERT_EQUAL_INT32_MESSAGE(count_after_clear, count_final,
|
|
111
|
+
"Count should not increase after clearInterval");
|
|
112
|
+
|
|
113
|
+
teardown();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
TEST_CASE("setTimeout passes extra arguments to callback", "[timers]") {
|
|
117
|
+
setup();
|
|
118
|
+
|
|
119
|
+
JSValue r = eval(R"(
|
|
120
|
+
globalThis.result = 0;
|
|
121
|
+
setTimeout((a, b) => { globalThis.result = a + b; }, 0, 17, 25);
|
|
122
|
+
)");
|
|
123
|
+
JS_FreeValue(ctx, r);
|
|
124
|
+
|
|
125
|
+
MIK_Loop(rt);
|
|
126
|
+
|
|
127
|
+
int32_t result = eval_int("globalThis.result");
|
|
128
|
+
TEST_ASSERT_EQUAL_INT32_MESSAGE(42, result,
|
|
129
|
+
"Callback should receive extra args passed to setTimeout");
|
|
130
|
+
|
|
131
|
+
teardown();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
TEST_CASE("clearTimeout on non-existent ID is a no-op", "[timers]") {
|
|
135
|
+
setup();
|
|
136
|
+
|
|
137
|
+
// Should not throw or crash
|
|
138
|
+
JSValue r = eval("clearTimeout(99999);");
|
|
139
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(r),
|
|
140
|
+
"clearTimeout with invalid ID should not throw");
|
|
141
|
+
JS_FreeValue(ctx, r);
|
|
142
|
+
|
|
143
|
+
teardown();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
TEST_CASE("setTimeout throws RangeError when passing too many args", "[timers]") {
|
|
147
|
+
setup();
|
|
148
|
+
|
|
149
|
+
JSValue r = eval(R"(
|
|
150
|
+
try {
|
|
151
|
+
setTimeout(() => {}, 0, 1, 2, 3, 4, 5);
|
|
152
|
+
false;
|
|
153
|
+
} catch (e) {
|
|
154
|
+
e instanceof RangeError;
|
|
155
|
+
}
|
|
156
|
+
)");
|
|
157
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(r), "Should not throw at top level");
|
|
158
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, r), "Should have caught a RangeError");
|
|
159
|
+
JS_FreeValue(ctx, r);
|
|
160
|
+
|
|
161
|
+
teardown();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
TEST_CASE("setTimeout with non-zero delay does not fire immediately", "[timers]") {
|
|
165
|
+
setup();
|
|
166
|
+
|
|
167
|
+
JSValue r = eval(R"(
|
|
168
|
+
globalThis.fired = false;
|
|
169
|
+
setTimeout(() => { globalThis.fired = true; }, 1000);
|
|
170
|
+
)");
|
|
171
|
+
JS_FreeValue(ctx, r);
|
|
172
|
+
|
|
173
|
+
// Run the loop once — the 1000ms timer should not fire yet
|
|
174
|
+
MIK_Loop(rt);
|
|
175
|
+
|
|
176
|
+
JSValue fired = eval("globalThis.fired");
|
|
177
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_ToBool(ctx, fired),
|
|
178
|
+
"setTimeout with 1000ms delay should not fire immediately");
|
|
179
|
+
JS_FreeValue(ctx, fired);
|
|
180
|
+
|
|
181
|
+
teardown();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
TEST_CASE("setInterval with non-zero delay does not fire immediately", "[timers]") {
|
|
185
|
+
setup();
|
|
186
|
+
|
|
187
|
+
JSValue r = eval(R"(
|
|
188
|
+
globalThis.count = 0;
|
|
189
|
+
setInterval(() => { globalThis.count++; }, 500);
|
|
190
|
+
)");
|
|
191
|
+
JS_FreeValue(ctx, r);
|
|
192
|
+
|
|
193
|
+
// Run the loop once — the 500ms interval should not fire yet
|
|
194
|
+
MIK_Loop(rt);
|
|
195
|
+
|
|
196
|
+
int32_t count = eval_int("globalThis.count");
|
|
197
|
+
TEST_ASSERT_EQUAL_INT32_MESSAGE(0, count,
|
|
198
|
+
"setInterval with 500ms delay should not fire immediately");
|
|
199
|
+
|
|
200
|
+
teardown();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
TEST_CASE("clearInterval inside callback does not crash", "[timers]") {
|
|
204
|
+
setup();
|
|
205
|
+
|
|
206
|
+
JSValue r = eval(R"(
|
|
207
|
+
globalThis.result = "";
|
|
208
|
+
var id = setInterval(() => {
|
|
209
|
+
globalThis.result += "tick";
|
|
210
|
+
clearInterval(id);
|
|
211
|
+
}, 0);
|
|
212
|
+
)");
|
|
213
|
+
JS_FreeValue(ctx, r);
|
|
214
|
+
|
|
215
|
+
// Run several loops — the callback should fire once and then stop.
|
|
216
|
+
// Before the fix this would crash (use-after-free) because clearInterval
|
|
217
|
+
// freed JSValues while the copied dueTimers vector still referenced them.
|
|
218
|
+
for (int i = 0; i < 5; i++) {
|
|
219
|
+
MIK_Loop(rt);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
JSValue val = eval("globalThis.result");
|
|
223
|
+
const char* str = JS_ToCString(ctx, val);
|
|
224
|
+
TEST_ASSERT_EQUAL_STRING_MESSAGE("tick", str,
|
|
225
|
+
"Callback should fire exactly once before clearInterval stops it");
|
|
226
|
+
JS_FreeCString(ctx, str);
|
|
227
|
+
JS_FreeValue(ctx, val);
|
|
228
|
+
|
|
229
|
+
teardown();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
TEST_CASE("setTimeout returns a numeric timer ID", "[timers]") {
|
|
233
|
+
setup();
|
|
234
|
+
|
|
235
|
+
JSValue r = eval("setTimeout(() => {}, 0)");
|
|
236
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_IsNumber(r), "setTimeout should return a number");
|
|
237
|
+
|
|
238
|
+
int32_t id;
|
|
239
|
+
JS_ToInt32(ctx, &id, r);
|
|
240
|
+
TEST_ASSERT_GREATER_THAN_INT32_MESSAGE(0, id, "Timer ID should be > 0");
|
|
241
|
+
JS_FreeValue(ctx, r);
|
|
242
|
+
|
|
243
|
+
teardown();
|
|
244
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/* Include standalone library source directly for testing internal functions */
|
|
2
|
+
#include "../../../../packages/@mikrojs/native/src/timers.cpp"
|
|
3
|
+
|
|
4
|
+
#include "quickjs.h"
|
|
5
|
+
#include "unity.h"
|
|
6
|
+
|
|
7
|
+
JSValue __timer_test__noop(JSContext* ctx, JSValue new_target, int argc, JSValue* argv) {
|
|
8
|
+
return JS_UNDEFINED;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
TEST_CASE("init and destroy", "[timers]") {
|
|
12
|
+
JSRuntime* rt = JS_NewRuntime();
|
|
13
|
+
JSContext* ctx = JS_NewContext(rt);
|
|
14
|
+
MIKTimerRegistry* timers = MIK_NewTimerRegistry();
|
|
15
|
+
|
|
16
|
+
JSValue argv[0];
|
|
17
|
+
JSValue argc = JS_NewUint32(ctx, 0);
|
|
18
|
+
JSValue cb = JS_NewCFunction(ctx, __timer_test__noop, "callme", 0);
|
|
19
|
+
|
|
20
|
+
uint32_t timer_id;
|
|
21
|
+
|
|
22
|
+
timer_id = MIK_Timer_Schedule(timers, ctx, cb, argc, argv, 1000, false);
|
|
23
|
+
TEST_ASSERT_EQUAL_UINT32(1, timer_id);
|
|
24
|
+
|
|
25
|
+
timer_id = MIK_Timer_Schedule(timers, ctx, cb, argc, argv, 1000, false);
|
|
26
|
+
TEST_ASSERT_EQUAL_UINT32(2, timer_id);
|
|
27
|
+
JS_FreeValue(ctx, cb);
|
|
28
|
+
MIK_Timer_ClearAll(timers, ctx);
|
|
29
|
+
JS_FreeContext(ctx);
|
|
30
|
+
JS_FreeRuntime(rt);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
TEST_CASE("Supports removing existing timers", "[timers]") {
|
|
34
|
+
JSRuntime* rt = JS_NewRuntime();
|
|
35
|
+
JSContext* ctx = JS_NewContext(rt);
|
|
36
|
+
MIKTimerRegistry* timers = MIK_NewTimerRegistry();
|
|
37
|
+
|
|
38
|
+
JSValue argv[0];
|
|
39
|
+
JSValue argc = JS_NewUint32(ctx, 0);
|
|
40
|
+
JSValue cb = JS_NewCFunction(ctx, __timer_test__noop, "callme", 0);
|
|
41
|
+
|
|
42
|
+
auto const timer_id_1 = MIK_Timer_Schedule(timers, ctx, cb, argc, argv, 1000, false);
|
|
43
|
+
auto const timer_id_2 = MIK_Timer_Schedule(timers, ctx, cb, argc, argv, 1000, false);
|
|
44
|
+
|
|
45
|
+
TEST_ASSERT_EQUAL_INT(timers->entries.size(), 2);
|
|
46
|
+
MIK_Timer_UnSchedule(timers, ctx, timer_id_1);
|
|
47
|
+
TEST_ASSERT_EQUAL_INT(timers->entries.size(), 1);
|
|
48
|
+
MIK_Timer_UnSchedule(timers, ctx, timer_id_2);
|
|
49
|
+
TEST_ASSERT_EQUAL_INT(timers->entries.size(), 0);
|
|
50
|
+
JS_FreeValue(ctx, cb);
|
|
51
|
+
MIK_Timer_ClearAll(timers, ctx);
|
|
52
|
+
JS_FreeContext(ctx);
|
|
53
|
+
JS_FreeRuntime(rt);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
TEST_CASE("Supports getting due deadline from timers", "[timers]") {
|
|
57
|
+
JSRuntime* rt = JS_NewRuntime();
|
|
58
|
+
JSContext* ctx = JS_NewContext(rt);
|
|
59
|
+
MIKTimerRegistry* timers = MIK_NewTimerRegistry();
|
|
60
|
+
|
|
61
|
+
JSValue argv[0];
|
|
62
|
+
JSValue argc = JS_NewUint32(ctx, 0);
|
|
63
|
+
JSValue cb = JS_NewCFunction(ctx, __timer_test__noop, "callme", 0);
|
|
64
|
+
|
|
65
|
+
MIK_Timer_Schedule(timers, ctx, cb, argc, argv, 10, false, 0);
|
|
66
|
+
MIK_Timer_Schedule(timers, ctx, cb, argc, argv, 100, false, 0);
|
|
67
|
+
MIK_Timer_Schedule(timers, ctx, cb, argc, argv, 200, false, 0);
|
|
68
|
+
|
|
69
|
+
TEST_ASSERT_EQUAL_INT(timers->entries.size(), 3);
|
|
70
|
+
TEST_ASSERT_EQUAL(MIK_Timer_CountDue(timers, 0), 0);
|
|
71
|
+
TEST_ASSERT_EQUAL(MIK_Timer_CountDue(timers, 50), 1);
|
|
72
|
+
TEST_ASSERT_EQUAL(MIK_Timer_CountDue(timers, 99), 1);
|
|
73
|
+
TEST_ASSERT_EQUAL(MIK_Timer_CountDue(timers, 100), 2);
|
|
74
|
+
TEST_ASSERT_EQUAL(MIK_Timer_CountDue(timers, 200), 3);
|
|
75
|
+
JS_FreeValue(ctx, cb);
|
|
76
|
+
MIK_Timer_ClearAll(timers, ctx);
|
|
77
|
+
JS_FreeContext(ctx);
|
|
78
|
+
JS_FreeRuntime(rt);
|
|
79
|
+
}
|