UNPKG

appium-adb

Version:

Android Debug Bridge interface

183 lines 7.29 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.listProcessStatus = listProcessStatus; exports.getProcessNameById = getProcessNameById; exports.getProcessIdsByName = getProcessIdsByName; exports.killProcessesByName = killProcessesByName; exports.killProcessByPID = killProcessByPID; exports.processExists = processExists; const lodash_1 = __importDefault(require("lodash")); const logger_1 = require("../logger"); const bluebird_1 = __importDefault(require("bluebird")); const PID_COLUMN_TITLE = 'PID'; const PROCESS_NAME_COLUMN_TITLE = 'NAME'; const PS_TITLE_PATTERN = new RegExp(`^(.*\\b${PID_COLUMN_TITLE}\\b.*\\b${PROCESS_NAME_COLUMN_TITLE}\\b.*)$`, 'm'); /** * At some point of time Google has changed the default `ps` behaviour, so it only * lists processes that belong to the current shell user rather to all * users. It is necessary to execute ps with -A command line argument * to mimic the previous behaviour. * * @returns the output of `ps` command where all processes are included */ async function listProcessStatus() { if (!lodash_1.default.isBoolean(this._doesPsSupportAOption)) { try { this._doesPsSupportAOption = /^-A\b/m.test(await this.shell(['ps', '--help'])); } catch (e) { const error = e; logger_1.log.debug(error.stack); this._doesPsSupportAOption = false; } } return await this.shell(this._doesPsSupportAOption ? ['ps', '-A'] : ['ps']); } /** * Returns process name for the given process identifier * * @param pid - The valid process identifier * @returns The process name * @throws {Error} If the given PID is either invalid or is not present * in the active processes list */ async function getProcessNameById(pid) { // @ts-ignore This validation works as expected if (isNaN(Number(pid))) { throw new Error(`The PID value must be a valid number. '${pid}' is given instead`); } const numericPid = parseInt(`${pid}`, 10); const stdout = await this.listProcessStatus(); const titleMatch = PS_TITLE_PATTERN.exec(stdout); if (!titleMatch) { logger_1.log.debug(stdout); throw new Error(`Could not get the process name for PID '${numericPid}'`); } const allTitles = titleMatch[1].trim().split(/\s+/); const pidIndex = allTitles.indexOf(PID_COLUMN_TITLE); // it might not be stable to take NAME by index, because depending on the // actual SDK the ps output might not contain an abbreviation for the S flag: // USER PID PPID VSIZE RSS WCHAN PC NAME // USER PID PPID VSIZE RSS WCHAN PC S NAME const nameOffset = allTitles.indexOf(PROCESS_NAME_COLUMN_TITLE) - allTitles.length; const pidRegex = new RegExp(`^(.*\\b${numericPid}\\b.*)$`, 'gm'); let matchedLine; while ((matchedLine = pidRegex.exec(stdout))) { const items = matchedLine[1].trim().split(/\s+/); if (parseInt(items[pidIndex], 10) === numericPid && items[items.length + nameOffset]) { return items[items.length + nameOffset]; } } logger_1.log.debug(stdout); throw new Error(`Could not get the process name for PID '${numericPid}'`); } /** * Get the list of process ids for the particular process on the device under test. * * @param name - The part of process name. * @returns The list of matched process IDs or an empty list. */ async function getProcessIdsByName(name) { logger_1.log.debug(`Getting IDs of all '${name}' processes`); const stdout = await this.listProcessStatus(); const titleMatch = PS_TITLE_PATTERN.exec(stdout); if (!titleMatch) { logger_1.log.debug(stdout); throw new Error(`Could not parse process list for name '${name}'`); } const allTitles = titleMatch[1].trim().split(/\s+/); const pidIndex = allTitles.indexOf(PID_COLUMN_TITLE); const nameIndex = allTitles.indexOf(PROCESS_NAME_COLUMN_TITLE); const pids = []; const lines = stdout.split('\n'); for (const line of lines) { const items = line.trim().split(/\s+/); if (items.length > Math.max(pidIndex, nameIndex) && items[nameIndex] && items[pidIndex] && items[nameIndex] === name) { const pid = parseInt(items[pidIndex], 10); if (!isNaN(pid)) { pids.push(pid); } } } return lodash_1.default.uniq(pids); } /** * Kill all processes with the given name on the device under test. * * @param name - The part of process name. * @param signal - The signal to send to the process. Default is 'SIGTERM' ('15'). * @throws {Error} If the processes cannot be killed. */ async function killProcessesByName(name, signal = 'SIGTERM') { try { logger_1.log.debug(`Attempting to kill all ${name} processes`); const pids = await this.getProcessIdsByName(name); if (lodash_1.default.isEmpty(pids)) { logger_1.log.info(`No '${name}' process has been found`); } else { await bluebird_1.default.all(pids.map((p) => this.killProcessByPID(p, signal))); } } catch (e) { const err = e; throw new Error(`Unable to kill ${name} processes. Original error: ${err.message}`); } } /** * Kill the particular process on the device under test. * The current user is automatically switched to root if necessary in order * to properly kill the process. * * @param pid - The ID of the process to be killed. * @param signal - The signal to send to the process. Default is 'SIGTERM' ('15'). * @throws {Error} If the process cannot be killed. */ async function killProcessByPID(pid, signal = 'SIGTERM') { logger_1.log.debug(`Attempting to kill process ${pid}`); const noProcessFlag = 'No such process'; try { // Check if the process exists and throw an exception otherwise await this.shell(['kill', `-${signal}`, `${pid}`]); } catch (e) { const err = e; if (lodash_1.default.includes(err.stderr, noProcessFlag)) { return; } if (!lodash_1.default.includes(err.stderr, 'Operation not permitted')) { throw err; } logger_1.log.info(`Cannot kill PID ${pid} due to insufficient permissions. Retrying as root`); try { await this.shell(['kill', `${pid}`], { privileged: true, }); } catch (e1) { const err1 = e1; if (lodash_1.default.includes(err1.stderr, noProcessFlag)) { return; } throw err1; } } } /** * Check whether the process with the particular name is running on the device * under test. * * @param processName - The name of the process to be checked. * @returns True if the given process is running. * @throws {Error} If the given process name is not a valid class name. */ async function processExists(processName) { return !lodash_1.default.isEmpty(await this.getProcessIdsByName(processName)); } //# sourceMappingURL=process-commands.js.map