Files
makelore/tests/unit/python-runtime.test.ts
2026-07-29 17:22:35 +08:00

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');
});
});