blob: b51effb23f82bcf898e68e9a48ad125906a02e15 [file] [log] [blame]
[email protected]fbe29322013-07-09 09:03:261#!/usr/bin/env python
2#
3# Copyright 2013 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 types of tests from one unified interface.
8
9TODO(gkanwar):
10* Add options to run Monkey tests.
11"""
12
13import collections
14import optparse
15import os
[email protected]6bc1bda22013-07-19 22:08:3716import shutil
[email protected]fbe29322013-07-09 09:03:2617import sys
18
19from pylib import cmd_helper
20from pylib import constants
21from pylib import ports
22from pylib.base import base_test_result
[email protected]6bc1bda22013-07-19 22:08:3723from pylib.base import test_dispatcher
[email protected]6bc1bda22013-07-19 22:08:3724from pylib.gtest import setup as gtest_setup
25from pylib.gtest import gtest_config
[email protected]fbe29322013-07-09 09:03:2626from pylib.host_driven import run_python_tests as python_dispatch
[email protected]6bc1bda22013-07-19 22:08:3727from pylib.instrumentation import setup as instrumentation_setup
28from pylib.uiautomator import setup as uiautomator_setup
29from pylib.utils import report_results
30from pylib.utils import run_tests_helper
[email protected]fbe29322013-07-09 09:03:2631
32
33_SDK_OUT_DIR = os.path.join(constants.DIR_SOURCE_ROOT, 'out')
34
35
36def AddBuildTypeOption(option_parser):
37 """Adds the build type option to |option_parser|."""
38 default_build_type = 'Debug'
39 if 'BUILDTYPE' in os.environ:
40 default_build_type = os.environ['BUILDTYPE']
41 option_parser.add_option('--debug', action='store_const', const='Debug',
42 dest='build_type', default=default_build_type,
43 help=('If set, run test suites under out/Debug. '
44 'Default is env var BUILDTYPE or Debug.'))
45 option_parser.add_option('--release', action='store_const',
46 const='Release', dest='build_type',
47 help=('If set, run test suites under out/Release.'
48 ' Default is env var BUILDTYPE or Debug.'))
49
50
[email protected]fbe29322013-07-09 09:03:2651def AddCommonOptions(option_parser):
52 """Adds all common options to |option_parser|."""
53
54 AddBuildTypeOption(option_parser)
55
[email protected]fbe29322013-07-09 09:03:2656 option_parser.add_option('-c', dest='cleanup_test_files',
57 help='Cleanup test files on the device after run',
58 action='store_true')
59 option_parser.add_option('--num_retries', dest='num_retries', type='int',
60 default=2,
61 help=('Number of retries for a test before '
62 'giving up.'))
63 option_parser.add_option('-v',
64 '--verbose',
65 dest='verbose_count',
66 default=0,
67 action='count',
68 help='Verbose level (multiple times for more)')
69 profilers = ['devicestatsmonitor', 'chrometrace', 'dumpheap', 'smaps',
70 'traceview']
71 option_parser.add_option('--profiler', dest='profilers', action='append',
72 choices=profilers,
73 help=('Profiling tool to run during test. Pass '
74 'multiple times to run multiple profilers. '
75 'Available profilers: %s' % profilers))
76 option_parser.add_option('--tool',
77 dest='tool',
78 help=('Run the test under a tool '
79 '(use --tool help to list them)'))
80 option_parser.add_option('--flakiness-dashboard-server',
81 dest='flakiness_dashboard_server',
82 help=('Address of the server that is hosting the '
83 'Chrome for Android flakiness dashboard.'))
84 option_parser.add_option('--skip-deps-push', dest='push_deps',
85 action='store_false', default=True,
86 help=('Do not push dependencies to the device. '
87 'Use this at own risk for speeding up test '
88 'execution on local machine.'))
[email protected]fbe29322013-07-09 09:03:2689 option_parser.add_option('-d', '--device', dest='test_device',
90 help=('Target device for the test suite '
91 'to run on.'))
92
93
94def ProcessCommonOptions(options):
95 """Processes and handles all common options."""
[email protected]fbe29322013-07-09 09:03:2696 run_tests_helper.SetLogLevel(options.verbose_count)
97
98
[email protected]fbe29322013-07-09 09:03:2699def AddGTestOptions(option_parser):
100 """Adds gtest options to |option_parser|."""
101
102 option_parser.usage = '%prog gtest [options]'
103 option_parser.command_list = []
104 option_parser.example = '%prog gtest -s base_unittests'
105
[email protected]6bc1bda22013-07-19 22:08:37106 # TODO(gkanwar): Make this option required
107 option_parser.add_option('-s', '--suite', dest='suite_name',
[email protected]fbe29322013-07-09 09:03:26108 help=('Executable name of the test suite to run '
109 '(use -s help to list them).'))
[email protected]9e689252013-07-30 20:14:36110 option_parser.add_option('-f', '--gtest_filter', dest='test_filter',
111 help='googletest-style filter string.')
112 option_parser.add_option('-a', '--test_arguments', dest='test_arguments',
113 help='Additional arguments to pass to the test.')
114 option_parser.add_option('-t', dest='timeout',
115 help='Timeout to wait for each test',
116 type='int',
117 default=60)
[email protected]fbe29322013-07-09 09:03:26118 # TODO(gkanwar): Move these to Common Options once we have the plumbing
119 # in our other test types to handle these commands
[email protected]fbe29322013-07-09 09:03:26120 AddCommonOptions(option_parser)
121
122
[email protected]6bc1bda22013-07-19 22:08:37123def ProcessGTestOptions(options):
124 """Intercept test suite help to list test suites.
125
126 Args:
127 options: Command line options.
128
129 Returns:
130 True if the command should continue.
131 """
132 if options.suite_name == 'help':
133 print 'Available test suites are:'
[email protected]9e689252013-07-30 20:14:36134 for test_suite in (gtest_config.STABLE_TEST_SUITES +
135 gtest_config.EXPERIMENTAL_TEST_SUITES):
136 print test_suite
[email protected]6bc1bda22013-07-19 22:08:37137 return False
138
139 # Convert to a list, assuming all test suites if nothing was specified.
140 # TODO(gkanwar): Require having a test suite
141 if options.suite_name:
142 options.suite_name = [options.suite_name]
143 else:
[email protected]9e689252013-07-30 20:14:36144 options.suite_name = [s for s in gtest_config.STABLE_TEST_SUITES]
[email protected]6bc1bda22013-07-19 22:08:37145 return True
146
147
[email protected]fbe29322013-07-09 09:03:26148def AddJavaTestOptions(option_parser):
149 """Adds the Java test options to |option_parser|."""
150
151 option_parser.add_option('-f', '--test_filter', dest='test_filter',
152 help=('Test filter (if not fully qualified, '
153 'will run all matches).'))
154 option_parser.add_option(
155 '-A', '--annotation', dest='annotation_str',
156 help=('Comma-separated list of annotations. Run only tests with any of '
157 'the given annotations. An annotation can be either a key or a '
158 'key-values pair. A test that has no annotation is considered '
159 '"SmallTest".'))
160 option_parser.add_option(
161 '-E', '--exclude-annotation', dest='exclude_annotation_str',
162 help=('Comma-separated list of annotations. Exclude tests with these '
163 'annotations.'))
164 option_parser.add_option('-j', '--java_only', action='store_true',
165 default=False, help='Run only the Java tests.')
166 option_parser.add_option('-p', '--python_only', action='store_true',
167 default=False,
168 help='Run only the host-driven tests.')
169 option_parser.add_option('--screenshot', dest='screenshot_failures',
170 action='store_true',
171 help='Capture screenshots of test failures')
172 option_parser.add_option('--save-perf-json', action='store_true',
173 help='Saves the JSON file for each UI Perf test.')
[email protected]fbe29322013-07-09 09:03:26174 option_parser.add_option('--official-build', help='Run official build tests.')
175 option_parser.add_option('--python_test_root',
176 help='Root of the host-driven tests.')
177 option_parser.add_option('--keep_test_server_ports',
178 action='store_true',
179 help=('Indicates the test server ports must be '
180 'kept. When this is run via a sharder '
181 'the test server ports should be kept and '
182 'should not be reset.'))
183 # TODO(gkanwar): This option is deprecated. Remove it in the future.
184 option_parser.add_option('--disable_assertions', action='store_true',
185 help=('(DEPRECATED) Run with java assertions '
186 'disabled.'))
187 option_parser.add_option('--test_data', action='append', default=[],
188 help=('Each instance defines a directory of test '
189 'data that should be copied to the target(s) '
190 'before running the tests. The argument '
191 'should be of the form <target>:<source>, '
192 '<target> is relative to the device data'
193 'directory, and <source> is relative to the '
194 'chromium build directory.'))
195
196
197def ProcessJavaTestOptions(options, error_func):
198 """Processes options/arguments and populates |options| with defaults."""
199
200 if options.java_only and options.python_only:
201 error_func('Options java_only (-j) and python_only (-p) '
202 'are mutually exclusive.')
203 options.run_java_tests = True
204 options.run_python_tests = True
205 if options.java_only:
206 options.run_python_tests = False
207 elif options.python_only:
208 options.run_java_tests = False
209
210 if not options.python_test_root:
211 options.run_python_tests = False
212
213 if options.annotation_str:
214 options.annotations = options.annotation_str.split(',')
215 elif options.test_filter:
216 options.annotations = []
217 else:
[email protected]6bc1bda22013-07-19 22:08:37218 options.annotations = ['Smoke', 'SmallTest', 'MediumTest', 'LargeTest',
219 'EnormousTest']
[email protected]fbe29322013-07-09 09:03:26220
221 if options.exclude_annotation_str:
222 options.exclude_annotations = options.exclude_annotation_str.split(',')
223 else:
224 options.exclude_annotations = []
225
226 if not options.keep_test_server_ports:
227 if not ports.ResetTestServerPortAllocation():
228 raise Exception('Failed to reset test server port.')
229
230
231def AddInstrumentationTestOptions(option_parser):
232 """Adds Instrumentation test options to |option_parser|."""
233
234 option_parser.usage = '%prog instrumentation [options]'
235 option_parser.command_list = []
[email protected]fb7ab5e82013-07-26 18:31:20236 option_parser.example = ('%prog instrumentation '
[email protected]fbe29322013-07-09 09:03:26237 '--test-apk=ChromiumTestShellTest')
238
239 AddJavaTestOptions(option_parser)
240 AddCommonOptions(option_parser)
241
242 option_parser.add_option('-w', '--wait_debugger', dest='wait_for_debugger',
243 action='store_true',
244 help='Wait for debugger.')
[email protected]fb7ab5e82013-07-26 18:31:20245 #TODO(craigdh): Remove option once -I is no longer passed downstream.
[email protected]fbe29322013-07-09 09:03:26246 option_parser.add_option('-I', dest='install_apk', action='store_true',
[email protected]fb7ab5e82013-07-26 18:31:20247 help='(DEPRECATED) Install the test apk.')
[email protected]fbe29322013-07-09 09:03:26248 option_parser.add_option(
249 '--test-apk', dest='test_apk',
250 help=('The name of the apk containing the tests '
251 '(without the .apk extension; e.g. "ContentShellTest"). '
252 'Alternatively, this can be a full path to the apk.'))
253
254
255def ProcessInstrumentationOptions(options, error_func):
256 """Processes options/arguments and populate |options| with defaults."""
257
258 ProcessJavaTestOptions(options, error_func)
259
260 if not options.test_apk:
261 error_func('--test-apk must be specified.')
262
263 if os.path.exists(options.test_apk):
264 # The APK is fully qualified, assume the JAR lives along side.
265 options.test_apk_path = options.test_apk
266 options.test_apk_jar_path = (os.path.splitext(options.test_apk_path)[0] +
267 '.jar')
268 else:
269 options.test_apk_path = os.path.join(_SDK_OUT_DIR,
270 options.build_type,
271 constants.SDK_BUILD_APKS_DIR,
272 '%s.apk' % options.test_apk)
273 options.test_apk_jar_path = os.path.join(
274 _SDK_OUT_DIR, options.build_type, constants.SDK_BUILD_TEST_JAVALIB_DIR,
275 '%s.jar' % options.test_apk)
276
277
278def AddUIAutomatorTestOptions(option_parser):
279 """Adds UI Automator test options to |option_parser|."""
280
281 option_parser.usage = '%prog uiautomator [options]'
282 option_parser.command_list = []
283 option_parser.example = (
284 '%prog uiautomator --test-jar=chromium_testshell_uiautomator_tests'
285 ' --package-name=org.chromium.chrome.testshell')
286 option_parser.add_option(
287 '--package-name',
288 help='The package name used by the apk containing the application.')
289 option_parser.add_option(
290 '--test-jar', dest='test_jar',
291 help=('The name of the dexed jar containing the tests (without the '
292 '.dex.jar extension). Alternatively, this can be a full path '
293 'to the jar.'))
294
295 AddJavaTestOptions(option_parser)
296 AddCommonOptions(option_parser)
297
298
299def ProcessUIAutomatorOptions(options, error_func):
300 """Processes UIAutomator options/arguments."""
301
302 ProcessJavaTestOptions(options, error_func)
303
304 if not options.package_name:
305 error_func('--package-name must be specified.')
306
307 if not options.test_jar:
308 error_func('--test-jar must be specified.')
309
310 if os.path.exists(options.test_jar):
311 # The dexed JAR is fully qualified, assume the info JAR lives along side.
312 options.uiautomator_jar = options.test_jar
313 else:
314 options.uiautomator_jar = os.path.join(
315 _SDK_OUT_DIR, options.build_type, constants.SDK_BUILD_JAVALIB_DIR,
316 '%s.dex.jar' % options.test_jar)
317 options.uiautomator_info_jar = (
318 options.uiautomator_jar[:options.uiautomator_jar.find('.dex.jar')] +
319 '_java.jar')
320
321
[email protected]6bc1bda22013-07-19 22:08:37322def _RunGTests(options, error_func):
323 """Subcommand of RunTestsCommands which runs gtests."""
324 if not ProcessGTestOptions(options):
325 return 0
326
327 exit_code = 0
328 for suite_name in options.suite_name:
329 runner_factory, tests = gtest_setup.Setup(
[email protected]9e689252013-07-30 20:14:36330 suite_name, options.test_arguments,
[email protected]6bc1bda22013-07-19 22:08:37331 options.timeout, options.cleanup_test_files, options.tool,
[email protected]db3bec22013-07-23 20:10:42332 options.build_type, options.push_deps, options.test_filter)
[email protected]6bc1bda22013-07-19 22:08:37333
334 results, test_exit_code = test_dispatcher.RunTests(
335 tests, runner_factory, False, options.test_device,
336 shard=True,
337 build_type=options.build_type,
338 test_timeout=None,
339 num_retries=options.num_retries)
340
341 if test_exit_code and exit_code != constants.ERROR_EXIT_CODE:
342 exit_code = test_exit_code
343
344 report_results.LogFull(
345 results=results,
346 test_type='Unit test',
347 test_package=suite_name,
348 build_type=options.build_type,
349 flakiness_server=options.flakiness_dashboard_server)
350
351 if os.path.isdir(constants.ISOLATE_DEPS_DIR):
352 shutil.rmtree(constants.ISOLATE_DEPS_DIR)
353
354 return exit_code
355
356
[email protected]6bc1bda22013-07-19 22:08:37357def _RunInstrumentationTests(options, error_func):
358 """Subcommand of RunTestsCommands which runs instrumentation tests."""
359 ProcessInstrumentationOptions(options, error_func)
360
361 results = base_test_result.TestRunResults()
362 exit_code = 0
363
364 if options.run_java_tests:
365 runner_factory, tests = instrumentation_setup.Setup(
366 options.test_apk_path, options.test_apk_jar_path, options.annotations,
367 options.exclude_annotations, options.test_filter, options.build_type,
[email protected]fb7ab5e82013-07-26 18:31:20368 options.test_data, options.save_perf_json, options.screenshot_failures,
369 options.tool, options.wait_for_debugger, options.disable_assertions,
370 options.push_deps, options.cleanup_test_files)
[email protected]6bc1bda22013-07-19 22:08:37371
372 test_results, exit_code = test_dispatcher.RunTests(
373 tests, runner_factory, options.wait_for_debugger,
374 options.test_device,
375 shard=True,
376 build_type=options.build_type,
377 test_timeout=None,
378 num_retries=options.num_retries)
379
380 results.AddTestRunResults(test_results)
381
382 if options.run_python_tests:
383 test_results, test_exit_code = (
384 python_dispatch.DispatchPythonTests(options))
385
386 results.AddTestRunResults(test_results)
387
388 # Only allow exit code escalation
389 if test_exit_code and exit_code != constants.ERROR_EXIT_CODE:
390 exit_code = test_exit_code
391
392 report_results.LogFull(
393 results=results,
394 test_type='Instrumentation',
395 test_package=os.path.basename(options.test_apk),
396 annotation=options.annotations,
397 build_type=options.build_type,
398 flakiness_server=options.flakiness_dashboard_server)
399
400 return exit_code
401
402
403def _RunUIAutomatorTests(options, error_func):
404 """Subcommand of RunTestsCommands which runs uiautomator tests."""
405 ProcessUIAutomatorOptions(options, error_func)
406
407 results = base_test_result.TestRunResults()
408 exit_code = 0
409
410 if options.run_java_tests:
411 runner_factory, tests = uiautomator_setup.Setup(
412 options.uiautomator_jar, options.uiautomator_info_jar,
413 options.annotations, options.exclude_annotations, options.test_filter,
414 options.package_name, options.build_type, options.test_data,
415 options.save_perf_json, options.screenshot_failures, options.tool,
416 options.disable_assertions, options.push_deps,
417 options.cleanup_test_files)
418
419 test_results, exit_code = test_dispatcher.RunTests(
420 tests, runner_factory, False, options.test_device,
421 shard=True,
422 build_type=options.build_type,
423 test_timeout=None,
424 num_retries=options.num_retries)
425
426 results.AddTestRunResults(test_results)
427
428 if options.run_python_tests:
429 test_results, test_exit_code = (
430 python_dispatch.DispatchPythonTests(options))
431
432 results.AddTestRunResults(test_results)
433
434 # Only allow exit code escalation
435 if test_exit_code and exit_code != constants.ERROR_EXIT_CODE:
436 exit_code = test_exit_code
437
438 report_results.LogFull(
439 results=results,
440 test_type='UIAutomator',
441 test_package=os.path.basename(options.test_jar),
442 annotation=options.annotations,
443 build_type=options.build_type,
444 flakiness_server=options.flakiness_dashboard_server)
445
446 return exit_code
447
448
[email protected]fbe29322013-07-09 09:03:26449def RunTestsCommand(command, options, args, option_parser):
450 """Checks test type and dispatches to the appropriate function.
451
452 Args:
453 command: String indicating the command that was received to trigger
454 this function.
455 options: optparse options dictionary.
456 args: List of extra args from optparse.
457 option_parser: optparse.OptionParser object.
458
459 Returns:
460 Integer indicated exit code.
[email protected]b3873892013-07-10 04:57:10461
462 Raises:
463 Exception: Unknown command name passed in, or an exception from an
464 individual test runner.
[email protected]fbe29322013-07-09 09:03:26465 """
466
[email protected]d82f0252013-07-12 23:22:57467 # Check for extra arguments
468 if len(args) > 2:
469 option_parser.error('Unrecognized arguments: %s' % (' '.join(args[2:])))
470 return constants.ERROR_EXIT_CODE
471
[email protected]fbe29322013-07-09 09:03:26472 ProcessCommonOptions(options)
473
[email protected]fbe29322013-07-09 09:03:26474 if command == 'gtest':
[email protected]6bc1bda22013-07-19 22:08:37475 return _RunGTests(options, option_parser.error)
[email protected]fbe29322013-07-09 09:03:26476 elif command == 'instrumentation':
[email protected]6bc1bda22013-07-19 22:08:37477 return _RunInstrumentationTests(options, option_parser.error)
[email protected]fbe29322013-07-09 09:03:26478 elif command == 'uiautomator':
[email protected]6bc1bda22013-07-19 22:08:37479 return _RunUIAutomatorTests(options, option_parser.error)
[email protected]fbe29322013-07-09 09:03:26480 else:
[email protected]6bc1bda22013-07-19 22:08:37481 raise Exception('Unknown test type.')
[email protected]fbe29322013-07-09 09:03:26482
[email protected]b3873892013-07-10 04:57:10483 return exit_code
[email protected]fbe29322013-07-09 09:03:26484
485
486def HelpCommand(command, options, args, option_parser):
487 """Display help for a certain command, or overall help.
488
489 Args:
490 command: String indicating the command that was received to trigger
491 this function.
492 options: optparse options dictionary.
493 args: List of extra args from optparse.
494 option_parser: optparse.OptionParser object.
495
496 Returns:
497 Integer indicated exit code.
498 """
499 # If we don't have any args, display overall help
500 if len(args) < 3:
501 option_parser.print_help()
502 return 0
[email protected]d82f0252013-07-12 23:22:57503 # If we have too many args, print an error
504 if len(args) > 3:
505 option_parser.error('Unrecognized arguments: %s' % (' '.join(args[3:])))
506 return constants.ERROR_EXIT_CODE
[email protected]fbe29322013-07-09 09:03:26507
508 command = args[2]
509
510 if command not in VALID_COMMANDS:
511 option_parser.error('Unrecognized command.')
512
513 # Treat the help command as a special case. We don't care about showing a
514 # specific help page for itself.
515 if command == 'help':
516 option_parser.print_help()
517 return 0
518
519 VALID_COMMANDS[command].add_options_func(option_parser)
520 option_parser.usage = '%prog ' + command + ' [options]'
521 option_parser.command_list = None
522 option_parser.print_help()
523
524 return 0
525
526
527# Define a named tuple for the values in the VALID_COMMANDS dictionary so the
528# syntax is a bit prettier. The tuple is two functions: (add options, run
529# command).
530CommandFunctionTuple = collections.namedtuple(
531 'CommandFunctionTuple', ['add_options_func', 'run_command_func'])
532VALID_COMMANDS = {
533 'gtest': CommandFunctionTuple(AddGTestOptions, RunTestsCommand),
[email protected]fbe29322013-07-09 09:03:26534 'instrumentation': CommandFunctionTuple(
535 AddInstrumentationTestOptions, RunTestsCommand),
536 'uiautomator': CommandFunctionTuple(
537 AddUIAutomatorTestOptions, RunTestsCommand),
538 'help': CommandFunctionTuple(lambda option_parser: None, HelpCommand)
539 }
540
541
542class CommandOptionParser(optparse.OptionParser):
543 """Wrapper class for OptionParser to help with listing commands."""
544
545 def __init__(self, *args, **kwargs):
546 self.command_list = kwargs.pop('command_list', [])
547 self.example = kwargs.pop('example', '')
548 optparse.OptionParser.__init__(self, *args, **kwargs)
549
550 #override
551 def get_usage(self):
552 normal_usage = optparse.OptionParser.get_usage(self)
553 command_list = self.get_command_list()
554 example = self.get_example()
555 return self.expand_prog_name(normal_usage + example + command_list)
556
557 #override
558 def get_command_list(self):
559 if self.command_list:
560 return '\nCommands:\n %s\n' % '\n '.join(sorted(self.command_list))
561 return ''
562
563 def get_example(self):
564 if self.example:
565 return '\nExample:\n %s\n' % self.example
566 return ''
567
[email protected]b3873892013-07-10 04:57:10568
[email protected]fbe29322013-07-09 09:03:26569def main(argv):
570 option_parser = CommandOptionParser(
571 usage='Usage: %prog <command> [options]',
572 command_list=VALID_COMMANDS.keys())
573
574 if len(argv) < 2 or argv[1] not in VALID_COMMANDS:
575 option_parser.print_help()
576 return 0
577 command = argv[1]
578 VALID_COMMANDS[command].add_options_func(option_parser)
579 options, args = option_parser.parse_args(argv)
[email protected]b3873892013-07-10 04:57:10580 return VALID_COMMANDS[command].run_command_func(
[email protected]fbe29322013-07-09 09:03:26581 command, options, args, option_parser)
582
[email protected]fbe29322013-07-09 09:03:26583
584if __name__ == '__main__':
585 sys.exit(main(sys.argv))