114 lines
4.1 KiB
TypeScript
114 lines
4.1 KiB
TypeScript
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { delimiter, dirname, join } from 'node:path';
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
import { prependPythonToPath, resolvePythonRuntime } from '@electron/utils/python-runtime';
|
|
|
|
const temporaryDirectories: string[] = [];
|
|
|
|
async function createFile(path: string): Promise<string> {
|
|
await mkdir(dirname(path), { recursive: true });
|
|
await writeFile(path, 'python');
|
|
return path;
|
|
}
|
|
|
|
async function temporaryDirectory(prefix: string): Promise<string> {
|
|
const directory = await mkdtemp(join(tmpdir(), prefix));
|
|
temporaryDirectories.push(directory);
|
|
return directory;
|
|
}
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(temporaryDirectories.splice(0).map((directory) => rm(directory, { recursive: true, force: true })));
|
|
});
|
|
|
|
describe('managed Python runtime resolution', () => {
|
|
it('prefers an explicit existing interpreter in development', async () => {
|
|
const root = await temporaryDirectory('niancode-python-configured-');
|
|
const configuredPath = await createFile(join(root, 'custom', 'python.exe'));
|
|
|
|
expect(resolvePythonRuntime({
|
|
isPackaged: false,
|
|
resourcesPath: root,
|
|
appPath: root,
|
|
platform: 'win32',
|
|
arch: 'x64',
|
|
configuredPath,
|
|
})).toEqual({
|
|
binDir: dirname(configuredPath),
|
|
executable: configuredPath,
|
|
source: 'configured',
|
|
});
|
|
});
|
|
|
|
it('resolves the packaged Windows interpreter', async () => {
|
|
const resourcesPath = await temporaryDirectory('niancode-python-packaged-win-');
|
|
const executable = await createFile(join(resourcesPath, 'python', 'python.exe'));
|
|
|
|
expect(resolvePythonRuntime({
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
appPath: resourcesPath,
|
|
platform: 'win32',
|
|
arch: 'x64',
|
|
})).toEqual({ executable, binDir: dirname(executable), source: 'bundled' });
|
|
});
|
|
|
|
it('resolves the packaged Unix interpreter', async () => {
|
|
const resourcesPath = await temporaryDirectory('niancode-python-packaged-unix-');
|
|
const executable = await createFile(join(resourcesPath, 'python', 'bin', 'python3'));
|
|
|
|
expect(resolvePythonRuntime({
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
appPath: resourcesPath,
|
|
platform: 'darwin',
|
|
arch: 'arm64',
|
|
})).toEqual({ executable, binDir: dirname(executable), source: 'bundled' });
|
|
});
|
|
|
|
it('fails closed when a packaged runtime is missing', async () => {
|
|
const resourcesPath = await temporaryDirectory('niancode-python-packaged-missing-');
|
|
|
|
expect(() => resolvePythonRuntime({
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
appPath: resourcesPath,
|
|
platform: 'linux',
|
|
arch: 'x64',
|
|
})).toThrow(`Bundled Python runtime is missing: ${join(resourcesPath, 'python', 'bin', 'python3')}`);
|
|
});
|
|
|
|
it('falls back to a system command only in development', async () => {
|
|
const root = await temporaryDirectory('niancode-python-development-');
|
|
|
|
expect(resolvePythonRuntime({
|
|
isPackaged: false,
|
|
resourcesPath: root,
|
|
appPath: root,
|
|
platform: 'linux',
|
|
arch: 'x64',
|
|
})).toEqual({ executable: 'python3', binDir: '', source: 'system' });
|
|
});
|
|
|
|
it('prepends the managed bin directory without mutating the input environment', () => {
|
|
const environment = { PATH: 'existing-bin', KEEP: 'yes' };
|
|
const runtime = { executable: 'C:\\NianCode\\python\\python.exe', binDir: 'C:\\NianCode\\python', source: 'bundled' as const };
|
|
|
|
expect(prependPythonToPath(environment, runtime)).toEqual({
|
|
PATH: `${runtime.binDir}${delimiter}existing-bin`,
|
|
KEEP: 'yes',
|
|
NIANCODE_PYTHON_PATH: runtime.executable,
|
|
});
|
|
expect(environment).toEqual({ PATH: 'existing-bin', KEEP: 'yes' });
|
|
});
|
|
|
|
it('preserves the Windows Path key casing', () => {
|
|
const runtime = { executable: 'C:\\NianCode\\python\\python.exe', binDir: 'C:\\NianCode\\python', source: 'bundled' as const };
|
|
const result = prependPythonToPath({ Path: 'existing-bin' }, runtime, 'win32');
|
|
|
|
expect(result.Path).toBe(`${runtime.binDir}${delimiter}existing-bin`);
|
|
expect(result).not.toHaveProperty('PATH');
|
|
});
|
|
});
|