Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
484 lines
16 KiB
JavaScript
484 lines
16 KiB
JavaScript
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const {
|
|
compareTapResults,
|
|
parseTapResult,
|
|
} = require('./compare-ci-test-baseline.cjs');
|
|
|
|
const tap = ({ failures = [], successes = [], fail = failures.length, cancelled = 0, skipped = 0, todo = 0, tests = 10 } = {}) => [
|
|
'TAP version 13',
|
|
...failures.map((name, index) => `not ok ${index + 1} - ${name}`),
|
|
...successes.map((name, index) => `ok ${failures.length + index + 1} - ${name}`),
|
|
`# fail ${fail}`,
|
|
`# cancelled ${cancelled}`,
|
|
`# skipped ${skipped}`,
|
|
`# todo ${todo}`,
|
|
`# tests ${tests}`,
|
|
].join('\n');
|
|
|
|
test('accepts a clean candidate even when the base was red', () => {
|
|
const result = compareTapResults(
|
|
parseTapResult(tap({ failures: ['base failure'] }), 1),
|
|
parseTapResult(tap({ successes: ['base failure'] }), 0),
|
|
);
|
|
assert.equal(result.passed, true);
|
|
assert.equal(result.kind, 'clean');
|
|
});
|
|
|
|
test('rejects a zero-exit candidate without a complete clean TAP summary', () => {
|
|
const result = compareTapResults(
|
|
parseTapResult(tap(), 0),
|
|
parseTapResult('custom test command completed', 0),
|
|
);
|
|
assert.equal(result.passed, false);
|
|
assert.equal(result.kind, 'unclassified_failure');
|
|
});
|
|
|
|
test('accepts renaming a successful test when quantitative coverage does not shrink', () => {
|
|
const result = compareTapResults(
|
|
parseTapResult(tap({ successes: ['kept test', 'removed test'] }), 0),
|
|
parseTapResult(tap({ successes: ['kept test', 'unrelated replacement'] }), 0),
|
|
);
|
|
assert.equal(result.passed, true);
|
|
assert.equal(result.kind, 'clean');
|
|
assert.deepEqual(result.missingBaselineSuccesses, ['removed test']);
|
|
|
|
const issueNumberName = compareTapResults(
|
|
parseTapResult(tap({
|
|
successes: ['accepts packages that ship upstream #6055/#5987/#6043'],
|
|
}), 0),
|
|
parseTapResult(tap({
|
|
successes: ['accepts packages that ship upstream #9999'],
|
|
}), 0),
|
|
);
|
|
assert.equal(issueNumberName.passed, true);
|
|
assert.equal(issueNumberName.kind, 'clean');
|
|
|
|
// Clean candidate that fixes a prior red must keep the failing test title.
|
|
const fixedFailure = compareTapResults(
|
|
parseTapResult(tap({ failures: ['broken test'], successes: ['other test'] }), 1),
|
|
parseTapResult(tap({ successes: ['broken test', 'other test'] }), 0),
|
|
);
|
|
assert.equal(fixedFailure.passed, true);
|
|
assert.equal(fixedFailure.kind, 'clean');
|
|
|
|
// Deleting the failing test and adding an unrelated passer is not a clean fix.
|
|
const deletedFailure = compareTapResults(
|
|
parseTapResult(tap({ failures: ['broken test'], successes: ['other test'] }), 1),
|
|
parseTapResult(tap({ successes: ['other test', 'unrelated replacement'] }), 0),
|
|
);
|
|
assert.equal(deletedFailure.passed, false);
|
|
assert.equal(deletedFailure.kind, 'unclassified_failure');
|
|
assert.deepEqual(deletedFailure.missingBaselineFailures, ['broken test']);
|
|
});
|
|
|
|
test('accepts a green candidate that renames one success while adding another', () => {
|
|
const result = compareTapResults(
|
|
parseTapResult(tap({
|
|
successes: ['alpha', 'beta', 'gamma'],
|
|
tests: 8886,
|
|
}), 0),
|
|
parseTapResult(tap({
|
|
successes: ['alpha', 'beta', 'delta', 'epsilon'],
|
|
tests: 8887,
|
|
}), 0),
|
|
);
|
|
assert.equal(result.passed, true);
|
|
assert.equal(result.kind, 'clean');
|
|
assert.deepEqual(result.missingBaselineSuccesses, ['gamma']);
|
|
});
|
|
|
|
test('rejects deleting successful coverage even when the run stays green', () => {
|
|
const result = compareTapResults(
|
|
parseTapResult(tap({ successes: ['kept test', 'removed test'], tests: 12 }), 0),
|
|
parseTapResult(tap({ successes: ['kept test'], tests: 11 }), 0),
|
|
);
|
|
assert.equal(result.passed, false);
|
|
assert.equal(result.kind, 'missing_baseline_successes');
|
|
assert.deepEqual(result.missingBaselineSuccesses, ['removed test']);
|
|
assert.deepEqual(result.newFailures, ['removed test']);
|
|
});
|
|
|
|
test('rejects a clean candidate when the exact-base TAP summary is incomplete (fail closed)', () => {
|
|
const result = compareTapResults(
|
|
parseTapResult('base runner stopped early', 1),
|
|
parseTapResult(tap(), 0),
|
|
);
|
|
assert.equal(result.passed, false);
|
|
assert.equal(result.kind, 'unclassified_failure');
|
|
});
|
|
|
|
test('accepts only failures already present on the exact base', () => {
|
|
const result = compareTapResults(
|
|
parseTapResult(tap({ failures: ['base A', 'base B'] }), 1),
|
|
parseTapResult(tap({ failures: ['base B'], successes: ['base A'] }), 1),
|
|
);
|
|
assert.equal(result.passed, true);
|
|
assert.equal(result.kind, 'baseline_only');
|
|
assert.deepEqual(result.newFailures, []);
|
|
|
|
const removedFailure = compareTapResults(
|
|
parseTapResult(tap({ failures: ['base A', 'base B'] }), 1),
|
|
parseTapResult(tap({
|
|
failures: ['base B'],
|
|
successes: ['unrelated replacement'],
|
|
}), 1),
|
|
);
|
|
assert.equal(removedFailure.passed, false);
|
|
assert.equal(removedFailure.kind, 'unclassified_failure');
|
|
});
|
|
|
|
test('rejects a different candidate failure even when both runs are red', () => {
|
|
const result = compareTapResults(
|
|
parseTapResult(tap({ failures: ['base failure'] }), 1),
|
|
parseTapResult(tap({ failures: ['candidate regression'] }), 1),
|
|
);
|
|
assert.equal(result.passed, false);
|
|
assert.equal(result.kind, 'new_failures');
|
|
assert.deepEqual(result.newFailures, ['candidate regression']);
|
|
});
|
|
|
|
test('rejects an additional runner failure after comparable TAP output', () => {
|
|
const sameTap = tap({ failures: ['base failure'] });
|
|
const differentExit = compareTapResults(
|
|
parseTapResult(sameTap, 1),
|
|
parseTapResult(`${sameTap}\nrunner crashed after tests`, 2),
|
|
);
|
|
assert.equal(differentExit.passed, false);
|
|
assert.equal(differentExit.kind, 'unclassified_failure');
|
|
|
|
const sameExit = compareTapResults(
|
|
parseTapResult(sameTap, 1),
|
|
parseTapResult(`${sameTap}\nrunner crashed after tests`, 1),
|
|
);
|
|
assert.equal(sameExit.passed, false);
|
|
assert.equal(sameExit.kind, 'unclassified_failure');
|
|
|
|
const preSummaryCrash = compareTapResults(
|
|
parseTapResult(sameTap, 1),
|
|
parseTapResult(`runner crashed before summary\n${sameTap}`, 1),
|
|
);
|
|
assert.equal(preSummaryCrash.passed, false);
|
|
assert.equal(preSummaryCrash.kind, 'unclassified_failure');
|
|
|
|
const crashWithoutFailureKeyword = compareTapResults(
|
|
parseTapResult(sameTap, 1),
|
|
parseTapResult(`${sameTap}\nSegmentation fault (core dumped)`, 1),
|
|
);
|
|
assert.equal(crashWithoutFailureKeyword.passed, false);
|
|
assert.equal(crashWithoutFailureKeyword.kind, 'unclassified_failure');
|
|
|
|
const crashInsideUnattachedYamlMarkers = compareTapResults(
|
|
parseTapResult(sameTap, 1),
|
|
parseTapResult(`${sameTap}\n---\nSegmentation fault (core dumped)\n...`, 1),
|
|
);
|
|
assert.equal(crashInsideUnattachedYamlMarkers.passed, false);
|
|
assert.equal(crashInsideUnattachedYamlMarkers.kind, 'unclassified_failure');
|
|
|
|
const diagnosticTap = (closed) => [
|
|
'TAP version 13',
|
|
'not ok 1 - base failure',
|
|
' ---',
|
|
" error: 'existing failure'",
|
|
" code: 'ERR_TEST_FAILURE'",
|
|
...(closed ? [' ...'] : []),
|
|
'# fail 1',
|
|
'# cancelled 0',
|
|
'# skipped 0',
|
|
'# todo 0',
|
|
'# tests 10',
|
|
].join('\n');
|
|
const crashAfterUnterminatedDiagnostic = compareTapResults(
|
|
parseTapResult(diagnosticTap(true), 1),
|
|
parseTapResult(`${diagnosticTap(false)}\nSegmentation fault (core dumped)`, 1),
|
|
);
|
|
assert.equal(crashAfterUnterminatedDiagnostic.passed, false);
|
|
assert.equal(crashAfterUnterminatedDiagnostic.kind, 'unclassified_failure');
|
|
|
|
const diagnosticWithExtra = (extra) => [
|
|
'TAP version 13',
|
|
'not ok 1 - base failure',
|
|
' ---',
|
|
" error: 'existing failure'",
|
|
" code: 'ERR_TEST_FAILURE'",
|
|
...(extra ? [` ${extra}`] : []),
|
|
' ...',
|
|
'# fail 1',
|
|
'# cancelled 0',
|
|
'# skipped 0',
|
|
'# todo 0',
|
|
'# tests 10',
|
|
].join('\n');
|
|
for (const extra of ['Segmentation fault (core dumped)', 'signal: SIGSEGV']) {
|
|
const crashInsideClosedDiagnostic = compareTapResults(
|
|
parseTapResult(diagnosticWithExtra(''), 1),
|
|
parseTapResult(diagnosticWithExtra(extra), 1),
|
|
);
|
|
assert.equal(crashInsideClosedDiagnostic.passed, false);
|
|
assert.equal(crashInsideClosedDiagnostic.kind, 'new_failures');
|
|
}
|
|
|
|
const unchangedArbitraryOutput = compareTapResults(
|
|
parseTapResult(`starting custom runner\n${sameTap}`, 1),
|
|
parseTapResult(`starting custom runner\n${sameTap}`, 1),
|
|
);
|
|
assert.equal(unchangedArbitraryOutput.passed, true);
|
|
assert.equal(unchangedArbitraryOutput.kind, 'baseline_only');
|
|
});
|
|
|
|
test('distinguishes same-title failures by stable TAP diagnostics', () => {
|
|
const withDiagnostic = (error, location = '/workspace/example.test.js:10:1') => [
|
|
'TAP version 13',
|
|
'not ok 1 - duplicate title',
|
|
' ---',
|
|
` location: '${location}'`,
|
|
" failureType: 'testCodeFailure'",
|
|
` error: '${error}'`,
|
|
" code: 'ERR_ASSERTION'",
|
|
' stack: |- ',
|
|
' volatile stack line',
|
|
' ...',
|
|
'# fail 1',
|
|
'# cancelled 0',
|
|
'# skipped 0',
|
|
'# todo 0',
|
|
'# tests 10',
|
|
].join('\n');
|
|
const same = compareTapResults(
|
|
parseTapResult(withDiagnostic('old failure'), 1),
|
|
parseTapResult(withDiagnostic('old failure'), 1),
|
|
);
|
|
assert.equal(same.passed, true);
|
|
|
|
const moved = compareTapResults(
|
|
parseTapResult(withDiagnostic('old failure'), 1),
|
|
parseTapResult(withDiagnostic('old failure', '/workspace/example.test.js:11:1'), 1),
|
|
);
|
|
assert.equal(moved.passed, true);
|
|
|
|
const changed = compareTapResults(
|
|
parseTapResult(withDiagnostic('old failure'), 1),
|
|
parseTapResult(withDiagnostic('new regression'), 1),
|
|
);
|
|
assert.equal(changed.passed, false);
|
|
assert.deepEqual(changed.newFailures, ['duplicate title']);
|
|
|
|
const dynamicValues = (actual, nextTitle) => [
|
|
'TAP version 13',
|
|
'not ok 1 - duplicate title',
|
|
' ---',
|
|
" location: '/workspace/example.test.js:10:1'",
|
|
" failureType: 'testCodeFailure'",
|
|
" error: 'old failure'",
|
|
` actual: ${actual}`,
|
|
' expected: 1',
|
|
" code: 'ERR_ASSERTION'",
|
|
' stack: |- ',
|
|
' volatile stack line',
|
|
' ...',
|
|
`# Subtest: ${nextTitle}`,
|
|
`ok 2 - ${nextTitle}`,
|
|
'1..2',
|
|
'# fail 1',
|
|
'# cancelled 0',
|
|
'# skipped 0',
|
|
'# todo 0',
|
|
'# tests 2',
|
|
].join('\n');
|
|
const dynamic = compareTapResults(
|
|
parseTapResult(dynamicValues(41831, 'later test'), 1),
|
|
parseTapResult(dynamicValues(52942, 'later test'), 1),
|
|
);
|
|
assert.equal(dynamic.passed, true);
|
|
|
|
const multilineError = (reason) => [
|
|
'TAP version 13',
|
|
'not ok 1 - duplicate title',
|
|
' ---',
|
|
" location: '/workspace/example.test.js:10:1'",
|
|
" failureType: 'testCodeFailure'",
|
|
' error: |-',
|
|
` ${reason}`,
|
|
" code: 'ERR_ASSERTION'",
|
|
" operator: 'strictEqual'",
|
|
' ...',
|
|
'# fail 1',
|
|
'# cancelled 0',
|
|
'# skipped 0',
|
|
'# todo 0',
|
|
'# tests 10',
|
|
].join('\n');
|
|
const changedMultilineError = compareTapResults(
|
|
parseTapResult(multilineError('old failure'), 1),
|
|
parseTapResult(multilineError('new regression'), 1),
|
|
);
|
|
assert.equal(changedMultilineError.passed, false);
|
|
assert.deepEqual(changedMultilineError.newFailures, ['duplicate title']);
|
|
|
|
const assertionDiff = (field, actual) => [
|
|
'TAP version 13',
|
|
'not ok 1 - duplicate title',
|
|
' ---',
|
|
" failureType: 'testCodeFailure'",
|
|
' error: |-',
|
|
' Expected values to be strictly equal:',
|
|
'',
|
|
' {',
|
|
` ${field}: ${actual}`,
|
|
' }',
|
|
" code: 'ERR_ASSERTION'",
|
|
" operator: 'strictEqual'",
|
|
' ...',
|
|
'# fail 1',
|
|
'# cancelled 0',
|
|
'# skipped 0',
|
|
'# todo 0',
|
|
'# tests 10',
|
|
].join('\n');
|
|
const changedAssertionValue = compareTapResults(
|
|
parseTapResult(assertionDiff('now', 111), 1),
|
|
parseTapResult(assertionDiff('now', 222), 1),
|
|
);
|
|
assert.equal(changedAssertionValue.passed, true);
|
|
|
|
const changedStableAssertionValue = compareTapResults(
|
|
parseTapResult(assertionDiff('x', 1), 1),
|
|
parseTapResult(assertionDiff('x', 3), 1),
|
|
);
|
|
assert.equal(changedStableAssertionValue.passed, false);
|
|
|
|
const changedPortAssertion = compareTapResults(
|
|
parseTapResult(assertionDiff('port', 2222), 1),
|
|
parseTapResult(assertionDiff('port', 443), 1),
|
|
);
|
|
assert.equal(changedPortAssertion.passed, false);
|
|
|
|
const customMultilineError = (reason) => multilineError(reason).replace(
|
|
"code: 'ERR_ASSERTION'",
|
|
"code: 'ERR_CUSTOM'",
|
|
);
|
|
const changedCustomError = compareTapResults(
|
|
parseTapResult(customMultilineError('service rejected: old reason'), 1),
|
|
parseTapResult(customMultilineError('service rejected: new reason'), 1),
|
|
);
|
|
assert.equal(changedCustomError.passed, false);
|
|
|
|
const sameCustomErrorAcrossRuns = compareTapResults(
|
|
parseTapResult(customMultilineError(
|
|
'service failed at /tmp/run-A/result on 2026-07-31T10:00:00Z in worker.js:10:2',
|
|
), 1),
|
|
parseTapResult(customMultilineError(
|
|
'service failed at /tmp/run-B/result on 2026-07-31T10:01:00Z in worker.js:11:3',
|
|
), 1),
|
|
);
|
|
assert.equal(sameCustomErrorAcrossRuns.passed, true);
|
|
});
|
|
|
|
test('rejects added duplicate failures, cancellations, skipped tests, and TODOs', () => {
|
|
const duplicate = compareTapResults(
|
|
parseTapResult(tap({ failures: ['same'] }), 1),
|
|
parseTapResult(tap({ failures: ['same', 'same'] }), 1),
|
|
);
|
|
assert.equal(duplicate.passed, false);
|
|
|
|
const cancelled = compareTapResults(
|
|
parseTapResult(tap({ failures: ['same'] }), 1),
|
|
parseTapResult(tap({ failures: ['same'], cancelled: 2 }), 1),
|
|
);
|
|
assert.equal(cancelled.passed, false);
|
|
assert.equal(cancelled.kind, 'cancelled_tests');
|
|
|
|
const skipped = compareTapResults(
|
|
parseTapResult(tap({ skipped: 1 }), 0),
|
|
parseTapResult(tap({ skipped: 10 }), 0),
|
|
);
|
|
assert.equal(skipped.passed, false);
|
|
assert.equal(skipped.kind, 'unclassified_failure');
|
|
|
|
const todo = compareTapResults(
|
|
parseTapResult(tap({ todo: 1 }), 0),
|
|
parseTapResult(tap({ todo: 10 }), 0),
|
|
);
|
|
assert.equal(todo.passed, false);
|
|
assert.equal(todo.kind, 'unclassified_failure');
|
|
});
|
|
|
|
test('fails closed when a red run has no complete TAP summary', () => {
|
|
const result = compareTapResults(
|
|
parseTapResult('runner stopped early', 1),
|
|
parseTapResult('runner stopped early', 1),
|
|
);
|
|
assert.equal(result.passed, false);
|
|
assert.equal(result.kind, 'unclassified_failure');
|
|
});
|
|
|
|
test('rejects non-test failures and a candidate that runs fewer tests', () => {
|
|
const posttestFailure = compareTapResults(
|
|
parseTapResult(tap({ failures: ['existing'] }), 1),
|
|
parseTapResult(tap({ fail: 0 }), 1),
|
|
);
|
|
assert.equal(posttestFailure.passed, false);
|
|
assert.equal(posttestFailure.kind, 'unclassified_failure');
|
|
|
|
const fewerTests = compareTapResults(
|
|
parseTapResult(tap({ failures: ['existing'], tests: 20 }), 1),
|
|
parseTapResult(tap({ failures: ['existing'], tests: 19 }), 1),
|
|
);
|
|
assert.equal(fewerTests.passed, false);
|
|
assert.equal(fewerTests.kind, 'unclassified_failure');
|
|
});
|
|
|
|
test('ignores success-case TAP YAML diagnostics and volatile console noise', () => {
|
|
const withOkDiagnostic = (durationMs, pid, sftpId, requestId, port, mtime) => [
|
|
'TAP version 13',
|
|
`# (node:${pid}) ExperimentalWarning: The MockTimers API is an experimental feature and might change at any time`,
|
|
`# sftpId: '${sftpId}',`,
|
|
`# requestId: '${requestId}',`,
|
|
`# OAuth callback server listening on http://127.0.0.1:${port}/oauth/callback`,
|
|
`# [transferDiag] {"event":"progress","id":"${requestId}","pct":1}`,
|
|
`# [FileWatcher] Initial file stats: mtime=${mtime}, size=7`,
|
|
`# [SessionLogStream] Started stream for session-${mtime}-abc -> /tmp/log`,
|
|
'ok 1 - passing helper',
|
|
' ---',
|
|
` duration_ms: ${durationMs}`,
|
|
" type: 'test'",
|
|
' ...',
|
|
'not ok 2 - base failure',
|
|
' ---',
|
|
" error: 'existing failure'",
|
|
" code: 'ERR_TEST_FAILURE'",
|
|
' ...',
|
|
'# fail 1',
|
|
'# cancelled 0',
|
|
'# skipped 0',
|
|
'# todo 0',
|
|
'# tests 10',
|
|
].join('\n');
|
|
|
|
const result = compareTapResults(
|
|
parseTapResult(
|
|
withOkDiagnostic(1.25, 9089, 'sftp-111261', 'ssh-6239b197-6933-41e3-845b-396a42d6c66b', 37289, 1786091556617.3306),
|
|
1,
|
|
),
|
|
parseTapResult(
|
|
withOkDiagnostic(9.88, 29926, 'sftp-573391', 'ssh-de14337d-3088-463a-a2ca-ffc93d609e74', 39199, 1786091557152.3345),
|
|
1,
|
|
),
|
|
);
|
|
assert.equal(result.passed, true);
|
|
assert.equal(result.kind, 'baseline_only');
|
|
assert.deepEqual(result.newFailures, []);
|
|
});
|
|
|
|
test('still rejects new bracket-prefixed runner comments outside transfer diagnostics', () => {
|
|
const sameTap = tap({ failures: ['base failure'] });
|
|
const result = compareTapResults(
|
|
parseTapResult(sameTap, 1),
|
|
parseTapResult(`${sameTap}\n# [fatal] worker aborted after suite`, 1),
|
|
);
|
|
assert.equal(result.passed, false);
|
|
assert.equal(result.kind, 'unclassified_failure');
|
|
});
|