refactor: 重命名 command checker 为 cmd checker 并适配跨平台测试
将 type/configKey 从 "command" 统一为 "cmd",源码目录 runner/command/ → runner/cmd/, spec 目录 command-checker/ → cmd-checker/,测试全部改用 bun -e 替代 Unix 系统命令, 归档 cmd-checker-enhancement 变更并同步 delta spec 到主 spec。
This commit is contained in:
26
tests/server/checker/runner/cmd/expect.test.ts
Normal file
26
tests/server/checker/runner/cmd/expect.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { checkExitCode } from "../../../../../src/server/checker/runner/cmd/expect";
|
||||
|
||||
describe("checkExitCode", () => {
|
||||
test("exitCode 在允许列表中匹配成功", () => {
|
||||
const r = checkExitCode(0, [0]);
|
||||
expect(r.matched).toBe(true);
|
||||
expect(r.failure).toBeNull();
|
||||
});
|
||||
|
||||
test("exitCode 不在允许列表中匹配失败", () => {
|
||||
const r = checkExitCode(1, [0]);
|
||||
expect(r.matched).toBe(false);
|
||||
expect(r.failure!.phase).toBe("exitCode");
|
||||
expect(r.failure!.kind).toBe("mismatch");
|
||||
expect(r.failure!.expected).toEqual([0]);
|
||||
expect(r.failure!.actual).toBe(1);
|
||||
});
|
||||
|
||||
test("多个允许退出码", () => {
|
||||
expect(checkExitCode(0, [0, 1]).matched).toBe(true);
|
||||
expect(checkExitCode(1, [0, 1]).matched).toBe(true);
|
||||
expect(checkExitCode(2, [0, 1]).matched).toBe(false);
|
||||
});
|
||||
});
|
||||
171
tests/server/checker/runner/cmd/runner.test.ts
Normal file
171
tests/server/checker/runner/cmd/runner.test.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import type { ResolvedCommandTarget } from "../../../../../src/server/checker/runner/cmd/types";
|
||||
import type { CheckerContext } from "../../../../../src/server/checker/runner/types";
|
||||
|
||||
import { CommandChecker } from "../../../../../src/server/checker/runner/cmd/execute";
|
||||
|
||||
const checker = new CommandChecker();
|
||||
|
||||
const processEnv = Object.fromEntries(
|
||||
Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined),
|
||||
);
|
||||
|
||||
function makeCtx(timeoutMs = 5000): CheckerContext {
|
||||
const controller = new AbortController();
|
||||
setTimeout(() => controller.abort(), timeoutMs);
|
||||
return { signal: controller.signal };
|
||||
}
|
||||
|
||||
function makeTarget(
|
||||
cmd: Partial<ResolvedCommandTarget["cmd"]>,
|
||||
overrides?: Partial<ResolvedCommandTarget>,
|
||||
): ResolvedCommandTarget {
|
||||
return {
|
||||
cmd: {
|
||||
args: ["-e", "console.log('hello')"],
|
||||
cwd: process.cwd(),
|
||||
env: processEnv,
|
||||
exec: "bun",
|
||||
maxOutputBytes: 1024 * 1024,
|
||||
...cmd,
|
||||
},
|
||||
group: "default",
|
||||
intervalMs: 60000,
|
||||
name: "test-cmd",
|
||||
timeoutMs: 5000,
|
||||
type: "cmd",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("CommandChecker", () => {
|
||||
test("exitCode=0 成功", async () => {
|
||||
const result = await checker.execute(makeTarget({ args: ["-e", "process.exit(0)"], exec: "bun" }), makeCtx());
|
||||
expect(result.matched).toBe(true);
|
||||
expect(result.statusDetail).toBe("exitCode=0");
|
||||
expect(result.failure).toBeNull();
|
||||
});
|
||||
|
||||
test("exitCode=1 不匹配默认 [0]", async () => {
|
||||
const result = await checker.execute(makeTarget({ args: ["-e", "process.exit(1)"], exec: "bun" }), makeCtx());
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.statusDetail).toBe("exitCode=1");
|
||||
expect(result.failure!.phase).toBe("exitCode");
|
||||
});
|
||||
|
||||
test("exitCode=1 匹配自定义 [1]", async () => {
|
||||
const result = await checker.execute(
|
||||
makeTarget({ args: ["-e", "process.exit(1)"], exec: "bun" }, { expect: { exitCode: [1] } }),
|
||||
makeCtx(),
|
||||
);
|
||||
expect(result.matched).toBe(true);
|
||||
expect(result.statusDetail).toBe("exitCode=1");
|
||||
});
|
||||
|
||||
test("命令不存在返回 spawn 错误", async () => {
|
||||
const result = await checker.execute(makeTarget({ exec: "dial-command-not-found-xyz" }), makeCtx());
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure!.phase).toBe("exitCode");
|
||||
expect(result.failure!.message).toBeTruthy();
|
||||
});
|
||||
|
||||
test("超时返回错误", async () => {
|
||||
const result = await checker.execute(
|
||||
makeTarget({ args: ["-e", "await Bun.sleep(10000)"], exec: "bun" }, { timeoutMs: 100 }),
|
||||
makeCtx(100),
|
||||
);
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure!.message).toContain("超时");
|
||||
});
|
||||
|
||||
test("stdout 输出捕获", async () => {
|
||||
const result = await checker.execute(
|
||||
makeTarget({ args: ["-e", "console.log('hello world')"], exec: "bun" }),
|
||||
makeCtx(),
|
||||
);
|
||||
expect(result.matched).toBe(true);
|
||||
});
|
||||
|
||||
test("stdout 匹配 expect", async () => {
|
||||
const result = await checker.execute(
|
||||
makeTarget(
|
||||
{ args: ["-e", "console.log('hello')"], exec: "bun" },
|
||||
{ expect: { stdout: [{ contains: "hello" }] } },
|
||||
),
|
||||
makeCtx(),
|
||||
);
|
||||
expect(result.matched).toBe(true);
|
||||
});
|
||||
|
||||
test("stdout 不匹配 expect", async () => {
|
||||
const result = await checker.execute(
|
||||
makeTarget(
|
||||
{ args: ["-e", "console.log('hello')"], exec: "bun" },
|
||||
{ expect: { stdout: [{ contains: "nonexistent" }] } },
|
||||
),
|
||||
makeCtx(),
|
||||
);
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure!.phase).toBe("stdout");
|
||||
});
|
||||
|
||||
test("stderr 匹配 expect", async () => {
|
||||
const result = await checker.execute(
|
||||
makeTarget(
|
||||
{ args: ["-e", "process.stderr.write('error\\n')"], exec: "bun" },
|
||||
{ expect: { stderr: [{ contains: "error" }] } },
|
||||
),
|
||||
makeCtx(),
|
||||
);
|
||||
expect(result.matched).toBe(true);
|
||||
});
|
||||
|
||||
test("输出超过 maxOutputBytes", async () => {
|
||||
const result = await checker.execute(
|
||||
makeTarget({ args: ["-e", "process.stdout.write('y\\n'.repeat(1000))"], exec: "bun", maxOutputBytes: 10 }),
|
||||
makeCtx(),
|
||||
);
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure!.message).toContain("超过限制");
|
||||
});
|
||||
|
||||
test("durationMs 非空", async () => {
|
||||
const result = await checker.execute(makeTarget({ args: ["-e", "process.exit(0)"], exec: "bun" }), makeCtx());
|
||||
expect(result.durationMs).not.toBeNull();
|
||||
expect(result.durationMs!).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
test("不使用 shell,通配符不被展开", async () => {
|
||||
const result = await checker.execute(
|
||||
makeTarget({ args: ["-e", "console.log('*')"], exec: "bun" }, { expect: { stdout: [{ contains: "*" }] } }),
|
||||
makeCtx(),
|
||||
);
|
||||
expect(result.matched).toBe(true);
|
||||
});
|
||||
|
||||
test("execute 使用 resolved env", async () => {
|
||||
const result = await checker.execute(
|
||||
makeTarget(
|
||||
{
|
||||
args: ["-e", "console.log(process.env.DIAL_TEST_ENV ?? '')"],
|
||||
env: { ...processEnv, DIAL_TEST_ENV: "resolved-env" },
|
||||
exec: "bun",
|
||||
},
|
||||
{ expect: { stdout: [{ contains: "resolved-env" }] } },
|
||||
),
|
||||
makeCtx(),
|
||||
);
|
||||
|
||||
expect(result.matched).toBe(true);
|
||||
});
|
||||
|
||||
test("serialize 返回命令摘要和 config JSON", () => {
|
||||
const target = makeTarget({ args: ["-e", "console.log('hello')"], exec: "bun" });
|
||||
const s = checker.serialize(target);
|
||||
expect(s.target).toBe("exec bun -e console.log('hello')");
|
||||
const config = JSON.parse(s.config) as { args: string[]; exec: string };
|
||||
expect(config.exec).toBe("bun");
|
||||
expect(config.args).toEqual(["-e", "console.log('hello')"]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user