Rolling release / release (push) Failing after 6m40s
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]>
194 lines
11 KiB
JavaScript
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 });
|
|
}
|
|
});
|