Files
bun-src/test/bundler/compile-windows-metadata.test.ts
T
2026-08-27 21:09:14 +00:00

819 lines
27 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { promises as fs } from "fs";
import { bunEnv, bunExe, isWindows, tempDir } from "harness";
import { join } from "path";
// PE optional-header Subsystem values
const IMAGE_SUBSYSTEM_WINDOWS_GUI = 2;
const IMAGE_SUBSYSTEM_WINDOWS_CUI = 3;
// Read the Subsystem field from a PE32+ optional header.
// Layout: e_lfanew at DOS+0x3C points to "PE\0\0" (4 bytes), followed by the
// 20-byte COFF header, then the optional header whose Subsystem is at +68.
// The whole header fits in the first page, so read 4 KiB instead of the full exe.
async function readPESubsystem(path: string): Promise<number> {
const data = await Bun.file(path).slice(0, 4096).bytes();
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
const peOffset = view.getUint32(0x3c, true);
return view.getUint16(peOffset + 24 + 68, true);
}
// Helper to ensure executable cleanup
function cleanup(outfile: string) {
return {
[Symbol.asyncDispose]: async () => {
try {
await fs.rm(outfile, { force: true });
} catch {}
},
};
}
// Drain stdout/stderr and assert the build succeeded. These tests run
// concurrently and spawn many `bun build --compile` processes from the same
// cwd; asserting on the combined object means a flake shows the real stderr
// instead of just "Expected: 0, Received: 1".
async function expectBuildOk(proc: Bun.Subprocess<"ignore", "pipe", "pipe">) {
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect({ stderr, exitCode }).toEqual({ stderr: "", exitCode: 0 });
return { stdout, stderr, exitCode };
}
// Read all VersionInfo fields in a single PowerShell invocation (spawning
// powershell is ~0.5-1s on CI). Forcing [Console]::OutputEncoding makes the
// read independent of the per-console output code page, which another process
// on the same console can flip off UTF-8.
async function readVersionInfo(outfile: string) {
const fields = [
"ProductName",
"CompanyName",
"FileDescription",
"LegalCopyright",
"ProductVersion",
"FileVersion",
"OriginalFilename",
];
await using proc = Bun.spawn({
cmd: [
"powershell",
"-NoProfile",
"-NonInteractive",
"-Command",
`[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ` +
`(Get-Item -LiteralPath '${outfile.replaceAll("'", "''")}').VersionInfo | ` +
`Select-Object ${fields.join(",")} | ConvertTo-Json -Compress`,
],
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect({ stderr, exitCode }).toEqual({ stderr: "", exitCode: 0 });
const info = JSON.parse(stdout) as Record<string, string | null>;
for (const k of fields) info[k] ??= "";
return info as Record<string, string>;
}
const windowsTarget = process.arch === "arm64" ? "bun-windows-aarch64" : "bun-windows-x64";
// https://github.com/oven-sh/bun/issues/19916
describe.skipIf(!isWindows).concurrent("--windows-hide-console", () => {
// The default-target console-subsystem baseline is asserted inside
// "Windows compile metadata > CLI flags > all metadata flags via CLI" to
// avoid a redundant full --compile.
test("CLI flag sets GUI subsystem", async () => {
using dir = tempDir("windows-subsystem-gui-cli", {
"app.js": `require("fs").writeFileSync(process.argv[2], "ran");`,
});
const outfile = join(String(dir), "gui.exe");
await using _cleanup = cleanup(outfile);
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
"--windows-hide-console",
join(String(dir), "app.js"),
"--outfile",
outfile,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
await expectBuildOk(proc);
expect(await readPESubsystem(outfile)).toBe(IMAGE_SUBSYSTEM_WINDOWS_GUI);
// The resulting GUI-subsystem exe still has to be a valid, runnable PE.
// A GUI process has no console, so assert via a file side effect + exit code.
const marker = join(String(dir), "marker.txt");
await using run = Bun.spawn({ cmd: [outfile, marker], env: bunEnv, stdout: "pipe", stderr: "pipe" });
const [, , runExit] = await Promise.all([run.stdout.text(), run.stderr.text(), run.exited]);
expect(await Bun.file(marker).text()).toBe("ran");
expect(runExit).toBe(0);
});
test("Bun.build() hideConsole sets GUI subsystem", async () => {
using dir = tempDir("windows-subsystem-gui-api", {
"app.js": `console.log("gui");`,
});
const result = await Bun.build({
entrypoints: [join(String(dir), "app.js")],
outdir: String(dir),
compile: {
target: windowsTarget,
outfile: "gui-api.exe",
windows: { hideConsole: true },
},
});
expect(result.success).toBe(true);
const outfile = result.outputs[0].path;
await using _cleanup = cleanup(outfile);
expect(await readPESubsystem(outfile)).toBe(IMAGE_SUBSYSTEM_WINDOWS_GUI);
});
// The "GUI subsystem survives the rescle metadata pass" case is asserted
// inside "Combined > metadata with --windows-hide-console" below, which
// builds the same flag combination and checks both Subsystem and VersionInfo.
});
describe.skipIf(!isWindows).concurrent("Windows compile metadata", () => {
describe("CLI flags", () => {
test("all metadata flags via CLI", async () => {
using dir = tempDir("windows-metadata-cli", {
"app.js": `console.log("Test app with metadata");`,
});
const outfile = join(String(dir), "app-with-metadata.exe");
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-title",
"My Application",
"--windows-publisher",
"Test Company Inc",
"--windows-version",
"1.2.3.4",
"--windows-description",
"A test application with metadata",
"--windows-copyright",
"Copyright © 2024 Test Company Inc",
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
await expectBuildOk(proc);
// No --windows-hide-console here, so the default build must stay a
// console (CUI) subsystem even after the rescle metadata pass.
expect(await readPESubsystem(outfile)).toBe(IMAGE_SUBSYSTEM_WINDOWS_CUI);
// OriginalFilename must be cleared (not "bun.exe") even with every
// metadata field set; this is the "Original Filename removal" coverage.
expect(await readVersionInfo(outfile)).toMatchObject({
ProductName: "My Application",
CompanyName: "Test Company Inc",
FileDescription: "A test application with metadata",
LegalCopyright: "Copyright © 2024 Test Company Inc",
ProductVersion: "1.2.3.4",
FileVersion: "1.2.3.4",
OriginalFilename: "",
});
});
test("partial metadata flags", async () => {
using dir = tempDir("windows-metadata-partial", {
"app.js": `console.log("Partial metadata test");`,
});
const outfile = join(String(dir), "app-partial.exe");
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-title",
"Simple App",
"--windows-version",
"10.20.30.40",
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
await expectBuildOk(proc);
// OriginalFilename must also be cleared with only a subset of flags.
// Version input "10.20.30.40" (multi-digit 4-part) round-trips unchanged.
expect(await readVersionInfo(outfile)).toMatchObject({
ProductName: "Simple App",
ProductVersion: "10.20.30.40",
FileVersion: "10.20.30.40",
OriginalFilename: "",
});
});
test("windows flags without --compile should error", async () => {
using dir = tempDir("windows-no-compile", {
"app.js": `console.log("test");`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "build", join(String(dir), "app.js"), "--windows-title", "Should Fail"],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toContain("--windows-title requires --compile");
expect(exitCode).not.toBe(0);
});
test("windows flags with non-Windows target should error", async () => {
using dir = tempDir("windows-wrong-target", {
"app.js": `console.log("test");`,
});
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
"--target",
"bun-linux-x64",
join(String(dir), "app.js"),
"--windows-title",
"Should Fail",
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Windows flags require a Windows compile target
expect(stderr.toLowerCase()).toContain("windows compile target");
expect(exitCode).not.toBe(0);
});
});
describe("Bun.build() API", () => {
test("all metadata via Bun.build()", async () => {
using dir = tempDir("windows-metadata-api", {
"app.js": `console.log("API metadata test");`,
});
const result = await Bun.build({
entrypoints: [join(String(dir), "app.js")],
outdir: String(dir),
compile: {
target: windowsTarget,
outfile: "app-api.exe",
windows: {
title: "API App",
publisher: "API Company",
version: "65535.65535.65535.65535",
description: "Built with Bun.build API",
copyright: "© 2024 API Company",
},
},
});
expect(result.success).toBe(true);
expect(result.outputs.length).toBe(1);
const outfile = result.outputs[0].path;
// Version "65535.65535.65535.65535" (u16 max per part) round-trips; pairs
// with the invalid "65536.0.0.0" case below as the valid-side fence post.
expect(await readVersionInfo(outfile)).toMatchObject({
ProductName: "API App",
CompanyName: "API Company",
FileDescription: "Built with Bun.build API",
LegalCopyright: "© 2024 API Company",
ProductVersion: "65535.65535.65535.65535",
FileVersion: "65535.65535.65535.65535",
OriginalFilename: "",
});
});
test("partial metadata via Bun.build()", async () => {
using dir = tempDir("windows-metadata-api-partial", {
"app.js": `console.log("Partial API test");`,
});
const result = await Bun.build({
entrypoints: [join(String(dir), "app.js")],
outdir: String(dir),
compile: {
target: windowsTarget,
outfile: "partial-api.exe",
windows: {
title: "Partial App",
version: "1.2",
},
},
});
expect(result.success).toBe(true);
const outfile = result.outputs[0].path;
// Version input "1.2" (2-part) is zero-padded to 4 parts.
expect(await readVersionInfo(outfile)).toMatchObject({
ProductName: "Partial App",
ProductVersion: "1.2.0.0",
FileVersion: "1.2.0.0",
OriginalFilename: "",
});
});
test("relative outdir with compile", async () => {
using dir = tempDir("windows-relative-outdir", {
"app.js": `console.log("Relative outdir test");`,
});
const result = await Bun.build({
entrypoints: [join(String(dir), "app.js")],
outdir: "./out",
compile: {
target: windowsTarget,
outfile: "relative.exe",
windows: {
title: "Relative Path App",
},
},
});
expect(result.success).toBe(true);
expect(result.outputs.length).toBe(1);
const outfile = result.outputs[0].path;
await using _cleanup = cleanup(outfile);
expect(await readVersionInfo(outfile)).toMatchObject({
ProductName: "Relative Path App",
OriginalFilename: "",
});
});
});
describe("Version string formats", () => {
// The normalization of each accepted arity is asserted on the binaries the
// surrounding tests already build, so only the "--windows-version with no
// other metadata" path needs its own compile here:
// 1 -> 1.0.0.0 (this test)
// 1.2 -> 1.2.0.0 (Bun.build API > partial metadata)
// 1.2.3 -> 1.2.3.0 (Combined > metadata with --windows-hide-console)
// 1.2.3.4 -> 1.2.3.4 (CLI flags > all metadata flags via CLI)
// 10.20.30.40 -> 10.20.30.40 (CLI flags > partial metadata flags)
// 65535.65535.65535.65535 -> same (Bun.build API > all metadata; u16 max)
test("--windows-version alone zero-pads to four parts", async () => {
using dir = tempDir("windows-version-alone", {
"app.js": `console.log("Version test");`,
});
const outfile = join(String(dir), "version-test.exe");
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-version",
"1",
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
await expectBuildOk(proc);
expect(await readVersionInfo(outfile)).toMatchObject({
ProductVersion: "1.0.0.0",
FileVersion: "1.0.0.0",
OriginalFilename: "",
});
});
test.each([
{ version: "not.a.version" },
{ version: "1.2.3.4.5" },
{ version: "1.-2.3.4" },
{ version: "65536.0.0.0" }, // > 65535
{ version: "" },
])("invalid version format should error gracefully: $version", async ({ version }) => {
// InvalidVersionFormat is raised by the Rust-side validator *before* the
// rescle C++ bindings touch the output, so we can compile against a
// ~128 KiB minimal PE template instead of cloning the full debug bun.exe.
using dir = tempDir("windows-invalid-version", {
"app.js": `console.log("Invalid version test");`,
});
const tmplPath = join(String(dir), "template.exe");
await Bun.write(tmplPath, minimalPE64Template());
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
"--compile-executable-path",
tmplPath,
join(String(dir), "app.js"),
"--outfile",
join(String(dir), "test.exe"),
"--windows-version",
version,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toContain("InvalidVersionFormat");
expect(exitCode).not.toBe(0);
});
});
describe("Edge cases", () => {
test("long strings in metadata", async () => {
using dir = tempDir("windows-long-strings", {
"app.js": `console.log("Long strings test");`,
});
const longString = Buffer.alloc(255, "A").toString();
const outfile = join(String(dir), "long-strings.exe");
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-title",
longString,
"--windows-description",
longString,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
await expectBuildOk(proc);
expect(await readVersionInfo(outfile)).toMatchObject({
ProductName: longString,
FileDescription: longString,
});
});
// Every --windows-* string field flows through the same
// `to_utf16_alloc_for_real` -> rescle `SetVersionString` wide-string path,
// so a single build that mixes Latin-1 symbols, ASCII punctuation, BMP CJK
// and surrogate-pair emoji round-tripping across the four fields covers the
// same encoding surface as two separate compiles would.
test("unicode and special characters in metadata", async () => {
using dir = tempDir("windows-unicode-special", {
"app.js": `console.log("Unicode + special chars test");`,
});
const outfile = join(String(dir), "unicode-special.exe");
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-title",
"App™ with® Special© アプリケーション",
"--windows-publisher",
"Company & Co. 会社名",
"--windows-description",
"Test \"quotes\" and 'apostrophes' 🚀 🎉",
"--windows-copyright",
"© 2024 <Company> 世界",
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
await expectBuildOk(proc);
expect(await readVersionInfo(outfile)).toMatchObject({
ProductName: "App™ with® Special© アプリケーション",
CompanyName: "Company & Co. 会社名",
FileDescription: "Test \"quotes\" and 'apostrophes' 🚀 🎉",
LegalCopyright: "© 2024 <Company> 世界",
});
});
test("empty strings in metadata", async () => {
using dir = tempDir("windows-empty-strings", {
"app.js": `console.log("Empty strings test");`,
});
const outfile = join(String(dir), "empty.exe");
// Empty strings should be treated as not provided
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-title",
"",
"--windows-description",
"",
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
await expectBuildOk(proc);
// rescle-binding.cpp skips empty title/description but still clears
// OriginalFilename unconditionally, so asserting on it proves the
// metadata pass ran (and that the output is a readable PE).
expect(await readVersionInfo(outfile)).toMatchObject({ OriginalFilename: "" });
});
});
describe("Combined with other compile options", () => {
test("metadata with --windows-hide-console", async () => {
using dir = tempDir("windows-metadata-hide-console", {
"app.js": `console.log("Hidden console test");`,
});
const outfile = join(String(dir), "hidden-with-metadata.exe");
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-hide-console",
"--windows-title",
"Hidden Console App",
"--windows-version",
"1.2.3",
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
await expectBuildOk(proc);
// rescle must not undo the GUI subsystem patch inject() applied.
expect(await readPESubsystem(outfile)).toBe(IMAGE_SUBSYSTEM_WINDOWS_GUI);
// Version input "1.2.3" (3-part) is zero-padded to 4 parts.
expect(await readVersionInfo(outfile)).toMatchObject({
ProductName: "Hidden Console App",
ProductVersion: "1.2.3.0",
FileVersion: "1.2.3.0",
});
});
test("metadata with --windows-icon", async () => {
// Create a simple .ico file (minimal valid ICO header)
const icoHeader = Buffer.from([
0x00,
0x00, // Reserved
0x01,
0x00, // Type (1 = ICO)
0x01,
0x00, // Count (1 image)
0x10, // Width (16)
0x10, // Height (16)
0x00, // Color count
0x00, // Reserved
0x01,
0x00, // Color planes
0x20,
0x00, // Bits per pixel
0x68,
0x01,
0x00,
0x00, // Size
0x16,
0x00,
0x00,
0x00, // Offset
]);
using dir = tempDir("windows-metadata-icon", {
"app.js": `console.log("Icon test");`,
"icon.ico": icoHeader,
});
const outfile = join(String(dir), "icon-with-metadata.exe");
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-icon",
join(String(dir), "icon.ico"),
"--windows-title",
"App with Icon",
"--windows-version",
"2.0.0.0",
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
await expectBuildOk(proc);
expect(await readVersionInfo(outfile)).toMatchObject({
ProductName: "App with Icon",
ProductVersion: "2.0.0.0",
});
});
});
});
// Build a minimal PE32+ image with one .text section and an optional Authenticode
// overlay past it. Large enough that the correct CheckSum exceeds 0xffff.
function minimalPE64Template(certSize = 0): Buffer {
const fileAlign = 512;
const sectAlign = 4096;
const peOff = 64;
const optOff = peOff + 24;
const optSize = 240;
const shOff = optOff + optSize;
const textRaw = 512;
const textRawSize = 128 * 1024;
const textVA = sectAlign;
const lastRawEnd = textRaw + textRawSize;
const tmpl = Buffer.alloc(lastRawEnd + certSize);
// DOS header
tmpl.writeUInt16LE(0x5a4d, 0); // "MZ"
tmpl.writeUInt32LE(peOff, 0x3c); // e_lfanew
// COFF header
tmpl.writeUInt32LE(0x00004550, peOff); // "PE\0\0"
tmpl.writeUInt16LE(0x8664, peOff + 4); // machine = AMD64
tmpl.writeUInt16LE(1, peOff + 6); // NumberOfSections
tmpl.writeUInt16LE(optSize, peOff + 20); // SizeOfOptionalHeader
tmpl.writeUInt16LE(0x0022, peOff + 22); // Characteristics
// Optional header (PE32+)
tmpl.writeUInt16LE(0x020b, optOff); // Magic
tmpl.writeUInt32LE(textVA, optOff + 16); // AddressOfEntryPoint
tmpl.writeUInt32LE(textVA, optOff + 20); // BaseOfCode
tmpl.writeBigUInt64LE(0x140000000n, optOff + 24); // ImageBase
tmpl.writeUInt32LE(sectAlign, optOff + 32); // SectionAlignment
tmpl.writeUInt32LE(fileAlign, optOff + 36); // FileAlignment
tmpl.writeUInt16LE(6, optOff + 40); // MajorOSVersion
tmpl.writeUInt16LE(6, optOff + 48); // MajorSubsystemVersion
tmpl.writeUInt32LE(textVA + sectAlign, optOff + 56); // SizeOfImage
tmpl.writeUInt32LE(512, optOff + 60); // SizeOfHeaders
tmpl.writeUInt16LE(3, optOff + 68); // Subsystem = CUI
tmpl.writeUInt32LE(16, optOff + 108); // NumberOfRvaAndSizes
if (certSize > 0) {
// Security directory (index 4): VirtualAddress is a file offset for this entry.
const secDir = optOff + 112 + 4 * 8;
tmpl.writeUInt32LE(lastRawEnd, secDir);
tmpl.writeUInt32LE(certSize, secDir + 4);
}
// Section header: .text
tmpl.write(".text\0\0\0", shOff, 8, "latin1");
tmpl.writeUInt32LE(textRawSize, shOff + 8); // VirtualSize
tmpl.writeUInt32LE(textVA, shOff + 12); // VirtualAddress
tmpl.writeUInt32LE(textRawSize, shOff + 16); // SizeOfRawData
tmpl.writeUInt32LE(textRaw, shOff + 20); // PointerToRawData
tmpl.writeUInt32LE(0x60000020, shOff + 36); // Characteristics
tmpl.fill(0xcc, textRaw, lastRawEnd); // .text body
if (certSize > 0) tmpl.fill(0xab, lastRawEnd); // cert overlay marker
return tmpl;
}
// Recompute the reference PE CheckSum over `bytes` with the CheckSum field zeroed.
function peChecksum(bytes: Buffer): { stored: number; expected: number } {
const peOff = bytes.readUInt32LE(0x3c);
const ckOff = peOff + 24 + 64;
const stored = bytes.readUInt32LE(ckOff);
const copy = Buffer.from(bytes);
copy.writeUInt32LE(0, ckOff);
let sum = 0;
for (let i = 0; i + 1 < copy.length; i += 2) {
sum += copy[i] | (copy[i + 1] << 8);
sum = (sum & 0xffff) + (sum >>> 16);
}
if (copy.length & 1) sum += copy[copy.length - 1];
sum = (sum & 0xffff) + (sum >>> 16);
sum = (sum & 0xffff) + (sum >>> 16);
return { stored, expected: (sum + copy.length) >>> 0 };
}
function lastSectionEnd(bytes: Buffer): number {
const peOff = bytes.readUInt32LE(0x3c);
const nSect = bytes.readUInt16LE(peOff + 6);
const shOff = peOff + 24 + bytes.readUInt16LE(peOff + 20);
let end = 0;
for (let i = 0; i < nSect; i++) {
const o = shOff + i * 40;
const rawEnd = bytes.readUInt32LE(o + 20) + bytes.readUInt32LE(o + 16);
if (rawEnd > end) end = rawEnd;
}
return end;
}
async function compileWindowsTemplate(dir: string, tmpl: Buffer): Promise<Buffer> {
const tmplPath = join(dir, "template.exe");
const outPath = join(dir, "out.exe");
await Bun.write(tmplPath, tmpl);
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
"--target=bun-windows-x64",
"--compile-executable-path",
tmplPath,
join(dir, "entry.js"),
"--outfile",
outPath,
],
env: bunEnv,
cwd: dir,
stdout: "pipe",
stderr: "pipe",
});
await expectBuildOk(proc);
return Buffer.from(await Bun.file(outPath).arrayBuffer());
}
// The PE OptionalHeader.CheckSum field is defined as `fold16(word_sum) + file_length`
// (the same algorithm Windows' MapFileAndCheckSum uses).
//
// These tests craft a minimal PE64 template and cross-compile via
// `--compile-executable-path`, so they run on every platform without downloading
// a real bun.exe.
test.concurrent("bun build --compile writes a valid PE OptionalHeader.CheckSum", async () => {
using dir = tempDir("pe-checksum", { "entry.js": `console.log("hi");` });
const out = await compileWindowsTemplate(String(dir), minimalPE64Template());
const { stored, expected } = peChecksum(out);
// Guard against the template shrinking to where the checksum fits in 16 bits
// and the assertion below becomes vacuous.
expect(expected).toBeGreaterThan(0xffff);
expect(stored).toBe(expected);
});
test.concurrent("bun build --compile truncates the PE output when Authenticode strip shrinks it", async () => {
// 16 KiB cert overlay: larger than the .bun section a trivial entry produces, so
// strip_authenticode makes the in-memory PE shorter than the cloned base file.
const certSize = 16 * 1024;
const tmpl = minimalPE64Template(certSize);
using dir = tempDir("pe-truncate", { "entry.js": `console.log("hi");` });
const out = await compileWindowsTemplate(String(dir), tmpl);
const lastEnd = lastSectionEnd(out);
expect({ fileSize: out.length, lastEnd }).toEqual({ fileSize: lastEnd, lastEnd });
expect(out.length).toBeLessThan(tmpl.length);
const { stored, expected } = peChecksum(out);
expect(stored).toBe(expected);
});