@mikrojs/firmware 0.16.0 → 0.17.0-next.20260722220623
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/components/mikrojs/CMakeLists.txt +9 -2
- package/components/mikrojs/include/mikrojs_esp32.h +23 -0
- package/components/mikrojs/mik_config.cpp +159 -5
- package/components/mikrojs/mik_deploy.cpp +54 -35
- package/components/mikrojs/mik_main.cpp +66 -5
- package/components/mikrojs/mik_nvs_kv.cpp +64 -18
- package/components/mikrojs/mik_ota.cpp +1791 -0
- package/components/mikrojs/platform_esp32.cpp +59 -0
- package/components/mikrojs/test/CMakeLists.txt +1 -0
- package/components/mikrojs/test/ble_test.cpp +2 -2
- package/components/mikrojs/test/fs_js_test.cpp +1 -1
- package/components/mikrojs/test/fs_pub_test.cpp +23 -23
- package/components/mikrojs/test/http_server_test.cpp +12 -2
- package/components/mikrojs/test/http_test.cpp +2 -2
- package/components/mikrojs/test/modules_extended_test.cpp +2 -2
- package/components/mikrojs/test/modules_test.cpp +2 -2
- package/components/mikrojs/test/nvs_kv_test.cpp +245 -0
- package/components/mikrojs/test/ota_host/gunzip_host_test.cpp +118 -0
- package/components/mikrojs/test/ota_host/run.sh +174 -0
- package/components/mikrojs/test/rtc_test.cpp +1 -1
- package/components/mikrojs/test/sleep_test.cpp +1 -1
- package/components/mikrojs/test/sntp_test.cpp +1 -1
- package/components/mikrojs/test/stdio_test.cpp +1 -1
- package/components/mikrojs/test/sys_test.cpp +1 -1
- package/components/mikrojs/test/udp_test.cpp +5 -5
- package/components/mikrojs/test/wifi_test.cpp +2 -2
- package/package.json +3 -3
- package/prebuilds/esp32/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32/mikrojs.bin +0 -0
- package/prebuilds/esp32c3/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32c3/mikrojs.bin +0 -0
- package/prebuilds/esp32c5/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32c5/mikrojs.bin +0 -0
- package/prebuilds/esp32c6/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32c6/mikrojs.bin +0 -0
- package/prebuilds/esp32s3/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32s3/mikrojs.bin +0 -0
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
#include <esp_log.h>
|
|
5
5
|
#include <esp_heap_caps.h>
|
|
6
6
|
#include <esp_mac.h>
|
|
7
|
+
#include <nanocbor/nanocbor.h>
|
|
8
|
+
#include <nvs.h>
|
|
7
9
|
#include <esp_random.h>
|
|
8
10
|
#include <esp_sleep.h>
|
|
9
11
|
#include <esp_system.h>
|
|
@@ -143,6 +145,61 @@ static int esp32_stdin_read(void* buf, size_t len) {
|
|
|
143
145
|
/* Base MAC (6 bytes) encoded as Crockford's Base32 (10 lowercase chars).
|
|
144
146
|
* Lossless: decode the 10 chars to recover the original 6 MAC bytes.
|
|
145
147
|
* Crockford's Base32 excludes I, L, O, U to avoid visual ambiguity. */
|
|
148
|
+
/* The name pair lives in mik.sys under `name`, stored the way every other
|
|
149
|
+
* mik.sys value is: a CBOR text-string blob, so `mikro/kv/nvs` and CMD_KV_SET
|
|
150
|
+
* both read and write it unchanged. */
|
|
151
|
+
static char esp32_device_name[128] = {0};
|
|
152
|
+
|
|
153
|
+
static const char* esp32_get_device_name(void) {
|
|
154
|
+
nvs_handle_t handle;
|
|
155
|
+
if (nvs_open("mik.sys", NVS_READONLY, &handle) != ESP_OK) return NULL;
|
|
156
|
+
|
|
157
|
+
size_t len = 0;
|
|
158
|
+
if (nvs_get_blob(handle, "name", NULL, &len) != ESP_OK || len == 0 || len > 256) {
|
|
159
|
+
nvs_close(handle);
|
|
160
|
+
return NULL;
|
|
161
|
+
}
|
|
162
|
+
uint8_t buf[256];
|
|
163
|
+
esp_err_t err = nvs_get_blob(handle, "name", buf, &len);
|
|
164
|
+
nvs_close(handle);
|
|
165
|
+
if (err != ESP_OK) return NULL;
|
|
166
|
+
|
|
167
|
+
nanocbor_value_t decoder;
|
|
168
|
+
nanocbor_decoder_init(&decoder, buf, len);
|
|
169
|
+
const uint8_t* str = NULL;
|
|
170
|
+
size_t str_len = 0;
|
|
171
|
+
if (nanocbor_get_tstr(&decoder, &str, &str_len) < 0) return NULL;
|
|
172
|
+
if (str_len == 0 || str_len >= sizeof(esp32_device_name)) return NULL;
|
|
173
|
+
|
|
174
|
+
memcpy(esp32_device_name, str, str_len);
|
|
175
|
+
esp32_device_name[str_len] = '\0';
|
|
176
|
+
return esp32_device_name;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
static void esp32_set_device_name(const char* value) {
|
|
180
|
+
nvs_handle_t handle;
|
|
181
|
+
if (nvs_open("mik.sys", NVS_READWRITE, &handle) != ESP_OK) return;
|
|
182
|
+
|
|
183
|
+
if (!value || value[0] == '\0') {
|
|
184
|
+
nvs_erase_key(handle, "name");
|
|
185
|
+
nvs_commit(handle);
|
|
186
|
+
nvs_close(handle);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
size_t value_len = strlen(value);
|
|
191
|
+
uint8_t buf[256];
|
|
192
|
+
nanocbor_encoder_t enc;
|
|
193
|
+
nanocbor_encoder_init(&enc, buf, sizeof(buf));
|
|
194
|
+
if (nanocbor_put_tstrn(&enc, value, value_len) < 0) {
|
|
195
|
+
nvs_close(handle);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
nvs_set_blob(handle, "name", buf, nanocbor_encoded_len(&enc));
|
|
199
|
+
nvs_commit(handle);
|
|
200
|
+
nvs_close(handle);
|
|
201
|
+
}
|
|
202
|
+
|
|
146
203
|
static const char* esp32_get_device_id(void) {
|
|
147
204
|
static const char cb32[] = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
148
205
|
static char id[11] = {0}; /* 6 bytes (48 bits) -> 10 x 5-bit chars + NUL */
|
|
@@ -275,6 +332,8 @@ static const MIKPlatform esp32_platform = {
|
|
|
275
332
|
.stderr_write = esp32_stderr_write,
|
|
276
333
|
.stdin_read = esp32_stdin_read,
|
|
277
334
|
.get_device_id = esp32_get_device_id,
|
|
335
|
+
.get_device_name = esp32_get_device_name,
|
|
336
|
+
.set_device_name = esp32_set_device_name,
|
|
278
337
|
.get_reset_reason = esp32_get_reset_reason,
|
|
279
338
|
};
|
|
280
339
|
|
|
@@ -28,7 +28,7 @@ static void teardown() {
|
|
|
28
28
|
/* Ensure the BLE stack is stopped between tests so every test starts
|
|
29
29
|
* from a clean state. stop() is idempotent. */
|
|
30
30
|
JSValue ret = MIK_EvalModuleContent(
|
|
31
|
-
ctx, "
|
|
31
|
+
ctx, "mikro/ble-test-teardown",
|
|
32
32
|
"import {Ble} from \"native:mikro/ble\"; new Ble().stop();", 48);
|
|
33
33
|
if (!JS_IsException(ret)) {
|
|
34
34
|
JS_FreeValue(ctx, ret);
|
|
@@ -43,7 +43,7 @@ static void teardown() {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
static JSValue eval_module(const char* code) {
|
|
46
|
-
JSValue ret = MIK_EvalModuleContent(ctx, "
|
|
46
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikro/ble-test", code, strlen(code));
|
|
47
47
|
if (!JS_IsException(ret)) {
|
|
48
48
|
JS_FreeValue(ctx, ret);
|
|
49
49
|
mik__execute_jobs(ctx);
|
|
@@ -44,7 +44,7 @@ static void fs_js_setup() {
|
|
|
44
44
|
static void fs_js_teardown() { MIK_FreeRuntime(mik_rt); }
|
|
45
45
|
|
|
46
46
|
static JSValue fs_eval_module(const char* code) {
|
|
47
|
-
JSValue ret = MIK_EvalModuleContent(mik_ctx, "
|
|
47
|
+
JSValue ret = MIK_EvalModuleContent(mik_ctx, "mikro/test", code, strlen(code));
|
|
48
48
|
if (!JS_IsException(ret)) {
|
|
49
49
|
JS_FreeValue(mik_ctx, ret);
|
|
50
50
|
mik__execute_jobs(mik_ctx);
|
|
@@ -25,7 +25,7 @@ static void pub_fs_teardown() {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
static JSValue pub_fs_eval(const char* code) {
|
|
28
|
-
JSValue ret = MIK_EvalModuleContent(mik_ctx, "
|
|
28
|
+
JSValue ret = MIK_EvalModuleContent(mik_ctx, "mikro/test", code, strlen(code));
|
|
29
29
|
if (!JS_IsException(ret)) {
|
|
30
30
|
JS_FreeValue(mik_ctx, ret);
|
|
31
31
|
mik__execute_jobs(mik_ctx);
|
|
@@ -65,11 +65,11 @@ static int32_t get_global_int32(const char* name) {
|
|
|
65
65
|
|
|
66
66
|
/* ── readFile ────────────────────────────────────────────────────── */
|
|
67
67
|
|
|
68
|
-
TEST_CASE("
|
|
68
|
+
TEST_CASE("mikro/fs readFile returns Uint8Array", "[fs]") {
|
|
69
69
|
pub_fs_setup();
|
|
70
70
|
|
|
71
71
|
JSValue ret = pub_fs_eval(R"(
|
|
72
|
-
import { readFile } from "
|
|
72
|
+
import { readFile } from "mikro/fs";
|
|
73
73
|
const r = readFile("/dummy.txt");
|
|
74
74
|
globalThis.__ok = r.ok;
|
|
75
75
|
if (r.ok) globalThis.__content = new TextDecoder().decode(r.value);
|
|
@@ -85,11 +85,11 @@ TEST_CASE("mikrojs/fs readFile returns Uint8Array", "[fs]") {
|
|
|
85
85
|
free(content);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
TEST_CASE("
|
|
88
|
+
TEST_CASE("mikro/fs readFile with utf-8 returns string", "[fs]") {
|
|
89
89
|
pub_fs_setup();
|
|
90
90
|
|
|
91
91
|
JSValue ret = pub_fs_eval(R"(
|
|
92
|
-
import { readFile } from "
|
|
92
|
+
import { readFile } from "mikro/fs";
|
|
93
93
|
const r = readFile("/dummy.txt", "utf-8");
|
|
94
94
|
globalThis.__ok = r.ok;
|
|
95
95
|
globalThis.__isString = r.ok && typeof r.value === "string";
|
|
@@ -108,11 +108,11 @@ TEST_CASE("mikrojs/fs readFile with utf-8 returns string", "[fs]") {
|
|
|
108
108
|
free(content);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
TEST_CASE("
|
|
111
|
+
TEST_CASE("mikro/fs readFile returns NotFound error for missing file", "[fs]") {
|
|
112
112
|
pub_fs_setup();
|
|
113
113
|
|
|
114
114
|
JSValue ret = pub_fs_eval(R"(
|
|
115
|
-
import { readFile } from "
|
|
115
|
+
import { readFile } from "mikro/fs";
|
|
116
116
|
const r = readFile("/nonexistent.txt");
|
|
117
117
|
globalThis.__ok = r.ok;
|
|
118
118
|
if (!r.ok) {
|
|
@@ -136,11 +136,11 @@ TEST_CASE("mikrojs/fs readFile returns NotFound error for missing file", "[fs]")
|
|
|
136
136
|
|
|
137
137
|
/* ── writeFile ───────────────────────────────────────────────────── */
|
|
138
138
|
|
|
139
|
-
TEST_CASE("
|
|
139
|
+
TEST_CASE("mikro/fs writeFile and readFile roundtrip", "[fs]") {
|
|
140
140
|
pub_fs_setup();
|
|
141
141
|
|
|
142
142
|
JSValue ret = pub_fs_eval(R"(
|
|
143
|
-
import { writeFile, readFile } from "
|
|
143
|
+
import { writeFile, readFile } from "mikro/fs";
|
|
144
144
|
const w = writeFile("/test_pub_write.txt", "hello from pub fs");
|
|
145
145
|
const r = readFile("/test_pub_write.txt", "utf-8");
|
|
146
146
|
globalThis.__ok = w.ok && r.ok;
|
|
@@ -157,11 +157,11 @@ TEST_CASE("mikrojs/fs writeFile and readFile roundtrip", "[fs]") {
|
|
|
157
157
|
free(content);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
TEST_CASE("
|
|
160
|
+
TEST_CASE("mikro/fs writeFile with Uint8Array", "[fs]") {
|
|
161
161
|
pub_fs_setup();
|
|
162
162
|
|
|
163
163
|
JSValue ret = pub_fs_eval(R"(
|
|
164
|
-
import { writeFile, readFile } from "
|
|
164
|
+
import { writeFile, readFile } from "mikro/fs";
|
|
165
165
|
const data = new TextEncoder().encode("binary data");
|
|
166
166
|
const w = writeFile("/test_pub_binary.txt", data);
|
|
167
167
|
const r = readFile("/test_pub_binary.txt", "utf-8");
|
|
@@ -181,11 +181,11 @@ TEST_CASE("mikrojs/fs writeFile with Uint8Array", "[fs]") {
|
|
|
181
181
|
|
|
182
182
|
/* ── stat ────────────────────────────────────────────────────────── */
|
|
183
183
|
|
|
184
|
-
TEST_CASE("
|
|
184
|
+
TEST_CASE("mikro/fs stat returns file info", "[fs]") {
|
|
185
185
|
pub_fs_setup();
|
|
186
186
|
|
|
187
187
|
JSValue ret = pub_fs_eval(R"(
|
|
188
|
-
import { stat } from "
|
|
188
|
+
import { stat } from "mikro/fs";
|
|
189
189
|
const r = stat("/dummy.txt");
|
|
190
190
|
globalThis.__ok = r.ok;
|
|
191
191
|
if (r.ok) {
|
|
@@ -208,11 +208,11 @@ TEST_CASE("mikrojs/fs stat returns file info", "[fs]") {
|
|
|
208
208
|
TEST_ASSERT_FALSE(is_dir);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
TEST_CASE("
|
|
211
|
+
TEST_CASE("mikro/fs stat returns NotFound for missing path", "[fs]") {
|
|
212
212
|
pub_fs_setup();
|
|
213
213
|
|
|
214
214
|
JSValue ret = pub_fs_eval(R"(
|
|
215
|
-
import { stat } from "
|
|
215
|
+
import { stat } from "mikro/fs";
|
|
216
216
|
const r = stat("/nonexistent.txt");
|
|
217
217
|
globalThis.__ok = r.ok;
|
|
218
218
|
if (!r.ok) globalThis.__name = r.error.name;
|
|
@@ -230,11 +230,11 @@ TEST_CASE("mikrojs/fs stat returns NotFound for missing path", "[fs]") {
|
|
|
230
230
|
|
|
231
231
|
/* ── exists ──────────────────────────────────────────────────────── */
|
|
232
232
|
|
|
233
|
-
TEST_CASE("
|
|
233
|
+
TEST_CASE("mikro/fs exists returns true for existing file", "[fs]") {
|
|
234
234
|
pub_fs_setup();
|
|
235
235
|
|
|
236
236
|
JSValue ret = pub_fs_eval(R"(
|
|
237
|
-
import { exists } from "
|
|
237
|
+
import { exists } from "mikro/fs";
|
|
238
238
|
globalThis.__exists = exists("/dummy.txt");
|
|
239
239
|
)");
|
|
240
240
|
bool was_exception = JS_IsException(ret);
|
|
@@ -245,11 +245,11 @@ TEST_CASE("mikrojs/fs exists returns true for existing file", "[fs]") {
|
|
|
245
245
|
TEST_ASSERT_TRUE(file_exists);
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
TEST_CASE("
|
|
248
|
+
TEST_CASE("mikro/fs exists returns false for missing file", "[fs]") {
|
|
249
249
|
pub_fs_setup();
|
|
250
250
|
|
|
251
251
|
JSValue ret = pub_fs_eval(R"(
|
|
252
|
-
import { exists } from "
|
|
252
|
+
import { exists } from "mikro/fs";
|
|
253
253
|
globalThis.__exists = exists("/nonexistent.txt");
|
|
254
254
|
)");
|
|
255
255
|
bool was_exception = JS_IsException(ret);
|
|
@@ -262,11 +262,11 @@ TEST_CASE("mikrojs/fs exists returns false for missing file", "[fs]") {
|
|
|
262
262
|
|
|
263
263
|
/* ── mkdir / readDir / rmdir ─────────────────────────────────────── */
|
|
264
264
|
|
|
265
|
-
TEST_CASE("
|
|
265
|
+
TEST_CASE("mikro/fs mkdir, readDir, rmdir roundtrip", "[fs]") {
|
|
266
266
|
pub_fs_setup();
|
|
267
267
|
|
|
268
268
|
JSValue ret = pub_fs_eval(R"(
|
|
269
|
-
import { mkdir, readDir, writeFile, unlink, rmdir } from "
|
|
269
|
+
import { mkdir, readDir, writeFile, unlink, rmdir } from "mikro/fs";
|
|
270
270
|
const m = mkdir("/test_pub_dir");
|
|
271
271
|
const w = writeFile("/test_pub_dir/file.txt", "hi");
|
|
272
272
|
const entries = readDir("/test_pub_dir");
|
|
@@ -288,11 +288,11 @@ TEST_CASE("mikrojs/fs mkdir, readDir, rmdir roundtrip", "[fs]") {
|
|
|
288
288
|
|
|
289
289
|
/* ── unlink / rename ─────────────────────────────────────────────── */
|
|
290
290
|
|
|
291
|
-
TEST_CASE("
|
|
291
|
+
TEST_CASE("mikro/fs unlink and rename", "[fs]") {
|
|
292
292
|
pub_fs_setup();
|
|
293
293
|
|
|
294
294
|
JSValue ret = pub_fs_eval(R"(
|
|
295
|
-
import { writeFile, rename, readFile, unlink } from "
|
|
295
|
+
import { writeFile, rename, readFile, unlink } from "mikro/fs";
|
|
296
296
|
const w = writeFile("/test_pub_rename_src.txt", "rename me");
|
|
297
297
|
const mv = rename("/test_pub_rename_src.txt", "/test_pub_rename_dst.txt");
|
|
298
298
|
const r = readFile("/test_pub_rename_dst.txt", "utf-8");
|
|
@@ -33,7 +33,7 @@ static void setup() {
|
|
|
33
33
|
static void teardown() { MIK_FreeRuntime(rt); }
|
|
34
34
|
|
|
35
35
|
static JSValue eval_module(const char* code) {
|
|
36
|
-
JSValue ret = MIK_EvalModuleContent(ctx, "
|
|
36
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikro/test", code, strlen(code));
|
|
37
37
|
if (!JS_IsException(ret)) {
|
|
38
38
|
JS_FreeValue(ctx, ret);
|
|
39
39
|
mik__execute_jobs(ctx);
|
|
@@ -43,13 +43,19 @@ static JSValue eval_module(const char* code) {
|
|
|
43
43
|
|
|
44
44
|
static void ensure_initialized() {
|
|
45
45
|
const char* code = "import { nextRequest } from 'native:mikro/http_server';";
|
|
46
|
-
JSValue ret = MIK_EvalModuleContent(ctx, "
|
|
46
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikro/test", code, strlen(code));
|
|
47
47
|
if (!JS_IsException(ret)) {
|
|
48
48
|
JS_FreeValue(ctx, ret);
|
|
49
49
|
mik__execute_jobs(ctx);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/* nextRequest() resolves with the closed sentinel unless a server is running.
|
|
54
|
+
* Fake a handle so tests can drive the queues without esp_http_server; clear
|
|
55
|
+
* it before teardown so destroy doesn't httpd_stop the fake. */
|
|
56
|
+
static void fake_server_start() { mik__hs(rt)->server = reinterpret_cast<httpd_handle_t>(1); }
|
|
57
|
+
static void fake_server_stop() { mik__hs(rt)->server = nullptr; }
|
|
58
|
+
|
|
53
59
|
static void read_global_string(const char* key, char* out, size_t out_len) {
|
|
54
60
|
JSValue global = JS_GetGlobalObject(ctx);
|
|
55
61
|
JSValue v = JS_GetPropertyStr(ctx, global, key);
|
|
@@ -139,6 +145,7 @@ TEST_CASE("http_server consume with no messages is a no-op", "[httpd]") {
|
|
|
139
145
|
TEST_CASE("nextRequest delivers method/url and respond fills the exchange", "[httpd]") {
|
|
140
146
|
setup();
|
|
141
147
|
ensure_initialized();
|
|
148
|
+
fake_server_start();
|
|
142
149
|
|
|
143
150
|
MIKHsExchange* ex = push_request("GET", "/hello?x=1");
|
|
144
151
|
|
|
@@ -168,6 +175,7 @@ TEST_CASE("nextRequest delivers method/url and respond fills the exchange", "[ht
|
|
|
168
175
|
size_t headerCount = ex->header_count;
|
|
169
176
|
|
|
170
177
|
free_exchange(ex);
|
|
178
|
+
fake_server_stop();
|
|
171
179
|
teardown();
|
|
172
180
|
|
|
173
181
|
TEST_ASSERT_EQUAL_STRING("GET", method);
|
|
@@ -184,6 +192,7 @@ TEST_CASE("nextRequest delivers method/url and respond fills the exchange", "[ht
|
|
|
184
192
|
TEST_CASE("respond tolerates a bodyless response", "[httpd]") {
|
|
185
193
|
setup();
|
|
186
194
|
ensure_initialized();
|
|
195
|
+
fake_server_start();
|
|
187
196
|
|
|
188
197
|
MIKHsExchange* ex = push_request("POST", "/save");
|
|
189
198
|
|
|
@@ -208,6 +217,7 @@ TEST_CASE("respond tolerates a bodyless response", "[httpd]") {
|
|
|
208
217
|
bool bodyNull = (ex->body == nullptr);
|
|
209
218
|
|
|
210
219
|
free_exchange(ex);
|
|
220
|
+
fake_server_stop();
|
|
211
221
|
teardown();
|
|
212
222
|
|
|
213
223
|
TEST_ASSERT_FALSE_MESSAGE(threw, "bodyless respond must not throw");
|
|
@@ -43,7 +43,7 @@ static void setup() {
|
|
|
43
43
|
|
|
44
44
|
static void ensure_http_initialized() {
|
|
45
45
|
const char* code = "import { request } from 'native:mikro/http';";
|
|
46
|
-
JSValue ret = MIK_EvalModuleContent(ctx, "
|
|
46
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikro/test", code, strlen(code));
|
|
47
47
|
if (!JS_IsException(ret)) {
|
|
48
48
|
JS_FreeValue(ctx, ret);
|
|
49
49
|
mik__execute_jobs(ctx);
|
|
@@ -53,7 +53,7 @@ static void ensure_http_initialized() {
|
|
|
53
53
|
static void teardown() { MIK_FreeRuntime(rt); }
|
|
54
54
|
|
|
55
55
|
static JSValue eval_module(const char* code) {
|
|
56
|
-
JSValue ret = MIK_EvalModuleContent(ctx, "
|
|
56
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikro/test", code, strlen(code));
|
|
57
57
|
if (!JS_IsException(ret)) {
|
|
58
58
|
JS_FreeValue(ctx, ret);
|
|
59
59
|
mik__execute_jobs(ctx);
|
|
@@ -108,8 +108,8 @@ TEST_CASE("native:* internal import blocked from user code", "[modules]") {
|
|
|
108
108
|
TEST_ASSERT_NULL_MESSAGE(result, "native:mikro/sys should be blocked from user code");
|
|
109
109
|
|
|
110
110
|
// But a built-in module should be allowed to import native:mikro/sys
|
|
111
|
-
result = mik_module_normalizer(ctx, "
|
|
112
|
-
TEST_ASSERT_NOT_NULL_MESSAGE(result, "native:mikro/sys should be allowed from
|
|
111
|
+
result = mik_module_normalizer(ctx, "mikro/sys", "native:mikro/sys", nullptr);
|
|
112
|
+
TEST_ASSERT_NOT_NULL_MESSAGE(result, "native:mikro/sys should be allowed from mikro/ modules");
|
|
113
113
|
TEST_ASSERT_EQUAL_STRING("native:mikro/sys", result);
|
|
114
114
|
js_free(ctx, result);
|
|
115
115
|
|
|
@@ -17,7 +17,7 @@ TEST_CASE("Bare module names pass through unchanged", "[modules]") {
|
|
|
17
17
|
JSRuntime* rt = JS_NewRuntime();
|
|
18
18
|
JSContext* ctx = JS_NewContext(rt);
|
|
19
19
|
|
|
20
|
-
assert_normalizes_to(ctx, "main.js", "
|
|
20
|
+
assert_normalizes_to(ctx, "main.js", "mikro/fs", "mikro/fs");
|
|
21
21
|
assert_normalizes_to(ctx, "main.js", "lodash", "lodash");
|
|
22
22
|
|
|
23
23
|
JS_FreeContext(ctx);
|
|
@@ -112,7 +112,7 @@ TEST_CASE("withUnload() unloads a module after the callback returns", "[modules]
|
|
|
112
112
|
|
|
113
113
|
/* Exercises the real shipped mikrojs/module withUnload() on device. */
|
|
114
114
|
const char* code =
|
|
115
|
-
"import {withUnload} from '
|
|
115
|
+
"import {withUnload} from 'mikro/module';\n"
|
|
116
116
|
"globalThis.__first = await withUnload(import('./leaf.js'), (mod) => mod.v);\n"
|
|
117
117
|
"globalThis.__after = true;\n"
|
|
118
118
|
"const b = await import('./leaf.js');\n"
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#include <nvs_flash.h>
|
|
2
|
+
|
|
3
|
+
#include "mikrojs.h"
|
|
4
|
+
#include "private.h"
|
|
5
|
+
#include "quickjs.h"
|
|
6
|
+
#include "unity.h"
|
|
7
|
+
|
|
8
|
+
/* nvs_kv module is self-registered via MIK_REGISTER_MODULE and lazily
|
|
9
|
+
* initialized. These tests cover the mik.kv / mik.sys namespace split on
|
|
10
|
+
* real NVS flash; each test clears both namespaces it touched. */
|
|
11
|
+
|
|
12
|
+
static MIKRuntime* rt;
|
|
13
|
+
static JSContext* ctx;
|
|
14
|
+
|
|
15
|
+
static void setup() {
|
|
16
|
+
/* The bare test runtime has no MIK_Main; init NVS flash once ourselves. */
|
|
17
|
+
static bool nvs_ready = false;
|
|
18
|
+
if (!nvs_ready) {
|
|
19
|
+
esp_err_t err = nvs_flash_init();
|
|
20
|
+
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
|
21
|
+
nvs_flash_erase();
|
|
22
|
+
nvs_flash_init();
|
|
23
|
+
}
|
|
24
|
+
nvs_ready = true;
|
|
25
|
+
}
|
|
26
|
+
rt = MIK_NewRuntime();
|
|
27
|
+
ctx = MIK_GetJSContext(rt);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static void teardown() { MIK_FreeRuntime(rt); }
|
|
31
|
+
|
|
32
|
+
static JSValue eval_module(const char* code) {
|
|
33
|
+
/* The name must pass the loader's builtin-importer check ("mikro/" prefix)
|
|
34
|
+
* or native: imports are denied. */
|
|
35
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikro/test", code, strlen(code));
|
|
36
|
+
if (!JS_IsException(ret)) {
|
|
37
|
+
JS_FreeValue(ctx, ret);
|
|
38
|
+
mik__execute_jobs(ctx);
|
|
39
|
+
} else {
|
|
40
|
+
JSValue exc = JS_GetException(ctx);
|
|
41
|
+
const char* msg = JS_ToCString(ctx, exc);
|
|
42
|
+
printf("eval exception: %s\n", msg ? msg : "(unprintable)");
|
|
43
|
+
JS_FreeCString(ctx, msg);
|
|
44
|
+
JS_Throw(ctx, exc);
|
|
45
|
+
}
|
|
46
|
+
return ret;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* ── Module exports ───────────────────────────────────────────────── */
|
|
50
|
+
|
|
51
|
+
TEST_CASE("native:mikro/nvs_kv exports sys functions and Result-returning clears", "[kv]") {
|
|
52
|
+
setup();
|
|
53
|
+
|
|
54
|
+
JSValue ret = eval_module(R"(
|
|
55
|
+
import { clear, sysSet, sysGet, sysRemove, sysClear } from "native:mikro/nvs_kv";
|
|
56
|
+
globalThis.__sysSetIsFunc = typeof sysSet === "function";
|
|
57
|
+
globalThis.__sysGetIsFunc = typeof sysGet === "function";
|
|
58
|
+
globalThis.__sysRemoveIsFunc = typeof sysRemove === "function";
|
|
59
|
+
globalThis.__sysClearIsFunc = typeof sysClear === "function";
|
|
60
|
+
globalThis.__clearOk = clear().ok === true;
|
|
61
|
+
globalThis.__sysClearOk = sysClear().ok === true;
|
|
62
|
+
)");
|
|
63
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
64
|
+
|
|
65
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
66
|
+
|
|
67
|
+
JSValue v;
|
|
68
|
+
v = JS_GetPropertyStr(ctx, global, "__sysSetIsFunc");
|
|
69
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "sysSet should be a function");
|
|
70
|
+
JS_FreeValue(ctx, v);
|
|
71
|
+
|
|
72
|
+
v = JS_GetPropertyStr(ctx, global, "__sysGetIsFunc");
|
|
73
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "sysGet should be a function");
|
|
74
|
+
JS_FreeValue(ctx, v);
|
|
75
|
+
|
|
76
|
+
v = JS_GetPropertyStr(ctx, global, "__sysRemoveIsFunc");
|
|
77
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "sysRemove should be a function");
|
|
78
|
+
JS_FreeValue(ctx, v);
|
|
79
|
+
|
|
80
|
+
v = JS_GetPropertyStr(ctx, global, "__sysClearIsFunc");
|
|
81
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "sysClear should be a function");
|
|
82
|
+
JS_FreeValue(ctx, v);
|
|
83
|
+
|
|
84
|
+
v = JS_GetPropertyStr(ctx, global, "__clearOk");
|
|
85
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "clear() should return {ok: true}");
|
|
86
|
+
JS_FreeValue(ctx, v);
|
|
87
|
+
|
|
88
|
+
v = JS_GetPropertyStr(ctx, global, "__sysClearOk");
|
|
89
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "sysClear() should return {ok: true}");
|
|
90
|
+
JS_FreeValue(ctx, v);
|
|
91
|
+
|
|
92
|
+
JS_FreeValue(ctx, global);
|
|
93
|
+
teardown();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* ── Namespace isolation ──────────────────────────────────────────── */
|
|
97
|
+
|
|
98
|
+
TEST_CASE("native:mikro/nvs_kv kv and sys namespaces are isolated", "[kv]") {
|
|
99
|
+
setup();
|
|
100
|
+
|
|
101
|
+
JSValue ret = eval_module(R"(
|
|
102
|
+
import { set, get, remove, clear, sysSet, sysGet, sysRemove, sysClear }
|
|
103
|
+
from "native:mikro/nvs_kv";
|
|
104
|
+
clear();
|
|
105
|
+
sysClear();
|
|
106
|
+
set("probe", "app");
|
|
107
|
+
sysSet("probe", "sys");
|
|
108
|
+
globalThis.__kvSees = get("probe") === "app";
|
|
109
|
+
globalThis.__sysSees = sysGet("probe") === "sys";
|
|
110
|
+
remove("probe");
|
|
111
|
+
globalThis.__kvGone = get("probe") === undefined;
|
|
112
|
+
globalThis.__sysSurvivesRemove = sysGet("probe") === "sys";
|
|
113
|
+
sysRemove("probe");
|
|
114
|
+
globalThis.__sysGone = sysGet("probe") === undefined;
|
|
115
|
+
clear();
|
|
116
|
+
sysClear();
|
|
117
|
+
)");
|
|
118
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
119
|
+
|
|
120
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
121
|
+
|
|
122
|
+
JSValue v;
|
|
123
|
+
v = JS_GetPropertyStr(ctx, global, "__kvSees");
|
|
124
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "kv should read its own value under a shared key");
|
|
125
|
+
JS_FreeValue(ctx, v);
|
|
126
|
+
|
|
127
|
+
v = JS_GetPropertyStr(ctx, global, "__sysSees");
|
|
128
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "sys should read its own value under a shared key");
|
|
129
|
+
JS_FreeValue(ctx, v);
|
|
130
|
+
|
|
131
|
+
v = JS_GetPropertyStr(ctx, global, "__kvGone");
|
|
132
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "remove should delete the kv entry");
|
|
133
|
+
JS_FreeValue(ctx, v);
|
|
134
|
+
|
|
135
|
+
v = JS_GetPropertyStr(ctx, global, "__sysSurvivesRemove");
|
|
136
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "kv remove must not touch the sys entry");
|
|
137
|
+
JS_FreeValue(ctx, v);
|
|
138
|
+
|
|
139
|
+
v = JS_GetPropertyStr(ctx, global, "__sysGone");
|
|
140
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "sysRemove should delete the sys entry");
|
|
141
|
+
JS_FreeValue(ctx, v);
|
|
142
|
+
|
|
143
|
+
JS_FreeValue(ctx, global);
|
|
144
|
+
teardown();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ── Clear scopes ─────────────────────────────────────────────────── */
|
|
148
|
+
|
|
149
|
+
TEST_CASE("native:mikro/nvs_kv clear() leaves sys, sysClear() leaves kv", "[kv]") {
|
|
150
|
+
setup();
|
|
151
|
+
|
|
152
|
+
JSValue ret = eval_module(R"(
|
|
153
|
+
import { set, get, clear, sysSet, sysGet, sysClear } from "native:mikro/nvs_kv";
|
|
154
|
+
clear();
|
|
155
|
+
sysClear();
|
|
156
|
+
set("a", 1);
|
|
157
|
+
sysSet("b", 2);
|
|
158
|
+
clear();
|
|
159
|
+
globalThis.__kvGone = get("a") === undefined;
|
|
160
|
+
globalThis.__sysIntact = sysGet("b") === 2;
|
|
161
|
+
set("a", 1);
|
|
162
|
+
sysClear();
|
|
163
|
+
globalThis.__kvIntact = get("a") === 1;
|
|
164
|
+
globalThis.__sysGone = sysGet("b") === undefined;
|
|
165
|
+
clear();
|
|
166
|
+
)");
|
|
167
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
168
|
+
|
|
169
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
170
|
+
|
|
171
|
+
JSValue v;
|
|
172
|
+
v = JS_GetPropertyStr(ctx, global, "__kvGone");
|
|
173
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "clear should empty the kv namespace");
|
|
174
|
+
JS_FreeValue(ctx, v);
|
|
175
|
+
|
|
176
|
+
v = JS_GetPropertyStr(ctx, global, "__sysIntact");
|
|
177
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "clear must not touch the sys namespace");
|
|
178
|
+
JS_FreeValue(ctx, v);
|
|
179
|
+
|
|
180
|
+
v = JS_GetPropertyStr(ctx, global, "__kvIntact");
|
|
181
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "sysClear must not touch the kv namespace");
|
|
182
|
+
JS_FreeValue(ctx, v);
|
|
183
|
+
|
|
184
|
+
v = JS_GetPropertyStr(ctx, global, "__sysGone");
|
|
185
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "sysClear should empty the sys namespace");
|
|
186
|
+
JS_FreeValue(ctx, v);
|
|
187
|
+
|
|
188
|
+
JS_FreeValue(ctx, global);
|
|
189
|
+
teardown();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* ── Public API full clear ────────────────────────────────────────── */
|
|
193
|
+
|
|
194
|
+
TEST_CASE("mikro/kv/nvs clear({full: true}) empties both stores", "[kv]") {
|
|
195
|
+
setup();
|
|
196
|
+
|
|
197
|
+
JSValue ret = eval_module(R"(
|
|
198
|
+
import { nvsStorage } from "mikro/kv/nvs";
|
|
199
|
+
import { set, get, clear, sysSet, sysGet, sysClear } from "native:mikro/nvs_kv";
|
|
200
|
+
clear();
|
|
201
|
+
sysClear();
|
|
202
|
+
set("a", 1);
|
|
203
|
+
sysSet("b", 2);
|
|
204
|
+
const partial = nvsStorage.clear();
|
|
205
|
+
globalThis.__partialOk = partial.ok === true;
|
|
206
|
+
globalThis.__kvAfterPartial = get("a") === undefined;
|
|
207
|
+
globalThis.__sysAfterPartial = sysGet("b") === 2;
|
|
208
|
+
set("a", 1);
|
|
209
|
+
const full = nvsStorage.clear({full: true});
|
|
210
|
+
globalThis.__fullOk = full.ok === true;
|
|
211
|
+
globalThis.__kvAfterFull = get("a") === undefined;
|
|
212
|
+
globalThis.__sysAfterFull = sysGet("b") === undefined;
|
|
213
|
+
)");
|
|
214
|
+
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
215
|
+
|
|
216
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
217
|
+
|
|
218
|
+
JSValue v;
|
|
219
|
+
v = JS_GetPropertyStr(ctx, global, "__partialOk");
|
|
220
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "clear() should return ok");
|
|
221
|
+
JS_FreeValue(ctx, v);
|
|
222
|
+
|
|
223
|
+
v = JS_GetPropertyStr(ctx, global, "__kvAfterPartial");
|
|
224
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "clear() should empty app data");
|
|
225
|
+
JS_FreeValue(ctx, v);
|
|
226
|
+
|
|
227
|
+
v = JS_GetPropertyStr(ctx, global, "__sysAfterPartial");
|
|
228
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "clear() must leave the system store intact");
|
|
229
|
+
JS_FreeValue(ctx, v);
|
|
230
|
+
|
|
231
|
+
v = JS_GetPropertyStr(ctx, global, "__fullOk");
|
|
232
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "clear({full: true}) should return ok");
|
|
233
|
+
JS_FreeValue(ctx, v);
|
|
234
|
+
|
|
235
|
+
v = JS_GetPropertyStr(ctx, global, "__kvAfterFull");
|
|
236
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "clear({full: true}) should empty app data");
|
|
237
|
+
JS_FreeValue(ctx, v);
|
|
238
|
+
|
|
239
|
+
v = JS_GetPropertyStr(ctx, global, "__sysAfterFull");
|
|
240
|
+
TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), "clear({full: true}) should empty the system store");
|
|
241
|
+
JS_FreeValue(ctx, v);
|
|
242
|
+
|
|
243
|
+
JS_FreeValue(ctx, global);
|
|
244
|
+
teardown();
|
|
245
|
+
}
|