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 5"""Base class for running tests on a single device.""" 6 7import contextlib 8import httplib 9import logging 10import os 11import tempfile 12import time 13 14from pylib import android_commands 15from pylib import constants 16from pylib import ports 17from pylib.chrome_test_server_spawner import SpawningServer 18from pylib.forwarder import Forwarder 19from pylib.valgrind_tools import CreateTool 20# TODO(frankf): Move this to pylib/utils 21import lighttpd_server 22 23 24# A file on device to store ports of net test server. The format of the file is 25# test-spawner-server-port:test-server-port 26NET_TEST_SERVER_PORT_INFO_FILE = 'net-test-server-ports' 27 28 29class BaseTestRunner(object): 30 """Base class for running tests on a single device.""" 31 32 def __init__(self, device, tool, push_deps=True, cleanup_test_files=False): 33 """ 34 Args: 35 device: Tests will run on the device of this ID. 36 tool: Name of the Valgrind tool. 37 push_deps: If True, push all dependencies to the device. 38 cleanup_test_files: Whether or not to cleanup test files on device. 39 """ 40 self.device = device 41 self.adb = android_commands.AndroidCommands(device=device) 42 self.tool = CreateTool(tool, self.adb) 43 self._http_server = None 44 self._forwarder_device_port = 8000 45 self.forwarder_base_url = ('http://localhost:%d' % 46 self._forwarder_device_port) 47 self._spawning_server = None 48 # We will allocate port for test server spawner when calling method 49 # LaunchChromeTestServerSpawner and allocate port for test server when 50 # starting it in TestServerThread. 51 self.test_server_spawner_port = 0 52 self.test_server_port = 0 53 self._push_deps = push_deps 54 self._cleanup_test_files = cleanup_test_files 55 56 def _PushTestServerPortInfoToDevice(self): 57 """Pushes the latest port information to device.""" 58 self.adb.SetFileContents(self.adb.GetExternalStorage() + '/' + 59 NET_TEST_SERVER_PORT_INFO_FILE, 60 '%d:%d' % (self.test_server_spawner_port, 61 self.test_server_port)) 62 63 def RunTest(self, test): 64 """Runs a test. Needs to be overridden. 65 66 Args: 67 test: A test to run. 68 69 Returns: 70 Tuple containing: 71 (base_test_result.TestRunResults, tests to rerun or None) 72 """ 73 raise NotImplementedError 74 75 def InstallTestPackage(self): 76 """Installs the test package once before all tests are run.""" 77 pass 78 79 def PushDataDeps(self): 80 """Push all data deps to device once before all tests are run.""" 81 pass 82 83 def SetUp(self): 84 """Run once before all tests are run.""" 85 self.InstallTestPackage() 86 push_size_before = self.adb.GetPushSizeInfo() 87 if self._push_deps: 88 logging.warning('Pushing data files to device.') 89 self.PushDataDeps() 90 push_size_after = self.adb.GetPushSizeInfo() 91 logging.warning( 92 'Total data: %0.3fMB' % 93 ((push_size_after[0] - push_size_before[0]) / float(2 ** 20))) 94 logging.warning( 95 'Total data transferred: %0.3fMB' % 96 ((push_size_after[1] - push_size_before[1]) / float(2 ** 20))) 97 else: 98 logging.warning('Skipping pushing data to device.') 99 100 def TearDown(self): 101 """Run once after all tests are run.""" 102 self.ShutdownHelperToolsForTestSuite() 103 if self._cleanup_test_files: 104 self.adb.RemovePushedFiles() 105 106 def LaunchTestHttpServer(self, document_root, port=None, 107 extra_config_contents=None): 108 """Launches an HTTP server to serve HTTP tests. 109 110 Args: 111 document_root: Document root of the HTTP server. 112 port: port on which we want to the http server bind. 113 extra_config_contents: Extra config contents for the HTTP server. 114 """ 115 self._http_server = lighttpd_server.LighttpdServer( 116 document_root, port=port, extra_config_contents=extra_config_contents) 117 if self._http_server.StartupHttpServer(): 118 logging.info('http server started: http://localhost:%s', 119 self._http_server.port) 120 else: 121 logging.critical('Failed to start http server') 122 self._ForwardPortsForHttpServer() 123 return (self._forwarder_device_port, self._http_server.port) 124 125 def _ForwardPorts(self, port_pairs): 126 """Forwards a port.""" 127 Forwarder.Map(port_pairs, self.adb, self.tool) 128 129 def _UnmapPorts(self, port_pairs): 130 """Unmap previously forwarded ports.""" 131 for (device_port, _) in port_pairs: 132 Forwarder.UnmapDevicePort(device_port, self.adb) 133 134 # Deprecated: Use ForwardPorts instead. 135 def StartForwarder(self, port_pairs): 136 """Starts TCP traffic forwarding for the given |port_pairs|. 137 138 Args: 139 host_port_pairs: A list of (device_port, local_port) tuples to forward. 140 """ 141 self._ForwardPorts(port_pairs) 142 143 def _ForwardPortsForHttpServer(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._ForwardPorts([(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, self._forwarder_device_port): 157 self._ForwardPortsForHttpServer() 158 159 def ShutdownHelperToolsForTestSuite(self): 160 """Shuts down the server and the forwarder.""" 161 if self._http_server: 162 self._UnmapPorts([(self._forwarder_device_port, self._http_server.port)]) 163 self._http_server.ShutdownHttpServer() 164 if self._spawning_server: 165 self._spawning_server.Stop() 166 167 def CleanupSpawningServerState(self): 168 """Tells the spawning server to clean up any state. 169 170 If the spawning server is reused for multiple tests, this should be called 171 after each test to prevent tests affecting each other. 172 """ 173 if self._spawning_server: 174 self._spawning_server.CleanupState() 175 176 def LaunchChromeTestServerSpawner(self): 177 """Launches test server spawner.""" 178 server_ready = False 179 error_msgs = [] 180 # TODO(pliard): deflake this function. The for loop should be removed as 181 # well as IsHttpServerConnectable(). spawning_server.Start() should also 182 # block until the server is ready. 183 # Try 3 times to launch test spawner server. 184 for i in xrange(0, 3): 185 self.test_server_spawner_port = ports.AllocateTestServerPort() 186 self._ForwardPorts( 187 [(self.test_server_spawner_port, self.test_server_spawner_port)]) 188 self._spawning_server = SpawningServer(self.test_server_spawner_port, 189 self.adb, 190 self.tool) 191 self._spawning_server.Start() 192 server_ready, error_msg = ports.IsHttpServerConnectable( 193 '127.0.0.1', self.test_server_spawner_port, path='/ping', 194 expected_read='ready') 195 if server_ready: 196 break 197 else: 198 error_msgs.append(error_msg) 199 self._spawning_server.Stop() 200 # Wait for 2 seconds then restart. 201 time.sleep(2) 202 if not server_ready: 203 logging.error(';'.join(error_msgs)) 204 raise Exception('Can not start the test spawner server.') 205 self._PushTestServerPortInfoToDevice() 206