Skip to content

Commit

Permalink
feat: remove idevicesyslog dependency for real devices(appium#1000)
Browse files Browse the repository at this point in the history
  • Loading branch information
umutuzgur committed Jul 9, 2019
1 parent 5243236 commit dcda8db
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 36 deletions.
23 changes: 15 additions & 8 deletions lib/commands/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import _ from 'lodash';
import { DEFAULT_WS_PATHNAME_PREFIX } from 'appium-base-driver';
import { iosCommands } from 'appium-ios-driver';
import { IOSCrashLog } from '../device-log/ios-crash-log';
import { IOSLog } from '../device-log/ios-log';
import { IOSSimulatorLog } from '../device-log/ios-simulator-log';
import { IOSDeviceLog } from '../device-log/ios-device-log';
import log from '../logger';
import WebSocket from 'ws';
import SafariConsoleLog from '../device-log/safari-console-log';
Expand Down Expand Up @@ -36,13 +37,19 @@ extensions.startLogCapture = async function startLogCapture () {
sim: this.opts.device,
udid: this.isRealDevice() ? this.opts.udid : undefined,
});
this.logs.syslog = new IOSLog({
sim: this.opts.device,
udid: this.isRealDevice() ? this.opts.udid : undefined,
showLogs: this.opts.showIOSLog,
realDeviceLogger: this.opts.realDeviceLogger,
xcodeVersion: this.xcodeVersion,
});

if (this.isRealDevice()) {
this.logs.syslog = new IOSDeviceLog({
udid: this.opts.udid,
showLogs: this.opts.showIOSLog,
});
} else {
this.logs.syslog = new IOSSimulatorLog({
sim: this.opts.device,
showLogs: this.opts.showIOSLog,
xcodeVersion: this.xcodeVersion,
});
}
this.logs.safariConsole = new SafariConsoleLog(!!this.opts.showSafariConsoleLog);
this.logs.safariNetwork = new SafariNetworkLog(!!this.opts.showSafariNetworkLog);
}
Expand Down
45 changes: 45 additions & 0 deletions lib/device-log/ios-device-log.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { logger } from 'appium-support';
import { IOSLog } from './ios-log';
import { services } from 'appium-ios-device';

const log = logger.getLogger('IOSDeviceLog');

class IOSDeviceLog extends IOSLog {

constructor (opts) {
super();
this.udid = opts.udid;
this.showLogs = !!opts.showLogs;
this.service = null;
}

async startCapture () {
if (this.service) {
return;
}
this.service = await services.startSyslogService(this.udid);
this.service.start(this.onLog.bind(this));
}

onLog (logLine) {
this.broadcast(logLine);
if (this.showLogs) {
log.info(logLine);
}
}

get isCapturing () {
return !!this.service;
}

stopCapture () {
if (!this.service) {
return;
}
this.service.close();
this.service = null;
}
}

export { IOSDeviceLog };
export default IOSDeviceLog;
75 changes: 49 additions & 26 deletions lib/device-log/ios-log.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,59 @@
import { IOSLog as IOSDriverIOSLog } from 'appium-ios-driver';
import _ from 'lodash';
import log from '../logger';
import { SubProcess, exec } from 'teen_process';
import { EventEmitter } from 'events';

// We keep only the most recent log entries to avoid out of memory error
const MAX_LOG_ENTRIES_COUNT = 10000;

class IOSLog extends IOSDriverIOSLog {
async startCaptureSimulator () {
if (_.isUndefined(this.sim.udid)) {
throw new Error(`Log capture requires a sim udid`);
}
class IOSLog extends EventEmitter {

constructor () {
super();
this.logs = [];
this.logIdxSinceLastRequest = -1;
this.maxBufferSize = MAX_LOG_ENTRIES_COUNT;
}

async startCapture () { // eslint-disable-line require-await
throw new Error(`Sub-classes need to implement a 'startCapture' function`);
}

async stopCapture () { // eslint-disable-line require-await
throw new Error(`Sub-classes need to implement a 'stopCapture' function`);
}

if (!await this.sim.isRunning()) {
throw new Error(`iOS Simulator with udid ${this.sim.udid} is not running`);
get isCapturing () {
throw new Error(`Sub-classes need to implement a 'isCapturing' function`);
}

broadcast (logLine) {
const logObj = {
timestamp: Date.now(),
level: 'ALL',
message: logLine
};
this.logs.push(logObj);
this.emit('output', logObj);
if (this.logs.length > this.maxBufferSize) {
this.logs.shift();
if (this.logIdxSinceLastRequest > 0) {
--this.logIdxSinceLastRequest;
}
}
const tool = 'xcrun';
const args = ['simctl', 'spawn', this.sim.udid, 'log', 'stream', '--style', 'compact'];
log.debug(`Starting log capture for iOS Simulator with udid '${this.sim.udid}', ` +
`using '${tool} ${args.join(' ')}'`);
try {
// cleanup existing listeners if the previous session has not been terminated properly
await exec('pkill', ['-xf', [tool, ...args].join(' ')]);
} catch (ign) {}
try {
this.proc = new SubProcess(tool, args);
await this.finishStartingLogCapture();
} catch (e) {
throw new Error(`Simulator log capture failed. Original error: ${e.message}`);
}

getLogs () {
if (this.logs.length && this.logIdxSinceLastRequest < this.logs.length) {
let result = this.logs;
if (this.logIdxSinceLastRequest > 0) {
result = result.slice(this.logIdxSinceLastRequest);
}
this.logIdxSinceLastRequest = this.logs.length;
return result;
}
return [];
}

get isCapturing () {
return !!(this.proc && this.proc.isRunning);
getAllLogs () {
return this.logs;
}
}

Expand Down
120 changes: 120 additions & 0 deletions lib/device-log/ios-simulator-log.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import _ from 'lodash';
import { IOSLog } from './ios-log';
import { logger } from 'appium-support';
import { SubProcess, exec } from 'teen_process';

const log = logger.getLogger('IOSSimulatorLog');

const START_TIMEOUT = 10000;

class IOSSimulatorLog extends IOSLog {
constructor (opts) {
super();
this.sim = opts.sim;
this.showLogs = !!opts.showLogs;
this.xcodeVersion = opts.xcodeVersion;
this.proc = null;
}

async startCapture () {
if (_.isUndefined(this.sim.udid)) {
throw new Error(`Log capture requires a sim udid`);
}

if (!await this.sim.isRunning()) {
throw new Error(`iOS Simulator with udid ${this.sim.udid} is not running`);
}
const tool = 'xcrun';
const args = ['simctl', 'spawn', this.sim.udid, 'log', 'stream', '--style', 'compact'];
log.debug(`Starting log capture for iOS Simulator with udid '${this.sim.udid}', ` +
`using '${tool} ${args.join(' ')}'`);
try {
// cleanup existing listeners if the previous session has not been terminated properly
await exec('pkill', ['-xf', [tool, ...args].join(' ')]);
} catch (ign) {}
try {
this.proc = new SubProcess(tool, args);
await this.finishStartingLogCapture();
} catch (e) {
throw new Error(`Simulator log capture failed. Original error: ${e.message}`);
}
}

async finishStartingLogCapture () {
if (!this.proc) {
log.errorAndThrow('Could not capture simulator log');
}
let firstLine = true;
let logRow = '';
this.proc.on('output', (stdout, stderr) => {
if (stdout) {
if (firstLine) {
if (stdout.endsWith('\n')) {
// don't store the first line of the log because it came before the sim was launched
firstLine = false;
}
} else {
logRow += stdout;
if (stdout.endsWith('\n')) {
this.onOutput(logRow);
logRow = '';
}
}
}
if (stderr) {
this.onOutput(logRow, 'STDERR');
}
});

let sd = (stdout, stderr) => {
if (/execvp\(\)/.test(stderr)) {
throw new Error('iOS log capture process failed to start');
}
return stdout || stderr;
};
await this.proc.start(sd, START_TIMEOUT);
}

async stopCapture () {
if (!this.proc) {
return;
}
await this.killLogSubProcess();
this.proc = null;
}

async killLogSubProcess () {
if (!this.proc.isRunning) {
return;
}
log.debug('Stopping iOS log capture');
try {
await this.proc.stop('SIGTERM', 1000);
} catch (e) {
if (!this.proc.isRunning) {
return;
}
logger.warn('Cannot stop log capture process. Sending SIGKILL...');
await this.proc.stop('SIGKILL');
}
}

get isCapturing () {
return this.proc && this.proc.isRunning;
}

onOutput (logRow, prefix = '') {
const logs = _.cloneDeep(logRow.split('\n'));
for (const logLine of logs) {
if (!logLine) continue; // eslint-disable-line curly
this.broadcast(logLine);
if (this.showLogs) {
const space = prefix.length > 0 ? ' ' : '';
log.info(`[IOS_SYSLOG_ROW${space}${prefix}] ${logLine}`);
}
}
}
}

export { IOSSimulatorLog };
export default IOSSimulatorLog;
4 changes: 2 additions & 2 deletions test/unit/log-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import _ from 'lodash';
import sinon from 'sinon';
import * as Logs from '../../lib/device-log/ios-log';
import * as Logs from '../../lib/device-log/ios-simulator-log';
import * as CrashLogs from '../../lib/device-log/ios-crash-log';
import log from '../../lib/commands/log';

Expand All @@ -21,7 +21,7 @@ describe('XCUITestDriver - startLogCapture', function () {
crashLogStub = sinon.stub(CrashLogs, 'IOSCrashLog').callsFake(function () {
this.startCapture = _.noop;
});
iosLogStub = sinon.stub(Logs, 'IOSLog').callsFake(function () {
iosLogStub = sinon.stub(Logs, 'IOSSimulatorLog').callsFake(function () {
this.startCapture = spy.startCapture;
});
});
Expand Down

0 comments on commit dcda8db

Please sign in to comment.