Files
scriptc/packages/compiler/test/source-debugging.test.ts
Chris Tate c08ab955ae Enforce linting and consistent source formatting
- Enforce oxlint and oxfmt for owned JavaScript and TypeScript with independent CI checks.
- Preserve compiler import boundaries and protected fixtures while cleaning up lint findings.
- Standardize source formatting and validate import, fixture, and embedded-source protections.
2026-10-05 17:51:24 -05:00

141 lines
5.4 KiB
TypeScript

import { execFile } from "node:child_process";
import { cp, mkdtemp, readFile, rm, stat } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";
import { expect, test } from "vitest";
import { compile } from "../src/index.js";
const exec = promisify(execFile);
const corpus = join(import.meta.dirname, "../../../tests/corpus/3066-source-debugging");
const sanitize = process.env["SCRIPTC_SAN"] === "1";
// Xcode ships this debugger. Resolve real breakpoints after temporary native
// objects have been deleted, including after the executable cache restores.
test.runIf(process.platform === "darwin").each(["llvm"] as const)(
"dev %s builds preserve source breakpoints across cache hits",
async (backend) => {
const dir = await mkdtemp(join(tmpdir(), "scriptc-debug-"));
try {
const source = join(dir, 'source space é "quoted"');
await cp(corpus, source, { recursive: true });
const entry = join(source, "main.ts");
const outPath = join(dir, "program");
const options = { outDir: dir, outPath, backend, optimization: "dev" as const, sanitize };
const oracle = await exec(process.execPath, [entry]);
for (let build = 0; build < 2; build++) {
const result = await compile(entry, options);
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics) : "").toBe(true);
if (!result.ok) return;
expect(
(await stat(`${outPath}.dSYM/Contents/Resources/DWARF/program`)).size,
).toBeGreaterThan(0);
const native = await exec(outPath);
expect(native.stdout).toBe(oracle.stdout);
expect(native.stderr).toBe(oracle.stderr);
const debug = await exec("lldb", [
"--batch",
outPath,
"-o",
"breakpoint set --file main.ts --line 4 --move-to-nearest-code false",
"-o",
"breakpoint set --file helper.ts --line 4 --move-to-nearest-code false",
"-o",
"breakpoint list",
]);
expect(debug.stdout).not.toContain("pending");
expect(debug.stdout).toContain("main.ts:4");
expect(debug.stdout).toContain("helper.ts:4");
await rm(outPath);
await rm(`${outPath}.dSYM`, { recursive: true });
}
for (const mode of ["release", "dev", "strip"] as const) {
const result = await compile(entry, {
...options,
optimization: mode === "release" ? "release" : "dev",
strip: mode === "strip",
});
expect(result.ok).toBe(true);
if (mode === "dev") {
expect((await stat(`${outPath}.dSYM`)).isDirectory()).toBe(true);
} else {
await expect(stat(`${outPath}.dSYM`)).rejects.toMatchObject({ code: "ENOENT" });
}
}
} finally {
await rm(dir, { recursive: true, force: true });
}
},
);
test.runIf(process.platform === "darwin")(
"dev LLVM object and assembly outputs carry source locations",
async () => {
const dir = await mkdtemp(join(tmpdir(), "scriptc-debug-object-"));
try {
for (const outputKind of ["obj", "asm"] as const) {
const outPath = join(dir, `program.${outputKind}`);
const result = await compile(join(corpus, "main.ts"), {
outDir: dir,
outPath,
outputKind,
optimization: "dev",
});
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics) : "").toBe(true);
if (!result.ok) continue;
if (outputKind === "obj") {
const { stdout } = await exec("dwarfdump", ["--debug-line", outPath]);
expect(stdout).toContain("main.ts");
expect(stdout).toContain("helper.ts");
const info = await exec("dwarfdump", ["--debug-info", outPath]);
expect(info.stdout).toContain("DW_TAG_variable");
expect(info.stdout).toContain("DW_TAG_formal_parameter");
} else {
const assembly = await readFile(outPath, "utf8");
expect(assembly).toContain(".loc");
expect(assembly).toContain("helper.ts");
}
}
} finally {
await rm(dir, { recursive: true, force: true });
}
},
);
test.each([
"800-switch-basics.ts",
"1021-async-ordering.ts",
"1452-return-through-finally.ts",
"2013-generators-sent-values.ts",
"603-closures-rc-stress.ts",
])("dev code generation preserves control flow: %s", async (fixture) => {
const dir = await mkdtemp(join(tmpdir(), "scriptc-debug-flow-"));
try {
const entry = join(corpus, "..", fixture);
const node = await exec(process.execPath, [entry]);
for (const backend of ["llvm"] as const) {
const outPath = join(dir, process.platform === "win32" ? `${backend}.exe` : backend);
const built = await compile(entry, {
outDir: dir,
outPath,
backend,
optimization: "dev",
sanitize,
});
expect(built.ok, !built.ok ? JSON.stringify(built.diagnostics) : "").toBe(true);
if (!built.ok) continue;
const native = await exec(outPath);
expect(native.stdout).toBe(node.stdout);
const stderr = sanitize
? native.stderr.replace(
/^==\d+==WARNING: ASan doesn't fully support makecontext\/swapcontext functions and may produce false positives in some cases!\n/gm,
"",
)
: native.stderr;
expect(stderr).toBe(node.stderr);
}
} finally {
await rm(dir, { recursive: true, force: true });
}
});