mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-28 16:49:50 -07:00
fix(linux): auto-install managed plugin copy; include in asset updates (#127)
This commit is contained in:
@@ -0,0 +1,399 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
ensureLinuxRuntimePluginAssets,
|
||||
resolveManagedLinuxRuntimePluginPaths,
|
||||
} from './linux-runtime-plugin-assets';
|
||||
|
||||
async function withTempDir<T>(fn: (dir: string) => Promise<T> | T): Promise<T> {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-linux-plugin-assets-test-'));
|
||||
try {
|
||||
return await fn(dir);
|
||||
} finally {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function withProcessResourcesPath<T>(
|
||||
resourcesPath: string,
|
||||
fn: () => Promise<T> | T,
|
||||
): Promise<T> {
|
||||
const processWithResources = process as NodeJS.Process & { resourcesPath?: string };
|
||||
const previousResourcesPath = processWithResources.resourcesPath;
|
||||
processWithResources.resourcesPath = resourcesPath;
|
||||
try {
|
||||
return await fn();
|
||||
} finally {
|
||||
if (previousResourcesPath === undefined) {
|
||||
Reflect.deleteProperty(processWithResources, 'resourcesPath');
|
||||
} else {
|
||||
processWithResources.resourcesPath = previousResourcesPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test('resolveManagedLinuxRuntimePluginPaths resolves XDG data target paths', () => {
|
||||
const resolved = resolveManagedLinuxRuntimePluginPaths({
|
||||
homeDir: '/home/tester',
|
||||
xdgDataHome: '/tmp/xdg-data',
|
||||
});
|
||||
|
||||
assert.deepEqual(resolved, {
|
||||
dataDir: '/tmp/xdg-data/SubMiner',
|
||||
rootDir: '/tmp/xdg-data/SubMiner/plugin',
|
||||
pluginDir: '/tmp/xdg-data/SubMiner/plugin/subminer',
|
||||
pluginEntrypointPath: '/tmp/xdg-data/SubMiner/plugin/subminer/main.lua',
|
||||
pluginConfigPath: '/tmp/xdg-data/SubMiner/plugin/subminer.conf',
|
||||
themePath: '/tmp/xdg-data/SubMiner/themes/subminer.rasi',
|
||||
});
|
||||
});
|
||||
|
||||
test('resolveManagedLinuxRuntimePluginPaths treats blank XDG data homes as unset', () => {
|
||||
const previousXdgDataHome = process.env.XDG_DATA_HOME;
|
||||
try {
|
||||
delete process.env.XDG_DATA_HOME;
|
||||
const emptyOption = resolveManagedLinuxRuntimePluginPaths({
|
||||
homeDir: '/home/tester',
|
||||
xdgDataHome: '',
|
||||
});
|
||||
assert.equal(emptyOption.dataDir, path.join('/home/tester', '.local', 'share', 'SubMiner'));
|
||||
|
||||
process.env.XDG_DATA_HOME = ' ';
|
||||
const blankEnv = resolveManagedLinuxRuntimePluginPaths({
|
||||
homeDir: '/home/tester',
|
||||
});
|
||||
assert.equal(blankEnv.dataDir, path.join('/home/tester', '.local', 'share', 'SubMiner'));
|
||||
} finally {
|
||||
if (previousXdgDataHome === undefined) {
|
||||
delete process.env.XDG_DATA_HOME;
|
||||
} else {
|
||||
process.env.XDG_DATA_HOME = previousXdgDataHome;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('ensureLinuxRuntimePluginAssets installs managed plugin dir, config, and rofi theme when missing', async () => {
|
||||
await withTempDir(async (tempDir) => {
|
||||
const sourceRoot = path.join(tempDir, 'source', 'plugin');
|
||||
const themeSourcePath = path.join(tempDir, 'source', 'assets', 'themes', 'subminer.rasi');
|
||||
const targetRoot = path.join(tempDir, 'xdg-data', 'SubMiner', 'plugin');
|
||||
fs.mkdirSync(path.join(sourceRoot, 'subminer'), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(themeSourcePath), { recursive: true });
|
||||
fs.writeFileSync(path.join(sourceRoot, 'subminer', 'main.lua'), '-- plugin\n');
|
||||
fs.writeFileSync(path.join(sourceRoot, 'subminer.conf'), 'configured=true\n');
|
||||
fs.writeFileSync(themeSourcePath, '/* theme */\n');
|
||||
|
||||
const result = await ensureLinuxRuntimePluginAssets({
|
||||
platform: 'linux',
|
||||
homeDir: path.join(tempDir, 'home'),
|
||||
xdgDataHome: path.join(tempDir, 'xdg-data'),
|
||||
resolveBundledAssets: () => ({
|
||||
pluginDirSource: path.join(sourceRoot, 'subminer'),
|
||||
pluginConfigSource: path.join(sourceRoot, 'subminer.conf'),
|
||||
themeSourcePath,
|
||||
}),
|
||||
});
|
||||
|
||||
assert.deepEqual(result, {
|
||||
ok: true,
|
||||
status: 'installed',
|
||||
path: path.join(targetRoot, 'subminer', 'main.lua'),
|
||||
});
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(targetRoot, 'subminer', 'main.lua'), 'utf8'),
|
||||
'-- plugin\n',
|
||||
);
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(targetRoot, 'subminer.conf'), 'utf8'),
|
||||
'configured=true\n',
|
||||
);
|
||||
assert.equal(
|
||||
fs.readFileSync(
|
||||
path.join(tempDir, 'xdg-data', 'SubMiner', 'themes', 'subminer.rasi'),
|
||||
'utf8',
|
||||
),
|
||||
'/* theme */\n',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('ensureLinuxRuntimePluginAssets installs managed theme when plugin assets already exist', async () => {
|
||||
await withTempDir(async (tempDir) => {
|
||||
const sourceRoot = path.join(tempDir, 'source', 'plugin');
|
||||
const themeSourcePath = path.join(tempDir, 'source', 'assets', 'themes', 'subminer.rasi');
|
||||
const xdgDataHome = path.join(tempDir, 'xdg-data');
|
||||
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
|
||||
fs.mkdirSync(path.join(sourceRoot, 'subminer'), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(themeSourcePath), { recursive: true });
|
||||
fs.mkdirSync(path.join(targetRoot, 'subminer'), { recursive: true });
|
||||
fs.writeFileSync(path.join(sourceRoot, 'subminer', 'main.lua'), '-- new plugin\n');
|
||||
fs.writeFileSync(path.join(sourceRoot, 'subminer.conf'), 'new=true\n');
|
||||
fs.writeFileSync(themeSourcePath, '/* theme */\n');
|
||||
fs.writeFileSync(path.join(targetRoot, 'subminer', 'main.lua'), '-- existing plugin\n');
|
||||
fs.writeFileSync(path.join(targetRoot, 'subminer.conf'), 'configured=true\n');
|
||||
|
||||
const result = await ensureLinuxRuntimePluginAssets({
|
||||
platform: 'linux',
|
||||
homeDir: path.join(tempDir, 'home'),
|
||||
xdgDataHome,
|
||||
resolveBundledAssets: () => ({
|
||||
pluginDirSource: path.join(sourceRoot, 'subminer'),
|
||||
pluginConfigSource: path.join(sourceRoot, 'subminer.conf'),
|
||||
themeSourcePath,
|
||||
}),
|
||||
});
|
||||
|
||||
assert.deepEqual(result, {
|
||||
ok: true,
|
||||
status: 'installed',
|
||||
path: path.join(targetRoot, 'subminer', 'main.lua'),
|
||||
});
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(targetRoot, 'subminer', 'main.lua'), 'utf8'),
|
||||
'-- existing plugin\n',
|
||||
);
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(targetRoot, 'subminer.conf'), 'utf8'),
|
||||
'configured=true\n',
|
||||
);
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(xdgDataHome, 'SubMiner', 'themes', 'subminer.rasi'), 'utf8'),
|
||||
'/* theme */\n',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('ensureLinuxRuntimePluginAssets installs managed theme without resolving plugin sources when plugin assets already exist', async () => {
|
||||
await withTempDir(async (tempDir) => {
|
||||
const themeSourcePath = path.join(tempDir, 'source', 'assets', 'themes', 'subminer.rasi');
|
||||
const xdgDataHome = path.join(tempDir, 'xdg-data');
|
||||
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
|
||||
fs.mkdirSync(path.dirname(themeSourcePath), { recursive: true });
|
||||
fs.mkdirSync(path.join(targetRoot, 'subminer'), { recursive: true });
|
||||
fs.writeFileSync(themeSourcePath, '/* theme */\n');
|
||||
fs.writeFileSync(path.join(targetRoot, 'subminer', 'main.lua'), '-- existing plugin\n');
|
||||
fs.writeFileSync(path.join(targetRoot, 'subminer.conf'), 'configured=true\n');
|
||||
|
||||
const result = await ensureLinuxRuntimePluginAssets({
|
||||
platform: 'linux',
|
||||
homeDir: path.join(tempDir, 'home'),
|
||||
xdgDataHome,
|
||||
resolveBundledAssets: () => ({
|
||||
themeSourcePath,
|
||||
}),
|
||||
});
|
||||
|
||||
assert.deepEqual(result, {
|
||||
ok: true,
|
||||
status: 'installed',
|
||||
path: path.join(targetRoot, 'subminer', 'main.lua'),
|
||||
});
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(xdgDataHome, 'SubMiner', 'themes', 'subminer.rasi'), 'utf8'),
|
||||
'/* theme */\n',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('ensureLinuxRuntimePluginAssets default resolver can recover a missing theme when plugin sources are unavailable', async () => {
|
||||
await withTempDir(async (tempDir) => {
|
||||
const xdgDataHome = path.join(tempDir, 'xdg-data');
|
||||
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
|
||||
fs.mkdirSync(path.join(targetRoot, 'subminer'), { recursive: true });
|
||||
fs.writeFileSync(path.join(targetRoot, 'subminer', 'main.lua'), '-- existing plugin\n');
|
||||
fs.writeFileSync(path.join(targetRoot, 'subminer.conf'), 'configured=true\n');
|
||||
|
||||
const result = await withProcessResourcesPath(process.cwd(), () =>
|
||||
ensureLinuxRuntimePluginAssets({
|
||||
platform: 'linux',
|
||||
homeDir: path.join(tempDir, 'home'),
|
||||
xdgDataHome,
|
||||
existsSync: (candidate) => {
|
||||
if (
|
||||
!candidate.startsWith(tempDir) &&
|
||||
(candidate.endsWith(path.join('plugin', 'subminer')) ||
|
||||
candidate.endsWith(path.join('plugin', 'subminer.conf')) ||
|
||||
candidate.endsWith(path.join('plugin', 'subminer', 'main.lua')))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return fs.existsSync(candidate);
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
assert.deepEqual(result, {
|
||||
ok: true,
|
||||
status: 'installed',
|
||||
path: path.join(targetRoot, 'subminer', 'main.lua'),
|
||||
});
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(xdgDataHome, 'SubMiner', 'themes', 'subminer.rasi'), 'utf8'),
|
||||
fs.readFileSync(path.join(process.cwd(), 'assets', 'themes', 'subminer.rasi'), 'utf8'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('ensureLinuxRuntimePluginAssets installs managed plugin assets without resolving theme source when theme already exists', async () => {
|
||||
await withTempDir(async (tempDir) => {
|
||||
const sourceRoot = path.join(tempDir, 'source', 'plugin');
|
||||
const xdgDataHome = path.join(tempDir, 'xdg-data');
|
||||
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
|
||||
fs.mkdirSync(path.join(sourceRoot, 'subminer'), { recursive: true });
|
||||
fs.mkdirSync(path.join(xdgDataHome, 'SubMiner', 'themes'), { recursive: true });
|
||||
fs.writeFileSync(path.join(sourceRoot, 'subminer', 'main.lua'), '-- plugin\n');
|
||||
fs.writeFileSync(path.join(sourceRoot, 'subminer.conf'), 'configured=true\n');
|
||||
fs.writeFileSync(
|
||||
path.join(xdgDataHome, 'SubMiner', 'themes', 'subminer.rasi'),
|
||||
'/* existing theme */\n',
|
||||
);
|
||||
|
||||
const result = await ensureLinuxRuntimePluginAssets({
|
||||
platform: 'linux',
|
||||
homeDir: path.join(tempDir, 'home'),
|
||||
xdgDataHome,
|
||||
resolveBundledAssets: () => ({
|
||||
pluginDirSource: path.join(sourceRoot, 'subminer'),
|
||||
pluginConfigSource: path.join(sourceRoot, 'subminer.conf'),
|
||||
}),
|
||||
});
|
||||
|
||||
assert.deepEqual(result, {
|
||||
ok: true,
|
||||
status: 'installed',
|
||||
path: path.join(targetRoot, 'subminer', 'main.lua'),
|
||||
});
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(targetRoot, 'subminer', 'main.lua'), 'utf8'),
|
||||
'-- plugin\n',
|
||||
);
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(targetRoot, 'subminer.conf'), 'utf8'),
|
||||
'configured=true\n',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('ensureLinuxRuntimePluginAssets default resolver can recover missing plugin assets when theme source is unavailable', async () => {
|
||||
await withTempDir(async (tempDir) => {
|
||||
const xdgDataHome = path.join(tempDir, 'xdg-data');
|
||||
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
|
||||
fs.mkdirSync(path.join(xdgDataHome, 'SubMiner', 'themes'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(xdgDataHome, 'SubMiner', 'themes', 'subminer.rasi'),
|
||||
'/* existing theme */\n',
|
||||
);
|
||||
|
||||
const result = await withProcessResourcesPath(process.cwd(), () =>
|
||||
ensureLinuxRuntimePluginAssets({
|
||||
platform: 'linux',
|
||||
homeDir: path.join(tempDir, 'home'),
|
||||
xdgDataHome,
|
||||
existsSync: (candidate) => {
|
||||
if (
|
||||
!candidate.startsWith(tempDir) &&
|
||||
candidate.endsWith(path.join('assets', 'themes', 'subminer.rasi'))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return fs.existsSync(candidate);
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
assert.deepEqual(result, {
|
||||
ok: true,
|
||||
status: 'installed',
|
||||
path: path.join(targetRoot, 'subminer', 'main.lua'),
|
||||
});
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(targetRoot, 'subminer', 'main.lua'), 'utf8'),
|
||||
fs.readFileSync(path.join(process.cwd(), 'plugin', 'subminer', 'main.lua'), 'utf8'),
|
||||
);
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(targetRoot, 'subminer.conf'), 'utf8'),
|
||||
fs.readFileSync(path.join(process.cwd(), 'plugin', 'subminer.conf'), 'utf8'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('ensureLinuxRuntimePluginAssets returns already-present when managed assets already exist', async () => {
|
||||
await withTempDir(async (tempDir) => {
|
||||
const xdgDataHome = path.join(tempDir, 'xdg-data');
|
||||
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
|
||||
fs.mkdirSync(path.join(targetRoot, 'subminer'), { recursive: true });
|
||||
fs.mkdirSync(path.join(xdgDataHome, 'SubMiner', 'themes'), { recursive: true });
|
||||
fs.writeFileSync(path.join(targetRoot, 'subminer', 'main.lua'), '-- existing\n');
|
||||
fs.writeFileSync(path.join(targetRoot, 'subminer.conf'), 'configured=true\n');
|
||||
fs.writeFileSync(
|
||||
path.join(xdgDataHome, 'SubMiner', 'themes', 'subminer.rasi'),
|
||||
'/* theme */\n',
|
||||
);
|
||||
|
||||
const result = await ensureLinuxRuntimePluginAssets({
|
||||
platform: 'linux',
|
||||
homeDir: path.join(tempDir, 'home'),
|
||||
xdgDataHome,
|
||||
resolveBundledAssets: () => {
|
||||
throw new Error('should not resolve bundled assets when already installed');
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(result, {
|
||||
ok: true,
|
||||
status: 'already-present',
|
||||
path: path.join(targetRoot, 'subminer', 'main.lua'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('ensureLinuxRuntimePluginAssets fails when bundled assets cannot be resolved', async () => {
|
||||
await withTempDir(async (tempDir) => {
|
||||
const result = await ensureLinuxRuntimePluginAssets({
|
||||
platform: 'linux',
|
||||
homeDir: path.join(tempDir, 'home'),
|
||||
xdgDataHome: path.join(tempDir, 'xdg-data'),
|
||||
resolveBundledAssets: () => ({}),
|
||||
});
|
||||
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.status, 'failed');
|
||||
assert.match(result.error ?? '', /bundled.*plugin assets/i);
|
||||
});
|
||||
});
|
||||
|
||||
test('ensureLinuxRuntimePluginAssets leaves no final target tree on failed install', async () => {
|
||||
await withTempDir(async (tempDir) => {
|
||||
const sourceRoot = path.join(tempDir, 'source', 'plugin');
|
||||
const themeSourcePath = path.join(tempDir, 'source', 'assets', 'themes', 'subminer.rasi');
|
||||
const xdgDataHome = path.join(tempDir, 'xdg-data');
|
||||
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
|
||||
fs.mkdirSync(path.join(sourceRoot, 'subminer'), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(themeSourcePath), { recursive: true });
|
||||
fs.writeFileSync(path.join(sourceRoot, 'subminer', 'main.lua'), '-- plugin\n');
|
||||
fs.writeFileSync(path.join(sourceRoot, 'subminer.conf'), 'configured=true\n');
|
||||
fs.writeFileSync(themeSourcePath, '/* theme */\n');
|
||||
|
||||
const result = await ensureLinuxRuntimePluginAssets({
|
||||
platform: 'linux',
|
||||
homeDir: path.join(tempDir, 'home'),
|
||||
xdgDataHome,
|
||||
resolveBundledAssets: () => ({
|
||||
pluginDirSource: path.join(sourceRoot, 'subminer'),
|
||||
pluginConfigSource: path.join(sourceRoot, 'subminer.conf'),
|
||||
themeSourcePath,
|
||||
}),
|
||||
copyFile: async () => {
|
||||
throw new Error('copy failed');
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.status, 'failed');
|
||||
assert.equal(fs.existsSync(path.join(targetRoot, 'subminer', 'main.lua')), false);
|
||||
assert.equal(fs.existsSync(path.join(targetRoot, 'subminer.conf')), false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user