Files
orca/config/scripts/check-flaky-test-pattern-ratchet.mjs
T
Neil 5447a26fd1 perf(lint): keep JS plugins out of default oxlint and adopt ast-grep (#33430)
* perf(lint): keep JS plugins out of default oxlint and adopt ast-grep

Any jsPlugin makes Oxlint hand every file's AST to Node, so the seven custom
plugins cost the default `oxlint` run several times its native time.

- Move app-store-performance, quadratic-buffer-concat, renderer-scrollbar-style
  and ime-enter-guard (scope- or dataflow-aware) to config/oxlint-js-plugins.json,
  run by `pnpm lint:js-plugins`, CI, lint-staged and the changed-lines gate.
- Port sort-comparator-performance, mobile-pairing and native-chat-design-tokens
  to ast-grep rules with rule tests, and delete the plugins.
- Add @ast-grep/cli, config/sgconfig.yml, `pnpm lint:ast-grep` / `test:ast-grep`,
  a CI step, and an ast-grep pass in check-changed-code-quality.
- Add flaky-test rules (wall-clock reads, real-time waits, polling loops,
  unquarantined skips, unexplained timeout bumps), baselined by a count ratchet.

* Enforce flaky-test ratchet on changed files; refresh baseline

* docs(agents): keep custom-rule details out of AGENTS.md

* Resolve ast-grep from the repo so gate scans of other roots work
2026-10-25 00:23:38 -07:00

104 lines
3.6 KiB
JavaScript

import { execFileSync } from 'node:child_process'
import process from 'node:process'
import { pathToFileURL } from 'node:url'
import { baseRuleId, scanAstGrep } from './ast-grep-scan.mjs'
import { resolvePullRequestDiffBase } from './git-pull-request-diff-base.mjs'
import { listTrackedFiles, runCountRatchet } from './ratchet-baseline.mjs'
// Ratchet on the flaky-test ast-grep rules in config/ast-grep/rules/flaky-tests: wall-clock reads,
// real-time waits, polling loops, unquarantined skips, and unexplained timeout bumps. Each row
// counts one rule in one file, so fixing one pattern cannot pay for adding another.
const BASELINE_PATH = 'config/flaky-test-pattern-baseline.txt'
const RULE_FILTER = '^test-'
const HEADER = [
'# Flaky-test pattern findings per rule and file (config/ast-grep/rules/flaky-tests).',
'# This is a RATCHET: counts may only go DOWN. Run `pnpm lint:ast-grep:flaky-tests` to list them.',
'# Prune after a fix: pnpm check:flaky-test-pattern-ratchet --prune'
]
export function countFindings(matches, isTracked = () => true) {
const counts = new Map()
for (const match of matches) {
const file = match.file.split('\\').join('/')
if (!isTracked(file)) {
continue
}
const key = `${baseRuleId(match.ruleId)}:${file}`
counts.set(key, (counts.get(key) ?? 0) + 1)
}
return counts
}
export function collectFlakyTestPatternCounts(root = process.cwd()) {
// Tracked files only, so untracked scratch never changes the baseline.
const tracked = new Set(listTrackedFiles(root, []))
return countFindings(scanAstGrep(root, { filter: RULE_FILTER }), (file) => tracked.has(file))
}
function git(root, args) {
return execFileSync('git', args, {
cwd: root,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore']
})
}
// Why: these patterns are common, so a whole-tree count breaks every PR as soon as any merge adds
// one. Enforce only the files this change touches; unchanged files keep their grandfathered counts.
export function listChangedFiles(root = process.cwd()) {
if (process.env.ORCA_FLAKY_TEST_RATCHET_ALL === '1') {
return null
}
let base
try {
const mergeBase = git(root, ['merge-base', 'HEAD', 'origin/main']).trim()
base = resolvePullRequestDiffBase(root, mergeBase)
} catch {
try {
base = resolvePullRequestDiffBase(root, undefined)
} catch {
return null
}
}
if (!base) {
return null
}
const changed = git(root, ['diff', '--name-only', '--no-renames', base])
.split('\n')
.filter(Boolean)
return new Set(changed)
}
export function changedFileScope(changed) {
if (!changed) {
return undefined
}
return (key) => changed.has(key.slice(key.indexOf(':') + 1))
}
export function main(root = process.cwd(), arg = undefined) {
return runCountRatchet(
{
name: 'flaky-test-pattern',
baselinePath: BASELINE_PATH,
header: HEADER,
collect: collectFlakyTestPatternCounts,
scope: (dir) => changedFileScope(listChangedFiles(dir)),
unit: 'flaky-test finding(s)',
fix: [
'Wait on the condition (expect.poll, vi.waitFor, locator assertions) instead of sleeping or',
'polling, use fake timers or fixed timestamps instead of the wall clock, put',
'`// quarantine: #<issue> <reason>` above a skip, and a reason comment above a timeout bump.',
'A deliberate exception takes `// ast-grep-ignore: <rule-id> -- <reason>` above the line.'
]
},
root,
arg
)
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
process.exit(main(process.cwd(), process.argv[2]))
}