fake-engine.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. /**
  2. * 假引擎:模拟「长时间运行 + 能被叫停 + 会跳心跳」,**完全不碰 TikTok**。
  3. *
  4. * 用途:验证界面侧的运行态交互(启动握手、状态条、进度、两段停止、心跳判活)。
  5. * 用真引擎验证这些时,它会把 TikTok 拉到前台,我就没法同时操作界面上的按钮了 ——
  6. * 而这个假引擎不切应用,界面始终留在前台,正好把 UI 交互测干净。
  7. *
  8. * 协议与真引擎完全一致:读 --config/--state/--stop-flag/--run-id,
  9. * 写 run-state.json(带 v / engine.capabilities)与 engine-beat.json。
  10. *
  11. * ★ 心跳不能省:界面在 v2 档位下是用心跳判活的。这个假引擎要是不跳,
  12. * 跑起来 15 秒就会被界面判成"引擎已退出" —— 最常用的联调手段反而成了误报源。
  13. */
  14. function argOf(argv, key) {
  15. for (var i = 0; argv && argv[i] !== undefined; i += 1) {
  16. if (String(argv[i]) === key) { return String(argv[i + 1]); }
  17. }
  18. return "";
  19. }
  20. var argv = engines.myEngine().execArgv;
  21. var STATE = argOf(argv, "--state");
  22. var STOP = argOf(argv, "--stop-flag");
  23. var RUN_ID = argOf(argv, "--run-id");
  24. /* 心跳文件跟 run-state 放同一目录(真引擎也是这么约定的),
  25. * 这样不必额外传一个 --beat 参数,命令行协议保持 5 个参数不变。 */
  26. var slash = STATE.lastIndexOf("/");
  27. var BEAT = (slash >= 0 ? STATE.substring(0, slash) : "") + "/engine-beat.json";
  28. var seq = 0;
  29. var seenAt = 0;
  30. function report(payload) {
  31. payload.v = 2;
  32. payload.runId = RUN_ID;
  33. payload.engine = payload.engine || { task: "fake", capabilities: { heartbeat: true } };
  34. payload.updatedAt = Date.now();
  35. files.write(STATE, JSON.stringify(payload));
  36. }
  37. /* 心跳必须是**独立线程**:主流程会 sleep(300) 轮询,
  38. * 若把心跳写在同一循环里,一旦某一步卡住心跳就跟着停 —— 那就失去意义了。
  39. * (真引擎侧也是同样的理由,见 tiktok-auto/src/bridge.js 的 heartbeatStart) */
  40. var beating = true;
  41. threads.start(function () {
  42. while (beating) {
  43. seq += 1;
  44. try {
  45. files.write(BEAT, JSON.stringify({
  46. v: 2, runId: RUN_ID, seq: seq, beatAt: Date.now(), stopSeenAt: seenAt
  47. }));
  48. } catch (e) { }
  49. sleep(2000);
  50. }
  51. });
  52. report({ status: "starting", phase: "假引擎启动中", done: 0, total: 3 });
  53. sleep(600);
  54. report({ status: "running", phase: "假引擎运行中", done: 0, total: 3 });
  55. var began = Date.now();
  56. var tick = 0;
  57. var done = 0;
  58. while (Date.now() - began < 60000) {
  59. tick += 1;
  60. /* 每 3 秒推进一格进度:用来验证状态条会跟着引擎上报刷新 */
  61. if (tick % 10 === 0 && done < 3) {
  62. done += 1;
  63. report({ status: "running", phase: "已处理 " + done + "/3", done: done, total: 3 });
  64. }
  65. var content = "";
  66. try {
  67. if (files.exists(STOP)) {
  68. content = String(files.read(STOP)).replace(/\s/g, "");
  69. }
  70. } catch (e) { }
  71. if (content !== "" && content === RUN_ID) {
  72. /* 先承认收到(sticky 的 stopSeenAt,界面靠它把"正在通知引擎"换成"正在收尾"),
  73. * 再走一段"收尾中"的过场 —— 真实引擎也是这样,不会立刻消失。 */
  74. seenAt = Date.now();
  75. report({ status: "stopping", phase: "正在收尾", message: "收到停止请求(假引擎)" });
  76. sleep(1200);
  77. beating = false;
  78. report({ status: "stopped", phase: "", message: "已停止(假引擎)" });
  79. exit();
  80. }
  81. sleep(300);
  82. }
  83. beating = false;
  84. report({ status: "finished", phase: "", done: 3, total: 3, message: "跑完了(假引擎)" });