James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame^] | 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 |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | """Host driven test server controller. |
| 6 | |
| 7 | This class controls the startup and shutdown of a python driven test server that |
| 8 | runs in a separate process. |
| 9 | |
| 10 | The server starts up automatically when the object is created. |
| 11 | |
| 12 | After it starts up, it is possible to retreive the hostname it started on |
| 13 | through accessing the member field |host| and the port name through |port|. |
| 14 | |
| 15 | For shutting down the server, call TearDown(). |
| 16 | """ |
| 17 | |
| 18 | import logging |
| 19 | import subprocess |
| 20 | import os |
| 21 | import os.path |
| 22 | import time |
| 23 | import urllib2 |
| 24 | |
| 25 | from pylib import constants |
| 26 | |
| 27 | # NOTE: when adding or modifying these lines, omit any leading slashes! |
| 28 | # Otherwise os.path.join() will (correctly) treat them as absolute paths |
| 29 | # instead of relative paths, and will do nothing. |
| 30 | _PYTHONPATH_DIRS = [ |
| 31 | 'net/tools/testserver/', |
| 32 | 'third_party/', |
| 33 | 'third_party/pyftpdlib/src/', |
| 34 | 'third_party/pywebsocket/src', |
| 35 | 'third_party/tlslite/', |
| 36 | ] |
| 37 | |
| 38 | # Python files in these directories are generated as part of the build. |
| 39 | # These dirs are located in out/(Debug|Release) directory. |
| 40 | # The correct path is determined based on the build type. E.g. out/Debug for |
| 41 | # debug builds and out/Release for release builds. |
| 42 | _GENERATED_PYTHONPATH_DIRS = [ |
| 43 | 'pyproto/sync/protocol/', |
| 44 | 'pyproto/' |
| 45 | ] |
| 46 | |
| 47 | _TEST_SERVER_HOST = '127.0.0.1' |
| 48 | # Paths for supported test server executables. |
| 49 | TEST_NET_SERVER_PATH = 'net/tools/testserver/testserver.py' |
| 50 | TEST_SYNC_SERVER_PATH = 'sync/tools/testserver/sync_testserver.py' |
| 51 | # Parameters to check that the server is up and running. |
| 52 | TEST_SERVER_CHECK_PARAMS = { |
| 53 | TEST_NET_SERVER_PATH: { |
| 54 | 'url_path': '/', |
| 55 | 'response': 'Default response given for path' |
| 56 | }, |
| 57 | TEST_SYNC_SERVER_PATH: { |
| 58 | 'url_path': 'chromiumsync/time', |
| 59 | 'response': '0123456789' |
| 60 | }, |
| 61 | } |
| 62 | |
| 63 | class TestServer(object): |
| 64 | """Sets up a host driven test server on the host machine. |
| 65 | |
| 66 | For shutting down the server, call TearDown(). |
| 67 | """ |
| 68 | |
| 69 | def __init__(self, shard_index, test_server_port, test_server_path): |
| 70 | """Sets up a Python driven test server on the host machine. |
| 71 | |
| 72 | Args: |
| 73 | shard_index: Index of the current shard. |
| 74 | test_server_port: Port to run the test server on. This is multiplexed with |
| 75 | the shard index. To retrieve the real port access the |
| 76 | member variable |port|. |
| 77 | test_server_path: The path (relative to the root src dir) of the server |
| 78 | """ |
| 79 | self.host = _TEST_SERVER_HOST |
| 80 | self.port = test_server_port + shard_index |
| 81 | |
| 82 | src_dir = constants.DIR_SOURCE_ROOT |
| 83 | # Make dirs into a list of absolute paths. |
| 84 | abs_dirs = [os.path.join(src_dir, d) for d in _PYTHONPATH_DIRS] |
| 85 | # Add the generated python files to the path |
| 86 | abs_dirs.extend([os.path.join(src_dir, constants.GetOutDirectory(), d) |
| 87 | for d in _GENERATED_PYTHONPATH_DIRS]) |
| 88 | current_python_path = os.environ.get('PYTHONPATH') |
| 89 | extra_python_path = ':'.join(abs_dirs) |
| 90 | if current_python_path: |
| 91 | python_path = current_python_path + ':' + extra_python_path |
| 92 | else: |
| 93 | python_path = extra_python_path |
| 94 | |
| 95 | # NOTE: A separate python process is used to simplify getting the right |
| 96 | # system path for finding includes. |
| 97 | cmd = ['python', os.path.join(src_dir, test_server_path), |
| 98 | '--log-to-console', |
| 99 | ('--host=%s' % self.host), |
| 100 | ('--port=%d' % self.port)] |
| 101 | self._test_server_process = subprocess.Popen( |
| 102 | cmd, env={'PYTHONPATH': python_path}) |
| 103 | test_url = 'http://%s:%d/%s' % (self.host, self.port, |
| 104 | TEST_SERVER_CHECK_PARAMS[test_server_path]['url_path']) |
| 105 | expected_response = TEST_SERVER_CHECK_PARAMS[test_server_path]['response'] |
| 106 | retries = 0 |
| 107 | while retries < 5: |
| 108 | try: |
| 109 | d = urllib2.urlopen(test_url).read() |
| 110 | logging.info('URL %s GOT: %s' % (test_url, d)) |
| 111 | if d.startswith(expected_response): |
| 112 | break |
| 113 | except Exception as e: |
| 114 | logging.info('URL %s GOT: %s' % (test_url, e)) |
| 115 | time.sleep(retries * 0.1) |
| 116 | retries += 1 |
| 117 | |
| 118 | def TearDown(self): |
| 119 | self._test_server_process.kill() |
| 120 | self._test_server_process.wait() |