// Focused test fixtures only; runtime CLIs never import this file. import { mkdtempSync, mkdirSync, writeFileSync, rmSync,readFileSync,readdirSync,readlinkSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; export function fixture(t) { if (!process.env.TMPDIR?.startsWith('/mnt/storage/scratch/tmp/') || process.env.TMPDIR.includes('_')) throw new Error('set TMPDIR with mktemp -d /mnt/storage/scratch/tmp/XXXXXX'); const root=mkdtempSync(join(tmpdir(),'ui797-')); const cleanups=[]; t.after(async()=>{ const owned=ownedProcesses(root); const errors=[]; for(const cleanup of cleanups.reverse()) try { await cleanup(); } catch(e) { errors.push(e); } // Preserve evidence if resource shutdown failed; never delete beneath a running service. const survivors=owned.filter(p=>processAlive(p)); if(survivors.length) errors.push(new Error(`owned processes survived fixture shutdown: ${survivors.map(p=>p.pid)}`)); if(errors.length) throw new AggregateError(errors,'fixture shutdown failed'); rmSync(root,{recursive:true,force:true}); }); const configDir=join(root,'mosaic-ui797'),dataRoot=join(root,'data'); mkdirSync(configDir,{mode:0o700});mkdirSync(dataRoot,{mode:0o700}); const config=join(configDir,'config.json'); writeFileSync(config,JSON.stringify({configVersion:1,environment:'development',dataRoot,execution:{backend:'docker',provider:'zai',model:'glm-5.3-flash'}})); writeFileSync(join(dataRoot,'instance.json'),JSON.stringify({instanceVersion:1,name:'ui797'})); return {root,config,dataRoot,after:fn=>cleanups.push(fn)}; } export async function closeServer(server) { const closed=new Promise((resolve,reject)=>server.close(e=>e?reject(e):resolve())); server.closeAllConnections(); await closed; } export async function closeChild(child) { if(child.exitCode!==null || child.signalCode!==null) return; const closed=new Promise(resolve=>child.once('close',resolve)); child.kill('SIGTERM'); await closed; } // Snapshot only this fixture's processes, including reparented Chromium crash handlers. export function ownedProcesses(root) { const processes=readdirSync('/proc').filter(n=>/^\d+$/.test(n)).flatMap(pid=>{ try { const args=readFileSync(`/proc/${pid}/cmdline`,'utf8').split('\0'); const stat=readFileSync(`/proc/${pid}/stat`,'utf8').split(' '); return [{pid:Number(pid),ppid:Number(stat[3]),start:stat[21],args}]; } catch { return []; } }); const selected=new Set(processes.filter(p=>p.args.some(a=>a===root || a.startsWith(root+'/') || a.startsWith('--user-data-dir='+root+'/'))).map(p=>p.pid)); // Chromium reports PIDs in its own namespace. Never mistake one for a host // PID (which could belong to another seat) when tracking orphan crash handlers. for(const p of processes) if(selected.has(p.pid)) for(const a of p.args) if(a.startsWith('--crashpad-handler-pid=')) { for(const candidate of processes) try { if(readlinkSync(`/proc/${candidate.pid}/ns/pid`)!==readlinkSync(`/proc/${p.pid}/ns/pid`))continue; const ids=readFileSync(`/proc/${candidate.pid}/status`,'utf8').match(/^NSpid:\s+(.+)$/m)?.[1].trim().split(/\s+/); if(Number(ids?.at(-1))===Number(a.split('=')[1]))selected.add(candidate.pid); } catch {} } let size; do {size=selected.size;for(const p of processes) if(selected.has(p.ppid))selected.add(p.pid);} while(size!==selected.size); return processes.filter(p=>selected.has(p.pid)); } export function processAlive(p) { try { const stat=readFileSync(`/proc/${p.pid}/stat`,'utf8').split(' ');return stat[21]===p.start && stat[2]!=='Z'; } catch {return false;} }