appium-ios-simulator
Version:
iOS Simulator interface for Appium.
642 lines (496 loc) • 39.3 kB
JavaScript
;
var _regeneratorRuntime = require('babel-runtime/regenerator')['default'];
var _getIterator = require('babel-runtime/core-js/get-iterator')['default'];
var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
Object.defineProperty(exports, '__esModule', {
value: true
});
var _logger = require('./logger');
var _logger2 = _interopRequireDefault(_logger);
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _teen_process = require('teen_process');
var _asyncbox = require('asyncbox');
var _appiumXcode = require('appium-xcode');
var _nodeSimctl = require('node-simctl');
var _appiumSupport = require('appium-support');
var _certificate = require('./certificate');
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
var _simulatorXcode6 = require('./simulator-xcode-6');
var _simulatorXcode62 = _interopRequireDefault(_simulatorXcode6);
var _fkill = require('fkill');
var _fkill2 = _interopRequireDefault(_fkill);
var DEFAULT_SIM_SHUTDOWN_TIMEOUT = 30000;
// pgrep/pkill exit codes:
// 0 One or more processes were matched.
// 1 No processes were matched.
// 2 Invalid options were specified on the command line.
// 3 An internal error occurred.
function pkill(appName) {
var forceKill = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
var args;
return _regeneratorRuntime.async(function pkill$(context$1$0) {
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
args = forceKill ? ['-9'] : [];
args.push('-x', appName);
context$1$0.prev = 2;
context$1$0.next = 5;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('pkill', args));
case 5:
return context$1$0.abrupt('return', 0);
case 8:
context$1$0.prev = 8;
context$1$0.t0 = context$1$0['catch'](2);
if (_lodash2['default'].isUndefined(context$1$0.t0.code)) {
context$1$0.next = 12;
break;
}
throw new Error('Cannot forcefully terminate ' + appName + '. pkill error code: ' + context$1$0.t0.code);
case 12:
_logger2['default'].error('Received unexpected error while trying to kill ' + appName + ': ' + context$1$0.t0.message);
throw context$1$0.t0;
case 14:
case 'end':
return context$1$0.stop();
}
}, null, this, [[2, 8]]);
}
function killAllSimulators() {
var timeout = arguments.length <= 0 || arguments[0] === undefined ? DEFAULT_SIM_SHUTDOWN_TIMEOUT : arguments[0];
var xcodeVersion, appName, pids, _ref, stdout, remainingDevices, allSimsAreDown, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, device;
return _regeneratorRuntime.async(function killAllSimulators$(context$1$0) {
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
allSimsAreDown = function allSimsAreDown() {
var devices;
return _regeneratorRuntime.async(function allSimsAreDown$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
remainingDevices = [];
context$2$0.next = 3;
return _regeneratorRuntime.awrap((0, _nodeSimctl.getDevices)());
case 3:
devices = context$2$0.sent;
devices = _lodash2['default'].flatten(_lodash2['default'].values(devices));
return context$2$0.abrupt('return', _lodash2['default'].every(devices, function (sim) {
var state = sim.state.toLowerCase();
var done = state === 'shutdown' || state === 'unavailable' || state === 'disconnected';
if (!done) {
remainingDevices.push(sim.name + ' (' + sim.sdk + ', udid: ' + sim.udid + ') is still in state \'' + state + '\'');
}
return done;
}));
case 6:
case 'end':
return context$2$0.stop();
}
}, null, this);
};
_logger2['default'].debug('Killing all iOS Simulators');
context$1$0.next = 4;
return _regeneratorRuntime.awrap((0, _appiumXcode.getVersion)(true));
case 4:
xcodeVersion = context$1$0.sent;
appName = xcodeVersion.major >= 7 ? 'Simulator' : 'iOS Simulator';
// later versions are slower to close
timeout = timeout * (xcodeVersion.major === 8 ? 2 : 1);
pids = undefined;
context$1$0.prev = 8;
context$1$0.next = 11;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('pgrep', ['-x', appName]));
case 11:
_ref = context$1$0.sent;
stdout = _ref.stdout;
pids = stdout.trim().split('\n').map(function (pid) {
return parseInt(pid, 10);
});
context$1$0.next = 22;
break;
case 16:
context$1$0.prev = 16;
context$1$0.t0 = context$1$0['catch'](8);
if (!(context$1$0.t0.code === 1)) {
context$1$0.next = 21;
break;
}
_logger2['default'].debug(appName + ' is not running. Continuing...');
return context$1$0.abrupt('return');
case 21:
_logger2['default'].warn('pgrep error ' + context$1$0.t0.code + ' while detecting whether ' + appName + ' is running. Trying to kill anyway.');
case 22:
context$1$0.next = 24;
return _regeneratorRuntime.awrap((function performKill() {
var pids = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0];
return _regeneratorRuntime.async(function performKill$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.prev = 0;
context$2$0.next = 3;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('xcrun', ['simctl', 'shutdown', 'booted'], { timeout: timeout }));
case 3:
context$2$0.next = 7;
break;
case 5:
context$2$0.prev = 5;
context$2$0.t0 = context$2$0['catch'](0);
case 7:
if (!pids.length) {
context$2$0.next = 18;
break;
}
_logger2['default'].debug('Using fkill to kill processes: ' + pids.join(', '));
context$2$0.prev = 9;
context$2$0.next = 12;
return _regeneratorRuntime.awrap((0, _fkill2['default'])(pids, { force: true }));
case 12:
context$2$0.next = 16;
break;
case 14:
context$2$0.prev = 14;
context$2$0.t1 = context$2$0['catch'](9);
case 16:
context$2$0.next = 21;
break;
case 18:
_logger2['default'].debug('Using pkill to kill application: ' + appName);
context$2$0.next = 21;
return _regeneratorRuntime.awrap(pkill(appName, true));
case 21:
case 'end':
return context$2$0.stop();
}
}, null, this, [[0, 5], [9, 14]]);
})(pids));
case 24:
remainingDevices = [];
context$1$0.prev = 25;
context$1$0.next = 28;
return _regeneratorRuntime.awrap((0, _asyncbox.waitForCondition)(allSimsAreDown, {
waitMs: timeout,
intervalMs: 200
}));
case 28:
context$1$0.next = 54;
break;
case 30:
context$1$0.prev = 30;
context$1$0.t1 = context$1$0['catch'](25);
if (!(remainingDevices.length > 0)) {
context$1$0.next = 53;
break;
}
_logger2['default'].warn('The following devices are still not in the correct state after ' + timeout + ' ms:');
_iteratorNormalCompletion = true;
_didIteratorError = false;
_iteratorError = undefined;
context$1$0.prev = 37;
for (_iterator = _getIterator(remainingDevices); !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
device = _step.value;
_logger2['default'].warn(' ' + device);
}
context$1$0.next = 45;
break;
case 41:
context$1$0.prev = 41;
context$1$0.t2 = context$1$0['catch'](37);
_didIteratorError = true;
_iteratorError = context$1$0.t2;
case 45:
context$1$0.prev = 45;
context$1$0.prev = 46;
if (!_iteratorNormalCompletion && _iterator['return']) {
_iterator['return']();
}
case 48:
context$1$0.prev = 48;
if (!_didIteratorError) {
context$1$0.next = 51;
break;
}
throw _iteratorError;
case 51:
return context$1$0.finish(48);
case 52:
return context$1$0.finish(45);
case 53:
throw context$1$0.t1;
case 54:
case 'end':
return context$1$0.stop();
}
}, null, this, [[8, 16], [25, 30], [37, 41, 45, 53], [46,, 48, 52]]);
}
function endAllSimulatorDaemons() {
var _arr, _i, servicePattern, launchCtlCommand, stopCmd, removeCmd;
return _regeneratorRuntime.async(function endAllSimulatorDaemons$(context$1$0) {
var _this = this;
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
_logger2['default'].debug('Ending all simulator daemons');
_arr = ['com.apple.iphonesimulator', 'com.apple.CoreSimulator'];
_i = 0;
case 3:
if (!(_i < _arr.length)) {
context$1$0.next = 28;
break;
}
servicePattern = _arr[_i];
_logger2['default'].debug('Killing any other ' + servicePattern + ' daemons');
launchCtlCommand = 'launchctl list | grep ' + servicePattern + ' | cut -f 3 | xargs -n 1 launchctl';
context$1$0.prev = 7;
stopCmd = launchCtlCommand + ' stop';
context$1$0.next = 11;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('bash', ['-c', stopCmd]));
case 11:
context$1$0.next = 16;
break;
case 13:
context$1$0.prev = 13;
context$1$0.t0 = context$1$0['catch'](7);
_logger2['default'].warn('Could not stop ' + servicePattern + ' daemons, carrying on anyway!');
case 16:
context$1$0.prev = 16;
removeCmd = launchCtlCommand + ' remove';
context$1$0.next = 20;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('bash', ['-c', removeCmd]));
case 20:
context$1$0.next = 25;
break;
case 22:
context$1$0.prev = 22;
context$1$0.t1 = context$1$0['catch'](16);
_logger2['default'].warn('Could not remove ' + servicePattern + ' daemons, carrying on anyway!');
case 25:
_i++;
context$1$0.next = 3;
break;
case 28:
context$1$0.prev = 28;
context$1$0.next = 31;
return _regeneratorRuntime.awrap((0, _asyncbox.waitForCondition)(function callee$1$0() {
var _ref2, stdout;
return _regeneratorRuntime.async(function callee$1$0$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.next = 2;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('bash', ['-c', 'ps -e | grep launchd_sim | grep -v bash | grep -v grep | awk {\'print$1\'}']));
case 2:
_ref2 = context$2$0.sent;
stdout = _ref2.stdout;
return context$2$0.abrupt('return', stdout.trim().length === 0);
case 5:
case 'end':
return context$2$0.stop();
}
}, null, _this);
}, { waitMs: 5000, intervalMs: 500 }));
case 31:
context$1$0.next = 36;
break;
case 33:
context$1$0.prev = 33;
context$1$0.t2 = context$1$0['catch'](28);
_logger2['default'].warn('Could not end all simulator daemons, carrying on!');
case 36:
_logger2['default'].debug('Finishing ending all simulator daemons');
case 37:
case 'end':
return context$1$0.stop();
}
}, null, this, [[7, 13], [16, 22], [28, 33]]);
}
function simExists(udid) {
var devices;
return _regeneratorRuntime.async(function simExists$(context$1$0) {
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
context$1$0.next = 2;
return _regeneratorRuntime.awrap((0, _nodeSimctl.getDevices)());
case 2:
devices = context$1$0.sent;
devices = _lodash2['default'].toPairs(devices).map(function (pair) {
return pair[1];
}).reduce(function (a, b) {
return a.concat(b);
}, []);
return context$1$0.abrupt('return', !!_lodash2['default'].find(devices, function (sim) {
return sim.udid === udid;
}));
case 5:
case 'end':
return context$1$0.stop();
}
}, null, this);
}
function safeRimRaf(delPath) {
var tryNum = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
return _regeneratorRuntime.async(function safeRimRaf$(context$1$0) {
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
context$1$0.prev = 0;
context$1$0.next = 3;
return _regeneratorRuntime.awrap(_appiumSupport.fs.rimraf(delPath));
case 3:
context$1$0.next = 16;
break;
case 5:
context$1$0.prev = 5;
context$1$0.t0 = context$1$0['catch'](0);
if (!(tryNum < 20)) {
context$1$0.next = 16;
break;
}
if (!(context$1$0.t0.message.indexOf('ENOTEMPTY') !== -1)) {
context$1$0.next = 13;
break;
}
_logger2['default'].debug('Path \'' + delPath + '\' was not empty during delete; retrying');
return context$1$0.abrupt('return', safeRimRaf(delPath, tryNum + 1));
case 13:
if (!(context$1$0.t0.message.indexOf('ENOENT') !== -1)) {
context$1$0.next = 16;
break;
}
_logger2['default'].debug('Path \'' + delPath + '\'\' did not exist when we tried to delete, ignoring');
return context$1$0.abrupt('return', safeRimRaf(delPath, tryNum + 1));
case 16:
case 'end':
return context$1$0.stop();
}
}, null, this, [[0, 5]]);
}
function installSSLCert(pemText, udid) {
var tempFileName, pathToKeychain, certificate;
return _regeneratorRuntime.async(function installSSLCert$(context$1$0) {
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
context$1$0.prev = 0;
context$1$0.next = 3;
return _regeneratorRuntime.awrap(_appiumSupport.fs.which('openssl'));
case 3:
context$1$0.next = 9;
break;
case 5:
context$1$0.prev = 5;
context$1$0.t0 = context$1$0['catch'](0);
_logger2['default'].debug('customSSLCert requires openssl to be available on path');
_logger2['default'].errorAndThrow('Command \'openssl\' not found');
case 9:
context$1$0.prev = 9;
context$1$0.next = 12;
return _regeneratorRuntime.awrap(_appiumSupport.fs.which('sqlite3'));
case 12:
context$1$0.next = 18;
break;
case 14:
context$1$0.prev = 14;
context$1$0.t1 = context$1$0['catch'](9);
_logger2['default'].debug('customSSLCert requires sqlite3 to be available on path');
_logger2['default'].errorAndThrow('Command \'sqlite3\' not found');
case 18:
tempFileName = _path2['default'].resolve(__dirname + '/temp-ssl-cert.pem');
pathToKeychain = _path2['default'].resolve(new _simulatorXcode62['default'](udid).getDir());
context$1$0.next = 22;
return _regeneratorRuntime.awrap(_appiumSupport.fs.writeFile(tempFileName, pemText));
case 22:
context$1$0.prev = 22;
context$1$0.next = 25;
return _regeneratorRuntime.awrap(_appiumSupport.fs.stat(pathToKeychain));
case 25:
context$1$0.next = 31;
break;
case 27:
context$1$0.prev = 27;
context$1$0.t2 = context$1$0['catch'](22);
_logger2['default'].debug('Could not install SSL certificate. No simulator with udid \'' + udid + '\'');
_logger2['default'].errorAndThrow(context$1$0.t2);
case 31:
certificate = new _certificate.Certificate(tempFileName);
_logger2['default'].debug('Installing certificate to ' + pathToKeychain);
context$1$0.next = 35;
return _regeneratorRuntime.awrap(certificate.add(pathToKeychain));
case 35:
context$1$0.next = 37;
return _regeneratorRuntime.awrap(_appiumSupport.fs.unlink(tempFileName));
case 37:
return context$1$0.abrupt('return', certificate);
case 38:
case 'end':
return context$1$0.stop();
}
}, null, this, [[0, 5], [9, 14], [22, 27]]);
}
function uninstallSSLCert(pemText, udid) {
var tempFileName, pathToKeychain, certificate;
return _regeneratorRuntime.async(function uninstallSSLCert$(context$1$0) {
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
context$1$0.prev = 0;
tempFileName = _path2['default'].resolve(__dirname, 'temp-ssl-cert.pem');
pathToKeychain = _path2['default'].resolve(new _simulatorXcode62['default'](udid).getDir());
context$1$0.next = 5;
return _regeneratorRuntime.awrap(_appiumSupport.fs.writeFile(tempFileName, pemText));
case 5:
certificate = new _certificate.Certificate(tempFileName);
context$1$0.next = 8;
return _regeneratorRuntime.awrap(certificate.remove(pathToKeychain));
case 8:
context$1$0.next = 10;
return _regeneratorRuntime.awrap(_appiumSupport.fs.unlink(tempFileName));
case 10:
return context$1$0.abrupt('return', certificate);
case 13:
context$1$0.prev = 13;
context$1$0.t0 = context$1$0['catch'](0);
_logger2['default'].debug('Could not uninstall SSL certificate. No simulator with udid \'' + udid + '\'');
_logger2['default'].errorAndThrow(context$1$0.t0);
case 17:
case 'end':
return context$1$0.stop();
}
}, null, this, [[0, 13]]);
}
/**
* Runs a command line sqlite3 query
*/
function execSQLiteQuery(db, query) {
for (var _len = arguments.length, queryParams = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
queryParams[_key - 2] = arguments[_key];
}
var queryTokens, formattedQuery;
return _regeneratorRuntime.async(function execSQLiteQuery$(context$1$0) {
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
queryTokens = query.split('?');
formattedQuery = [];
queryParams.forEach(function (param, i) {
formattedQuery.push(queryTokens[i]);
formattedQuery.push(param.replace(/'/g, "''"));
});
formattedQuery.push(queryTokens[queryTokens.length - 1]);
context$1$0.next = 6;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('sqlite3', ['-line', db, formattedQuery.join('')]));
case 6:
return context$1$0.abrupt('return', context$1$0.sent);
case 7:
case 'end':
return context$1$0.stop();
}
}, null, this);
}
exports.killAllSimulators = killAllSimulators;
exports.endAllSimulatorDaemons = endAllSimulatorDaemons;
exports.safeRimRaf = safeRimRaf;
exports.simExists = simExists;
exports.installSSLCert = installSSLCert;
exports.uninstallSSLCert = uninstallSSLCert;
exports.execSQLiteQuery = execSQLiteQuery;
// wait for all the devices to be shutdown before Continuing
// but only print out the failed ones when they are actually fully failed
// waiting until the simulator service has died.
// see the README for github.com/appium/node-simctl for example output of getDevices()
// Check that openssl is installed on the path
// Check that sqlite3 is installed on the path
//# sourceMappingURL=data:application/json;charset=utf8;base64,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RSxJQUFJLFFBQUksQ0FBQztBQUNuRiw0QkFBSSxhQUFhLGdCQUFHLENBQUM7Ozs7Ozs7Q0FFeEI7Ozs7O0FBS0QsU0FBZSxlQUFlLENBQUUsRUFBRSxFQUFFLEtBQUs7b0NBQUssV0FBVztBQUFYLGVBQVc7OztNQUNuRCxXQUFXLEVBQ1gsY0FBYzs7OztBQURkLG1CQUFXLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUM7QUFDOUIsc0JBQWMsR0FBRyxFQUFFOztBQUN2QixtQkFBVyxDQUFDLE9BQU8sQ0FBQyxVQUFDLEtBQUssRUFBRSxDQUFDLEVBQUs7QUFDaEMsd0JBQWMsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDcEMsd0JBQWMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQztTQUNoRCxDQUFDLENBQUM7QUFDSCxzQkFBYyxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsV0FBVyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDOzs7eUNBRTVDLHdCQUFLLFNBQVMsRUFBRSxDQUFDLE9BQU8sRUFBRSxFQUFFLEVBQUUsY0FBYyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDOzs7Ozs7Ozs7O0NBQ3JFOztRQUVRLGlCQUFpQixHQUFqQixpQkFBaUI7UUFBRSxzQkFBc0IsR0FBdEIsc0JBQXNCO1FBQUUsVUFBVSxHQUFWLFVBQVU7UUFBRSxTQUFTLEdBQVQsU0FBUztRQUFFLGNBQWMsR0FBZCxjQUFjO1FBQUUsZ0JBQWdCLEdBQWhCLGdCQUFnQjtRQUFFLGVBQWUsR0FBZixlQUFlIiwiZmlsZSI6ImxpYi91dGlscy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBsb2cgZnJvbSAnLi9sb2dnZXInO1xuaW1wb3J0IF8gZnJvbSAnbG9kYXNoJztcbmltcG9ydCB7IGV4ZWMgfSBmcm9tICd0ZWVuX3Byb2Nlc3MnO1xuaW1wb3J0IHsgd2FpdEZvckNvbmRpdGlvbiB9IGZyb20gJ2FzeW5jYm94JztcbmltcG9ydCB7IGdldFZlcnNpb24gfSBmcm9tICdhcHBpdW0teGNvZGUnO1xuaW1wb3J0IHsgZ2V0RGV2aWNlcyB9IGZyb20gJ25vZGUtc2ltY3RsJztcbmltcG9ydCB7IGZzIH0gZnJvbSAnYXBwaXVtLXN1cHBvcnQnO1xuaW1wb3J0IHsgQ2VydGlmaWNhdGUgfSBmcm9tICcuL2NlcnRpZmljYXRlJztcbmltcG9ydCBwYXRoIGZyb20gJ3BhdGgnO1xuaW1wb3J0IFNpbXVsYXRvciBmcm9tICcuL3NpbXVsYXRvci14Y29kZS02JztcbmltcG9ydCBma2lsbCBmcm9tICdma2lsbCc7XG5cblxuY29uc3QgREVGQVVMVF9TSU1fU0hVVERPV05fVElNRU9VVCA9IDMwMDAwO1xuXG4vLyBwZ3JlcC9wa2lsbCBleGl0IGNvZGVzOlxuLy8gMCAgICAgICBPbmUgb3IgbW9yZSBwcm9jZXNzZXMgd2VyZSBtYXRjaGVkLlxuLy8gMSAgICAgICBObyBwcm9jZXNzZXMgd2VyZSBtYXRjaGVkLlxuLy8gMiAgICAgICBJbnZhbGlkIG9wdGlvbnMgd2VyZSBzcGVjaWZpZWQgb24gdGhlIGNvbW1hbmQgbGluZS5cbi8vIDMgICAgICAgQW4gaW50ZXJuYWwgZXJyb3Igb2NjdXJyZWQuXG5cbmFzeW5jIGZ1bmN0aW9uIHBraWxsIChhcHBOYW1lLCBmb3JjZUtpbGwgPSBmYWxzZSkge1xuICBsZXQgYXJncyA9IGZvcmNlS2lsbCA/IFsnLTknXSA6IFtdO1xuICBhcmdzLnB1c2goJy14JywgYXBwTmFtZSk7XG4gIHRyeSB7XG4gICAgYXdhaXQgZXhlYygncGtpbGwnLCBhcmdzKTtcbiAgICByZXR1cm4gMDtcbiAgfSBjYXRjaCAoZXJyKSB7XG4gICAgaWYgKCFfLmlzVW5kZWZpbmVkKGVyci5jb2RlKSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBDYW5ub3QgZm9yY2VmdWxseSB0ZXJtaW5hdGUgJHthcHBOYW1lfS4gcGtpbGwgZXJyb3IgY29kZTogJHtlcnIuY29kZX1gKTtcbiAgICB9XG4gICAgbG9nLmVycm9yKGBSZWNlaXZlZCB1bmV4cGVjdGVkIGVycm9yIHdoaWxlIHRyeWluZyB0byBraWxsICR7YXBwTmFtZX06ICR7ZXJyLm1lc3NhZ2V9YCk7XG4gICAgdGhyb3cgZXJyO1xuICB9XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGtpbGxBbGxTaW11bGF0b3JzICh0aW1lb3V0ID0gREVGQVVMVF9TSU1fU0hVVERPV05fVElNRU9VVCkge1xuICBsb2cuZGVidWcoJ0tpbGxpbmcgYWxsIGlPUyBTaW11bGF0b3JzJyk7XG4gIGNvbnN0IHhjb2RlVmVyc2lvbiA9IGF3YWl0IGdldFZlcnNpb24odHJ1ZSk7XG4gIGNvbnN0IGFwcE5hbWUgPSB4Y29kZVZlcnNpb24ubWFqb3IgPj0gNyA/ICdTaW11bGF0b3InIDogJ2lPUyBTaW11bGF0b3InO1xuXG4gIC8vIGxhdGVyIHZlcnNpb25zIGFyZSBzbG93ZXIgdG8gY2xvc2VcbiAgdGltZW91dCA9IHRpbWVvdXQgKiAoeGNvZGVWZXJzaW9uLm1ham9yID09PSA4ID8gMiA6IDEpO1xuXG4gIGxldCBwaWRzO1xuICB0cnkge1xuICAgIGxldCB7c3Rkb3V0fSA9IGF3YWl0IGV4ZWMoJ3BncmVwJywgWycteCcsIGFwcE5hbWVdKTtcbiAgICBwaWRzID0gc3Rkb3V0LnRyaW0oKS5zcGxpdCgnXFxuJykubWFwKChwaWQpID0+IHBhcnNlSW50KHBpZCwgMTApKTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGlmIChlLmNvZGUgPT09IDEpIHtcbiAgICAgIGxvZy5kZWJ1ZyhgJHthcHBOYW1lfSBpcyBub3QgcnVubmluZy4gQ29udGludWluZy4uLmApO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBsb2cud2FybihgcGdyZXAgZXJyb3IgJHtlLmNvZGV9IHdoaWxlIGRldGVjdGluZyB3aGV0aGVyICR7YXBwTmFtZX0gaXMgcnVubmluZy4gVHJ5aW5nIHRvIGtpbGwgYW55d2F5LmApO1xuICB9XG5cbiAgYXdhaXQgKGFzeW5jIGZ1bmN0aW9uIHBlcmZvcm1LaWxsIChwaWRzID0gW10pIHtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgZXhlYygneGNydW4nLCBbJ3NpbWN0bCcsICdzaHV0ZG93bicsICdib290ZWQnXSwge3RpbWVvdXR9KTtcbiAgICB9IGNhdGNoIChpZ24pIHt9XG5cbiAgICBpZiAocGlkcy5sZW5ndGgpIHtcbiAgICAgIGxvZy5kZWJ1ZyhgVXNpbmcgZmtpbGwgdG8ga2lsbCBwcm9jZXNzZXM6ICR7cGlkcy5qb2luKCcsICcpfWApO1xuICAgICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgZmtpbGwocGlkcywge2ZvcmNlOiB0cnVlfSk7XG4gICAgICB9IGNhdGNoIChpZ24pIHt9XG4gICAgfSBlbHNlIHtcbiAgICAgIGxvZy5kZWJ1ZyhgVXNpbmcgcGtpbGwgdG8ga2lsbCBhcHBsaWNhdGlvbjogJHthcHBOYW1lfWApO1xuICAgICAgYXdhaXQgcGtpbGwoYXBwTmFtZSwgdHJ1ZSk7XG4gICAgfVxuICB9KShwaWRzKTtcblxuICAvLyB3YWl0IGZvciBhbGwgdGhlIGRldmljZXMgdG8gYmUgc2h1dGRvd24gYmVmb3JlIENvbnRpbnVpbmdcbiAgLy8gYnV0IG9ubHkgcHJpbnQgb3V0IHRoZSBmYWlsZWQgb25lcyB3aGVuIHRoZXkgYXJlIGFjdHVhbGx5IGZ1bGx5IGZhaWxlZFxuICBsZXQgcmVtYWluaW5nRGV2aWNlcyA9IFtdO1xuICBhc3luYyBmdW5jdGlvbiBhbGxTaW1zQXJlRG93biAoKSB7XG4gICAgcmVtYWluaW5nRGV2aWNlcyA9IFtdO1xuICAgIGxldCBkZXZpY2VzID0gYXdhaXQgZ2V0RGV2aWNlcygpO1xuICAgIGRldmljZXMgPSBfLmZsYXR0ZW4oXy52YWx1ZXMoZGV2aWNlcykpO1xuICAgIHJldHVybiBfLmV2ZXJ5KGRldmljZXMsIChzaW0pID0+IHtcbiAgICAgIGxldCBzdGF0ZSA9IHNpbS5zdGF0ZS50b0xvd2VyQ2FzZSgpO1xuICAgICAgbGV0IGRvbmUgPSBzdGF0ZSA9PT0gJ3NodXRkb3duJyB8fFxuICAgICAgICAgICAgICAgICBzdGF0ZSA9PT0gJ3VuYXZhaWxhYmxlJyB8fFxuICAgICAgICAgICAgICAgICBzdGF0ZSA9PT0gJ2Rpc2Nvbm5lY3RlZCc7XG4gICAgICBpZiAoIWRvbmUpIHtcbiAgICAgICAgcmVtYWluaW5nRGV2aWNlcy5wdXNoKGAke3NpbS5uYW1lfSAoJHtzaW0uc2RrfSwgdWRpZDogJHtzaW0udWRpZH0pIGlzIHN0aWxsIGluIHN0YXRlICcke3N0YXRlfSdgKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBkb25lO1xuICAgIH0pO1xuICB9XG4gIHRyeSB7XG4gICAgYXdhaXQgd2FpdEZvckNvbmRpdGlvbihhbGxTaW1zQXJlRG93biwge1xuICAgICAgd2FpdE1zOiB0aW1lb3V0LFxuICAgICAgaW50ZXJ2YWxNczogMjAwXG4gICAgfSk7XG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGlmIChyZW1haW5pbmdEZXZpY2VzLmxlbmd0aCA+IDApIHtcbiAgICAgIGxvZy53YXJuKGBUaGUgZm9sbG93aW5nIGRldmljZXMgYXJlIHN0aWxsIG5vdCBpbiB0aGUgY29ycmVjdCBzdGF0ZSBhZnRlciAke3RpbWVvdXR9IG1zOmApO1xuICAgICAgZm9yIChsZXQgZGV2aWNlIG9mIHJlbWFpbmluZ0RldmljZXMpIHtcbiAgICAgICAgbG9nLndhcm4oYCAgICAke2RldmljZX1gKTtcbiAgICAgIH1cbiAgICB9XG4gICAgdGhyb3cgZXJyO1xuICB9XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGVuZEFsbFNpbXVsYXRvckRhZW1vbnMgKCkge1xuICBsb2cuZGVidWcoJ0VuZGluZyBhbGwgc2ltdWxhdG9yIGRhZW1vbnMnKTtcbiAgZm9yIChsZXQgc2VydmljZVBhdHRlcm4gb2YgWydjb20uYXBwbGUuaXBob25lc2ltdWxhdG9yJywgJ2NvbS5hcHBsZS5Db3JlU2ltdWxhdG9yJ10pIHtcbiAgICBsb2cuZGVidWcoYEtpbGxpbmcgYW55IG90aGVyICR7c2VydmljZVBhdHRlcm59IGRhZW1vbnNgKTtcbiAgICBsZXQgbGF1bmNoQ3RsQ29tbWFuZCA9IGBsYXVuY2hjdGwgbGlzdCB8IGdyZXAgJHtzZXJ2aWNlUGF0dGVybn0gfCBjdXQgLWYgMyB8IHhhcmdzIC1uIDEgbGF1bmNoY3RsYDtcbiAgICB0cnkge1xuICAgICAgbGV0IHN0b3BDbWQgPSBgJHtsYXVuY2hDdGxDb21tYW5kfSBzdG9wYDtcbiAgICAgIGF3YWl0IGV4ZWMoJ2Jhc2gnLCBbJy1jJywgc3RvcENtZF0pO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgbG9nLndhcm4oYENvdWxkIG5vdCBzdG9wICR7c2VydmljZVBhdHRlcm59IGRhZW1vbnMsIGNhcnJ5aW5nIG9uIGFueXdheSFgKTtcbiAgICB9XG4gICAgdHJ5IHtcbiAgICAgIGxldCByZW1vdmVDbWQgPSBgJHtsYXVuY2hDdGxDb21tYW5kfSByZW1vdmVgO1xuICAgICAgYXdhaXQgZXhlYygnYmFzaCcsIFsnLWMnLCByZW1vdmVDbWRdKTtcbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIGxvZy53YXJuKGBDb3VsZCBub3QgcmVtb3ZlICR7c2VydmljZVBhdHRlcm59IGRhZW1vbnMsIGNhcnJ5aW5nIG9uIGFueXdheSFgKTtcbiAgICB9XG4gIH1cbiAgLy8gd2FpdGluZyB1bnRpbCB0aGUgc2ltdWxhdG9yIHNlcnZpY2UgaGFzIGRpZWQuXG4gIHRyeSB7XG4gICAgYXdhaXQgd2FpdEZvckNvbmRpdGlvbihhc3luYyAoKSA9PiB7XG4gICAgICBsZXQge3N0ZG91dH0gPSBhd2FpdCBleGVjKCdiYXNoJywgWyctYycsXG4gICAgICAgIGBwcyAtZSAgfCBncmVwIGxhdW5jaGRfc2ltIHwgZ3JlcCAtdiBiYXNoIHwgZ3JlcCAtdiBncmVwIHwgYXdrIHsncHJpbnQkMSd9YF0pO1xuICAgICAgcmV0dXJuIHN0ZG91dC50cmltKCkubGVuZ3RoID09PSAwO1xuICAgIH0sIHt3YWl0TXM6IDUwMDAsIGludGVydmFsTXM6IDUwMH0pO1xuICB9IGNhdGNoIChlcnIpIHtcbiAgICBsb2cud2FybihgQ291bGQgbm90IGVuZCBhbGwgc2ltdWxhdG9yIGRhZW1vbnMsIGNhcnJ5aW5nIG9uIWApO1xuICB9XG4gIGxvZy5kZWJ1ZygnRmluaXNoaW5nIGVuZGluZyBhbGwgc2ltdWxhdG9yIGRhZW1vbnMnKTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gc2ltRXhpc3RzICh1ZGlkKSB7XG4gIC8vIHNlZSB0aGUgUkVBRE1FIGZvciBnaXRodWIuY29tL2FwcGl1bS9ub2RlLXNpbWN0bCBmb3IgZXhhbXBsZSBvdXRwdXQgb2YgZ2V0RGV2aWNlcygpXG4gIGxldCBkZXZpY2VzID0gYXdhaXQgZ2V0RGV2aWNlcygpO1xuXG4gIGRldmljZXMgPSBfLnRvUGFpcnMoZGV2aWNlcykubWFwKChwYWlyKSA9PiB7XG4gICAgcmV0dXJuIHBhaXJbMV07XG4gIH0pLnJlZHVjZSgoYSwgYikgPT4ge1xuICAgIHJldHVybiBhLmNvbmNhdChiKTtcbiAgfSwgW10pO1xuICByZXR1cm4gISFfLmZpbmQoZGV2aWNlcywgKHNpbSkgPT4ge1xuICAgIHJldHVybiBzaW0udWRpZCA9PT0gdWRpZDtcbiAgfSk7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHNhZmVSaW1SYWYgKGRlbFBhdGgsIHRyeU51bSA9IDApIHtcbiAgdHJ5IHtcbiAgICBhd2FpdCBmcy5yaW1yYWYoZGVsUGF0aCk7XG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGlmICh0cnlOdW0gPCAyMCkge1xuICAgICAgaWYgKGVyci5tZXNzYWdlLmluZGV4T2YoJ0VOT1RFTVBUWScpICE9PSAtMSkge1xuICAgICAgICBsb2cuZGVidWcoYFBhdGggJyR7ZGVsUGF0aH0nIHdhcyBub3QgZW1wdHkgZHVyaW5nIGRlbGV0ZTsgcmV0cnlpbmdgKTtcbiAgICAgICAgcmV0dXJuIHNhZmVSaW1SYWYoZGVsUGF0aCwgdHJ5TnVtICsgMSk7XG4gICAgICB9IGVsc2UgaWYgKGVyci5tZXNzYWdlLmluZGV4T2YoJ0VOT0VOVCcpICE9PSAtMSkge1xuICAgICAgICBsb2cuZGVidWcoYFBhdGggJyR7ZGVsUGF0aH0nJyBkaWQgbm90IGV4aXN0IHdoZW4gd2UgdHJpZWQgdG8gZGVsZXRlLCBpZ25vcmluZ2ApO1xuICAgICAgICByZXR1cm4gc2FmZVJpbVJhZihkZWxQYXRoLCB0cnlOdW0gKyAxKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cblxuYXN5bmMgZnVuY3Rpb24gaW5zdGFsbFNTTENlcnQgKHBlbVRleHQsIHVkaWQpIHtcbiAgLy8gQ2hlY2sgdGhhdCBvcGVuc3NsIGlzIGluc3RhbGxlZCBvbiB0aGUgcGF0aFxuICB0cnkge1xuICAgIGF3YWl0IGZzLndoaWNoKCdvcGVuc3NsJyk7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBsb2cuZGVidWcoYGN1c3RvbVNTTENlcnQgcmVxdWlyZXMgb3BlbnNzbCB0byBiZSBhdmFpbGFibGUgb24gcGF0aGApO1xuICAgIGxvZy5lcnJvckFuZFRocm93KGBDb21tYW5kICdvcGVuc3NsJyBub3QgZm91bmRgKTtcbiAgfVxuXG4gIC8vIENoZWNrIHRoYXQgc3FsaXRlMyBpcyBpbnN0YWxsZWQgb24gdGhlIHBhdGhcbiAgdHJ5IHtcbiAgICBhd2FpdCBmcy53aGljaCgnc3FsaXRlMycpO1xuICB9IGNhdGNoIChlKSB7XG4gICAgbG9nLmRlYnVnKGBjdXN0b21TU0xDZXJ0IHJlcXVpcmVzIHNxbGl0ZTMgdG8gYmUgYXZhaWxhYmxlIG9uIHBhdGhgKTtcbiAgICBsb2cuZXJyb3JBbmRUaHJvdyhgQ29tbWFuZCAnc3FsaXRlMycgbm90IGZvdW5kYCk7XG4gIH1cblxuICBsZXQgdGVtcEZpbGVOYW1lID0gcGF0aC5yZXNvbHZlKGAke19fZGlybmFtZX0vdGVtcC1zc2wtY2VydC5wZW1gKTtcbiAgbGV0IHBhdGhUb0tleWNoYWluID0gcGF0aC5yZXNvbHZlKG5ldyBTaW11bGF0b3IodWRpZCkuZ2V0RGlyKCkpO1xuICBhd2FpdCBmcy53cml0ZUZpbGUodGVtcEZpbGVOYW1lLCBwZW1UZXh0KTtcbiAgdHJ5IHtcbiAgICBhd2FpdCBmcy5zdGF0KHBhdGhUb0tleWNoYWluKTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGxvZy5kZWJ1ZyhgQ291bGQgbm90IGluc3RhbGwgU1NMIGNlcnRpZmljYXRlLiBObyBzaW11bGF0b3Igd2l0aCB1ZGlkICcke3VkaWR9J2ApO1xuICAgIGxvZy5lcnJvckFuZFRocm93KGUpO1xuICB9XG4gIGxldCBjZXJ0aWZpY2F0ZSA9IG5ldyBDZXJ0aWZpY2F0ZSh0ZW1wRmlsZU5hbWUpO1xuICBsb2cuZGVidWcoYEluc3RhbGxpbmcgY2VydGlmaWNhdGUgdG8gJHtwYXRoVG9LZXljaGFpbn1gKTtcbiAgYXdhaXQgY2VydGlmaWNhdGUuYWRkKHBhdGhUb0tleWNoYWluKTtcbiAgYXdhaXQgZnMudW5saW5rKHRlbXBGaWxlTmFtZSk7XG4gIHJldHVybiBjZXJ0aWZpY2F0ZTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gdW5pbnN0YWxsU1NMQ2VydCAocGVtVGV4dCwgdWRpZCkge1xuICB0cnkge1xuICAgIGxldCB0ZW1wRmlsZU5hbWUgPSBwYXRoLnJlc29sdmUoX19kaXJuYW1lLCAndGVtcC1zc2wtY2VydC5wZW0nKTtcbiAgICBsZXQgcGF0aFRvS2V5Y2hhaW4gPSBwYXRoLnJlc29sdmUobmV3IFNpbXVsYXRvcih1ZGlkKS5nZXREaXIoKSk7XG4gICAgYXdhaXQgZnMud3JpdGVGaWxlKHRlbXBGaWxlTmFtZSwgcGVtVGV4dCk7XG4gICAgbGV0IGNlcnRpZmljYXRlID0gbmV3IENlcnRpZmljYXRlKHRlbXBGaWxlTmFtZSk7XG4gICAgYXdhaXQgY2VydGlmaWNhdGUucmVtb3ZlKHBhdGhUb0tleWNoYWluKTtcbiAgICBhd2FpdCBmcy51bmxpbmsodGVtcEZpbGVOYW1lKTtcbiAgICByZXR1cm4gY2VydGlmaWNhdGU7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBsb2cuZGVidWcoYENvdWxkIG5vdCB1bmluc3RhbGwgU1NMIGNlcnRpZmljYXRlLiBObyBzaW11bGF0b3Igd2l0aCB1ZGlkICcke3VkaWR9J2ApO1xuICAgIGxvZy5lcnJvckFuZFRocm93KGUpO1xuICB9XG59XG5cbi8qKlxuICogUnVucyBhIGNvbW1hbmQgbGluZSBzcWxpdGUzIHF1ZXJ5XG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGV4ZWNTUUxpdGVRdWVyeSAoZGIsIHF1ZXJ5LCAuLi5xdWVyeVBhcmFtcykge1xuICBsZXQgcXVlcnlUb2tlbnMgPSBxdWVyeS5zcGxpdCgnPycpO1xuICBsZXQgZm9ybWF0dGVkUXVlcnkgPSBbXTtcbiAgcXVlcnlQYXJhbXMuZm9yRWFjaCgocGFyYW0sIGkpID0+IHtcbiAgICBmb3JtYXR0ZWRRdWVyeS5wdXNoKHF1ZXJ5VG9rZW5zW2ldKTtcbiAgICBmb3JtYXR0ZWRRdWVyeS5wdXNoKHBhcmFtLnJlcGxhY2UoLycvZywgXCInJ1wiKSk7XG4gIH0pO1xuICBmb3JtYXR0ZWRRdWVyeS5wdXNoKHF1ZXJ5VG9rZW5zW3F1ZXJ5VG9rZW5zLmxlbmd0aCAtIDFdKTtcblxuICByZXR1cm4gYXdhaXQgZXhlYygnc3FsaXRlMycsIFsnLWxpbmUnLCBkYiwgZm9ybWF0dGVkUXVlcnkuam9pbignJyldKTtcbn1cblxuZXhwb3J0IHsga2lsbEFsbFNpbXVsYXRvcnMsIGVuZEFsbFNpbXVsYXRvckRhZW1vbnMsIHNhZmVSaW1SYWYsIHNpbUV4aXN0cywgaW5zdGFsbFNTTENlcnQsIHVuaW5zdGFsbFNTTENlcnQsIGV4ZWNTUUxpdGVRdWVyeSB9O1xuIl0sInNvdXJjZVJvb3QiOiIuLi8uLiJ9