Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/android/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ const help = `
--target <id> ........... Use a specific target
--connect ............... Tie process to app process
--forward <port:port> ... Forward a port from device to host

ADB operations use a default timeout of 5000 milliseconds. Set
NATIVE_RUN_ADB_TIMEOUT to change it for all ADB operations.
The legacy ADB_TIMEOUT variable is also supported, but
NATIVE_RUN_ADB_TIMEOUT takes precedence when both are set.
Invalid or non-positive values fall back to the 5000 millisecond default.
`;

export async function run(args: readonly string[]): Promise<void> {
Expand Down
39 changes: 39 additions & 0 deletions src/android/utils/__tests__/adb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,45 @@ import * as os from 'os';
import type * as adb from '../adb';

describe('android/utils/adb', () => {
describe('getAdbTimeout', () => {
let adbUtils: typeof adb;

beforeEach(async () => {
jest.resetModules();
adbUtils = await import('../adb');
});

afterEach(() => {
delete process.env.NATIVE_RUN_ADB_TIMEOUT;
delete process.env.ADB_TIMEOUT;
});

it('should read the timeout from NATIVE_RUN_ADB_TIMEOUT', () => {
process.env.NATIVE_RUN_ADB_TIMEOUT = '12345';

expect(adbUtils.getAdbTimeout()).toBe(12345);
});

it('should prefer NATIVE_RUN_ADB_TIMEOUT over the legacy ADB_TIMEOUT', () => {
process.env.NATIVE_RUN_ADB_TIMEOUT = '12345';
process.env.ADB_TIMEOUT = '67890';

expect(adbUtils.getAdbTimeout()).toBe(12345);
});

it('should support the legacy ADB_TIMEOUT variable', () => {
process.env.ADB_TIMEOUT = '12345';

expect(adbUtils.getAdbTimeout()).toBe(12345);
});

it('should fall back to 5000 when the timeout is invalid', () => {
process.env.ADB_TIMEOUT = 'nope';

expect(adbUtils.getAdbTimeout()).toBe(5000);
});
});

describe('parseAdbDevices', () => {
let adbUtils: typeof adb;

Expand Down
26 changes: 16 additions & 10 deletions src/android/utils/adb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export async function getDevices(sdk: SDK): Promise<Device[]> {
const args = ['devices', '-l'];

debug('Invoking adb with args: %O', args);
const stdout = await execAdb(sdk, args, { timeout: 5000 });
const stdout = await execAdb(sdk, args);

const devices = parseAdbDevices(stdout);

Expand Down Expand Up @@ -85,7 +85,7 @@ export async function getDeviceProperty(sdk: SDK, device: Device, property: stri
const args = ['-s', device.serial, 'shell', 'getprop', property];

debug('Invoking adb with args: %O', args);
const stdout = await execAdb(sdk, args, { timeout: 5000 });
const stdout = await execAdb(sdk, args);

return stdout.trim();
}
Expand All @@ -95,7 +95,7 @@ export async function getDeviceProperties(sdk: SDK, device: Device): Promise<Dev
const args = ['-s', device.serial, 'shell', 'getprop'];

debug('Invoking adb with args: %O', args);
const stdout = await execAdb(sdk, args, { timeout: 5000 });
const stdout = await execAdb(sdk, args);

const re = /^\[([a-z0-9.]+)\]: \[(.*)\]$/;
const propAllowList = [...ADB_GETPROP_MAP.keys()];
Expand Down Expand Up @@ -274,7 +274,7 @@ export async function startActivity(
const args = ['-s', device.serial, 'shell', 'am', 'start', '-W', '-n', `${packageName}/${activityName}`];

debug('Invoking adb with args: %O', args);
await execAdb(sdk, args, { timeout: 5000 });
await execAdb(sdk, args);
}

export function parseAdbDevices(output: string): Device[] {
Expand Down Expand Up @@ -340,15 +340,20 @@ export async function forwardPorts(sdk: SDK, device: Device, ports: Ports): Prom
const args = ['-s', device.serial, 'reverse', `tcp:${ports.device}`, `tcp:${ports.host}`];

debug('Invoking adb with args: %O', args);
await execAdb(sdk, args, { timeout: 5000 });
await execAdb(sdk, args);
}

export async function unforwardPorts(sdk: SDK, device: Device, ports: Ports): Promise<void> {
const debug = Debug(`${modulePrefix}:${unforwardPorts.name}`);
const args = ['-s', device.serial, 'reverse', '--remove', `tcp:${ports.device}`];

debug('Invoking adb with args: %O', args);
await execAdb(sdk, args, { timeout: 5000 });
await execAdb(sdk, args);
}

export function getAdbTimeout(): number {
const timeout = Number.parseFloat(process.env.NATIVE_RUN_ADB_TIMEOUT || process.env.ADB_TIMEOUT || '5000');
return Number.isFinite(timeout) && timeout > 0 ? timeout : 5000;
}

export interface ExecADBOptions {
Expand All @@ -358,16 +363,17 @@ export interface ExecADBOptions {
export async function execAdb(sdk: SDK, args: string[], options: ExecADBOptions = {}): Promise<string> {
const debug = Debug(`${modulePrefix}:${execAdb.name}`);
let timer: NodeJS.Timer | undefined;
const timeout = typeof options.timeout === 'number' ? options.timeout : getAdbTimeout();

const retry = async () => {
const msg = `ADBs is unresponsive after ${options.timeout}ms, killing server and retrying...\n`;
const msg = `ADBs is unresponsive after ${timeout}ms, killing server and retrying...\n`;
if (process.argv.includes('--json')) {
debug(msg);
} else {
process.stderr.write(msg);
}

debug('ADB timeout of %O reached, killing server and retrying...', options.timeout);
debug('ADB timeout of %O reached, killing server and retrying...', timeout);
debug('Invoking adb with args: %O', ['kill-server']);
await execAdb(sdk, ['kill-server']);
debug('Invoking adb with args: %O', ['start-server']);
Expand All @@ -392,8 +398,8 @@ export async function execAdb(sdk: SDK, args: string[], options: ExecADBOptions
};

return new Promise((resolve, reject) => {
if (options.timeout) {
timer = setTimeout(() => retry().then(resolve, reject), options.timeout);
if (timeout) {
timer = setTimeout(() => retry().then(resolve, reject), timeout);
}

run().then(resolve, (err) => {
Expand Down