From fe75d329a236a8e804972e0b1fdd59fcf01f6da7 Mon Sep 17 00:00:00 2001 From: Jon Peterson Date: Tue, 23 Sep 2025 00:21:45 +0000 Subject: [PATCH 1/3] Add test suite --- __tests__/report/get-report.test.ts | 83 +++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 __tests__/report/get-report.test.ts diff --git a/__tests__/report/get-report.test.ts b/__tests__/report/get-report.test.ts new file mode 100644 index 0000000..050b86a --- /dev/null +++ b/__tests__/report/get-report.test.ts @@ -0,0 +1,83 @@ +import { + DEFAULT_OPTIONS, + getByteLength, + getReport, + MAX_ACTIONS_SUMMARY_LENGTH, + trimReport +} from '../../src/report/get-report' +import {TestCaseResult, TestGroupResult, TestRunResult, TestSuiteResult} from '../../src/test-results' + +// space reserved for closing backticks and newlines +const ALWAYS_RESERVED_SPACE = 5 + +describe('trimReport', () => { + const TEST_LINE_CHAR = '----' + const TEST_LINE_LEN = getByteLength(`${TEST_LINE_CHAR}\n`) + const MAX_LEN_REPORT: string[] = [] + for (let i = 0; i < MAX_ACTIONS_SUMMARY_LENGTH / 5; i++) { + MAX_LEN_REPORT.push(TEST_LINE_CHAR) + } + + it('trims reports that exceed Github limits', () => { + const trimmed = trimReport(MAX_LEN_REPORT, 0, DEFAULT_OPTIONS) + const trimmedLength = getByteLength(trimmed) + + expect(trimmed).toMatch(/has been trimmed\*\*$/) + expectToBeWithinRangeOfCeiling(trimmedLength, MAX_ACTIONS_SUMMARY_LENGTH - ALWAYS_RESERVED_SPACE, TEST_LINE_LEN) + }) + + it('reserves space for prepended string', () => { + const prependStringLen = 10 + const trimmed = trimReport(MAX_LEN_REPORT, prependStringLen, DEFAULT_OPTIONS) + const trimmedLength = getByteLength(trimmed) + + expect(trimmed).toMatch(/has been trimmed\*\*$/) + expectToBeWithinRangeOfCeiling( + trimmedLength, + MAX_ACTIONS_SUMMARY_LENGTH - ALWAYS_RESERVED_SPACE - prependStringLen, + TEST_LINE_LEN + ) + }) +}) + +describe('getReport', () => { + const testCases: TestCaseResult[] = [] + const testCaseString = '-------------------------' // 25 chars long + const FAILURE_ROW_LEN = getByteLength(` ❌ ${testCaseString}\n`) + for (let i = 0; i < MAX_ACTIONS_SUMMARY_LENGTH / 30; i++) { + testCases.push(new TestCaseResult(testCaseString, 'failed', 1)) + } + const MAX_LEN_RESULT = [ + new TestRunResult('run', [new TestSuiteResult('suite', [new TestGroupResult('group', testCases)])]) + ] + + it('trims reports that exceed Github limits', () => { + const trimmed = getReport(MAX_LEN_RESULT, DEFAULT_OPTIONS) + const trimmedLength = getByteLength(trimmed) + + expect(trimmed).toMatch(/has been trimmed\*\*$/) + expectToBeWithinRangeOfCeiling(trimmedLength, MAX_ACTIONS_SUMMARY_LENGTH - ALWAYS_RESERVED_SPACE, FAILURE_ROW_LEN) + }) + + it('reserves space for prepended string', () => { + const prependString = `1 passed, 1 failed and 1 skipped` + const prependStringLen = getByteLength(prependString) + const trimmed = getReport(MAX_LEN_RESULT, DEFAULT_OPTIONS, prependString) + const trimmedLength = getByteLength(trimmed) + + expect(trimmed).toMatch(/has been trimmed\*\*$/) + expectToBeWithinRangeOfCeiling( + trimmedLength, + MAX_ACTIONS_SUMMARY_LENGTH - ALWAYS_RESERVED_SPACE - prependStringLen, + FAILURE_ROW_LEN + ) + }) +}) + +/** + * Asserts a subject number is between a ceiling value and a range below it (inclusive) + */ +function expectToBeWithinRangeOfCeiling(subject: number, ceiling: number, range: number): void { + expect(subject).toBeLessThanOrEqual(ceiling) + expect(subject).toBeGreaterThanOrEqual(ceiling - range) +} From d31e4909786cb402dd7515c383807f913a480bae Mon Sep 17 00:00:00 2001 From: Jon Peterson Date: Tue, 23 Sep 2025 00:22:32 +0000 Subject: [PATCH 2/3] Add shortSummary to getReport truncation logic --- src/main.ts | 25 +++++++++++++++---------- src/report/get-report.ts | 21 +++++++++++++-------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/main.ts b/src/main.ts index 57137ab..0c20284 100644 --- a/src/main.ts +++ b/src/main.ts @@ -175,19 +175,24 @@ class TestReporter { let baseUrl = '' if (this.useActionsSummary) { - const summary = getReport(results, { - listSuites, - listTests, - baseUrl, - onlySummary, - useActionsSummary, - badgeTitle, - reportTitle - }) + const summaryPrepend = `# ${shortSummary}\n` + const summary = getReport( + results, + { + listSuites, + listTests, + baseUrl, + onlySummary, + useActionsSummary, + badgeTitle, + reportTitle + }, + summaryPrepend + ) core.info('Summary content:') core.info(summary) - core.summary.addRaw(`# ${shortSummary}`) + core.summary.addRaw(summaryPrepend) await core.summary.addRaw(summary).write() } else { core.info(`Creating check run ${name}`) diff --git a/src/report/get-report.ts b/src/report/get-report.ts index 5ea44fe..7db2505 100644 --- a/src/report/get-report.ts +++ b/src/report/get-report.ts @@ -6,7 +6,7 @@ import {getFirstNonEmptyLine} from '../utils/parse-utils' import {slug} from '../utils/slugger' const MAX_REPORT_LENGTH = 65535 -const MAX_ACTIONS_SUMMARY_LENGTH = 1048576 +export const MAX_ACTIONS_SUMMARY_LENGTH = 1048576 export interface ReportOptions { listSuites: 'all' | 'failed' | 'none' @@ -28,16 +28,21 @@ export const DEFAULT_OPTIONS: ReportOptions = { reportTitle: '' } -export function getReport(results: TestRunResult[], options: ReportOptions = DEFAULT_OPTIONS): string { +export function getReport( + results: TestRunResult[], + options: ReportOptions = DEFAULT_OPTIONS, + prependString?: string +): string { core.info('Generating check run summary') applySort(results) const opts = {...options} + const prependStringLen = getByteLength(prependString || '') let lines = renderReport(results, opts) let report = lines.join('\n') - if (getByteLength(report) <= getMaxReportLength(options)) { + if (getByteLength(report) + prependStringLen <= getMaxReportLength(options)) { return report } @@ -46,24 +51,24 @@ export function getReport(results: TestRunResult[], options: ReportOptions = DEF opts.listTests = 'failed' lines = renderReport(results, opts) report = lines.join('\n') - if (getByteLength(report) <= getMaxReportLength(options)) { + if (getByteLength(report) + prependStringLen <= getMaxReportLength(options)) { return report } } core.warning(`Test report summary exceeded limit of ${getMaxReportLength(options)} bytes and will be trimmed`) - return trimReport(lines, options) + return trimReport(lines, prependStringLen, options) } function getMaxReportLength(options: ReportOptions = DEFAULT_OPTIONS): number { return options.useActionsSummary ? MAX_ACTIONS_SUMMARY_LENGTH : MAX_REPORT_LENGTH } -function trimReport(lines: string[], options: ReportOptions): string { +export function trimReport(lines: string[], prependStringLen: number, options: ReportOptions): string { const closingBlock = '```' const errorMsg = `**Report exceeded GitHub limit of ${getMaxReportLength(options)} bytes and has been trimmed**` const maxErrorMsgLength = closingBlock.length + errorMsg.length + 2 - const maxReportLength = getMaxReportLength(options) - maxErrorMsgLength + const maxReportLength = getMaxReportLength(options) - maxErrorMsgLength - prependStringLen let reportLength = 0 let codeBlock = false @@ -97,7 +102,7 @@ function applySort(results: TestRunResult[]): void { } } -function getByteLength(text: string): number { +export function getByteLength(text: string): number { return Buffer.byteLength(text, 'utf8') } From 58382da825b376ea6111255f36f419bde8e0fbea Mon Sep 17 00:00:00 2001 From: Jon Peterson Date: Tue, 23 Sep 2025 00:47:06 +0000 Subject: [PATCH 3/3] Restructure assertion for friendlier errors --- __tests__/report/get-report.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/__tests__/report/get-report.test.ts b/__tests__/report/get-report.test.ts index 050b86a..64bf514 100644 --- a/__tests__/report/get-report.test.ts +++ b/__tests__/report/get-report.test.ts @@ -22,7 +22,7 @@ describe('trimReport', () => { const trimmed = trimReport(MAX_LEN_REPORT, 0, DEFAULT_OPTIONS) const trimmedLength = getByteLength(trimmed) - expect(trimmed).toMatch(/has been trimmed\*\*$/) + expect(trimmed.endsWith('has been trimmed**')).toBe(true) // assert was trimmed expectToBeWithinRangeOfCeiling(trimmedLength, MAX_ACTIONS_SUMMARY_LENGTH - ALWAYS_RESERVED_SPACE, TEST_LINE_LEN) }) @@ -31,7 +31,7 @@ describe('trimReport', () => { const trimmed = trimReport(MAX_LEN_REPORT, prependStringLen, DEFAULT_OPTIONS) const trimmedLength = getByteLength(trimmed) - expect(trimmed).toMatch(/has been trimmed\*\*$/) + expect(trimmed.endsWith('has been trimmed**')).toBe(true) // assert was trimmed expectToBeWithinRangeOfCeiling( trimmedLength, MAX_ACTIONS_SUMMARY_LENGTH - ALWAYS_RESERVED_SPACE - prependStringLen, @@ -55,7 +55,7 @@ describe('getReport', () => { const trimmed = getReport(MAX_LEN_RESULT, DEFAULT_OPTIONS) const trimmedLength = getByteLength(trimmed) - expect(trimmed).toMatch(/has been trimmed\*\*$/) + expect(trimmed.endsWith('has been trimmed**')).toBe(true) // assert was trimmed expectToBeWithinRangeOfCeiling(trimmedLength, MAX_ACTIONS_SUMMARY_LENGTH - ALWAYS_RESERVED_SPACE, FAILURE_ROW_LEN) }) @@ -65,7 +65,7 @@ describe('getReport', () => { const trimmed = getReport(MAX_LEN_RESULT, DEFAULT_OPTIONS, prependString) const trimmedLength = getByteLength(trimmed) - expect(trimmed).toMatch(/has been trimmed\*\*$/) + expect(trimmed.endsWith('has been trimmed**')).toBe(true) // assert was trimmed expectToBeWithinRangeOfCeiling( trimmedLength, MAX_ACTIONS_SUMMARY_LENGTH - ALWAYS_RESERVED_SPACE - prependStringLen,