Skip to content
Merged
Show file tree
Hide file tree
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
111 changes: 110 additions & 1 deletion cli/remote.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { execFileSync } from "node:child_process";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

Expand Down Expand Up @@ -1857,3 +1857,112 @@ describe("crewAddDeps().gitBundle, against a real repo", () => {
}
});
});

// The bundle route from a SHALLOW lead, which every Herdr-managed install is (ADR 0006): the lead's
// commit reaches the member without its parent, and a bundle carries no shallow marker. The leg runs
// for real here, with real git and a stand-in `bun` that writes the binary the build would.
describe.skipIf(process.platform === "win32")("installScript from a shallow lead, run for real", () => {
const BUILT_VERSION = "9.9.9";

async function shallowLeadFixture() {
const dir = mkdtempSync(join(tmpdir(), "collie-shallow-lead-"));
const fakeBin = join(dir, "fakebin");
mkdirSync(fakeBin);
writeFileSync(
join(fakeBin, "bun"),
`#!/bin/sh\n[ "$*" = "run cli/main.ts build" ] || exit 9\nmkdir -p bin\nprintf '#!/bin/sh\\necho ${BUILT_VERSION}+test\\n' > bin/collie\nchmod +x bin/collie\n`,
);
chmodSync(join(fakeBin, "bun"), 0o755);
const home = join(dir, "home");
mkdirSync(home);
const env = { ...gitEnv(), PATH: `${fakeBin}:${process.env.PATH ?? ""}`, HOME: home };
const git = (cwd: string, ...args: string[]) =>
execFileSync("git", ["-C", cwd, ...args], { env, encoding: "utf8" }).trim();
const src = join(dir, "src");
mkdirSync(src);
git(src, "init", "-q");
git(src, "config", "user.email", "test@example.com");
git(src, "config", "user.name", "Test");
const commit = (n: number) => {
writeFileSync(join(src, "file.txt"), `${n}\n`);
git(src, "add", "file.txt");
git(src, "commit", "-q", "-m", `commit ${n}`);
return git(src, "rev-parse", "HEAD");
};
commit(1);
// A member that took release 1 and has not fetched since: it never saw commit 2.
execFileSync("git", ["clone", "-q", src, join(home, "behind")], { env });
const parent = commit(2);
// A member that already holds the parent of the lead's commit.
execFileSync("git", ["clone", "-q", src, join(home, "current")], { env });
const tip = commit(3);
// The lead, shallow the way `herdr plugin install` leaves it: commit 3 and nothing behind it.
const leadRoot = join(dir, "lead");
execFileSync("git", ["clone", "-q", "--depth", "1", `file://${src}`, leadRoot], { env });
const deps = minimalCrewDeps(leadRoot);
const bundle = await crewAddDeps(deps).gitBundle(tip, deps.io);
if (bundle === null) throw new Error(`gitBundle returned null; stderr: ${deps.io.stderr.join("\n")}`);

/** Run the leg against `root`, and check it ends on the lead's commit with the built binary. */
const install = (root: string) => {
const out = execFileSync("/bin/sh", ["-s"], {
input: composeStdin(installScript({ root, commit: tip, version: BUILT_VERSION }), bundle),
env,
cwd: home,
encoding: "utf8",
});
expect(out).toContain(`collie-install:version=${BUILT_VERSION}+test`);
expect(git(root, "rev-parse", "HEAD")).toBe(tip);
};
const connected = (root: string) => {
try {
git(root, "fsck", "--connectivity-only");
return true;
} catch {
return false;
}
};
const shallowFile = (root: string) => {
const path = join(root, ".git", "shallow");
return existsSync(path) ? readFileSync(path, "utf8") : null;
};
const cleanup = () => rmSync(dir, { recursive: true, force: true });
return { home, git, parent, tip, install, connected, shallowFile, cleanup };
}

test("a member that lacks the parent takes the commit, and records it as a shallow boundary", async () => {
const lead = await shallowLeadFixture();
try {
const root = join(lead.home, "behind");
lead.install(root);
expect(lead.connected(root)).toBe(true);
expect(lead.shallowFile(root)).toBe(`${lead.tip}\n`);
} finally {
lead.cleanup();
}
});

test("a member that has the parent fetches as before, and its history is not cut", async () => {
const lead = await shallowLeadFixture();
try {
const root = join(lead.home, "current");
lead.install(root);
expect(lead.git(root, "rev-parse", "HEAD^")).toBe(lead.parent);
expect(lead.shallowFile(root)).toBeNull();
} finally {
lead.cleanup();
}
});

test("a fresh install from the bundle is a connected checkout", async () => {
const lead = await shallowLeadFixture();
try {
const root = join(lead.home, ".collie");
lead.install(root);
expect(lead.connected(root)).toBe(true);
expect(lead.shallowFile(root)).toBe(`${lead.tip}\n`);
} finally {
lead.cleanup();
}
});
});
23 changes: 22 additions & 1 deletion cli/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,13 @@ export function installScript(opts: {
'VMSG=$("$GIT" -C "$WORK/verify" bundle verify "$WORK/bundle.part" 2>&1 >/dev/null) || { echo "error: the pushed bundle did not verify: $VMSG" >&2; exit 22; }',
'mv "$WORK/bundle.part" "$WORK/bundle"',
'if [ -d "$ROOT/.git" ]; then',
' "$GIT" -C "$ROOT" fetch --no-tags --update-shallow "$WORK/bundle" HEAD',
// A lead on a shallow checkout (every Herdr-managed install, ADR 0006) bundles its commit
// without the parent, and a bundle carries no shallow marker, so this fetch refuses whenever
// the member lacks that parent. Unbundling stores the same objects without the connectivity
// check; the pin is unchanged, because the checkout below still names the lead's commit.
' if ! "$GIT" -C "$ROOT" fetch --no-tags --update-shallow "$WORK/bundle" HEAD 2>"$WORK/fetch.err"; then',
' "$GIT" -C "$ROOT" bundle unbundle "$WORK/bundle" >/dev/null 2>>"$WORK/fetch.err" || { cat "$WORK/fetch.err" >&2; echo "error: the pushed bundle could not be applied to $ROOT" >&2; exit 28; }',
" fi",
' "$GIT" -C "$ROOT" checkout --detach "$COMMIT"',
// Never `mv` a fresh clone onto an existing non-checkout: `mv a b` when `b` is a directory puts
// `a` INSIDE it, which would leave a working Collie at a path nothing else knows about.
Expand All @@ -541,6 +547,21 @@ export function installScript(opts: {
' "$GIT" -C "$ROOT.part" checkout --detach "$COMMIT"',
' mv "$ROOT.part" "$ROOT"',
"fi",
// Either way the commit may now name a parent this checkout does not have. Record the commit as
// a shallow boundary, as `git fetch --depth` would have, or git reads the history as broken
// (`rev-list` and `fsck` fail). A checkout that has the parent is left alone, so a full clone
// keeps its history (ADR 0006). Two costs come with the boundary. A merge that arrives with one
// parent missing is cut as a whole, so the parent the member does hold drops out of view, though
// not off disk. And the checkout now reads as shallow, so `collie update` there fetches with
// `--depth 1` from then on, as it already does on a Herdr-managed checkout. The parents come off
// the commit's own header: `git log` would try to read the very parent that is missing, fail,
// and leave the loop with nothing to check.
`for PARENT in $("$GIT" -C "$ROOT" cat-file commit "$COMMIT" | sed -n '/^$/q; s/^parent //p'); do`,
' if ! "$GIT" -C "$ROOT" cat-file -e "$PARENT^{commit}" 2>/dev/null; then',
' grep -qx "$COMMIT" "$ROOT/.git/shallow" 2>/dev/null || printf \'%s\\n\' "$COMMIT" >> "$ROOT/.git/shallow"',
" break",
" fi",
"done",
'GOT=$("$GIT" -C "$ROOT" rev-parse HEAD)',
'[ "$GOT" = "$COMMIT" ] || { echo "error: checkout is at $GOT, expected $COMMIT" >&2; exit 23; }',
// The shim's bootstrap, verbatim in mechanism: prepend Bun's own directory and build from source.
Expand Down
10 changes: 9 additions & 1 deletion cli/testdata/leg2-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ __COLLIE_PAYLOAD__
VMSG=$("$GIT" -C "$WORK/verify" bundle verify "$WORK/bundle.part" 2>&1 >/dev/null) || { echo "error: the pushed bundle did not verify: $VMSG" >&2; exit 22; }
mv "$WORK/bundle.part" "$WORK/bundle"
if [ -d "$ROOT/.git" ]; then
"$GIT" -C "$ROOT" fetch --no-tags --update-shallow "$WORK/bundle" HEAD
if ! "$GIT" -C "$ROOT" fetch --no-tags --update-shallow "$WORK/bundle" HEAD 2>"$WORK/fetch.err"; then
"$GIT" -C "$ROOT" bundle unbundle "$WORK/bundle" >/dev/null 2>>"$WORK/fetch.err" || { cat "$WORK/fetch.err" >&2; echo "error: the pushed bundle could not be applied to $ROOT" >&2; exit 28; }
fi
"$GIT" -C "$ROOT" checkout --detach "$COMMIT"
elif [ -e "$ROOT" ]; then
echo "error: $ROOT exists and is not a git checkout — move it aside or pass --path" >&2
Expand All @@ -40,6 +42,12 @@ else
"$GIT" -C "$ROOT.part" checkout --detach "$COMMIT"
mv "$ROOT.part" "$ROOT"
fi
for PARENT in $("$GIT" -C "$ROOT" cat-file commit "$COMMIT" | sed -n '/^$/q; s/^parent //p'); do
if ! "$GIT" -C "$ROOT" cat-file -e "$PARENT^{commit}" 2>/dev/null; then
grep -qx "$COMMIT" "$ROOT/.git/shallow" 2>/dev/null || printf '%s\n' "$COMMIT" >> "$ROOT/.git/shallow"
break
fi
done
GOT=$("$GIT" -C "$ROOT" rev-parse HEAD)
[ "$GOT" = "$COMMIT" ] || { echo "error: checkout is at $GOT, expected $COMMIT" >&2; exit 23; }
BUNDIR=$(dirname "$BUN")
Expand Down
8 changes: 5 additions & 3 deletions scripts/check-flake-bun.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,21 +101,23 @@ describe("the Windows check runs the flake's Bun", () => {
expect(String(setup[0]?.with?.["bun-version"])).toBe(flakeBunVersion());
});

test("windows-suites.ps1 gives each suite its own skip budget, 99 in all, each skip named", () => {
test("windows-suites.ps1 gives each suite its own skip budget, 102 in all, each skip named", () => {
const script = readFileSync(join(ROOT, "scripts", "windows-suites.ps1"), "utf8");
const budget = (suite: string): number => Number(new RegExp(`\\[int\\]\\$MaxSkips${suite} = (\\d+)`).exec(script)?.[1] ?? Number.NaN);
expect([budget("Bridge"), budget("Cli"), budget("Scripts")]).toEqual([62, 14, 23]);
expect([budget("Bridge"), budget("Cli"), budget("Scripts")]).toEqual([62, 17, 23]);
// The bridge allowance is the local-cli tests that stay POSIX-only, the one real-engine Whistle
// case that needs the uncommitted model, the 21 durable example tests and the 36 durable socket
// tests; the comment names them all.
const localCli = readFileSync(join(ROOT, "bridge", "stt", "local-cli.test.ts"), "utf8");
const posixOnly = [...localCli.matchAll(/test\.if\(posix\)\("([^"]+)"/g)].map((m) => m[1]!);
expect(posixOnly).toHaveLength(4);
expect(budget("Bridge")).toBe(posixOnly.length + 1 + 21 + 36);
expect(budget("Cli")).toBe(2 + 12);
// The cli allowance adds the three real runs of the POSIX install leg a crew member runs.
expect(budget("Cli")).toBe(2 + 12 + 3);
for (const name of posixOnly) expect(script).toContain(name);
expect(script).toContain("a 13 s WAV becomes text through a real child of this Collie");
expect(script).toContain("a child of this checkout's CLI turns 10.4 s of speech into text");
expect(script).toContain("installScript from a shallow lead, run for real");
expect(script).not.toContain("[int]$MaxSkips = ");
expect(script).toContain("if ($skip -gt $skipBudget[$name])");
});
Expand Down
15 changes: 9 additions & 6 deletions scripts/windows-suites.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# cannot hide behind a removed one in another. A test must not be skipped just to get
# green, so a higher count is a failure. Raise a number only with a reason.
# Bridge went from 2 to 0 when M43 spec 04 checked the access list instead of the mode.
# Today, 99 in all, each one named, with its reason:
# Today, 102 in all, each one named, with its reason:
# bridge 62. 5 are the POSIX-only and engine cases below, 21 are the durable example apps, and
# 36 are the durable socket tests (see "Durable, bridge" below).
# Of the first 5, four are in bridge/stt/local-cli.test.ts. Windows has no executable bit
Expand Down Expand Up @@ -45,19 +45,22 @@
# handed a socket path), asks.test.ts (a socket with nothing listening) and
# http-client.test.ts (the socket client sends no Authorization header), one each; and
# bridge/durable/log.test.ts (a mode 0755 folder put back to 0700).
# cli 14. 12 are every test of the "doctor durable" describe in cli/doctor.test.ts: each reads a
# cli 17. 12 are every test of the "doctor durable" describe in cli/doctor.test.ts: each reads a
# real Unix socket. Its Windows rows are tested in cli/durable-findings.test.ts with a fake
# platform. The other 2:
# platform. The other 5:
# "a per-call override wins over the Exec's own environment (#283) > a child
# killed by a signal says which one" (Windows has no POSIX signals), and
# killed by a signal says which one" (Windows has no POSIX signals),
# "collie _whistle-transcribe - the real engine, end to end > a child of this checkout's CLI turns 10.4 s of speech into text"
# (the same missing model as the bridge case).
# (the same missing model as the bridge case), and the 3 tests of
# "installScript from a shallow lead, run for real" in cli/remote.test.ts: they run the
# install leg, POSIX sh that a crew member runs over ssh, and a Windows machine cannot be a
# crew member (WINDOWS_CREW_SENTENCE in cli/crew.ts).
# scripts 23: every test of scripts/install.test.ts ("scripts/install.sh > ..."):
# install.sh is POSIX sh, and Windows installs with install.ps1, tested beside it.
param(
[Parameter(Mandatory = $true)][string]$Out,
[int]$MaxSkipsBridge = 62,
[int]$MaxSkipsCli = 14,
[int]$MaxSkipsCli = 17,
[int]$MaxSkipsScripts = 23
)
$skipBudget = @{ bridge = $MaxSkipsBridge; cli = $MaxSkipsCli; scripts = $MaxSkipsScripts }
Expand Down
Loading