221 lines
7.7 KiB
TypeScript
221 lines
7.7 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
import { mkdir, rm } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import {
|
|
ALL_TARGETS,
|
|
archiveName,
|
|
checksumName,
|
|
computeChecksum,
|
|
execName,
|
|
packageTarget,
|
|
parseTargets,
|
|
} from "../../scripts/release";
|
|
|
|
describe("parseTargets", () => {
|
|
test("默认返回全部目标", () => {
|
|
const targets = parseTargets([]);
|
|
expect(targets).toEqual(ALL_TARGETS);
|
|
});
|
|
|
|
test("解析单一目标", () => {
|
|
const targets = parseTargets(["--target", "linux-x64"]);
|
|
expect(targets).toHaveLength(1);
|
|
expect(targets[0]!.bunTarget).toBe("bun-linux-x64");
|
|
expect(targets[0]!.os).toBe("linux");
|
|
expect(targets[0]!.arch).toBe("x64");
|
|
});
|
|
|
|
test("解析多个逗号分隔目标", () => {
|
|
const targets = parseTargets(["--target", "linux-x64,darwin-arm64,windows-x64"]);
|
|
expect(targets).toHaveLength(3);
|
|
expect(targets[0]!.bunTarget).toBe("bun-linux-x64");
|
|
expect(targets[1]!.bunTarget).toBe("bun-darwin-arm64");
|
|
expect(targets[2]!.bunTarget).toBe("bun-windows-x64");
|
|
});
|
|
|
|
test("解析 musl 变体", () => {
|
|
const targets = parseTargets(["--target", "linux-x64-musl"]);
|
|
expect(targets).toHaveLength(1);
|
|
expect(targets[0]!.bunTarget).toBe("bun-linux-x64-musl");
|
|
expect(targets[0]!.os).toBe("linux");
|
|
expect(targets[0]!.arch).toBe("x64-musl");
|
|
});
|
|
|
|
test("无效 target 导致进程退出", () => {
|
|
const exitCalls: number[] = [];
|
|
const exitSpy = spyOn(process, "exit").mockImplementation(((code: number) => {
|
|
exitCalls.push(code);
|
|
}) as typeof process.exit);
|
|
|
|
parseTargets(["--target", "invalid-target"]);
|
|
exitSpy.mockRestore();
|
|
|
|
expect(exitCalls).toEqual([1]);
|
|
});
|
|
});
|
|
|
|
describe("execName", () => {
|
|
const linuxTarget = ALL_TARGETS.find((t) => t.bunTarget === "bun-linux-x64")!;
|
|
const windowsTarget = ALL_TARGETS.find((t) => t.bunTarget === "bun-windows-x64")!;
|
|
const muslTarget = ALL_TARGETS.find((t) => t.bunTarget === "bun-linux-x64-musl")!;
|
|
|
|
test("Linux x64 可执行文件命名", () => {
|
|
expect(execName(linuxTarget, "0.1.0")).toBe("dial-server-0.1.0-linux-x64");
|
|
});
|
|
|
|
test("Windows 可执行文件命名带 .exe 后缀", () => {
|
|
expect(execName(windowsTarget, "0.1.0")).toBe("dial-server-0.1.0-windows-x64.exe");
|
|
});
|
|
|
|
test("musl 变体命名", () => {
|
|
expect(execName(muslTarget, "0.1.0")).toBe("dial-server-0.1.0-linux-x64-musl");
|
|
});
|
|
|
|
test("版本号正确嵌入", () => {
|
|
expect(execName(linuxTarget, "1.2.3")).toBe("dial-server-1.2.3-linux-x64");
|
|
});
|
|
});
|
|
|
|
describe("archiveName", () => {
|
|
const linuxTarget = ALL_TARGETS.find((t) => t.bunTarget === "bun-linux-x64")!;
|
|
const windowsTarget = ALL_TARGETS.find((t) => t.bunTarget === "bun-windows-x64")!;
|
|
|
|
test("Linux 压缩包命名", () => {
|
|
expect(archiveName(linuxTarget, "0.1.0")).toBe("dial-server_0.1.0_linux_x64.tar.gz");
|
|
});
|
|
|
|
test("Windows 压缩包命名", () => {
|
|
expect(archiveName(windowsTarget, "0.1.0")).toBe("dial-server_0.1.0_windows_x64.tar.gz");
|
|
});
|
|
});
|
|
|
|
describe("checksumName", () => {
|
|
const linuxTarget = ALL_TARGETS.find((t) => t.bunTarget === "bun-linux-x64")!;
|
|
|
|
test("校验和文件命名", () => {
|
|
expect(checksumName(linuxTarget, "0.1.0")).toBe("dial-server_0.1.0_linux_x64.tar.gz.sha256");
|
|
});
|
|
});
|
|
|
|
describe("computeChecksum", () => {
|
|
const projectRoot = fileURLToPath(new URL("../../", import.meta.url));
|
|
const packagesDir = join(projectRoot, "dist/release/packages");
|
|
|
|
beforeEach(async () => {
|
|
await mkdir(packagesDir, { recursive: true });
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await rm(packagesDir, { force: true, recursive: true });
|
|
});
|
|
|
|
test("生成正确格式的 sha256 文件", async () => {
|
|
const archivePath = join(packagesDir, "dial-server_0.1.0_linux_x64.tar.gz");
|
|
const content = Buffer.from("test archive content");
|
|
await Bun.write(archivePath, content);
|
|
|
|
const checksumPath = await computeChecksum(archivePath);
|
|
const checksumContent = await Bun.file(checksumPath).text();
|
|
|
|
const parts = checksumContent.split(" ");
|
|
expect(parts).toHaveLength(2);
|
|
expect(parts[0]).toMatch(/^[0-9a-f]{64}$/);
|
|
expect(parts[1]).toBe("dial-server_0.1.0_linux_x64.tar.gz\n");
|
|
});
|
|
|
|
test("校验和与文件内容一致", async () => {
|
|
const archivePath = join(packagesDir, "test.tar.gz");
|
|
const content = Buffer.from("hello world");
|
|
await Bun.write(archivePath, content);
|
|
|
|
const checksumPath = await computeChecksum(archivePath);
|
|
const checksumContent = await Bun.file(checksumPath).text();
|
|
const hash = checksumContent.split(" ")[0]!;
|
|
|
|
const expectedHash = Bun.CryptoHasher.hash("sha256", content, "hex");
|
|
expect(hash).toBe(expectedHash);
|
|
});
|
|
});
|
|
|
|
describe("packageTarget", () => {
|
|
const projectRoot = fileURLToPath(new URL("../../", import.meta.url));
|
|
const packagesDir = join(projectRoot, "dist/release/packages");
|
|
|
|
beforeEach(async () => {
|
|
await mkdir(packagesDir, { recursive: true });
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await rm(packagesDir, { force: true, recursive: true });
|
|
});
|
|
|
|
test("Linux 压缩包包含正确文件、目录前缀和权限位", async () => {
|
|
const linuxTarget = ALL_TARGETS.find((t) => t.bunTarget === "bun-linux-x64")!;
|
|
const binaryPath = join(packagesDir, "test-binary");
|
|
await Bun.write(binaryPath, "binary content");
|
|
|
|
const archivePath = await packageTarget(linuxTarget, "0.1.0", binaryPath);
|
|
const archiveContent = await Bun.file(archivePath).arrayBuffer();
|
|
|
|
const tar = await import("tar-stream");
|
|
const zlib = await import("node:zlib");
|
|
const extract = tar.extract();
|
|
|
|
const entries: Array<{ mode: number; name: string }> = [];
|
|
await new Promise<void>((resolve, reject) => {
|
|
const gunzip = zlib.createGunzip();
|
|
gunzip.write(Buffer.from(archiveContent));
|
|
gunzip.end();
|
|
gunzip.pipe(extract);
|
|
extract.on("entry", (header: { mode?: number; name: string }, _stream: unknown, next: () => void) => {
|
|
entries.push({ mode: header.mode ?? 0, name: header.name });
|
|
next();
|
|
});
|
|
extract.on("finish", resolve);
|
|
extract.on("error", reject);
|
|
});
|
|
|
|
const names = entries.map((e) => e.name);
|
|
expect(names).toContain("dial-server_0.1.0_linux_x64/dial-server");
|
|
expect(names).toContain("dial-server_0.1.0_linux_x64/probes.example.yaml");
|
|
expect(names).toContain("dial-server_0.1.0_linux_x64/LICENSE");
|
|
|
|
const binaryEntry = entries.find((e) => e.name.endsWith("/dial-server"))!;
|
|
expect(binaryEntry.mode).toBe(0o755);
|
|
|
|
const probesEntry = entries.find((e) => e.name.endsWith("probes.example.yaml"))!;
|
|
expect(probesEntry.mode).toBe(0o644);
|
|
});
|
|
|
|
test("Windows 压缩包内可执行文件名为 dial-server.exe", async () => {
|
|
const windowsTarget = ALL_TARGETS.find((t) => t.bunTarget === "bun-windows-x64")!;
|
|
const binaryPath = join(packagesDir, "test-binary.exe");
|
|
await Bun.write(binaryPath, "binary content");
|
|
|
|
const archivePath = await packageTarget(windowsTarget, "0.1.0", binaryPath);
|
|
const archiveContent = await Bun.file(archivePath).arrayBuffer();
|
|
|
|
const tar = await import("tar-stream");
|
|
const zlib = await import("node:zlib");
|
|
const extract = tar.extract();
|
|
|
|
const names: string[] = [];
|
|
await new Promise<void>((resolve, reject) => {
|
|
const gunzip = zlib.createGunzip();
|
|
gunzip.write(Buffer.from(archiveContent));
|
|
gunzip.end();
|
|
gunzip.pipe(extract);
|
|
extract.on("entry", (header: { name: string }, _stream: unknown, next: () => void) => {
|
|
names.push(header.name);
|
|
next();
|
|
});
|
|
extract.on("finish", resolve);
|
|
extract.on("error", reject);
|
|
});
|
|
|
|
expect(names).toContain("dial-server_0.1.0_windows_x64/dial-server.exe");
|
|
});
|
|
});
|