mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-11-06 22:40:09 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b4845d7b7 | ||
|
|
b17c885515 | ||
|
|
69ecc6a6ce | ||
|
|
9c6bd72768 | ||
|
|
dabe1397ee |
No files matched your search
@@ -2093,6 +2093,7 @@ jobs:
|
||||
".cursor-runtime/followup-pending-snapshots.json",
|
||||
".cursor-runtime/followup-verify-result.json",
|
||||
".cursor-runtime/followup-test-comparison.json",
|
||||
".cursor-runtime/followup-base-tests.log",
|
||||
".cursor-runtime/followup-candidate-lint.log",
|
||||
".cursor-runtime/followup-candidate-tests.log",
|
||||
".cursor-runtime/followup-candidate-build.log",
|
||||
@@ -2114,6 +2115,7 @@ jobs:
|
||||
.cursor-runtime/followup-pending-snapshots.json
|
||||
.cursor-runtime/followup-verify-result.json
|
||||
.cursor-runtime/followup-test-comparison.json
|
||||
.cursor-runtime/followup-base-tests.log
|
||||
.cursor-runtime/followup-candidate-lint.log
|
||||
.cursor-runtime/followup-candidate-tests.log
|
||||
.cursor-runtime/followup-candidate-build.log
|
||||
@@ -3379,6 +3381,7 @@ jobs:
|
||||
".cursor-runtime/implement-pr-body.md",
|
||||
".cursor-runtime/verify-result.json",
|
||||
".cursor-runtime/test-comparison.json",
|
||||
".cursor-runtime/base-tests.log",
|
||||
".cursor-runtime/candidate-lint.log",
|
||||
".cursor-runtime/candidate-tests.log",
|
||||
".cursor-runtime/candidate-build.log",
|
||||
@@ -3400,6 +3403,7 @@ jobs:
|
||||
.cursor-runtime/implement-pr-body.out.md
|
||||
.cursor-runtime/verify-result.json
|
||||
.cursor-runtime/test-comparison.json
|
||||
.cursor-runtime/base-tests.log
|
||||
.cursor-runtime/candidate-lint.log
|
||||
.cursor-runtime/candidate-tests.log
|
||||
.cursor-runtime/candidate-build.log
|
||||
@@ -3409,6 +3413,14 @@ jobs:
|
||||
if: steps.patch.outputs.artifact_ready == 'true' && steps.verify.outputs.passed != 'true'
|
||||
run: |
|
||||
echo "Candidate patch was preserved, but candidate-specific verification failed." >&2
|
||||
if [[ -f .cursor-runtime/test-comparison.json ]]; then
|
||||
echo "Test comparison:" >&2
|
||||
cat .cursor-runtime/test-comparison.json >&2 || true
|
||||
fi
|
||||
if [[ -f .cursor-runtime/verify-result.json ]]; then
|
||||
echo "Verify result:" >&2
|
||||
cat .cursor-runtime/verify-result.json >&2 || true
|
||||
fi
|
||||
exit 1
|
||||
|
||||
- name: Mark needs human on implement failure
|
||||
@@ -4812,6 +4824,7 @@ jobs:
|
||||
".cursor-runtime/fix.patch",
|
||||
".cursor-runtime/fix-verify-result.json",
|
||||
".cursor-runtime/fix-test-comparison.json",
|
||||
".cursor-runtime/fix-base-tests.log",
|
||||
".cursor-runtime/fix-candidate-lint.log",
|
||||
".cursor-runtime/fix-candidate-tests.log",
|
||||
".cursor-runtime/fix-candidate-build.log",
|
||||
@@ -4831,6 +4844,7 @@ jobs:
|
||||
.cursor-runtime/fix.patch
|
||||
.cursor-runtime/fix-verify-result.json
|
||||
.cursor-runtime/fix-test-comparison.json
|
||||
.cursor-runtime/fix-base-tests.log
|
||||
.cursor-runtime/fix-candidate-lint.log
|
||||
.cursor-runtime/fix-candidate-tests.log
|
||||
.cursor-runtime/fix-candidate-build.log
|
||||
@@ -4840,6 +4854,14 @@ jobs:
|
||||
if: steps.codex.outputs.action == 'fix' && steps.fixpatch.outputs.artifact_ready == 'true' && steps.fixverify.outputs.passed != 'true'
|
||||
run: |
|
||||
echo "Codex fix patch was preserved, but candidate-specific verification failed." >&2
|
||||
if [[ -f .cursor-runtime/fix-test-comparison.json ]]; then
|
||||
echo "Test comparison:" >&2
|
||||
cat .cursor-runtime/fix-test-comparison.json >&2 || true
|
||||
fi
|
||||
if [[ -f .cursor-runtime/fix-verify-result.json ]]; then
|
||||
echo "Verify result:" >&2
|
||||
cat .cursor-runtime/fix-verify-result.json >&2 || true
|
||||
fi
|
||||
exit 1
|
||||
|
||||
- name: Mark needs human when no fix produced
|
||||
|
||||
@@ -241,64 +241,111 @@ function compareTapResults(baseline, candidate) {
|
||||
missingBaselineFailures.push(failure.name);
|
||||
}
|
||||
|
||||
const result = ({
|
||||
passed,
|
||||
kind,
|
||||
newFailures = [],
|
||||
candidateFailures = candidate.exitCode === 0 ? [] : candidate.failures,
|
||||
}) => ({
|
||||
passed,
|
||||
kind,
|
||||
baselineFailures: baseline.failures,
|
||||
candidateFailures,
|
||||
newFailures,
|
||||
missingBaselineSuccesses,
|
||||
missingBaselineFailures,
|
||||
});
|
||||
|
||||
if (candidate.exitCode === 0) {
|
||||
const completeCleanRun =
|
||||
// Require every baseline failure title to reappear as a same-named success
|
||||
// before accepting a red-to-green transition. Aggregate counts alone would allow
|
||||
// deleting the failing test and adding an unrelated passer.
|
||||
const baselineFailCounts = countFailures(baseline.failures);
|
||||
const baselineSuccessCountsForFix = countFailures(baseline.successes);
|
||||
const candidateSuccessCountsForFix = countFailures(candidate.successes);
|
||||
const unresolvedBaselineFailures = [];
|
||||
for (const [name, failCount] of baselineFailCounts) {
|
||||
const required =
|
||||
failCount + (baselineSuccessCountsForFix.get(name) || 0);
|
||||
const available = candidateSuccessCountsForFix.get(name) || 0;
|
||||
for (let i = 0; i < required - available; i += 1) {
|
||||
unresolvedBaselineFailures.push(name);
|
||||
}
|
||||
}
|
||||
// A fully green candidate may rename success titles when quantitative
|
||||
// coverage does not shrink, but only against a complete baseline summary.
|
||||
// An incomplete baseline cannot prove coverage was preserved, so fail closed.
|
||||
const quantitativeClean =
|
||||
baseline.complete &&
|
||||
candidate.complete &&
|
||||
candidate.failCount === 0 &&
|
||||
candidate.cancelledCount === 0 &&
|
||||
unresolvedBaselineFailures.length === 0 &&
|
||||
candidate.skippedCount <= baseline.skippedCount &&
|
||||
candidate.todoCount <= baseline.todoCount &&
|
||||
candidate.testCount >= baseline.testCount;
|
||||
if (quantitativeClean) {
|
||||
return result({ passed: true, kind: 'clean' });
|
||||
}
|
||||
const coverageShrunk =
|
||||
baseline.complete &&
|
||||
candidate.complete &&
|
||||
candidate.failCount === 0 &&
|
||||
candidate.cancelledCount === 0 &&
|
||||
candidate.skippedCount <= baseline.skippedCount &&
|
||||
candidate.todoCount <= baseline.todoCount &&
|
||||
candidate.testCount >= baseline.testCount &&
|
||||
missingBaselineSuccesses.length === 0 &&
|
||||
missingBaselineFailures.length === 0;
|
||||
return {
|
||||
passed: completeCleanRun,
|
||||
kind: completeCleanRun ? 'clean' : 'unclassified_failure',
|
||||
baselineFailures: baseline.failures,
|
||||
candidateFailures: [],
|
||||
newFailures: [],
|
||||
};
|
||||
candidate.testCount < baseline.testCount &&
|
||||
missingBaselineSuccesses.length > 0;
|
||||
if (coverageShrunk) {
|
||||
return result({
|
||||
passed: false,
|
||||
kind: 'missing_baseline_successes',
|
||||
newFailures: missingBaselineSuccesses,
|
||||
});
|
||||
}
|
||||
return result({
|
||||
passed: false,
|
||||
kind: 'unclassified_failure',
|
||||
newFailures: unresolvedBaselineFailures.length
|
||||
? unresolvedBaselineFailures
|
||||
: missingBaselineSuccesses,
|
||||
});
|
||||
}
|
||||
|
||||
if (baseline.exitCode === 0) {
|
||||
return {
|
||||
return result({
|
||||
passed: false,
|
||||
kind: 'new_failures',
|
||||
baselineFailures: [],
|
||||
candidateFailures: candidate.failures,
|
||||
newFailures: candidate.failures,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (!baseline.complete || !candidate.complete) {
|
||||
return {
|
||||
return result({
|
||||
passed: false,
|
||||
kind: 'unclassified_failure',
|
||||
baselineFailures: baseline.failures,
|
||||
candidateFailures: candidate.failures,
|
||||
newFailures: candidate.failures,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (missingBaselineSuccesses.length) {
|
||||
return {
|
||||
return result({
|
||||
passed: false,
|
||||
kind: 'unclassified_failure',
|
||||
baselineFailures: baseline.failures,
|
||||
kind: 'missing_baseline_successes',
|
||||
candidateFailures: candidate.failures,
|
||||
newFailures: missingBaselineSuccesses,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (candidate.exitCode !== baseline.exitCode) {
|
||||
return {
|
||||
return result({
|
||||
passed: false,
|
||||
kind: 'unclassified_failure',
|
||||
baselineFailures: baseline.failures,
|
||||
candidateFailures: candidate.failures,
|
||||
newFailures: candidate.failures,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const baselineNonTapCounts = countFailures(baseline.nonTapOutput);
|
||||
@@ -307,23 +354,21 @@ function compareTapResults(baseline, candidate) {
|
||||
([line, count]) => count > (baselineNonTapCounts.get(line) || 0),
|
||||
);
|
||||
if (addedNonTapOutput) {
|
||||
return {
|
||||
return result({
|
||||
passed: false,
|
||||
kind: 'unclassified_failure',
|
||||
baselineFailures: baseline.failures,
|
||||
candidateFailures: candidate.failures,
|
||||
newFailures: candidate.failures,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (candidate.failCount === 0 || candidate.testCount < baseline.testCount) {
|
||||
return {
|
||||
return result({
|
||||
passed: false,
|
||||
kind: 'unclassified_failure',
|
||||
baselineFailures: baseline.failures,
|
||||
candidateFailures: candidate.failures,
|
||||
newFailures: candidate.failures,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -331,13 +376,12 @@ function compareTapResults(baseline, candidate) {
|
||||
candidate.skippedCount > baseline.skippedCount ||
|
||||
candidate.todoCount > baseline.todoCount
|
||||
) {
|
||||
return {
|
||||
return result({
|
||||
passed: false,
|
||||
kind: 'cancelled_tests',
|
||||
baselineFailures: baseline.failures,
|
||||
candidateFailures: candidate.failures,
|
||||
newFailures: candidate.failures,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const baselineCounts = countFailures(
|
||||
@@ -364,17 +408,16 @@ function compareTapResults(baseline, candidate) {
|
||||
const reportedFailures = newFailures.length
|
||||
? newFailures
|
||||
: missingBaselineFailures;
|
||||
return {
|
||||
return result({
|
||||
passed,
|
||||
kind: passed
|
||||
? 'baseline_only'
|
||||
: newFailures.length
|
||||
? 'new_failures'
|
||||
: 'unclassified_failure',
|
||||
baselineFailures: baseline.failures,
|
||||
candidateFailures: candidate.failures,
|
||||
newFailures: reportedFailures,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
@@ -418,6 +461,11 @@ function main(argv = process.argv.slice(2)) {
|
||||
if (result.newFailures.length) {
|
||||
console.error(`New failures:\n- ${result.newFailures.join('\n- ')}`);
|
||||
}
|
||||
if (!result.passed && result.missingBaselineSuccesses?.length) {
|
||||
console.error(
|
||||
`Missing baseline successes:\n- ${result.missingBaselineSuccesses.join('\n- ')}`,
|
||||
);
|
||||
}
|
||||
return result.passed ? 0 : 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,13 +36,14 @@ test('rejects a zero-exit candidate without a complete clean TAP summary', () =>
|
||||
assert.equal(result.kind, 'unclassified_failure');
|
||||
});
|
||||
|
||||
test('rejects replacing an existing successful test with an unrelated one', () => {
|
||||
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, false);
|
||||
assert.equal(result.kind, 'unclassified_failure');
|
||||
assert.equal(result.passed, true);
|
||||
assert.equal(result.kind, 'clean');
|
||||
assert.deepEqual(result.missingBaselineSuccesses, ['removed test']);
|
||||
|
||||
const issueNumberName = compareTapResults(
|
||||
parseTapResult(tap({
|
||||
@@ -52,16 +53,55 @@ test('rejects replacing an existing successful test with an unrelated one', () =
|
||||
successes: ['accepts packages that ship upstream #9999'],
|
||||
}), 0),
|
||||
);
|
||||
assert.equal(issueNumberName.passed, false);
|
||||
assert.equal(issueNumberName.passed, true);
|
||||
assert.equal(issueNumberName.kind, 'clean');
|
||||
|
||||
const duplicateName = compareTapResults(
|
||||
parseTapResult(tap({ failures: ['same name'], successes: ['same name'] }), 1),
|
||||
parseTapResult(tap({ successes: ['same name', 'unrelated replacement'] }), 0),
|
||||
// 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(duplicateName.passed, false);
|
||||
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('rejects a clean candidate when the exact-base TAP summary is incomplete', () => {
|
||||
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),
|
||||
|
||||
@@ -2293,6 +2293,9 @@ test('every code-writing Cursor path compares exact-base failures and preserves
|
||||
assert.match(codex, /fix-test-comparison\.json/);
|
||||
assert.match(codex, /steps\.fixpatch\.outputs\.artifact_ready == 'true'/);
|
||||
assert.match(codex, /fix-candidate-tests\.log/);
|
||||
assert.match(codex, /fix-base-tests\.log/);
|
||||
assert.match(implement, /base-tests\.log/);
|
||||
assert.match(followup, /followup-base-tests\.log/);
|
||||
assert.ok(
|
||||
codex.indexOf('name: Install test shell dependencies') <
|
||||
codex.indexOf('name: Capture Codex-fix exact-base test baseline'),
|
||||
|
||||
Reference in new issue
Block a user