文件
BotStore/test/targetLease.test.js
lyizai 8ab2d59eb0 feat: 新增出站包抓取,供「走不动」逐包对照(TODO 假设 5)
线上实测确认手动执行器(1.7.0 备选方案)不能解决「走不动」:前 20 秒走 13 格后
即卡在「执行中断(stuck)→重规划→缩短段长→跳过目标→重新规划同一目标」的死循环。
只靠 packetStats.outboundTypes 的计数看不出「发出去的内容对不对」,所以补一个
能录原始出站序列、并能在「能走」与「走不动」两种状态下 diff 的诊断能力。

新增 src/core/packetCapture.js:
- 只在录制期间挂钩 client.write,其余时间零开销(该钩子每个出站包都要过一遍);
- 录到的内容做降深 + 截断的 JSON 化处理,向量对象只留坐标
  (move_minecart 这类包能带上万个浮点数,不处理会把缓冲区撑爆);
- 只放内存、有上限(默认 4000 包),不写盘。

接口(控制台):
- POST /api/bots/:id/capture/start(可带 seconds 到点自动停止)
- POST /api/bots/:id/capture/stop
- GET  /api/bots/:id/capture(支持 ?types= 与 ?limit=)

顺带修掉两个先前就存在的问题:
- scripts/check-protocol.js 其实一直跑不通:minecraft-data 的
  types.packet[1][1].type[1].fields[包名] 给的是类型名字符串,
  要再去 types[名字] 取 ['container', [字段...]] 才能展开,原实现直接 .map() 必然抛错;
  另有读 node_modules 少一层 '..' 的路径问题。
- 两条租约用例(expansionController / targetLease)的时序余量只有 15ms,
  全量并行跑测试时偶发失败;改为 TTL 400ms 配 280ms 睡眠,各留 120ms 余量,
  断言内容不变。

测试:436 → 451 个用例,连续 16 轮全量运行全绿。
2026-09-29 22:01:02 +08:00

89 行
4.0 KiB
JavaScript

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
'use strict';
const test = require('node:test');
const assert = require('node:assert');
const { TargetLeaseTable, DEFAULT_TTL_MS } = require('../src/core/targetLease');
test('tryAcquire:先抢到的机器人独占,其他机器人拿不到', () => {
const table = new TargetLeaseTable();
const chunk = { x: 10, z: 20 };
assert.strictEqual(table.tryAcquire(chunk, 'A'), true);
assert.strictEqual(table.tryAcquire(chunk, 'B'), false, '被别人占用时应失败');
assert.strictEqual(table.isLeasedByOther(chunk, 'B'), true);
assert.strictEqual(table.isLeasedByOther(chunk, 'A'), false, '自己占用不算「被别人占用」');
});
test('tryAcquire:同一机器人可重复占用(视为续租)', () => {
const table = new TargetLeaseTable({ ttlMs: 1000 });
const chunk = { x: 1, z: 1 };
assert.strictEqual(table.tryAcquire(chunk, 'A'), true);
assert.strictEqual(table.tryAcquire(chunk, 'A'), true, '自己再抢一次不应失败');
assert.strictEqual(table.size, 1, '不应产生第二条记录');
});
test('租约到期后自动失效,其他机器人可接手(不会永久锁死区块)', async () => {
const table = new TargetLeaseTable({ ttlMs: 30 });
const chunk = { x: 5, z: 5 };
assert.strictEqual(table.tryAcquire(chunk, 'A'), true);
assert.strictEqual(table.tryAcquire(chunk, 'B'), false);
await new Promise((resolve) => setTimeout(resolve, 45));
assert.strictEqual(table.isLeasedByOther(chunk, 'B'), false, '过期后应视为空闲');
assert.strictEqual(table.tryAcquire(chunk, 'B'), true, '过期后其他机器人可接手');
});
test('release:只有持有者能释放,释放后别人立刻可用', () => {
const table = new TargetLeaseTable();
const chunk = { x: 2, z: 3 };
table.tryAcquire(chunk, 'A');
assert.strictEqual(table.release(chunk, 'B'), false, '非持有者不能释放');
assert.strictEqual(table.tryAcquire(chunk, 'B'), false);
assert.strictEqual(table.release(chunk, 'A'), true);
assert.strictEqual(table.tryAcquire(chunk, 'B'), true);
});
test('releaseAll 释放某机器人的全部占用(停止任务 / 断线时用)', () => {
const table = new TargetLeaseTable();
table.tryAcquire({ x: 0, z: 0 }, 'A');
table.tryAcquire({ x: 1, z: 0 }, 'A');
table.tryAcquire({ x: 0, z: 1 }, 'B');
assert.strictEqual(table.releaseAll('A'), 2);
assert.strictEqual(table.size, 1, '只应剩下 B 的占用');
assert.strictEqual(table.isLeasedByOther({ x: 0, z: 0 }, 'B'), false);
});
test('renew 延长租约,防止长距离寻路中途被抢', async () => {
// 时序余量刻意放宽:要证明的是「续租把到期时间往后推了」,
// 即「总耗时 > 原始 TTL」但「距续租的耗时 < TTL」。
// 睡眠取 0.7 × TTL,两侧各留 0.3 × TTL(120ms)调度余量 ——
// 早先用 TTL=40ms / 睡眠 25ms 只剩 15ms,全量并行跑测试时会偶发失败。
const ttlMs = 400;
const sleepMs = 280;
const table = new TargetLeaseTable({ ttlMs });
const chunk = { x: 7, z: 7 };
table.tryAcquire(chunk, 'A');
await new Promise((resolve) => setTimeout(resolve, sleepMs));
assert.strictEqual(table.renew(chunk, 'A'), true);
await new Promise((resolve) => setTimeout(resolve, sleepMs));
// 已超过原始 TTL,但续租过 → 仍是 A 的
assert.strictEqual(table.tryAcquire(chunk, 'B'), false, '续租后不应被抢走');
});
test('renew 对非持有者无效', () => {
const table = new TargetLeaseTable();
table.tryAcquire({ x: 9, z: 9 }, 'A');
assert.strictEqual(table.renew({ x: 9, z: 9 }, 'B'), false);
});
test('sweep 清理过期租约,snapshot 只列出有效占用', async () => {
const table = new TargetLeaseTable({ ttlMs: 20 });
table.tryAcquire({ x: 1, z: 1 }, 'A');
await new Promise((resolve) => setTimeout(resolve, 30));
assert.strictEqual(table.sweep(), 1);
assert.deepStrictEqual(table.snapshot(), []);
});
test('默认租约时长与配置常量一致', () => {
assert.strictEqual(new TargetLeaseTable().ttlMs, DEFAULT_TTL_MS);
assert.ok(DEFAULT_TTL_MS >= 60000, '默认不应太短,否则长距离寻路会被抢');
});