1
0

refactor: 将 memory checker 重命名为 mem

- 类型标识符 memory → mem
- 类名 MemoryChecker → MemChecker
- 内部类型名统一 Memory* → Mem*
- 内部函数名统一 *Memory* → *Mem*
- 目录重命名 memory/ → mem/(源码、测试、文档)
- 配置键 memory: → mem:
- 重新生成 probe-config.schema.json
- 保留中文"内存"用户提示

破坏性变更:无向后兼容
This commit is contained in:
2026-05-27 18:16:33 +08:00
parent 3390eb5e8d
commit 2f8fd8bd9c
20 changed files with 194 additions and 196 deletions

View File

@@ -1,13 +1,13 @@
# Memory Checker # Mem Checker
`type: memory` 用于检查本机系统级内存使用状况,包括物理内存和交换空间的使用率及字节数。 `type: mem` 用于检查本机系统级内存使用状况,包括物理内存和交换空间的使用率及字节数。
## 配置项 ## 配置项
Memory checker 配置为空对象,无需额外参数: Mem checker 配置为空对象,无需额外参数:
```yaml ```yaml
memory: {} mem: {}
``` ```
## expect 校验项 ## expect 校验项
@@ -54,10 +54,10 @@ memory: {}
```yaml ```yaml
- id: "local-memory" - id: "local-memory"
name: "本机内存" name: "本机内存"
type: memory type: mem
interval: "30s" interval: "30s"
timeout: "5s" timeout: "5s"
memory: {} mem: {}
expect: expect:
usagePercent: usagePercent:
lte: 85 lte: 85
@@ -68,8 +68,8 @@ memory: {}
```yaml ```yaml
- id: "local-memory-available" - id: "local-memory-available"
name: "可用内存检查" name: "可用内存检查"
type: memory type: mem
memory: {} mem: {}
expect: expect:
availableBytes: availableBytes:
gte: "4GB" gte: "4GB"
@@ -80,8 +80,8 @@ memory: {}
```yaml ```yaml
- id: "local-memory-swap" - id: "local-memory-swap"
name: "内存和交换空间" name: "内存和交换空间"
type: memory type: mem
memory: {} mem: {}
expect: expect:
usagePercent: usagePercent:
lte: 80 lte: 80
@@ -91,14 +91,14 @@ memory: {}
## 语义说明 ## 语义说明
Memory checker 通过 `systeminformation` 库读取系统内存数据,在 Linux、macOS 和 Windows 上均可运行。 Mem checker 通过 `systeminformation` 库读取系统内存数据,在 Linux、macOS 和 Windows 上均可运行。
- **`usagePercent`** 使用 `activeBytes / totalBytes` 计算,反映真实的内存压力,不受 Linux buffers/cache 缓存影响。推荐使用此字段进行内存健康检查。 - **`usagePercent`** 使用 `activeBytes / totalBytes` 计算,反映真实的内存压力,不受 Linux buffers/cache 缓存影响。推荐使用此字段进行内存健康检查。
- **`usedPercent`** 使用 `usedBytes / totalBytes` 计算,包含 buffers/cache。在 Linux 上此值通常高于 `usagePercent` - **`usedPercent`** 使用 `usedBytes / totalBytes` 计算,包含 buffers/cache。在 Linux 上此值通常高于 `usagePercent`
- **Swap 字段**:当系统未配置交换分区时,`swapTotalBytes``0``swapUsagePercent``null`(非 `0`)。 - **Swap 字段**:当系统未配置交换分区时,`swapTotalBytes``0``swapUsagePercent``null`(非 `0`)。
- **`buffcacheBytes`**:反映 Linux 的 buffers + cache 用量,在其他平台上可能为 `null` - **`buffcacheBytes`**:反映 Linux 的 buffers + cache 用量,在其他平台上可能为 `null`
Memory checker 是即时读取(非采样),无需 `sampleDuration`,执行速度远快于 CPU checker。虽然读取本身很快但仍受 target `timeout` 约束——若底层系统调用悬挂或阻塞超过 `timeout`checker 会返回 `memory/timeout` failure。 Mem checker 是即时读取(非采样),无需 `sampleDuration`,执行速度远快于 CPU checker。虽然读取本身很快但仍受 target `timeout` 约束——若底层系统调用悬挂或阻塞超过 `timeout`checker 会返回 `mem/timeout` failure。
## 跨平台注意事项 ## 跨平台注意事项
@@ -116,4 +116,4 @@ Memory checker 是即时读取(非采样),无需 `sampleDuration`,执行
## 更新触发条件 ## 更新触发条件
修改 Memory checker 配置、expect 字段、行为或语义时,必须更新本文档。 修改 Mem checker 配置、expect 字段、行为或语义时,必须更新本文档。

View File

@@ -6857,7 +6857,7 @@
"required": [ "required": [
"id", "id",
"type", "type",
"memory" "mem"
], ],
"properties": { "properties": {
"description": { "description": {
@@ -7616,10 +7616,10 @@
"type": "string" "type": "string"
}, },
"type": { "type": {
"const": "memory", "const": "mem",
"type": "string" "type": "string"
}, },
"memory": { "mem": {
"additionalProperties": false, "additionalProperties": false,
"type": "object", "type": "object",
"properties": {} "properties": {}

View File

@@ -370,11 +370,11 @@ targets:
- id: "local-memory" - id: "local-memory"
name: "本机内存" name: "本机内存"
type: memory type: mem
group: "基础设施" group: "基础设施"
interval: "30s" interval: "30s"
timeout: "5s" timeout: "5s"
memory: {} mem: {}
expect: expect:
usagePercent: usagePercent:
lte: 85 lte: 85

View File

@@ -5,7 +5,7 @@ import { DnsChecker } from "./dns";
import { HttpChecker } from "./http"; import { HttpChecker } from "./http";
import { IcmpChecker } from "./icmp"; import { IcmpChecker } from "./icmp";
import { LlmChecker } from "./llm"; import { LlmChecker } from "./llm";
import { MemoryChecker } from "./memory"; import { MemChecker } from "./mem";
import { CheckerRegistry } from "./registry"; import { CheckerRegistry } from "./registry";
import { TcpChecker } from "./tcp"; import { TcpChecker } from "./tcp";
import { UdpChecker } from "./udp"; import { UdpChecker } from "./udp";
@@ -22,7 +22,7 @@ const checkers = [
new DnsChecker(), new DnsChecker(),
new WsChecker(), new WsChecker(),
new CpuChecker(), new CpuChecker(),
new MemoryChecker(), new MemChecker(),
]; ];
export function createDefaultCheckerRegistry(): CheckerRegistry { export function createDefaultCheckerRegistry(): CheckerRegistry {

View File

@@ -1,8 +1,8 @@
import type { Systeminformation } from "systeminformation"; import type { Systeminformation } from "systeminformation";
import type { MemoryStats } from "./types"; import type { MemStats } from "./types";
export function calculateMemoryStats(data: Systeminformation.MemData): MemoryStats { export function calculateMemStats(data: Systeminformation.MemData): MemStats {
const totalBytes = data.total; const totalBytes = data.total;
const usedBytes = data.used; const usedBytes = data.used;
const activeBytes = data.active; const activeBytes = data.active;
@@ -41,7 +41,7 @@ export function calculateMemoryStats(data: Systeminformation.MemData): MemorySta
}; };
} }
export async function readMemoryData(): Promise<Systeminformation.MemData> { export async function readMemData(): Promise<Systeminformation.MemData> {
const si = await import("systeminformation"); const si = await import("systeminformation");
return si.mem(); return si.mem();
} }

View File

@@ -2,11 +2,11 @@ import type { Systeminformation } from "systeminformation";
import type { CheckResult, RawTargetConfig } from "../../types"; import type { CheckResult, RawTargetConfig } from "../../types";
import type { CheckerContext, CheckerDefinition, CheckerValidationInput, ResolveContext } from "../types"; import type { CheckerContext, CheckerDefinition, CheckerValidationInput, ResolveContext } from "../types";
import type { MemoryStats, ResolvedMemoryExpectConfig, ResolvedMemoryTarget } from "./types"; import type { MemStats, ResolvedMemExpectConfig, ResolvedMemTarget } from "./types";
import { errorFailure } from "../../expect/failure"; import { errorFailure } from "../../expect/failure";
import { checkValueExpectation } from "../../expect/value"; import { checkValueExpectation } from "../../expect/value";
import { calculateMemoryStats, readMemoryData } from "./calculate"; import { calculateMemStats, readMemData } from "./calculate";
import { import {
checkActiveBytes, checkActiveBytes,
checkActivePercent, checkActivePercent,
@@ -25,17 +25,17 @@ import {
checkUsedPercent, checkUsedPercent,
} from "./expect"; } from "./expect";
import { normalizeTargetExpect } from "./normalize"; import { normalizeTargetExpect } from "./normalize";
import { memoryCheckerSchemas } from "./schema"; import { memCheckerSchemas } from "./schema";
import { validateMemoryConfig } from "./validate"; import { validateMemConfig } from "./validate";
export class MemoryChecker implements CheckerDefinition<ResolvedMemoryTarget> { export class MemChecker implements CheckerDefinition<ResolvedMemTarget> {
readonly configKey = "memory"; readonly configKey = "mem";
readonly schemas = memoryCheckerSchemas; readonly schemas = memCheckerSchemas;
readonly type = "memory"; readonly type = "mem";
constructor(private readonly reader: () => Promise<Systeminformation.MemData> = readMemoryData) {} constructor(private readonly reader: () => Promise<Systeminformation.MemData> = readMemData) {}
buildDetail(observation: Record<string, unknown>): null | string { buildDetail(observation: Record<string, unknown>): null | string {
const usage = observation["usagePercent"]; const usage = observation["usagePercent"];
@@ -45,7 +45,7 @@ export class MemoryChecker implements CheckerDefinition<ResolvedMemoryTarget> {
return `usage ${usageStr}%, total ${totalStr}`; return `usage ${usageStr}%, total ${totalStr}`;
} }
async execute(t: ResolvedMemoryTarget, ctx: CheckerContext): Promise<CheckResult> { async execute(t: ResolvedMemTarget, ctx: CheckerContext): Promise<CheckResult> {
const timestamp = new Date().toISOString(); const timestamp = new Date().toISOString();
const start = performance.now(); const start = performance.now();
@@ -54,7 +54,7 @@ export class MemoryChecker implements CheckerDefinition<ResolvedMemoryTarget> {
return { return {
detail: null, detail: null,
durationMs, durationMs,
failure: errorFailure("memory", "timeout", "内存读取超时signal 已取消"), failure: errorFailure("mem", "timeout", "内存读取超时signal 已取消"),
matched: false, matched: false,
observation: null, observation: null,
targetId: t.id, targetId: t.id,
@@ -68,14 +68,14 @@ export class MemoryChecker implements CheckerDefinition<ResolvedMemoryTarget> {
} catch (error) { } catch (error) {
const durationMs = Math.round(performance.now() - start); const durationMs = Math.round(performance.now() - start);
const isTimeout = const isTimeout =
error instanceof AbortError || (error instanceof Error && error.message === MEMORY_TIMEOUT_MESSAGE); error instanceof AbortError || (error instanceof Error && error.message === MEM_TIMEOUT_MESSAGE);
return { return {
detail: null, detail: null,
durationMs, durationMs,
failure: isTimeout failure: isTimeout
? errorFailure("memory", "timeout", "内存读取超时") ? errorFailure("mem", "timeout", "内存读取超时")
: errorFailure( : errorFailure(
"memory", "mem",
"snapshot", "snapshot",
`内存数据读取失败: ${error instanceof Error ? error.message : String(error)}`, `内存数据读取失败: ${error instanceof Error ? error.message : String(error)}`,
), ),
@@ -87,7 +87,7 @@ export class MemoryChecker implements CheckerDefinition<ResolvedMemoryTarget> {
} }
const durationMs = Math.round(performance.now() - start); const durationMs = Math.round(performance.now() - start);
const stats = calculateMemoryStats(data); const stats = calculateMemStats(data);
const result = checkStats(stats, t.expect, durationMs); const result = checkStats(stats, t.expect, durationMs);
const observation: Record<string, unknown> = { const observation: Record<string, unknown> = {
@@ -124,33 +124,33 @@ export class MemoryChecker implements CheckerDefinition<ResolvedMemoryTarget> {
return normalizeTargetExpect(target); return normalizeTargetExpect(target);
} }
resolve(target: RawTargetConfig, context: ResolveContext): ResolvedMemoryTarget { resolve(target: RawTargetConfig, context: ResolveContext): ResolvedMemTarget {
return { return {
description: null, description: null,
expect: target.expect as ResolvedMemoryExpectConfig | undefined, expect: target.expect as ResolvedMemExpectConfig | undefined,
group: target.group ?? "default", group: target.group ?? "default",
id: target.id, id: target.id,
intervalMs: context.defaultIntervalMs, intervalMs: context.defaultIntervalMs,
memory: {}, mem: {},
name: target.name ?? null, name: target.name ?? null,
timeoutMs: context.defaultTimeoutMs, timeoutMs: context.defaultTimeoutMs,
type: "memory", type: "mem",
} satisfies ResolvedMemoryTarget; } satisfies ResolvedMemTarget;
} }
serialize(t: ResolvedMemoryTarget): { config: string; target: string } { serialize(t: ResolvedMemTarget): { config: string; target: string } {
return { return {
config: JSON.stringify(t.memory), config: JSON.stringify(t.mem),
target: `memory`, target: `mem`,
}; };
} }
validate(input: CheckerValidationInput) { validate(input: CheckerValidationInput) {
return validateMemoryConfig(input); return validateMemConfig(input);
} }
} }
function checkStats(stats: MemoryStats, expect: ResolvedMemoryExpectConfig | undefined, durationMs: number) { function checkStats(stats: MemStats, expect: ResolvedMemExpectConfig | undefined, durationMs: number) {
let result = checkUsagePercent(stats.usagePercent, expect?.usagePercent); let result = checkUsagePercent(stats.usagePercent, expect?.usagePercent);
if (!result.matched) return result; if (!result.matched) return result;
result = checkUsedPercent(stats.usedPercent, expect?.usedPercent); result = checkUsedPercent(stats.usedPercent, expect?.usedPercent);
@@ -199,11 +199,11 @@ function formatNumber(value: number): string {
return Number.isInteger(value) ? String(value) : String(Number(value.toFixed(1))); return Number.isInteger(value) ? String(value) : String(Number(value.toFixed(1)));
} }
const MEMORY_TIMEOUT_MESSAGE = "Memory read aborted by signal"; const MEM_TIMEOUT_MESSAGE = "Memory read aborted by signal";
class AbortError extends Error { class AbortError extends Error {
constructor() { constructor() {
super(MEMORY_TIMEOUT_MESSAGE); super(MEM_TIMEOUT_MESSAGE);
this.name = "AbortError"; this.name = "AbortError";
} }
} }

View File

@@ -0,0 +1 @@
export { MemChecker } from "./execute";

View File

@@ -9,22 +9,22 @@ import {
sizeSchema, sizeSchema,
} from "../../schema/fragments"; } from "../../schema/fragments";
export const memoryCheckerSchemas: CheckerSchemas = { export const memCheckerSchemas: CheckerSchemas = {
authoring: { authoring: {
config: createMemoryConfigSchema("authoring"), config: createMemConfigSchema("authoring"),
expect: createMemoryExpectSchema("authoring"), expect: createMemExpectSchema("authoring"),
}, },
normalized: { normalized: {
config: createMemoryConfigSchema("normalized"), config: createMemConfigSchema("normalized"),
expect: createMemoryExpectSchema("normalized"), expect: createMemExpectSchema("normalized"),
}, },
}; };
function createMemoryConfigSchema(_kind: "authoring" | "normalized") { function createMemConfigSchema(_kind: "authoring" | "normalized") {
return Type.Object({}, { additionalProperties: false }); return Type.Object({}, { additionalProperties: false });
} }
function createMemoryExpectSchema(kind: "authoring" | "normalized") { function createMemExpectSchema(kind: "authoring" | "normalized") {
const valueSchema = const valueSchema =
kind === "authoring" ? createAuthoringValueExpectationSchema() : createNormalizedValueExpectationSchema(); kind === "authoring" ? createAuthoringValueExpectationSchema() : createNormalizedValueExpectationSchema();

View File

@@ -3,9 +3,9 @@ import type { Systeminformation } from "systeminformation";
import type { RawValueExpectation, ValueExpectation } from "../../expect/types"; import type { RawValueExpectation, ValueExpectation } from "../../expect/types";
import type { ResolvedTargetBase } from "../../types"; import type { ResolvedTargetBase } from "../../types";
export type MemoryDataReader = () => Promise<Systeminformation.MemData>; export type MemDataReader = () => Promise<Systeminformation.MemData>;
export interface MemoryStats { export interface MemStats {
activeBytes: number; activeBytes: number;
activePercent: number; activePercent: number;
availableBytes: number; availableBytes: number;
@@ -23,7 +23,7 @@ export interface MemoryStats {
usedPercent: number; usedPercent: number;
} }
export interface RawMemoryExpectConfig { export interface RawMemExpectConfig {
activeBytes?: RawValueExpectation; activeBytes?: RawValueExpectation;
activePercent?: RawValueExpectation; activePercent?: RawValueExpectation;
availableBytes?: RawValueExpectation; availableBytes?: RawValueExpectation;
@@ -43,9 +43,9 @@ export interface RawMemoryExpectConfig {
} }
// eslint-disable-next-line @typescript-eslint/no-empty-object-type // eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface ResolvedMemoryConfig {} export interface ResolvedMemConfig {}
export interface ResolvedMemoryExpectConfig { export interface ResolvedMemExpectConfig {
activeBytes?: ValueExpectation; activeBytes?: ValueExpectation;
activePercent?: ValueExpectation; activePercent?: ValueExpectation;
availableBytes?: ValueExpectation; availableBytes?: ValueExpectation;
@@ -64,12 +64,12 @@ export interface ResolvedMemoryExpectConfig {
usedPercent?: ValueExpectation; usedPercent?: ValueExpectation;
} }
export interface ResolvedMemoryTarget extends ResolvedTargetBase { export interface ResolvedMemTarget extends ResolvedTargetBase {
expect?: ResolvedMemoryExpectConfig; expect?: ResolvedMemExpectConfig;
group: string; group: string;
intervalMs: number; intervalMs: number;
memory: ResolvedMemoryConfig; mem: ResolvedMemConfig;
name: null | string; name: null | string;
timeoutMs: number; timeoutMs: number;
type: "memory"; type: "mem";
} }

View File

@@ -7,9 +7,9 @@ import { isPlainRecord, validateRawValueExpectation } from "../../expect/validat
import { issue, joinPath } from "../../schema/issues"; import { issue, joinPath } from "../../schema/issues";
import { parseSize } from "../../utils"; import { parseSize } from "../../utils";
const MEMORY_CONFIG_KEYS = new Set<string>([]); const MEM_CONFIG_KEYS = new Set<string>([]);
const MEMORY_EXPECT_FIELDS = [ const MEM_EXPECT_FIELDS = [
"activeBytes", "activeBytes",
"activePercent", "activePercent",
"availableBytes", "availableBytes",
@@ -40,16 +40,16 @@ const BYTE_EXPECT_FIELDS = new Set([
"usedBytes", "usedBytes",
]); ]);
const MEMORY_EXPECT_KEYS = new Set<string>(MEMORY_EXPECT_FIELDS); const MEM_EXPECT_KEYS = new Set<string>(MEM_EXPECT_FIELDS);
export function validateMemoryConfig(input: CheckerValidationInput): ConfigValidationIssue[] { export function validateMemConfig(input: CheckerValidationInput): ConfigValidationIssue[] {
const issues: ConfigValidationIssue[] = []; const issues: ConfigValidationIssue[] = [];
for (let i = 0; i < input.targets.length; i++) { for (let i = 0; i < input.targets.length; i++) {
const target = input.targets[i] as unknown; const target = input.targets[i] as unknown;
if (!isPlainRecord(target)) continue; if (!isPlainRecord(target)) continue;
if (target["type"] !== "memory") continue; if (target["type"] !== "mem") continue;
issues.push(...validateMemoryTarget(target, `targets[${i}]`)); issues.push(...validateMemTarget(target, `targets[${i}]`));
} }
return issues; return issues;
@@ -60,7 +60,7 @@ function getTargetName(target: Record<string, unknown>): string | undefined {
return isString(target["id"]) ? target["id"] : undefined; return isString(target["id"]) ? target["id"] : undefined;
} }
function validateMemoryExpect(target: Record<string, unknown>, path: string): ConfigValidationIssue[] { function validateMemExpect(target: Record<string, unknown>, path: string): ConfigValidationIssue[] {
const rawExpect = target["expect"]; const rawExpect = target["expect"];
if (rawExpect === undefined || rawExpect === null || !isPlainRecord(rawExpect)) return []; if (rawExpect === undefined || rawExpect === null || !isPlainRecord(rawExpect)) return [];
const expect = rawExpect; const expect = rawExpect;
@@ -68,7 +68,7 @@ function validateMemoryExpect(target: Record<string, unknown>, path: string): Co
const targetName = getTargetName(target); const targetName = getTargetName(target);
const expectPath = joinPath(path, "expect"); const expectPath = joinPath(path, "expect");
for (const key of MEMORY_EXPECT_FIELDS) { for (const key of MEM_EXPECT_FIELDS) {
if (expect[key] !== undefined) { if (expect[key] !== undefined) {
issues.push(...validateRawValueExpectation(expect[key], joinPath(expectPath, key), targetName)); issues.push(...validateRawValueExpectation(expect[key], joinPath(expectPath, key), targetName));
if (BYTE_EXPECT_FIELDS.has(key) && isString(expect[key])) { if (BYTE_EXPECT_FIELDS.has(key) && isString(expect[key])) {
@@ -82,7 +82,7 @@ function validateMemoryExpect(target: Record<string, unknown>, path: string): Co
} }
for (const key of Object.keys(expect)) { for (const key of Object.keys(expect)) {
if (!MEMORY_EXPECT_KEYS.has(key)) { if (!MEM_EXPECT_KEYS.has(key)) {
issues.push(issue("unknown-field", joinPath(expectPath, key), "是未知字段", targetName)); issues.push(issue("unknown-field", joinPath(expectPath, key), "是未知字段", targetName));
} }
} }
@@ -90,22 +90,22 @@ function validateMemoryExpect(target: Record<string, unknown>, path: string): Co
return issues; return issues;
} }
function validateMemoryTarget(target: Record<string, unknown>, path: string): ConfigValidationIssue[] { function validateMemTarget(target: Record<string, unknown>, path: string): ConfigValidationIssue[] {
const issues: ConfigValidationIssue[] = []; const issues: ConfigValidationIssue[] = [];
const targetName = getTargetName(target); const targetName = getTargetName(target);
const rawMemory = target["memory"]; const rawMem = target["mem"];
if (!isPlainRecord(rawMemory)) { if (!isPlainRecord(rawMem)) {
issues.push(issue("required", joinPath(path, "memory"), "缺少 memory 配置分组", targetName)); issues.push(issue("required", joinPath(path, "mem"), "缺少 mem 配置分组", targetName));
} else { } else {
for (const key of Object.keys(rawMemory)) { for (const key of Object.keys(rawMem)) {
if (!MEMORY_CONFIG_KEYS.has(key)) { if (!MEM_CONFIG_KEYS.has(key)) {
issues.push(issue("unknown-field", joinPath(joinPath(path, "memory"), key), "是未知字段", targetName)); issues.push(issue("unknown-field", joinPath(joinPath(path, "mem"), key), "是未知字段", targetName));
} }
} }
} }
issues.push(...validateMemoryExpect(target, path)); issues.push(...validateMemExpect(target, path));
return issues; return issues;
} }

View File

@@ -1 +0,0 @@
export { MemoryChecker } from "./execute";

View File

@@ -2,7 +2,7 @@ import type { Systeminformation } from "systeminformation";
import { describe, expect, test } from "bun:test"; import { describe, expect, test } from "bun:test";
import { calculateMemoryStats } from "../../../../../src/server/checker/runner/memory/calculate"; import { calculateMemStats } from "../../../../../src/server/checker/runner/mem/calculate";
function makeMemData(overrides: Partial<Systeminformation.MemData> = {}): Systeminformation.MemData { function makeMemData(overrides: Partial<Systeminformation.MemData> = {}): Systeminformation.MemData {
return { return {
@@ -25,56 +25,56 @@ function makeMemData(overrides: Partial<Systeminformation.MemData> = {}): System
}; };
} }
describe("calculateMemoryStats", () => { describe("calculateMemStats", () => {
test("usagePercent = activeBytes / totalBytes * 100", () => { test("usagePercent = activeBytes / totalBytes * 100", () => {
const stats = calculateMemoryStats(makeMemData({ active: 4294967296, total: 8589934592 })); const stats = calculateMemStats(makeMemData({ active: 4294967296, total: 8589934592 }));
expect(stats.usagePercent).toBe(50); expect(stats.usagePercent).toBe(50);
}); });
test("usedPercent = usedBytes / totalBytes * 100", () => { test("usedPercent = usedBytes / totalBytes * 100", () => {
const stats = calculateMemoryStats(makeMemData({ total: 8589934592, used: 6442450944 })); const stats = calculateMemStats(makeMemData({ total: 8589934592, used: 6442450944 }));
expect(stats.usedPercent).toBe(75); expect(stats.usedPercent).toBe(75);
}); });
test("freePercent = freeBytes / totalBytes * 100", () => { test("freePercent = freeBytes / totalBytes * 100", () => {
const stats = calculateMemoryStats(makeMemData({ free: 2147483648, total: 8589934592 })); const stats = calculateMemStats(makeMemData({ free: 2147483648, total: 8589934592 }));
expect(stats.freePercent).toBe(25); expect(stats.freePercent).toBe(25);
}); });
test("activePercent = activeBytes / totalBytes * 100", () => { test("activePercent = activeBytes / totalBytes * 100", () => {
const stats = calculateMemoryStats(makeMemData({ active: 3221225472, total: 8589934592 })); const stats = calculateMemStats(makeMemData({ active: 3221225472, total: 8589934592 }));
expect(stats.activePercent).toBe(37.5); expect(stats.activePercent).toBe(37.5);
}); });
test("availablePercent = availableBytes / totalBytes * 100", () => { test("availablePercent = availableBytes / totalBytes * 100", () => {
const stats = calculateMemoryStats(makeMemData({ available: 6442450944, total: 8589934592 })); const stats = calculateMemStats(makeMemData({ available: 6442450944, total: 8589934592 }));
expect(stats.availablePercent).toBe(75); expect(stats.availablePercent).toBe(75);
}); });
test("保留 1 位小数", () => { test("保留 1 位小数", () => {
const stats = calculateMemoryStats(makeMemData({ active: 3000000000, total: 8000000000 })); const stats = calculateMemStats(makeMemData({ active: 3000000000, total: 8000000000 }));
expect(stats.usagePercent).toBe(37.5); expect(stats.usagePercent).toBe(37.5);
}); });
test("round1 处理需要四舍五入的情况", () => { test("round1 处理需要四舍五入的情况", () => {
const stats = calculateMemoryStats(makeMemData({ active: 3333333333, total: 10000000000 })); const stats = calculateMemStats(makeMemData({ active: 3333333333, total: 10000000000 }));
expect(stats.usagePercent).toBe(33.3); expect(stats.usagePercent).toBe(33.3);
}); });
test("total 为 0 时百分比为 0", () => { test("total 为 0 时百分比为 0", () => {
const stats = calculateMemoryStats(makeMemData({ active: 0, available: 0, free: 0, total: 0, used: 0 })); const stats = calculateMemStats(makeMemData({ active: 0, available: 0, free: 0, total: 0, used: 0 }));
expect(stats.usagePercent).toBe(0); expect(stats.usagePercent).toBe(0);
expect(stats.usedPercent).toBe(0); expect(stats.usedPercent).toBe(0);
expect(stats.freePercent).toBe(0); expect(stats.freePercent).toBe(0);
}); });
test("buffcacheBytes 为 null 映射", () => { test("buffcacheBytes 为 null 映射", () => {
const stats = calculateMemoryStats(makeMemData({ buffcache: 0 })); const stats = calculateMemStats(makeMemData({ buffcache: 0 }));
expect(stats.buffcacheBytes).toBe(0); expect(stats.buffcacheBytes).toBe(0);
}); });
test("buffcacheBytes 为正数时保留", () => { test("buffcacheBytes 为正数时保留", () => {
const stats = calculateMemoryStats(makeMemData({ buffcache: 1073741824 })); const stats = calculateMemStats(makeMemData({ buffcache: 1073741824 }));
expect(stats.buffcacheBytes).toBe(1073741824); expect(stats.buffcacheBytes).toBe(1073741824);
}); });
@@ -86,7 +86,7 @@ describe("calculateMemoryStats", () => {
total: 4000, total: 4000,
used: 3500, used: 3500,
}); });
const stats = calculateMemoryStats(data); const stats = calculateMemStats(data);
expect(stats.activeBytes).toBe(1000); expect(stats.activeBytes).toBe(1000);
expect(stats.availableBytes).toBe(2000); expect(stats.availableBytes).toBe(2000);
expect(stats.freeBytes).toBe(3000); expect(stats.freeBytes).toBe(3000);
@@ -95,9 +95,9 @@ describe("calculateMemoryStats", () => {
}); });
}); });
describe("calculateMemoryStats swap", () => { describe("calculateMemStats swap", () => {
test("swap 不可用swaptotal=0 时 swapUsagePercent=null", () => { test("swap 不可用swaptotal=0 时 swapUsagePercent=null", () => {
const stats = calculateMemoryStats(makeMemData({ swapfree: 0, swaptotal: 0, swapused: 0 })); const stats = calculateMemStats(makeMemData({ swapfree: 0, swaptotal: 0, swapused: 0 }));
expect(stats.swapUsagePercent).toBe(null); expect(stats.swapUsagePercent).toBe(null);
expect(stats.swapTotalBytes).toBe(0); expect(stats.swapTotalBytes).toBe(0);
expect(stats.swapUsedBytes).toBe(0); expect(stats.swapUsedBytes).toBe(0);
@@ -105,14 +105,12 @@ describe("calculateMemoryStats swap", () => {
}); });
test("swap 总量为 0swapUsagePercent 为 null不是 0", () => { test("swap 总量为 0swapUsagePercent 为 null不是 0", () => {
const stats = calculateMemoryStats(makeMemData({ swaptotal: 0 })); const stats = calculateMemStats(makeMemData({ swaptotal: 0 }));
expect(stats.swapUsagePercent).toBe(null); expect(stats.swapUsagePercent).toBe(null);
}); });
test("swap 已使用", () => { test("swap 已使用", () => {
const stats = calculateMemoryStats( const stats = calculateMemStats(makeMemData({ swapfree: 1073741824, swaptotal: 4294967296, swapused: 3221225472 }));
makeMemData({ swapfree: 1073741824, swaptotal: 4294967296, swapused: 3221225472 }),
);
expect(stats.swapUsagePercent).toBe(75); expect(stats.swapUsagePercent).toBe(75);
expect(stats.swapTotalBytes).toBe(4294967296); expect(stats.swapTotalBytes).toBe(4294967296);
expect(stats.swapUsedBytes).toBe(3221225472); expect(stats.swapUsedBytes).toBe(3221225472);
@@ -120,21 +118,21 @@ describe("calculateMemoryStats swap", () => {
}); });
test("swap 未使用swapUsagePercent=0不是 null", () => { test("swap 未使用swapUsagePercent=0不是 null", () => {
const stats = calculateMemoryStats(makeMemData({ swapfree: 4294967296, swaptotal: 4294967296, swapused: 0 })); const stats = calculateMemStats(makeMemData({ swapfree: 4294967296, swaptotal: 4294967296, swapused: 0 }));
expect(stats.swapUsagePercent).toBe(0); expect(stats.swapUsagePercent).toBe(0);
expect(stats.swapUsedBytes).toBe(0); expect(stats.swapUsedBytes).toBe(0);
expect(stats.swapFreeBytes).toBe(4294967296); expect(stats.swapFreeBytes).toBe(4294967296);
}); });
test("swap 部分使用保留 1 位小数", () => { test("swap 部分使用保留 1 位小数", () => {
const stats = calculateMemoryStats( const stats = calculateMemStats(
makeMemData({ swapfree: 3000000000, swaptotal: 10000000000, swapused: 7000000000 }), makeMemData({ swapfree: 3000000000, swaptotal: 10000000000, swapused: 7000000000 }),
); );
expect(stats.swapUsagePercent).toBe(70); expect(stats.swapUsagePercent).toBe(70);
}); });
test("swap 合法 0 不被转换为 null", () => { test("swap 合法 0 不被转换为 null", () => {
const stats = calculateMemoryStats(makeMemData({ swapfree: 4294967296, swaptotal: 4294967296, swapused: 0 })); const stats = calculateMemStats(makeMemData({ swapfree: 4294967296, swaptotal: 4294967296, swapused: 0 }));
expect(stats.swapUsedBytes).toBe(0); expect(stats.swapUsedBytes).toBe(0);
expect(stats.swapUsagePercent).toBe(0); expect(stats.swapUsagePercent).toBe(0);
}); });

View File

@@ -4,7 +4,7 @@ import { describe, expect, test } from "bun:test";
import type { RawTargetConfig } from "../../../../../src/server/checker/types"; import type { RawTargetConfig } from "../../../../../src/server/checker/types";
import { MemoryChecker } from "../../../../../src/server/checker/runner/memory/execute"; import { MemChecker } from "../../../../../src/server/checker/runner/mem/execute";
function makeMemData(overrides: Partial<Systeminformation.MemData> = {}): Systeminformation.MemData { function makeMemData(overrides: Partial<Systeminformation.MemData> = {}): Systeminformation.MemData {
return { return {
@@ -38,17 +38,17 @@ function makeResolveContext(
}; };
} }
describe("MemoryChecker resolve", () => { describe("MemChecker resolve", () => {
const checker = new MemoryChecker(); const checker = new MemChecker();
test("默认值memory 为空对象", () => { test("默认值mem 为空对象", () => {
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" }; const target: RawTargetConfig = { id: "mem-test", mem: {}, type: "mem" };
const resolved = checker.resolve(target, makeResolveContext()); const resolved = checker.resolve(target, makeResolveContext());
expect(resolved.memory).toEqual({}); expect(resolved.mem).toEqual({});
}); });
test("无 expect 时 expect 为 undefined", () => { test("无 expect 时 expect 为 undefined", () => {
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" }; const target: RawTargetConfig = { id: "mem-test", mem: {}, type: "mem" };
const resolved = checker.resolve(target, makeResolveContext()); const resolved = checker.resolve(target, makeResolveContext());
expect(resolved.expect).toBeUndefined(); expect(resolved.expect).toBeUndefined();
}); });
@@ -57,27 +57,27 @@ describe("MemoryChecker resolve", () => {
const target: RawTargetConfig = { const target: RawTargetConfig = {
expect: { usagePercent: { lte: 85 } }, expect: { usagePercent: { lte: 85 } },
id: "mem-test", id: "mem-test",
memory: {}, mem: {},
type: "memory", type: "mem",
}; };
const resolved = checker.resolve(target, makeResolveContext()); const resolved = checker.resolve(target, makeResolveContext());
expect(resolved.expect).toEqual({ usagePercent: { lte: 85 } }); expect(resolved.expect).toEqual({ usagePercent: { lte: 85 } });
}); });
test("type 为 memory", () => { test("type 为 mem", () => {
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" }; const target: RawTargetConfig = { id: "mem-test", mem: {}, type: "mem" };
const resolved = checker.resolve(target, makeResolveContext()); const resolved = checker.resolve(target, makeResolveContext());
expect(resolved.type).toBe("memory"); expect(resolved.type).toBe("mem");
}); });
}); });
describe("MemoryChecker execute", () => { describe("MemChecker execute", () => {
test("成功匹配", async () => { test("成功匹配", async () => {
const data = makeMemData({ active: 4294967296, total: 8589934592 }); const data = makeMemData({ active: 4294967296, total: 8589934592 });
const reader = () => Promise.resolve(data); const reader = () => Promise.resolve(data);
const checker = new MemoryChecker(reader); const checker = new MemChecker(reader);
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" }; const target: RawTargetConfig = { id: "mem-test", mem: {}, type: "mem" };
const resolved = checker.resolve(target, makeResolveContext()); const resolved = checker.resolve(target, makeResolveContext());
resolved.expect = { usagePercent: { lte: 85 } }; resolved.expect = { usagePercent: { lte: 85 } };
@@ -95,9 +95,9 @@ describe("MemoryChecker execute", () => {
test("usagePercent mismatch", async () => { test("usagePercent mismatch", async () => {
const data = makeMemData({ active: 7730941132, total: 8589934592 }); const data = makeMemData({ active: 7730941132, total: 8589934592 });
const reader = () => Promise.resolve(data); const reader = () => Promise.resolve(data);
const checker = new MemoryChecker(reader); const checker = new MemChecker(reader);
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" }; const target: RawTargetConfig = { id: "mem-test", mem: {}, type: "mem" };
const resolved = checker.resolve(target, makeResolveContext()); const resolved = checker.resolve(target, makeResolveContext());
resolved.expect = { usagePercent: { lte: 50 } }; resolved.expect = { usagePercent: { lte: 50 } };
@@ -111,9 +111,9 @@ describe("MemoryChecker execute", () => {
test("observation 包含所有字段", async () => { test("observation 包含所有字段", async () => {
const data = makeMemData(); const data = makeMemData();
const reader = () => Promise.resolve(data); const reader = () => Promise.resolve(data);
const checker = new MemoryChecker(reader); const checker = new MemChecker(reader);
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" }; const target: RawTargetConfig = { id: "mem-test", mem: {}, type: "mem" };
const resolved = checker.resolve(target, makeResolveContext()); const resolved = checker.resolve(target, makeResolveContext());
const ctx = { signal: new AbortController().signal }; const ctx = { signal: new AbortController().signal };
@@ -139,25 +139,25 @@ describe("MemoryChecker execute", () => {
test("reader reject 返回失败结果", async () => { test("reader reject 返回失败结果", async () => {
const reader = () => Promise.reject(new Error("read error")); const reader = () => Promise.reject(new Error("read error"));
const checker = new MemoryChecker(reader); const checker = new MemChecker(reader);
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" }; const target: RawTargetConfig = { id: "mem-test", mem: {}, type: "mem" };
const resolved = checker.resolve(target, makeResolveContext()); const resolved = checker.resolve(target, makeResolveContext());
const ctx = { signal: new AbortController().signal }; const ctx = { signal: new AbortController().signal };
const result = await checker.execute(resolved, ctx); const result = await checker.execute(resolved, ctx);
expect(result.matched).toBe(false); expect(result.matched).toBe(false);
expect(result.failure?.phase).toBe("memory"); expect(result.failure?.phase).toBe("mem");
expect(result.failure?.path).toBe("snapshot"); expect(result.failure?.path).toBe("snapshot");
expect(result.observation).toBeNull(); expect(result.observation).toBeNull();
}); });
test("signal 已 abort 时返回 timeout failure", async () => { test("signal 已 abort 时返回 timeout failure", async () => {
const reader = () => Promise.resolve(makeMemData()); const reader = () => Promise.resolve(makeMemData());
const checker = new MemoryChecker(reader); const checker = new MemChecker(reader);
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" }; const target: RawTargetConfig = { id: "mem-test", mem: {}, type: "mem" };
const resolved = checker.resolve(target, makeResolveContext()); const resolved = checker.resolve(target, makeResolveContext());
const controller = new AbortController(); const controller = new AbortController();
@@ -165,7 +165,7 @@ describe("MemoryChecker execute", () => {
const result = await checker.execute(resolved, { signal: controller.signal }); const result = await checker.execute(resolved, { signal: controller.signal });
expect(result.matched).toBe(false); expect(result.matched).toBe(false);
expect(result.failure?.phase).toBe("memory"); expect(result.failure?.phase).toBe("mem");
expect(result.failure?.path).toBe("timeout"); expect(result.failure?.path).toBe("timeout");
expect(result.observation).toBeNull(); expect(result.observation).toBeNull();
}); });
@@ -175,9 +175,9 @@ describe("MemoryChecker execute", () => {
new Promise<Systeminformation.MemData>(() => { new Promise<Systeminformation.MemData>(() => {
// 故意永不 resolve模拟悬挂的 reader // 故意永不 resolve模拟悬挂的 reader
}); });
const checker = new MemoryChecker(reader); const checker = new MemChecker(reader);
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" }; const target: RawTargetConfig = { id: "mem-test", mem: {}, type: "mem" };
const resolved = checker.resolve(target, makeResolveContext()); const resolved = checker.resolve(target, makeResolveContext());
const controller = new AbortController(); const controller = new AbortController();
@@ -188,7 +188,7 @@ describe("MemoryChecker execute", () => {
const result = await executePromise; const result = await executePromise;
expect(result.matched).toBe(false); expect(result.matched).toBe(false);
expect(result.failure?.phase).toBe("memory"); expect(result.failure?.phase).toBe("mem");
expect(result.failure?.path).toBe("timeout"); expect(result.failure?.path).toBe("timeout");
expect(result.observation).toBeNull(); expect(result.observation).toBeNull();
}); });
@@ -196,9 +196,9 @@ describe("MemoryChecker execute", () => {
test("reader 在 abort 前 resolve 时返回正常结果", async () => { test("reader 在 abort 前 resolve 时返回正常结果", async () => {
const data = makeMemData({ active: 4294967296, total: 8589934592 }); const data = makeMemData({ active: 4294967296, total: 8589934592 });
const reader = () => Promise.resolve(data); const reader = () => Promise.resolve(data);
const checker = new MemoryChecker(reader); const checker = new MemChecker(reader);
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" }; const target: RawTargetConfig = { id: "mem-test", mem: {}, type: "mem" };
const resolved = checker.resolve(target, makeResolveContext()); const resolved = checker.resolve(target, makeResolveContext());
resolved.expect = { usagePercent: { lte: 85 } }; resolved.expect = { usagePercent: { lte: 85 } };
@@ -216,9 +216,9 @@ describe("MemoryChecker execute", () => {
test("detail 格式", async () => { test("detail 格式", async () => {
const data = makeMemData({ active: 4294967296, total: 8589934592 }); const data = makeMemData({ active: 4294967296, total: 8589934592 });
const reader = () => Promise.resolve(data); const reader = () => Promise.resolve(data);
const checker = new MemoryChecker(reader); const checker = new MemChecker(reader);
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" }; const target: RawTargetConfig = { id: "mem-test", mem: {}, type: "mem" };
const resolved = checker.resolve(target, makeResolveContext()); const resolved = checker.resolve(target, makeResolveContext());
const ctx = { signal: new AbortController().signal }; const ctx = { signal: new AbortController().signal };
@@ -231,13 +231,13 @@ describe("MemoryChecker execute", () => {
}); });
}); });
describe("MemoryChecker serialize", () => { describe("MemChecker serialize", () => {
test("序列化输出", () => { test("序列化输出", () => {
const checker = new MemoryChecker(); const checker = new MemChecker();
const target: RawTargetConfig = { id: "mem-test", memory: {}, type: "memory" }; const target: RawTargetConfig = { id: "mem-test", mem: {}, type: "mem" };
const resolved = checker.resolve(target, makeResolveContext()); const resolved = checker.resolve(target, makeResolveContext());
const result = checker.serialize(resolved); const result = checker.serialize(resolved);
expect(result.target).toBe("memory"); expect(result.target).toBe("mem");
const config = JSON.parse(result.config) as Record<string, unknown>; const config = JSON.parse(result.config) as Record<string, unknown>;
expect(config).toEqual({}); expect(config).toEqual({});
}); });

View File

@@ -16,9 +16,9 @@ import {
checkUsagePercent, checkUsagePercent,
checkUsedBytes, checkUsedBytes,
checkUsedPercent, checkUsedPercent,
} from "../../../../../src/server/checker/runner/memory/expect"; } from "../../../../../src/server/checker/runner/mem/expect";
describe("Memory expect checks - 百分比字段", () => { describe("Mem expect checks - 百分比字段", () => {
test("checkUsagePercent 匹配", () => { test("checkUsagePercent 匹配", () => {
expect(checkUsagePercent(50, { lte: 85 }).matched).toBe(true); expect(checkUsagePercent(50, { lte: 85 }).matched).toBe(true);
}); });
@@ -63,7 +63,7 @@ describe("Memory expect checks - 百分比字段", () => {
}); });
}); });
describe("Memory expect checks - 字节字段", () => { describe("Mem expect checks - 字节字段", () => {
test("checkActiveBytes 匹配", () => { test("checkActiveBytes 匹配", () => {
expect(checkActiveBytes(4294967296, { lte: 8589934592 }).matched).toBe(true); expect(checkActiveBytes(4294967296, { lte: 8589934592 }).matched).toBe(true);
}); });
@@ -87,7 +87,7 @@ describe("Memory expect checks - 字节字段", () => {
}); });
}); });
describe("Memory expect checks - swap 字段", () => { describe("Mem expect checks - swap 字段", () => {
test("checkSwapUsagePercent null 通过 gte 检查 (Number(null)=0)", () => { test("checkSwapUsagePercent null 通过 gte 检查 (Number(null)=0)", () => {
expect(checkSwapUsagePercent(null, { gte: 0 }).matched).toBe(true); expect(checkSwapUsagePercent(null, { gte: 0 }).matched).toBe(true);
}); });
@@ -132,7 +132,7 @@ describe("Memory expect checks - swap 字段", () => {
}); });
}); });
describe("Memory expect checks - buffcacheBytes", () => { describe("Mem expect checks - buffcacheBytes", () => {
test("checkBuffcacheBytes 有值时匹配", () => { test("checkBuffcacheBytes 有值时匹配", () => {
expect(checkBuffcacheBytes(1073741824, { lte: 2147483648 }).matched).toBe(true); expect(checkBuffcacheBytes(1073741824, { lte: 2147483648 }).matched).toBe(true);
}); });

View File

@@ -1,50 +1,50 @@
import { describe, expect, test } from "bun:test"; import { describe, expect, test } from "bun:test";
import { normalizeTargetExpect } from "../../../../../src/server/checker/runner/memory/normalize"; import { normalizeTargetExpect } from "../../../../../src/server/checker/runner/mem/normalize";
describe("normalizeTargetExpect (memory)", () => { describe("normalizeTargetExpect (mem)", () => {
test("无 expect 直接返回", () => { test("无 expect 直接返回", () => {
const target = { id: "test", memory: {}, type: "memory" }; const target = { id: "test", mem: {}, type: "mem" };
expect(normalizeTargetExpect(target)).toEqual(target); expect(normalizeTargetExpect(target)).toEqual(target);
}); });
test("expect 为非对象直接返回", () => { test("expect 为非对象直接返回", () => {
const target = { expect: "not-an-object", id: "test", memory: {}, type: "memory" }; const target = { expect: "not-an-object", id: "test", mem: {}, type: "mem" };
expect(normalizeTargetExpect(target)).toEqual(target); expect(normalizeTargetExpect(target)).toEqual(target);
}); });
test("字节大小字符串 512MB 转换为数字", () => { test("字节大小字符串 512MB 转换为数字", () => {
const target = { expect: { usedBytes: "512MB" }, id: "test", memory: {}, type: "memory" }; const target = { expect: { usedBytes: "512MB" }, id: "test", mem: {}, type: "mem" };
const result = normalizeTargetExpect(target); const result = normalizeTargetExpect(target);
expect((result.expect as Record<string, unknown>)["usedBytes"]).toEqual({ equals: 536870912 }); expect((result.expect as Record<string, unknown>)["usedBytes"]).toEqual({ equals: 536870912 });
}); });
test("字节大小字符串 1GB 转换为数字", () => { test("字节大小字符串 1GB 转换为数字", () => {
const target = { expect: { totalBytes: "1GB" }, id: "test", memory: {}, type: "memory" }; const target = { expect: { totalBytes: "1GB" }, id: "test", mem: {}, type: "mem" };
const result = normalizeTargetExpect(target); const result = normalizeTargetExpect(target);
expect((result.expect as Record<string, unknown>)["totalBytes"]).toEqual({ equals: 1073741824 }); expect((result.expect as Record<string, unknown>)["totalBytes"]).toEqual({ equals: 1073741824 });
}); });
test("数字字节 matcher 保持不变", () => { test("数字字节 matcher 保持不变", () => {
const target = { expect: { usedBytes: 1073741824 }, id: "test", memory: {}, type: "memory" }; const target = { expect: { usedBytes: 1073741824 }, id: "test", mem: {}, type: "mem" };
const result = normalizeTargetExpect(target); const result = normalizeTargetExpect(target);
expect((result.expect as Record<string, unknown>)["usedBytes"]).toEqual({ equals: 1073741824 }); expect((result.expect as Record<string, unknown>)["usedBytes"]).toEqual({ equals: 1073741824 });
}); });
test("百分比 matcher 正常展开", () => { test("百分比 matcher 正常展开", () => {
const target = { expect: { usagePercent: 85 }, id: "test", memory: {}, type: "memory" }; const target = { expect: { usagePercent: 85 }, id: "test", mem: {}, type: "mem" };
const result = normalizeTargetExpect(target); const result = normalizeTargetExpect(target);
expect((result.expect as Record<string, unknown>)["usagePercent"]).toEqual({ equals: 85 }); expect((result.expect as Record<string, unknown>)["usagePercent"]).toEqual({ equals: 85 });
}); });
test("matcher 对象保持不变", () => { test("matcher 对象保持不变", () => {
const target = { expect: { usagePercent: { lte: 85 } }, id: "test", memory: {}, type: "memory" }; const target = { expect: { usagePercent: { lte: 85 } }, id: "test", mem: {}, type: "mem" };
const result = normalizeTargetExpect(target); const result = normalizeTargetExpect(target);
expect((result.expect as Record<string, unknown>)["usagePercent"]).toEqual({ lte: 85 }); expect((result.expect as Record<string, unknown>)["usagePercent"]).toEqual({ lte: 85 });
}); });
test("字节 matcher 对象内字符串转换", () => { test("字节 matcher 对象内字符串转换", () => {
const target = { expect: { usedBytes: { gte: "512MB" } }, id: "test", memory: {}, type: "memory" }; const target = { expect: { usedBytes: { gte: "512MB" } }, id: "test", mem: {}, type: "mem" };
const result = normalizeTargetExpect(target); const result = normalizeTargetExpect(target);
expect((result.expect as Record<string, unknown>)["usedBytes"]).toEqual({ gte: 536870912 }); expect((result.expect as Record<string, unknown>)["usedBytes"]).toEqual({ gte: 536870912 });
}); });
@@ -53,8 +53,8 @@ describe("normalizeTargetExpect (memory)", () => {
const target = { const target = {
expect: { freePercent: 25, totalBytes: "16GB", usagePercent: { lte: 85 } }, expect: { freePercent: 25, totalBytes: "16GB", usagePercent: { lte: 85 } },
id: "test", id: "test",
memory: {}, mem: {},
type: "memory", type: "mem",
}; };
const result = normalizeTargetExpect(target); const result = normalizeTargetExpect(target);
const expectObj = result.expect as Record<string, unknown>; const expectObj = result.expect as Record<string, unknown>;
@@ -64,9 +64,9 @@ describe("normalizeTargetExpect (memory)", () => {
}); });
}); });
describe("normalizeTargetExpect (memory) 错误", () => { describe("normalizeTargetExpect (mem) 错误", () => {
test("非法大小字符串抛出", () => { test("非法大小字符串抛出", () => {
const target = { expect: { usedBytes: "abc" }, id: "test", memory: {}, type: "memory" }; const target = { expect: { usedBytes: "abc" }, id: "test", mem: {}, type: "mem" };
expect(() => normalizeTargetExpect(target)).toThrow(); expect(() => normalizeTargetExpect(target)).toThrow();
}); });
}); });

View File

@@ -1,60 +1,60 @@
import Ajv from "ajv"; import Ajv from "ajv";
import { describe, expect, test } from "bun:test"; import { describe, expect, test } from "bun:test";
import { memoryCheckerSchemas } from "../../../../../src/server/checker/runner/memory/schema"; import { memCheckerSchemas } from "../../../../../src/server/checker/runner/mem/schema";
const ajv = new Ajv({ strict: false }); const ajv = new Ajv({ strict: false });
describe("Memory checker schema", () => { describe("Mem checker schema", () => {
test("authoring config 空配置通过", () => { test("authoring config 空配置通过", () => {
const validate = ajv.compile(memoryCheckerSchemas.authoring.config); const validate = ajv.compile(memCheckerSchemas.authoring.config);
expect(validate({})).toBe(true); expect(validate({})).toBe(true);
}); });
test("normalized config 空配置通过", () => { test("normalized config 空配置通过", () => {
const validate = ajv.compile(memoryCheckerSchemas.normalized.config); const validate = ajv.compile(memCheckerSchemas.normalized.config);
expect(validate({})).toBe(true); expect(validate({})).toBe(true);
}); });
test("config 拒绝额外字段", () => { test("config 拒绝额外字段", () => {
const validate = ajv.compile(memoryCheckerSchemas.authoring.config); const validate = ajv.compile(memCheckerSchemas.authoring.config);
expect(validate({ extraField: true })).toBe(false); expect(validate({ extraField: true })).toBe(false);
}); });
test("authoring expect 允许百分比 ValueMatcher 简写", () => { test("authoring expect 允许百分比 ValueMatcher 简写", () => {
const validate = ajv.compile(memoryCheckerSchemas.authoring.expect); const validate = ajv.compile(memCheckerSchemas.authoring.expect);
expect(validate({ usagePercent: 85 })).toBe(true); expect(validate({ usagePercent: 85 })).toBe(true);
expect(validate({ usagePercent: { lte: 85 } })).toBe(true); expect(validate({ usagePercent: { lte: 85 } })).toBe(true);
}); });
test("authoring expect 允许字节字段字符串", () => { test("authoring expect 允许字节字段字符串", () => {
const validate = ajv.compile(memoryCheckerSchemas.authoring.expect); const validate = ajv.compile(memCheckerSchemas.authoring.expect);
expect(validate({ usedBytes: "512MB" })).toBe(true); expect(validate({ usedBytes: "512MB" })).toBe(true);
expect(validate({ totalBytes: "1GB" })).toBe(true); expect(validate({ totalBytes: "1GB" })).toBe(true);
}); });
test("authoring expect 允许字节字段数字", () => { test("authoring expect 允许字节字段数字", () => {
const validate = ajv.compile(memoryCheckerSchemas.authoring.expect); const validate = ajv.compile(memCheckerSchemas.authoring.expect);
expect(validate({ usedBytes: 536870912 })).toBe(true); expect(validate({ usedBytes: 536870912 })).toBe(true);
}); });
test("normalized expect 允许 matcher 对象", () => { test("normalized expect 允许 matcher 对象", () => {
const validate = ajv.compile(memoryCheckerSchemas.normalized.expect); const validate = ajv.compile(memCheckerSchemas.normalized.expect);
expect(validate({ freePercent: { gte: 15 }, usagePercent: { lte: 85 } })).toBe(true); expect(validate({ freePercent: { gte: 15 }, usagePercent: { lte: 85 } })).toBe(true);
}); });
test("expect 拒绝未知字段", () => { test("expect 拒绝未知字段", () => {
const validate = ajv.compile(memoryCheckerSchemas.authoring.expect); const validate = ajv.compile(memCheckerSchemas.authoring.expect);
expect(validate({ unknownField: 1 })).toBe(false); expect(validate({ unknownField: 1 })).toBe(false);
}); });
test("expect 空对象通过", () => { test("expect 空对象通过", () => {
const validate = ajv.compile(memoryCheckerSchemas.normalized.expect); const validate = ajv.compile(memCheckerSchemas.normalized.expect);
expect(validate({})).toBe(true); expect(validate({})).toBe(true);
}); });
test("expect 允许所有合法百分比字段", () => { test("expect 允许所有合法百分比字段", () => {
const validate = ajv.compile(memoryCheckerSchemas.normalized.expect); const validate = ajv.compile(memCheckerSchemas.normalized.expect);
expect( expect(
validate({ validate({
activePercent: { lte: 80 }, activePercent: { lte: 80 },
@@ -68,7 +68,7 @@ describe("Memory checker schema", () => {
}); });
test("expect 允许所有合法字节字段", () => { test("expect 允许所有合法字节字段", () => {
const validate = ajv.compile(memoryCheckerSchemas.normalized.expect); const validate = ajv.compile(memCheckerSchemas.normalized.expect);
expect( expect(
validate({ validate({
activeBytes: { lte: 8589934592 }, activeBytes: { lte: 8589934592 },
@@ -84,12 +84,12 @@ describe("Memory checker schema", () => {
}); });
test("expect 允许 durationMs 字段", () => { test("expect 允许 durationMs 字段", () => {
const validate = ajv.compile(memoryCheckerSchemas.normalized.expect); const validate = ajv.compile(memCheckerSchemas.normalized.expect);
expect(validate({ durationMs: { lte: 5000 } })).toBe(true); expect(validate({ durationMs: { lte: 5000 } })).toBe(true);
}); });
test("expect 允许 buffcacheBytes 字段", () => { test("expect 允许 buffcacheBytes 字段", () => {
const validate = ajv.compile(memoryCheckerSchemas.normalized.expect); const validate = ajv.compile(memCheckerSchemas.normalized.expect);
expect(validate({ buffcacheBytes: { lte: 2147483648 } })).toBe(true); expect(validate({ buffcacheBytes: { lte: 2147483648 } })).toBe(true);
}); });
}); });

View File

@@ -2,29 +2,29 @@ import { describe, expect, test } from "bun:test";
import type { RawTargetConfig } from "../../../../../src/server/checker/types"; import type { RawTargetConfig } from "../../../../../src/server/checker/types";
import { validateMemoryConfig } from "../../../../../src/server/checker/runner/memory/validate"; import { validateMemConfig } from "../../../../../src/server/checker/runner/mem/validate";
function validate(target: RawTargetConfig) { function validate(target: RawTargetConfig) {
return validateMemoryConfig({ targets: [target] }); return validateMemConfig({ targets: [target] });
} }
describe("validateMemoryConfig", () => { describe("validateMemConfig", () => {
test("有效配置无错误", () => { test("有效配置无错误", () => {
expect(validate({ id: "mem-test", memory: {}, type: "memory" })).toEqual([]); expect(validate({ id: "mem-test", mem: {}, type: "mem" })).toEqual([]);
}); });
test("缺少 memory 配置分组", () => { test("缺少 mem 配置分组", () => {
const issues = validate({ id: "mem-test", type: "memory" }); const issues = validate({ id: "mem-test", type: "mem" });
expect(issues.some((i) => i.path.endsWith("memory") && i.code === "required")).toBe(true); expect(issues.some((i) => i.path.endsWith("mem") && i.code === "required")).toBe(true);
}); });
test("memory 未知字段报错", () => { test("mem 未知字段报错", () => {
const issues = validate({ id: "mem-test", memory: { extra: true }, type: "memory" }); const issues = validate({ id: "mem-test", mem: { extra: true }, type: "mem" });
expect(issues.some((i) => i.path.endsWith("extra") && i.code === "unknown-field")).toBe(true); expect(issues.some((i) => i.path.endsWith("extra") && i.code === "unknown-field")).toBe(true);
}); });
test("expect 未知字段报错", () => { test("expect 未知字段报错", () => {
const issues = validate({ expect: { logicalCoreCount: { gte: 4 } }, id: "mem-test", memory: {}, type: "memory" }); const issues = validate({ expect: { logicalCoreCount: { gte: 4 } }, id: "mem-test", mem: {}, type: "mem" });
expect(issues.some((i) => i.path.endsWith("logicalCoreCount") && i.code === "unknown-field")).toBe(true); expect(issues.some((i) => i.path.endsWith("logicalCoreCount") && i.code === "unknown-field")).toBe(true);
}); });
@@ -32,24 +32,24 @@ describe("validateMemoryConfig", () => {
const issues = validate({ const issues = validate({
expect: { usagePercent: { lte: 85 }, usedBytes: { lte: 8589934592 } }, expect: { usagePercent: { lte: 85 }, usedBytes: { lte: 8589934592 } },
id: "mem-test", id: "mem-test",
memory: {}, mem: {},
type: "memory", type: "mem",
}); });
expect(issues.filter((i) => i.path.includes("expect"))).toEqual([]); expect(issues.filter((i) => i.path.includes("expect"))).toEqual([]);
}); });
test("expect 非法 ValueMatcher 报错", () => { test("expect 非法 ValueMatcher 报错", () => {
const issues = validate({ expect: { usagePercent: [1, 2] }, id: "mem-test", memory: {}, type: "memory" }); const issues = validate({ expect: { usagePercent: [1, 2] }, id: "mem-test", mem: {}, type: "mem" });
expect(issues.some((i) => i.path.includes("usagePercent"))).toBe(true); expect(issues.some((i) => i.path.includes("usagePercent"))).toBe(true);
}); });
test("expect 合法字节大小字符串通过", () => { test("expect 合法字节大小字符串通过", () => {
const issues = validate({ expect: { usedBytes: "512MB" }, id: "mem-test", memory: {}, type: "memory" }); const issues = validate({ expect: { usedBytes: "512MB" }, id: "mem-test", mem: {}, type: "mem" });
expect(issues.filter((i) => i.path.includes("usedBytes"))).toEqual([]); expect(issues.filter((i) => i.path.includes("usedBytes"))).toEqual([]);
}); });
test("expect 非法字节大小字符串报错", () => { test("expect 非法字节大小字符串报错", () => {
const issues = validate({ expect: { usedBytes: "abc" }, id: "mem-test", memory: {}, type: "memory" }); const issues = validate({ expect: { usedBytes: "abc" }, id: "mem-test", mem: {}, type: "mem" });
expect(issues.some((i) => i.path.includes("usedBytes") && i.message.includes("字节大小"))).toBe(true); expect(issues.some((i) => i.path.includes("usedBytes") && i.message.includes("字节大小"))).toBe(true);
}); });
@@ -74,13 +74,13 @@ describe("validateMemoryConfig", () => {
usedPercent: { lte: 90 }, usedPercent: { lte: 90 },
}, },
id: "mem-test", id: "mem-test",
memory: {}, mem: {},
type: "memory", type: "mem",
}); });
expect(issues).toEqual([]); expect(issues).toEqual([]);
}); });
test("非 memory type 的 target 不校验", () => { test("非 mem type 的 target 不校验", () => {
const issues = validate({ id: "other-test", type: "http" }); const issues = validate({ id: "other-test", type: "http" });
expect(issues).toEqual([]); expect(issues).toEqual([]);
}); });

View File

@@ -84,7 +84,7 @@ describe("CheckerRegistry", () => {
"dns", "dns",
"ws", "ws",
"cpu", "cpu",
"memory", "mem",
"custom", "custom",
]); ]);
expect(second.supportedTypes).toEqual([ expect(second.supportedTypes).toEqual([
@@ -98,7 +98,7 @@ describe("CheckerRegistry", () => {
"dns", "dns",
"ws", "ws",
"cpu", "cpu",
"memory", "mem",
]); ]);
expect( expect(
first.definitions.every((checker) => checker.schemas.authoring.config && checker.schemas.normalized.expect), first.definitions.every((checker) => checker.schemas.authoring.config && checker.schemas.normalized.expect),