Files
pnpm/.github/scripts/ts-test-scope.test.mjs
T
00f739e767 fix(ci): run v11 tests for registry changes (#17547)
Include pnpr sources and fixtures in the TypeScript CI change filter.
The downstream test scope already selects the full suite for registry changes,
but the entry gate skipped every test job for pnpr-only pull requests.

Exclude pnpr/docs/** with the some-with-excludes quantifier, matching the
pnpr/docs/ exemption in ts-test-scope.mjs, so docs-only registry changes
still skip the TS jobs.

Cover the filter configuration and full-suite selection for registry-only PRs
in the existing CI scope tests.

Fixes https://github.com/pnpm/tasks/issues/234

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 21:44:23 +01:00

110 lines
6.9 KiB
JavaScript

import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { test } from 'node:test'
import { URL } from 'node:url'
import { determineTestScope } from './ts-test-scope.mjs'
test('PR scope stays pinned when main advances; global changes and missing bases run everything', () => {
const dir = mkdtempSync(path.join(tmpdir(), 'ci-scope-'))
const origin = path.join(dir, 'origin')
const checkout = path.join(dir, 'checkout')
const git = (cwd, ...args) => execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim()
const commit = (cwd, file, content) => {
mkdirSync(path.dirname(path.join(cwd, file)), { recursive: true })
writeFileSync(path.join(cwd, file), content)
git(cwd, 'add', '.')
git(cwd, '-c', 'user.name=Test', '-c', 'user.email=test@example.invalid', '-c', 'commit.gpgsign=false', 'commit', '-m', 'test')
return git(cwd, 'rev-parse', 'HEAD')
}
try {
mkdirSync(origin)
git(origin, 'init', '-b', 'main')
commit(origin, 'pnpm11/example/src/index.ts', 'base')
const base = commit(origin, 'global-test-config.json', 'original config')
git(dir, 'clone', origin, checkout)
git(checkout, 'switch', '-c', 'pr')
commit(checkout, 'pnpr/crates/example/src/lib.rs', 'registry change')
assert.equal(determineTestScope({ event: 'pull_request', base, cwd: checkout }).full_tests, 'true', 'registry-only PRs run all TS tests')
git(checkout, 'reset', '--hard', 'HEAD^')
commit(checkout, 'pnpr/docs/introduction.md', 'registry docs')
assert.equal(determineTestScope({ event: 'pull_request', base, cwd: checkout }).full_tests, 'false', 'registry docs alone do not require all TS tests')
git(checkout, 'reset', '--hard', 'HEAD^')
commit(checkout, 'pnpm11/example/src/index.ts', 'pr')
commit(origin, 'pnpm11/unrelated/src/index.ts', 'new main')
const scope = () => determineTestScope({ event: 'pull_request', base, cwd: checkout })
assert.equal(scope().full_tests, 'false')
assert.equal(git(checkout, 'rev-parse', 'origin/main'), base)
assert.equal(git(checkout, 'diff', '--name-only', 'origin/main', 'HEAD'), 'pnpm11/example/src/index.ts')
commit(checkout, '.changeset/fix-example.md', 'Release note')
assert.equal(scope().full_tests, 'false', 'TS code plus release notes remain affected')
commit(checkout, 'pnpm/crates/example/src/lib.rs', 'rust')
assert.equal(scope().full_tests, 'false', 'TS code plus pacquet sources remain affected')
for (const file of ['package.json', 'pnpm-lock.yaml', '.pnpmfile.cjs', 'pnpm-workspace.yaml', '.config/pnpm/patches/dependency.patch', '.github/scripts/new-helper.mjs', '.changeset/config.json', 'pnpr/crates/example/src/lib.rs', 'pnpr/.fixtures/packages/example/package.json', 'Cargo.lock']) {
commit(checkout, file, 'global input')
assert.equal(scope().full_tests, 'true', file)
git(checkout, 'reset', '--hard', 'HEAD^')
}
git(checkout, 'mv', 'global-test-config.json', 'pnpm11/example/moved-config.json')
git(checkout, '-c', 'user.name=Test', '-c', 'user.email=test@example.invalid', '-c', 'commit.gpgsign=false', 'commit', '-m', 'rename global config')
assert.equal(scope().full_tests, 'true', 'moving a global input into a package still affects all tests')
git(checkout, 'reset', '--hard', 'HEAD^')
git(checkout, 'rm', 'global-test-config.json')
git(checkout, '-c', 'user.name=Test', '-c', 'user.email=test@example.invalid', '-c', 'commit.gpgsign=false', 'commit', '-m', 'delete global config')
assert.equal(scope().full_tests, 'true', 'deleted global inputs still affect all tests')
assert.equal(determineTestScope({ event: 'pull_request', base: 'f'.repeat(40), cwd: checkout }).full_tests, 'true')
} finally {
rmSync(dir, { recursive: true, force: true })
}
})
test('merge queues, main, manual and unknown events cannot narrow tests', () => {
for (const event of ['merge_group', 'push', 'workflow_dispatch', 'unknown', undefined]) {
assert.equal(determineTestScope({ event, base: 'a'.repeat(40) }).full_tests, 'true')
}
for (const base of [undefined, '', 'main', '--upload-pack=evil', 'a'.repeat(39)]) {
assert.equal(determineTestScope({ event: 'pull_request', base }).full_tests, 'true')
}
})
test('TS gate includes the registry sources and fixtures used by the v11 tests', () => {
const workflow = readFileSync(new URL('../workflows/ci.yml', import.meta.url), 'utf8')
const filters = workflow.split(' ts:\n')[1].split(' changesets:\n')[0]
const patterns = filters.split('\n').map(line => line.match(/- '([^']+)'/)?.[1]).filter(Boolean)
assert.ok(workflow.includes('predicate-quantifier: some-with-excludes'), 'the pnpr/docs exclusion needs some-with-excludes')
for (const pattern of ['!pnpr/docs/**', 'pnpm11/**', 'pnpr/**', '.github/workflows/build-pnpr.yml']) {
assert.ok(patterns.includes(pattern), `required TS test input: ${pattern}`)
}
})
test('Rust gate configuration keeps exclusions limited to approved documentation', () => {
const workflow = readFileSync(new URL('../workflows/pacquet-ci.yml', import.meta.url), 'utf8')
const filters = workflow.slice(workflow.indexOf(' predicate-quantifier:'), workflow.indexOf('\n test:'))
assert.match(filters, /predicate-quantifier: some-with-excludes/)
const patterns = filters.split(' rust:\n')[1].split('\n').map(line => line.match(/- '([^']+)'/)?.[1]).filter(Boolean)
const docs = filters.split(' docs:\n')[1].split(' rust:\n')[0].split('\n').map(line => line.match(/- '([^']+)'/)?.[1]).filter(Boolean)
assert.deepEqual(patterns.filter(pattern => pattern.startsWith('!')), [
'!pnpm/docs/**',
'!pnpr/docs/**',
'!pnpm/*.md',
'!pnpm/plans/*.md',
'!pnpm/scripts/*.md',
'!pnpm/crates/*/*.md',
'!pnpm/tasks/*/*.md',
'!pnpr/*.md',
'!pnpr/crates/*/*.md',
'!pnpr/docker/*.md',
'!pnpr/client/*.md',
])
// Markdown under pnpr/.fixtures ships inside registry fixtures.
const recursiveDocs = new Set(['!pnpm/docs/**', '!pnpr/docs/**'])
assert.ok(!patterns.some(pattern => pattern.startsWith('!') && pattern.includes('**') && !recursiveDocs.has(pattern)), 'recursive exclusions only cover product documentation')
for (const pattern of ['pnpm/**', 'pnpr/**', '.config/nextest.toml', 'fixtures/**', 'pnpm11/installing/deps-installer/test/fixtures/patch-pkg/**', 'pnpm11/deps/compliance/commands/test/sbom/fixtures/**']) {
assert.ok(patterns.includes(pattern), `required test input: ${pattern}`)
}
assert.deepEqual(docs, patterns.filter(pattern => pattern.startsWith('!')).map(pattern => pattern.slice(1)), 'docs covers every exclusion, so the spell check still runs')
assert.match(workflow.slice(workflow.indexOf('\n typos:'), workflow.indexOf('\n deny:')), /needs.changes.outputs.docs == 'true'/)
})