import assert from 'node:assert/strict' import { spawn } from 'node:child_process' import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' import os from 'node:os' import path from 'node:path' import test from 'node:test' import { fileURLToPath } from 'node:url' const launcher = fileURLToPath(new URL('./local-check.ps1', import.meta.url)) const quote = value => `'${String(value).replaceAll("'", "''")}'` const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) function run(state, args, { cwd = os.tmpdir(), timeout = 15, wait = 15 } = {}) { const script = `& ${quote(launcher)} -WorkingDirectory ${quote(cwd)} -TimeoutSeconds ${timeout} -WaitSeconds ${wait} -Command @(${args.map(quote).join(',')}); exit $LASTEXITCODE` const env = { ...process.env, LOCALAPPDATA: state } delete env.CHONGCHONG_LOCAL_CHECK_OWNER const child = spawn('pwsh', ['-NoProfile', '-NonInteractive', '-OutputFormat', 'Text', '-EncodedCommand', Buffer.from(script, 'utf16le').toString('base64')], { env }) let output = '' child.stdout.on('data', chunk => { output += chunk }) child.stderr.on('data', chunk => { output += chunk }) const done = new Promise((resolve, reject) => { child.on('error', reject) child.on('close', code => resolve({ code, output })) }) return { child, done, output: () => output } } async function until(check, message) { const deadline = Date.now() + 12000 while (Date.now() < deadline) { if (await check()) return await delay(100) } assert.fail(message) } test('local-check serializes different directories, handles failures and recovers process trees', { skip: process.platform !== 'win32', timeout: 90000 }, async t => { const state = await mkdtemp(path.join(os.tmpdir(), 'local-check-test-')) const processes = [] const start = (...args) => { const result = run(state, ...args); processes.push(result); return result } try { await t.test('literal arguments, environment caps and exit codes are preserved', async () => { const result = await start([process.execPath, '-e', 'console.log(JSON.stringify([process.argv[1],process.env.VITEST_MAX_WORKERS,process.env.NODE_OPTIONS,process.env.JAVA_TOOL_OPTIONS]));process.exit(7)', 'space quote \' $() ` ; 中文']).done assert.equal(result.code, 7, result.output) assert.ok(result.output.includes(JSON.stringify('space quote \' $() ` ; 中文'))) assert.match(result.output, /"2","--max-old-space-size=2048"/) assert.match(result.output, /ActiveProcessorCount=2 -Xmx1536m/) }) await t.test('a running check excludes another directory and queue timeout starts nothing', async () => { const first = start([process.execPath, '-e', 'console.log("HOLDER_READY");setTimeout(()=>{},4000)']) await until(() => first.output().includes('HOLDER_READY'), first.output()) const timedOut = await start([process.execPath, '-e', 'console.log("SHOULD_NOT_RUN")'], { wait: 0, cwd: path.dirname(launcher) }).done assert.equal(timedOut.code, 75, timedOut.output) assert.doesNotMatch(timedOut.output, /SHOULD_NOT_RUN/) const second = start([process.execPath, '-e', 'console.log("SECOND_READY")'], { cwd: path.dirname(launcher) }) await until(() => second.output().includes('WAITING'), second.output()) assert.doesNotMatch(second.output(), /SECOND_READY/) assert.equal((await first.done).code, 0) const result = await second.done assert.equal(result.code, 0, result.output) assert.match(result.output, /SECOND_READY/) }) await t.test('execution timeout kills descendants and frees the slot', async () => { const pidFile = path.join(state, 'descendant.pid') const code = `const {spawn}=require('child_process');const fs=require('fs');const c=spawn(process.execPath,['-e','setInterval(()=>{},1000)'],{stdio:'ignore'});fs.writeFileSync(${JSON.stringify(pidFile)},String(c.pid));setInterval(()=>{},1000)` const result = await start([process.execPath, '-e', code], { timeout: 3 }).done assert.equal(result.code, 124, result.output) const descendant = Number(await readFile(pidFile, 'utf8')) await until(() => { try { process.kill(descendant, 0); return false } catch { return true } }, 'descendant survived timeout') assert.equal((await start([process.execPath, '-e', 'process.exit(0)']).done).code, 0) }) await t.test('abrupt wrapper termination kills its descendants and releases the file lock', async () => { const pidFile = path.join(state, 'abrupt.pid') const code = `require('fs').writeFileSync(${JSON.stringify(pidFile)},String(process.pid));console.log('ABRUPT_READY');setInterval(()=>{},1000)` const active = start([process.execPath, '-e', code]) await until(() => active.output().includes('ABRUPT_READY'), active.output()) const descendant = Number(await readFile(pidFile, 'utf8')) active.child.kill() await active.done await until(() => { try { process.kill(descendant, 0); return false } catch { return true } }, 'descendant survived wrapper termination') assert.equal((await start([process.execPath, '-e', 'process.exit(0)']).done).code, 0) }) await t.test('missing commands fail and do not leave the slot occupied', async () => { const result = await start(['chongchong-missing-command-for-test']).done assert.notEqual(result.code, 0, result.output) assert.equal((await start([process.execPath, '-e', 'process.exit(0)']).done).code, 0) }) await t.test('Maven arguments are limited without launching Maven', async () => { const fakeMaven = path.join(state, 'mvn.ps1') await writeFile(fakeMaven, 'ConvertTo-Json -InputObject @($args) -Compress\n') const result = await start([fakeMaven, '-pl', 'example', 'test']).done assert.equal(result.code, 0, result.output) assert.match(result.output, /\["-T","1","-DforkCount=1","-DreuseForks=true","-pl","example","test"\]/) const override = await start([fakeMaven, '-T4', 'test']).done assert.notEqual(override.code, 0, override.output) assert.match(override.output, /Do not override Maven concurrency/) }) await t.test('direct Node test concurrency is supplied and nested locks are rejected', async () => { const nodeTest = path.join(state, 'probe.test.mjs') await writeFile(nodeTest, `import assert from 'node:assert/strict'; assert.ok(process.execArgv.includes('--test-concurrency=2'));\n`) const result = await start([process.execPath, '--test', nodeTest]).done assert.equal(result.code, 0, result.output) const nested = await start(['pwsh', '-NoProfile', '-File', launcher, '-Command', 'node']).done assert.notEqual(nested.code, 0, nested.output) assert.match(nested.output, /Already inside local-check/) }) } finally { for (const item of processes) if (item.child.exitCode === null) item.child.kill() await Promise.all(processes.map(item => item.done)) await rm(state, { recursive: true, force: true }) } })