Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion src/automation/publishOnPark.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ vi.mock('../support/worktreeManager.js', () => ({ commitAndCreatePRWithHead }));
vi.mock('../core/eventHub.js', () => ({ broadcastEvent: vi.fn() }));

import { PublicationScopeMismatchError } from '../support/publicationScopeFence.js';
import { PUBLICATION_SCOPE_PARK_REASON, WORKER_NO_CHANGES_PARK_REASON, publishApprovedWork, publishFinishedRun, publishParkedIfNeeded, publishParkedWork, publishStuckWork, publishUnfinishedWork, shouldPublishParkedWork, shouldPublishUnfinishedWork } from './publishOnPark.js';
import { PUBLICATION_SCOPE_PARK_REASON, WORKER_NO_CHANGES_PARK_REASON, publishApprovedWork, publishFinishedRun, publishParkedIfNeeded, publishParkedWork, publishStuckWork, publishUnfinishedWork, shouldPublishParkedWork, shouldPublishUnfinishedWork, summarizeFailure } from './publishOnPark.js';

beforeEach(() => {
commitAndCreatePRWithHead.mockReset();
Expand Down Expand Up @@ -48,6 +48,41 @@ describe('shouldPublishParkedWork (AGT-4076)', () => {
});
});

describe('summarizeFailure (AGT-4672)', () => {
const dots = '.'.repeat(71);
const pytest = [
`[pytest:apps/pipelines] ${dots} [ 10%]`,
`${dots.slice(0, 40)}ss${dots.slice(0, 29)} [ 11%]`,
`${'s'.repeat(30)}${dots.slice(0, 41)} [ 12%]`,
'=================================== FAILURES ===================================',
'___________________________ test_month_column_width ____________________________',
'FAILED apps/pipelines/tests/test_b1_store_names.py::test_alias_rows - AssertionError: 4 != 8',
'1 failed, 412 passed, 31 skipped in 212.40s',
].join('\n');

it('keeps the failing test and the totals of a long pytest run', () => {
const summary = summarizeFailure(pytest);
expect(summary).toContain('FAILED apps/pipelines/tests/test_b1_store_names.py::test_alias_rows');
expect(summary).toContain('1 failed, 412 passed');
expect(summary).not.toMatch(/\.{8,}/);
expect(summary.length).toBeLessThanOrEqual(500 + 5);
});

it('keeps the head and the tail of a long detail that has no progress rows', () => {
const detail = `worker: ${'a'.repeat(900)} end-marker`;
const summary = summarizeFailure(detail);
expect(summary.startsWith('worker: aaa')).toBe(true);
expect(summary.endsWith('end-marker')).toBe(true);
expect(summary).toContain(' … ');
});

it('leaves a short detail alone and names an empty one', () => {
expect(summarizeFailure('reviewer: tests missing for the new branch')).toBe('reviewer: tests missing for the new branch');
expect(summarizeFailure(undefined)).toBe('no failure detail was recorded');
expect(summarizeFailure(' \n ')).toBe('no failure detail was recorded');
});
});

describe('publication identity (AGT-4145)', () => {
it('durably attaches the exact head returned by a successful reviewed publication', async () => {
commitAndCreatePRWithHead.mockResolvedValue({
Expand Down
21 changes: 18 additions & 3 deletions src/automation/publishOnPark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,11 +459,26 @@ export function shouldPublishUnfinishedWork(
return UNFINISHED_STATUSES.has(result.finalStatus ?? '');
}

// A pytest progress row is only result marks and a percentage. The first 500
// characters of a failing run were all of them, so a draft PR said nothing about
// what failed (AGT-4672: #796, #798); the failing ids and totals come last.
const PROGRESS_RUN = /(?:^|\s)[.sxXFE]{8,}(?=\s|$)/g;
const PERCENT_MARK = /\s\[\s*\d{1,3}%\]/g;
const SUMMARY_CAP = 500;
const SUMMARY_HEAD = 150;

/** One line of a failure detail, bounded, for the draft PR's body. */
function summarizeFailure(detail: string | undefined): string {
const flat = (detail ?? '').replace(/\s+/g, ' ').trim();
export function summarizeFailure(detail: string | undefined): string {
const flat = (detail ?? '')
.replace(/\s+/g, ' ')
.replace(PROGRESS_RUN, ' ')
.replace(PERCENT_MARK, '')
.replace(/\s+/g, ' ')
.trim();
if (!flat) return 'no failure detail was recorded';
return flat.length > 500 ? `${flat.slice(0, 500)}…` : flat;
if (flat.length <= SUMMARY_CAP) return flat;
// Keep both ends: the stage that failed leads, the final failure lines trail.
return `${flat.slice(0, SUMMARY_HEAD)} … ${flat.slice(-(SUMMARY_CAP - SUMMARY_HEAD))}`;
}

/**
Expand Down
Loading