@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,249 @@
|
|
|
1
|
+
#include "mikrojs.h"
|
|
2
|
+
#include "private.h"
|
|
3
|
+
#include "quickjs.h"
|
|
4
|
+
#include "unity.h"
|
|
5
|
+
|
|
6
|
+
/* SNTP module is self-registered via MIK_REGISTER_MODULE and lazily initialized.
|
|
7
|
+
* mik__sntp_consume and mik__sntp_inject_sync are exposed for test use. */
|
|
8
|
+
extern void mik__sntp_consume(JSContext* ctx);
|
|
9
|
+
extern void mik__sntp_inject_sync(int64_t epoch_sec);
|
|
10
|
+
|
|
11
|
+
static MIKRuntime* rt;
|
|
12
|
+
static JSContext* ctx;
|
|
13
|
+
|
|
14
|
+
static void setup() {
|
|
15
|
+
rt = MIK_NewRuntime();
|
|
16
|
+
ctx = MIK_GetJSContext(rt);
|
|
17
|
+
/* SNTP module + loop consumer are registered lazily on first import */
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static void teardown() { MIK_FreeRuntime(rt); }
|
|
21
|
+
|
|
22
|
+
static JSValue eval_module(const char* code) {
|
|
23
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikrojs/test", code, strlen(code));
|
|
24
|
+
if (!JS_IsException(ret)) {
|
|
25
|
+
JS_FreeValue(ctx, ret);
|
|
26
|
+
mik__execute_jobs(ctx);
|
|
27
|
+
}
|
|
28
|
+
return ret;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* ── Module structure tests ───────────────────────────────────────── */
|
|
32
|
+
|
|
33
|
+
TEST_CASE("native:sntp exports sync, stop, and setTimezone", "[modules]") {
|
|
34
|
+
setup();
|
|
35
|
+
|
|
36
|
+
JSValue ret = eval_module(R"(
|
|
37
|
+
import { sync, stop, setTimezone } from "native:sntp";
|
|
38
|
+
globalThis.__syncIsFunc = typeof sync === "function";
|
|
39
|
+
globalThis.__stopIsFunc = typeof stop === "function";
|
|
40
|
+
globalThis.__setTzIsFunc = typeof setTimezone === "function";
|
|
41
|
+
)");
|
|
42
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
43
|
+
|
|
44
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
45
|
+
|
|
46
|
+
JSValue v;
|
|
47
|
+
v = JS_GetPropertyStr(ctx, global, "__syncIsFunc");
|
|
48
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "sync should be a function");
|
|
49
|
+
JS_FreeValue(ctx, v);
|
|
50
|
+
|
|
51
|
+
v = JS_GetPropertyStr(ctx, global, "__stopIsFunc");
|
|
52
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "stop should be a function");
|
|
53
|
+
JS_FreeValue(ctx, v);
|
|
54
|
+
|
|
55
|
+
v = JS_GetPropertyStr(ctx, global, "__setTzIsFunc");
|
|
56
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "setTimezone should be a function");
|
|
57
|
+
JS_FreeValue(ctx, v);
|
|
58
|
+
|
|
59
|
+
JS_FreeValue(ctx, global);
|
|
60
|
+
teardown();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* ── sync returns a promise ───────────────────────────────────────── */
|
|
64
|
+
|
|
65
|
+
TEST_CASE("native:sntp sync returns a result-wrapped promise", "[modules]") {
|
|
66
|
+
setup();
|
|
67
|
+
|
|
68
|
+
JSValue ret = eval_module(R"(
|
|
69
|
+
import { sync, stop } from "native:sntp";
|
|
70
|
+
const result = sync(["pool.ntp.org"], "UTC0", false);
|
|
71
|
+
globalThis.__isOk = result.ok === true;
|
|
72
|
+
globalThis.__isPromise = result.value instanceof Promise;
|
|
73
|
+
stop();
|
|
74
|
+
)");
|
|
75
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
76
|
+
|
|
77
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
78
|
+
JSValue isOk = JS_GetPropertyStr(ctx, global, "__isOk");
|
|
79
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, isOk), "sync() should return ok=true");
|
|
80
|
+
JS_FreeValue(ctx, isOk);
|
|
81
|
+
JSValue isPromise = JS_GetPropertyStr(ctx, global, "__isPromise");
|
|
82
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, isPromise), "sync().value should be a Promise");
|
|
83
|
+
JS_FreeValue(ctx, isPromise);
|
|
84
|
+
JS_FreeValue(ctx, global);
|
|
85
|
+
teardown();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* ── sync promise resolves with time on injected sync event ───────── */
|
|
89
|
+
|
|
90
|
+
TEST_CASE("native:sntp sync resolves with time when sync completes", "[modules]") {
|
|
91
|
+
setup();
|
|
92
|
+
|
|
93
|
+
JSValue ret = eval_module(R"(
|
|
94
|
+
import { sync, stop } from "native:sntp";
|
|
95
|
+
const startResult = sync(["pool.ntp.org"], "UTC0", false);
|
|
96
|
+
startResult.value.then(result => {
|
|
97
|
+
globalThis.__resolved = true;
|
|
98
|
+
globalThis.__hasTime = result.ok && "time" in result.value;
|
|
99
|
+
globalThis.__timeValue = result.ok ? result.value.time : 0;
|
|
100
|
+
});
|
|
101
|
+
)");
|
|
102
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
103
|
+
|
|
104
|
+
/* Inject a fake sync event: 2026-06-15T12:00:00Z = 1781524800 */
|
|
105
|
+
mik__sntp_inject_sync(1781524800);
|
|
106
|
+
mik__sntp_consume(ctx);
|
|
107
|
+
mik__execute_jobs(ctx);
|
|
108
|
+
|
|
109
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
110
|
+
|
|
111
|
+
JSValue resolved = JS_GetPropertyStr(ctx, global, "__resolved");
|
|
112
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, resolved), "sync promise should have resolved");
|
|
113
|
+
JS_FreeValue(ctx, resolved);
|
|
114
|
+
|
|
115
|
+
JSValue hasTime = JS_GetPropertyStr(ctx, global, "__hasTime");
|
|
116
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, hasTime), "result should have 'time' property");
|
|
117
|
+
JS_FreeValue(ctx, hasTime);
|
|
118
|
+
|
|
119
|
+
JSValue timeVal = JS_GetPropertyStr(ctx, global, "__timeValue");
|
|
120
|
+
double time_ms;
|
|
121
|
+
JS_ToFloat64(ctx, &time_ms, timeVal);
|
|
122
|
+
JS_FreeValue(ctx, timeVal);
|
|
123
|
+
/* 1781524800 seconds = 1781524800000 ms */
|
|
124
|
+
TEST_ASSERT_FLOAT_WITHIN(1000.0, 1781524800000.0, time_ms);
|
|
125
|
+
|
|
126
|
+
JS_FreeValue(ctx, global);
|
|
127
|
+
teardown();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* ── one-shot sync stops SNTP after resolving ─────────────────────── */
|
|
131
|
+
|
|
132
|
+
TEST_CASE("native:sntp one-shot sync stops after first sync", "[modules]") {
|
|
133
|
+
setup();
|
|
134
|
+
|
|
135
|
+
JSValue ret = eval_module(R"(
|
|
136
|
+
import { sync, stop } from "native:sntp";
|
|
137
|
+
globalThis.__resolved = false;
|
|
138
|
+
const startResult = sync(["pool.ntp.org"], "UTC0", false);
|
|
139
|
+
startResult.value.then(() => {
|
|
140
|
+
globalThis.__resolved = true;
|
|
141
|
+
});
|
|
142
|
+
)");
|
|
143
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
144
|
+
|
|
145
|
+
mik__sntp_inject_sync(1781524800);
|
|
146
|
+
mik__sntp_consume(ctx);
|
|
147
|
+
mik__execute_jobs(ctx);
|
|
148
|
+
|
|
149
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
150
|
+
JSValue resolved = JS_GetPropertyStr(ctx, global, "__resolved");
|
|
151
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, resolved), "sync promise should have resolved");
|
|
152
|
+
JS_FreeValue(ctx, resolved);
|
|
153
|
+
|
|
154
|
+
/* A second injected event should be ignored (no pending promise) */
|
|
155
|
+
mik__sntp_inject_sync(1781524800);
|
|
156
|
+
mik__sntp_consume(ctx);
|
|
157
|
+
/* No crash = pass */
|
|
158
|
+
|
|
159
|
+
JS_FreeValue(ctx, global);
|
|
160
|
+
teardown();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* ── stop does not crash when not running ─────────────────────────── */
|
|
164
|
+
|
|
165
|
+
TEST_CASE("native:sntp stop does not crash when not running", "[modules]") {
|
|
166
|
+
setup();
|
|
167
|
+
|
|
168
|
+
JSValue ret = eval_module(R"(
|
|
169
|
+
import { stop } from "native:sntp";
|
|
170
|
+
stop();
|
|
171
|
+
globalThis.__stopDone = true;
|
|
172
|
+
)");
|
|
173
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
174
|
+
|
|
175
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
176
|
+
JSValue done = JS_GetPropertyStr(ctx, global, "__stopDone");
|
|
177
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, done), "stop should complete without error");
|
|
178
|
+
JS_FreeValue(ctx, done);
|
|
179
|
+
JS_FreeValue(ctx, global);
|
|
180
|
+
teardown();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* ── setTimezone does not crash ───────────────────────────────────── */
|
|
184
|
+
|
|
185
|
+
TEST_CASE("native:sntp setTimezone sets TZ without error", "[modules]") {
|
|
186
|
+
setup();
|
|
187
|
+
|
|
188
|
+
JSValue ret = eval_module(R"(
|
|
189
|
+
import { setTimezone } from "native:sntp";
|
|
190
|
+
setTimezone("CET-1CEST,M3.5.0,M10.5.0/3");
|
|
191
|
+
globalThis.__tzDone = true;
|
|
192
|
+
)");
|
|
193
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
194
|
+
|
|
195
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
196
|
+
JSValue done = JS_GetPropertyStr(ctx, global, "__tzDone");
|
|
197
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, done), "setTimezone should complete without error");
|
|
198
|
+
JS_FreeValue(ctx, done);
|
|
199
|
+
JS_FreeValue(ctx, global);
|
|
200
|
+
teardown();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* ── sync rejects invalid server array ────────────────────────────── */
|
|
204
|
+
|
|
205
|
+
TEST_CASE("native:sntp sync throws on invalid servers argument", "[modules]") {
|
|
206
|
+
setup();
|
|
207
|
+
|
|
208
|
+
JSValue ret = eval_module(R"(
|
|
209
|
+
import { sync } from "native:sntp";
|
|
210
|
+
try {
|
|
211
|
+
sync("not-an-array", "UTC0", false);
|
|
212
|
+
globalThis.__threw = false;
|
|
213
|
+
} catch (e) {
|
|
214
|
+
globalThis.__threw = true;
|
|
215
|
+
}
|
|
216
|
+
)");
|
|
217
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
218
|
+
|
|
219
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
220
|
+
JSValue threw = JS_GetPropertyStr(ctx, global, "__threw");
|
|
221
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, threw), "sync should throw on non-array servers");
|
|
222
|
+
JS_FreeValue(ctx, threw);
|
|
223
|
+
JS_FreeValue(ctx, global);
|
|
224
|
+
teardown();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* ── sync throws on empty server array ────────────────────────────── */
|
|
228
|
+
|
|
229
|
+
TEST_CASE("native:sntp sync throws on empty servers array", "[modules]") {
|
|
230
|
+
setup();
|
|
231
|
+
|
|
232
|
+
JSValue ret = eval_module(R"(
|
|
233
|
+
import { sync } from "native:sntp";
|
|
234
|
+
try {
|
|
235
|
+
sync([], "UTC0", false);
|
|
236
|
+
globalThis.__threw = false;
|
|
237
|
+
} catch (e) {
|
|
238
|
+
globalThis.__threw = true;
|
|
239
|
+
}
|
|
240
|
+
)");
|
|
241
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
242
|
+
|
|
243
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
244
|
+
JSValue threw = JS_GetPropertyStr(ctx, global, "__threw");
|
|
245
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, threw), "sync should throw on empty servers array");
|
|
246
|
+
JS_FreeValue(ctx, threw);
|
|
247
|
+
JS_FreeValue(ctx, global);
|
|
248
|
+
teardown();
|
|
249
|
+
}
|
|
@@ -0,0 +1,449 @@
|
|
|
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
|
+
/* ── stdout.write ─────────────────────────────────────────────────── */
|
|
26
|
+
|
|
27
|
+
TEST_CASE("stdout.write with string does not throw", "[stdio]") {
|
|
28
|
+
setup();
|
|
29
|
+
|
|
30
|
+
JSValue ret = eval_module(R"(
|
|
31
|
+
import { stdout } from "native:stdio";
|
|
32
|
+
stdout.write("hello from test\n");
|
|
33
|
+
globalThis.__ok = true;
|
|
34
|
+
)");
|
|
35
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
36
|
+
|
|
37
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
38
|
+
JSValue ok = JS_GetPropertyStr(ctx, global, "__ok");
|
|
39
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, ok), "stdout.write with string should succeed");
|
|
40
|
+
JS_FreeValue(ctx, ok);
|
|
41
|
+
JS_FreeValue(ctx, global);
|
|
42
|
+
|
|
43
|
+
teardown();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
TEST_CASE("stdout.write with Uint8Array does not throw", "[stdio]") {
|
|
47
|
+
setup();
|
|
48
|
+
|
|
49
|
+
JSValue ret = eval_module(R"(
|
|
50
|
+
import { stdout } from "native:stdio";
|
|
51
|
+
const enc = new TextEncoder();
|
|
52
|
+
stdout.write(enc.encode("binary data\n"));
|
|
53
|
+
globalThis.__ok = true;
|
|
54
|
+
)");
|
|
55
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
56
|
+
|
|
57
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
58
|
+
JSValue ok = JS_GetPropertyStr(ctx, global, "__ok");
|
|
59
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, ok), "stdout.write with Uint8Array should succeed");
|
|
60
|
+
JS_FreeValue(ctx, ok);
|
|
61
|
+
JS_FreeValue(ctx, global);
|
|
62
|
+
|
|
63
|
+
teardown();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
TEST_CASE("stdout.write returns undefined", "[stdio]") {
|
|
67
|
+
setup();
|
|
68
|
+
|
|
69
|
+
JSValue ret = eval_module(R"(
|
|
70
|
+
import { stdout } from "native:stdio";
|
|
71
|
+
globalThis.__result = stdout.write("test");
|
|
72
|
+
globalThis.__isUndef = globalThis.__result === undefined;
|
|
73
|
+
)");
|
|
74
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
75
|
+
|
|
76
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
77
|
+
JSValue isUndef = JS_GetPropertyStr(ctx, global, "__isUndef");
|
|
78
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, isUndef), "stdout.write should return undefined");
|
|
79
|
+
JS_FreeValue(ctx, isUndef);
|
|
80
|
+
JS_FreeValue(ctx, global);
|
|
81
|
+
|
|
82
|
+
teardown();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
TEST_CASE("stdout.write with empty string does not throw", "[stdio]") {
|
|
86
|
+
setup();
|
|
87
|
+
|
|
88
|
+
JSValue ret = eval_module(R"(
|
|
89
|
+
import { stdout } from "native:stdio";
|
|
90
|
+
stdout.write("");
|
|
91
|
+
globalThis.__ok = true;
|
|
92
|
+
)");
|
|
93
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
94
|
+
|
|
95
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
96
|
+
JSValue ok = JS_GetPropertyStr(ctx, global, "__ok");
|
|
97
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, ok), "stdout.write with empty string should succeed");
|
|
98
|
+
JS_FreeValue(ctx, ok);
|
|
99
|
+
JS_FreeValue(ctx, global);
|
|
100
|
+
|
|
101
|
+
teardown();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
TEST_CASE("stdout.write with empty Uint8Array does not throw", "[stdio]") {
|
|
105
|
+
setup();
|
|
106
|
+
|
|
107
|
+
JSValue ret = eval_module(R"(
|
|
108
|
+
import { stdout } from "native:stdio";
|
|
109
|
+
stdout.write(new Uint8Array(0));
|
|
110
|
+
globalThis.__ok = true;
|
|
111
|
+
)");
|
|
112
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
113
|
+
|
|
114
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
115
|
+
JSValue ok = JS_GetPropertyStr(ctx, global, "__ok");
|
|
116
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, ok),
|
|
117
|
+
"stdout.write with empty Uint8Array should succeed");
|
|
118
|
+
JS_FreeValue(ctx, ok);
|
|
119
|
+
JS_FreeValue(ctx, global);
|
|
120
|
+
|
|
121
|
+
teardown();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* ── stdout.flush ─────────────────────────────────────────────────── */
|
|
125
|
+
|
|
126
|
+
TEST_CASE("stdout.flush does not throw", "[stdio]") {
|
|
127
|
+
setup();
|
|
128
|
+
|
|
129
|
+
JSValue ret = eval_module(R"(
|
|
130
|
+
import { stdout } from "native:stdio";
|
|
131
|
+
stdout.flush();
|
|
132
|
+
globalThis.__ok = true;
|
|
133
|
+
)");
|
|
134
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
135
|
+
|
|
136
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
137
|
+
JSValue ok = JS_GetPropertyStr(ctx, global, "__ok");
|
|
138
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, ok), "stdout.flush should succeed");
|
|
139
|
+
JS_FreeValue(ctx, ok);
|
|
140
|
+
JS_FreeValue(ctx, global);
|
|
141
|
+
|
|
142
|
+
teardown();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
TEST_CASE("stdout.flush returns undefined", "[stdio]") {
|
|
146
|
+
setup();
|
|
147
|
+
|
|
148
|
+
JSValue ret = eval_module(R"(
|
|
149
|
+
import { stdout } from "native:stdio";
|
|
150
|
+
globalThis.__isUndef = stdout.flush() === undefined;
|
|
151
|
+
)");
|
|
152
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
153
|
+
|
|
154
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
155
|
+
JSValue isUndef = JS_GetPropertyStr(ctx, global, "__isUndef");
|
|
156
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, isUndef), "stdout.flush should return undefined");
|
|
157
|
+
JS_FreeValue(ctx, isUndef);
|
|
158
|
+
JS_FreeValue(ctx, global);
|
|
159
|
+
|
|
160
|
+
teardown();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* ── stdin.read ───────────────────────────────────────────────────── */
|
|
164
|
+
|
|
165
|
+
TEST_CASE("stdin.read returns undefined when no data available", "[stdio]") {
|
|
166
|
+
setup();
|
|
167
|
+
|
|
168
|
+
JSValue ret = eval_module(R"(
|
|
169
|
+
import { stdin } from "native:stdio";
|
|
170
|
+
const data = stdin.read();
|
|
171
|
+
globalThis.__isUndef = data === undefined;
|
|
172
|
+
)");
|
|
173
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
174
|
+
|
|
175
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
176
|
+
JSValue isUndef = JS_GetPropertyStr(ctx, global, "__isUndef");
|
|
177
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, isUndef),
|
|
178
|
+
"stdin.read should return undefined when no data is available");
|
|
179
|
+
JS_FreeValue(ctx, isUndef);
|
|
180
|
+
JS_FreeValue(ctx, global);
|
|
181
|
+
|
|
182
|
+
teardown();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* ── stdin.setHandler / clearHandler ──────────────────────────────── */
|
|
186
|
+
|
|
187
|
+
TEST_CASE("stdin.setHandler accepts a function", "[stdio]") {
|
|
188
|
+
setup();
|
|
189
|
+
|
|
190
|
+
JSValue ret = eval_module(R"(
|
|
191
|
+
import { stdin } from "native:stdio";
|
|
192
|
+
stdin.setHandler(() => {});
|
|
193
|
+
globalThis.__ok = true;
|
|
194
|
+
)");
|
|
195
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
196
|
+
|
|
197
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
198
|
+
JSValue ok = JS_GetPropertyStr(ctx, global, "__ok");
|
|
199
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, ok), "stdin.setHandler should accept a function");
|
|
200
|
+
JS_FreeValue(ctx, ok);
|
|
201
|
+
JS_FreeValue(ctx, global);
|
|
202
|
+
|
|
203
|
+
teardown();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
TEST_CASE("stdin.setHandler throws TypeError for non-function", "[stdio]") {
|
|
207
|
+
setup();
|
|
208
|
+
|
|
209
|
+
JSValue ret = eval_module(R"(
|
|
210
|
+
import { stdin } from "native:stdio";
|
|
211
|
+
try {
|
|
212
|
+
stdin.setHandler("not a function");
|
|
213
|
+
globalThis.__threw = false;
|
|
214
|
+
} catch (e) {
|
|
215
|
+
globalThis.__threw = true;
|
|
216
|
+
globalThis.__isTypeError = e instanceof TypeError;
|
|
217
|
+
}
|
|
218
|
+
)");
|
|
219
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw at top level");
|
|
220
|
+
|
|
221
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
222
|
+
|
|
223
|
+
JSValue threw = JS_GetPropertyStr(ctx, global, "__threw");
|
|
224
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, threw),
|
|
225
|
+
"stdin.setHandler should throw for non-function");
|
|
226
|
+
JS_FreeValue(ctx, threw);
|
|
227
|
+
|
|
228
|
+
JSValue isTypeError = JS_GetPropertyStr(ctx, global, "__isTypeError");
|
|
229
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, isTypeError),
|
|
230
|
+
"stdin.setHandler should throw a TypeError");
|
|
231
|
+
JS_FreeValue(ctx, isTypeError);
|
|
232
|
+
|
|
233
|
+
JS_FreeValue(ctx, global);
|
|
234
|
+
|
|
235
|
+
teardown();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
TEST_CASE("stdin.clearHandler does not throw when no handler set", "[stdio]") {
|
|
239
|
+
setup();
|
|
240
|
+
|
|
241
|
+
JSValue ret = eval_module(R"(
|
|
242
|
+
import { stdin } from "native:stdio";
|
|
243
|
+
stdin.clearHandler();
|
|
244
|
+
globalThis.__ok = true;
|
|
245
|
+
)");
|
|
246
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
247
|
+
|
|
248
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
249
|
+
JSValue ok = JS_GetPropertyStr(ctx, global, "__ok");
|
|
250
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, ok),
|
|
251
|
+
"stdin.clearHandler should be safe when no handler is set");
|
|
252
|
+
JS_FreeValue(ctx, ok);
|
|
253
|
+
JS_FreeValue(ctx, global);
|
|
254
|
+
|
|
255
|
+
teardown();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
TEST_CASE("stdin.clearHandler unregisters a previously set handler", "[stdio]") {
|
|
259
|
+
setup();
|
|
260
|
+
|
|
261
|
+
JSValue ret = eval_module(R"(
|
|
262
|
+
import { stdin } from "native:stdio";
|
|
263
|
+
stdin.setHandler(() => {});
|
|
264
|
+
stdin.clearHandler();
|
|
265
|
+
globalThis.__ok = true;
|
|
266
|
+
)");
|
|
267
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
268
|
+
|
|
269
|
+
/* Verify at the C level that on_data was reset to JS_UNDEFINED */
|
|
270
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
271
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_IsUndefined(mik_rt->stdin_state.on_data),
|
|
272
|
+
"on_data should be JS_UNDEFINED after clearHandler");
|
|
273
|
+
|
|
274
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
275
|
+
JSValue ok = JS_GetPropertyStr(ctx, global, "__ok");
|
|
276
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, ok), "clearHandler after setHandler should succeed");
|
|
277
|
+
JS_FreeValue(ctx, ok);
|
|
278
|
+
JS_FreeValue(ctx, global);
|
|
279
|
+
|
|
280
|
+
teardown();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
TEST_CASE("stdin.setHandler replaces previous handler", "[stdio]") {
|
|
284
|
+
setup();
|
|
285
|
+
|
|
286
|
+
JSValue ret = eval_module(R"(
|
|
287
|
+
import { stdin } from "native:stdio";
|
|
288
|
+
stdin.setHandler(() => { globalThis.__first = true; });
|
|
289
|
+
stdin.setHandler(() => { globalThis.__second = true; });
|
|
290
|
+
globalThis.__ok = true;
|
|
291
|
+
)");
|
|
292
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
293
|
+
|
|
294
|
+
/* Verify handler was set (on_data should not be undefined) */
|
|
295
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
296
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsUndefined(mik_rt->stdin_state.on_data),
|
|
297
|
+
"on_data should be set after setHandler");
|
|
298
|
+
|
|
299
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
300
|
+
JSValue ok = JS_GetPropertyStr(ctx, global, "__ok");
|
|
301
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, ok), "Replacing handler should succeed");
|
|
302
|
+
JS_FreeValue(ctx, ok);
|
|
303
|
+
JS_FreeValue(ctx, global);
|
|
304
|
+
|
|
305
|
+
teardown();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* ── mik__stdin_consume (C-level event loop integration) ──────────── */
|
|
309
|
+
|
|
310
|
+
TEST_CASE("mik__stdin_consume is a no-op when no handler is registered", "[stdio]") {
|
|
311
|
+
setup();
|
|
312
|
+
|
|
313
|
+
/* Call consume with no handler — should not crash or throw */
|
|
314
|
+
mik__stdin_consume(ctx);
|
|
315
|
+
|
|
316
|
+
/* Verify on_data is still undefined */
|
|
317
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
318
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_IsUndefined(mik_rt->stdin_state.on_data),
|
|
319
|
+
"on_data should remain JS_UNDEFINED");
|
|
320
|
+
|
|
321
|
+
teardown();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* ── Module exports shape ─────────────────────────────────────────── */
|
|
325
|
+
|
|
326
|
+
TEST_CASE("native:stdio exports stdout and stdin objects", "[stdio]") {
|
|
327
|
+
setup();
|
|
328
|
+
|
|
329
|
+
JSValue ret = eval_module(R"(
|
|
330
|
+
import { stdout, stdin } from "native:stdio";
|
|
331
|
+
globalThis.__hasStdout = typeof stdout === "object" && stdout !== null;
|
|
332
|
+
globalThis.__hasStdin = typeof stdin === "object" && stdin !== null;
|
|
333
|
+
)");
|
|
334
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Importing native:stdio should not throw");
|
|
335
|
+
|
|
336
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
337
|
+
|
|
338
|
+
JSValue v;
|
|
339
|
+
v = JS_GetPropertyStr(ctx, global, "__hasStdout");
|
|
340
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "stdout should be an object");
|
|
341
|
+
JS_FreeValue(ctx, v);
|
|
342
|
+
|
|
343
|
+
v = JS_GetPropertyStr(ctx, global, "__hasStdin");
|
|
344
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "stdin should be an object");
|
|
345
|
+
JS_FreeValue(ctx, v);
|
|
346
|
+
|
|
347
|
+
JS_FreeValue(ctx, global);
|
|
348
|
+
|
|
349
|
+
teardown();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
TEST_CASE("stdout has write and flush functions", "[stdio]") {
|
|
353
|
+
setup();
|
|
354
|
+
|
|
355
|
+
JSValue ret = eval_module(R"(
|
|
356
|
+
import { stdout } from "native:stdio";
|
|
357
|
+
globalThis.__hasWrite = typeof stdout.write === "function";
|
|
358
|
+
globalThis.__hasFlush = typeof stdout.flush === "function";
|
|
359
|
+
)");
|
|
360
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
361
|
+
|
|
362
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
363
|
+
|
|
364
|
+
JSValue v;
|
|
365
|
+
v = JS_GetPropertyStr(ctx, global, "__hasWrite");
|
|
366
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "stdout.write should be a function");
|
|
367
|
+
JS_FreeValue(ctx, v);
|
|
368
|
+
|
|
369
|
+
v = JS_GetPropertyStr(ctx, global, "__hasFlush");
|
|
370
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "stdout.flush should be a function");
|
|
371
|
+
JS_FreeValue(ctx, v);
|
|
372
|
+
|
|
373
|
+
JS_FreeValue(ctx, global);
|
|
374
|
+
|
|
375
|
+
teardown();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
TEST_CASE("stdin has read, setHandler, and clearHandler functions", "[stdio]") {
|
|
379
|
+
setup();
|
|
380
|
+
|
|
381
|
+
JSValue ret = eval_module(R"(
|
|
382
|
+
import { stdin } from "native:stdio";
|
|
383
|
+
globalThis.__hasRead = typeof stdin.read === "function";
|
|
384
|
+
globalThis.__hasSetHandler = typeof stdin.setHandler === "function";
|
|
385
|
+
globalThis.__hasClearHandler = typeof stdin.clearHandler === "function";
|
|
386
|
+
)");
|
|
387
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
388
|
+
|
|
389
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
390
|
+
|
|
391
|
+
JSValue v;
|
|
392
|
+
v = JS_GetPropertyStr(ctx, global, "__hasRead");
|
|
393
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "stdin.read should be a function");
|
|
394
|
+
JS_FreeValue(ctx, v);
|
|
395
|
+
|
|
396
|
+
v = JS_GetPropertyStr(ctx, global, "__hasSetHandler");
|
|
397
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "stdin.setHandler should be a function");
|
|
398
|
+
JS_FreeValue(ctx, v);
|
|
399
|
+
|
|
400
|
+
v = JS_GetPropertyStr(ctx, global, "__hasClearHandler");
|
|
401
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "stdin.clearHandler should be a function");
|
|
402
|
+
JS_FreeValue(ctx, v);
|
|
403
|
+
|
|
404
|
+
JS_FreeValue(ctx, global);
|
|
405
|
+
|
|
406
|
+
teardown();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/* ── stdout.write coercion edge cases ─────────────────────────────── */
|
|
410
|
+
|
|
411
|
+
TEST_CASE("stdout.write coerces number to string", "[stdio]") {
|
|
412
|
+
setup();
|
|
413
|
+
|
|
414
|
+
JSValue ret = eval_module(R"(
|
|
415
|
+
import { stdout } from "native:stdio";
|
|
416
|
+
stdout.write(42);
|
|
417
|
+
globalThis.__ok = true;
|
|
418
|
+
)");
|
|
419
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
420
|
+
|
|
421
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
422
|
+
JSValue ok = JS_GetPropertyStr(ctx, global, "__ok");
|
|
423
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, ok),
|
|
424
|
+
"stdout.write with a number should not throw");
|
|
425
|
+
JS_FreeValue(ctx, ok);
|
|
426
|
+
JS_FreeValue(ctx, global);
|
|
427
|
+
|
|
428
|
+
teardown();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/* ── Runtime cleanup ──────────────────────────────────────────────── */
|
|
432
|
+
|
|
433
|
+
TEST_CASE("stdin handler is cleaned up on runtime free", "[stdio]") {
|
|
434
|
+
setup();
|
|
435
|
+
|
|
436
|
+
JSValue ret = eval_module(R"(
|
|
437
|
+
import { stdin } from "native:stdio";
|
|
438
|
+
stdin.setHandler((data) => {});
|
|
439
|
+
)");
|
|
440
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
441
|
+
|
|
442
|
+
/* Verify handler is set before teardown */
|
|
443
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
444
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsUndefined(mik_rt->stdin_state.on_data),
|
|
445
|
+
"on_data should be set before runtime free");
|
|
446
|
+
|
|
447
|
+
/* teardown will call MIK_FreeRuntime — should not leak or crash */
|
|
448
|
+
teardown();
|
|
449
|
+
}
|