const assert = require('node:assert/strict') const { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } = require('node:fs') const { tmpdir } = require('node:os') const { dirname, join, resolve } = require('node:path') const { spawnSync } = require('node:child_process') const test = require('node:test') const root = resolve(__dirname, '../..') const wrapper = join(root, 'deploy/build-image.sh') const bash = process.platform === 'win32' ? 'C:/Program Files/Git/bin/bash.exe' : 'bash' const shellAvailable = process.platform !== 'win32' || existsSync(bash) // 使用替身验证参数、内存预检和退出码,不启动 Docker 或访问部署环境。 function runBuild(availableMemory, childStatus = '0') { const directory = mkdtempSync(join(tmpdir(), 'frontend-build-resource-')) try { writeFileSync(join(directory, 'awk'), '#!/bin/sh\nprintf "%s\\n" "$TEST_AVAILABLE_MEMORY"\n', { mode: 0o755 }) writeFileSync(join(directory, 'timeout'), '#!/bin/sh\nprintf "%s\\n" watchdog "$@"\nshift 3\nexec "$@"\n', { mode: 0o755 }) writeFileSync(join(directory, 'docker'), '#!/bin/sh\nprintf "%s\\n" "builder=$DOCKER_BUILDKIT" "$@"\nexit "$TEST_CHILD_STATUS"\n', { mode: 0o755 }) return spawnSync(bash, ['-c', 'testbin=$(cd "$1" && pwd); shift; PATH="$testbin:$PATH"; export PATH; exec sh "$@"', 'fixture', directory.replaceAll('\\', '/'), wrapper.replaceAll('\\', '/'), '--build-arg', 'VALUE=with space', '-t', 'web:test', '.'], { encoding: 'utf8', timeout: 10000, env: { ...process.env, DOCKER_BUILDKIT: '1', TEST_AVAILABLE_MEMORY: availableMemory, TEST_CHILD_STATUS: childStatus } }) } finally { assert.equal(dirname(resolve(directory)), resolve(tmpdir())) rmSync(directory, { recursive: true }) } } test('all image targets use the resource gate', () => { const pipeline = readFileSync(join(root, 'Jenkinsfile'), 'utf8') for (const target of ['test', 'production', 'certbot']) { assert.ok(pipeline.includes(`sh deploy/build-image.sh --target ${target} `)) } assert.doesNotMatch(pipeline, /docker build\s/) assert.match(pipeline, /timeout\(time: 30, unit: 'MINUTES'\)/) }) test('build forwards quoted arguments, enforces container budgets and propagates failure', { skip: !shellAvailable }, () => { const result = runBuild('4194304', '23') assert.equal(result.status, 23, result.stderr) assert.match(result.stdout, /watchdog\r?\n--signal=TERM\r?\n--kill-after=30s\r?\n15m\r?\ndocker\r?\nbuild/) assert.match(result.stdout, /builder=0\r?\nbuild/) for (const argument of ['--memory=3g', '--memory-swap=3g', '--cpu-period=100000', '--cpu-quota=200000', '--force-rm']) { assert.ok(result.stdout.split(/\r?\n/).includes(argument), argument) } assert.match(result.stdout, /--build-arg\r?\nVALUE=with space\r?\n-t\r?\nweb:test\r?\n\./) assert.equal(runBuild('8388608').status, 0) assert.equal(runBuild('8388608', '124').status, 124) }) test('low or unreadable host memory refuses to start Docker', { skip: !shellAvailable }, () => { for (const [memory, status] of [['4194303', 75], ['', 69], ['unknown', 69]]) { const result = runBuild(memory) assert.equal(result.status, status, result.stderr) assert.equal(result.stdout, '') } })