media/webrtc/trunk/build/android/run_tests.py

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:2fd501ad0744
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
6
7 """Runs all the native unit tests.
8
9 1. Copy over test binary to /data/local on device.
10 2. Resources: chrome/unit_tests requires resources (chrome.pak and en-US.pak)
11 to be deployed to the device. We use the device's $EXTERNAL_STORAGE as the
12 base dir (which maps to Context.getExternalFilesDir()).
13 3. Environment:
14 3.1. chrome/unit_tests requires (via chrome_paths.cc) a directory named:
15 $EXTERNAL_STORAGE + /chrome/test/data
16 3.2. page_cycler_tests have following requirements,
17 3.2.1 the following data on host:
18 <chrome_src_dir>/tools/page_cycler
19 <chrome_src_dir>/data/page_cycler
20 3.2.2. two data directories to store above test data on device named:
21 $EXTERNAL_STORAGE + /tools/ (for database perf test)
22 $EXTERNAL_STORAGE + /data/ (for other perf tests)
23 3.2.3. a http server to serve http perf tests.
24 The http root is host's <chrome_src_dir>/data/page_cycler/, port 8000.
25 3.2.4 a tool named forwarder is also required to run on device to
26 forward the http request/response between host and device.
27 3.2.5 Chrome is installed on device.
28 4. Run the binary in the device and stream the log to the host.
29 4.1. Optionally, filter specific tests.
30 4.2. Optionally, rebaseline: run the available tests and update the
31 suppressions file for failures.
32 4.3. If we're running a single test suite and we have multiple devices
33 connected, we'll shard the tests.
34 5. Clean up the device.
35
36 Suppressions:
37
38 Individual tests in a test binary can be suppressed by listing it in
39 the gtest_filter directory in a file of the same name as the test binary,
40 one test per line. Here is an example:
41
42 $ cat gtest_filter/base_unittests_disabled
43 DataPackTest.Load
44 ReadOnlyFileUtilTest.ContentsEqual
45
46 This file is generated by the tests running on devices. If running on emulator,
47 additonal filter file which lists the tests only failed in emulator will be
48 loaded. We don't care about the rare testcases which succeeded on emuatlor, but
49 failed on device.
50 """
51
52 import fnmatch
53 import logging
54 import optparse
55 import os
56 import signal
57 import subprocess
58 import sys
59 import time
60
61 from pylib import android_commands
62 from pylib.base_test_sharder import BaseTestSharder
63 from pylib import buildbot_report
64 from pylib import constants
65 from pylib import debug_info
66 import emulator
67 from pylib import ports
68 from pylib import run_tests_helper
69 from pylib import test_options_parser
70 from pylib.single_test_runner import SingleTestRunner
71 from pylib.test_result import BaseTestResult, TestResults
72
73
74 _TEST_SUITES = ['base_unittests',
75 'content_unittests',
76 'gpu_unittests',
77 'ipc_tests',
78 'media_unittests',
79 'net_unittests',
80 'sql_unittests',
81 'sync_unit_tests',
82 'ui_unittests',
83 'unit_tests',
84 ]
85
86
87 def TestSuiteDir(build_type):
88 """Return the base directory of test suites."""
89 return os.path.abspath(os.path.join(constants.CHROME_DIR, 'out', build_type))
90
91 def FullyQualifiedTestSuites(exe, option_test_suite, build_type):
92 """Return a fully qualified list
93
94 Args:
95 exe: if True, use the executable-based test runner.
96 option_test_suite: the test_suite specified as an option.
97 build_type: 'Release' or 'Debug'.
98 """
99 test_suite_dir = TestSuiteDir(build_type)
100 if option_test_suite:
101 all_test_suites = [option_test_suite]
102 else:
103 all_test_suites = _TEST_SUITES
104
105 if exe:
106 qualified_test_suites = [os.path.join(test_suite_dir, t)
107 for t in all_test_suites]
108 else:
109 # out/(Debug|Release)/$SUITE_apk/$SUITE-debug.apk
110 qualified_test_suites = [os.path.join(test_suite_dir,
111 t + '_apk',
112 t + '-debug.apk')
113 for t in all_test_suites]
114 for t, q in zip(all_test_suites, qualified_test_suites):
115 if not os.path.exists(q):
116 logging.critical('Test suite %s not found in %s.\n'
117 'Supported test suites:\n %s\n'
118 'Ensure it has been built.\n',
119 t, q, _TEST_SUITES)
120 return []
121 return qualified_test_suites
122
123
124 class TimeProfile(object):
125 """Class for simple profiling of action, with logging of cost."""
126
127 def __init__(self, description):
128 self._description = description
129 self.Start()
130
131 def Start(self):
132 self._starttime = time.time()
133
134 def Stop(self):
135 """Stop profiling and dump a log."""
136 if self._starttime:
137 stoptime = time.time()
138 logging.info('%fsec to perform %s',
139 stoptime - self._starttime, self._description)
140 self._starttime = None
141
142
143 class Xvfb(object):
144 """Class to start and stop Xvfb if relevant. Nop if not Linux."""
145
146 def __init__(self):
147 self._pid = 0
148
149 def _IsLinux(self):
150 """Return True if on Linux; else False."""
151 return sys.platform.startswith('linux')
152
153 def Start(self):
154 """Start Xvfb and set an appropriate DISPLAY environment. Linux only.
155
156 Copied from tools/code_coverage/coverage_posix.py
157 """
158 if not self._IsLinux():
159 return
160 proc = subprocess.Popen(['Xvfb', ':9', '-screen', '0', '1024x768x24',
161 '-ac'],
162 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
163 self._pid = proc.pid
164 if not self._pid:
165 raise Exception('Could not start Xvfb')
166 os.environ['DISPLAY'] = ':9'
167
168 # Now confirm, giving a chance for it to start if needed.
169 for _ in range(10):
170 proc = subprocess.Popen('xdpyinfo >/dev/null', shell=True)
171 _, retcode = os.waitpid(proc.pid, 0)
172 if retcode == 0:
173 break
174 time.sleep(0.25)
175 if retcode != 0:
176 raise Exception('Could not confirm Xvfb happiness')
177
178 def Stop(self):
179 """Stop Xvfb if needed. Linux only."""
180 if self._pid:
181 try:
182 os.kill(self._pid, signal.SIGKILL)
183 except:
184 pass
185 del os.environ['DISPLAY']
186 self._pid = 0
187
188
189 class TestSharder(BaseTestSharder):
190 """Responsible for sharding the tests on the connected devices."""
191
192 def __init__(self, attached_devices, test_suite, gtest_filter,
193 test_arguments, timeout, rebaseline, performance_test,
194 cleanup_test_files, tool, log_dump_name, fast_and_loose,
195 build_type):
196 BaseTestSharder.__init__(self, attached_devices)
197 self.test_suite = test_suite
198 self.test_suite_basename = os.path.basename(test_suite)
199 self.gtest_filter = gtest_filter or ''
200 self.test_arguments = test_arguments
201 self.timeout = timeout
202 self.rebaseline = rebaseline
203 self.performance_test = performance_test
204 self.cleanup_test_files = cleanup_test_files
205 self.tool = tool
206 self.log_dump_name = log_dump_name
207 self.fast_and_loose = fast_and_loose
208 self.build_type = build_type
209 test = SingleTestRunner(self.attached_devices[0], test_suite, gtest_filter,
210 test_arguments, timeout, rebaseline,
211 performance_test, cleanup_test_files, tool, 0,
212 not not self.log_dump_name, fast_and_loose,
213 build_type)
214 self.tests = []
215 if not self.gtest_filter:
216 # No filter has been specified, let's add all tests then.
217 # The executable/apk needs to be copied before we can call GetAllTests.
218 test.test_package.StripAndCopyExecutable()
219 all_tests = test.test_package.GetAllTests()
220 if not rebaseline:
221 disabled_list = test.GetDisabledTests()
222 # Only includes tests that do not have any match in the disabled list.
223 all_tests = filter(lambda t:
224 not any([fnmatch.fnmatch(t, disabled_pattern)
225 for disabled_pattern in disabled_list]),
226 all_tests)
227 self.tests = all_tests
228
229 def CreateShardedTestRunner(self, device, index):
230 """Creates a suite-specific test runner.
231
232 Args:
233 device: Device serial where this shard will run.
234 index: Index of this device in the pool.
235
236 Returns:
237 A SingleTestRunner object.
238 """
239 device_num = len(self.attached_devices)
240 shard_size = (len(self.tests) + device_num - 1) / device_num
241 shard_test_list = self.tests[index * shard_size : (index + 1) * shard_size]
242 test_filter = ':'.join(shard_test_list) + self.gtest_filter
243 return SingleTestRunner(device, self.test_suite,
244 test_filter, self.test_arguments, self.timeout,
245 self.rebaseline, self.performance_test,
246 self.cleanup_test_files, self.tool, index,
247 not not self.log_dump_name, self.fast_and_loose,
248 self.build_type)
249
250 def OnTestsCompleted(self, test_runners, test_results):
251 """Notifies that we completed the tests."""
252 test_results.LogFull('Unit test', os.path.basename(self.test_suite),
253 self.build_type)
254 test_results.PrintAnnotation()
255 if test_results.failed and self.rebaseline:
256 test_runners[0].UpdateFilter(test_results.failed)
257 if self.log_dump_name:
258 # Zip all debug info outputs into a file named by log_dump_name.
259 debug_info.GTestDebugInfo.ZipAndCleanResults(
260 os.path.join(TestSuiteDir(self.build_type), 'debug_info_dumps'),
261 self.log_dump_name)
262
263
264 def _RunATestSuite(options):
265 """Run a single test suite.
266
267 Helper for Dispatch() to allow stop/restart of the emulator across
268 test bundles. If using the emulator, we start it on entry and stop
269 it on exit.
270
271 Args:
272 options: options for running the tests.
273
274 Returns:
275 0 if successful, number of failing tests otherwise.
276 """
277 step_name = os.path.basename(options.test_suite).replace('-debug.apk', '')
278 buildbot_report.PrintNamedStep(step_name)
279 attached_devices = []
280 buildbot_emulators = []
281
282 if options.use_emulator:
283 for n in range(options.emulator_count):
284 t = TimeProfile('Emulator launch %d' % n)
285 avd_name = None
286 if n > 0:
287 # Creates a temporary AVD for the extra emulators.
288 avd_name = 'run_tests_avd_%d' % n
289 buildbot_emulator = emulator.Emulator(avd_name, options.fast_and_loose)
290 buildbot_emulator.Launch(kill_all_emulators=n == 0)
291 t.Stop()
292 buildbot_emulators.append(buildbot_emulator)
293 attached_devices.append(buildbot_emulator.device)
294 # Wait for all emulators to boot completed.
295 map(lambda buildbot_emulator: buildbot_emulator.ConfirmLaunch(True),
296 buildbot_emulators)
297 elif options.test_device:
298 attached_devices = [options.test_device]
299 else:
300 attached_devices = android_commands.GetAttachedDevices()
301
302 if not attached_devices:
303 logging.critical('A device must be attached and online.')
304 buildbot_report.PrintError()
305 return 1
306
307 # Reset the test port allocation. It's important to do it before starting
308 # to dispatch any tests.
309 if not ports.ResetTestServerPortAllocation():
310 raise Exception('Failed to reset test server port.')
311
312 if options.performance_test or options.gtest_filter:
313 # These configuration can't be split in multiple devices.
314 attached_devices = [attached_devices[0]]
315 sharder = TestSharder(attached_devices, options.test_suite,
316 options.gtest_filter, options.test_arguments,
317 options.timeout, options.rebaseline,
318 options.performance_test,
319 options.cleanup_test_files, options.tool,
320 options.log_dump, options.fast_and_loose,
321 options.build_type)
322 test_results = sharder.RunShardedTests()
323
324 for buildbot_emulator in buildbot_emulators:
325 buildbot_emulator.Shutdown()
326
327 # Another chance if we timed out? At this point It is safe(r) to
328 # run fast and loose since we just uploaded all the test data and
329 # binary.
330 if test_results.timed_out and options.repeat:
331 logging.critical('Timed out; repeating in fast_and_loose mode.')
332 options.fast_and_loose = True
333 options.repeat -= 1
334 logging.critical('Repeats left: ' + str(options.repeat))
335 return _RunATestSuite(options)
336 return len(test_results.failed)
337
338
339 def Dispatch(options):
340 """Dispatches the tests, sharding if possible.
341
342 If options.use_emulator is True, all tests will be run in new emulator
343 instance.
344
345 Args:
346 options: options for running the tests.
347
348 Returns:
349 0 if successful, number of failing tests otherwise.
350 """
351 if options.test_suite == 'help':
352 ListTestSuites()
353 return 0
354
355 if options.use_xvfb:
356 xvfb = Xvfb()
357 xvfb.Start()
358
359 all_test_suites = FullyQualifiedTestSuites(options.exe, options.test_suite,
360 options.build_type)
361 failures = 0
362 for suite in all_test_suites:
363 options.test_suite = suite
364 failures += _RunATestSuite(options)
365
366 if options.use_xvfb:
367 xvfb.Stop()
368 return failures
369
370
371 def ListTestSuites():
372 """Display a list of available test suites."""
373 print 'Available test suites are:'
374 for test_suite in _TEST_SUITES:
375 print test_suite
376
377
378 def main(argv):
379 option_parser = optparse.OptionParser()
380 test_options_parser.AddTestRunnerOptions(option_parser, default_timeout=0)
381 option_parser.add_option('-s', '--suite', dest='test_suite',
382 help='Executable name of the test suite to run '
383 '(use -s help to list them)')
384 option_parser.add_option('-d', '--device', dest='test_device',
385 help='Target device the test suite to run ')
386 option_parser.add_option('-r', dest='rebaseline',
387 help='Rebaseline and update *testsuite_disabled',
388 action='store_true')
389 option_parser.add_option('-f', '--gtest_filter', dest='gtest_filter',
390 help='gtest filter')
391 option_parser.add_option('-a', '--test_arguments', dest='test_arguments',
392 help='Additional arguments to pass to the test')
393 option_parser.add_option('-p', dest='performance_test',
394 help='Indicator of performance test',
395 action='store_true')
396 option_parser.add_option('-L', dest='log_dump',
397 help='file name of log dump, which will be put in '
398 'subfolder debug_info_dumps under the same '
399 'directory in where the test_suite exists.')
400 option_parser.add_option('-e', '--emulator', dest='use_emulator',
401 action='store_true',
402 help='Run tests in a new instance of emulator')
403 option_parser.add_option('-n', '--emulator_count',
404 type='int', default=1,
405 help='Number of emulators to launch for running the '
406 'tests.')
407 option_parser.add_option('-x', '--xvfb', dest='use_xvfb',
408 action='store_true',
409 help='Use Xvfb around tests (ignored if not Linux)')
410 option_parser.add_option('--fast', '--fast_and_loose', dest='fast_and_loose',
411 action='store_true',
412 help='Go faster (but be less stable), '
413 'for quick testing. Example: when tracking down '
414 'tests that hang to add to the disabled list, '
415 'there is no need to redeploy the test binary '
416 'or data to the device again. '
417 'Don\'t use on bots by default!')
418 option_parser.add_option('--repeat', dest='repeat', type='int',
419 default=2,
420 help='Repeat count on test timeout')
421 option_parser.add_option('--exit_code', action='store_true',
422 help='If set, the exit code will be total number '
423 'of failures.')
424 option_parser.add_option('--exe', action='store_true',
425 help='If set, use the exe test runner instead of '
426 'the APK.')
427
428 options, args = option_parser.parse_args(argv)
429 if len(args) > 1:
430 print 'Unknown argument:', args[1:]
431 option_parser.print_usage()
432 sys.exit(1)
433 run_tests_helper.SetLogLevel(options.verbose_count)
434 emulator.DeleteAllTempAVDs()
435 failed_tests_count = Dispatch(options)
436
437 # Failures of individual test suites are communicated by printing a
438 # STEP_FAILURE message.
439 # Returning a success exit status also prevents the buildbot from incorrectly
440 # marking the last suite as failed if there were failures in other suites in
441 # the batch (this happens because the exit status is a sum of all failures
442 # from all suites, but the buildbot associates the exit status only with the
443 # most recent step).
444 if options.exit_code:
445 return failed_tests_count
446 return 0
447
448
449 if __name__ == '__main__':
450 sys.exit(main(sys.argv))

mercurial