mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
fix(ops96-audit): parseVitestStdout tolera sufixo do pnpm em suite vermelha — retrato impresso mesmo com falha (contrato do metrics)
This commit is contained in:
parent
62a7dab8a7
commit
aeb98ad468
3 changed files with 77 additions and 13 deletions
|
|
@ -9,6 +9,40 @@ export const DEFAULT_TOP = 15
|
|||
|
||||
export const ms = (value) => `${(value / 1000).toFixed(1)}s`
|
||||
|
||||
/** Vitest's jest-compatible JSON payload starts with this key. */
|
||||
const VITEST_JSON_ANCHOR = '{"numTotal'
|
||||
|
||||
/**
|
||||
* Extract the Vitest JSON report from the runner's raw stdout. pnpm echoes the
|
||||
* script line BEFORE the JSON and appends its own noise (e.g. the `ELIFECYCLE`
|
||||
* notice) AFTER it when the suite is red; tests may also print console blobs
|
||||
* with braces around the payload. Walking back from the last `}` finds the
|
||||
* payload boundary, so a red suite still yields its portrait.
|
||||
*
|
||||
* @param {string} stdout raw stdout of `vitest run --reporter=json`
|
||||
* @returns {Record<string, unknown>} parsed JSON report
|
||||
*/
|
||||
export const parseVitestStdout = (stdout) => {
|
||||
if (typeof stdout !== 'string' || stdout.length === 0) {
|
||||
throw new Error('vitest não produziu relatório JSON no stdout')
|
||||
}
|
||||
const jsonStart = stdout.indexOf(VITEST_JSON_ANCHOR)
|
||||
if (jsonStart === -1) {
|
||||
throw new Error('vitest não produziu relatório JSON no stdout')
|
||||
}
|
||||
let end = stdout.length
|
||||
while (end > jsonStart) {
|
||||
end = stdout.lastIndexOf('}', end - 1)
|
||||
if (end < jsonStart) break
|
||||
try {
|
||||
return JSON.parse(stdout.slice(jsonStart, end + 1))
|
||||
} catch {
|
||||
// not the payload boundary yet — keep walking back
|
||||
}
|
||||
}
|
||||
throw new Error('relatório JSON inválido')
|
||||
}
|
||||
|
||||
/** @returns {number} */
|
||||
const fileDurationMs = (entry) => {
|
||||
if (typeof entry.endTime === 'number' && typeof entry.startTime === 'number') {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
DEFAULT_TOP,
|
||||
inventoryE2ESource,
|
||||
ms,
|
||||
parseVitestStdout,
|
||||
renderE2EInventoryTable,
|
||||
renderSlowestFilesTable,
|
||||
summarizeVitestReport,
|
||||
|
|
@ -43,9 +44,6 @@ const topFlag = args.indexOf('--top')
|
|||
const top = topFlag === -1 ? DEFAULT_TOP : Number(args[topFlag + 1])
|
||||
if (!Number.isInteger(top) || top <= 0) usage()
|
||||
|
||||
/** Vitest's jest-compatible JSON payload starts with this key. */
|
||||
const VITEST_JSON_ANCHOR = '{"numTotal'
|
||||
|
||||
const runVitestLayer = async (layer) => {
|
||||
console.log(`[metrics] rodando pnpm test:${layer} --reporter=json …`)
|
||||
const result = spawnSync('pnpm', [`test:${layer}`, '--reporter=json'], {
|
||||
|
|
@ -53,19 +51,15 @@ const runVitestLayer = async (layer) => {
|
|||
encoding: 'utf8',
|
||||
maxBuffer: 256 * 1024 * 1024,
|
||||
})
|
||||
const jsonStart = (result.stdout ?? '').indexOf(VITEST_JSON_ANCHOR)
|
||||
if (jsonStart === -1) {
|
||||
console.error(`[metrics] vitest não produziu relatório JSON no stdout (exit ${result.status})`)
|
||||
console.error((result.stderr ?? '').slice(-4000))
|
||||
process.exit(result.status === 0 ? 2 : result.status)
|
||||
}
|
||||
let report
|
||||
try {
|
||||
report = JSON.parse(result.stdout.slice(jsonStart))
|
||||
report = parseVitestStdout(result.stdout ?? '')
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[metrics] relatório JSON inválido: ${error instanceof Error ? error.message : error}`,
|
||||
)
|
||||
console.error(`[metrics] ${error instanceof Error ? error.message : error}`)
|
||||
if (result.status !== 0) {
|
||||
console.error((result.stderr ?? '').slice(-4000))
|
||||
process.exit(result.status)
|
||||
}
|
||||
process.exit(2)
|
||||
}
|
||||
const summary = summarizeVitestReport(report)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
|
|||
|
||||
import {
|
||||
inventoryE2ESource,
|
||||
parseVitestStdout,
|
||||
renderE2EInventoryTable,
|
||||
renderSlowestFilesTable,
|
||||
summarizeVitestReport,
|
||||
|
|
@ -75,6 +76,41 @@ describe('summarizeVitestReport', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('parseVitestStdout', () => {
|
||||
const json = '{"numTotalTestSuites":1,"numTotalTests":3,"numFailedTests":1,"testResults":[]}'
|
||||
|
||||
it('parses the payload when pnpm echoes the script before and appends noise after (red suite)', () => {
|
||||
const stdout = `\n> teqo@1.0.0 test:int /repo\n> cross-env … vitest run\n\n${json}\n ELIFECYCLE Command failed with exit code 1.\n`
|
||||
|
||||
expect(parseVitestStdout(stdout)).toEqual({
|
||||
numTotalTestSuites: 1,
|
||||
numTotalTests: 3,
|
||||
numFailedTests: 1,
|
||||
testResults: [],
|
||||
})
|
||||
})
|
||||
|
||||
it('parses the payload when test console noise with braces follows the report', () => {
|
||||
const stdout = `${json}\nconsole output { "not": "json" } trailing\n`
|
||||
|
||||
expect(parseVitestStdout(stdout).numTotalTests).toBe(3)
|
||||
})
|
||||
|
||||
it('parses a bare payload with surrounding whitespace', () => {
|
||||
expect(parseVitestStdout(` ${json}\n`).numFailedTests).toBe(1)
|
||||
})
|
||||
|
||||
it('rejects output without the vitest anchor', () => {
|
||||
expect(() => parseVitestStdout('no json here')).toThrow(
|
||||
'vitest não produziu relatório JSON no stdout',
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects an anchor without a valid JSON boundary', () => {
|
||||
expect(() => parseVitestStdout('{"numTotal"not json')).toThrow('relatório JSON inválido')
|
||||
})
|
||||
})
|
||||
|
||||
describe('renderSlowestFilesTable', () => {
|
||||
it('renders a markdown table limited to top N rows', () => {
|
||||
const table = renderSlowestFilesTable(summarizeVitestReport(reportFixture()), { top: 1 })
|
||||
|
|
|
|||
Loading…
Reference in a new issue