test: giving steroids to the test suite 💪 (#122)
* purely test suite on steroids * verify stuff * more fun verifies * ok ok ok I'm done * extend timeout and consolidate to constant for easier usage across tests * PR feedback :) Remove warnings by adding transform regex to `ts-jest` and `testMatch`. * cleanup * cleanup * PR feedback & converting `export function` to `export const` in `util.ts`
This commit is contained in:
@@ -3,13 +3,14 @@ module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
'.': [
|
||||
'^.+\\.ts?$': [
|
||||
'ts-jest',
|
||||
{
|
||||
tsconfig: 'tsconfig.jest.json'
|
||||
}
|
||||
]
|
||||
tsconfig: 'tsconfig.jest.json',
|
||||
},
|
||||
],
|
||||
},
|
||||
testMatch: ['<rootDir>/test/**/*.spec.ts'],
|
||||
globalSetup: './jest.setup.ts',
|
||||
testTimeout: 10000,
|
||||
};
|
||||
@@ -1,28 +1,6 @@
|
||||
import { downloadArtifact } from '@electron/get';
|
||||
import * as zip from 'cross-zip';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
|
||||
const asarsDir = path.resolve(__dirname, 'test', 'fixtures', 'asars');
|
||||
const appsDir = path.resolve(__dirname, 'test', 'fixtures', 'apps');
|
||||
|
||||
const templateApp = async (
|
||||
name: string,
|
||||
arch: string,
|
||||
modify: (appPath: string) => Promise<void>,
|
||||
) => {
|
||||
const electronZip = await downloadArtifact({
|
||||
artifactName: 'electron',
|
||||
version: '27.0.0',
|
||||
platform: 'darwin',
|
||||
arch,
|
||||
});
|
||||
const appPath = path.resolve(appsDir, name);
|
||||
zip.unzipSync(electronZip, appsDir);
|
||||
await fs.rename(path.resolve(appsDir, 'Electron.app'), appPath);
|
||||
await fs.remove(path.resolve(appPath, 'Contents', 'Resources', 'default_app.asar'));
|
||||
await modify(appPath);
|
||||
};
|
||||
import { appsDir, asarsDir, templateApp } from './test/util';
|
||||
|
||||
export default async () => {
|
||||
await fs.remove(appsDir);
|
||||
|
||||
@@ -84,8 +84,10 @@ export const mergeASARs = async ({
|
||||
}: MergeASARsOptions): Promise<void> => {
|
||||
d(`merging ${x64AsarPath} and ${arm64AsarPath}`);
|
||||
|
||||
const x64Files = new Set(asar.listPackage(x64AsarPath).map(toRelativePath));
|
||||
const arm64Files = new Set(asar.listPackage(arm64AsarPath).map(toRelativePath));
|
||||
const x64Files = new Set(asar.listPackage(x64AsarPath, { isPack: false }).map(toRelativePath));
|
||||
const arm64Files = new Set(
|
||||
asar.listPackage(arm64AsarPath, { isPack: false }).map(toRelativePath),
|
||||
);
|
||||
|
||||
//
|
||||
// Build set of unpacked directories and files
|
||||
|
||||
410
test/__snapshots__/index.spec.ts.snap
Normal file
410
test/__snapshots__/index.spec.ts.snap
Normal file
@@ -0,0 +1,410 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`makeUniversalApp asar mode should correctly merge two identical asars 1`] = `
|
||||
{
|
||||
"files": {
|
||||
"index.js": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"8c8cefe616b330a70980c457e479360417a320f53f484d34df65227ce3add026",
|
||||
],
|
||||
"hash": "8c8cefe616b330a70980c457e479360417a320f53f484d34df65227ce3add026",
|
||||
},
|
||||
"size": 64,
|
||||
},
|
||||
"package.json": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
||||
],
|
||||
"hash": "d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
||||
},
|
||||
"size": 41,
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp asar mode should correctly merge two identical asars 2`] = `
|
||||
{
|
||||
"Contents/Info.plist": {
|
||||
"Resources/app.asar": {
|
||||
"algorithm": "SHA256",
|
||||
"hash": "85fff474383bd8df11cd9c5784e8fcd1525af71ff140a8a882e1dc9d5b39fcbf",
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp asar mode should create a shim if asars are different between architectures 1`] = `
|
||||
{
|
||||
"files": {
|
||||
"extra-file.txt": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"b8f261b95f81761658c8875b33a68001d8750fd898f447373bf6347e779bc3de",
|
||||
],
|
||||
"hash": "b8f261b95f81761658c8875b33a68001d8750fd898f447373bf6347e779bc3de",
|
||||
},
|
||||
"size": 15,
|
||||
},
|
||||
"index.js": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"8c8cefe616b330a70980c457e479360417a320f53f484d34df65227ce3add026",
|
||||
],
|
||||
"hash": "8c8cefe616b330a70980c457e479360417a320f53f484d34df65227ce3add026",
|
||||
},
|
||||
"size": 64,
|
||||
},
|
||||
"package.json": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
||||
],
|
||||
"hash": "d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
||||
},
|
||||
"size": 41,
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp asar mode should create a shim if asars are different between architectures 2`] = `
|
||||
{
|
||||
"files": {
|
||||
"index.js": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"8c8cefe616b330a70980c457e479360417a320f53f484d34df65227ce3add026",
|
||||
],
|
||||
"hash": "8c8cefe616b330a70980c457e479360417a320f53f484d34df65227ce3add026",
|
||||
},
|
||||
"size": 64,
|
||||
},
|
||||
"package.json": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
||||
],
|
||||
"hash": "d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
||||
},
|
||||
"size": 41,
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp asar mode should create a shim if asars are different between architectures 3`] = `
|
||||
{
|
||||
"files": {
|
||||
"index.js": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"b7e5f58d3c0fddc1a57d1279a7f19a34a01784f4036920d4b60a1e33f6d1635b",
|
||||
],
|
||||
"hash": "b7e5f58d3c0fddc1a57d1279a7f19a34a01784f4036920d4b60a1e33f6d1635b",
|
||||
},
|
||||
"size": 1068,
|
||||
},
|
||||
"package.json": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"2873266521e41d58d02e7acfbbbdb046edfa04b6ce262b8987de8e8548671fc7",
|
||||
],
|
||||
"hash": "2873266521e41d58d02e7acfbbbdb046edfa04b6ce262b8987de8e8548671fc7",
|
||||
},
|
||||
"size": 33,
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp asar mode should create a shim if asars are different between architectures 4`] = `
|
||||
{
|
||||
"Contents/Info.plist": {
|
||||
"Resources/app-arm64.asar": {
|
||||
"algorithm": "SHA256",
|
||||
"hash": "71db54541357128943df64d54480a22d0cdf4c283f2044f48101fb1fc6e6fb2d",
|
||||
},
|
||||
"Resources/app-x64.asar": {
|
||||
"algorithm": "SHA256",
|
||||
"hash": "85fff474383bd8df11cd9c5784e8fcd1525af71ff140a8a882e1dc9d5b39fcbf",
|
||||
},
|
||||
"Resources/app.asar": {
|
||||
"algorithm": "SHA256",
|
||||
"hash": "b62aaaed07ff72dc33da1720d900e0443c060285ef374ce1bdaef1d4f28b5fe4",
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp asar mode should merge two different asars when \`mergeASARs\` is enabled 1`] = `
|
||||
{
|
||||
"files": {
|
||||
"extra-file.txt": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"b8f261b95f81761658c8875b33a68001d8750fd898f447373bf6347e779bc3de",
|
||||
],
|
||||
"hash": "b8f261b95f81761658c8875b33a68001d8750fd898f447373bf6347e779bc3de",
|
||||
},
|
||||
"size": 15,
|
||||
},
|
||||
"index.js": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"8c8cefe616b330a70980c457e479360417a320f53f484d34df65227ce3add026",
|
||||
],
|
||||
"hash": "8c8cefe616b330a70980c457e479360417a320f53f484d34df65227ce3add026",
|
||||
},
|
||||
"size": 64,
|
||||
},
|
||||
"package.json": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
||||
],
|
||||
"hash": "d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
||||
},
|
||||
"size": 41,
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp asar mode should merge two different asars when \`mergeASARs\` is enabled 2`] = `
|
||||
{
|
||||
"Contents/Info.plist": {
|
||||
"Resources/app.asar": {
|
||||
"algorithm": "SHA256",
|
||||
"hash": "71db54541357128943df64d54480a22d0cdf4c283f2044f48101fb1fc6e6fb2d",
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp asar mode should not inject ElectronAsarIntegrity into \`infoPlistsToIgnore\` 1`] = `
|
||||
{
|
||||
"files": {
|
||||
"index.js": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"0f6311dac07f0876c436ce2be042eb88c96e17eaf140b39627cf720dd87ad5b8",
|
||||
],
|
||||
"hash": "0f6311dac07f0876c436ce2be042eb88c96e17eaf140b39627cf720dd87ad5b8",
|
||||
},
|
||||
"size": 66,
|
||||
},
|
||||
"package.json": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
||||
],
|
||||
"hash": "d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
||||
},
|
||||
"size": 41,
|
||||
},
|
||||
"private": {
|
||||
"files": {
|
||||
"var": {
|
||||
"files": {
|
||||
"app": {
|
||||
"files": {
|
||||
"file.txt": {
|
||||
"link": "private/var/file.txt",
|
||||
},
|
||||
},
|
||||
},
|
||||
"file.txt": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
|
||||
],
|
||||
"hash": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
|
||||
},
|
||||
"size": 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"var": {
|
||||
"link": "private/var",
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp asar mode should not inject ElectronAsarIntegrity into \`infoPlistsToIgnore\` 2`] = `
|
||||
{
|
||||
"Contents/Info.plist": undefined,
|
||||
"Contents/Resources/SubApp-1.app/Contents/Info.plist": undefined,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp force packages successfully if \`out\` bundle already exists and \`force\` is \`true\` 1`] = `
|
||||
{
|
||||
"files": {
|
||||
"index.js": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"8c8cefe616b330a70980c457e479360417a320f53f484d34df65227ce3add026",
|
||||
],
|
||||
"hash": "8c8cefe616b330a70980c457e479360417a320f53f484d34df65227ce3add026",
|
||||
},
|
||||
"size": 64,
|
||||
},
|
||||
"package.json": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
||||
],
|
||||
"hash": "d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
||||
},
|
||||
"size": 41,
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp force packages successfully if \`out\` bundle already exists and \`force\` is \`true\` 2`] = `
|
||||
{
|
||||
"Contents/Info.plist": {
|
||||
"Resources/app.asar": {
|
||||
"algorithm": "SHA256",
|
||||
"hash": "85fff474383bd8df11cd9c5784e8fcd1525af71ff140a8a882e1dc9d5b39fcbf",
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp no asar mode should correctly merge two identical app folders 1`] = `
|
||||
[
|
||||
"index.js",
|
||||
{
|
||||
"content": "{
|
||||
"name": "app",
|
||||
"main": "index.js"
|
||||
}",
|
||||
"name": "package.json",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp no asar mode should correctly merge two identical app folders 2`] = `
|
||||
{
|
||||
"Contents/Info.plist": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp no asar mode should shim two different app folders 1`] = `
|
||||
{
|
||||
"files": {
|
||||
"index.js": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"f1e14240f7c833900fca84fabc2f0ff27084efdf1c5b228b015515de3f8fa28e",
|
||||
],
|
||||
"hash": "f1e14240f7c833900fca84fabc2f0ff27084efdf1c5b228b015515de3f8fa28e",
|
||||
},
|
||||
"size": 1063,
|
||||
},
|
||||
"package.json": {
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"blockSize": 4194304,
|
||||
"blocks": [
|
||||
"2873266521e41d58d02e7acfbbbdb046edfa04b6ce262b8987de8e8548671fc7",
|
||||
],
|
||||
"hash": "2873266521e41d58d02e7acfbbbdb046edfa04b6ce262b8987de8e8548671fc7",
|
||||
},
|
||||
"size": 33,
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp no asar mode should shim two different app folders 2`] = `
|
||||
[
|
||||
"private/var/i-aint-got-no-rhythm.bin",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp no asar mode should shim two different app folders 3`] = `
|
||||
[
|
||||
"index.js",
|
||||
{
|
||||
"content": "{
|
||||
"name": "app",
|
||||
"main": "index.js"
|
||||
}",
|
||||
"name": "package.json",
|
||||
},
|
||||
{
|
||||
"content": "hello world",
|
||||
"name": "private/var/file.txt",
|
||||
},
|
||||
"private/var/i-aint-got-no-rhythm.bin",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp no asar mode should shim two different app folders 4`] = `
|
||||
[
|
||||
"index.js",
|
||||
{
|
||||
"content": "{
|
||||
"name": "app",
|
||||
"main": "index.js"
|
||||
}",
|
||||
"name": "package.json",
|
||||
},
|
||||
{
|
||||
"content": "hello world",
|
||||
"name": "private/var/file.txt",
|
||||
},
|
||||
"private/var/hello-world.bin",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`makeUniversalApp no asar mode should shim two different app folders 5`] = `
|
||||
{
|
||||
"Contents/Info.plist": {},
|
||||
}
|
||||
`;
|
||||
@@ -1,20 +1,13 @@
|
||||
import { spawn } from '@malept/cross-spawn-promise';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
|
||||
import { makeUniversalApp } from '../dist/cjs/index';
|
||||
import { createTestApp, templateApp, VERIFY_APP_TIMEOUT, verifyApp } from './util';
|
||||
import { createPackage } from '@electron/asar';
|
||||
|
||||
const appsPath = path.resolve(__dirname, 'fixtures', 'apps');
|
||||
const appsOutPath = path.resolve(__dirname, 'fixtures', 'apps', 'out');
|
||||
|
||||
async function ensureUniversal(app: string) {
|
||||
const exe = path.resolve(app, 'Contents', 'MacOS', 'Electron');
|
||||
const result = await spawn(exe);
|
||||
expect(result).toContain('arm64');
|
||||
const result2 = await spawn('arch', ['-x86_64', exe]);
|
||||
expect(result2).toContain('x64');
|
||||
}
|
||||
|
||||
// See `jest.setup.ts` for app fixture setup process
|
||||
describe('makeUniversalApp', () => {
|
||||
afterEach(async () => {
|
||||
@@ -49,7 +42,9 @@ describe('makeUniversalApp', () => {
|
||||
).rejects.toThrow(/The out path ".*" already exists and force is not set to true/);
|
||||
});
|
||||
|
||||
it('packages successfully if `out` bundle already exists and `force` is `true`', async () => {
|
||||
it(
|
||||
'packages successfully if `out` bundle already exists and `force` is `true`',
|
||||
async () => {
|
||||
const out = path.resolve(appsOutPath, 'Error.app');
|
||||
await fs.mkdirp(out);
|
||||
await makeUniversalApp({
|
||||
@@ -58,50 +53,44 @@ describe('makeUniversalApp', () => {
|
||||
outAppPath: out,
|
||||
force: true,
|
||||
});
|
||||
await ensureUniversal(out);
|
||||
// Only a single asar as they were identical
|
||||
expect(
|
||||
(await fs.readdir(path.resolve(out, 'Contents', 'Resources'))).filter((p) =>
|
||||
p.endsWith('asar'),
|
||||
),
|
||||
).toEqual(['app.asar']);
|
||||
}, 60000);
|
||||
await verifyApp(out);
|
||||
},
|
||||
VERIFY_APP_TIMEOUT,
|
||||
);
|
||||
});
|
||||
|
||||
describe('asar mode', () => {
|
||||
it('should correctly merge two identical asars', async () => {
|
||||
it(
|
||||
'should correctly merge two identical asars',
|
||||
async () => {
|
||||
const out = path.resolve(appsOutPath, 'MergedAsar.app');
|
||||
await makeUniversalApp({
|
||||
x64AppPath: path.resolve(appsPath, 'X64Asar.app'),
|
||||
arm64AppPath: path.resolve(appsPath, 'Arm64Asar.app'),
|
||||
outAppPath: out,
|
||||
});
|
||||
await ensureUniversal(out);
|
||||
// Only a single asar as they were identical
|
||||
expect(
|
||||
(await fs.readdir(path.resolve(out, 'Contents', 'Resources'))).filter((p) =>
|
||||
p.endsWith('asar'),
|
||||
),
|
||||
).toEqual(['app.asar']);
|
||||
}, 60000);
|
||||
await verifyApp(out);
|
||||
},
|
||||
VERIFY_APP_TIMEOUT,
|
||||
);
|
||||
|
||||
it('should create a shim if asars are different between architectures', async () => {
|
||||
it(
|
||||
'should create a shim if asars are different between architectures',
|
||||
async () => {
|
||||
const out = path.resolve(appsOutPath, 'ShimmedAsar.app');
|
||||
await makeUniversalApp({
|
||||
x64AppPath: path.resolve(appsPath, 'X64Asar.app'),
|
||||
arm64AppPath: path.resolve(appsPath, 'Arm64AsarExtraFile.app'),
|
||||
outAppPath: out,
|
||||
});
|
||||
await ensureUniversal(out);
|
||||
// We have three asars including the arch-agnostic shim
|
||||
expect(
|
||||
(await fs.readdir(path.resolve(out, 'Contents', 'Resources')))
|
||||
.filter((p) => p.endsWith('asar'))
|
||||
.sort(),
|
||||
).toEqual(['app.asar', 'app-x64.asar', 'app-arm64.asar'].sort());
|
||||
}, 60000);
|
||||
await verifyApp(out);
|
||||
},
|
||||
VERIFY_APP_TIMEOUT,
|
||||
);
|
||||
|
||||
it('should merge two different asars when `mergeASARs` is enabled', async () => {
|
||||
it(
|
||||
'should merge two different asars when `mergeASARs` is enabled',
|
||||
async () => {
|
||||
const out = path.resolve(appsOutPath, 'MergedAsar.app');
|
||||
await makeUniversalApp({
|
||||
x64AppPath: path.resolve(appsPath, 'X64Asar.app'),
|
||||
@@ -110,16 +99,14 @@ describe('makeUniversalApp', () => {
|
||||
mergeASARs: true,
|
||||
singleArchFiles: 'extra-file.txt',
|
||||
});
|
||||
await ensureUniversal(out);
|
||||
// Only a single merged asar
|
||||
expect(
|
||||
(await fs.readdir(path.resolve(out, 'Contents', 'Resources'))).filter((p) =>
|
||||
p.endsWith('asar'),
|
||||
),
|
||||
).toEqual(['app.asar']);
|
||||
}, 60000);
|
||||
await verifyApp(out);
|
||||
},
|
||||
VERIFY_APP_TIMEOUT,
|
||||
);
|
||||
|
||||
it('throws an error if `mergeASARs` is enabled and `singleArchFiles` is missing a unique file', async () => {
|
||||
it(
|
||||
'throws an error if `mergeASARs` is enabled and `singleArchFiles` is missing a unique file',
|
||||
async () => {
|
||||
const out = path.resolve(appsOutPath, 'Error.app');
|
||||
await expect(
|
||||
makeUniversalApp({
|
||||
@@ -130,29 +117,87 @@ describe('makeUniversalApp', () => {
|
||||
singleArchFiles: 'bad-rule',
|
||||
}),
|
||||
).rejects.toThrow(/Detected unique file "extra-file\.txt"/);
|
||||
}, 60000);
|
||||
},
|
||||
VERIFY_APP_TIMEOUT,
|
||||
);
|
||||
|
||||
it.todo('should not inject ElectronAsarIntegrity into `infoPlistsToIgnore`');
|
||||
it(
|
||||
'should not inject ElectronAsarIntegrity into `infoPlistsToIgnore`',
|
||||
async () => {
|
||||
const arm64AppPath = await templateApp('Arm64-1.app', 'arm64', async (appPath) => {
|
||||
const { testPath } = await createTestApp('Arm64-1');
|
||||
await createPackage(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app.asar'));
|
||||
await templateApp('SubApp-1.app', 'arm64', async (subArm64AppPath) => {
|
||||
await fs.move(
|
||||
subArm64AppPath,
|
||||
path.resolve(appPath, 'Contents', 'Resources', path.basename(subArm64AppPath)),
|
||||
);
|
||||
});
|
||||
});
|
||||
const x64AppPath = await templateApp('X64-1.app', 'x64', async (appPath) => {
|
||||
const { testPath } = await createTestApp('X64-1');
|
||||
await createPackage(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app.asar'));
|
||||
await templateApp('SubApp-1.app', 'x64', async (subArm64AppPath) => {
|
||||
await fs.move(
|
||||
subArm64AppPath,
|
||||
path.resolve(appPath, 'Contents', 'Resources', path.basename(subArm64AppPath)),
|
||||
);
|
||||
});
|
||||
});
|
||||
const outAppPath = path.resolve(appsOutPath, 'UnmodifiedPlist.app');
|
||||
await makeUniversalApp({
|
||||
x64AppPath,
|
||||
arm64AppPath,
|
||||
outAppPath,
|
||||
mergeASARs: true,
|
||||
infoPlistsToIgnore: 'SubApp-1.app/Contents/Info.plist',
|
||||
});
|
||||
await verifyApp(outAppPath);
|
||||
},
|
||||
VERIFY_APP_TIMEOUT,
|
||||
);
|
||||
});
|
||||
|
||||
describe('no asar mode', () => {
|
||||
it('should correctly merge two identical app folders', async () => {
|
||||
it(
|
||||
'should correctly merge two identical app folders',
|
||||
async () => {
|
||||
const out = path.resolve(appsOutPath, 'MergedNoAsar.app');
|
||||
await makeUniversalApp({
|
||||
x64AppPath: path.resolve(appsPath, 'X64NoAsar.app'),
|
||||
arm64AppPath: path.resolve(appsPath, 'Arm64NoAsar.app'),
|
||||
outAppPath: out,
|
||||
});
|
||||
await ensureUniversal(out);
|
||||
// Only a single app folder as they were identical
|
||||
expect(
|
||||
(await fs.readdir(path.resolve(out, 'Contents', 'Resources'))).filter((p) =>
|
||||
p.startsWith('app'),
|
||||
),
|
||||
).toEqual(['app']);
|
||||
}, 60000);
|
||||
await verifyApp(out);
|
||||
},
|
||||
VERIFY_APP_TIMEOUT,
|
||||
);
|
||||
|
||||
it.todo('should shim two different app folders');
|
||||
it(
|
||||
'should shim two different app folders',
|
||||
async () => {
|
||||
const arm64AppPath = await templateApp('ShimArm64.app', 'arm64', async (appPath) => {
|
||||
const { testPath } = await createTestApp('shimArm64', {
|
||||
'i-aint-got-no-rhythm.bin': 'boomshakalaka',
|
||||
});
|
||||
await fs.copy(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app'));
|
||||
});
|
||||
|
||||
const x64AppPath = await templateApp('ShimX64.app', 'x64', async (appPath) => {
|
||||
const { testPath } = await createTestApp('shimX64', { 'hello-world.bin': 'Hello World' });
|
||||
await fs.copy(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app'));
|
||||
});
|
||||
|
||||
const outAppPath = path.resolve(appsOutPath, 'ShimNoAsar.app');
|
||||
await makeUniversalApp({
|
||||
x64AppPath,
|
||||
arm64AppPath,
|
||||
outAppPath,
|
||||
});
|
||||
await verifyApp(outAppPath);
|
||||
},
|
||||
VERIFY_APP_TIMEOUT,
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: Add tests for
|
||||
|
||||
180
test/util.ts
Normal file
180
test/util.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import { downloadArtifact } from '@electron/get';
|
||||
import { spawn } from '@malept/cross-spawn-promise';
|
||||
import * as zip from 'cross-zip';
|
||||
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';
|
||||
|
||||
// We do a LOT of verifications in `verifyApp` 😅
|
||||
// exec universal binary -> verify ALL asars -> verify ALL app dirs -> verify ALL asar integrity entries
|
||||
// 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 verifyApp = async (appPath: string) => {
|
||||
await ensureUniversal(appPath);
|
||||
|
||||
const resourcesDir = path.resolve(appPath, 'Contents', 'Resources');
|
||||
const resourcesDirContents = await fs.readdir(resourcesDir);
|
||||
|
||||
// sort for consistent result
|
||||
const asars = resourcesDirContents.filter((p) => p.endsWith('.asar')).sort();
|
||||
for await (const asar of asars) {
|
||||
// verify header
|
||||
const asarFs = getRawHeader(path.resolve(resourcesDir, asar));
|
||||
expect(removeUnstableProperties(asarFs.header)).toMatchSnapshot();
|
||||
}
|
||||
|
||||
// check all app and unpacked dirs (incl. shimmed)
|
||||
const dirsToSnapshot = [
|
||||
'app',
|
||||
'app.asar.unpacked',
|
||||
'app-x64',
|
||||
'app-x64.asar.unpacked',
|
||||
'app-arm64',
|
||||
'app-arm64.asar.unpacked',
|
||||
];
|
||||
const appDirs = resourcesDirContents
|
||||
.filter((p) => dirsToSnapshot.includes(path.basename(p)))
|
||||
.sort();
|
||||
for await (const dir of appDirs) {
|
||||
await verifyFileTree(path.resolve(resourcesDir, dir));
|
||||
}
|
||||
|
||||
const allFiles = await fileUtils.getAllAppFiles(appPath);
|
||||
const infoPlists = allFiles
|
||||
.filter(
|
||||
(appFile) =>
|
||||
appFile.type === fileUtils.AppFileType.INFO_PLIST &&
|
||||
// These are test app fixtures, no need to snapshot within `TestApp.app/Contents/Frameworks`
|
||||
!appFile.relativePath.includes(path.join('Contents', 'Frameworks')),
|
||||
)
|
||||
.map((af) => af.relativePath)
|
||||
.sort();
|
||||
|
||||
const integrityMap: Record<string, string> = {};
|
||||
const integrity = await Promise.all(
|
||||
infoPlists.map((ip) => extractAsarIntegrity(path.resolve(appPath, ip))),
|
||||
);
|
||||
for (let i = 0; i < integrity.length; i++) {
|
||||
const relativePath = infoPlists[i];
|
||||
const asarIntegrity = integrity[i];
|
||||
integrityMap[relativePath] = asarIntegrity;
|
||||
}
|
||||
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'),
|
||||
) as any;
|
||||
return integrity;
|
||||
};
|
||||
|
||||
export const verifyFileTree = async (dirPath: string) => {
|
||||
const dirFiles = await fileUtils.getAllAppFiles(dirPath);
|
||||
const files = dirFiles.map((file) => {
|
||||
const it = path.join(dirPath, file.relativePath);
|
||||
const name = toSystemIndependentPath(file.relativePath);
|
||||
if (it.endsWith('.txt') || it.endsWith('.json')) {
|
||||
return { name, content: fs.readFileSync(it, 'utf-8') };
|
||||
}
|
||||
return name;
|
||||
});
|
||||
expect(files).toMatchSnapshot();
|
||||
};
|
||||
|
||||
export const ensureUniversal = async (app: string) => {
|
||||
const exe = path.resolve(app, 'Contents', 'MacOS', 'Electron');
|
||||
const result = await spawn(exe);
|
||||
expect(result).toContain('arm64');
|
||||
const result2 = await spawn('arch', ['-x86_64', exe]);
|
||||
expect(result2).toContain('x64');
|
||||
};
|
||||
|
||||
export const toSystemIndependentPath = (s: string): string => {
|
||||
return path.sep === '/' ? s : s.replace(/\\/g, '/');
|
||||
};
|
||||
|
||||
export const removeUnstableProperties = (data: any) => {
|
||||
return JSON.parse(
|
||||
JSON.stringify(data, (name, value) => {
|
||||
if (name === 'offset') {
|
||||
return undefined;
|
||||
}
|
||||
return value;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Directory structure:
|
||||
* testName
|
||||
* ├── private
|
||||
* │ └── var
|
||||
* │ ├── app
|
||||
* │ │ └── file.txt -> ../file.txt
|
||||
* │ └── file.txt
|
||||
* └── var -> private/var
|
||||
* ├── index.js
|
||||
* ├── package.json
|
||||
*/
|
||||
export const createTestApp = async (
|
||||
testName: string | undefined,
|
||||
additionalFiles: Record<string, string> = {},
|
||||
) => {
|
||||
const outDir = (testName || 'app') + Math.floor(Math.random() * 100); // tests run in parallel, randomize dir suffix to prevent naming collisions
|
||||
const testPath = path.join(appsDir, outDir);
|
||||
await fs.remove(testPath);
|
||||
|
||||
await fs.copy(path.join(asarsDir, 'app'), testPath);
|
||||
|
||||
const privateVarPath = path.join(testPath, 'private', 'var');
|
||||
const varPath = path.join(testPath, 'var');
|
||||
|
||||
await fs.mkdir(privateVarPath, { recursive: true });
|
||||
await fs.symlink(path.relative(testPath, privateVarPath), varPath);
|
||||
|
||||
const files = {
|
||||
'file.txt': 'hello world',
|
||||
...additionalFiles,
|
||||
};
|
||||
for await (const [filename, fileData] of Object.entries(files)) {
|
||||
const originFilePath = path.join(varPath, filename);
|
||||
await fs.writeFile(originFilePath, fileData);
|
||||
}
|
||||
const appPath = path.join(varPath, 'app');
|
||||
await fs.mkdirp(appPath);
|
||||
await fs.symlink('../file.txt', path.join(appPath, 'file.txt'));
|
||||
|
||||
return {
|
||||
testPath,
|
||||
varPath,
|
||||
appPath,
|
||||
};
|
||||
};
|
||||
|
||||
export const templateApp = async (
|
||||
name: string,
|
||||
arch: string,
|
||||
modify: (appPath: string) => Promise<void>,
|
||||
) => {
|
||||
const electronZip = await downloadArtifact({
|
||||
artifactName: 'electron',
|
||||
version: '27.0.0',
|
||||
platform: 'darwin',
|
||||
arch,
|
||||
});
|
||||
const appPath = path.resolve(appsDir, name);
|
||||
zip.unzipSync(electronZip, appsDir);
|
||||
await fs.rename(path.resolve(appsDir, 'Electron.app'), appPath);
|
||||
await fs.remove(path.resolve(appPath, 'Contents', 'Resources', 'default_app.asar'));
|
||||
await modify(appPath);
|
||||
|
||||
return appPath;
|
||||
};
|
||||
Reference in New Issue
Block a user