Files
ECC/tests/scripts/eval-harness-package.test.js
T
montjeffrey 20cd70e06d test: stop Windows developer tooling from leaking into setup, tar, and audit tests
On a Windows machine with Claude Code installed natively, Git for Windows,
and the ECC plugin installed, `node tests/run-all.js` on main fails 44 tests
in 7 files. Linux CI never sees them. Three of the four causes are test
isolation gaps, fixed here (43 tests):

- claude-plugin-setup, claude-scope-migration, setup (38): fixtures put a
  fake claude.cmd first on PATH, but on Windows spawning a bare `claude`
  resolves only .com/.exe, so a native ~/.local/bin/claude.exe ran instead
  of the fake. Fixture PATHs now drop entries holding claude.exe/claude.com.
- ecc-universal-bin, eval-harness-package (4): tar received an absolute
  C:\ archive path, which Git Bash's GNU tar reads as a host:path remote
  spec ("Cannot connect to C: resolve failed"). Extract from inside the
  destination with a relative, forward-slash archive name, which both GNU
  tar and bsdtar accept.
- harness-audit (1): the cache-layout test overrode HOME only, while
  findPluginInstall also reads USERPROFILE and os.homedir(), so the real
  installed plugin won over the fixture. Override USERPROFILE too.

The two helpers live in tests/lib/helpers/windows-test-env.js with a
platform-independent test, so Linux CI holds both invariants too.

The 44th failure, plugin-hook-bootstrap's no-bash test, is a production
bug rather than a test leak: System32\bash.exe launches an installed WSL
distro, which passes the bash probe. It gets a separate fix.
2026-10-09 14:32:52 -04:00

124 lines
6.6 KiB
JavaScript

'use strict';
// Dependency-free package contract only: never run prepack, build, or install.
// Run serially: node tests/scripts/eval-harness-package.test.js
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const { getNpmPackEntry } = require('../lib/npm-pack-output');
const { test, tempDir, cleanup, finish, runNpm } = require('../lib/eval-harness/helpers');
const { toRelativeTarPath } = require('../lib/helpers/windows-test-env');
const repo = path.resolve(__dirname, '../..');
const work = tempDir('package smoke');
const pkg = JSON.parse(fs.readFileSync(path.join(repo, 'package.json'), 'utf8'));
const childEnv = { ...process.env, NODE_PATH: '', NODE_OPTIONS: '' };
const command = (binary, args, options = {}) => spawnSync(binary, args, {
encoding: 'utf8', timeout: 60000, maxBuffer: 16 * 1024 * 1024,
env: childEnv, ...options,
});
function sourceFiles(relative) {
const dir = path.join(repo, relative);
return fs.readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
const file = `${relative}/${entry.name}`;
assert.ok(!entry.isSymbolicLink(), `fixture must be a regular source tree: ${file}`);
return entry.isDirectory() ? sourceFiles(file) : [file];
}).sort();
}
// Match the actual aggregation contract in tests/run-all.js.
function counts(stdout) {
const passed = stdout.match(/Passed:\s*(\d+)/);
const failed = stdout.match(/Failed:\s*(\d+)/);
assert.ok(passed && failed, 'result tokens must be parseable by tests/run-all.js');
return { passed: Number(passed[1]), failed: Number(failed[1]) };
}
let archive;
try {
test('ignore-scripts tarball ships every example fixture and eval library file', () => {
const result = runNpm(['pack', '--ignore-scripts', '--offline', '--json',
'--pack-destination', work, '--cache', path.join(work, 'npm-cache')], {
cwd: repo, env: childEnv,
});
assert.strictEqual(result.status, 0, result.error?.message || result.stderr);
const entry = getNpmPackEntry(JSON.parse(result.stdout), pkg.name);
assert.ok(entry && typeof entry.filename === 'string');
assert.strictEqual(path.basename(entry.filename), entry.filename);
assert.ok(!entry.filename.startsWith('-'));
archive = path.join(work, entry.filename);
assert.ok(fs.statSync(archive).isFile());
const packed = new Set(entry.files.map(file => file.path));
const examples = sourceFiles('examples/eval-harness');
const required = ['scripts/eval-harness.js', ...sourceFiles('scripts/lib/eval-harness'), ...examples];
for (const file of required) assert.ok(packed.has(file), `package is missing ${file}`);
assert.ok(!packed.has('examples/CLAUDE.md'), 'do not publish unrelated examples');
console.log(` package closure: ${examples.length} example files; prepack/build/install skipped`);
});
test('actual extracted CLI example runs from the package without installing dependencies', () => {
assert.ok(archive, 'packing must succeed before extracting');
const extract = path.join(work, 'extracted');
fs.mkdirSync(extract);
const unpack = command('tar', ['-xzf', toRelativeTarPath(extract, archive)], { cwd: extract });
assert.strictEqual(unpack.status, 0, unpack.error?.message || unpack.stderr);
const installed = path.join(extract, 'package');
assert.ok(!fs.existsSync(path.join(installed, 'node_modules')));
const runtimeTemp = path.join(work, 'example-runtime');
fs.mkdirSync(runtimeTemp);
const result = command(process.execPath, [path.join(installed, 'scripts/eval-harness.js'), 'example', '--keep'], {
cwd: installed,
env: { ...childEnv, TMPDIR: runtimeTemp, TMP: runtimeTemp, TEMP: runtimeTemp },
});
assert.strictEqual(result.status, 0, result.error?.message || result.stderr || result.stdout);
assert.match(result.stdout, /all steps passed/);
const runs = fs.readdirSync(runtimeTemp).filter(name => name.startsWith('ecc-eval-harness-example-'));
assert.strictEqual(runs.length, 1);
const run = path.join(runtimeTemp, runs[0]);
const journal = fs.readFileSync(path.join(run, 'capsule/journal.ndjson'), 'utf8').trim().split('\n').map(JSON.parse);
assert.ok(journal.some(entry => entry.kind === 'gate.unavailable' && entry.payload.status === 'blocked'));
assert.strictEqual(new Set(journal.map(entry => entry.lineage)).size, 5);
const receipt = JSON.parse(fs.readFileSync(path.join(run, 'bundle/receipt.json'), 'utf8'));
assert.strictEqual(receipt.gate_receipt_digest, null);
assert.strictEqual(receipt.gate_verdict, null);
assert.ok(!fs.existsSync(path.join(run, 'gate-candidate')));
assert.ok(journal.every(entry => entry.payload.verdict !== 'PROMOTE'));
for (const file of sourceFiles('examples/eval-harness')) {
assert.deepStrictEqual(fs.readFileSync(path.join(installed, file)), fs.readFileSync(path.join(repo, file)));
}
console.log(' extracted example: five lineages, no candidate execution or gate verdict');
});
test('aggregator regexes count every real framework check accurately', () => {
const suites = fs.readdirSync(path.join(repo, 'tests/lib/eval-harness')).filter(file => file.endsWith('.test.js')).sort();
let total = 0;
for (const suite of suites) {
const result = command(process.execPath, [path.join(repo, 'tests/lib/eval-harness', suite)], { cwd: work });
assert.strictEqual(result.status, 0, `${suite}: ${result.error?.message || result.stderr || result.stdout}`);
const parsed = counts(result.stdout);
const actualPassed = (result.stdout.match(/^\s*✓ /gm) || []).length;
const actualFailed = (result.stdout.match(/^\s*✗ /gm) || []).length;
assert.deepStrictEqual(parsed, { passed: actualPassed, failed: actualFailed }, suite);
assert.ok(actualPassed > 0, `${suite} must run actual checks`);
assert.strictEqual(parsed.failed, 0);
total += parsed.passed;
}
assert.ok(suites.length > 0);
console.log(` framework aggregation: ${suites.length} suites, ${total} actual checks`);
});
test('failed checks remain visible to aggregation and return a failing exit', () => {
const helper = path.join(repo, 'tests/lib/eval-harness/helpers.js');
const script = `const h=require(${JSON.stringify(helper)});h.test('pass fixture',()=>{});h.test('failure fixture',()=>{throw new Error('synthetic failure');});h.finish('count fixture');`;
const result = command(process.execPath, ['-e', script], { cwd: work });
assert.strictEqual(result.status, 1);
assert.deepStrictEqual(counts(result.stdout), { passed: 1, failed: 1 });
});
} finally {
cleanup(work);
}
finish('eval-harness package');