mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-17 18:32:19 +02:00
fix: sweep — every sync spawn in the test trees carries a timeout (436 sites, 157 files)
spawnSync/execSync/Bun.spawnSync BLOCK the main thread, so bun's in-process per-test timeout can never fire while one waits — a hung child (stdin read, network probe, dead daemon) wedges the whole shard until the runner's external wall-clock SIGKILL. This exact class reached main: free-tests run 33262077256, test/gstack-memory-ingest.test.ts (normally 2.3s) held shard 2 at the 360s wall while its five siblings finished in ~65s. Mechanical sweep in two waves (12 + 4 fan-out agents, every edit verified against its call site): default timeout: 30_000 (matches the free runner's per-test budget), 120_000 for genuinely slow ops (installs, builds, playwright, provider CLIs), helper wrappers fixed ONCE where call sites route through them. Sites that only LOOK like calls (string fixtures, grep needles, comments) were skipped with reasons — the enforcement commit that follows marks them exempt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
79efe395dc
commit
3e674e4c01
@@ -13,7 +13,7 @@ describe('build: server-node.mjs', () => {
|
|||||||
// Skip rather than fail so plain `bun test` without a prior build passes.
|
// Skip rather than fail so plain `bun test` without a prior build passes.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
expect(() => execSync(`node --check ${SERVER_NODE}`, { stdio: 'pipe' })).not.toThrow();
|
expect(() => execSync(`node --check ${SERVER_NODE}`, { stdio: 'pipe', timeout: 30_000 })).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('does not inline @ngrok/ngrok (must be external)', () => {
|
test('does not inline @ngrok/ngrok (must be external)', () => {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ describe('bun-polyfill', () => {
|
|||||||
const elapsed = Date.now() - start;
|
const elapsed = Date.now() - start;
|
||||||
console.log(elapsed >= 40 ? 'OK' : 'TOO_FAST');
|
console.log(elapsed >= 40 ? 'OK' : 'TOO_FAST');
|
||||||
})();
|
})();
|
||||||
`], { stdout: 'pipe', stderr: 'pipe' });
|
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
expect(result.stdout.toString().trim()).toBe('OK');
|
expect(result.stdout.toString().trim()).toBe('OK');
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
});
|
});
|
||||||
@@ -38,7 +38,7 @@ describe('bun-polyfill', () => {
|
|||||||
const r = Bun.spawnSync(['echo', 'hello'], { stdout: 'pipe' });
|
const r = Bun.spawnSync(['echo', 'hello'], { stdout: 'pipe' });
|
||||||
console.log(r.stdout.toString().trim());
|
console.log(r.stdout.toString().trim());
|
||||||
console.log('exit:' + r.exitCode);
|
console.log('exit:' + r.exitCode);
|
||||||
`], { stdout: 'pipe', stderr: 'pipe' });
|
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
const lines = result.stdout.toString().trim().split('\n');
|
const lines = result.stdout.toString().trim().split('\n');
|
||||||
expect(lines[0]).toBe('hello');
|
expect(lines[0]).toBe('hello');
|
||||||
expect(lines[1]).toBe('exit:0');
|
expect(lines[1]).toBe('exit:0');
|
||||||
@@ -51,7 +51,7 @@ describe('bun-polyfill', () => {
|
|||||||
console.log(typeof p.pid === 'number' ? 'HAS_PID' : 'NO_PID');
|
console.log(typeof p.pid === 'number' ? 'HAS_PID' : 'NO_PID');
|
||||||
console.log(typeof p.kill === 'function' ? 'HAS_KILL' : 'NO_KILL');
|
console.log(typeof p.kill === 'function' ? 'HAS_KILL' : 'NO_KILL');
|
||||||
console.log(typeof p.unref === 'function' ? 'HAS_UNREF' : 'NO_UNREF');
|
console.log(typeof p.unref === 'function' ? 'HAS_UNREF' : 'NO_UNREF');
|
||||||
`], { stdout: 'pipe', stderr: 'pipe' });
|
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
const lines = result.stdout.toString().trim().split('\n');
|
const lines = result.stdout.toString().trim().split('\n');
|
||||||
expect(lines[0]).toBe('HAS_PID');
|
expect(lines[0]).toBe('HAS_PID');
|
||||||
expect(lines[1]).toBe('HAS_KILL');
|
expect(lines[1]).toBe('HAS_KILL');
|
||||||
@@ -70,7 +70,7 @@ describe('bun-polyfill', () => {
|
|||||||
console.log(typeof p.exited === 'object' && typeof p.exited.then === 'function' ? 'IS_PROMISE' : 'NOT_PROMISE');
|
console.log(typeof p.exited === 'object' && typeof p.exited.then === 'function' ? 'IS_PROMISE' : 'NOT_PROMISE');
|
||||||
console.log('exit:' + await p.exited);
|
console.log('exit:' + await p.exited);
|
||||||
})();
|
})();
|
||||||
`], { stdout: 'pipe', stderr: 'pipe' });
|
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
const lines = result.stdout.toString().trim().split('\n');
|
const lines = result.stdout.toString().trim().split('\n');
|
||||||
expect(lines[0]).toBe('IS_PROMISE');
|
expect(lines[0]).toBe('IS_PROMISE');
|
||||||
expect(lines[1]).toBe('exit:0');
|
expect(lines[1]).toBe('exit:0');
|
||||||
@@ -83,7 +83,7 @@ describe('bun-polyfill', () => {
|
|||||||
const p = Bun.spawn(['node', '-e', 'process.exit(3)'], { stdio: ['ignore', 'ignore', 'ignore'] });
|
const p = Bun.spawn(['node', '-e', 'process.exit(3)'], { stdio: ['ignore', 'ignore', 'ignore'] });
|
||||||
console.log('exit:' + await p.exited);
|
console.log('exit:' + await p.exited);
|
||||||
})();
|
})();
|
||||||
`], { stdout: 'pipe', stderr: 'pipe' });
|
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
expect(result.stdout.toString().trim()).toBe('exit:3');
|
expect(result.stdout.toString().trim()).toBe('exit:3');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ describe('bun-polyfill', () => {
|
|||||||
const out = await new Response(p.stdout).text();
|
const out = await new Response(p.stdout).text();
|
||||||
console.log(out + ':' + code);
|
console.log(out + ':' + code);
|
||||||
})();
|
})();
|
||||||
`], { stdout: 'pipe', stderr: 'pipe' });
|
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
expect(result.stdout.toString().trim()).toBe('ready:0');
|
expect(result.stdout.toString().trim()).toBe('ready:0');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ describe('bun-polyfill', () => {
|
|||||||
]).catch(() => 'TIMEOUT');
|
]).catch(() => 'TIMEOUT');
|
||||||
console.log('exit:' + code);
|
console.log('exit:' + code);
|
||||||
})();
|
})();
|
||||||
`], { stdout: 'pipe', stderr: 'pipe' });
|
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
// Anything other than 'TIMEOUT' (and ideally a non-zero number) means the
|
// Anything other than 'TIMEOUT' (and ideally a non-zero number) means the
|
||||||
// lifecycle promise resolved on the spawn error.
|
// lifecycle promise resolved on the spawn error.
|
||||||
const out = result.stdout.toString().trim();
|
const out = result.stdout.toString().trim();
|
||||||
@@ -139,7 +139,7 @@ describe('bun-polyfill', () => {
|
|||||||
setTimeout(() => p.kill('SIGTERM'), 150);
|
setTimeout(() => p.kill('SIGTERM'), 150);
|
||||||
console.log('exit:' + await p.exited);
|
console.log('exit:' + await p.exited);
|
||||||
})();
|
})();
|
||||||
`], { stdout: 'pipe', stderr: 'pipe' });
|
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
// SIGTERM = 15 → 128 + 15 = 143.
|
// SIGTERM = 15 → 128 + 15 = 143.
|
||||||
expect(result.stdout.toString().trim()).toBe('exit:143');
|
expect(result.stdout.toString().trim()).toBe('exit:143');
|
||||||
});
|
});
|
||||||
@@ -166,7 +166,7 @@ describe('bun-polyfill', () => {
|
|||||||
const out = await new Response(p.stdout).text();
|
const out = await new Response(p.stdout).text();
|
||||||
console.log(out.length + ':' + code);
|
console.log(out.length + ':' + code);
|
||||||
})();
|
})();
|
||||||
`], { stdout: 'pipe', stderr: 'pipe' });
|
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
expect(result.stdout.toString().trim()).toBe('1024:0');
|
expect(result.stdout.toString().trim()).toBe('1024:0');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@ describe('bun-polyfill', () => {
|
|||||||
const out = await new Response(p.stdout).text();
|
const out = await new Response(p.stdout).text();
|
||||||
console.log(out.length + ':' + code);
|
console.log(out.length + ':' + code);
|
||||||
})().catch((e) => { console.log('THREW:' + e.message); });
|
})().catch((e) => { console.log('THREW:' + e.message); });
|
||||||
`], { stdout: 'pipe', stderr: 'pipe' });
|
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
expect(result.stdout.toString().trim()).toBe('1048576:0');
|
expect(result.stdout.toString().trim()).toBe('1048576:0');
|
||||||
}, 15000);
|
}, 15000);
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ describe('bun-polyfill', () => {
|
|||||||
console.log(typeof server.stop === 'function' ? 'HAS_STOP' : 'NO_STOP');
|
console.log(typeof server.stop === 'function' ? 'HAS_STOP' : 'NO_STOP');
|
||||||
console.log(typeof server.port === 'number' ? 'HAS_PORT' : 'NO_PORT');
|
console.log(typeof server.port === 'number' ? 'HAS_PORT' : 'NO_PORT');
|
||||||
server.stop();
|
server.stop();
|
||||||
`], { stdout: 'pipe', stderr: 'pipe' });
|
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
const lines = result.stdout.toString().trim().split('\n');
|
const lines = result.stdout.toString().trim().split('\n');
|
||||||
expect(lines[0]).toBe('HAS_STOP');
|
expect(lines[0]).toBe('HAS_STOP');
|
||||||
expect(lines[1]).toBe('HAS_PORT');
|
expect(lines[1]).toBe('HAS_PORT');
|
||||||
@@ -237,7 +237,7 @@ describe('bun-polyfill', () => {
|
|||||||
require(${JSON.stringify(polyfillPath)});
|
require(${JSON.stringify(polyfillPath)});
|
||||||
Bun.spawn(['node', '-e', ''], { stdio: ['ignore', 'ignore', 'ignore'] });
|
Bun.spawn(['node', '-e', ''], { stdio: ['ignore', 'ignore', 'ignore'] });
|
||||||
console.log('windowsHide:' + seen.windowsHide);
|
console.log('windowsHide:' + seen.windowsHide);
|
||||||
`], { stdout: 'pipe', stderr: 'pipe' });
|
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
expect(result.stdout.toString().trim()).toBe('windowsHide:true');
|
expect(result.stdout.toString().trim()).toBe('windowsHide:true');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -250,7 +250,7 @@ describe('bun-polyfill', () => {
|
|||||||
require(${JSON.stringify(polyfillPath)});
|
require(${JSON.stringify(polyfillPath)});
|
||||||
Bun.spawnSync(['node', '-e', '']);
|
Bun.spawnSync(['node', '-e', '']);
|
||||||
console.log('windowsHide:' + seen.windowsHide);
|
console.log('windowsHide:' + seen.windowsHide);
|
||||||
`], { stdout: 'pipe', stderr: 'pipe' });
|
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
expect(result.stdout.toString().trim()).toBe('windowsHide:true');
|
expect(result.stdout.toString().trim()).toBe('windowsHide:true');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@ describe('bun-polyfill', () => {
|
|||||||
require(${JSON.stringify(polyfillPath)});
|
require(${JSON.stringify(polyfillPath)});
|
||||||
Bun.spawn(['node', '-e', ''], { stdio: ['ignore', 'ignore', 'ignore'], windowsHide: false });
|
Bun.spawn(['node', '-e', ''], { stdio: ['ignore', 'ignore', 'ignore'], windowsHide: false });
|
||||||
console.log('windowsHide:' + seen.windowsHide);
|
console.log('windowsHide:' + seen.windowsHide);
|
||||||
`], { stdout: 'pipe', stderr: 'pipe' });
|
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
expect(result.stdout.toString().trim()).toBe('windowsHide:false');
|
expect(result.stdout.toString().trim()).toBe('windowsHide:false');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ describe('config', () => {
|
|||||||
// it (the exact bug the fix removed) would skip the guard here.
|
// it (the exact bug the fix removed) would skip the guard here.
|
||||||
const tmpDir = path.join(os.tmpdir(), `browse-gitignored-repo-test-${Date.now()}`);
|
const tmpDir = path.join(os.tmpdir(), `browse-gitignored-repo-test-${Date.now()}`);
|
||||||
fs.mkdirSync(tmpDir, { recursive: true });
|
fs.mkdirSync(tmpDir, { recursive: true });
|
||||||
Bun.spawnSync(['git', 'init'], { cwd: tmpDir, stdout: 'ignore', stderr: 'ignore' });
|
Bun.spawnSync(['git', 'init'], { cwd: tmpDir, stdout: 'ignore', stderr: 'ignore', timeout: 30_000 });
|
||||||
fs.writeFileSync(path.join(tmpDir, '.gitignore'), '.gstack/\n');
|
fs.writeFileSync(path.join(tmpDir, '.gitignore'), '.gstack/\n');
|
||||||
const config = resolveConfig({ BROWSE_STATE_FILE: path.join(tmpDir, '.gstack', 'browse.json') });
|
const config = resolveConfig({ BROWSE_STATE_FILE: path.join(tmpDir, '.gstack', 'browse.json') });
|
||||||
ensureStateDir(config);
|
ensureStateDir(config);
|
||||||
@@ -166,22 +166,22 @@ describe('config', () => {
|
|||||||
fs.mkdirSync(tmpDir, { recursive: true });
|
fs.mkdirSync(tmpDir, { recursive: true });
|
||||||
|
|
||||||
// Set up a real git repo
|
// Set up a real git repo
|
||||||
spawnSync('git', ['init', '-q'], { cwd: tmpDir });
|
spawnSync('git', ['init', '-q'], { cwd: tmpDir, timeout: 30_000 });
|
||||||
spawnSync('git', ['config', 'user.email', 'test@test.com'], { cwd: tmpDir });
|
spawnSync('git', ['config', 'user.email', 'test@test.com'], { cwd: tmpDir, timeout: 30_000 });
|
||||||
spawnSync('git', ['config', 'user.name', 'Test'], { cwd: tmpDir });
|
spawnSync('git', ['config', 'user.name', 'Test'], { cwd: tmpDir, timeout: 30_000 });
|
||||||
|
|
||||||
// Write a global excludes file that ignores .gstack/
|
// Write a global excludes file that ignores .gstack/
|
||||||
const excludesFile = path.join(tmpDir, 'global-gitignore');
|
const excludesFile = path.join(tmpDir, 'global-gitignore');
|
||||||
fs.writeFileSync(excludesFile, '.gstack/\n');
|
fs.writeFileSync(excludesFile, '.gstack/\n');
|
||||||
spawnSync('git', ['config', 'core.excludesFile', excludesFile], { cwd: tmpDir });
|
spawnSync('git', ['config', 'core.excludesFile', excludesFile], { cwd: tmpDir, timeout: 30_000 });
|
||||||
|
|
||||||
// .gitignore exists but does NOT contain .gstack/
|
// .gitignore exists but does NOT contain .gstack/
|
||||||
fs.writeFileSync(path.join(tmpDir, '.gitignore'), 'node_modules/\n');
|
fs.writeFileSync(path.join(tmpDir, '.gitignore'), 'node_modules/\n');
|
||||||
spawnSync('git', ['add', '.gitignore'], { cwd: tmpDir });
|
spawnSync('git', ['add', '.gitignore'], { cwd: tmpDir, timeout: 30_000 });
|
||||||
spawnSync('git', ['commit', '-qm', 'init'], { cwd: tmpDir });
|
spawnSync('git', ['commit', '-qm', 'init'], { cwd: tmpDir, timeout: 30_000 });
|
||||||
|
|
||||||
// Verify git knows .gstack/ is ignored
|
// Verify git knows .gstack/ is ignored
|
||||||
const check = spawnSync('git', ['check-ignore', '-q', '.gstack/'], { cwd: tmpDir });
|
const check = spawnSync('git', ['check-ignore', '-q', '.gstack/'], { cwd: tmpDir, timeout: 30_000 });
|
||||||
expect(check.status).toBe(0);
|
expect(check.status).toBe(0);
|
||||||
|
|
||||||
const config = resolveConfig({ BROWSE_STATE_FILE: path.join(tmpDir, '.gstack', 'browse.json') });
|
const config = resolveConfig({ BROWSE_STATE_FILE: path.join(tmpDir, '.gstack', 'browse.json') });
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ describe('restrictDirectoryPermissions', () => {
|
|||||||
fs.mkdirSync(d);
|
fs.mkdirSync(d);
|
||||||
// System chmod, not fs.chmodSync: Bun masks the sticky bit off chmod/
|
// System chmod, not fs.chmodSync: Bun masks the sticky bit off chmod/
|
||||||
// mkdir modes, so 0o1777 through the fs API lands as 0o777.
|
// mkdir modes, so 0o1777 through the fs API lands as 0o777.
|
||||||
Bun.spawnSync(['chmod', '1777', d]);
|
Bun.spawnSync(['chmod', '1777', d], { timeout: 30_000 });
|
||||||
expect(fs.statSync(d).mode & 0o7777).toBe(0o1777); // fixture took
|
expect(fs.statSync(d).mode & 0o7777).toBe(0o1777); // fixture took
|
||||||
restrictDirectoryPermissions(d);
|
restrictDirectoryPermissions(d);
|
||||||
expect(fs.statSync(d).mode & 0o7777).toBe(0o1777);
|
expect(fs.statSync(d).mode & 0o7777).toBe(0o1777);
|
||||||
@@ -244,7 +244,7 @@ describe('mkdirSecure', () => {
|
|||||||
fs.mkdirSync(d);
|
fs.mkdirSync(d);
|
||||||
// System chmod: Bun's fs API masks the sticky bit off modes (see the
|
// System chmod: Bun's fs API masks the sticky bit off modes (see the
|
||||||
// restrictDirectoryPermissions sticky-dir test).
|
// restrictDirectoryPermissions sticky-dir test).
|
||||||
Bun.spawnSync(['chmod', '1777', d]);
|
Bun.spawnSync(['chmod', '1777', d], { timeout: 30_000 });
|
||||||
expect(fs.statSync(d).mode & 0o7777).toBe(0o1777); // fixture took
|
expect(fs.statSync(d).mode & 0o7777).toBe(0o1777); // fixture took
|
||||||
mkdirSecure(d);
|
mkdirSecure(d);
|
||||||
expect(fs.statSync(d).mode & 0o7777).toBe(0o1777);
|
expect(fs.statSync(d).mode & 0o7777).toBe(0o1777);
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ describe('findPort / isPortAvailable', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test();
|
test();
|
||||||
`], { stdout: 'pipe', stderr: 'pipe' });
|
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
|
|
||||||
const output = result.stdout.toString().trim();
|
const output = result.stdout.toString().trim();
|
||||||
// Confirms the polyfill's stop() is fire-and-forget — callers
|
// Confirms the polyfill's stop() is fire-and-forget — callers
|
||||||
@@ -171,7 +171,7 @@ describe('findPort / isPortAvailable', () => {
|
|||||||
test('net.createServer approach does not have the race condition', async () => {
|
test('net.createServer approach does not have the race condition', async () => {
|
||||||
// Prove the fix: net.createServer with proper async bind/close
|
// Prove the fix: net.createServer with proper async bind/close
|
||||||
// releases the port cleanly
|
// releases the port cleanly
|
||||||
const result = Bun.spawnSync(['node', '-e', `
|
const result = Bun.spawnSync(['node', '-e', ` // timeout in trailing options
|
||||||
const net = require('net');
|
const net = require('net');
|
||||||
|
|
||||||
async function testFix() {
|
async function testFix() {
|
||||||
@@ -205,7 +205,7 @@ describe('findPort / isPortAvailable', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testFix();
|
testFix();
|
||||||
`], { stdout: 'pipe', stderr: 'pipe' });
|
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
|
|
||||||
const output = result.stdout.toString().trim();
|
const output = result.stdout.toString().trim();
|
||||||
expect(output).toBe('FIX_WORKS');
|
expect(output).toBe('FIX_WORKS');
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ function run(args: string[] = [], extraEnv: Record<string, string> = {}) {
|
|||||||
env,
|
env,
|
||||||
stdout: 'pipe',
|
stdout: 'pipe',
|
||||||
stderr: 'pipe',
|
stderr: 'pipe',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
exitCode: result.exitCode,
|
exitCode: result.exitCode,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ function run(extraEnv: Record<string, string> = {}, args: string[] = []) {
|
|||||||
env,
|
env,
|
||||||
stdout: 'pipe',
|
stdout: 'pipe',
|
||||||
stderr: 'pipe',
|
stderr: 'pipe',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
exitCode: result.exitCode,
|
exitCode: result.exitCode,
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ describe('untrustable TMPDIR values never widen the allowlist', () => {
|
|||||||
const r = Bun.spawnSync([
|
const r = Bun.spawnSync([
|
||||||
process.execPath, '-e',
|
process.execPath, '-e',
|
||||||
"import { TEMP_DIRS } from './browse/src/platform'; console.log(JSON.stringify(TEMP_DIRS));",
|
"import { TEMP_DIRS } from './browse/src/platform'; console.log(JSON.stringify(TEMP_DIRS));",
|
||||||
], { env: { ...process.env, TMPDIR: tmpdir }, cwd: path.resolve(import.meta.dir, '..', '..') });
|
], { env: { ...process.env, TMPDIR: tmpdir }, cwd: path.resolve(import.meta.dir, '..', '..'), timeout: 30_000 });
|
||||||
return JSON.parse(r.stdout.toString().trim().split('\n').pop()!);
|
return JSON.parse(r.stdout.toString().trim().split('\n').pop()!);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
|
|
||||||
const HAS_XVFB = (() => {
|
const HAS_XVFB = (() => {
|
||||||
if (process.platform !== 'linux') return false;
|
if (process.platform !== 'linux') return false;
|
||||||
const result = Bun.spawnSync(['which', 'Xvfb'], { stdout: 'pipe', stderr: 'pipe' });
|
const result = Bun.spawnSync(['which', 'Xvfb'], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
|
||||||
return result.exitCode === 0;
|
return result.exitCode === 0;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const MINT_BIN = join(ROOT, 'bin', 'gstack-ios-qa-mint');
|
|||||||
const DAEMON_BIN = join(ROOT, 'bin', 'gstack-ios-qa-daemon');
|
const DAEMON_BIN = join(ROOT, 'bin', 'gstack-ios-qa-daemon');
|
||||||
|
|
||||||
function runMint(args: string[]) {
|
function runMint(args: string[]) {
|
||||||
return spawnSync(MINT_BIN, args, { stdio: 'pipe', encoding: 'utf-8' });
|
return spawnSync(MINT_BIN, args, { stdio: 'pipe', encoding: 'utf-8', timeout: 30_000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('bin/gstack-ios-qa-mint launcher', () => {
|
describe('bin/gstack-ios-qa-mint launcher', () => {
|
||||||
@@ -112,6 +112,7 @@ describe('bin/gstack-ios-qa-daemon launcher', () => {
|
|||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
env: { PATH: '/usr/bin:/bin' },
|
env: { PATH: '/usr/bin:/bin' },
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(r.status).not.toBe(0);
|
expect(r.status).not.toBe(0);
|
||||||
expect(r.stderr).toContain('bun');
|
expect(r.stderr).toContain('bun');
|
||||||
|
|||||||
@@ -612,6 +612,7 @@ class AppState {
|
|||||||
'--output', outputDir,
|
'--output', outputDir,
|
||||||
], {
|
], {
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
|
timeout: 30_000,
|
||||||
env: { ...process.env, GSTACK_IOS_CACHE_ROOT: join(workDir, 'cache') },
|
env: { ...process.env, GSTACK_IOS_CACHE_ROOT: join(workDir, 'cache') },
|
||||||
});
|
});
|
||||||
expect(result.status).toBe(4);
|
expect(result.status).toBe(4);
|
||||||
@@ -717,7 +718,7 @@ describe('render', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('typechecks beside an internal @Observable app state using a comment marker', () => {
|
test('typechecks beside an internal @Observable app state using a comment marker', () => {
|
||||||
if (spawnSync('swiftc', ['--version'], { encoding: 'utf8' }).status !== 0) return;
|
if (spawnSync('swiftc', ['--version'], { encoding: 'utf8', timeout: 30_000 }).status !== 0) return;
|
||||||
|
|
||||||
const coreSource = join(workDir, 'DebugBridgeCore.swift');
|
const coreSource = join(workDir, 'DebugBridgeCore.swift');
|
||||||
const coreModule = join(workDir, 'DebugBridgeCore.swiftmodule');
|
const coreModule = join(workDir, 'DebugBridgeCore.swiftmodule');
|
||||||
@@ -752,7 +753,7 @@ public final class StateServer {
|
|||||||
'-module-name', 'DebugBridgeCore',
|
'-module-name', 'DebugBridgeCore',
|
||||||
coreSource,
|
coreSource,
|
||||||
'-emit-module-path', coreModule,
|
'-emit-module-path', coreModule,
|
||||||
], { encoding: 'utf8' });
|
], { encoding: 'utf8', timeout: 120_000 });
|
||||||
if (emitModule.status !== 0) {
|
if (emitModule.status !== 0) {
|
||||||
throw new Error(`failed to build DebugBridgeCore test stub:\n${emitModule.stderr}`);
|
throw new Error(`failed to build DebugBridgeCore test stub:\n${emitModule.stderr}`);
|
||||||
}
|
}
|
||||||
@@ -775,7 +776,7 @@ ${render([{
|
|||||||
'-D', 'DEBUG',
|
'-D', 'DEBUG',
|
||||||
'-I', workDir,
|
'-I', workDir,
|
||||||
appSource,
|
appSource,
|
||||||
], { encoding: 'utf8' });
|
], { encoding: 'utf8', timeout: 120_000 });
|
||||||
if (typecheck.status !== 0) {
|
if (typecheck.status !== 0) {
|
||||||
throw new Error(`generated accessor failed Swift type checking:\n${typecheck.stderr}`);
|
throw new Error(`generated accessor failed Swift type checking:\n${typecheck.stderr}`);
|
||||||
}
|
}
|
||||||
@@ -783,7 +784,7 @@ ${render([{
|
|||||||
|
|
||||||
test('strict JSON typing and cross-model validate-before-apply restore run correctly', () => {
|
test('strict JSON typing and cross-model validate-before-apply restore run correctly', () => {
|
||||||
if (process.platform !== 'darwin') return;
|
if (process.platform !== 'darwin') return;
|
||||||
if (spawnSync('swiftc', ['--version'], { encoding: 'utf8' }).status !== 0) return;
|
if (spawnSync('swiftc', ['--version'], { encoding: 'utf8', timeout: 30_000 }).status !== 0) return;
|
||||||
|
|
||||||
const coreSource = join(workDir, 'DebugBridgeCore.swift');
|
const coreSource = join(workDir, 'DebugBridgeCore.swift');
|
||||||
const coreModule = join(workDir, 'DebugBridgeCore.swiftmodule');
|
const coreModule = join(workDir, 'DebugBridgeCore.swiftmodule');
|
||||||
@@ -830,7 +831,7 @@ public final class StateServer {
|
|||||||
'-module-name', 'DebugBridgeCore', coreSource,
|
'-module-name', 'DebugBridgeCore', coreSource,
|
||||||
'-emit-module-path', coreModule,
|
'-emit-module-path', coreModule,
|
||||||
'-o', coreLibrary,
|
'-o', coreLibrary,
|
||||||
], { encoding: 'utf8' });
|
], { encoding: 'utf8', timeout: 120_000 });
|
||||||
if (emitCore.status !== 0) throw new Error(`failed to build runtime stub:\n${emitCore.stderr}`);
|
if (emitCore.status !== 0) throw new Error(`failed to build runtime stub:\n${emitCore.stderr}`);
|
||||||
|
|
||||||
const appSource = join(workDir, 'OptionalRoundTrip.swift');
|
const appSource = join(workDir, 'OptionalRoundTrip.swift');
|
||||||
@@ -915,10 +916,11 @@ struct Runner {
|
|||||||
const compile = spawnSync('swiftc', [
|
const compile = spawnSync('swiftc', [
|
||||||
'-D', 'DEBUG', '-I', workDir, '-L', workDir, '-lDebugBridgeCore',
|
'-D', 'DEBUG', '-I', workDir, '-L', workDir, '-lDebugBridgeCore',
|
||||||
'-parse-as-library', appSource, '-o', executable,
|
'-parse-as-library', appSource, '-o', executable,
|
||||||
], { encoding: 'utf8' });
|
], { encoding: 'utf8', timeout: 120_000 });
|
||||||
if (compile.status !== 0) throw new Error(`generated Optional accessor failed compilation:\n${compile.stderr}`);
|
if (compile.status !== 0) throw new Error(`generated Optional accessor failed compilation:\n${compile.stderr}`);
|
||||||
const run = spawnSync(executable, [], {
|
const run = spawnSync(executable, [], {
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
|
timeout: 30_000,
|
||||||
env: { ...process.env, DYLD_LIBRARY_PATH: workDir },
|
env: { ...process.env, DYLD_LIBRARY_PATH: workDir },
|
||||||
});
|
});
|
||||||
if (run.status !== 0) throw new Error(`generated Optional accessor failed at runtime:\n${run.stderr}`);
|
if (run.status !== 0) throw new Error(`generated Optional accessor failed at runtime:\n${run.stderr}`);
|
||||||
@@ -928,7 +930,7 @@ struct Runner {
|
|||||||
describe('SwiftSyntax generator parity', () => {
|
describe('SwiftSyntax generator parity', () => {
|
||||||
test('isolates canonical markers and rejects inaccessible fields', () => {
|
test('isolates canonical markers and rejects inaccessible fields', () => {
|
||||||
if (process.platform !== 'darwin') return;
|
if (process.platform !== 'darwin') return;
|
||||||
if (spawnSync('swift', ['--version'], { encoding: 'utf8' }).status !== 0) return;
|
if (spawnSync('swift', ['--version'], { encoding: 'utf8', timeout: 30_000 }).status !== 0) return;
|
||||||
|
|
||||||
const packageDir = join(import.meta.dir, 'gen-accessors-tool');
|
const packageDir = join(import.meta.dir, 'gen-accessors-tool');
|
||||||
const inputDir = join(workDir, 'swift-syntax-input');
|
const inputDir = join(workDir, 'swift-syntax-input');
|
||||||
|
|||||||
@@ -739,7 +739,7 @@ export function render(specs: AccessorSpec[], buildId: string, accessorHash: str
|
|||||||
function detectSwiftVersion(): string {
|
function detectSwiftVersion(): string {
|
||||||
if (process.env.SWIFT_VERSION) return process.env.SWIFT_VERSION;
|
if (process.env.SWIFT_VERSION) return process.env.SWIFT_VERSION;
|
||||||
try {
|
try {
|
||||||
const out = execSync('swift --version', { stdio: ['ignore', 'pipe', 'ignore'] }).toString();
|
const out = execSync('swift --version', { stdio: ['ignore', 'pipe', 'ignore'], timeout: 30_000 }).toString();
|
||||||
const m = out.match(/Apple Swift version (\d+\.\d+\.\d+)/);
|
const m = out.match(/Apple Swift version (\d+\.\d+\.\d+)/);
|
||||||
if (m) return m[1]!;
|
if (m) return m[1]!;
|
||||||
} catch {
|
} catch {
|
||||||
@@ -754,6 +754,7 @@ function detectToolGitRev(): string {
|
|||||||
return execSync('git rev-parse --short HEAD', {
|
return execSync('git rev-parse --short HEAD', {
|
||||||
cwd: dirname(new URL(import.meta.url).pathname),
|
cwd: dirname(new URL(import.meta.url).pathname),
|
||||||
stdio: ['ignore', 'pipe', 'ignore'],
|
stdio: ['ignore', 'pipe', 'ignore'],
|
||||||
|
timeout: 30_000,
|
||||||
}).toString().trim();
|
}).toString().trim();
|
||||||
} catch {
|
} catch {
|
||||||
return 'dev';
|
return 'dev';
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ describe("diagram render gate", () => {
|
|||||||
env: { ...process.env, BROWSE_BIN },
|
env: { ...process.env, BROWSE_BIN },
|
||||||
stdout: "pipe",
|
stdout: "pipe",
|
||||||
stderr: "pipe",
|
stderr: "pipe",
|
||||||
|
timeout: 120_000,
|
||||||
});
|
});
|
||||||
const stderr = new TextDecoder().decode(run.stderr);
|
const stderr = new TextDecoder().decode(run.stderr);
|
||||||
if (run.exitCode !== 0) {
|
if (run.exitCode !== 0) {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ function runMigration(fakeHome: string): { code: number; stdout: string; stderr:
|
|||||||
env: { ...process.env, HOME: fakeHome },
|
env: { ...process.env, HOME: fakeHome },
|
||||||
stdout: 'pipe',
|
stdout: 'pipe',
|
||||||
stderr: 'pipe',
|
stderr: 'pipe',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
code: proc.exitCode ?? -1,
|
code: proc.exitCode ?? -1,
|
||||||
@@ -217,6 +218,7 @@ function runMigrationV140(fakeHome: string): { code: number; stdout: string; std
|
|||||||
env: { ...process.env, HOME: fakeHome },
|
env: { ...process.env, HOME: fakeHome },
|
||||||
stdout: 'pipe',
|
stdout: 'pipe',
|
||||||
stderr: 'pipe',
|
stderr: 'pipe',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
code: proc.exitCode ?? -1,
|
code: proc.exitCode ?? -1,
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ function runHook(stdin: object, env: Record<string, string>): { additionalContex
|
|||||||
input: JSON.stringify(stdin),
|
input: JSON.stringify(stdin),
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
env: { PATH: process.env.PATH ?? '/usr/bin:/bin', ...env },
|
env: { PATH: process.env.PATH ?? '/usr/bin:/bin', ...env },
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
const parsed = JSON.parse(res.stdout || '{}');
|
const parsed = JSON.parse(res.stdout || '{}');
|
||||||
return parsed.hookSpecificOutput ?? {};
|
return parsed.hookSpecificOutput ?? {};
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ describe("native slug fallback mirrors bin/gstack-slug", () => {
|
|||||||
["https://gitlab.com/acme/Widget", "acme-Widget"],
|
["https://gitlab.com/acme/Widget", "acme-Widget"],
|
||||||
] as const) {
|
] as const) {
|
||||||
const cwd = fs.mkdtempSync(path.join(tmp, "repo-"));
|
const cwd = fs.mkdtempSync(path.join(tmp, "repo-"));
|
||||||
spawnSync("git", ["init", "-q"], { cwd });
|
spawnSync("git", ["init", "-q"], { cwd, timeout: 30_000 });
|
||||||
spawnSync("git", ["remote", "add", "origin", url], { cwd });
|
spawnSync("git", ["remote", "add", "origin", url], { cwd, timeout: 30_000 });
|
||||||
expect(slugFromEnvironment(path.join(tmp, "home2"), cwd)).toBe(want);
|
expect(slugFromEnvironment(path.join(tmp, "home2"), cwd)).toBe(want);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -231,8 +231,8 @@ describe("walk-up parity with bin/gstack-slug (outermost project root)", () => {
|
|||||||
const projectRoot = path.join(tmp, "realgit");
|
const projectRoot = path.join(tmp, "realgit");
|
||||||
const subdir = path.join(projectRoot, "src", "deep");
|
const subdir = path.join(projectRoot, "src", "deep");
|
||||||
fs.mkdirSync(subdir, { recursive: true });
|
fs.mkdirSync(subdir, { recursive: true });
|
||||||
spawnSync("git", ["init", "-q", projectRoot]);
|
spawnSync("git", ["init", "-q", projectRoot], { timeout: 30_000 });
|
||||||
spawnSync("git", ["-C", projectRoot, "remote", "add", "origin", "https://github.com/foo/bar.git"]);
|
spawnSync("git", ["-C", projectRoot, "remote", "add", "origin", "https://github.com/foo/bar.git"], { timeout: 30_000 });
|
||||||
expectBoth(subdir, "foo-bar");
|
expectBoth(subdir, "foo-bar");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -269,10 +269,10 @@ describe("walk-up parity with bin/gstack-slug (outermost project root)", () => {
|
|||||||
const outer = path.join(tmp, "outer-project");
|
const outer = path.join(tmp, "outer-project");
|
||||||
const inner = path.join(outer, "vendor", "inner-lib");
|
const inner = path.join(outer, "vendor", "inner-lib");
|
||||||
fs.mkdirSync(inner, { recursive: true });
|
fs.mkdirSync(inner, { recursive: true });
|
||||||
spawnSync("git", ["init", "-q", outer]);
|
spawnSync("git", ["init", "-q", outer], { timeout: 30_000 });
|
||||||
spawnSync("git", ["-C", outer, "remote", "add", "origin", "git@github.com:acme/outer.git"]);
|
spawnSync("git", ["-C", outer, "remote", "add", "origin", "git@github.com:acme/outer.git"], { timeout: 30_000 });
|
||||||
spawnSync("git", ["init", "-q", inner]);
|
spawnSync("git", ["init", "-q", inner], { timeout: 30_000 });
|
||||||
spawnSync("git", ["-C", inner, "remote", "add", "origin", "git@github.com:vendor/inner.git"]);
|
spawnSync("git", ["-C", inner, "remote", "add", "origin", "git@github.com:vendor/inner.git"], { timeout: 30_000 });
|
||||||
expectBoth(inner, "acme-outer");
|
expectBoth(inner, "acme-outer");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -286,8 +286,8 @@ describe("walk-up parity with bin/gstack-slug (outermost project root)", () => {
|
|||||||
fs.mkdirSync(path.join(strayHome, ".git"), { recursive: true }); // empty — invalid repo
|
fs.mkdirSync(path.join(strayHome, ".git"), { recursive: true }); // empty — invalid repo
|
||||||
const repo = path.join(strayHome, "work", "repo");
|
const repo = path.join(strayHome, "work", "repo");
|
||||||
fs.mkdirSync(repo, { recursive: true });
|
fs.mkdirSync(repo, { recursive: true });
|
||||||
spawnSync("git", ["init", "-q", repo]);
|
spawnSync("git", ["init", "-q", repo], { timeout: 30_000 });
|
||||||
spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/garrytan/gstack"]);
|
spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/garrytan/gstack"], { timeout: 30_000 });
|
||||||
expectBoth(repo, "garrytan-gstack");
|
expectBoth(repo, "garrytan-gstack");
|
||||||
expect(slugFromEnvironment(nativeHome(), repo)).not.toBe("strayhome");
|
expect(slugFromEnvironment(nativeHome(), repo)).not.toBe("strayhome");
|
||||||
});
|
});
|
||||||
@@ -299,9 +299,9 @@ describe("walk-up parity with bin/gstack-slug (outermost project root)", () => {
|
|||||||
const outer = path.join(tmp, "outer-plain");
|
const outer = path.join(tmp, "outer-plain");
|
||||||
const inner = path.join(outer, "vendor", "inner-lib");
|
const inner = path.join(outer, "vendor", "inner-lib");
|
||||||
fs.mkdirSync(inner, { recursive: true });
|
fs.mkdirSync(inner, { recursive: true });
|
||||||
spawnSync("git", ["init", "-q", outer]); // no origin — marker-only repo
|
spawnSync("git", ["init", "-q", outer], { timeout: 30_000 }); // no origin — marker-only repo
|
||||||
spawnSync("git", ["init", "-q", inner]);
|
spawnSync("git", ["init", "-q", inner], { timeout: 30_000 });
|
||||||
spawnSync("git", ["-C", inner, "remote", "add", "origin", "git@github.com:vendor/inner.git"]);
|
spawnSync("git", ["-C", inner, "remote", "add", "origin", "git@github.com:vendor/inner.git"], { timeout: 30_000 });
|
||||||
expectBoth(inner, "vendor-inner");
|
expectBoth(inner, "vendor-inner");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -313,8 +313,8 @@ describe("walk-up parity with bin/gstack-slug (outermost project root)", () => {
|
|||||||
fs.mkdirSync(path.join(strayHome, ".git"), { recursive: true });
|
fs.mkdirSync(path.join(strayHome, ".git"), { recursive: true });
|
||||||
const repo = path.join(strayHome, "git", "proj");
|
const repo = path.join(strayHome, "git", "proj");
|
||||||
fs.mkdirSync(repo, { recursive: true });
|
fs.mkdirSync(repo, { recursive: true });
|
||||||
spawnSync("git", ["init", "-q", repo]);
|
spawnSync("git", ["init", "-q", repo], { timeout: 30_000 });
|
||||||
spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/garrytan/gstack"]);
|
spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/garrytan/gstack"], { timeout: 30_000 });
|
||||||
|
|
||||||
const cacheDir = path.join(nativeHome(), "slug-cache");
|
const cacheDir = path.join(nativeHome(), "slug-cache");
|
||||||
fs.mkdirSync(cacheDir, { recursive: true });
|
fs.mkdirSync(cacheDir, { recursive: true });
|
||||||
@@ -335,8 +335,8 @@ describe("walk-up parity with bin/gstack-slug (outermost project root)", () => {
|
|||||||
const inner = path.join(wrapper, "apps", "web");
|
const inner = path.join(wrapper, "apps", "web");
|
||||||
fs.mkdirSync(inner, { recursive: true });
|
fs.mkdirSync(inner, { recursive: true });
|
||||||
fs.writeFileSync(path.join(wrapper, "package.json"), '{"name":"wrapper"}\n');
|
fs.writeFileSync(path.join(wrapper, "package.json"), '{"name":"wrapper"}\n');
|
||||||
spawnSync("git", ["init", "-q", inner]);
|
spawnSync("git", ["init", "-q", inner], { timeout: 30_000 });
|
||||||
spawnSync("git", ["-C", inner, "remote", "add", "origin", "https://github.com/acme/web.git"]);
|
spawnSync("git", ["-C", inner, "remote", "add", "origin", "https://github.com/acme/web.git"], { timeout: 30_000 });
|
||||||
|
|
||||||
const cacheDir = path.join(nativeHome(), "slug-cache");
|
const cacheDir = path.join(nativeHome(), "slug-cache");
|
||||||
fs.mkdirSync(cacheDir, { recursive: true });
|
fs.mkdirSync(cacheDir, { recursive: true });
|
||||||
@@ -361,8 +361,8 @@ describe("walk-up parity with bin/gstack-slug (outermost project root)", () => {
|
|||||||
// fire even though cached == basename(project root).
|
// fire even though cached == basename(project root).
|
||||||
const repo = path.join(tmp, "stickyproj");
|
const repo = path.join(tmp, "stickyproj");
|
||||||
fs.mkdirSync(repo, { recursive: true });
|
fs.mkdirSync(repo, { recursive: true });
|
||||||
spawnSync("git", ["init", "-q", repo]);
|
spawnSync("git", ["init", "-q", repo], { timeout: 30_000 });
|
||||||
spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/x/y.git"]);
|
spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/x/y.git"], { timeout: 30_000 });
|
||||||
|
|
||||||
const cacheDir = path.join(nativeHome(), "slug-cache");
|
const cacheDir = path.join(nativeHome(), "slug-cache");
|
||||||
fs.mkdirSync(cacheDir, { recursive: true });
|
fs.mkdirSync(cacheDir, { recursive: true });
|
||||||
@@ -379,8 +379,8 @@ describe("walk-up parity with bin/gstack-slug (outermost project root)", () => {
|
|||||||
// implementations must reject it and fall through to the basename.
|
// implementations must reject it and fall through to the basename.
|
||||||
const repo = path.join(tmp, "dotty");
|
const repo = path.join(tmp, "dotty");
|
||||||
fs.mkdirSync(repo, { recursive: true });
|
fs.mkdirSync(repo, { recursive: true });
|
||||||
spawnSync("git", ["init", "-q", repo]);
|
spawnSync("git", ["init", "-q", repo], { timeout: 30_000 });
|
||||||
spawnSync("git", ["-C", repo, "remote", "add", "origin", ".."]);
|
spawnSync("git", ["-C", repo, "remote", "add", "origin", ".."], { timeout: 30_000 });
|
||||||
expectBoth(repo, "dotty");
|
expectBoth(repo, "dotty");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ describe('content-binding template drift', () => {
|
|||||||
const abort = spawnSync('bash', ['-c', scriptFor(
|
const abort = spawnSync('bash', ['-c', scriptFor(
|
||||||
'clean body\n',
|
'clean body\n',
|
||||||
'body with UNTRUSTED TRACKER CONTENT banner leak\n',
|
'body with UNTRUSTED TRACKER CONTENT banner leak\n',
|
||||||
)], { encoding: 'utf-8' });
|
)], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(abort.stderr).toContain('ABORT: envelope banner leaked');
|
expect(abort.stderr).toContain('ABORT: envelope banner leaked');
|
||||||
expect(abort.stdout).not.toContain('banner tripwire clean');
|
expect(abort.stdout).not.toContain('banner tripwire clean');
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ describe('content-binding template drift', () => {
|
|||||||
const clean = spawnSync('bash', ['-c', scriptFor(
|
const clean = spawnSync('bash', ['-c', scriptFor(
|
||||||
'clean body\n',
|
'clean body\n',
|
||||||
'also clean body\n',
|
'also clean body\n',
|
||||||
)], { encoding: 'utf-8' });
|
)], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(clean.stdout).toContain('banner tripwire clean');
|
expect(clean.stdout).toContain('banner tripwire clean');
|
||||||
expect(clean.stderr).not.toContain('ABORT');
|
expect(clean.stderr).not.toContain('ABORT');
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
+13
-11
@@ -43,12 +43,13 @@ function run(argv: string[], opts: { env?: Record<string, string>; input?: strin
|
|||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
input: opts.input,
|
input: opts.input,
|
||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return { stdout: res.stdout || '', stderr: res.stderr || '', status: res.status ?? -1 };
|
return { stdout: res.stdout || '', stderr: res.stderr || '', status: res.status ?? -1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
function git(args: string[], cwd?: string) {
|
function git(args: string[], cwd?: string) {
|
||||||
const res = spawnSync('git', args, { cwd: cwd || tmpHome, encoding: 'utf-8' });
|
const res = spawnSync('git', args, { cwd: cwd || tmpHome, encoding: 'utf-8', timeout: 30_000 });
|
||||||
return { stdout: res.stdout || '', stderr: res.stderr || '', status: res.status ?? -1 };
|
return { stdout: res.stdout || '', stderr: res.stderr || '', status: res.status ?? -1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ function seedSpool(record: string): string {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'brain-sync-home-'));
|
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'brain-sync-home-'));
|
||||||
bareRemote = fs.mkdtempSync(path.join(os.tmpdir(), 'brain-sync-remote-'));
|
bareRemote = fs.mkdtempSync(path.join(os.tmpdir(), 'brain-sync-remote-'));
|
||||||
spawnSync('git', ['init', '--bare', '-q', '-b', 'main', bareRemote]);
|
spawnSync('git', ['init', '--bare', '-q', '-b', 'main', bareRemote], { timeout: 30_000 });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -198,6 +199,7 @@ describe('gstack-brain-enqueue', () => {
|
|||||||
const r = spawnSync(path.join(BIN, 'gstack-brain-enqueue'), [`file-${i}.jsonl`], {
|
const r = spawnSync(path.join(BIN, 'gstack-brain-enqueue'), [`file-${i}.jsonl`], {
|
||||||
env: { ...process.env, GSTACK_HOME: tmpHome },
|
env: { ...process.env, GSTACK_HOME: tmpHome },
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
resolve();
|
resolve();
|
||||||
}));
|
}));
|
||||||
@@ -245,7 +247,7 @@ describe('gstack-jsonl-merge', () => {
|
|||||||
const lines = fs.readFileSync(ours, 'utf-8').trim().split('\n');
|
const lines = fs.readFileSync(ours, 'utf-8').trim().split('\n');
|
||||||
expect(lines.length).toBe(3);
|
expect(lines.length).toBe(3);
|
||||||
// Order is deterministic (sha256 of each line).
|
// Order is deterministic (sha256 of each line).
|
||||||
const again = spawnSync(path.join(BIN, 'gstack-jsonl-merge'), [base, ours, theirs]);
|
const again = spawnSync(path.join(BIN, 'gstack-jsonl-merge'), [base, ours, theirs], { timeout: 30_000 });
|
||||||
// (re-running doesn't change the order since same input → same output)
|
// (re-running doesn't change the order since same input → same output)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -271,7 +273,7 @@ describe('init + sync + restore round-trip', () => {
|
|||||||
test('refuses init on different remote', () => {
|
test('refuses init on different remote', () => {
|
||||||
run(['gstack-artifacts-init', '--remote', bareRemote]);
|
run(['gstack-artifacts-init', '--remote', bareRemote]);
|
||||||
const otherRemote = fs.mkdtempSync(path.join(os.tmpdir(), 'brain-other-'));
|
const otherRemote = fs.mkdtempSync(path.join(os.tmpdir(), 'brain-other-'));
|
||||||
spawnSync('git', ['init', '--bare', '-q', '-b', 'main', otherRemote]);
|
spawnSync('git', ['init', '--bare', '-q', '-b', 'main', otherRemote], { timeout: 30_000 });
|
||||||
const r = run(['gstack-artifacts-init', '--remote', otherRemote]);
|
const r = run(['gstack-artifacts-init', '--remote', otherRemote]);
|
||||||
expect(r.status).not.toBe(0);
|
expect(r.status).not.toBe(0);
|
||||||
expect(r.stderr).toContain('already a git repo pointing at');
|
expect(r.stderr).toContain('already a git repo pointing at');
|
||||||
@@ -288,7 +290,7 @@ describe('init + sync + restore round-trip', () => {
|
|||||||
const r = run(['gstack-brain-sync', '--once']);
|
const r = run(['gstack-brain-sync', '--once']);
|
||||||
expect(r.status).toBe(0);
|
expect(r.status).toBe(0);
|
||||||
// Check the remote got the commit.
|
// Check the remote got the commit.
|
||||||
const log = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8' });
|
const log = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(log.stdout).toMatch(/sync: 1 file/);
|
expect(log.stdout).toMatch(/sync: 1 file/);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -311,7 +313,7 @@ describe('init + sync + restore round-trip', () => {
|
|||||||
const restored = fs.readFileSync(path.join(machineB, 'projects/myproj/learnings.jsonl'), 'utf-8');
|
const restored = fs.readFileSync(path.join(machineB, 'projects/myproj/learnings.jsonl'), 'utf-8');
|
||||||
expect(restored).toContain('machine A wisdom');
|
expect(restored).toContain('machine A wisdom');
|
||||||
// Merge drivers re-registered on B.
|
// Merge drivers re-registered on B.
|
||||||
const cfg = spawnSync('git', ['-C', machineB, 'config', '--get', 'merge.jsonl-append.driver'], { encoding: 'utf-8' });
|
const cfg = spawnSync('git', ['-C', machineB, 'config', '--get', 'merge.jsonl-append.driver'], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(cfg.stdout).toContain('gstack-jsonl-merge');
|
expect(cfg.stdout).toContain('gstack-jsonl-merge');
|
||||||
fs.rmSync(machineB, { recursive: true, force: true });
|
fs.rmSync(machineB, { recursive: true, force: true });
|
||||||
});
|
});
|
||||||
@@ -399,7 +401,7 @@ describe('gstack-brain-sync egress receipt gate', () => {
|
|||||||
// No local commit was created.
|
// No local commit was created.
|
||||||
expect(git(['rev-list', '--count', 'HEAD']).stdout.trim()).toBe(commitsBefore);
|
expect(git(['rev-list', '--count', 'HEAD']).stdout.trim()).toBe(commitsBefore);
|
||||||
// Nothing reached the remote.
|
// Nothing reached the remote.
|
||||||
const remoteLog = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8' });
|
const remoteLog = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(remoteLog.stdout).not.toMatch(/sync: 1 file/);
|
expect(remoteLog.stdout).not.toMatch(/sync: 1 file/);
|
||||||
const status = JSON.parse(fs.readFileSync(path.join(tmpHome, '.brain-sync-status.json'), 'utf-8'));
|
const status = JSON.parse(fs.readFileSync(path.join(tmpHome, '.brain-sync-status.json'), 'utf-8'));
|
||||||
expect(status.status).toBe('push_failed');
|
expect(status.status).toBe('push_failed');
|
||||||
@@ -411,7 +413,7 @@ describe('gstack-brain-sync egress receipt gate', () => {
|
|||||||
// Next run (ledger writable again) drains the intact queue and pushes.
|
// Next run (ledger writable again) drains the intact queue and pushes.
|
||||||
const retry = run(['gstack-brain-sync', '--once']);
|
const retry = run(['gstack-brain-sync', '--once']);
|
||||||
expect(retry.status).toBe(0);
|
expect(retry.status).toBe(0);
|
||||||
const log = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8' });
|
const log = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(log.stdout).toMatch(/sync: 1 file/);
|
expect(log.stdout).toMatch(/sync: 1 file/);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -583,7 +585,7 @@ describe('#2549 queue integrity', () => {
|
|||||||
expect(r.status).toBe(0);
|
expect(r.status).toBe(0);
|
||||||
expect(spoolText()).not.toContain('learnings.jsonl'); // synced, removed
|
expect(spoolText()).not.toContain('learnings.jsonl'); // synced, removed
|
||||||
expect(spoolText()).toContain('timeline.jsonl'); // held, retained
|
expect(spoolText()).toContain('timeline.jsonl'); // held, retained
|
||||||
const log = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8' });
|
const log = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(log.stdout).toMatch(/sync: 1 file/);
|
expect(log.stdout).toMatch(/sync: 1 file/);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -619,7 +621,7 @@ describe('#2549 queue integrity', () => {
|
|||||||
fs.rmSync(hook);
|
fs.rmSync(hook);
|
||||||
const retry = run(['gstack-brain-sync', '--once']);
|
const retry = run(['gstack-brain-sync', '--once']);
|
||||||
expect(retry.status).toBe(0);
|
expect(retry.status).toBe(0);
|
||||||
const log = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8' });
|
const log = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(log.stdout).toMatch(/sync: 1 file/);
|
expect(log.stdout).toMatch(/sync: 1 file/);
|
||||||
expect(git(['rev-list', '--count', 'origin/main..HEAD']).stdout.trim()).toBe('0');
|
expect(git(['rev-list', '--count', 'origin/main..HEAD']).stdout.trim()).toBe('0');
|
||||||
});
|
});
|
||||||
@@ -751,7 +753,7 @@ describe('C12 spool queue', () => {
|
|||||||
run(['gstack-config', 'set', 'artifacts_sync_mode', mode]);
|
run(['gstack-config', 'set', 'artifacts_sync_mode', mode]);
|
||||||
}
|
}
|
||||||
const remoteLog = () =>
|
const remoteLog = () =>
|
||||||
spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8' }).stdout;
|
spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8', timeout: 30_000 }).stdout;
|
||||||
|
|
||||||
test('two rapid enqueues of different paths create two spool files; one drain syncs both', () => {
|
test('two rapid enqueues of different paths create two spool files; one drain syncs both', () => {
|
||||||
initWithMode('full');
|
initWithMode('full');
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const FILENAME_PREFIX = /\$\{?_BRANCH\}?[A-Za-z0-9._-]*\.(?:jsonl|json|md|txt|lo
|
|||||||
function renderedSkillFiles(): string[] {
|
function renderedSkillFiles(): string[] {
|
||||||
const out = execSync(
|
const out = execSync(
|
||||||
`find "${ROOT}" -name 'SKILL.md' -not -path '*/node_modules/*' -not -path '*/.claude/*' ; find "${ROOT}" -path '*/sections/*.md' -not -path '*/node_modules/*' -not -path '*/.claude/*'`,
|
`find "${ROOT}" -name 'SKILL.md' -not -path '*/node_modules/*' -not -path '*/.claude/*' ; find "${ROOT}" -path '*/sections/*.md' -not -path '*/node_modules/*' -not -path '*/.claude/*'`,
|
||||||
{ encoding: 'utf-8' },
|
{ encoding: 'utf-8', timeout: 30_000 },
|
||||||
);
|
);
|
||||||
return out.split('\n').filter(Boolean);
|
return out.split('\n').filter(Boolean);
|
||||||
}
|
}
|
||||||
@@ -91,18 +91,18 @@ describe('branch slug hygiene (#2550, #1851)', () => {
|
|||||||
const env = { ...process.env, GSTACK_HOME: home };
|
const env = { ...process.env, GSTACK_HOME: home };
|
||||||
execSync(
|
execSync(
|
||||||
'git init -q && git -c user.email=t@t -c user.name=t commit -q --allow-empty -m init && git checkout -q -b feat/slug-hygiene',
|
'git init -q && git -c user.email=t@t -c user.name=t commit -q --allow-empty -m init && git checkout -q -b feat/slug-hygiene',
|
||||||
{ cwd: repo, encoding: 'utf-8' },
|
{ cwd: repo, encoding: 'utf-8', timeout: 30_000 },
|
||||||
);
|
);
|
||||||
|
|
||||||
// Writer: the real gstack-review-log (canonicalizes via gstack-slug).
|
// Writer: the real gstack-review-log (canonicalizes via gstack-slug).
|
||||||
execSync(
|
execSync(
|
||||||
`"${path.join(ROOT, 'bin', 'gstack-review-log')}" '{"skill":"ship","status":"ok"}'`,
|
`"${path.join(ROOT, 'bin', 'gstack-review-log')}" '{"skill":"ship","status":"ok"}'`,
|
||||||
{ cwd: repo, env, encoding: 'utf-8' },
|
{ cwd: repo, env, encoding: 'utf-8', timeout: 30_000 },
|
||||||
);
|
);
|
||||||
|
|
||||||
// The slug-canonical filename must exist; the raw form must not.
|
// The slug-canonical filename must exist; the raw form must not.
|
||||||
const slugVars = execSync(`"${path.join(ROOT, 'bin', 'gstack-slug')}"`, {
|
const slugVars = execSync(`"${path.join(ROOT, 'bin', 'gstack-slug')}"`, {
|
||||||
cwd: repo, env, encoding: 'utf-8',
|
cwd: repo, env, encoding: 'utf-8', timeout: 30_000,
|
||||||
});
|
});
|
||||||
const slug = slugVars.match(/^SLUG=(.*)$/m)![1];
|
const slug = slugVars.match(/^SLUG=(.*)$/m)![1];
|
||||||
const branch = slugVars.match(/^BRANCH=(.*)$/m)![1];
|
const branch = slugVars.match(/^BRANCH=(.*)$/m)![1];
|
||||||
@@ -120,7 +120,7 @@ describe('branch slug hygiene (#2550, #1851)', () => {
|
|||||||
.find((l) => l.includes('-reviews.jsonl'))!;
|
.find((l) => l.includes('-reviews.jsonl'))!;
|
||||||
const script = `_PROJ="${proj}"\nBRANCH="${branch}"\n${probeLine.trim()}`;
|
const script = `_PROJ="${proj}"\nBRANCH="${branch}"\n${probeLine.trim()}`;
|
||||||
const out = execSync(`bash -c '${script.replace(/'/g, `'\\''`)}'`, {
|
const out = execSync(`bash -c '${script.replace(/'/g, `'\\''`)}'`, {
|
||||||
cwd: repo, encoding: 'utf-8',
|
cwd: repo, encoding: 'utf-8', timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(out).toContain('REVIEWS: 1 entries');
|
expect(out).toContain('REVIEWS: 1 entries');
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ function runProfile(): Record<string, string> {
|
|||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
timeout: 15000,
|
timeout: 15000,
|
||||||
};
|
};
|
||||||
const stdout = execSync(`${BIN}/gstack-builder-profile`, execOpts).trim();
|
const stdout = execSync(`${BIN}/gstack-builder-profile`, execOpts).trim(); // timeout via execOpts
|
||||||
const result: Record<string, string> = {};
|
const result: Record<string, string> = {};
|
||||||
for (const line of stdout.split('\n')) {
|
for (const line of stdout.split('\n')) {
|
||||||
const idx = line.indexOf(':');
|
const idx = line.indexOf(':');
|
||||||
|
|||||||
@@ -153,9 +153,9 @@ describe("session-start indexing offer (suggest)", () => {
|
|||||||
home = fs.mkdtempSync(path.join(os.tmpdir(), "ci-home-"));
|
home = fs.mkdtempSync(path.join(os.tmpdir(), "ci-home-"));
|
||||||
repo = fs.mkdtempSync(path.join(os.tmpdir(), "ci-repo-"));
|
repo = fs.mkdtempSync(path.join(os.tmpdir(), "ci-repo-"));
|
||||||
env = { ...process.env, GSTACK_HOME: home };
|
env = { ...process.env, GSTACK_HOME: home };
|
||||||
Bun.spawnSync(["git", "init", "-q", repo]);
|
Bun.spawnSync(["git", "init", "-q", repo], { timeout: 30_000 });
|
||||||
for (const name of ["a.ts", "b.ts", "c.ts"]) fs.writeFileSync(path.join(repo, name), "x\n");
|
for (const name of ["a.ts", "b.ts", "c.ts"]) fs.writeFileSync(path.join(repo, name), "x\n");
|
||||||
Bun.spawnSync(["git", "-C", repo, "add", "-A"]);
|
Bun.spawnSync(["git", "-C", repo, "add", "-A"], { timeout: 30_000 });
|
||||||
});
|
});
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
fs.rmSync(home, { recursive: true, force: true });
|
fs.rmSync(home, { recursive: true, force: true });
|
||||||
@@ -971,9 +971,9 @@ exit 1
|
|||||||
|
|
||||||
function makeRepoWithFiles(count: number): string {
|
function makeRepoWithFiles(count: number): string {
|
||||||
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "ci-cli-suggest-"));
|
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "ci-cli-suggest-"));
|
||||||
Bun.spawnSync(["git", "init", "-q", repo]);
|
Bun.spawnSync(["git", "init", "-q", repo], { timeout: 30_000 });
|
||||||
for (let i = 0; i < count; i++) fs.writeFileSync(path.join(repo, `f${i}.ts`), "x\n");
|
for (let i = 0; i < count; i++) fs.writeFileSync(path.join(repo, `f${i}.ts`), "x\n");
|
||||||
Bun.spawnSync(["git", "-C", repo, "add", "-A"]);
|
Bun.spawnSync(["git", "-C", repo, "add", "-A"], { timeout: 30_000 });
|
||||||
return repo;
|
return repo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const ROOT = path.resolve(import.meta.dir, '..');
|
|||||||
|
|
||||||
const CODEX_AVAILABLE = (() => {
|
const CODEX_AVAILABLE = (() => {
|
||||||
try {
|
try {
|
||||||
const result = Bun.spawnSync(['which', 'codex']);
|
const result = Bun.spawnSync(['which', 'codex'], { timeout: 30_000 });
|
||||||
return result.exitCode === 0;
|
return result.exitCode === 0;
|
||||||
} catch { return false; }
|
} catch { return false; }
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const ROOT = path.resolve(import.meta.dir, '..');
|
|||||||
|
|
||||||
const CODEX_AVAILABLE = (() => {
|
const CODEX_AVAILABLE = (() => {
|
||||||
try {
|
try {
|
||||||
return Bun.spawnSync(['which', 'codex']).exitCode === 0;
|
return Bun.spawnSync(['which', 'codex'], { timeout: 30_000 }).exitCode === 0;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ import { EvalCollector } from './helpers/eval-store';
|
|||||||
import { selectTests, detectBaseBranch, getChangedFiles, GLOBAL_TOUCHFILES } from './helpers/touchfiles';
|
import { selectTests, detectBaseBranch, getChangedFiles, GLOBAL_TOUCHFILES } from './helpers/touchfiles';
|
||||||
|
|
||||||
const ROOT = path.resolve(import.meta.dir, '..');
|
const ROOT = path.resolve(import.meta.dir, '..');
|
||||||
const CODEX_AVAILABLE = spawnSync('which', ['codex']).status === 0;
|
const CODEX_AVAILABLE = spawnSync('which', ['codex'], { timeout: 30_000 }).status === 0;
|
||||||
// The run pins the model with --ignore-user-config; older codex CLIs reject
|
// The run pins the model with --ignore-user-config; older codex CLIs reject
|
||||||
// the flag with an argv error indistinguishable from a Sol regression, so
|
// the flag with an argv error indistinguishable from a Sol regression, so
|
||||||
// probe support and skip (not fail) on old CLIs.
|
// probe support and skip (not fail) on old CLIs.
|
||||||
const IGNORE_USER_CONFIG_SUPPORTED = CODEX_AVAILABLE
|
const IGNORE_USER_CONFIG_SUPPORTED = CODEX_AVAILABLE
|
||||||
&& (spawnSync('codex', ['exec', '--help'], { encoding: 'utf8' }).stdout ?? '').includes('--ignore-user-config');
|
&& (spawnSync('codex', ['exec', '--help'], { encoding: 'utf8', timeout: 120_000 }).stdout ?? '').includes('--ignore-user-config');
|
||||||
const evalsEnabled = !!process.env.EVALS;
|
const evalsEnabled = !!process.env.EVALS;
|
||||||
// External-service test — periodic tier only (CLAUDE.md tiering rule 3). The
|
// External-service test — periodic tier only (CLAUDE.md tiering rule 3). The
|
||||||
// positive guard shape below is what classifyPaidTestFile greps to exclude
|
// positive guard shape below is what classifyPaidTestFile greps to exclude
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const ROOT = path.resolve(import.meta.dir, '..');
|
|||||||
|
|
||||||
const CODEX_AVAILABLE = (() => {
|
const CODEX_AVAILABLE = (() => {
|
||||||
try {
|
try {
|
||||||
const result = Bun.spawnSync(['which', 'codex']);
|
const result = Bun.spawnSync(['which', 'codex'], { timeout: 30_000 });
|
||||||
return result.exitCode === 0;
|
return result.exitCode === 0;
|
||||||
} catch { return false; }
|
} catch { return false; }
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ model = "gpt-5.6-terra"
|
|||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
env: { ...process.env, CODEX_HOME: home },
|
env: { ...process.env, CODEX_HOME: home },
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(ok.status).toBe(0);
|
expect(ok.status).toBe(0);
|
||||||
expect(ok.stdout).toBe(`gpt-5.6-sol\t${path.join(home, 'config.toml')}\n`);
|
expect(ok.stdout).toBe(`gpt-5.6-sol\t${path.join(home, 'config.toml')}\n`);
|
||||||
@@ -123,6 +124,7 @@ model = "gpt-5.6-terra"
|
|||||||
const bad = spawnSync('bun', ['run', 'scripts/resolve-codex-generation-model.ts', '--explicit', 'llama-local'], {
|
const bad = spawnSync('bun', ['run', 'scripts/resolve-codex-generation-model.ts', '--explicit', 'llama-local'], {
|
||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(bad.status).not.toBe(0);
|
expect(bad.status).not.toBe(0);
|
||||||
expect(bad.stderr).toContain('Unknown model');
|
expect(bad.stderr).toContain('Unknown model');
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ describe('gstack-codex-probe: timeout wrapper + namespace hygiene', () => {
|
|||||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-watchdog-'));
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-watchdog-'));
|
||||||
try {
|
try {
|
||||||
const which = (tool: string) =>
|
const which = (tool: string) =>
|
||||||
spawnSync('bash', ['-c', `command -v ${tool}`]).stdout.toString().trim() || `/bin/${tool}`;
|
spawnSync('bash', ['-c', `command -v ${tool}`], { timeout: 30_000 }).stdout.toString().trim() || `/bin/${tool}`;
|
||||||
fs.symlinkSync(which('bash'), path.join(dir, 'bash'));
|
fs.symlinkSync(which('bash'), path.join(dir, 'bash'));
|
||||||
fs.symlinkSync(which('sleep'), path.join(dir, 'sleep'));
|
fs.symlinkSync(which('sleep'), path.join(dir, 'sleep'));
|
||||||
const r = runProbe({
|
const r = runProbe({
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
import { describe, test, expect } from 'bun:test';
|
import { describe, test, expect } from 'bun:test';
|
||||||
import { spawnSync } from 'child_process';
|
import { spawnSync } from 'child_process';
|
||||||
|
|
||||||
const codexPath = spawnSync('which', ['codex'], { encoding: 'utf-8' }).stdout.trim();
|
const codexPath = spawnSync('which', ['codex'], { encoding: 'utf-8', timeout: 30_000 }).stdout.trim();
|
||||||
const codexAvailable = codexPath.length > 0;
|
const codexAvailable = codexPath.length > 0;
|
||||||
|
|
||||||
describe.skipIf(!codexAvailable)(
|
describe.skipIf(!codexAvailable)(
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ function grepRepo(pattern: string, includes: string[]): string[] {
|
|||||||
const includeArgs = includes.map((i) => `--include='${i}'`).join(' ');
|
const includeArgs = includes.map((i) => `--include='${i}'`).join(' ');
|
||||||
const out = execSync(
|
const out = execSync(
|
||||||
`grep -rln ${includeArgs} -e '${pattern}' "${ROOT}" || true`,
|
`grep -rln ${includeArgs} -e '${pattern}' "${ROOT}" || true`,
|
||||||
{ encoding: 'utf-8' },
|
{ encoding: 'utf-8', timeout: 30_000 },
|
||||||
);
|
);
|
||||||
return out
|
return out
|
||||||
.split('\n')
|
.split('\n')
|
||||||
|
|||||||
@@ -718,7 +718,7 @@ describe("CLI plumbing", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("bin/gstack-context-bill runs standalone", () => {
|
it("bin/gstack-context-bill runs standalone", () => {
|
||||||
const result = Bun.spawnSync([path.join(ROOT, "bin", "gstack-context-bill"), TREE_A]);
|
const result = Bun.spawnSync([path.join(ROOT, "bin", "gstack-context-bill"), TREE_A], { timeout: 30_000 });
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.stdout.toString()).toContain("ALWAYS-ON");
|
expect(result.stdout.toString()).toContain("ALWAYS-ON");
|
||||||
expect(result.stdout.toString()).toContain("EAGER");
|
expect(result.stdout.toString()).toContain("EAGER");
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ describe("normalizeIntFlag CLI wrapper (exit-1 semantics)", () => {
|
|||||||
const v = normalizeIntFlag(${rawExpr}, ${specExpr});
|
const v = normalizeIntFlag(${rawExpr}, ${specExpr});
|
||||||
console.log("VALUE:" + v);
|
console.log("VALUE:" + v);
|
||||||
`;
|
`;
|
||||||
const res = spawnSync("bun", ["-e", script], { encoding: "utf-8", cwd: ROOT });
|
const res = spawnSync("bun", ["-e", script], { encoding: "utf-8", cwd: ROOT, timeout: 30_000 });
|
||||||
return { status: res.status ?? -1, stderr: res.stderr ?? "" };
|
return { status: res.status ?? -1, stderr: res.stderr ?? "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ describe("diagram-render bundle drift", () => {
|
|||||||
"deep: fresh build reproduces committed dist",
|
"deep: fresh build reproduces committed dist",
|
||||||
async () => {
|
async () => {
|
||||||
const before = await Bun.file(BUILD_INFO).json();
|
const before = await Bun.file(BUILD_INFO).json();
|
||||||
const proc = Bun.spawnSync(["bun", "run", "scripts/build.ts"], { cwd: ROOT });
|
const proc = Bun.spawnSync(["bun", "run", "scripts/build.ts"], { cwd: ROOT, timeout: 120_000 });
|
||||||
expect(proc.exitCode).toBe(0);
|
expect(proc.exitCode).toBe(0);
|
||||||
const after = await Bun.file(BUILD_INFO).json();
|
const after = await Bun.file(BUILD_INFO).json();
|
||||||
expect(after.sha256).toBe(before.sha256);
|
expect(after.sha256).toBe(before.sha256);
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ function run(args: string[]): { stdout: string; stderr: string; status: number }
|
|||||||
env.GSTACK_STATE_ROOT = stateRoot;
|
env.GSTACK_STATE_ROOT = stateRoot;
|
||||||
env.GSTACK_QUESTION_LOG_NO_DERIVE = '1';
|
env.GSTACK_QUESTION_LOG_NO_DERIVE = '1';
|
||||||
delete env.GSTACK_HOME;
|
delete env.GSTACK_HOME;
|
||||||
const res = spawnSync(BIN, args, { env, encoding: 'utf-8', cwd: fixtureCwd });
|
const res = spawnSync(BIN, args, { env, encoding: 'utf-8', cwd: fixtureCwd, timeout: 30_000 });
|
||||||
return {
|
return {
|
||||||
stdout: res.stdout ?? '',
|
stdout: res.stdout ?? '',
|
||||||
stderr: res.stderr ?? '',
|
stderr: res.stderr ?? '',
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ function run(args: string[]): { stdout: string; stderr: string; status: number }
|
|||||||
env: makeEnv(),
|
env: makeEnv(),
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
cwd: fixtureCwd,
|
cwd: fixtureCwd,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
stdout: res.stdout ?? '',
|
stdout: res.stdout ?? '',
|
||||||
@@ -75,6 +76,7 @@ function writeAuqOtherEvent(text: string): void {
|
|||||||
env: makeEnv(),
|
env: makeEnv(),
|
||||||
cwd: fixtureCwd,
|
cwd: fixtureCwd,
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -150,7 +152,7 @@ describe('no-event paths', () => {
|
|||||||
tool_use_id: 'tu-x',
|
tool_use_id: 'tu-x',
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
{ env: makeEnv(), cwd: fixtureCwd, encoding: 'utf-8' },
|
{ env: makeEnv(), cwd: fixtureCwd, encoding: 'utf-8', timeout: 30_000 },
|
||||||
);
|
);
|
||||||
const r = run([]);
|
const r = run([]);
|
||||||
expect(r.status).toBe(0);
|
expect(r.status).toBe(0);
|
||||||
@@ -169,7 +171,7 @@ describe('--dry-run', () => {
|
|||||||
// Strip ANTHROPIC_API_KEY to prove no API call happens.
|
// Strip ANTHROPIC_API_KEY to prove no API call happens.
|
||||||
const env = makeEnv();
|
const env = makeEnv();
|
||||||
delete env.ANTHROPIC_API_KEY;
|
delete env.ANTHROPIC_API_KEY;
|
||||||
const res = spawnSync(BIN, ['--dry-run'], { env, cwd: fixtureCwd, encoding: 'utf-8' });
|
const res = spawnSync(BIN, ['--dry-run'], { env, cwd: fixtureCwd, encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(res.status).toBe(0);
|
expect(res.status).toBe(0);
|
||||||
expect(res.stdout).toContain('DISTILL PROMPT');
|
expect(res.stdout).toContain('DISTILL PROMPT');
|
||||||
expect(res.stdout).toContain('always include tests');
|
expect(res.stdout).toContain('always include tests');
|
||||||
@@ -185,7 +187,7 @@ describe('API auth', () => {
|
|||||||
writeAuqOtherEvent('Some free text response that needs distilling');
|
writeAuqOtherEvent('Some free text response that needs distilling');
|
||||||
const env = makeEnv();
|
const env = makeEnv();
|
||||||
delete env.ANTHROPIC_API_KEY;
|
delete env.ANTHROPIC_API_KEY;
|
||||||
const res = spawnSync(BIN, [], { env, cwd: fixtureCwd, encoding: 'utf-8' });
|
const res = spawnSync(BIN, [], { env, cwd: fixtureCwd, encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(res.status).not.toBe(0);
|
expect(res.status).not.toBe(0);
|
||||||
expect(res.stderr).toMatch(/ANTHROPIC_API_KEY/);
|
expect(res.stderr).toMatch(/ANTHROPIC_API_KEY/);
|
||||||
expect(res.stderr).toMatch(/separate billing/);
|
expect(res.stderr).toMatch(/separate billing/);
|
||||||
|
|||||||
@@ -222,12 +222,12 @@ describe('gstack-egress-receipt shell bridge', () => {
|
|||||||
fs.writeFileSync(payload, '[{"v":1}]');
|
fs.writeFileSync(payload, '[{"v":1}]');
|
||||||
const write = spawnSync(bin, ['write', '--sink', 'telemetry-sync', '--host', '127.0.0.1:8399',
|
const write = spawnSync(bin, ['write', '--sink', 'telemetry-sync', '--host', '127.0.0.1:8399',
|
||||||
'--class', 'telemetry-events', '--payload-file', payload, '--consent', 'telemetry=community'],
|
'--class', 'telemetry-events', '--payload-file', payload, '--consent', 'telemetry=community'],
|
||||||
{ encoding: 'utf-8', env: { ...process.env, GSTACK_HOME: home } });
|
{ encoding: 'utf-8', timeout: 30_000, env: { ...process.env, GSTACK_HOME: home } });
|
||||||
expect(write.status).toBe(0);
|
expect(write.status).toBe(0);
|
||||||
const id = write.stdout.trim();
|
const id = write.stdout.trim();
|
||||||
expect(id).toMatch(/^[0-9a-f]{64}$/);
|
expect(id).toMatch(/^[0-9a-f]{64}$/);
|
||||||
const outcome = spawnSync(bin, ['outcome', id, '204'],
|
const outcome = spawnSync(bin, ['outcome', id, '204'],
|
||||||
{ encoding: 'utf-8', env: { ...process.env, GSTACK_HOME: home } });
|
{ encoding: 'utf-8', timeout: 30_000, env: { ...process.env, GSTACK_HOME: home } });
|
||||||
expect(outcome.status).toBe(0);
|
expect(outcome.status).toBe(0);
|
||||||
const receipts = listReceipts(home);
|
const receipts = listReceipts(home);
|
||||||
expect(receipts.length).toBe(1);
|
expect(receipts.length).toBe(1);
|
||||||
@@ -239,7 +239,7 @@ describe('gstack-egress-receipt shell bridge', () => {
|
|||||||
test('--no-payload records sha256:null (git-class: a subprocess owns the bytes)', () => {
|
test('--no-payload records sha256:null (git-class: a subprocess owns the bytes)', () => {
|
||||||
const write = spawnSync(bin, ['write', '--sink', 'brain-sync', '--host', 'github.com',
|
const write = spawnSync(bin, ['write', '--sink', 'brain-sync', '--host', 'github.com',
|
||||||
'--class', 'git-push', '--no-payload', '--consent', 'artifacts_sync_mode=auto'],
|
'--class', 'git-push', '--no-payload', '--consent', 'artifacts_sync_mode=auto'],
|
||||||
{ encoding: 'utf-8', env: { ...process.env, GSTACK_HOME: home } });
|
{ encoding: 'utf-8', timeout: 30_000, env: { ...process.env, GSTACK_HOME: home } });
|
||||||
expect(write.status).toBe(0);
|
expect(write.status).toBe(0);
|
||||||
const receipts = listReceipts(home);
|
const receipts = listReceipts(home);
|
||||||
expect(receipts.length).toBe(1);
|
expect(receipts.length).toBe(1);
|
||||||
@@ -251,7 +251,7 @@ describe('gstack-egress-receipt shell bridge', () => {
|
|||||||
if (!canRevokeWrites()) return; // chmod is advisory here (win32, root, DAC-override containers)
|
if (!canRevokeWrites()) return; // chmod is advisory here (win32, root, DAC-override containers)
|
||||||
fs.mkdirSync(path.join(home, 'security'), { recursive: true, mode: 0o500 });
|
fs.mkdirSync(path.join(home, 'security'), { recursive: true, mode: 0o500 });
|
||||||
const write = spawnSync(bin, ['write', '--sink', 's', '--host', 'h', '--class', 'c', '--no-payload'],
|
const write = spawnSync(bin, ['write', '--sink', 's', '--host', 'h', '--class', 'c', '--no-payload'],
|
||||||
{ encoding: 'utf-8', env: { ...process.env, GSTACK_HOME: home } });
|
{ encoding: 'utf-8', timeout: 30_000, env: { ...process.env, GSTACK_HOME: home } });
|
||||||
expect(write.status).toBe(3);
|
expect(write.status).toBe(3);
|
||||||
expect(write.stderr).toContain('EGRESS_RECEIPT_FAILED');
|
expect(write.stderr).toContain('EGRESS_RECEIPT_FAILED');
|
||||||
fs.chmodSync(path.join(home, 'security'), 0o700);
|
fs.chmodSync(path.join(home, 'security'), 0o700);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ describe('empty find must not fall through to cwd (#2483)', () => {
|
|||||||
test('no resolver emits a bare `xargs ls -t` (must be `xargs -r ls -t`)', () => {
|
test('no resolver emits a bare `xargs ls -t` (must be `xargs -r ls -t`)', () => {
|
||||||
const out = execSync(
|
const out = execSync(
|
||||||
`grep -rn "xargs ls -t" "${path.join(ROOT, 'scripts')}" "${path.join(ROOT, 'bin')}" || true`,
|
`grep -rn "xargs ls -t" "${path.join(ROOT, 'scripts')}" "${path.join(ROOT, 'bin')}" || true`,
|
||||||
{ encoding: 'utf-8' },
|
{ encoding: 'utf-8', timeout: 30_000 },
|
||||||
);
|
);
|
||||||
expect(out.trim()).toBe('');
|
expect(out.trim()).toBe('');
|
||||||
});
|
});
|
||||||
@@ -67,6 +67,7 @@ describe('empty find must not fall through to cwd (#2483)', () => {
|
|||||||
const out = execSync(`bash -c '${script.replace(/'/g, `'\\''`)}'`, {
|
const out = execSync(`bash -c '${script.replace(/'/g, `'\\''`)}'`, {
|
||||||
cwd,
|
cwd,
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(out).toContain('LATEST_CP=[]');
|
expect(out).toContain('LATEST_CP=[]');
|
||||||
expect(out).not.toContain('DECOY.md');
|
expect(out).not.toContain('DECOY.md');
|
||||||
@@ -79,7 +80,7 @@ describe('empty find must not fall through to cwd (#2483)', () => {
|
|||||||
test('no generated SKILL.md carries the unguarded form', () => {
|
test('no generated SKILL.md carries the unguarded form', () => {
|
||||||
const out = execSync(
|
const out = execSync(
|
||||||
`grep -rln "xargs ls -t" --include=SKILL.md "${ROOT}" || true`,
|
`grep -rln "xargs ls -t" --include=SKILL.md "${ROOT}" || true`,
|
||||||
{ encoding: 'utf-8' },
|
{ encoding: 'utf-8', timeout: 30_000 },
|
||||||
);
|
);
|
||||||
// node_modules and vendored trees are not generated output; nothing in
|
// node_modules and vendored trees are not generated output; nothing in
|
||||||
// the repo's generated skills may carry the unguarded form.
|
// the repo's generated skills may carry the unguarded form.
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ describe('eval budget tiers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('no paid-test timeout literal exceeds the ceiling tier', () => {
|
test('no paid-test timeout literal exceeds the ceiling tier', () => {
|
||||||
const out = spawnSync('git', ['ls-files', 'test/*.test.ts'], { cwd: ROOT, encoding: 'utf-8' });
|
const out = spawnSync('git', ['ls-files', 'test/*.test.ts'], { cwd: ROOT, encoding: 'utf-8', timeout: 30_000 });
|
||||||
const files = out.stdout.split('\n').filter((f) => f && isPaidTestFile(f));
|
const files = out.stdout.split('\n').filter((f) => f && isPaidTestFile(f));
|
||||||
expect(files.length).toBeGreaterThan(50); // scan-rot guard
|
expect(files.length).toBeGreaterThan(50); // scan-rot guard
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ function runEvalList(...args: string[]): { stdout: string; stderr: string; statu
|
|||||||
GSTACK_HOME: path.join(tmpHome, '.gstack'),
|
GSTACK_HOME: path.join(tmpHome, '.gstack'),
|
||||||
},
|
},
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
stdout: result.stdout ?? '',
|
stdout: result.stdout ?? '',
|
||||||
|
|||||||
@@ -74,11 +74,11 @@ describe("swept mkdirp sites under bun-on-Windows EEXIST semantics (#2635)", ()
|
|||||||
fs.mkdirSync(work, { recursive: true });
|
fs.mkdirSync(work, { recursive: true });
|
||||||
const payload = '{"decision":"eexist probe","rationale":"r","scope":"repo","source":"user"}';
|
const payload = '{"decision":"eexist probe","rationale":"r","scope":"repo","source":"user"}';
|
||||||
const env = { ...process.env, HOME: base };
|
const env = { ...process.env, HOME: base };
|
||||||
const first = spawnSync("bun", [DECISION_LOG, payload], { cwd: work, encoding: "utf8", env });
|
const first = spawnSync("bun", [DECISION_LOG, payload], { cwd: work, encoding: "utf8", env, timeout: 30_000 });
|
||||||
expect(first.status).toBe(0);
|
expect(first.status).toBe(0);
|
||||||
const second = spawnSync(
|
const second = spawnSync(
|
||||||
"bun", ["--preload", EEXIST_PRELOAD, DECISION_LOG, payload],
|
"bun", ["--preload", EEXIST_PRELOAD, DECISION_LOG, payload],
|
||||||
{ cwd: work, encoding: "utf8", env },
|
{ cwd: work, encoding: "utf8", env, timeout: 30_000 },
|
||||||
);
|
);
|
||||||
expect(second.status).toBe(0);
|
expect(second.status).toBe(0);
|
||||||
expect(second.stderr ?? "").not.toContain("EEXIST");
|
expect(second.stderr ?? "").not.toContain("EEXIST");
|
||||||
@@ -93,7 +93,7 @@ describe("install-prepush-hook under bun-on-Windows EEXIST semantics (#2635)", (
|
|||||||
const base = tmpdir();
|
const base = tmpdir();
|
||||||
try {
|
try {
|
||||||
const repo = path.join(base, "repo");
|
const repo = path.join(base, "repo");
|
||||||
spawnSync("git", ["init", "-q", repo]);
|
spawnSync("git", ["init", "-q", repo], { timeout: 30_000 });
|
||||||
const hookDir = path.join(repo, ".git", "hooks");
|
const hookDir = path.join(repo, ".git", "hooks");
|
||||||
fs.mkdirSync(hookDir, { recursive: true });
|
fs.mkdirSync(hookDir, { recursive: true });
|
||||||
const hookPath = path.join(hookDir, "pre-push");
|
const hookPath = path.join(hookDir, "pre-push");
|
||||||
@@ -105,6 +105,7 @@ describe("install-prepush-hook under bun-on-Windows EEXIST semantics (#2635)", (
|
|||||||
const r = spawnSync("bun", ["--preload", EEXIST_PRELOAD, REDACT, "install-prepush-hook"], {
|
const r = spawnSync("bun", ["--preload", EEXIST_PRELOAD, REDACT, "install-prepush-hook"], {
|
||||||
cwd: repo,
|
cwd: repo,
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(r.status).toBe(0);
|
expect(r.status).toBe(0);
|
||||||
expect(r.stderr ?? "").not.toContain("EEXIST");
|
expect(r.stderr ?? "").not.toContain("EEXIST");
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ function run(bin: string, args: string[], opts: RunOpts = {}) {
|
|||||||
env,
|
env,
|
||||||
cwd: opts.cwd,
|
cwd: opts.cwd,
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
stdout: (res.stdout || '').trim(),
|
stdout: (res.stdout || '').trim(),
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ echo "ok"
|
|||||||
HOME: env.home,
|
HOME: env.home,
|
||||||
PATH: `${env.bindir}:/usr/bin:/bin`,
|
PATH: `${env.bindir}:/usr/bin:/bin`,
|
||||||
},
|
},
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
exitCode: result.status ?? 1,
|
exitCode: result.status ?? 1,
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ gbrain init --pglite --json "$@"
|
|||||||
const result = spawnSync(shell, ["-c", script], {
|
const result = spawnSync(shell, ["-c", script], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
env: baseEnv,
|
env: baseEnv,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
if (result.status !== 0) {
|
if (result.status !== 0) {
|
||||||
throw new Error(`init script exited ${result.status}: ${result.stderr}`);
|
throw new Error(`init script exited ${result.status}: ${result.stderr}`);
|
||||||
@@ -133,7 +134,7 @@ function lastArgc(env: FakeEnv): number {
|
|||||||
return parseInt(lines[lines.length - 1], 10);
|
return parseInt(lines[lines.length - 1], 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
const HAVE_ZSH = spawnSync("zsh", ["-c", "true"]).status === 0;
|
const HAVE_ZSH = spawnSync("zsh", ["-c", "true"], { timeout: 30_000 }).status === 0;
|
||||||
|
|
||||||
describe("voyage-code-3 default for gstack-driven PGLite init", () => {
|
describe("voyage-code-3 default for gstack-driven PGLite init", () => {
|
||||||
it("passes voyage-code-3 flags when VOYAGE_API_KEY is set", () => {
|
it("passes voyage-code-3 flags when VOYAGE_API_KEY is set", () => {
|
||||||
@@ -193,6 +194,7 @@ gbrain init --pglite --json $GBRAIN_EMBED_FLAGS
|
|||||||
const result = spawnSync("zsh", ["-c", brokenShape], {
|
const result = spawnSync("zsh", ["-c", brokenShape], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
env: { ...process.env, HOME: env.home, PATH: `${env.bindir}:/usr/bin:/bin` },
|
env: { ...process.env, HOME: env.home, PATH: `${env.bindir}:/usr/bin:/bin` },
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(result.status).toBe(0);
|
expect(result.status).toBe(0);
|
||||||
expect(lastArgc(env)).toBe(4); // init, --pglite, --json, "<entire flag string>"
|
expect(lastArgc(env)).toBe(4); // init, --pglite, --json, "<entire flag string>"
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ function runVerify(arg: string, stdin?: string) {
|
|||||||
const res = spawnSync(VERIFY, arg === '' ? [] : [arg], {
|
const res = spawnSync(VERIFY, arg === '' ? [] : [arg], {
|
||||||
input: stdin,
|
input: stdin,
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
stdout: (res.stdout || '').trim(),
|
stdout: (res.stdout || '').trim(),
|
||||||
@@ -43,6 +44,7 @@ function runLibSnippet(snippet: string, stdin: string = '') {
|
|||||||
const res = spawnSync('bash', ['-c', script], {
|
const res = spawnSync('bash', ['-c', script], {
|
||||||
input: stdin,
|
input: stdin,
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
stdout: (res.stdout || '').trim(),
|
stdout: (res.stdout || '').trim(),
|
||||||
|
|||||||
@@ -538,6 +538,7 @@ describe("lib/gbrain-local-status — thin-client (#2051)", () => {
|
|||||||
GSTACK_HOME: env.gstackHome,
|
GSTACK_HOME: env.gstackHome,
|
||||||
GSTACK_DETECT_NO_CACHE: "1",
|
GSTACK_DETECT_NO_CACHE: "1",
|
||||||
},
|
},
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(r.status).toBe(0);
|
expect(r.status).toBe(0);
|
||||||
});
|
});
|
||||||
@@ -554,6 +555,7 @@ describe("lib/gbrain-local-status — thin-client (#2051)", () => {
|
|||||||
GSTACK_HOME: env.gstackHome,
|
GSTACK_HOME: env.gstackHome,
|
||||||
GSTACK_DETECT_NO_CACHE: "1",
|
GSTACK_DETECT_NO_CACHE: "1",
|
||||||
},
|
},
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(r.status).toBe(1);
|
expect(r.status).toBe(1);
|
||||||
});
|
});
|
||||||
@@ -790,6 +792,7 @@ describe("lib/gbrain-local-status — bearer-token thin-client (#2520)", () => {
|
|||||||
GSTACK_HOME: env.gstackHome,
|
GSTACK_HOME: env.gstackHome,
|
||||||
GSTACK_DETECT_NO_CACHE: "1",
|
GSTACK_DETECT_NO_CACHE: "1",
|
||||||
},
|
},
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(r.status).toBe(0);
|
expect(r.status).toBe(0);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ function env(): NodeJS.ProcessEnv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function run(args: string[], input?: string) {
|
function run(args: string[], input?: string) {
|
||||||
const res = spawnSync(BIN, args, { env: env(), encoding: "utf-8", input });
|
const res = spawnSync(BIN, args, { env: env(), encoding: "utf-8", input, timeout: 30_000 });
|
||||||
return {
|
return {
|
||||||
stdout: res.stdout || "",
|
stdout: res.stdout || "",
|
||||||
stderr: res.stderr || "",
|
stderr: res.stderr || "",
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ function run(args: string[], opts: { env?: Record<string, string> } = {}) {
|
|||||||
const res = spawnSync(BIN, args, {
|
const res = spawnSync(BIN, args, {
|
||||||
env: { ...process.env, GSTACK_HOME: tmpHome, ...(opts.env || {}) },
|
env: { ...process.env, GSTACK_HOME: tmpHome, ...(opts.env || {}) },
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
stdout: (res.stdout || '').trim(),
|
stdout: (res.stdout || '').trim(),
|
||||||
@@ -263,6 +264,7 @@ describe('get without arg (auto-detect from current dir)', () => {
|
|||||||
env: { ...process.env, GSTACK_HOME: tmpHome },
|
env: { ...process.env, GSTACK_HOME: tmpHome },
|
||||||
cwd: cwdTmp,
|
cwd: cwdTmp,
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect((res.stdout || '').trim()).toBe('unset');
|
expect((res.stdout || '').trim()).toBe('unset');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -288,7 +290,7 @@ describe('gstack-gbrain-sync code stage honors the repo policy (#2140 sync path)
|
|||||||
function makeRepo(): void {
|
function makeRepo(): void {
|
||||||
repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gbrain-policy-repo-'));
|
repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gbrain-policy-repo-'));
|
||||||
const git = (...args: string[]) =>
|
const git = (...args: string[]) =>
|
||||||
spawnSync('git', args, { cwd: repoDir, encoding: 'utf-8' });
|
spawnSync('git', args, { cwd: repoDir, encoding: 'utf-8', timeout: 30_000 });
|
||||||
git('init', '-q', '.');
|
git('init', '-q', '.');
|
||||||
git('remote', 'add', 'origin', REPO_URL);
|
git('remote', 'add', 'origin', REPO_URL);
|
||||||
fs.writeFileSync(path.join(repoDir, 'README.md'), 'fixture\n');
|
fs.writeFileSync(path.join(repoDir, 'README.md'), 'fixture\n');
|
||||||
|
|||||||
@@ -23,12 +23,13 @@ function run(argv: string[], env: Record<string, string> = {}) {
|
|||||||
env: { ...process.env, HOME: tmpHome, GSTACK_HOME: tmpHome, ...env },
|
env: { ...process.env, HOME: tmpHome, GSTACK_HOME: tmpHome, ...env },
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return { stdout: res.stdout || '', stderr: res.stderr || '', status: res.status ?? -1 };
|
return { stdout: res.stdout || '', stderr: res.stderr || '', status: res.status ?? -1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
function git(args: string[], cwd: string) {
|
function git(args: string[], cwd: string) {
|
||||||
const res = spawnSync('git', args, { cwd, encoding: 'utf-8' });
|
const res = spawnSync('git', args, { cwd, encoding: 'utf-8', timeout: 30_000 });
|
||||||
return { stdout: (res.stdout || '').trim(), status: res.status ?? -1 };
|
return { stdout: (res.stdout || '').trim(), status: res.status ?? -1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,9 +112,10 @@ function runOrchestrator(
|
|||||||
): { stdout: string; stderr: string; exitCode: number } {
|
): { stdout: string; stderr: string; exitCode: number } {
|
||||||
// Initialize a git repo in the sandbox so repoRoot() finds it (otherwise
|
// Initialize a git repo in the sandbox so repoRoot() finds it (otherwise
|
||||||
// code stage skips with "not in git repo" before our check ever fires).
|
// code stage skips with "not in git repo" before our check ever fires).
|
||||||
spawnSync("git", ["init", "-q", env.home], { encoding: "utf-8" });
|
spawnSync("git", ["init", "-q", env.home], { encoding: "utf-8", timeout: 30_000 });
|
||||||
spawnSync("git", ["-C", env.home, "commit", "--allow-empty", "-m", "init", "-q"], {
|
spawnSync("git", ["-C", env.home, "commit", "--allow-empty", "-m", "init", "-q"], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
|
timeout: 30_000,
|
||||||
env: { ...process.env, GIT_AUTHOR_NAME: "T", GIT_AUTHOR_EMAIL: "t@t", GIT_COMMITTER_NAME: "T", GIT_COMMITTER_EMAIL: "t@t" },
|
env: { ...process.env, GIT_AUTHOR_NAME: "T", GIT_AUTHOR_EMAIL: "t@t", GIT_COMMITTER_NAME: "T", GIT_COMMITTER_EMAIL: "t@t" },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import { tmpdir } from "os";
|
|||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { spawnSync } from "child_process";
|
import { spawnSync } from "child_process";
|
||||||
|
|
||||||
const gbrainPath = spawnSync("which", ["gbrain"], { encoding: "utf-8" }).stdout.trim();
|
const gbrainPath = spawnSync("which", ["gbrain"], { encoding: "utf-8", timeout: 30_000 }).stdout.trim();
|
||||||
const gbrainAvailable = gbrainPath.length > 0;
|
const gbrainAvailable = gbrainPath.length > 0;
|
||||||
const voyageKey = process.env.VOYAGE_API_KEY?.trim() ?? "";
|
const voyageKey = process.env.VOYAGE_API_KEY?.trim() ?? "";
|
||||||
const voyageKeyPresent = voyageKey.length > 0;
|
const voyageKeyPresent = voyageKey.length > 0;
|
||||||
@@ -114,14 +114,14 @@ The queue module is a simple FIFO job queue.
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Make it a git repo because gbrain's code-sync strategy expects one.
|
// Make it a git repo because gbrain's code-sync strategy expects one.
|
||||||
const gitInit = spawnSync("git", ["init", "-q"], { cwd: fixtureDir, encoding: "utf-8" });
|
const gitInit = spawnSync("git", ["init", "-q"], { cwd: fixtureDir, encoding: "utf-8", timeout: 30_000 });
|
||||||
if (gitInit.status !== 0) {
|
if (gitInit.status !== 0) {
|
||||||
throw new Error(`git init failed: ${gitInit.stderr}`);
|
throw new Error(`git init failed: ${gitInit.stderr}`);
|
||||||
}
|
}
|
||||||
spawnSync("git", ["config", "user.email", "test@example.invalid"], { cwd: fixtureDir });
|
spawnSync("git", ["config", "user.email", "test@example.invalid"], { cwd: fixtureDir, timeout: 30_000 });
|
||||||
spawnSync("git", ["config", "user.name", "test"], { cwd: fixtureDir });
|
spawnSync("git", ["config", "user.name", "test"], { cwd: fixtureDir, timeout: 30_000 });
|
||||||
spawnSync("git", ["add", "."], { cwd: fixtureDir });
|
spawnSync("git", ["add", "."], { cwd: fixtureDir, timeout: 30_000 });
|
||||||
spawnSync("git", ["commit", "-q", "-m", "fixture"], { cwd: fixtureDir });
|
spawnSync("git", ["commit", "-q", "-m", "fixture"], { cwd: fixtureDir, timeout: 30_000 });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
root,
|
root,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const ROOT = path.resolve(import.meta.dir, '..');
|
|||||||
|
|
||||||
const GEMINI_AVAILABLE = (() => {
|
const GEMINI_AVAILABLE = (() => {
|
||||||
try {
|
try {
|
||||||
const result = Bun.spawnSync(['which', 'gemini']);
|
const result = Bun.spawnSync(['which', 'gemini'], { timeout: 30_000 });
|
||||||
return result.exitCode === 0;
|
return result.exitCode === 0;
|
||||||
} catch { return false; }
|
} catch { return false; }
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ describe('gen-skill-docs import purity', () => {
|
|||||||
}
|
}
|
||||||
console.log('IMPORT_PURE');
|
console.log('IMPORT_PURE');
|
||||||
`;
|
`;
|
||||||
const out = Bun.spawnSync(['bun', '-e', probe], { cwd: ROOT });
|
const out = Bun.spawnSync(['bun', '-e', probe], { cwd: ROOT, timeout: 120_000 });
|
||||||
const stdout = out.stdout.toString();
|
const stdout = out.stdout.toString();
|
||||||
const stderr = out.stderr.toString();
|
const stderr = out.stderr.toString();
|
||||||
expect(stderr, stderr).not.toContain('import mutated');
|
expect(stderr, stderr).not.toContain('import mutated');
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ describe('gen-skill-docs --out-dir (B2 render isolation)', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function porcelain(): string {
|
function porcelain(): string {
|
||||||
const r = spawnSync('git', ['status', '--porcelain'], { cwd: ROOT, encoding: 'utf-8' });
|
const r = spawnSync('git', ['status', '--porcelain'], { cwd: ROOT, encoding: 'utf-8', timeout: 30_000 });
|
||||||
return r.status === 0 ? r.stdout : '';
|
return r.status === 0 ? r.stdout : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ const EXTERNAL_OUT = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-gen-docs-out-
|
|||||||
{
|
{
|
||||||
const render = Bun.spawnSync(
|
const render = Bun.spawnSync(
|
||||||
['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'all', '--out-dir', EXTERNAL_OUT],
|
['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'all', '--out-dir', EXTERNAL_OUT],
|
||||||
{ cwd: ROOT, stdout: 'pipe', stderr: 'pipe' },
|
{ cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 120_000 },
|
||||||
);
|
);
|
||||||
if (render.exitCode !== 0) {
|
if (render.exitCode !== 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -315,6 +315,7 @@ describe('gen-skill-docs', () => {
|
|||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
stdout: 'pipe',
|
stdout: 'pipe',
|
||||||
stderr: 'pipe',
|
stderr: 'pipe',
|
||||||
|
timeout: 120_000,
|
||||||
});
|
});
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
const output = result.stdout.toString();
|
const output = result.stdout.toString();
|
||||||
@@ -1965,7 +1966,7 @@ describe('Codex generation (--host codex)', () => {
|
|||||||
'/tmp/gstack-claude-error-XXXXXX',
|
'/tmp/gstack-claude-error-XXXXXX',
|
||||||
'/tmp/gstack-claude-diff-XXXXXX',
|
'/tmp/gstack-claude-diff-XXXXXX',
|
||||||
]) {
|
]) {
|
||||||
const result = spawnSync('mktemp', [template], { encoding: 'utf-8' });
|
const result = spawnSync('mktemp', [template], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(result.status).toBe(0);
|
expect(result.status).toBe(0);
|
||||||
const created = result.stdout.trim();
|
const created = result.stdout.trim();
|
||||||
expect(created.startsWith(template.replace('XXXXXX', ''))).toBe(true);
|
expect(created.startsWith(template.replace('XXXXXX', ''))).toBe(true);
|
||||||
@@ -1990,6 +1991,7 @@ describe('Codex generation (--host codex)', () => {
|
|||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
stdout: 'pipe',
|
stdout: 'pipe',
|
||||||
stderr: 'pipe',
|
stderr: 'pipe',
|
||||||
|
timeout: 120_000,
|
||||||
});
|
});
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
const output = result.stdout.toString();
|
const output = result.stdout.toString();
|
||||||
@@ -2005,11 +2007,13 @@ describe('Codex generation (--host codex)', () => {
|
|||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
stdout: 'pipe',
|
stdout: 'pipe',
|
||||||
stderr: 'pipe',
|
stderr: 'pipe',
|
||||||
|
timeout: 120_000,
|
||||||
});
|
});
|
||||||
const agentsResult = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'agents', '--dry-run', '--out-dir', EXTERNAL_OUT], {
|
const agentsResult = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'agents', '--dry-run', '--out-dir', EXTERNAL_OUT], {
|
||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
stdout: 'pipe',
|
stdout: 'pipe',
|
||||||
stderr: 'pipe',
|
stderr: 'pipe',
|
||||||
|
timeout: 120_000,
|
||||||
});
|
});
|
||||||
expect(codexResult.exitCode).toBe(0);
|
expect(codexResult.exitCode).toBe(0);
|
||||||
expect(agentsResult.exitCode).toBe(0);
|
expect(agentsResult.exitCode).toBe(0);
|
||||||
@@ -2224,6 +2228,7 @@ describe('Codex generation (--host codex)', () => {
|
|||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
stdout: 'pipe',
|
stdout: 'pipe',
|
||||||
stderr: 'pipe',
|
stderr: 'pipe',
|
||||||
|
timeout: 120_000,
|
||||||
});
|
});
|
||||||
expect(override.exitCode).toBe(0);
|
expect(override.exitCode).toBe(0);
|
||||||
const content = fs.readFileSync(path.join(overrideOut, '.agents', 'skills', 'gstack-ship', 'SKILL.md'), 'utf-8');
|
const content = fs.readFileSync(path.join(overrideOut, '.agents', 'skills', 'gstack-ship', 'SKILL.md'), 'utf-8');
|
||||||
@@ -2342,10 +2347,10 @@ describe('Factory generation (--host factory)', () => {
|
|||||||
|
|
||||||
test('--host droid alias works', () => {
|
test('--host droid alias works', () => {
|
||||||
const factoryResult = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'factory', '--dry-run', '--out-dir', EXTERNAL_OUT], {
|
const factoryResult = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'factory', '--dry-run', '--out-dir', EXTERNAL_OUT], {
|
||||||
cwd: ROOT, stdout: 'pipe', stderr: 'pipe',
|
cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 120_000,
|
||||||
});
|
});
|
||||||
const droidResult = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'droid', '--dry-run', '--out-dir', EXTERNAL_OUT], {
|
const droidResult = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'droid', '--dry-run', '--out-dir', EXTERNAL_OUT], {
|
||||||
cwd: ROOT, stdout: 'pipe', stderr: 'pipe',
|
cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 120_000,
|
||||||
});
|
});
|
||||||
expect(factoryResult.exitCode).toBe(0);
|
expect(factoryResult.exitCode).toBe(0);
|
||||||
expect(droidResult.exitCode).toBe(0);
|
expect(droidResult.exitCode).toBe(0);
|
||||||
@@ -2354,7 +2359,7 @@ describe('Factory generation (--host factory)', () => {
|
|||||||
|
|
||||||
test('--host factory --dry-run freshness', () => {
|
test('--host factory --dry-run freshness', () => {
|
||||||
const result = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'factory', '--dry-run', '--out-dir', EXTERNAL_OUT], {
|
const result = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'factory', '--dry-run', '--out-dir', EXTERNAL_OUT], {
|
||||||
cwd: ROOT, stdout: 'pipe', stderr: 'pipe',
|
cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 120_000,
|
||||||
});
|
});
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
const output = result.stdout.toString();
|
const output = result.stdout.toString();
|
||||||
@@ -2432,7 +2437,7 @@ describe('Parameterized host smoke tests', () => {
|
|||||||
test('--dry-run freshness check passes', () => {
|
test('--dry-run freshness check passes', () => {
|
||||||
const result = Bun.spawnSync(
|
const result = Bun.spawnSync(
|
||||||
['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', hostConfig.name, '--dry-run', '--out-dir', EXTERNAL_OUT],
|
['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', hostConfig.name, '--dry-run', '--out-dir', EXTERNAL_OUT],
|
||||||
{ cwd: ROOT, stdout: 'pipe', stderr: 'pipe' }
|
{ cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 120_000 }
|
||||||
);
|
);
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
const output = result.stdout.toString();
|
const output = result.stdout.toString();
|
||||||
@@ -2457,7 +2462,7 @@ describe('--host all', () => {
|
|||||||
// claude host plus every external host regenerate deterministically.
|
// claude host plus every external host regenerate deterministically.
|
||||||
test('--host all generates for all registered hosts', () => {
|
test('--host all generates for all registered hosts', () => {
|
||||||
const result = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'all', '--dry-run', '--out-dir', EXTERNAL_OUT], {
|
const result = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'all', '--dry-run', '--out-dir', EXTERNAL_OUT], {
|
||||||
cwd: ROOT, stdout: 'pipe', stderr: 'pipe',
|
cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 120_000,
|
||||||
});
|
});
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
const output = result.stdout.toString();
|
const output = result.stdout.toString();
|
||||||
|
|||||||
@@ -180,10 +180,11 @@ describe("gstack-global-discover", () => {
|
|||||||
// Create a git repo as the session target
|
// Create a git repo as the session target
|
||||||
const repoDir = join(tmpDir, "fake-repo");
|
const repoDir = join(tmpDir, "fake-repo");
|
||||||
mkdirSync(repoDir);
|
mkdirSync(repoDir);
|
||||||
spawnSync("git", ["init"], { cwd: repoDir, stdio: "pipe" });
|
spawnSync("git", ["init"], { cwd: repoDir, stdio: "pipe", timeout: 30_000 });
|
||||||
spawnSync("git", ["commit", "--allow-empty", "-m", "init"], {
|
spawnSync("git", ["commit", "--allow-empty", "-m", "init"], {
|
||||||
cwd: repoDir,
|
cwd: repoDir,
|
||||||
stdio: "pipe",
|
stdio: "pipe",
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Write a session with a 20KB first line (simulates Codex v0.117+)
|
// Write a session with a 20KB first line (simulates Codex v0.117+)
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ exit 0
|
|||||||
* test focused on artifacts-init's branching logic, not git plumbing.
|
* test focused on artifacts-init's branching logic, not git plumbing.
|
||||||
*/
|
*/
|
||||||
function makeFakeGit() {
|
function makeFakeGit() {
|
||||||
const realGit = spawnSync('which', ['git'], { encoding: 'utf-8' }).stdout.trim();
|
const realGit = spawnSync('which', ['git'], { encoding: 'utf-8', timeout: 30_000 }).stdout.trim();
|
||||||
const script = `#!/bin/bash
|
const script = `#!/bin/bash
|
||||||
# Walk argv past leading -C <dir> and similar flags to find the real subcommand.
|
# Walk argv past leading -C <dir> and similar flags to find the real subcommand.
|
||||||
args=("$@")
|
args=("$@")
|
||||||
@@ -157,6 +157,7 @@ function run(argv: string[], opts: { env?: Record<string, string>; input?: strin
|
|||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
input: opts.input,
|
input: opts.input,
|
||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
stdout: res.stdout || '',
|
stdout: res.stdout || '',
|
||||||
@@ -176,7 +177,7 @@ beforeEach(() => {
|
|||||||
fakeBinDir = fs.mkdtempSync(path.join(os.tmpdir(), 'artifacts-fake-bin-'));
|
fakeBinDir = fs.mkdtempSync(path.join(os.tmpdir(), 'artifacts-fake-bin-'));
|
||||||
ghCallLog = path.join(fakeBinDir, 'gh-calls.log');
|
ghCallLog = path.join(fakeBinDir, 'gh-calls.log');
|
||||||
glabCallLog = path.join(fakeBinDir, 'glab-calls.log');
|
glabCallLog = path.join(fakeBinDir, 'glab-calls.log');
|
||||||
spawnSync('git', ['init', '--bare', '-q', '-b', 'main', bareRemote]);
|
spawnSync('git', ['init', '--bare', '-q', '-b', 'main', bareRemote], { timeout: 30_000 });
|
||||||
makeFakeGit();
|
makeFakeGit();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -277,7 +278,7 @@ describe('gstack-artifacts-init canonical URL storage (codex Finding #10)', () =
|
|||||||
makeFakeGh({ webUrl: 'https://github.com/testuser/gstack-artifacts-testuser' });
|
makeFakeGh({ webUrl: 'https://github.com/testuser/gstack-artifacts-testuser' });
|
||||||
const r = run(['--host', 'github']);
|
const r = run(['--host', 'github']);
|
||||||
expect(r.status).toBe(0);
|
expect(r.status).toBe(0);
|
||||||
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
|
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(remote.stdout.trim()).toBe('https://github.com/testuser/gstack-artifacts-testuser');
|
expect(remote.stdout.trim()).toBe('https://github.com/testuser/gstack-artifacts-testuser');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -288,7 +289,7 @@ describe('gstack-artifacts-init canonical URL storage (codex Finding #10)', () =
|
|||||||
});
|
});
|
||||||
const r = run(['--host', 'github']);
|
const r = run(['--host', 'github']);
|
||||||
expect(r.status).toBe(0);
|
expect(r.status).toBe(0);
|
||||||
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
|
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(remote.stdout.trim()).toBe('git@github.com:testuser/gstack-artifacts-testuser.git');
|
expect(remote.stdout.trim()).toBe('git@github.com:testuser/gstack-artifacts-testuser.git');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -296,7 +297,7 @@ describe('gstack-artifacts-init canonical URL storage (codex Finding #10)', () =
|
|||||||
makeFakeGh({ gitProtocol: 'unset' });
|
makeFakeGh({ gitProtocol: 'unset' });
|
||||||
const r = run(['--host', 'github']);
|
const r = run(['--host', 'github']);
|
||||||
expect(r.status).toBe(0);
|
expect(r.status).toBe(0);
|
||||||
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
|
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(remote.stdout.trim()).toBe('https://github.com/testuser/gstack-artifacts-testuser');
|
expect(remote.stdout.trim()).toBe('https://github.com/testuser/gstack-artifacts-testuser');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -304,7 +305,7 @@ describe('gstack-artifacts-init canonical URL storage (codex Finding #10)', () =
|
|||||||
makeFakeGlab({ gitProtocol: 'ssh' });
|
makeFakeGlab({ gitProtocol: 'ssh' });
|
||||||
const r = run(['--host', 'gitlab']);
|
const r = run(['--host', 'gitlab']);
|
||||||
expect(r.status).toBe(0);
|
expect(r.status).toBe(0);
|
||||||
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
|
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(remote.stdout.trim()).toBe('git@gitlab.com:testuser/gstack-artifacts-testuser.git');
|
expect(remote.stdout.trim()).toBe('git@gitlab.com:testuser/gstack-artifacts-testuser.git');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -359,7 +360,7 @@ describe('gstack-artifacts-init idempotency', () => {
|
|||||||
makeFakeGh({ gitProtocol: 'ssh' });
|
makeFakeGh({ gitProtocol: 'ssh' });
|
||||||
const r = run(['--remote', 'https://github.com/testuser/gstack-artifacts-testuser']);
|
const r = run(['--remote', 'https://github.com/testuser/gstack-artifacts-testuser']);
|
||||||
expect(r.status).toBe(0);
|
expect(r.status).toBe(0);
|
||||||
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
|
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(remote.stdout.trim()).toBe('https://github.com/testuser/gstack-artifacts-testuser');
|
expect(remote.stdout.trim()).toBe('https://github.com/testuser/gstack-artifacts-testuser');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -372,7 +373,7 @@ describe('gstack-artifacts-init idempotency', () => {
|
|||||||
'ssh',
|
'ssh',
|
||||||
]);
|
]);
|
||||||
expect(r.status).toBe(0);
|
expect(r.status).toBe(0);
|
||||||
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
|
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(remote.stdout.trim()).toBe('git@github.com:testuser/gstack-artifacts-testuser.git');
|
expect(remote.stdout.trim()).toBe('git@github.com:testuser/gstack-artifacts-testuser.git');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const ROOT = path.resolve(import.meta.dir, '..');
|
|||||||
const URL_BIN = path.join(ROOT, 'bin', 'gstack-artifacts-url');
|
const URL_BIN = path.join(ROOT, 'bin', 'gstack-artifacts-url');
|
||||||
|
|
||||||
function run(args: string[]): { code: number; stdout: string; stderr: string } {
|
function run(args: string[]): { code: number; stdout: string; stderr: string } {
|
||||||
const r = spawnSync(URL_BIN, args, { encoding: 'utf-8' });
|
const r = spawnSync(URL_BIN, args, { encoding: 'utf-8', timeout: 30_000 });
|
||||||
return {
|
return {
|
||||||
code: r.status ?? -1,
|
code: r.status ?? -1,
|
||||||
stdout: (r.stdout || '').trim(),
|
stdout: (r.stdout || '').trim(),
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ function runImport(sessionPath: string): { stdout: string; stderr: string; statu
|
|||||||
env.GSTACK_STATE_ROOT = stateRoot;
|
env.GSTACK_STATE_ROOT = stateRoot;
|
||||||
env.GSTACK_QUESTION_LOG_NO_DERIVE = '1';
|
env.GSTACK_QUESTION_LOG_NO_DERIVE = '1';
|
||||||
delete env.GSTACK_HOME;
|
delete env.GSTACK_HOME;
|
||||||
const res = spawnSync(BIN, [sessionPath], { env, encoding: 'utf-8', cwd: ROOT });
|
const res = spawnSync(BIN, [sessionPath], { env, encoding: 'utf-8', cwd: ROOT, timeout: 30_000 });
|
||||||
return {
|
return {
|
||||||
stdout: res.stdout ?? '',
|
stdout: res.stdout ?? '',
|
||||||
stderr: res.stderr ?? '',
|
stderr: res.stderr ?? '',
|
||||||
@@ -196,7 +196,7 @@ describe('default mode (no args → latest)', () => {
|
|||||||
}
|
}
|
||||||
env.GSTACK_STATE_ROOT = stateRoot;
|
env.GSTACK_STATE_ROOT = stateRoot;
|
||||||
env.CODEX_SESSIONS_ROOT = emptyDir;
|
env.CODEX_SESSIONS_ROOT = emptyDir;
|
||||||
const res = spawnSync(BIN, [], { env, encoding: 'utf-8', cwd: ROOT });
|
const res = spawnSync(BIN, [], { env, encoding: 'utf-8', cwd: ROOT, timeout: 30_000 });
|
||||||
expect(res.status).toBe(0);
|
expect(res.status).toBe(0);
|
||||||
expect(res.stdout).toMatch(/NO_SESSIONS/);
|
expect(res.stdout).toMatch(/NO_SESSIONS/);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ const STATE = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-config-test-'));
|
|||||||
function get(key: string): { out: string; code: number } {
|
function get(key: string): { out: string; code: number } {
|
||||||
const r = spawnSync('bash', [CONFIG_BIN, 'get', key], {
|
const r = spawnSync('bash', [CONFIG_BIN, 'get', key], {
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
env: { ...process.env, GSTACK_STATE_ROOT: STATE },
|
env: { ...process.env, GSTACK_STATE_ROOT: STATE },
|
||||||
});
|
});
|
||||||
return { out: r.stdout ?? '', code: r.status ?? -1 };
|
return { out: r.stdout ?? '', code: r.status ?? -1 };
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ function run(args: string[]) {
|
|||||||
// live tree (observed in the free-tests CI job). Relink behavior itself is
|
// live tree (observed in the free-tests CI job). Relink behavior itself is
|
||||||
// covered in isolation by test/relink.test.ts's mock install.
|
// covered in isolation by test/relink.test.ts's mock install.
|
||||||
env: { ...process.env, GSTACK_STATE_ROOT: stateRoot, GSTACK_SETUP_RUNNING: "1" },
|
env: { ...process.env, GSTACK_STATE_ROOT: stateRoot, GSTACK_SETUP_RUNNING: "1" },
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ function cfg(args: string[]): { code: number; out: string; err: string } {
|
|||||||
const r = spawnSync(CONFIG, args, {
|
const r = spawnSync(CONFIG, args, {
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
env: { ...process.env, GSTACK_HOME: home },
|
env: { ...process.env, GSTACK_HOME: home },
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return { code: r.status ?? 0, out: r.stdout ?? "", err: r.stderr ?? "" };
|
return { code: r.status ?? 0, out: r.stdout ?? "", err: r.stderr ?? "" };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,18 +20,18 @@ function opts(): ExecSyncOptionsWithStringEncoding {
|
|||||||
}
|
}
|
||||||
function log(arg: string, expectFail = false): { out: string; code: number } {
|
function log(arg: string, expectFail = false): { out: string; code: number } {
|
||||||
try {
|
try {
|
||||||
return { out: execSync(`${LOG} '${arg.replace(/'/g, "'\\''")}'`, opts()).trim(), code: 0 };
|
return { out: execSync(`${LOG} '${arg.replace(/'/g, "'\\''")}'`, opts()).trim(), code: 0 }; // timeout via opts()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (expectFail) return { out: (e.stderr?.toString() || "").trim(), code: e.status || 1 };
|
if (expectFail) return { out: (e.stderr?.toString() || "").trim(), code: e.status || 1 };
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function logFlag(flag: string): string {
|
function logFlag(flag: string): string {
|
||||||
return execSync(`${LOG} ${flag}`, opts()).trim();
|
return execSync(`${LOG} ${flag}`, opts()).trim(); // timeout via opts()
|
||||||
}
|
}
|
||||||
function search(args = ""): string {
|
function search(args = ""): string {
|
||||||
try {
|
try {
|
||||||
return execSync(`${SEARCH} ${args}`, opts()).trim();
|
return execSync(`${SEARCH} ${args}`, opts()).trim(); // timeout via opts()
|
||||||
} catch {
|
} catch {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ const ROOT = path.resolve(import.meta.dir, '..');
|
|||||||
const DETACH = path.join(ROOT, 'bin', 'gstack-detach');
|
const DETACH = path.join(ROOT, 'bin', 'gstack-detach');
|
||||||
|
|
||||||
function ownPgid(): string {
|
function ownPgid(): string {
|
||||||
return (spawnSync('ps', ['-o', 'pgid=', '-p', String(process.pid)], { encoding: 'utf-8' }).stdout || '').trim();
|
return (spawnSync('ps', ['-o', 'pgid=', '-p', String(process.pid)], { encoding: 'utf-8', timeout: 30_000 }).stdout || '').trim();
|
||||||
}
|
}
|
||||||
function waitFor(pred: () => boolean, ms: number): boolean {
|
function waitFor(pred: () => boolean, ms: number): boolean {
|
||||||
const end = Date.now() + ms;
|
const end = Date.now() + ms;
|
||||||
while (Date.now() < end) {
|
while (Date.now() < end) {
|
||||||
if (pred()) return true;
|
if (pred()) return true;
|
||||||
spawnSync('sleep', ['0.2']);
|
spawnSync('sleep', ['0.2'], { timeout: 30_000 });
|
||||||
}
|
}
|
||||||
return pred();
|
return pred();
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ describe('gstack-detach', () => {
|
|||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
test('rejects missing command (exit 2)', () => {
|
test('rejects missing command (exit 2)', () => {
|
||||||
const r = spawnSync(DETACH, ['--label', 'x'], { encoding: 'utf-8' });
|
const r = spawnSync(DETACH, ['--label', 'x'], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(r.status).toBe(2);
|
expect(r.status).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ function runDev(...args: string[]): { stdout: string; stderr: string; status: nu
|
|||||||
env: { ...process.env, GSTACK_HOME: tmpHome },
|
env: { ...process.env, GSTACK_HOME: tmpHome },
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
stdout: res.stdout ?? '',
|
stdout: res.stdout ?? '',
|
||||||
@@ -49,6 +50,7 @@ function logQuestion(payload: Record<string, unknown>): number {
|
|||||||
env: { ...process.env, GSTACK_HOME: tmpHome },
|
env: { ...process.env, GSTACK_HOME: tmpHome },
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return res.status ?? -1;
|
return res.status ?? -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ function run(args: string[]) {
|
|||||||
const result = spawnSync(BIN, args, {
|
const result = spawnSync(BIN, args, {
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
env: { ...process.env, GSTACK_HOME: home },
|
env: { ...process.env, GSTACK_HOME: home },
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return { code: result.status ?? -1, stdout: result.stdout || '', stderr: result.stderr || '' };
|
return { code: result.status ?? -1, stdout: result.stdout || '', stderr: result.stderr || '' };
|
||||||
}
|
}
|
||||||
@@ -122,11 +123,13 @@ describe('gstack-egress grants', () => {
|
|||||||
const config = spawnSync(path.join(ROOT, 'bin', 'gstack-config'), ['set', 'telemetry', 'community'], {
|
const config = spawnSync(path.join(ROOT, 'bin', 'gstack-config'), ['set', 'telemetry', 'community'], {
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
env: { ...process.env, GSTACK_HOME: home },
|
env: { ...process.env, GSTACK_HOME: home },
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(config.status).toBe(0);
|
expect(config.status).toBe(0);
|
||||||
spawnSync(path.join(ROOT, 'bin', 'gstack-config'), ['set', 'artifacts_sync_mode', 'full'], {
|
spawnSync(path.join(ROOT, 'bin', 'gstack-config'), ['set', 'artifacts_sync_mode', 'full'], {
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
env: { ...process.env, GSTACK_HOME: home },
|
env: { ...process.env, GSTACK_HOME: home },
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
const r = run(['grants', '--json']);
|
const r = run(['grants', '--json']);
|
||||||
expect(r.code).toBe(0);
|
expect(r.code).toBe(0);
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ function runDetect(extraEnv: Record<string, string> = {}): { code: number; json:
|
|||||||
...extraEnv,
|
...extraEnv,
|
||||||
},
|
},
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
let json: any = null;
|
let json: any = null;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ function runVerify(token: string, url: string): { code: number; stdout: string;
|
|||||||
GSTACK_HOME: tmpDir,
|
GSTACK_HOME: tmpDir,
|
||||||
},
|
},
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
code: result.status ?? -1,
|
code: result.status ?? -1,
|
||||||
@@ -249,6 +250,7 @@ describe('gstack-gbrain-mcp-verify', () => {
|
|||||||
const r = spawnSync(VERIFY_BIN, ['https://example.com/mcp'], {
|
const r = spawnSync(VERIFY_BIN, ['https://example.com/mcp'], {
|
||||||
env: { ...process.env, PATH: `${fakeBinDir}:${process.env.PATH}`, GBRAIN_MCP_TOKEN: '' },
|
env: { ...process.env, PATH: `${fakeBinDir}:${process.env.PATH}`, GBRAIN_MCP_TOKEN: '' },
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(r.status).toBe(2);
|
expect(r.status).toBe(2);
|
||||||
expect(r.stderr).toContain('GBRAIN_MCP_TOKEN');
|
expect(r.stderr).toContain('GBRAIN_MCP_TOKEN');
|
||||||
@@ -259,6 +261,7 @@ describe('gstack-gbrain-mcp-verify', () => {
|
|||||||
const r = spawnSync(VERIFY_BIN, [], {
|
const r = spawnSync(VERIFY_BIN, [], {
|
||||||
env: { ...process.env, PATH: `${fakeBinDir}:${process.env.PATH}`, GBRAIN_MCP_TOKEN: 'x' },
|
env: { ...process.env, PATH: `${fakeBinDir}:${process.env.PATH}`, GBRAIN_MCP_TOKEN: 'x' },
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(r.status).toBe(2);
|
expect(r.status).toBe(2);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ function run(
|
|||||||
env,
|
env,
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,13 +164,13 @@ function gbrainCalls(): string[] {
|
|||||||
function setupGstackRepo(remoteUrl: string) {
|
function setupGstackRepo(remoteUrl: string) {
|
||||||
// Real git repo at gstackHome with at least one commit + an origin remote.
|
// Real git repo at gstackHome with at least one commit + an origin remote.
|
||||||
fs.mkdirSync(gstackHome, { recursive: true });
|
fs.mkdirSync(gstackHome, { recursive: true });
|
||||||
spawnSync('git', ['-C', gstackHome, 'init', '-q', '-b', 'main'], { stdio: 'pipe' });
|
spawnSync('git', ['-C', gstackHome, 'init', '-q', '-b', 'main'], { stdio: 'pipe', timeout: 30_000 });
|
||||||
spawnSync('git', ['-C', gstackHome, 'config', 'user.email', 'test@example.com'], { stdio: 'pipe' });
|
spawnSync('git', ['-C', gstackHome, 'config', 'user.email', 'test@example.com'], { stdio: 'pipe', timeout: 30_000 });
|
||||||
spawnSync('git', ['-C', gstackHome, 'config', 'user.name', 'test'], { stdio: 'pipe' });
|
spawnSync('git', ['-C', gstackHome, 'config', 'user.name', 'test'], { stdio: 'pipe', timeout: 30_000 });
|
||||||
fs.writeFileSync(path.join(gstackHome, '.brain-allowlist'), '# allowlist\n');
|
fs.writeFileSync(path.join(gstackHome, '.brain-allowlist'), '# allowlist\n');
|
||||||
spawnSync('git', ['-C', gstackHome, 'add', '.'], { stdio: 'pipe' });
|
spawnSync('git', ['-C', gstackHome, 'add', '.'], { stdio: 'pipe', timeout: 30_000 });
|
||||||
spawnSync('git', ['-C', gstackHome, 'commit', '-q', '-m', 'init'], { stdio: 'pipe' });
|
spawnSync('git', ['-C', gstackHome, 'commit', '-q', '-m', 'init'], { stdio: 'pipe', timeout: 30_000 });
|
||||||
spawnSync('git', ['-C', gstackHome, 'remote', 'add', 'origin', remoteUrl], { stdio: 'pipe' });
|
spawnSync('git', ['-C', gstackHome, 'remote', 'add', 'origin', remoteUrl], { stdio: 'pipe', timeout: 30_000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -316,6 +317,7 @@ describe('gstack-gbrain-source-wireup — wireup mode', () => {
|
|||||||
const check = spawnSync('bash', ['-c', `command -v gbrain && gbrain --version`], {
|
const check = spawnSync('bash', ['-c', `command -v gbrain && gbrain --version`], {
|
||||||
env: { PATH: `${hostLikeDir}:${process.env.PATH || '/usr/bin:/bin'}` },
|
env: { PATH: `${hostLikeDir}:${process.env.PATH || '/usr/bin:/bin'}` },
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(check.status).toBe(0);
|
expect(check.status).toBe(0);
|
||||||
expect(check.stdout).toContain('gbrain 0.18.2');
|
expect(check.stdout).toContain('gbrain 0.18.2');
|
||||||
@@ -526,13 +528,15 @@ describe('gstack-gbrain-source-wireup — defensive paths', () => {
|
|||||||
run([], { env: { GSTACK_BRAIN_NO_SYNC: '1' } });
|
run([], { env: { GSTACK_BRAIN_NO_SYNC: '1' } });
|
||||||
// Make a new commit on parent so worktree HEAD is "behind"
|
// Make a new commit on parent so worktree HEAD is "behind"
|
||||||
fs.writeFileSync(path.join(gstackHome, 'newfile.md'), 'new');
|
fs.writeFileSync(path.join(gstackHome, 'newfile.md'), 'new');
|
||||||
spawnSync('git', ['-C', gstackHome, 'add', '.'], { stdio: 'pipe' });
|
spawnSync('git', ['-C', gstackHome, 'add', '.'], { stdio: 'pipe', timeout: 30_000 });
|
||||||
spawnSync('git', ['-C', gstackHome, 'commit', '-q', '-m', 'second commit'], { stdio: 'pipe' });
|
spawnSync('git', ['-C', gstackHome, 'commit', '-q', '-m', 'second commit'], { stdio: 'pipe', timeout: 30_000 });
|
||||||
const parentHeadAfter = spawnSync('git', ['-C', gstackHome, 'rev-parse', 'HEAD'], {
|
const parentHeadAfter = spawnSync('git', ['-C', gstackHome, 'rev-parse', 'HEAD'], {
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
}).stdout.trim();
|
}).stdout.trim();
|
||||||
const worktreeHeadBefore = spawnSync('git', ['-C', worktreeDir, 'rev-parse', 'HEAD'], {
|
const worktreeHeadBefore = spawnSync('git', ['-C', worktreeDir, 'rev-parse', 'HEAD'], {
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
}).stdout.trim();
|
}).stdout.trim();
|
||||||
expect(parentHeadAfter).not.toBe(worktreeHeadBefore); // sanity: parent advanced
|
expect(parentHeadAfter).not.toBe(worktreeHeadBefore); // sanity: parent advanced
|
||||||
// --no-pull should leave worktree HEAD where it was
|
// --no-pull should leave worktree HEAD where it was
|
||||||
@@ -540,6 +544,7 @@ describe('gstack-gbrain-source-wireup — defensive paths', () => {
|
|||||||
expect(r.status).toBe(0);
|
expect(r.status).toBe(0);
|
||||||
const worktreeHeadAfter = spawnSync('git', ['-C', worktreeDir, 'rev-parse', 'HEAD'], {
|
const worktreeHeadAfter = spawnSync('git', ['-C', worktreeDir, 'rev-parse', 'HEAD'], {
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
}).stdout.trim();
|
}).stdout.trim();
|
||||||
expect(worktreeHeadAfter).toBe(worktreeHeadBefore);
|
expect(worktreeHeadAfter).toBe(worktreeHeadBefore);
|
||||||
expect(worktreeHeadAfter).not.toBe(parentHeadAfter);
|
expect(worktreeHeadAfter).not.toBe(parentHeadAfter);
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ describe("gstack-gbrain-sync CLI", () => {
|
|||||||
const repo = mkdtempSync(join(tmpdir(), "gstack-pinned-source-repo-"));
|
const repo = mkdtempSync(join(tmpdir(), "gstack-pinned-source-repo-"));
|
||||||
const commandLog = join(home, "gbrain-commands.log");
|
const commandLog = join(home, "gbrain-commands.log");
|
||||||
mkdirSync(gstackHome, { recursive: true });
|
mkdirSync(gstackHome, { recursive: true });
|
||||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
|
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||||
writeFileSync(join(repo, ".gbrain-source"), "client-acme-app\n");
|
writeFileSync(join(repo, ".gbrain-source"), "client-acme-app\n");
|
||||||
writeFileSync(join(bindir, "gbrain"), `#!/bin/sh
|
writeFileSync(join(bindir, "gbrain"), `#!/bin/sh
|
||||||
printf '%s\\n' "$*" >> "$GSTACK_TEST_GBRAIN_LOG"
|
printf '%s\\n' "$*" >> "$GSTACK_TEST_GBRAIN_LOG"
|
||||||
@@ -179,7 +179,7 @@ exit 99
|
|||||||
mkdirSync(gstackHome, { recursive: true });
|
mkdirSync(gstackHome, { recursive: true });
|
||||||
mkdirSync(join(home, ".gbrain"), { recursive: true });
|
mkdirSync(join(home, ".gbrain"), { recursive: true });
|
||||||
writeFileSync(join(home, ".gbrain", "config.json"), JSON.stringify({ engine: "pglite", database_url: "pglite:///test" }));
|
writeFileSync(join(home, ".gbrain", "config.json"), JSON.stringify({ engine: "pglite", database_url: "pglite:///test" }));
|
||||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
|
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||||
writeFileSync(join(repo, ".gbrain-source"), "client-acme-app\n");
|
writeFileSync(join(repo, ".gbrain-source"), "client-acme-app\n");
|
||||||
symlinkSync(repo, link, "dir");
|
symlinkSync(repo, link, "dir");
|
||||||
writeFileSync(join(bindir, "gbrain"), `#!/bin/sh
|
writeFileSync(join(bindir, "gbrain"), `#!/bin/sh
|
||||||
@@ -223,7 +223,7 @@ esac
|
|||||||
const bindir = mkdtempSync(join(tmpdir(), "gstack-pinned-dream-bin-"));
|
const bindir = mkdtempSync(join(tmpdir(), "gstack-pinned-dream-bin-"));
|
||||||
const repo = mkdtempSync(join(tmpdir(), "gstack-pinned-dream-repo-"));
|
const repo = mkdtempSync(join(tmpdir(), "gstack-pinned-dream-repo-"));
|
||||||
mkdirSync(gstackHome, { recursive: true });
|
mkdirSync(gstackHome, { recursive: true });
|
||||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
|
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||||
writeFileSync(join(repo, ".gbrain-source"), "client-acme-app\n");
|
writeFileSync(join(repo, ".gbrain-source"), "client-acme-app\n");
|
||||||
writeFileSync(join(bindir, "gbrain"), "#!/bin/sh\nexit 99\n");
|
writeFileSync(join(bindir, "gbrain"), "#!/bin/sh\nexit 99\n");
|
||||||
chmodSync(join(bindir, "gbrain"), 0o755);
|
chmodSync(join(bindir, "gbrain"), 0o755);
|
||||||
@@ -247,7 +247,7 @@ esac
|
|||||||
const gstackHome = join(home, ".gstack");
|
const gstackHome = join(home, ".gstack");
|
||||||
const repo = mkdtempSync(join(tmpdir(), "gstack-unreadable-pin-repo-"));
|
const repo = mkdtempSync(join(tmpdir(), "gstack-unreadable-pin-repo-"));
|
||||||
mkdirSync(gstackHome, { recursive: true });
|
mkdirSync(gstackHome, { recursive: true });
|
||||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
|
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||||
mkdirSync(join(repo, ".gbrain-source"));
|
mkdirSync(join(repo, ".gbrain-source"));
|
||||||
|
|
||||||
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
||||||
@@ -282,8 +282,8 @@ esac
|
|||||||
const gstackHome = join(home, ".gstack");
|
const gstackHome = join(home, ".gstack");
|
||||||
mkdirSync(gstackHome, { recursive: true });
|
mkdirSync(gstackHome, { recursive: true });
|
||||||
const repo = mkdtempSync(join(tmpdir(), "gstack-source-id-repo-"));
|
const repo = mkdtempSync(join(tmpdir(), "gstack-source-id-repo-"));
|
||||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
|
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||||
spawnSync("git", ["remote", "add", "origin", remote], { cwd: repo });
|
spawnSync("git", ["remote", "add", "origin", remote], { cwd: repo, timeout: 30_000 });
|
||||||
|
|
||||||
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
@@ -312,7 +312,7 @@ esac
|
|||||||
const gstackHome = join(home, ".gstack");
|
const gstackHome = join(home, ".gstack");
|
||||||
mkdirSync(gstackHome, { recursive: true });
|
mkdirSync(gstackHome, { recursive: true });
|
||||||
const repo = mkdtempSync(join(tmpdir(), "gstack-no-origin-"));
|
const repo = mkdtempSync(join(tmpdir(), "gstack-no-origin-"));
|
||||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
|
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||||
// No `git remote add origin` — this is the no-remote case.
|
// No `git remote add origin` — this is the no-remote case.
|
||||||
|
|
||||||
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
||||||
@@ -347,7 +347,7 @@ esac
|
|||||||
const parent = mkdtempSync(join(tmpdir(), "gstack-empty-base-"));
|
const parent = mkdtempSync(join(tmpdir(), "gstack-empty-base-"));
|
||||||
const repo = join(parent, "___");
|
const repo = join(parent, "___");
|
||||||
mkdirSync(repo);
|
mkdirSync(repo);
|
||||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
|
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||||
// No `origin` remote — forces the basename-fallback path.
|
// No `origin` remote — forces the basename-fallback path.
|
||||||
|
|
||||||
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
||||||
@@ -381,8 +381,8 @@ esac
|
|||||||
const gstackHome = join(home, ".gstack");
|
const gstackHome = join(home, ".gstack");
|
||||||
mkdirSync(gstackHome, { recursive: true });
|
mkdirSync(gstackHome, { recursive: true });
|
||||||
const repo = mkdtempSync(join(tmpdir(), "gstack-host-collide-"));
|
const repo = mkdtempSync(join(tmpdir(), "gstack-host-collide-"));
|
||||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
|
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||||
spawnSync("git", ["remote", "add", "origin", "https://github.com/example/multihost.git"], { cwd: repo });
|
spawnSync("git", ["remote", "add", "origin", "https://github.com/example/multihost.git"], { cwd: repo, timeout: 30_000 });
|
||||||
|
|
||||||
// Dry-run still gates the code stage on `command -v gbrain`. Drop a no-op
|
// Dry-run still gates the code stage on `command -v gbrain`. Drop a no-op
|
||||||
// shim on PATH so the stage runs (we only assert the preview line, never
|
// shim on PATH so the stage runs (we only assert the preview line, never
|
||||||
@@ -569,8 +569,8 @@ esac
|
|||||||
const repoA = mkdtempSync(join(tmpdir(), "gstack-worktree-a-"));
|
const repoA = mkdtempSync(join(tmpdir(), "gstack-worktree-a-"));
|
||||||
const repoB = mkdtempSync(join(tmpdir(), "gstack-worktree-b-"));
|
const repoB = mkdtempSync(join(tmpdir(), "gstack-worktree-b-"));
|
||||||
for (const repo of [repoA, repoB]) {
|
for (const repo of [repoA, repoB]) {
|
||||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
|
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||||
spawnSync("git", ["remote", "add", "origin", remote], { cwd: repo });
|
spawnSync("git", ["remote", "add", "origin", remote], { cwd: repo, timeout: 30_000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const idOf = (cwd: string): string => {
|
const idOf = (cwd: string): string => {
|
||||||
@@ -606,8 +606,8 @@ esac
|
|||||||
const gstackHome = join(home, ".gstack");
|
const gstackHome = join(home, ".gstack");
|
||||||
mkdirSync(gstackHome, { recursive: true });
|
mkdirSync(gstackHome, { recursive: true });
|
||||||
const repo = mkdtempSync(join(tmpdir(), "gstack-worktree-stable-"));
|
const repo = mkdtempSync(join(tmpdir(), "gstack-worktree-stable-"));
|
||||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
|
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||||
spawnSync("git", ["remote", "add", "origin", remote], { cwd: repo });
|
spawnSync("git", ["remote", "add", "origin", remote], { cwd: repo, timeout: 30_000 });
|
||||||
|
|
||||||
const idOf = (): string => {
|
const idOf = (): string => {
|
||||||
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
||||||
@@ -635,8 +635,8 @@ esac
|
|||||||
const gstackHome = join(home, ".gstack");
|
const gstackHome = join(home, ".gstack");
|
||||||
mkdirSync(gstackHome, { recursive: true });
|
mkdirSync(gstackHome, { recursive: true });
|
||||||
const repo = mkdtempSync(join(tmpdir(), "gstack-legacy-cleanup-"));
|
const repo = mkdtempSync(join(tmpdir(), "gstack-legacy-cleanup-"));
|
||||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
|
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||||
spawnSync("git", ["remote", "add", "origin", "https://github.com/garrytan/gstack.git"], { cwd: repo });
|
spawnSync("git", ["remote", "add", "origin", "https://github.com/garrytan/gstack.git"], { cwd: repo, timeout: 30_000 });
|
||||||
|
|
||||||
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
@@ -671,8 +671,8 @@ esac
|
|||||||
const gstackHome = join(home, ".gstack");
|
const gstackHome = join(home, ".gstack");
|
||||||
mkdirSync(gstackHome, { recursive: true });
|
mkdirSync(gstackHome, { recursive: true });
|
||||||
const repo = mkdtempSync(join(tmpdir(), "gstack-attach-preview-"));
|
const repo = mkdtempSync(join(tmpdir(), "gstack-attach-preview-"));
|
||||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
|
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||||
spawnSync("git", ["remote", "add", "origin", "https://github.com/garrytan/gstack.git"], { cwd: repo });
|
spawnSync("git", ["remote", "add", "origin", "https://github.com/garrytan/gstack.git"], { cwd: repo, timeout: 30_000 });
|
||||||
|
|
||||||
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
@@ -727,8 +727,8 @@ describe("derivePathOnlyHashLegacyId", () => {
|
|||||||
// legacy id regardless of $GSTACK_HOSTNAME, because the pre-#1468 hash
|
// legacy id regardless of $GSTACK_HOSTNAME, because the pre-#1468 hash
|
||||||
// didn't include hostname.
|
// didn't include hostname.
|
||||||
const repo = mkdtempSync(join(tmpdir(), "gstack-legacy-id-"));
|
const repo = mkdtempSync(join(tmpdir(), "gstack-legacy-id-"));
|
||||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
|
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||||
spawnSync("git", ["remote", "add", "origin", "https://github.com/example/legacy-test.git"], { cwd: repo });
|
spawnSync("git", ["remote", "add", "origin", "https://github.com/example/legacy-test.git"], { cwd: repo, timeout: 30_000 });
|
||||||
|
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
try {
|
try {
|
||||||
@@ -754,8 +754,8 @@ describe("derivePathOnlyHashLegacyId", () => {
|
|||||||
// host-fold id must differ for any non-empty hostname, so the migration
|
// host-fold id must differ for any non-empty hostname, so the migration
|
||||||
// can detect + clean up the orphan.
|
// can detect + clean up the orphan.
|
||||||
const repo = mkdtempSync(join(tmpdir(), "gstack-legacy-id-distinct-"));
|
const repo = mkdtempSync(join(tmpdir(), "gstack-legacy-id-distinct-"));
|
||||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
|
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||||
spawnSync("git", ["remote", "add", "origin", "https://github.com/example/distinct.git"], { cwd: repo });
|
spawnSync("git", ["remote", "add", "origin", "https://github.com/example/distinct.git"], { cwd: repo, timeout: 30_000 });
|
||||||
|
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
try {
|
try {
|
||||||
@@ -890,10 +890,10 @@ describe("constrainSourceId truncation (hyphen-boundary cut)", () => {
|
|||||||
const gstackHome = join(home, ".gstack");
|
const gstackHome = join(home, ".gstack");
|
||||||
mkdirSync(gstackHome, { recursive: true });
|
mkdirSync(gstackHome, { recursive: true });
|
||||||
const repo = mkdtempSync(join(tmpdir(), "gstack-hyphen-cut-"));
|
const repo = mkdtempSync(join(tmpdir(), "gstack-hyphen-cut-"));
|
||||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
|
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||||
// Remote chosen to be long enough that constrainSourceId truncates and
|
// Remote chosen to be long enough that constrainSourceId truncates and
|
||||||
// the boundary lands inside the word `skill`.
|
// the boundary lands inside the word `skill`.
|
||||||
spawnSync("git", ["remote", "add", "origin", "https://github.com/drummerms-av-sow-wiz/skill-270c0001.git"], { cwd: repo });
|
spawnSync("git", ["remote", "add", "origin", "https://github.com/drummerms-av-sow-wiz/skill-270c0001.git"], { cwd: repo, timeout: 30_000 });
|
||||||
|
|
||||||
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
@@ -924,8 +924,8 @@ describe("constrainSourceId truncation (hyphen-boundary cut)", () => {
|
|||||||
const gstackHome = join(home, ".gstack");
|
const gstackHome = join(home, ".gstack");
|
||||||
mkdirSync(gstackHome, { recursive: true });
|
mkdirSync(gstackHome, { recursive: true });
|
||||||
const repo = mkdtempSync(join(tmpdir(), "gstack-https-period-"));
|
const repo = mkdtempSync(join(tmpdir(), "gstack-https-period-"));
|
||||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
|
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||||
spawnSync("git", ["remote", "add", "origin", "https://github.com/foo/bar.git"], { cwd: repo });
|
spawnSync("git", ["remote", "add", "origin", "https://github.com/foo/bar.git"], { cwd: repo, timeout: 30_000 });
|
||||||
|
|
||||||
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const ROOT = path.resolve(__dirname, '..');
|
|||||||
|
|
||||||
function trackedTestFiles(): string[] {
|
function trackedTestFiles(): string[] {
|
||||||
const out = spawnSync('git', ['ls-files', '*.test.ts'], {
|
const out = spawnSync('git', ['ls-files', '*.test.ts'], {
|
||||||
cwd: ROOT, encoding: 'utf-8',
|
cwd: ROOT, encoding: 'utf-8', timeout: 30_000,
|
||||||
});
|
});
|
||||||
if (out.status !== 0) throw new Error(`git ls-files failed: ${out.stderr}`);
|
if (out.status !== 0) throw new Error(`git ls-files failed: ${out.stderr}`);
|
||||||
return out.stdout.split('\n').filter(Boolean);
|
return out.stdout.split('\n').filter(Boolean);
|
||||||
|
|||||||
@@ -500,6 +500,7 @@ describe("gstack-memory-ingest writer (gbrain v0.20+ batch `import` interface)",
|
|||||||
const POLICY = join(import.meta.dir, "..", "bin", "gstack-gbrain-repo-policy");
|
const POLICY = join(import.meta.dir, "..", "bin", "gstack-gbrain-repo-policy");
|
||||||
const seeded = spawnSync("bash", [POLICY, "set", "_unattributed", "deny"], {
|
const seeded = spawnSync("bash", [POLICY, "set", "_unattributed", "deny"], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
|
timeout: 30_000,
|
||||||
env: { ...process.env, HOME: home, GSTACK_HOME: gstackHome },
|
env: { ...process.env, HOME: home, GSTACK_HOME: gstackHome },
|
||||||
});
|
});
|
||||||
expect(seeded.status).toBe(0);
|
expect(seeded.status).toBe(0);
|
||||||
@@ -622,6 +623,7 @@ esac
|
|||||||
expect(existsSync(stagingCopy)).toBe(true);
|
expect(existsSync(stagingCopy)).toBe(true);
|
||||||
const findMd = spawnSync("find", [stagingCopy, "-name", "*.md", "-type", "f"], {
|
const findMd = spawnSync("find", [stagingCopy, "-name", "*.md", "-type", "f"], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
const mdPaths = (findMd.stdout || "").trim().split("\n").filter(Boolean);
|
const mdPaths = (findMd.stdout || "").trim().split("\n").filter(Boolean);
|
||||||
expect(mdPaths.length).toBeGreaterThan(0);
|
expect(mdPaths.length).toBeGreaterThan(0);
|
||||||
@@ -685,6 +687,7 @@ esac
|
|||||||
// walk to find a .md and read its head.)
|
// walk to find a .md and read its head.)
|
||||||
const findMd = spawnSync("find", [stagingCopy, "-name", "*.md", "-type", "f"], {
|
const findMd = spawnSync("find", [stagingCopy, "-name", "*.md", "-type", "f"], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
const mdPaths = (findMd.stdout || "").trim().split("\n").filter(Boolean);
|
const mdPaths = (findMd.stdout || "").trim().split("\n").filter(Boolean);
|
||||||
expect(mdPaths.length).toBeGreaterThan(0);
|
expect(mdPaths.length).toBeGreaterThan(0);
|
||||||
@@ -913,8 +916,8 @@ describe("#2394: probe applies the same attribution gate as prepare", () => {
|
|||||||
function makeAttributableCwd(home: string): string {
|
function makeAttributableCwd(home: string): string {
|
||||||
const repo = join(home, "work", "attributable-repo");
|
const repo = join(home, "work", "attributable-repo");
|
||||||
mkdirSync(repo, { recursive: true });
|
mkdirSync(repo, { recursive: true });
|
||||||
spawnSync("git", ["-C", repo, "init", "-q"], { encoding: "utf-8" });
|
spawnSync("git", ["-C", repo, "init", "-q"], { encoding: "utf-8", timeout: 30_000 });
|
||||||
spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/foo/bar.git"], { encoding: "utf-8" });
|
spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/foo/bar.git"], { encoding: "utf-8", timeout: 30_000 });
|
||||||
return repo;
|
return repo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -993,8 +996,8 @@ describe("#2394: probe applies the same attribution gate as prepare", () => {
|
|||||||
mkdirSync(gstackHome, { recursive: true });
|
mkdirSync(gstackHome, { recursive: true });
|
||||||
const attributableCwd = join(home, "work", "attributable-repo");
|
const attributableCwd = join(home, "work", "attributable-repo");
|
||||||
mkdirSync(attributableCwd, { recursive: true });
|
mkdirSync(attributableCwd, { recursive: true });
|
||||||
spawnSync("git", ["-C", attributableCwd, "init", "-q"], { encoding: "utf-8" });
|
spawnSync("git", ["-C", attributableCwd, "init", "-q"], { encoding: "utf-8", timeout: 30_000 });
|
||||||
spawnSync("git", ["-C", attributableCwd, "remote", "add", "origin", "https://github.com/foo/bar.git"], { encoding: "utf-8" });
|
spawnSync("git", ["-C", attributableCwd, "remote", "add", "origin", "https://github.com/foo/bar.git"], { encoding: "utf-8", timeout: 30_000 });
|
||||||
|
|
||||||
const ts = new Date().toISOString();
|
const ts = new Date().toISOString();
|
||||||
const cwdLine = `{"type":"user","message":{"role":"user","content":"hello"},"timestamp":"${ts}","cwd":"${attributableCwd.replace(/\\/g, "\\\\")}"}\n`;
|
const cwdLine = `{"type":"user","message":{"role":"user","content":"hello"},"timestamp":"${ts}","cwd":"${attributableCwd.replace(/\\/g, "\\\\")}"}\n`;
|
||||||
@@ -1077,8 +1080,8 @@ describe("#2392: transcript ingest honors per-remote trust policy", () => {
|
|||||||
function makeRepoWithRemote(home: string, name: string, remoteUrl: string): string {
|
function makeRepoWithRemote(home: string, name: string, remoteUrl: string): string {
|
||||||
const repo = join(home, "work", name);
|
const repo = join(home, "work", name);
|
||||||
mkdirSync(repo, { recursive: true });
|
mkdirSync(repo, { recursive: true });
|
||||||
spawnSync("git", ["-C", repo, "init", "-q"], { encoding: "utf-8" });
|
spawnSync("git", ["-C", repo, "init", "-q"], { encoding: "utf-8", timeout: 30_000 });
|
||||||
spawnSync("git", ["-C", repo, "remote", "add", "origin", remoteUrl], { encoding: "utf-8" });
|
spawnSync("git", ["-C", repo, "remote", "add", "origin", remoteUrl], { encoding: "utf-8", timeout: 30_000 });
|
||||||
return repo;
|
return repo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1095,6 +1098,7 @@ describe("#2392: transcript ingest honors per-remote trust policy", () => {
|
|||||||
function setPolicy(gstackHome: string, url: string, tier: string): void {
|
function setPolicy(gstackHome: string, url: string, tier: string): void {
|
||||||
const r = spawnSync(POLICY_BIN, ["set", url, tier], {
|
const r = spawnSync(POLICY_BIN, ["set", url, tier], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
|
timeout: 30_000,
|
||||||
env: { ...process.env, GSTACK_HOME: gstackHome },
|
env: { ...process.env, GSTACK_HOME: gstackHome },
|
||||||
});
|
});
|
||||||
expect(r.status).toBe(0);
|
expect(r.status).toBe(0);
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ describe("default-base detection (no --base)", () => {
|
|||||||
function runWithoutBase(cwd: string): { exitCode: number; parsed: any } {
|
function runWithoutBase(cwd: string): { exitCode: number; parsed: any } {
|
||||||
const proc = Bun.spawnSync(
|
const proc = Bun.spawnSync(
|
||||||
["bun", "run", SCRIPT, "--bump", "patch", "--workspace-root", "null"],
|
["bun", "run", SCRIPT, "--bump", "patch", "--workspace-root", "null"],
|
||||||
{ cwd },
|
{ cwd, timeout: 30_000 },
|
||||||
);
|
);
|
||||||
const out = new TextDecoder().decode(proc.stdout);
|
const out = new TextDecoder().decode(proc.stdout);
|
||||||
return { exitCode: proc.exitCode, parsed: JSON.parse(out) };
|
return { exitCode: proc.exitCode, parsed: JSON.parse(out) };
|
||||||
@@ -403,11 +403,11 @@ describe("offline output contract (what /ship branches on, #2545)", () => {
|
|||||||
// host:"unknown" on CI) while keeping ls-remote/fetch fully local.
|
// host:"unknown" on CI) while keeping ls-remote/fetch fully local.
|
||||||
const bare = join(root, "github.com", "origin.git");
|
const bare = join(root, "github.com", "origin.git");
|
||||||
mkdirSync(bare, { recursive: true });
|
mkdirSync(bare, { recursive: true });
|
||||||
Bun.spawnSync(["git", "init", "-q", "--bare", "-b", "main", bare]);
|
Bun.spawnSync(["git", "init", "-q", "--bare", "-b", "main", bare], { timeout: 30_000 });
|
||||||
const work = join(root, "work");
|
const work = join(root, "work");
|
||||||
mkdirSync(work);
|
mkdirSync(work);
|
||||||
const git = (...args: string[]) =>
|
const git = (...args: string[]) =>
|
||||||
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd: work });
|
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd: work, timeout: 30_000 });
|
||||||
git("init", "-q", "-b", "main");
|
git("init", "-q", "-b", "main");
|
||||||
writeFileSync(join(work, "VERSION"), "1.0.0.0\n");
|
writeFileSync(join(work, "VERSION"), "1.0.0.0\n");
|
||||||
git("add", "-A");
|
git("add", "-A");
|
||||||
@@ -427,7 +427,7 @@ describe("offline output contract (what /ship branches on, #2545)", () => {
|
|||||||
const proc = Bun.spawnSync(
|
const proc = Bun.spawnSync(
|
||||||
["bun", "run", NEXTVER, "--base", "main",
|
["bun", "run", NEXTVER, "--base", "main",
|
||||||
"--bump", "patch", "--current-version", "1.0.0.0", "--workspace-root", "null"],
|
"--bump", "patch", "--current-version", "1.0.0.0", "--workspace-root", "null"],
|
||||||
{ cwd: work, env: { ...process.env, PATH: `${stubDir}:${process.env.PATH}` } },
|
{ cwd: work, env: { ...process.env, PATH: `${stubDir}:${process.env.PATH}` }, timeout: 30_000 },
|
||||||
);
|
);
|
||||||
rmSync(stubDir, { recursive: true, force: true });
|
rmSync(stubDir, { recursive: true, force: true });
|
||||||
rmSync(root, { recursive: true, force: true });
|
rmSync(root, { recursive: true, force: true });
|
||||||
@@ -454,7 +454,7 @@ describe("offline output contract (what /ship branches on, #2545)", () => {
|
|||||||
const proc = Bun.spawnSync(
|
const proc = Bun.spawnSync(
|
||||||
["bun", "run", NEXTVER, "--base", "main",
|
["bun", "run", NEXTVER, "--base", "main",
|
||||||
"--bump", "patch", "--current-version", "1.0.0.0", "--workspace-root", "null"],
|
"--bump", "patch", "--current-version", "1.0.0.0", "--workspace-root", "null"],
|
||||||
{ cwd: work, env: { ...process.env, PATH: `${stubDir}:${process.env.PATH}` } },
|
{ cwd: work, env: { ...process.env, PATH: `${stubDir}:${process.env.PATH}` }, timeout: 30_000 },
|
||||||
);
|
);
|
||||||
rmSync(stubDir, { recursive: true, force: true });
|
rmSync(stubDir, { recursive: true, force: true });
|
||||||
rmSync(root, { recursive: true, force: true });
|
rmSync(root, { recursive: true, force: true });
|
||||||
@@ -475,7 +475,7 @@ describe("fetchGitClaimed (offline allocation — the anti-duplicate fallback, #
|
|||||||
// (plus three earlier pairs found in the same audit). Git knows what the API
|
// (plus three earlier pairs found in the same audit). Git knows what the API
|
||||||
// was asked for, so offline now degrades the QUEUE VIEW, not the ALLOCATION.
|
// was asked for, so offline now degrades the QUEUE VIEW, not the ALLOCATION.
|
||||||
function git(cwd: string, ...args: string[]) {
|
function git(cwd: string, ...args: string[]) {
|
||||||
return Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd });
|
return Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd, timeout: 30_000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
function fixture(): string {
|
function fixture(): string {
|
||||||
@@ -600,7 +600,7 @@ describe("fetchGitClaimed — non-mutating live remote query (ls-remote first)",
|
|||||||
// remote's LIVE branch list with zero local mutation — a path/file remote
|
// remote's LIVE branch list with zero local mutation — a path/file remote
|
||||||
// answers it offline, which is exactly what these fixtures use.
|
// answers it offline, which is exactly what these fixtures use.
|
||||||
function git(cwd: string, ...args: string[]) {
|
function git(cwd: string, ...args: string[]) {
|
||||||
return Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd });
|
return Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd, timeout: 30_000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local origin with: main (0.1.66.0), sibling (0.1.67.0, live claim), and
|
// Local origin with: main (0.1.66.0), sibling (0.1.67.0, live claim), and
|
||||||
@@ -718,7 +718,7 @@ describe("fetchGitClaimed — unfetched live claims (G2: ls-remote advertises SH
|
|||||||
// VERSION reads fail. The old `continue` silently dropped that LIVE claim —
|
// VERSION reads fail. The old `continue` silently dropped that LIVE claim —
|
||||||
// the exact duplicate-allocation this fallback exists to prevent.
|
// the exact duplicate-allocation this fallback exists to prevent.
|
||||||
function git(cwd: string, ...args: string[]) {
|
function git(cwd: string, ...args: string[]) {
|
||||||
return Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd });
|
return Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd, timeout: 30_000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
function cloneFixture(): { root: string; origin: string; clone: string } {
|
function cloneFixture(): { root: string; origin: string; clone: string } {
|
||||||
@@ -908,14 +908,14 @@ describe("width pinned on failed base read (3-digit repos)", () => {
|
|||||||
// the base read fails too, which is the path under test.
|
// the base read fails too, which is the path under test.
|
||||||
writeFileSync(join(stubDir, "gh"), "#!/bin/sh\nexit 1\n", { mode: 0o755 });
|
writeFileSync(join(stubDir, "gh"), "#!/bin/sh\nexit 1\n", { mode: 0o755 });
|
||||||
writeFileSync(join(stubDir, "glab"), "#!/bin/sh\nexit 1\n", { mode: 0o755 });
|
writeFileSync(join(stubDir, "glab"), "#!/bin/sh\nexit 1\n", { mode: 0o755 });
|
||||||
Bun.spawnSync(["git", "init", "-q", "-b", "main"], { cwd: dir });
|
Bun.spawnSync(["git", "init", "-q", "-b", "main"], { cwd: dir, timeout: 30_000 });
|
||||||
writeFileSync(join(dir, "VERSION"), "0.99.2\n");
|
writeFileSync(join(dir, "VERSION"), "0.99.2\n");
|
||||||
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", "add", "-A"], { cwd: dir });
|
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", "add", "-A"], { cwd: dir, timeout: 30_000 });
|
||||||
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "init"], { cwd: dir });
|
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "init"], { cwd: dir, timeout: 30_000 });
|
||||||
|
|
||||||
const proc = Bun.spawnSync(
|
const proc = Bun.spawnSync(
|
||||||
["bun", "run", SCRIPT, "--base", "main", "--bump", "patch", "--workspace-root", "null"],
|
["bun", "run", SCRIPT, "--base", "main", "--bump", "patch", "--workspace-root", "null"],
|
||||||
{ cwd: dir, env: { ...process.env, PATH: `${stubDir}:${process.env.PATH}` } },
|
{ cwd: dir, env: { ...process.env, PATH: `${stubDir}:${process.env.PATH}` }, timeout: 30_000 },
|
||||||
);
|
);
|
||||||
const out = JSON.parse(new TextDecoder().decode(proc.stdout));
|
const out = JSON.parse(new TextDecoder().decode(proc.stdout));
|
||||||
// Zero base at the repo's OWN width — never "0.0.0.0" in a 3-digit repo.
|
// Zero base at the repo's OWN width — never "0.0.0.0" in a 3-digit repo.
|
||||||
@@ -946,7 +946,7 @@ describe("integration (smoke)", () => {
|
|||||||
"1.6.3.0",
|
"1.6.3.0",
|
||||||
"--workspace-root",
|
"--workspace-root",
|
||||||
"null", // skip sibling scan in CI
|
"null", // skip sibling scan in CI
|
||||||
]);
|
], { timeout: 30_000 });
|
||||||
const out = new TextDecoder().decode(proc.stdout);
|
const out = new TextDecoder().decode(proc.stdout);
|
||||||
const parsed = JSON.parse(out);
|
const parsed = JSON.parse(out);
|
||||||
expect(parsed).toHaveProperty("version");
|
expect(parsed).toHaveProperty("version");
|
||||||
@@ -976,7 +976,7 @@ describe("integration (smoke)", () => {
|
|||||||
"null",
|
"null",
|
||||||
"--version-path",
|
"--version-path",
|
||||||
"Tinas Second Brain/health-tracker/VERSION",
|
"Tinas Second Brain/health-tracker/VERSION",
|
||||||
]);
|
], { timeout: 30_000 });
|
||||||
const out = new TextDecoder().decode(proc.stdout);
|
const out = new TextDecoder().decode(proc.stdout);
|
||||||
const parsed = JSON.parse(out);
|
const parsed = JSON.parse(out);
|
||||||
expect(parsed).toHaveProperty("version_path", "Tinas Second Brain/health-tracker/VERSION");
|
expect(parsed).toHaveProperty("version_path", "Tinas Second Brain/health-tracker/VERSION");
|
||||||
@@ -1003,7 +1003,7 @@ describe("fetchGitClaimed — laundered ls-remote (exit 0, empty output) is neve
|
|||||||
chmodSync(join(stubDir, "git"), 0o755);
|
chmodSync(join(stubDir, "git"), 0o755);
|
||||||
|
|
||||||
const git = (cwd: string, ...args: string[]) =>
|
const git = (cwd: string, ...args: string[]) =>
|
||||||
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd });
|
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd, timeout: 30_000 });
|
||||||
|
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
const oldPath = process.env.PATH;
|
const oldPath = process.env.PATH;
|
||||||
@@ -1052,7 +1052,7 @@ describe("fetchGitClaimed — laundered ls-remote (exit 0, empty output) is neve
|
|||||||
chmodSync(join(stubDir, "git"), 0o755);
|
chmodSync(join(stubDir, "git"), 0o755);
|
||||||
|
|
||||||
const git = (cwd: string, ...args: string[]) =>
|
const git = (cwd: string, ...args: string[]) =>
|
||||||
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd });
|
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd, timeout: 30_000 });
|
||||||
|
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
const oldPath = process.env.PATH;
|
const oldPath = process.env.PATH;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ function run(env: Record<string, string | undefined>): Record<string, string> {
|
|||||||
const result = spawnSync('bash', [BIN], {
|
const result = spawnSync('bash', [BIN], {
|
||||||
env: { PATH: process.env.PATH, USERPROFILE: '', ...env } as Record<string, string>,
|
env: { PATH: process.env.PATH, USERPROFILE: '', ...env } as Record<string, string>,
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
if (result.status !== 0) {
|
if (result.status !== 0) {
|
||||||
throw new Error(`gstack-paths failed (status ${result.status}): ${result.stderr}`);
|
throw new Error(`gstack-paths failed (status ${result.status}): ${result.stderr}`);
|
||||||
@@ -117,6 +118,7 @@ describe('gstack-paths', () => {
|
|||||||
{
|
{
|
||||||
env: { PATH: process.env.PATH, USERPROFILE: '', ...env } as Record<string, string>,
|
env: { PATH: process.env.PATH, USERPROFILE: '', ...env } as Record<string, string>,
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (result.status !== 0) {
|
if (result.status !== 0) {
|
||||||
@@ -159,6 +161,7 @@ describe('gstack-paths', () => {
|
|||||||
const result = spawnSync('bash', [BIN], {
|
const result = spawnSync('bash', [BIN], {
|
||||||
env: { PATH: process.env.PATH, USERPROFILE: '', HOME: '/tmp/h' } as Record<string, string>,
|
env: { PATH: process.env.PATH, USERPROFILE: '', HOME: '/tmp/h' } as Record<string, string>,
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
const lines = result.stdout.split('\n').filter(Boolean);
|
const lines = result.stdout.split('\n').filter(Boolean);
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ function run(payload: string): { stdout: string; stderr: string; status: number
|
|||||||
env: { ...process.env, GSTACK_HOME: tmpHome },
|
env: { ...process.env, GSTACK_HOME: tmpHome },
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
stdout: res.stdout ?? '',
|
stdout: res.stdout ?? '',
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ function run(...args: string[]): { stdout: string; stderr: string; status: numbe
|
|||||||
env: { ...process.env, GSTACK_HOME: tmpHome },
|
env: { ...process.env, GSTACK_HOME: tmpHome },
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
stdout: res.stdout ?? '',
|
stdout: res.stdout ?? '',
|
||||||
@@ -45,6 +46,7 @@ function runWithStdin(input: string, ...args: string[]): { stdout: string; stder
|
|||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
input,
|
input,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
stdout: res.stdout ?? '',
|
stdout: res.stdout ?? '',
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ function run(
|
|||||||
): { code: number; stdout: string; stderr: string } {
|
): { code: number; stdout: string; stderr: string } {
|
||||||
const proc = Bun.spawnSync(["bun", BIN, ...args], {
|
const proc = Bun.spawnSync(["bun", BIN, ...args], {
|
||||||
stdin: Buffer.from(stdin),
|
stdin: Buffer.from(stdin),
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
code: proc.exitCode,
|
code: proc.exitCode,
|
||||||
@@ -88,7 +89,7 @@ describe("gstack-redact --from-file", () => {
|
|||||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "redact-file-"));
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "redact-file-"));
|
||||||
const f = path.join(dir, "spec.md");
|
const f = path.join(dir, "spec.md");
|
||||||
fs.writeFileSync(f, "leaked ghp_" + "a".repeat(36));
|
fs.writeFileSync(f, "leaked ghp_" + "a".repeat(36));
|
||||||
const proc = Bun.spawnSync(["bun", BIN, "--from-file", f, "--json"]);
|
const proc = Bun.spawnSync(["bun", BIN, "--from-file", f, "--json"], { timeout: 30_000 });
|
||||||
const parsed = JSON.parse(proc.stdout.toString());
|
const parsed = JSON.parse(proc.stdout.toString());
|
||||||
expect(parsed.findings[0].id).toBe("github.pat");
|
expect(parsed.findings[0].id).toBe("github.pat");
|
||||||
fs.rmSync(dir, { recursive: true, force: true });
|
fs.rmSync(dir, { recursive: true, force: true });
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ function run(command: string, args: string[], cwd: string, home: string): Comman
|
|||||||
cwd,
|
cwd,
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
env: { ...process.env, HOME: home, GSTACK_HOME: path.join(home, '.gstack') },
|
env: { ...process.env, HOME: home, GSTACK_HOME: path.join(home, '.gstack') },
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
stdout: result.stdout ?? '',
|
stdout: result.stdout ?? '',
|
||||||
|
|||||||
@@ -919,6 +919,7 @@ describe('prune-stale', () => {
|
|||||||
const live = mkCanon(tmpDir, 'live-worktree');
|
const live = mkCanon(tmpDir, 'live-worktree');
|
||||||
execSync(`'${path.join(ROOT, 'bin', 'gstack-config')}' set plan_tune_hooks no`, {
|
execSync(`'${path.join(ROOT, 'bin', 'gstack-config')}' set plan_tune_hooks no`, {
|
||||||
env: { ...process.env, GSTACK_STATE_ROOT: tmpDir },
|
env: { ...process.env, GSTACK_STATE_ROOT: tmpDir },
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
fs.writeFileSync(settingsFile, JSON.stringify({
|
fs.writeFileSync(settingsFile, JSON.stringify({
|
||||||
hooks: {
|
hooks: {
|
||||||
|
|||||||
@@ -172,12 +172,13 @@ describe('gstack-slug — outermost project-root resolution', () => {
|
|||||||
// succeeds. (The script's step 2 reads the remote when there's no cache.)
|
// succeeds. (The script's step 2 reads the remote when there's no cache.)
|
||||||
const gitInit = spawnSync('git', ['init', '-q', '-b', 'main', projectRoot], {
|
const gitInit = spawnSync('git', ['init', '-q', '-b', 'main', projectRoot], {
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(gitInit.status).toBe(0);
|
expect(gitInit.status).toBe(0);
|
||||||
const gitRemote = spawnSync(
|
const gitRemote = spawnSync(
|
||||||
'git',
|
'git',
|
||||||
['-C', projectRoot, 'remote', 'add', 'origin', 'https://github.com/foo/bar.git'],
|
['-C', projectRoot, 'remote', 'add', 'origin', 'https://github.com/foo/bar.git'],
|
||||||
{ encoding: 'utf8' },
|
{ encoding: 'utf8', timeout: 30_000 },
|
||||||
);
|
);
|
||||||
expect(gitRemote.status).toBe(0);
|
expect(gitRemote.status).toBe(0);
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ function runSlug(cwd: string, home: string) {
|
|||||||
return spawnSync([SLUG_BIN], {
|
return spawnSync([SLUG_BIN], {
|
||||||
cwd,
|
cwd,
|
||||||
env: { ...process.env, HOME: home },
|
env: { ...process.env, HOME: home },
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +78,7 @@ describe('slug cache hygiene', () => {
|
|||||||
const r = spawnSync(['bash', SLUG_BIN], {
|
const r = spawnSync(['bash', SLUG_BIN], {
|
||||||
cwd: os.tmpdir(),
|
cwd: os.tmpdir(),
|
||||||
env: { ...process.env, GSTACK_HOME: home, GSTACK_PROJECT_SLUG: 'override-slug' },
|
env: { ...process.env, GSTACK_HOME: home, GSTACK_PROJECT_SLUG: 'override-slug' },
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(r.stdout.toString()).toContain('SLUG=override-slug');
|
expect(r.stdout.toString()).toContain('SLUG=override-slug');
|
||||||
expect(fs.existsSync(path.join(home, 'slug-cache'))).toBe(false);
|
expect(fs.existsSync(path.join(home, 'slug-cache'))).toBe(false);
|
||||||
@@ -95,6 +97,7 @@ describe('slug cache hygiene', () => {
|
|||||||
const r = spawnSync(['bash', SLUG_BIN], {
|
const r = spawnSync(['bash', SLUG_BIN], {
|
||||||
cwd: os.tmpdir(),
|
cwd: os.tmpdir(),
|
||||||
env: { ...ambient, GSTACK_HOME: home },
|
env: { ...ambient, GSTACK_HOME: home },
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(r.exitCode).toBe(0);
|
expect(r.exitCode).toBe(0);
|
||||||
const entries = fs.readdirSync(path.join(home, 'slug-cache'));
|
const entries = fs.readdirSync(path.join(home, 'slug-cache'));
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ function runBin(
|
|||||||
env: cleaned,
|
env: cleaned,
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
stdout: res.stdout ?? '',
|
stdout: res.stdout ?? '',
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ describe('gstack-team-init required: PreToolUse hook schema (#2413)', () => {
|
|||||||
const stdout = execSync(`bash "${hookPath}"`, {
|
const stdout = execSync(`bash "${hookPath}"`, {
|
||||||
env: { ...process.env, HOME: home },
|
env: { ...process.env, HOME: home },
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return { status: 0, stdout, stderr: '' };
|
return { status: 0, stdout, stderr: '' };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ function run(opts: { env?: Record<string, string> } = {}) {
|
|||||||
env,
|
env,
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
cwd: tmpHome,
|
cwd: tmpHome,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ function run(opts: { path?: string } = {}) {
|
|||||||
env,
|
env,
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
cwd: tmpHome,
|
cwd: tmpHome,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ export function verboseSkill(gitRef = 'ab66193e^'): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function execGit(args: string[]): string {
|
function execGit(args: string[]): string {
|
||||||
const r = spawnSync('git', args, { cwd: ROOT, encoding: 'utf-8', maxBuffer: 64 * 1024 * 1024 });
|
const r = spawnSync('git', args, { cwd: ROOT, encoding: 'utf-8', maxBuffer: 64 * 1024 * 1024, timeout: 30_000 });
|
||||||
if (r.status !== 0) throw new Error(`git ${args.join(' ')} failed: ${r.stderr}`);
|
if (r.status !== 0) throw new Error(`git ${args.join(' ')} failed: ${r.stderr}`);
|
||||||
return r.stdout;
|
return r.stdout;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,8 +141,8 @@ function discoverEvalCoverage(repoRoot: string, skills: string[]): {
|
|||||||
|
|
||||||
function getGitInfo(repoRoot: string): { commit: string; branch: string } {
|
function getGitInfo(repoRoot: string): { commit: string; branch: string } {
|
||||||
try {
|
try {
|
||||||
const commit = execSync('git rev-parse --short HEAD', { cwd: repoRoot, encoding: 'utf-8' }).trim();
|
const commit = execSync('git rev-parse --short HEAD', { cwd: repoRoot, encoding: 'utf-8', timeout: 30_000 }).trim();
|
||||||
const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: repoRoot, encoding: 'utf-8' }).trim();
|
const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: repoRoot, encoding: 'utf-8', timeout: 30_000 }).trim();
|
||||||
return { commit, branch };
|
return { commit, branch };
|
||||||
} catch {
|
} catch {
|
||||||
return { commit: 'unknown', branch: 'unknown' };
|
return { commit: 'unknown', branch: 'unknown' };
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ export async function runCodexSkill(opts: {
|
|||||||
const name = skillName || path.basename(skillDir) || 'gstack';
|
const name = skillName || path.basename(skillDir) || 'gstack';
|
||||||
|
|
||||||
// Check if codex binary exists
|
// Check if codex binary exists
|
||||||
const whichResult = Bun.spawnSync(['which', 'codex']);
|
const whichResult = Bun.spawnSync(['which', 'codex'], { timeout: 30_000 });
|
||||||
if (whichResult.exitCode !== 0) {
|
if (whichResult.exitCode !== 0) {
|
||||||
return {
|
return {
|
||||||
output: 'SKIP: codex binary not found',
|
output: 'SKIP: codex binary not found',
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export async function runGeminiSkill(opts: {
|
|||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
// Check if gemini binary exists
|
// Check if gemini binary exists
|
||||||
const whichResult = Bun.spawnSync(['which', 'gemini']);
|
const whichResult = Bun.spawnSync(['which', 'gemini'], { timeout: 30_000 });
|
||||||
if (whichResult.exitCode !== 0) {
|
if (whichResult.exitCode !== 0) {
|
||||||
return {
|
return {
|
||||||
output: 'SKIP: gemini binary not found',
|
output: 'SKIP: gemini binary not found',
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const MAX_BODY = 64 * 1024;
|
|||||||
const GUARD_RE = /^\s*(?::\s*"\$\{)?BASH_COMPAT(?:[:=]|\}")/m;
|
const GUARD_RE = /^\s*(?::\s*"\$\{)?BASH_COMPAT(?:[:=]|\}")/m;
|
||||||
|
|
||||||
function trackedShellScripts(): string[] {
|
function trackedShellScripts(): string[] {
|
||||||
const out = execSync('git ls-files', { cwd: ROOT, encoding: 'utf-8', maxBuffer: 32 * 1024 * 1024 });
|
const out = execSync('git ls-files', { cwd: ROOT, encoding: 'utf-8', maxBuffer: 32 * 1024 * 1024, timeout: 30_000 });
|
||||||
return out
|
return out
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map((s) => s.trim())
|
.map((s) => s.trim())
|
||||||
@@ -88,6 +88,7 @@ describe('heredoc pipe-deadlock guard', () => {
|
|||||||
test('the guard actually moves the body off the pipe', () => {
|
test('the guard actually moves the body off the pipe', () => {
|
||||||
const bash = spawnSync('bash', ['-c', 'echo "${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}"'], {
|
const bash = spawnSync('bash', ['-c', 'echo "${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}"'], {
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
const version = (bash.stdout ?? '').trim();
|
const version = (bash.stdout ?? '').trim();
|
||||||
const [maj, min] = version.split('.').map((n) => parseInt(n, 10));
|
const [maj, min] = version.split('.').map((n) => parseInt(n, 10));
|
||||||
@@ -102,6 +103,7 @@ describe('heredoc pipe-deadlock guard', () => {
|
|||||||
// probe would answer OTHER for an unobservable fd. Skip rather than fail.
|
// probe would answer OTHER for an unobservable fd. Skip rather than fail.
|
||||||
const devStdin = spawnSync('bash', ['-c', '[ -e /dev/stdin ] && echo yes || echo no'], {
|
const devStdin = spawnSync('bash', ['-c', '[ -e /dev/stdin ] && echo yes || echo no'], {
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
if ((devStdin.stdout ?? '').trim() !== 'yes') return;
|
if ((devStdin.stdout ?? '').trim() !== 'yes') return;
|
||||||
|
|
||||||
@@ -114,7 +116,7 @@ $body
|
|||||||
EOF
|
EOF
|
||||||
`;
|
`;
|
||||||
const run = (guard: string) =>
|
const run = (guard: string) =>
|
||||||
(spawnSync('bash', ['-c', probe(guard)], { encoding: 'utf-8' }).stdout ?? '').trim();
|
(spawnSync('bash', ['-c', probe(guard)], { encoding: 'utf-8', timeout: 30_000 }).stdout ?? '').trim();
|
||||||
|
|
||||||
expect(run('')).toBe('PIPE');
|
expect(run('')).toBe('PIPE');
|
||||||
expect(run('BASH_COMPAT=50')).toBe('TEMPFILE');
|
expect(run('BASH_COMPAT=50')).toBe('TEMPFILE');
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ describe('host-config-export.ts CLI', () => {
|
|||||||
|
|
||||||
function run(...args: string[]): { stdout: string; stderr: string; exitCode: number } {
|
function run(...args: string[]): { stdout: string; stderr: string; exitCode: number } {
|
||||||
const result = Bun.spawnSync(['bun', 'run', EXPORT_SCRIPT, ...args], {
|
const result = Bun.spawnSync(['bun', 'run', EXPORT_SCRIPT, ...args], {
|
||||||
cwd: ROOT, stdout: 'pipe', stderr: 'pipe',
|
cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 30_000,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
stdout: result.stdout.toString().trim(),
|
stdout: result.stdout.toString().trim(),
|
||||||
@@ -445,7 +445,7 @@ describe('golden-file regression', () => {
|
|||||||
for (const host of ['codex', 'factory']) {
|
for (const host of ['codex', 'factory']) {
|
||||||
const result = Bun.spawnSync(
|
const result = Bun.spawnSync(
|
||||||
['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', host, '--out-dir', GOLDEN_OUT],
|
['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', host, '--out-dir', GOLDEN_OUT],
|
||||||
{ cwd: ROOT },
|
{ cwd: ROOT, timeout: 120_000 },
|
||||||
);
|
);
|
||||||
if (result.exitCode !== 0) {
|
if (result.exitCode !== 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ describe('gstack-ios-qa-regen', () => {
|
|||||||
const workDir = mkdtempSync(join(tmpdir(), 'ios-qa-regen-'));
|
const workDir = mkdtempSync(join(tmpdir(), 'ios-qa-regen-'));
|
||||||
workDirs.push(workDir);
|
workDirs.push(workDir);
|
||||||
const { launcher } = copyIntoFakeInstall(workDir);
|
const { launcher } = copyIntoFakeInstall(workDir);
|
||||||
const result = spawnSync('bash', [launcher, '--app-source', workDir], { encoding: 'utf8' });
|
const result = spawnSync('bash', [launcher, '--app-source', workDir], { encoding: 'utf8', timeout: 30_000 });
|
||||||
|
|
||||||
expect(result.status).toBe(2);
|
expect(result.status).toBe(2);
|
||||||
expect(result.stderr).toContain('both --app-source and --bridge-dir are required');
|
expect(result.stderr).toContain('both --app-source and --bridge-dir are required');
|
||||||
@@ -134,6 +134,7 @@ describe('gstack-ios-qa-regen', () => {
|
|||||||
], {
|
], {
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
env: { ...process.env, PATH: `${fakeBin}:${process.env.PATH ?? ''}` },
|
env: { ...process.env, PATH: `${fakeBin}:${process.env.PATH ?? ''}` },
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.status).toBe(17);
|
expect(result.status).toBe(17);
|
||||||
@@ -184,7 +185,7 @@ final class AppState {
|
|||||||
GEN_ACCESSORS_REV: 'regen-test',
|
GEN_ACCESSORS_REV: 'regen-test',
|
||||||
};
|
};
|
||||||
const args = [launcher, '--app-source', appSource, '--bridge-dir', bridgeDir];
|
const args = [launcher, '--app-source', appSource, '--bridge-dir', bridgeDir];
|
||||||
const first = spawnSync('bash', args, { encoding: 'utf8', env });
|
const first = spawnSync('bash', args, { encoding: 'utf8', env, timeout: 30_000 });
|
||||||
expect(first.status).toBe(0);
|
expect(first.status).toBe(0);
|
||||||
expect(first.stderr).toBe('');
|
expect(first.stderr).toBe('');
|
||||||
|
|
||||||
@@ -232,10 +233,11 @@ final class AppState {
|
|||||||
expect(installedContents).not.toContain('FORBIDDEN-STATE-SENTINEL');
|
expect(installedContents).not.toContain('FORBIDDEN-STATE-SENTINEL');
|
||||||
expect(installedContents).not.toContain('OBSOLETE-HARNESS-SENTINEL');
|
expect(installedContents).not.toContain('OBSOLETE-HARNESS-SENTINEL');
|
||||||
|
|
||||||
const swiftAvailable = spawnSync('swift', ['--version'], { encoding: 'utf8' }).status === 0;
|
const swiftAvailable = spawnSync('swift', ['--version'], { encoding: 'utf8', timeout: 30_000 }).status === 0;
|
||||||
if (swiftAvailable) {
|
if (swiftAvailable) {
|
||||||
const dump = spawnSync('swift', ['package', 'dump-package', '--package-path', bridgeDir], {
|
const dump = spawnSync('swift', ['package', 'dump-package', '--package-path', bridgeDir], {
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
expect(dump.status).toBe(0);
|
expect(dump.status).toBe(0);
|
||||||
const manifest = JSON.parse(dump.stdout) as { targets: Array<{ name: string }> };
|
const manifest = JSON.parse(dump.stdout) as { targets: Array<{ name: string }> };
|
||||||
@@ -248,7 +250,7 @@ final class AppState {
|
|||||||
|
|
||||||
const firstHash = treeHash(bridgeDir, generatedDir);
|
const firstHash = treeHash(bridgeDir, generatedDir);
|
||||||
const firstAccessorHash = accessor.match(/accessorHash: "([a-f0-9]+)"/)?.[1];
|
const firstAccessorHash = accessor.match(/accessorHash: "([a-f0-9]+)"/)?.[1];
|
||||||
const second = spawnSync('bash', args, { encoding: 'utf8', env });
|
const second = spawnSync('bash', args, { encoding: 'utf8', env, timeout: 30_000 });
|
||||||
expect(second.status).toBe(0);
|
expect(second.status).toBe(0);
|
||||||
expect(second.stderr).toBe('');
|
expect(second.stderr).toBe('');
|
||||||
expect(second.stdout).toContain('gen-accessors: cache hit');
|
expect(second.stdout).toContain('gen-accessors: cache hit');
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ function runSearch(args: string = ''): string {
|
|||||||
timeout: 15000,
|
timeout: 15000,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
return execSync(`${BIN}/gstack-learnings-search ${args}`, execOpts).trim();
|
return execSync(`${BIN}/gstack-learnings-search ${args}`, execOpts).trim(); // timeout via execOpts
|
||||||
} catch {
|
} catch {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ function runHook(stdin: object): { stdout: string; stderr: string; status: numbe
|
|||||||
input: JSON.stringify({ ...stdin, cwd: fixtureCwd }),
|
input: JSON.stringify({ ...stdin, cwd: fixtureCwd }),
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
cwd: ROOT,
|
cwd: ROOT,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
let parsed: any = null;
|
let parsed: any = null;
|
||||||
try { parsed = JSON.parse(res.stdout || '{}'); } catch {}
|
try { parsed = JSON.parse(res.stdout || '{}'); } catch {}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ function run(extraEnv: Record<string, string> = {}, input = ''): { code: number;
|
|||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
input,
|
input,
|
||||||
cwd: tmpHome,
|
cwd: tmpHome,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return { code: r.status ?? -1, stdout: r.stdout || '', stderr: r.stderr || '' };
|
return { code: r.status ?? -1, stdout: r.stdout || '', stderr: r.stderr || '' };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ function run(extraEnv: Record<string, string> = {}): {
|
|||||||
},
|
},
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
cwd: tmpHome,
|
cwd: tmpHome,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
return { code: r.status ?? -1, stdout: r.stdout || '', stderr: r.stderr || '' };
|
return { code: r.status ?? -1, stdout: r.stdout || '', stderr: r.stderr || '' };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ const SCAN_PATHS = [
|
|||||||
|
|
||||||
function grepRefs(pattern: string): string[] {
|
function grepRefs(pattern: string): string[] {
|
||||||
const args = ['-rn', '--', pattern, ...SCAN_PATHS.map((p) => path.join(ROOT, p))];
|
const args = ['-rn', '--', pattern, ...SCAN_PATHS.map((p) => path.join(ROOT, p))];
|
||||||
const r = spawnSync('grep', args, { encoding: 'utf-8' });
|
const r = spawnSync('grep', args, { encoding: 'utf-8', timeout: 30_000 });
|
||||||
// grep exits 1 when no matches — that's fine for our purposes.
|
// grep exits 1 when no matches — that's fine for our purposes.
|
||||||
const lines = (r.stdout || '').split('\n').filter((l) => l.trim().length > 0);
|
const lines = (r.stdout || '').split('\n').filter((l) => l.trim().length > 0);
|
||||||
return lines
|
return lines
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const GATE_PATTERNS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
function trackedTestFiles(): string[] {
|
function trackedTestFiles(): string[] {
|
||||||
const out = spawnSync('git', ['ls-files', '*.test.ts'], { cwd: ROOT, encoding: 'utf-8' });
|
const out = spawnSync('git', ['ls-files', '*.test.ts'], { cwd: ROOT, encoding: 'utf-8', timeout: 30_000 });
|
||||||
if (out.status !== 0) throw new Error(`git ls-files failed: ${out.stderr}`);
|
if (out.status !== 0) throw new Error(`git ls-files failed: ${out.stderr}`);
|
||||||
return out.stdout.split('\n').filter(Boolean);
|
return out.stdout.split('\n').filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ function runBin(
|
|||||||
}
|
}
|
||||||
env.GSTACK_STATE_ROOT = stateRoot;
|
env.GSTACK_STATE_ROOT = stateRoot;
|
||||||
delete env.GSTACK_HOME;
|
delete env.GSTACK_HOME;
|
||||||
const res = spawnSync(bin, args, { env, encoding: 'utf-8', cwd: ROOT });
|
const res = spawnSync(bin, args, { env, encoding: 'utf-8', cwd: ROOT, timeout: 30_000 });
|
||||||
return {
|
return {
|
||||||
stdout: res.stdout ?? '',
|
stdout: res.stdout ?? '',
|
||||||
stderr: res.stderr ?? '',
|
stderr: res.stderr ?? '',
|
||||||
|
|||||||
@@ -568,15 +568,15 @@ describe('end-to-end pipeline (binaries working together)', () => {
|
|||||||
ts: `2026-04-0${i + 1}T10:00:00Z`,
|
ts: `2026-04-0${i + 1}T10:00:00Z`,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
{ env, cwd: ROOT, encoding: 'utf-8' },
|
{ env, cwd: ROOT, encoding: 'utf-8', timeout: 30_000 },
|
||||||
);
|
);
|
||||||
expect(r.status).toBe(0);
|
expect(r.status).toBe(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const derive = spawnSync(devBin, ['--derive'], { env, cwd: ROOT, encoding: 'utf-8' });
|
const derive = spawnSync(devBin, ['--derive'], { env, cwd: ROOT, encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(derive.status).toBe(0);
|
expect(derive.status).toBe(0);
|
||||||
|
|
||||||
const profileOut = spawnSync(devBin, ['--profile'], { env, cwd: ROOT, encoding: 'utf-8' });
|
const profileOut = spawnSync(devBin, ['--profile'], { env, cwd: ROOT, encoding: 'utf-8', timeout: 30_000 });
|
||||||
const p = JSON.parse(profileOut.stdout);
|
const p = JSON.parse(profileOut.stdout);
|
||||||
expect(p.inferred.sample_size).toBe(5);
|
expect(p.inferred.sample_size).toBe(5);
|
||||||
expect(p.inferred.values.scope_appetite).toBeGreaterThan(0.5);
|
expect(p.inferred.values.scope_appetite).toBeGreaterThan(0.5);
|
||||||
@@ -598,13 +598,13 @@ describe('end-to-end pipeline (binaries working together)', () => {
|
|||||||
'--write',
|
'--write',
|
||||||
JSON.stringify({ question_id: 'fake-id', preference: 'never-ask', source: 'inline-tool-output' }),
|
JSON.stringify({ question_id: 'fake-id', preference: 'never-ask', source: 'inline-tool-output' }),
|
||||||
],
|
],
|
||||||
{ env, cwd: ROOT, encoding: 'utf-8' },
|
{ env, cwd: ROOT, encoding: 'utf-8', timeout: 30_000 },
|
||||||
);
|
);
|
||||||
expect(r.status).toBe(2);
|
expect(r.status).toBe(2);
|
||||||
expect(r.stderr).toContain('poisoning');
|
expect(r.stderr).toContain('poisoning');
|
||||||
|
|
||||||
// Verify no preference was written
|
// Verify no preference was written
|
||||||
const read = spawnSync(prefBin, ['--read'], { env, cwd: ROOT, encoding: 'utf-8' });
|
const read = spawnSync(prefBin, ['--read'], { env, cwd: ROOT, encoding: 'utf-8', timeout: 30_000 });
|
||||||
const prefs = JSON.parse(read.stdout);
|
const prefs = JSON.parse(read.stdout);
|
||||||
expect(prefs['fake-id']).toBeUndefined();
|
expect(prefs['fake-id']).toBeUndefined();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -633,11 +633,11 @@ describe('end-to-end pipeline (binaries working together)', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Migrate
|
// Migrate
|
||||||
const m = spawnSync(devBin, ['--migrate'], { env, cwd: ROOT, encoding: 'utf-8' });
|
const m = spawnSync(devBin, ['--migrate'], { env, cwd: ROOT, encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(m.status).toBe(0);
|
expect(m.status).toBe(0);
|
||||||
|
|
||||||
// Legacy shim should still return the same KEY: VALUE shape
|
// Legacy shim should still return the same KEY: VALUE shape
|
||||||
const shimOut = spawnSync(shimBin, [], { env, cwd: ROOT, encoding: 'utf-8' });
|
const shimOut = spawnSync(shimBin, [], { env, cwd: ROOT, encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(shimOut.status).toBe(0);
|
expect(shimOut.status).toBe(0);
|
||||||
expect(shimOut.stdout).toContain('SESSION_COUNT: 3');
|
expect(shimOut.stdout).toContain('SESSION_COUNT: 3');
|
||||||
expect(shimOut.stdout).toContain('TIER: welcome_back');
|
expect(shimOut.stdout).toContain('TIER: welcome_back');
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import * as path from 'path';
|
|||||||
const HELPER = path.join(import.meta.dir, '..', 'bin', 'gstack-pr-title-rewrite.sh');
|
const HELPER = path.join(import.meta.dir, '..', 'bin', 'gstack-pr-title-rewrite.sh');
|
||||||
|
|
||||||
function rewrite(version: string, title: string): { stdout: string; status: number; stderr: string } {
|
function rewrite(version: string, title: string): { stdout: string; status: number; stderr: string } {
|
||||||
const r = spawnSync(HELPER, [version, title], { encoding: 'utf-8' });
|
const r = spawnSync(HELPER, [version, title], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
return { stdout: (r.stdout ?? '').trimEnd(), status: r.status ?? -1, stderr: r.stderr ?? '' };
|
return { stdout: (r.stdout ?? '').trimEnd(), status: r.status ?? -1, stderr: r.stderr ?? '' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ describe('gstack-pr-title-rewrite', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('errors on missing args', () => {
|
test('errors on missing args', () => {
|
||||||
const r = spawnSync(HELPER, ['1.2.3.4'], { encoding: 'utf-8' });
|
const r = spawnSync(HELPER, ['1.2.3.4'], { encoding: 'utf-8', timeout: 30_000 });
|
||||||
expect(r.status).not.toBe(0);
|
expect(r.status).not.toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function detect(cwd: string): string {
|
|||||||
return execFileSync(DETECT, [], { cwd, encoding: 'utf-8', env: GIT_ENV }).trim();
|
return execFileSync(DETECT, [], { cwd, encoding: 'utf-8', env: GIT_ENV }).trim();
|
||||||
}
|
}
|
||||||
function git(cwd: string, args: string) {
|
function git(cwd: string, args: string) {
|
||||||
execSync(`git ${args}`, { cwd, env: GIT_ENV, stdio: 'ignore' });
|
execSync(`git ${args}`, { cwd, env: GIT_ENV, stdio: 'ignore', timeout: 30_000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
let tmp: string;
|
let tmp: string;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user