Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7bbaf954f |
No files matched your search
@@ -158,7 +158,7 @@ one binary and copies it to `~/bin/quak`. It uses bun, a JavaScript runtime, to
|
||||
bundle the built `dist/` into one executable. Besides what Getting Started
|
||||
needs, `make install` needs `make`. It downloads bun from GitHub with `curl` and
|
||||
unpacks it with `unzip`. It checks the download against a SHA-256 hash in the
|
||||
repo. `make install` works on Linux and macOS.
|
||||
repo. `make install` works on Linux and macOS, on x86-64 and arm64.
|
||||
|
||||
On Linux systems that use the musl C library, such as Alpine, `make install`
|
||||
downloads bun's musl build. On Alpine, both that bun and `~/bin/quak` need the
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
//
|
||||
// Each test copies the script into a fresh directory, which the script then
|
||||
// treats as the checkout, and runs it there. Stubs first on PATH stand in for
|
||||
// `uname`, `ldd`, `curl`, `unzip` and `nix-shell`, so no test downloads
|
||||
// anything or runs a real bun.
|
||||
// `uname`, `ldd`, `curl`, `unzip`, `nix-shell` and `shasum`, so no test
|
||||
// downloads anything or runs a real bun.
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { execFileSync, spawnSync } from "node:child_process";
|
||||
import {
|
||||
chmodSync,
|
||||
copyFileSync,
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
symlinkSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
@@ -41,9 +42,10 @@ import { fileURLToPath } from "node:url";
|
||||
const repoRoot = fileURLToPath(new URL("../../", import.meta.url));
|
||||
|
||||
// The stubs, keyed by the command each replaces. `uname` and `ldd` describe the
|
||||
// system that runBuildBin passes in the environment. `curl`, `unzip` and
|
||||
// `nix-shell` write the arguments they were given, one per line, to a file
|
||||
// named after the command in $STUB_LOG_DIR, which also shows that they ran.
|
||||
// system that runBuildBin passes in the environment. `curl`, `unzip`,
|
||||
// `nix-shell` and `shasum` write the arguments they were given, one per line,
|
||||
// to a file named after the command in $STUB_LOG_DIR, which also shows that
|
||||
// they ran.
|
||||
const stubs: Record<string, string> = {
|
||||
uname: `
|
||||
case "$1" in
|
||||
@@ -81,6 +83,11 @@ const stubs: Record<string, string> = {
|
||||
chmod +x "$bun"`,
|
||||
"nix-shell": `
|
||||
printf '%s\\n' "$@" > "$STUB_LOG_DIR/nix-shell"`,
|
||||
// The script checks the hash with `shasum` where there is no `sha256sum`.
|
||||
// The stub fails, as `shasum` does when the hash does not match.
|
||||
shasum: `
|
||||
printf '%s\\n' "$@" > "$STUB_LOG_DIR/shasum"
|
||||
exit 1`,
|
||||
};
|
||||
|
||||
let checkout = "";
|
||||
@@ -113,13 +120,21 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
// Runs the script with nothing in its environment but the stubs' settings and a
|
||||
// PATH that finds the stubs first.
|
||||
const runBuildBin = (system: string, machine: string, libc = "glibc") =>
|
||||
// PATH. Unless the caller passes one, the PATH finds the stubs first and then
|
||||
// the host's commands.
|
||||
const runBuildBin = (
|
||||
system: string,
|
||||
machine: string,
|
||||
{
|
||||
libc = "glibc",
|
||||
path = `${join(checkout, "stubs")}:${process.env.PATH}`,
|
||||
} = {},
|
||||
) =>
|
||||
spawnSync(join(checkout, "script/build-bin"), {
|
||||
cwd: checkout,
|
||||
encoding: "utf-8",
|
||||
env: {
|
||||
PATH: `${join(checkout, "stubs")}:${process.env.PATH}`,
|
||||
PATH: path,
|
||||
STUB_UNAME_S: system,
|
||||
STUB_UNAME_M: machine,
|
||||
STUB_LIBC: libc,
|
||||
@@ -127,6 +142,31 @@ const runBuildBin = (system: string, machine: string, libc = "glibc") =>
|
||||
},
|
||||
});
|
||||
|
||||
// The real commands that the script and the stubs run.
|
||||
const hostCommands = [
|
||||
"basename",
|
||||
"chmod",
|
||||
"dirname",
|
||||
"grep",
|
||||
"mkdir",
|
||||
"mktemp",
|
||||
"rm",
|
||||
];
|
||||
|
||||
// A Mac has no `ldd`, and not every Mac has `sha256sum`. This makes a PATH that
|
||||
// finds neither: the stubs other than `ldd`, then links to hostCommands.
|
||||
const makeMacPath = (): string => {
|
||||
rmSync(join(checkout, "stubs", "ldd"));
|
||||
mkdirSync(join(checkout, "host-commands"));
|
||||
for (const command of hostCommands) {
|
||||
const target = execFileSync("sh", ["-c", `command -v ${command}`], {
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
symlinkSync(target, join(checkout, "host-commands", command));
|
||||
}
|
||||
return `${join(checkout, "stubs")}:${join(checkout, "host-commands")}`;
|
||||
};
|
||||
|
||||
const ran = (command: string): boolean =>
|
||||
existsSync(join(checkout, "log", command));
|
||||
|
||||
@@ -139,19 +179,15 @@ describe("script/build-bin", () => {
|
||||
// The stub curl writes a file that is not the archive, so the hash check
|
||||
// must stop the script before unzip runs. A script without the check would
|
||||
// run unzip, and the stub bun it unpacks would write bin/quak.
|
||||
// On macOS `uname -m` says arm64 on Apple silicon, where bun's archive
|
||||
// says aarch64.
|
||||
it.each([
|
||||
["Linux", "x86_64", "glibc", "bun-linux-x64-baseline.zip"],
|
||||
["Linux", "x86_64", "musl", "bun-linux-x64-musl-baseline.zip"],
|
||||
["Linux", "aarch64", "glibc", "bun-linux-aarch64.zip"],
|
||||
["Linux", "aarch64", "musl", "bun-linux-aarch64-musl.zip"],
|
||||
["Darwin", "x86_64", "glibc", "bun-darwin-x64-baseline.zip"],
|
||||
["Darwin", "arm64", "glibc", "bun-darwin-aarch64.zip"],
|
||||
])(
|
||||
"on %s %s with %s, downloads %s and stops when its hash does not match",
|
||||
(system, machine, libc, archive) => {
|
||||
const result = runBuildBin(system, machine, libc);
|
||||
const result = runBuildBin(system, machine, { libc });
|
||||
expect(argumentsOf("curl")).toContain(
|
||||
"https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/" +
|
||||
archive,
|
||||
@@ -163,6 +199,31 @@ describe("script/build-bin", () => {
|
||||
},
|
||||
);
|
||||
|
||||
// On the PATH from makeMacPath the script finds no `sha256sum` and checks
|
||||
// the hash with the stub `shasum`. That stub fails, so the script must stop
|
||||
// before unzip runs.
|
||||
// On Apple silicon `uname -m` says arm64, where bun's archive says aarch64.
|
||||
it.each([
|
||||
["Darwin", "x86_64", "bun-darwin-x64-baseline.zip"],
|
||||
["Darwin", "arm64", "bun-darwin-aarch64.zip"],
|
||||
])(
|
||||
"on %s %s, downloads %s and stops when shasum rejects its hash",
|
||||
(system, machine, archive) => {
|
||||
const result = runBuildBin(system, machine, {
|
||||
path: makeMacPath(),
|
||||
});
|
||||
expect(argumentsOf("curl")).toContain(
|
||||
"https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/" +
|
||||
archive,
|
||||
);
|
||||
expect(argumentsOf("shasum")).toEqual(["-a", "256", "-c", "-"]);
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(ran("unzip")).toBe(false);
|
||||
expect(ran("nix-shell")).toBe(false);
|
||||
expect(existsSync(join(checkout, "bin/quak"))).toBe(false);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["Linux", "riscv64"],
|
||||
["FreeBSD", "amd64"],
|
||||
|
||||
Reference in new issue
Block a user