Skip to content

Commit

Permalink
chore: Make xcodebuild error message more helpful (#816)
Browse files Browse the repository at this point in the history
  • Loading branch information
mykola-mokhnach authored Nov 24, 2023
1 parent 8c76380 commit 2d7fc03
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 53 deletions.
9 changes: 8 additions & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,12 +265,19 @@ function getXctestrunFileName (deviceInfo, version) {
: `WebDriverAgentRunner_iphone${deviceInfo.isRealDevice ? `os${version}-arm64` : `simulator${version}-x86_64`}.xctestrun`;
}

/**
* Ensures the process is killed after the timeout
*
* @param {string} name
* @param {import('teen_process').SubProcess} proc
* @returns {Promise<void>}
*/
async function killProcess (name, proc) {
if (!proc || !proc.isRunning) {
return;
}

log.info(`Shutting down '${name}' process (pid '${proc.proc.pid}')`);
log.info(`Shutting down '${name}' process (pid '${proc.proc?.pid}')`);

log.info(`Sending 'SIGTERM'...`);
try {
Expand Down
72 changes: 20 additions & 52 deletions lib/xcodebuild.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { retryInterval } from 'asyncbox';
import { SubProcess, exec } from 'teen_process';
import { fs, logger, timing } from '@appium/support';
import { logger, timing } from '@appium/support';
import defaultLogger from './logger';
import B from 'bluebird';
import {
setRealDeviceSecurity, setXctestrunFile,
updateProjectFile, resetProjectFile, killProcess,
getWDAUpgradeTimestamp, isTvOS } from './utils';
getWDAUpgradeTimestamp, isTvOS
} from './utils';
import _ from 'lodash';
import path from 'path';
import { EOL } from 'os';
import { WDA_RUNNER_BUNDLE_ID } from './constants';
import readline from 'node:readline';


const DEFAULT_SIGNING_ID = 'iPhone Developer';
Expand All @@ -34,6 +34,9 @@ const xcodeLog = logger.getLogger('Xcode');


class XcodeBuild {
/** @type {SubProcess} */
xcodebuild;

/**
* @param {string} xcodeVersion
* @param {any} device
Expand Down Expand Up @@ -85,10 +88,6 @@ class XcodeBuild {
this.resultBundlePath = args.resultBundlePath;
this.resultBundleVersion = args.resultBundleVersion;

/** @type {string} */
this._logLocation = '';
/** @type {string[]} */
this._wdaErrorMessage = [];
this._didBuildFail = false;
this._didProcessExit = false;
}
Expand Down Expand Up @@ -168,8 +167,6 @@ class XcodeBuild {
this.usePrebuiltWDA = true;
await this.start(true);

this.xcodebuild = null;

if (this.prebuildDelay > 0) {
// pause a moment
await B.delay(this.prebuildDelay);
Expand Down Expand Up @@ -286,7 +283,6 @@ class XcodeBuild {
if (upgradeTimestamp) {
env.UPGRADE_TIMESTAMP = upgradeTimestamp;
}
this._logLocation = '';
this._didBuildFail = false;
const xcodebuild = new SubProcess(cmd, args, {
cwd: this.bootstrapPath,
Expand All @@ -302,14 +298,6 @@ class XcodeBuild {
this.log.debug(`${logMsg}. To change this, use 'showXcodeLog' desired capability`);
xcodebuild.on('output', (stdout, stderr) => {
let out = stdout || stderr;
// we want to pull out the log file that is created, and highlight it
// for diagnostic purposes
if (out.includes('Writing diagnostic log for test session to')) {
// pull out the first line that begins with the path separator
// which *should* be the line indicating the log file generated
this._logLocation = _.first(_.remove(out.trim().split('\n'), (v) => v.startsWith(path.sep))) ?? '';
xcodeLog.debug(`Log file location for xcodebuild test: ${this._logLocation || 'unknown'}`);
}

// if we have an error we want to output the logs
// otherwise the failure is inscrutible
Expand All @@ -326,9 +314,6 @@ class XcodeBuild {
if (logXcodeOutput && !ignoreError) {
for (const line of out.split(EOL)) {
xcodeLog.error(line);
if (line) {
this._wdaErrorMessage.push(line);
}
}
}
});
Expand All @@ -338,43 +323,27 @@ class XcodeBuild {

async start (buildOnly = false) {
this.xcodebuild = await this.createSubProcess(buildOnly);
// Store xcodebuild message
this._wdaErrorMessage = [];

// wrap the start procedure in a promise so that we can catch, and report,
// any startup errors that are thrown as events
return await new B((resolve, reject) => {
// @ts-ignore xcodebuild must be present here
this.xcodebuild.once('exit', async (code, signal) => {
this.xcodebuild.once('exit', (code, signal) => {
xcodeLog.error(`xcodebuild exited with code '${code}' and signal '${signal}'`);
this.xcodebuild?.removeAllListeners();
const xcodeErrorMessage = this._wdaErrorMessage.join('\n');
this._wdaErrorMessage = [];
// print out the xcodebuild file if users have asked for it
if (this.showXcodeLog && this._logLocation) {
xcodeLog.error(`Contents of xcodebuild log file '${this._logLocation}':`);
try {
const logFile = readline.createInterface({
input: fs.createReadStream(this._logLocation),
terminal: false
});
logFile.on('line', (line) => {
xcodeLog.error(line);
});
await new B((_resolve) => {
logFile.once('close', () => {
logFile.removeAllListeners();
_resolve();
});
});
} catch (err) {
xcodeLog.error(`Unable to access xcodebuild log file: '${err.message}'`);
}
}
this.xcodebuild.removeAllListeners();
this.didProcessExit = true;
if (this._didBuildFail || (!signal && code !== 0)) {
return reject(new Error(`xcodebuild failed with code ${code}\n` +
`xcodebuild error message:\n${xcodeErrorMessage}`));
let errorMessage = `xcodebuild failed with code ${code}.` +
` This usually indicates an issue with the local Xcode setup or WebDriverAgent` +
` project configuration or the driver-to-platform version mismatch.`;
if (!this.showXcodeLog) {
errorMessage += ` Consider setting 'showXcodeLog' capability to true in` +
` order to check the Appium server log for build-related error messages.`;
} else if (this.realDevice) {
errorMessage += ` Consider checking the WebDriverAgent configuration guide` +
` for real iOS devices at` +
` https://github.com/appium/appium-xcuitest-driver/blob/master/docs/real-device-config.md.`;
}
return reject(new Error(errorMessage));
}
// in the case of just building, the process will exit and that is our finish
if (buildOnly) {
Expand All @@ -385,7 +354,6 @@ class XcodeBuild {
return (async () => {
try {
const timer = new timing.Timer().start();
// @ts-ignore this.xcodebuild must be defined
await this.xcodebuild.start(true);
if (!buildOnly) {
let status = await this.waitForStart(timer);
Expand Down

0 comments on commit 2d7fc03

Please sign in to comment.