feedBack-desktop/scripts/dev/load-gr6.js
2026-06-16 18:48:12 +02:00

130 lines
5.7 KiB
JavaScript

// Smoke-test harness for the Windows VST sandbox path. Loads the addon,
// spawns the sandbox subprocess for Guitar Rig 6, opens its editor, then
// closes and shuts down cleanly. Used by clean-rerun.cmd on the test VM.
//
// Run from the repo root: `node scripts/dev/load-gr6.js > load-gr6-sandbox.log`.
// The Guitar Rig 6 path is hardcoded to its standard Win11 install location;
// adjust the GR6 path below if your install differs.
'use strict';
const path = require('path');
const addonPath = path.join(process.cwd(), 'build', 'Release', 'slopsmith_audio.node');
console.log('[test] loading addon from', addonPath);
const addon = require(addonPath);
console.log('[test] addon loaded; methods:', Object.keys(addon).slice(0, 10).join(', '), '...');
// Global watchdog — best-effort coverage for *asynchronous* hang paths
// (event-loop livelocks, setTimeout-stacked cleanup). loadVST is now a
// Napi::AsyncWorker, so the libuv event loop continues to fire timers
// while the load runs on a worker thread; this watchdog can pre-empt
// a hung async load. addon.shutdown still parks the event loop
// synchronously inside dispatchOnMessageThread, so if shutdown itself
// hangs the timer callback never fires — a proper supervisor-process
// + SIGKILL watchdog belongs in the CI harness (test-suite follow-up).
//
// The timer callback hard-exits — do NOT call addon.shutdown() here,
// it would block on the same dispatchOnMessageThread the addon is
// already stuck in and deadlock the process.
const WATCHDOG_MS = 60000;
const watchdog = setTimeout(() => {
console.error(`[test] FATAL: watchdog tripped after ${WATCHDOG_MS} ms (async hang)`);
process.exit(1);
}, WATCHDOG_MS);
function failExit(msg) {
if (msg) console.log('[test] FAIL:', msg);
// addon.shutdown blocks on dispatchOnMessageThread (up to 15s) if
// JUCE init partially succeeded; an init-failure path that triggered
// *because* the message thread never came up would then time out
// before the process exits. Cap with a hard process.exit timer so a
// hung shutdown can't extend the failure window beyond 3s.
const hardKill = setTimeout(() => {
console.error('[test] FAIL: addon.shutdown hung, force-exiting');
process.exit(2);
}, 3000);
hardKill.unref();
try { addon.shutdown(); } catch (_) {}
try { clearTimeout(watchdog); } catch (_) {}
try { clearTimeout(hardKill); } catch (_) {}
process.exit(1);
}
console.log('[test] addon.init()');
try {
addon.init();
} catch (e) {
failExit('EXCEPTION on init: ' + e.message);
}
setTimeout(async () => {
// Allow override for CI / dev machines whose VST3 layout differs from
// the standard "C:\Program Files\Common Files\VST3" install location.
// The default Native Instruments install ships "Guitar Rig 6.vst3";
// some installer versions or FX-only variants land as
// "Guitar Rig 6 FX.vst3". Try both before giving up.
const fs = require('fs');
const candidates = process.env.GR6_PATH
? [process.env.GR6_PATH]
: [
// NI's own installer drops into a vendor subdir; this is the
// most common default on a fresh GR6 install.
'C:\\Program Files\\Native Instruments\\VST3\\Guitar Rig 6.vst3',
'C:\\Program Files\\Native Instruments\\VST3\\Guitar Rig 6 FX.vst3',
// Some installs (and the existing CI fixture VM) drop into the
// shared Common Files VST3 dir; keep these as fallbacks so the
// existing smoke harness doesn't have to flip overnight.
'C:\\Program Files\\Common Files\\VST3\\Guitar Rig 6.vst3',
'C:\\Program Files\\Common Files\\VST3\\Guitar Rig 6 FX.vst3',
];
const gr6 = candidates.find(p => { try { return fs.existsSync(p); } catch (_) { return false; } });
if (!gr6) {
console.error('[test] FATAL: no Guitar Rig 6 install found at any of:');
for (const p of candidates) console.error(' - ' + p);
console.error('Set GR6_PATH to override (e.g. GR6_PATH="C:\\path\\to\\Guitar Rig 6.vst3" node scripts\\dev\\load-gr6.js).');
try { addon.shutdown(); } catch (_) {}
clearTimeout(watchdog);
process.exit(2);
return;
}
console.log('[test] calling addon.loadVST(' + gr6 + ')');
let slot;
try {
// addon.loadVST is now a Promise<number> (Napi::AsyncWorker); await
// it. The enclosing setTimeout callback was made async above.
slot = await addon.loadVST(gr6);
console.log('[test] loadVST returned slot:', slot);
} catch (e) {
failExit('EXCEPTION on loadVST: ' + e.message);
return;
}
if (!Number.isInteger(slot) || slot < 0) {
failExit('loadVST returned invalid slot: ' + String(slot));
return;
}
setTimeout(() => {
console.log('[test] calling addon.openPluginEditor(' + slot + ')');
let ok = false;
try {
ok = addon.openPluginEditor(slot);
console.log('[test] openPluginEditor returned:', ok);
} catch (e) {
failExit('EXCEPTION on openPluginEditor: ' + e.message);
return;
}
if (!ok) {
try { addon.closePluginEditor(slot); } catch (_) {}
failExit('openPluginEditor returned false');
return;
}
console.log('[test] sleeping 5s for editor creation + potential crash...');
setTimeout(() => {
console.log('[test] still alive after editor wait; closing');
try { addon.closePluginEditor(slot); } catch (e) {}
try { addon.shutdown(); } catch (e) {}
clearTimeout(watchdog);
setTimeout(() => process.exit(0), 1000);
}, 5000);
}, 1500);
}, 2000);