Files
Genarrative/scripts/rust-build-cache.test.mjs
T
Git Hooks Test 00b78c9701
Project CI / Repository checks (push) Successful in 3m40s
Project CI / Backend tests (push) Successful in 4m9s
Project CI / Frontend tests (push) Successful in 4m10s
Project CI / Native shell tests (push) Failing after 11m55s
治理Rust构建缓存膨胀
关闭测试 profile 的增量编译并对齐 AGC 开发调试配置

新增受控缓存审计和显式 incremental 清理命令

补齐安全测试、开发运维说明与团队踩坑记录
2026-08-22 11:52:14 +08:00

254 lines
5.7 KiB
JavaScript

import assert from 'node:assert/strict';
import {
mkdir,
mkdtemp,
readFile,
rm,
symlink,
writeFile,
} from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import {
activeRustBuildProcesses,
auditRustBuildCache,
cleanIncrementalCaches,
parseArguments,
parseWindowsTaskList,
} from './rust-build-cache.mjs';
async function createFixture(t) {
const root = await mkdtemp(path.join(os.tmpdir(), 'genarrative-rust-cache-'));
t.after(() => rm(root, { recursive: true, force: true }));
await mkdir(path.join(root, 'server-rs', 'target', 'debug', 'incremental'), {
recursive: true,
});
await mkdir(path.join(root, 'server-rs', 'target', 'debug', 'deps'), {
recursive: true,
});
await mkdir(
path.join(
root,
'apps',
'ai-game-creator-shell',
'src-tauri',
'target',
'debug',
'incremental',
),
{ recursive: true },
);
await mkdir(
path.join(
root,
'apps',
'ai-game-creator-shell',
'src-tauri',
'target',
'release',
),
{ recursive: true },
);
await writeFile(path.join(root, 'package.json'), '{}');
await writeFile(path.join(root, 'server-rs', 'Cargo.toml'), '[workspace]');
await writeFile(
path.join(root, 'apps', 'ai-game-creator-shell', 'src-tauri', 'Cargo.toml'),
'[package]\nname="fixture"\nversion="0.0.0"',
);
await writeFile(
path.join(
root,
'server-rs',
'target',
'debug',
'incremental',
'server.bin',
),
Buffer.alloc(17),
);
await writeFile(
path.join(root, 'server-rs', 'target', 'debug', 'deps', 'keep.rlib'),
Buffer.alloc(5),
);
await writeFile(
path.join(
root,
'apps',
'ai-game-creator-shell',
'src-tauri',
'target',
'debug',
'incremental',
'agc.bin',
),
Buffer.alloc(23),
);
await writeFile(
path.join(
root,
'apps',
'ai-game-creator-shell',
'src-tauri',
'target',
'release',
'keep.exe',
),
Buffer.alloc(7),
);
return root;
}
test('audit is read-only and clean requires explicit apply', async (t) => {
const root = await createFixture(t);
const audit = await auditRustBuildCache({ repoRoot: root, maxBytes: 1 });
assert.equal(audit.incrementalBytes, 40);
assert.equal(audit.warning, true);
const dryRun = await cleanIncrementalCaches({ repoRoot: root });
assert.equal(dryRun.applied, false);
assert.equal(dryRun.plannedBytes, 40);
assert.equal(
(
await readFile(
path.join(
root,
'server-rs',
'target',
'debug',
'incremental',
'server.bin',
),
)
).length,
17,
);
});
test('apply removes only fixed incremental directories and is idempotent', async (t) => {
const root = await createFixture(t);
const result = await cleanIncrementalCaches({
repoRoot: root,
apply: true,
processNames: [],
});
assert.equal(result.applied, true);
assert.equal(result.auditAfter.incrementalBytes, 0);
assert.equal(
(
await readFile(
path.join(root, 'server-rs', 'target', 'debug', 'deps', 'keep.rlib'),
)
).length,
5,
);
assert.equal(
(
await readFile(
path.join(
root,
'apps',
'ai-game-creator-shell',
'src-tauri',
'target',
'release',
'keep.exe',
),
)
).length,
7,
);
const replay = await cleanIncrementalCaches({
repoRoot: root,
apply: true,
processNames: [],
});
assert.equal(replay.freedBytes, 0);
});
test('apply fails closed while cargo or rustc is active', async (t) => {
const root = await createFixture(t);
await assert.rejects(
cleanIncrementalCaches({
repoRoot: root,
apply: true,
processNames: ['cargo.exe', 'node.exe'],
}),
/活跃 Rust 构建进程/u,
);
assert.equal(
(
await readFile(
path.join(
root,
'server-rs',
'target',
'debug',
'incremental',
'server.bin',
),
)
).length,
17,
);
});
test('managed paths reject a linked target directory', async (t) => {
const root = await createFixture(t);
const incremental = path.join(
root,
'apps',
'ai-game-creator-shell',
'src-tauri',
'target',
'debug',
'incremental',
);
const outside = await mkdtemp(
path.join(os.tmpdir(), 'genarrative-rust-cache-outside-'),
);
t.after(() => rm(outside, { recursive: true, force: true }));
await rm(incremental, { recursive: true });
try {
await symlink(
outside,
incremental,
process.platform === 'win32' ? 'junction' : 'dir',
);
} catch (error) {
if (error?.code === 'EPERM') {
t.skip('current Windows token cannot create a junction fixture');
return;
}
throw error;
}
await assert.rejects(
auditRustBuildCache({ repoRoot: root }),
/符号链接或 junction/u,
);
});
test('argument and process parsing keep the destructive boundary explicit', () => {
assert.deepEqual(parseArguments([]), {
cleanIncremental: false,
apply: false,
json: false,
maxGiB: 120,
});
assert.equal(parseArguments(['--clean-incremental', '--apply']).apply, true);
assert.throws(() => parseArguments(['--apply']), /只能与/u);
assert.throws(() => parseArguments(['--root', 'C:\\']), /未知参数/u);
assert.deepEqual(
activeRustBuildProcesses(['Cargo.EXE', 'node.exe', 'rustc.exe']),
['cargo.exe', 'rustc.exe'],
);
assert.deepEqual(
parseWindowsTaskList(
'"cargo.exe","1","Console","1","10 K"\r\n"node.exe","2"',
),
['cargo.exe', 'node.exe'],
);
});