@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,405 @@
|
|
|
1
|
+
#include <cerrno>
|
|
2
|
+
#include <cstring>
|
|
3
|
+
#include <string>
|
|
4
|
+
#include <vector>
|
|
5
|
+
|
|
6
|
+
#include "mikrojs.h"
|
|
7
|
+
#include "private.h"
|
|
8
|
+
#include "quickjs.h"
|
|
9
|
+
#include "unity.h"
|
|
10
|
+
|
|
11
|
+
/* ── Mock transport ──────────────────────────────────────────────── */
|
|
12
|
+
|
|
13
|
+
struct MockTransportCtx {
|
|
14
|
+
std::vector<uint8_t> input;
|
|
15
|
+
size_t input_pos = 0;
|
|
16
|
+
std::vector<uint8_t> output;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
static int mock_transport_read(uint8_t* buf, size_t size, void* opaque) {
|
|
20
|
+
auto* ctx = (MockTransportCtx*)opaque;
|
|
21
|
+
if (ctx->input_pos >= ctx->input.size()) {
|
|
22
|
+
errno = 0; /* not EAGAIN — signal true EOF */
|
|
23
|
+
return -1;
|
|
24
|
+
}
|
|
25
|
+
size_t avail = ctx->input.size() - ctx->input_pos;
|
|
26
|
+
size_t n = avail < size ? avail : size;
|
|
27
|
+
memcpy(buf, ctx->input.data() + ctx->input_pos, n);
|
|
28
|
+
ctx->input_pos += n;
|
|
29
|
+
return (int)n;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static void mock_transport_write(const void* buf, size_t len, void* opaque) {
|
|
33
|
+
auto* ctx = (MockTransportCtx*)opaque;
|
|
34
|
+
auto* bytes = (const uint8_t*)buf;
|
|
35
|
+
ctx->output.insert(ctx->output.end(), bytes, bytes + len);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* ── TLV helpers ─────────────────────────────────────────────────── */
|
|
39
|
+
|
|
40
|
+
static void append_frame(std::vector<uint8_t>& buf, uint8_t type, const char* payload) {
|
|
41
|
+
uint32_t len = payload ? (uint32_t)strlen(payload) : 0;
|
|
42
|
+
buf.push_back(type);
|
|
43
|
+
buf.push_back((uint8_t)(len & 0xFF));
|
|
44
|
+
buf.push_back((uint8_t)((len >> 8) & 0xFF));
|
|
45
|
+
buf.push_back((uint8_t)((len >> 16) & 0xFF));
|
|
46
|
+
buf.push_back((uint8_t)((len >> 24) & 0xFF));
|
|
47
|
+
if (len > 0) {
|
|
48
|
+
buf.insert(buf.end(), (const uint8_t*)payload, (const uint8_t*)payload + len);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
struct ParsedFrame {
|
|
53
|
+
uint8_t type;
|
|
54
|
+
std::string payload;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
static std::vector<ParsedFrame> parse_output(const std::vector<uint8_t>& data) {
|
|
58
|
+
std::vector<ParsedFrame> frames;
|
|
59
|
+
size_t pos = 0;
|
|
60
|
+
while (pos + MIK_PROTO_HEADER_SIZE <= data.size()) {
|
|
61
|
+
uint8_t type = data[pos];
|
|
62
|
+
uint32_t len = (uint32_t)data[pos + 1] | ((uint32_t)data[pos + 2] << 8) |
|
|
63
|
+
((uint32_t)data[pos + 3] << 16) | ((uint32_t)data[pos + 4] << 24);
|
|
64
|
+
if (pos + MIK_PROTO_HEADER_SIZE + len > data.size()) break;
|
|
65
|
+
std::string payload((const char*)data.data() + pos + MIK_PROTO_HEADER_SIZE, len);
|
|
66
|
+
frames.push_back({type, payload});
|
|
67
|
+
pos += MIK_PROTO_HEADER_SIZE + len;
|
|
68
|
+
}
|
|
69
|
+
return frames;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static const ParsedFrame* find_frame(const std::vector<ParsedFrame>& frames, uint8_t type) {
|
|
73
|
+
for (auto& f : frames) {
|
|
74
|
+
if (f.type == type) return &f;
|
|
75
|
+
}
|
|
76
|
+
return nullptr;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static std::vector<const ParsedFrame*> find_frames(const std::vector<ParsedFrame>& frames,
|
|
80
|
+
uint8_t type) {
|
|
81
|
+
std::vector<const ParsedFrame*> result;
|
|
82
|
+
for (auto& f : frames) {
|
|
83
|
+
if (f.type == type) result.push_back(&f);
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* ── Shared runtime — one allocation for all protocol tests ──────── */
|
|
89
|
+
|
|
90
|
+
static MIKRuntime* proto_rt = nullptr;
|
|
91
|
+
|
|
92
|
+
static void proto_ensure_rt() {
|
|
93
|
+
if (!proto_rt) {
|
|
94
|
+
proto_rt = MIK_NewRuntime();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static std::vector<ParsedFrame> run_protocol(const std::vector<uint8_t>& input) {
|
|
99
|
+
proto_ensure_rt();
|
|
100
|
+
|
|
101
|
+
MockTransportCtx mock;
|
|
102
|
+
mock.input = input;
|
|
103
|
+
|
|
104
|
+
MIKReplTransport transport = {};
|
|
105
|
+
transport.read = mock_transport_read;
|
|
106
|
+
transport.write = mock_transport_write;
|
|
107
|
+
transport.ctx = &mock;
|
|
108
|
+
|
|
109
|
+
MIK_ProtocolOpen(&transport);
|
|
110
|
+
MIK_ProtocolAttach(proto_rt);
|
|
111
|
+
MIK_ProtocolServeLoop();
|
|
112
|
+
MIK_ProtocolDetach();
|
|
113
|
+
MIK_ProtocolClose();
|
|
114
|
+
|
|
115
|
+
return parse_output(mock.output);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* ── Tests ───────────────────────────────────────────────────────── */
|
|
119
|
+
|
|
120
|
+
TEST_CASE("Protocol stays silent until CMD_HELLO", "[repl_protocol]") {
|
|
121
|
+
std::vector<uint8_t> input;
|
|
122
|
+
auto frames = run_protocol(input);
|
|
123
|
+
|
|
124
|
+
TEST_ASSERT_TRUE_MESSAGE(frames.empty(),
|
|
125
|
+
"Device must not send any frames before CMD_HELLO");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
TEST_CASE("CMD_HELLO triggers MSG_READY", "[repl_protocol]") {
|
|
129
|
+
std::vector<uint8_t> input;
|
|
130
|
+
append_frame(input, MIK_CMD_HELLO, nullptr);
|
|
131
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
132
|
+
|
|
133
|
+
auto frames = run_protocol(input);
|
|
134
|
+
|
|
135
|
+
auto* ready = find_frame(frames, MIK_MSG_READY);
|
|
136
|
+
TEST_ASSERT_NOT_NULL_MESSAGE(ready, "Should receive MSG_READY in response to CMD_HELLO");
|
|
137
|
+
TEST_ASSERT_TRUE_MESSAGE(ready->payload.find("chip") != std::string::npos,
|
|
138
|
+
"MSG_READY should contain chip info");
|
|
139
|
+
TEST_ASSERT_TRUE_MESSAGE(ready->payload.find("v") != std::string::npos,
|
|
140
|
+
"MSG_READY should contain firmware version");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
TEST_CASE("Protocol exits on CMD_EXIT", "[repl_protocol]") {
|
|
144
|
+
std::vector<uint8_t> input;
|
|
145
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
146
|
+
|
|
147
|
+
auto frames = run_protocol(input);
|
|
148
|
+
|
|
149
|
+
TEST_ASSERT_TRUE_MESSAGE(frames.empty(), "Plain CMD_EXIT should produce no output");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
TEST_CASE("CMD_EVAL returns MSG_RESULT for expression", "[repl_protocol]") {
|
|
153
|
+
std::vector<uint8_t> input;
|
|
154
|
+
append_frame(input, MIK_CMD_EVAL, "1 + 2");
|
|
155
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
156
|
+
|
|
157
|
+
auto frames = run_protocol(input);
|
|
158
|
+
|
|
159
|
+
auto* result = find_frame(frames, MIK_MSG_RESULT);
|
|
160
|
+
TEST_ASSERT_NOT_NULL_MESSAGE(result, "Should receive MSG_RESULT");
|
|
161
|
+
TEST_ASSERT_EQUAL_STRING_MESSAGE("3", result->payload.c_str(),
|
|
162
|
+
"Result should be the evaluated value");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
TEST_CASE("CMD_EVAL returns empty MSG_RESULT for undefined", "[repl_protocol]") {
|
|
166
|
+
std::vector<uint8_t> input;
|
|
167
|
+
append_frame(input, MIK_CMD_EVAL, "undefined");
|
|
168
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
169
|
+
|
|
170
|
+
auto frames = run_protocol(input);
|
|
171
|
+
|
|
172
|
+
auto* result = find_frame(frames, MIK_MSG_RESULT);
|
|
173
|
+
TEST_ASSERT_NOT_NULL_MESSAGE(result, "Should receive MSG_RESULT for undefined");
|
|
174
|
+
TEST_ASSERT_EQUAL_STRING_MESSAGE("", result->payload.c_str(),
|
|
175
|
+
"undefined result should have empty payload");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
TEST_CASE("CMD_EVAL returns MSG_EVAL_ERROR for syntax error", "[repl_protocol]") {
|
|
179
|
+
std::vector<uint8_t> input;
|
|
180
|
+
append_frame(input, MIK_CMD_EVAL, "function(");
|
|
181
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
182
|
+
|
|
183
|
+
auto frames = run_protocol(input);
|
|
184
|
+
|
|
185
|
+
auto* err = find_frame(frames, MIK_MSG_EVAL_ERROR);
|
|
186
|
+
TEST_ASSERT_NOT_NULL_MESSAGE(err, "Should receive MSG_EVAL_ERROR for syntax error");
|
|
187
|
+
TEST_ASSERT_TRUE_MESSAGE(err->payload.find("SyntaxError") != std::string::npos,
|
|
188
|
+
"Error should mention SyntaxError");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
TEST_CASE("console.log sends MSG_LOG in protocol mode", "[repl_protocol]") {
|
|
192
|
+
std::vector<uint8_t> input;
|
|
193
|
+
append_frame(input, MIK_CMD_EVAL, "console.log('test output')");
|
|
194
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
195
|
+
|
|
196
|
+
auto frames = run_protocol(input);
|
|
197
|
+
|
|
198
|
+
auto* log = find_frame(frames, MIK_MSG_LOG);
|
|
199
|
+
TEST_ASSERT_NOT_NULL_MESSAGE(log, "Should receive MSG_LOG from console.log");
|
|
200
|
+
TEST_ASSERT_TRUE_MESSAGE(log->payload.find("test output") != std::string::npos,
|
|
201
|
+
"MSG_LOG should contain the logged text");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
TEST_CASE("console.warn sends MSG_WARN in protocol mode", "[repl_protocol]") {
|
|
205
|
+
std::vector<uint8_t> input;
|
|
206
|
+
append_frame(input, MIK_CMD_EVAL, "console.warn('caution')");
|
|
207
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
208
|
+
|
|
209
|
+
auto frames = run_protocol(input);
|
|
210
|
+
|
|
211
|
+
auto* warn = find_frame(frames, MIK_MSG_WARN);
|
|
212
|
+
TEST_ASSERT_NOT_NULL_MESSAGE(warn, "Should receive MSG_WARN from console.warn");
|
|
213
|
+
TEST_ASSERT_TRUE_MESSAGE(warn->payload.find("caution") != std::string::npos,
|
|
214
|
+
"MSG_WARN should contain the warning text");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
TEST_CASE("console.error sends MSG_ERROR in protocol mode", "[repl_protocol]") {
|
|
218
|
+
std::vector<uint8_t> input;
|
|
219
|
+
append_frame(input, MIK_CMD_EVAL, "console.error('bad')");
|
|
220
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
221
|
+
|
|
222
|
+
auto frames = run_protocol(input);
|
|
223
|
+
|
|
224
|
+
auto* error = find_frame(frames, MIK_MSG_ERROR);
|
|
225
|
+
TEST_ASSERT_NOT_NULL_MESSAGE(error, "Should receive MSG_ERROR from console.error");
|
|
226
|
+
TEST_ASSERT_TRUE_MESSAGE(error->payload.find("bad") != std::string::npos,
|
|
227
|
+
"MSG_ERROR should contain the error text");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
TEST_CASE("CMD_DIRECTIVE /help returns MSG_INFO", "[repl_protocol]") {
|
|
231
|
+
std::vector<uint8_t> input;
|
|
232
|
+
append_frame(input, MIK_CMD_DIRECTIVE, "/help");
|
|
233
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
234
|
+
|
|
235
|
+
auto frames = run_protocol(input);
|
|
236
|
+
|
|
237
|
+
auto* info = find_frame(frames, MIK_MSG_INFO);
|
|
238
|
+
TEST_ASSERT_NOT_NULL_MESSAGE(info, "Should receive MSG_INFO for /help");
|
|
239
|
+
TEST_ASSERT_TRUE_MESSAGE(info->payload.find("/help") != std::string::npos,
|
|
240
|
+
"Help output should list /help");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
TEST_CASE("CMD_DIRECTIVE /mem returns MSG_INFO", "[repl_protocol]") {
|
|
244
|
+
std::vector<uint8_t> input;
|
|
245
|
+
append_frame(input, MIK_CMD_DIRECTIVE, "/mem");
|
|
246
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
247
|
+
|
|
248
|
+
auto frames = run_protocol(input);
|
|
249
|
+
|
|
250
|
+
auto* info = find_frame(frames, MIK_MSG_INFO);
|
|
251
|
+
TEST_ASSERT_NOT_NULL_MESSAGE(info, "Should receive MSG_INFO for .mem");
|
|
252
|
+
/* /mem emits a Usage breakdown with "QuickJS:" and "System:" rows
|
|
253
|
+
* (the "heap" lines belong to /info, not /mem). */
|
|
254
|
+
TEST_ASSERT_TRUE_MESSAGE(info->payload.find("QuickJS") != std::string::npos,
|
|
255
|
+
".mem output should contain 'QuickJS'");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
TEST_CASE("CMD_COMPLETE returns MSG_COMPLETIONS", "[repl_protocol]") {
|
|
259
|
+
std::vector<uint8_t> input;
|
|
260
|
+
append_frame(input, MIK_CMD_COMPLETE, "consol");
|
|
261
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
262
|
+
|
|
263
|
+
auto frames = run_protocol(input);
|
|
264
|
+
|
|
265
|
+
auto* completions = find_frame(frames, MIK_MSG_COMPLETIONS);
|
|
266
|
+
TEST_ASSERT_NOT_NULL_MESSAGE(completions, "Should receive MSG_COMPLETIONS");
|
|
267
|
+
/* Completions are CBOR-encoded: a map starting with 0xA2 (2-item map) or 0xBF (indef map) */
|
|
268
|
+
TEST_ASSERT_TRUE_MESSAGE(!completions->payload.empty(),
|
|
269
|
+
"Completions payload should not be empty");
|
|
270
|
+
uint8_t first = (uint8_t)completions->payload[0];
|
|
271
|
+
TEST_ASSERT_TRUE_MESSAGE((first & 0xE0) == 0xA0 || first == 0xBF,
|
|
272
|
+
"Completions payload should be CBOR map");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/* ── Control bytes in TLV frames (regression) ────────────────────── */
|
|
276
|
+
|
|
277
|
+
TEST_CASE("CMD_EVAL with 13-byte payload (0x0D = CR length)", "[repl_protocol]") {
|
|
278
|
+
std::vector<uint8_t> input;
|
|
279
|
+
append_frame(input, MIK_CMD_EVAL, "const a = \"b\"");
|
|
280
|
+
TEST_ASSERT_EQUAL_INT(0x0D, input[1]); /* length = 13 = CR */
|
|
281
|
+
append_frame(input, MIK_CMD_EVAL, "a");
|
|
282
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
283
|
+
|
|
284
|
+
auto frames = run_protocol(input);
|
|
285
|
+
|
|
286
|
+
auto errors = find_frames(frames, MIK_MSG_EVAL_ERROR);
|
|
287
|
+
TEST_ASSERT_TRUE_MESSAGE(errors.empty(),
|
|
288
|
+
"const a = \"b\" should not produce a syntax error");
|
|
289
|
+
|
|
290
|
+
auto results = find_frames(frames, MIK_MSG_RESULT);
|
|
291
|
+
TEST_ASSERT_TRUE_MESSAGE(results.size() >= 2, "Should have at least 2 results");
|
|
292
|
+
TEST_ASSERT_TRUE_MESSAGE(results.back()->payload.find("b") != std::string::npos,
|
|
293
|
+
"Variable 'a' should contain 'b'");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
TEST_CASE("CMD_EVAL with 10-byte payload (0x0A = LF length)", "[repl_protocol]") {
|
|
297
|
+
std::vector<uint8_t> input;
|
|
298
|
+
append_frame(input, MIK_CMD_EVAL, "1234567890");
|
|
299
|
+
TEST_ASSERT_EQUAL_INT(0x0A, input[1]); /* length = 10 = LF */
|
|
300
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
301
|
+
|
|
302
|
+
auto frames = run_protocol(input);
|
|
303
|
+
|
|
304
|
+
auto errors = find_frames(frames, MIK_MSG_EVAL_ERROR);
|
|
305
|
+
TEST_ASSERT_TRUE_MESSAGE(errors.empty(),
|
|
306
|
+
"10-byte payload should not produce errors");
|
|
307
|
+
|
|
308
|
+
auto* result = find_frame(frames, MIK_MSG_RESULT);
|
|
309
|
+
TEST_ASSERT_NOT_NULL_MESSAGE(result, "Should get a result");
|
|
310
|
+
TEST_ASSERT_EQUAL_STRING_MESSAGE("1234567890", result->payload.c_str(),
|
|
311
|
+
"Result should be 1234567890");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
TEST_CASE("Directive then eval does not corrupt stream", "[repl_protocol]") {
|
|
315
|
+
std::vector<uint8_t> input;
|
|
316
|
+
append_frame(input, MIK_CMD_DIRECTIVE, "/help");
|
|
317
|
+
append_frame(input, MIK_CMD_EVAL, "42");
|
|
318
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
319
|
+
|
|
320
|
+
auto frames = run_protocol(input);
|
|
321
|
+
|
|
322
|
+
auto* info = find_frame(frames, MIK_MSG_INFO);
|
|
323
|
+
TEST_ASSERT_NOT_NULL_MESSAGE(info, "Should receive MSG_INFO for /help");
|
|
324
|
+
|
|
325
|
+
auto results = find_frames(frames, MIK_MSG_RESULT);
|
|
326
|
+
TEST_ASSERT_TRUE_MESSAGE(!results.empty(),
|
|
327
|
+
"Should get a result after /help (stream not corrupted)");
|
|
328
|
+
TEST_ASSERT_EQUAL_STRING_MESSAGE("42", results.back()->payload.c_str(),
|
|
329
|
+
"Result after /help should be 42");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
TEST_CASE("Multiple evals in sequence", "[repl_protocol]") {
|
|
333
|
+
std::vector<uint8_t> input;
|
|
334
|
+
append_frame(input, MIK_CMD_EVAL, "var x = 10");
|
|
335
|
+
append_frame(input, MIK_CMD_EVAL, "var y = 20");
|
|
336
|
+
append_frame(input, MIK_CMD_EVAL, "x + y");
|
|
337
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
338
|
+
|
|
339
|
+
auto frames = run_protocol(input);
|
|
340
|
+
|
|
341
|
+
auto results = find_frames(frames, MIK_MSG_RESULT);
|
|
342
|
+
TEST_ASSERT_TRUE_MESSAGE(results.size() >= 3, "Should have 3 results");
|
|
343
|
+
TEST_ASSERT_EQUAL_STRING_MESSAGE("30", results[2]->payload.c_str(),
|
|
344
|
+
"x + y should be 30");
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
TEST_CASE("Protocol mode flag is reset after exit", "[repl_protocol]") {
|
|
348
|
+
TEST_ASSERT_FALSE_MESSAGE(mik__repl_is_protocol_mode(),
|
|
349
|
+
"Protocol mode should be off initially");
|
|
350
|
+
|
|
351
|
+
std::vector<uint8_t> input;
|
|
352
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
353
|
+
run_protocol(input);
|
|
354
|
+
|
|
355
|
+
TEST_ASSERT_FALSE_MESSAGE(mik__repl_is_protocol_mode(),
|
|
356
|
+
"Protocol mode should be off after exit");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
TEST_CASE("All output frames have valid TLV structure", "[repl_protocol]") {
|
|
360
|
+
proto_ensure_rt();
|
|
361
|
+
|
|
362
|
+
std::vector<uint8_t> input;
|
|
363
|
+
append_frame(input, MIK_CMD_EVAL, "1 + 2");
|
|
364
|
+
append_frame(input, MIK_CMD_DIRECTIVE, "/help");
|
|
365
|
+
append_frame(input, MIK_CMD_COMPLETE, "consol");
|
|
366
|
+
append_frame(input, MIK_CMD_EXIT, nullptr);
|
|
367
|
+
|
|
368
|
+
MockTransportCtx mock;
|
|
369
|
+
mock.input = input;
|
|
370
|
+
|
|
371
|
+
MIKReplTransport transport = {};
|
|
372
|
+
transport.read = mock_transport_read;
|
|
373
|
+
transport.write = mock_transport_write;
|
|
374
|
+
transport.ctx = &mock;
|
|
375
|
+
|
|
376
|
+
MIK_ProtocolOpen(&transport);
|
|
377
|
+
MIK_ProtocolAttach(proto_rt);
|
|
378
|
+
MIK_ProtocolServeLoop();
|
|
379
|
+
MIK_ProtocolDetach();
|
|
380
|
+
MIK_ProtocolClose();
|
|
381
|
+
|
|
382
|
+
size_t pos = 0;
|
|
383
|
+
int frame_count = 0;
|
|
384
|
+
while (pos + MIK_PROTO_HEADER_SIZE <= mock.output.size()) {
|
|
385
|
+
uint32_t len = (uint32_t)mock.output[pos + 1] | ((uint32_t)mock.output[pos + 2] << 8) |
|
|
386
|
+
((uint32_t)mock.output[pos + 3] << 16) | ((uint32_t)mock.output[pos + 4] << 24);
|
|
387
|
+
TEST_ASSERT_TRUE_MESSAGE(pos + MIK_PROTO_HEADER_SIZE + len <= mock.output.size(),
|
|
388
|
+
"Frame payload should not exceed output buffer");
|
|
389
|
+
pos += MIK_PROTO_HEADER_SIZE + len;
|
|
390
|
+
frame_count++;
|
|
391
|
+
}
|
|
392
|
+
TEST_ASSERT_TRUE_MESSAGE(pos == mock.output.size(),
|
|
393
|
+
"All output bytes should be consumed by frame parser");
|
|
394
|
+
TEST_ASSERT_TRUE_MESSAGE(frame_count > 0, "Should have at least one output frame");
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* Last test frees the shared runtime to release memory for subsequent
|
|
398
|
+
* test suites (e.g. wifi_test which needs large contiguous allocations). */
|
|
399
|
+
TEST_CASE("Cleanup shared protocol runtime", "[repl_protocol]") {
|
|
400
|
+
if (proto_rt) {
|
|
401
|
+
MIK_FreeRuntime(proto_rt);
|
|
402
|
+
proto_rt = nullptr;
|
|
403
|
+
}
|
|
404
|
+
TEST_ASSERT_NULL_MESSAGE(proto_rt, "Runtime should be freed");
|
|
405
|
+
}
|