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