base_runner.py 24.7 KB
Newer Older
1 2 3 4 5
# Copyright 2017 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.


6
from collections import OrderedDict
7
import json
8
import multiprocessing
9
import optparse
10
import os
11
import shlex
12 13 14 15 16 17 18 19 20 21
import sys


# Add testrunner to the path.
sys.path.insert(
  0,
  os.path.dirname(
    os.path.dirname(os.path.abspath(__file__))))


22 23 24
from testrunner.local import testsuite
from testrunner.local import utils
from testrunner.test_config import TestConfig
25
from testrunner.testproc import progress
26
from testrunner.testproc.rerun import RerunProc
27
from testrunner.testproc.shard import ShardProc
28
from testrunner.testproc.sigproc import SignalProc
29
from testrunner.testproc.timeout import TimeoutProc
30

31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72

BASE_DIR = (
    os.path.dirname(
      os.path.dirname(
        os.path.dirname(
          os.path.abspath(__file__)))))

DEFAULT_OUT_GN = 'out.gn'

# Map of test name synonyms to lists of test suites. Should be ordered by
# expected runtimes (suites with slow test cases first). These groups are
# invoked in separate steps on the bots.
TEST_MAP = {
  # This needs to stay in sync with test/bot_default.isolate.
  "bot_default": [
    "debugger",
    "mjsunit",
    "cctest",
    "wasm-spec-tests",
    "inspector",
    "webkit",
    "mkgrokdump",
    "fuzzer",
    "message",
    "preparser",
    "intl",
    "unittests",
  ],
  # This needs to stay in sync with test/default.isolate.
  "default": [
    "debugger",
    "mjsunit",
    "cctest",
    "wasm-spec-tests",
    "inspector",
    "mkgrokdump",
    "fuzzer",
    "message",
    "preparser",
    "intl",
    "unittests",
  ],
73 74
  # This needs to stay in sync with test/d8_default.isolate.
  "d8_default": [
75 76
    # TODO(machenbach): uncomment after infra side lands.
    #"debugger",
77 78
    "mjsunit",
    "webkit",
79 80 81
    #"message",
    #"preparser",
    #"intl",
82
  ],
83 84 85 86 87 88 89 90 91 92 93 94 95 96
  # This needs to stay in sync with test/optimize_for_size.isolate.
  "optimize_for_size": [
    "debugger",
    "mjsunit",
    "cctest",
    "inspector",
    "webkit",
    "intl",
  ],
  "unittests": [
    "unittests",
  ],
}

97 98 99 100 101 102 103 104 105 106
# Double the timeout for these:
SLOW_ARCHS = ["arm",
              "mips",
              "mipsel",
              "mips64",
              "mips64el",
              "s390",
              "s390x",
              "arm64"]

107

108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
class ModeConfig(object):
  def __init__(self, flags, timeout_scalefactor, status_mode, execution_mode):
    self.flags = flags
    self.timeout_scalefactor = timeout_scalefactor
    self.status_mode = status_mode
    self.execution_mode = execution_mode


DEBUG_FLAGS = ["--nohard-abort", "--enable-slow-asserts", "--verify-heap"]
RELEASE_FLAGS = ["--nohard-abort"]
MODES = {
  "debug": ModeConfig(
    flags=DEBUG_FLAGS,
    timeout_scalefactor=4,
    status_mode="debug",
    execution_mode="debug",
  ),
  "optdebug": ModeConfig(
    flags=DEBUG_FLAGS,
    timeout_scalefactor=4,
    status_mode="debug",
    execution_mode="debug",
  ),
  "release": ModeConfig(
    flags=RELEASE_FLAGS,
    timeout_scalefactor=1,
    status_mode="release",
    execution_mode="release",
  ),
  # Normal trybot release configuration. There, dchecks are always on which
  # implies debug is set. Hence, the status file needs to assume debug-like
  # behavior/timeouts.
  "tryrelease": ModeConfig(
    flags=RELEASE_FLAGS,
    timeout_scalefactor=1,
    status_mode="debug",
    execution_mode="release",
  ),
  # This mode requires v8 to be compiled with dchecks and slow dchecks.
  "slowrelease": ModeConfig(
    flags=RELEASE_FLAGS + ["--enable-slow-asserts"],
    timeout_scalefactor=2,
    status_mode="debug",
    execution_mode="release",
  ),
}

155 156 157 158 159 160
PROGRESS_INDICATORS = {
  'verbose': progress.VerboseProgressIndicator,
  'dots': progress.DotsProgressIndicator,
  'color': progress.ColorProgressIndicator,
  'mono': progress.MonochromeProgressIndicator,
}
161

162 163 164 165
class TestRunnerError(Exception):
  pass


166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
class BuildConfig(object):
  def __init__(self, build_config):
    # In V8 land, GN's x86 is called ia32.
    if build_config['v8_target_cpu'] == 'x86':
      self.arch = 'ia32'
    else:
      self.arch = build_config['v8_target_cpu']

    self.is_debug = build_config['is_debug']
    self.asan = build_config['is_asan']
    self.cfi_vptr = build_config['is_cfi']
    self.dcheck_always_on = build_config['dcheck_always_on']
    self.gcov_coverage = build_config['is_gcov_coverage']
    self.msan = build_config['is_msan']
    self.no_i18n = not build_config['v8_enable_i18n_support']
    self.no_snap = not build_config['v8_use_snapshot']
    self.predictable = build_config['v8_enable_verify_predictable']
    self.tsan = build_config['is_tsan']
    self.ubsan_vptr = build_config['is_ubsan_vptr']
185 186 187 188
    # Export only for MIPS target
    if self.arch in ['mips', 'mipsel', 'mips64', 'mips64el']:
      self.mips_arch_variant = build_config['mips_arch_variant']
      self.mips_use_msa = build_config['mips_use_msa']
189

190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
  def __str__(self):
    detected_options = []

    if self.asan:
      detected_options.append('asan')
    if self.cfi_vptr:
      detected_options.append('cfi_vptr')
    if self.dcheck_always_on:
      detected_options.append('dcheck_always_on')
    if self.gcov_coverage:
      detected_options.append('gcov_coverage')
    if self.msan:
      detected_options.append('msan')
    if self.no_i18n:
      detected_options.append('no_i18n')
    if self.no_snap:
      detected_options.append('no_snap')
    if self.predictable:
      detected_options.append('predictable')
    if self.tsan:
      detected_options.append('tsan')
    if self.ubsan_vptr:
      detected_options.append('ubsan_vptr')

    return '\n'.join(detected_options)

216

217
class BaseTestRunner(object):
218 219
  def __init__(self, basedir=None):
    self.basedir = basedir or BASE_DIR
220
    self.outdir = None
221 222 223
    self.build_config = None
    self.mode_name = None
    self.mode_options = None
224

225 226 227
  def execute(self, sys_args=None):
    if sys_args is None:  # pragma: no cover
      sys_args = sys.argv[1:]
228
    try:
229
      parser = self._create_parser()
230
      options, args = self._parse_args(parser, sys_args)
231 232 233 234
      if options.swarming:
        # Swarming doesn't print how isolated commands are called. Lets make
        # this less cryptic by printing it ourselves.
        print ' '.join(sys.argv)
235 236 237 238 239 240 241 242 243 244

      self._load_build_config(options)

      try:
        self._process_default_options(options)
        self._process_options(options)
      except TestRunnerError:
        parser.print_help()
        raise

245
      args = self._parse_test_args(args)
246
      suites = self._get_suites(args, options)
247
      self._prepare_suites(suites, options)
248

249
      self._setup_env()
250 251 252 253 254

      print(">>> Running tests for %s.%s" % (self.build_config.arch,
                                            self.mode_name))
      tests = [t for s in suites for t in s.tests]
      return self._do_execute(tests, args, options)
255
    except TestRunnerError:
256
      return utils.EXIT_CODE_INTERNAL_ERROR
257
    except KeyboardInterrupt:
258
      return utils.EXIT_CODE_INTERRUPTED
259

260
  def _create_parser(self):
261 262 263 264 265
    parser = optparse.OptionParser()
    parser.usage = '%prog [options] [tests]'
    parser.description = """TESTS: %s""" % (TEST_MAP["default"])
    self._add_parser_default_options(parser)
    self._add_parser_options(parser)
266
    return parser
267 268 269 270 271 272 273

  def _add_parser_default_options(self, parser):
    parser.add_option("--gn", help="Scan out.gn for the last built"
                      " configuration",
                      default=False, action="store_true")
    parser.add_option("--outdir", help="Base directory with compile output",
                      default="out")
274
    parser.add_option("--buildbot", help="DEPRECATED!",
275 276
                      default=False, action="store_true")
    parser.add_option("--arch",
277
                      help="The architecture to run tests for")
278
    parser.add_option("-m", "--mode",
279 280
                      help="The test mode in which to run (uppercase for ninja"
                      " and buildbot builds): %s" % MODES.keys())
281 282
    parser.add_option("--shell-dir", help="DEPRECATED! Executables from build "
                      "directory will be used")
283 284
    parser.add_option("--test-root", help="Root directory of the test suites",
                      default=os.path.join(self.basedir, 'test'))
285 286
    parser.add_option("--total-timeout-sec", default=0, type="int",
                      help="How long should fuzzer run")
287 288 289 290 291
    parser.add_option("--swarming", default=False, action="store_true",
                      help="Indicates running test driver on swarming.")

    parser.add_option("-j", help="The number of parallel tasks to run",
                      default=0, type=int)
292 293 294 295 296 297

    # Shard
    parser.add_option("--shard-count", default=1, type=int,
                      help="Split tests into this number of shards")
    parser.add_option("--shard-run", default=1, type=int,
                      help="Run this shard from the split up tests.")
298

299 300 301 302 303 304 305 306 307 308 309 310
    # Progress
    parser.add_option("-p", "--progress",
                      choices=PROGRESS_INDICATORS.keys(), default="mono",
                      help="The style of progress indicator (verbose, dots, "
                           "color, mono)")
    parser.add_option("--json-test-results",
                      help="Path to a file for storing json results.")
    parser.add_option("--junitout", help="File name of the JUnit output")
    parser.add_option("--junittestsuite", default="v8tests",
                      help="The testsuite name in the JUnit output file")

    # Rerun
311 312 313 314 315
    parser.add_option("--rerun-failures-count", default=0, type=int,
                      help="Number of times to rerun each failing test case. "
                           "Very slow tests will be rerun only once.")
    parser.add_option("--rerun-failures-max", default=100, type=int,
                      help="Maximum number of failing test cases to rerun")
316

317
    # Test config
318 319 320 321 322 323 324 325 326
    parser.add_option("--command-prefix", default="",
                      help="Prepended to each shell command used to run a test")
    parser.add_option("--extra-flags", action="append", default=[],
                      help="Additional flags to pass to each test command")
    parser.add_option("--isolates", action="store_true", default=False,
                      help="Whether to test isolates")
    parser.add_option("--no-harness", "--noharness",
                      default=False, action="store_true",
                      help="Run without test harness of a given suite")
327 328
    parser.add_option("--random-seed", default=0, type=int,
                      help="Default seed for initializing random generator")
329 330 331 332 333
    parser.add_option("-t", "--timeout", default=60, type=int,
                      help="Timeout for single test in seconds")
    parser.add_option("-v", "--verbose", default=False, action="store_true",
                      help="Verbose output")

334 335
    # TODO(machenbach): Temporary options for rolling out new test runner
    # features.
336 337 338 339 340 341
    parser.add_option("--mastername", default='',
                      help="Mastername property from infrastructure. Not "
                           "setting this option indicates manual usage.")
    parser.add_option("--buildername", default='',
                      help="Buildername property from infrastructure. Not "
                           "setting this option indicates manual usage.")
342

343 344 345
  def _add_parser_options(self, parser):
    pass

346 347
  def _parse_args(self, parser, sys_args):
    options, args = parser.parse_args(sys_args)
348

349
    if any(map(lambda v: v and ',' in v,
350
                [options.arch, options.mode])):  # pragma: no cover
351 352
      print 'Multiple arch/mode are deprecated'
      raise TestRunnerError()
353

354
    return options, args
355

356 357 358
  def _load_build_config(self, options):
    for outdir in self._possible_outdirs(options):
      try:
359
        self.build_config = self._do_load_build_config(outdir, options.verbose)
360 361
      except TestRunnerError:
        pass
362

363
    if not self.build_config:  # pragma: no cover
364 365 366 367
      print 'Failed to load build config'
      raise TestRunnerError

    print 'Build found: %s' % self.outdir
368 369 370 371 372 373 374 375 376
    if str(self.build_config):
      print '>>> Autodetected:'
      print self.build_config

  # Returns possible build paths in order:
  # gn
  # outdir
  # outdir/arch.mode
  # Each path is provided in two versions: <path> and <path>/mode for buildbot.
377
  def _possible_outdirs(self, options):
378 379 380 381 382 383 384 385 386
    def outdirs():
      if options.gn:
        yield self._get_gn_outdir()
        return

      yield options.outdir
      if options.arch and options.mode:
        yield os.path.join(options.outdir,
                          '%s.%s' % (options.arch, options.mode))
387

388
    for outdir in outdirs():
389
      yield os.path.join(self.basedir, outdir)
390 391 392

      # buildbot option
      if options.mode:
393
        yield os.path.join(self.basedir, outdir, options.mode)
394

395
  def _get_gn_outdir(self):
396
    gn_out_dir = os.path.join(self.basedir, DEFAULT_OUT_GN)
397 398 399 400 401 402 403 404 405 406 407 408 409
    latest_timestamp = -1
    latest_config = None
    for gn_config in os.listdir(gn_out_dir):
      gn_config_dir = os.path.join(gn_out_dir, gn_config)
      if not os.path.isdir(gn_config_dir):
        continue
      if os.path.getmtime(gn_config_dir) > latest_timestamp:
        latest_timestamp = os.path.getmtime(gn_config_dir)
        latest_config = gn_config
    if latest_config:
      print(">>> Latest GN build found: %s" % latest_config)
      return os.path.join(DEFAULT_OUT_GN, latest_config)

410 411
  def _do_load_build_config(self, outdir, verbose=False):
    build_config_path = os.path.join(outdir, "v8_build_config.json")
412
    if not os.path.exists(build_config_path):
413 414
      if verbose:
        print("Didn't find build config: %s" % build_config_path)
415
      raise TestRunnerError()
416 417 418

    with open(build_config_path) as f:
      try:
419
        build_config_json = json.load(f)
420
      except Exception:  # pragma: no cover
421 422 423 424
        print("%s exists but contains invalid json. Is your build up-to-date?"
              % build_config_path)
        raise TestRunnerError()

425
    # In auto-detect mode the outdir is always where we found the build config.
426 427
    # This ensures that we'll also take the build products from there.
    self.outdir = os.path.dirname(build_config_path)
428

429
    return BuildConfig(build_config_json)
430

431 432 433
  def _process_default_options(self, options):
    # We don't use the mode for more path-magic.
    # Therefore transform the buildbot mode here to fix build_config value.
434
    if options.mode:
435 436
      options.mode = self._buildbot_to_v8_mode(options.mode)

437 438
    build_config_mode = 'debug' if self.build_config.is_debug else 'release'
    if options.mode:
439
      if options.mode not in MODES:  # pragma: no cover
440
        print '%s mode is invalid' % options.mode
441
        raise TestRunnerError()
442 443 444 445 446 447
      if MODES[options.mode].execution_mode != build_config_mode:
        print ('execution mode (%s) for %s is inconsistent with build config '
               '(%s)' % (
            MODES[options.mode].execution_mode,
            options.mode,
            build_config_mode))
448
        raise TestRunnerError()
449

450 451 452 453 454 455 456 457 458 459 460
      self.mode_name = options.mode
    else:
      self.mode_name = build_config_mode

    self.mode_options = MODES[self.mode_name]

    if options.arch and options.arch != self.build_config.arch:
      print('--arch value (%s) inconsistent with build config (%s).' % (
        options.arch, self.build_config.arch))
      raise TestRunnerError()

461
    if options.shell_dir:  # pragma: no cover
462 463
      print('Warning: --shell-dir is deprecated. Searching for executables in '
            'build directory (%s) instead.' % self.outdir)
464

465 466 467
    if options.j == 0:
      options.j = multiprocessing.cpu_count()

468 469 470
    options.command_prefix = shlex.split(options.command_prefix)
    options.extra_flags = sum(map(shlex.split, options.extra_flags), [])

471 472 473 474 475 476 477 478 479 480 481
  def _buildbot_to_v8_mode(self, config):
    """Convert buildbot build configs to configs understood by the v8 runner.

    V8 configs are always lower case and without the additional _x64 suffix
    for 64 bit builds on windows with ninja.
    """
    mode = config[:-4] if config.endswith('_x64') else config
    return mode.lower()

  def _process_options(self, options):
    pass
482

483 484
  def _setup_env(self):
    # Use the v8 root as cwd as some test cases use "load" with relative paths.
485
    os.chdir(self.basedir)
486 487 488 489 490 491 492

    # Many tests assume an English interface.
    os.environ['LANG'] = 'en_US.UTF-8'

    symbolizer_option = self._get_external_symbolizer_option()

    if self.build_config.asan:
493 494 495 496 497
      asan_options = [
          symbolizer_option,
          'allow_user_segv_handler=1',
          'allocator_may_return_null=1',
      ]
498 499 500
      if not utils.GuessOS() in ['macos', 'windows']:
        # LSAN is not available on mac and windows.
        asan_options.append('detect_leaks=1')
501 502
      else:
        asan_options.append('detect_leaks=0')
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523
      os.environ['ASAN_OPTIONS'] = ":".join(asan_options)

    if self.build_config.cfi_vptr:
      os.environ['UBSAN_OPTIONS'] = ":".join([
        'print_stacktrace=1',
        'print_summary=1',
        'symbolize=1',
        symbolizer_option,
      ])

    if self.build_config.ubsan_vptr:
      os.environ['UBSAN_OPTIONS'] = ":".join([
        'print_stacktrace=1',
        symbolizer_option,
      ])

    if self.build_config.msan:
      os.environ['MSAN_OPTIONS'] = symbolizer_option

    if self.build_config.tsan:
      suppressions_file = os.path.join(
524
          self.basedir,
525 526 527 528 529 530 531 532 533 534 535 536 537 538
          'tools',
          'sanitizers',
          'tsan_suppressions.txt')
      os.environ['TSAN_OPTIONS'] = " ".join([
        symbolizer_option,
        'suppressions=%s' % suppressions_file,
        'exit_code=0',
        'report_thread_leaks=0',
        'history_size=7',
        'report_destroy_locked=0',
      ])

  def _get_external_symbolizer_option(self):
    external_symbolizer_path = os.path.join(
539
        self.basedir,
540 541 542 543 544 545 546 547 548 549 550 551 552
        'third_party',
        'llvm-build',
        'Release+Asserts',
        'bin',
        'llvm-symbolizer',
    )

    if utils.IsWindows():
      # Quote, because sanitizers might confuse colon as option separator.
      external_symbolizer_path = '"%s.exe"' % external_symbolizer_path

    return 'external_symbolizer_path=%s' % external_symbolizer_path

553
  def _parse_test_args(self, args):
554 555 556 557 558 559 560 561
    if not args:
      args = self._get_default_suite_names()

    # Expand arguments with grouped tests. The args should reflect the list
    # of suites as otherwise filters would break.
    def expand_test_group(name):
      return TEST_MAP.get(name, [name])

562
    return reduce(list.__add__, map(expand_test_group, args), [])
563

564
  def _get_suites(self, args, options):
565
    names = self._args_to_suite_names(args, options.test_root)
566
    return self._load_suites(names, options)
567

568
  def _args_to_suite_names(self, args, test_root):
569
    # Use default tests if no test configuration was provided at the cmd line.
570
    all_names = set(utils.GetSuitePaths(test_root))
571 572 573 574 575 576
    args_names = OrderedDict([(arg.split('/')[0], None) for arg in args]) # set
    return [name for name in args_names if name in all_names]

  def _get_default_suite_names(self):
    return []

577 578
  def _load_suites(self, names, options):
    test_config = self._create_test_config(options)
579
    def load_suite(name):
580
      if options.verbose:
581 582
        print '>>> Loading test suite: %s' % name
      return testsuite.TestSuite.LoadTestSuite(
583
          os.path.join(options.test_root, name),
584
          test_config)
585
    return map(load_suite, names)
586

587 588 589 590 591
  def _prepare_suites(self, suites, options):
    self._load_status_files(suites, options)
    for s in suites:
      s.ReadTestCases()

592 593 594 595 596 597 598 599 600 601 602 603
  def _load_status_files(self, suites, options):
    # simd_mips is true if SIMD is fully supported on MIPS
    variables = self._get_statusfile_variables(options)
    for s in suites:
      s.ReadStatusFile(variables)

  def _get_statusfile_variables(self, options):
    simd_mips = (
      self.build_config.arch in ['mipsel', 'mips', 'mips64', 'mips64el'] and
      self.build_config.mips_arch_variant == "r6" and
      self.build_config.mips_use_msa)

604 605 606 607
    mips_arch_variant = (
      self.build_config.arch in ['mipsel', 'mips', 'mips64', 'mips64el'] and
      self.build_config.mips_arch_variant)

608 609 610 611 612 613 614 615 616
    # TODO(all): Combine "simulator" and "simulator_run".
    # TODO(machenbach): In GN we can derive simulator run from
    # target_arch != v8_target_arch in the dumped build config.
    return {
      "arch": self.build_config.arch,
      "asan": self.build_config.asan,
      "byteorder": sys.byteorder,
      "dcheck_always_on": self.build_config.dcheck_always_on,
      "deopt_fuzzer": False,
617
      "endurance_fuzzer": False,
618 619 620 621
      "gc_fuzzer": False,
      "gc_stress": False,
      "gcov_coverage": self.build_config.gcov_coverage,
      "isolates": options.isolates,
622
      "mips_arch_variant": mips_arch_variant,
623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
      "mode": self.mode_options.status_mode,
      "msan": self.build_config.msan,
      "no_harness": options.no_harness,
      "no_i18n": self.build_config.no_i18n,
      "no_snap": self.build_config.no_snap,
      "novfp3": False,
      "predictable": self.build_config.predictable,
      "simd_mips": simd_mips,
      "simulator": utils.UseSimulator(self.build_config.arch),
      "simulator_run": False,
      "system": utils.GuessOS(),
      "tsan": self.build_config.tsan,
      "ubsan_vptr": self.build_config.ubsan_vptr,
    }

638
  def _create_test_config(self, options):
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
    timeout = options.timeout * self._timeout_scalefactor(options)
    return TestConfig(
        command_prefix=options.command_prefix,
        extra_flags=options.extra_flags,
        isolates=options.isolates,
        mode_flags=self.mode_options.flags,
        no_harness=options.no_harness,
        noi18n=self.build_config.no_i18n,
        random_seed=options.random_seed,
        shell_dir=self.outdir,
        timeout=timeout,
        verbose=options.verbose,
    )

  def _timeout_scalefactor(self, options):
    factor = self.mode_options.timeout_scalefactor

    # Simulators are slow, therefore allow a longer timeout.
    if self.build_config.arch in SLOW_ARCHS:
      factor *= 2

    # Predictable mode is slower.
    if self.build_config.predictable:
      factor *= 2

    return factor
665

666
  # TODO(majeski): remove options & args parameters
667
  def _do_execute(self, suites, args, options):
668
    raise NotImplementedError()
669

670 671 672 673 674 675
  def _prepare_procs(self, procs):
    procs = filter(None, procs)
    for i in xrange(0, len(procs) - 1):
      procs[i].connect_to(procs[i + 1])
    procs[0].setup()

676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717
  def _create_shard_proc(self, options):
    myid, count = self._get_shard_info(options)
    if count == 1:
      return None
    return ShardProc(myid - 1, count)

  def _get_shard_info(self, options):
    """
    Returns pair:
      (id of the current shard [1; number of shards], number of shards)
    """
    # Read gtest shard configuration from environment (e.g. set by swarming).
    # If none is present, use values passed on the command line.
    shard_count = int(
      os.environ.get('GTEST_TOTAL_SHARDS', options.shard_count))
    shard_run = os.environ.get('GTEST_SHARD_INDEX')
    if shard_run is not None:
      # The v8 shard_run starts at 1, while GTEST_SHARD_INDEX starts at 0.
      shard_run = int(shard_run) + 1
    else:
      shard_run = options.shard_run

    if options.shard_count > 1:
      # Log if a value was passed on the cmd line and it differs from the
      # environment variables.
      if options.shard_count != shard_count:  # pragma: no cover
        print("shard_count from cmd line differs from environment variable "
              "GTEST_TOTAL_SHARDS")
      if (options.shard_run > 1 and
          options.shard_run != shard_run):  # pragma: no cover
        print("shard_run from cmd line differs from environment variable "
              "GTEST_SHARD_INDEX")

    if shard_run < 1 or shard_run > shard_count:
      # TODO(machenbach): Turn this into an assert. If that's wrong on the
      # bots, printing will be quite useless. Or refactor this code to make
      # sure we get a return code != 0 after testing if we got here.
      print "shard-run not a valid number, should be in [1:shard-count]"
      print "defaulting back to running all tests"
      return 1, 1

    return shard_run, shard_count
718

719 720 721 722 723 724 725 726 727 728 729 730
  def _create_progress_indicators(self, options):
    procs = [PROGRESS_INDICATORS[options.progress]()]
    if options.junitout:
      procs.append(progress.JUnitTestProgressIndicator(options.junitout,
                                                       options.junittestsuite))
    if options.json_test_results:
      procs.append(progress.JsonTestProgressIndicator(
        options.json_test_results,
        self.build_config.arch,
        self.mode_options.execution_mode))
    return procs

731 732 733 734
  def _create_timeout_proc(self, options):
    if not options.total_timeout_sec:
      return None
    return TimeoutProc(options.total_timeout_sec)
735 736 737

  def _create_signal_proc(self):
    return SignalProc()
738 739 740 741 742 743

  def _create_rerun_proc(self, options):
    if not options.rerun_failures_count:
      return None
    return RerunProc(options.rerun_failures_count,
                     options.rerun_failures_max)