// Tests for the Windows/WSL2 bundled-engine runtime plumbing. // // Two layers are covered: // 1. the pure main-process module wsl-runtime.js, driven with an injected // spawner so no Windows host is required; // 2. the renderer-side launch translation and runtime gate in TrimSPelec.js, // lifted out of the shipped source and run against minimal stubs. // // Run with: node tests/wsl-runtime.test.js const assert = require('assert'); const fs = require('fs'); const os = require('os'); const path = require('path'); const wsl = require('../wsl-runtime'); // --- helpers --------------------------------------------------------------- function makeRun(responses) { const calls = []; const run = function (args, onOutput) { calls.push(args); const next = responses.shift() || { status: 0, stdout: '', stderr: '' }; if (typeof onOutput === 'function' && next.stderr) onOutput(next.stderr); return Promise.resolve(next); }; run.calls = calls; return run; } function makeRootfs() { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'trimsp-wsl-')); const file = path.join(dir, wsl.ROOTFS_BASENAME); fs.writeFileSync(file, 'rootfs'); return { dir, file }; } // --- 1. pure module -------------------------------------------------------- function testDecodeWslOutput() { assert.strictEqual(wsl.decodeWslOutput(Buffer.from('abc', 'utf16le')), 'abc'); assert.strictEqual(wsl.decodeWslOutput(Buffer.from('abc', 'utf8')), 'abc'); assert.strictEqual(wsl.decodeWslOutput('plain'), 'plain'); assert.strictEqual(wsl.decodeWslOutput(null), ''); assert.strictEqual(wsl.decodeWslOutput(Buffer.alloc(0)), ''); } function testParseDistroList() { assert.deepStrictEqual(wsl.parseDistroList('a\r\nb\r\n'), ['a', 'b']); assert.deepStrictEqual(wsl.parseDistroList(' a \n\n b '), ['a', 'b']); assert.deepStrictEqual(wsl.parseDistroList('a\u0000\nb\u0000'), ['a', 'b']); assert.deepStrictEqual(wsl.parseDistroList(''), []); assert.deepStrictEqual(wsl.parseDistroList(null), []); } function testDistroIsRegistered() { assert.strictEqual(wsl.distroIsRegistered('Ubuntu\ntrimspNL-runtime\n', wsl.DEFAULT_DISTRO), true); assert.strictEqual(wsl.distroIsRegistered('TRIMSPNL-RUNTIME\n', wsl.DEFAULT_DISTRO), true); assert.strictEqual(wsl.distroIsRegistered('Ubuntu\n', wsl.DEFAULT_DISTRO), false); } function testArgumentBuilders() { assert.deepStrictEqual(wsl.listArgs(), ['--list', '--quiet']); assert.deepStrictEqual( wsl.importArgs('/tmp/r.tar.gz', 'C:\\dir', 'trimspNL-runtime'), ['--import', 'trimspNL-runtime', 'C:\\dir', '/tmp/r.tar.gz', '--version', '2']); assert.strictEqual( wsl.distroInstallDir('C:\\Users\\x\\AppData\\Local', 'trimspNL-runtime'), path.join('C:\\Users\\x\\AppData\\Local', 'TrimSP', 'trimspNL-runtime')); } function testFindRootfsTarball() { assert.strictEqual(wsl.findRootfsTarball('/nonexistent'), ''); // forge copies extraResource entries flat into resources/, so the flat path // is the primary candidate. const flat = fs.mkdtempSync(path.join(os.tmpdir(), 'trimsp-flat-')); fs.writeFileSync(path.join(flat, wsl.ROOTFS_BASENAME), 'rootfs'); assert.strictEqual(wsl.findRootfsTarball(flat), path.join(flat, wsl.ROOTFS_BASENAME)); // A wsl-runtime/ subdirectory is also honoured. const { dir, file } = makeRootfs(); const sub = path.join(dir, 'wsl-runtime'); fs.mkdirSync(sub); fs.renameSync(file, path.join(sub, wsl.ROOTFS_BASENAME)); assert.strictEqual(wsl.findRootfsTarball(dir), path.join(sub, wsl.ROOTFS_BASENAME)); assert.ok(wsl.rootfsCandidates('/x')[0].endsWith(wsl.ROOTFS_BASENAME)); } async function testNotWindows() { const run = makeRun([]); const result = await wsl.ensureWSLRuntime({ platform: 'linux', run }); assert.strictEqual(result.ok, true); assert.strictEqual(result.status, 'skipped'); assert.strictEqual(result.reason, 'not-windows'); assert.strictEqual(run.calls.length, 0); } async function testMissingRootfs() { const run = makeRun([]); const result = await wsl.ensureWSLRuntime({ platform: 'win32', resourcesPath: '/nonexistent', run }); assert.strictEqual(result.ok, false); assert.strictEqual(result.status, 'missing-rootfs'); } async function testAlreadyPresentSkipsImport() { const { file } = makeRootfs(); const run = makeRun([{ status: 0, stdout: wsl.DEFAULT_DISTRO + '\r\n', stderr: '' }]); const result = await wsl.ensureWSLRuntime({ platform: 'win32', rootfsPath: file, appDataPath: '/tmp/appdata', run }); assert.strictEqual(result.status, 'already-present'); assert.strictEqual(run.calls.length, 1); } async function testImportSucceeds() { const { file } = makeRootfs(); const phases = []; const run = makeRun([ { status: 0, stdout: 'No installed distributions\r\n', stderr: '' }, { status: 0, stdout: '', stderr: 'importing...' } ]); const result = await wsl.ensureWSLRuntime({ platform: 'win32', rootfsPath: file, appDataPath: '/tmp/appdata', run, onProgress: function (p) { phases.push(p.phase); } }); assert.strictEqual(result.ok, true); assert.strictEqual(result.status, 'imported'); assert.strictEqual(result.target, wsl.distroInstallDir('/tmp/appdata', wsl.DEFAULT_DISTRO)); assert.deepStrictEqual(run.calls[1], wsl.importArgs(file, result.target, wsl.DEFAULT_DISTRO)); assert.strictEqual(phases[0], 'checking'); assert.ok(phases.includes('importing')); assert.strictEqual(phases[phases.length - 1], 'ready'); } async function testImportFailure() { const { file } = makeRootfs(); const run = makeRun([ { status: 0, stdout: '', stderr: '' }, { status: 1, stdout: '', stderr: 'boom' } ]); const result = await wsl.ensureWSLRuntime({ platform: 'win32', rootfsPath: file, appDataPath: '/tmp/appdata', run }); assert.strictEqual(result.ok, false); assert.strictEqual(result.status, 'import-failed'); assert.ok(result.error.includes('boom')); } // --- 2. renderer translation + gate --------------------------------------- function testRendererTranslation() { const src = path.join(__dirname, '..', 'TrimSPelec.js'); const srcText = fs.readFileSync(src, 'utf8'); // Extract just the translation helpers by satisfying their free variables. const bundled = 'C:\\app\\resources\\bin\\trimspNL'; const fakeProcess = { platform: 'win32', resourcesPath: 'C:\\app\\resources' }; const fakePath = path.win32; const fileExists = function (p) { return p === bundled; }; function grab(name) { const re = new RegExp('function ' + name + '\\s*\\([^)]*\\)\\s*\\{[\\s\\S]*?\\n\\}', 'm'); const m = srcText.match(re); if (!m) throw new Error('function not found: ' + name); return m[0]; } const body = [ 'var TRIMSP_WSL_DISTRO = ' + JSON.stringify('trimspNL-runtime') + ';', 'var TRIMSP_WSL_PREFIX = ' + JSON.stringify('/opt/trimsp-runtime') + ';', grab('bundledTrimBinaryPath'), grab('isWslRuntimeHost'), grab('windowsPathToWsl'), grab('prepareTrimspLaunch'), 'return { bundledTrimBinaryPath, isWslRuntimeHost, windowsPathToWsl, prepareTrimspLaunch };' ].join('\n'); const api = new Function('process', 'path', 'fileExists', body)( fakeProcess, fakePath, fileExists); assert.strictEqual(api.isWslRuntimeHost(), true); assert.strictEqual(api.windowsPathToWsl('C:\\work\\job.inp'), '/mnt/c/work/job.inp'); assert.strictEqual(api.windowsPathToWsl('D:/data/x.rge'), '/mnt/d/data/x.rge'); assert.strictEqual(api.windowsPathToWsl('pcg32'), 'pcg32'); const launch = api.prepareTrimspLaunch(bundled, ['job', 'pcg32']); assert.strictEqual(launch.command, 'wsl.exe'); assert.strictEqual(launch.viaWsl, true); assert.deepStrictEqual(launch.args, ['-d', 'trimspNL-runtime', '--', '/opt/trimsp-runtime/bin/trimspNL', 'job', 'pcg32']); // A user-configured Windows-native binary must pass through untouched. const native = api.prepareTrimspLaunch('C:\\tools\\trimspNL.exe', ['job']); assert.strictEqual(native.command, 'C:\\tools\\trimspNL.exe'); assert.strictEqual(native.viaWsl, false); // On Linux the translation is inert. fakeProcess.platform = 'linux'; assert.strictEqual(api.isWslRuntimeHost(), false); const inert = api.prepareTrimspLaunch(bundled, ['job']); assert.strictEqual(inert.viaWsl, false); assert.strictEqual(inert.command, bundled); } function run() { const tests = [ ['decodeWslOutput', testDecodeWslOutput], ['parseDistroList', testParseDistroList], ['distroIsRegistered', testDistroIsRegistered], ['argumentBuilders', testArgumentBuilders], ['findRootfsTarball', testFindRootfsTarball], ['notWindows', testNotWindows], ['missingRootfs', testMissingRootfs], ['alreadyPresent', testAlreadyPresentSkipsImport], ['importSucceeds', testImportSucceeds], ['importFailure', testImportFailure], ['rendererTranslation', testRendererTranslation] ]; let failed = 0; (async function () { for (const [name, fn] of tests) { try { await fn(); console.log('PASS ' + name); } catch (error) { failed++; console.log('FAIL ' + name + ' ' + (error && error.message)); } } console.log('\n' + (tests.length - failed) + ' passed, ' + failed + ' failed'); process.exit(failed ? 1 : 0); })(); } run();