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:
salman committed 2026-10-20 09:37:42 +02:00
1 parent db390d3969
commit 0c8e8f2e03
3 files changed
+133 -5

No files matched your search

+1 -1
View File
@@ -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"
+29 -4
View File
@@ -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.
+103
View File
@@ -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();