/**
* concurrent-same-app — two simultaneous `pgh app install <name>`.
*
* Asserts:
* - Both exit with deterministic codes (both 0, or one 0 + one EBUSY-like).
* - The install dir ends up consistent (matches the latest write).
* - No corrupted refs.json (every hash referenced still exists).
*
* This surfaces B3 from the audit (CLI ↔ autoupdate / CLI ↔ CLI
* coordination). Today's sidecar has the _installInFlight mutex but
* the CLIs talk to the sidecar separately; this test checks the
* end-to-end behavior.
*/
import fs from 'fs'
import path from 'path'
import { publishFixture, pghUrl } from '../../bench/scenarios/_lib.js'
import { runPgh } from '../../bench/runner.js'
import { CLI_PATH } from '../../bench/scenarios/_lib.js'
const fixture = process.env.FIXTURE || 'bench-medium'
export const name = 'concurrent-same-app/' + fixture
export const description = 'two pgh installs of the same app fired simultaneously'
export async function setup (ctx) {
const { lockfile } = await publishFixture(ctx, fixture)
ctx._lockfile = lockfile
ctx.emit('fixture', fixture)
}
export async function run (ctx) {
// Fire both in parallel — no awaits between them.
const url = pghUrl(ctx._lockfile)
const [a, b] = await Promise.all([
runPgh(ctx, ['app', 'install', url, '--name', fixture], { cliPath: CLI_PATH }),
runPgh(ctx, ['app', 'install', url, '--name', fixture], { cliPath: CLI_PATH })
])
ctx.emit('a_exit_code', a.code)
ctx.emit('b_exit_code', b.code)
ctx.emit('a_duration_ms', a.durationMs)
ctx.emit('b_duration_ms', b.durationMs)
// At least one must succeed.
if (a.code !== 0 && b.code !== 0) {
throw new Error('both concurrent installs failed:\n[A]\n' + a.stderr + '\n[B]\n' + b.stderr)
}
// The install dir must exist + be consistent.
const appPath = path.join(ctx.appsDir, fixture)
if (!fs.existsSync(appPath)) {
throw new Error('no app dir after concurrent installs')
}
const ok = fs.existsSync(path.join(appPath, 'package.json')) &&
fs.existsSync(path.join(appPath, 'index.js'))
ctx.emit('app_dir_consistent', ok)
if (!ok) {
throw new Error('app dir is inconsistent (missing core files)')
}
// refs.json points at real trees.
const refsPath = path.join(ctx.storeRoot, 'refs.json')
const depsDir = path.join(ctx.storeRoot, 'dependencies')
if (fs.existsSync(refsPath)) {
const refs = JSON.parse(fs.readFileSync(refsPath, 'utf8'))
const orphans = []
for (const hash of Object.keys(refs)) {
if (!fs.existsSync(path.join(depsDir, hash))) orphans.push(hash)
}
ctx.emit('orphan_refs', orphans.length)
if (orphans.length > 0) {
throw new Error('refs.json corrupted: ' + orphans.length + ' orphans')
}
}
}