Files
pnpm/pnpm11/exec/commands/test/taskRunState.test.ts
Zoltan Kochan 4edeef9176 style(ts): port the perfectionist lint rules to ESLint (#16415)
Add TypeScript ports of the perfectionist rules that the Rust workspace
enforces through dylint.toml to @pnpm/eslint-config, with the same limits,
and fix every finding that needs no refactor.

The size and shape rules (function length, cognitive complexity, nesting,
local bindings, file length) are enforced for packages that already meet
them. The packages that do not are listed in PENDING_SIZE_AND_SHAPE_REFACTOR
in eslint.config.mjs and are refactored in follow-up changes.

The custom rules mirror perfectionist's semantics rather than reusing
sonarjs or max-lines-per-function, so that one limit means the same thing
in both codebases: a closure is a level of the body that contains it, a
nested function declaration is measured on its own, and a test-framework
callback plays the role of a #[test] function.

Bugs found and fixed along the way:
- a registry error body cut at exactly 64 KiB lost its truncation marker
- removing a dependency could leave dangling .bin shims
- constructor/__proto__ names crashed the projects graph, were skipped by
  the injected-deps syncer, and were dropped from writeWantedLockfile's
  returned lockfile

The CI chunk runner learns to run the node:test suite of the ESLint
config package.
2026-09-30 15:10:08 +02:00

524 lines
18 KiB
TypeScript

import fs from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import { afterEach, expect, jest, test } from '@jest/globals'
import type { ProjectRootDir } from '@pnpm/types'
import { type TaskGraph, taskKey } from '@pnpm/workspace.task-scheduler'
import { taskRunExecutionSettings, TaskRunStateContext } from '../src/taskRunState.js'
const temporaryDirectories: string[] = []
const testOnPosix = process.platform === 'win32' ? test.skip : test
afterEach(async () => {
await Promise.all(temporaryDirectories.splice(0).map(async (dir) => fs.rm(dir, { force: true, recursive: true })))
})
test('task execution settings have a stable canonical encoding', () => {
expect(taskRunExecutionSettings({
extraBinPaths: ['tools', 'other-tools'],
extraEnv: { ZED: 'last', ALPHA: 'first' },
modulesDir: 'vendor',
nodeExperimentalPackageMap: true,
nodeOptions: '--conditions=development',
userAgent: 'pnpm/test',
})).toStrictEqual([
'extra-bin-paths=["tools","other-tools"]',
'extra-env=[["ALPHA","first"],["ZED","last"]]',
'modules-dir=vendor',
'node-experimental-package-map=true',
'node-options=--conditions=development',
'user-agent=pnpm/test',
])
})
test('a changed execution setting produces a different invocation identity', async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pnpm-task-state-'))
temporaryDirectories.push(workspaceDir)
const project = path.join(workspaceDir, 'project') as ProjectRootDir
const key = taskKey(project, 'build')
const graph: TaskGraph = new Map([[key, {
project,
taskName: 'build',
scripts: ['build'],
requested: true,
dependencies: [],
}]])
const contextOptions = {
command: 'run' as const,
params: ['build'],
graph,
workspaceDir,
scriptCommands: () => ['build-command'],
}
const first = new TaskRunStateContext({
...contextOptions,
settings: taskRunExecutionSettings({ extraEnv: { MODE: 'first' } }),
})
const second = new TaskRunStateContext({
...contextOptions,
settings: taskRunExecutionSettings({ extraEnv: { MODE: 'second' } }),
})
expect(first.invocation).not.toBe(second.invocation)
})
test('task run state ignores a torn trailing record and removes a completed journal', async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pnpm-task-state-'))
temporaryDirectories.push(workspaceDir)
const firstProject = path.join(workspaceDir, 'first') as ProjectRootDir
const secondProject = path.join(workspaceDir, 'second') as ProjectRootDir
const firstKey = taskKey(firstProject, 'build')
const secondKey = taskKey(secondProject, 'build')
const graph: TaskGraph = new Map([
[firstKey, {
project: firstProject,
taskName: 'build',
scripts: ['build'],
requested: true,
dependencies: [],
}],
[secondKey, {
project: secondProject,
taskName: 'build',
scripts: ['build'],
requested: true,
dependencies: [firstKey],
}],
])
const context = new TaskRunStateContext({
command: 'run',
params: ['build'],
graph,
workspaceDir,
scriptCommands: () => ['build-command'],
})
expect(context.invocation).toBe('76a575bfcc3b67becd98b5ec661be54567b53954a723da167603dad119fab140')
const state = await context.start(new Set([firstKey]))
await state.recordPassed(secondKey, graph.get(secondKey)!)
await fs.appendFile(state.filePath, '{"run":"superseded","project":"unknown","task":"build"}\n')
await fs.appendFile(state.filePath, '{"project":"torn')
await expect(context.readCompletedTasks()).resolves.toStrictEqual(new Set([firstKey, secondKey]))
await state.finish()
await expect(context.readCompletedTasks()).resolves.toBeUndefined()
})
test('task run state rejects a malformed complete record', async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pnpm-task-state-'))
temporaryDirectories.push(workspaceDir)
const project = path.join(workspaceDir, 'project') as ProjectRootDir
const key = taskKey(project, 'build')
const graph: TaskGraph = new Map([[key, {
project,
taskName: 'build',
scripts: ['build'],
requested: true,
dependencies: [],
}]])
const context = new TaskRunStateContext({
command: 'run',
params: ['build'],
graph,
workspaceDir,
scriptCommands: () => ['build-command'],
})
const state = await context.start(new Set())
await fs.appendFile(state.filePath, 'not-json\n')
await expect(context.readCompletedTasks()).resolves.toBeUndefined()
await state.close()
})
test('task run state rejects a null record and a null latest pointer', async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pnpm-task-state-'))
temporaryDirectories.push(workspaceDir)
const project = path.join(workspaceDir, 'project') as ProjectRootDir
const key = taskKey(project, 'build')
const graph: TaskGraph = new Map([[key, {
project,
taskName: 'build',
scripts: ['build'],
requested: true,
dependencies: [],
}]])
const context = new TaskRunStateContext({
command: 'run',
params: ['build'],
graph,
workspaceDir,
scriptCommands: () => ['build-command'],
})
const state = await context.start(new Set())
await fs.appendFile(state.filePath, 'null\n')
await expect(context.readCompletedTasks()).resolves.toBeUndefined()
await fs.writeFile(context.latestStatePath, 'null')
await expect(context.readCompletedTasks()).resolves.toBeUndefined()
await state.close()
})
test('task run state recovers its write queue after an append failure', async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pnpm-task-state-'))
temporaryDirectories.push(workspaceDir)
const project = path.join(workspaceDir, 'project') as ProjectRootDir
const key = taskKey(project, 'build')
const graph: TaskGraph = new Map([[key, {
project,
taskName: 'build',
scripts: ['build'],
requested: true,
dependencies: [],
}]])
const context = new TaskRunStateContext({
command: 'run',
params: ['build'],
graph,
workspaceDir,
scriptCommands: () => ['build-command'],
})
const state = await context.start(new Set())
const file = (state as unknown as { file: { appendFile: (data: string) => Promise<void> } }).file
const appendFile = jest.spyOn(file, 'appendFile').mockRejectedValueOnce(new Error('append failed'))
await expect(state.recordPassed(key, graph.get(key)!)).rejects.toThrow()
await state.recordPassed(key, graph.get(key)!)
appendFile.mockRestore()
await expect(context.readCompletedTasks()).resolves.toStrictEqual(new Set([key]))
await state.finish()
})
test('task run state is disabled after an append permission error', async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pnpm-task-state-'))
temporaryDirectories.push(workspaceDir)
const project = path.join(workspaceDir, 'project') as ProjectRootDir
const key = taskKey(project, 'build')
const secondKey = taskKey(project, 'test')
const graph: TaskGraph = new Map([
[key, {
project,
taskName: 'build',
scripts: ['build'],
requested: true,
dependencies: [],
}],
[secondKey, {
project,
taskName: 'test',
scripts: ['test'],
requested: true,
dependencies: [],
}],
])
const context = new TaskRunStateContext({
command: 'run',
params: ['build'],
graph,
workspaceDir,
scriptCommands: () => ['build-command'],
})
const state = await context.start(new Set())
const file = (state as unknown as { file: { appendFile: (data: string) => Promise<void> } }).file
const appendFile = jest.spyOn(file, 'appendFile').mockRejectedValueOnce(Object.assign(new Error('permission denied'), { code: 'EACCES' }))
await state.recordPassed(key, graph.get(key)!)
await state.recordPassed(secondKey, graph.get(secondKey)!)
expect(appendFile).toHaveBeenCalledTimes(1)
await expect(fs.access(state.filePath)).rejects.toMatchObject({ code: 'ENOENT' })
await expect(context.readCompletedTasks()).resolves.toBeUndefined()
appendFile.mockRestore()
await state.finish()
})
test('task run state stops waiting when an abandoned lock cannot be removed', async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pnpm-task-state-'))
temporaryDirectories.push(workspaceDir)
const project = path.join(workspaceDir, 'project') as ProjectRootDir
const key = taskKey(project, 'build')
const graph: TaskGraph = new Map([[key, {
project,
taskName: 'build',
scripts: ['build'],
requested: true,
dependencies: [],
}]])
const context = new TaskRunStateContext({
command: 'run',
params: ['build'],
graph,
workspaceDir,
scriptCommands: () => ['build-command'],
})
const lockPath = path.join(workspaceDir, 'node_modules', '.pnpm-task-run-state-v1', 'start.lock')
await fs.mkdir(lockPath, { recursive: true })
const now = Date.now()
const staleTime = new Date(now - 60_000)
await fs.utimes(lockPath, staleTime, staleTime)
const rm = jest.spyOn(fs, 'rm').mockRejectedValue(Object.assign(new Error('permission denied'), { code: 'EACCES' }))
const dateNow = jest.spyOn(Date, 'now')
.mockReturnValueOnce(now)
.mockReturnValueOnce(now)
.mockReturnValue(now + 2_000)
try {
const state = await context.start(new Set())
expect(rm).toHaveBeenCalledTimes(1)
await expect(fs.access(context.latestStatePath)).rejects.toMatchObject({ code: 'ENOENT' })
await state.finish()
} finally {
dateNow.mockRestore()
rm.mockRestore()
}
})
test('task run state rejects a symlinked state directory', async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pnpm-task-state-'))
const outsideDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pnpm-task-state-outside-'))
temporaryDirectories.push(workspaceDir, outsideDir)
const project = path.join(workspaceDir, 'project') as ProjectRootDir
const key = taskKey(project, 'build')
const graph: TaskGraph = new Map([[key, {
project,
taskName: 'build',
scripts: ['build'],
requested: true,
dependencies: [],
}]])
const nodeModulesDir = path.join(workspaceDir, 'node_modules')
await fs.mkdir(nodeModulesDir)
await fs.symlink(outsideDir, path.join(nodeModulesDir, '.pnpm-task-run-state-v1'), process.platform === 'win32' ? 'junction' : 'dir')
const context = new TaskRunStateContext({
command: 'run',
params: ['build'],
graph,
workspaceDir,
scriptCommands: () => ['build-command'],
})
await expect(context.start(new Set())).rejects.toMatchObject({ code: 'ERR_PNPM_UNSAFE_TASK_RUN_STATE_PATH' })
await expect(fs.access(path.join(outsideDir, 'latest.json'))).rejects.toMatchObject({ code: 'ENOENT' })
})
testOnPosix('task run state is disabled when node_modules is read-only', async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pnpm-task-state-'))
temporaryDirectories.push(workspaceDir)
const project = path.join(workspaceDir, 'project') as ProjectRootDir
const key = taskKey(project, 'build')
const graph: TaskGraph = new Map([[key, {
project,
taskName: 'build',
scripts: ['build'],
requested: true,
dependencies: [],
}]])
const nodeModulesDir = path.join(workspaceDir, 'node_modules')
await fs.mkdir(nodeModulesDir)
await fs.chmod(nodeModulesDir, 0o555)
const context = new TaskRunStateContext({
command: 'run',
params: ['build'],
graph,
workspaceDir,
scriptCommands: () => ['build-command'],
})
try {
const state = await context.start(new Set())
await state.recordPassed(key, graph.get(key)!)
await state.finish()
await expect(fs.access(context.latestStatePath)).rejects.toMatchObject({ code: 'ENOENT' })
} finally {
await fs.chmod(nodeModulesDir, 0o755)
}
})
test('finishing an older invocation preserves the newer invocation journal', async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pnpm-task-state-'))
temporaryDirectories.push(workspaceDir)
const project = path.join(workspaceDir, 'project') as ProjectRootDir
const key = taskKey(project, 'build')
const graph: TaskGraph = new Map([[key, {
project,
taskName: 'build',
scripts: ['build'],
requested: true,
dependencies: [],
}]])
const context = new TaskRunStateContext({
command: 'run',
params: ['build'],
graph,
workspaceDir,
scriptCommands: () => ['build-command'],
})
const older = await context.start(new Set())
const newer = await context.start(new Set([key]))
await older.finish()
await expect(context.readCompletedTasks()).resolves.toStrictEqual(new Set([key]))
await newer.finish()
await expect(context.readCompletedTasks()).resolves.toBeUndefined()
})
test('a finished journal is not resumable when cleanup is unavailable', async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pnpm-task-state-'))
temporaryDirectories.push(workspaceDir)
const project = path.join(workspaceDir, 'project') as ProjectRootDir
const key = taskKey(project, 'build')
const graph: TaskGraph = new Map([[key, {
project,
taskName: 'build',
scripts: ['build'],
requested: true,
dependencies: [],
}]])
const context = new TaskRunStateContext({
command: 'run',
params: ['build'],
graph,
workspaceDir,
scriptCommands: () => ['build-command'],
})
const state = await context.start(new Set([key]))
const unlink = jest.spyOn(fs, 'unlink').mockRejectedValue(Object.assign(new Error('permission denied'), { code: 'EACCES' }))
try {
await state.finish()
} finally {
unlink.mockRestore()
}
await expect(context.readCompletedTasks()).resolves.toBeUndefined()
})
test('a stale pointer does not hide a newer published invocation journal', async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pnpm-task-state-'))
temporaryDirectories.push(workspaceDir)
const project = path.join(workspaceDir, 'project') as ProjectRootDir
const key = taskKey(project, 'build')
const graph: TaskGraph = new Map([[key, {
project,
taskName: 'build',
scripts: ['build'],
requested: true,
dependencies: [],
}]])
const context = new TaskRunStateContext({
command: 'run',
params: ['build'],
graph,
workspaceDir,
scriptCommands: () => ['build-command'],
})
const older = await context.start(new Set())
const olderHeader = (await fs.readFile(older.filePath, 'utf8')).split('\n')[0]
const newer = await context.start(new Set([key]))
const newerPublishedPath = newer.filePath.replace(/\.jsonl$/, '.published')
await fs.unlink(newerPublishedPath)
await fs.writeFile(context.latestStatePath, olderHeader)
await expect(context.readCompletedTasks()).resolves.toStrictEqual(new Set())
await fs.writeFile(newerPublishedPath, '')
await expect(context.readCompletedTasks()).resolves.toStrictEqual(new Set([key]))
await older.finish()
await expect(context.readCompletedTasks()).resolves.toStrictEqual(new Set([key]))
await newer.finish()
})
test('a stale start does not overwrite a newer invocation', async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pnpm-task-state-'))
temporaryDirectories.push(workspaceDir)
const project = path.join(workspaceDir, 'project') as ProjectRootDir
const key = taskKey(project, 'build')
const graph: TaskGraph = new Map([[key, {
project,
taskName: 'build',
scripts: ['build'],
requested: true,
dependencies: [],
}]])
const context = new TaskRunStateContext({
command: 'run',
params: ['build'],
graph,
workspaceDir,
scriptCommands: () => ['build-command'],
})
const originalOpen = fs.open.bind(fs)
let markOlderBlocked!: () => void
let unblockOlder!: () => void
const olderBlocked = new Promise<void>(resolve => {
markOlderBlocked = resolve
})
const olderGate = new Promise<void>(resolve => {
unblockOlder = resolve
})
let blockNextJournal = true
const open = jest.spyOn(fs, 'open').mockImplementation(async (...args) => {
if (blockNextJournal && String(args[0]).endsWith('.jsonl')) {
blockNextJournal = false
markOlderBlocked()
await olderGate
}
return originalOpen(...args)
})
try {
const olderPromise = context.start(new Set())
await olderBlocked
const lockPath = path.join(workspaceDir, 'node_modules', '.pnpm-task-run-state-v1', 'start.lock')
const staleTime = new Date(Date.now() - 60_000)
await fs.utimes(lockPath, staleTime, staleTime)
const newer = await context.start(new Set([key]))
unblockOlder()
const older = await olderPromise
await expect(context.readCompletedTasks()).resolves.toStrictEqual(new Set([key]))
await older.close()
await newer.close()
} finally {
unblockOlder()
open.mockRestore()
}
})
test('a stale start cannot revive state after a newer invocation finishes', async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pnpm-task-state-'))
temporaryDirectories.push(workspaceDir)
const project = path.join(workspaceDir, 'project') as ProjectRootDir
const key = taskKey(project, 'build')
const graph: TaskGraph = new Map([[key, {
project,
taskName: 'build',
scripts: ['build'],
requested: true,
dependencies: [],
}]])
const context = new TaskRunStateContext({
command: 'run',
params: ['build'],
graph,
workspaceDir,
scriptCommands: () => ['build-command'],
})
const older = await context.start(new Set())
const olderContents = await fs.readFile(older.filePath, 'utf8')
const olderPublishedPath = older.filePath.replace(/\.jsonl$/, '.published')
const newer = await context.start(new Set([key]))
await newer.finish()
await fs.writeFile(older.filePath, olderContents)
await fs.writeFile(olderPublishedPath, '')
await fs.writeFile(context.latestStatePath, olderContents.split('\n')[0])
await expect(context.readCompletedTasks()).resolves.toBeUndefined()
await older.close()
})