Files
gnome-jarvis/test/install.test.js
T
snxravenandCursor c88062ba69
Rolling release / release (push) Failing after 6m40s
Ship the Bare ELF in the release and stop baking host paths into the service.
The rolling bundle already contains the runtime binary. The installer now starts that file through a relative launcher, and the checkout unit is no longer packed for other users.

Co-authored-by: Cursor <[email protected]>
2026-09-14 12:29:26 -04:00

194 lines
11 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { spawn } from 'node:child_process';
import { chmod, cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { existsSync, readFileSync } from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const repo = path.resolve(fileURLToPath(new URL('..', import.meta.url)));
function run(command, args, env) {
return new Promise((resolve, reject) => {
const child = spawn(command, args, { env, cwd: repo });
let stdout = '';
let stderr = '';
child.stdout.on('data', (chunk) => { stdout += chunk; });
child.stderr.on('data', (chunk) => { stderr += chunk; });
child.on('error', reject);
child.on('close', (code) => {
if (code === 0) resolve({ stdout, stderr });
else reject(new Error(`install failed (${code}): ${stderr || stdout}`));
});
});
}
test('install.sh replaces leftover files from an older version and keeps config', async () => {
const work = await mkdtemp(path.join(os.tmpdir(), 'jarvis-install-'));
const home = path.join(work, 'home');
const root = path.join(work, 'src');
const bin = path.join(work, 'bin');
const log = path.join(work, 'commands.log');
const machine = (await run('uname', ['-m'], process.env)).stdout.trim();
const bareName = /aarch64|arm64/.test(machine) ? 'bare-runtime-linux-arm64' : 'bare-runtime-linux-x64';
try {
await mkdir(path.join(root, 'packaging'), { recursive: true });
await mkdir(path.join(root, 'apps/gnome-extension'), { recursive: true });
await mkdir(path.join(root, 'node_modules', bareName, 'bin'), { recursive: true });
await mkdir(bin, { recursive: true });
await cp(path.join(repo, 'packaging/install.sh'), path.join(root, 'packaging/install.sh'));
await cp(path.join(repo, 'packaging/jarvisd.service'), path.join(root, 'packaging/jarvisd.service'));
await cp(path.join(repo, 'packaging/jarvisd-launch.sh'), path.join(root, 'packaging/jarvisd-launch.sh'));
await cp(path.join(repo, 'packaging/qvac.config.template.json'), path.join(root, 'packaging/qvac.config.template.json'));
await cp(path.join(repo, 'apps/gnome-extension/[email protected]'), path.join(root, 'apps/gnome-extension/[email protected]'), { recursive: true });
await writeFile(path.join(root, 'node_modules', bareName, 'bin', 'bare'), '#!/bin/sh\nexit 0\n');
await chmod(path.join(root, 'node_modules', bareName, 'bin', 'bare'), 0o755);
await writeFile(path.join(root, 'daemon-marker'), 'new-tree\n');
const appDir = path.join(home, '.local/share/jarvis-qvac');
const extDir = path.join(home, '.local/share/gnome-shell/extensions/[email protected]');
const configDir = path.join(home, '.config/jarvis');
await mkdir(path.join(appDir, 'node_modules/old-pkg'), { recursive: true });
await mkdir(extDir, { recursive: true });
await mkdir(configDir, { recursive: true });
await mkdir(path.join(home, '.local/share/jarvis-qvac.staging.oldfail'), { recursive: true });
await writeFile(path.join(appDir, 'stale-old-version.js'), 'leftover from previous install\n');
await writeFile(path.join(extDir, 'old-extension.js'), 'leftover extension\n');
await writeFile(path.join(configDir, 'config.json'), '{"wakePhrase":"hey jarvis","ttsEnabled":true}\n');
await writeFile(path.join(home, '.local/share/jarvis-qvac.staging.oldfail/junk'), 'failed staging\n');
await writeFile(path.join(bin, 'systemctl'), `#!/bin/sh
echo "systemctl $*" >> '${log}'
if [ "$2" = is-active ]; then exit 0; fi
exit 0
`);
await writeFile(path.join(bin, 'gnome-extensions'), `#!/bin/sh
echo "gnome-extensions $*" >> '${log}'
if [ "$1" = install ]; then exit 1; fi
if [ "$1" = info ]; then printf '%s\\n' 'Enabled: Yes' 'State: ACTIVE'; exit 0; fi
exit 0
`);
await writeFile(path.join(bin, 'gsettings'), `#!/bin/sh
echo "gsettings $*" >> '${log}'
if [ "$1" = get ]; then printf "%s\\n" "['[email protected]']"; fi
exit 0
`);
await writeFile(path.join(bin, 'gdbus'), `#!/bin/sh
echo "gdbus $*" >> '${log}'
exit 0
`);
await writeFile(path.join(bin, 'logger'), `#!/bin/sh
echo "logger $*" >> '${log}'
exit 0
`);
await chmod(path.join(bin, 'systemctl'), 0o755);
await chmod(path.join(bin, 'gnome-extensions'), 0o755);
await chmod(path.join(bin, 'gsettings'), 0o755);
await chmod(path.join(bin, 'gdbus'), 0o755);
await chmod(path.join(bin, 'logger'), 0o755);
const result = await run('bash', [path.join(root, 'packaging/install.sh'), '--enable'], {
...process.env,
HOME: home,
PATH: `${bin}:${process.env.PATH}`,
XDG_DATA_HOME: path.join(home, '.local/share'),
XDG_CACHE_HOME: path.join(home, '.cache'),
XDG_STATE_HOME: path.join(home, '.local/state'),
});
assert.equal(existsSync(path.join(appDir, 'stale-old-version.js')), false);
assert.equal(existsSync(path.join(appDir, 'node_modules/old-pkg')), false);
assert.equal(existsSync(path.join(home, '.local/share/jarvis-qvac.staging.oldfail')), false);
assert.equal(existsSync(path.join(extDir, 'old-extension.js')), false);
assert.equal(existsSync(path.join(extDir, 'extension.js')), true);
assert.equal(existsSync(path.join(extDir, 'brand/icons/jarvis-mark.svg')), true);
assert.equal(existsSync(path.join(home, '.local/share/applications/io.qvac.Jarvis.Control.desktop')), true);
assert.equal(existsSync(path.join(home, '.local/share/icons/hicolor/scalable/apps/io.qvac.Jarvis.Control.svg')), true);
assert.match(await readFile(path.join(home, '.local/share/applications/io.qvac.Jarvis.Control.desktop'), 'utf8'), /Local voice for your desktop/);
assert.equal(await readFile(path.join(appDir, 'daemon-marker'), 'utf8'), 'new-tree\n');
assert.equal(await readFile(path.join(configDir, 'config.json'), 'utf8'), '{"wakePhrase":"hey jarvis","ttsEnabled":true}\n');
const unit = await readFile(path.join(home, '.config/systemd/user/jarvisd.service'), 'utf8');
const execStart = unit.split('\n').find((line) => line.startsWith('ExecStart='));
assert.match(execStart, /ExecStart=%h\/\.local\/share\/jarvis-qvac\/packaging\/jarvisd-launch\.sh/);
assert.doesNotMatch(execStart, /\/home\/|dev\/jarvis|__JARVIS_BARE__|\/usr\/bin\/node/);
assert.match(unit, /WantedBy=default\.target/);
assert.match(unit, /Restart=always/);
assert.doesNotMatch(unit, /ExecStart=\/usr\/bin\/node/);
assert.match(unit, /JARVIS_BROWSER_NODE=/);
const dbusService = await readFile(path.join(home, '.local/share/dbus-1/services/io.qvac.Jarvis.service'), 'utf8');
assert.match(dbusService, /Name=io\.qvac\.Jarvis/);
assert.match(dbusService, /SystemdService=jarvisd\.service/);
const commands = await readFile(log, 'utf8');
assert.match(commands, /systemctl --user stop jarvisd\.service/);
assert.match(commands, /gnome-extensions enable jarvis@qvac\.local/);
assert.match(commands, /gdbus call .*ReloadExtension/);
assert.match(commands, /gdbus call .*EnableExtension/);
assert.match(commands, /systemctl --user restart jarvisd\.service/);
const installLog = await readFile(path.join(home, '.local/state/jarvis/install.log'), 'utf8');
assert.match(installLog, /stopping running jarvisd\.service/);
assert.match(installLog, /removing leftover staging directory/);
assert.match(installLog, /removing previous application tree/);
assert.match(installLog, /copied GNOME extension files/);
assert.match(installLog, /reloaded jarvis@qvac\.local in the running GNOME Shell/);
assert.match(installLog, /enabled GNOME extension jarvis@qvac\.local/);
assert.match(installLog, /kept existing .*config\.json/);
assert.match(installLog, /install complete; log file/);
assert.doesNotMatch(installLog, /will enable Jarvis after the next login/);
assert.match(result.stdout, /jarvis-install: stopping running jarvisd\.service/);
} finally {
await rm(work, { recursive: true, force: true });
}
});
test('install.sh seeds default config when missing and does not require first-run', async () => {
const work = await mkdtemp(path.join(os.tmpdir(), 'jarvis-install-defaults-'));
const home = path.join(work, 'home');
const root = path.join(work, 'src');
const bin = path.join(work, 'bin');
const machine = (await run('uname', ['-m'], process.env)).stdout.trim();
const bareName = /aarch64|arm64/.test(machine) ? 'bare-runtime-linux-arm64' : 'bare-runtime-linux-x64';
try {
await mkdir(path.join(root, 'packaging'), { recursive: true });
await mkdir(path.join(root, 'apps/gnome-extension'), { recursive: true });
await mkdir(path.join(root, 'node_modules', bareName, 'bin'), { recursive: true });
await mkdir(bin, { recursive: true });
await cp(path.join(repo, 'packaging/install.sh'), path.join(root, 'packaging/install.sh'));
await cp(path.join(repo, 'packaging/jarvisd.service'), path.join(root, 'packaging/jarvisd.service'));
await cp(path.join(repo, 'packaging/jarvisd-launch.sh'), path.join(root, 'packaging/jarvisd-launch.sh'));
await cp(path.join(repo, 'packaging/qvac.config.template.json'), path.join(root, 'packaging/qvac.config.template.json'));
await cp(path.join(repo, 'apps/gnome-extension/[email protected]'), path.join(root, 'apps/gnome-extension/[email protected]'), { recursive: true });
await writeFile(path.join(root, 'node_modules', bareName, 'bin', 'bare'), '#!/bin/sh\nexit 0\n');
await chmod(path.join(root, 'node_modules', bareName, 'bin', 'bare'), 0o755);
for (const name of ['systemctl', 'gnome-extensions', 'gsettings', 'gdbus', 'logger']) {
await writeFile(path.join(bin, name), `#!/bin/sh\nexit 0\n`);
await chmod(path.join(bin, name), 0o755);
}
const result = await run('bash', [path.join(root, 'packaging/install.sh'), '--enable'], {
...process.env,
HOME: home,
PATH: `${bin}:${process.env.PATH}`,
XDG_DATA_HOME: path.join(home, '.local/share'),
XDG_CACHE_HOME: path.join(home, '.cache'),
XDG_STATE_HOME: path.join(home, '.local/state'),
});
const config = JSON.parse(await readFile(path.join(home, '.config/jarvis/config.json'), 'utf8'));
assert.equal(config.wakePhrase, 'hey jarvis');
assert.equal(config.wakeCommand, 'jarvis-wake-bridge');
assert.equal(config.modelProfile, 'laptop-16gb');
assert.equal(config.ttsEnabled, true);
assert.equal(config.fsAccess, 'workspace');
assert.equal(config.freeVramOnIdle, true);
assert.match(result.stdout, /Jarvis is ready/);
assert.doesNotMatch(result.stdout, /first-run/);
const installSource = readFileSync(new URL('../packaging/install.sh', import.meta.url), 'utf8');
assert.doesNotMatch(installSource, /first-run/);
assert.equal(existsSync(path.join(repo, 'packaging/first-run.sh')), false);
assert.equal(existsSync(path.join(repo, 'packaging/write-config.js')), false);
} finally {
await rm(work, { recursive: true, force: true });
}
});