diff options
author | pliard@chromium.org <pliard@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-12 12:46:37 +0000 |
---|---|---|
committer | pliard@chromium.org <pliard@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-12 12:46:37 +0000 |
commit | 4c0871855cba62292ca55b2f7467d075247c725a (patch) | |
tree | 8b96fdf32f0c50b5e9e97e438f83921a9158dac4 /build/android/pylib/forwarder.py | |
parent | a756e8791d0ef190b89a80cb71c0722270f0731d (diff) | |
download | chromium_src-4c0871855cba62292ca55b2f7467d075247c725a.zip chromium_src-4c0871855cba62292ca55b2f7467d075247c725a.tar.gz chromium_src-4c0871855cba62292ca55b2f7467d075247c725a.tar.bz2 |
Support HTTP test-server based net unit tests on Android.
This changes forwarder2 to support both test-server spawner and HTTP server
forwarding.
The main issue was that the device_forwarder was killed when instantiating a
second Forwarder (Python object).
Test server based unit tests require two device-to-host redirections, one for
the test server spawner and one for the HTTP server.
The port used by the HTTP server is allocated dynamically which means that we
can't know the full forwarding configuration before we spawn a HTTP server
(through the test server spawner).
This CL changes the forwarder to let it forward new ports while it is running
by making host_forwarder a daemon. This is similar to how ADB works.
This also means that a single host_forwarder process (daemon) can now handle
multiple devices.
BUG=146979
Review URL: https://chromiumcodereview.appspot.com/11269036
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@167167 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'build/android/pylib/forwarder.py')
-rw-r--r-- | build/android/pylib/forwarder.py | 244 |
1 files changed, 104 insertions, 140 deletions
diff --git a/build/android/pylib/forwarder.py b/build/android/pylib/forwarder.py index bc41db3..a0dc5f2 100644 --- a/build/android/pylib/forwarder.py +++ b/build/android/pylib/forwarder.py @@ -15,6 +15,11 @@ import ports from pylib import pexpect + +def _MakeBinaryPath(build_type, binary_name): + return os.path.join(constants.CHROME_DIR, 'out', build_type, binary_name) + + class Forwarder(object): """Class to manage port forwards from the device to the host.""" @@ -24,13 +29,27 @@ class Forwarder(object): _DEVICE_ADB_CONTROL_PORT = 'chrome_device_forwarder' _TIMEOUT_SECS = 30 - def __init__(self, adb, port_pairs, tool, host_name, build_type): + 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 @@ -40,159 +59,104 @@ class Forwarder(object): 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'. - build_type: 'Release' or 'Debug'. Raises: Exception on failure to forward the port. """ - self._adb = adb - self._host_to_device_port_map = dict() - self._host_process = None - self._device_process = None - self._adb_forward_process = None - - self._host_adb_control_port = ports.AllocateTestServerPort() - if not self._host_adb_control_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.') - adb.PushIfNeeded( - os.path.join(constants.CHROME_DIR, 'out', build_type, - 'device_forwarder'), - Forwarder._DEVICE_FORWARDER_PATH) - self._host_forwarder_path = os.path.join(constants.CHROME_DIR, - 'out', - build_type, - 'host_forwarder') - forward_string = ['%d:%d:%s' % - (device, host, host_name) for device, host in port_pairs] - logging.info('Forwarding ports: %s', forward_string) - timeout_sec = 5 - host_pattern = 'host_forwarder.*' + ' '.join(forward_string) - # TODO(felipeg): Rather than using a blocking kill() here, the device - # forwarder could try to bind the Unix Domain Socket until it succeeds or - # while it fails because the socket is already bound (with appropriate - # timeout handling obviously). - self._KillHostForwarderBlocking(host_pattern, timeout_sec) - self._KillDeviceForwarderBlocking(timeout_sec) - self._adb_forward_process = pexpect.spawn( - 'adb', ['-s', - adb._adb.GetSerialNumber(), - 'forward', - 'tcp:%s' % self._host_adb_control_port, - 'localabstract:%s' % Forwarder._DEVICE_ADB_CONTROL_PORT]) - self._device_process = pexpect.spawn( - 'adb', ['-s', - adb._adb.GetSerialNumber(), - 'shell', - '%s %s -D --adb_sock=%s' % ( - tool.GetUtilWrapper(), - Forwarder._DEVICE_FORWARDER_PATH, - Forwarder._DEVICE_ADB_CONTROL_PORT)]) - - device_success_re = re.compile('Starting Device Forwarder.') - device_failure_re = re.compile('.*:ERROR:(.*)') - index = self._device_process.expect([device_success_re, - device_failure_re, - pexpect.EOF, - pexpect.TIMEOUT], - Forwarder._TIMEOUT_SECS) - if index == 1: - # Failure - error_msg = str(self._device_process.match.group(1)) - logging.error(self._device_process.before) - self._CloseProcess() - raise Exception('Failed to start Device Forwarder with Error: %s' % - error_msg) - elif index == 2: - logging.error(self._device_process.before) - self._CloseProcess() - raise Exception('Unexpected EOF while trying to start Device Forwarder.') - elif index == 3: - logging.error(self._device_process.before) - self._CloseProcess() - raise Exception('Timeout while trying start Device Forwarder') - - self._host_process = pexpect.spawn(self._host_forwarder_path, - ['--adb_port=%s' % ( - self._host_adb_control_port)] + - forward_string) - - # Read the output of the command to determine which device ports where - # forwarded to which host ports (necessary if - host_success_re = re.compile('Forwarding device port (\d+) to host (\d+):') - host_failure_re = re.compile('Couldn\'t start forwarder server for port ' - 'spec: (\d+):(\d+)') - for pair in port_pairs: - index = self._host_process.expect([host_success_re, - host_failure_re, - pexpect.EOF, - pexpect.TIMEOUT], - Forwarder._TIMEOUT_SECS) - if index == 0: - # Success - device_port = int(self._host_process.match.group(1)) - host_port = int(self._host_process.match.group(2)) - self._host_to_device_port_map[host_port] = device_port - logging.info("Forwarding device port: %d to host port: %d." % - (device_port, host_port)) - elif index == 1: - # Failure - device_port = int(self._host_process.match.group(1)) - host_port = int(self._host_process.match.group(2)) - self._CloseProcess() - raise Exception('Failed to forward port %d to %d' % (device_port, - host_port)) + 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.') + + if not self._adb.ExtractPid('device_forwarder'): + # TODO(pliard): Get rid of pexpect here and make device_forwarder a daemon + # with a blocking CLI process that exits with a proper exit code and not + # while the daemon is still setting up. This would be similar to how + # host_forwarder works. + self._device_process = pexpect.spawn( + 'adb', ['-s', + self._adb._adb.GetSerialNumber(), + 'shell', + '%s %s -D --adb_sock=%s' % ( + tool.GetUtilWrapper(), + Forwarder._DEVICE_FORWARDER_PATH, + Forwarder._DEVICE_ADB_CONTROL_PORT)]) + device_success_re = re.compile('Starting Device Forwarder.') + device_failure_re = re.compile('.*:ERROR:(.*)') + index = self._device_process.expect([device_success_re, + device_failure_re, + pexpect.EOF, + pexpect.TIMEOUT], + Forwarder._TIMEOUT_SECS) + if index == 1: + error_msg = str(self._device_process.match.group(1)) + logging.error(self._device_process.before) + self._device_process.close() + raise Exception('Failed to start Device Forwarder with Error: %s' % + error_msg) elif index == 2: - logging.error(self._host_process.before) - self._CloseProcess() - raise Exception('Unexpected EOF while trying to forward ports %s' % - port_pairs) + logging.error(self._device_process.before) + self._device_process.close() + raise Exception( + 'Unexpected EOF while trying to start Device Forwarder.') elif index == 3: - logging.error(self._host_process.before) - self._CloseProcess() - raise Exception('Timeout while trying to forward ports %s' % port_pairs) - - def _KillHostForwarderBlocking(self, host_pattern, timeout_sec): - """Kills any existing host forwarders using the provided pattern. - - Note that this waits until the process terminates. - """ - cmd_helper.RunCmd(['pkill', '-f', host_pattern]) - elapsed = 0 - wait_period = 0.1 - while not cmd_helper.RunCmd(['pgrep', '-f', host_pattern]) and ( - elapsed < timeout_sec): - time.sleep(wait_period) - elapsed += wait_period - if elapsed >= timeout_sec: - raise Exception('Timed out while killing ' + host_pattern) - - def _KillDeviceForwarderBlocking(self, timeout_sec): - """Kills any existing device forwarders. - - Note that this waits until the process terminates. - """ - processes_killed = self._adb.KillAllBlocking( - 'device_forwarder', timeout_sec) + logging.error(self._device_process.before) + self._device_process.close() + raise Exception('Timeout while trying start Device Forwarder') + + 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: %s' % (self._host_forwarder_path, + exit_code, 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): + host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder') + (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( + [host_forwarder_path, 'kill-server']) + if exit_code != 0: + raise Exception('%s exited with %d: %s' % (host_forwarder_path, + exit_code, output)) + + @staticmethod + def KillDevice(adb): + logging.info('Killing device_forwarder.') + timeout_sec = 5 + processes_killed = adb.KillAllBlocking('device_forwarder', timeout_sec) if not processes_killed: - pids = self._adb.ExtractPid('device_forwarder') + pids = adb.ExtractPid('device_forwarder') if pids: raise Exception('Timed out while killing device_forwarder') - def _CloseProcess(self): - if self._host_process: - self._host_process.close() - if self._device_process: - self._device_process.close() - if self._adb_forward_process: - self._adb_forward_process.close() - self._host_process = None - self._device_process = None - self._adb_forward_process = None - 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.""" - self._CloseProcess() + if self._device_process: + self._device_process.close() + self._device_process = None |