Smail/test/run.mjs
smail bb4ac4d5f5
Some checks are pending
Security Checks / security (push) Waiting to run
过滤无价值日志并实现最近24小时倒序分页查询
2026-09-09 01:06:45 +08:00

96 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { spawn } from 'node:child_process'
import { createWriteStream } from 'node:fs'
import { mkdir, readdir, writeFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
const root = fileURLToPath(new URL('../', import.meta.url))
const [suite = 'all', ...extra] = process.argv.slice(2)
if (!['all', 'frontend', 'backend', 'deployment'].includes(suite)) {
console.error('用法:node test/run.mjs [all|frontend|backend|deployment] [Go 测试参数]')
process.exit(2)
}
const stamp = new Date().toISOString().replace(/[:.]/g, '-')
const directory = path.join(root, 'test-log', `run-${stamp}-${process.pid}`)
const temporary = path.join(directory, 'tmp')
await mkdir(temporary, { recursive: true })
async function run(name, command, args, cwd) {
const log = createWriteStream(path.join(directory, `${name}.log`))
// 测试使用独立临时根目录,不能继承开发服务的真实配置根目录。
const env = { ...process.env, SMAIL_ROOT: temporary, GOTMPDIR: temporary, TMPDIR: temporary, TEMP: temporary, TMP: temporary }
const child = spawn(command, args, { cwd, env, windowsHide: true, stdio: ['inherit', 'pipe', 'pipe'] })
child.stdout.on('data', data => { process.stdout.write(data); log.write(data) })
child.stderr.on('data', data => { process.stderr.write(data); log.write(data) })
const code = await new Promise(resolve => {
child.once('error', error => { console.error(`测试启动失败:${error.message}`); resolve(1) })
child.once('close', value => resolve(value ?? 1))
})
await new Promise(resolve => log.end(resolve))
return code
}
let result = 0
if (suite === 'all' || suite === 'frontend') {
const tests = (await readdir(path.join(root, 'test', 'frontend')))
.filter(name => name.endsWith('.test.mjs'))
.map(name => path.join(root, 'test', 'frontend', name))
if (!tests.length) throw new Error('没有找到前端测试,拒绝空测试通过')
result = await run('frontend', process.execPath, ['--test', ...tests], path.join(root, 'frontend'))
}
if ((suite === 'all' || suite === 'deployment') && result === 0) {
const tests = (await readdir(path.join(root, 'test', 'deployment')))
.filter(name => name.endsWith('.test.mjs'))
.map(name => path.join(root, 'test', 'deployment', name))
if (!tests.length) throw new Error('没有找到部署测试,拒绝空测试通过')
result = await run('deployment', process.execPath, ['--test', ...tests], root)
}
if ((suite === 'all' || suite === 'backend') && result === 0) {
const args = extra[0] === '--' ? extra.slice(1) : extra
// 独立回归源码集中放在 test/backend,通过 overlay 保留同包测试语义。
const replacements = {
'backend/internal/app/runtime_log_test.go': 'test/backend/app_runtime_log_test.go',
'backend/listen/http_server/operation_log_test.go': 'test/backend/operation_log_test.go',
'backend/controllers/login_audit_test.go': 'test/backend/login_audit_test.go',
'backend/controllers/logs_api_test.go': 'test/backend/logs_api_test.go',
'backend/services/logstore/log_store_test.go': 'test/backend/log_store_test.go',
'backend/services/logstore/log_store_integration_test.go': 'test/backend/log_store_integration_test.go',
'backend/services/runtimelog/runtime_logs_test.go': 'test/backend/runtime_logs_test.go',
'backend/services/runtimelog/migration_test.go': 'test/backend/runtime_log_migration_test.go',
'backend/db/migration/log_migration_test.go': 'test/backend/log_migration_test.go',
'backend/config/audit_changes_test.go': 'test/backend/audit_changes_test.go',
'backend/config/json_format_test.go': 'test/backend/json_format_test.go',
'backend/config/log_formatter_test.go': 'test/backend/log_formatter_test.go',
'backend/config/deployment_state_test.go': 'test/backend/deployment_state_test.go',
'backend/config/ssl_retention_test.go': 'test/backend/ssl_retention_test.go',
'backend/utils/runtimepath/layout_test.go': 'test/backend/runtime_layout_test.go',
'backend/config/data_paths_test.go': 'test/backend/data_paths_test.go',
'backend/config/ssl_custom_regression_test.go': 'test/backend/ssl_custom_test.go',
'backend/config/webauthn_defaults_test.go': 'test/backend/webauthn_config_test.go',
'backend/res_init/webauthn_runtime_test.go': 'test/backend/webauthn_runtime_test.go',
'backend/services/credentials/client_regression_test.go': 'test/backend/protocol_credentials_test.go',
'backend/controllers/client_regression_test.go': 'test/backend/protocol_access_test.go',
'backend/res_init/client_listeners_test.go': 'test/backend/mail_listeners_test.go',
}
const overlay = path.join(directory, 'overlay.json')
const Replace = Object.fromEntries(Object.entries(replacements).map(([target, source]) => [
path.join(root, target), path.join(root, source),
]))
await writeFile(overlay, JSON.stringify({ Replace }, null, 2) + '\n')
// 显式传入的审查 overlay 保持原有行为,默认入口自动包含本轮回归。
const overlayArgs = args.some(arg => arg === '-overlay' || arg.startsWith('-overlay=')) ? [] : [`-overlay=${overlay}`]
const goArguments = ['test', ...overlayArgs, ...(args.length ? args : ['./...'])]
if (process.platform === 'win32') {
const invocation = path.join(directory, 'go-invocation.json')
await writeFile(invocation, JSON.stringify({ arguments: goArguments }, null, 2) + '\n')
result = await run('backend', 'powershell.exe', [
'-NoProfile', '-ExecutionPolicy', 'Bypass', '-File',
path.join(root, 'test', 'deployment', 'run-go.ps1'), '-InvocationFile', invocation,
], root)
} else {
result = await run('backend', 'go', goArguments, path.join(root, 'backend'))
}
}
console.log(`测试记录:${path.relative(root, directory)}`)
process.exitCode = result