1eeffdb10STomasz Sapetaimport spawnAsync from '@expo/spawn-async'; 2eeffdb10STomasz Sapetaimport chalk from 'chalk'; 34ff2cc49SŁukasz Kosmatyimport path from 'path'; 4eeffdb10STomasz Sapeta 5eeffdb10STomasz Sapetaimport * as Directories from '../Directories'; 6eeffdb10STomasz Sapetaimport * as Packages from '../Packages'; 7782a85d5SEric Samelsonimport { filterAsync } from '../Utils'; 8eeffdb10STomasz Sapeta 9eeffdb10STomasz Sapetaconst ANDROID_DIR = Directories.getAndroidDir(); 10eeffdb10STomasz Sapeta 114ff2cc49SŁukasz Kosmatyconst BARE_EXPO_DIR = path.join(Directories.getAppsDir(), 'bare-expo', 'android'); 124ff2cc49SŁukasz Kosmaty 13eeffdb10STomasz Sapetaconst excludedInTests = [ 14eeffdb10STomasz Sapeta 'expo-module-template', 15ab91dd02Saleqsio 'expo-module-template-local', 16eeffdb10STomasz Sapeta 'expo-notifications', 17eeffdb10STomasz Sapeta 'expo-in-app-purchases', 18eeffdb10STomasz Sapeta 'expo-splash-screen', 19e229380eSŁukasz Kosmaty 'expo-modules-test-core', 202acc3378SŁukasz Kosmaty 'expo-dev-client', 21eeffdb10STomasz Sapeta]; 22eeffdb10STomasz Sapeta 234ff2cc49SŁukasz Kosmatyconst packagesNeedToBeTestedUsingBareExpo = [ 244ff2cc49SŁukasz Kosmaty 'expo-dev-launcher', 254ff2cc49SŁukasz Kosmaty 'expo-dev-menu-interface', 26*61b15591SŁukasz Kosmaty 'expo-dev-menu', 27*61b15591SŁukasz Kosmaty 'expo-modules-core', 284ff2cc49SŁukasz Kosmaty]; 294ff2cc49SŁukasz Kosmaty 30eeffdb10STomasz Sapetatype TestType = 'local' | 'instrumented'; 31eeffdb10STomasz Sapeta 324ff2cc49SŁukasz Kosmatyfunction consoleErrorOutput(output: string, label: string, colorifyLine: (string) => string): void { 334ff2cc49SŁukasz Kosmaty const lines = output.trim().split(/\r\n?|\n/g); 344ff2cc49SŁukasz Kosmaty console.error(lines.map((line) => `${chalk.gray(label)} ${colorifyLine(line)}`).join('\n')); 354ff2cc49SŁukasz Kosmaty} 364ff2cc49SŁukasz Kosmaty 37782a85d5SEric Samelsonexport async function androidNativeUnitTests({ 38782a85d5SEric Samelson type, 39782a85d5SEric Samelson packages, 40782a85d5SEric Samelson}: { 41782a85d5SEric Samelson type: TestType; 42782a85d5SEric Samelson packages?: string; 43782a85d5SEric Samelson}) { 44eeffdb10STomasz Sapeta if (!type) { 45eeffdb10STomasz Sapeta throw new Error( 46eeffdb10STomasz Sapeta 'Must specify which type of unit test to run with `--type local` or `--type instrumented`.' 47eeffdb10STomasz Sapeta ); 48eeffdb10STomasz Sapeta } 49eeffdb10STomasz Sapeta if (type !== 'local' && type !== 'instrumented') { 50eeffdb10STomasz Sapeta throw new Error('Invalid type specified. Must use `--type local` or `--type instrumented`.'); 51eeffdb10STomasz Sapeta } 52eeffdb10STomasz Sapeta 53782a85d5SEric Samelson const allPackages = await Packages.getListOfPackagesAsync(); 54782a85d5SEric Samelson const packageNamesFilter = packages ? packages.split(',') : []; 55eeffdb10STomasz Sapeta 56782a85d5SEric Samelson const androidPackages = await filterAsync(allPackages, async (pkg) => { 57782a85d5SEric Samelson if (packageNamesFilter.length > 0 && !packageNamesFilter.includes(pkg.packageName)) { 58782a85d5SEric Samelson return false; 59782a85d5SEric Samelson } 60782a85d5SEric Samelson 61782a85d5SEric Samelson let includesTests; 62fc12ed2dSBartosz Kaszubowski if (pkg.isSupportedOnPlatform('android') && !excludedInTests.includes(pkg.packageSlug)) { 63eeffdb10STomasz Sapeta if (type === 'instrumented') { 64fc12ed2dSBartosz Kaszubowski includesTests = await pkg.hasNativeInstrumentationTestsAsync('android'); 65eeffdb10STomasz Sapeta } else { 66fc12ed2dSBartosz Kaszubowski includesTests = await pkg.hasNativeTestsAsync('android'); 67fc12ed2dSBartosz Kaszubowski } 68782a85d5SEric Samelson } 69782a85d5SEric Samelson 70782a85d5SEric Samelson if (!includesTests && packageNamesFilter.includes(pkg.packageName)) { 71782a85d5SEric Samelson throw new Error( 72782a85d5SEric Samelson `The package ${pkg.packageName} does not include Android ${type} unit tests.` 73eeffdb10STomasz Sapeta ); 74eeffdb10STomasz Sapeta } 75782a85d5SEric Samelson 76782a85d5SEric Samelson return includesTests; 77eeffdb10STomasz Sapeta }); 78eeffdb10STomasz Sapeta 79eeffdb10STomasz Sapeta console.log(chalk.green('Packages to test: ')); 80eeffdb10STomasz Sapeta androidPackages.forEach((pkg) => { 81eeffdb10STomasz Sapeta console.log(chalk.yellow(pkg.packageSlug)); 82eeffdb10STomasz Sapeta }); 83eeffdb10STomasz Sapeta 844ff2cc49SŁukasz Kosmaty const partition = <T>(arr: T[], condition: (T) => boolean) => { 854ff2cc49SŁukasz Kosmaty const trues = arr.filter((el) => condition(el)); 864ff2cc49SŁukasz Kosmaty const falses = arr.filter((el) => !condition(el)); 874ff2cc49SŁukasz Kosmaty return [trues, falses]; 884ff2cc49SŁukasz Kosmaty }; 894ff2cc49SŁukasz Kosmaty 90a272999eSBartosz Kaszubowski const [androidPackagesTestedUsingBareProject, androidPackagesTestedUsingExpoProject] = partition( 91a272999eSBartosz Kaszubowski androidPackages, 92a272999eSBartosz Kaszubowski (element) => packagesNeedToBeTestedUsingBareExpo.includes(element.packageName) 934ff2cc49SŁukasz Kosmaty ); 944ff2cc49SŁukasz Kosmaty 9575be7bc1SKudo Chien if (type === 'instrumented') { 96e0f520f5SKudo Chien const testCommand = 'connectedAndroidTest'; 9775be7bc1SKudo Chien const uninstallTestCommand = 'uninstallDebugAndroidTest'; 98e0f520f5SKudo Chien 99e0f520f5SKudo Chien // TODO: remove this once avd cache saved to storage 10075be7bc1SKudo Chien await runGradlew(androidPackagesTestedUsingExpoProject, uninstallTestCommand, ANDROID_DIR); 10175be7bc1SKudo Chien await runGradlew(androidPackagesTestedUsingBareProject, uninstallTestCommand, BARE_EXPO_DIR); 10275be7bc1SKudo Chien 103e0f520f5SKudo Chien // We should build and test expo-modules-core first 104e0f520f5SKudo Chien // that to make the `isExpoModulesCoreTests` in _expo-modules-core/android/build.gradle_ working. 105e0f520f5SKudo Chien // Otherwise, the `./gradlew :expo-modules-core:connectedAndroidTest :expo-eas-client:connectedAndroidTest` 106e0f520f5SKudo Chien // will have duplicated fbjni.so when building expo-eas-client. 107e0f520f5SKudo Chien const isExpoModulesCore = (pkg: Packages.Package) => pkg.packageName === 'expo-modules-core'; 108e0f520f5SKudo Chien const isNotExpoModulesCore = (pkg: Packages.Package) => pkg.packageName !== 'expo-modules-core'; 109*61b15591SŁukasz Kosmaty await runGradlew(androidPackages.filter(isExpoModulesCore), testCommand, BARE_EXPO_DIR); 110e0f520f5SKudo Chien 111e0f520f5SKudo Chien await runGradlew( 112e0f520f5SKudo Chien androidPackagesTestedUsingExpoProject.filter(isNotExpoModulesCore), 113e0f520f5SKudo Chien testCommand, 114e0f520f5SKudo Chien ANDROID_DIR 115e0f520f5SKudo Chien ); 116e0f520f5SKudo Chien await runGradlew( 117e0f520f5SKudo Chien androidPackagesTestedUsingBareProject.filter(isNotExpoModulesCore), 118e0f520f5SKudo Chien testCommand, 119e0f520f5SKudo Chien BARE_EXPO_DIR 120e0f520f5SKudo Chien ); 121e0f520f5SKudo Chien 122e0f520f5SKudo Chien // Cleanup installed test app 123e0f520f5SKudo Chien await runGradlew(androidPackagesTestedUsingExpoProject, uninstallTestCommand, ANDROID_DIR); 124e0f520f5SKudo Chien await runGradlew(androidPackagesTestedUsingBareProject, uninstallTestCommand, BARE_EXPO_DIR); 125e0f520f5SKudo Chien } else { 126e0f520f5SKudo Chien const testCommand = 'testDebugUnitTest'; 1274ff2cc49SŁukasz Kosmaty await runGradlew(androidPackagesTestedUsingExpoProject, testCommand, ANDROID_DIR); 1284ff2cc49SŁukasz Kosmaty await runGradlew(androidPackagesTestedUsingBareProject, testCommand, BARE_EXPO_DIR); 129e0f520f5SKudo Chien } 130e0f520f5SKudo Chien 1314ff2cc49SŁukasz Kosmaty console.log(chalk.green('Finished android unit tests successfully.')); 1324ff2cc49SŁukasz Kosmaty} 1334ff2cc49SŁukasz Kosmaty 1344ff2cc49SŁukasz Kosmatyasync function runGradlew(packages: Packages.Package[], testCommand: string, cwd: string) { 1354ff2cc49SŁukasz Kosmaty if (!packages.length) { 1364ff2cc49SŁukasz Kosmaty return; 1374ff2cc49SŁukasz Kosmaty } 1384ff2cc49SŁukasz Kosmaty 139eeffdb10STomasz Sapeta try { 140eeffdb10STomasz Sapeta await spawnAsync( 141eeffdb10STomasz Sapeta './gradlew', 1424ff2cc49SŁukasz Kosmaty packages.map((pkg) => `:${pkg.packageSlug}:${testCommand}`), 143eeffdb10STomasz Sapeta { 1444ff2cc49SŁukasz Kosmaty cwd, 145eeffdb10STomasz Sapeta stdio: 'inherit', 146eeffdb10STomasz Sapeta env: { ...process.env }, 147eeffdb10STomasz Sapeta } 148eeffdb10STomasz Sapeta ); 149eeffdb10STomasz Sapeta } catch (error) { 150eeffdb10STomasz Sapeta console.error('Failed while executing android unit tests'); 151eeffdb10STomasz Sapeta consoleErrorOutput(error.stdout, 'stdout >', chalk.reset); 152eeffdb10STomasz Sapeta consoleErrorOutput(error.stderr, 'stderr >', chalk.red); 153eeffdb10STomasz Sapeta throw error; 154eeffdb10STomasz Sapeta } 155eeffdb10STomasz Sapeta} 156eeffdb10STomasz Sapeta 157eeffdb10STomasz Sapetaexport default (program: any) => { 158eeffdb10STomasz Sapeta program 159eeffdb10STomasz Sapeta .command('android-native-unit-tests') 160eeffdb10STomasz Sapeta .option('-t, --type <string>', 'Type of unit test to run: local or instrumented') 161782a85d5SEric Samelson .option( 162782a85d5SEric Samelson '--packages <string>', 163782a85d5SEric Samelson '[optional] Comma-separated list of package names to run unit tests for. Defaults to all packages with unit tests.' 164782a85d5SEric Samelson ) 165eeffdb10STomasz Sapeta .description('Runs Android native unit tests for each package that provides them.') 166eeffdb10STomasz Sapeta .asyncAction(androidNativeUnitTests); 167eeffdb10STomasz Sapeta}; 168