also looking at this
fix(dev): validate the pinned Zig toolchain before builds
#299
5 files
+137
-14
| @@ -0,0 +1,51 @@ | ||
| 1 | import { afterEach, expect, test } from 'bun:test' | |
| 2 | import { chmodSync, mkdtempSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs' | |
| 3 | import { tmpdir } from 'node:os' | |
| 4 | import { join } from 'node:path' | |
| 5 | import { pinnedZigVersion, resolveZig, runZig } from './zig' | |
| 6 | ||
| 7 | const roots: string[] = [] | |
| 8 | afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }) }) | |
| 9 | ||
| 10 | function fixture(version = '0.17.0-dev.1963+e00c6c439') { | |
| 11 | const root = realpathSync(mkdtempSync(join(tmpdir(), 'craft-zig-toolchain-'))) | |
| 12 | roots.push(root) | |
| 13 | mkdirSync(join(root, 'packages/zig'), { recursive: true }) | |
| 14 | writeFileSync(join(root, 'pantry.lock'), JSON.stringify({ workspaces: { '': { system: { 'ziglang.org': '0.17.0-dev.1963_e00c6c439' } } } })) | |
| 15 | writeFileSync(join(root, 'package.json'), JSON.stringify({ version: '1.2.3' })) | |
| 16 | const path = join(root, 'zig') | |
| 17 | writeFileSync(path, `#!${process.execPath}\nif (process.argv[2] === 'version') console.log(${JSON.stringify(version)}); else { await Bun.write(process.env.ZIG_TEST_LOG, JSON.stringify({ cwd: process.cwd(), args: process.argv.slice(2) })); process.exit(23); }\n`) | |
| 18 | chmodSync(path, 0o755) | |
| 19 | return { root, path, env: { ...process.env, CRAFT_ZIG: '', CRAFT_ALLOW_UNPINNED_ZIG: '', PATH: root, ZIG_TEST_LOG: join(root, 'invocation.json') } } | |
| 20 | } | |
| 21 | ||
| 22 | test('accepts only the locked snapshot by default, even when PATH shadows it', () => { | |
| 23 | const f = fixture('0.17.0-dev.2163+89ff10d56') | |
| 24 | expect(pinnedZigVersion(f.root)).toBe('0.17.0-dev.1963+e00c6c439') | |
| 25 | expect(() => resolveZig(f.root, f.env)).toThrow('version mismatch') | |
| 26 | expect(() => resolveZig(f.root, f.env)).toThrow('CRAFT_ALLOW_UNPINNED_ZIG=1') | |
| 27 | const pinned = fixture() | |
| 28 | expect(resolveZig(f.root, { ...f.env, CRAFT_ZIG: pinned.path })).toEqual({ path: pinned.path, version: '0.17.0-dev.1963+e00c6c439', overridden: false }) | |
| 29 | }) | |
| 30 | ||
| 31 | test('requires an explicit opt-in for unpinned snapshots', () => { | |
| 32 | const f = fixture('0.17.0-dev.2163+89ff10d56') | |
| 33 | expect(() => resolveZig(f.root, { ...f.env, CRAFT_ALLOW_UNPINNED_ZIG: 'true' })).toThrow('version mismatch') | |
| 34 | expect(resolveZig(f.root, { ...f.env, CRAFT_ALLOW_UNPINNED_ZIG: '1' }).overridden).toBe(true) | |
| 35 | }) | |
| 36 | ||
| 37 | test('fails early for absent executables, invalid lock versions, and invalid compiler output', () => { | |
| 38 | const f = fixture('not a version') | |
| 39 | expect(() => resolveZig(f.root, { ...f.env, CRAFT_ZIG: join(f.root, 'missing') })).toThrow('was not found') | |
| 40 | expect(() => resolveZig(f.root, f.env)).toThrow('Invalid Zig version') | |
| 41 | writeFileSync(join(f.root, 'pantry.lock'), JSON.stringify({ workspaces: { '': { system: { 'ziglang.org': 'latest' } } } })) | |
| 42 | expect(() => pinnedZigVersion(f.root)).toThrow('exact Zig version') | |
| 43 | }) | |
| 44 | ||
| 45 | test('forwards arguments, core working directory, package version and compiler exit status', () => { | |
| 46 | const f = fixture() | |
| 47 | expect(runZig(f.root, ['--core', '--versioned', 'build', '-Doptimize=ReleaseSafe', '-Dfoo=a b'], f.env)).toBe(23) | |
| 48 | expect(JSON.parse(readFileSync(f.env.ZIG_TEST_LOG, 'utf8'))).toEqual({ cwd: join(f.root, 'packages/zig'), args: ['build', '-Doptimize=ReleaseSafe', '-Dfoo=a b', '-Dversion=1.2.3'] }) | |
| 49 | expect(runZig(f.root, ['fmt', 'src/'], f.env)).toBe(23) | |
| 50 | expect(JSON.parse(readFileSync(f.env.ZIG_TEST_LOG, 'utf8'))).toEqual({ cwd: f.root, args: ['fmt', 'src/'] }) | |
| 51 | }) | |