Files
scriptc/packages/compiler/test/debug-variables.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.1 KiB
TypeScript

import { execFile } from "node:child_process";
import { cp, mkdtemp, readFile, rm } 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/3068-debug-variables");
const sanitize = process.env["SCRIPTC_SAN"] === "1";
test.runIf(process.platform === "darwin")(
"LLVM dev builds expose source variables and lexical scopes in LLDB",
async () => {
const dir = await mkdtemp(join(tmpdir(), "scriptc-debug-variables-"));
try {
const source = join(dir, "source space é");
await cp(corpus, source, { recursive: true });
const entry = join(source, "main.ts");
const outPath = join(dir, "program");
const text = await readFile(entry, "utf8");
const line = (marker: string): number =>
text.split("\n").findIndex((l) => l.endsWith(`// debug: ${marker}`)) + 1;
const oracle = await exec(process.execPath, [entry]);
for (let build = 0; build < 2; build++) {
const result = await compile(entry, {
outDir: dir,
outPath,
backend: "llvm",
optimization: "dev",
sanitize,
});
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics) : "").toBe(true);
if (!result.ok) return;
const native = await exec(outPath);
expect(native.stdout).toBe(oracle.stdout);
expect(native.stderr).toBe(oracle.stderr);
const commands = [
...["globals", "locals", "shadow", "capture", "restored", "loop", "item", "hoisted"].map(
(marker) =>
`breakpoint set --file main.ts --line ${line(marker)} --move-to-nearest-code false`,
),
"breakpoint set --file other.ts --line 4 --move-to-nearest-code false",
"run",
"frame variable moduleValue moduleText",
"continue",
"frame variable input enabled fallback value text",
"frame variable text->len",
"frame variable text->data",
"continue",
"frame variable value",
"continue",
"frame variable value",
"continue",
"frame variable value",
"continue",
"frame variable i",
"continue",
"frame variable i",
"continue",
"frame variable item->tag item->slot.arm0",
"continue",
"frame variable item->tag item->slot.arm0",
"continue",
"frame variable hoisted",
"continue",
"frame variable moduleValue",
"continue",
];
const debug = await exec("lldb", [
"--batch",
outPath,
...commands.flatMap((c) => ["-o", c]),
]).catch((error: { stdout: string; stderr: string }) => {
throw new Error(`${error.stdout}\n${error.stderr}`);
});
expect(debug.stderr).toBe("");
expect(debug.stdout).not.toContain("pending");
for (const value of [
"moduleValue = 42",
"moduleValue = 100",
"input = 3",
"enabled = true",
"fallback = 9",
"value = 4",
"value = 99",
"value = 6",
"i = 0",
"i = 1",
"item->tag = f64",
"item->slot.arm0 = 6",
"item->slot.arm0 = 7",
"hoisted = 13",
]) {
expect(debug.stdout).toContain(value);
}
expect(debug.stdout).toContain("text->len = 8");
expect(debug.stdout).toContain('text->data = "hello é"');
const shadowValues = [
...debug.stdout.matchAll(/\(lldb\) frame variable value\r?\n\(double\) value = (\d+)/g),
].map((match) => Number(match[1]));
expect(shadowValues).toEqual([99, 6, 6]);
expect(debug.stdout).not.toContain("sc_l_");
// Exercise the executable cache's restored dSYM, with native objects
// and the first linked binary already gone.
await rm(outPath);
await rm(`${outPath}.dSYM`, { recursive: true });
}
} finally {
await rm(dir, { recursive: true, force: true });
}
},
);
test("release LLVM output omits dev variable metadata", async () => {
const dir = await mkdtemp(join(tmpdir(), "scriptc-debug-modes-"));
try {
for (const mode of ["dev", "release"] as const) {
const outPath = join(dir, `${mode}.ll`);
const result = await compile(join(corpus, "main.ts"), {
outDir: dir,
outPath,
outputKind: "llvm",
optimization: mode,
});
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics) : "").toBe(true);
const llvm = await readFile(outPath, "utf8");
if (mode === "dev") {
expect(llvm).toContain("emissionKind: FullDebug");
expect(llvm).toContain('!DILocalVariable(name: "input", arg: 1');
expect(llvm).not.toContain("LineTablesOnly");
} else {
expect(llvm).not.toMatch(/!DI|llvm\.dbg\./);
}
}
} finally {
await rm(dir, { recursive: true, force: true });
}
});