• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env vpython3
2#
3# Copyright 2013 The Chromium Authors
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
9from __future__ import absolute_import
10import argparse
11import collections
12import contextlib
13import io
14import itertools
15import logging
16import os
17import re
18import shlex
19import shutil
20import signal
21import sys
22import tempfile
23import threading
24import traceback
25import unittest
26
27# Import _strptime before threaded code. datetime.datetime.strptime is
28# threadsafe except for the initial import of the _strptime module.
29# See http://crbug.com/724524 and https://bugs.python.org/issue7980.
30import _strptime  # pylint: disable=unused-import
31
32# pylint: disable=ungrouped-imports
33from pylib.constants import host_paths
34
35if host_paths.DEVIL_PATH not in sys.path:
36  sys.path.append(host_paths.DEVIL_PATH)
37
38from devil import base_error
39from devil.utils import reraiser_thread
40from devil.utils import run_tests_helper
41
42from pylib import constants
43from pylib.base import base_test_result
44from pylib.base import environment_factory
45from pylib.base import output_manager
46from pylib.base import output_manager_factory
47from pylib.base import test_instance_factory
48from pylib.base import test_run_factory
49from pylib.results import json_results
50from pylib.results import report_results
51from pylib.results.presentation import test_results_presentation
52from pylib.utils import local_utils
53from pylib.utils import logdog_helper
54from pylib.utils import logging_utils
55from pylib.utils import test_filter
56
57from py_utils import contextlib_ext
58
59from lib.results import result_sink  # pylint: disable=import-error
60
61_DEVIL_STATIC_CONFIG_FILE = os.path.abspath(os.path.join(
62    host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'devil_config.json'))
63
64_RERUN_FAILED_TESTS_FILE = 'rerun_failed_tests.filter'
65
66
67def _RealPath(arg):
68  if arg.startswith('//'):
69    arg = os.path.abspath(os.path.join(host_paths.DIR_SOURCE_ROOT,
70                                       arg[2:].replace('/', os.sep)))
71  return os.path.realpath(arg)
72
73
74def AddTestLauncherOptions(parser):
75  """Adds arguments mirroring //base/test/launcher.
76
77  Args:
78    parser: The parser to which arguments should be added.
79  Returns:
80    The given parser.
81  """
82  parser.add_argument(
83      '--test-launcher-retry-limit',
84      '--test_launcher_retry_limit',
85      '--num_retries', '--num-retries',
86      '--isolated-script-test-launcher-retry-limit',
87      dest='num_retries', type=int, default=2,
88      help='Number of retries for a test before '
89           'giving up (default: %(default)s).')
90  parser.add_argument(
91      '--test-launcher-summary-output',
92      '--json-results-file',
93      dest='json_results_file', type=os.path.realpath,
94      help='If set, will dump results in JSON form to the specified file. '
95           'Note that this will also trigger saving per-test logcats to '
96           'logdog.')
97  parser.add_argument(
98      '--test-launcher-shard-index',
99      type=int, default=os.environ.get('GTEST_SHARD_INDEX', 0),
100      help='Index of the external shard to run.')
101  parser.add_argument(
102      '--test-launcher-total-shards',
103      type=int, default=os.environ.get('GTEST_TOTAL_SHARDS', 1),
104      help='Total number of external shards.')
105
106  test_filter.AddFilterOptions(parser)
107
108  return parser
109
110
111def AddCommandLineOptions(parser):
112  """Adds arguments to support passing command-line flags to the device."""
113  parser.add_argument(
114      '--device-flags-file',
115      type=os.path.realpath,
116      help='The relative filepath to a file containing '
117           'command-line flags to set on the device')
118  parser.add_argument(
119      '--use-apk-under-test-flags-file',
120      action='store_true',
121      help='Wether to use the flags file for the apk under test. If set, '
122           "the filename will be looked up in the APK's PackageInfo.")
123  parser.add_argument('--variations-test-seed-path',
124                      type=os.path.relpath,
125                      default=None,
126                      help='Path to variations seed file.')
127  parser.add_argument('--webview-variations-test-seed-path',
128                      type=os.path.relpath,
129                      default=None,
130                      help='Path to variations seed file for WebView.')
131
132  parser.set_defaults(allow_unknown=True)
133  parser.set_defaults(command_line_flags=None)
134
135
136def AddTracingOptions(parser):
137  # TODO(shenghuazhang): Move this into AddCommonOptions once it's supported
138  # for all test types.
139  parser.add_argument(
140      '--trace-output',
141      metavar='FILENAME', type=os.path.realpath,
142      help='Path to save test_runner trace json output to.')
143
144  parser.add_argument(
145      '--trace-all',
146      action='store_true',
147      help='Whether to trace all function calls.')
148
149
150def AddCommonOptions(parser):
151  """Adds all common options to |parser|."""
152
153  default_build_type = os.environ.get('BUILDTYPE', 'Debug')
154
155  debug_or_release_group = parser.add_mutually_exclusive_group()
156  debug_or_release_group.add_argument(
157      '--debug',
158      action='store_const', const='Debug', dest='build_type',
159      default=default_build_type,
160      help='If set, run test suites under out/Debug. '
161           'Default is env var BUILDTYPE or Debug.')
162  debug_or_release_group.add_argument(
163      '--release',
164      action='store_const', const='Release', dest='build_type',
165      help='If set, run test suites under out/Release. '
166           'Default is env var BUILDTYPE or Debug.')
167
168  parser.add_argument(
169      '--break-on-failure', '--break_on_failure',
170      dest='break_on_failure', action='store_true',
171      help='Whether to break on failure.')
172
173  # TODO(jbudorick): Remove this once everything has switched to platform
174  # mode.
175  parser.add_argument(
176      '--enable-platform-mode',
177      action='store_true',
178      help='Run the test scripts in platform mode, which '
179           'conceptually separates the test runner from the '
180           '"device" (local or remote, real or emulated) on '
181           'which the tests are running. [experimental]')
182
183  parser.add_argument(
184      '-e', '--environment',
185      default='local', choices=constants.VALID_ENVIRONMENTS,
186      help='Test environment to run in (default: %(default)s).')
187
188  parser.add_argument(
189      '--local-output',
190      action='store_true',
191      help='Whether to archive test output locally and generate '
192           'a local results detail page.')
193
194  parser.add_argument('--list-tests',
195                      action='store_true',
196                      help='List available tests and exit.')
197
198  parser.add_argument('--wrapper-script-args',
199                      help='A string of args that were passed to the wrapper '
200                      'script. This should probably not be edited by a '
201                      'user as it is passed by the wrapper itself.')
202
203  class FastLocalDevAction(argparse.Action):
204    def __call__(self, parser, namespace, values, option_string=None):
205      namespace.enable_concurrent_adb = True
206      namespace.enable_device_cache = True
207      namespace.extract_test_list_from_filter = True
208      namespace.local_output = True
209      namespace.num_retries = 0
210      namespace.skip_clear_data = True
211      namespace.use_persistent_shell = True
212
213  parser.add_argument(
214      '--fast-local-dev',
215      type=bool,
216      nargs=0,
217      action=FastLocalDevAction,
218      help='Alias for: --num-retries=0 --enable-device-cache '
219      '--enable-concurrent-adb --skip-clear-data '
220      '--extract-test-list-from-filter --use-persistent-shell --local-output')
221
222  # TODO(jbudorick): Remove this once downstream bots have switched to
223  # api.test_results.
224  parser.add_argument(
225      '--flakiness-dashboard-server',
226      dest='flakiness_dashboard_server',
227      help=argparse.SUPPRESS)
228  parser.add_argument(
229      '--gs-results-bucket',
230      help='Google Storage bucket to upload results to.')
231
232  parser.add_argument(
233      '--output-directory',
234      dest='output_directory', type=os.path.realpath,
235      help='Path to the directory in which build files are'
236           ' located (must include build type). This will take'
237           ' precedence over --debug and --release')
238  parser.add_argument(
239      '-v', '--verbose',
240      dest='verbose_count', default=0, action='count',
241      help='Verbose level (multiple times for more)')
242
243  parser.add_argument(
244      '--repeat', '--gtest_repeat', '--gtest-repeat',
245      '--isolated-script-test-repeat',
246      dest='repeat', type=int, default=0,
247      help='Number of times to repeat the specified set of tests.')
248
249  # Not useful for junit tests.
250  parser.add_argument(
251      '--use-persistent-shell',
252      action='store_true',
253      help='Uses a persistent shell connection for the adb connection.')
254
255  parser.add_argument('--disable-test-server',
256                      action='store_true',
257                      help='Disables SpawnedTestServer which doesn'
258                      't work with remote adb. '
259                      'WARNING: Will break tests which require the server.')
260
261  # This is currently only implemented for gtests and instrumentation tests.
262  parser.add_argument(
263      '--gtest_also_run_disabled_tests', '--gtest-also-run-disabled-tests',
264      '--isolated-script-test-also-run-disabled-tests',
265      dest='run_disabled', action='store_true',
266      help='Also run disabled tests if applicable.')
267
268  # These are currently only implemented for gtests.
269  parser.add_argument('--isolated-script-test-output',
270                      help='If present, store test results on this path.')
271  parser.add_argument('--isolated-script-test-perf-output',
272                      help='If present, store chartjson results on this path.')
273
274  AddTestLauncherOptions(parser)
275
276
277def ProcessCommonOptions(args):
278  """Processes and handles all common options."""
279  run_tests_helper.SetLogLevel(args.verbose_count, add_handler=False)
280  if args.verbose_count > 0:
281    handler = logging_utils.ColorStreamHandler()
282  else:
283    handler = logging.StreamHandler(sys.stdout)
284  handler.setFormatter(run_tests_helper.CustomFormatter())
285  logging.getLogger().addHandler(handler)
286
287  constants.SetBuildType(args.build_type)
288  if args.output_directory:
289    constants.SetOutputDirectory(args.output_directory)
290
291
292def AddDeviceOptions(parser):
293  """Adds device options to |parser|."""
294
295  parser = parser.add_argument_group('device arguments')
296
297  parser.add_argument(
298      '--adb-path',
299      type=os.path.realpath,
300      help='Specify the absolute path of the adb binary that '
301           'should be used.')
302  parser.add_argument(
303      '--use-local-devil-tools',
304      action='store_true',
305      help='Use locally built versions of tools used by devil_chromium.')
306  parser.add_argument('--denylist-file',
307                      type=os.path.realpath,
308                      help='Device denylist file.')
309  parser.add_argument(
310      '-d', '--device', nargs='+',
311      dest='test_devices',
312      help='Target device(s) for the test suite to run on.')
313  parser.add_argument(
314      '--enable-concurrent-adb',
315      action='store_true',
316      help='Run multiple adb commands at the same time, even '
317           'for the same device.')
318  parser.add_argument(
319      '--enable-device-cache',
320      action='store_true',
321      help='Cache device state to disk between runs')
322  parser.add_argument(
323      '--skip-clear-data',
324      action='store_true',
325      help='Do not wipe app data between tests. Use this to '
326           'speed up local development and never on bots '
327                     '(increases flakiness)')
328  parser.add_argument(
329      '--recover-devices',
330      action='store_true',
331      help='Attempt to recover devices prior to the final retry. Warning: '
332           'this will cause all devices to reboot.')
333  parser.add_argument(
334      '--tool',
335      dest='tool',
336      help='Run the test under a tool '
337           '(use --tool help to list them)')
338
339  parser.add_argument(
340      '--upload-logcats-file',
341      action='store_true',
342      dest='upload_logcats_file',
343      help='Whether to upload logcat file to logdog.')
344
345  logcat_output_group = parser.add_mutually_exclusive_group()
346  logcat_output_group.add_argument(
347      '--logcat-output-dir', type=os.path.realpath,
348      help='If set, will dump logcats recorded during test run to directory. '
349           'File names will be the device ids with timestamps.')
350  logcat_output_group.add_argument(
351      '--logcat-output-file', type=os.path.realpath,
352      help='If set, will merge logcats recorded during test run and dump them '
353           'to the specified file.')
354
355  parser.add_argument(
356      '--force-main-user',
357      action='store_true',
358      help='Force the applicable adb commands to run with "--user" param set '
359      'to the id of the main user on device. Only use when the main user is a '
360      'secondary user, e.g. Android Automotive OS.')
361
362
363def AddEmulatorOptions(parser):
364  """Adds emulator-specific options to |parser|."""
365  parser = parser.add_argument_group('emulator arguments')
366
367  parser.add_argument(
368      '--avd-config',
369      type=os.path.realpath,
370      help='Path to the avd config textpb. '
371      '(See //tools/android/avd/proto/ for message definition'
372      ' and existing textpb files.)')
373  parser.add_argument(
374      '--emulator-count',
375      type=int,
376      default=1,
377      help='Number of emulators to use.')
378  parser.add_argument(
379      '--emulator-window',
380      action='store_true',
381      default=False,
382      help='Enable graphical window display on the emulator.')
383  parser.add_argument(
384      '--emulator-debug-tags',
385      help='Comma-separated list of debug tags. This can be used to enable or '
386      'disable debug messages from specific parts of the emulator, e.g. '
387      'init,snapshot. See "emulator -help-debug-tags" '
388      'for a full list of tags.')
389  parser.add_argument(
390      '--emulator-enable-network',
391      action='store_true',
392      help='Enable the network (WiFi and mobile data) on the emulator.')
393
394
395def AddGTestOptions(parser):
396  """Adds gtest options to |parser|."""
397
398  parser = parser.add_argument_group('gtest arguments')
399
400  parser.add_argument(
401      '--app-data-file',
402      action='append', dest='app_data_files',
403      help='A file path relative to the app data directory '
404           'that should be saved to the host.')
405  parser.add_argument(
406      '--app-data-file-dir',
407      help='Host directory to which app data files will be'
408           ' saved. Used with --app-data-file.')
409  parser.add_argument(
410      '--enable-xml-result-parsing',
411      action='store_true', help=argparse.SUPPRESS)
412  parser.add_argument(
413      '--executable-dist-dir',
414      type=os.path.realpath,
415      help="Path to executable's dist directory for native"
416           " (non-apk) tests.")
417  parser.add_argument(
418      '--extract-test-list-from-filter',
419      action='store_true',
420      help='When a test filter is specified, and the list of '
421           'tests can be determined from it, skip querying the '
422           'device for the list of all tests. Speeds up local '
423           'development, but is not safe to use on bots ('
424           'http://crbug.com/549214')
425  parser.add_argument(
426      '--gs-test-artifacts-bucket',
427      help=('If present, test artifacts will be uploaded to this Google '
428            'Storage bucket.'))
429  parser.add_argument(
430      '--render-test-output-dir',
431      help='If present, store rendering artifacts in this path.')
432  parser.add_argument(
433      '--runtime-deps-path',
434      dest='runtime_deps_path', type=os.path.realpath,
435      help='Runtime data dependency file from GN.')
436  parser.add_argument(
437      '-t', '--shard-timeout',
438      dest='shard_timeout', type=int, default=120,
439      help='Timeout to wait for each test (default: %(default)s).')
440  parser.add_argument(
441      '--store-tombstones',
442      dest='store_tombstones', action='store_true',
443      help='Add tombstones in results if crash.')
444  parser.add_argument(
445      '-s', '--suite',
446      dest='suite_name', nargs='+', metavar='SUITE_NAME', required=True,
447      help='Executable name of the test suite to run.')
448  parser.add_argument(
449      '--test-apk-incremental-install-json',
450      type=os.path.realpath,
451      help='Path to install json for the test apk.')
452  parser.add_argument('--test-launcher-batch-limit',
453                      dest='test_launcher_batch_limit',
454                      type=int,
455                      help='The max number of tests to run in a shard. '
456                      'Ignores non-positive ints and those greater than '
457                      'MAX_SHARDS')
458  parser.add_argument(
459      '-w', '--wait-for-java-debugger', action='store_true',
460      help='Wait for java debugger to attach before running any application '
461           'code. Also disables test timeouts and sets retries=0.')
462  parser.add_argument(
463      '--coverage-dir',
464      type=os.path.realpath,
465      help='Directory in which to place all generated coverage files.')
466  parser.add_argument(
467      '--use-existing-test-data',
468      action='store_true',
469      help='Do not push new files to the device, instead using existing APK '
470      'and test data. Only use when running the same test for multiple '
471      'iterations.')
472
473
474def AddInstrumentationTestOptions(parser):
475  """Adds Instrumentation test options to |parser|."""
476
477  parser = parser.add_argument_group('instrumentation arguments')
478
479  parser.add_argument('--additional-apex',
480                      action='append',
481                      dest='additional_apexs',
482                      default=[],
483                      type=_RealPath,
484                      help='Additional apex that must be installed on '
485                      'the device when the tests are run')
486  parser.add_argument(
487      '--additional-apk',
488      action='append', dest='additional_apks', default=[],
489      type=_RealPath,
490      help='Additional apk that must be installed on '
491           'the device when the tests are run')
492  parser.add_argument('--forced-queryable-additional-apk',
493                      action='append',
494                      dest='forced_queryable_additional_apks',
495                      default=[],
496                      type=_RealPath,
497                      help='Configures an additional-apk to be forced '
498                      'to be queryable by other APKs.')
499  parser.add_argument('--instant-additional-apk',
500                      action='append',
501                      dest='instant_additional_apks',
502                      default=[],
503                      type=_RealPath,
504                      help='Configures an additional-apk to be an instant APK')
505  parser.add_argument(
506      '-A', '--annotation',
507      dest='annotation_str',
508      help='Comma-separated list of annotations. Run only tests with any of '
509           'the given annotations. An annotation can be either a key or a '
510           'key-values pair. A test that has no annotation is considered '
511           '"SmallTest".')
512  # TODO(jbudorick): Remove support for name-style APK specification once
513  # bots are no longer doing it.
514  parser.add_argument(
515      '--apk-under-test',
516      help='Path or name of the apk under test.')
517  parser.add_argument(
518      '--store-data-dependencies-in-temp',
519      action='store_true',
520      help='Store data dependencies in /data/local/tmp/chromium_tests_root')
521  parser.add_argument(
522      '--module',
523      action='append',
524      dest='modules',
525      help='Specify Android App Bundle modules to install in addition to the '
526      'base module.')
527  parser.add_argument(
528      '--fake-module',
529      action='append',
530      dest='fake_modules',
531      help='Specify Android App Bundle modules to fake install in addition to '
532      'the real modules.')
533  parser.add_argument(
534      '--additional-locale',
535      action='append',
536      dest='additional_locales',
537      help='Specify locales in addition to the device locale to install splits '
538      'for when --apk-under-test is an Android App Bundle.')
539  parser.add_argument(
540      '--coverage-dir',
541      type=os.path.realpath,
542      help='Directory in which to place all generated '
543      'Jacoco coverage files.')
544  parser.add_argument(
545      '--disable-dalvik-asserts',
546      dest='set_asserts', action='store_false', default=True,
547      help='Removes the dalvik.vm.enableassertions property')
548  parser.add_argument(
549      '--proguard-mapping-path',
550      help='.mapping file to use to Deobfuscate java stack traces in test '
551      'output and logcat.')
552  parser.add_argument(
553      '-E', '--exclude-annotation',
554      dest='exclude_annotation_str',
555      help='Comma-separated list of annotations. Exclude tests with these '
556           'annotations.')
557  parser.add_argument(
558      '--enable-breakpad-dump',
559      action='store_true',
560      help='Stores any breakpad dumps till the end of the test.')
561  parser.add_argument(
562      '--replace-system-package',
563      type=_RealPath,
564      default=None,
565      help='Use this apk to temporarily replace a system package with the same '
566      'package name.')
567  parser.add_argument(
568      '--remove-system-package',
569      default=[],
570      action='append',
571      dest='system_packages_to_remove',
572      help='Specifies a system package to remove before testing if it exists '
573      'on the system. WARNING: THIS WILL PERMANENTLY REMOVE THE SYSTEM APP. '
574      'Unlike --replace-system-package, the app will not be restored after '
575      'tests are finished.')
576  parser.add_argument(
577      '--use-voice-interaction-service',
578      help='This can be used to update the voice interaction service to be a '
579      'custom one. This is useful for mocking assistants. eg: '
580      'android.assist.service/.MainInteractionService')
581  parser.add_argument(
582      '--use-webview-provider',
583      type=_RealPath, default=None,
584      help='Use this apk as the webview provider during test. '
585           'The original provider will be restored if possible, '
586           "on Nougat the provider can't be determined and so "
587           'the system will choose the default provider.')
588  parser.add_argument(
589      '--webview-command-line-arg',
590      default=[],
591      action='append',
592      help="Specifies command line arguments to add to WebView's flag file")
593  parser.add_argument(
594      '--run-setup-command',
595      default=[],
596      action='append',
597      dest='run_setup_commands',
598      help='This can be used to run a custom shell command on the device as a '
599      'setup step')
600  parser.add_argument(
601      '--run-teardown-command',
602      default=[],
603      action='append',
604      dest='run_teardown_commands',
605      help='This can be used to run a custom shell command on the device as a '
606      'teardown step')
607  parser.add_argument(
608      '--runtime-deps-path',
609      dest='runtime_deps_path', type=os.path.realpath,
610      help='Runtime data dependency file from GN.')
611  parser.add_argument(
612      '--screenshot-directory',
613      dest='screenshot_dir', type=os.path.realpath,
614      help='Capture screenshots of test failures')
615  parser.add_argument(
616      '--shared-prefs-file',
617      dest='shared_prefs_file', type=_RealPath,
618      help='The relative path to a file containing JSON list of shared '
619           'preference files to edit and how to do so. Example list: '
620           '[{'
621           '  "package": "com.package.example",'
622           '  "filename": "ExampleSettings.xml",'
623           '  "set": {'
624           '    "boolean_key_in_xml": true,'
625           '    "string_key_in_xml": "string_value"'
626           '  },'
627           '  "remove": ['
628           '    "key_in_xml_to_remove"'
629           '  ]'
630           '}]')
631  parser.add_argument(
632      '--store-tombstones',
633      action='store_true', dest='store_tombstones',
634      help='Add tombstones in results if crash.')
635  parser.add_argument(
636      '--strict-mode',
637      dest='strict_mode', default='testing',
638      help='StrictMode command-line flag set on the device, '
639           'death/testing to kill the process, off to stop '
640           'checking, flash to flash only. (default: %(default)s)')
641  parser.add_argument(
642      '--test-apk',
643      required=True,
644      help='Path or name of the apk containing the tests.')
645  parser.add_argument(
646      '--test-apk-as-instant',
647      action='store_true',
648      help='Install the test apk as an instant app. '
649      'Instant apps run in a more restrictive execution environment.')
650  parser.add_argument(
651      '--test-launcher-batch-limit',
652      dest='test_launcher_batch_limit',
653      type=int,
654      help=('Not actually used for instrumentation tests, but can be used as '
655            'a proxy for determining if the current run is a retry without '
656            'patch.'))
657  parser.add_argument(
658      '--timeout-scale',
659      type=float,
660      help='Factor by which timeouts should be scaled.')
661  parser.add_argument(
662      '--is-unit-test',
663      action='store_true',
664      help=('Specify the test suite as composed of unit tests, blocking '
665            'certain operations.'))
666  parser.add_argument(
667      '-w', '--wait-for-java-debugger', action='store_true',
668      help='Wait for java debugger to attach before running any application '
669           'code. Also disables test timeouts and sets retries=0.')
670
671  # WPR record mode.
672  parser.add_argument('--wpr-enable-record',
673                      action='store_true',
674                      default=False,
675                      help='If true, WPR server runs in record mode.'
676                      'otherwise, runs in replay mode.')
677
678  parser.add_argument(
679      '--approve-app-links',
680      help='Force enables Digital Asset Link verification for the provided '
681      'package and domain, example usage: --approve-app-links '
682      'com.android.package:www.example.com')
683
684  # These arguments are suppressed from the help text because they should
685  # only ever be specified by an intermediate script.
686  parser.add_argument(
687      '--apk-under-test-incremental-install-json',
688      help=argparse.SUPPRESS)
689  parser.add_argument(
690      '--test-apk-incremental-install-json',
691      type=os.path.realpath,
692      help=argparse.SUPPRESS)
693
694
695def AddSkiaGoldTestOptions(parser):
696  """Adds Skia Gold test options to |parser|."""
697  parser = parser.add_argument_group("Skia Gold arguments")
698  parser.add_argument(
699      '--code-review-system',
700      help='A non-default code review system to pass to pass to Gold, if '
701      'applicable')
702  parser.add_argument(
703      '--continuous-integration-system',
704      help='A non-default continuous integration system to pass to Gold, if '
705      'applicable')
706  parser.add_argument(
707      '--git-revision', help='The git commit currently being tested.')
708  parser.add_argument(
709      '--gerrit-issue',
710      help='The Gerrit issue this test is being run on, if applicable.')
711  parser.add_argument(
712      '--gerrit-patchset',
713      help='The Gerrit patchset this test is being run on, if applicable.')
714  parser.add_argument(
715      '--buildbucket-id',
716      help='The Buildbucket build ID that this test was triggered from, if '
717      'applicable.')
718  local_group = parser.add_mutually_exclusive_group()
719  local_group.add_argument(
720      '--local-pixel-tests',
721      action='store_true',
722      default=None,
723      help='Specifies to run the Skia Gold pixel tests in local mode. When run '
724      'in local mode, uploading to Gold is disabled and traditional '
725      'generated/golden/diff images are output instead of triage links. '
726      'Running in local mode also implies --no-luci-auth. If both this '
727      'and --no-local-pixel-tests are left unset, the test harness will '
728      'attempt to detect whether it is running on a workstation or not '
729      'and set the options accordingly.')
730  local_group.add_argument(
731      '--no-local-pixel-tests',
732      action='store_false',
733      dest='local_pixel_tests',
734      help='Specifies to run the Skia Gold pixel tests in non-local (bot) '
735      'mode. When run in this mode, data is actually uploaded to Gold and '
736      'triage links are generated. If both this and --local-pixel-tests '
737      'are left unset, the test harness will attempt to detect whether '
738      'it is running on a workstation or not and set the options '
739      'accordingly.')
740  parser.add_argument(
741      '--no-luci-auth',
742      action='store_true',
743      default=False,
744      help="Don't use the serve account provided by LUCI for authentication "
745      'with Skia Gold, instead relying on gsutil to be pre-authenticated. '
746      'Meant for testing locally instead of on the bots.')
747  parser.add_argument(
748      '--bypass-skia-gold-functionality',
749      action='store_true',
750      default=False,
751      help='Bypass all interaction with Skia Gold, effectively disabling the '
752      'image comparison portion of any tests that use Gold. Only meant to be '
753      'used in case a Gold outage occurs and cannot be fixed quickly.')
754
755
756def AddJUnitTestOptions(parser):
757  """Adds junit test options to |parser|."""
758
759  parser = parser.add_argument_group('junit arguments')
760
761  parser.add_argument(
762      '--coverage-on-the-fly',
763      action='store_true',
764      help='Generate coverage data by Jacoco on-the-fly instrumentation.')
765  parser.add_argument(
766      '--coverage-dir', type=os.path.realpath,
767      help='Directory to store coverage info.')
768  parser.add_argument(
769      '--package-filter',
770      help='Filters tests by package.')
771  parser.add_argument(
772      '--runner-filter',
773      help='Filters tests by runner class. Must be fully qualified.')
774  parser.add_argument('--json-config',
775                      help='Runs only tests listed in this config.')
776  parser.add_argument(
777      '--shards',
778      type=int,
779      help='Number of shards to run junit tests in parallel on. Only 1 shard '
780      'is supported when test-filter is specified. Values less than 1 will '
781      'use auto select.')
782  parser.add_argument('--shard-filter',
783                      help='Comma separated list of shard indices to run.')
784  parser.add_argument(
785      '-s', '--test-suite', required=True,
786      help='JUnit test suite to run.')
787  debug_group = parser.add_mutually_exclusive_group()
788  debug_group.add_argument(
789      '-w', '--wait-for-java-debugger', action='store_const', const='8701',
790      dest='debug_socket', help='Alias for --debug-socket=8701')
791  debug_group.add_argument(
792      '--debug-socket',
793      help='Wait for java debugger to attach at specified socket address '
794           'before running any application code. Also disables test timeouts '
795           'and sets retries=0.')
796
797  # These arguments are for Android Robolectric tests.
798  parser.add_argument(
799      '--robolectric-runtime-deps-dir',
800      help='Path to runtime deps for Robolectric.')
801  parser.add_argument('--native-libs-dir',
802                      help='Path to search for native libraries.')
803  parser.add_argument(
804      '--resource-apk',
805      required=True,
806      help='Path to .ap_ containing binary resources for Robolectric.')
807
808
809def AddLinkerTestOptions(parser):
810
811  parser = parser.add_argument_group('linker arguments')
812
813  parser.add_argument(
814      '--test-apk',
815      type=os.path.realpath,
816      help='Path to the linker test APK.')
817
818
819def AddMonkeyTestOptions(parser):
820  """Adds monkey test options to |parser|."""
821
822  parser = parser.add_argument_group('monkey arguments')
823
824  parser.add_argument('--browser',
825                      required=True,
826                      choices=list(constants.PACKAGE_INFO.keys()),
827                      metavar='BROWSER',
828                      help='Browser under test.')
829  parser.add_argument(
830      '--category',
831      nargs='*', dest='categories', default=[],
832      help='A list of allowed categories. Monkey will only visit activities '
833           'that are listed with one of the specified categories.')
834  parser.add_argument(
835      '--event-count',
836      default=10000, type=int,
837      help='Number of events to generate (default: %(default)s).')
838  parser.add_argument(
839      '--seed',
840      type=int,
841      help='Seed value for pseudo-random generator. Same seed value generates '
842           'the same sequence of events. Seed is randomized by default.')
843  parser.add_argument(
844      '--throttle',
845      default=100, type=int,
846      help='Delay between events (ms) (default: %(default)s). ')
847
848
849def AddPythonTestOptions(parser):
850
851  parser = parser.add_argument_group('python arguments')
852
853  parser.add_argument('-s',
854                      '--suite',
855                      dest='suite_name',
856                      metavar='SUITE_NAME',
857                      choices=list(constants.PYTHON_UNIT_TEST_SUITES.keys()),
858                      help='Name of the test suite to run.')
859
860
861def _CreateClassToFileNameDict(test_apk):
862  """Creates a dict mapping classes to file names from size-info apk."""
863  constants.CheckOutputDirectory()
864  test_apk_size_info = os.path.join(constants.GetOutDirectory(), 'size-info',
865                                    os.path.basename(test_apk) + '.jar.info')
866
867  class_to_file_dict = {}
868  # Some tests such as webview_cts_tests use a separately downloaded apk to run
869  # tests. This means the apk may not have been built by the system and hence
870  # no size info file exists.
871  if not os.path.exists(test_apk_size_info):
872    logging.debug('Apk size file not found. %s', test_apk_size_info)
873    return class_to_file_dict
874
875  with open(test_apk_size_info, 'r') as f:
876    for line in f:
877      file_class, file_name = line.rstrip().split(',', 1)
878      # Only want files that are not prebuilt.
879      if file_name.startswith('../../'):
880        class_to_file_dict[file_class] = str(
881            file_name.replace('../../', '//', 1))
882
883  return class_to_file_dict
884
885
886def _RunPythonTests(args):
887  """Subcommand of RunTestsCommand which runs python unit tests."""
888  suite_vars = constants.PYTHON_UNIT_TEST_SUITES[args.suite_name]
889  suite_path = suite_vars['path']
890  suite_test_modules = suite_vars['test_modules']
891
892  sys.path = [suite_path] + sys.path
893  try:
894    suite = unittest.TestSuite()
895    suite.addTests(unittest.defaultTestLoader.loadTestsFromName(m)
896                   for m in suite_test_modules)
897    runner = unittest.TextTestRunner(verbosity=1+args.verbose_count)
898    return 0 if runner.run(suite).wasSuccessful() else 1
899  finally:
900    sys.path = sys.path[1:]
901
902
903_DEFAULT_PLATFORM_MODE_TESTS = [
904    'gtest', 'instrumentation', 'junit', 'linker', 'monkey'
905]
906
907
908def RunTestsCommand(args, result_sink_client=None):
909  """Checks test type and dispatches to the appropriate function.
910
911  Args:
912    args: argparse.Namespace object.
913    result_sink_client: A ResultSinkClient object.
914
915  Returns:
916    Integer indicated exit code.
917
918  Raises:
919    Exception: Unknown command name passed in, or an exception from an
920        individual test runner.
921  """
922  command = args.command
923
924  ProcessCommonOptions(args)
925  logging.info('command: %s', ' '.join(sys.argv))
926  if args.enable_platform_mode or command in _DEFAULT_PLATFORM_MODE_TESTS:
927    return RunTestsInPlatformMode(args, result_sink_client)
928
929  if command == 'python':
930    return _RunPythonTests(args)
931  raise Exception('Unknown test type.')
932
933
934def _SinkTestResult(test_result, test_file_name, result_sink_client):
935  """Upload test result to result_sink.
936
937  Args:
938    test_result: A BaseTestResult object
939    test_file_name: A string representing the file location of the test
940    result_sink_client: A ResultSinkClient object
941
942  Returns:
943    N/A
944  """
945  # Some tests put in non utf-8 char as part of the test
946  # which breaks uploads, so need to decode and re-encode.
947  log_decoded = test_result.GetLog()
948  if isinstance(log_decoded, bytes):
949    log_decoded = log_decoded.decode('utf-8', 'replace')
950  html_artifact = ''
951  https_artifacts = []
952  for link_name, link_url in sorted(test_result.GetLinks().items()):
953    if link_url.startswith('https:'):
954      https_artifacts.append('<li><a target="_blank" href=%s>%s</a></li>' %
955                             (link_url, link_name))
956    else:
957      logging.info('Skipping non-https link %r (%s) for test %s.', link_name,
958                   link_url, test_result.GetName())
959  if https_artifacts:
960    html_artifact += '<ul>%s</ul>' % '\n'.join(https_artifacts)
961  result_sink_client.Post(test_result.GetNameForResultSink(),
962                          test_result.GetType(),
963                          test_result.GetDuration(),
964                          log_decoded,
965                          test_file_name,
966                          variant=test_result.GetVariantForResultSink(),
967                          failure_reason=test_result.GetFailureReason(),
968                          html_artifact=html_artifact)
969
970
971_SUPPORTED_IN_PLATFORM_MODE = [
972  # TODO(jbudorick): Add support for more test types.
973  'gtest',
974  'instrumentation',
975  'junit',
976  'linker',
977  'monkey',
978]
979
980
981def RunTestsInPlatformMode(args, result_sink_client=None):
982
983  def infra_error(message):
984    logging.fatal(message)
985    sys.exit(constants.INFRA_EXIT_CODE)
986
987  if args.command not in _SUPPORTED_IN_PLATFORM_MODE:
988    infra_error('%s is not yet supported in platform mode' % args.command)
989
990  ### Set up sigterm handler.
991
992  contexts_to_notify_on_sigterm = []
993  def unexpected_sigterm(_signum, _frame):
994    msg = [
995      'Received SIGTERM. Shutting down.',
996    ]
997    for live_thread in threading.enumerate():
998      # pylint: disable=protected-access
999      thread_stack = ''.join(traceback.format_stack(
1000          sys._current_frames()[live_thread.ident]))
1001      msg.extend([
1002        'Thread "%s" (ident: %s) is currently running:' % (
1003            live_thread.name, live_thread.ident),
1004        thread_stack])
1005
1006    for context in contexts_to_notify_on_sigterm:
1007      context.ReceivedSigterm()
1008
1009    infra_error('\n'.join(msg))
1010
1011  signal.signal(signal.SIGTERM, unexpected_sigterm)
1012
1013  ### Set up results handling.
1014  # TODO(jbudorick): Rewrite results handling.
1015
1016  # all_raw_results is a list of lists of
1017  # base_test_result.TestRunResults objects. Each instance of
1018  # TestRunResults contains all test results produced by a single try,
1019  # while each list of TestRunResults contains all tries in a single
1020  # iteration.
1021  all_raw_results = []
1022
1023  # all_iteration_results is a list of base_test_result.TestRunResults
1024  # objects. Each instance of TestRunResults contains the last test
1025  # result for each test run in that iteration.
1026  all_iteration_results = []
1027
1028  global_results_tags = set()
1029
1030  json_file = tempfile.NamedTemporaryFile(delete=False)
1031  json_file.close()
1032
1033  @contextlib.contextmanager
1034  def json_finalizer():
1035    try:
1036      yield
1037    finally:
1038      if args.json_results_file and os.path.exists(json_file.name):
1039        shutil.move(json_file.name, args.json_results_file)
1040      elif args.isolated_script_test_output and os.path.exists(json_file.name):
1041        shutil.move(json_file.name, args.isolated_script_test_output)
1042      else:
1043        os.remove(json_file.name)
1044
1045  @contextlib.contextmanager
1046  def json_writer():
1047    try:
1048      yield
1049    except Exception:
1050      global_results_tags.add('UNRELIABLE_RESULTS')
1051      raise
1052    finally:
1053      if args.isolated_script_test_output:
1054        interrupted = 'UNRELIABLE_RESULTS' in global_results_tags
1055        json_results.GenerateJsonTestResultFormatFile(all_raw_results,
1056                                                      interrupted,
1057                                                      json_file.name,
1058                                                      indent=2)
1059      else:
1060        json_results.GenerateJsonResultsFile(
1061            all_raw_results,
1062            json_file.name,
1063            global_tags=list(global_results_tags),
1064            indent=2)
1065
1066      test_class_to_file_name_dict = {}
1067      # Test Location is only supported for instrumentation tests as it
1068      # requires the size-info file.
1069      if test_instance.TestType() == 'instrumentation':
1070        test_class_to_file_name_dict = _CreateClassToFileNameDict(args.test_apk)
1071
1072      if result_sink_client:
1073        for run in all_raw_results:
1074          for results in run:
1075            for r in results.GetAll():
1076              # Matches chrome.page_info.PageInfoViewTest#testChromePage
1077              match = re.search(r'^(.+\..+)#', r.GetName())
1078              test_file_name = test_class_to_file_name_dict.get(
1079                  match.group(1)) if match else None
1080              _SinkTestResult(r, test_file_name, result_sink_client)
1081
1082  @contextlib.contextmanager
1083  def upload_logcats_file():
1084    try:
1085      yield
1086    finally:
1087      if not args.logcat_output_file:
1088        logging.critical('Cannot upload logcat file: no file specified.')
1089      elif not os.path.exists(args.logcat_output_file):
1090        logging.critical("Cannot upload logcat file: file doesn't exist.")
1091      else:
1092        with open(args.logcat_output_file) as src:
1093          dst = logdog_helper.open_text('unified_logcats')
1094          if dst:
1095            shutil.copyfileobj(src, dst)
1096            dst.close()
1097            logging.critical(
1098                'Logcat: %s', logdog_helper.get_viewer_url('unified_logcats'))
1099
1100
1101  logcats_uploader = contextlib_ext.Optional(
1102      upload_logcats_file(),
1103      'upload_logcats_file' in args and args.upload_logcats_file)
1104
1105  save_detailed_results = (args.local_output or not local_utils.IsOnSwarming()
1106                           ) and not args.isolated_script_test_output
1107
1108  ### Set up test objects.
1109
1110  out_manager = output_manager_factory.CreateOutputManager(args)
1111  env = environment_factory.CreateEnvironment(
1112      args, out_manager, infra_error)
1113  test_instance = test_instance_factory.CreateTestInstance(args, infra_error)
1114  test_run = test_run_factory.CreateTestRun(env, test_instance, infra_error)
1115
1116  contexts_to_notify_on_sigterm.append(env)
1117  contexts_to_notify_on_sigterm.append(test_run)
1118
1119  if args.list_tests:
1120    try:
1121      with out_manager, env, test_instance, test_run:
1122        test_names = test_run.GetTestsForListing()
1123      print('There are {} tests:'.format(len(test_names)))
1124      for n in test_names:
1125        print(n)
1126      return 0
1127    except NotImplementedError:
1128      sys.stderr.write('Test does not support --list-tests (type={}).\n'.format(
1129          args.command))
1130      return 1
1131
1132  ### Run.
1133  with out_manager, json_finalizer():
1134    # |raw_logs_fh| is only used by Robolectric tests.
1135    raw_logs_fh = io.StringIO() if save_detailed_results else None
1136
1137    with json_writer(), logcats_uploader, env, test_instance, test_run:
1138
1139      repetitions = (range(args.repeat +
1140                           1) if args.repeat >= 0 else itertools.count())
1141      result_counts = collections.defaultdict(
1142          lambda: collections.defaultdict(int))
1143      iteration_count = 0
1144      for _ in repetitions:
1145        # raw_results will be populated with base_test_result.TestRunResults by
1146        # test_run.RunTests(). It is immediately added to all_raw_results so
1147        # that in the event of an exception, all_raw_results will already have
1148        # the up-to-date results and those can be written to disk.
1149        raw_results = []
1150        all_raw_results.append(raw_results)
1151
1152        test_run.RunTests(raw_results, raw_logs_fh=raw_logs_fh)
1153        if not raw_results:
1154          all_raw_results.pop()
1155          continue
1156
1157        iteration_results = base_test_result.TestRunResults()
1158        for r in reversed(raw_results):
1159          iteration_results.AddTestRunResults(r)
1160        all_iteration_results.append(iteration_results)
1161        iteration_count += 1
1162
1163        for r in iteration_results.GetAll():
1164          result_counts[r.GetName()][r.GetType()] += 1
1165
1166        report_results.LogFull(
1167            results=iteration_results,
1168            test_type=test_instance.TestType(),
1169            test_package=test_run.TestPackage(),
1170            annotation=getattr(args, 'annotations', None),
1171            flakiness_server=getattr(args, 'flakiness_dashboard_server',
1172                                     None))
1173
1174        failed_tests = (iteration_results.GetNotPass() -
1175                        iteration_results.GetSkip())
1176        if failed_tests:
1177          _LogRerunStatement(failed_tests, args.wrapper_script_args)
1178
1179        if args.break_on_failure and not iteration_results.DidRunPass():
1180          break
1181
1182      if iteration_count > 1:
1183        # display summary results
1184        # only display results for a test if at least one test did not pass
1185        all_pass = 0
1186        tot_tests = 0
1187        for test_name in result_counts:
1188          tot_tests += 1
1189          if any(result_counts[test_name][x] for x in (
1190              base_test_result.ResultType.FAIL,
1191              base_test_result.ResultType.CRASH,
1192              base_test_result.ResultType.TIMEOUT,
1193              base_test_result.ResultType.UNKNOWN)):
1194            logging.critical(
1195                '%s: %s',
1196                test_name,
1197                ', '.join('%s %s' % (str(result_counts[test_name][i]), i)
1198                          for i in base_test_result.ResultType.GetTypes()))
1199          else:
1200            all_pass += 1
1201
1202        logging.critical('%s of %s tests passed in all %s runs',
1203                         str(all_pass),
1204                         str(tot_tests),
1205                         str(iteration_count))
1206
1207    if save_detailed_results:
1208      assert raw_logs_fh
1209      raw_logs_fh.seek(0)
1210      raw_logs = raw_logs_fh.read()
1211      if raw_logs:
1212        with out_manager.ArchivedTempfile(
1213            'raw_logs.txt', 'raw_logs',
1214            output_manager.Datatype.TEXT) as raw_logs_file:
1215          raw_logs_file.write(raw_logs)
1216        logging.critical('RAW LOGS: %s', raw_logs_file.Link())
1217
1218      with out_manager.ArchivedTempfile(
1219          'test_results_presentation.html',
1220          'test_results_presentation',
1221          output_manager.Datatype.HTML) as results_detail_file:
1222        result_html_string, _, _ = test_results_presentation.result_details(
1223            json_path=json_file.name,
1224            test_name=args.command,
1225            cs_base_url='http://cs.chromium.org',
1226            local_output=True)
1227        results_detail_file.write(result_html_string)
1228        results_detail_file.flush()
1229      logging.critical('TEST RESULTS: %s', results_detail_file.Link())
1230
1231      ui_screenshots = test_results_presentation.ui_screenshot_set(
1232          json_file.name)
1233      if ui_screenshots:
1234        with out_manager.ArchivedTempfile(
1235            'ui_screenshots.json',
1236            'ui_capture',
1237            output_manager.Datatype.JSON) as ui_screenshot_file:
1238          ui_screenshot_file.write(ui_screenshots)
1239        logging.critical('UI Screenshots: %s', ui_screenshot_file.Link())
1240
1241  return (0 if all(r.DidRunPass() for r in all_iteration_results)
1242          else constants.ERROR_EXIT_CODE)
1243
1244
1245def _LogRerunStatement(failed_tests, wrapper_arg_str):
1246  """Logs a message that can rerun the failed tests.
1247
1248  Logs a copy/pasteable message that filters tests so just the failing tests
1249  are run.
1250
1251  Args:
1252    failed_tests: A set of test results that did not pass.
1253    wrapper_arg_str: A string of args that were passed to the called wrapper
1254        script.
1255  """
1256  rerun_arg_list = []
1257  try:
1258    constants.CheckOutputDirectory()
1259  # constants.CheckOutputDirectory throws bare exceptions.
1260  except:  # pylint: disable=bare-except
1261    logging.exception('Output directory not found. Unable to generate failing '
1262                      'test filter file.')
1263    return
1264
1265  output_directory = constants.GetOutDirectory()
1266  if not os.path.exists(output_directory):
1267    logging.error('Output directory not found. Unable to generate failing '
1268                  'test filter file.')
1269    return
1270
1271  test_filter_file = os.path.join(os.path.relpath(output_directory),
1272                                  _RERUN_FAILED_TESTS_FILE)
1273  arg_list = shlex.split(wrapper_arg_str) if wrapper_arg_str else sys.argv
1274  index = 0
1275  while index < len(arg_list):
1276    arg = arg_list[index]
1277    # Skip adding the filter=<file> and/or the filter arg as we're replacing
1278    # it with the new filter arg.
1279    # This covers --test-filter=, --test-launcher-filter-file=, --gtest-filter=,
1280    # --test-filter *Foobar.baz, -f *foobar, --package-filter <package>,
1281    # --runner-filter <runner>.
1282    if 'filter' in arg or arg == '-f':
1283      index += 1 if '=' in arg else 2
1284      continue
1285
1286    rerun_arg_list.append(arg)
1287    index += 1
1288
1289  failed_test_list = [str(t) for t in failed_tests]
1290  with open(test_filter_file, 'w') as fp:
1291    for t in failed_test_list:
1292      # Test result names can have # in them that don't match when applied as
1293      # a test name filter.
1294      fp.write('%s\n' % t.replace('#', '.'))
1295
1296  rerun_arg_list.append('--test-launcher-filter-file=%s' % test_filter_file)
1297  msg = """
1298    %d Test(s) failed.
1299    Rerun failed tests with copy and pastable command:
1300        %s
1301    """
1302  logging.critical(msg, len(failed_tests), shlex.join(rerun_arg_list))
1303
1304
1305def DumpThreadStacks(_signal, _frame):
1306  for thread in threading.enumerate():
1307    reraiser_thread.LogThreadStack(thread)
1308
1309
1310def main():
1311  signal.signal(signal.SIGUSR1, DumpThreadStacks)
1312
1313  parser = argparse.ArgumentParser()
1314  command_parsers = parser.add_subparsers(
1315      title='test types', dest='command')
1316
1317  subp = command_parsers.add_parser(
1318      'gtest',
1319      help='googletest-based C++ tests')
1320  AddCommonOptions(subp)
1321  AddDeviceOptions(subp)
1322  AddEmulatorOptions(subp)
1323  AddGTestOptions(subp)
1324  AddTracingOptions(subp)
1325  AddCommandLineOptions(subp)
1326
1327  subp = command_parsers.add_parser(
1328      'instrumentation',
1329      help='InstrumentationTestCase-based Java tests')
1330  AddCommonOptions(subp)
1331  AddDeviceOptions(subp)
1332  AddEmulatorOptions(subp)
1333  AddInstrumentationTestOptions(subp)
1334  AddSkiaGoldTestOptions(subp)
1335  AddTracingOptions(subp)
1336  AddCommandLineOptions(subp)
1337
1338  subp = command_parsers.add_parser(
1339      'junit',
1340      help='JUnit4-based Java tests')
1341  AddCommonOptions(subp)
1342  AddJUnitTestOptions(subp)
1343
1344  subp = command_parsers.add_parser(
1345      'linker',
1346      help='linker tests')
1347  AddCommonOptions(subp)
1348  AddDeviceOptions(subp)
1349  AddEmulatorOptions(subp)
1350  AddLinkerTestOptions(subp)
1351
1352  subp = command_parsers.add_parser(
1353      'monkey',
1354      help="tests based on Android's monkey command")
1355  AddCommonOptions(subp)
1356  AddDeviceOptions(subp)
1357  AddEmulatorOptions(subp)
1358  AddMonkeyTestOptions(subp)
1359
1360  subp = command_parsers.add_parser(
1361      'python',
1362      help='python tests based on unittest.TestCase')
1363  AddCommonOptions(subp)
1364  AddPythonTestOptions(subp)
1365
1366  args, unknown_args = parser.parse_known_args()
1367  if unknown_args:
1368    if hasattr(args, 'allow_unknown') and args.allow_unknown:
1369      args.command_line_flags = unknown_args
1370    else:
1371      parser.error('unrecognized arguments: %s' % ' '.join(unknown_args))
1372
1373  # --replace-system-package/--remove-system-package has the potential to cause
1374  # issues if --enable-concurrent-adb is set, so disallow that combination.
1375  concurrent_adb_enabled = (hasattr(args, 'enable_concurrent_adb')
1376                            and args.enable_concurrent_adb)
1377  replacing_system_packages = (hasattr(args, 'replace_system_package')
1378                               and args.replace_system_package)
1379  removing_system_packages = (hasattr(args, 'system_packages_to_remove')
1380                              and args.system_packages_to_remove)
1381  if (concurrent_adb_enabled
1382      and (replacing_system_packages or removing_system_packages)):
1383    parser.error('--enable-concurrent-adb cannot be used with either '
1384                 '--replace-system-package or --remove-system-package')
1385
1386  # --use-webview-provider has the potential to cause issues if
1387  # --enable-concurrent-adb is set, so disallow that combination
1388  if (hasattr(args, 'use_webview_provider') and
1389      hasattr(args, 'enable_concurrent_adb') and args.use_webview_provider and
1390      args.enable_concurrent_adb):
1391    parser.error('--use-webview-provider and --enable-concurrent-adb cannot '
1392                 'be used together')
1393
1394  if (getattr(args, 'coverage_on_the_fly', False)
1395      and not getattr(args, 'coverage_dir', '')):
1396    parser.error('--coverage-on-the-fly requires --coverage-dir')
1397
1398  if (hasattr(args, 'debug_socket') or
1399      (hasattr(args, 'wait_for_java_debugger') and
1400      args.wait_for_java_debugger)):
1401    args.num_retries = 0
1402
1403  # Result-sink may not exist in the environment if rdb stream is not enabled.
1404  result_sink_client = result_sink.TryInitClient()
1405
1406  try:
1407    return RunTestsCommand(args, result_sink_client)
1408  except base_error.BaseError as e:
1409    logging.exception('Error occurred.')
1410    if e.is_infra_error:
1411      return constants.INFRA_EXIT_CODE
1412    return constants.ERROR_EXIT_CODE
1413  except: # pylint: disable=W0702
1414    logging.exception('Unrecognized error occurred.')
1415    return constants.ERROR_EXIT_CODE
1416
1417
1418if __name__ == '__main__':
1419  sys.exit(main())
1420