blob: 4113158fd38235779b852788867da6071b183cc3 [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
[email protected]fbe29322013-07-09 09:03:2619from pylib import constants
20from pylib import ports
21from pylib.base import base_test_result
[email protected]6bc1bda22013-07-19 22:08:3722from pylib.base import test_dispatcher
[email protected]6bc1bda22013-07-19 22:08:3723from pylib.gtest import gtest_config
[email protected]2a684222013-08-01 16:59:2224from pylib.gtest import setup as gtest_setup
25from pylib.gtest import test_options as gtest_test_options
[email protected]37ee0c792013-08-06 19:10:1326from pylib.host_driven import setup as host_driven_setup
[email protected]6bc1bda22013-07-19 22:08:3727from pylib.instrumentation import setup as instrumentation_setup
[email protected]2a684222013-08-01 16:59:2228from pylib.instrumentation import test_options as instrumentation_test_options
[email protected]6bc1bda22013-07-19 22:08:3729from pylib.uiautomator import setup as uiautomator_setup
[email protected]2a684222013-08-01 16:59:2230from pylib.uiautomator import test_options as uiautomator_test_options
[email protected]6bc1bda22013-07-19 22:08:3731from pylib.utils import report_results
32from pylib.utils import run_tests_helper
[email protected]fbe29322013-07-09 09:03:2633
34
35_SDK_OUT_DIR = os.path.join(constants.DIR_SOURCE_ROOT, 'out')
36
37
38def AddBuildTypeOption(option_parser):
39 """Adds the build type option to |option_parser|."""
40 default_build_type = 'Debug'
41 if 'BUILDTYPE' in os.environ:
42 default_build_type = os.environ['BUILDTYPE']
43 option_parser.add_option('--debug', action='store_const', const='Debug',
44 dest='build_type', default=default_build_type,
45 help=('If set, run test suites under out/Debug. '
46 'Default is env var BUILDTYPE or Debug.'))
47 option_parser.add_option('--release', action='store_const',
48 const='Release', dest='build_type',
49 help=('If set, run test suites under out/Release.'
50 ' Default is env var BUILDTYPE or Debug.'))
51
52
[email protected]fbe29322013-07-09 09:03:2653def AddCommonOptions(option_parser):
54 """Adds all common options to |option_parser|."""
55
56 AddBuildTypeOption(option_parser)
57
[email protected]fbe29322013-07-09 09:03:2658 option_parser.add_option('-c', dest='cleanup_test_files',
59 help='Cleanup test files on the device after run',
60 action='store_true')
61 option_parser.add_option('--num_retries', dest='num_retries', type='int',
62 default=2,
63 help=('Number of retries for a test before '
64 'giving up.'))
65 option_parser.add_option('-v',
66 '--verbose',
67 dest='verbose_count',
68 default=0,
69 action='count',
70 help='Verbose level (multiple times for more)')
71 profilers = ['devicestatsmonitor', 'chrometrace', 'dumpheap', 'smaps',
72 'traceview']
73 option_parser.add_option('--profiler', dest='profilers', action='append',
74 choices=profilers,
75 help=('Profiling tool to run during test. Pass '
76 'multiple times to run multiple profilers. '
77 'Available profilers: %s' % profilers))
78 option_parser.add_option('--tool',
79 dest='tool',
80 help=('Run the test under a tool '
81 '(use --tool help to list them)'))
82 option_parser.add_option('--flakiness-dashboard-server',
83 dest='flakiness_dashboard_server',
84 help=('Address of the server that is hosting the '
85 'Chrome for Android flakiness dashboard.'))
86 option_parser.add_option('--skip-deps-push', dest='push_deps',
87 action='store_false', default=True,
88 help=('Do not push dependencies to the device. '
89 'Use this at own risk for speeding up test '
90 'execution on local machine.'))
[email protected]fbe29322013-07-09 09:03:2691 option_parser.add_option('-d', '--device', dest='test_device',
92 help=('Target device for the test suite '
93 'to run on.'))
94
95
96def ProcessCommonOptions(options):
97 """Processes and handles all common options."""
[email protected]fbe29322013-07-09 09:03:2698 run_tests_helper.SetLogLevel(options.verbose_count)
99
100
[email protected]fbe29322013-07-09 09:03:26101def AddGTestOptions(option_parser):
102 """Adds gtest options to |option_parser|."""
103
104 option_parser.usage = '%prog gtest [options]'
105 option_parser.command_list = []
106 option_parser.example = '%prog gtest -s base_unittests'
107
[email protected]6bc1bda22013-07-19 22:08:37108 # TODO(gkanwar): Make this option required
109 option_parser.add_option('-s', '--suite', dest='suite_name',
[email protected]fbe29322013-07-09 09:03:26110 help=('Executable name of the test suite to run '
111 '(use -s help to list them).'))
[email protected]9e689252013-07-30 20:14:36112 option_parser.add_option('-f', '--gtest_filter', dest='test_filter',
113 help='googletest-style filter string.')
114 option_parser.add_option('-a', '--test_arguments', dest='test_arguments',
115 help='Additional arguments to pass to the test.')
116 option_parser.add_option('-t', dest='timeout',
117 help='Timeout to wait for each test',
118 type='int',
119 default=60)
[email protected]fbe29322013-07-09 09:03:26120 # TODO(gkanwar): Move these to Common Options once we have the plumbing
121 # in our other test types to handle these commands
[email protected]fbe29322013-07-09 09:03:26122 AddCommonOptions(option_parser)
123
124
[email protected]6bc1bda22013-07-19 22:08:37125def ProcessGTestOptions(options):
126 """Intercept test suite help to list test suites.
127
128 Args:
129 options: Command line options.
[email protected]6bc1bda22013-07-19 22:08:37130 """
131 if options.suite_name == 'help':
132 print 'Available test suites are:'
[email protected]9e689252013-07-30 20:14:36133 for test_suite in (gtest_config.STABLE_TEST_SUITES +
134 gtest_config.EXPERIMENTAL_TEST_SUITES):
135 print test_suite
[email protected]2a684222013-08-01 16:59:22136 sys.exit(0)
[email protected]6bc1bda22013-07-19 22:08:37137
138 # Convert to a list, assuming all test suites if nothing was specified.
139 # TODO(gkanwar): Require having a test suite
140 if options.suite_name:
141 options.suite_name = [options.suite_name]
142 else:
[email protected]9e689252013-07-30 20:14:36143 options.suite_name = [s for s in gtest_config.STABLE_TEST_SUITES]
[email protected]6bc1bda22013-07-19 22:08:37144
145
[email protected]fbe29322013-07-09 09:03:26146def AddJavaTestOptions(option_parser):
147 """Adds the Java test options to |option_parser|."""
148
149 option_parser.add_option('-f', '--test_filter', dest='test_filter',
150 help=('Test filter (if not fully qualified, '
151 'will run all matches).'))
152 option_parser.add_option(
153 '-A', '--annotation', dest='annotation_str',
154 help=('Comma-separated list of annotations. Run only tests with any of '
155 'the given annotations. An annotation can be either a key or a '
156 'key-values pair. A test that has no annotation is considered '
157 '"SmallTest".'))
158 option_parser.add_option(
159 '-E', '--exclude-annotation', dest='exclude_annotation_str',
160 help=('Comma-separated list of annotations. Exclude tests with these '
161 'annotations.'))
[email protected]fbe29322013-07-09 09:03:26162 option_parser.add_option('--screenshot', dest='screenshot_failures',
163 action='store_true',
164 help='Capture screenshots of test failures')
165 option_parser.add_option('--save-perf-json', action='store_true',
166 help='Saves the JSON file for each UI Perf test.')
[email protected]37ee0c792013-08-06 19:10:13167 option_parser.add_option('--official-build', action='store_true',
168 help='Run official build tests.')
[email protected]fbe29322013-07-09 09:03:26169 option_parser.add_option('--keep_test_server_ports',
170 action='store_true',
171 help=('Indicates the test server ports must be '
172 'kept. When this is run via a sharder '
173 'the test server ports should be kept and '
174 'should not be reset.'))
175 # TODO(gkanwar): This option is deprecated. Remove it in the future.
176 option_parser.add_option('--disable_assertions', action='store_true',
177 help=('(DEPRECATED) Run with java assertions '
178 'disabled.'))
179 option_parser.add_option('--test_data', action='append', default=[],
180 help=('Each instance defines a directory of test '
181 'data that should be copied to the target(s) '
182 'before running the tests. The argument '
183 'should be of the form <target>:<source>, '
184 '<target> is relative to the device data'
185 'directory, and <source> is relative to the '
186 'chromium build directory.'))
187
188
189def ProcessJavaTestOptions(options, error_func):
190 """Processes options/arguments and populates |options| with defaults."""
191
[email protected]fbe29322013-07-09 09:03:26192 if options.annotation_str:
193 options.annotations = options.annotation_str.split(',')
194 elif options.test_filter:
195 options.annotations = []
196 else:
[email protected]6bc1bda22013-07-19 22:08:37197 options.annotations = ['Smoke', 'SmallTest', 'MediumTest', 'LargeTest',
198 'EnormousTest']
[email protected]fbe29322013-07-09 09:03:26199
200 if options.exclude_annotation_str:
201 options.exclude_annotations = options.exclude_annotation_str.split(',')
202 else:
203 options.exclude_annotations = []
204
205 if not options.keep_test_server_ports:
206 if not ports.ResetTestServerPortAllocation():
207 raise Exception('Failed to reset test server port.')
208
209
210def AddInstrumentationTestOptions(option_parser):
211 """Adds Instrumentation test options to |option_parser|."""
212
213 option_parser.usage = '%prog instrumentation [options]'
214 option_parser.command_list = []
[email protected]fb7ab5e82013-07-26 18:31:20215 option_parser.example = ('%prog instrumentation '
[email protected]fbe29322013-07-09 09:03:26216 '--test-apk=ChromiumTestShellTest')
217
218 AddJavaTestOptions(option_parser)
219 AddCommonOptions(option_parser)
220
[email protected]37ee0c792013-08-06 19:10:13221 option_parser.add_option('-j', '--java_only', action='store_true',
222 default=False, help='Run only the Java tests.')
223 option_parser.add_option('-p', '--python_only', action='store_true',
224 default=False,
225 help='Run only the host-driven tests.')
226 option_parser.add_option('--python_test_root',
227 help='Root of the host-driven tests.')
[email protected]fbe29322013-07-09 09:03:26228 option_parser.add_option('-w', '--wait_debugger', dest='wait_for_debugger',
229 action='store_true',
230 help='Wait for debugger.')
[email protected]fb7ab5e82013-07-26 18:31:20231 #TODO(craigdh): Remove option once -I is no longer passed downstream.
[email protected]fbe29322013-07-09 09:03:26232 option_parser.add_option('-I', dest='install_apk', action='store_true',
[email protected]fb7ab5e82013-07-26 18:31:20233 help='(DEPRECATED) Install the test apk.')
[email protected]fbe29322013-07-09 09:03:26234 option_parser.add_option(
235 '--test-apk', dest='test_apk',
236 help=('The name of the apk containing the tests '
237 '(without the .apk extension; e.g. "ContentShellTest"). '
238 'Alternatively, this can be a full path to the apk.'))
239
240
241def ProcessInstrumentationOptions(options, error_func):
[email protected]2a684222013-08-01 16:59:22242 """Processes options/arguments and populate |options| with defaults.
243
244 Args:
245 options: optparse.Options object.
246 error_func: Function to call with the error message in case of an error.
247
248 Returns:
249 An InstrumentationOptions named tuple which contains all options relevant to
250 instrumentation tests.
251 """
[email protected]fbe29322013-07-09 09:03:26252
253 ProcessJavaTestOptions(options, error_func)
254
[email protected]37ee0c792013-08-06 19:10:13255 if options.java_only and options.python_only:
256 error_func('Options java_only (-j) and python_only (-p) '
257 'are mutually exclusive.')
258 options.run_java_tests = True
259 options.run_python_tests = True
260 if options.java_only:
261 options.run_python_tests = False
262 elif options.python_only:
263 options.run_java_tests = False
264
265 if not options.python_test_root:
266 options.run_python_tests = False
267
[email protected]fbe29322013-07-09 09:03:26268 if not options.test_apk:
269 error_func('--test-apk must be specified.')
270
271 if os.path.exists(options.test_apk):
272 # The APK is fully qualified, assume the JAR lives along side.
273 options.test_apk_path = options.test_apk
274 options.test_apk_jar_path = (os.path.splitext(options.test_apk_path)[0] +
275 '.jar')
276 else:
277 options.test_apk_path = os.path.join(_SDK_OUT_DIR,
278 options.build_type,
279 constants.SDK_BUILD_APKS_DIR,
280 '%s.apk' % options.test_apk)
281 options.test_apk_jar_path = os.path.join(
282 _SDK_OUT_DIR, options.build_type, constants.SDK_BUILD_TEST_JAVALIB_DIR,
283 '%s.jar' % options.test_apk)
284
[email protected]2a684222013-08-01 16:59:22285 return instrumentation_test_options.InstrumentationOptions(
286 options.build_type,
287 options.tool,
288 options.cleanup_test_files,
289 options.push_deps,
290 options.annotations,
291 options.exclude_annotations,
292 options.test_filter,
293 options.test_data,
294 options.save_perf_json,
295 options.screenshot_failures,
296 options.disable_assertions,
297 options.wait_for_debugger,
298 options.test_apk,
299 options.test_apk_path,
300 options.test_apk_jar_path)
301
[email protected]fbe29322013-07-09 09:03:26302
303def AddUIAutomatorTestOptions(option_parser):
304 """Adds UI Automator test options to |option_parser|."""
305
306 option_parser.usage = '%prog uiautomator [options]'
307 option_parser.command_list = []
308 option_parser.example = (
309 '%prog uiautomator --test-jar=chromium_testshell_uiautomator_tests'
310 ' --package-name=org.chromium.chrome.testshell')
311 option_parser.add_option(
312 '--package-name',
313 help='The package name used by the apk containing the application.')
314 option_parser.add_option(
315 '--test-jar', dest='test_jar',
316 help=('The name of the dexed jar containing the tests (without the '
317 '.dex.jar extension). Alternatively, this can be a full path '
318 'to the jar.'))
319
320 AddJavaTestOptions(option_parser)
321 AddCommonOptions(option_parser)
322
323
324def ProcessUIAutomatorOptions(options, error_func):
[email protected]2a684222013-08-01 16:59:22325 """Processes UIAutomator options/arguments.
326
327 Args:
328 options: optparse.Options object.
329 error_func: Function to call with the error message in case of an error.
330
331 Returns:
332 A UIAutomatorOptions named tuple which contains all options relevant to
333 instrumentation tests.
334 """
[email protected]fbe29322013-07-09 09:03:26335
336 ProcessJavaTestOptions(options, error_func)
337
338 if not options.package_name:
339 error_func('--package-name must be specified.')
340
341 if not options.test_jar:
342 error_func('--test-jar must be specified.')
343
344 if os.path.exists(options.test_jar):
345 # The dexed JAR is fully qualified, assume the info JAR lives along side.
346 options.uiautomator_jar = options.test_jar
347 else:
348 options.uiautomator_jar = os.path.join(
349 _SDK_OUT_DIR, options.build_type, constants.SDK_BUILD_JAVALIB_DIR,
350 '%s.dex.jar' % options.test_jar)
351 options.uiautomator_info_jar = (
352 options.uiautomator_jar[:options.uiautomator_jar.find('.dex.jar')] +
353 '_java.jar')
354
[email protected]2a684222013-08-01 16:59:22355 return uiautomator_test_options.UIAutomatorOptions(
356 options.build_type,
357 options.tool,
358 options.cleanup_test_files,
359 options.push_deps,
360 options.annotations,
361 options.exclude_annotations,
362 options.test_filter,
363 options.test_data,
364 options.save_perf_json,
365 options.screenshot_failures,
366 options.disable_assertions,
367 options.uiautomator_jar,
368 options.uiautomator_info_jar,
369 options.package_name)
370
[email protected]fbe29322013-07-09 09:03:26371
[email protected]6bc1bda22013-07-19 22:08:37372def _RunGTests(options, error_func):
373 """Subcommand of RunTestsCommands which runs gtests."""
[email protected]2a684222013-08-01 16:59:22374 ProcessGTestOptions(options)
[email protected]6bc1bda22013-07-19 22:08:37375
376 exit_code = 0
377 for suite_name in options.suite_name:
[email protected]2a684222013-08-01 16:59:22378 # TODO(gkanwar): Move this into ProcessGTestOptions once we require -s for
379 # the gtest command.
380 gtest_options = gtest_test_options.GTestOptions(
381 options.build_type,
382 options.tool,
383 options.cleanup_test_files,
384 options.push_deps,
385 options.test_filter,
386 options.test_arguments,
387 options.timeout,
388 suite_name)
389 runner_factory, tests = gtest_setup.Setup(gtest_options)
[email protected]6bc1bda22013-07-19 22:08:37390
391 results, test_exit_code = test_dispatcher.RunTests(
392 tests, runner_factory, False, options.test_device,
393 shard=True,
394 build_type=options.build_type,
395 test_timeout=None,
396 num_retries=options.num_retries)
397
398 if test_exit_code and exit_code != constants.ERROR_EXIT_CODE:
399 exit_code = test_exit_code
400
401 report_results.LogFull(
402 results=results,
403 test_type='Unit test',
404 test_package=suite_name,
405 build_type=options.build_type,
406 flakiness_server=options.flakiness_dashboard_server)
407
408 if os.path.isdir(constants.ISOLATE_DEPS_DIR):
409 shutil.rmtree(constants.ISOLATE_DEPS_DIR)
410
411 return exit_code
412
413
[email protected]6bc1bda22013-07-19 22:08:37414def _RunInstrumentationTests(options, error_func):
415 """Subcommand of RunTestsCommands which runs instrumentation tests."""
[email protected]2a684222013-08-01 16:59:22416 instrumentation_options = ProcessInstrumentationOptions(options, error_func)
[email protected]6bc1bda22013-07-19 22:08:37417
418 results = base_test_result.TestRunResults()
419 exit_code = 0
420
421 if options.run_java_tests:
[email protected]2a684222013-08-01 16:59:22422 runner_factory, tests = instrumentation_setup.Setup(instrumentation_options)
[email protected]6bc1bda22013-07-19 22:08:37423
424 test_results, exit_code = test_dispatcher.RunTests(
425 tests, runner_factory, options.wait_for_debugger,
426 options.test_device,
427 shard=True,
428 build_type=options.build_type,
429 test_timeout=None,
430 num_retries=options.num_retries)
431
432 results.AddTestRunResults(test_results)
433
434 if options.run_python_tests:
[email protected]37ee0c792013-08-06 19:10:13435 runner_factory, tests = host_driven_setup.InstrumentationSetup(
436 options.python_test_root, options.official_build,
437 instrumentation_options)
438
[email protected]34020022013-08-06 23:35:34439 if tests:
440 test_results, test_exit_code = test_dispatcher.RunTests(
441 tests, runner_factory, False,
442 options.test_device,
443 shard=True,
444 build_type=options.build_type,
445 test_timeout=None,
446 num_retries=options.num_retries)
[email protected]6bc1bda22013-07-19 22:08:37447
[email protected]34020022013-08-06 23:35:34448 results.AddTestRunResults(test_results)
[email protected]6bc1bda22013-07-19 22:08:37449
[email protected]34020022013-08-06 23:35:34450 # Only allow exit code escalation
451 if test_exit_code and exit_code != constants.ERROR_EXIT_CODE:
452 exit_code = test_exit_code
[email protected]6bc1bda22013-07-19 22:08:37453
454 report_results.LogFull(
455 results=results,
456 test_type='Instrumentation',
457 test_package=os.path.basename(options.test_apk),
458 annotation=options.annotations,
459 build_type=options.build_type,
460 flakiness_server=options.flakiness_dashboard_server)
461
462 return exit_code
463
464
465def _RunUIAutomatorTests(options, error_func):
466 """Subcommand of RunTestsCommands which runs uiautomator tests."""
[email protected]2a684222013-08-01 16:59:22467 uiautomator_options = ProcessUIAutomatorOptions(options, error_func)
[email protected]6bc1bda22013-07-19 22:08:37468
469 results = base_test_result.TestRunResults()
470 exit_code = 0
471
[email protected]37ee0c792013-08-06 19:10:13472 runner_factory, tests = uiautomator_setup.Setup(uiautomator_options)
[email protected]6bc1bda22013-07-19 22:08:37473
[email protected]37ee0c792013-08-06 19:10:13474 results, exit_code = test_dispatcher.RunTests(
475 tests, runner_factory, False, options.test_device,
476 shard=True,
477 build_type=options.build_type,
478 test_timeout=None,
479 num_retries=options.num_retries)
[email protected]6bc1bda22013-07-19 22:08:37480
481 report_results.LogFull(
482 results=results,
483 test_type='UIAutomator',
484 test_package=os.path.basename(options.test_jar),
485 annotation=options.annotations,
486 build_type=options.build_type,
487 flakiness_server=options.flakiness_dashboard_server)
488
489 return exit_code
490
491
[email protected]fbe29322013-07-09 09:03:26492def RunTestsCommand(command, options, args, option_parser):
493 """Checks test type and dispatches to the appropriate function.
494
495 Args:
496 command: String indicating the command that was received to trigger
497 this function.
498 options: optparse options dictionary.
499 args: List of extra args from optparse.
500 option_parser: optparse.OptionParser object.
501
502 Returns:
503 Integer indicated exit code.
[email protected]b3873892013-07-10 04:57:10504
505 Raises:
506 Exception: Unknown command name passed in, or an exception from an
507 individual test runner.
[email protected]fbe29322013-07-09 09:03:26508 """
509
[email protected]d82f0252013-07-12 23:22:57510 # Check for extra arguments
511 if len(args) > 2:
512 option_parser.error('Unrecognized arguments: %s' % (' '.join(args[2:])))
513 return constants.ERROR_EXIT_CODE
514
[email protected]fbe29322013-07-09 09:03:26515 ProcessCommonOptions(options)
516
[email protected]fbe29322013-07-09 09:03:26517 if command == 'gtest':
[email protected]6bc1bda22013-07-19 22:08:37518 return _RunGTests(options, option_parser.error)
[email protected]fbe29322013-07-09 09:03:26519 elif command == 'instrumentation':
[email protected]6bc1bda22013-07-19 22:08:37520 return _RunInstrumentationTests(options, option_parser.error)
[email protected]fbe29322013-07-09 09:03:26521 elif command == 'uiautomator':
[email protected]6bc1bda22013-07-19 22:08:37522 return _RunUIAutomatorTests(options, option_parser.error)
[email protected]fbe29322013-07-09 09:03:26523 else:
[email protected]6bc1bda22013-07-19 22:08:37524 raise Exception('Unknown test type.')
[email protected]fbe29322013-07-09 09:03:26525
[email protected]fbe29322013-07-09 09:03:26526
527def HelpCommand(command, options, args, option_parser):
528 """Display help for a certain command, or overall help.
529
530 Args:
531 command: String indicating the command that was received to trigger
532 this function.
533 options: optparse options dictionary.
534 args: List of extra args from optparse.
535 option_parser: optparse.OptionParser object.
536
537 Returns:
538 Integer indicated exit code.
539 """
540 # If we don't have any args, display overall help
541 if len(args) < 3:
542 option_parser.print_help()
543 return 0
[email protected]d82f0252013-07-12 23:22:57544 # If we have too many args, print an error
545 if len(args) > 3:
546 option_parser.error('Unrecognized arguments: %s' % (' '.join(args[3:])))
547 return constants.ERROR_EXIT_CODE
[email protected]fbe29322013-07-09 09:03:26548
549 command = args[2]
550
551 if command not in VALID_COMMANDS:
552 option_parser.error('Unrecognized command.')
553
554 # Treat the help command as a special case. We don't care about showing a
555 # specific help page for itself.
556 if command == 'help':
557 option_parser.print_help()
558 return 0
559
560 VALID_COMMANDS[command].add_options_func(option_parser)
561 option_parser.usage = '%prog ' + command + ' [options]'
562 option_parser.command_list = None
563 option_parser.print_help()
564
565 return 0
566
567
568# Define a named tuple for the values in the VALID_COMMANDS dictionary so the
569# syntax is a bit prettier. The tuple is two functions: (add options, run
570# command).
571CommandFunctionTuple = collections.namedtuple(
572 'CommandFunctionTuple', ['add_options_func', 'run_command_func'])
573VALID_COMMANDS = {
574 'gtest': CommandFunctionTuple(AddGTestOptions, RunTestsCommand),
[email protected]fbe29322013-07-09 09:03:26575 'instrumentation': CommandFunctionTuple(
576 AddInstrumentationTestOptions, RunTestsCommand),
577 'uiautomator': CommandFunctionTuple(
578 AddUIAutomatorTestOptions, RunTestsCommand),
579 'help': CommandFunctionTuple(lambda option_parser: None, HelpCommand)
580 }
581
582
583class CommandOptionParser(optparse.OptionParser):
584 """Wrapper class for OptionParser to help with listing commands."""
585
586 def __init__(self, *args, **kwargs):
587 self.command_list = kwargs.pop('command_list', [])
588 self.example = kwargs.pop('example', '')
589 optparse.OptionParser.__init__(self, *args, **kwargs)
590
591 #override
592 def get_usage(self):
593 normal_usage = optparse.OptionParser.get_usage(self)
594 command_list = self.get_command_list()
595 example = self.get_example()
596 return self.expand_prog_name(normal_usage + example + command_list)
597
598 #override
599 def get_command_list(self):
600 if self.command_list:
601 return '\nCommands:\n %s\n' % '\n '.join(sorted(self.command_list))
602 return ''
603
604 def get_example(self):
605 if self.example:
606 return '\nExample:\n %s\n' % self.example
607 return ''
608
[email protected]b3873892013-07-10 04:57:10609
[email protected]fbe29322013-07-09 09:03:26610def main(argv):
611 option_parser = CommandOptionParser(
612 usage='Usage: %prog <command> [options]',
613 command_list=VALID_COMMANDS.keys())
614
615 if len(argv) < 2 or argv[1] not in VALID_COMMANDS:
616 option_parser.print_help()
617 return 0
618 command = argv[1]
619 VALID_COMMANDS[command].add_options_func(option_parser)
620 options, args = option_parser.parse_args(argv)
[email protected]b3873892013-07-10 04:57:10621 return VALID_COMMANDS[command].run_command_func(
[email protected]fbe29322013-07-09 09:03:26622 command, options, args, option_parser)
623
[email protected]fbe29322013-07-09 09:03:26624
625if __name__ == '__main__':
626 sys.exit(main(sys.argv))