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
7This class controls the startup and shutdown of a python driven test server that
8runs in a separate process.
9
10The server starts up automatically when the object is created.
11
12After it starts up, it is possible to retreive the hostname it started on
13through accessing the member field |host| and the port name through |port|.
14
15For shutting down the server, call TearDown().
16"""
17
18import logging
19import subprocess
20import os
21import os.path
22import time
23import urllib2
24
25from 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.
49TEST_NET_SERVER_PATH = 'net/tools/testserver/testserver.py'
50TEST_SYNC_SERVER_PATH = 'sync/tools/testserver/sync_testserver.py'
51# Parameters to check that the server is up and running.
52TEST_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
63class 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