scense_runtime/scripts/runtime_min_test.js
2026-08-29 21:10:24 +08:00

112 lines
4.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// scripts/runtime_min_test.js — runtime 引擎本地最小运行验证(Node 18+)
//
// 用法:node scripts/runtime_min_test.js
// 功能:vm 模拟 window 加载 wwwroot/runtime.js,逐项验证 W-10a~W-10i,
// 输出 PASS/FAIL 与汇总,fail=0 时退出码 0(可接入 CI/部署门禁)。
//
// 预期输出(全 PASS):
// PASS W-10a 实体树构建 (entities=5)
// PASS W-10i 独立模式降级初始化
// PASS W-10c/W-10d 事件路由到实体脚本执行 (hero.state=done)
// PASS W-10b 实体状态机流转 (idle->active->done)
// PASS W-10e 定时器按间隔触发 (box1.y 增大)
// PASS W-10f 碰撞检测触发 onCollide 日志
// PASS W-10g 暂停/恢复
// PASS W-10h 错误降级不崩溃 (errorCount 递增, running 仍 true)
// PASS W-10h 失败实体标记 failed
// ==== RESULT: pass=9 fail=0 ====
'use strict';
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const jsPath = path.join(__dirname, '..', 'wwwroot', 'runtime.js');
const src = fs.readFileSync(jsPath, 'utf8');
const sandbox = {
window: {},
console: console,
Date: Date,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
};
sandbox.window.window = sandbox.window;
vm.createContext(sandbox);
vm.runInContext(src, sandbox);
const RuntimeEngine = sandbox.window.RuntimeEngine;
const sleep = function (ms) { return new Promise(function (r) { setTimeout(r, ms); }); };
let pass = 0, fail = 0;
function check(name, cond, extra) {
if (cond) { pass++; console.log('PASS ' + name + (extra ? ' | ' + extra : '')); }
else { fail++; console.log('FAIL ' + name + (extra ? ' | ' + extra : '')); }
}
(async function () {
// ---- W-10i + W-10a:独立模式初始化 / 实体树 ----
const rt = new RuntimeEngine({ worldId: 't1', tickMs: 8 });
const st = rt.initWorld(null);
check('W-10a 实体树构建', rt.getEntities().length === 5,
'entities=' + rt.getEntities().length);
check('W-10i 独立模式降级初始化',
st.mode === 'independent' && st.degraded === true && st.running === false,
'mode=' + st.mode + ' degraded=' + st.degraded);
const evtLog = [];
rt.on('log', function (m) { evtLog.push(m.message); });
// ---- W-10c/W-10d:点击 hero -> click 事件路由到脚本执行 ----
rt.trigger('hero', 'click');
rt.processEvents();
const hero = rt.getEntity('hero');
check('W-10c/W-10d 事件路由到实体脚本执行', hero.state === 'done',
'hero.state=' + hero.state);
check('W-10b 实体状态机流转',
evtLog.some(function (m) { return m.indexOf('路由事件 click') >= 0; }) &&
hero.state === 'done', 'log含路由记录');
// ---- W-10e 定时器:box1 绑定 script_timer,3s 语义 -> 测试用 10ms ----
rt.start();
const box1 = rt.getEntity('box1');
const y0 = box1.y;
rt.addTimer(10, function () { rt.dispatchEvent('timer', 'box1', {}); });
await sleep(120); // 多个 tick 周期,定时器应已触发且脚本已路由
check('W-10e 定时器按间隔触发', rt.getEntity('box1').y > y0,
'box1.y=' + rt.getEntity('box1').y + ' (from ' + y0 + ')');
// ---- W-10f 碰撞检测:hero 移入 box1 包围盒 ----
const boxNow = rt.getEntity('box1');
hero.x = boxNow.x;
hero.y = boxNow.y;
const before = evtLog.length;
rt.checkCollisions();
check('W-10f 碰撞检测触发', evtLog.length > before &&
evtLog.slice(before).some(function (m) { return m.indexOf('碰撞') >= 0; }),
evtLog.slice(before).join(';'));
// ---- W-10g 暂停/恢复 ----
rt.pause();
check('W-10g 暂停', rt.getStatus().paused === true);
rt.resume();
check('W-10g 恢复', rt.getStatus().paused === false && rt.getStatus().running === true);
// ---- W-10h 错误降级:点击 box2(script_fail 故意抛错) ----
const errBefore = rt.getStatus().errorCount;
rt.trigger('box2', 'click');
rt.processEvents();
const st2 = rt.getStatus();
check('W-10h 错误降级不崩溃',
st2.errorCount > errBefore && st2.running === true,
'errors=' + st2.errorCount + ' running=' + st2.running);
check('W-10h 失败实体标记 failed', rt.getEntity('box2').state === 'failed',
'box2.state=' + rt.getEntity('box2').state);
rt.stop();
console.log('\n==== RESULT: pass=' + pass + ' fail=' + fail + ' ====');
process.exit(fail === 0 ? 0 : 1);
})().catch(function (e) {
console.error('TEST CRASH:', e);
process.exit(2);
});