cj-tauri 0.8.0 → 1.1.0
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/README.md +3 -3
- package/cjpm.toml +52 -25
- package/cli/cjpm.toml +1 -1
- package/cli/src/doctor.cj +304 -0
- package/cli/src/main.cj +33 -7
- package/cli/src/project.cj +59 -1
- package/cli/src/resolve.cj +348 -304
- package/cli/src/scaffold.cj +139 -22
- package/cli/templates/ohos-pc/AppScope/app.json5 +13 -0
- package/cli/templates/ohos-pc/AppScope/resources/base/element/string.json +8 -0
- package/cli/templates/ohos-pc/AppScope/resources/base/media/background.png +0 -0
- package/cli/templates/ohos-pc/AppScope/resources/base/media/foreground.png +0 -0
- package/cli/templates/ohos-pc/AppScope/resources/base/media/layered_image.json +6 -0
- package/cli/templates/ohos-pc/README.md +79 -0
- package/cli/templates/ohos-pc/build-hap.sh +190 -0
- package/cli/templates/ohos-pc/build-profile.json5 +46 -0
- package/cli/templates/ohos-pc/entry/build-profile.json5 +26 -0
- package/cli/templates/ohos-pc/entry/cjpm.toml +43 -0
- package/cli/templates/ohos-pc/entry/hvigorfile.ts +6 -0
- package/cli/templates/ohos-pc/entry/oh-package.json5 +9 -0
- package/cli/templates/ohos-pc/entry/src/main/cangjie/ability_stage.cj +17 -0
- package/cli/templates/ohos-pc/entry/src/main/cangjie/app_host.cj +94 -0
- package/cli/templates/ohos-pc/entry/src/main/cangjie/index.cj +167 -0
- package/cli/templates/ohos-pc/entry/src/main/cangjie/main_ability.cj +139 -0
- package/cli/templates/ohos-pc/entry/src/main/module.json5 +49 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/element/color.json +8 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/element/string.json +16 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/media/background.png +0 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/media/foreground.png +0 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/media/layered_image.json +6 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/media/startIcon.png +0 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/profile/main_pages.json +2 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/rawfile/capabilities.json +6 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/rawfile/index.html +92 -0
- package/cli/templates/ohos-pc/hvigor/hvigor-config.json5 +5 -0
- package/cli/templates/ohos-pc/hvigorfile.ts +6 -0
- package/cli/templates/ohos-pc/oh-package.json5 +10 -0
- package/native/build_linux.sh +30 -2
- package/native/build_win.bat +62 -61
- package/native/core/bridge_core.c +1626 -0
- package/native/{bridge_core.h → core/bridge_core.h} +246 -6
- package/native/platforms/harmonyos/README.md +106 -0
- package/native/platforms/linux/bridge_linux.c +2485 -0
- package/native/platforms/windows/bridge_win.c +3248 -0
- package/native/tests/test_bridge_core.c +528 -1
- package/package.json +12 -3
- package/prebuilt/win32-x64/cj-tauri.exe +0 -0
- package/src/api_system.cj +10 -2
- package/src/app.cj +248 -85
- package/src/arkweb_proxy.cj +56 -0
- package/src/arkweb_script.cj +154 -0
- package/src/capability.cj +140 -0
- package/src/clipboard_bridge.cj +55 -0
- package/src/dragdrop.cj +26 -0
- package/src/embedded_assets.cj +98 -0
- package/src/hexutil.cj +53 -0
- package/src/host.cj +197 -26
- package/src/host_arkweb.cj +740 -0
- package/src/host_factory.cj +48 -0
- package/src/host_webkit.cj +202 -26
- package/src/host_webview2.cj +176 -3
- package/src/hotkey.cj +66 -0
- package/src/ipc_hub.cj +49 -0
- package/src/ipc_message.cj +2 -1
- package/src/jsutil.cj +33 -0
- package/src/log_bridge.cj +107 -0
- package/src/logging.cj +127 -0
- package/src/menu.cj +197 -196
- package/src/plugin.cj +40 -0
- package/src/plugins/clipboard/plugin_clipboard.cj +144 -0
- package/src/{plugin_dialog.cj → plugins/dialog/plugin_dialog.cj} +74 -17
- package/src/plugins/fs/plugin_fs.cj +194 -0
- package/src/plugins/http/plugin_http.cj +459 -0
- package/src/plugins/log/plugin_log.cj +105 -0
- package/src/{plugin_menu.cj → plugins/menu/plugin_menu.cj} +4 -2
- package/src/plugins/mqtt/mqtt_codec.cj +661 -0
- package/src/plugins/mqtt/plugin_mqtt.cj +1446 -0
- package/src/plugins/notification/plugin_notification.cj +146 -0
- package/src/plugins/plugins.cj +5 -0
- package/src/plugins/redis/plugin_redis.cj +2409 -0
- package/src/plugins/redis/redis_codec.cj +556 -0
- package/src/{plugin_serial.cj → plugins/serial/plugin_serial.cj} +442 -489
- package/src/{serial_bridge.cj → plugins/serial/serial_bridge.cj} +185 -184
- package/src/{plugin_shell.cj → plugins/shell/plugin_shell.cj} +230 -228
- package/src/plugins/sqlite/plugin_sqlite.cj +697 -0
- package/src/plugins/sqlite/sqlite_ffi.cj +206 -0
- package/src/plugins/store/plugin_store.cj +287 -0
- package/src/plugins/ws/plugin_ws.cj +470 -0
- package/src/process_hidden.cj +174 -0
- package/src/scope.cj +391 -0
- package/src/shell_event.cj +69 -68
- package/src/tests/capability_test.cj +53 -0
- package/src/tests/fake_host.cj +85 -20
- package/src/tests/ipc_hub_test.cj +13 -0
- package/src/tests/jsutil_test.cj +62 -0
- package/src/tests/logging_test.cj +62 -0
- package/src/tests/mqtt_codec_test.cj +405 -0
- package/src/tests/plugin_clipboard_test.cj +222 -0
- package/src/tests/plugin_dialog_test.cj +98 -4
- package/src/tests/plugin_http_test.cj +124 -0
- package/src/tests/plugin_menu_test.cj +1 -0
- package/src/tests/plugin_notification_test.cj +205 -0
- package/src/tests/plugin_serial_test.cj +269 -267
- package/src/tests/plugin_shell_test.cj +189 -188
- package/src/tests/plugin_sqlite_test.cj +230 -0
- package/src/tests/plugin_store_test.cj +260 -0
- package/src/tests/plugin_test.cj +542 -524
- package/src/tests/plugin_ws_test.cj +40 -0
- package/src/tests/process_hidden_test.cj +129 -0
- package/src/tests/redis_codec_test.cj +396 -0
- package/src/tests/scope_test.cj +350 -0
- package/src/tests/shell_event_test.cj +0 -1
- package/src/tests/system_host_test.cj +81 -79
- package/src/tests/urlutil_test.cj +53 -0
- package/src/tests/version_test.cj +75 -65
- package/src/tray.cj +90 -0
- package/src/urlutil.cj +159 -0
- package/src/version.cj +23 -2
- package/src/window.cj +51 -42
- package/native/bridge_core.c +0 -797
- package/native/bridge_linux.c +0 -1268
- package/native/bridge_win.c +0 -1633
- package/src/plugin_fs.cj +0 -118
- /package/native/{bridge_js.h → core/bridge_js.h} +0 -0
|
@@ -16,6 +16,18 @@
|
|
|
16
16
|
#include <stdio.h>
|
|
17
17
|
#include <stdlib.h>
|
|
18
18
|
#include <string.h>
|
|
19
|
+
#include <sys/stat.h>
|
|
20
|
+
#include <unistd.h>
|
|
21
|
+
|
|
22
|
+
/* mingw 的 <io.h> 把 mkdir 声明成单参(MSVCRT 语义,且已标 deprecated),
|
|
23
|
+
POSIX 的两参形态在 Windows 上直接编不过(too many arguments to function call)——
|
|
24
|
+
用例里统一走这个包装,两边的语义都是「建一级目录」。 */
|
|
25
|
+
#ifdef _WIN32
|
|
26
|
+
#include <direct.h>
|
|
27
|
+
static int cj_test_mkdir(const char *p) { return _mkdir(p); }
|
|
28
|
+
#else
|
|
29
|
+
static int cj_test_mkdir(const char *p) { return mkdir(p, 0700); }
|
|
30
|
+
#endif
|
|
19
31
|
|
|
20
32
|
/* ===== 断言与用例 ===== */
|
|
21
33
|
|
|
@@ -39,6 +51,47 @@ static void case_begin(const char *name) {
|
|
|
39
51
|
fprintf(stderr, " - %s\n", name);
|
|
40
52
|
}
|
|
41
53
|
|
|
54
|
+
/* ===== 日志落盘用例的辅助(RFC-003 第 5 期) =====
|
|
55
|
+
开启日志后 fd 2 就被日志文件接管了,此时 CHECK 的失败行会**写进日志文件**、外面根本看不见。
|
|
56
|
+
所以接管期间改用 LOG_CHECK:结果先攒在内存里,等 stderr 还原之后再一次性打出来。 */
|
|
57
|
+
static char g_log_fail_buf[4096];
|
|
58
|
+
|
|
59
|
+
static void cj_test_log_check(int cond, int line, const char *what) {
|
|
60
|
+
if (cond) {
|
|
61
|
+
g_pass++;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
g_fail++;
|
|
65
|
+
if (strlen(g_log_fail_buf) + 240 < sizeof(g_log_fail_buf)) {
|
|
66
|
+
char row[200];
|
|
67
|
+
snprintf(row, sizeof(row), " [FAIL] %s:%d [log file] %s\n", __FILE__, line, what);
|
|
68
|
+
strcat(g_log_fail_buf, row);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#define LOG_CHECK(cond, what) cj_test_log_check((cond) ? 1 : 0, __LINE__, what)
|
|
73
|
+
|
|
74
|
+
static int cj_test_file_exists(const char *path) {
|
|
75
|
+
FILE *f = fopen(path, "rb");
|
|
76
|
+
if (!f) return 0;
|
|
77
|
+
fclose(f);
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* 读全文(截断到 cap-1);返回读到的字节数,-1 = 打不开 */
|
|
82
|
+
static long cj_test_read_all(const char *path, char *buf, long cap) {
|
|
83
|
+
FILE *f = fopen(path, "rb");
|
|
84
|
+
long n;
|
|
85
|
+
if (!f) {
|
|
86
|
+
if (cap > 0) buf[0] = '\0';
|
|
87
|
+
return -1;
|
|
88
|
+
}
|
|
89
|
+
n = (long)fread(buf, 1, (size_t)(cap - 1), f);
|
|
90
|
+
buf[n > 0 ? n : 0] = '\0';
|
|
91
|
+
fclose(f);
|
|
92
|
+
return n;
|
|
93
|
+
}
|
|
94
|
+
|
|
42
95
|
/* ===== 桩平台:只记录调用,不做真实副作用 ===== */
|
|
43
96
|
|
|
44
97
|
struct cj_sync {
|
|
@@ -69,11 +122,24 @@ static int g_last_menu_state_checked;
|
|
|
69
122
|
static int g_caps_calls;
|
|
70
123
|
static int g_caps_value = CJ_CAP_MENU; /* 桩平台自称的能力位(用例里改它,验证 core 只是如实缓存) */
|
|
71
124
|
|
|
125
|
+
/* 拖放策略:桩平台只记录调用(core 的职责是 pending 存取与 drop 报文解析)*/
|
|
126
|
+
static int g_drag_calls;
|
|
127
|
+
static int g_drag_last_enabled;
|
|
128
|
+
static int g_drag_last_files;
|
|
129
|
+
|
|
130
|
+
/* 标题栏副标题:桩平台只记录调用(core 的职责是参数校验 + 透传)*/
|
|
131
|
+
static int g_subtitle_calls;
|
|
132
|
+
static char *g_last_subtitle_text;
|
|
133
|
+
|
|
72
134
|
static void reset_counters(void) {
|
|
73
135
|
g_post_js_calls = g_wake_calls = g_quit_calls = g_start_calls = 0;
|
|
74
136
|
g_fini_calls = g_run_dialog_calls = g_post_dialog_calls = 0;
|
|
75
137
|
g_load_url_calls = g_reload_calls = g_devtools_calls = 0;
|
|
76
138
|
g_set_menu_calls = g_menu_state_calls = g_caps_calls = 0;
|
|
139
|
+
g_drag_calls = g_drag_last_enabled = g_drag_last_files = 0;
|
|
140
|
+
g_subtitle_calls = 0;
|
|
141
|
+
free(g_last_subtitle_text);
|
|
142
|
+
g_last_subtitle_text = NULL;
|
|
77
143
|
free(g_last_js);
|
|
78
144
|
g_last_js = NULL;
|
|
79
145
|
free(g_last_menu_text);
|
|
@@ -130,6 +196,49 @@ void cj_plat_post_js(cj_host *h, const char *script_utf8) {
|
|
|
130
196
|
int cj_plat_is_ui_thread(cj_host *h) { (void)h; return g_is_ui_thread; }
|
|
131
197
|
void cj_plat_post_dialog(cj_host *h, dlg_req *req) { (void)h; (void)req; g_post_dialog_calls++; }
|
|
132
198
|
|
|
199
|
+
/* ---- 剪贴板桩:记调用次数与最后一次写的文本;读返回桩文本 ---- */
|
|
200
|
+
static int g_clip_write_calls = 0;
|
|
201
|
+
static int g_clip_read_calls = 0;
|
|
202
|
+
static char *g_clip_last_written = NULL;
|
|
203
|
+
static int g_clip_write_ok = 1; /* 用例可扳成 0 模拟平台失败 */
|
|
204
|
+
|
|
205
|
+
int cj_plat_post_ui(cj_host *h, cj_ui_fn fn, void *ud) {
|
|
206
|
+
(void)h;
|
|
207
|
+
fn(ud); /* 桩平台没有真 UI 线程:就地执行(core 的 is_ui_thread 桩可扳成 0 验投递路径)*/
|
|
208
|
+
return 1;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
int cj_plat_clipboard_write_text(cj_host *h, const char *text, char *err, int err_len) {
|
|
212
|
+
(void)h; (void)err; (void)err_len;
|
|
213
|
+
g_clip_write_calls++;
|
|
214
|
+
if (!g_clip_write_ok) return -3;
|
|
215
|
+
free(g_clip_last_written);
|
|
216
|
+
g_clip_last_written = strdup(text ? text : "");
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
char *cj_plat_clipboard_read_text(cj_host *h, char *err, int err_len) {
|
|
221
|
+
(void)h; (void)err; (void)err_len;
|
|
222
|
+
g_clip_read_calls++;
|
|
223
|
+
if (!g_clip_last_written) return NULL;
|
|
224
|
+
return strdup(g_clip_last_written);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* ---- 通知桩:记调用次数与最后一次的标题;用例可扳失败 ---- */
|
|
228
|
+
static int g_note_calls = 0;
|
|
229
|
+
static char *g_note_last_title = NULL;
|
|
230
|
+
static int g_note_ok = 1;
|
|
231
|
+
|
|
232
|
+
int cj_plat_notification_notify(cj_host *h, const char *title, const char *body,
|
|
233
|
+
char *err, int err_len) {
|
|
234
|
+
(void)h; (void)body; (void)err; (void)err_len;
|
|
235
|
+
g_note_calls++;
|
|
236
|
+
if (!g_note_ok) return -3;
|
|
237
|
+
free(g_note_last_title);
|
|
238
|
+
g_note_last_title = strdup(title ? title : "");
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
133
242
|
int cj_plat_run_dialog(cj_host *h, dlg_req *req) {
|
|
134
243
|
(void)h;
|
|
135
244
|
g_run_dialog_calls++;
|
|
@@ -151,6 +260,15 @@ void cj_plat_set_menu(cj_host *h, const char *wire) {
|
|
|
151
260
|
g_last_menu_text = wire ? strdup(wire) : NULL;
|
|
152
261
|
}
|
|
153
262
|
|
|
263
|
+
/* 桩平台:副标题一律接受(真平台未就绪返回 -3、Windows 返回 -4,那是平台自己的事)*/
|
|
264
|
+
int cj_plat_set_titlebar_subtitle(cj_host *h, const char *text) {
|
|
265
|
+
(void)h;
|
|
266
|
+
g_subtitle_calls++;
|
|
267
|
+
free(g_last_subtitle_text);
|
|
268
|
+
g_last_subtitle_text = text ? strdup(text) : NULL;
|
|
269
|
+
return 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
154
272
|
void cj_plat_menu_item_state(cj_host *h, const char *id, int enabled, int checked) {
|
|
155
273
|
(void)h;
|
|
156
274
|
g_menu_state_calls++;
|
|
@@ -159,6 +277,49 @@ void cj_plat_menu_item_state(cj_host *h, const char *id, int enabled, int checke
|
|
|
159
277
|
g_last_menu_state_checked = checked;
|
|
160
278
|
}
|
|
161
279
|
|
|
280
|
+
/* ---- 拖放策略:声明在计数器区(reset_counters 之前),这里只留原语 ---- */
|
|
281
|
+
void cj_plat_set_drag_drop(cj_host *h, int enabled, int files) {
|
|
282
|
+
(void)h;
|
|
283
|
+
g_drag_calls++;
|
|
284
|
+
g_drag_last_enabled = enabled;
|
|
285
|
+
g_drag_last_files = files;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* ---- 全局快捷键:桩平台没有 X11,只记录调用(接线层自检用)---- */
|
|
289
|
+
static int g_hotkey_calls;
|
|
290
|
+
static char g_hotkey_last_wire[512];
|
|
291
|
+
void cj_plat_set_global_hotkeys(cj_host *h, const char *wire) {
|
|
292
|
+
(void)h;
|
|
293
|
+
g_hotkey_calls++;
|
|
294
|
+
snprintf(g_hotkey_last_wire, sizeof(g_hotkey_last_wire), "%s", wire ? wire : "");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/* ---- 托盘:桩平台没有 appindicator,只记录调用(接线层自检用)---- */
|
|
298
|
+
static int g_tray_calls;
|
|
299
|
+
static char g_tray_last_wire[512];
|
|
300
|
+
void cj_plat_set_tray(cj_host *h, const char *wire) {
|
|
301
|
+
(void)h;
|
|
302
|
+
g_tray_calls++;
|
|
303
|
+
snprintf(g_tray_last_wire, sizeof(g_tray_last_wire), "%s", wire ? wire : "");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/* ---- 窗口可见性 / 最小化到托盘:桩平台没有窗口,只记录调用(接线层自检用)---- */
|
|
307
|
+
static int g_win_visible_calls;
|
|
308
|
+
static int g_win_visible_last = -1;
|
|
309
|
+
void cj_plat_set_window_visible(cj_host *h, int visible) {
|
|
310
|
+
(void)h;
|
|
311
|
+
g_win_visible_calls++;
|
|
312
|
+
g_win_visible_last = visible;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
static int g_min_tray_calls;
|
|
316
|
+
static int g_min_tray_last = -1;
|
|
317
|
+
void cj_plat_set_minimize_to_tray(cj_host *h, int enabled) {
|
|
318
|
+
(void)h;
|
|
319
|
+
g_min_tray_calls++;
|
|
320
|
+
g_min_tray_last = enabled;
|
|
321
|
+
}
|
|
322
|
+
|
|
162
323
|
int cj_plat_host_capabilities(cj_host *h) {
|
|
163
324
|
(void)h;
|
|
164
325
|
g_caps_calls++;
|
|
@@ -225,6 +386,55 @@ int cj_plat_serial_list(char *out, int out_len) {
|
|
|
225
386
|
return 0;
|
|
226
387
|
}
|
|
227
388
|
|
|
389
|
+
/* ===== 静默子进程桩:桩平台建不了真进程,只为让 core 的**参数校验与转发**可调可断言 =====
|
|
390
|
+
真进程那条路(Windows 的 DETACHED_PROCESS / Linux 的 posix_spawn)由平台实机验证。 */
|
|
391
|
+
long long cj_plat_spawn_hidden(const char *exe, const char *args, char *err, int err_len) {
|
|
392
|
+
(void)args;
|
|
393
|
+
if (err && err_len > 0) {
|
|
394
|
+
err[0] = '\0';
|
|
395
|
+
}
|
|
396
|
+
/* 只认一个哨兵:命中就给个假 pid,其余回 0 并留一句原因(两条分支 core 都要覆盖) */
|
|
397
|
+
if (exe && strcmp(exe, "stub-ok") == 0) {
|
|
398
|
+
return 4242;
|
|
399
|
+
}
|
|
400
|
+
if (err && err_len > 0) {
|
|
401
|
+
snprintf(err, (size_t)err_len, "stub: 不能建进程");
|
|
402
|
+
}
|
|
403
|
+
return 0;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
int cj_plat_kill_pid(long long pid) {
|
|
407
|
+
(void)pid;
|
|
408
|
+
return 1; /* 幂等口径:桩上一律算「已结束」 */
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
int cj_plat_pid_alive(long long pid) {
|
|
412
|
+
(void)pid;
|
|
413
|
+
return 0; /* 桩平台没有真进程 */
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/* ===== 内嵌资源桩:桩平台没有 PE 资源表,如实返回 0 或留一句原因,
|
|
417
|
+
让 core 的三条分支(参数校验 / 无资源 / 失败)都能在无 SDK 的机器上被断言。 ===== */
|
|
418
|
+
int cj_plat_extract_assets(const char *dir, char *err, int err_len) {
|
|
419
|
+
if (err && err_len > 0) err[0] = '\0';
|
|
420
|
+
if (dir && strcmp(dir, "stub-missing") == 0) {
|
|
421
|
+
if (err && err_len > 0) snprintf(err, (size_t)err_len, "桩平台:没有内嵌资源");
|
|
422
|
+
return -1;
|
|
423
|
+
}
|
|
424
|
+
return 0; /* 普通产物没有内嵌资源——如实回报 */
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/* ===== 动态库预加载桩:桩平台不去真加载库,只让 core 的「空路径拒绝 / 失败出口」可断言。
|
|
428
|
+
真加载那条路(Windows 的 LoadLibraryW / Linux 的 dlopen)由平台实机验证。 ===== */
|
|
429
|
+
int cj_plat_preload_library(const char *path, char *err, int err_len) {
|
|
430
|
+
if (err && err_len > 0) err[0] = '\0';
|
|
431
|
+
if (path && strcmp(path, "stub-broken") == 0) {
|
|
432
|
+
if (err && err_len > 0) snprintf(err, (size_t)err_len, "桩平台:加载失败");
|
|
433
|
+
return 0;
|
|
434
|
+
}
|
|
435
|
+
return 1; /* 其余一律算加载成功(core 只关心 0 / 非 0 与日志) */
|
|
436
|
+
}
|
|
437
|
+
|
|
228
438
|
/* ===== 线路解析的回调收集器(菜单是树,桩侧用一张表记录 core 走出来的每一行)===== */
|
|
229
439
|
|
|
230
440
|
typedef struct row_rec {
|
|
@@ -320,6 +530,7 @@ int main(void) {
|
|
|
320
530
|
case_begin("set_window / set_icon / set_devtools");
|
|
321
531
|
cj_bridge_set_window(h, "First", 800, 600);
|
|
322
532
|
CHECK(h->win_w == 800 && h->win_h == 600, "尺寸生效");
|
|
533
|
+
|
|
323
534
|
CHECK(h->win_title && strcmp(h->win_title, "First") == 0, "标题是拷贝");
|
|
324
535
|
cj_bridge_set_window(h, "Second", 0, -1);
|
|
325
536
|
CHECK(h->win_title && strcmp(h->win_title, "Second") == 0, "标题替换(旧值已释放)");
|
|
@@ -333,6 +544,22 @@ int main(void) {
|
|
|
333
544
|
cj_bridge_set_devtools(h, 1);
|
|
334
545
|
CHECK(h->devtools == 1, "devtools 可开");
|
|
335
546
|
|
|
547
|
+
/* --- 2b. 窗口可见性 / 最小化到托盘:两头都能用,但「建窗前」只记不下发 --- */
|
|
548
|
+
case_begin("set_window_visible / set_minimize_to_tray");
|
|
549
|
+
cj_bridge_set_window_visible(h, 0);
|
|
550
|
+
CHECK(h->win_visible == 0, "未就绪:记下待定可见性");
|
|
551
|
+
CHECK(g_win_visible_calls == 0, "未就绪:不下发平台(建窗时按 h->win_visible 定稿,省一次闪烁)");
|
|
552
|
+
h->ready = 1;
|
|
553
|
+
cj_bridge_set_window_visible(h, 1);
|
|
554
|
+
CHECK(h->win_visible == 1 && g_win_visible_calls == 1 && g_win_visible_last == 1,
|
|
555
|
+
"就绪后:投平台执行显示");
|
|
556
|
+
cj_bridge_set_minimize_to_tray(h, 1);
|
|
557
|
+
CHECK(h->minimize_to_tray == 1 && g_min_tray_calls == 1 && g_min_tray_last == 1,
|
|
558
|
+
"最小化到托盘策略转给平台");
|
|
559
|
+
cj_bridge_set_minimize_to_tray(h, 0);
|
|
560
|
+
CHECK(h->minimize_to_tray == 0 && g_min_tray_last == 0, "策略可关(回到普通最小化)");
|
|
561
|
+
h->ready = 0; /* 后面的用例还按「未启动」的前提走 */
|
|
562
|
+
|
|
336
563
|
/* --- 3. 预执行脚本:忽略空串、顺序保持、容量翻倍 --- */
|
|
337
564
|
case_begin("add_init_script 顺序与扩容");
|
|
338
565
|
cj_bridge_add_init_script(h, "");
|
|
@@ -558,7 +785,28 @@ int main(void) {
|
|
|
558
785
|
cj_bridge_set_menu(NULL, "x");
|
|
559
786
|
CHECK(1, "set_menu 对 NULL 宿主安全");
|
|
560
787
|
|
|
561
|
-
/* --- 15.
|
|
788
|
+
/* --- 15. titlebar 副标题:core 校验参数、原样透传平台 --- */
|
|
789
|
+
case_begin("titlebar 副标题:校验 + 透传平台");
|
|
790
|
+
reset_counters();
|
|
791
|
+
CHECK(cj_bridge_set_titlebar_subtitle(h, "n.md · 已保存 · 54 字") == 0, "正常文本返回 0");
|
|
792
|
+
CHECK(g_subtitle_calls == 1 && g_last_subtitle_text &&
|
|
793
|
+
strcmp(g_last_subtitle_text, "n.md · 已保存 · 54 字") == 0,
|
|
794
|
+
"原文透传平台(UTF-8 多字节不截断)");
|
|
795
|
+
cj_bridge_set_titlebar_subtitle(h, "");
|
|
796
|
+
CHECK(g_last_subtitle_text && g_last_subtitle_text[0] == '\0', "空串 = 清空副标题(仍透传)");
|
|
797
|
+
{
|
|
798
|
+
/* 超 1024 字节:core 当场拒绝,不透传——平台在 UI 线程里不该再做第二份校验 */
|
|
799
|
+
char big[1100];
|
|
800
|
+
memset(big, 'a', sizeof(big) - 1);
|
|
801
|
+
big[sizeof(big) - 1] = '\0';
|
|
802
|
+
reset_counters();
|
|
803
|
+
CHECK(cj_bridge_set_titlebar_subtitle(h, big) == -1, "超过 1024 字节返回 -1");
|
|
804
|
+
CHECK(g_subtitle_calls == 0, "被拒的请求不透传到平台");
|
|
805
|
+
}
|
|
806
|
+
CHECK(cj_bridge_set_titlebar_subtitle(h, NULL) == -1, "NULL 文本返回 -1");
|
|
807
|
+
CHECK(cj_bridge_set_titlebar_subtitle(NULL, "x") == -1, "NULL 宿主返回 -1");
|
|
808
|
+
|
|
809
|
+
/* --- 16. 菜单点击:shell 回调带回宿主身份 + JSON 载荷 --- */
|
|
562
810
|
case_begin("menu 点击回调带宿主句柄 + JSON 载荷(多窗口不串台)");
|
|
563
811
|
reset_counters();
|
|
564
812
|
g_shell_cb_calls = 0;
|
|
@@ -596,6 +844,117 @@ int main(void) {
|
|
|
596
844
|
cj_bridge_set_menu_item_state(NULL, "x", 1, 1);
|
|
597
845
|
CHECK(1, "对 NULL 宿主安全");
|
|
598
846
|
|
|
847
|
+
/* --- 16b. 拖放:策略透传 + drop 报文解析(RFC-002 §5 / §7.B)--- */
|
|
848
|
+
case_begin("set_drag_drop 透传与 pending");
|
|
849
|
+
reset_counters();
|
|
850
|
+
cj_bridge_set_drag_drop(h, 1, 1);
|
|
851
|
+
CHECK(g_drag_calls == 1 && g_drag_last_enabled == 1 && g_drag_last_files == 1,
|
|
852
|
+
"策略透传到平台原语");
|
|
853
|
+
CHECK(h->pending_drop_enabled == 1 && h->pending_drop_files == 1,
|
|
854
|
+
"策略同时记进 pending(建 view 时读)");
|
|
855
|
+
cj_bridge_set_drag_drop(h, 0, 0);
|
|
856
|
+
CHECK(g_drag_calls == 2 && h->pending_drop_enabled == 0 && h->pending_drop_files == 0,
|
|
857
|
+
"关掉也透传(平台摘 drag dest)");
|
|
858
|
+
cj_bridge_set_drag_drop(NULL, 1, 1);
|
|
859
|
+
CHECK(1, "对 NULL 宿主安全");
|
|
860
|
+
|
|
861
|
+
case_begin("cj_core_drop_files 解析与报文");
|
|
862
|
+
reset_counters();
|
|
863
|
+
g_shell_cb_calls = 0; /* shell 回调计数不在 reset_counters 里(menu 用例也是手动清零) */
|
|
864
|
+
cj_core_drop_files(h, "file:///tmp/a.png\r\nfile:///tmp/b%20c.txt\r\n");
|
|
865
|
+
CHECK(g_shell_cb_calls == 1 && g_shell_cb_host == h, "drop 回调一次、带回该宿主句柄");
|
|
866
|
+
CHECK(strstr(g_shell_cb_json, "\"kind\":\"drop\"") != NULL &&
|
|
867
|
+
strstr(g_shell_cb_json, "\"paths\":[\"/tmp/a.png\",\"/tmp/b c.txt\"]") != NULL,
|
|
868
|
+
"file:// 解码成路径(\\r\\n 分隔、%20 还原空格),报文是 {kind,paths} JSON");
|
|
869
|
+
cj_core_drop_files(h, "# comment line\n\nfile:///tmp/only.txt\nhttps://example.com/x");
|
|
870
|
+
CHECK(g_shell_cb_calls == 2 &&
|
|
871
|
+
strstr(g_shell_cb_json, "\"/tmp/only.txt\"") != NULL &&
|
|
872
|
+
strstr(g_shell_cb_json, "\"https://example.com/x\"") != NULL &&
|
|
873
|
+
strstr(g_shell_cb_json, "comment") == NULL,
|
|
874
|
+
"注释/空行跳过,非 file:// URI 原样保留");
|
|
875
|
+
cj_core_drop_files(h, "");
|
|
876
|
+
CHECK(g_shell_cb_calls == 2, "空载荷不回调");
|
|
877
|
+
cj_core_drop_files(h, NULL);
|
|
878
|
+
CHECK(g_shell_cb_calls == 2, "NULL 载荷安全");
|
|
879
|
+
cj_bridge_set_shell_callback(h2, on_shell);
|
|
880
|
+
cj_core_drop_files(h2, "file:///tmp/second.txt");
|
|
881
|
+
CHECK(g_shell_cb_calls == 3 && g_shell_cb_host == h2,
|
|
882
|
+
"第二个宿主的 drop 带回它自己的句柄(句柄路由不串台)");
|
|
883
|
+
cj_core_drop_files(NULL, "file:///tmp/x");
|
|
884
|
+
CHECK(1, "对 NULL 宿主安全");
|
|
885
|
+
|
|
886
|
+
/* --- 16c. 全局快捷键:接线层(wire 记 pending + 透传平台;触发报文走 shell 通道)--- */
|
|
887
|
+
case_begin("set_global_hotkeys 接线(pending + 平台透传)");
|
|
888
|
+
reset_counters();
|
|
889
|
+
g_hotkey_calls = 0;
|
|
890
|
+
g_hotkey_last_wire[0] = '\0';
|
|
891
|
+
cj_bridge_set_global_hotkeys(h, "Ctrl+Alt+C:show-panel\nCtrl+Shift+P:pause");
|
|
892
|
+
CHECK(g_hotkey_calls == 1 && strcmp(g_hotkey_last_wire,
|
|
893
|
+
"Ctrl+Alt+C:show-panel\nCtrl+Shift+P:pause") == 0,
|
|
894
|
+
"wire 透传到平台原语(解析与 grab 在平台层)");
|
|
895
|
+
CHECK(h->pending_hotkeys != NULL &&
|
|
896
|
+
strcmp(h->pending_hotkeys, "Ctrl+Alt+C:show-panel\nCtrl+Shift+P:pause") == 0,
|
|
897
|
+
"wire 同时记进 pending(grab 线程启动时读)");
|
|
898
|
+
cj_bridge_set_global_hotkeys(h, "");
|
|
899
|
+
CHECK(g_hotkey_calls == 2 && h->pending_hotkeys != NULL &&
|
|
900
|
+
strcmp(h->pending_hotkeys, "") == 0,
|
|
901
|
+
"空 wire = 全部注销(整表替换语义)");
|
|
902
|
+
cj_bridge_set_global_hotkeys(NULL, "Ctrl+Alt+C:x");
|
|
903
|
+
CHECK(1, "对 NULL 宿主安全");
|
|
904
|
+
|
|
905
|
+
case_begin("cj_core_hotkey_pressed 触发报文");
|
|
906
|
+
g_shell_cb_calls = 0;
|
|
907
|
+
cj_bridge_set_shell_callback(h, on_shell);
|
|
908
|
+
cj_core_hotkey_pressed(h, "show-panel");
|
|
909
|
+
CHECK(g_shell_cb_calls == 1 && g_shell_cb_host == h,
|
|
910
|
+
"触发回调一次、带回该宿主句柄");
|
|
911
|
+
CHECK(strstr(g_shell_cb_json, "\"kind\":\"hotkey\"") != NULL &&
|
|
912
|
+
strstr(g_shell_cb_json, "\"id\":\"show-panel\"") != NULL,
|
|
913
|
+
"载荷是 {kind,id} JSON(与菜单点击同族,走 on_shell 通道)");
|
|
914
|
+
cj_core_hotkey_pressed(h, "");
|
|
915
|
+
CHECK(g_shell_cb_calls == 1, "空 id 不回调(无法路由,直接丢弃)");
|
|
916
|
+
cj_bridge_set_shell_callback(h2, on_shell);
|
|
917
|
+
cj_core_hotkey_pressed(h2, "second");
|
|
918
|
+
CHECK(g_shell_cb_calls == 2 && g_shell_cb_host == h2,
|
|
919
|
+
"第二个宿主的触发带回它自己的句柄(句柄路由不串台)");
|
|
920
|
+
cj_core_hotkey_pressed(NULL, "x");
|
|
921
|
+
CHECK(1, "对 NULL 宿主安全");
|
|
922
|
+
|
|
923
|
+
/* --- 16d. 托盘:接线层(wire 记 pending + 透传平台;点击报文走 shell 通道)--- */
|
|
924
|
+
case_begin("set_tray 接线(pending + 平台透传)");
|
|
925
|
+
reset_counters();
|
|
926
|
+
g_tray_calls = 0;
|
|
927
|
+
g_tray_last_wire[0] = '\0';
|
|
928
|
+
cj_bridge_set_tray(h, "icon\t/tmp/tray.png\ntooltip\tcj-tauri\nn\ttoggle\t显示/隐藏\t1\t\nn\tquit\t退出\t1\t\n");
|
|
929
|
+
CHECK(g_tray_calls == 1 && strstr(g_tray_last_wire, "icon\t/tmp/tray.png") != NULL &&
|
|
930
|
+
strstr(g_tray_last_wire, "n\ttoggle") != NULL,
|
|
931
|
+
"wire 透传到平台原语(解析与 appindicator 在平台层)");
|
|
932
|
+
CHECK(h->pending_tray != NULL && strstr(h->pending_tray, "tooltip\tcj-tauri") != NULL,
|
|
933
|
+
"wire 同时记进 pending(宿主线程装配时读)");
|
|
934
|
+
cj_bridge_set_tray(h, "");
|
|
935
|
+
CHECK(g_tray_calls == 2 && h->pending_tray != NULL && strcmp(h->pending_tray, "") == 0,
|
|
936
|
+
"空 wire = 移除托盘(pending 同步清空)");
|
|
937
|
+
cj_bridge_set_tray(NULL, "icon\tx");
|
|
938
|
+
CHECK(1, "对 NULL 宿主安全");
|
|
939
|
+
|
|
940
|
+
case_begin("cj_core_tray_clicked 触发报文");
|
|
941
|
+
g_shell_cb_calls = 0;
|
|
942
|
+
cj_bridge_set_shell_callback(h, on_shell);
|
|
943
|
+
cj_core_tray_clicked(h, "toggle");
|
|
944
|
+
CHECK(g_shell_cb_calls == 1 && g_shell_cb_host == h,
|
|
945
|
+
"点击回调一次、带回该宿主句柄");
|
|
946
|
+
CHECK(strstr(g_shell_cb_json, "\"kind\":\"tray\"") != NULL &&
|
|
947
|
+
strstr(g_shell_cb_json, "\"id\":\"toggle\"") != NULL,
|
|
948
|
+
"载荷是 {kind,id} JSON(RFC-002 §5.5 的 tray 信封,走 on_shell 通道)");
|
|
949
|
+
cj_core_tray_clicked(h, "");
|
|
950
|
+
CHECK(g_shell_cb_calls == 1, "空 id 不回调(无法路由,直接丢弃)");
|
|
951
|
+
cj_bridge_set_shell_callback(h2, on_shell);
|
|
952
|
+
cj_core_tray_clicked(h2, "quit");
|
|
953
|
+
CHECK(g_shell_cb_calls == 2 && g_shell_cb_host == h2,
|
|
954
|
+
"第二个宿主的点击带回它自己的句柄(句柄路由不串台)");
|
|
955
|
+
cj_core_tray_clicked(NULL, "x");
|
|
956
|
+
CHECK(1, "对 NULL 宿主安全");
|
|
957
|
+
|
|
599
958
|
/* --- 17. 能力位:cj_plat_init 时问一次,此后只读 --- */
|
|
600
959
|
case_begin("host capabilities 透传(system:host 的依据)");
|
|
601
960
|
{
|
|
@@ -688,6 +1047,174 @@ int main(void) {
|
|
|
688
1047
|
CHECK(1, "串口导出不需要宿主(本用例跑在两个宿主都已销毁之后)");
|
|
689
1048
|
}
|
|
690
1049
|
|
|
1050
|
+
/* --- 20. 剪贴板:参数校验在 core,平台失败码原样返回 --- */
|
|
1051
|
+
case_begin("clipboard 校验与转发");
|
|
1052
|
+
CHECK(cj_bridge_clipboard_write_text(NULL, "x") == -1, "NULL 文本被拒(-1)");
|
|
1053
|
+
{
|
|
1054
|
+
char *big = (char *)malloc((8u << 20) + 1);
|
|
1055
|
+
cj_host *hc = cj_bridge_create(); /* 剪贴板是宿主能力:要一个活句柄(不 start,window_gone=0)*/
|
|
1056
|
+
if (big && hc) {
|
|
1057
|
+
memset(big, 'a', (8u << 20));
|
|
1058
|
+
big[8u << 20] = '\0';
|
|
1059
|
+
CHECK(cj_bridge_clipboard_write_text(hc, big) == -1, "超 8 MiB 被拒");
|
|
1060
|
+
free(big);
|
|
1061
|
+
}
|
|
1062
|
+
if (hc) {
|
|
1063
|
+
g_clip_write_calls = 0;
|
|
1064
|
+
g_clip_read_calls = 0;
|
|
1065
|
+
free(g_clip_last_written);
|
|
1066
|
+
g_clip_last_written = NULL;
|
|
1067
|
+
CHECK(cj_bridge_clipboard_write_text(hc, "hello clip") == 0, "写文本成功");
|
|
1068
|
+
CHECK(g_clip_write_calls == 1 && g_clip_last_written &&
|
|
1069
|
+
strcmp(g_clip_last_written, "hello clip") == 0, "文本原样到达平台");
|
|
1070
|
+
{
|
|
1071
|
+
char *got = cj_bridge_clipboard_read_text(hc);
|
|
1072
|
+
CHECK(got && strcmp(got, "hello clip") == 0, "读回刚写的文本");
|
|
1073
|
+
cj_bridge_clipboard_free(got);
|
|
1074
|
+
CHECK(g_clip_read_calls == 1, "读触达平台一次");
|
|
1075
|
+
}
|
|
1076
|
+
cj_bridge_clipboard_free(NULL);
|
|
1077
|
+
CHECK(1, "free(NULL) 幂等安全");
|
|
1078
|
+
g_clip_write_ok = 0;
|
|
1079
|
+
CHECK(cj_bridge_clipboard_write_text(hc, "x") == -3, "平台失败码返回 -3");
|
|
1080
|
+
g_clip_write_ok = 1;
|
|
1081
|
+
/* 通知:参数校验 + 发后即忘(投递即返回 0,平台成败看 stderr)*/
|
|
1082
|
+
g_note_calls = 0;
|
|
1083
|
+
hc->capabilities |= CJ_CAP_NOTIFICATION; /* 通知是宿主能力:声明了才走到平台 */
|
|
1084
|
+
CHECK(cj_bridge_notification_notify(hc, "", "b") == -1, "空标题被拒");
|
|
1085
|
+
CHECK(cj_bridge_notification_notify(hc, NULL, "b") == -1, "NULL 标题被拒");
|
|
1086
|
+
{
|
|
1087
|
+
char long_title[258];
|
|
1088
|
+
memset(long_title, 't', sizeof(long_title));
|
|
1089
|
+
long_title[sizeof(long_title) - 1] = '\0';
|
|
1090
|
+
CHECK(cj_bridge_notification_notify(hc, long_title, "b") == -1, "超长标题被拒");
|
|
1091
|
+
}
|
|
1092
|
+
CHECK(g_note_calls == 0, "被拒的通知不触达平台");
|
|
1093
|
+
CHECK(cj_bridge_notification_notify(hc, "hello", "body") == 0, "合法通知提交成功(发后即忘)");
|
|
1094
|
+
CHECK(g_note_calls == 1 && g_note_last_title && strcmp(g_note_last_title, "hello") == 0,
|
|
1095
|
+
"标题原样到达平台");
|
|
1096
|
+
g_note_ok = 0;
|
|
1097
|
+
CHECK(cj_bridge_notification_notify(hc, "x", NULL) == -3, "UI 直行路径平台失败如实回 -3");
|
|
1098
|
+
g_note_ok = 1;
|
|
1099
|
+
/* 未声明通知能力的平台(如 Windows 桩)要当场拒绝,不能走「发后即忘」被读成成功 */
|
|
1100
|
+
hc->capabilities &= ~CJ_CAP_NOTIFICATION;
|
|
1101
|
+
g_note_calls = 0;
|
|
1102
|
+
CHECK(cj_bridge_notification_notify(hc, "x", "b") == -4, "未声明能力位如实回 -4");
|
|
1103
|
+
CHECK(g_note_calls == 0, "未声明能力位不触达平台");
|
|
1104
|
+
cj_bridge_destroy(hc);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/* --- 日志落盘(RFC-003 第 5 期):接管 stderr、按大小轮转、失败如实回 0 --- */
|
|
1109
|
+
case_begin("log file:接管 stderr / 按大小轮转 / 失败如实回 0");
|
|
1110
|
+
{
|
|
1111
|
+
char dir[256];
|
|
1112
|
+
char path[300];
|
|
1113
|
+
char archive1[320];
|
|
1114
|
+
char archive2[320];
|
|
1115
|
+
char archive3[320];
|
|
1116
|
+
char blocker[320];
|
|
1117
|
+
char unwritable[340];
|
|
1118
|
+
char long_path[1200];
|
|
1119
|
+
char buf[65536];
|
|
1120
|
+
FILE *bf;
|
|
1121
|
+
int saved_fd;
|
|
1122
|
+
int i;
|
|
1123
|
+
|
|
1124
|
+
snprintf(dir, sizeof(dir), "/tmp/cj-bridge-logtest-%ld", (long)getpid());
|
|
1125
|
+
cj_test_mkdir(dir);
|
|
1126
|
+
/* 路径故意多两级不存在的目录:默认日志目录(~/.local/state/cj-tauri)常常还不存在,
|
|
1127
|
+
走到 ③ 顺带证明「父目录按需建出来」 */
|
|
1128
|
+
snprintf(path, sizeof(path), "%s/nested/deep/app.log", dir);
|
|
1129
|
+
snprintf(archive1, sizeof(archive1), "%s.1", path);
|
|
1130
|
+
snprintf(archive2, sizeof(archive2), "%s.2", path);
|
|
1131
|
+
snprintf(archive3, sizeof(archive3), "%s.3", path);
|
|
1132
|
+
|
|
1133
|
+
/* ① 未开启(对照组):回读空串、不创建文件,c j_logf 仍写 stderr */
|
|
1134
|
+
CHECK(strcmp(cj_bridge_log_file(), "") == 0, "未开启时回读空串");
|
|
1135
|
+
cj_logf("[bridge-core] probe before enable\n");
|
|
1136
|
+
CHECK(cj_test_file_exists(path) == 0, "未开启时不创建日志文件");
|
|
1137
|
+
|
|
1138
|
+
/* ② 参数与失败路径都回 0(stderr 完好,原因打在 stderr 上) */
|
|
1139
|
+
CHECK(cj_bridge_set_log_file(NULL, 0, 0) == 0, "NULL 路径被拒");
|
|
1140
|
+
CHECK(cj_bridge_set_log_file("", 0, 0) == 0, "空路径被拒");
|
|
1141
|
+
memset(long_path, 'a', sizeof(long_path) - 1);
|
|
1142
|
+
long_path[sizeof(long_path) - 1] = '\0';
|
|
1143
|
+
CHECK(cj_bridge_set_log_file(long_path, 0, 0) == 0, "超长路径被拒");
|
|
1144
|
+
/* 「不可写」不能拿一个不存在的目录测:父目录缺失会被如实建出来(默认目录要的就是这行为),
|
|
1145
|
+
所以拿一个**普通文件**当路径的中间段——父级不是目录,建也建不了、开也开不了 */
|
|
1146
|
+
snprintf(blocker, sizeof(blocker), "%s/blocker", dir);
|
|
1147
|
+
bf = fopen(blocker, "wb");
|
|
1148
|
+
if (bf) fclose(bf);
|
|
1149
|
+
snprintf(unwritable, sizeof(unwritable), "%s/app.log", blocker);
|
|
1150
|
+
CHECK(cj_bridge_set_log_file(unwritable, 0, 0) == 0, "不可写路径被拒(父级是普通文件)");
|
|
1151
|
+
CHECK(cj_test_file_exists(path) == 0, "被拒的开启不产生文件");
|
|
1152
|
+
CHECK(strcmp(cj_bridge_log_file(), "") == 0, "被拒之后仍未激活");
|
|
1153
|
+
|
|
1154
|
+
/* ③ 留底原 stderr fd(必须在接管之前取),然后开启:max 1KB、保留 2 份 */
|
|
1155
|
+
saved_fd = dup(2);
|
|
1156
|
+
LOG_CHECK(saved_fd >= 0, "留底原 stderr fd 成功");
|
|
1157
|
+
LOG_CHECK(cj_bridge_set_log_file(path, 1, 2) == 1, "开启成功回 1(父目录缺失也按需建出来)");
|
|
1158
|
+
LOG_CHECK(strcmp(cj_bridge_log_file(), path) == 0, "回读路径与传入一致");
|
|
1159
|
+
LOG_CHECK(cj_bridge_set_log_file(path, 1, 2) == 0, "重复开启被拒(换了目标只能重启)");
|
|
1160
|
+
|
|
1161
|
+
/* ④ 开启后的诊断确实落在文件里 */
|
|
1162
|
+
cj_logf("[bridge-core] after enable marker=alpha\n");
|
|
1163
|
+
CHECK(1, "接管期间断言走 LOG_CHECK(失败行不会丢进日志文件)");
|
|
1164
|
+
LOG_CHECK(cj_test_read_all(path, buf, sizeof(buf)) > 0, "日志文件有内容");
|
|
1165
|
+
LOG_CHECK(strstr(buf, "[cj-bridge] log file: ") != NULL, "首行记录了日志路径与预算");
|
|
1166
|
+
LOG_CHECK(strstr(buf, "marker=alpha") != NULL, "开启后的 cj_logf 落到文件");
|
|
1167
|
+
LOG_CHECK(strstr(buf, "[2") != NULL, "落盘行带时间戳前缀([YYYY-…)");
|
|
1168
|
+
LOG_CHECK(strstr(buf, "probe before enable") == NULL, "开启之前的输出不会被追认进文件");
|
|
1169
|
+
|
|
1170
|
+
/* ⑤ 前端/插件通道:写一行 + NULL 拒绝 */
|
|
1171
|
+
LOG_CHECK(cj_bridge_log_write(NULL) == -1, "log_write(NULL) 被拒");
|
|
1172
|
+
LOG_CHECK(cj_bridge_log_write("[frontend] marker=beta") == 0, "log_write 回 0");
|
|
1173
|
+
LOG_CHECK(cj_test_read_all(path, buf, sizeof(buf)) > 0 && strstr(buf, "marker=beta") != NULL,
|
|
1174
|
+
"log_write 的行也落在同一份文件");
|
|
1175
|
+
|
|
1176
|
+
/* ⑥ 轮转:1KB 预算下连写,档案要出现、keep 上限要守住 */
|
|
1177
|
+
for (i = 0; i < 60; i++) cj_logf("[bridge-core] filler line %02d padding ................\n", i);
|
|
1178
|
+
LOG_CHECK(cj_test_file_exists(archive1) != 0, "超预算后出现 log.1");
|
|
1179
|
+
LOG_CHECK(cj_test_read_all(archive1, buf, sizeof(buf)) > 0, "log.1 有内容");
|
|
1180
|
+
LOG_CHECK(cj_test_file_exists(archive2) != 0, "继续写出现 log.2");
|
|
1181
|
+
LOG_CHECK(cj_test_file_exists(archive3) == 0, "keep=2 时不出现 log.3(上限守住)");
|
|
1182
|
+
LOG_CHECK(cj_test_read_all(path, buf, sizeof(buf)) > 0 &&
|
|
1183
|
+
strstr(buf, "log rotated") != NULL, "轮转后当前文件仍继续写(有 rotated 行)");
|
|
1184
|
+
|
|
1185
|
+
/* ⑦ 还原 stderr:后续断言与收尾输出照常可见(顺带证明接管可逆) */
|
|
1186
|
+
fflush(stderr);
|
|
1187
|
+
dup2(saved_fd, 2);
|
|
1188
|
+
close(saved_fd);
|
|
1189
|
+
fprintf(stderr, " - [bridge-core] stderr 已还原(日志用例期间的失败行如下,无输出即全过)\n");
|
|
1190
|
+
if (g_log_fail_buf[0]) fprintf(stderr, "%s", g_log_fail_buf);
|
|
1191
|
+
CHECK(cj_test_read_all(path, buf, sizeof(buf)) > 0, "还原后仍能读日志文件");
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/* ===== 静默子进程:core 侧的校验与转发(真进程由平台原语负责,实机验证) ===== */
|
|
1195
|
+
{
|
|
1196
|
+
case_begin("spawn hidden");
|
|
1197
|
+
CHECK(cj_bridge_spawn_hidden(NULL, "") == 0, "spawn_hidden(NULL) 被拒");
|
|
1198
|
+
CHECK(cj_bridge_spawn_hidden("", "") == 0, "spawn_hidden 空路径被拒");
|
|
1199
|
+
CHECK(cj_bridge_spawn_hidden("stub-ok", "a\x1f" "b") == 4242,
|
|
1200
|
+
"参数串按 0x1F 分隔转发给平台,pid 原样回给上层");
|
|
1201
|
+
CHECK(cj_bridge_spawn_hidden("stub-no", "") == 0, "平台失败时 core 回 0(绝不谎报 pid)");
|
|
1202
|
+
CHECK(cj_bridge_kill_pid(0) == 0, "kill_pid(0) 被拒");
|
|
1203
|
+
CHECK(cj_bridge_kill_pid(-5) == 0, "kill_pid(负数) 被拒");
|
|
1204
|
+
CHECK(cj_bridge_pid_alive(0) == 0, "pid_alive(0) 恒为 0(不落到平台原语)");
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
/* ===== 内嵌资源释放:单文件包随包资源(core 负责校验与错误出口) ===== */
|
|
1208
|
+
{
|
|
1209
|
+
case_begin("extract assets");
|
|
1210
|
+
CHECK(cj_bridge_extract_assets(NULL) == -1, "extract_assets(NULL) 被拒");
|
|
1211
|
+
CHECK(cj_bridge_extract_assets("") == -1, "extract_assets 空目录被拒");
|
|
1212
|
+
CHECK(cj_bridge_extract_assets("some/runtime/dir") == 0,
|
|
1213
|
+
"没有内嵌资源的产物如实回 0(普通构建就是这样,不是错误)");
|
|
1214
|
+
CHECK(cj_bridge_extract_assets("stub-missing") == -1,
|
|
1215
|
+
"平台报错时 core 回 -1(绝不把失败说成 0 个文件)");
|
|
1216
|
+
}
|
|
1217
|
+
|
|
691
1218
|
fprintf(stderr, "[bridge-core] 断言 %d 项:通过 %d,失败 %d\n", g_pass + g_fail,
|
|
692
1219
|
g_pass, g_fail);
|
|
693
1220
|
free(g_last_js);
|
package/package.json
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cj-tauri",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "仓颉语言的 Tauri:用仓颉写桌面应用后端 / 系统 WebView 写前端的混合开发框架脚手架 CLI(IPC 双向桥 + 能力安全模型,Windows / Linux / 鸿蒙 PC)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cangjie",
|
|
7
|
+
"cangjie-lang",
|
|
7
8
|
"仓颉",
|
|
8
9
|
"tauri",
|
|
9
10
|
"webview",
|
|
11
|
+
"webview2",
|
|
12
|
+
"webkitgtk",
|
|
10
13
|
"desktop",
|
|
14
|
+
"gui",
|
|
15
|
+
"framework",
|
|
16
|
+
"ipc",
|
|
11
17
|
"cli",
|
|
12
|
-
"scaffold"
|
|
18
|
+
"scaffold",
|
|
19
|
+
"cross-platform",
|
|
20
|
+
"windows",
|
|
21
|
+
"linux"
|
|
13
22
|
],
|
|
14
23
|
"license": "MIT",
|
|
15
24
|
"bin": {
|
|
Binary file
|