Home | History | Annotate | Download | only in host_driven
      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()
    121