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 8export async function iosNativeUnitTests({ packages }: { packages?: string }) { 9 const allPackages = await Packages.getListOfPackagesAsync(); 10 const packageNamesFilter = packages ? packages.split(',') : []; 11 let packagesTested: string[] = []; 12 let errors: any[] = []; 13 for (const pkg of allPackages) { 14 if (!(await pkg.hasNativeTestsAsync('ios'))) { 15 if (packageNamesFilter.includes(pkg.packageName)) { 16 throw new Error(`The package ${pkg.packageName} does not include iOS unit tests.`); 17 } 18 continue; 19 } 20 if (packageNamesFilter.length > 0 && !packageNamesFilter.includes(pkg.packageName)) { 21 continue; 22 } 23 24 try { 25 await spawnAsync('fastlane', ['prepare_schemes', `pod:${pkg.podspecName}`], { 26 cwd: Directories.getExpoRepositoryRootDir(), 27 stdio: 'inherit', 28 }); 29 30 // make schemes shared by moving them from xcodeproj/xcuserdata/runner.xcuserdatad/xcschemes 31 // to xcodeproj/xcshareddata/xcschemes 32 // otherwise they aren't visible to fastlane 33 const xcodeprojDir = path.join( 34 Directories.getIosDir(), 35 'Pods', 36 `${pkg.podspecName}.xcodeproj` 37 ); 38 const destinationDir = path.join(xcodeprojDir, 'xcshareddata', 'xcschemes'); 39 await fs.mkdirp(destinationDir); 40 41 // find user directory name, should be runner.xcuserdatad but depends on the OS username 42 const xcuserdataDirName = (await fs.readdir(path.join(xcodeprojDir, 'xcuserdata')))[0]; 43 44 const xcschemesDir = path.join(xcodeprojDir, 'xcuserdata', xcuserdataDirName, 'xcschemes'); 45 const xcschemesFiles = (await fs.readdir(xcschemesDir)).filter((file) => 46 file.endsWith('.xcscheme') 47 ); 48 if (!xcschemesFiles.length) { 49 throw new Error(`No scheme could be found to run tests for ${pkg.podspecName}`); 50 } 51 for (const file of xcschemesFiles) { 52 await fs.move(path.join(xcschemesDir, file), path.join(destinationDir, file), { 53 overwrite: true, 54 }); 55 } 56 57 await spawnAsync('fastlane', ['test_module', `pod:${pkg.podspecName}`], { 58 cwd: Directories.getExpoRepositoryRootDir(), 59 stdio: 'inherit', 60 }); 61 62 packagesTested.push(pkg.packageName); 63 } catch (error) { 64 errors.push(error); 65 } 66 } 67 if (errors.length) { 68 console.error('One or more iOS unit tests failed:'); 69 for (const error of errors) { 70 console.error(error.message); 71 console.error('stdout >', error.stdout); 72 console.error('stderr >', error.stderr); 73 if (error.message.startsWith('fastlane exited')) { 74 console.warn( 75 "Did you add unit tests to a package that didn't have unit tests before? If so, make sure to add the correct subspec to ios/Podfile." 76 ); 77 } 78 } 79 throw new Error('Unit tests failed'); 80 } else { 81 console.log('✅ All unit tests passed for the following packages:', packagesTested.join(', ')); 82 } 83} 84 85export default (program: any) => { 86 program 87 .command('ios-native-unit-tests') 88 .option( 89 '--packages <string>', 90 '[optional] Comma-separated list of package names to run unit tests for. Defaults to all packages with unit tests.' 91 ) 92 .description('Runs iOS native unit tests for each package that provides them.') 93 .asyncAction(iosNativeUnitTests); 94}; 95