OLD | NEW |
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
| 5 import contextlib |
| 6 import httplib |
5 import logging | 7 import logging |
6 import os | 8 import os |
| 9 import tempfile |
| 10 import time |
7 | 11 |
8 import android_commands | 12 import android_commands |
9 from chrome_test_server_spawner import SpawningServer | 13 from chrome_test_server_spawner import SpawningServer |
| 14 import constants |
10 from flag_changer import FlagChanger | 15 from flag_changer import FlagChanger |
| 16 from forwarder import Forwarder |
11 import lighttpd_server | 17 import lighttpd_server |
12 import run_tests_helper | 18 import ports |
| 19 from valgrind_tools import CreateTool |
13 | 20 |
14 FORWARDER_PATH = '/data/local/tmp/forwarder' | 21 |
15 # These ports must match up with the constants in net/test/test_server.cc | 22 # A file on device to store ports of net test server. The format of the file is |
16 TEST_SERVER_SPAWNER_PORT = 8001 | 23 # test-spawner-server-port:test-server-port |
17 TEST_SERVER_PORT = 8002 | 24 NET_TEST_SERVER_PORT_INFO_FILE = '/data/local/tmp/net-test-server-ports' |
18 TEST_SYNC_SERVER_PORT = 8003 | |
19 | 25 |
20 | 26 |
21 class BaseTestRunner(object): | 27 class BaseTestRunner(object): |
22 """Base class for running tests on a single device. | 28 """Base class for running tests on a single device. |
23 | 29 |
24 A subclass should implement RunTests() with no parameter, so that calling | 30 A subclass should implement RunTests() with no parameter, so that calling |
25 the Run() method will set up tests, run them and tear them down. | 31 the Run() method will set up tests, run them and tear them down. |
26 """ | 32 """ |
27 | 33 |
28 def __init__(self, device, shard_index): | 34 def __init__(self, device, tool, shard_index): |
29 """ | 35 """ |
30 Args: | 36 Args: |
31 device: Tests will run on the device of this ID. | 37 device: Tests will run on the device of this ID. |
32 shard_index: Index number of the shard on which the test suite will run. | 38 shard_index: Index number of the shard on which the test suite will run. |
33 """ | 39 """ |
34 self.device = device | 40 self.device = device |
35 self.adb = android_commands.AndroidCommands(device=device) | 41 self.adb = android_commands.AndroidCommands(device=device) |
| 42 self.tool = CreateTool(tool, self.adb) |
36 # Synchronize date/time between host and device. Otherwise same file on | 43 # Synchronize date/time between host and device. Otherwise same file on |
37 # host and device may have different timestamp which may cause | 44 # host and device may have different timestamp which may cause |
38 # AndroidCommands.PushIfNeeded failed, or a test which may compare timestamp | 45 # AndroidCommands.PushIfNeeded failed, or a test which may compare timestamp |
39 # got from http head and local time could be failed. | 46 # got from http head and local time could be failed. |
40 self.adb.SynchronizeDateTime() | 47 self.adb.SynchronizeDateTime() |
41 self._http_server = None | 48 self._http_server = None |
42 self._forwarder = None | 49 self._forwarder = None |
43 self._spawning_server = None | |
44 self._spawner_forwarder = None | |
45 self._forwarder_device_port = 8000 | 50 self._forwarder_device_port = 8000 |
46 self.forwarder_base_url = ('http://localhost:%d' % | 51 self.forwarder_base_url = ('http://localhost:%d' % |
47 self._forwarder_device_port) | 52 self._forwarder_device_port) |
48 self.flags = FlagChanger(self.adb) | 53 self.flags = FlagChanger(self.adb) |
49 self.shard_index = shard_index | 54 self.shard_index = shard_index |
| 55 self.flags.AddFlags(['--disable-fre']) |
| 56 self._spawning_server = None |
| 57 self._spawner_forwarder = None |
| 58 # We will allocate port for test server spawner when calling method |
| 59 # LaunchChromeTestServerSpawner and allocate port for test server when |
| 60 # starting it in TestServerThread. |
| 61 self.test_server_spawner_port = 0 |
| 62 self.test_server_port = 0 |
| 63 |
| 64 def _PushTestServerPortInfoToDevice(self): |
| 65 """Pushes the latest port information to device.""" |
| 66 self.adb.SetFileContents(NET_TEST_SERVER_PORT_INFO_FILE, |
| 67 '%d:%d' % (self.test_server_spawner_port, |
| 68 self.test_server_port)) |
50 | 69 |
51 def Run(self): | 70 def Run(self): |
52 """Calls subclass functions to set up tests, run them and tear them down. | 71 """Calls subclass functions to set up tests, run them and tear them down. |
53 | 72 |
54 Returns: | 73 Returns: |
55 Test results returned from RunTests(). | 74 Test results returned from RunTests(). |
56 """ | 75 """ |
| 76 if not self.HasTests(): |
| 77 return True |
57 self.SetUp() | 78 self.SetUp() |
58 try: | 79 try: |
59 return self.RunTests() | 80 return self.RunTests() |
60 finally: | 81 finally: |
61 self.TearDown() | 82 self.TearDown() |
62 | 83 |
63 def SetUp(self): | 84 def SetUp(self): |
64 """Called before tests run.""" | 85 """Called before tests run.""" |
65 pass | 86 pass |
66 | 87 |
| 88 def HasTests(self): |
| 89 """Whether the test suite has tests to run.""" |
| 90 return True |
| 91 |
67 def RunTests(self): | 92 def RunTests(self): |
68 """Runs the tests. Need to be overridden.""" | 93 """Runs the tests. Need to be overridden.""" |
69 raise NotImplementedError | 94 raise NotImplementedError |
70 | 95 |
71 def TearDown(self): | 96 def TearDown(self): |
72 """Called when tests finish running.""" | 97 """Called when tests finish running.""" |
73 self.ShutdownHelperToolsForTestSuite() | 98 self.ShutdownHelperToolsForTestSuite() |
74 | 99 |
75 def CopyTestData(self, test_data_paths, dest_dir): | 100 def CopyTestData(self, test_data_paths, dest_dir): |
76 """Copies |test_data_paths| list of files/directories to |dest_dir|. | 101 """Copies |test_data_paths| list of files/directories to |dest_dir|. |
77 | 102 |
78 Args: | 103 Args: |
79 test_data_paths: A list of files or directories relative to |dest_dir| | 104 test_data_paths: A list of files or directories relative to |dest_dir| |
80 which should be copied to the device. The paths must exist in | 105 which should be copied to the device. The paths must exist in |
81 |CHROME_DIR|. | 106 |CHROME_DIR|. |
82 dest_dir: Absolute path to copy to on the device. | 107 dest_dir: Absolute path to copy to on the device. |
83 """ | 108 """ |
84 for p in test_data_paths: | 109 for p in test_data_paths: |
85 self.adb.PushIfNeeded( | 110 self.adb.PushIfNeeded( |
86 os.path.join(run_tests_helper.CHROME_DIR, p), | 111 os.path.join(constants.CHROME_DIR, p), |
87 os.path.join(dest_dir, p)) | 112 os.path.join(dest_dir, p)) |
88 | 113 |
89 def LaunchTestHttpServer(self, document_root, extra_config_contents=None): | 114 def LinkSdCardPathsToTempDir(self, paths): |
| 115 """Link |paths| which are under sdcard to /data/local/tmp. |
| 116 |
| 117 For example, the test data '/sdcard/my_data' will be linked to |
| 118 '/data/local/tmp/my_data'. |
| 119 |
| 120 Args: |
| 121 paths: A list of files and directories relative to /sdcard. |
| 122 """ |
| 123 links = set() |
| 124 for path in paths: |
| 125 link_name = os.path.dirname(path) |
| 126 assert link_name, 'Linked paths must be in a subdir of /sdcard/.' |
| 127 link_name = link_name.split('/')[0] |
| 128 if link_name not in links: |
| 129 mapped_device_path = '/data/local/tmp/' + link_name |
| 130 # Unlink the mapped_device_path at first in case it was mapped to |
| 131 # a wrong path. Add option '-r' becuase the old path could be a dir. |
| 132 self.adb.RunShellCommand('rm -r %s' % mapped_device_path) |
| 133 self.adb.RunShellCommand( |
| 134 'ln -s /sdcard/%s %s' % (link_name, mapped_device_path)) |
| 135 links.add(link_name) |
| 136 |
| 137 def LaunchTestHttpServer(self, document_root, port=None, |
| 138 extra_config_contents=None): |
90 """Launches an HTTP server to serve HTTP tests. | 139 """Launches an HTTP server to serve HTTP tests. |
91 | 140 |
92 Args: | 141 Args: |
93 document_root: Document root of the HTTP server. | 142 document_root: Document root of the HTTP server. |
| 143 port: port on which we want to the http server bind. |
94 extra_config_contents: Extra config contents for the HTTP server. | 144 extra_config_contents: Extra config contents for the HTTP server. |
95 """ | 145 """ |
96 self._http_server = lighttpd_server.LighttpdServer( | 146 self._http_server = lighttpd_server.LighttpdServer( |
97 document_root, extra_config_contents=extra_config_contents) | 147 document_root, port=port, extra_config_contents=extra_config_contents) |
98 if self._http_server.StartupHttpServer(): | 148 if self._http_server.StartupHttpServer(): |
99 logging.info('http server started: http://localhost:%s', | 149 logging.info('http server started: http://localhost:%s', |
100 self._http_server.port) | 150 self._http_server.port) |
101 else: | 151 else: |
102 logging.critical('Failed to start http server') | 152 logging.critical('Failed to start http server') |
103 # Root access needed to make the forwarder executable work. | |
104 self.adb.EnableAdbRoot() | |
105 self.StartForwarderForHttpServer() | 153 self.StartForwarderForHttpServer() |
106 | 154 |
| 155 def StartForwarder(self, port_pairs): |
| 156 """Starts TCP traffic forwarding for the given |port_pairs|. |
| 157 |
| 158 Args: |
| 159 host_port_pairs: A list of (device_port, local_port) tuples to forward. |
| 160 """ |
| 161 # Sometimes the forwarder device port may be already used. We have to kill |
| 162 # all forwarder processes to ensure that the forwarder can be started since |
| 163 # currently we can not associate the specified port to related pid. |
| 164 self.adb.KillAll('forwarder') |
| 165 if self._forwarder: |
| 166 self._forwarder.Close() |
| 167 self._forwarder = Forwarder( |
| 168 self.adb, port_pairs, self.tool, '127.0.0.1') |
| 169 |
107 def StartForwarderForHttpServer(self): | 170 def StartForwarderForHttpServer(self): |
108 """Starts a forwarder for the HTTP server. | 171 """Starts a forwarder for the HTTP server. |
109 | 172 |
110 The forwarder forwards HTTP requests and responses between host and device. | 173 The forwarder forwards HTTP requests and responses between host and device. |
111 """ | 174 """ |
112 # Sometimes the forwarder device port may be already used. We have to kill | 175 self.StartForwarder([(self._forwarder_device_port, self._http_server.port)]) |
113 # all forwarder processes to ensure that the forwarder can be started since | |
114 # currently we can not associate the specified port to related pid. | |
115 # TODO(yfriedman/wangxianzhu): This doesn't work as most of the time the | |
116 # port is in use but the forwarder is already dead. Killing all forwarders | |
117 # is overly destructive and breaks other tests which make use of forwarders. | |
118 # if IsDevicePortUsed(self.adb, self._forwarder_device_port): | |
119 # self.adb.KillAll('forwarder') | |
120 self._forwarder = run_tests_helper.ForwardDevicePorts( | |
121 self.adb, [(self._forwarder_device_port, self._http_server.port)]) | |
122 | 176 |
123 def RestartHttpServerForwarderIfNecessary(self): | 177 def RestartHttpServerForwarderIfNecessary(self): |
124 """Restarts the forwarder if it's not open.""" | 178 """Restarts the forwarder if it's not open.""" |
125 # Checks to see if the http server port is being used. If not forwards the | 179 # Checks to see if the http server port is being used. If not forwards the |
126 # request. | 180 # request. |
127 # TODO(dtrainor): This is not always reliable because sometimes the port | 181 # TODO(dtrainor): This is not always reliable because sometimes the port |
128 # will be left open even after the forwarder has been killed. | 182 # will be left open even after the forwarder has been killed. |
129 if not run_tests_helper.IsDevicePortUsed(self.adb, | 183 if not ports.IsDevicePortUsed(self.adb, |
130 self._forwarder_device_port): | 184 self._forwarder_device_port): |
131 self.StartForwarderForHttpServer() | 185 self.StartForwarderForHttpServer() |
132 | 186 |
133 def ShutdownHelperToolsForTestSuite(self): | 187 def ShutdownHelperToolsForTestSuite(self): |
134 """Shuts down the server and the forwarder.""" | 188 """Shuts down the server and the forwarder.""" |
135 # Forwarders should be killed before the actual servers they're forwarding | 189 # Forwarders should be killed before the actual servers they're forwarding |
136 # to as they are clients potentially with open connections and to allow for | 190 # to as they are clients potentially with open connections and to allow for |
137 # proper hand-shake/shutdown. | 191 # proper hand-shake/shutdown. |
138 if self._forwarder or self._spawner_forwarder: | 192 if self._forwarder or self._spawner_forwarder: |
139 # Kill all forwarders on the device and then kill the process on the host | 193 # Kill all forwarders on the device and then kill the process on the host |
140 # (if it exists) | 194 # (if it exists) |
141 self.adb.KillAll('forwarder') | 195 self.adb.KillAll('forwarder') |
142 if self._forwarder: | 196 if self._forwarder: |
143 self._forwarder.kill() | 197 self._forwarder.Close() |
144 if self._spawner_forwarder: | 198 if self._spawner_forwarder: |
145 self._spawner_forwarder.kill() | 199 self._spawner_forwarder.Close() |
146 if self._http_server: | 200 if self._http_server: |
147 self._http_server.ShutdownHttpServer() | 201 self._http_server.ShutdownHttpServer() |
148 if self._spawning_server: | 202 if self._spawning_server: |
149 self._spawning_server.Stop() | 203 self._spawning_server.Stop() |
150 self.flags.Restore() | 204 self.flags.Restore() |
151 | 205 |
152 def LaunchChromeTestServerSpawner(self): | 206 def LaunchChromeTestServerSpawner(self): |
153 """Launches test server spawner.""" | 207 """Launches test server spawner.""" |
154 self._spawning_server = SpawningServer(TEST_SERVER_SPAWNER_PORT, | 208 server_ready = False |
155 TEST_SERVER_PORT) | 209 error_msgs = [] |
156 self._spawning_server.Start() | 210 # Try 3 times to launch test spawner server. |
157 # TODO(yfriedman): Ideally we'll only try to start up a port forwarder if | 211 for i in xrange(0, 3): |
158 # there isn't one already running but for now we just get an error message | 212 # Do not allocate port for test server here. We will allocate |
159 # and the existing forwarder still works. | 213 # different port for individual test in TestServerThread. |
160 self._spawner_forwarder = run_tests_helper.ForwardDevicePorts( | 214 self.test_server_spawner_port = ports.AllocateTestServerPort() |
161 self.adb, [(TEST_SERVER_SPAWNER_PORT, TEST_SERVER_SPAWNER_PORT), | 215 self._spawning_server = SpawningServer(self.test_server_spawner_port, |
162 (TEST_SERVER_PORT, TEST_SERVER_PORT)]) | 216 self.adb, |
| 217 self.tool) |
| 218 self._spawning_server.Start() |
| 219 server_ready, error_msg = ports.IsHttpServerConnectable( |
| 220 '127.0.0.1', self.test_server_spawner_port, path='/ping', |
| 221 expected_read='ready') |
| 222 if server_ready: |
| 223 break |
| 224 else: |
| 225 error_msgs.append(error_msg) |
| 226 self._spawning_server.Stop() |
| 227 # Wait for 2 seconds then restart. |
| 228 time.sleep(2) |
| 229 if not server_ready: |
| 230 logging.error(';'.join(error_msgs)) |
| 231 raise Exception('Can not start the test spawner server.') |
| 232 self._PushTestServerPortInfoToDevice() |
| 233 self._spawner_forwarder = Forwarder( |
| 234 self.adb, |
| 235 [(self.test_server_spawner_port, self.test_server_spawner_port)], |
| 236 self.tool, '127.0.0.1') |
OLD | NEW |