UNPKG

appium-instruments

Version:

IOS Instruments + instruments-without-delay launcher used by Appium

810 lines (654 loc) 62 kB
// Wrapper around Apple's Instruments app 'use strict'; var _createClass = require('babel-runtime/helpers/create-class')['default']; var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default']; var _slicedToArray = require('babel-runtime/helpers/sliced-to-array')['default']; var _toConsumableArray = require('babel-runtime/helpers/to-consumable-array')['default']; 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 _teen_process = require('teen_process'); var _logger = require('./logger'); var _logger2 = _interopRequireDefault(_logger); var _lodash = require('lodash'); var _lodash2 = _interopRequireDefault(_lodash); var _through = require('through'); var _path = require('path'); var _path2 = _interopRequireDefault(_path); var _appiumSupport = require('appium-support'); var _appiumXcode = require('appium-xcode'); var _appiumXcode2 = _interopRequireDefault(_appiumXcode); var _bluebird = require('bluebird'); var _bluebird2 = _interopRequireDefault(_bluebird); var _appiumIosSimulator = require('appium-ios-simulator'); var _utils = require('./utils'); var _streams = require('./streams'); require('colors'); var ERR_NEVER_CHECKED_IN = 'Instruments never checked in'; var ERR_CRASHED_ON_STARTUP = 'Instruments crashed on startup'; var ERR_AMBIGUOUS_DEVICE = 'Instruments Usage Error : Ambiguous device name/identifier'; var Instruments = (function () { _createClass(Instruments, null, [{ key: 'quickInstruments', // simple factory with sane defaults value: function quickInstruments(opts) { var xcodeTraceTemplatePath; return _regeneratorRuntime.async(function quickInstruments$(context$2$0) { while (1) switch (context$2$0.prev = context$2$0.next) { case 0: opts = _lodash2['default'].clone(opts); context$2$0.next = 3; return _regeneratorRuntime.awrap(_appiumXcode2['default'].getAutomationTraceTemplatePath()); case 3: xcodeTraceTemplatePath = context$2$0.sent; _lodash2['default'].defaults(opts, { launchTimeout: 60000, template: xcodeTraceTemplatePath, withoutDelay: true, xcodeVersion: '8.1', webSocket: null, flakeyRetries: 2 }); return context$2$0.abrupt('return', new Instruments(opts)); case 6: case 'end': return context$2$0.stop(); } }, null, this); } /* * opts: * - app * - termTimeout - defaults to 5000 * - flakeyRetries - defaults to 0 * - udid * - bootstrap * - template * - withoutDelay * - processArguments * - simulatorSdkAndDevice * - tmpDir - defaults to `/tmp/appium-instruments` * - traceDir * - launchTimeout - defaults to 90000 * - webSocket * - instrumentsPath * - realDevice - true/false, defaults to false */ }]); function Instruments(opts) { var _this = this; _classCallCheck(this, Instruments); opts = _lodash2['default'].cloneDeep(opts); _lodash2['default'].defaults(opts, { termTimeout: 5000, tmpDir: '/tmp/appium-instruments', launchTimeout: 90000, flakeyRetries: 0, realDevice: false }); // config var _arr = ['app', 'termTimeout', 'flakeyRetries', 'udid', 'bootstrap', 'template', 'withoutDelay', 'processArguments', 'realDevice', 'simulatorSdkAndDevice', 'tmpDir', 'traceDir', 'locale', 'language']; for (var _i = 0; _i < _arr.length; _i++) { var f = _arr[_i]; this[f] = opts[f]; } this.traceDir = this.traceDir || this.tmpDir; this.launchTimeout = (0, _utils.parseLaunchTimeout)(opts.launchTimeout); // state this.proc = null; this.webSocket = opts.webSocket; this.instrumentsPath = opts.instrumentsPath; this.launchTries = 0; this.socketConnectDelays = []; this.gotFBSOpenApplicationError = false; this.onShutdown = new _bluebird2['default'](function (resolve, reject) { _this.onShutdownDeferred = { resolve: resolve, reject: reject }; }); // avoids UnhandledException this.onShutdown['catch'](function () {}).done(); } _createClass(Instruments, [{ key: 'configure', value: function configure() { return _regeneratorRuntime.async(function configure$(context$2$0) { while (1) switch (context$2$0.prev = context$2$0.next) { case 0: if (this.xcodeVersion) { context$2$0.next = 4; break; } context$2$0.next = 3; return _regeneratorRuntime.awrap(_appiumXcode2['default'].getVersion(true)); case 3: this.xcodeVersion = context$2$0.sent; case 4: if (this.xcodeVersion.versionFloat === 6.0 && this.withoutDelay) { _logger2['default'].info('In xcode 6.0, instruments-without-delay does not work. ' + 'If using Appium, you can disable instruments-without-delay ' + 'with the --native-instruments-lib server flag'); } if (!(this.xcodeVersion.versionString === '5.0.1')) { context$2$0.next = 7; break; } throw new Error('Xcode 5.0.1 ships with a broken version of ' + 'Instruments. please upgrade to 5.0.2'); case 7: if (this.template) { context$2$0.next = 11; break; } context$2$0.next = 10; return _regeneratorRuntime.awrap(_appiumXcode2['default'].getAutomationTraceTemplatePath()); case 10: this.template = context$2$0.sent; case 11: if (this.instrumentsPath) { context$2$0.next = 15; break; } context$2$0.next = 14; return _regeneratorRuntime.awrap((0, _utils.getInstrumentsPath)()); case 14: this.instrumentsPath = context$2$0.sent; case 15: case 'end': return context$2$0.stop(); } }, null, this); } }, { key: 'launchOnce', value: function launchOnce() { var launchResultPromise, actOnStderr; return _regeneratorRuntime.async(function launchOnce$(context$2$0) { var _this2 = this; while (1) switch (context$2$0.prev = context$2$0.next) { case 0: _logger2['default'].info('Launching instruments'); // prepare temp dir context$2$0.next = 3; return _regeneratorRuntime.awrap(_appiumSupport.fs.rimraf(this.tmpDir)); case 3: context$2$0.next = 5; return _regeneratorRuntime.awrap((0, _appiumSupport.mkdirp)(this.tmpDir)); case 5: context$2$0.next = 7; return _regeneratorRuntime.awrap((0, _appiumSupport.mkdirp)(this.traceDir)); case 7: this.exitListener = null; context$2$0.next = 10; return _regeneratorRuntime.awrap(this.spawnInstruments()); case 10: this.proc = context$2$0.sent; this.proc.on('exit', function (code, signal) { var msg = code !== null ? 'code: ' + code : 'signal: ' + signal; _logger2['default'].debug('Instruments exited with ' + msg); }); // set up the promise to handle launch launchResultPromise = new _bluebird2['default'](function (resolve, reject) { _this2.launchResultDeferred = { resolve: resolve, reject: reject }; }); // There was a special case for ignoreStartupExit // but it is not needed anymore, you may just listen for exit. this.setExitListener(function () { _this2.proc = null; _this2.launchResultDeferred.reject(new Error(ERR_CRASHED_ON_STARTUP)); }); this.proc.on('error', function (err) { _logger2['default'].debug('Error with instruments proc: ' + err.message); if (err.message.indexOf('ENOENT') !== -1) { _this2.proc = null; // otherwise we'll try to send sigkill _logger2['default'].error('Unable to spawn instruments: ' + err.message); _this2.launchResultDeferred.reject(err); } }); this.proc.stdout.setEncoding('utf8'); this.proc.stdout.pipe((0, _streams.outputStream)()).pipe((0, _streams.dumpStream)()); this.proc.stderr.setEncoding('utf8'); actOnStderr = function actOnStderr(output) { if (_this2.launchTimeout.afterSimLaunch && output && output.match(/CLTilesManagerClient: initialize/)) { _this2.addSocketConnectTimer(_this2.launchTimeout.afterSimLaunch, 'afterLaunch', function callee$3$0() { return _regeneratorRuntime.async(function callee$3$0$(context$4$0) { while (1) switch (context$4$0.prev = context$4$0.next) { case 0: context$4$0.next = 2; return _regeneratorRuntime.awrap(this.killInstruments()); case 2: this.launchResultDeferred.reject(new Error(ERR_NEVER_CHECKED_IN)); case 3: case 'end': return context$4$0.stop(); } }, null, _this2); }); } var fbsErrStr = '(FBSOpenApplicationErrorDomain error 8.)'; if (output.indexOf(fbsErrStr) !== -1) { _this2.gotFBSOpenApplicationError = true; } if (output.indexOf(ERR_AMBIGUOUS_DEVICE) !== -1) { var msg = ERR_AMBIGUOUS_DEVICE + ': \'' + _this2.simulatorSdkAndDevice + '\''; _this2.launchResultDeferred.reject(new Error(msg)); } }; this.proc.stderr.pipe((0, _through.through)(function (output) { actOnStderr(output); this.queue(output); })).pipe((0, _streams.errorStream)()).pipe((0, _streams.webSocketAlertStream)(this.webSocket)).pipe((0, _streams.dumpStream)()); // start waiting for instruments to launch successfully this.addSocketConnectTimer(this.launchTimeout.global, 'global', function callee$2$0() { return _regeneratorRuntime.async(function callee$2$0$(context$3$0) { while (1) switch (context$3$0.prev = context$3$0.next) { case 0: context$3$0.next = 2; return _regeneratorRuntime.awrap(this.killInstruments()); case 2: this.launchResultDeferred.reject(new Error(ERR_NEVER_CHECKED_IN)); case 3: case 'end': return context$3$0.stop(); } }, null, _this2); }); context$2$0.prev = 21; context$2$0.next = 24; return _regeneratorRuntime.awrap(launchResultPromise); case 24: context$2$0.prev = 24; this.clearSocketConnectTimers(); return context$2$0.finish(24); case 27: this.setExitListener(function (code, signal) { _this2.proc = null; var msg = code !== null ? 'code: ' + code : 'signal: ' + signal; _this2.onShutdownDeferred.reject(new Error('Abnormal exit with ' + msg)); }); case 28: case 'end': return context$2$0.stop(); } }, null, this, [[21,, 24, 27]]); } }, { key: 'launch', value: function launch() { var launchTries, errIsCatchable; return _regeneratorRuntime.async(function launch$(context$2$0) { while (1) switch (context$2$0.prev = context$2$0.next) { case 0: context$2$0.next = 2; return _regeneratorRuntime.awrap(this.configure()); case 2: launchTries = 0; case 3: // eslint-disable-line no-constant-condition launchTries++; _logger2['default'].debug('Attempting to launch instruments, this is try #' + launchTries); context$2$0.prev = 5; context$2$0.next = 8; return _regeneratorRuntime.awrap(this.launchOnce()); case 8: return context$2$0.abrupt('break', 34); case 11: context$2$0.prev = 11; context$2$0.t0 = context$2$0['catch'](5); _logger2['default'].error('Error launching instruments: ' + context$2$0.t0.message); errIsCatchable = context$2$0.t0.message === ERR_NEVER_CHECKED_IN || context$2$0.t0.message === ERR_CRASHED_ON_STARTUP; if (errIsCatchable) { context$2$0.next = 17; break; } throw context$2$0.t0; case 17: if (!(launchTries <= this.flakeyRetries)) { context$2$0.next = 32; break; } if (!this.gotFBSOpenApplicationError) { context$2$0.next = 25; break; } _logger2['default'].debug('Got the FBSOpenApplicationError, not killing the ' + 'sim but leaving it open so the app will launch'); this.gotFBSOpenApplicationError = false; // clear out for next launch context$2$0.next = 23; return _regeneratorRuntime.awrap(_bluebird2['default'].delay(1000)); case 23: context$2$0.next = 30; break; case 25: if (this.realDevice) { context$2$0.next = 28; break; } context$2$0.next = 28; return _regeneratorRuntime.awrap((0, _appiumIosSimulator.killAllSimulators)()); case 28: context$2$0.next = 30; return _regeneratorRuntime.awrap(_bluebird2['default'].delay(5000)); case 30: context$2$0.next = 33; break; case 32: _logger2['default'].errorAndThrow('We exceeded the number of retries allowed for ' + 'instruments to successfully start; failing launch'); case 33: if (true) { context$2$0.next = 3; break; } case 34: case 'end': return context$2$0.stop(); } }, null, this, [[5, 11]]); } }, { key: 'registerLaunch', value: function registerLaunch() { this.launchResultDeferred.resolve(); } }, { key: 'spawnInstruments', value: function spawnInstruments() { var traceDir, i, args, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, arg, space, flag, value, _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, _step2$value, env, iwdPath, instrumentsExecArgs; return _regeneratorRuntime.async(function spawnInstruments$(context$2$0) { while (1) switch (context$2$0.prev = context$2$0.next) { case 0: traceDir = undefined; i = 0; case 2: // loop while there are tracedirs to delete traceDir = _path2['default'].resolve(this.traceDir, 'instrumentscli' + i + '.trace'); context$2$0.next = 5; return _regeneratorRuntime.awrap(_appiumSupport.fs.exists(traceDir)); case 5: if (context$2$0.sent) { context$2$0.next = 7; break; } return context$2$0.abrupt('break', 10); case 7: i++; context$2$0.next = 2; break; case 10: args = ['-t', this.template, '-D', traceDir]; if (this.udid) { // real device, so specify udid args = args.concat(['-w', this.udid]); _logger2['default'].debug('Attempting to run app on real device with UDID \'' + this.udid + '\''); } if (!this.udid && this.simulatorSdkAndDevice) { // sim, so specify the sdk and device args = args.concat(['-w', this.simulatorSdkAndDevice]); _logger2['default'].debug('Attempting to run app on ' + this.simulatorSdkAndDevice); } args = args.concat([this.app]); if (!this.processArguments) { context$2$0.next = 63; break; } _logger2['default'].debug('Attempting to run app with process arguments: ' + JSON.stringify(this.processArguments)); // any additional stuff specified by the user if (!_lodash2['default'].isString(this.processArguments)) { context$2$0.next = 44; break; } if (!(this.processArguments.indexOf('-e ') === -1)) { context$2$0.next = 22; break; } _logger2['default'].debug('Plain string process arguments being pushed into arguments'); args.push(this.processArguments); context$2$0.next = 42; break; case 22: _logger2['default'].debug('Environment variables being pushed into arguments'); _iteratorNormalCompletion = true; _didIteratorError = false; _iteratorError = undefined; context$2$0.prev = 26; for (_iterator = _getIterator(this.processArguments.split('-e ')); !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { arg = _step.value; arg = arg.trim(); if (arg.length) { space = arg.indexOf(' '); flag = arg.substring(0, space); value = arg.substring(space + 1); args.push('-e', flag, value); } } context$2$0.next = 34; break; case 30: context$2$0.prev = 30; context$2$0.t0 = context$2$0['catch'](26); _didIteratorError = true; _iteratorError = context$2$0.t0; case 34: context$2$0.prev = 34; context$2$0.prev = 35; if (!_iteratorNormalCompletion && _iterator['return']) { _iterator['return'](); } case 37: context$2$0.prev = 37; if (!_didIteratorError) { context$2$0.next = 40; break; } throw _iteratorError; case 40: return context$2$0.finish(37); case 41: return context$2$0.finish(34); case 42: context$2$0.next = 63; break; case 44: _iteratorNormalCompletion2 = true; _didIteratorError2 = false; _iteratorError2 = undefined; context$2$0.prev = 47; // process arguments can also be a hash of flags and values for (_iterator2 = _getIterator(_lodash2['default'].pairs(this.processArguments)); !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { _step2$value = _slicedToArray(_step2.value, 2); flag = _step2$value[0]; value = _step2$value[1]; args.push('-e', flag, value); } context$2$0.next = 55; break; case 51: context$2$0.prev = 51; context$2$0.t1 = context$2$0['catch'](47); _didIteratorError2 = true; _iteratorError2 = context$2$0.t1; case 55: context$2$0.prev = 55; context$2$0.prev = 56; if (!_iteratorNormalCompletion2 && _iterator2['return']) { _iterator2['return'](); } case 58: context$2$0.prev = 58; if (!_didIteratorError2) { context$2$0.next = 61; break; } throw _iteratorError2; case 61: return context$2$0.finish(58); case 62: return context$2$0.finish(55); case 63: args = args.concat(['-e', 'UIASCRIPT', this.bootstrap]); args = args.concat(['-e', 'UIARESULTSPATH', this.tmpDir]); if (this.language) { args = args.concat(['-AppleLanguages (' + this.language + ')']); args = args.concat(['-NSLanguages (' + this.language + ')']); } if (this.locale) { args = args.concat(['-AppleLocale ' + this.locale]); } env = _lodash2['default'].clone(process.env); if (this.xcodeVersion.major >= 7 && !this.udid) { // iwd currently does not work with xcode7, setting withoutDelay to false _logger2['default'].info("On xcode 7.0+, instruments-without-delay does not work, " + "skipping instruments-without-delay"); this.withoutDelay = false; } context$2$0.next = 71; return _regeneratorRuntime.awrap((0, _utils.getIwdPath)(this.xcodeVersion.major)); case 71: iwdPath = context$2$0.sent; env.CA_DEBUG_TRANSACTIONS = 1; if (this.withoutDelay && !this.udid) { // sim, and using i-w-d env.DYLD_INSERT_LIBRARIES = _path2['default'].resolve(iwdPath, 'InstrumentsShim.dylib'); env.LIB_PATH = iwdPath; } instrumentsExecArgs = [this.instrumentsPath].concat(_toConsumableArray(args)); instrumentsExecArgs = _lodash2['default'].map(instrumentsExecArgs, function (arg) { if (arg === null) { throw new Error('A null value was passed as an arg to execute ' + 'instruments on the command line. A letiable is ' + 'probably not getting set. Array of command args: ' + JSON.stringify(instrumentsExecArgs)); } // escape any argument that has a space in it if (_lodash2['default'].isString(arg) && arg.indexOf(' ') !== -1) { return '"' + arg + '"'; } // otherwise just use the argument return arg; }); _logger2['default'].debug('Spawning instruments with command: \'' + instrumentsExecArgs.join(' ') + '\''); if (this.withoutDelay) { _logger2['default'].debug('And extra without-delay env: ' + JSON.stringify({ DYLD_INSERT_LIBRARIES: env.DYLD_INSERT_LIBRARIES, LIB_PATH: env.LIB_PATH })); } _logger2['default'].debug('And launch timeouts (in ms): ' + JSON.stringify(this.launchTimeout)); context$2$0.next = 81; return _regeneratorRuntime.awrap((0, _teen_process.spawn)(this.instrumentsPath, args, { env: env })); case 81: return context$2$0.abrupt('return', context$2$0.sent); case 82: case 'end': return context$2$0.stop(); } }, null, this, [[26, 30, 34, 42], [35,, 37, 41], [47, 51, 55, 63], [56,, 58, 62]]); } }, { key: 'addSocketConnectTimer', value: function addSocketConnectTimer(delay, type, doAction) { var socketConnectDelay = (0, _appiumSupport.cancellableDelay)(delay); socketConnectDelay.then(function () { _logger2['default'].warn('Instruments socket client never checked in; timing out (' + type + ')'); return doAction(); })['catch'](_bluebird2['default'].CancellationError, function () {}).done(); this.socketConnectDelays.push(socketConnectDelay); } }, { key: 'clearSocketConnectTimers', value: function clearSocketConnectTimers() { var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = _getIterator(this.socketConnectDelays), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var delay = _step3.value; delay.cancel(); } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3['return']) { _iterator3['return'](); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } this.socketConnectDelays = []; } }, { key: 'setExitListener', value: function setExitListener(exitListener) { if (!this.proc) return; if (this.exitListener) { this.proc.removeListener('exit', this.exitListener); } this.exitListener = exitListener; this.proc.on('exit', exitListener); } }, { key: 'killInstruments', value: function killInstruments() { var _this4 = this; if (!this.proc) return; _logger2['default'].debug('Kill Instruments process (pid: ' + this.proc.pid + ')'); return new _bluebird2['default'](function callee$2$0(resolve) { var wasTerminated, termDelay, termPromise; return _regeneratorRuntime.async(function callee$2$0$(context$3$0) { var _this3 = this; while (1) switch (context$3$0.prev = context$3$0.next) { case 0: wasTerminated = false; termDelay = (0, _appiumSupport.cancellableDelay)(this.termTimeout); termPromise = termDelay['catch'](_bluebird2['default'].CancellationError, function () {}); this.setExitListener(function () { _this3.proc = null; wasTerminated = true; termDelay.cancel(); resolve(); }); _logger2['default'].debug('Sending SIGTERM'); this.proc.kill('SIGTERM'); context$3$0.next = 8; return _regeneratorRuntime.awrap(termPromise); case 8: if (!wasTerminated) { _logger2['default'].warn('Instruments did not terminate after ' + this.termTimeout / 1000 + ' seconds!'); _logger2['default'].debug('Sending SIGKILL'); this.proc.kill('SIGKILL'); if (_lodash2['default'].isFunction(this.exitListener)) { this.exitListener(); } } case 9: case 'end': return context$3$0.stop(); } }, null, _this4); }); } /* PROCESS MANAGEMENT */ }, { key: 'shutdown', value: function shutdown() { return _regeneratorRuntime.async(function shutdown$(context$2$0) { while (1) switch (context$2$0.prev = context$2$0.next) { case 0: _logger2['default'].debug('Starting shutdown.'); context$2$0.next = 3; return _regeneratorRuntime.awrap(this.killInstruments()); case 3: this.onShutdownDeferred.resolve(); case 4: case 'end': return context$2$0.stop(); } }, null, this); } }]); return Instruments; })(); exports['default'] = Instruments; module.exports = exports['default']; // build up the arguments to use // {"processArguments": {"flag1": "value1", "flag2": "value2"}} // monitoring process termination //# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImxpYi9pbnN0cnVtZW50cy5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OzRCQUVzQixjQUFjOztzQkFDcEIsVUFBVTs7OztzQkFDWixRQUFROzs7O3VCQUNFLFNBQVM7O29CQUNoQixNQUFNOzs7OzZCQUNzQixnQkFBZ0I7OzJCQUMzQyxjQUFjOzs7O3dCQUNsQixVQUFVOzs7O2tDQUNVLHNCQUFzQjs7cUJBQ1csU0FBUzs7dUJBQ0EsV0FBVzs7UUFDaEYsUUFBUTs7QUFHZixJQUFNLG9CQUFvQixHQUFHLDhCQUE4QixDQUFDO0FBQzVELElBQU0sc0JBQXNCLEdBQUcsZ0NBQWdDLENBQUM7QUFDaEUsSUFBTSxvQkFBb0IsR0FBRyw0REFBNEQsQ0FBQzs7SUFFcEYsV0FBVztlQUFYLFdBQVc7Ozs7V0FFZSwwQkFBQyxJQUFJO1VBRTdCLHNCQUFzQjs7OztBQUQxQixnQkFBSSxHQUFHLG9CQUFFLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQzs7NkNBQ2MseUJBQU0sOEJBQThCLEVBQUU7OztBQUFyRSxrQ0FBc0I7O0FBQzFCLGdDQUFFLFFBQVEsQ0FBQyxJQUFJLEVBQUU7QUFDZiwyQkFBYSxFQUFFLEtBQUs7QUFDcEIsc0JBQVEsRUFBRSxzQkFBc0I7QUFDaEMsMEJBQVksRUFBRSxJQUFJO0FBQ2xCLDBCQUFZLEVBQUUsS0FBSztBQUNuQix1QkFBUyxFQUFFLElBQUk7QUFDZiwyQkFBYSxFQUFFLENBQUM7YUFDakIsQ0FBQyxDQUFDO2dEQUNJLElBQUksV0FBVyxDQUFDLElBQUksQ0FBQzs7Ozs7OztLQUM3Qjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQW9CVyxXQWxDUixXQUFXLENBa0NGLElBQUksRUFBRTs7OzBCQWxDZixXQUFXOztBQW1DYixRQUFJLEdBQUcsb0JBQUUsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO0FBQ3pCLHdCQUFFLFFBQVEsQ0FBQyxJQUFJLEVBQUU7QUFDZixpQkFBVyxFQUFFLElBQUk7QUFD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QUFHLElBQUksZUFBWSxDQUFDO0FBQ3BGLG9DQUFJLEtBQUssQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO0FBQzdCLG9CQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQztBQUMxQixvQkFBSSxvQkFBRSxVQUFVLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxFQUFFO0FBQ25DLHNCQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7aUJBQ3JCO2VBQ0Y7Ozs7Ozs7T0FDRixDQUFDLENBQUM7S0FDSjs7Ozs7V0FHYzs7OztBQUNiLGdDQUFJLEtBQUssQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDOzs2Q0FDMUIsSUFBSSxDQUFDLGVBQWUsRUFBRTs7O0FBQzVCLGdCQUFJLENBQUMsa0JBQWtCLENBQUMsT0FBTyxFQUFFLENBQUM7Ozs7Ozs7S0FDbkM7OztTQW5YRyxXQUFXOzs7cUJBc1hGLFdBQVciLCJmaWxlIjoibGliL2luc3RydW1lbnRzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiLy8gV3JhcHBlciBhcm91bmQgQXBwbGUncyBJbnN0cnVtZW50cyBhcHBcblxuaW1wb3J0IHsgc3Bhd24gfSBmcm9tICd0ZWVuX3Byb2Nlc3MnO1xuaW1wb3J0IGxvZyBmcm9tICcuL2xvZ2dlcic7XG5pbXBvcnQgXyBmcm9tICdsb2Rhc2gnO1xuaW1wb3J0IHsgdGhyb3VnaCB9IGZyb20gJ3Rocm91Z2gnO1xuaW1wb3J0IHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgeyBta2RpcnAsIGZzLCBjYW5jZWxsYWJsZURlbGF5IH0gZnJvbSAnYXBwaXVtLXN1cHBvcnQnO1xuaW1wb3J0IHhjb2RlIGZyb20gJ2FwcGl1bS14Y29kZSc7XG5pbXBvcnQgQiBmcm9tICdibHVlYmlyZCc7XG5pbXBvcnQgeyBraWxsQWxsU2ltdWxhdG9ycyB9IGZyb20gJ2FwcGl1bS1pb3Mtc2ltdWxhdG9yJztcbmltcG9ydCB7IGdldEluc3RydW1lbnRzUGF0aCwgcGFyc2VMYXVuY2hUaW1lb3V0LCBnZXRJd2RQYXRoIH0gZnJvbSAnLi91dGlscyc7XG5pbXBvcnQgeyBvdXRwdXRTdHJlYW0sIGVycm9yU3RyZWFtLCB3ZWJTb2NrZXRBbGVydFN0cmVhbSwgZHVtcFN0cmVhbSB9IGZyb20gJy4vc3RyZWFtcyc7XG5pbXBvcnQgJ2NvbG9ycyc7XG5cblxuY29uc3QgRVJSX05FVkVSX0NIRUNLRURfSU4gPSAnSW5zdHJ1bWVudHMgbmV2ZXIgY2hlY2tlZCBpbic7XG5jb25zdCBFUlJfQ1JBU0hFRF9PTl9TVEFSVFVQID0gJ0luc3RydW1lbnRzIGNyYXNoZWQgb24gc3RhcnR1cCc7XG5jb25zdCBFUlJfQU1CSUdVT1VTX0RFVklDRSA9ICdJbnN0cnVtZW50cyBVc2FnZSBFcnJvciA6IEFtYmlndW91cyBkZXZpY2UgbmFtZS9pZGVudGlmaWVyJztcblxuY2xhc3MgSW5zdHJ1bWVudHMge1xuICAvLyBzaW1wbGUgZmFjdG9yeSB3aXRoIHNhbmUgZGVmYXVsdHNcbiAgc3RhdGljIGFzeW5jIHF1aWNrSW5zdHJ1bWVudHMgKG9wdHMpIHtcbiAgICBvcHRzID0gXy5jbG9uZShvcHRzKTtcbiAgICBsZXQgeGNvZGVUcmFjZVRlbXBsYXRlUGF0aCA9IGF3YWl0IHhjb2RlLmdldEF1dG9tYXRpb25UcmFjZVRlbXBsYXRlUGF0aCgpO1xuICAgIF8uZGVmYXVsdHMob3B0cywge1xuICAgICAgbGF1bmNoVGltZW91dDogNjAwMDAsXG4gICAgICB0ZW1wbGF0ZTogeGNvZGVUcmFjZVRlbXBsYXRlUGF0aCxcbiAgICAgIHdpdGhvdXREZWxheTogdHJ1ZSxcbiAgICAgIHhjb2RlVmVyc2lvbjogJzguMScsXG4gICAgICB3ZWJTb2NrZXQ6IG51bGwsXG4gICAgICBmbGFrZXlSZXRyaWVzOiAyXG4gICAgfSk7XG4gICAgcmV0dXJuIG5ldyBJbnN0cnVtZW50cyhvcHRzKTtcbiAgfVxuXG4gIC8qXG4gICAqIG9wdHM6XG4gICAqICAgLSBhcHBcbiAgICogICAtIHRlcm1UaW1lb3V0IC0gZGVmYXVsdHMgdG8gNTAwMFxuICAgKiAgIC0gZmxha2V5UmV0cmllcyAtIGRlZmF1bHRzIHRvIDBcbiAgICogICAtIHVkaWRcbiAgICogICAtIGJvb3RzdHJhcFxuICAgKiAgIC0gdGVtcGxhdGVcbiAgICogICAtIHdpdGhvdXREZWxheVxuICAgKiAgIC0gcHJvY2Vzc0FyZ3VtZW50c1xuICAgKiAgIC0gc2ltdWxhdG9yU2RrQW5kRGV2aWNlXG4gICAqICAgLSB0bXBEaXIgLSBkZWZhdWx0cyB0byBgL3RtcC9hcHBpdW0taW5zdHJ1bWVudHNgXG4gICAqICAgLSB0cmFjZURpclxuICAgKiAgIC0gbGF1bmNoVGltZW91dCAtIGRlZmF1bHRzIHRvIDkwMDAwXG4gICAqICAgLSB3ZWJTb2NrZXRcbiAgICogICAtIGluc3RydW1lbnRzUGF0aFxuICAgKiAgIC0gcmVhbERldmljZSAtIHRydWUvZmFsc2UsIGRlZmF1bHRzIHRvIGZhbHNlXG4gICAqL1xuICBjb25zdHJ1Y3RvciAob3B0cykge1xuICAgIG9wdHMgPSBfLmNsb25lRGVlcChvcHRzKTtcbiAgICBfLmRlZmF1bHRzKG9wdHMsIHtcbiAgICAgIHRlcm1UaW1lb3V0OiA1MDAwLFxuICAgICAgdG1wRGlyOiAnL3RtcC9hcHBpdW0taW5zdHJ1bWVudHMnLFxuICAgICAgbGF1bmNoVGltZW91dDogOTAwMDAsXG4gICAgICBmbGFrZXlSZXRyaWVzOiAwLFxuICAgICAgcmVhbERldmljZTogZmFsc2VcbiAgICB9KTtcblxuICAgIC8vIGNvbmZpZ1xuICAgIGZvciAobGV0IGYgb2YgWydhcHAnLCAndGVybVRpbWVvdXQnLCAnZmxha2V5UmV0cmllcycsICd1ZGlkJywgJ2Jvb3RzdHJhcCcsXG4gICAgICAgICAgICAgICAgICAgJ3RlbXBsYXRlJywgJ3dpdGhvdXREZWxheScsICdwcm9jZXNzQXJndW1lbnRzJywgJ3JlYWxEZXZpY2UnLFxuICAgICAgICAgICAgICAgICAgICdzaW11bGF0b3JTZGtBbmREZXZpY2UnLCAndG1wRGlyJywgJ3RyYWNlRGlyJywgJ2xvY2FsZScsICdsYW5ndWFnZSddKSB7XG4gICAgICB0aGlzW2ZdID0gb3B0c1tmXTtcbiAgICB9XG4gICAgdGhpcy50cmFjZURpciA9IHRoaXMudHJhY2VEaXIgfHwgdGhpcy50bXBEaXI7XG4gICAgdGhpcy5sYXVuY2hUaW1lb3V0ID0gcGFyc2VMYXVuY2hUaW1lb3V0KG9wdHMubGF1bmNoVGltZW91dCk7XG5cbiAgICAvLyBzdGF0ZVxuICAgIHRoaXMucHJvYyA9IG51bGw7XG4gICAgdGhpcy53ZWJTb2NrZXQgPSBvcHRzLndlYlNvY2tldDtcbiAgICB0aGlzLmluc3RydW1lbnRzUGF0aCA9IG9wdHMuaW5zdHJ1bWVudHNQYXRoO1xuICAgIHRoaXMubGF1bmNoVHJpZXMgPSAwO1xuICAgIHRoaXMuc29ja2V0Q29ubmVjdERlbGF5cyA9IFtdO1xuICAgIHRoaXMuZ290RkJTT3BlbkFwcGxpY2F0aW9uRXJyb3IgPSBmYWxzZTtcbiAgICB0aGlzLm9uU2h1dGRvd24gPSBuZXcgQigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICB0aGlzLm9uU2h1dGRvd25EZWZlcnJlZCA9IHtyZXNvbHZlLCByZWplY3R9O1xuICAgIH0pO1xuICAgIC8vIGF2b2lkcyBVbmhhbmRsZWRFeGNlcHRpb25cbiAgICB0aGlzLm9uU2h1dGRvd24uY2F0Y2goKCkgPT4ge30pLmRvbmUoKTtcbiAgfVxuXG4gIGFzeW5jIGNvbmZpZ3VyZSAoKSB7XG4gICAgaWYgKCF0aGlzLnhjb2RlVmVyc2lvbikge1xuICAgICAgdGhpcy54Y29kZVZlcnNpb24gPSBhd2FpdCB4Y29kZS5nZXRWZXJzaW9uKHRydWUpO1xuICAgIH1cbiAgICBpZiAodGhpcy54Y29kZVZlcnNpb24udmVyc2lvbkZsb2F0ID09PSA2LjAgJiYgdGhpcy53aXRob3V0RGVsYXkpIHtcbiAgICAgIGxvZy5pbmZvKCdJbiB4Y29kZSA2LjAsIGluc3RydW1lbnRzLXdpdGhvdXQtZGVsYXkgZG9lcyBub3Qgd29yay4gJyArXG4gICAgICAgICAgICAgICAnSWYgdXNpbmcgQXBwaXVtLCB5b3UgY2FuIGRpc2FibGUgaW5zdHJ1bWVudHMtd2l0aG91dC1kZWxheSAnICtcbiAgICAgICAgICAgICAgICd3aXRoIHRoZSAtLW5hdGl2ZS1pbnN0cnVtZW50cy1saWIgc2VydmVyIGZsYWcnKTtcbiAgICB9XG4gICAgaWYgKHRoaXMueGNvZGVWZXJzaW9uLnZlcnNpb25TdHJpbmcgPT09ICc1LjAuMScpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignWGNvZGUgNS4wLjEgc2hpcHMgd2l0aCBhIGJyb2tlbiB2ZXJzaW9uIG9mICcgK1xuICAgICAgICAgICAgICAgICAgICAgICdJbnN0cnVtZW50cy4gcGxlYXNlIHVwZ3JhZGUgdG8gNS4wLjInKTtcbiAgICB9XG5cbiAgICBpZiAoIXRoaXMudGVtcGxhdGUpIHtcbiAgICAgIHRoaXMudGVtcGxhdGUgPSBhd2FpdCB4Y29kZS5nZXRBdXRvbWF0aW9uVHJhY2VUZW1wbGF0ZVBhdGgoKTtcbiAgICB9XG5cbiAgICBpZiAoIXRoaXMuaW5zdHJ1bWVudHNQYXRoKSB7XG4gICAgICB0aGlzLmluc3RydW1lbnRzUGF0aCA9IGF3YWl0IGdldEluc3RydW1lbnRzUGF0aCgpO1xuICAgIH1cbiAgfVxuXG4gIGFzeW5jIGxhdW5jaE9uY2UgKCkge1xuICAgIGxvZy5pbmZvKCdMYXVuY2hpbmcgaW5zdHJ1bWVudHMnKTtcbiAgICAvLyBwcmVwYXJlIHRlbXAgZGlyXG4gICAgYXdhaXQgZnMucmltcmFmKHRoaXMudG1wRGlyKTtcbiAgICBhd2FpdCBta2RpcnAodGhpcy50bXBEaXIpO1xuICAgIGF3YWl0IG1rZGlycCh0aGlzLnRyYWNlRGlyKTtcblxuICAgIHRoaXMuZXhpdExpc3RlbmVyID0gbnVsbDtcbiAgICB0aGlzLnByb2MgPSBhd2FpdCB0aGlzLnNwYXduSW5zdHJ1bWVudHMoKTtcbiAgICB0aGlzLnByb2Mub24oJ2V4aXQnLCAoY29kZSwgc2lnbmFsKSA9PiB7XG4gICAgICBjb25zdCBtc2cgPSBjb2RlICE9PSBudWxsID8gYGNvZGU6ICR7Y29kZX1gIDogYHNpZ25hbDogJHtzaWduYWx9YDtcbiAgICAgIGxvZy5kZWJ1ZyhgSW5zdHJ1bWVudHMgZXhpdGVkIHdpdGggJHttc2d9YCk7XG4gICAgfSk7XG5cbiAgICAvLyBzZXQgdXAgdGhlIHByb21pc2UgdG8gaGFuZGxlIGxhdW5jaFxuICAgIGxldCBsYXVuY2hSZXN1bHRQcm9taXNlID0gbmV3IEIoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgdGhpcy5sYXVuY2hSZXN1bHREZWZlcnJlZCA9IHtyZXNvbHZlLCByZWplY3R9O1xuICAgIH0pO1xuXG4gICAgLy8gVGhlcmUgd2FzIGEgc3BlY2lhbCBjYXNlIGZvciBpZ25vcmVTdGFydHVwRXhpdFxuICAgIC8vIGJ1dCBpdCBpcyBub3QgbmVlZGVkIGFueW1vcmUsIHlvdSBtYXkganVzdCBsaXN0ZW4gZm9yIGV4aXQuXG4gICAgdGhpcy5zZXRFeGl0TGlzdGVuZXIoKCkgPT4ge1xuICAgICAgdGhpcy5wcm9jID0gbnVsbDtcbiAgICAgIHRoaXMubGF1bmNoUmVzdWx0RGVmZXJyZWQucmVqZWN0KG5ldyBFcnJvcihFUlJfQ1JBU0hFRF9PTl9TVEFSVFVQKSk7XG4gICAgfSk7XG5cbiAgICB0aGlzLnByb2Mub24oJ2Vycm9yJywgKGVycikgPT4ge1xuICAgICAgbG9nLmRlYnVnKGBFcnJvciB3aXRoIGluc3RydW1lbnRzIHByb2M6ICR7ZXJyLm1lc3NhZ2V9YCk7XG4gICAgICBpZiAoZXJyLm1lc3NhZ2UuaW5kZXhPZignRU5PRU5UJykgIT09IC0xKSB7XG4gICAgICAgIHRoaXMucHJvYyA9IG51bGw7IC8vIG90aGVyd2lzZSB3ZSdsbCB0cnkgdG8gc2VuZCBzaWdraWxsXG4gICAgICAgIGxvZy5lcnJvcihgVW5hYmxlIHRvIHNwYXduIGluc3RydW1lbnRzOiAke2Vyci5tZXNzYWdlfWApO1xuICAgICAgICB0aGlzLmxhdW5jaFJlc3VsdERlZmVycmVkLnJlamVjdChlcnIpO1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgdGhpcy5wcm9jLnN0ZG91dC5zZXRFbmNvZGluZygndXRmOCcpO1xuICAgIHRoaXMucHJvYy5zdGRvdXQucGlwZShvdXRwdXRTdHJlYW0oKSkucGlwZShkdW1wU3RyZWFtKCkpO1xuXG4gICAgdGhpcy5wcm9jLnN0ZGVyci5zZXRFbmNvZGluZygndXRmOCcpO1xuICAgIGxldCBhY3RPblN0ZGVyciA9IChvdXRwdXQpID0+IHtcbiAgICAgIGlmICh0aGlzLmxhdW5jaFRpbWVvdXQuYWZ0ZXJTaW1MYXVuY2ggJiYgb3V0cHV0ICYmIG91dHB1dC5tYXRjaCgvQ0xUaWxlc01hbmFnZXJDbGllbnQ6IGluaXRpYWxpemUvKSkge1xuICAgICAgICB0aGlzLmFkZFNvY2tldE