48eb3d5007
Project CI / AI game creator shell Rust crates (push) Successful in 2m22s
Project CI / AI game creator shell Rust smoke (push) Successful in 3m13s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m28s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m58s
Project CI / Backend tests (push) Successful in 7m16s
Project CI / Native shell tests (push) Successful in 8m10s
Project CI / Frontend tests (push) Successful in 3m49s
Project CI / Repository checks (push) Successful in 4m8s
Project CI / AI game creator shell web tests (push) Successful in 4m1s
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/456 Co-authored-by: Linghong <ink29535@proton.me> Co-committed-by: Linghong <ink29535@proton.me>
218 lines
7.0 KiB
JavaScript
218 lines
7.0 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { spawnSync } from 'node:child_process';
|
|
import {
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
readFileSync,
|
|
rmSync,
|
|
writeFileSync,
|
|
} from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join, resolve } from 'node:path';
|
|
import { test } from 'node:test';
|
|
|
|
const script = resolve('scripts/ci-rust-cache.sh');
|
|
const linuxTest = process.platform === 'linux' ? test : test.skip;
|
|
|
|
function fixture(t) {
|
|
const directory = mkdtempSync(join(tmpdir(), 'ci-rust-cache-test-'));
|
|
const root = join(directory, 'snapshot');
|
|
const bin = join(directory, 'bin');
|
|
mkdirSync(join(root, 'objects'), { recursive: true });
|
|
mkdirSync(bin);
|
|
const envFile = join(directory, 'github-env');
|
|
writeFileSync(envFile, '');
|
|
writeFileSync(join(root, 'rustc.txt'), 'fixture rustc identity\n');
|
|
writeFileSync(join(root, 'source-commit.txt'), 'trusted-master-commit\n');
|
|
writeFileSync(join(root, 'workspace.txt'), `${process.cwd()}\n`);
|
|
writeFileSync(
|
|
join(bin, 'rustc'),
|
|
'#!/bin/bash\necho "fixture rustc identity"\n',
|
|
{ mode: 0o755 },
|
|
);
|
|
writeFileSync(
|
|
join(root, 'sccache'),
|
|
`#!/bin/bash
|
|
set -eu
|
|
case "$1" in
|
|
--stop-server|--show-stats) exit 0 ;;
|
|
esac
|
|
if [[ "$*" == *-vV ]]; then
|
|
[[ "\${PROBE_FAILURE:-}" != 1 ]] || exit 1
|
|
exec "$@"
|
|
fi
|
|
printf '%s\\n' "\${SCCACHE_OSS_BUCKET-unset}" "\${SCCACHE_CONF}" "\${SCCACHE_DIR}" "\${SCCACHE_SERVER_UDS}" "\${SCCACHE_IGNORE_SERVER_IO_ERROR}" "\${SCCACHE_IDLE_TIMEOUT}" >> "\${TRACE}"
|
|
[[ "\${CACHE_FAILURE:-}" != 2 ]] || exit 2
|
|
exec "$@"
|
|
`,
|
|
{ mode: 0o755 },
|
|
);
|
|
const env = {
|
|
...process.env,
|
|
PATH: `${bin}:${process.env.PATH}`,
|
|
GITHUB_ENV: envFile,
|
|
GENARRATIVE_CI_RUST_CACHE_ROOT: root,
|
|
GENARRATIVE_CI_RUST_CACHE_STATE: '',
|
|
RUSTC_WRAPPER: 'bad-inherited-wrapper',
|
|
CARGO_BUILD_RUSTC_WRAPPER: 'bad-inherited-wrapper',
|
|
SCCACHE_OSS_BUCKET: 'must-not-use-publishing-cache',
|
|
TRACE: join(directory, 'trace'),
|
|
};
|
|
function run(args, extraEnv = {}) {
|
|
return spawnSync('bash', [script, ...args], {
|
|
env: { ...env, ...extraEnv },
|
|
encoding: 'utf8',
|
|
});
|
|
}
|
|
function preparedEnv() {
|
|
return Object.fromEntries(
|
|
readFileSync(envFile, 'utf8')
|
|
.trim()
|
|
.split('\n')
|
|
.map((line) => {
|
|
const separator = line.indexOf('=');
|
|
return [line.slice(0, separator), line.slice(separator + 1)];
|
|
}),
|
|
);
|
|
}
|
|
t.after(() => {
|
|
run(['report'], preparedEnv());
|
|
rmSync(directory, { recursive: true, force: true });
|
|
});
|
|
return { directory, root, env, run, preparedEnv };
|
|
}
|
|
|
|
linuxTest(
|
|
'missing snapshot and toolchain mismatch retain direct rustc',
|
|
(t) => {
|
|
const f = fixture(t);
|
|
const missing = f.run(['prepare'], {
|
|
GENARRATIVE_CI_RUST_CACHE_ROOT: join(f.directory, 'absent'),
|
|
});
|
|
assert.equal(missing.status, 0, missing.stderr);
|
|
assert.match(missing.stdout, /reason=snapshot-unavailable/);
|
|
assert.equal(f.preparedEnv().RUSTC_WRAPPER, '');
|
|
writeFileSync(join(f.root, 'rustc.txt'), 'different compiler\n');
|
|
const mismatch = f.run(['prepare']);
|
|
assert.equal(mismatch.status, 0, mismatch.stderr);
|
|
assert.match(mismatch.stdout, /reason=toolchain-mismatch/);
|
|
assert.equal(f.preparedEnv().CARGO_BUILD_RUSTC_WRAPPER, '');
|
|
writeFileSync(join(f.root, 'rustc.txt'), 'fixture rustc identity\n');
|
|
writeFileSync(join(f.root, 'workspace.txt'), '/different-checkout\n');
|
|
const moved = f.run(['prepare']);
|
|
assert.equal(moved.status, 0, moved.stderr);
|
|
assert.match(moved.stdout, /reason=workspace-mismatch/);
|
|
assert.equal(f.preparedEnv().RUSTC_WRAPPER, '');
|
|
},
|
|
);
|
|
|
|
linuxTest(
|
|
'preparations keep the compiler wrapper path stable with separate daemons',
|
|
(t) => {
|
|
const f = fixture(t);
|
|
assert.equal(f.run(['prepare']).status, 0);
|
|
const first = f.preparedEnv();
|
|
f.run(['report'], first);
|
|
assert.equal(f.run(['prepare']).status, 0);
|
|
const second = f.preparedEnv();
|
|
assert.equal(
|
|
first.CARGO_BUILD_RUSTC_WRAPPER,
|
|
second.CARGO_BUILD_RUSTC_WRAPPER,
|
|
);
|
|
assert.equal(first.RUSTC_WRAPPER, second.RUSTC_WRAPPER);
|
|
assert.notEqual(
|
|
first.GENARRATIVE_CI_RUST_CACHE_STATE,
|
|
second.GENARRATIVE_CI_RUST_CACHE_STATE,
|
|
);
|
|
},
|
|
);
|
|
|
|
linuxTest('failed wrapper probe falls back without enabling the cache', (t) => {
|
|
const f = fixture(t);
|
|
const result = f.run(['prepare'], { PROBE_FAILURE: '1' });
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.match(result.stdout, /reason=wrapper-probe-failed/);
|
|
assert.equal(f.preparedEnv().RUSTC_WRAPPER, '');
|
|
assert.equal(f.preparedEnv().GENARRATIVE_CI_RUST_CACHE_STATE, '');
|
|
});
|
|
|
|
linuxTest(
|
|
'wrapper isolates remote settings and preserves compiler failures without retry',
|
|
(t) => {
|
|
const f = fixture(t);
|
|
const prepared = f.run(['prepare']);
|
|
assert.equal(prepared.status, 0, prepared.stderr);
|
|
const cachedEnv = f.preparedEnv();
|
|
const result = spawnSync(
|
|
cachedEnv.RUSTC_WRAPPER,
|
|
['/bin/bash', '-c', 'exit 42'],
|
|
{
|
|
env: { ...f.env, ...cachedEnv },
|
|
encoding: 'utf8',
|
|
},
|
|
);
|
|
assert.equal(result.status, 42, result.stderr);
|
|
const trace = readFileSync(f.env.TRACE, 'utf8').trim().split('\n');
|
|
assert.deepEqual(trace, [
|
|
'unset',
|
|
`${cachedEnv.GENARRATIVE_CI_RUST_CACHE_STATE}/config`,
|
|
`${f.root}/objects`,
|
|
`${cachedEnv.GENARRATIVE_CI_RUST_CACHE_STATE}/server.sock`,
|
|
'1',
|
|
'0',
|
|
]);
|
|
},
|
|
);
|
|
|
|
linuxTest(
|
|
'job copies use different cache directories and daemon sockets',
|
|
(t) => {
|
|
const first = fixture(t);
|
|
const second = fixture(t);
|
|
for (const f of [first, second]) {
|
|
const prepared = f.run(['prepare']);
|
|
assert.equal(prepared.status, 0, prepared.stderr);
|
|
const result = spawnSync(f.preparedEnv().RUSTC_WRAPPER, ['/bin/true'], {
|
|
env: { ...f.env, ...f.preparedEnv() },
|
|
encoding: 'utf8',
|
|
});
|
|
assert.equal(result.status, 0, result.stderr);
|
|
}
|
|
const a = readFileSync(first.env.TRACE, 'utf8').split('\n');
|
|
const b = readFileSync(second.env.TRACE, 'utf8').split('\n');
|
|
assert.notEqual(a[2], b[2]);
|
|
assert.notEqual(a[3], b[3]);
|
|
},
|
|
);
|
|
|
|
linuxTest(
|
|
'sccache infrastructure failure invokes rustc and preserves its result',
|
|
(t) => {
|
|
const f = fixture(t);
|
|
const prepared = f.run(['prepare']);
|
|
assert.equal(prepared.status, 0, prepared.stderr);
|
|
for (const [compiler, status] of [
|
|
['/bin/false', 1],
|
|
['/bin/true', 0],
|
|
]) {
|
|
const result = spawnSync(f.preparedEnv().RUSTC_WRAPPER, [compiler], {
|
|
env: { ...f.env, ...f.preparedEnv(), CACHE_FAILURE: '2' },
|
|
encoding: 'utf8',
|
|
});
|
|
assert.equal(result.status, status, result.stderr);
|
|
if (status === 1)
|
|
assert.match(result.stderr, /cache infrastructure failed/);
|
|
else assert.equal(result.stderr, '');
|
|
}
|
|
assert.equal(
|
|
readFileSync(f.env.TRACE, 'utf8').trim().split('\n').length,
|
|
6,
|
|
);
|
|
},
|
|
);
|
|
|
|
linuxTest('lost local cache state still invokes the real compiler', (t) => {
|
|
const f = fixture(t);
|
|
assert.equal(f.run(['/bin/bash', '-c', 'exit 43']).status, 43);
|
|
});
|