mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-12 22:13:05 +00:00
feat(ops96): motor de métricas baratas + @vitest/coverage-v8 atrás de flag
This commit is contained in:
parent
b271d83f6f
commit
c13174b8aa
6 changed files with 397 additions and 1 deletions
|
|
@ -19,7 +19,7 @@
|
|||
"server-only": ["tests/helpers/serverOnly.ts"]
|
||||
},
|
||||
"ignoreBinaries": [],
|
||||
"ignoreDependencies": ["tailwindcss-animate", "tw-animate-css", "shadcn", "eslint-config-next"],
|
||||
"ignoreDependencies": ["tailwindcss-animate", "tw-animate-css", "shadcn", "eslint-config-next", "@vitest/coverage-v8"],
|
||||
"rules": {
|
||||
"exports": "error",
|
||||
"types": "error",
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@
|
|||
"@types/web-push": "^3.6.4",
|
||||
"@types/yauzl": "^3.4.0",
|
||||
"@vitejs/plugin-react": "4.5.2",
|
||||
"@vitest/coverage-v8": "4.0.18",
|
||||
"csv-parse": "^7.0.1",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-next": "15.4.11",
|
||||
|
|
|
|||
|
|
@ -219,6 +219,9 @@ importers:
|
|||
'@vitejs/plugin-react':
|
||||
specifier: 4.5.2
|
||||
version: 4.5.2(vite@7.3.1(@types/node@22.19.9)(jiti@2.7.0)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0))
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 4.0.18
|
||||
version: 4.0.18(vitest@4.0.18(@types/node@22.19.9)(happy-dom@20.8.3)(jiti@2.7.0)(jsdom@28.0.0(@noble/hashes@1.8.0))(lightningcss@1.30.2)(msw@2.12.10(@types/node@22.19.9)(@typescript/typescript6@6.0.2))(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0))
|
||||
csv-parse:
|
||||
specifier: ^7.0.1
|
||||
version: 7.0.1
|
||||
|
|
@ -630,6 +633,10 @@ packages:
|
|||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2':
|
||||
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@borewit/text-codec@0.2.1':
|
||||
resolution: {integrity: sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==}
|
||||
|
||||
|
|
@ -3794,6 +3801,15 @@ packages:
|
|||
peerDependencies:
|
||||
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0
|
||||
|
||||
'@vitest/coverage-v8@4.0.18':
|
||||
resolution: {integrity: sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==}
|
||||
peerDependencies:
|
||||
'@vitest/browser': 4.0.18
|
||||
vitest: 4.0.18
|
||||
peerDependenciesMeta:
|
||||
'@vitest/browser':
|
||||
optional: true
|
||||
|
||||
'@vitest/expect@4.0.18':
|
||||
resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==}
|
||||
|
||||
|
|
@ -3992,6 +4008,9 @@ packages:
|
|||
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
ast-v8-to-istanbul@0.3.12:
|
||||
resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==}
|
||||
|
||||
async-function@1.0.0:
|
||||
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -5376,6 +5395,9 @@ packages:
|
|||
resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||
|
||||
html-escaper@2.0.2:
|
||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||
|
||||
html-url-attributes@3.0.1:
|
||||
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
|
||||
|
||||
|
|
@ -5699,6 +5721,18 @@ packages:
|
|||
isomorphic.js@0.2.5:
|
||||
resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==}
|
||||
|
||||
istanbul-lib-coverage@3.2.2:
|
||||
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
istanbul-lib-report@3.0.1:
|
||||
resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
istanbul-reports@3.2.0:
|
||||
resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
iterator.prototype@1.1.5:
|
||||
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -5721,6 +5755,9 @@ packages:
|
|||
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
js-tokens@10.0.0:
|
||||
resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
|
|
@ -5963,6 +6000,13 @@ packages:
|
|||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
magicast@0.5.4:
|
||||
resolution: {integrity: sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==}
|
||||
|
||||
make-dir@4.0.0:
|
||||
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
markdown-table@3.0.4:
|
||||
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
|
||||
|
||||
|
|
@ -8583,6 +8627,8 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2': {}
|
||||
|
||||
'@borewit/text-codec@0.2.1': {}
|
||||
|
||||
'@csstools/color-helpers@6.0.1': {}
|
||||
|
|
@ -11656,6 +11702,20 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitest/coverage-v8@4.0.18(vitest@4.0.18(@types/node@22.19.9)(happy-dom@20.8.3)(jiti@2.7.0)(jsdom@28.0.0(@noble/hashes@1.8.0))(lightningcss@1.30.2)(msw@2.12.10(@types/node@22.19.9)(@typescript/typescript6@6.0.2))(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0))':
|
||||
dependencies:
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
'@vitest/utils': 4.0.18
|
||||
ast-v8-to-istanbul: 0.3.12
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
istanbul-lib-report: 3.0.1
|
||||
istanbul-reports: 3.2.0
|
||||
magicast: 0.5.4
|
||||
obug: 2.1.1
|
||||
std-env: 3.10.0
|
||||
tinyrainbow: 3.0.3
|
||||
vitest: 4.0.18(@types/node@22.19.9)(happy-dom@20.8.3)(jiti@2.7.0)(jsdom@28.0.0(@noble/hashes@1.8.0))(lightningcss@1.30.2)(msw@2.12.10(@types/node@22.19.9)(@typescript/typescript6@6.0.2))(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0)
|
||||
|
||||
'@vitest/expect@4.0.18':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
|
|
@ -11909,6 +11969,12 @@ snapshots:
|
|||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
ast-v8-to-istanbul@0.3.12:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
estree-walker: 3.0.3
|
||||
js-tokens: 10.0.0
|
||||
|
||||
async-function@1.0.0: {}
|
||||
|
||||
atomic-sleep@1.0.0: {}
|
||||
|
|
@ -13435,6 +13501,8 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- '@noble/hashes'
|
||||
|
||||
html-escaper@2.0.2: {}
|
||||
|
||||
html-url-attributes@3.0.1: {}
|
||||
|
||||
http-errors@2.0.1:
|
||||
|
|
@ -13699,6 +13767,19 @@ snapshots:
|
|||
|
||||
isomorphic.js@0.2.5: {}
|
||||
|
||||
istanbul-lib-coverage@3.2.2: {}
|
||||
|
||||
istanbul-lib-report@3.0.1:
|
||||
dependencies:
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
make-dir: 4.0.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
istanbul-reports@3.2.0:
|
||||
dependencies:
|
||||
html-escaper: 2.0.2
|
||||
istanbul-lib-report: 3.0.1
|
||||
|
||||
iterator.prototype@1.1.5:
|
||||
dependencies:
|
||||
define-data-property: 1.1.4
|
||||
|
|
@ -13718,6 +13799,8 @@ snapshots:
|
|||
|
||||
joycon@3.1.1: {}
|
||||
|
||||
js-tokens@10.0.0: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-yaml@4.1.1:
|
||||
|
|
@ -13967,6 +14050,16 @@ snapshots:
|
|||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
magicast@0.5.4:
|
||||
dependencies:
|
||||
'@babel/parser': 7.29.7
|
||||
'@babel/types': 7.29.7
|
||||
source-map-js: 1.2.1
|
||||
|
||||
make-dir@4.0.0:
|
||||
dependencies:
|
||||
semver: 7.7.4
|
||||
|
||||
markdown-table@3.0.4: {}
|
||||
|
||||
marked@14.0.0: {}
|
||||
|
|
|
|||
105
scripts/lib/testing-audit-metrics-core.mjs
Normal file
105
scripts/lib/testing-audit-metrics-core.mjs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
* Pure core for testing-audit metrics (OPS96) — normalization of native
|
||||
* runner reports into the audit's per-layer portrait. No I/O here: the CLI
|
||||
* (scripts/testing-audit-metrics.mjs) spawns the suites and feeds these
|
||||
* functions; the unit spec pins their behavior against fixture payloads.
|
||||
*/
|
||||
|
||||
const DEFAULT_TOP = 15
|
||||
|
||||
const ms = (value) => `${(value / 1000).toFixed(1)}s`
|
||||
|
||||
/** @returns {number} */
|
||||
const fileDurationMs = (entry) => {
|
||||
if (typeof entry.endTime === 'number' && typeof entry.startTime === 'number') {
|
||||
return Math.max(0, entry.endTime - entry.startTime)
|
||||
}
|
||||
return entry.assertionResults?.reduce((sum, result) => sum + (result.duration ?? 0), 0) ?? 0
|
||||
}
|
||||
|
||||
const slowestAssertion = (entry) =>
|
||||
(entry.assertionResults ?? [])
|
||||
.filter((result) => typeof result.duration === 'number')
|
||||
.reduce(
|
||||
(slowest, result) => (slowest === null || result.duration > slowest.duration ? result : slowest),
|
||||
null,
|
||||
)
|
||||
|
||||
/**
|
||||
* Normalize a Vitest JSON reporter payload (jest-compatible shape) into a
|
||||
* layer summary: totals plus per-file rows sorted by duration desc.
|
||||
*
|
||||
* @param {unknown} report parsed JSON from `vitest run --reporter=json`
|
||||
* @returns {{
|
||||
* totals: { files: number, tests: number, failedTests: number, durationMs: number },
|
||||
* rows: Array<{ file: string, tests: number, durationMs: number, slowestTest: string | null, slowestMs: number | null }>,
|
||||
* }}
|
||||
*/
|
||||
export const summarizeVitestReport = (report) => {
|
||||
if (!report || !Array.isArray(report.testResults)) {
|
||||
throw new Error('invalid vitest JSON report: missing testResults')
|
||||
}
|
||||
const rows = report.testResults.map((entry) => {
|
||||
const slowest = slowestAssertion(entry)
|
||||
return {
|
||||
file: String(entry.name ?? '(desconhecido)').replace(/^.*tests\//, 'tests/'),
|
||||
tests: entry.assertionResults?.length ?? 0,
|
||||
durationMs: fileDurationMs(entry),
|
||||
slowestTest: slowest ? slowest.title : null,
|
||||
slowestMs: slowest ? slowest.duration : null,
|
||||
}
|
||||
})
|
||||
rows.sort((a, b) => b.durationMs - a.durationMs)
|
||||
return {
|
||||
totals: {
|
||||
files: rows.length,
|
||||
tests: Number(report.numTotalTests ?? rows.reduce((sum, row) => sum + row.tests, 0)),
|
||||
failedTests: Number(report.numFailedTests ?? 0),
|
||||
durationMs: rows.reduce((sum, row) => sum + row.durationMs, 0),
|
||||
},
|
||||
rows,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Markdown table of the slowest spec files of a layer.
|
||||
*
|
||||
* @param {ReturnType<typeof summarizeVitestReport>} summary
|
||||
* @param {{ top?: number }} [options]
|
||||
*/
|
||||
export const renderSlowestFilesTable = (summary, { top = DEFAULT_TOP } = {}) => {
|
||||
const lines = [
|
||||
'| arquivo | testes | duração | teste mais lento |',
|
||||
'|---|---:|---:|---|',
|
||||
]
|
||||
for (const row of summary.rows.slice(0, Math.max(0, top))) {
|
||||
const slowest =
|
||||
row.slowestTest === null ? '—' : `${row.slowestTest} (${ms(row.slowestMs ?? 0)})`
|
||||
lines.push(`| ${row.file} | ${row.tests} | ${ms(row.durationMs)} | ${slowest} |`)
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* Static inventory of an e2e spec source — counting only, never execution.
|
||||
*
|
||||
* @param {{ file: string, content: string }} sources
|
||||
* @returns {Array<{ file: string, describes: number, tests: number }>}
|
||||
*/
|
||||
export const inventoryE2ESource = ({ file, content }) => ({
|
||||
file,
|
||||
describes: (content.match(/\bdescribe\s*\(/g) ?? []).length,
|
||||
tests: (content.match(/\btest\s*\(/g) ?? []).length,
|
||||
})
|
||||
|
||||
/** Markdown table of the static e2e inventory (durations deliberately absent). */
|
||||
export const renderE2EInventoryTable = (rows) => {
|
||||
const lines = [
|
||||
'| arquivo | describes | testes | duração |',
|
||||
'|---|---:|---:|---|',
|
||||
...rows.map(
|
||||
(row) => `| ${row.file} | ${row.describes} | ${row.tests} | não medido — execução noturna proibida |`,
|
||||
),
|
||||
]
|
||||
return lines.join('\n')
|
||||
}
|
||||
96
scripts/testing-audit-metrics.mjs
Normal file
96
scripts/testing-audit-metrics.mjs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* testing-audit-metrics (OPS96) — the cheap-metrics tool behind /testing-audit:
|
||||
* per-layer portrait of the suite from native reporters, nothing third-party.
|
||||
*
|
||||
* node scripts/testing-audit-metrics.mjs unit|int [--top N]
|
||||
* node scripts/testing-audit-metrics.mjs e2e-inventory
|
||||
*
|
||||
* unit/int: spawns `pnpm test:<layer> --reporter=json` (the package scripts
|
||||
* carry their own env guards — invalid DATABASE_URL for unit, .env.test.local
|
||||
* worktree DB for int), parses the JSON payload and prints totals plus a
|
||||
* slowest-files markdown table for the audit report. The runner's exit code
|
||||
* propagates: a red suite still prints its portrait, then fails.
|
||||
* e2e-inventory: static count of describe/test blocks per spec file — durations
|
||||
* are deliberately absent because running Playwright overnight is forbidden.
|
||||
*/
|
||||
import { readdirSync, readFileSync } from 'node:fs'
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import {
|
||||
inventoryE2ESource,
|
||||
renderE2EInventoryTable,
|
||||
renderSlowestFilesTable,
|
||||
summarizeVitestReport,
|
||||
} from './lib/testing-audit-metrics-core.mjs'
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const E2E_DIR = 'tests/e2e'
|
||||
const DEFAULT_TOP = 15
|
||||
|
||||
const usage = () => {
|
||||
console.error('usage: node scripts/testing-audit-metrics.mjs <unit|int|e2e-inventory> [--top N]')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
const mode = args.find((arg) => !arg.startsWith('--'))
|
||||
if (mode !== 'unit' && mode !== 'int' && mode !== 'e2e-inventory') usage()
|
||||
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'], {
|
||||
cwd: repoRoot,
|
||||
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 ?? 2)
|
||||
}
|
||||
let report
|
||||
try {
|
||||
report = JSON.parse(result.stdout.slice(jsonStart))
|
||||
} catch (error) {
|
||||
console.error(`[metrics] relatório JSON inválido: ${error instanceof Error ? error.message : error}`)
|
||||
process.exit(2)
|
||||
}
|
||||
const summary = summarizeVitestReport(report)
|
||||
console.log(
|
||||
`\n## ${layer} — ${summary.totals.files} arquivos / ${summary.totals.tests} testes` +
|
||||
` (${summary.totals.failedTests} falhas) / parede dos arquivos ${ms(summary.totals.durationMs)}\n`,
|
||||
)
|
||||
console.log(renderSlowestFilesTable(summary, { top }))
|
||||
if (result.status !== 0) process.exit(result.status)
|
||||
}
|
||||
|
||||
const ms = (value) => `${(value / 1000).toFixed(1)}s`
|
||||
|
||||
const runE2EInventory = () => {
|
||||
const dir = path.join(repoRoot, E2E_DIR)
|
||||
const rows = readdirSync(dir)
|
||||
.filter((name) => name.endsWith('.spec.ts'))
|
||||
.sort()
|
||||
.map((name) =>
|
||||
inventoryE2ESource({
|
||||
file: `${E2E_DIR}/${name}`,
|
||||
content: readFileSync(path.join(dir, name), 'utf8'),
|
||||
}),
|
||||
)
|
||||
const totalTests = rows.reduce((sum, row) => sum + row.tests, 0)
|
||||
console.log(`\n## e2e (inventário estático) — ${rows.length} arquivos / ${totalTests} testes\n`)
|
||||
console.log(renderE2EInventoryTable(rows))
|
||||
}
|
||||
|
||||
if (mode === 'e2e-inventory') runE2EInventory()
|
||||
else await runVitestLayer(mode)
|
||||
101
tests/unit/testingAuditMetricsCore.unit.spec.ts
Normal file
101
tests/unit/testingAuditMetricsCore.unit.spec.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
inventoryE2ESource,
|
||||
renderE2EInventoryTable,
|
||||
renderSlowestFilesTable,
|
||||
summarizeVitestReport,
|
||||
} from '../../scripts/lib/testing-audit-metrics-core.mjs'
|
||||
|
||||
const reportFixture = () => ({
|
||||
numTotalTests: 3,
|
||||
numFailedTests: 0,
|
||||
testResults: [
|
||||
{
|
||||
name: '/abs/repo/tests/unit/b.unit.spec.ts',
|
||||
endTime: 1600,
|
||||
startTime: 500,
|
||||
assertionResults: [
|
||||
{ title: 'slow one', status: 'passed', duration: 400 },
|
||||
{ title: 'fast one', status: 'passed', duration: 10 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '/abs/repo/tests/unit/a.unit.spec.ts',
|
||||
endTime: 2000,
|
||||
startTime: 1000,
|
||||
assertionResults: [{ title: 'only one', status: 'passed', duration: 50 }],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
describe('summarizeVitestReport', () => {
|
||||
it('totals files and tests and sorts rows by duration desc', () => {
|
||||
const summary = summarizeVitestReport(reportFixture())
|
||||
|
||||
expect(summary.totals).toEqual({ files: 2, tests: 3, failedTests: 0, durationMs: 2100 })
|
||||
expect(summary.rows.map((row) => row.file)).toEqual([
|
||||
'tests/unit/b.unit.spec.ts',
|
||||
'tests/unit/a.unit.spec.ts',
|
||||
])
|
||||
})
|
||||
|
||||
it('keeps the slowest assertion per file', () => {
|
||||
const summary = summarizeVitestReport(reportFixture())
|
||||
|
||||
expect(summary.rows[0].slowestTest).toBe('slow one')
|
||||
expect(summary.rows[0].slowestMs).toBe(400)
|
||||
})
|
||||
|
||||
it('falls back to summed durations when the payload lacks timestamps', () => {
|
||||
const payload = {
|
||||
testResults: [{ name: '/x/y.unit.spec.ts', assertionResults: [{ title: 't', duration: 120 }] }],
|
||||
}
|
||||
|
||||
expect(summarizeVitestReport(payload).rows[0].durationMs).toBe(120)
|
||||
})
|
||||
|
||||
it('rejects payloads without testResults', () => {
|
||||
expect(() => summarizeVitestReport({})).toThrow('missing testResults')
|
||||
})
|
||||
})
|
||||
|
||||
describe('renderSlowestFilesTable', () => {
|
||||
it('renders a markdown table limited to top N rows', () => {
|
||||
const table = renderSlowestFilesTable(summarizeVitestReport(reportFixture()), { top: 1 })
|
||||
|
||||
expect(table.split('\n')).toHaveLength(3)
|
||||
expect(table).toContain('| tests/unit/b.unit.spec.ts | 2 | 1.1s | slow one (0.4s) |')
|
||||
})
|
||||
})
|
||||
|
||||
describe('inventoryE2ESource', () => {
|
||||
it('counts describe and test blocks statically', () => {
|
||||
const row = inventoryE2ESource({
|
||||
file: 'tests/e2e/x.e2e.spec.ts',
|
||||
content: "test('a', () => {})\n describe('d', () => { test('b', () => {}) })",
|
||||
})
|
||||
|
||||
expect(row).toEqual({ file: 'tests/e2e/x.e2e.spec.ts', describes: 1, tests: 2 })
|
||||
})
|
||||
|
||||
it('does not count words merely containing the keywords', () => {
|
||||
const row = inventoryE2ESource({
|
||||
file: 'tests/e2e/x.e2e.spec.ts',
|
||||
content: '// contest( and described( are not blocks',
|
||||
})
|
||||
|
||||
expect(row.tests).toBe(0)
|
||||
expect(row.describes).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('renderE2EInventoryTable', () => {
|
||||
it('marks durations as not measured overnight', () => {
|
||||
const table = renderE2EInventoryTable([
|
||||
{ file: 'tests/e2e/x.e2e.spec.ts', describes: 1, tests: 4 },
|
||||
])
|
||||
|
||||
expect(table).toContain('| tests/e2e/x.e2e.spec.ts | 1 | 4 | não medido — execução noturna proibida |')
|
||||
})
|
||||
})
|
||||
Loading…
Reference in a new issue