appium-adb
Version:
Android Debug Bridge interface
183 lines • 7.29 kB
JavaScript
;
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