Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(518)

Side by Side Diff: build/android/pylib/forwarder.py

Issue 11269036: Support HTTP test-server based net unit tests on Android. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Refine error handling in Daemon::Kill() Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import logging 5 import logging
6 import os 6 import os
7 import re 7 import re
8 import sys 8 import sys
9 import time 9 import time
10 10
11 import android_commands 11 import android_commands
12 import cmd_helper 12 import cmd_helper
13 import constants 13 import constants
14 import ports 14 import ports
15 15
16 from pylib import pexpect 16 from pylib import pexpect
17 17
bulach 2012/11/05 20:15:07 nit: need another \n here, 20, 28 and 37 (that is,
Philippe 2012/11/06 09:58:04 Done.
18 def _MakeBinaryPath(build_type, binary_name):
19 return os.path.join(constants.CHROME_DIR, 'out', build_type, binary_name)
20
21 def KillHostForwarder(build_type):
22 host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder')
23 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
24 [host_forwarder_path, 'kill-server'])
25 if exit_code != 0:
26 raise Exception('%s exited with %d: %s' % (host_forwarder_path,
27 exit_code, output))
28
29 def KillDeviceForwarder(adb):
bulach 2012/11/05 20:15:07 suggestion: how about making all of this @staticme
Philippe 2012/11/06 09:58:04 Good idea.
30 logging.info('Killing device_forwarder.')
31 timeout_sec = 5
32 processes_killed = adb.KillAllBlocking('device_forwarder', timeout_sec)
33 if not processes_killed:
34 pids = adb.ExtractPid('device_forwarder')
35 if pids:
36 raise Exception('Timed out while killing device_forwarder')
37
18 class Forwarder(object): 38 class Forwarder(object):
19 """Class to manage port forwards from the device to the host.""" 39 """Class to manage port forwards from the device to the host."""
20 40
21 _DEVICE_FORWARDER_PATH = constants.TEST_EXECUTABLE_DIR + '/device_forwarder' 41 _DEVICE_FORWARDER_PATH = constants.TEST_EXECUTABLE_DIR + '/device_forwarder'
22 42
23 # Unix Abstract socket path: 43 # Unix Abstract socket path:
24 _DEVICE_ADB_CONTROL_PORT = 'chrome_device_forwarder' 44 _DEVICE_ADB_CONTROL_PORT = 'chrome_device_forwarder'
25 _TIMEOUT_SECS = 30 45 _TIMEOUT_SECS = 30
26 46
27 def __init__(self, adb, port_pairs, tool, host_name, build_type): 47 def __init__(self, adb, build_type):
28 """Forwards TCP ports on the device back to the host. 48 """Forwards TCP ports on the device back to the host.
29 49
30 Works like adb forward, but in reverse. 50 Works like adb forward, but in reverse.
31 51
32 Args: 52 Args:
33 adb: Instance of AndroidCommands for talking to the device. 53 adb: Instance of AndroidCommands for talking to the device.
54 build_type: 'Release' or 'Debug'.
55 """
56 assert build_type in ('Release', 'Debug')
57 self._adb = adb
58 self._host_to_device_port_map = dict()
59 self._device_process = None
60 self._host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder')
61 self._device_forwarder_path = _MakeBinaryPath(
62 build_type, 'device_forwarder')
63
64 def Run(self, port_pairs, tool, host_name):
65 """Runs the forwarder.
66
67 Args:
34 port_pairs: A list of tuples (device_port, host_port) to forward. Note 68 port_pairs: A list of tuples (device_port, host_port) to forward. Note
35 that you can specify 0 as a device_port, in which case a 69 that you can specify 0 as a device_port, in which case a
36 port will by dynamically assigned on the device. You can 70 port will by dynamically assigned on the device. You can
37 get the number of the assigned port using the 71 get the number of the assigned port using the
38 DevicePortForHostPort method. 72 DevicePortForHostPort method.
39 tool: Tool class to use to get wrapper, if necessary, for executing the 73 tool: Tool class to use to get wrapper, if necessary, for executing the
40 forwarder (see valgrind_tools.py). 74 forwarder (see valgrind_tools.py).
41 host_name: Address to forward to, must be addressable from the 75 host_name: Address to forward to, must be addressable from the
42 host machine. Usually use loopback '127.0.0.1'. 76 host machine. Usually use loopback '127.0.0.1'.
43 build_type: 'Release' or 'Debug'.
44 77
45 Raises: 78 Raises:
46 Exception on failure to forward the port. 79 Exception on failure to forward the port.
47 """ 80 """
48 self._adb = adb 81 host_adb_control_port = ports.AllocateTestServerPort()
49 self._host_to_device_port_map = dict() 82 if not host_adb_control_port:
50 self._host_process = None 83 raise Exception('Failed to allocate a TCP port in the host machine.')
51 self._device_process = None 84 self._adb.PushIfNeeded(self._device_forwarder_path,
52 self._adb_forward_process = None 85 Forwarder._DEVICE_FORWARDER_PATH)
86 redirection_commands = [
87 '%d:%d:%d:%s' % (host_adb_control_port, device, host,
88 host_name) for device, host in port_pairs]
89 logging.info('Command format: <ADB port>:<Device port>' +
90 '[:<Forward to port>:<Forward to address>]')
91 logging.info('Forwarding using commands: %s', redirection_commands)
92 if cmd_helper.RunCmd(
93 ['adb', '-s', self._adb._adb.GetSerialNumber(), 'forward',
94 'tcp:%s' % host_adb_control_port,
95 'localabstract:%s' % Forwarder._DEVICE_ADB_CONTROL_PORT]) != 0:
96 raise Exception('Error while running adb forward.')
53 97
54 self._host_adb_control_port = ports.AllocateTestServerPort() 98 if not self._adb.ExtractPid('device_forwarder'):
55 if not self._host_adb_control_port: 99 # TODO(pliard): Get rid of pexpect here and make device_forwarder a daemon
56 raise Exception('Failed to allocate a TCP port in the host machine.') 100 # with a blocking CLI process that exits with a proper exit code and not
57 adb.PushIfNeeded( 101 # while the daemon is still setting up. This would be similar to how
58 os.path.join(constants.CHROME_DIR, 'out', build_type, 102 # host_forwarder works.
59 'device_forwarder'), 103 self._device_process = pexpect.spawn(
60 Forwarder._DEVICE_FORWARDER_PATH) 104 'adb', ['-s',
61 self._host_forwarder_path = os.path.join(constants.CHROME_DIR, 105 self._adb._adb.GetSerialNumber(),
62 'out', 106 'shell',
63 build_type, 107 '%s %s -D --adb_sock=%s' % (
64 'host_forwarder') 108 tool.GetUtilWrapper(),
65 forward_string = ['%d:%d:%s' % 109 Forwarder._DEVICE_FORWARDER_PATH,
66 (device, host, host_name) for device, host in port_pairs] 110 Forwarder._DEVICE_ADB_CONTROL_PORT)])
67 logging.info('Forwarding ports: %s', forward_string) 111 device_success_re = re.compile('Starting Device Forwarder.')
68 timeout_sec = 5 112 device_failure_re = re.compile('.*:ERROR:(.*)')
69 host_pattern = 'host_forwarder.*' + ' '.join(forward_string) 113 index = self._device_process.expect([device_success_re,
70 # TODO(felipeg): Rather than using a blocking kill() here, the device 114 device_failure_re,
71 # forwarder could try to bind the Unix Domain Socket until it succeeds or 115 pexpect.EOF,
72 # while it fails because the socket is already bound (with appropriate 116 pexpect.TIMEOUT],
73 # timeout handling obviously). 117 Forwarder._TIMEOUT_SECS)
74 self._KillHostForwarderBlocking(host_pattern, timeout_sec) 118 if index == 1:
75 self._KillDeviceForwarderBlocking(timeout_sec) 119 error_msg = str(self._device_process.match.group(1))
76 self._adb_forward_process = pexpect.spawn( 120 logging.error(self._device_process.before)
77 'adb', ['-s', 121 self._device_process.close()
78 adb._adb.GetSerialNumber(), 122 raise Exception('Failed to start Device Forwarder with Error: %s' %
79 'forward', 123 error_msg)
80 'tcp:%s' % self._host_adb_control_port, 124 elif index == 2:
81 'localabstract:%s' % Forwarder._DEVICE_ADB_CONTROL_PORT]) 125 logging.error(self._device_process.before)
82 self._device_process = pexpect.spawn( 126 self._device_process.close()
83 'adb', ['-s', 127 raise Exception(
84 adb._adb.GetSerialNumber(), 128 'Unexpected EOF while trying to start Device Forwarder.')
85 'shell', 129 elif index == 3:
86 '%s %s -D --adb_sock=%s' % ( 130 logging.error(self._device_process.before)
87 tool.GetUtilWrapper(), 131 self._device_process.close()
88 Forwarder._DEVICE_FORWARDER_PATH, 132 raise Exception('Timeout while trying start Device Forwarder')
89 Forwarder._DEVICE_ADB_CONTROL_PORT)])
90 133
91 device_success_re = re.compile('Starting Device Forwarder.') 134 for redirection_command in redirection_commands:
92 device_failure_re = re.compile('.*:ERROR:(.*)') 135 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
93 index = self._device_process.expect([device_success_re, 136 [self._host_forwarder_path, redirection_command])
94 device_failure_re, 137 if exit_code != 0:
95 pexpect.EOF, 138 raise Exception('%s exited with %d: %s' % (self._host_forwarder_path,
96 pexpect.TIMEOUT], 139 exit_code, output))
97 Forwarder._TIMEOUT_SECS) 140 tokens = output.split(':')
98 if index == 1: 141 if len(tokens) != 2:
99 # Failure 142 raise Exception('Unexpected host forwarder output "%s", ' +
100 error_msg = str(self._device_process.match.group(1)) 143 'expected "device_port:host_port"' % output)
101 logging.error(self._device_process.before) 144 device_port = int(tokens[0])
102 self._CloseProcess() 145 host_port = int(tokens[1])
103 raise Exception('Failed to start Device Forwarder with Error: %s' % 146 self._host_to_device_port_map[host_port] = device_port
104 error_msg) 147 logging.info(
105 elif index == 2: 148 "Forwarding device port: %d to host port: %d." % (device_port,
bulach 2012/11/05 20:15:07 nit: single quotes. also, "logging" takes a vararg
Philippe 2012/11/06 09:58:04 Done.
106 logging.error(self._device_process.before) 149 host_port))
107 self._CloseProcess()
108 raise Exception('Unexpected EOF while trying to start Device Forwarder.')
109 elif index == 3:
110 logging.error(self._device_process.before)
111 self._CloseProcess()
112 raise Exception('Timeout while trying start Device Forwarder')
113
114 self._host_process = pexpect.spawn(self._host_forwarder_path,
115 ['--adb_port=%s' % (
116 self._host_adb_control_port)] +
117 forward_string)
118
119 # Read the output of the command to determine which device ports where
120 # forwarded to which host ports (necessary if
121 host_success_re = re.compile('Forwarding device port (\d+) to host (\d+):')
122 host_failure_re = re.compile('Couldn\'t start forwarder server for port '
123 'spec: (\d+):(\d+)')
124 for pair in port_pairs:
125 index = self._host_process.expect([host_success_re,
126 host_failure_re,
127 pexpect.EOF,
128 pexpect.TIMEOUT],
129 Forwarder._TIMEOUT_SECS)
130 if index == 0:
131 # Success
132 device_port = int(self._host_process.match.group(1))
133 host_port = int(self._host_process.match.group(2))
134 self._host_to_device_port_map[host_port] = device_port
135 logging.info("Forwarding device port: %d to host port: %d." %
136 (device_port, host_port))
137 elif index == 1:
138 # Failure
139 device_port = int(self._host_process.match.group(1))
140 host_port = int(self._host_process.match.group(2))
141 self._CloseProcess()
142 raise Exception('Failed to forward port %d to %d' % (device_port,
143 host_port))
144 elif index == 2:
145 logging.error(self._host_process.before)
146 self._CloseProcess()
147 raise Exception('Unexpected EOF while trying to forward ports %s' %
148 port_pairs)
149 elif index == 3:
150 logging.error(self._host_process.before)
151 self._CloseProcess()
152 raise Exception('Timeout while trying to forward ports %s' % port_pairs)
153
154 def _KillHostForwarderBlocking(self, host_pattern, timeout_sec):
155 """Kills any existing host forwarders using the provided pattern.
156
157 Note that this waits until the process terminates.
158 """
159 cmd_helper.RunCmd(['pkill', '-f', host_pattern])
160 elapsed = 0
161 wait_period = 0.1
162 while not cmd_helper.RunCmd(['pgrep', '-f', host_pattern]) and (
163 elapsed < timeout_sec):
164 time.sleep(wait_period)
165 elapsed += wait_period
166 if elapsed >= timeout_sec:
167 raise Exception('Timed out while killing ' + host_pattern)
168
169 def _KillDeviceForwarderBlocking(self, timeout_sec):
170 """Kills any existing device forwarders.
171
172 Note that this waits until the process terminates.
173 """
174 processes_killed = self._adb.KillAllBlocking(
175 'device_forwarder', timeout_sec)
176 if not processes_killed:
177 pids = self._adb.ExtractPid('device_forwarder')
178 if pids:
179 raise Exception('Timed out while killing device_forwarder')
180
181 def _CloseProcess(self):
182 if self._host_process:
183 self._host_process.close()
184 if self._device_process:
185 self._device_process.close()
186 if self._adb_forward_process:
187 self._adb_forward_process.close()
188 self._host_process = None
189 self._device_process = None
190 self._adb_forward_process = None
191 150
192 def DevicePortForHostPort(self, host_port): 151 def DevicePortForHostPort(self, host_port):
193 """Get the device port that corresponds to a given host port.""" 152 """Get the device port that corresponds to a given host port."""
194 return self._host_to_device_port_map.get(host_port) 153 return self._host_to_device_port_map.get(host_port)
195 154
196 def Close(self): 155 def Close(self):
197 """Terminate the forwarder process.""" 156 """Terminate the forwarder process."""
198 self._CloseProcess() 157 if self._device_process:
158 self._device_process.close()
159 self._device_process = None
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698