xref: /expo/tools/src/IOSSimulatorTestSuite.ts (revision a272999e)
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