From 358b7a4458859f768921a31dd3dfe85947d9abeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ramos?= Date: Tue, 1 Nov 2022 18:27:39 -0700 Subject: [PATCH] hermes-utils.js: Add test coverage Summary: Improving test coverage in anticipation of some changes being made to `hermes-utils.js`. Moved test scripts to `hermes/__tests__` and grouped related tests. We have been delegating some of the work to local binaries via `execSync`, which can be hard to mock or test. We now use a proxy `delegateSync` method that uses `spawnSync` internally to break down the invocation into `command`, `arguments`, `options`. Instead of simply mocking based on the command being executed, we can now conditionally mock based on the arguments being passed. Added a `createTarballFromDirectory` method. This can be used later when creating different tarballs. Added `populateMockFilesystemWithHermesBuildArtifacts()` to mock the filesystem state after Hermes has been built. Changelog: [internal] Reviewed By: cipolleschi Differential Revision: D40871802 fbshipit-source-id: 4348d3c38926ec7eb13d794040a9040010879f58 --- scripts/__tests__/hermes-utils-test.js | 336 ------------- scripts/hermes/__tests__/hermes-utils-test.js | 466 ++++++++++++++++++ scripts/hermes/hermes-utils.js | 56 ++- 3 files changed, 507 insertions(+), 351 deletions(-) delete mode 100644 scripts/__tests__/hermes-utils-test.js create mode 100644 scripts/hermes/__tests__/hermes-utils-test.js diff --git a/scripts/__tests__/hermes-utils-test.js b/scripts/__tests__/hermes-utils-test.js deleted file mode 100644 index 68ee3edaba..0000000000 --- a/scripts/__tests__/hermes-utils-test.js +++ /dev/null @@ -1,336 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -import * as path from 'path'; - -const { - configureMakeForPrebuiltHermesC, - copyBuildScripts, - copyPodSpec, - downloadHermesSourceTarball, - expandHermesSourceTarball, - getHermesPrebuiltArtifactsTarballName, - getHermesTagSHA, - readHermesTag, - setHermesTag, - shouldUsePrebuiltHermesC, -} = require('../hermes/hermes-utils'); - -const hermesTag = - 'hermes-2022-04-28-RNv0.69.0-15d07c2edd29a4ea0b8f15ab0588a0c1adb1200f'; -const tarballContents = 'dummy string'; -const hermescContents = 'dummy string'; -const hermesTagSha = '5244f819b2f3949ca94a3a1bf75d54a8ed59d94a'; - -const ROOT_DIR = path.normalize(path.join(__dirname, '..', '..')); -const SDKS_DIR = path.join(ROOT_DIR, 'sdks'); - -const MemoryFs = require('metro-memory-fs'); - -let execCalls; -let fs; - -jest.mock('child_process', () => ({ - execSync: jest.fn(command => { - if (command.startsWith('curl')) { - fs.writeFileSync( - path.join(SDKS_DIR, 'download', `hermes-${hermesTagSha}.tgz`), - tarballContents, - ); - execCalls.curl = true; - return {code: 0}; - } - - if (command.startsWith('git')) { - execCalls.git = true; - return hermesTagSha + '\n'; - } - - if (command.startsWith('tar')) { - fs.mkdirSync(path.join(SDKS_DIR, 'hermes', 'utils'), { - recursive: true, - }); - fs.writeFileSync(path.join(SDKS_DIR, 'hermes', `package.json`), '{}'); - execCalls.tar = true; - return {code: 0}; - } - }), -})); - -function populateMockFilesystem() { - fs.mkdirSync(path.join(SDKS_DIR, 'hermes-engine', 'utils'), { - recursive: true, - }); - fs.writeFileSync( - path.join( - ROOT_DIR, - 'sdks', - 'hermes-engine', - 'utils', - 'build-apple-framework.sh', - ), - 'Dummy file', - ); - fs.writeFileSync( - path.join( - ROOT_DIR, - 'sdks', - 'hermes-engine', - 'utils', - 'build-ios-framework.sh', - ), - 'Dummy file', - ); - fs.writeFileSync( - path.join( - ROOT_DIR, - 'sdks', - 'hermes-engine', - 'utils', - 'build-mac-framework.sh', - ), - 'Dummy file', - ); - fs.writeFileSync( - path.join(SDKS_DIR, 'hermes-engine', 'hermes-engine.podspec'), - 'Dummy file', - ); - fs.writeFileSync( - path.join(SDKS_DIR, 'hermes-engine', 'hermes-utils.rb'), - 'Dummy file', - ); -} - -describe('hermes-utils', () => { - beforeEach(() => { - jest.resetModules(); - - jest.mock( - 'fs', - () => - new MemoryFs({ - platform: process.platform === 'win32' ? 'win32' : 'posix', - }), - ); - fs = require('fs'); - fs.reset(); - - populateMockFilesystem(); - - execCalls = Object.create(null); - }); - describe('readHermesTag', () => { - it('should return main if .hermesversion does not exist', () => { - expect(readHermesTag()).toEqual('main'); - }); - it('should fail if hermes tag is empty', () => { - fs.writeFileSync(path.join(SDKS_DIR, '.hermesversion'), ''); - expect(() => { - readHermesTag(); - }).toThrow('[Hermes] .hermesversion file is empty.'); - }); - it('should return tag from .hermesversion if file exists', () => { - fs.writeFileSync(path.join(SDKS_DIR, '.hermesversion'), hermesTag); - expect(readHermesTag()).toEqual(hermesTag); - }); - }); - describe('setHermesTag', () => { - it('should write tag to .hermesversion file', () => { - setHermesTag(hermesTag); - expect( - fs.readFileSync(path.join(SDKS_DIR, '.hermesversion'), { - encoding: 'utf8', - flag: 'r', - }), - ).toEqual(hermesTag); - }); - it('should set Hermes tag and read it back', () => { - setHermesTag(hermesTag); - expect(readHermesTag()).toEqual(hermesTag); - }); - }); - describe('getHermesPrebuiltArtifactsTarballName', () => { - it('should return Hermes tarball name', () => { - expect( - getHermesPrebuiltArtifactsTarballName('Debug', '1000.0.0'), - ).toEqual('hermes-runtime-darwin-debug-v1000.0.0.tar.gz'); - }); - it('should throw if build type is undefined', () => { - expect(() => { - getHermesPrebuiltArtifactsTarballName(); - }).toThrow('Did not specify build type.'); - }); - it('should throw if release version is undefined', () => { - expect(() => { - getHermesPrebuiltArtifactsTarballName('Release'); - }).toThrow('Did not specify release version.'); - }); - it('should return debug Hermes tarball name for RN 0.70.0', () => { - expect(getHermesPrebuiltArtifactsTarballName('Debug', '0.70.0')).toEqual( - 'hermes-runtime-darwin-debug-v0.70.0.tar.gz', - ); - }); - it('should return a wildcard Hermes tarball name for any RN version', () => { - expect(getHermesPrebuiltArtifactsTarballName('Debug', '*')).toEqual( - 'hermes-runtime-darwin-debug-v*.tar.gz', - ); - }); - }); - describe('getHermesTagSHA', () => { - it('should return trimmed commit SHA for Hermes tag', () => { - expect(getHermesTagSHA(hermesTag)).toEqual(hermesTagSha); - expect(execCalls.git).toBe(true); - }); - }); - describe('downloadHermesSourceTarball', () => { - it('should download Hermes source tarball to download dir', () => { - fs.writeFileSync(path.join(SDKS_DIR, '.hermesversion'), hermesTag); - downloadHermesSourceTarball(); - expect(execCalls.curl).toBe(true); - expect( - fs.readFileSync( - path.join(SDKS_DIR, 'download', `hermes-${hermesTagSha}.tgz`), - { - encoding: 'utf8', - flag: 'r', - }, - ), - ).toEqual(tarballContents); - }); - it('should not re-download Hermes tarball if tarball exists', () => { - fs.mkdirSync(path.join(SDKS_DIR, 'download'), {recursive: true}); - fs.writeFileSync( - path.join(SDKS_DIR, 'download', `hermes-${hermesTagSha}.tgz`), - tarballContents, - ); - - downloadHermesSourceTarball(); - expect(execCalls.curl).toBeUndefined(); - }); - }); - describe('expandHermesSourceTarball', () => { - it('should expand Hermes source tarball to Hermes source dir', () => { - fs.mkdirSync(path.join(SDKS_DIR, 'download'), {recursive: true}); - fs.writeFileSync( - path.join(SDKS_DIR, 'download', `hermes-${hermesTagSha}.tgz`), - tarballContents, - ); - expect(fs.existsSync(path.join(SDKS_DIR, 'hermes'))).toBeFalsy(); - expandHermesSourceTarball(); - expect(execCalls.tar).toBe(true); - expect(fs.existsSync(path.join(SDKS_DIR, 'hermes'))).toBe(true); - }); - it('should fail if Hermes source tarball does not exist', () => { - expect(() => { - expandHermesSourceTarball(); - }).toThrow('[Hermes] Could not locate Hermes tarball.'); - expect(execCalls.tar).toBeUndefined(); - }); - }); - describe('copyBuildScripts', () => { - it('should copy React Native Hermes build scripts to Hermes source directory', () => { - copyBuildScripts(); - - [ - 'build-apple-framework.sh', - 'build-ios-framework.sh', - 'build-mac-framework.sh', - ].forEach(buildScript => { - expect( - fs.readFileSync( - path.join(ROOT_DIR, 'sdks', 'hermes', 'utils', buildScript), - { - encoding: 'utf8', - flag: 'r', - }, - ), - ).toEqual( - fs.readFileSync( - path.join(ROOT_DIR, 'sdks', 'hermes-engine', 'utils', buildScript), - { - encoding: 'utf8', - flag: 'r', - }, - ), - ); - }); - }); - }); - describe('copyPodSpec', () => { - it('should copy React Native Hermes Podspec to Hermes source directory', () => { - copyPodSpec(); - expect( - fs.readFileSync( - path.join(SDKS_DIR, 'hermes', 'hermes-engine.podspec'), - { - encoding: 'utf8', - flag: 'r', - }, - ), - ).toEqual( - fs.readFileSync( - path.join(SDKS_DIR, 'hermes-engine', 'hermes-engine.podspec'), - { - encoding: 'utf8', - flag: 'r', - }, - ), - ); - }); - it('should copy hermes-utils.rb to Hermes source directory', () => { - copyPodSpec(); - expect( - fs.readFileSync(path.join(SDKS_DIR, 'hermes', 'hermes-utils.rb'), { - encoding: 'utf8', - flag: 'r', - }), - ).toEqual( - fs.readFileSync( - path.join(SDKS_DIR, 'hermes-engine', 'hermes-utils.rb'), - { - encoding: 'utf8', - flag: 'r', - }, - ), - ); - }); - }); - describe('shouldUsePrebuiltHermesC', () => { - it('returns false if path to osx hermesc does not exist', () => { - expect(shouldUsePrebuiltHermesC('macos')).toBeFalsy(); - }); - it('returns false for non-macOS', () => { - expect(shouldUsePrebuiltHermesC('windows')).toBeFalsy(); - }); - it('return true only if path to hermesc exists', () => { - fs.mkdirSync(path.join(SDKS_DIR, 'hermesc', 'osx-bin'), { - recursive: true, - }); - fs.writeFileSync( - path.join(SDKS_DIR, 'hermesc', 'osx-bin', 'hermesc'), - hermescContents, - ); - expect(shouldUsePrebuiltHermesC('macos')).toBe(true); - }); - }); - - describe('configureMakeForPrebuiltHermesC', () => { - it('creates ImportHermesC file', () => { - fs.mkdirSync(path.join(SDKS_DIR, 'hermesc', 'osx-bin'), { - recursive: true, - }); - configureMakeForPrebuiltHermesC(); - expect( - fs.existsSync( - path.join(SDKS_DIR, 'hermesc', 'osx-bin', 'ImportHermesc.cmake'), - ), - ).toBe(true); - }); - }); -}); diff --git a/scripts/hermes/__tests__/hermes-utils-test.js b/scripts/hermes/__tests__/hermes-utils-test.js new file mode 100644 index 0000000000..f9c446b9a8 --- /dev/null +++ b/scripts/hermes/__tests__/hermes-utils-test.js @@ -0,0 +1,466 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import * as path from 'path'; + +const os = require('os'); + +const { + configureMakeForPrebuiltHermesC, + copyBuildScripts, + copyPodSpec, + createHermesPrebuiltArtifactsTarball, + createTarballFromDirectory, + downloadHermesSourceTarball, + expandHermesSourceTarball, + getHermesTarballDownloadPath, + getHermesPrebuiltArtifactsTarballName, + getHermesTagSHA, + readHermesTag, + setHermesTag, + shouldUsePrebuiltHermesC, +} = require('../hermes-utils'); + +const hermesTag = + 'hermes-2022-04-28-RNv0.69.0-15d07c2edd29a4ea0b8f15ab0588a0c1adb1200f'; +const tarballContents = 'dummy string'; +const hermescContents = 'dummy string'; +const hermesTagSha = '5244f819b2f3949ca94a3a1bf75d54a8ed59d94a'; + +const ROOT_DIR = path.normalize(path.join(__dirname, '../../..')); +const SDKS_DIR = path.join(ROOT_DIR, 'sdks'); + +const MemoryFs = require('metro-memory-fs'); + +let execCalls, spawnCalls; +let fs; + +jest.mock('child_process', () => ({ + execSync: jest.fn((command, options) => { + // git is used in getHermesTagSHA to obtain the commit sha for the latest commit to Hermes main + if (command.startsWith('git')) { + execCalls.git = true; + return hermesTagSha + '\n'; + } + }), + spawnSync: jest.fn((command, args, options) => { + // curl is used in downloadHermesSourceTarball to fetch the source code from github.com/facebook/hermes for a specific Hermes commit sha + if (command === 'curl') { + const downloadPath = args[2]; + fs.writeFileSync(downloadPath, tarballContents); + spawnCalls.curl = true; + return {code: 0}; + } + + // tar is used in createTarballFromDirectory + if (command === 'tar') { + spawnCalls.tar = true; + + if (args[0] === '-zxf') { + // We are expanding the tarball + fs.mkdirSync(path.join(SDKS_DIR, 'hermes/utils'), { + recursive: true, + }); + fs.writeFileSync(path.join(SDKS_DIR, 'hermes/package.json'), '{}'); + return {code: 0}; + } else if (args[2] === '-czvf') { + // We are creating the tarball + const filename = args[3]; + fs.writeFileSync(filename, tarballContents); + return {code: 0}; + } + } + + // rsync is used in createHermesPrebuiltArtifactsTarball + if (command === 'rsync') { + spawnCalls.rsync = true; + spawnCalls.rsyncArgs = args; + const destination = args[args.length - 1]; + + // Create destination directory + fs.mkdirSync(path.join(options.cwd, destination), { + recursive: true, + }); + } + }), +})); + +function populateMockFilesystemWithHermesBuildScripts() { + fs.mkdirSync(path.join(SDKS_DIR, 'hermes-engine/utils'), { + recursive: true, + }); + fs.writeFileSync( + path.join(SDKS_DIR, 'hermes-engine/utils/build-apple-framework.sh'), + 'Dummy file', + ); + fs.writeFileSync( + path.join(SDKS_DIR, 'hermes-engine/utils/build-ios-framework.sh'), + 'Dummy file', + ); + fs.writeFileSync( + path.join(SDKS_DIR, 'hermes-engine/utils/build-mac-framework.sh'), + 'Dummy file', + ); + fs.writeFileSync( + path.join(SDKS_DIR, 'hermes-engine/hermes-engine.podspec'), + 'Dummy file', + ); + fs.writeFileSync( + path.join(SDKS_DIR, 'hermes-engine/hermes-utils.rb'), + 'Dummy file', + ); +} + +function populateMockFilesystemWithHermesBuildArtifacts() { + fs.mkdirSync(os.tmpdir(), {recursive: true}); + const frameworksDir = path.join( + SDKS_DIR, + 'hermes/destroot/Library/Frameworks', + ); + fs.mkdirSync(path.join(frameworksDir, 'macosx/hermes.framework'), { + recursive: true, + }); + fs.mkdirSync(path.join(frameworksDir, 'universal/hermes.xcframework'), { + recursive: true, + }); + + const dsymsDirs = [ + 'macosx', + 'universal/hermes.xcframework/ios-arm64/dSYMs', + 'universal/hermes.xcframework/ios-arm64_x86_64-simulator/dSYMs', + 'universal/hermes.xcframework/ios-arm64_x86_64-maccatalyst/dSYMs', + ]; + + for (const dsymsDir of dsymsDirs) { + fs.mkdirSync(path.join(frameworksDir, dsymsDir, 'hermes.framework.dSYM'), { + recursive: true, + }); + } +} + +describe('hermes-utils', () => { + beforeEach(() => { + jest.resetModules(); + + jest.mock( + 'fs', + () => + new MemoryFs({ + platform: process.platform === 'win32' ? 'win32' : 'posix', + }), + ); + fs = require('fs'); + fs.reset(); + + populateMockFilesystemWithHermesBuildScripts(); + + execCalls = Object.create(null); + spawnCalls = Object.create(null); + }); + + describe('Versioning Hermes', () => { + describe('readHermesTag', () => { + it('should return main if .hermesversion does not exist', () => { + expect(readHermesTag()).toEqual('main'); + }); + it('should fail if hermes tag is empty', () => { + fs.writeFileSync(path.join(SDKS_DIR, '.hermesversion'), ''); + expect(() => { + readHermesTag(); + }).toThrow('[Hermes] .hermesversion file is empty.'); + }); + it('should return tag from .hermesversion if file exists', () => { + fs.writeFileSync(path.join(SDKS_DIR, '.hermesversion'), hermesTag); + expect(readHermesTag()).toEqual(hermesTag); + }); + }); + + describe('setHermesTag', () => { + it('should write tag to .hermesversion file', () => { + setHermesTag(hermesTag); + expect( + fs.readFileSync(path.join(SDKS_DIR, '.hermesversion'), { + encoding: 'utf8', + flag: 'r', + }), + ).toEqual(hermesTag); + }); + it('should set Hermes tag and read it back', () => { + setHermesTag(hermesTag); + expect(readHermesTag()).toEqual(hermesTag); + }); + }); + + describe('getHermesTagSHA', () => { + it('should return trimmed commit SHA for Hermes tag', () => { + expect(getHermesTagSHA(hermesTag)).toEqual(hermesTagSha); + expect(execCalls.git).toBe(true); + }); + }); + }); + + describe('Downloading Hermes', () => { + describe('getHermesTarballDownloadPath', () => { + it('returns download path with Hermes tag sha', () => { + const hermesTarballDownloadPath = + getHermesTarballDownloadPath(hermesTag); + expect(hermesTarballDownloadPath).toEqual( + path.join( + SDKS_DIR, + 'download', + `hermes-${getHermesTagSHA(hermesTag)}.tgz`, + ), + ); + }); + }); + describe('downloadHermesSourceTarball', () => { + it('should download Hermes source tarball to download dir', () => { + fs.writeFileSync(path.join(SDKS_DIR, '.hermesversion'), hermesTag); + const hermesTarballDownloadPath = + getHermesTarballDownloadPath(hermesTag); + downloadHermesSourceTarball(); + expect(spawnCalls.curl).toBe(true); + expect( + fs.readFileSync(hermesTarballDownloadPath, { + encoding: 'utf8', + flag: 'r', + }), + ).toEqual(tarballContents); + }); + it('should not re-download Hermes source tarball if tarball exists', () => { + fs.mkdirSync(path.join(SDKS_DIR, 'download'), {recursive: true}); + fs.writeFileSync( + path.join(SDKS_DIR, 'download', `hermes-${hermesTagSha}.tgz`), + tarballContents, + ); + + downloadHermesSourceTarball(); + expect(spawnCalls.curl).toBeUndefined(); + }); + }); + + describe('expandHermesSourceTarball', () => { + it('should expand Hermes source tarball to Hermes source dir', () => { + fs.mkdirSync(path.join(SDKS_DIR, 'download'), {recursive: true}); + fs.writeFileSync( + path.join(SDKS_DIR, 'download', `hermes-${hermesTagSha}.tgz`), + tarballContents, + ); + expect(fs.existsSync(path.join(SDKS_DIR, 'hermes'))).toBeFalsy(); + expandHermesSourceTarball(); + expect(fs.existsSync(path.join(SDKS_DIR, 'hermes'))).toBe(true); + }); + it('should fail if Hermes source tarball does not exist', () => { + expect(() => { + expandHermesSourceTarball(); + }).toThrow('[Hermes] Could not locate Hermes tarball.'); + }); + }); + }); + + describe('Configuring Hermes Build', () => { + describe('copyBuildScripts', () => { + it('should copy React Native Hermes build scripts to Hermes source directory', () => { + copyBuildScripts(); + + [ + 'build-apple-framework.sh', + 'build-ios-framework.sh', + 'build-mac-framework.sh', + ].forEach(buildScript => { + expect( + fs.readFileSync( + path.join(ROOT_DIR, 'sdks/hermes/utils', buildScript), + { + encoding: 'utf8', + flag: 'r', + }, + ), + ).toEqual( + fs.readFileSync( + path.join(ROOT_DIR, 'sdks/hermes-engine/utils', buildScript), + { + encoding: 'utf8', + flag: 'r', + }, + ), + ); + }); + }); + }); + describe('copyPodSpec', () => { + it('should copy React Native Hermes Podspec to Hermes source directory', () => { + copyPodSpec(); + expect( + fs.readFileSync(path.join(SDKS_DIR, 'hermes/hermes-engine.podspec'), { + encoding: 'utf8', + flag: 'r', + }), + ).toEqual( + fs.readFileSync( + path.join(SDKS_DIR, 'hermes-engine/hermes-engine.podspec'), + { + encoding: 'utf8', + flag: 'r', + }, + ), + ); + }); + it('should copy hermes-utils.rb to Hermes source directory', () => { + copyPodSpec(); + expect( + fs.readFileSync(path.join(SDKS_DIR, 'hermes/hermes-utils.rb'), { + encoding: 'utf8', + flag: 'r', + }), + ).toEqual( + fs.readFileSync( + path.join(SDKS_DIR, 'hermes-engine/hermes-utils.rb'), + { + encoding: 'utf8', + flag: 'r', + }, + ), + ); + }); + }); + describe('shouldUsePrebuiltHermesC', () => { + it('returns false if path to osx hermesc does not exist', () => { + expect(shouldUsePrebuiltHermesC('macos')).toBeFalsy(); + }); + it('returns false for non-macOS', () => { + expect(shouldUsePrebuiltHermesC('windows')).toBeFalsy(); + }); + it('return true only if path to hermesc exists', () => { + fs.mkdirSync(path.join(SDKS_DIR, 'hermesc/osx-bin'), { + recursive: true, + }); + fs.writeFileSync( + path.join(SDKS_DIR, 'hermesc/osx-bin/hermesc'), + hermescContents, + ); + expect(shouldUsePrebuiltHermesC('macos')).toBe(true); + }); + }); + + describe('configureMakeForPrebuiltHermesC', () => { + it('creates ImportHermesC file', () => { + fs.mkdirSync(path.join(SDKS_DIR, 'hermesc/osx-bin'), { + recursive: true, + }); + configureMakeForPrebuiltHermesC(); + expect( + fs.existsSync( + path.join(SDKS_DIR, 'hermesc/osx-bin/ImportHermesc.cmake'), + ), + ).toBe(true); + }); + }); + }); + + describe('Packaging Hermes', () => { + beforeEach(() => { + populateMockFilesystemWithHermesBuildArtifacts(); + }); + + describe('createTarballFromDirectory', () => { + it('should create the tarball', () => { + fs.mkdirSync(path.join(SDKS_DIR, 'downloads'), {recursive: true}); + const tarballFilename = path.join( + SDKS_DIR, + 'downloads/hermes-runtime-darwin.tar.gz', + ); + createTarballFromDirectory( + path.join(SDKS_DIR, 'hermes/destroot'), + tarballFilename, + ); + expect(spawnCalls.tar).toBe(true); + expect(fs.existsSync(tarballFilename)).toBe(true); + }); + }); + + describe('getHermesPrebuiltArtifactsTarballName', () => { + it('should return Hermes prebuilts tarball name', () => { + expect( + getHermesPrebuiltArtifactsTarballName('Debug', '1000.0.0'), + ).toEqual('hermes-runtime-darwin-debug-v1000.0.0.tar.gz'); + }); + it('should throw if build type is undefined', () => { + expect(() => { + getHermesPrebuiltArtifactsTarballName(); + }).toThrow('Did not specify build type.'); + }); + it('should throw if release version is undefined', () => { + expect(() => { + getHermesPrebuiltArtifactsTarballName('Release'); + }).toThrow('Did not specify release version.'); + }); + it('should return debug Hermes prebuilts tarball name for RN 0.70.0', () => { + expect( + getHermesPrebuiltArtifactsTarballName('Debug', '0.70.0'), + ).toEqual('hermes-runtime-darwin-debug-v0.70.0.tar.gz'); + }); + it('should return a wildcard Hermes prebuilts tarball name for any RN version', () => { + expect(getHermesPrebuiltArtifactsTarballName('Debug', '*')).toEqual( + 'hermes-runtime-darwin-debug-v*.tar.gz', + ); + }); + }); + + describe('createHermesPrebuiltArtifactsTarball', () => { + it('creates tarball', () => { + const tarballOutputDir = fs.mkdtempSync( + path.join(os.tmpdir(), 'hermes-prebuilts-'), + ); + fs.mkdirSync(tarballOutputDir, { + recursive: true, + }); + + const excludeDebugSymbols = false; + const tarballOutputPath = createHermesPrebuiltArtifactsTarball( + path.join(SDKS_DIR, 'hermes'), + 'Debug', + '1000.0.0', + tarballOutputDir, + excludeDebugSymbols, + ); + expect(fs.existsSync(tarballOutputPath)).toBe(true); + expect(spawnCalls.rsync).toBe(true); + // rsync -a src dest + expect(spawnCalls.rsyncArgs.length).toEqual(3); + }); + + it('creates tarball with debug symbols excluded', () => { + const tarballOutputDir = fs.mkdtempSync( + path.join(os.tmpdir(), 'hermes-prebuilts-'), + ); + fs.mkdirSync(tarballOutputDir, { + recursive: true, + }); + + const excludeDebugSymbols = true; + const tarballOutputPath = createHermesPrebuiltArtifactsTarball( + path.join(SDKS_DIR, 'hermes'), + 'Debug', + '1000.0.0', + tarballOutputDir, + excludeDebugSymbols, + ); + expect(fs.existsSync(tarballOutputPath)).toBe(true); + expect(spawnCalls.rsync).toBe(true); + + // When the debug symbols are excluded, we pass an additional two parameters to rsync: + // rsync -a --exclude=dSYMs/ --exclude=*.dSYM/ src dest + expect(spawnCalls.rsyncArgs.length).toEqual(5); + expect(spawnCalls.rsyncArgs[1]).toEqual('--exclude=dSYMs/'); + expect(spawnCalls.rsyncArgs[2]).toEqual('--exclude=*.dSYM/'); + }); + }); + }); +}); diff --git a/scripts/hermes/hermes-utils.js b/scripts/hermes/hermes-utils.js index 2ddbfe5224..918e3014f2 100644 --- a/scripts/hermes/hermes-utils.js +++ b/scripts/hermes/hermes-utils.js @@ -12,7 +12,7 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); -const {execSync} = require('child_process'); +const {execSync, spawnSync} = require('child_process'); const SDKS_DIR = path.normalize(path.join(__dirname, '..', '..', 'sdks')); const HERMES_DIR = path.join(SDKS_DIR, 'hermes'); @@ -27,6 +27,17 @@ const MACOS_IMPORT_HERMESC_PATH = path.join( 'ImportHermesc.cmake', ); +/** + * Delegate execution to the supplied command. + * + * @param command Path to the command. + * @param args Array of arguments pass to the command. + * @param options child process options. + */ +function delegateSync(command, args, options) { + return spawnSync(command, args, {stdio: 'inherit', ...options}); +} + function readHermesTag() { if (fs.existsSync(HERMES_TAG_FILE_PATH)) { const data = fs @@ -90,7 +101,7 @@ function downloadHermesSourceTarball() { `[Hermes] Downloading Hermes source code for commit ${hermesTagSHA}`, ); try { - execSync(`curl ${hermesTarballUrl} -Lo ${hermesTarballDownloadPath}`); + delegateSync('curl', [hermesTarballUrl, '-Lo', hermesTarballDownloadPath]); } catch (error) { throw new Error(`[Hermes] Failed to download Hermes tarball. ${error}`); } @@ -110,9 +121,13 @@ function expandHermesSourceTarball() { } console.info(`[Hermes] Expanding Hermes tarball for commit ${hermesTagSHA}`); try { - execSync( - `tar -zxf ${hermesTarballDownloadPath} --strip-components=1 --directory ${HERMES_DIR}`, - ); + delegateSync('tar', [ + '-zxf', + hermesTarballDownloadPath, + '--strip-components=1', + '--directory', + HERMES_DIR, + ]); } catch (error) { throw new Error('[Hermes] Failed to expand Hermes tarball.'); } @@ -207,6 +222,14 @@ function getHermesPrebuiltArtifactsTarballName(buildType, releaseVersion) { return `hermes-runtime-darwin-${buildType.toLowerCase()}-v${releaseVersion}.tar.gz`; } +/** + * Creates a tarball with the contents of the supplied directory. + */ +function createTarballFromDirectory(directory, filename) { + const args = ['-C', directory, '-czvf', filename, '.']; + delegateSync('tar', args); +} + function createHermesPrebuiltArtifactsTarball( hermesDir, buildType, @@ -231,35 +254,36 @@ function createHermesPrebuiltArtifactsTarball( args.push('--exclude=dSYMs/'); args.push('--exclude=*.dSYM/'); } - execSync(`rsync ${args.join(' ')} ./destroot ${tarballTempDir}`, { + args.push('./destroot'); + args.push(tarballTempDir); + delegateSync('rsync', args, { cwd: hermesDir, }); if (fs.existsSync(path.join(hermesDir, 'LICENSE'))) { - execSync(`cp LICENSE ${tarballTempDir}`, {cwd: hermesDir}); + delegateSync('cp', ['LICENSE', tarballTempDir], {cwd: hermesDir}); } } catch (error) { throw new Error(`Failed to copy destroot to tempdir: ${error}`); } - const tarballFilename = getHermesPrebuiltArtifactsTarballName( - buildType, - releaseVersion, + const tarballFilename = path.join( + tarballOutputDir, + getHermesPrebuiltArtifactsTarballName(buildType, releaseVersion), ); - const tarballOutputPath = path.join(tarballOutputDir, tarballFilename); try { - execSync(`tar -C ${tarballTempDir} -czvf ${tarballOutputPath} .`); + createTarballFromDirectory(tarballTempDir, tarballFilename); } catch (error) { throw new Error(`[Hermes] Failed to create tarball: ${error}`); } - if (!fs.existsSync(tarballOutputPath)) { + if (!fs.existsSync(tarballFilename)) { throw new Error( - `Tarball creation failed, could not locate tarball at ${tarballOutputPath}`, + `Tarball creation failed, could not locate tarball at ${tarballFilename}`, ); } - return tarballOutputPath; + return tarballFilename; } function validateHermesFrameworksExist(destrootDir) { @@ -288,9 +312,11 @@ module.exports = { copyBuildScripts, copyPodSpec, createHermesPrebuiltArtifactsTarball, + createTarballFromDirectory, downloadHermesSourceTarball, expandHermesSourceTarball, getHermesTagSHA, + getHermesTarballDownloadPath, getHermesPrebuiltArtifactsTarballName, readHermesTag, setHermesTag,