mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-16 09:55:29 +02:00
* fix: report health failures and coverage accurately * chore: prepare health reporting release 1.87.4.0 * test: restrict routing evaluations to installed project skills * test: stabilize health selection and terminal fixtures
93 lines
3.9 KiB
TypeScript
93 lines
3.9 KiB
TypeScript
import { afterEach, describe, expect, test } from 'bun:test';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { chmodSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
|
|
// Execute the documented capture, so the example cannot drift from its tests.
|
|
const template = readFileSync(join(import.meta.dir, '../health/SKILL.md.tmpl'), 'utf8');
|
|
const capture = template.split('## Step 2: Run Tools')[1].match(/```bash\n([\s\S]*?)```/)![1];
|
|
const temporaryDirectories: string[] = [];
|
|
|
|
afterEach(() => {
|
|
for (const directory of temporaryDirectories.splice(0)) rmSync(directory, { recursive: true, force: true });
|
|
});
|
|
|
|
function runCapture(checker: string, setup = '') {
|
|
const directory = mkdtempSync(join(tmpdir(), 'health-capture-test-'));
|
|
temporaryDirectories.push(directory);
|
|
const logs = join(directory, 'logs');
|
|
mkdirSync(logs);
|
|
const executable = join(directory, 'tsc');
|
|
writeFileSync(executable, '#!/usr/bin/env bash\n' + checker + '\n');
|
|
chmodSync(executable, 0o755);
|
|
const result = spawnSync('/bin/bash', ['-euc', setup + '\n' + capture], {
|
|
encoding: 'utf8',
|
|
timeout: 10_000,
|
|
env: { ...process.env, PATH: directory + ':' + process.env.PATH, TMPDIR: logs },
|
|
});
|
|
expect(result.error).toBeUndefined();
|
|
expect(readdirSync(logs)).toEqual([]);
|
|
return result;
|
|
}
|
|
|
|
describe('/health command capture', () => {
|
|
test('a successful empty checker reports zero matches under set -e', () => {
|
|
const result = runCapture('exit 0');
|
|
expect(result.status).toBe(0);
|
|
expect(result.stdout).toMatch(/TOOL:typecheck EXIT:0 DURATION:\d+s ERRORS:0/);
|
|
});
|
|
|
|
test('failure survives an earlier success, stderr capture, and display', () => {
|
|
const result = runCapture('echo "source.ts: error TS2322: incorrect type" >&2\nexit 2', 'true');
|
|
expect(result.status).toBe(2);
|
|
expect(result.stdout).toContain('incorrect type');
|
|
expect(result.stdout).toMatch(/EXIT:2 DURATION:\d+s ERRORS:1/);
|
|
});
|
|
|
|
test('counts findings outside the displayed tail and prints only fifty log lines', () => {
|
|
const result = runCapture([
|
|
'for ((i=1; i<=60; i++)); do echo "source.ts: error TS2322: finding $i"; done',
|
|
'for ((i=1; i<=80; i++)); do echo "detail $i"; done',
|
|
'exit 2',
|
|
].join('\n'));
|
|
expect(result.status).toBe(2);
|
|
expect(result.stdout).toMatch(/EXIT:2 DURATION:\d+s ERRORS:60/);
|
|
const lines = result.stdout.trimEnd().split('\n');
|
|
expect(lines.length).toBe(51);
|
|
expect(lines[0]).toBe('detail 31');
|
|
expect(lines[49]).toBe('detail 80');
|
|
});
|
|
|
|
test('an executed checker returning 127 remains a failure', () => {
|
|
const result = runCapture('echo "a checker dependency failed" >&2\nexit 127');
|
|
expect(result.status).toBe(127);
|
|
expect(result.stdout).toContain('EXIT:127');
|
|
expect(result.stdout).not.toContain('SKIPPED');
|
|
});
|
|
|
|
test('empty failing output does not inherit a successful parser status', () => {
|
|
const result = runCapture('exit 1');
|
|
expect(result.status).toBe(1);
|
|
expect(result.stdout).toMatch(/EXIT:1 DURATION:\d+s ERRORS:0/);
|
|
});
|
|
|
|
test('capture log permissions are private even with a permissive caller umask', () => {
|
|
const result = runCapture('stat -c %a "$TMPDIR"/gstack-health.* 2>/dev/null || stat -f %Lp "$TMPDIR"/gstack-health.*', 'umask 000');
|
|
expect(result.status).toBe(0);
|
|
expect(result.stdout.split('\n')[0]).toBe('600');
|
|
});
|
|
|
|
test.each([
|
|
['log_creation', 'mktemp() { return 1; }'],
|
|
['redirection', 'mktemp() { printf "%s/missing/log\\n" "$TMPDIR"; }'],
|
|
['parsing', 'awk() { return 2; }'],
|
|
['display', 'tail() { return 1; }'],
|
|
])('%s failure reports an error instead of a clean result', (phase, setup) => {
|
|
const result = runCapture('exit 0', setup);
|
|
expect(result.status).toBe(125);
|
|
expect(result.stderr).toContain('ERROR:typecheck CAPTURE:' + phase);
|
|
expect(result.stdout).not.toContain('TOOL:typecheck EXIT:0');
|
|
});
|
|
});
|