xref: /aosp_15_r20/external/cronet/build/android/test_runner.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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      '--additional-apk',
402      action='append', dest='additional_apks', default=[],
403      type=_RealPath,
404      help='Additional apk that must be installed on '
405           'the device when the tests are run.')
406  parser.add_argument(
407      '--app-data-file',
408      action='append', dest='app_data_files',
409      help='A file path relative to the app data directory '
410           'that should be saved to the host.')
411  parser.add_argument(
412      '--app-data-file-dir',
413      help='Host directory to which app data files will be'
414           ' saved. Used with --app-data-file.')
415  parser.add_argument(
416      '--enable-xml-result-parsing',
417      action='store_true', help=argparse.SUPPRESS)
418  parser.add_argument(
419      '--executable-dist-dir',
420      type=os.path.realpath,
421      help="Path to executable's dist directory for native"
422           " (non-apk) tests.")
423  parser.add_argument(
424      '--extract-test-list-from-filter',
425      action='store_true',
426      help='When a test filter is specified, and the list of '
427           'tests can be determined from it, skip querying the '
428           'device for the list of all tests. Speeds up local '
429           'development, but is not safe to use on bots ('
430           'http://crbug.com/549214')
431  parser.add_argument(
432      '--gs-test-artifacts-bucket',
433      help=('If present, test artifacts will be uploaded to this Google '
434            'Storage bucket.'))
435  parser.add_argument(
436      '--render-test-output-dir',
437      help='If present, store rendering artifacts in this path.')
438  parser.add_argument(
439      '--runtime-deps-path',
440      dest='runtime_deps_path', type=os.path.realpath,
441      help='Runtime data dependency file from GN.')
442  parser.add_argument(
443      '-t', '--shard-timeout',
444      dest='shard_timeout', type=int, default=120,
445      help='Timeout to wait for each test (default: %(default)s).')
446  parser.add_argument(
447      '--store-tombstones',
448      dest='store_tombstones', action='store_true',
449      help='Add tombstones in results if crash.')
450  parser.add_argument(
451      '-s', '--suite',
452      dest='suite_name', nargs='+', metavar='SUITE_NAME', required=True,
453      help='Executable name of the test suite to run.')
454  parser.add_argument(
455      '--test-apk-incremental-install-json',
456      type=os.path.realpath,
457      help='Path to install json for the test apk.')
458  parser.add_argument('--test-launcher-batch-limit',
459                      dest='test_launcher_batch_limit',
460                      type=int,
461                      help='The max number of tests to run in a shard. '
462                      'Ignores non-positive ints and those greater than '
463                      'MAX_SHARDS')
464  parser.add_argument(
465      '-w', '--wait-for-java-debugger', action='store_true',
466      help='Wait for java debugger to attach before running any application '
467           'code. Also disables test timeouts and sets retries=0.')
468  parser.add_argument(
469      '--coverage-dir',
470      type=os.path.realpath,
471      help='Directory in which to place all generated coverage files.')
472  parser.add_argument(
473      '--use-existing-test-data',
474      action='store_true',
475      help='Do not push new files to the device, instead using existing APK '
476      'and test data. Only use when running the same test for multiple '
477      'iterations.')
478  # This is currently only implemented for gtests tests.
479  parser.add_argument('--gtest_also_run_pre_tests',
480                      '--gtest-also-run-pre-tests',
481                      dest='run_pre_tests',
482                      action='store_true',
483                      help='Also run PRE_ tests if applicable.')
484
485
486def AddInstrumentationTestOptions(parser):
487  """Adds Instrumentation test options to |parser|."""
488
489  parser = parser.add_argument_group('instrumentation arguments')
490
491  parser.add_argument('--additional-apex',
492                      action='append',
493                      dest='additional_apexs',
494                      default=[],
495                      type=_RealPath,
496                      help='Additional apex that must be installed on '
497                      'the device when the tests are run')
498  parser.add_argument(
499      '--additional-apk',
500      action='append', dest='additional_apks', default=[],
501      type=_RealPath,
502      help='Additional apk that must be installed on '
503           'the device when the tests are run')
504  parser.add_argument('--forced-queryable-additional-apk',
505                      action='append',
506                      dest='forced_queryable_additional_apks',
507                      default=[],
508                      type=_RealPath,
509                      help='Configures an additional-apk to be forced '
510                      'to be queryable by other APKs.')
511  parser.add_argument('--instant-additional-apk',
512                      action='append',
513                      dest='instant_additional_apks',
514                      default=[],
515                      type=_RealPath,
516                      help='Configures an additional-apk to be an instant APK')
517  parser.add_argument(
518      '-A', '--annotation',
519      dest='annotation_str',
520      help='Comma-separated list of annotations. Run only tests with any of '
521           'the given annotations. An annotation can be either a key or a '
522           'key-values pair. A test that has no annotation is considered '
523           '"SmallTest".')
524  # TODO(jbudorick): Remove support for name-style APK specification once
525  # bots are no longer doing it.
526  parser.add_argument(
527      '--apk-under-test',
528      help='Path or name of the apk under test.')
529  parser.add_argument(
530      '--store-data-dependencies-in-temp',
531      action='store_true',
532      help='Store data dependencies in /data/local/tmp/chromium_tests_root')
533  parser.add_argument(
534      '--module',
535      action='append',
536      dest='modules',
537      help='Specify Android App Bundle modules to install in addition to the '
538      'base module.')
539  parser.add_argument(
540      '--fake-module',
541      action='append',
542      dest='fake_modules',
543      help='Specify Android App Bundle modules to fake install in addition to '
544      'the real modules.')
545  parser.add_argument(
546      '--additional-locale',
547      action='append',
548      dest='additional_locales',
549      help='Specify locales in addition to the device locale to install splits '
550      'for when --apk-under-test is an Android App Bundle.')
551  parser.add_argument(
552      '--coverage-dir',
553      type=os.path.realpath,
554      help='Directory in which to place all generated '
555      'Jacoco coverage files.')
556  parser.add_argument(
557      '--disable-dalvik-asserts',
558      dest='set_asserts', action='store_false', default=True,
559      help='Removes the dalvik.vm.enableassertions property')
560  parser.add_argument(
561      '--proguard-mapping-path',
562      help='.mapping file to use to Deobfuscate java stack traces in test '
563      'output and logcat.')
564  parser.add_argument(
565      '-E', '--exclude-annotation',
566      dest='exclude_annotation_str',
567      help='Comma-separated list of annotations. Exclude tests with these '
568           'annotations.')
569  parser.add_argument(
570      '--enable-breakpad-dump',
571      action='store_true',
572      help='Stores any breakpad dumps till the end of the test.')
573  parser.add_argument(
574      '--replace-system-package',
575      type=_RealPath,
576      default=None,
577      help='Use this apk to temporarily replace a system package with the same '
578      'package name.')
579  parser.add_argument(
580      '--remove-system-package',
581      default=[],
582      action='append',
583      dest='system_packages_to_remove',
584      help='Specifies a system package to remove before testing if it exists '
585      'on the system. WARNING: THIS WILL PERMANENTLY REMOVE THE SYSTEM APP. '
586      'Unlike --replace-system-package, the app will not be restored after '
587      'tests are finished.')
588  parser.add_argument(
589      '--use-voice-interaction-service',
590      help='This can be used to update the voice interaction service to be a '
591      'custom one. This is useful for mocking assistants. eg: '
592      'android.assist.service/.MainInteractionService')
593  parser.add_argument(
594      '--use-webview-provider',
595      type=_RealPath, default=None,
596      help='Use this apk as the webview provider during test. '
597           'The original provider will be restored if possible, '
598           "on Nougat the provider can't be determined and so "
599           'the system will choose the default provider.')
600  parser.add_argument(
601      '--webview-command-line-arg',
602      default=[],
603      action='append',
604      help="Specifies command line arguments to add to WebView's flag file")
605  parser.add_argument(
606      '--run-setup-command',
607      default=[],
608      action='append',
609      dest='run_setup_commands',
610      help='This can be used to run a custom shell command on the device as a '
611      'setup step')
612  parser.add_argument(
613      '--run-teardown-command',
614      default=[],
615      action='append',
616      dest='run_teardown_commands',
617      help='This can be used to run a custom shell command on the device as a '
618      'teardown step')
619  parser.add_argument(
620      '--runtime-deps-path',
621      dest='runtime_deps_path', type=os.path.realpath,
622      help='Runtime data dependency file from GN.')
623  parser.add_argument(
624      '--screenshot-directory',
625      dest='screenshot_dir', type=os.path.realpath,
626      help='Capture screenshots of test failures')
627  parser.add_argument(
628      '--store-tombstones',
629      action='store_true', dest='store_tombstones',
630      help='Add tombstones in results if crash.')
631  parser.add_argument(
632      '--strict-mode',
633      dest='strict_mode', default='testing',
634      help='StrictMode command-line flag set on the device, '
635           'death/testing to kill the process, off to stop '
636           'checking, flash to flash only. (default: %(default)s)')
637  parser.add_argument(
638      '--test-apk',
639      required=True,
640      help='Path or name of the apk containing the tests.')
641  parser.add_argument(
642      '--test-apk-as-instant',
643      action='store_true',
644      help='Install the test apk as an instant app. '
645      'Instant apps run in a more restrictive execution environment.')
646  parser.add_argument(
647      '--test-launcher-batch-limit',
648      dest='test_launcher_batch_limit',
649      type=int,
650      help=('Not actually used for instrumentation tests, but can be used as '
651            'a proxy for determining if the current run is a retry without '
652            'patch.'))
653  parser.add_argument(
654      '--timeout-scale',
655      type=float,
656      help='Factor by which timeouts should be scaled.')
657  parser.add_argument(
658      '--is-unit-test',
659      action='store_true',
660      help=('Specify the test suite as composed of unit tests, blocking '
661            'certain operations.'))
662  parser.add_argument(
663      '-w', '--wait-for-java-debugger', action='store_true',
664      help='Wait for java debugger to attach before running any application '
665           'code. Also disables test timeouts and sets retries=0.')
666
667  # WPR record mode.
668  parser.add_argument('--wpr-enable-record',
669                      action='store_true',
670                      default=False,
671                      help='If true, WPR server runs in record mode.'
672                      'otherwise, runs in replay mode.')
673
674  parser.add_argument(
675      '--approve-app-links',
676      help='Force enables Digital Asset Link verification for the provided '
677      'package and domain, example usage: --approve-app-links '
678      'com.android.package:www.example.com')
679
680  # These arguments are suppressed from the help text because they should
681  # only ever be specified by an intermediate script.
682  parser.add_argument(
683      '--apk-under-test-incremental-install-json',
684      help=argparse.SUPPRESS)
685  parser.add_argument(
686      '--test-apk-incremental-install-json',
687      type=os.path.realpath,
688      help=argparse.SUPPRESS)
689
690
691def AddSkiaGoldTestOptions(parser):
692  """Adds Skia Gold test options to |parser|."""
693  parser = parser.add_argument_group("Skia Gold arguments")
694  parser.add_argument(
695      '--code-review-system',
696      help='A non-default code review system to pass to pass to Gold, if '
697      'applicable')
698  parser.add_argument(
699      '--continuous-integration-system',
700      help='A non-default continuous integration system to pass to Gold, if '
701      'applicable')
702  parser.add_argument(
703      '--git-revision', help='The git commit currently being tested.')
704  parser.add_argument(
705      '--gerrit-issue',
706      help='The Gerrit issue this test is being run on, if applicable.')
707  parser.add_argument(
708      '--gerrit-patchset',
709      help='The Gerrit patchset this test is being run on, if applicable.')
710  parser.add_argument(
711      '--buildbucket-id',
712      help='The Buildbucket build ID that this test was triggered from, if '
713      'applicable.')
714  local_group = parser.add_mutually_exclusive_group()
715  local_group.add_argument(
716      '--local-pixel-tests',
717      action='store_true',
718      default=None,
719      help='Specifies to run the Skia Gold pixel tests in local mode. When run '
720      'in local mode, uploading to Gold is disabled and traditional '
721      'generated/golden/diff images are output instead of triage links. '
722      'Running in local mode also implies --no-luci-auth. If both this '
723      'and --no-local-pixel-tests are left unset, the test harness will '
724      'attempt to detect whether it is running on a workstation or not '
725      'and set the options accordingly.')
726  local_group.add_argument(
727      '--no-local-pixel-tests',
728      action='store_false',
729      dest='local_pixel_tests',
730      help='Specifies to run the Skia Gold pixel tests in non-local (bot) '
731      'mode. When run in this mode, data is actually uploaded to Gold and '
732      'triage links are generated. If both this and --local-pixel-tests '
733      'are left unset, the test harness will attempt to detect whether '
734      'it is running on a workstation or not and set the options '
735      'accordingly.')
736  parser.add_argument(
737      '--no-luci-auth',
738      action='store_true',
739      default=False,
740      help="Don't use the serve account provided by LUCI for authentication "
741      'with Skia Gold, instead relying on gsutil to be pre-authenticated. '
742      'Meant for testing locally instead of on the bots.')
743  parser.add_argument(
744      '--bypass-skia-gold-functionality',
745      action='store_true',
746      default=False,
747      help='Bypass all interaction with Skia Gold, effectively disabling the '
748      'image comparison portion of any tests that use Gold. Only meant to be '
749      'used in case a Gold outage occurs and cannot be fixed quickly.')
750
751
752def AddHostsideTestOptions(parser):
753  """Adds hostside test options to |parser|."""
754
755  parser = parser.add_argument_group('hostside arguments')
756
757  parser.add_argument(
758      '-s', '--test-suite', required=True,
759      help='Hostside test suite to run.')
760  parser.add_argument(
761      '--test-apk-as-instant',
762      action='store_true',
763      help='Install the test apk as an instant app. '
764      'Instant apps run in a more restrictive execution environment.')
765  parser.add_argument(
766      '--additional-apk',
767      action='append',
768      dest='additional_apks',
769      default=[],
770      type=_RealPath,
771      help='Additional apk that must be installed on '
772           'the device when the tests are run')
773  parser.add_argument(
774      '--use-webview-provider',
775      type=_RealPath, default=None,
776      help='Use this apk as the webview provider during test. '
777           'The original provider will be restored if possible, '
778           "on Nougat the provider can't be determined and so "
779           'the system will choose the default provider.')
780  parser.add_argument(
781      '--tradefed-executable',
782      type=_RealPath, default=None,
783      help='Location of the cts-tradefed script')
784  parser.add_argument(
785      '--tradefed-aapt-path',
786      type=_RealPath, default=None,
787      help='Location of the directory containing aapt binary')
788  parser.add_argument(
789      '--tradefed-adb-path',
790      type=_RealPath, default=None,
791      help='Location of the directory containing adb binary')
792  # The below arguments are not used, but allow us to pass the same arguments
793  # from run_cts.py regardless of type of run (instrumentation/hostside)
794  parser.add_argument(
795      '--apk-under-test',
796      help=argparse.SUPPRESS)
797  parser.add_argument(
798      '--use-apk-under-test-flags-file',
799      action='store_true',
800      help=argparse.SUPPRESS)
801  parser.add_argument(
802      '-E', '--exclude-annotation',
803      dest='exclude_annotation_str',
804      help=argparse.SUPPRESS)
805
806
807def AddJUnitTestOptions(parser):
808  """Adds junit test options to |parser|."""
809
810  parser = parser.add_argument_group('junit arguments')
811
812  parser.add_argument(
813      '--coverage-on-the-fly',
814      action='store_true',
815      help='Generate coverage data by Jacoco on-the-fly instrumentation.')
816  parser.add_argument(
817      '--coverage-dir', type=os.path.realpath,
818      help='Directory to store coverage info.')
819  parser.add_argument(
820      '--package-filter',
821      help='Filters tests by package.')
822  parser.add_argument(
823      '--runner-filter',
824      help='Filters tests by runner class. Must be fully qualified.')
825  parser.add_argument('--json-config',
826                      help='Runs only tests listed in this config.')
827  parser.add_argument(
828      '--shards',
829      type=int,
830      help='Number of shards to run junit tests in parallel on. Only 1 shard '
831      'is supported when test-filter is specified. Values less than 1 will '
832      'use auto select.')
833  parser.add_argument('--shard-filter',
834                      help='Comma separated list of shard indices to run.')
835  parser.add_argument(
836      '-s', '--test-suite', required=True,
837      help='JUnit test suite to run.')
838  debug_group = parser.add_mutually_exclusive_group()
839  debug_group.add_argument(
840      '-w', '--wait-for-java-debugger', action='store_const', const='8701',
841      dest='debug_socket', help='Alias for --debug-socket=8701')
842  debug_group.add_argument(
843      '--debug-socket',
844      help='Wait for java debugger to attach at specified socket address '
845           'before running any application code. Also disables test timeouts '
846           'and sets retries=0.')
847
848  # These arguments are for Android Robolectric tests.
849  parser.add_argument(
850      '--robolectric-runtime-deps-dir',
851      help='Path to runtime deps for Robolectric.')
852  parser.add_argument('--native-libs-dir',
853                      help='Path to search for native libraries.')
854  parser.add_argument(
855      '--resource-apk',
856      required=True,
857      help='Path to .ap_ containing binary resources for Robolectric.')
858
859
860def AddLinkerTestOptions(parser):
861
862  parser = parser.add_argument_group('linker arguments')
863
864  parser.add_argument(
865      '--test-apk',
866      type=os.path.realpath,
867      help='Path to the linker test APK.')
868
869
870def AddMonkeyTestOptions(parser):
871  """Adds monkey test options to |parser|."""
872
873  parser = parser.add_argument_group('monkey arguments')
874
875  parser.add_argument('--browser',
876                      required=True,
877                      choices=list(constants.PACKAGE_INFO.keys()),
878                      metavar='BROWSER',
879                      help='Browser under test.')
880  parser.add_argument(
881      '--category',
882      nargs='*', dest='categories', default=[],
883      help='A list of allowed categories. Monkey will only visit activities '
884           'that are listed with one of the specified categories.')
885  parser.add_argument(
886      '--event-count',
887      default=10000, type=int,
888      help='Number of events to generate (default: %(default)s).')
889  parser.add_argument(
890      '--seed',
891      type=int,
892      help='Seed value for pseudo-random generator. Same seed value generates '
893           'the same sequence of events. Seed is randomized by default.')
894  parser.add_argument(
895      '--throttle',
896      default=100, type=int,
897      help='Delay between events (ms) (default: %(default)s). ')
898
899
900def AddPythonTestOptions(parser):
901
902  parser = parser.add_argument_group('python arguments')
903
904  parser.add_argument('-s',
905                      '--suite',
906                      dest='suite_name',
907                      metavar='SUITE_NAME',
908                      choices=list(constants.PYTHON_UNIT_TEST_SUITES.keys()),
909                      help='Name of the test suite to run.')
910
911
912def _CreateClassToFileNameDict(test_apk):
913  """Creates a dict mapping classes to file names from size-info apk."""
914  constants.CheckOutputDirectory()
915  test_apk_size_info = os.path.join(constants.GetOutDirectory(), 'size-info',
916                                    os.path.basename(test_apk) + '.jar.info')
917
918  class_to_file_dict = {}
919  # Some tests such as webview_cts_tests use a separately downloaded apk to run
920  # tests. This means the apk may not have been built by the system and hence
921  # no size info file exists.
922  if not os.path.exists(test_apk_size_info):
923    logging.debug('Apk size file not found. %s', test_apk_size_info)
924    return class_to_file_dict
925
926  with open(test_apk_size_info, 'r') as f:
927    for line in f:
928      file_class, file_name = line.rstrip().split(',', 1)
929      # Only want files that are not prebuilt.
930      if file_name.startswith('../../'):
931        class_to_file_dict[file_class] = str(
932            file_name.replace('../../', '//', 1))
933
934  return class_to_file_dict
935
936
937def _RunPythonTests(args):
938  """Subcommand of RunTestsCommand which runs python unit tests."""
939  suite_vars = constants.PYTHON_UNIT_TEST_SUITES[args.suite_name]
940  suite_path = suite_vars['path']
941  suite_test_modules = suite_vars['test_modules']
942
943  sys.path = [suite_path] + sys.path
944  try:
945    suite = unittest.TestSuite()
946    suite.addTests(unittest.defaultTestLoader.loadTestsFromName(m)
947                   for m in suite_test_modules)
948    runner = unittest.TextTestRunner(verbosity=1+args.verbose_count)
949    return 0 if runner.run(suite).wasSuccessful() else 1
950  finally:
951    sys.path = sys.path[1:]
952
953
954_DEFAULT_PLATFORM_MODE_TESTS = [
955    'gtest', 'hostside', 'instrumentation', 'junit', 'linker', 'monkey'
956]
957
958
959def RunTestsCommand(args, result_sink_client=None):
960  """Checks test type and dispatches to the appropriate function.
961
962  Args:
963    args: argparse.Namespace object.
964    result_sink_client: A ResultSinkClient object.
965
966  Returns:
967    Integer indicated exit code.
968
969  Raises:
970    Exception: Unknown command name passed in, or an exception from an
971        individual test runner.
972  """
973  command = args.command
974
975  ProcessCommonOptions(args)
976  logging.info('command: %s', ' '.join(sys.argv))
977  if args.enable_platform_mode or command in _DEFAULT_PLATFORM_MODE_TESTS:
978    return RunTestsInPlatformMode(args, result_sink_client)
979
980  if command == 'python':
981    return _RunPythonTests(args)
982  raise Exception('Unknown test type.')
983
984
985def _SinkTestResult(test_result, test_file_name, result_sink_client):
986  """Upload test result to result_sink.
987
988  Args:
989    test_result: A BaseTestResult object
990    test_file_name: A string representing the file location of the test
991    result_sink_client: A ResultSinkClient object
992
993  Returns:
994    N/A
995  """
996  # Some tests put in non utf-8 char as part of the test
997  # which breaks uploads, so need to decode and re-encode.
998  log_decoded = test_result.GetLog()
999  if isinstance(log_decoded, bytes):
1000    log_decoded = log_decoded.decode('utf-8', 'replace')
1001  html_artifact = ''
1002  https_artifacts = []
1003  for link_name, link_url in sorted(test_result.GetLinks().items()):
1004    if link_url.startswith('https:'):
1005      https_artifacts.append('<li><a target="_blank" href=%s>%s</a></li>' %
1006                             (link_url, link_name))
1007    else:
1008      logging.info('Skipping non-https link %r (%s) for test %s.', link_name,
1009                   link_url, test_result.GetName())
1010  if https_artifacts:
1011    html_artifact += '<ul>%s</ul>' % '\n'.join(https_artifacts)
1012  result_sink_client.Post(test_result.GetNameForResultSink(),
1013                          test_result.GetType(),
1014                          test_result.GetDuration(),
1015                          log_decoded,
1016                          test_file_name,
1017                          variant=test_result.GetVariantForResultSink(),
1018                          failure_reason=test_result.GetFailureReason(),
1019                          html_artifact=html_artifact)
1020
1021
1022_SUPPORTED_IN_PLATFORM_MODE = [
1023  # TODO(jbudorick): Add support for more test types.
1024  'gtest',
1025  'hostside',
1026  'instrumentation',
1027  'junit',
1028  'linker',
1029  'monkey',
1030]
1031
1032
1033def RunTestsInPlatformMode(args, result_sink_client=None):
1034
1035  def infra_error(message):
1036    logging.fatal(message)
1037    sys.exit(constants.INFRA_EXIT_CODE)
1038
1039  if args.command not in _SUPPORTED_IN_PLATFORM_MODE:
1040    infra_error('%s is not yet supported in platform mode' % args.command)
1041
1042  ### Set up sigterm handler.
1043
1044  contexts_to_notify_on_sigterm = []
1045  def unexpected_sigterm(_signum, _frame):
1046    msg = [
1047      'Received SIGTERM. Shutting down.',
1048    ]
1049    for live_thread in threading.enumerate():
1050      # pylint: disable=protected-access
1051      thread_stack = ''.join(traceback.format_stack(
1052          sys._current_frames()[live_thread.ident]))
1053      msg.extend([
1054        'Thread "%s" (ident: %s) is currently running:' % (
1055            live_thread.name, live_thread.ident),
1056        thread_stack])
1057
1058    for context in contexts_to_notify_on_sigterm:
1059      context.ReceivedSigterm()
1060
1061    infra_error('\n'.join(msg))
1062
1063  signal.signal(signal.SIGTERM, unexpected_sigterm)
1064
1065  ### Set up results handling.
1066  # TODO(jbudorick): Rewrite results handling.
1067
1068  # all_raw_results is a list of lists of
1069  # base_test_result.TestRunResults objects. Each instance of
1070  # TestRunResults contains all test results produced by a single try,
1071  # while each list of TestRunResults contains all tries in a single
1072  # iteration.
1073  all_raw_results = []
1074
1075  # all_iteration_results is a list of base_test_result.TestRunResults
1076  # objects. Each instance of TestRunResults contains the last test
1077  # result for each test run in that iteration.
1078  all_iteration_results = []
1079
1080  global_results_tags = set()
1081
1082  json_file = tempfile.NamedTemporaryFile(delete=False)
1083  json_file.close()
1084
1085  @contextlib.contextmanager
1086  def json_finalizer():
1087    try:
1088      yield
1089    finally:
1090      if args.json_results_file and os.path.exists(json_file.name):
1091        shutil.move(json_file.name, args.json_results_file)
1092      elif args.isolated_script_test_output and os.path.exists(json_file.name):
1093        shutil.move(json_file.name, args.isolated_script_test_output)
1094      else:
1095        os.remove(json_file.name)
1096
1097  @contextlib.contextmanager
1098  def json_writer():
1099    try:
1100      yield
1101    except Exception:
1102      global_results_tags.add('UNRELIABLE_RESULTS')
1103      raise
1104    finally:
1105      if args.isolated_script_test_output:
1106        interrupted = 'UNRELIABLE_RESULTS' in global_results_tags
1107        json_results.GenerateJsonTestResultFormatFile(all_raw_results,
1108                                                      interrupted,
1109                                                      json_file.name,
1110                                                      indent=2)
1111      else:
1112        json_results.GenerateJsonResultsFile(
1113            all_raw_results,
1114            json_file.name,
1115            global_tags=list(global_results_tags),
1116            indent=2)
1117
1118      test_class_to_file_name_dict = {}
1119      # Test Location is only supported for instrumentation tests as it
1120      # requires the size-info file.
1121      if test_instance.TestType() == 'instrumentation':
1122        test_class_to_file_name_dict = _CreateClassToFileNameDict(args.test_apk)
1123
1124      if result_sink_client:
1125        for run in all_raw_results:
1126          for results in run:
1127            for r in results.GetAll():
1128              # Matches chrome.page_info.PageInfoViewTest#testChromePage
1129              match = re.search(r'^(.+\..+)#', r.GetName())
1130              test_file_name = test_class_to_file_name_dict.get(
1131                  match.group(1)) if match else None
1132              _SinkTestResult(r, test_file_name, result_sink_client)
1133
1134  @contextlib.contextmanager
1135  def upload_logcats_file():
1136    try:
1137      yield
1138    finally:
1139      if not args.logcat_output_file:
1140        logging.critical('Cannot upload logcat file: no file specified.')
1141      elif not os.path.exists(args.logcat_output_file):
1142        logging.critical("Cannot upload logcat file: file doesn't exist.")
1143      else:
1144        with open(args.logcat_output_file) as src:
1145          dst = logdog_helper.open_text('unified_logcats')
1146          if dst:
1147            shutil.copyfileobj(src, dst)
1148            dst.close()
1149            logging.critical(
1150                'Logcat: %s', logdog_helper.get_viewer_url('unified_logcats'))
1151
1152
1153  logcats_uploader = contextlib_ext.Optional(
1154      upload_logcats_file(),
1155      'upload_logcats_file' in args and args.upload_logcats_file)
1156
1157  save_detailed_results = (args.local_output or not local_utils.IsOnSwarming()
1158                           ) and not args.isolated_script_test_output
1159
1160  ### Set up test objects.
1161
1162  out_manager = output_manager_factory.CreateOutputManager(args)
1163  env = environment_factory.CreateEnvironment(
1164      args, out_manager, infra_error)
1165  test_instance = test_instance_factory.CreateTestInstance(args, infra_error)
1166  test_run = test_run_factory.CreateTestRun(env, test_instance, infra_error)
1167
1168  contexts_to_notify_on_sigterm.append(env)
1169  contexts_to_notify_on_sigterm.append(test_run)
1170
1171  if args.list_tests:
1172    try:
1173      with out_manager, env, test_instance, test_run:
1174        test_names = test_run.GetTestsForListing()
1175      print('There are {} tests:'.format(len(test_names)))
1176      for n in test_names:
1177        print(n)
1178      return 0
1179    except NotImplementedError:
1180      sys.stderr.write('Test does not support --list-tests (type={}).\n'.format(
1181          args.command))
1182      return 1
1183
1184  ### Run.
1185  with out_manager, json_finalizer():
1186    # |raw_logs_fh| is only used by Robolectric tests.
1187    raw_logs_fh = io.StringIO() if save_detailed_results else None
1188
1189    with json_writer(), logcats_uploader, env, test_instance, test_run:
1190
1191      repetitions = (range(args.repeat +
1192                           1) if args.repeat >= 0 else itertools.count())
1193      result_counts = collections.defaultdict(
1194          lambda: collections.defaultdict(int))
1195      iteration_count = 0
1196      for _ in repetitions:
1197        # raw_results will be populated with base_test_result.TestRunResults by
1198        # test_run.RunTests(). It is immediately added to all_raw_results so
1199        # that in the event of an exception, all_raw_results will already have
1200        # the up-to-date results and those can be written to disk.
1201        raw_results = []
1202        all_raw_results.append(raw_results)
1203
1204        test_run.RunTests(raw_results, raw_logs_fh=raw_logs_fh)
1205        if not raw_results:
1206          all_raw_results.pop()
1207          continue
1208
1209        iteration_results = base_test_result.TestRunResults()
1210        for r in reversed(raw_results):
1211          iteration_results.AddTestRunResults(r)
1212        all_iteration_results.append(iteration_results)
1213        iteration_count += 1
1214
1215        for r in iteration_results.GetAll():
1216          result_counts[r.GetName()][r.GetType()] += 1
1217
1218        report_results.LogFull(
1219            results=iteration_results,
1220            test_type=test_instance.TestType(),
1221            test_package=test_run.TestPackage(),
1222            annotation=getattr(args, 'annotations', None),
1223            flakiness_server=getattr(args, 'flakiness_dashboard_server',
1224                                     None))
1225
1226        failed_tests = (iteration_results.GetNotPass() -
1227                        iteration_results.GetSkip())
1228        if failed_tests:
1229          _LogRerunStatement(failed_tests, args.wrapper_script_args)
1230
1231        if args.break_on_failure and not iteration_results.DidRunPass():
1232          break
1233
1234      if iteration_count > 1:
1235        # display summary results
1236        # only display results for a test if at least one test did not pass
1237        all_pass = 0
1238        tot_tests = 0
1239        for test_name in result_counts:
1240          tot_tests += 1
1241          if any(result_counts[test_name][x] for x in (
1242              base_test_result.ResultType.FAIL,
1243              base_test_result.ResultType.CRASH,
1244              base_test_result.ResultType.TIMEOUT,
1245              base_test_result.ResultType.UNKNOWN)):
1246            logging.critical(
1247                '%s: %s',
1248                test_name,
1249                ', '.join('%s %s' % (str(result_counts[test_name][i]), i)
1250                          for i in base_test_result.ResultType.GetTypes()))
1251          else:
1252            all_pass += 1
1253
1254        logging.critical('%s of %s tests passed in all %s runs',
1255                         str(all_pass),
1256                         str(tot_tests),
1257                         str(iteration_count))
1258
1259    if save_detailed_results:
1260      assert raw_logs_fh
1261      raw_logs_fh.seek(0)
1262      raw_logs = raw_logs_fh.read()
1263      if raw_logs:
1264        with out_manager.ArchivedTempfile(
1265            'raw_logs.txt', 'raw_logs',
1266            output_manager.Datatype.TEXT) as raw_logs_file:
1267          raw_logs_file.write(raw_logs)
1268        logging.critical('RAW LOGS: %s', raw_logs_file.Link())
1269
1270      with out_manager.ArchivedTempfile(
1271          'test_results_presentation.html',
1272          'test_results_presentation',
1273          output_manager.Datatype.HTML) as results_detail_file:
1274        result_html_string, _, _ = test_results_presentation.result_details(
1275            json_path=json_file.name,
1276            test_name=args.command,
1277            cs_base_url='http://cs.chromium.org',
1278            local_output=True)
1279        results_detail_file.write(result_html_string)
1280        results_detail_file.flush()
1281      logging.critical('TEST RESULTS: %s', results_detail_file.Link())
1282
1283      ui_screenshots = test_results_presentation.ui_screenshot_set(
1284          json_file.name)
1285      if ui_screenshots:
1286        with out_manager.ArchivedTempfile(
1287            'ui_screenshots.json',
1288            'ui_capture',
1289            output_manager.Datatype.JSON) as ui_screenshot_file:
1290          ui_screenshot_file.write(ui_screenshots)
1291        logging.critical('UI Screenshots: %s', ui_screenshot_file.Link())
1292
1293  return (0 if all(r.DidRunPass() for r in all_iteration_results)
1294          else constants.ERROR_EXIT_CODE)
1295
1296
1297def _LogRerunStatement(failed_tests, wrapper_arg_str):
1298  """Logs a message that can rerun the failed tests.
1299
1300  Logs a copy/pasteable message that filters tests so just the failing tests
1301  are run.
1302
1303  Args:
1304    failed_tests: A set of test results that did not pass.
1305    wrapper_arg_str: A string of args that were passed to the called wrapper
1306        script.
1307  """
1308  rerun_arg_list = []
1309  try:
1310    constants.CheckOutputDirectory()
1311  # constants.CheckOutputDirectory throws bare exceptions.
1312  except:  # pylint: disable=bare-except
1313    logging.exception('Output directory not found. Unable to generate failing '
1314                      'test filter file.')
1315    return
1316
1317  output_directory = constants.GetOutDirectory()
1318  if not os.path.exists(output_directory):
1319    logging.error('Output directory not found. Unable to generate failing '
1320                  'test filter file.')
1321    return
1322
1323  test_filter_file = os.path.join(os.path.relpath(output_directory),
1324                                  _RERUN_FAILED_TESTS_FILE)
1325  arg_list = shlex.split(wrapper_arg_str) if wrapper_arg_str else sys.argv
1326  index = 0
1327  while index < len(arg_list):
1328    arg = arg_list[index]
1329    # Skip adding the filter=<file> and/or the filter arg as we're replacing
1330    # it with the new filter arg.
1331    # This covers --test-filter=, --test-launcher-filter-file=, --gtest-filter=,
1332    # --test-filter *Foobar.baz, -f *foobar, --package-filter <package>,
1333    # --runner-filter <runner>.
1334    if 'filter' in arg or arg == '-f':
1335      index += 1 if '=' in arg else 2
1336      continue
1337
1338    rerun_arg_list.append(arg)
1339    index += 1
1340
1341  failed_test_list = [str(t) for t in failed_tests]
1342  with open(test_filter_file, 'w') as fp:
1343    for t in failed_test_list:
1344      # Test result names can have # in them that don't match when applied as
1345      # a test name filter.
1346      fp.write('%s\n' % t.replace('#', '.'))
1347
1348  rerun_arg_list.append('--test-launcher-filter-file=%s' % test_filter_file)
1349  msg = """
1350    %d Test(s) failed.
1351    Rerun failed tests with copy and pastable command:
1352        %s
1353    """
1354  logging.critical(msg, len(failed_tests), shlex.join(rerun_arg_list))
1355
1356
1357def DumpThreadStacks(_signal, _frame):
1358  for thread in threading.enumerate():
1359    reraiser_thread.LogThreadStack(thread)
1360
1361
1362def main():
1363  signal.signal(signal.SIGUSR1, DumpThreadStacks)
1364
1365  parser = argparse.ArgumentParser()
1366  command_parsers = parser.add_subparsers(
1367      title='test types', dest='command')
1368
1369  subp = command_parsers.add_parser(
1370      'gtest',
1371      help='googletest-based C++ tests')
1372  AddCommonOptions(subp)
1373  AddDeviceOptions(subp)
1374  AddEmulatorOptions(subp)
1375  AddGTestOptions(subp)
1376  AddTracingOptions(subp)
1377  AddCommandLineOptions(subp)
1378
1379  subp = command_parsers.add_parser(
1380      'hostside',
1381      help='Webview CTS host-side tests')
1382  AddCommonOptions(subp)
1383  AddDeviceOptions(subp)
1384  AddEmulatorOptions(subp)
1385  AddHostsideTestOptions(subp)
1386
1387  subp = command_parsers.add_parser(
1388      'instrumentation',
1389      help='InstrumentationTestCase-based Java tests')
1390  AddCommonOptions(subp)
1391  AddDeviceOptions(subp)
1392  AddEmulatorOptions(subp)
1393  AddInstrumentationTestOptions(subp)
1394  AddSkiaGoldTestOptions(subp)
1395  AddTracingOptions(subp)
1396  AddCommandLineOptions(subp)
1397
1398  subp = command_parsers.add_parser(
1399      'junit',
1400      help='JUnit4-based Java tests')
1401  AddCommonOptions(subp)
1402  AddJUnitTestOptions(subp)
1403
1404  subp = command_parsers.add_parser(
1405      'linker',
1406      help='linker tests')
1407  AddCommonOptions(subp)
1408  AddDeviceOptions(subp)
1409  AddEmulatorOptions(subp)
1410  AddLinkerTestOptions(subp)
1411
1412  subp = command_parsers.add_parser(
1413      'monkey',
1414      help="tests based on Android's monkey command")
1415  AddCommonOptions(subp)
1416  AddDeviceOptions(subp)
1417  AddEmulatorOptions(subp)
1418  AddMonkeyTestOptions(subp)
1419
1420  subp = command_parsers.add_parser(
1421      'python',
1422      help='python tests based on unittest.TestCase')
1423  AddCommonOptions(subp)
1424  AddPythonTestOptions(subp)
1425
1426  args, unknown_args = parser.parse_known_args()
1427  if unknown_args:
1428    if hasattr(args, 'allow_unknown') and args.allow_unknown:
1429      args.command_line_flags = unknown_args
1430    else:
1431      parser.error('unrecognized arguments: %s' % ' '.join(unknown_args))
1432
1433  # --replace-system-package/--remove-system-package has the potential to cause
1434  # issues if --enable-concurrent-adb is set, so disallow that combination.
1435  concurrent_adb_enabled = (hasattr(args, 'enable_concurrent_adb')
1436                            and args.enable_concurrent_adb)
1437  replacing_system_packages = (hasattr(args, 'replace_system_package')
1438                               and args.replace_system_package)
1439  removing_system_packages = (hasattr(args, 'system_packages_to_remove')
1440                              and args.system_packages_to_remove)
1441  if (concurrent_adb_enabled
1442      and (replacing_system_packages or removing_system_packages)):
1443    parser.error('--enable-concurrent-adb cannot be used with either '
1444                 '--replace-system-package or --remove-system-package')
1445
1446  # --use-webview-provider has the potential to cause issues if
1447  # --enable-concurrent-adb is set, so disallow that combination
1448  if (hasattr(args, 'use_webview_provider') and
1449      hasattr(args, 'enable_concurrent_adb') and args.use_webview_provider and
1450      args.enable_concurrent_adb):
1451    parser.error('--use-webview-provider and --enable-concurrent-adb cannot '
1452                 'be used together')
1453
1454  if (getattr(args, 'coverage_on_the_fly', False)
1455      and not getattr(args, 'coverage_dir', '')):
1456    parser.error('--coverage-on-the-fly requires --coverage-dir')
1457
1458  if (hasattr(args, 'debug_socket') or
1459      (hasattr(args, 'wait_for_java_debugger') and
1460      args.wait_for_java_debugger)):
1461    args.num_retries = 0
1462
1463  # Result-sink may not exist in the environment if rdb stream is not enabled.
1464  result_sink_client = result_sink.TryInitClient()
1465
1466  try:
1467    return RunTestsCommand(args, result_sink_client)
1468  except base_error.BaseError as e:
1469    logging.exception('Error occurred.')
1470    if e.is_infra_error:
1471      return constants.INFRA_EXIT_CODE
1472    return constants.ERROR_EXIT_CODE
1473  except Exception:  # pylint: disable=W0703
1474    logging.exception('Unrecognized error occurred.')
1475    return constants.ERROR_EXIT_CODE
1476
1477
1478if __name__ == '__main__':
1479  exit_code = main()
1480  if exit_code == constants.INFRA_EXIT_CODE:
1481    # This exit code is returned in case of missing, unreachable,
1482    # or otherwise not fit for purpose test devices.
1483    # When this happens, the graceful cleanup triggered by sys.exit()
1484    # hangs indefinitely (on swarming - until it hits 20min timeout).
1485    # Skip cleanup (other than flushing output streams) and exit forcefully
1486    # to avoid the hang.
1487    sys.stdout.flush()
1488    sys.stderr.flush()
1489    os._exit(exit_code)  # pylint: disable=protected-access
1490  else:
1491    sys.exit(exit_code)
1492