1import JUnitReportBuilder from 'junit-report-builder'; 2import path from 'path'; 3 4import * as IOSSimulator from './IOSSimulator'; 5import * as Log from './Log'; 6 7const TEST_SUITE_BUNDLE_ID = 'io.expo.testsuite'; 8const TEST_SUITE_END_SENTINEL = '[TEST-SUITE-END]'; 9 10// Keep this type in sync with test-suite 11type TestSuiteResults = { 12 failed: number; 13 failures: string; 14}; 15 16export async function runTestSuiteOnIOSSimulatorAsync(simulatorId, archivePath, reportPath) { 17 Log.collapsed(`Running test-suite on the iOS simulator`); 18 if (!simulatorId) { 19 console.log(`Starting a new simulator`); 20 await IOSSimulator.startSimulatorAsync(); 21 simulatorId = 'booted'; 22 } 23 24 console.log(`Installing test-suite on the simulator`); 25 await IOSSimulator.installSimulatorAppAsync(simulatorId, path.resolve(archivePath)); 26 console.log(`Streaming logs from the simulator`); 27 const resultsPromise = _streamSimulatorLogsAsync(simulatorId); 28 console.log(`Launching the test-suite app and waiting for tests to complete`); 29 await IOSSimulator.launchSimulatorAppAsync(simulatorId, TEST_SUITE_BUNDLE_ID); 30 31 const results = await resultsPromise; 32 if (results.failed === 0) { 33 console.log(` All tests passed`); 34 } else { 35 console.error(` ${results.failed} ${results.failed === 1 ? 'test' : 'tests'} failed`); 36 } 37 38 if (reportPath) { 39 _writeJUnitReport(results, reportPath); 40 console.log(`Saved test results to ${reportPath}`); 41 } 42 43 return results; 44} 45 46function _streamSimulatorLogsAsync(simulatorId: string): Promise<TestSuiteResults> { 47 return new Promise((resolve, reject) => { 48 const logProcess = IOSSimulator.getSimulatorLogProcess( 49 simulatorId, 50 '(subsystem == "host.exp.Exponent") && (category == "test")' 51 ); 52 const logStream = new IOSSimulator.IOSLogStream(); 53 logProcess.stdout.pipe(logStream); 54 55 logStream.on('data', (entry) => { 56 // Show the log messages in the CI log 57 console.log(entry.eventMessage); 58 59 if (!entry.eventMessage.startsWith(TEST_SUITE_END_SENTINEL)) { 60 return; 61 } 62 63 try { 64 logStream.removeAllListeners('data'); 65 66 const resultsJson = entry.eventMessage.substring(TEST_SUITE_END_SENTINEL.length).trim(); 67 const results = JSON.parse(resultsJson); 68 resolve(results); 69 } catch (e) { 70 reject(e); 71 } finally { 72 console.log(`Terminating simulator log stream`); 73 logProcess.kill('SIGTERM'); 74 } 75 }); 76 }); 77} 78 79function _writeJUnitReport(results: TestSuiteResults, reportPath: string): void { 80 const builder = JUnitReportBuilder.newBuilder(); 81 // let suite = builder.testSuite().name('Test Suite'); 82 83 // TODO: parse the results 84 85 builder.writeTo(reportPath); 86} 87