1import spawnAsync from '@expo/spawn-async'; 2import fs from 'fs-extra'; 3import path from 'path'; 4 5import * as Directories from '../Directories'; 6import * as Packages from '../Packages'; 7 8async function runTests(testTargets: string[]) { 9 await spawnAsync('fastlane', ['ios', 'unit_tests', `targets:${testTargets.join(',')}`], { 10 cwd: Directories.getExpoRepositoryRootDir(), 11 stdio: 'inherit', 12 }); 13} 14 15function getTestSpecNames(pkg: Packages.Package): string[] { 16 const podspec = fs.readFileSync(path.join(pkg.path, pkg.podspecPath!), 'utf8'); 17 const regex = new RegExp("test_spec\\s'([^']*)'", 'g'); 18 const testSpecNames: string[] = []; 19 let match: RegExpExecArray | null; 20 while ((match = regex.exec(podspec)) !== null) { 21 testSpecNames.push(match[1]); 22 } 23 return testSpecNames; 24} 25 26export async function iosNativeUnitTests({ packages }: { packages?: string }) { 27 const allPackages = await Packages.getListOfPackagesAsync(); 28 const packageNamesFilter = packages ? packages.split(',') : []; 29 30 const targetsToTest: string[] = []; 31 const packagesToTest: string[] = []; 32 for (const pkg of allPackages) { 33 if (!pkg.podspecName || !pkg.podspecPath || !(await pkg.hasNativeTestsAsync('ios'))) { 34 if (packageNamesFilter.includes(pkg.packageName)) { 35 throw new Error(`The package ${pkg.packageName} does not include iOS unit tests.`); 36 } 37 continue; 38 } 39 40 if (packageNamesFilter.length > 0 && !packageNamesFilter.includes(pkg.packageName)) { 41 continue; 42 } 43 44 const testSpecNames = getTestSpecNames(pkg); 45 if (!testSpecNames.length) { 46 throw new Error( 47 `Failed to test package ${pkg.packageName}: no test specs were found in podspec file.` 48 ); 49 } 50 51 for (const testSpecName of testSpecNames) { 52 targetsToTest.push(`${pkg.podspecName}-Unit-${testSpecName}`); 53 } 54 packagesToTest.push(pkg.packageName); 55 } 56 57 if (packageNamesFilter.length && !targetsToTest.length) { 58 throw new Error( 59 `No packages were found with the specified names: ${packageNamesFilter.join(', ')}` 60 ); 61 } 62 63 try { 64 await runTests(targetsToTest); 65 } catch (error) { 66 console.error('iOS unit tests failed:'); 67 console.error('stdout >', error.stdout); 68 console.error('stderr >', error.stderr); 69 throw new Error('iOS Unit tests failed'); 70 } 71 console.log('✅ All unit tests passed for the following packages:', packagesToTest.join(', ')); 72} 73 74export default (program: any) => { 75 program 76 .command('ios-native-unit-tests') 77 .option( 78 '--packages <string>', 79 '[optional] Comma-separated list of package names to run unit tests for. Defaults to all packages with unit tests.' 80 ) 81 .description('Runs iOS native unit tests for each package that provides them.') 82 .asyncAction(iosNativeUnitTests); 83}; 84