fix: Skip lipo if native module is already universal. Add native module fixtures for lipo tests (#126)
* fix: when native modules are already universal, don't lipo. adds `node-mac-permissions` fixture from https://github.com/codebytere/node-mac-permissions and resolves 3 `it.todo` test cases * add test `different app dirs with different macho files (shim and lipo)` * add additional test * PR feedback * gotta close `fd` * use `stream` to read first 4 bytes. copy native fixture before packing into asar to leverage `unpack: "**/*.node"` properly. * convert params to object * rename `createTestApp` to `createStagingAppDir` and add jsdoc to the function * compiler error from merge conflict * update snapshots * update snapshots * only check x64Content since it's the tmp app * compile macho binaries at runtime using hellow-world.c for fixtures in lipo tests * Update jest.setup.ts Co-authored-by: Erik Moura <erikian@erikian.dev> * Update jest.setup.ts Co-authored-by: Erik Moura <erikian@erikian.dev> * remove unstable properties for specific keys * force redo * update snapshots * stripping only hello-world from snapshot and only hash from macho-specific asar integrity * optimize logic :) --------- Co-authored-by: Erik Moura <erikian@erikian.dev>
This commit is contained in:
88
test/util.ts
88
test/util.ts
@@ -5,7 +5,7 @@ import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import plist from 'plist';
|
||||
import * as fileUtils from '../dist/cjs/file-utils';
|
||||
import { getRawHeader } from '@electron/asar';
|
||||
import { createPackageWithOptions, getRawHeader } from '@electron/asar';
|
||||
|
||||
declare const expect: typeof import('@jest/globals').expect;
|
||||
|
||||
@@ -14,11 +14,12 @@ declare const expect: typeof import('@jest/globals').expect;
|
||||
// plus some tests create fixtures at runtime
|
||||
export const VERIFY_APP_TIMEOUT = 80 * 1000;
|
||||
|
||||
export const asarsDir = path.resolve(__dirname, 'fixtures', 'asars');
|
||||
export const appsDir = path.resolve(__dirname, 'fixtures', 'apps');
|
||||
export const fixtureDir = path.resolve(__dirname, 'fixtures');
|
||||
export const asarsDir = path.resolve(fixtureDir, 'asars');
|
||||
export const appsDir = path.resolve(fixtureDir, 'apps');
|
||||
export const appsOutPath = path.resolve(appsDir, 'out');
|
||||
|
||||
export const verifyApp = async (appPath: string) => {
|
||||
export const verifyApp = async (appPath: string, containsRuntimeGeneratedMacho = false) => {
|
||||
await ensureUniversal(appPath);
|
||||
|
||||
const resourcesDir = path.resolve(appPath, 'Contents', 'Resources');
|
||||
@@ -29,7 +30,9 @@ export const verifyApp = async (appPath: string) => {
|
||||
for await (const asar of asars) {
|
||||
// verify header
|
||||
const asarFs = getRawHeader(path.resolve(resourcesDir, asar));
|
||||
expect(removeUnstableProperties(asarFs.header)).toMatchSnapshot();
|
||||
expect(
|
||||
removeUnstableProperties(asarFs.header, containsRuntimeGeneratedMacho ? ['hello-world'] : []),
|
||||
).toMatchSnapshot();
|
||||
}
|
||||
|
||||
// check all app and unpacked dirs (incl. shimmed)
|
||||
@@ -66,12 +69,14 @@ export const verifyApp = async (appPath: string) => {
|
||||
for (let i = 0; i < integrity.length; i++) {
|
||||
const relativePath = infoPlists[i];
|
||||
const asarIntegrity = integrity[i];
|
||||
integrityMap[relativePath] = asarIntegrity;
|
||||
// note: `infoPlistsToIgnore` will not have integrity in sub-app plists
|
||||
integrityMap[relativePath] = asarIntegrity
|
||||
? removeUnstableProperties(asarIntegrity, containsRuntimeGeneratedMacho ? ['hash'] : [])
|
||||
: undefined;
|
||||
}
|
||||
expect(integrityMap).toMatchSnapshot();
|
||||
};
|
||||
|
||||
// note: `infoPlistsToIgnore` will not have integrity in sub-app plists
|
||||
const extractAsarIntegrity = async (infoPlist: string) => {
|
||||
const { ElectronAsarIntegrity: integrity, ...otherData } = plist.parse(
|
||||
await fs.readFile(infoPlist, 'utf-8'),
|
||||
@@ -104,9 +109,29 @@ export const toSystemIndependentPath = (s: string): string => {
|
||||
return path.sep === '/' ? s : s.replace(/\\/g, '/');
|
||||
};
|
||||
|
||||
export const removeUnstableProperties = (data: any) => {
|
||||
export const removeUnstableProperties = (data: any, stripKeys: string[]) => {
|
||||
const removeKeysRecursively: (obj: any, keysToRemove: string[]) => any = (obj, keysToRemove) => {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
// if the value is an array, map over it
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item: any) => removeKeysRecursively(item, keysToRemove));
|
||||
}
|
||||
return Object.keys(obj).reduce<any>((acc, key) => {
|
||||
// if the value of the current key is another object, make a recursive call to remove the key from the nested object
|
||||
if (!keysToRemove.includes(key)) {
|
||||
acc[key] = removeKeysRecursively(obj[key], keysToRemove);
|
||||
} else {
|
||||
acc[key] = '<stripped>';
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const filteredData = removeKeysRecursively(data, stripKeys);
|
||||
return JSON.parse(
|
||||
JSON.stringify(data, (name, value) => {
|
||||
JSON.stringify(filteredData, (name, value) => {
|
||||
if (name === 'offset') {
|
||||
return undefined;
|
||||
}
|
||||
@@ -116,6 +141,10 @@ export const removeUnstableProperties = (data: any) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an app directory at runtime for usage:
|
||||
* - `testPath` can be used with `asar.createPackage`. Just set the output `.asar` path to `Test.app/Contents/Resources/<asar_name>.asar`
|
||||
* - `testPath` can be utilized for logic paths involving `AsarMode.NO_ASAR` and copied directly to `Test.app/Contents/Resources`
|
||||
*
|
||||
* Directory structure:
|
||||
* testName
|
||||
* ├── private
|
||||
@@ -127,7 +156,7 @@ export const removeUnstableProperties = (data: any) => {
|
||||
* ├── index.js
|
||||
* ├── package.json
|
||||
*/
|
||||
export const createTestApp = async (
|
||||
export const createStagingAppDir = async (
|
||||
testName: string | undefined,
|
||||
additionalFiles: Record<string, string> = {},
|
||||
) => {
|
||||
@@ -181,3 +210,42 @@ export const templateApp = async (
|
||||
|
||||
return appPath;
|
||||
};
|
||||
|
||||
export const generateNativeApp = async (options: {
|
||||
appNameWithExtension: string;
|
||||
arch: string;
|
||||
createAsar: boolean;
|
||||
nativeModuleArch?: string;
|
||||
additionalFiles?: Record<string, string>;
|
||||
}) => {
|
||||
const {
|
||||
appNameWithExtension,
|
||||
arch,
|
||||
createAsar,
|
||||
nativeModuleArch = arch,
|
||||
additionalFiles,
|
||||
} = options;
|
||||
const appPath = await templateApp(appNameWithExtension, arch, async (appPath) => {
|
||||
const resources = path.join(appPath, 'Contents', 'Resources');
|
||||
const resourcesApp = path.resolve(resources, 'app');
|
||||
if (!fs.existsSync(resourcesApp)) {
|
||||
await fs.mkdir(resourcesApp);
|
||||
}
|
||||
const { testPath } = await createStagingAppDir(
|
||||
path.basename(appNameWithExtension, '.app'),
|
||||
additionalFiles,
|
||||
);
|
||||
await fs.copy(
|
||||
path.join(appsDir, `hello-world-${nativeModuleArch}`),
|
||||
path.join(testPath, 'hello-world'),
|
||||
);
|
||||
if (createAsar) {
|
||||
await createPackageWithOptions(testPath, path.resolve(resources, 'app.asar'), {
|
||||
unpack: '**/hello-world',
|
||||
});
|
||||
} else {
|
||||
await fs.copy(testPath, resourcesApp);
|
||||
}
|
||||
});
|
||||
return appPath;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user