fix: 强化 CPU/memory checker 错误处理、timeout 遵守和快照校验
- Memory checker: reader 与 ctx.signal race,abort 返回 memory/timeout,reject 保持 memory/snapshot - CPU checker: 第二次快照异常返回 cpu/snapshot,计算前校验空数组/核心数不一致/非有限值/负 delta - CPU 计算: 零 delta 安全处理,observation 不含 NaN/Infinity - 文档: CPU 互补描述修正,Memory timeout 约束说明 - 测试: +18 覆盖 timeout、异常和边界输入
This commit is contained in:
@@ -210,6 +210,130 @@ describe("CpuChecker execute", () => {
|
||||
expect(result.failure?.path).toBe("timeout");
|
||||
});
|
||||
|
||||
test("第二次 snapshot 抛错返回 cpu/snapshot failure", async () => {
|
||||
const before = [makeCore(0, 0, 0, 10000, 0)];
|
||||
let callCount = 0;
|
||||
const reader: SnapshotReader = () => {
|
||||
callCount++;
|
||||
if (callCount === 1) return before;
|
||||
throw new Error("second snapshot failed");
|
||||
};
|
||||
const checker = new CpuChecker(reader);
|
||||
|
||||
const target: RawTargetConfig = { cpu: {}, id: "cpu-test", type: "cpu" };
|
||||
const resolved = checker.resolve(target, makeResolveContext({ defaultTimeoutMs: 5000 }));
|
||||
|
||||
const ctx = { signal: new AbortController().signal };
|
||||
const result = await checker.execute(resolved, ctx);
|
||||
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure?.phase).toBe("cpu");
|
||||
expect(result.failure?.path).toBe("snapshot");
|
||||
expect(result.observation).toBeNull();
|
||||
});
|
||||
|
||||
test("空 snapshot pair 返回 cpu/snapshot failure", async () => {
|
||||
const reader: SnapshotReader = () => [];
|
||||
const checker = new CpuChecker(reader);
|
||||
|
||||
const target: RawTargetConfig = { cpu: {}, id: "cpu-test", type: "cpu" };
|
||||
const resolved = checker.resolve(target, makeResolveContext({ defaultTimeoutMs: 5000 }));
|
||||
|
||||
const ctx = { signal: new AbortController().signal };
|
||||
const result = await checker.execute(resolved, ctx);
|
||||
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure?.phase).toBe("cpu");
|
||||
expect(result.failure?.path).toBe("snapshot");
|
||||
});
|
||||
|
||||
test("核心数不一致返回 cpu/snapshot failure", async () => {
|
||||
let callCount = 0;
|
||||
const snapshots = [[makeCore(0, 0, 0, 100, 0)], [makeCore(0, 0, 0, 100, 0), makeCore(0, 0, 0, 100, 0)]];
|
||||
const reader: SnapshotReader = () => {
|
||||
const result = snapshots[Math.min(callCount, snapshots.length - 1)]!;
|
||||
callCount++;
|
||||
return result;
|
||||
};
|
||||
const checker = new CpuChecker(reader);
|
||||
|
||||
const target: RawTargetConfig = { cpu: {}, id: "cpu-test", type: "cpu" };
|
||||
const resolved = checker.resolve(target, makeResolveContext({ defaultTimeoutMs: 5000 }));
|
||||
|
||||
const ctx = { signal: new AbortController().signal };
|
||||
const result = await checker.execute(resolved, ctx);
|
||||
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure?.phase).toBe("cpu");
|
||||
expect(result.failure?.path).toBe("snapshot");
|
||||
expect(result.failure?.message).toContain("核心数不一致");
|
||||
});
|
||||
|
||||
test("非有限 CPU time 值返回 cpu/snapshot failure", async () => {
|
||||
let callCount = 0;
|
||||
const snapshots: CpuCoreSnapshot[][] = [
|
||||
[makeCore(0, 0, 0, 100, 0)],
|
||||
[{ times: { idle: NaN, irq: 0, nice: 0, sys: 0, user: 100 } }],
|
||||
];
|
||||
const reader: SnapshotReader = () => {
|
||||
const result = snapshots[Math.min(callCount, snapshots.length - 1)]!;
|
||||
callCount++;
|
||||
return result;
|
||||
};
|
||||
const checker = new CpuChecker(reader);
|
||||
|
||||
const target: RawTargetConfig = { cpu: {}, id: "cpu-test", type: "cpu" };
|
||||
const resolved = checker.resolve(target, makeResolveContext({ defaultTimeoutMs: 5000 }));
|
||||
|
||||
const ctx = { signal: new AbortController().signal };
|
||||
const result = await checker.execute(resolved, ctx);
|
||||
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure?.phase).toBe("cpu");
|
||||
expect(result.failure?.path).toBe("snapshot");
|
||||
expect(result.failure?.message).toContain("非有限值");
|
||||
});
|
||||
|
||||
test("负数 CPU time delta 返回 cpu/snapshot failure", async () => {
|
||||
const before = [makeCore(1000, 0, 0, 0, 0)];
|
||||
const after = [makeCore(100, 0, 0, 0, 0)];
|
||||
const reader = makeSnapshotReader(before, after);
|
||||
const checker = new CpuChecker(reader);
|
||||
|
||||
const target: RawTargetConfig = { cpu: {}, id: "cpu-test", type: "cpu" };
|
||||
const resolved = checker.resolve(target, makeResolveContext({ defaultTimeoutMs: 5000 }));
|
||||
|
||||
const ctx = { signal: new AbortController().signal };
|
||||
const result = await checker.execute(resolved, ctx);
|
||||
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure?.phase).toBe("cpu");
|
||||
expect(result.failure?.path).toBe("snapshot");
|
||||
expect(result.failure?.message).toContain("负数 delta");
|
||||
});
|
||||
|
||||
test("零 delta snapshot 返回稳定安全值", async () => {
|
||||
const before = [makeCore(100, 0, 0, 100, 0)];
|
||||
const after = [makeCore(100, 0, 0, 100, 0)];
|
||||
const reader = makeSnapshotReader(before, after);
|
||||
const checker = new CpuChecker(reader);
|
||||
|
||||
const target: RawTargetConfig = { cpu: {}, id: "cpu-test", type: "cpu" };
|
||||
const resolved = checker.resolve(target, makeResolveContext({ defaultTimeoutMs: 5000 }));
|
||||
|
||||
const ctx = { signal: new AbortController().signal };
|
||||
const result = await checker.execute(resolved, ctx);
|
||||
|
||||
expect(result.matched).toBe(true);
|
||||
expect(result.failure).toBeNull();
|
||||
expect(result.observation).toMatchObject({
|
||||
idlePercent: 0,
|
||||
maxCoreUsagePercent: 0,
|
||||
minCoreUsagePercent: 0,
|
||||
usagePercent: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test("includePerCore=true 时输出 perCoreUsagePercent", async () => {
|
||||
const before = [makeCore(0, 0, 0, 0, 0), makeCore(0, 0, 0, 0, 0)];
|
||||
const after = [makeCore(8000, 0, 0, 2000, 0), makeCore(2000, 0, 0, 8000, 0)];
|
||||
|
||||
Reference in New Issue
Block a user