feat: 新增 memory checker,支持系统级内存和交换空间检测
This commit is contained in:
141
tests/server/checker/runner/memory/calculate.test.ts
Normal file
141
tests/server/checker/runner/memory/calculate.test.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import type { Systeminformation } from "systeminformation";
|
||||
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { calculateMemoryStats } from "../../../../../src/server/checker/runner/memory/calculate";
|
||||
|
||||
function makeMemData(overrides: Partial<Systeminformation.MemData> = {}): Systeminformation.MemData {
|
||||
return {
|
||||
active: 4294967296,
|
||||
available: 8589934592,
|
||||
buffcache: 1073741824,
|
||||
buffers: 536870912,
|
||||
cached: 536870912,
|
||||
dirty: null,
|
||||
free: 4294967296,
|
||||
reclaimable: 0,
|
||||
slab: 0,
|
||||
swapfree: 0,
|
||||
swaptotal: 0,
|
||||
swapused: 0,
|
||||
total: 17179869184,
|
||||
used: 8589934592,
|
||||
writeback: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("calculateMemoryStats", () => {
|
||||
test("usagePercent = activeBytes / totalBytes * 100", () => {
|
||||
const stats = calculateMemoryStats(makeMemData({ active: 4294967296, total: 8589934592 }));
|
||||
expect(stats.usagePercent).toBe(50);
|
||||
});
|
||||
|
||||
test("usedPercent = usedBytes / totalBytes * 100", () => {
|
||||
const stats = calculateMemoryStats(makeMemData({ total: 8589934592, used: 6442450944 }));
|
||||
expect(stats.usedPercent).toBe(75);
|
||||
});
|
||||
|
||||
test("freePercent = freeBytes / totalBytes * 100", () => {
|
||||
const stats = calculateMemoryStats(makeMemData({ free: 2147483648, total: 8589934592 }));
|
||||
expect(stats.freePercent).toBe(25);
|
||||
});
|
||||
|
||||
test("activePercent = activeBytes / totalBytes * 100", () => {
|
||||
const stats = calculateMemoryStats(makeMemData({ active: 3221225472, total: 8589934592 }));
|
||||
expect(stats.activePercent).toBe(37.5);
|
||||
});
|
||||
|
||||
test("availablePercent = availableBytes / totalBytes * 100", () => {
|
||||
const stats = calculateMemoryStats(makeMemData({ available: 6442450944, total: 8589934592 }));
|
||||
expect(stats.availablePercent).toBe(75);
|
||||
});
|
||||
|
||||
test("保留 1 位小数", () => {
|
||||
const stats = calculateMemoryStats(makeMemData({ active: 3000000000, total: 8000000000 }));
|
||||
expect(stats.usagePercent).toBe(37.5);
|
||||
});
|
||||
|
||||
test("round1 处理需要四舍五入的情况", () => {
|
||||
const stats = calculateMemoryStats(makeMemData({ active: 3333333333, total: 10000000000 }));
|
||||
expect(stats.usagePercent).toBe(33.3);
|
||||
});
|
||||
|
||||
test("total 为 0 时百分比为 0", () => {
|
||||
const stats = calculateMemoryStats(makeMemData({ active: 0, available: 0, free: 0, total: 0, used: 0 }));
|
||||
expect(stats.usagePercent).toBe(0);
|
||||
expect(stats.usedPercent).toBe(0);
|
||||
expect(stats.freePercent).toBe(0);
|
||||
});
|
||||
|
||||
test("buffcacheBytes 为 null 映射", () => {
|
||||
const stats = calculateMemoryStats(makeMemData({ buffcache: 0 }));
|
||||
expect(stats.buffcacheBytes).toBe(0);
|
||||
});
|
||||
|
||||
test("buffcacheBytes 为正数时保留", () => {
|
||||
const stats = calculateMemoryStats(makeMemData({ buffcache: 1073741824 }));
|
||||
expect(stats.buffcacheBytes).toBe(1073741824);
|
||||
});
|
||||
|
||||
test("所有字节字段正确映射", () => {
|
||||
const data = makeMemData({
|
||||
active: 1000,
|
||||
available: 2000,
|
||||
free: 3000,
|
||||
total: 4000,
|
||||
used: 3500,
|
||||
});
|
||||
const stats = calculateMemoryStats(data);
|
||||
expect(stats.activeBytes).toBe(1000);
|
||||
expect(stats.availableBytes).toBe(2000);
|
||||
expect(stats.freeBytes).toBe(3000);
|
||||
expect(stats.totalBytes).toBe(4000);
|
||||
expect(stats.usedBytes).toBe(3500);
|
||||
});
|
||||
});
|
||||
|
||||
describe("calculateMemoryStats swap", () => {
|
||||
test("swap 不可用:swaptotal=0 时 swapUsagePercent=null", () => {
|
||||
const stats = calculateMemoryStats(makeMemData({ swapfree: 0, swaptotal: 0, swapused: 0 }));
|
||||
expect(stats.swapUsagePercent).toBe(null);
|
||||
expect(stats.swapTotalBytes).toBe(0);
|
||||
expect(stats.swapUsedBytes).toBe(0);
|
||||
expect(stats.swapFreeBytes).toBe(0);
|
||||
});
|
||||
|
||||
test("swap 总量为 0,swapUsagePercent 为 null(不是 0)", () => {
|
||||
const stats = calculateMemoryStats(makeMemData({ swaptotal: 0 }));
|
||||
expect(stats.swapUsagePercent).toBe(null);
|
||||
});
|
||||
|
||||
test("swap 已使用", () => {
|
||||
const stats = calculateMemoryStats(
|
||||
makeMemData({ swapfree: 1073741824, swaptotal: 4294967296, swapused: 3221225472 }),
|
||||
);
|
||||
expect(stats.swapUsagePercent).toBe(75);
|
||||
expect(stats.swapTotalBytes).toBe(4294967296);
|
||||
expect(stats.swapUsedBytes).toBe(3221225472);
|
||||
expect(stats.swapFreeBytes).toBe(1073741824);
|
||||
});
|
||||
|
||||
test("swap 未使用:swapUsagePercent=0(不是 null)", () => {
|
||||
const stats = calculateMemoryStats(makeMemData({ swapfree: 4294967296, swaptotal: 4294967296, swapused: 0 }));
|
||||
expect(stats.swapUsagePercent).toBe(0);
|
||||
expect(stats.swapUsedBytes).toBe(0);
|
||||
expect(stats.swapFreeBytes).toBe(4294967296);
|
||||
});
|
||||
|
||||
test("swap 部分使用保留 1 位小数", () => {
|
||||
const stats = calculateMemoryStats(
|
||||
makeMemData({ swapfree: 3000000000, swaptotal: 10000000000, swapused: 7000000000 }),
|
||||
);
|
||||
expect(stats.swapUsagePercent).toBe(70);
|
||||
});
|
||||
|
||||
test("swap 合法 0 不被转换为 null", () => {
|
||||
const stats = calculateMemoryStats(makeMemData({ swapfree: 4294967296, swaptotal: 4294967296, swapused: 0 }));
|
||||
expect(stats.swapUsedBytes).toBe(0);
|
||||
expect(stats.swapUsagePercent).toBe(0);
|
||||
});
|
||||
});
|
||||
184
tests/server/checker/runner/memory/execute.test.ts
Normal file
184
tests/server/checker/runner/memory/execute.test.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
import type { Systeminformation } from "systeminformation";
|
||||
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import type { RawTargetConfig } from "../../../../../src/server/checker/types";
|
||||
|
||||
import { MemoryChecker } from "../../../../../src/server/checker/runner/memory/execute";
|
||||
|
||||
function makeMemData(overrides: Partial<Systeminformation.MemData> = {}): Systeminformation.MemData {
|
||||
return {
|
||||
active: 4294967296,
|
||||
available: 8589934592,
|
||||
buffcache: 1073741824,
|
||||
buffers: 536870912,
|
||||
cached: 536870912,
|
||||
dirty: null,
|
||||
free: 4294967296,
|
||||
reclaimable: 0,
|
||||
slab: 0,
|
||||
swapfree: 0,
|
||||
swaptotal: 0,
|
||||
swapused: 0,
|
||||
total: 17179869184,
|
||||
used: 8589934592,
|
||||
writeback: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeResolveContext(
|
||||
overrides: Partial<{ configDir: string; defaultIntervalMs: number; defaultTimeoutMs: number }> = {},
|
||||
) {
|
||||
return {
|
||||
configDir: "/test",
|
||||
defaultIntervalMs: 30000,
|
||||
defaultTimeoutMs: 10000,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("MemoryChecker resolve", () => {
|
||||
const checker = new MemoryChecker();
|
||||
|
||||
test("默认值:memory 为空对象", () => {
|
||||
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" };
|
||||
const resolved = checker.resolve(target, makeResolveContext());
|
||||
expect(resolved.memory).toEqual({});
|
||||
});
|
||||
|
||||
test("无 expect 时 expect 为 undefined", () => {
|
||||
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" };
|
||||
const resolved = checker.resolve(target, makeResolveContext());
|
||||
expect(resolved.expect).toBeUndefined();
|
||||
});
|
||||
|
||||
test("保留 expect 字段", () => {
|
||||
const target: RawTargetConfig = {
|
||||
expect: { usagePercent: { lte: 85 } },
|
||||
id: "mem-test",
|
||||
memory: {},
|
||||
type: "memory",
|
||||
};
|
||||
const resolved = checker.resolve(target, makeResolveContext());
|
||||
expect(resolved.expect).toEqual({ usagePercent: { lte: 85 } });
|
||||
});
|
||||
|
||||
test("type 为 memory", () => {
|
||||
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" };
|
||||
const resolved = checker.resolve(target, makeResolveContext());
|
||||
expect(resolved.type).toBe("memory");
|
||||
});
|
||||
});
|
||||
|
||||
describe("MemoryChecker execute", () => {
|
||||
test("成功匹配", async () => {
|
||||
const data = makeMemData({ active: 4294967296, total: 8589934592 });
|
||||
const reader = () => Promise.resolve(data);
|
||||
const checker = new MemoryChecker(reader);
|
||||
|
||||
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" };
|
||||
const resolved = checker.resolve(target, makeResolveContext());
|
||||
resolved.expect = { usagePercent: { lte: 85 } };
|
||||
|
||||
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({
|
||||
totalBytes: 8589934592,
|
||||
usagePercent: 50,
|
||||
});
|
||||
});
|
||||
|
||||
test("usagePercent mismatch", async () => {
|
||||
const data = makeMemData({ active: 7730941132, total: 8589934592 });
|
||||
const reader = () => Promise.resolve(data);
|
||||
const checker = new MemoryChecker(reader);
|
||||
|
||||
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" };
|
||||
const resolved = checker.resolve(target, makeResolveContext());
|
||||
resolved.expect = { usagePercent: { lte: 50 } };
|
||||
|
||||
const ctx = { signal: new AbortController().signal };
|
||||
const result = await checker.execute(resolved, ctx);
|
||||
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure?.phase).toBe("usage");
|
||||
});
|
||||
|
||||
test("observation 包含所有字段", async () => {
|
||||
const data = makeMemData();
|
||||
const reader = () => Promise.resolve(data);
|
||||
const checker = new MemoryChecker(reader);
|
||||
|
||||
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" };
|
||||
const resolved = checker.resolve(target, makeResolveContext());
|
||||
|
||||
const ctx = { signal: new AbortController().signal };
|
||||
const result = await checker.execute(resolved, ctx);
|
||||
|
||||
const obs = result.observation!;
|
||||
expect(obs).toHaveProperty("activeBytes");
|
||||
expect(obs).toHaveProperty("activePercent");
|
||||
expect(obs).toHaveProperty("availableBytes");
|
||||
expect(obs).toHaveProperty("availablePercent");
|
||||
expect(obs).toHaveProperty("buffcacheBytes");
|
||||
expect(obs).toHaveProperty("freeBytes");
|
||||
expect(obs).toHaveProperty("freePercent");
|
||||
expect(obs).toHaveProperty("swapFreeBytes");
|
||||
expect(obs).toHaveProperty("swapTotalBytes");
|
||||
expect(obs).toHaveProperty("swapUsagePercent");
|
||||
expect(obs).toHaveProperty("swapUsedBytes");
|
||||
expect(obs).toHaveProperty("totalBytes");
|
||||
expect(obs).toHaveProperty("usagePercent");
|
||||
expect(obs).toHaveProperty("usedBytes");
|
||||
expect(obs).toHaveProperty("usedPercent");
|
||||
});
|
||||
|
||||
test("reader reject 返回失败结果", async () => {
|
||||
const reader = () => Promise.reject(new Error("read error"));
|
||||
const checker = new MemoryChecker(reader);
|
||||
|
||||
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" };
|
||||
const resolved = checker.resolve(target, makeResolveContext());
|
||||
|
||||
const ctx = { signal: new AbortController().signal };
|
||||
const result = await checker.execute(resolved, ctx);
|
||||
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure?.phase).toBe("memory");
|
||||
expect(result.failure?.path).toBe("snapshot");
|
||||
expect(result.observation).toBeNull();
|
||||
});
|
||||
|
||||
test("detail 格式", async () => {
|
||||
const data = makeMemData({ active: 4294967296, total: 8589934592 });
|
||||
const reader = () => Promise.resolve(data);
|
||||
const checker = new MemoryChecker(reader);
|
||||
|
||||
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" };
|
||||
const resolved = checker.resolve(target, makeResolveContext());
|
||||
|
||||
const ctx = { signal: new AbortController().signal };
|
||||
const result = await checker.execute(resolved, ctx);
|
||||
|
||||
const detail = checker.buildDetail(result.observation!);
|
||||
expect(detail).toContain("usage");
|
||||
expect(detail).toContain("%");
|
||||
expect(detail).toContain("total");
|
||||
});
|
||||
});
|
||||
|
||||
describe("MemoryChecker serialize", () => {
|
||||
test("序列化输出", () => {
|
||||
const checker = new MemoryChecker();
|
||||
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" };
|
||||
const resolved = checker.resolve(target, makeResolveContext());
|
||||
const result = checker.serialize(resolved);
|
||||
expect(result.target).toBe("memory");
|
||||
const config = JSON.parse(result.config) as Record<string, unknown>;
|
||||
expect(config).toEqual({});
|
||||
});
|
||||
});
|
||||
149
tests/server/checker/runner/memory/expect.test.ts
Normal file
149
tests/server/checker/runner/memory/expect.test.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import {
|
||||
checkActiveBytes,
|
||||
checkActivePercent,
|
||||
checkAvailableBytes,
|
||||
checkAvailablePercent,
|
||||
checkBuffcacheBytes,
|
||||
checkFreeBytes,
|
||||
checkFreePercent,
|
||||
checkSwapFreeBytes,
|
||||
checkSwapTotalBytes,
|
||||
checkSwapUsagePercent,
|
||||
checkSwapUsedBytes,
|
||||
checkTotalBytes,
|
||||
checkUsagePercent,
|
||||
checkUsedBytes,
|
||||
checkUsedPercent,
|
||||
} from "../../../../../src/server/checker/runner/memory/expect";
|
||||
|
||||
describe("Memory expect checks - 百分比字段", () => {
|
||||
test("checkUsagePercent 匹配", () => {
|
||||
expect(checkUsagePercent(50, { lte: 85 }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("checkUsagePercent 不匹配", () => {
|
||||
const result = checkUsagePercent(90, { lte: 85 });
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure?.phase).toBe("usage");
|
||||
});
|
||||
|
||||
test("checkUsedPercent 匹配", () => {
|
||||
expect(checkUsedPercent(75, { lte: 80 }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("checkUsedPercent 不匹配", () => {
|
||||
const result = checkUsedPercent(85, { lte: 80 });
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure?.phase).toBe("used");
|
||||
});
|
||||
|
||||
test("checkFreePercent 匹配", () => {
|
||||
expect(checkFreePercent(25, { gte: 15 }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("checkFreePercent 不匹配", () => {
|
||||
const result = checkFreePercent(10, { gte: 15 });
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure?.phase).toBe("free");
|
||||
});
|
||||
|
||||
test("checkActivePercent 匹配", () => {
|
||||
expect(checkActivePercent(50, { lte: 85 }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("checkAvailablePercent 匹配", () => {
|
||||
expect(checkAvailablePercent(50, { gte: 20 }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("undefined matcher 直接通过", () => {
|
||||
expect(checkUsagePercent(99.9, undefined).matched).toBe(true);
|
||||
expect(checkFreePercent(0, undefined).matched).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Memory expect checks - 字节字段", () => {
|
||||
test("checkActiveBytes 匹配", () => {
|
||||
expect(checkActiveBytes(4294967296, { lte: 8589934592 }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("checkUsedBytes 不匹配", () => {
|
||||
const result = checkUsedBytes(10737418240, { lte: 8589934592 });
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure?.phase).toBe("usedBytes");
|
||||
});
|
||||
|
||||
test("checkFreeBytes 匹配", () => {
|
||||
expect(checkFreeBytes(4294967296, { gte: 2147483648 }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("checkAvailableBytes 匹配", () => {
|
||||
expect(checkAvailableBytes(6442450944, { gte: 4294967296 }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("checkTotalBytes 匹配", () => {
|
||||
expect(checkTotalBytes(17179869184, { gte: 8589934592 }).matched).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Memory expect checks - swap 字段", () => {
|
||||
test("checkSwapUsagePercent null 通过 gte 检查 (Number(null)=0)", () => {
|
||||
expect(checkSwapUsagePercent(null, { gte: 0 }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("checkSwapUsagePercent null 不匹配大于 0 的 gte", () => {
|
||||
const result = checkSwapUsagePercent(null, { gte: 1 });
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure?.phase).toBe("swapUsage");
|
||||
});
|
||||
|
||||
test("checkSwapUsagePercent 有值时正常匹配", () => {
|
||||
expect(checkSwapUsagePercent(50, { lte: 80 }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("checkSwapUsagePercent 有值时不匹配", () => {
|
||||
const result = checkSwapUsagePercent(90, { lte: 80 });
|
||||
expect(result.matched).toBe(false);
|
||||
});
|
||||
|
||||
test("checkSwapUsedBytes null 通过 gte:0 (Number(null)=0)", () => {
|
||||
expect(checkSwapUsedBytes(null, { gte: 0 }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("checkSwapUsedBytes 0 通过 gte:0", () => {
|
||||
expect(checkSwapUsedBytes(0, { gte: 0 }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("checkSwapFreeBytes null 通过 gte:0", () => {
|
||||
expect(checkSwapFreeBytes(null, { gte: 0 }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("checkSwapTotalBytes null 匹配 equals:null", () => {
|
||||
expect(checkSwapTotalBytes(null, { equals: null }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("checkSwapTotalBytes 0 匹配 equals:0", () => {
|
||||
expect(checkSwapTotalBytes(0, { equals: 0 }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("checkSwapTotalBytes null 不匹配 equals:0", () => {
|
||||
expect(checkSwapTotalBytes(null, { equals: 0 }).matched).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Memory expect checks - buffcacheBytes", () => {
|
||||
test("checkBuffcacheBytes 有值时匹配", () => {
|
||||
expect(checkBuffcacheBytes(1073741824, { lte: 2147483648 }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("checkBuffcacheBytes null 通过 gte:0 (Number(null)=0)", () => {
|
||||
expect(checkBuffcacheBytes(null, { gte: 0 }).matched).toBe(true);
|
||||
});
|
||||
|
||||
test("checkBuffcacheBytes null 不匹配 gte:1", () => {
|
||||
const result = checkBuffcacheBytes(null, { gte: 1 });
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure?.phase).toBe("buffcacheBytes");
|
||||
});
|
||||
});
|
||||
72
tests/server/checker/runner/memory/normalize.test.ts
Normal file
72
tests/server/checker/runner/memory/normalize.test.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { normalizeTargetExpect } from "../../../../../src/server/checker/runner/memory/normalize";
|
||||
|
||||
describe("normalizeTargetExpect (memory)", () => {
|
||||
test("无 expect 直接返回", () => {
|
||||
const target = { id: "test", memory: {}, type: "memory" };
|
||||
expect(normalizeTargetExpect(target)).toEqual(target);
|
||||
});
|
||||
|
||||
test("expect 为非对象直接返回", () => {
|
||||
const target = { expect: "not-an-object", id: "test", memory: {}, type: "memory" };
|
||||
expect(normalizeTargetExpect(target)).toEqual(target);
|
||||
});
|
||||
|
||||
test("字节大小字符串 512MB 转换为数字", () => {
|
||||
const target = { expect: { usedBytes: "512MB" }, id: "test", memory: {}, type: "memory" };
|
||||
const result = normalizeTargetExpect(target);
|
||||
expect((result.expect as Record<string, unknown>)["usedBytes"]).toEqual({ equals: 536870912 });
|
||||
});
|
||||
|
||||
test("字节大小字符串 1GB 转换为数字", () => {
|
||||
const target = { expect: { totalBytes: "1GB" }, id: "test", memory: {}, type: "memory" };
|
||||
const result = normalizeTargetExpect(target);
|
||||
expect((result.expect as Record<string, unknown>)["totalBytes"]).toEqual({ equals: 1073741824 });
|
||||
});
|
||||
|
||||
test("数字字节 matcher 保持不变", () => {
|
||||
const target = { expect: { usedBytes: 1073741824 }, id: "test", memory: {}, type: "memory" };
|
||||
const result = normalizeTargetExpect(target);
|
||||
expect((result.expect as Record<string, unknown>)["usedBytes"]).toEqual({ equals: 1073741824 });
|
||||
});
|
||||
|
||||
test("百分比 matcher 正常展开", () => {
|
||||
const target = { expect: { usagePercent: 85 }, id: "test", memory: {}, type: "memory" };
|
||||
const result = normalizeTargetExpect(target);
|
||||
expect((result.expect as Record<string, unknown>)["usagePercent"]).toEqual({ equals: 85 });
|
||||
});
|
||||
|
||||
test("matcher 对象保持不变", () => {
|
||||
const target = { expect: { usagePercent: { lte: 85 } }, id: "test", memory: {}, type: "memory" };
|
||||
const result = normalizeTargetExpect(target);
|
||||
expect((result.expect as Record<string, unknown>)["usagePercent"]).toEqual({ lte: 85 });
|
||||
});
|
||||
|
||||
test("字节 matcher 对象内字符串转换", () => {
|
||||
const target = { expect: { usedBytes: { gte: "512MB" } }, id: "test", memory: {}, type: "memory" };
|
||||
const result = normalizeTargetExpect(target);
|
||||
expect((result.expect as Record<string, unknown>)["usedBytes"]).toEqual({ gte: 536870912 });
|
||||
});
|
||||
|
||||
test("多个字段同时处理", () => {
|
||||
const target = {
|
||||
expect: { freePercent: 25, totalBytes: "16GB", usagePercent: { lte: 85 } },
|
||||
id: "test",
|
||||
memory: {},
|
||||
type: "memory",
|
||||
};
|
||||
const result = normalizeTargetExpect(target);
|
||||
const expectObj = result.expect as Record<string, unknown>;
|
||||
expect(expectObj["freePercent"]).toEqual({ equals: 25 });
|
||||
expect(expectObj["totalBytes"]).toEqual({ equals: 17179869184 });
|
||||
expect(expectObj["usagePercent"]).toEqual({ lte: 85 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeTargetExpect (memory) 错误", () => {
|
||||
test("非法大小字符串抛出", () => {
|
||||
const target = { expect: { usedBytes: "abc" }, id: "test", memory: {}, type: "memory" };
|
||||
expect(() => normalizeTargetExpect(target)).toThrow();
|
||||
});
|
||||
});
|
||||
95
tests/server/checker/runner/memory/schema.test.ts
Normal file
95
tests/server/checker/runner/memory/schema.test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import Ajv from "ajv";
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { memoryCheckerSchemas } from "../../../../../src/server/checker/runner/memory/schema";
|
||||
|
||||
const ajv = new Ajv({ strict: false });
|
||||
|
||||
describe("Memory checker schema", () => {
|
||||
test("authoring config 空配置通过", () => {
|
||||
const validate = ajv.compile(memoryCheckerSchemas.authoring.config);
|
||||
expect(validate({})).toBe(true);
|
||||
});
|
||||
|
||||
test("normalized config 空配置通过", () => {
|
||||
const validate = ajv.compile(memoryCheckerSchemas.normalized.config);
|
||||
expect(validate({})).toBe(true);
|
||||
});
|
||||
|
||||
test("config 拒绝额外字段", () => {
|
||||
const validate = ajv.compile(memoryCheckerSchemas.authoring.config);
|
||||
expect(validate({ extraField: true })).toBe(false);
|
||||
});
|
||||
|
||||
test("authoring expect 允许百分比 ValueMatcher 简写", () => {
|
||||
const validate = ajv.compile(memoryCheckerSchemas.authoring.expect);
|
||||
expect(validate({ usagePercent: 85 })).toBe(true);
|
||||
expect(validate({ usagePercent: { lte: 85 } })).toBe(true);
|
||||
});
|
||||
|
||||
test("authoring expect 允许字节字段字符串", () => {
|
||||
const validate = ajv.compile(memoryCheckerSchemas.authoring.expect);
|
||||
expect(validate({ usedBytes: "512MB" })).toBe(true);
|
||||
expect(validate({ totalBytes: "1GB" })).toBe(true);
|
||||
});
|
||||
|
||||
test("authoring expect 允许字节字段数字", () => {
|
||||
const validate = ajv.compile(memoryCheckerSchemas.authoring.expect);
|
||||
expect(validate({ usedBytes: 536870912 })).toBe(true);
|
||||
});
|
||||
|
||||
test("normalized expect 允许 matcher 对象", () => {
|
||||
const validate = ajv.compile(memoryCheckerSchemas.normalized.expect);
|
||||
expect(validate({ freePercent: { gte: 15 }, usagePercent: { lte: 85 } })).toBe(true);
|
||||
});
|
||||
|
||||
test("expect 拒绝未知字段", () => {
|
||||
const validate = ajv.compile(memoryCheckerSchemas.authoring.expect);
|
||||
expect(validate({ unknownField: 1 })).toBe(false);
|
||||
});
|
||||
|
||||
test("expect 空对象通过", () => {
|
||||
const validate = ajv.compile(memoryCheckerSchemas.normalized.expect);
|
||||
expect(validate({})).toBe(true);
|
||||
});
|
||||
|
||||
test("expect 允许所有合法百分比字段", () => {
|
||||
const validate = ajv.compile(memoryCheckerSchemas.normalized.expect);
|
||||
expect(
|
||||
validate({
|
||||
activePercent: { lte: 80 },
|
||||
availablePercent: { gte: 20 },
|
||||
freePercent: { gte: 15 },
|
||||
swapUsagePercent: { lte: 50 },
|
||||
usagePercent: { lte: 85 },
|
||||
usedPercent: { lte: 90 },
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test("expect 允许所有合法字节字段", () => {
|
||||
const validate = ajv.compile(memoryCheckerSchemas.normalized.expect);
|
||||
expect(
|
||||
validate({
|
||||
activeBytes: { lte: 8589934592 },
|
||||
availableBytes: { gte: 4294967296 },
|
||||
freeBytes: { gte: 2147483648 },
|
||||
swapFreeBytes: { gte: 0 },
|
||||
swapTotalBytes: { lte: 4294967296 },
|
||||
swapUsedBytes: { lte: 2147483648 },
|
||||
totalBytes: { equals: 17179869184 },
|
||||
usedBytes: { lte: 8589934592 },
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test("expect 允许 durationMs 字段", () => {
|
||||
const validate = ajv.compile(memoryCheckerSchemas.normalized.expect);
|
||||
expect(validate({ durationMs: { lte: 5000 } })).toBe(true);
|
||||
});
|
||||
|
||||
test("expect 允许 buffcacheBytes 字段", () => {
|
||||
const validate = ajv.compile(memoryCheckerSchemas.normalized.expect);
|
||||
expect(validate({ buffcacheBytes: { lte: 2147483648 } })).toBe(true);
|
||||
});
|
||||
});
|
||||
87
tests/server/checker/runner/memory/validate.test.ts
Normal file
87
tests/server/checker/runner/memory/validate.test.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import type { RawTargetConfig } from "../../../../../src/server/checker/types";
|
||||
|
||||
import { validateMemoryConfig } from "../../../../../src/server/checker/runner/memory/validate";
|
||||
|
||||
function validate(target: RawTargetConfig) {
|
||||
return validateMemoryConfig({ targets: [target] });
|
||||
}
|
||||
|
||||
describe("validateMemoryConfig", () => {
|
||||
test("有效配置无错误", () => {
|
||||
expect(validate({ id: "mem-test", memory: {}, type: "memory" })).toEqual([]);
|
||||
});
|
||||
|
||||
test("缺少 memory 配置分组", () => {
|
||||
const issues = validate({ id: "mem-test", type: "memory" });
|
||||
expect(issues.some((i) => i.path.endsWith("memory") && i.code === "required")).toBe(true);
|
||||
});
|
||||
|
||||
test("memory 未知字段报错", () => {
|
||||
const issues = validate({ id: "mem-test", memory: { extra: true }, type: "memory" });
|
||||
expect(issues.some((i) => i.path.endsWith("extra") && i.code === "unknown-field")).toBe(true);
|
||||
});
|
||||
|
||||
test("expect 未知字段报错", () => {
|
||||
const issues = validate({ expect: { logicalCoreCount: { gte: 4 } }, id: "mem-test", memory: {}, type: "memory" });
|
||||
expect(issues.some((i) => i.path.endsWith("logicalCoreCount") && i.code === "unknown-field")).toBe(true);
|
||||
});
|
||||
|
||||
test("expect 合法 ValueMatcher 通过", () => {
|
||||
const issues = validate({
|
||||
expect: { usagePercent: { lte: 85 }, usedBytes: { lte: 8589934592 } },
|
||||
id: "mem-test",
|
||||
memory: {},
|
||||
type: "memory",
|
||||
});
|
||||
expect(issues.filter((i) => i.path.includes("expect"))).toEqual([]);
|
||||
});
|
||||
|
||||
test("expect 非法 ValueMatcher 报错", () => {
|
||||
const issues = validate({ expect: { usagePercent: [1, 2] }, id: "mem-test", memory: {}, type: "memory" });
|
||||
expect(issues.some((i) => i.path.includes("usagePercent"))).toBe(true);
|
||||
});
|
||||
|
||||
test("expect 合法字节大小字符串通过", () => {
|
||||
const issues = validate({ expect: { usedBytes: "512MB" }, id: "mem-test", memory: {}, type: "memory" });
|
||||
expect(issues.filter((i) => i.path.includes("usedBytes"))).toEqual([]);
|
||||
});
|
||||
|
||||
test("expect 非法字节大小字符串报错", () => {
|
||||
const issues = validate({ expect: { usedBytes: "abc" }, id: "mem-test", memory: {}, type: "memory" });
|
||||
expect(issues.some((i) => i.path.includes("usedBytes") && i.message.includes("字节大小"))).toBe(true);
|
||||
});
|
||||
|
||||
test("expect 所有合法字段通过", () => {
|
||||
const issues = validate({
|
||||
expect: {
|
||||
activeBytes: { lte: 8589934592 },
|
||||
activePercent: { lte: 80 },
|
||||
availableBytes: { gte: 4294967296 },
|
||||
availablePercent: { gte: 20 },
|
||||
buffcacheBytes: { lte: 2147483648 },
|
||||
durationMs: { lte: 5000 },
|
||||
freeBytes: { gte: 2147483648 },
|
||||
freePercent: { gte: 15 },
|
||||
swapFreeBytes: { gte: 0 },
|
||||
swapTotalBytes: { lte: 4294967296 },
|
||||
swapUsagePercent: { lte: 50 },
|
||||
swapUsedBytes: { lte: 2147483648 },
|
||||
totalBytes: { equals: 17179869184 },
|
||||
usagePercent: { lte: 85 },
|
||||
usedBytes: { lte: 8589934592 },
|
||||
usedPercent: { lte: 90 },
|
||||
},
|
||||
id: "mem-test",
|
||||
memory: {},
|
||||
type: "memory",
|
||||
});
|
||||
expect(issues).toEqual([]);
|
||||
});
|
||||
|
||||
test("非 memory type 的 target 不校验", () => {
|
||||
const issues = validate({ id: "other-test", type: "http" });
|
||||
expect(issues).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -84,9 +84,22 @@ describe("CheckerRegistry", () => {
|
||||
"dns",
|
||||
"ws",
|
||||
"cpu",
|
||||
"memory",
|
||||
"custom",
|
||||
]);
|
||||
expect(second.supportedTypes).toEqual(["http", "cmd", "db", "tcp", "icmp", "udp", "llm", "dns", "ws", "cpu"]);
|
||||
expect(second.supportedTypes).toEqual([
|
||||
"http",
|
||||
"cmd",
|
||||
"db",
|
||||
"tcp",
|
||||
"icmp",
|
||||
"udp",
|
||||
"llm",
|
||||
"dns",
|
||||
"ws",
|
||||
"cpu",
|
||||
"memory",
|
||||
]);
|
||||
expect(
|
||||
first.definitions.every((checker) => checker.schemas.authoring.config && checker.schemas.normalized.expect),
|
||||
).toBe(true);
|
||||
|
||||
Reference in New Issue
Block a user