85 lines
4.6 KiB
Bash
Executable File
85 lines
4.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Disposable runtime restored from Phase 5; primary server is never selected.
|
|
set -euo pipefail
|
|
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
|
evidence="$PWD/.tools/phase6-evidence"
|
|
root="$PWD/.tools/phase6-server"
|
|
loop_root="$PWD/.tools/phase6-loop-server"
|
|
mkdir -p "$evidence"
|
|
[[ ! -e "$root" && ! -e "$loop_root" ]] || { echo 'Phase 6 fixtures already exist; use new roots to repeat.' >&2; exit 1; }
|
|
snapshot=$(cat .tools/phase5-evidence/snapshot-path.txt)
|
|
./mcpack restore --backup "$snapshot" --destination "$root"
|
|
export MC_ROOT="$root" MC_RELEASE_DIR="$root/release"
|
|
cp .tools/phase4-server/.env "$root/.env"
|
|
chmod 600 "$root/.env"
|
|
# A real cold JVM start cannot finish within two seconds.
|
|
if scripts/server.sh up --wait --wait-timeout 2 --stable-seconds 1 > "$evidence/timeout.txt" 2>&1; then
|
|
echo 'Short startup timeout unexpectedly passed' >&2; exit 1
|
|
fi
|
|
scripts/server.sh health --wait-timeout 120 --stable-seconds 3 > "$evidence/healthy.txt" 2>&1
|
|
scripts/server.sh logs --tail 100 > "$evidence/healthy-start.log"
|
|
# Abrupt termination only affects this disposable copied world.
|
|
docker kill --signal KILL minecraft-lab-phase4-minecraft-1 > "$evidence/kill.txt"
|
|
if scripts/server.sh health --wait-timeout 30 --stable-seconds 2 > "$evidence/crash.txt" 2>&1; then
|
|
echo 'Crashed container unexpectedly healthy' >&2; exit 1
|
|
fi
|
|
scripts/server.sh up --wait --wait-timeout 120 --stable-seconds 3 > "$evidence/recovered.txt" 2>&1
|
|
scripts/server.sh down
|
|
# Deliberately broken Compose fixture, separate from immutable public release.
|
|
mkdir -p "$loop_root/release"
|
|
cp -a "$MC_RELEASE_DIR/." "$loop_root/release/"
|
|
cp "$root/.env" "$loop_root/.env"
|
|
LOOP_ROOT="$loop_root" node --input-type=module <<'JS'
|
|
import fs from 'node:fs';
|
|
import crypto from 'node:crypto';
|
|
import {parse,stringify} from 'yaml';
|
|
const release=process.env.LOOP_ROOT+'/release';
|
|
const composePath=release+'/server/compose.yaml';
|
|
const compose=parse(fs.readFileSync(composePath,'utf8'));
|
|
compose.services.minecraft.restart='on-failure:5';
|
|
compose.services.minecraft.entrypoint=['/bin/sh','-c','echo phase6-intentional-restart-loop; sleep 1; exit 42'];
|
|
fs.writeFileSync(composePath,stringify(compose));
|
|
const hash=file=>crypto.createHash('sha256').update(fs.readFileSync(release+'/'+file)).digest('hex');
|
|
const manifest=JSON.parse(fs.readFileSync(release+'/manifest.json'));
|
|
manifest.pack.version='phase6-failure-fixture';
|
|
for(const file of manifest.release_files)file.sha256=hash(file.path);
|
|
fs.writeFileSync(release+'/manifest.json',JSON.stringify(manifest,null,2)+'\n');
|
|
const files=[...manifest.release_files.map(file=>file.path),'manifest.json'].sort();
|
|
fs.writeFileSync(release+'/SHA256SUMS',files.map(file=>`${hash(file)} ${file}\n`).join(''));
|
|
JS
|
|
export MC_ROOT="$loop_root" MC_RELEASE_DIR="$loop_root/release"
|
|
scripts/server.sh up -d
|
|
# Wait until the real Docker container has restarted at least once.
|
|
for attempt in {1..20}; do
|
|
count=$(docker inspect minecraft-lab-phase4-minecraft-1 --format '{{.RestartCount}}')
|
|
if [[ "$count" -ge 1 ]]; then break; fi
|
|
sleep 0.2
|
|
done
|
|
docker inspect minecraft-lab-phase4-minecraft-1 --format '{{json .State}} {{.RestartCount}}' > "$evidence/loop-docker-state.txt"
|
|
if scripts/server.sh health --wait-timeout 15 --stable-seconds 2 > "$evidence/restart-loop.txt" 2>&1; then
|
|
echo 'Restart loop unexpectedly healthy' >&2; exit 1
|
|
fi
|
|
scripts/server.sh down
|
|
ROOT="$root" LOOP_ROOT="$loop_root" node --input-type=module <<'JS' > "$evidence/assertions.txt"
|
|
import fs from 'node:fs';
|
|
import assert from 'node:assert/strict';
|
|
const reports=root=>fs.readdirSync(root+'/diagnostics').filter(name=>name.endsWith('.json')).map(name=>JSON.parse(fs.readFileSync(root+'/diagnostics/'+name)));
|
|
const normal=reports(process.env.ROOT),loop=reports(process.env.LOOP_ROOT);
|
|
assert(normal.some(report=>report.reason==='timeout'));
|
|
assert(normal.some(report=>report.reason==='crash'&&report.state.exit_code===137));
|
|
assert(loop.some(report=>report.reason==='restart-loop'));
|
|
for(const root of [process.env.ROOT,process.env.LOOP_ROOT]){
|
|
assert(!fs.existsSync(root+'/.mcpack-runtime.lock'));
|
|
assert(fs.existsSync(root+'/.mcpack-runtime.json'));
|
|
for(const report of reports(root)){
|
|
assert(fs.existsSync(report.logs));
|
|
assert.equal(report.recovery.automatic_rollback,false);
|
|
assert.equal(report.recovery.runtime_preserved,true);
|
|
}
|
|
}
|
|
assert(fs.existsSync(process.env.ROOT+'/runtime/mod-state/phase5.json'));
|
|
console.log('Real success, timeout, crash (exit 137), restart-loop, diagnostics and runtime/binding preservation passed.');
|
|
JS
|
|
scripts/spike-server.sh ps > "$evidence/primary-server-status.txt"
|
|
printf 'Phase 6 integration passed.\n'
|