Files
paseo/vendor/react-native-gpui/packages/hermes/scripts/fetch-binaries.test.mjs
T

247 lines
7.8 KiB
JavaScript
Vendored

import {
copyFileSync,
mkdirSync,
mkdtempSync,
realpathSync,
rmSync,
symlinkSync,
writeFileSync,
} from 'node:fs';
import { execFileSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { afterAll, describe, expect, test } from 'vitest';
import {
binRootFrom,
installedState,
recordedSource,
repositoryFromManifest,
selectedPlatforms,
writeRecordedSource,
} from './fetch-binaries.mjs';
const RELEASE = '@react-native-gpui/hermes@0.1.1';
const packageRoot = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'..',
);
describe('repositoryFromManifest', () => {
test('reads the string form', () => {
expect(
repositoryFromManifest({ repository: 'streetwriters/react-native-gpui' }),
).toBe('streetwriters/react-native-gpui');
});
test('reads the object form', () => {
expect(
repositoryFromManifest({
repository: {
type: 'git',
url: 'https://github.com/streetwriters/react-native-gpui',
},
}),
).toBe('streetwriters/react-native-gpui');
});
test('strips git+, the GitHub URL, .git and a directory field', () => {
expect(
repositoryFromManifest({
repository: {
type: 'git',
url: 'git+https://github.com/streetwriters/react-native-gpui.git',
directory: 'packages/hermes',
},
}),
).toBe('streetwriters/react-native-gpui');
});
test('is unaffected by GITHUB_REPOSITORY, which points at a fork in CI', () => {
const env = { GITHUB_REPOSITORY: 'someone/else' };
expect(
repositoryFromManifest(
{ repository: 'https://github.com/streetwriters/react-native-gpui' },
env,
),
).toBe('streetwriters/react-native-gpui');
expect(env.GITHUB_REPOSITORY).toBe('someone/else');
});
test('RN_GPUI_HERMES_REPO overrides the manifest, for mirrors', () => {
expect(
repositoryFromManifest(
{ repository: 'https://github.com/streetwriters/react-native-gpui' },
{ RN_GPUI_HERMES_REPO: 'https://example.test/mirror' },
),
).toBe('https://example.test/mirror');
});
test('names the manifest when there is no usable repository', () => {
expect(() => repositoryFromManifest({ name: 'hermes' })).toThrow(
/could not read the repository/u,
);
});
});
describe('binRootFrom', () => {
test('defaults to the package bin directory', () => {
expect(binRootFrom({})).toBe(path.join(packageRoot, 'bin'));
});
test('honours RN_GPUI_HERMES_BIN_ROOT, which CMake also reads', () => {
// CMake's desktop/CMakeLists.txt and desktop/node-api-exports.test.js both accept
// this variable. When the downloader ignored it, the binaries landed in the
// package while the build looked elsewhere, and configure failed with
// "Hermes library not found".
expect(binRootFrom({ RN_GPUI_HERMES_BIN_ROOT: '/tmp/hermes-bin' })).toBe(
'/tmp/hermes-bin',
);
});
});
describe('selectedPlatforms', () => {
test('honours --platform on any host', () => {
expect(selectedPlatforms({ platform: 'darwin-x64' })).toEqual([
'darwin-x64',
]);
expect(selectedPlatforms({ platform: 'win32-arm64' })).toEqual([
'win32-arm64',
]);
});
test('--all lists every platform', () => {
expect(selectedPlatforms({ all: true })).toHaveLength(6);
});
});
describe('installedState', () => {
const roots = [];
const tempRoot = () => {
const root = mkdtempSync(path.join(os.tmpdir(), 'hermes-test-'));
roots.push(root);
return root;
};
const install = (root, platform, files) => {
mkdirSync(path.join(root, platform), { recursive: true });
for (const file of files) {
writeFileSync(path.join(root, platform, file), 'binary');
}
};
afterAll(() => {
for (const root of roots) rmSync(root, { recursive: true, force: true });
});
test('a complete directory with no .release is stale', () => {
const root = tempRoot();
install(root, 'darwin-arm64', ['libhermesvm.dylib', 'hermesc']);
expect(installedState('darwin-arm64', RELEASE, root)).toEqual({
state: 'stale',
});
});
test('a matching .release is current', () => {
const root = tempRoot();
install(root, 'darwin-arm64', ['libhermesvm.dylib', 'hermesc']);
writeRecordedSource('darwin-arm64', RELEASE, root);
expect(installedState('darwin-arm64', RELEASE, root)).toEqual({
state: 'current',
});
expect(recordedSource('darwin-arm64', root)).toBe(RELEASE);
});
test('a different tag is stale, so a version bump replaces the binaries', () => {
const root = tempRoot();
install(root, 'darwin-arm64', ['libhermesvm.dylib', 'hermesc']);
writeRecordedSource(
'darwin-arm64',
'@react-native-gpui/hermes@0.1.0',
root,
);
expect(installedState('darwin-arm64', RELEASE, root)).toEqual({
state: 'stale',
});
});
test('a hand-installed source is foreign, and must be kept', () => {
const root = tempRoot();
install(root, 'darwin-arm64', ['libhermesvm.dylib', 'hermesc']);
writeRecordedSource('darwin-arm64', 'from:/tmp/my-build.zip', root);
expect(installedState('darwin-arm64', RELEASE, root)).toEqual({
state: 'foreign',
source: 'from:/tmp/my-build.zip',
});
});
test('a run source is foreign too', () => {
const root = tempRoot();
install(root, 'win32-x64', ['hermesvm.dll', 'hermesvm.lib', 'hermesc.exe']);
writeRecordedSource('win32-x64', 'run:12345', root);
expect(installedState('win32-x64', RELEASE, root)).toEqual({
state: 'foreign',
source: 'run:12345',
});
});
test('missing files win over a recorded source', () => {
const root = tempRoot();
install(root, 'darwin-arm64', ['libhermesvm.dylib']);
writeRecordedSource('darwin-arm64', RELEASE, root);
expect(installedState('darwin-arm64', RELEASE, root)).toEqual({
state: 'missing',
});
});
test('an empty directory is missing', () => {
const root = tempRoot();
expect(installedState('darwin-arm64', RELEASE, root)).toEqual({
state: 'missing',
});
});
});
describe('the CLI entry point', () => {
const script = fileURLToPath(
new URL('./fetch-binaries.mjs', import.meta.url),
);
const roots = [];
afterAll(() => {
for (const root of roots) rmSync(root, { recursive: true, force: true });
});
test('runs when reached through a path that differs from its real one', () => {
// A CMake build under /tmp is reached as /private/tmp, and os.tmpdir() is
// itself a symlink on macOS. Comparing unresolved paths made the script
// exit 0 without doing anything, which a missing Hermes library turned
// into a link error much later.
const root = mkdtempSync(path.join(os.tmpdir(), 'hermes-entry-'));
roots.push(root);
const scripts = path.join(root, 'scripts');
mkdirSync(scripts);
copyFileSync(script, path.join(scripts, 'fetch-binaries.mjs'));
const linked = path.join(root, 'linked');
// A directory symlink needs an elevated process or Developer Mode on
// Windows; a junction needs neither, and `realpathSync` resolves it to the
// same target, which is the behaviour under test.
symlinkSync(
scripts,
linked,
process.platform === 'win32' ? 'junction' : 'dir',
);
const invoked = path.join(linked, 'fetch-binaries.mjs');
expect(realpathSync(invoked)).not.toBe(invoked);
const output = execFileSync(process.execPath, [invoked, '--help'], {
encoding: 'utf8',
});
expect(output).toContain('Usage: node scripts/fetch-binaries.mjs');
});
test('does nothing when it is imported rather than run', () => {
// The import at the top of this file is the test: reaching this point
// without a download proves the guard holds.
expect(typeof repositoryFromManifest).toBe('function');
});
});