|  | # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  |  | 
|  | import logging | 
|  | import os | 
|  | import re | 
|  | import sys | 
|  | import time | 
|  |  | 
|  | import android_commands | 
|  | import cmd_helper | 
|  | import constants | 
|  | import ports | 
|  |  | 
|  | from pylib import pexpect | 
|  |  | 
|  |  | 
|  | def _MakeBinaryPath(build_type, binary_name): | 
|  | return os.path.join(cmd_helper.OutDirectory.get(), build_type, binary_name) | 
|  |  | 
|  |  | 
|  | class Forwarder(object): | 
|  | """Class to manage port forwards from the device to the host.""" | 
|  |  | 
|  | # Unix Abstract socket path: | 
|  | _DEVICE_ADB_CONTROL_PORT = 'chrome_device_forwarder' | 
|  | _TIMEOUT_SECS = 30 | 
|  |  | 
|  | _DEVICE_FORWARDER_PATH = constants.TEST_EXECUTABLE_DIR + '/device_forwarder' | 
|  |  | 
|  | def __init__(self, adb, build_type): | 
|  | """Forwards TCP ports on the device back to the host. | 
|  |  | 
|  | Works like adb forward, but in reverse. | 
|  |  | 
|  | Args: | 
|  | adb: Instance of AndroidCommands for talking to the device. | 
|  | build_type: 'Release' or 'Debug'. | 
|  | """ | 
|  | assert build_type in ('Release', 'Debug') | 
|  | self._adb = adb | 
|  | self._host_to_device_port_map = dict() | 
|  | self._device_process = None | 
|  | self._host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder') | 
|  | self._device_forwarder_path = _MakeBinaryPath( | 
|  | build_type, 'device_forwarder') | 
|  |  | 
|  | def Run(self, port_pairs, tool, host_name): | 
|  | """Runs the forwarder. | 
|  |  | 
|  | Args: | 
|  | port_pairs: A list of tuples (device_port, host_port) to forward. Note | 
|  | that you can specify 0 as a device_port, in which case a | 
|  | port will by dynamically assigned on the device. You can | 
|  | get the number of the assigned port using the | 
|  | DevicePortForHostPort method. | 
|  | tool: Tool class to use to get wrapper, if necessary, for executing the | 
|  | forwarder (see valgrind_tools.py). | 
|  | host_name: Address to forward to, must be addressable from the | 
|  | host machine. Usually use loopback '127.0.0.1'. | 
|  |  | 
|  | Raises: | 
|  | Exception on failure to forward the port. | 
|  | """ | 
|  | host_adb_control_port = ports.AllocateTestServerPort() | 
|  | if not host_adb_control_port: | 
|  | raise Exception('Failed to allocate a TCP port in the host machine.') | 
|  | self._adb.PushIfNeeded( | 
|  | self._device_forwarder_path, Forwarder._DEVICE_FORWARDER_PATH) | 
|  | redirection_commands = [ | 
|  | '%d:%d:%d:%s' % (host_adb_control_port, device, host, | 
|  | host_name) for device, host in port_pairs] | 
|  | logging.info('Command format: <ADB port>:<Device port>' + | 
|  | '[:<Forward to port>:<Forward to address>]') | 
|  | logging.info('Forwarding using commands: %s', redirection_commands) | 
|  | if cmd_helper.RunCmd( | 
|  | ['adb', '-s', self._adb._adb.GetSerialNumber(), 'forward', | 
|  | 'tcp:%s' % host_adb_control_port, | 
|  | 'localabstract:%s' % Forwarder._DEVICE_ADB_CONTROL_PORT]) != 0: | 
|  | raise Exception('Error while running adb forward.') | 
|  |  | 
|  | (exit_code, output) = self._adb.GetShellCommandStatusAndOutput( | 
|  | '%s %s' % (Forwarder._DEVICE_FORWARDER_PATH, | 
|  | Forwarder._DEVICE_ADB_CONTROL_PORT)) | 
|  | if exit_code != 0: | 
|  | raise Exception( | 
|  | 'Failed to start device forwarder:\n%s' % '\n'.join(output)) | 
|  |  | 
|  | for redirection_command in redirection_commands: | 
|  | (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( | 
|  | [self._host_forwarder_path, redirection_command]) | 
|  | if exit_code != 0: | 
|  | raise Exception('%s exited with %d:\n%s' % ( | 
|  | self._host_forwarder_path, exit_code, '\n'.join(output))) | 
|  | tokens = output.split(':') | 
|  | if len(tokens) != 2: | 
|  | raise Exception('Unexpected host forwarder output "%s", ' + | 
|  | 'expected "device_port:host_port"' % output) | 
|  | device_port = int(tokens[0]) | 
|  | host_port = int(tokens[1]) | 
|  | self._host_to_device_port_map[host_port] = device_port | 
|  | logging.info('Forwarding device port: %d to host port: %d.', device_port, | 
|  | host_port) | 
|  |  | 
|  | @staticmethod | 
|  | def KillHost(build_type): | 
|  | logging.info('Killing host_forwarder.') | 
|  | host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder') | 
|  | (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( | 
|  | [host_forwarder_path, 'kill-server']) | 
|  | if exit_code != 0: | 
|  | (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( | 
|  | ['pkill', 'host_forwarder']) | 
|  | if exit_code != 0: | 
|  | raise Exception('%s exited with %d:\n%s' % ( | 
|  | host_forwarder_path, exit_code, '\n'.join(output))) | 
|  |  | 
|  | @staticmethod | 
|  | def KillDevice(adb): | 
|  | logging.info('Killing device_forwarder.') | 
|  | if not adb.FileExistsOnDevice(Forwarder._DEVICE_FORWARDER_PATH): | 
|  | return | 
|  | (exit_code, output) = adb.GetShellCommandStatusAndOutput( | 
|  | '%s kill-server' % Forwarder._DEVICE_FORWARDER_PATH) | 
|  | # TODO(pliard): Remove the following call to KillAllBlocking() when we are | 
|  | # sure that the old version of device_forwarder (not supporting | 
|  | # 'kill-server') is not running on the bots anymore. | 
|  | timeout_sec = 5 | 
|  | processes_killed = adb.KillAllBlocking('device_forwarder', timeout_sec) | 
|  | if not processes_killed: | 
|  | pids = adb.ExtractPid('device_forwarder') | 
|  | if pids: | 
|  | raise Exception('Timed out while killing device_forwarder') | 
|  |  | 
|  | def DevicePortForHostPort(self, host_port): | 
|  | """Get the device port that corresponds to a given host port.""" | 
|  | return self._host_to_device_port_map.get(host_port) | 
|  |  | 
|  | def Close(self): | 
|  | """Terminate the forwarder process.""" | 
|  | if self._device_process: | 
|  | self._device_process.close() | 
|  | self._device_process = None |