also looking at this
Test the built native CLI startup in CI
#317
3 files
+54
-2
Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.
| @@ -57,8 +57,13 @@ jobs: | ||
| 57 | 57 | uses: ./.github/actions/first-party-zig-deps |
| 58 | 58 | |
| 59 | 59 | - name: Build Zig core |
| 60 | working-directory: packages/zig | |
| 61 | run: zig build | |
| 60 | run: bun run build:core | |
| 61 | ||
| 62 | - name: Verify built native CLI startup | |
| 63 | run: | | |
| 64 | bun test scripts/verify-native-startup.test.ts | |
| 65 | VERSION="$(bun -p "require('./package.json').version")" | |
| 66 | bun scripts/verify-native-startup.ts packages/zig/zig-out/bin/craft "$VERSION" | |
| 62 | 67 | |
| 63 | 68 | - name: First-party Zig dependencies |
| 64 | 69 | uses: ./.github/actions/first-party-zig-deps |
| @@ -0,0 +1,27 @@ | ||
| 1 | import { afterEach, expect, test } from 'bun:test' | |
| 2 | import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' | |
| 3 | import { tmpdir } from 'node:os' | |
| 4 | import { join } from 'node:path' | |
| 5 | import { verifyNativeStartup } from './verify-native-startup' | |
| 6 | ||
| 7 | const roots: string[] = [] | |
| 8 | afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }) }) | |
| 9 | ||
| 10 | function executable(body: string): string { | |
| 11 | const root = mkdtempSync(join(tmpdir(), 'craft-native-startup-test-')) | |
| 12 | roots.push(root) | |
| 13 | const path = join(root, 'craft') | |
| 14 | writeFileSync(path, `#!/bin/sh\n${body}\n`) | |
| 15 | chmodSync(path, 0o755) | |
| 16 | return path | |
| 17 | } | |
| 18 | ||
| 19 | test('accepts a running native CLI with the requested version', () => { | |
| 20 | expect(() => verifyNativeStartup(executable("printf 'craft version 1.2.3\\nBuilt with Zig\\n'"), '1.2.3')).not.toThrow() | |
| 21 | }) | |
| 22 | ||
| 23 | test('rejects a stale binary, startup failure, and missing executable', () => { | |
| 24 | expect(() => verifyNativeStartup(executable("printf 'craft version 1.2.2\\n'"), '1.2.3')).toThrow('reported') | |
| 25 | expect(() => verifyNativeStartup(executable('exit 7'), '1.2.3')).toThrow('failed to start') | |
| 26 | expect(() => verifyNativeStartup('/path/that/does/not/exist', '1.2.3')).toThrow() | |
| 27 | }) | |
| @@ -0,0 +1,20 @@ | ||
| 1 | import { resolve } from 'node:path' | |
| 2 | ||
| 3 | export function verifyNativeStartup(binary: string, version: string): void { | |
| 4 | const result = Bun.spawnSync([resolve(binary), '--version'], { | |
| 5 | stdout: 'pipe', stderr: 'pipe', timeout: 30_000, | |
| 6 | }) | |
| 7 | if (result.exitCode !== 0) | |
| 8 | throw new Error(`Native CLI failed to start (${result.signalCode ?? result.exitCode}): ${result.stderr.toString()}`) | |
| 9 | const firstLine = result.stdout.toString().trim().split('\n')[0] | |
| 10 | if (firstLine !== `craft version ${version}`) | |
| 11 | throw new Error(`Native CLI reported ${JSON.stringify(firstLine)}, expected craft version ${version}`) | |
| 12 | console.log(`Verified native CLI startup: craft version ${version}`) | |
| 13 | } | |
| 14 | ||
| 15 | if (import.meta.main) { | |
| 16 | const [binary, version] = Bun.argv.slice(2) | |
| 17 | if (!binary || !version) | |
| 18 | throw new Error('Usage: bun scripts/verify-native-startup.ts <binary> <version>') | |
| 19 | verifyNativeStartup(binary, version) | |
| 20 | } | |