Add runtime-gate test and WSL2 remote smoke-test checklist
- tests/runtime-gate.test.js lifts the shipped ensureRuntimeGateReady() and checks its behaviour against stub gates, and statically asserts that startSequence() awaits the gate before launching the engine, so a refactor cannot silently drop the first-run import wait. - packaging/wsl2/README.md documents the manual remote-session (SSH/RDP) verification, since the engine path is a console process that needs a real Windows host and cannot be covered by an automated test.
This commit is contained in:
1 parent
db390d3969
commit
0c8e8f2e03
3 files changed
+133
-5
No files matched your search
+1
-1
@@ -15,7 +15,7 @@
|
||||
"make:dynamic": "npm run build:fortran:dynamic && electron-forge make",
|
||||
"make:flatpak": "npm run build:fortran && FORGE_FLATPAK=1 electron-forge make --targets=@electron-forge/maker-flatpak",
|
||||
"build:wsl2": "bash packaging/wsl2/build-wsl2.sh",
|
||||
"test:wsl-runtime": "node tests/wsl-runtime.test.js",
|
||||
"test:wsl-runtime": "node tests/wsl-runtime.test.js && node tests/runtime-gate.test.js",
|
||||
"checksums:md5": "sh -c 'for d in out/make/deb/x64 out/make/rpm/x64 out/make/flatpak/x86_64; do [ -d \"$d\" ] || continue; for f in \"$d\"/*; do [ -f \"$f\" ] || continue; case \"$f\" in *.deb|*.rpm|*.flatpak) base=$(basename \"$f\"); (cd \"$d\" && md5sum \"$base\" > \"$base.md5\");; esac; done; done'",
|
||||
"checksums:sha256": "sh -c 'for d in out/make/deb/x64 out/make/rpm/x64 out/make/flatpak/x86_64; do [ -d \"$d\" ] || continue; for f in \"$d\"/*; do [ -f \"$f\" ] || continue; case \"$f\" in *.deb|*.rpm|*.flatpak) base=$(basename \"$f\"); (cd \"$d\" && sha256sum \"$base\" > \"$base.sha256\");; esac; done; done'",
|
||||
"checksums": "npm run checksums:md5 && npm run checksums:sha256"
|
||||
|
||||
@@ -77,13 +77,38 @@ lets the renderer detect the bundled runtime and route through WSL.
|
||||
## Tests
|
||||
|
||||
```sh
|
||||
npm run test:wsl-runtime # tests/wsl-runtime.test.js
|
||||
npm run test:wsl-runtime # tests/wsl-runtime.test.js + tests/runtime-gate.test.js
|
||||
```
|
||||
|
||||
Covers the pure main-process module (with an injected `wsl.exe` spawner) and the
|
||||
renderer-side launch translation, including inertness on Linux.
|
||||
Covers the pure main-process module (with an injected `wsl.exe` spawner), the
|
||||
renderer-side launch translation, including inertness on Linux, and that the
|
||||
scan pipeline awaits the runtime gate before launching the engine.
|
||||
|
||||
## Open items
|
||||
## Manual remote-session smoke test
|
||||
|
||||
The engine path is a console process and never touches the display, so it cannot
|
||||
misroute a window the way the file dialogs could. It is still worth checking once
|
||||
on a real Windows host, and especially over SSH/RDP, since that is how the
|
||||
workbench is often driven remotely. After the first-run import, verify:
|
||||
|
||||
1. The import succeeded and the engine runs in the distribution:
|
||||
|
||||
```powershell
|
||||
wsl -d trimspNL-runtime -- /opt/trimsp-runtime/bin/trimspNL
|
||||
```
|
||||
|
||||
Expected: the `Usage: trimspNL <run_basename> [ranlux|pcg32|xoshiro]` banner.
|
||||
|
||||
2. Over a remote session (SSH/RDP, not just a local console), launch the app, set
|
||||
a Work Folder on a Windows drive, and click Run. Expected: `<prefix>_E*_.rge`
|
||||
and `_Seq_Results.dat` appear in that folder, proving both the path
|
||||
translation and the `wsl.exe` spawn work without a local display.
|
||||
|
||||
3. The status bar first shows "Preparing the bundled engine runtime (first run
|
||||
only)..." and the run then proceeds, proving the runtime gate waited for the
|
||||
import rather than racing it.
|
||||
|
||||
### Open items
|
||||
|
||||
- The rootfs is built from `almalinux:8`. Adjust `BASE_IMAGE` if the engine is
|
||||
rebuilt against a different baseline.
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
// Checks the shared scan pipeline is wired to the shell's runtime gate.
|
||||
//
|
||||
// On Windows the bundled WSL2 engine is imported on first run, so a Run must
|
||||
// wait for the gate before launching the engine. This test guards two things
|
||||
// without needing a Windows host:
|
||||
// 1. the behaviour of ensureRuntimeGateReady() against stub gates;
|
||||
// 2. that startSequence() actually awaits it, so a future refactor cannot
|
||||
// silently drop the gate and race the import.
|
||||
//
|
||||
// Run with: node tests/runtime-gate.test.js
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const libPath = path.join(__dirname, '..', 'TrimSPlib.js');
|
||||
const src = fs.readFileSync(libPath, 'utf8');
|
||||
|
||||
function grab(re) {
|
||||
const m = src.match(re);
|
||||
if (!m) {
|
||||
throw new Error('pattern not found in ' + libPath + ': ' + re);
|
||||
}
|
||||
return m[0];
|
||||
}
|
||||
|
||||
// Lift the real ensureRuntimeGateReady() so its shipped code is exercised.
|
||||
const gateSource = grab(/async function ensureRuntimeGateReady\s*\([^)]*\)\s*\{[\s\S]*?\n\}/m);
|
||||
|
||||
let statusCalls = [];
|
||||
let stubWindow = {};
|
||||
|
||||
const factory = new Function('window', 'updateStatusBar',
|
||||
gateSource + '\nreturn ensureRuntimeGateReady;');
|
||||
|
||||
function makeGateFn() {
|
||||
statusCalls = [];
|
||||
return factory(stubWindow, function (payload) { statusCalls.push(payload); });
|
||||
}
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
function check(name, ok, detail) {
|
||||
if (ok) { passed++; } else { failed++; }
|
||||
console.log((ok ? 'PASS ' : 'FAIL ') + name.padEnd(46) + (detail || ''));
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// 1. No gate installed (web/standalone) -> proceed, no status write.
|
||||
stubWindow = {};
|
||||
let gate = makeGateFn();
|
||||
let result = await gate('Run');
|
||||
check('no gate: proceeds', result === true && statusCalls.length === 0);
|
||||
|
||||
// 2. Gate resolves true -> proceed.
|
||||
stubWindow = { TrimSPRuntimeGate: { ready: () => Promise.resolve(true) } };
|
||||
gate = makeGateFn();
|
||||
result = await gate('Run');
|
||||
check('gate ready: proceeds', result === true);
|
||||
|
||||
// 3. Gate resolves false -> blocked and message surfaced.
|
||||
stubWindow = {
|
||||
TrimSPRuntimeGate: {
|
||||
ready: () => Promise.resolve(false),
|
||||
message: () => 'Check that WSL2 is installed, then try again.'
|
||||
}
|
||||
};
|
||||
gate = makeGateFn();
|
||||
result = await gate('Run');
|
||||
check('gate not ready: blocked', result === false);
|
||||
check('gate not ready: status shown',
|
||||
statusCalls.length === 1
|
||||
&& /runtime is not ready/.test(statusCalls[0].run)
|
||||
&& /WSL2 is installed/.test(statusCalls[0].run),
|
||||
statusCalls.length ? '"' + statusCalls[0].run + '"' : 'no status');
|
||||
|
||||
// 4. Gate that throws -> treated as not ready, not an unhandled rejection.
|
||||
stubWindow = {
|
||||
TrimSPRuntimeGate: {
|
||||
ready: () => Promise.reject(new Error('boom')),
|
||||
message: () => 'failed'
|
||||
}
|
||||
};
|
||||
gate = makeGateFn();
|
||||
result = await gate('Run');
|
||||
check('gate throwing: blocked', result === false && statusCalls.length === 1);
|
||||
|
||||
// 5. startSequence must await the gate before running the engine.
|
||||
const startSequence = grab(/async function startSequence\s*\([^)]*\)\s*\{[\s\S]*?\n\}/m);
|
||||
const guardIndex = startSequence.indexOf('ensureRuntimeGateReady');
|
||||
const launchIndex = startSequence.indexOf('runSimulationSequence');
|
||||
check('startSequence awaits gate',
|
||||
guardIndex !== -1,
|
||||
guardIndex !== -1 ? '' : 'no ensureRuntimeGateReady() call');
|
||||
check('gate checked before launch',
|
||||
guardIndex !== -1 && launchIndex !== -1 && guardIndex < launchIndex);
|
||||
|
||||
console.log('\n' + passed + ' passed, ' + failed + ' failed');
|
||||
process.exit(failed ? 1 : 0);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in new issue
Block a user