254 lines
5.7 KiB
JavaScript
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'],
|
|
);
|
|
});
|