Support for iOS logs for devices and simulators

Fix after comments by @jlipps

Fix require section
This commit is contained in:
eComRobotics Team 2013-11-04 20:19:31 +04:00
Родитель 1ddc5f4e19
Коммит caf7d789e9
10 изменённых файлов: 247 добавлений и 34 удалений

3
.gitmodules поставляемый
Просмотреть файл

@ -25,3 +25,6 @@
[submodule "submodules/SafariLauncher"]
path = submodules/SafariLauncher
url = https://github.com/snevesbarros/SafariLauncher.git
[submodule "submodules/libimobiledevice-macosx"]
path = submodules/libimobiledevice-macosx
url = https://github.com/benvium/libimobiledevice-macosx.git

Просмотреть файл

@ -71,6 +71,7 @@ module.exports = function(grunt) {
reporter: 'spec'
}
}
, maxBuffer: 2000*1024
});
grunt.loadNpmTasks('grunt-mocha-test');

Просмотреть файл

@ -15,6 +15,11 @@ var errors = require('../../server/errors.js')
var androidController = {};
var logTypesSupported = {
'logcat' : 'Logs for Android applications on real device and emulators via ADB'
};
androidController.keyevent = function(keycode, metastate, cb) {
this.proxy(["pressKeyCode", {keycode: keycode, metastate: metastate}], cb);
};
@ -661,8 +666,43 @@ androidController.installApp = function(appPackage, cb) {
deviceCommon.installApp(installationCommand, this.udid, appPackage, cb);
};
androidController.getLog = deviceCommon.getLog;
androidController.getLogTypes = deviceCommon.getLogTypes;
androidController.getLog = function(logType, cb) {
// Check if passed logType is supported
if (!_.has(logTypesSupported, logType)) {
return cb(null, {
status: status.codes.UnknownError.code
, value: "Unsupported log type '" + logType + "', supported types : " + JSON.stringify(logTypesSupported)
});
}
var logs;
// Check that current logType and instance is compatible
if (logType == 'logcat') {
try {
logs = this.adb.getLogcatLogs();
} catch (e) {
return cb(e);
}
}
// If logs captured sucessfully send response with data, else send error
if (logs) {
return cb(null, {
status: status.codes.Success.code
, value: logs
});
} else {
return cb(null, {
status: status.codes.UnknownError.code
, value: "Incompatible logType for this device"
});
}
};
androidController.getLogTypes = function(cb) {
return cb(null, {
status: status.codes.Success.code
, value: logTypesSupported
});
};
androidController.unpackApp = function(req, cb) {
deviceCommon.unpackApp(req, '.apk', cb);

Просмотреть файл

@ -10,7 +10,6 @@ var errors = require('../server/errors.js')
var UnknownError = errors.UnknownError
, ProtocolError = errors.ProtocolError;
exports.respond = function(response, cb) {
if (typeof response === 'undefined') {
cb(null, '');
@ -233,28 +232,3 @@ exports.convertElementForAtoms = function(args, cb) {
cb(null, args);
};
exports.getLog = function(logType, cb) {
// go ahead and respond with 'logcat' type no matter what they send in
if (logType !== 'logcat') {
logger.warn("Trying to get log type of " + logType + ", giving logcat " +
"instead");
}
var logs;
try {
logs = this.adb.getLogcatLogs();
} catch (e) {
return cb(e);
}
cb(null, {
status: status.codes.Success.code
, value: logs
});
};
exports.getLogTypes = function(cb) {
return cb(null, {
status: status.codes.Success.code
, value: ['logcat']
});
};

Просмотреть файл

@ -19,6 +19,10 @@ var uuid = require('uuid-js')
var iOSController = {};
var logTypesSupported = {
'syslog' : 'Logs for iOS applications on real device and simulators'
};
iOSController.findUIElementOrElements = function(strategy, selector, ctx, many, cb) {
selector = escapeSpecialChars(selector, "'");
if (typeof ctx === "undefined" || !ctx) {
@ -1427,12 +1431,39 @@ iOSController.getCurrentActivity= function(cb) {
cb(new NotYetImplementedError(), null);
};
iOSController.getLogs = function(logType, cb) {
cb(new NotYetImplementedError(), null);
iOSController.getLogTypes = function(cb) {
return cb(null, {
status: status.codes.Success.code
, value: logTypesSupported
});
};
iOSController.getLogTypes = function(cb) {
cb(new NotYetImplementedError(), null);
iOSController.getLog = function(logType, cb) {
// Check if passed logType is supported
if (!_.has(logTypesSupported, logType)) {
return cb(null, {
status: status.codes.UnknownError.code
, value: "Unsupported log type '" + logType + "' for this device, supported types : " + JSON.stringify(logTypesSupported)
});
}
var logs;
try {
logs = this.logs.getLogs(logType);
} catch (e) {
return cb(e);
}
// If logs captured successfully send response with data, else send error
if (logs) {
return cb(null, {
status: status.codes.Success.code
, value: logs
});
} else {
return cb(null, {
status: status.codes.UnknownError.code
, value: "Unknown error while getting logs"
});
}
};
module.exports = iOSController;

137
lib/devices/ios/ios-log.js Normal file
Просмотреть файл

@ -0,0 +1,137 @@
"use strict";
var spawn = require('win-spawn')
, through = require('through')
, _ = require('underscore')
, logger = require('../../server/logger.js').get('appium');
// Date-Utils: Polyfills for the Date object
require('date-utils');
var IosLog = function(opts) {
this.udid = opts.udid;
this.xcodeVersion = opts.xcodeVersion;
this.debugMode = opts.debug;
this.debugTrace = opts.debugTrace;
this.proc = null;
this.onIosLogStart = null;
this.iosLogStarted = false;
this.iosLogStartTime = null;
this.calledBack = false;
this.logs = [];
this.logRow = "";
this.logsSinceLastRequest = [];
};
IosLog.prototype.debug = function(msg) {
if (this.debugMode) {
logger.debug(("[IOS_SYSLOG_CAPTURE] " + msg).grey);
}
};
IosLog.prototype.startCapture = function(cb) {
this.onIosLogStart = cb;
// Select cmd for log capture
if (this.udid) {
var spawnEnv = _.clone(process.env);
spawnEnv.PATH = process.env.PATH + ":" + process.cwd() + "/build/libimobiledevice-macosx/";
spawnEnv.DYLD_LIBRARY_PATH = process.cwd() + "/build/libimobiledevice-macosx/:" + process.env.DYLD_LIBRARY_PATH
this.debug("Starting iOS device log capture via idevicesyslog");
this.proc = spawn("idevicesyslog", ["-u", this.udid], {env: spawnEnv});
}
else {
if (parseInt(this.xcodeVersion.split(".")[0], 10) >= 5) {
this.debug("Starting iOS 7.* simulator log capture");
this.proc = spawn("tail", ["-f", "-n0", process.env.HOME + "/Library/Logs/iOS Simulator/7.0/system.log"]);
}
else {
this.debug("Starting iOS 6.* simulator log capture");
this.proc = spawn("tail", ["-f", "-n0", "/var/log/system.log"]);
}
}
this.proc.stdout.setEncoding('utf8');
this.proc.stderr.setEncoding('utf8');
this.proc.on('error', function(err) {
logger.error("iOS log capture failed: " + err.message);
if (!this.calledBack) {
this.calledBack = true;
cb(err);
}
}.bind(this));
this.proc.stdout.pipe(through(this.onStdout.bind(this)));
this.proc.stderr.pipe(through(this.onStderr.bind(this)));
};
IosLog.prototype.stopCapture = function() {
this.debug("Stopping iOS log capture");
this.proc.kill();
this.proc = null;
};
IosLog.prototype.onStdout = function(data) {
this.logRow += data;
if (data.substr(-1,1) == "\n") {
this.onOutput(data, "");
this.logRow = "";
}
};
IosLog.prototype.onStderr = function(data) {
if (/execvp\(\)/.test(data)) {
logger.error("iOS log capture process failed to start");
if (!this.calledBack) {
this.calledBack = true;
this.onIosLogStart(new Error("iOS log capture process failed to start"));
return;
}
}
this.onOutput(data, ' STDERR');
};
IosLog.prototype.onOutput = function(data, prefix) {
if (!this.iosLogStarted) {
this.iosLogStarted = true;
this.iosLogStartTime = new Date();
if (!this.calledBack) {
this.calledBack = true;
this.onIosLogStart();
}
}
var logs = this.logRow.split("\n");
_.each(logs, function(log) {
log = log.trim();
if (log) {
// Filter old log rows
var logRowParts = log.split(" ");
var logRowDate = new Date(
Date.parse(this.iosLogStartTime.getFullYear() + " " + logRowParts[0] + " " + logRowParts[2] + " " + logRowParts[3])
);
if (logRowDate.isAfter(this.iosLogStartTime)) {
var logObj = {
timestamp: Date.now()
, level: 'ALL'
, message: log
};
this.logs.push(logObj);
this.logsSinceLastRequest.push(logObj);
if (this.debugMode && this.debugTrace) {
logger.debug('[IOS_SYSLOG_ROW ' + prefix + '] ' + log);
}
}
}
}.bind(this));
};
IosLog.prototype.getLogs = function() {
var ret = this.logsSinceLastRequest;
this.logsSinceLastRequest = [];
return ret;
};
IosLog.prototype.getAllLogs = function() {
return this.logs;
};
module.exports = function(opts) {
return new IosLog(opts);
};

Просмотреть файл

@ -14,6 +14,7 @@ var path = require('path')
, helpers = require('../../helpers.js')
, errors = require('../../server/errors.js')
, deviceCommon = require('../common.js')
, iOSLog = require('./ios-log.js')
, status = require("../../server/status.js")
, IDevice = require('node-idevice')
, async = require('async')
@ -38,6 +39,7 @@ var IOS = function(args) {
this.automationTraceTemplatePath = args.automationTraceTemplatePath;
this.removeTraceDir = args.removeTraceDir;
this.deviceType = args.deviceType;
this.logs = null;
this.startingOrientation = args.startingOrientation || "PORTRAIT";
this.curOrientation = this.startingOrientation;
this.instruments = null;
@ -171,6 +173,7 @@ IOS.prototype.start = function(cb, onDie) {
this.detectUdid.bind(this),
this.parseLocalizableStrings.bind(this),
this.setDeviceType.bind(this),
this.startLogCapture.bind(this),
this.installToRealDevice.bind(this),
this.startInstruments.bind(this),
this.onInstrumentsLaunch.bind(this),
@ -301,6 +304,10 @@ IOS.prototype.onInstrumentsExit = function(code, traceDir, launchCb) {
}
}.bind(this);
if (this.logs !== null) {
this.logs.stopCapture();
}
async.series([removeTraceDir, cleanup], function() {});
};
@ -705,6 +712,20 @@ IOS.prototype.unpackApp = function(req, cb) {
deviceCommon.unpackApp(req, '.app', cb);
};
IOS.prototype.startLogCapture = function(cb) {
if (this.logs !== null) {
cb(new Error("Trying to start iOS log capture but it's already started!"));
return;
}
this.logs = new iOSLog({
udid: this.udid
, xcodeVersion: this.xcodeVersion
, debug: false
, debugTrace: false
});
this.logs.startCapture(cb);
};
_.each(_.keys(iOSHybrid), function(method) {
IOS.prototype[method] = iOSHybrid[method];
});

Просмотреть файл

@ -69,7 +69,8 @@
"ws": "0.4.31",
"socket.io" : "~0.9.16",
"MD5" : "~1.1.0",
"through": "~2.3.4"
"through": "~2.3.4",
"date-utils": "~1.2.14"
},
"scripts": {
"test": "grunt travis"

Просмотреть файл

@ -179,7 +179,11 @@ reset_ios() {
echo "* Copying SafariLauncher for real devices to build"
run_cmd zip -r build/SafariLauncher/SafariLauncher submodules/SafariLauncher/build/Release-iphoneos/SafariLauncher.app
fi
echo "* Cloning/updating libimobiledevice-macosx"
run_cmd git submodule update --init submodules/libimobiledevice-macosx
echo "* Copying libimobiledevice-macosx to build"
run_cmd rm -rf build/libimobiledevice-macosx
run_cmd cp -r submodules/libimobiledevice-macosx build/libimobiledevice-macosx
}
get_apidemos() {

@ -0,0 +1 @@
Subproject commit 8507dd21f9f3198e988ad68b306613097721523c