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 3# found in the LICENSE file. 4 5import contextlib 6import httplib 7import logging 8import os 9import tempfile 10import time 11 12import android_commands 13import constants 14from chrome_test_server_spawner import SpawningServer 15import constants 16from flag_changer import FlagChanger 17from forwarder import Forwarder 18import lighttpd_server 19import ports 20from valgrind_tools import CreateTool 21 22 23# A file on device to store ports of net test server. The format of the file is 24# test-spawner-server-port:test-server-port 25NET_TEST_SERVER_PORT_INFO_FILE = 'net-test-server-ports' 26 27 28class BaseTestRunner(object): 29 """Base class for running tests on a single device. 30 31 A subclass should implement RunTests() with no parameter, so that calling 32 the Run() method will set up tests, run them and tear them down. 33 """ 34 35 def __init__(self, device, tool, shard_index, build_type): 36 """ 37 Args: 38 device: Tests will run on the device of this ID. 39 shard_index: Index number of the shard on which the test suite will run. 40 build_type: 'Release' or 'Debug'. 41 """ 42 self.device = device 43 self.adb = android_commands.AndroidCommands(device=device) 44 self.tool = CreateTool(tool, self.adb) 45 self._http_server = None 46 self._forwarder = None 47 self._forwarder_device_port = 8000 48 self.forwarder_base_url = ('http://localhost:%d' % 49 self._forwarder_device_port) 50 self.flags = FlagChanger(self.adb) 51 self.shard_index = shard_index 52 self.flags.AddFlags(['--disable-fre']) 53 self._spawning_server = None 54 self._spawner_forwarder = None 55 # We will allocate port for test server spawner when calling method 56 # LaunchChromeTestServerSpawner and allocate port for test server when 57 # starting it in TestServerThread. 58 self.test_server_spawner_port = 0 59 self.test_server_port = 0 60 self.build_type = build_type 61 62 def _PushTestServerPortInfoToDevice(self): 63 """Pushes the latest port information to device.""" 64 self.adb.SetFileContents(self.adb.GetExternalStorage() + '/' + 65 NET_TEST_SERVER_PORT_INFO_FILE, 66 '%d:%d' % (self.test_server_spawner_port, 67 self.test_server_port)) 68 69 def Run(self): 70 """Calls subclass functions to set up tests, run them and tear them down. 71 72 Returns: 73 Test results returned from RunTests(). 74 """ 75 if not self.HasTests(): 76 return True 77 self.SetUp() 78 try: 79 return self.RunTests() 80 finally: 81 self.TearDown() 82 83 def SetUp(self): 84 """Called before tests run.""" 85 pass 86 87 def HasTests(self): 88 """Whether the test suite has tests to run.""" 89 return True 90 91 def RunTests(self): 92 """Runs the tests. Need to be overridden.""" 93 raise NotImplementedError 94 95 def TearDown(self): 96 """Called when tests finish running.""" 97 self.ShutdownHelperToolsForTestSuite() 98 99 def CopyTestData(self, test_data_paths, dest_dir): 100 """Copies |test_data_paths| list of files/directories to |dest_dir|. 101 102 Args: 103 test_data_paths: A list of files or directories relative to |dest_dir| 104 which should be copied to the device. The paths must exist in 105 |CHROME_DIR|. 106 dest_dir: Absolute path to copy to on the device. 107 """ 108 for p in test_data_paths: 109 self.adb.PushIfNeeded( 110 os.path.join(constants.CHROME_DIR, p), 111 os.path.join(dest_dir, p)) 112 113 def LaunchTestHttpServer(self, document_root, port=None, 114 extra_config_contents=None): 115 """Launches an HTTP server to serve HTTP tests. 116 117 Args: 118 document_root: Document root of the HTTP server. 119 port: port on which we want to the http server bind. 120 extra_config_contents: Extra config contents for the HTTP server. 121 """ 122 self._http_server = lighttpd_server.LighttpdServer( 123 document_root, port=port, extra_config_contents=extra_config_contents) 124 if self._http_server.StartupHttpServer(): 125 logging.info('http server started: http://localhost:%s', 126 self._http_server.port) 127 else: 128 logging.critical('Failed to start http server') 129 self.StartForwarderForHttpServer() 130 return (self._forwarder_device_port, self._http_server.port) 131 132 def StartForwarder(self, port_pairs): 133 """Starts TCP traffic forwarding for the given |port_pairs|. 134 135 Args: 136 host_port_pairs: A list of (device_port, local_port) tuples to forward. 137 """ 138 if self._forwarder: 139 self._forwarder.Close() 140 self._forwarder = Forwarder( 141 self.adb, port_pairs, self.tool, '127.0.0.1', self.build_type) 142 143 def StartForwarderForHttpServer(self): 144 """Starts a forwarder for the HTTP server. 145 146 The forwarder forwards HTTP requests and responses between host and device. 147 """ 148 self.StartForwarder([(self._forwarder_device_port, self._http_server.port)]) 149 150 def RestartHttpServerForwarderIfNecessary(self): 151 """Restarts the forwarder if it's not open.""" 152 # Checks to see if the http server port is being used. If not forwards the 153 # request. 154 # TODO(dtrainor): This is not always reliable because sometimes the port 155 # will be left open even after the forwarder has been killed. 156 if not ports.IsDevicePortUsed(self.adb, 157 self._forwarder_device_port): 158 self.StartForwarderForHttpServer() 159 160 def ShutdownHelperToolsForTestSuite(self): 161 """Shuts down the server and the forwarder.""" 162 # Forwarders should be killed before the actual servers they're forwarding 163 # to as they are clients potentially with open connections and to allow for 164 # proper hand-shake/shutdown. 165 if self._forwarder or self._spawner_forwarder: 166 # Kill all forwarders on the device and then kill the process on the host 167 # (if it exists) 168 self.adb.KillAll('device_forwarder') 169 if self._forwarder: 170 self._forwarder.Close() 171 if self._spawner_forwarder: 172 self._spawner_forwarder.Close() 173 if self._http_server: 174 self._http_server.ShutdownHttpServer() 175 if self._spawning_server: 176 self._spawning_server.Stop() 177 self.flags.Restore() 178 179 def LaunchChromeTestServerSpawner(self): 180 """Launches test server spawner.""" 181 server_ready = False 182 error_msgs = [] 183 # Try 3 times to launch test spawner server. 184 for i in xrange(0, 3): 185 # Do not allocate port for test server here. We will allocate 186 # different port for individual test in TestServerThread. 187 self.test_server_spawner_port = ports.AllocateTestServerPort() 188 self._spawning_server = SpawningServer(self.test_server_spawner_port, 189 self.adb, 190 self.tool, 191 self.build_type) 192 self._spawning_server.Start() 193 server_ready, error_msg = ports.IsHttpServerConnectable( 194 '127.0.0.1', self.test_server_spawner_port, path='/ping', 195 expected_read='ready') 196 if server_ready: 197 break 198 else: 199 error_msgs.append(error_msg) 200 self._spawning_server.Stop() 201 # Wait for 2 seconds then restart. 202 time.sleep(2) 203 if not server_ready: 204 logging.error(';'.join(error_msgs)) 205 raise Exception('Can not start the test spawner server.') 206 self._PushTestServerPortInfoToDevice() 207 self._spawner_forwarder = Forwarder( 208 self.adb, 209 [(self.test_server_spawner_port, self.test_server_spawner_port)], 210 self.tool, '127.0.0.1', self.build_type) 211