@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,331 @@
|
|
|
1
|
+
#include "mikrojs.h"
|
|
2
|
+
#include "private.h"
|
|
3
|
+
#include "quickjs.h"
|
|
4
|
+
#include "unity.h"
|
|
5
|
+
|
|
6
|
+
/* RTC module is self-registered via MIK_REGISTER_MODULE and lazily initialized */
|
|
7
|
+
|
|
8
|
+
static MIKRuntime* rt;
|
|
9
|
+
static JSContext* ctx;
|
|
10
|
+
|
|
11
|
+
static void setup() {
|
|
12
|
+
rt = MIK_NewRuntime();
|
|
13
|
+
ctx = MIK_GetJSContext(rt);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static void teardown() { MIK_FreeRuntime(rt); }
|
|
17
|
+
|
|
18
|
+
static JSValue eval_module(const char* code) {
|
|
19
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikrojs/test", code, strlen(code));
|
|
20
|
+
if (!JS_IsException(ret)) {
|
|
21
|
+
JS_FreeValue(ctx, ret);
|
|
22
|
+
mik__execute_jobs(ctx);
|
|
23
|
+
}
|
|
24
|
+
return ret;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* ── Module structure tests ───────────────────────────────────────── */
|
|
28
|
+
|
|
29
|
+
TEST_CASE("native:rtc exports set, get, remove, clear, info", "[modules]") {
|
|
30
|
+
setup();
|
|
31
|
+
|
|
32
|
+
JSValue ret = eval_module(R"(
|
|
33
|
+
import { set, get, remove, clear, info } from "native:rtc";
|
|
34
|
+
globalThis.__setIsFunc = typeof set === "function";
|
|
35
|
+
globalThis.__getIsFunc = typeof get === "function";
|
|
36
|
+
globalThis.__removeIsFunc = typeof remove === "function";
|
|
37
|
+
globalThis.__clearIsFunc = typeof clear === "function";
|
|
38
|
+
globalThis.__infoIsFunc = typeof info === "function";
|
|
39
|
+
)");
|
|
40
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
41
|
+
|
|
42
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
43
|
+
|
|
44
|
+
JSValue v;
|
|
45
|
+
v = JS_GetPropertyStr(ctx, global, "__setIsFunc");
|
|
46
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "set should be a function");
|
|
47
|
+
JS_FreeValue(ctx, v);
|
|
48
|
+
|
|
49
|
+
v = JS_GetPropertyStr(ctx, global, "__getIsFunc");
|
|
50
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "get should be a function");
|
|
51
|
+
JS_FreeValue(ctx, v);
|
|
52
|
+
|
|
53
|
+
v = JS_GetPropertyStr(ctx, global, "__removeIsFunc");
|
|
54
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "remove should be a function");
|
|
55
|
+
JS_FreeValue(ctx, v);
|
|
56
|
+
|
|
57
|
+
v = JS_GetPropertyStr(ctx, global, "__clearIsFunc");
|
|
58
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "clear should be a function");
|
|
59
|
+
JS_FreeValue(ctx, v);
|
|
60
|
+
|
|
61
|
+
v = JS_GetPropertyStr(ctx, global, "__infoIsFunc");
|
|
62
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "info should be a function");
|
|
63
|
+
JS_FreeValue(ctx, v);
|
|
64
|
+
|
|
65
|
+
JS_FreeValue(ctx, global);
|
|
66
|
+
teardown();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* ── set/get round-trip ───────────────────────────────────────────── */
|
|
70
|
+
|
|
71
|
+
TEST_CASE("native:rtc set/get round-trip", "[modules]") {
|
|
72
|
+
setup();
|
|
73
|
+
|
|
74
|
+
JSValue ret = eval_module(R"(
|
|
75
|
+
import { set, get, clear } from "native:rtc";
|
|
76
|
+
clear();
|
|
77
|
+
set("hello", "world");
|
|
78
|
+
globalThis.__result = get("hello");
|
|
79
|
+
)");
|
|
80
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
81
|
+
|
|
82
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
83
|
+
JSValue result = JS_GetPropertyStr(ctx, global, "__result");
|
|
84
|
+
const char* str = JS_ToCString(ctx, result);
|
|
85
|
+
TEST_ASSERT_EQUAL_STRING("world", str);
|
|
86
|
+
JS_FreeCString(ctx, str);
|
|
87
|
+
JS_FreeValue(ctx, result);
|
|
88
|
+
JS_FreeValue(ctx, global);
|
|
89
|
+
teardown();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ── get returns undefined for missing key ────────────────────────── */
|
|
93
|
+
|
|
94
|
+
TEST_CASE("native:rtc get returns undefined for missing key", "[modules]") {
|
|
95
|
+
setup();
|
|
96
|
+
|
|
97
|
+
JSValue ret = eval_module(R"(
|
|
98
|
+
import { get, clear } from "native:rtc";
|
|
99
|
+
clear();
|
|
100
|
+
globalThis.__result = get("nonexistent");
|
|
101
|
+
globalThis.__isUndef = get("nonexistent") === undefined;
|
|
102
|
+
)");
|
|
103
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
104
|
+
|
|
105
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
106
|
+
JSValue isUndef = JS_GetPropertyStr(ctx, global, "__isUndef");
|
|
107
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, isUndef), "get should return undefined for missing key");
|
|
108
|
+
JS_FreeValue(ctx, isUndef);
|
|
109
|
+
JS_FreeValue(ctx, global);
|
|
110
|
+
teardown();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* ── remove deletes entry ─────────────────────────────────────────── */
|
|
114
|
+
|
|
115
|
+
TEST_CASE("native:rtc remove deletes entry", "[modules]") {
|
|
116
|
+
setup();
|
|
117
|
+
|
|
118
|
+
JSValue ret = eval_module(R"(
|
|
119
|
+
import { set, get, remove, clear } from "native:rtc";
|
|
120
|
+
clear();
|
|
121
|
+
set("key1", "val1");
|
|
122
|
+
set("key2", "val2");
|
|
123
|
+
const removed = remove("key1");
|
|
124
|
+
globalThis.__removed = removed;
|
|
125
|
+
globalThis.__key1Gone = get("key1") === undefined;
|
|
126
|
+
globalThis.__key2Still = get("key2") === "val2";
|
|
127
|
+
)");
|
|
128
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
129
|
+
|
|
130
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
131
|
+
|
|
132
|
+
JSValue removed = JS_GetPropertyStr(ctx, global, "__removed");
|
|
133
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, removed), "remove should return true");
|
|
134
|
+
JS_FreeValue(ctx, removed);
|
|
135
|
+
|
|
136
|
+
JSValue key1Gone = JS_GetPropertyStr(ctx, global, "__key1Gone");
|
|
137
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, key1Gone), "key1 should be gone after remove");
|
|
138
|
+
JS_FreeValue(ctx, key1Gone);
|
|
139
|
+
|
|
140
|
+
JSValue key2Still = JS_GetPropertyStr(ctx, global, "__key2Still");
|
|
141
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, key2Still), "key2 should still exist");
|
|
142
|
+
JS_FreeValue(ctx, key2Still);
|
|
143
|
+
|
|
144
|
+
JS_FreeValue(ctx, global);
|
|
145
|
+
teardown();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* ── remove returns false for missing key ─────────────────────────── */
|
|
149
|
+
|
|
150
|
+
TEST_CASE("native:rtc remove returns false for missing key", "[modules]") {
|
|
151
|
+
setup();
|
|
152
|
+
|
|
153
|
+
JSValue ret = eval_module(R"(
|
|
154
|
+
import { remove, clear } from "native:rtc";
|
|
155
|
+
clear();
|
|
156
|
+
globalThis.__result = remove("nonexistent");
|
|
157
|
+
)");
|
|
158
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
159
|
+
|
|
160
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
161
|
+
JSValue result = JS_GetPropertyStr(ctx, global, "__result");
|
|
162
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_ToBool(ctx, result), "remove should return false for missing key");
|
|
163
|
+
JS_FreeValue(ctx, result);
|
|
164
|
+
JS_FreeValue(ctx, global);
|
|
165
|
+
teardown();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* ── clear removes all entries ────────────────────────────────────── */
|
|
169
|
+
|
|
170
|
+
TEST_CASE("native:rtc clear removes all entries", "[modules]") {
|
|
171
|
+
setup();
|
|
172
|
+
|
|
173
|
+
JSValue ret = eval_module(R"(
|
|
174
|
+
import { set, get, clear, info } from "native:rtc";
|
|
175
|
+
clear();
|
|
176
|
+
set("a", "1");
|
|
177
|
+
set("b", "2");
|
|
178
|
+
clear();
|
|
179
|
+
globalThis.__aGone = get("a") === undefined;
|
|
180
|
+
globalThis.__bGone = get("b") === undefined;
|
|
181
|
+
const i = info();
|
|
182
|
+
globalThis.__entries = i.entries;
|
|
183
|
+
globalThis.__used = i.used;
|
|
184
|
+
)");
|
|
185
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
186
|
+
|
|
187
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
188
|
+
|
|
189
|
+
JSValue aGone = JS_GetPropertyStr(ctx, global, "__aGone");
|
|
190
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, aGone), "a should be gone after clear");
|
|
191
|
+
JS_FreeValue(ctx, aGone);
|
|
192
|
+
|
|
193
|
+
JSValue bGone = JS_GetPropertyStr(ctx, global, "__bGone");
|
|
194
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, bGone), "b should be gone after clear");
|
|
195
|
+
JS_FreeValue(ctx, bGone);
|
|
196
|
+
|
|
197
|
+
JSValue entries = JS_GetPropertyStr(ctx, global, "__entries");
|
|
198
|
+
int32_t count;
|
|
199
|
+
JS_ToInt32(ctx, &count, entries);
|
|
200
|
+
TEST_ASSERT_EQUAL_INT(0, count);
|
|
201
|
+
JS_FreeValue(ctx, entries);
|
|
202
|
+
|
|
203
|
+
JSValue used = JS_GetPropertyStr(ctx, global, "__used");
|
|
204
|
+
int32_t used_val;
|
|
205
|
+
JS_ToInt32(ctx, &used_val, used);
|
|
206
|
+
TEST_ASSERT_EQUAL_INT(0, used_val);
|
|
207
|
+
JS_FreeValue(ctx, used);
|
|
208
|
+
|
|
209
|
+
JS_FreeValue(ctx, global);
|
|
210
|
+
teardown();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* ── info returns correct structure ───────────────────────────────── */
|
|
214
|
+
|
|
215
|
+
TEST_CASE("native:rtc info returns used, total, entries", "[modules]") {
|
|
216
|
+
setup();
|
|
217
|
+
|
|
218
|
+
JSValue ret = eval_module(R"(
|
|
219
|
+
import { set, info, clear } from "native:rtc";
|
|
220
|
+
clear();
|
|
221
|
+
set("key", "value");
|
|
222
|
+
const i = info();
|
|
223
|
+
globalThis.__hasUsed = "used" in i;
|
|
224
|
+
globalThis.__hasTotal = "total" in i;
|
|
225
|
+
globalThis.__hasEntries = "entries" in i;
|
|
226
|
+
globalThis.__entries = i.entries;
|
|
227
|
+
globalThis.__totalGt0 = i.total > 0;
|
|
228
|
+
globalThis.__usedGt0 = i.used > 0;
|
|
229
|
+
)");
|
|
230
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
231
|
+
|
|
232
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
233
|
+
|
|
234
|
+
JSValue v;
|
|
235
|
+
v = JS_GetPropertyStr(ctx, global, "__hasUsed");
|
|
236
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "info should have 'used'");
|
|
237
|
+
JS_FreeValue(ctx, v);
|
|
238
|
+
|
|
239
|
+
v = JS_GetPropertyStr(ctx, global, "__hasTotal");
|
|
240
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "info should have 'total'");
|
|
241
|
+
JS_FreeValue(ctx, v);
|
|
242
|
+
|
|
243
|
+
v = JS_GetPropertyStr(ctx, global, "__hasEntries");
|
|
244
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "info should have 'entries'");
|
|
245
|
+
JS_FreeValue(ctx, v);
|
|
246
|
+
|
|
247
|
+
JSValue entries = JS_GetPropertyStr(ctx, global, "__entries");
|
|
248
|
+
int32_t count;
|
|
249
|
+
JS_ToInt32(ctx, &count, entries);
|
|
250
|
+
TEST_ASSERT_EQUAL_INT(1, count);
|
|
251
|
+
JS_FreeValue(ctx, entries);
|
|
252
|
+
|
|
253
|
+
v = JS_GetPropertyStr(ctx, global, "__totalGt0");
|
|
254
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "total should be > 0");
|
|
255
|
+
JS_FreeValue(ctx, v);
|
|
256
|
+
|
|
257
|
+
v = JS_GetPropertyStr(ctx, global, "__usedGt0");
|
|
258
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "used should be > 0 after set");
|
|
259
|
+
JS_FreeValue(ctx, v);
|
|
260
|
+
|
|
261
|
+
JS_FreeValue(ctx, global);
|
|
262
|
+
teardown();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* ── set overwrites existing key ──────────────────────────────────── */
|
|
266
|
+
|
|
267
|
+
TEST_CASE("native:rtc set overwrites existing key", "[modules]") {
|
|
268
|
+
setup();
|
|
269
|
+
|
|
270
|
+
JSValue ret = eval_module(R"(
|
|
271
|
+
import { set, get, info, clear } from "native:rtc";
|
|
272
|
+
clear();
|
|
273
|
+
set("key", "old");
|
|
274
|
+
set("key", "new");
|
|
275
|
+
globalThis.__result = get("key");
|
|
276
|
+
globalThis.__entries = info().entries;
|
|
277
|
+
)");
|
|
278
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
279
|
+
|
|
280
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
281
|
+
|
|
282
|
+
JSValue result = JS_GetPropertyStr(ctx, global, "__result");
|
|
283
|
+
const char* str = JS_ToCString(ctx, result);
|
|
284
|
+
TEST_ASSERT_EQUAL_STRING("new", str);
|
|
285
|
+
JS_FreeCString(ctx, str);
|
|
286
|
+
JS_FreeValue(ctx, result);
|
|
287
|
+
|
|
288
|
+
JSValue entries = JS_GetPropertyStr(ctx, global, "__entries");
|
|
289
|
+
int32_t count;
|
|
290
|
+
JS_ToInt32(ctx, &count, entries);
|
|
291
|
+
TEST_ASSERT_EQUAL_INT(1, count);
|
|
292
|
+
JS_FreeValue(ctx, entries);
|
|
293
|
+
|
|
294
|
+
JS_FreeValue(ctx, global);
|
|
295
|
+
teardown();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* ── set throws on overflow ───────────────────────────────────────── */
|
|
299
|
+
|
|
300
|
+
TEST_CASE("native:rtc set returns an error Result when storage is full", "[modules]") {
|
|
301
|
+
setup();
|
|
302
|
+
|
|
303
|
+
/* native:rtc set returns a Result rather than throwing. A 2100-byte
|
|
304
|
+
* value is well under MIK_RTC_MAX_VAL_LEN (64KB) but larger than the
|
|
305
|
+
* RTC store capacity, so the error is MIK_ERR_KV_STORAGE_FULL
|
|
306
|
+
* (0x8000 + 0xD3 = 0x80D3), not KV_TOO_LARGE. */
|
|
307
|
+
JSValue ret = eval_module(R"(
|
|
308
|
+
import { set, clear } from "native:rtc";
|
|
309
|
+
clear();
|
|
310
|
+
const big = "x".repeat(2100);
|
|
311
|
+
const result = set("big", big);
|
|
312
|
+
globalThis.__ok = result.ok;
|
|
313
|
+
globalThis.__errorCode = result.ok ? 0 : result.error.code;
|
|
314
|
+
)");
|
|
315
|
+
bool evalOk = !JS_IsException(ret);
|
|
316
|
+
|
|
317
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
318
|
+
JSValue okVal = JS_GetPropertyStr(ctx, global, "__ok");
|
|
319
|
+
bool resultOk = JS_ToBool(ctx, okVal);
|
|
320
|
+
JS_FreeValue(ctx, okVal);
|
|
321
|
+
JSValue codeVal = JS_GetPropertyStr(ctx, global, "__errorCode");
|
|
322
|
+
int32_t errorCode = 0;
|
|
323
|
+
JS_ToInt32(ctx, &errorCode, codeVal);
|
|
324
|
+
JS_FreeValue(ctx, codeVal);
|
|
325
|
+
JS_FreeValue(ctx, global);
|
|
326
|
+
teardown();
|
|
327
|
+
|
|
328
|
+
TEST_ASSERT_TRUE_MESSAGE(evalOk, "Module eval should not throw");
|
|
329
|
+
TEST_ASSERT_FALSE_MESSAGE(resultOk, "set should return ok=false when over capacity");
|
|
330
|
+
TEST_ASSERT_EQUAL_INT32_MESSAGE(0x80D3, errorCode, "error code should be KV_STORAGE_FULL");
|
|
331
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#include "esp_flash.h"
|
|
2
|
+
#include "esp_system.h"
|
|
3
|
+
#include "mikrojs.h"
|
|
4
|
+
#include "quickjs.h"
|
|
5
|
+
#include "unity.h"
|
|
6
|
+
|
|
7
|
+
int jsval_as_int32(JSContext* ctx, JSValue val) {
|
|
8
|
+
int32_t num;
|
|
9
|
+
JS_ToInt32(ctx, &num, val);
|
|
10
|
+
JS_FreeValue(ctx, val);
|
|
11
|
+
return num;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
TEST_CASE("Simple eval works", "[runtime]") {
|
|
15
|
+
const auto rt = MIK_NewRuntime();
|
|
16
|
+
const auto ctx = MIK_GetJSContext(rt);
|
|
17
|
+
const char* code = "2+2";
|
|
18
|
+
const auto result = JS_Eval(ctx, code, strlen(code), "main.js", JS_EVAL_TYPE_GLOBAL);
|
|
19
|
+
|
|
20
|
+
TEST_ASSERT_TRUE(JS_IsNumber(result));
|
|
21
|
+
TEST_ASSERT_TRUE(JS_IsEqual(ctx, result, JS_NewNumber(ctx, 4)));
|
|
22
|
+
|
|
23
|
+
MIK_FreeRuntime(rt);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
TEST_CASE("Returns exception upon error", "[runtime]") {
|
|
27
|
+
const auto rt = MIK_NewRuntime();
|
|
28
|
+
const auto ctx = MIK_GetJSContext(rt);
|
|
29
|
+
|
|
30
|
+
const char* code = "this.will.fail";
|
|
31
|
+
|
|
32
|
+
const auto result = JS_Eval(ctx, code, strlen(code), "main.js", JS_EVAL_TYPE_GLOBAL);
|
|
33
|
+
JS_Eval(ctx, code, strlen(code), "main.js", JS_EVAL_TYPE_GLOBAL);
|
|
34
|
+
|
|
35
|
+
TEST_ASSERT_TRUE(JS_IsException(result));
|
|
36
|
+
JS_FreeValue(ctx, result);
|
|
37
|
+
MIK_FreeRuntime(rt);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
TEST_CASE("Defines setTimeout, clearTimeout, setInterval, clearInterval", "[runtime]") {
|
|
41
|
+
const auto rt = MIK_NewRuntime();
|
|
42
|
+
const auto ctx = MIK_GetJSContext(rt);
|
|
43
|
+
|
|
44
|
+
auto const eval = [ctx](const char* code) -> JSValue {
|
|
45
|
+
return JS_Eval(ctx, code, strlen(code), "main.js", JS_EVAL_TYPE_GLOBAL);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
TEST_ASSERT_EQUAL_STRING_MESSAGE("function", JS_ToCString(ctx, eval("typeof setTimeout")),
|
|
49
|
+
"Expected setTimeout to be defined");
|
|
50
|
+
TEST_ASSERT_EQUAL_STRING_MESSAGE("function", JS_ToCString(ctx, eval("typeof clearTimeout")),
|
|
51
|
+
"Expected clearTimeout to be defined");
|
|
52
|
+
TEST_ASSERT_EQUAL_STRING_MESSAGE("function", JS_ToCString(ctx, eval("typeof setInterval")),
|
|
53
|
+
"Expected setInterval to be defined");
|
|
54
|
+
TEST_ASSERT_EQUAL_STRING_MESSAGE("function", JS_ToCString(ctx, eval("typeof clearInterval")),
|
|
55
|
+
"Expected clearInterval to be defined");
|
|
56
|
+
|
|
57
|
+
MIK_FreeRuntime(rt);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
TEST_CASE("Does not leak memory when running timers", "[runtime]") {
|
|
61
|
+
const auto rt = MIK_NewRuntime();
|
|
62
|
+
const auto ctx = MIK_GetJSContext(rt);
|
|
63
|
+
|
|
64
|
+
const char* code = R"(
|
|
65
|
+
globalThis.ticks = 0;
|
|
66
|
+
const tick = () => {
|
|
67
|
+
globalThis.ticks++
|
|
68
|
+
setTimeout(() => tick(), 0)
|
|
69
|
+
}
|
|
70
|
+
setTimeout(() => tick(), 0)
|
|
71
|
+
)";
|
|
72
|
+
|
|
73
|
+
auto const eval = [ctx](const char* code) -> JSValue {
|
|
74
|
+
return JS_Eval(ctx, code, strlen(code), "main.js", JS_EVAL_TYPE_GLOBAL);
|
|
75
|
+
};
|
|
76
|
+
JSValue result = eval(code);
|
|
77
|
+
|
|
78
|
+
auto const free_heap_before = esp_get_minimum_free_heap_size();
|
|
79
|
+
for (int i = 0; i < 1000; i++) {
|
|
80
|
+
MIK_Loop(rt);
|
|
81
|
+
}
|
|
82
|
+
auto ticks = jsval_as_int32(ctx, eval("globalThis.ticks"));
|
|
83
|
+
|
|
84
|
+
TEST_ASSERT_EQUAL(ticks, 1000);
|
|
85
|
+
TEST_ASSERT_INT_WITHIN(1, free_heap_before, esp_get_minimum_free_heap_size());
|
|
86
|
+
|
|
87
|
+
JS_FreeValue(ctx, result);
|
|
88
|
+
MIK_FreeRuntime(rt);
|
|
89
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#include "mikrojs.h"
|
|
2
|
+
#include "private.h"
|
|
3
|
+
#include "quickjs.h"
|
|
4
|
+
#include "soc/soc_caps.h"
|
|
5
|
+
#include "unity.h"
|
|
6
|
+
|
|
7
|
+
/* Sleep module is self-registered via MIK_REGISTER_MODULE and lazily initialized */
|
|
8
|
+
|
|
9
|
+
static MIKRuntime* rt;
|
|
10
|
+
static JSContext* ctx;
|
|
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
|
+
static JSValue eval_module(const char* code) {
|
|
20
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikrojs/test", code, strlen(code));
|
|
21
|
+
if (!JS_IsException(ret)) {
|
|
22
|
+
JS_FreeValue(ctx, ret);
|
|
23
|
+
mik__execute_jobs(ctx);
|
|
24
|
+
}
|
|
25
|
+
return ret;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* ── Module exports ──────────────────────────────────────────────── */
|
|
29
|
+
|
|
30
|
+
TEST_CASE("native:sleep exports expected functions", "[modules]") {
|
|
31
|
+
setup();
|
|
32
|
+
|
|
33
|
+
JSValue ret = eval_module(R"(
|
|
34
|
+
import {
|
|
35
|
+
deepSleep, lightSleep, getWakeupCause,
|
|
36
|
+
enableTimerWakeup, enableGpioWakeup,
|
|
37
|
+
disableWakeupSource
|
|
38
|
+
} from "native:sleep";
|
|
39
|
+
globalThis.__deepSleep = typeof deepSleep === "function";
|
|
40
|
+
globalThis.__lightSleep = typeof lightSleep === "function";
|
|
41
|
+
globalThis.__getWakeupCause = typeof getWakeupCause === "function";
|
|
42
|
+
globalThis.__enableTimerWakeup = typeof enableTimerWakeup === "function";
|
|
43
|
+
globalThis.__enableGpioWakeup = typeof enableGpioWakeup === "function";
|
|
44
|
+
globalThis.__disableWakeupSource = typeof disableWakeupSource === "function";
|
|
45
|
+
)");
|
|
46
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
47
|
+
|
|
48
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
49
|
+
|
|
50
|
+
const char* names[] = {"__deepSleep", "__lightSleep", "__getWakeupCause",
|
|
51
|
+
"__enableTimerWakeup", "__enableGpioWakeup",
|
|
52
|
+
"__disableWakeupSource"};
|
|
53
|
+
for (int i = 0; i < 6; i++) {
|
|
54
|
+
JSValue v = JS_GetPropertyStr(ctx, global, names[i]);
|
|
55
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), names[i]);
|
|
56
|
+
JS_FreeValue(ctx, v);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
JS_FreeValue(ctx, global);
|
|
60
|
+
teardown();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* ── getWakeupCause returns a string ─────────────────────────────── */
|
|
64
|
+
|
|
65
|
+
TEST_CASE("native:sleep getWakeupCause returns a string", "[modules]") {
|
|
66
|
+
setup();
|
|
67
|
+
|
|
68
|
+
JSValue ret = eval_module(R"(
|
|
69
|
+
import { getWakeupCause } from "native:sleep";
|
|
70
|
+
const cause = getWakeupCause();
|
|
71
|
+
globalThis.__isString = typeof cause === "string";
|
|
72
|
+
globalThis.__cause = cause;
|
|
73
|
+
)");
|
|
74
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
75
|
+
|
|
76
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
77
|
+
|
|
78
|
+
JSValue isString = JS_GetPropertyStr(ctx, global, "__isString");
|
|
79
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, isString), "getWakeupCause should return a string");
|
|
80
|
+
JS_FreeValue(ctx, isString);
|
|
81
|
+
|
|
82
|
+
JSValue cause = JS_GetPropertyStr(ctx, global, "__cause");
|
|
83
|
+
const char* str = JS_ToCString(ctx, cause);
|
|
84
|
+
TEST_ASSERT_NOT_NULL_MESSAGE(str, "cause should be a valid string");
|
|
85
|
+
/* On fresh boot (no prior sleep), cause is typically "undefined" or "timer"
|
|
86
|
+
* depending on how the test harness rebooted. Just verify it's non-empty. */
|
|
87
|
+
TEST_ASSERT_GREATER_THAN_MESSAGE(0, strlen(str), "cause should be non-empty");
|
|
88
|
+
JS_FreeCString(ctx, str);
|
|
89
|
+
JS_FreeValue(ctx, cause);
|
|
90
|
+
|
|
91
|
+
JS_FreeValue(ctx, global);
|
|
92
|
+
teardown();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* ── enableTimerWakeup accepts microseconds ──────────────────────── */
|
|
96
|
+
|
|
97
|
+
TEST_CASE("native:sleep enableTimerWakeup does not throw", "[modules]") {
|
|
98
|
+
setup();
|
|
99
|
+
|
|
100
|
+
JSValue ret = eval_module(R"(
|
|
101
|
+
import { enableTimerWakeup } from "native:sleep";
|
|
102
|
+
enableTimerWakeup(5000000);
|
|
103
|
+
globalThis.__ok = true;
|
|
104
|
+
)");
|
|
105
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
106
|
+
|
|
107
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
108
|
+
JSValue ok = JS_GetPropertyStr(ctx, global, "__ok");
|
|
109
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, ok), "enableTimerWakeup should succeed");
|
|
110
|
+
JS_FreeValue(ctx, ok);
|
|
111
|
+
JS_FreeValue(ctx, global);
|
|
112
|
+
teardown();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* ── disableWakeupSource works ───────────────────────────────────── */
|
|
116
|
+
|
|
117
|
+
TEST_CASE("native:sleep disableWakeupSource clears all sources", "[modules]") {
|
|
118
|
+
setup();
|
|
119
|
+
|
|
120
|
+
JSValue ret = eval_module(R"(
|
|
121
|
+
import { enableTimerWakeup, disableWakeupSource } from "native:sleep";
|
|
122
|
+
enableTimerWakeup(1000000);
|
|
123
|
+
disableWakeupSource();
|
|
124
|
+
globalThis.__ok = true;
|
|
125
|
+
)");
|
|
126
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
127
|
+
|
|
128
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
129
|
+
JSValue ok = JS_GetPropertyStr(ctx, global, "__ok");
|
|
130
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, ok), "disableWakeupSource should succeed");
|
|
131
|
+
JS_FreeValue(ctx, ok);
|
|
132
|
+
JS_FreeValue(ctx, global);
|
|
133
|
+
teardown();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* ── disableWakeupSource with specific source ────────────────────── */
|
|
137
|
+
|
|
138
|
+
TEST_CASE("native:sleep disableWakeupSource with 'timer'", "[modules]") {
|
|
139
|
+
setup();
|
|
140
|
+
|
|
141
|
+
JSValue ret = eval_module(R"(
|
|
142
|
+
import { enableTimerWakeup, disableWakeupSource } from "native:sleep";
|
|
143
|
+
enableTimerWakeup(1000000);
|
|
144
|
+
disableWakeupSource("timer");
|
|
145
|
+
globalThis.__ok = true;
|
|
146
|
+
)");
|
|
147
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
148
|
+
|
|
149
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
150
|
+
JSValue ok = JS_GetPropertyStr(ctx, global, "__ok");
|
|
151
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, ok), "disableWakeupSource('timer') should succeed");
|
|
152
|
+
JS_FreeValue(ctx, ok);
|
|
153
|
+
JS_FreeValue(ctx, global);
|
|
154
|
+
teardown();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* ── disableWakeupSource throws on invalid source ────────────────── */
|
|
158
|
+
|
|
159
|
+
TEST_CASE("native:sleep disableWakeupSource throws on invalid source", "[modules]") {
|
|
160
|
+
setup();
|
|
161
|
+
|
|
162
|
+
JSValue ret = eval_module(R"(
|
|
163
|
+
import { disableWakeupSource } from "native:sleep";
|
|
164
|
+
try {
|
|
165
|
+
disableWakeupSource("invalid");
|
|
166
|
+
globalThis.__threw = false;
|
|
167
|
+
} catch (e) {
|
|
168
|
+
globalThis.__threw = true;
|
|
169
|
+
globalThis.__isRangeError = e instanceof RangeError;
|
|
170
|
+
}
|
|
171
|
+
)");
|
|
172
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
173
|
+
|
|
174
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
175
|
+
|
|
176
|
+
JSValue threw = JS_GetPropertyStr(ctx, global, "__threw");
|
|
177
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, threw),
|
|
178
|
+
"disableWakeupSource should throw on invalid source");
|
|
179
|
+
JS_FreeValue(ctx, threw);
|
|
180
|
+
|
|
181
|
+
JSValue isRange = JS_GetPropertyStr(ctx, global, "__isRangeError");
|
|
182
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, isRange), "error should be RangeError");
|
|
183
|
+
JS_FreeValue(ctx, isRange);
|
|
184
|
+
|
|
185
|
+
JS_FreeValue(ctx, global);
|
|
186
|
+
teardown();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* ── lightSleep with very short duration ─────────────────────────── */
|
|
190
|
+
/* Light sleep disconnects UART console on targets without USB Serial/JTAG,
|
|
191
|
+
causing the test monitor to stall. Only run on chips that have USB
|
|
192
|
+
Serial/JTAG which keeps the connection alive through light sleep. */
|
|
193
|
+
TEST_CASE("native:sleep lightSleep with 1ms returns or rejects gracefully", "[modules]") {
|
|
194
|
+
#if !SOC_USB_SERIAL_JTAG_SUPPORTED
|
|
195
|
+
TEST_IGNORE_MESSAGE("light sleep disconnects UART console — skipped on non-USB targets");
|
|
196
|
+
#endif
|
|
197
|
+
setup();
|
|
198
|
+
|
|
199
|
+
JSValue ret = eval_module(R"(
|
|
200
|
+
import { lightSleep } from "native:sleep";
|
|
201
|
+
try {
|
|
202
|
+
lightSleep(1);
|
|
203
|
+
globalThis.__result = "ok";
|
|
204
|
+
} catch (e) {
|
|
205
|
+
// Light sleep may fail on boards with USB JTAG or other constraints
|
|
206
|
+
globalThis.__result = "caught:" + e.message;
|
|
207
|
+
}
|
|
208
|
+
)");
|
|
209
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
210
|
+
|
|
211
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
212
|
+
JSValue result = JS_GetPropertyStr(ctx, global, "__result");
|
|
213
|
+
const char* str = JS_ToCString(ctx, result);
|
|
214
|
+
TEST_ASSERT_NOT_NULL_MESSAGE(str, "result should be set");
|
|
215
|
+
/* Either "ok" (sleep succeeded) or "caught:..." (threw a catchable error) — both are fine */
|
|
216
|
+
TEST_ASSERT_TRUE_MESSAGE(strncmp(str, "ok", 2) == 0 || strncmp(str, "caught:", 7) == 0,
|
|
217
|
+
"lightSleep should either succeed or throw a catchable error");
|
|
218
|
+
JS_FreeCString(ctx, str);
|
|
219
|
+
JS_FreeValue(ctx, result);
|
|
220
|
+
JS_FreeValue(ctx, global);
|
|
221
|
+
teardown();
|
|
222
|
+
}
|