Shutdown Check

搜索文档

查找页面或章节

在 JavaScript 或 TypeScript 中运行同样的关闭测试,并检查结果。

如果关闭检查要放进测试套件或自定义脚本里,适合使用 shutdown-check 的 Node API。它和 CLI 使用同一套引擎,区别在于它把结果作为数据返回,不打印输出,也不会退出你的进程。

导入包

ES 模块:

import { checkShutdown, defineConfig } from "shutdown-check";

CommonJS(1.0.1 起支持):

const { checkShutdown, defineConfig } = require("shutdown-check");

两个入口都自带 TypeScript 类型声明。

导出项

导出项适用场景
checkShutdown已有输入对象,想一步完成校验和完整运行
defineConfig编写配置时想要类型检查和自动补全
parseConfig想校验一个对象并填入默认值
loadConfig想读取并校验一个 JSON 文件
runCheck手上已经有校验过的 CheckConfig
junitXml想把 CheckResult 转成 JUnit XML

包里还导出了配置、结果、诊断和时间线相关的类型。

用 checkShutdown 运行检查

推荐使用高层函数 checkShutdown()。它会校验输入、填入默认值、运行检查、完成清理,最后 resolve 为一个 CheckResult。

test/shutdown.test.ts
import assert from "node:assert/strict";
import { test } from "node:test";
import { checkShutdown } from "shutdown-check";
 
test("finishes active requests on SIGTERM", { timeout: 30_000 }, async () => {
  const result = await checkShutdown({
    command: ["node", "dist/server.js"],
    env: { PORT: "3510" },
    baseUrl: "http://127.0.0.1:3510",
    readiness: { path: "/health" },
    workload: {
      path: "/slow",
      bodyIncludes: "work complete",
      started: { type: "response-headers" },
    },
    shutdown: {
      deadlineMs: 10_000,
      readinessWithdrawal: true,
    },
  });
 
  assert.equal(result.pass, true, `${result.code}: ${result.message}`);
});

普通的关闭失败不会抛出异常,而是 resolve 为 pass: false,并带上诊断码。配置无效时,Promise 会以校验 Error reject。

function checkShutdown(input: CheckConfigInput, baseDirectory?: string): Promise<CheckResult>

先用 parseConfig() 校验 input,再运行检查。相对路径从 baseDirectory 解析(默认 process.cwd())。函数会自己启动并清理进程;检查失败时返回 pass: false,而不是抛出异常;只有配置无效时才会抛出。

解析相对路径

第二个参数是基准目录,用来解析相对的 cwd,默认是 process.cwd()。

const result = await checkShutdown(config, import.meta.dirname);

如果测试运行器可能在别的工作目录下执行,就传入测试文件所在的目录。

用 defineConfig 为配置添加类型

defineConfig() 原样返回传入的对象。它的作用是让编辑器提供补全,并在测试运行前发现类型错误。

shutdown.config.ts
import { defineConfig } from "shutdown-check";
 
export default defineConfig({
  command: ["node", "dist/server.js"],
  baseUrl: "http://127.0.0.1:3510",
  readiness: { path: "/health", timeoutMs: 30_000 },
  workload: {
    path: "/slow",
    concurrent: 2,
    started: { type: "response-headers" },
  },
  shutdown: { deadlineMs: 15_000 },
});

导入这个对象,再传给 checkShutdown()。注意 CLI 不会加载 TypeScript 配置文件。

function defineConfig<T extends CheckConfigInput>(input: T): T

原样返回 input。用 TypeScript 编写配置时,可以借助它获得编辑器补全和类型检查。

用 parseConfig 校验输入

parseConfig() 校验一个类型未知的对象,返回填好默认值的完整 CheckConfig。

import { parseConfig } from "shutdown-check";
 
const config = parseConfig(
  {
    command: ["node", "dist/server.js"],
    baseUrl: "http://127.0.0.1:3510",
    readiness: { path: "/health" },
    workload: {
      path: "/slow",
      started: { type: "response-headers" },
    },
  },
  process.cwd()
);
 
console.log(config.shutdown.deadlineMs); // 10000

可选的第二个参数决定相对 cwd 如何解析。校验失败时会抛出 Error,消息中指出第一个无效字段。

用 loadConfig 读取 JSON 配置

loadConfig() 读取文件、解析 JSON、完成校验,并以文件所在目录为基准解析 cwd。

import { loadConfig } from "shutdown-check";
 
const config = await loadConfig("config/shutdown-check.json");

文件错误、JSON 语法错误和校验错误都会抛出,错误消息与 CLI 使用的一致。

function parseConfig(raw: unknown, baseDirectory?: string): CheckConfig

校验配置对象并填入所有默认值,其中 cwd 从 baseDirectory 解析(默认 process.cwd())。配置无效时抛出 Error,并指出第一个无效字段。

function loadConfig(file: string): Promise<CheckConfig>

读取并校验 JSON 配置文件。文件中的相对路径从该文件所在目录解析。

编写自定义运行器

如果需要自定义日志或报告处理,先用 parseConfig() 或 loadConfig() 得到配置,再调用 runCheck()。

scripts/check-shutdown.ts
import { mkdir, writeFile } from "node:fs/promises";
import { junitXml, loadConfig, runCheck } from "shutdown-check";
 
const config = await loadConfig("shutdown-check.json");
const result = await runCheck(config);
 
for (const item of result.timeline) {
  const detail = item.detail ? ` — ${item.detail}` : "";
  console.log(`+${item.ms} ms ${item.event}${detail}`);
}
 
await mkdir("reports", { recursive: true });
await writeFile(
  "reports/shutdown.xml",
  junitXml(result, "api-server shutdown")
);
 
if (!result.pass) {
  console.error(`${result.code}: ${result.message}`);
  if (result.stderr) console.error(result.stderr);
  process.exitCode = 1;
}

runCheck() 要求传入有效的 CheckConfig,不会再校验一遍手写的对象。跳过校验可能导致 SC002 或 SC999。

function runCheck(config: CheckConfig): Promise<CheckResult>

使用已校验的配置运行检查,配置来自 parseConfig() 或 loadConfig()。

function junitXml(result: CheckResult, suiteName?: string): string

把结果转换为 JUnit XML:一个测试套件(名称为 suiteName,默认 "shutdown-check"),其中只有一个测试用例“graceful shutdown”。时间线写入 system-out,stderr 写入 system-err;检查失败时会添加一个 failure 元素,类型为对应的诊断码。

junitXml() 的可选第二个参数用来命名测试套件,默认是 shutdown-check。

理解 CheckResult

每次检查完成后,都会 resolve 为同一个对象,也就是 CLI --json 打印的那个对象。

选项类型默认值说明
pass*boolean—仅当结果为 SC000 时为 true。
code*string—诊断码:通过时为 SC000,否则为第一个失败项的诊断码。
message*string—用一句话说明发生了什么,并附上观察到的值。
timeline*TimelineEvent[]—按顺序列出检查的每一步,并标出从检查开始算起的毫秒数。
stdout*string—服务写入 stdout 的最后 8 KiB 内容。
stderr*string—服务写入 stderr 的最后 8 KiB 内容。

重要字段:

  • pass:在测试里断言的就是这个值。
  • code:通过时为 SC000,否则为第一个失败的诊断码。
  • message:给人看的结果说明。
  • timeline:记录观察到的每一步。
  • stdout 和 stderr:捕获到的服务输出末尾部分。
  • startedAt 和 durationMs:本次运行的开始时间和耗时。

timeline 中每一项的结构如下:

选项类型默认值说明
ms*number—从检查开始算起的毫秒数,已取整。
event*string—发生了什么:"process launched"、"service ready"、"signal sent"、"work request finished"、"process exited" 等。
detailstring—事件对应的具体值,例如 "HTTP 503" 或 "code=0, signal=none"。

所有事件名称及其详情见输出参考。

什么情况会抛出异常?

情况checkShutdown()runCheck()
输入配置无效以 Error reject不校验
预期内的检查失败resolve 为 pass: falseresolve 为 pass: false
运行中出现意外错误resolve 为 SC999resolve 为 SC999

如果配置可能无效,用 try/catch 包住调用。普通的测试失败则查看返回的诊断码和时间线。

清理与并行测试

在 resolve 之前,API 会关闭它发出的客户端请求、清除定时器,必要时强制杀掉服务的进程组。能通过检查的服务,此时已经自行退出并关闭了端口。

并行运行的检查必须使用不同的端口和相互隔离的测试数据。否则某次运行可能以 SC001 失败,或者影响另一次运行的测试请求。