Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 105 additions & 79 deletions test/cli/install/bun-upgrade.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,84 +99,98 @@ describe.concurrent(() => {
});

it("zero arguments, should succeed", async () => {
const tagName = bunExe().includes("-debug") ? "canary" : `bun-v${Bun.version}`;
using server = Bun.serve({
tls: tls,
port: 0,
async fetch() {
return new Response(
JSON.stringify({
"tag_name": tagName,
"assets": [
{
"url": "foo",
"content_type": "application/zip",
"name": "bun-windows-x64.zip",
"browser_download_url": `https://pub-5e11e972747a44bf9aaf9394f185a982.r2.dev/releases/${tagName}/bun-windows-x64.zip`,
},
{
"url": "foo",
"content_type": "application/zip",
"name": "bun-windows-x64-baseline.zip",
"browser_download_url": `https://pub-5e11e972747a44bf9aaf9394f185a982.r2.dev/releases/${tagName}/bun-windows-x64-baseline.zip`,
},
{
"url": "foo",
"content_type": "application/zip",
"name": "bun-windows-aarch64.zip",
"browser_download_url": `https://pub-5e11e972747a44bf9aaf9394f185a982.r2.dev/releases/${tagName}/bun-windows-aarch64.zip`,
},
{
"url": "foo",
"content_type": "application/zip",
"name": "bun-linux-x64.zip",
"browser_download_url": `https://pub-5e11e972747a44bf9aaf9394f185a982.r2.dev/releases/${tagName}/bun-linux-x64.zip`,
},
{
"url": "foo",
"content_type": "application/zip",
"name": "bun-linux-x64-baseline.zip",
"browser_download_url": `https://pub-5e11e972747a44bf9aaf9394f185a982.r2.dev/releases/${tagName}/bun-linux-x64-baseline.zip`,
},
{
"url": "foo",
"content_type": "application/zip",
"name": "bun-linux-aarch64.zip",
"browser_download_url": `https://pub-5e11e972747a44bf9aaf9394f185a982.r2.dev/releases/${tagName}/bun-linux-aarch64.zip`,
},
{
"url": "foo",
"content_type": "application/zip",
"name": "bun-darwin-x64.zip",
"browser_download_url": `https://pub-5e11e972747a44bf9aaf9394f185a982.r2.dev/releases/${tagName}/bun-darwin-x64.zip`,
},
{
"url": "foo",
"content_type": "application/zip",
"name": "bun-darwin-x64-baseline.zip",
"browser_download_url": `https://pub-5e11e972747a44bf9aaf9394f185a982.r2.dev/releases/${tagName}/bun-darwin-x64-baseline.zip`,
},
{
"url": "foo",
"content_type": "application/zip",
"name": "bun-darwin-aarch64.zip",
"browser_download_url": `https://pub-5e11e972747a44bf9aaf9394f185a982.r2.dev/releases/${tagName}/bun-darwin-aarch64.zip`,
},
],
}),
);
},
});

// On windows, open the temporary directory without FILE_SHARE_DELETE before spawning
// the upgrade process. This is to test for EBUSY errors
// On Windows, hold the temp dir open without FILE_SHARE_DELETE for the
// whole run to exercise EBUSY handling. The canary path hard-codes
// github.com, so this also doubles as the one live end-to-end check.
openTempDirWithoutSharingDelete();
const cwd = tmpdirSync();
const execPath = join(cwd, basename(bunExe()));
await copyFile(bunExe(), execPath);

let stderr: string;
try {
await using proc = Bun.spawn({
cmd: [execPath, "upgrade"],
cwd,
stdout: null,
stdin: "pipe",
stderr: "pipe",
env,
});
[stderr] = await Promise.all([proc.stderr.text(), proc.exited]);
} finally {
closeTempDirHandle();
}

expect(stderr).not.toContain("This command updates Bun itself, and does not take package names.");

// The canary release on github.com is periodically rebuilt by deleting the
// old assets and uploading new ones, leaving a short window where the
// asset 404s. That window is not a bug in `bun upgrade`.
if (stderr.includes("Canary builds are not available for this platform yet")) {
console.warn("bun upgrade: skipped live end-to-end assertions, canary asset 404'd (release re-upload window)");
} else {
expect(stderr).not.toContain("error:");
}
});
});

// https://github.com/oven-sh/bun/pull/10387 : upgrading must not EBUSY on
// Windows when another handle holds the OS temp dir without FILE_SHARE_DELETE.
// The open/close helpers are no-ops on other platforms.
it("completes the download when the OS temp dir is held open without FILE_SHARE_DELETE", async () => {
const tagName = "bun-v9.8.7";
const assetNames: string[] = [];
for (const os of ["windows", "linux", "darwin"]) {
for (const arch of ["x64", "aarch64"]) {
for (const abi of ["", "-musl"]) {
for (const cpu of ["", "-baseline"]) {
assetNames.push(`bun-${os}-${arch}${abi}${cpu}.zip`);
}
}
}
}

let apiHits = 0;
let downloadHits = 0;
using server = Bun.serve({
tls: tls,
port: 0,
async fetch(req) {
const { pathname } = new URL(req.url);
if (pathname.startsWith("/releases/")) {
downloadHits++;
return new Response("this is not a real zip archive");
}
apiHits++;
return new Response(
JSON.stringify({
"tag_name": tagName,
"assets": assetNames.map(name => ({
"url": "foo",
"content_type": "application/zip",
"name": name,
"browser_download_url": `https://${server.hostname}:${server.port}/releases/${tagName}/${name}`,
})),
}),
);
},
});

const cwd = tmpdirSync();
const execPath = join(cwd, basename(bunExe()));
await copyFile(bunExe(), execPath);

// On Windows, hold a handle to the OS temp directory without FILE_SHARE_DELETE
// for the whole upgrade run so EBUSY handling is actually exercised.
openTempDirWithoutSharingDelete();
let stderr: string;
let exitCode: number;
try {
await using proc = Bun.spawn({
cmd: [execPath, "upgrade"],
// --stable routes through GITHUB_API_DOMAIN (the canary path hard-codes
// github.com and would never touch this mock).
cmd: [execPath, "upgrade", "--stable"],
cwd,
stdout: null,
stdin: "pipe",
Expand All @@ -185,18 +199,30 @@ describe.concurrent(() => {
...env,
NODE_TLS_REJECT_UNAUTHORIZED: "0",
GITHUB_API_DOMAIN: `${server.hostname}:${server.port}`,
// Clear ambient proxy configuration so the mock is reached directly.
HTTPS_PROXY: "",
HTTP_PROXY: "",
https_proxy: "",
http_proxy: "",
NO_PROXY: "",
no_proxy: "",
ASAN_OPTIONS: [env.ASAN_OPTIONS, "detect_leaks=0"].filter(Boolean).join(":"),
},
});

[stderr, exitCode] = await Promise.all([proc.stderr.text(), proc.exited]);
} finally {
closeTempDirHandle();
}

// Should not contain error message
expect(await proc.stderr.text()).not.toContain("error:");
// Reap the subprocess: stderr can close before the child exits, and an
// unreaped child is force-killed by the test runner at test end without
// draining the Subprocess refcount — LSan then flags it as a leak.
await proc.exited;
});
// Both the release metadata and the archive must have been served locally;
// the archive fetch is what proves we reached filesystem.tmpdir().
expect({ apiHits, downloadHits }).toEqual({ apiHits: 1, downloadHits: 1 });
expect(stderr).toContain("9.8.7");
// No EBUSY while staging into the temp directory.
expect(stderr).not.toContain("EBUSY");
// The payload is not a real zip, so extraction fails cleanly.
expect(exitCode).toBe(1);
});

it("recreates the staging directory in the temp dir instead of reusing a pre-existing one", async () => {
Expand Down
Loading