| OLD | NEW |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 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 """A "Test Server Spawner" that handles killing/stopping per-test test servers. | 5 """A "Test Server Spawner" that handles killing/stopping per-test test servers. |
| 6 | 6 |
| 7 It's used to accept requests from the device to spawn and kill instances of the | 7 It's used to accept requests from the device to spawn and kill instances of the |
| 8 chrome test server on the host. | 8 chrome test server on the host. |
| 9 """ | 9 """ |
| 10 | 10 |
| 11 import BaseHTTPServer | 11 import BaseHTTPServer |
| 12 import json | 12 import json |
| 13 import logging | 13 import logging |
| 14 import os | 14 import os |
| 15 import select | 15 import select |
| 16 import struct | 16 import struct |
| 17 import subprocess | 17 import subprocess |
| 18 import sys | 18 import sys |
| 19 import threading | 19 import threading |
| 20 import time | 20 import time |
| 21 import urlparse | 21 import urlparse |
| 22 | 22 |
| 23 import constants | 23 import constants |
| 24 import ports | 24 import ports |
| 25 | 25 |
| 26 from pylib.forwarder import Forwarder |
| 26 | 27 |
| 27 # Path that are needed to import necessary modules when launching a testserver. | 28 # Path that are needed to import necessary modules when launching a testserver. |
| 28 os.environ['PYTHONPATH'] = os.environ.get('PYTHONPATH', '') + (':%s:%s:%s:%s:%s' | 29 os.environ['PYTHONPATH'] = os.environ.get('PYTHONPATH', '') + (':%s:%s:%s:%s:%s' |
| 29 % (os.path.join(constants.DIR_SOURCE_ROOT, 'third_party'), | 30 % (os.path.join(constants.DIR_SOURCE_ROOT, 'third_party'), |
| 30 os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', 'tlslite'), | 31 os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', 'tlslite'), |
| 31 os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', 'pyftpdlib', | 32 os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', 'pyftpdlib', |
| 32 'src'), | 33 'src'), |
| 33 os.path.join(constants.DIR_SOURCE_ROOT, 'net', 'tools', 'testserver'), | 34 os.path.join(constants.DIR_SOURCE_ROOT, 'net', 'tools', 'testserver'), |
| 34 os.path.join(constants.DIR_SOURCE_ROOT, 'sync', 'tools', 'testserver'))) | 35 os.path.join(constants.DIR_SOURCE_ROOT, 'sync', 'tools', 'testserver'))) |
| 35 | 36 |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 92 raise NotImplementedError('Unknown server type: %s' % server_type) | 93 raise NotImplementedError('Unknown server type: %s' % server_type) |
| 93 if server_type == 'udpecho': | 94 if server_type == 'udpecho': |
| 94 raise Exception('Please do not run UDP echo tests because we do not have ' | 95 raise Exception('Please do not run UDP echo tests because we do not have ' |
| 95 'a UDP forwarder tool.') | 96 'a UDP forwarder tool.') |
| 96 return SERVER_TYPES[server_type] | 97 return SERVER_TYPES[server_type] |
| 97 | 98 |
| 98 | 99 |
| 99 class TestServerThread(threading.Thread): | 100 class TestServerThread(threading.Thread): |
| 100 """A thread to run the test server in a separate process.""" | 101 """A thread to run the test server in a separate process.""" |
| 101 | 102 |
| 102 def __init__(self, ready_event, arguments, adb, tool, forwarder, build_type): | 103 def __init__(self, ready_event, arguments, adb, tool, build_type): |
| 103 """Initialize TestServerThread with the following argument. | 104 """Initialize TestServerThread with the following argument. |
| 104 | 105 |
| 105 Args: | 106 Args: |
| 106 ready_event: event which will be set when the test server is ready. | 107 ready_event: event which will be set when the test server is ready. |
| 107 arguments: dictionary of arguments to run the test server. | 108 arguments: dictionary of arguments to run the test server. |
| 108 adb: instance of AndroidCommands. | 109 adb: instance of AndroidCommands. |
| 109 tool: instance of runtime error detection tool. | 110 tool: instance of runtime error detection tool. |
| 110 forwarder: instance of Forwarder. | |
| 111 build_type: 'Release' or 'Debug'. | 111 build_type: 'Release' or 'Debug'. |
| 112 """ | 112 """ |
| 113 threading.Thread.__init__(self) | 113 threading.Thread.__init__(self) |
| 114 self.wait_event = threading.Event() | 114 self.wait_event = threading.Event() |
| 115 self.stop_flag = False | 115 self.stop_flag = False |
| 116 self.ready_event = ready_event | 116 self.ready_event = ready_event |
| 117 self.ready_event.clear() | 117 self.ready_event.clear() |
| 118 self.arguments = arguments | 118 self.arguments = arguments |
| 119 self.adb = adb | 119 self.adb = adb |
| 120 self.tool = tool | 120 self.tool = tool |
| 121 self.test_server_process = None | 121 self.test_server_process = None |
| 122 self.is_ready = False | 122 self.is_ready = False |
| 123 self.host_port = self.arguments['port'] | 123 self.host_port = self.arguments['port'] |
| 124 assert isinstance(self.host_port, int) | 124 assert isinstance(self.host_port, int) |
| 125 self._test_server_forwarder = forwarder | |
| 126 # The forwarder device port now is dynamically allocated. | 125 # The forwarder device port now is dynamically allocated. |
| 127 self.forwarder_device_port = 0 | 126 self.forwarder_device_port = 0 |
| 128 # Anonymous pipe in order to get port info from test server. | 127 # Anonymous pipe in order to get port info from test server. |
| 129 self.pipe_in = None | 128 self.pipe_in = None |
| 130 self.pipe_out = None | 129 self.pipe_out = None |
| 131 self.command_line = [] | 130 self.command_line = [] |
| 132 self.build_type = build_type | 131 self.build_type = build_type |
| 133 | 132 |
| 134 def _WaitToStartAndGetPortFromTestServer(self): | 133 def _WaitToStartAndGetPortFromTestServer(self): |
| 135 """Waits for the Python test server to start and gets the port it is using. | 134 """Waits for the Python test server to start and gets the port it is using. |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 214 self.command_line.append('--tls-intolerant=%s' % | 213 self.command_line.append('--tls-intolerant=%s' % |
| 215 self.arguments['tls-intolerant']) | 214 self.arguments['tls-intolerant']) |
| 216 if self.arguments.has_key('ssl-client-ca'): | 215 if self.arguments.has_key('ssl-client-ca'): |
| 217 for ca in self.arguments['ssl-client-ca']: | 216 for ca in self.arguments['ssl-client-ca']: |
| 218 self.command_line.append('--ssl-client-ca=%s' % | 217 self.command_line.append('--ssl-client-ca=%s' % |
| 219 os.path.join(constants.DIR_SOURCE_ROOT, ca)) | 218 os.path.join(constants.DIR_SOURCE_ROOT, ca)) |
| 220 if self.arguments.has_key('ssl-bulk-cipher'): | 219 if self.arguments.has_key('ssl-bulk-cipher'): |
| 221 for bulk_cipher in self.arguments['ssl-bulk-cipher']: | 220 for bulk_cipher in self.arguments['ssl-bulk-cipher']: |
| 222 self.command_line.append('--ssl-bulk-cipher=%s' % bulk_cipher) | 221 self.command_line.append('--ssl-bulk-cipher=%s' % bulk_cipher) |
| 223 | 222 |
| 223 def _CloseUnnecessaryFDsForTestServerProcess(self): |
| 224 # This is required to avoid subtle deadlocks that could be caused by the |
| 225 # test server child process inheriting undesirable file descriptors such as |
| 226 # file lock file descriptors. |
| 227 for fd in xrange(0, 1024): |
| 228 if fd != self.pipe_out: |
| 229 try: |
| 230 os.close(fd) |
| 231 except: |
| 232 pass |
| 233 |
| 224 def run(self): | 234 def run(self): |
| 225 logging.info('Start running the thread!') | 235 logging.info('Start running the thread!') |
| 226 self.wait_event.clear() | 236 self.wait_event.clear() |
| 227 self._GenerateCommandLineArguments() | 237 self._GenerateCommandLineArguments() |
| 228 command = constants.DIR_SOURCE_ROOT | 238 command = constants.DIR_SOURCE_ROOT |
| 229 if self.arguments['server-type'] == 'sync': | 239 if self.arguments['server-type'] == 'sync': |
| 230 command = [os.path.join(command, 'sync', 'tools', 'testserver', | 240 command = [os.path.join(command, 'sync', 'tools', 'testserver', |
| 231 'sync_testserver.py')] + self.command_line | 241 'sync_testserver.py')] + self.command_line |
| 232 else: | 242 else: |
| 233 command = [os.path.join(command, 'net', 'tools', 'testserver', | 243 command = [os.path.join(command, 'net', 'tools', 'testserver', |
| 234 'testserver.py')] + self.command_line | 244 'testserver.py')] + self.command_line |
| 235 logging.info('Running: %s', command) | 245 logging.info('Running: %s', command) |
| 236 self.process = subprocess.Popen(command) | 246 self.process = subprocess.Popen( |
| 247 command, preexec_fn=self._CloseUnnecessaryFDsForTestServerProcess) |
| 237 if self.process: | 248 if self.process: |
| 238 if self.pipe_out: | 249 if self.pipe_out: |
| 239 self.is_ready = self._WaitToStartAndGetPortFromTestServer() | 250 self.is_ready = self._WaitToStartAndGetPortFromTestServer() |
| 240 else: | 251 else: |
| 241 self.is_ready = _CheckPortStatus(self.host_port, True) | 252 self.is_ready = _CheckPortStatus(self.host_port, True) |
| 242 if self.is_ready: | 253 if self.is_ready: |
| 243 self._test_server_forwarder.Run([(0, self.host_port)], self.tool) | 254 Forwarder.Map([(0, self.host_port)], self.adb, self.build_type, self.tool) |
| 244 # Check whether the forwarder is ready on the device. | 255 # Check whether the forwarder is ready on the device. |
| 245 self.is_ready = False | 256 self.is_ready = False |
| 246 device_port = self._test_server_forwarder.DevicePortForHostPort( | 257 device_port = Forwarder.DevicePortForHostPort(self.host_port) |
| 247 self.host_port) | |
| 248 if device_port and _CheckDevicePortStatus(self.adb, device_port): | 258 if device_port and _CheckDevicePortStatus(self.adb, device_port): |
| 249 self.is_ready = True | 259 self.is_ready = True |
| 250 self.forwarder_device_port = device_port | 260 self.forwarder_device_port = device_port |
| 251 # Wake up the request handler thread. | 261 # Wake up the request handler thread. |
| 252 self.ready_event.set() | 262 self.ready_event.set() |
| 253 # Keep thread running until Stop() gets called. | 263 # Keep thread running until Stop() gets called. |
| 254 _WaitUntil(lambda: self.stop_flag, max_attempts=sys.maxint) | 264 _WaitUntil(lambda: self.stop_flag, max_attempts=sys.maxint) |
| 255 if self.process.poll() is None: | 265 if self.process.poll() is None: |
| 256 self.process.kill() | 266 self.process.kill() |
| 257 self._test_server_forwarder.UnmapDevicePort(self.forwarder_device_port) | 267 Forwarder.UnmapDevicePort(self.forwarder_device_port, self.adb) |
| 258 self.process = None | 268 self.process = None |
| 259 self.is_ready = False | 269 self.is_ready = False |
| 260 if self.pipe_out: | 270 if self.pipe_out: |
| 261 os.close(self.pipe_in) | 271 os.close(self.pipe_in) |
| 262 os.close(self.pipe_out) | 272 os.close(self.pipe_out) |
| 263 self.pipe_in = None | 273 self.pipe_in = None |
| 264 self.pipe_out = None | 274 self.pipe_out = None |
| 265 logging.info('Test-server has died.') | 275 logging.info('Test-server has died.') |
| 266 self.wait_event.set() | 276 self.wait_event.set() |
| 267 | 277 |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 317 logging.info(content_length) | 327 logging.info(content_length) |
| 318 test_server_argument_json = self.rfile.read(content_length) | 328 test_server_argument_json = self.rfile.read(content_length) |
| 319 logging.info(test_server_argument_json) | 329 logging.info(test_server_argument_json) |
| 320 assert not self.server.test_server_instance | 330 assert not self.server.test_server_instance |
| 321 ready_event = threading.Event() | 331 ready_event = threading.Event() |
| 322 self.server.test_server_instance = TestServerThread( | 332 self.server.test_server_instance = TestServerThread( |
| 323 ready_event, | 333 ready_event, |
| 324 json.loads(test_server_argument_json), | 334 json.loads(test_server_argument_json), |
| 325 self.server.adb, | 335 self.server.adb, |
| 326 self.server.tool, | 336 self.server.tool, |
| 327 self.server.forwarder, | |
| 328 self.server.build_type) | 337 self.server.build_type) |
| 329 self.server.test_server_instance.setDaemon(True) | 338 self.server.test_server_instance.setDaemon(True) |
| 330 self.server.test_server_instance.start() | 339 self.server.test_server_instance.start() |
| 331 ready_event.wait() | 340 ready_event.wait() |
| 332 if self.server.test_server_instance.is_ready: | 341 if self.server.test_server_instance.is_ready: |
| 333 self._SendResponse(200, 'OK', {}, json.dumps( | 342 self._SendResponse(200, 'OK', {}, json.dumps( |
| 334 {'port': self.server.test_server_instance.forwarder_device_port, | 343 {'port': self.server.test_server_instance.forwarder_device_port, |
| 335 'message': 'started'})) | 344 'message': 'started'})) |
| 336 logging.info('Test server is running on port: %d.', | 345 logging.info('Test server is running on port: %d.', |
| 337 self.server.test_server_instance.host_port) | 346 self.server.test_server_instance.host_port) |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 385 self._SendResponse(200, 'OK', {}, 'ready') | 394 self._SendResponse(200, 'OK', {}, 'ready') |
| 386 logging.info('Handled ping request and sent response.') | 395 logging.info('Handled ping request and sent response.') |
| 387 else: | 396 else: |
| 388 self._SendResponse(400, 'Unknown request', {}, '') | 397 self._SendResponse(400, 'Unknown request', {}, '') |
| 389 logging.info('Encounter unknown request: %s.', action) | 398 logging.info('Encounter unknown request: %s.', action) |
| 390 | 399 |
| 391 | 400 |
| 392 class SpawningServer(object): | 401 class SpawningServer(object): |
| 393 """The class used to start/stop a http server.""" | 402 """The class used to start/stop a http server.""" |
| 394 | 403 |
| 395 def __init__(self, test_server_spawner_port, adb, tool, forwarder, | 404 def __init__(self, test_server_spawner_port, adb, tool, build_type): |
| 396 build_type): | |
| 397 logging.info('Creating new spawner on port: %d.', test_server_spawner_port) | 405 logging.info('Creating new spawner on port: %d.', test_server_spawner_port) |
| 398 self.server = BaseHTTPServer.HTTPServer(('', test_server_spawner_port), | 406 self.server = BaseHTTPServer.HTTPServer(('', test_server_spawner_port), |
| 399 SpawningServerRequestHandler) | 407 SpawningServerRequestHandler) |
| 400 self.server.adb = adb | 408 self.server.adb = adb |
| 401 self.server.tool = tool | 409 self.server.tool = tool |
| 402 self.server.forwarder = forwarder | |
| 403 self.server.test_server_instance = None | 410 self.server.test_server_instance = None |
| 404 self.server.build_type = build_type | 411 self.server.build_type = build_type |
| 405 | 412 |
| 406 def _Listen(self): | 413 def _Listen(self): |
| 407 logging.info('Starting test server spawner') | 414 logging.info('Starting test server spawner') |
| 408 self.server.serve_forever() | 415 self.server.serve_forever() |
| 409 | 416 |
| 410 def Start(self): | 417 def Start(self): |
| 411 """Starts the test server spawner.""" | 418 """Starts the test server spawner.""" |
| 412 listener_thread = threading.Thread(target=self._Listen) | 419 listener_thread = threading.Thread(target=self._Listen) |
| (...skipping 10 matching lines...) Expand all Loading... |
| 423 | 430 |
| 424 def CleanupState(self): | 431 def CleanupState(self): |
| 425 """Cleans up the spawning server state. | 432 """Cleans up the spawning server state. |
| 426 | 433 |
| 427 This should be called if the test server spawner is reused, | 434 This should be called if the test server spawner is reused, |
| 428 to avoid sharing the test server instance. | 435 to avoid sharing the test server instance. |
| 429 """ | 436 """ |
| 430 if self.server.test_server_instance: | 437 if self.server.test_server_instance: |
| 431 self.server.test_server_instance.Stop() | 438 self.server.test_server_instance.Stop() |
| 432 self.server.test_server_instance = None | 439 self.server.test_server_instance = None |
| OLD | NEW |