优化
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
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 })
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user