import { execFile } from "node:child_process"; import { readFileSync } from "node:fs"; import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { promisify } from "node:util"; import { expect, test } from "vitest"; const execFileAsync = promisify(execFile); const srcDir = join(import.meta.dirname, "../src"); /** The module headers scr_runtime.h includes, in include order. */ const MODULE_HEADERS = [ ...readFileSync(join(srcDir, "scr_runtime.h"), "utf8").matchAll(/^#include "(scr_\w+\.h)"/gm), ].map((m) => m[1]!); const DEFINE_SETS = [[], ["-DSCR_LIB"], ["-DSCR_LIB", "-DSCR_THREAD_INSTANCES"], ["-DSCR_WORKERS"]]; // Each module header includes what it uses, so a runtime unit can include // only the headers it needs. A header that relies on an earlier one it does // not include fails here; -Werror=visibility catches a `struct T *` // parameter whose tag is not declared at file scope yet. test("every module header compiles on its own", async () => { expect(MODULE_HEADERS.length).toBeGreaterThan(40); const dir = await mkdtemp(join(tmpdir(), "scriptc-runtime-headers-")); try { const failures: string[] = []; await Promise.all( MODULE_HEADERS.map(async (header) => { const source = join(dir, `${header}.c`); await writeFile(source, `#include "${header}"\n`); for (const defines of DEFINE_SETS) { try { await execFileAsync("clang", [ "-std=c11", "-fsyntax-only", "-Werror=visibility", "-Werror=implicit-function-declaration", ...(process.platform === "linux" ? ["-D_GNU_SOURCE"] : []), ...defines, "-I", srcDir, source, ]); } catch (error) { failures.push( `${header} ${defines.join(" ")}: ${(error as { stderr?: string }).stderr}`, ); } } }), ); expect(failures).toEqual([]); } finally { await rm(dir, { recursive: true, force: true }); } }); test("scr_runtime.h holds no declarations of its own", () => { const text = readFileSync(join(srcDir, "scr_runtime.h"), "utf8") .replace(/\/\*[\s\S]*?\*\//g, "") .split("\n") .map((line) => line.trim()) .filter((line) => line !== ""); expect(text.filter((line) => !/^#(include "scr_\w+\.h"|ifndef|define|endif)/.test(line))).toEqual( [], ); });