Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(360)

Unified Diff: swarm_client/googletest/run_test_cases.py

Issue 69143004: Delete swarm_client. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/
Patch Set: Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « swarm_client/googletest/list_test_cases.py ('k') | swarm_client/googletest/shard_test_cases.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: swarm_client/googletest/run_test_cases.py
===================================================================
--- swarm_client/googletest/run_test_cases.py (revision 235167)
+++ swarm_client/googletest/run_test_cases.py (working copy)
@@ -1,1608 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2013 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Runs each test cases as a single shard, single process execution.
-
-Similar to sharding_supervisor.py but finer grained. It runs each test case
-individually instead of running per shard. Runs multiple instances in parallel.
-"""
-
-import datetime
-import fnmatch
-import json
-import logging
-import optparse
-import os
-import random
-import re
-import subprocess
-import sys
-import threading
-import time
-from xml.dom import minidom
-import xml.parsers.expat
-
-# Directory with this file.
-BASE_DIR = os.path.dirname(os.path.abspath(__file__))
-# Root of a repository.
-ROOT_DIR = os.path.dirname(BASE_DIR)
-# Name of the optional package with all dependencies.
-DEPENDENCIES_ZIP = os.path.join(BASE_DIR, 'run_isolated.zip')
-
-# When running in isolated environment, dependencies is in zipped package.
-if os.path.exists(DEPENDENCIES_ZIP):
- sys.path.insert(0, DEPENDENCIES_ZIP)
-else:
- # Otherwise it is in the root of the repository.
- if not ROOT_DIR in sys.path:
- sys.path.insert(0, ROOT_DIR)
-
-
-from utils import threading_utils
-from utils import tools
-
-
-# These are known to influence the way the output is generated.
-KNOWN_GTEST_ENV_VARS = [
- 'GTEST_ALSO_RUN_DISABLED_TESTS',
- 'GTEST_BREAK_ON_FAILURE',
- 'GTEST_CATCH_EXCEPTIONS',
- 'GTEST_COLOR',
- 'GTEST_FILTER',
- 'GTEST_OUTPUT',
- 'GTEST_PRINT_TIME',
- 'GTEST_RANDOM_SEED',
- 'GTEST_REPEAT',
- 'GTEST_SHARD_INDEX',
- 'GTEST_SHARD_STATUS_FILE',
- 'GTEST_SHUFFLE',
- 'GTEST_THROW_ON_FAILURE',
- 'GTEST_TOTAL_SHARDS',
-]
-
-# These needs to be poped out before running a test.
-GTEST_ENV_VARS_TO_REMOVE = [
- 'GTEST_ALSO_RUN_DISABLED_TESTS',
- 'GTEST_FILTER',
- 'GTEST_OUTPUT',
- 'GTEST_RANDOM_SEED',
- # TODO(maruel): Handle.
- 'GTEST_REPEAT',
- 'GTEST_SHARD_INDEX',
- # TODO(maruel): Handle.
- 'GTEST_SHUFFLE',
- 'GTEST_TOTAL_SHARDS',
-]
-
-
-RUN_PREFIX = '[ RUN ] '
-OK_PREFIX = '[ OK ] '
-FAILED_PREFIX = '[ FAILED ] '
-
-
-if subprocess.mswindows:
- import msvcrt # pylint: disable=F0401
- from ctypes import wintypes
- from ctypes import windll
-
- def ReadFile(handle, desired_bytes):
- """Calls kernel32.ReadFile()."""
- c_read = wintypes.DWORD()
- buff = wintypes.create_string_buffer(desired_bytes+1)
- windll.kernel32.ReadFile(
- handle, buff, desired_bytes, wintypes.byref(c_read), None)
- # NULL terminate it.
- buff[c_read.value] = '\x00'
- return wintypes.GetLastError(), buff.value
-
- def PeekNamedPipe(handle):
- """Calls kernel32.PeekNamedPipe(). Simplified version."""
- c_avail = wintypes.DWORD()
- c_message = wintypes.DWORD()
- success = windll.kernel32.PeekNamedPipe(
- handle, None, 0, None, wintypes.byref(c_avail),
- wintypes.byref(c_message))
- if not success:
- raise OSError(wintypes.GetLastError())
- return c_avail.value
-
- def recv_multi_impl(conns, maxsize, timeout):
- """Reads from the first available pipe.
-
- If timeout is None, it's blocking. If timeout is 0, it is not blocking.
- """
- # TODO(maruel): Use WaitForMultipleObjects(). Python creates anonymous pipes
- # for proc.stdout and proc.stderr but they are implemented as named pipes on
- # Windows. Since named pipes are not waitable object, they can't be passed
- # as-is to WFMO(). So this means N times CreateEvent(), N times ReadFile()
- # and finally WFMO(). This requires caching the events handles in the Popen
- # object and remembering the pending ReadFile() calls. This will require
- # some re-architecture.
- maxsize = max(maxsize or 16384, 1)
- if timeout:
- start = time.time()
- handles = [msvcrt.get_osfhandle(conn.fileno()) for conn in conns]
- while handles:
- for i, handle in enumerate(handles):
- try:
- avail = min(PeekNamedPipe(handle), maxsize)
- if avail:
- return i, ReadFile(handle, avail)[1]
- if (timeout and (time.time() - start) >= timeout) or timeout == 0:
- return None, None
- # Polling rocks.
- time.sleep(0.001)
- except OSError:
- handles.pop(i)
- break
- # Nothing to wait for.
- return None, None
-
-else:
- import fcntl # pylint: disable=F0401
- import select
-
- def recv_multi_impl(conns, maxsize, timeout):
- """Reads from the first available pipe.
-
- If timeout is None, it's blocking. If timeout is 0, it is not blocking.
- """
- try:
- r, _, _ = select.select(conns, [], [], timeout)
- except select.error:
- return None, None
- if not r:
- return None, None
-
- conn = r[0]
- # Temporarily make it non-blocking.
- flags = fcntl.fcntl(conn, fcntl.F_GETFL)
- if not conn.closed:
- # pylint: disable=E1101
- fcntl.fcntl(conn, fcntl.F_SETFL, flags | os.O_NONBLOCK)
- try:
- data = conn.read(max(maxsize or 16384, 1))
- return conns.index(conn), data
- finally:
- if not conn.closed:
- fcntl.fcntl(conn, fcntl.F_SETFL, flags)
-
-
-class Failure(Exception):
- pass
-
-
-class Popen(subprocess.Popen):
- """Adds timeout support on stdout and stderr.
-
- Inspired by
- http://code.activestate.com/recipes/440554-module-to-allow-asynchronous-subprocess-use-on-win/
- """
- def __init__(self, *args, **kwargs):
- self.start = time.time()
- self.end = None
- super(Popen, self).__init__(*args, **kwargs)
-
- def duration(self):
- """Duration of the child process.
-
- It is greater or equal to the actual time the child process ran. It can be
- significantly higher than the real value if neither .wait() nor .poll() was
- used.
- """
- return (self.end or time.time()) - self.start
-
- def wait(self):
- ret = super(Popen, self).wait()
- if not self.end:
- # communicate() uses wait() internally.
- self.end = time.time()
- return ret
-
- def poll(self):
- ret = super(Popen, self).poll()
- if ret is not None and not self.end:
- self.end = time.time()
- return ret
-
- def yield_any(self, timeout=None):
- """Yields output until the process terminates or is killed by a timeout.
-
- Yielded values are in the form (pipename, data).
-
- If timeout is None, it is blocking. If timeout is 0, it doesn't block. This
- is not generally useful to use timeout=0.
- """
- remaining = 0
- while self.poll() is None:
- if timeout:
- # While these float() calls seem redundant, they are to force
- # ResetableTimeout to "render" itself into a float. At each call, the
- # resulting value could be different, depending if a .reset() call
- # occurred.
- remaining = max(float(timeout) - self.duration(), 0.001)
- else:
- remaining = timeout
- t, data = self.recv_any(timeout=remaining)
- if data or timeout == 0:
- yield (t, data)
- if timeout and self.duration() >= float(timeout):
- break
- if self.poll() is None and timeout and self.duration() >= float(timeout):
- logging.debug('Kill %s %s', self.duration(), float(timeout))
- self.kill()
- self.wait()
- # Read all remaining output in the pipes.
- while True:
- t, data = self.recv_any()
- if not data:
- break
- yield (t, data)
-
- def recv_any(self, maxsize=None, timeout=None):
- """Reads from stderr and if empty, from stdout.
-
- If timeout is None, it is blocking. If timeout is 0, it doesn't block.
- """
- pipes = [
- x for x in ((self.stderr, 'stderr'), (self.stdout, 'stdout')) if x[0]
- ]
- if len(pipes) == 2 and self.stderr.fileno() == self.stdout.fileno():
- pipes.pop(0)
- if not pipes:
- return None, None
- conns, names = zip(*pipes)
- index, data = recv_multi_impl(conns, maxsize, timeout)
- if index is None:
- return index, data
- if not data:
- self._close(names[index])
- return None, None
- if self.universal_newlines:
- data = self._translate_newlines(data)
- return names[index], data
-
- def recv_out(self, maxsize=None, timeout=None):
- """Reads from stdout asynchronously."""
- return self._recv('stdout', maxsize, timeout)
-
- def recv_err(self, maxsize=None, timeout=None):
- """Reads from stderr asynchronously."""
- return self._recv('stderr', maxsize, timeout)
-
- def _close(self, which):
- getattr(self, which).close()
- setattr(self, which, None)
-
- def _recv(self, which, maxsize, timeout):
- conn = getattr(self, which)
- if conn is None:
- return None
- data = recv_multi_impl([conn], maxsize, timeout)
- if not data:
- return self._close(which)
- if self.universal_newlines:
- data = self._translate_newlines(data)
- return data
-
-
-def call_with_timeout(cmd, timeout, **kwargs):
- """Runs an executable with an optional timeout.
-
- timeout 0 or None disables the timeout.
- """
- proc = Popen(
- cmd,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- **kwargs)
- if timeout:
- out = ''
- err = ''
- for t, data in proc.yield_any(timeout):
- if t == 'stdout':
- out += data
- else:
- err += data
- else:
- # This code path is much faster.
- out, err = proc.communicate()
- return out, err, proc.returncode, proc.duration()
-
-
-def setup_gtest_env():
- """Copy the enviroment variables and setup for running a gtest."""
- env = os.environ.copy()
- for name in GTEST_ENV_VARS_TO_REMOVE:
- env.pop(name, None)
-
- # Forcibly enable color by default, if not already disabled.
- env.setdefault('GTEST_COLOR', 'on')
-
- return env
-
-
-def gtest_list_tests(cmd, cwd):
- """List all the test cases for a google test.
-
- See more info at http://code.google.com/p/googletest/.
- """
- cmd = cmd[:]
- cmd.append('--gtest_list_tests')
- env = setup_gtest_env()
- timeout = 0.
- try:
- out, err, returncode, _ = call_with_timeout(
- cmd,
- timeout,
- stderr=subprocess.PIPE,
- env=env,
- cwd=cwd)
- except OSError, e:
- raise Failure('Failed to run %s\ncwd=%s\n%s' % (' '.join(cmd), cwd, str(e)))
- if returncode:
- raise Failure(
- 'Failed to run %s\nstdout:\n%s\nstderr:\n%s' %
- (' '.join(cmd), out, err), returncode)
- # pylint: disable=E1103
- if err and not err.startswith('Xlib: extension "RANDR" missing on display '):
- logging.error('Unexpected spew in gtest_list_tests:\n%s\n%s', err, cmd)
- return out
-
-
-def filter_shards(tests, index, shards):
- """Filters the shards.
-
- Watch out about integer based arithmetics.
- """
- # The following code could be made more terse but I liked the extra clarity.
- assert 0 <= index < shards
- total = len(tests)
- quotient, remainder = divmod(total, shards)
- # 1 item of each remainder is distributed over the first 0:remainder shards.
- # For example, with total == 5, index == 1, shards == 3
- # min_bound == 2, max_bound == 4.
- min_bound = quotient * index + min(index, remainder)
- max_bound = quotient * (index + 1) + min(index + 1, remainder)
- return tests[min_bound:max_bound]
-
-
-def _starts_with(a, b, prefix):
- return a.startswith(prefix) or b.startswith(prefix)
-
-
-def is_valid_test_case(test, disabled):
- """Returns False on malformed or DISABLED_ test cases."""
- if not '.' in test:
- logging.error('Ignoring unknown test %s', test)
- return False
- fixture, case = test.split('.', 1)
- if not disabled and _starts_with(fixture, case, 'DISABLED_'):
- return False
- return True
-
-
-def filter_bad_tests(tests, disabled):
- """Filters out malformed or DISABLED_ test cases."""
- return [test for test in tests if is_valid_test_case(test, disabled)]
-
-
-def chromium_is_valid_test_case(test, disabled, fails, flaky, pre, manual):
- """Return False on chromium specific bad tests in addition to
- is_valid_test_case().
-
- FAILS_, FLAKY_, PRE_, MANUAL_ and other weird Chromium-specific test cases.
- """
- if not is_valid_test_case(test, disabled):
- return False
- fixture, case = test.split('.', 1)
- if not fails and _starts_with(fixture, case, 'FAILS_'):
- return False
- if not flaky and _starts_with(fixture, case, 'FLAKY_'):
- return False
- if not pre and _starts_with(fixture, case, 'PRE_'):
- return False
- if not manual and _starts_with(fixture, case, 'MANUAL_'):
- return False
- if test == 'InProcessBrowserTest.Empty':
- return False
- return True
-
-
-def chromium_filter_bad_tests(tests, disabled, fails, flaky, pre, manual):
- """Filters out chromium specific bad tests in addition to filter_bad_tests().
-
- Filters out FAILS_, FLAKY_, PRE_, MANUAL_ and other weird Chromium-specific
- test cases.
- """
- return [
- test for test in tests if chromium_is_valid_test_case(
- test, disabled, fails, flaky, pre, manual)
- ]
-
-
-def chromium_filter_pre_tests(test_case_results):
- """Filters out PRE_ test case results."""
- return (
- i for i in test_case_results if chromium_is_valid_test_case(
- i['test_case'],
- disabled=True,
- fails=True,
- flaky=True,
- pre=False,
- manual=True))
-
-
-def parse_gtest_cases(out, seed):
- """Returns the flattened list of test cases in the executable.
-
- The returned list is sorted so it is not dependent on the order of the linked
- objects. Then |seed| is applied to deterministically shuffle the list if
- |seed| is a positive value. The rationale is that the probability of two test
- cases stomping on each other when run simultaneously is high for test cases in
- the same fixture. By shuffling the tests, the probability of these badly
- written tests running simultaneously, let alone being in the same shard, is
- lower.
-
- Expected format is a concatenation of this:
- TestFixture1
- TestCase1
- TestCase2
- """
- tests = []
- fixture = None
- lines = out.splitlines()
- while lines:
- line = lines.pop(0)
- if not line:
- break
- if not line.startswith(' '):
- fixture = line
- else:
- case = line[2:]
- if case.startswith('YOU HAVE'):
- # It's a 'YOU HAVE foo bar' line. We're done.
- break
- assert ' ' not in case
- tests.append(fixture + case)
- tests = sorted(tests)
- if seed:
- # Sadly, python's random module doesn't permit local seeds.
- state = random.getstate()
- try:
- # This is totally deterministic.
- random.seed(seed)
- random.shuffle(tests)
- finally:
- random.setstate(state)
- return tests
-
-
-def list_test_cases(cmd, cwd, index, shards, seed, disabled):
- """Returns the list of test cases according to the specified criterias."""
- tests = parse_gtest_cases(gtest_list_tests(cmd, cwd), seed)
-
- # TODO(maruel): Splitting shards before filtering bad test cases could result
- # in inbalanced shards.
- if shards:
- tests = filter_shards(tests, index, shards)
- return filter_bad_tests(tests, disabled)
-
-
-def chromium_list_test_cases(
- cmd, cwd, index, shards, seed, disabled, fails, flaky, pre, manual):
- """Returns the list of test cases according to the specified criterias."""
- tests = list_test_cases(cmd, cwd, index, shards, seed, disabled)
- return chromium_filter_bad_tests(tests, disabled, fails, flaky, pre, manual)
-
-
-class RunSome(object):
- """Thread-safe object deciding if testing should continue."""
- def __init__(
- self, expected_count, retries, min_failures, max_failure_ratio,
- max_failures):
- """Determines if it is better to give up testing after an amount of failures
- and successes.
-
- Arguments:
- - expected_count is the expected number of elements to run.
- - retries is how many time a failing element can be retried. retries should
- be set to the maximum number of retries per failure. This permits
- dampening the curve to determine threshold where to stop.
- - min_failures is the minimal number of failures to tolerate, to put a lower
- limit when expected_count is small. This value is multiplied by the number
- of retries.
- - max_failure_ratio is the ratio of permitted failures, e.g. 0.1 to stop
- after 10% of failed test cases.
- - max_failures is the absolute maximum number of tolerated failures or None.
-
- For large values of expected_count, the number of tolerated failures will be
- at maximum "(expected_count * retries) * max_failure_ratio".
-
- For small values of expected_count, the number of tolerated failures will be
- at least "min_failures * retries".
- """
- assert 0 < expected_count
- assert 0 <= retries < 100
- assert 0 <= min_failures
- assert 0. < max_failure_ratio < 1.
- # Constants.
- self._expected_count = expected_count
- self._retries = retries
- self._min_failures = min_failures
- self._max_failure_ratio = max_failure_ratio
-
- self._min_failures_tolerated = self._min_failures * (self._retries + 1)
- # Pre-calculate the maximum number of allowable failures. Note that
- # _max_failures can be lower than _min_failures.
- self._max_failures_tolerated = round(
- (expected_count * (retries + 1)) * max_failure_ratio)
- if max_failures is not None:
- # Override the ratio if necessary.
- self._max_failures_tolerated = min(
- self._max_failures_tolerated, max_failures)
- self._min_failures_tolerated = min(
- self._min_failures_tolerated, max_failures)
-
- # Variables.
- self._lock = threading.Lock()
- self._passed = 0
- self._failures = 0
- self.stopped = False
-
- def should_stop(self):
- """Stops once a threshold was reached. This includes retries."""
- with self._lock:
- if self.stopped:
- return True
- # Accept at least the minimum number of failures.
- if self._failures <= self._min_failures_tolerated:
- return False
- if self._failures >= self._max_failures_tolerated:
- self.stopped = True
- return self.stopped
-
- def got_result(self, passed):
- with self._lock:
- if passed:
- self._passed += 1
- else:
- self._failures += 1
-
- def __str__(self):
- return '%s(%d, %d, %d, %.3f)' % (
- self.__class__.__name__,
- self._expected_count,
- self._retries,
- self._min_failures,
- self._max_failure_ratio)
-
-
-class RunAll(object):
- """Never fails."""
- stopped = False
-
- @staticmethod
- def should_stop():
- return False
-
- @staticmethod
- def got_result(_):
- pass
-
-
-def process_output(lines, test_cases):
- """Yield the data of each test cases.
-
- Expects the test cases to be run in the order of the list.
-
- Handles the following google-test behavior:
- - Test case crash causing a partial number of test cases to be run.
- - Invalid test case name so the test case wasn't run at all.
-
- This function automatically distribute the startup cost across each test case.
- """
- test_cases = test_cases[:]
- test_case = None
- test_case_data = None
- # Accumulates the junk between test cases.
- accumulation = ''
- eat_last_lines = False
-
- for line in lines:
- if eat_last_lines:
- test_case_data['output'] += line
- continue
-
- i = line.find(RUN_PREFIX)
- if i > 0 and test_case_data:
- # This may occur specifically in browser_tests, because the test case is
- # run in a child process. If the child process doesn't terminate its
- # output with a LF, it may cause the "[ RUN ]" line to be improperly
- # printed out in the middle of a line.
- test_case_data['output'] += line[:i]
- line = line[i:]
- i = 0
- if i >= 0:
- if test_case:
- # The previous test case had crashed. No idea about its duration
- test_case_data['returncode'] = 1
- test_case_data['duration'] = 0
- test_case_data['crashed'] = True
- yield test_case_data
-
- test_case = line[len(RUN_PREFIX):].strip().split(' ', 1)[0]
- # Accept the test case even if it was unexpected.
- if test_case in test_cases:
- test_cases.remove(test_case)
- else:
- logging.warning('Unexpected test case: %s', test_case)
- test_case_data = {
- 'test_case': test_case,
- 'returncode': None,
- 'duration': None,
- 'output': accumulation + line,
- }
- accumulation = ''
-
- elif test_case:
- test_case_data['output'] += line
- i = line.find(OK_PREFIX)
- if i >= 0:
- result = 0
- line = line[i + len(OK_PREFIX):]
- else:
- i = line.find(FAILED_PREFIX)
- if i >= 0:
- line = line[i + len(FAILED_PREFIX):]
- result = 1
- if i >= 0:
- # The test completed. It's important to make sure the test case name
- # match too, since it could be a fake output.
- if line.startswith(test_case):
- line = line[len(test_case):]
- match = re.search(r' \((\d+) ms\)', line)
- if match:
- test_case_data['duration'] = float(match.group(1)) / 1000.
- else:
- # Make sure duration is at least not None since the test case ran.
- test_case_data['duration'] = 0
- test_case_data['returncode'] = result
- if not test_cases:
- # Its the last test case. Eat all the remaining lines.
- eat_last_lines = True
- continue
- yield test_case_data
- test_case = None
- test_case_data = None
- else:
- accumulation += line
-
- # It's guaranteed here that the lines generator is exhausted.
- if eat_last_lines:
- yield test_case_data
- test_case = None
- test_case_data = None
-
- if test_case_data:
- # This means the last one likely crashed.
- test_case_data['crashed'] = True
- test_case_data['duration'] = 0
- test_case_data['returncode'] = 1
- test_case_data['output'] += accumulation
- yield test_case_data
-
- # If test_cases is not empty, these test cases were not run.
- for t in test_cases:
- yield {
- 'test_case': t,
- 'returncode': None,
- 'duration': None,
- 'output': None,
- }
-
-
-def convert_to_lines(generator):
- """Turn input coming from a generator into lines.
-
- It is Windows-friendly.
- """
- accumulator = ''
- for data in generator:
- items = (accumulator + data).splitlines(True)
- for item in items[:-1]:
- yield item
- if items[-1].endswith(('\r', '\n')):
- yield items[-1]
- accumulator = ''
- else:
- accumulator = items[-1]
- if accumulator:
- yield accumulator
-
-
-class ResetableTimeout(object):
- """A resetable timeout that acts as a float.
-
- At each reset, the timeout is increased so that it still has the equivalent
- of the original timeout value, but according to 'now' at the time of the
- reset.
- """
- def __init__(self, timeout):
- assert timeout >= 0.
- self.timeout = float(timeout)
- self.last_reset = time.time()
-
- def reset(self):
- """Respendish the timeout."""
- now = time.time()
- self.timeout += max(0., now - self.last_reset)
- self.last_reset = now
- return now
-
- @staticmethod
- def __bool__():
- return True
-
- def __float__(self):
- """To be used as a timeout value for a function call."""
- return self.timeout
-
-
-class GoogleTestRunner(object):
- """Immutable settings to run many test cases in a loop."""
- def __init__(
- self,
- cmd,
- cwd_dir,
- timeout,
- progress,
- retries,
- decider,
- verbose,
- add_task,
- add_serial_task,
- filter_results):
- """Defines how to run a googletest executable.
-
- Arguments:
- - cmd: command line to start with.
- - cwd_dir: directory to start the app in.
- - timeout: timeout while waiting for output.
- - progress: object to present the user with status updates.
- - retries: number of allowed retries. For example if 2, the test case will
- be tried 3 times in total.
- - decider: object to decide if the run should be stopped early.
- - verbose: inconditionally prints output.
- - add_task: function to add the task back when failing, for retry.
- - add_serial_task: function to add the task back when failing too often so
- it should be run serially.
- - filter_results: optional function to filter undesired extraneous test case
- run without our consent.
- """
- self.cmd = cmd[:]
- self.cwd_dir = cwd_dir
- self.timeout = timeout
- self.progress = progress
- self.retries = retries
- self.decider = decider
- self.verbose = verbose
- self.add_task = add_task
- self.add_serial_task = add_serial_task
- self.filter_results = filter_results or (lambda x: x)
- # It is important to remove the shard environment variables since it could
- # conflict with --gtest_filter.
- self.env = setup_gtest_env()
-
- def map(self, priority, test_cases, try_count):
- """Traces a single test case and returns its output.
-
- try_count is 0 based, the original try is 0.
- """
- if self.decider.should_stop():
- raise StopIteration()
- cmd = self.cmd + ['--gtest_filter=%s' % ':'.join(test_cases)]
- if '--gtest_print_time' not in cmd:
- cmd.append('--gtest_print_time')
- proc = Popen(
- cmd,
- cwd=self.cwd_dir,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- env=self.env)
-
- # Use an intelligent timeout that can be reset. The idea is simple, the
- # timeout is set to the value of the timeout for a single test case.
- # Everytime a test case is parsed, the timeout is reset to its full value.
- # proc.yield_any() uses float() to extract the instantaneous value of
- # 'timeout'.
- timeout = ResetableTimeout(self.timeout)
-
- # Create a pipeline of generators.
- gen_lines = convert_to_lines(data for _, data in proc.yield_any(timeout))
- # It needs to be valid utf-8 otherwise it can't be stored.
- # TODO(maruel): Be more intelligent than decoding to ascii.
- gen_lines_utf8 = (
- line.decode('ascii', 'ignore').encode('utf-8') for line in gen_lines)
- gen_test_cases = process_output(gen_lines_utf8, test_cases)
- last_timestamp = proc.start
- got_failure_at_least_once = False
- results = []
- for i in self.filter_results(gen_test_cases):
- results.append(i)
- now = timeout.reset()
- test_case_has_passed = (i['returncode'] == 0)
- if i['duration'] is None:
- assert not test_case_has_passed
- # Do not notify self.decider, because an early crash in a large cluster
- # could cause the test to quit early.
- else:
- i['duration'] = max(i['duration'], now - last_timestamp)
- # A new test_case completed.
- self.decider.got_result(test_case_has_passed)
-
- need_to_retry = not test_case_has_passed and try_count < self.retries
- got_failure_at_least_once |= not test_case_has_passed
- last_timestamp = now
-
- # Create the line to print out.
- if i['duration'] is not None:
- duration = '(%.2fs)' % i['duration']
- else:
- duration = '<unknown>'
- if try_count:
- line = '%s %s - retry #%d' % (i['test_case'], duration, try_count)
- else:
- line = '%s %s' % (i['test_case'], duration)
- if self.verbose or not test_case_has_passed or try_count > 0:
- # Print output in one of three cases:
- # - --verbose was specified.
- # - The test failed.
- # - The wasn't the first attempt (this is needed so the test parser can
- # detect that a test has been successfully retried).
- if i['output']:
- line += '\n' + i['output']
- self.progress.update_item(line, index=1, size=int(need_to_retry))
-
- if need_to_retry:
- priority = self._retry(priority, i['test_case'], try_count)
-
- # Delay yielding when only one test case is running, in case of a
- # crash-after-succeed.
- if len(test_cases) > 1:
- yield i
-
- if proc.returncode and not got_failure_at_least_once:
- if results and len(test_cases) == 1:
- # Crash after pass.
- results[-1]['returncode'] = proc.returncode
-
- if try_count < self.retries:
- # This is tricky, one of the test case failed but each did print that
- # they succeeded! Retry them *all* individually.
- if not self.verbose and not try_count:
- # Print all the output as one shot when not verbose to be sure the
- # potential stack trace is printed.
- output = ''.join(i['output'] for i in results)
- self.progress.update_item(output, raw=True)
- for i in results:
- priority = self._retry(priority, i['test_case'], try_count)
- self.progress.update_item('', size=1)
-
- # Only yield once the process completed when there is only one test case as
- # a safety precaution.
- if results and len(test_cases) == 1:
- yield results[-1]
-
- def _retry(self, priority, test_case, try_count):
- """Adds back the same task again only if relevant.
-
- It may add it either at lower (e.g. higher value) priority or at the end of
- the serially executed list.
- """
- if try_count + 1 < self.retries:
- # The test failed and needs to be retried normally.
- # Leave a buffer of ~40 test cases before retrying.
- priority += 40
- self.add_task(priority, self.map, priority, [test_case], try_count + 1)
- else:
- # This test only has one retry left, so the final retry should be
- # done serially.
- self.add_serial_task(
- priority, self.map, priority, [test_case], try_count + 1)
- return priority
-
-
-class ChromiumGoogleTestRunner(GoogleTestRunner):
- def __init__(self, *args, **kwargs):
- super(ChromiumGoogleTestRunner, self).__init__(
- *args, filter_results=chromium_filter_pre_tests, **kwargs)
-
-
-def get_test_cases(
- cmd, cwd, whitelist, blacklist, index, shards, seed, disabled, fails, flaky,
- manual):
- """Returns the filtered list of test cases.
-
- This is done synchronously.
- """
- try:
- # List all the test cases if a whitelist is used.
- tests = chromium_list_test_cases(
- cmd,
- cwd,
- index=index,
- shards=shards,
- seed=seed,
- disabled=disabled,
- fails=fails,
- flaky=flaky,
- pre=False,
- manual=manual)
- except Failure, e:
- print('Failed to list test cases. This means the test executable is so '
- 'broken that it failed to start and enumerate its test cases.\n\n'
- 'An example of a potential problem causing this is a Windows API '
- 'function not available on this version of Windows.')
- print(e.args[0])
- return None
-
- if shards:
- # This is necessary for Swarm log parsing.
- print('Note: This is test shard %d of %d.' % (index+1, shards))
-
- # Filters the test cases with the two lists.
- if blacklist:
- tests = [
- t for t in tests if not any(fnmatch.fnmatch(t, s) for s in blacklist)
- ]
- if whitelist:
- tests = [
- t for t in tests if any(fnmatch.fnmatch(t, s) for s in whitelist)
- ]
- logging.info('Found %d test cases in %s' % (len(tests), ' '.join(cmd)))
- return tests
-
-
-def dump_results_as_json(result_file, results):
- """Write the results out to a json file."""
- base_path = os.path.dirname(result_file)
- if base_path and not os.path.isdir(base_path):
- os.makedirs(base_path)
- with open(result_file, 'wb') as f:
- json.dump(results, f, sort_keys=True, indent=2)
-
-
-def dump_results_as_xml(gtest_output, results, now):
- """Write the results out to a xml file in google-test compatible format."""
- # TODO(maruel): Print all the test cases, including the ones that weren't run
- # and the retries.
- test_suites = {}
- for test_case, result in results['test_cases'].iteritems():
- suite, case = test_case.split('.', 1)
- test_suites.setdefault(suite, {})[case] = result[0]
-
- with open(gtest_output, 'wb') as f:
- # Sanity warning: hand-rolling XML. What could possibly go wrong?
- f.write('<?xml version="1.0" ?>\n')
- # TODO(maruel): File the fields nobody reads anyway.
- # disabled="%d" errors="%d" failures="%d"
- f.write(
- ('<testsuites name="AllTests" tests="%d" time="%f" timestamp="%s">\n')
- % (results['expected'], results['duration'], now))
- for suite_name, suite in test_suites.iteritems():
- # TODO(maruel): disabled="0" errors="0" failures="0" time="0"
- f.write('<testsuite name="%s" tests="%d">\n' % (suite_name, len(suite)))
- for case_name, case in suite.iteritems():
- if case['returncode'] == 0:
- f.write(
- ' <testcase classname="%s" name="%s" status="run" time="%f"/>\n' %
- (suite_name, case_name, case['duration']))
- else:
- f.write(
- ' <testcase classname="%s" name="%s" status="run" time="%f">\n' %
- (suite_name, case_name, (case['duration'] or 0)))
- # While at it, hand-roll CDATA escaping too.
- output = ']]><![CDATA['.join((case['output'] or '').split(']]>'))
- # TODO(maruel): message="" type=""
- f.write('<failure><![CDATA[%s]]></failure></testcase>\n' % output)
- f.write('</testsuite>\n')
- f.write('</testsuites>')
-
-
-def append_gtest_output_to_xml(final_xml, filepath):
- """Combines the shard xml file with the final xml file."""
- try:
- with open(filepath) as shard_xml_file:
- shard_xml = minidom.parse(shard_xml_file)
- except xml.parsers.expat.ExpatError as e:
- logging.error('Failed to parse %s: %s', filepath, e)
- return final_xml
- except IOError as e:
- logging.error('Failed to load %s: %s', filepath, e)
- # If the shard crashed, gtest will not have generated an xml file.
- return final_xml
-
- if not final_xml:
- # Out final xml is empty, let's prepopulate it with the first one we see.
- return shard_xml
-
- final_testsuites_by_name = dict(
- (suite.getAttribute('name'), suite)
- for suite in final_xml.documentElement.getElementsByTagName('testsuite'))
-
- for testcase in shard_xml.documentElement.getElementsByTagName('testcase'):
- # Don't bother updating the final xml if there is no data.
- status = testcase.getAttribute('status')
- if status == 'notrun':
- continue
-
- name = testcase.getAttribute('name')
- # Look in our final xml to see if it's there.
- to_remove = []
- final_testsuite = final_testsuites_by_name[
- testcase.getAttribute('classname')]
- for final_testcase in final_testsuite.getElementsByTagName('testcase'):
- # Trim all the notrun testcase instances to add the new instance there.
- # This is to make sure it works properly in case of a testcase being run
- # multiple times.
- if (final_testcase.getAttribute('name') == name and
- final_testcase.getAttribute('status') == 'notrun'):
- to_remove.append(final_testcase)
-
- for item in to_remove:
- final_testsuite.removeChild(item)
- # Reparent the XML node.
- final_testsuite.appendChild(testcase)
-
- return final_xml
-
-
-def running_serial_warning():
- return ['*****************************************************',
- '*****************************************************',
- '*****************************************************',
- 'WARNING: The remaining tests are going to be retried',
- 'serially. All tests should be isolated and be able to pass',
- 'regardless of what else is running.',
- 'If you see a test that can only pass serially, that test is',
- 'probably broken and should be fixed.',
- '*****************************************************',
- '*****************************************************',
- '*****************************************************']
-
-
-def gen_gtest_output_dir(cwd, gtest_output):
- """Converts gtest_output to an actual path that can be used in parallel.
-
- Returns a 'corrected' gtest_output value.
- """
- if not gtest_output.startswith('xml'):
- raise Failure('Can\'t parse --gtest_output=%s' % gtest_output)
- # Figure out the result filepath in case we can't parse it, it'd be
- # annoying to error out *after* running the tests.
- if gtest_output == 'xml':
- gtest_output = os.path.join(cwd, 'test_detail.xml')
- else:
- match = re.match(r'xml\:(.+)', gtest_output)
- if not match:
- raise Failure('Can\'t parse --gtest_output=%s' % gtest_output)
- # If match.group(1) is an absolute path, os.path.join() will do the right
- # thing.
- if match.group(1).endswith((os.path.sep, '/')):
- gtest_output = os.path.join(cwd, match.group(1), 'test_detail.xml')
- else:
- gtest_output = os.path.join(cwd, match.group(1))
-
- base_path = os.path.dirname(gtest_output)
- if base_path and not os.path.isdir(base_path):
- os.makedirs(base_path)
-
- # Emulate google-test' automatic increasing index number.
- while True:
- try:
- # Creates a file exclusively.
- os.close(os.open(gtest_output, os.O_CREAT|os.O_EXCL|os.O_RDWR, 0666))
- # It worked, we are done.
- return gtest_output
- except OSError:
- pass
- logging.debug('%s existed', gtest_output)
- base, ext = os.path.splitext(gtest_output)
- match = re.match(r'^(.+?_)(\d+)$', base)
- if match:
- base = match.group(1) + str(int(match.group(2)) + 1)
- else:
- base = base + '_0'
- gtest_output = base + ext
-
-
-def calc_cluster_default(num_test_cases, jobs):
- """Calculates a desired number for clusters depending on the number of test
- cases and parallel jobs.
- """
- if not num_test_cases:
- return 0
- chunks = 6 * jobs
- if chunks >= num_test_cases:
- # Too many chunks, use 1~5 test case per thread. Not enough to start
- # chunking.
- value = num_test_cases / jobs
- else:
- # Use chunks that are spread across threads.
- value = (num_test_cases + chunks - 1) / chunks
- # Limit to 10 test cases per cluster.
- return min(10, max(1, value))
-
-
-def run_test_cases(
- cmd, cwd, test_cases, jobs, timeout, clusters, retries, run_all,
- max_failures, no_cr, gtest_output, result_file, verbose):
- """Runs test cases in parallel.
-
- Arguments:
- - cmd: command to run.
- - cwd: working directory.
- - test_cases: list of preprocessed test cases to run.
- - jobs: number of parallel execution threads to do.
- - timeout: individual test case timeout. Modulated when used with
- clustering.
- - clusters: number of test cases to lump together in a single execution. 0
- means the default automatic value which depends on len(test_cases) and
- jobs. Capped to len(test_cases) / jobs.
- - retries: number of times a test case can be retried.
- - run_all: If true, do not early return even if all test cases fail.
- - max_failures is the absolute maximum number of tolerated failures or None.
- - no_cr: makes output friendly to piped logs.
- - gtest_output: saves results as xml.
- - result_file: saves results as json.
- - verbose: print more details.
-
- It may run a subset of the test cases if too many test cases failed, as
- determined with max_failures, retries and run_all.
- """
- assert 0 <= retries <= 100000
- if not test_cases:
- return 0
- if run_all:
- decider = RunAll()
- else:
- # If 10% of test cases fail, just too bad.
- decider = RunSome(len(test_cases), retries, 2, 0.1, max_failures)
-
- if not clusters:
- clusters = calc_cluster_default(len(test_cases), jobs)
- else:
- # Limit the value.
- clusters = max(min(clusters, len(test_cases) / jobs), 1)
-
- logging.debug('%d test cases with clusters of %d', len(test_cases), clusters)
-
- if gtest_output:
- gtest_output = gen_gtest_output_dir(cwd, gtest_output)
- columns = [('index', 0), ('size', len(test_cases))]
- progress = threading_utils.Progress(columns)
- progress.use_cr_only = not no_cr
- serial_tasks = threading_utils.QueueWithProgress(progress)
-
- def add_serial_task(priority, func, *args, **kwargs):
- """Adds a serial task, to be executed later."""
- assert isinstance(priority, int)
- assert callable(func)
- serial_tasks.put((priority, func, args, kwargs))
-
- with threading_utils.ThreadPoolWithProgress(
- progress, jobs, jobs, len(test_cases)) as pool:
- runner = ChromiumGoogleTestRunner(
- cmd,
- cwd,
- timeout,
- progress,
- retries,
- decider,
- verbose,
- pool.add_task,
- add_serial_task)
- function = runner.map
- # Cluster the test cases right away.
- for i in xrange((len(test_cases) + clusters - 1) / clusters):
- cluster = test_cases[i*clusters : (i+1)*clusters]
- pool.add_task(i, function, i, cluster, 0)
- results = pool.join()
-
- # Retry any failed tests serially.
- if not serial_tasks.empty():
- progress.update_item('\n'.join(running_serial_warning()), raw=True)
- progress.print_update()
-
- while not serial_tasks.empty():
- _priority, func, args, kwargs = serial_tasks.get()
- for out in func(*args, **kwargs):
- results.append(out)
- serial_tasks.task_done()
- progress.print_update()
-
- # Call join since that is a standard call once a queue has been emptied.
- serial_tasks.join()
-
- duration = time.time() - pool.tasks.progress.start
-
- cleaned = {}
- for i in results:
- cleaned.setdefault(i['test_case'], []).append(i)
- results = cleaned
-
- # Total time taken to run each test case.
- test_case_duration = dict(
- (test_case, sum((i.get('duration') or 0) for i in item))
- for test_case, item in results.iteritems())
-
- # Classify the results
- success = []
- flaky = []
- fail = []
- nb_runs = 0
- for test_case in sorted(results):
- items = results[test_case]
- nb_runs += len(items)
- if not any(i['returncode'] == 0 for i in items):
- fail.append(test_case)
- elif len(items) > 1 and any(i['returncode'] == 0 for i in items):
- flaky.append(test_case)
- elif len(items) == 1 and items[0]['returncode'] == 0:
- success.append(test_case)
- else:
- # The test never ran.
- assert False, items
- missing = sorted(set(test_cases) - set(success) - set(flaky) - set(fail))
-
- saved = {
- 'test_cases': results,
- 'expected': len(test_cases),
- 'success': success,
- 'flaky': flaky,
- 'fail': fail,
- 'missing': missing,
- 'duration': duration,
- }
- if result_file:
- dump_results_as_json(result_file, saved)
- if gtest_output:
- dump_results_as_xml(gtest_output, saved, datetime.datetime.now())
- sys.stdout.write('\n')
- if not results:
- return 1
-
- if flaky:
- print('Flaky tests:')
- for test_case in sorted(flaky):
- items = results[test_case]
- print(' %s (tried %d times)' % (test_case, len(items)))
-
- if fail:
- print('Failed tests:')
- for test_case in sorted(fail):
- print(' %s' % test_case)
-
- if not decider.should_stop() and missing:
- print('Missing tests:')
- for test_case in sorted(missing):
- print(' %s' % test_case)
-
- print('Summary:')
- if decider.should_stop():
- print(' ** STOPPED EARLY due to high failure rate **')
- output = [
- ('Success', success),
- ('Flaky', flaky),
- ('Fail', fail),
- ]
- if missing:
- output.append(('Missing', missing))
- total_expected = len(test_cases)
- for name, items in output:
- number = len(items)
- print(
- ' %7s: %4d %6.2f%% %7.2fs' % (
- name,
- number,
- number * 100. / total_expected,
- sum(test_case_duration.get(item, 0) for item in items)))
- print(' %.2fs Done running %d tests with %d executions. %.2f test/s' % (
- duration,
- len(results),
- nb_runs,
- nb_runs / duration if duration else 0))
- return int(bool(fail) or decider.stopped or bool(missing))
-
-
-class OptionParserWithLogging(tools.OptionParserWithLogging):
- def __init__(self, **kwargs):
- tools.OptionParserWithLogging.__init__(
- self,
- log_file=os.environ.get('RUN_TEST_CASES_LOG_FILE', ''),
- **kwargs)
-
-
-class OptionParserWithTestSharding(OptionParserWithLogging):
- """Adds automatic handling of test sharding"""
- def __init__(self, **kwargs):
- OptionParserWithLogging.__init__(self, **kwargs)
-
- def as_digit(variable, default):
- return int(variable) if variable.isdigit() else default
-
- group = optparse.OptionGroup(self, 'Which shard to select')
- group.add_option(
- '-I', '--index',
- type='int',
- default=as_digit(os.environ.get('GTEST_SHARD_INDEX', ''), None),
- help='Shard index to select')
- group.add_option(
- '-S', '--shards',
- type='int',
- default=as_digit(os.environ.get('GTEST_TOTAL_SHARDS', ''), None),
- help='Total number of shards to calculate from the --index to select')
- self.add_option_group(group)
-
- def parse_args(self, *args, **kwargs):
- options, args = OptionParserWithLogging.parse_args(self, *args, **kwargs)
- if bool(options.shards) != bool(options.index is not None):
- self.error('Use both --index X --shards Y or none of them')
- return options, args
-
-
-class OptionParserWithTestShardingAndFiltering(OptionParserWithTestSharding):
- """Adds automatic handling of test sharding and filtering."""
- def __init__(self, *args, **kwargs):
- OptionParserWithTestSharding.__init__(self, *args, **kwargs)
-
- group = optparse.OptionGroup(self, 'Which test cases to select')
- group.add_option(
- '-w', '--whitelist',
- default=[],
- action='append',
- help='filter to apply to test cases to run, wildcard-style, defaults '
- 'to all test')
- group.add_option(
- '-b', '--blacklist',
- default=[],
- action='append',
- help='filter to apply to test cases to skip, wildcard-style, defaults '
- 'to no test')
- group.add_option(
- '-T', '--test-case-file',
- help='File containing the exact list of test cases to run')
- group.add_option(
- '--gtest_filter',
- default=os.environ.get('GTEST_FILTER', ''),
- help='Select test cases like google-test does, separated with ":"')
- group.add_option(
- '--seed',
- type='int',
- default=os.environ.get('GTEST_RANDOM_SEED', '1'),
- help='Deterministically shuffle the test list if non-0. default: '
- '%default')
- group.add_option(
- '-d', '--disabled',
- action='store_true',
- default=int(os.environ.get('GTEST_ALSO_RUN_DISABLED_TESTS', '0')),
- help='Include DISABLED_ tests')
- group.add_option(
- '--gtest_also_run_disabled_tests',
- action='store_true',
- dest='disabled',
- help='same as --disabled')
- self.add_option_group(group)
-
- group = optparse.OptionGroup(
- self, 'Which test cases to select; chromium-specific')
- group.add_option(
- '-f', '--fails',
- action='store_true',
- help='Include FAILS_ tests')
- group.add_option(
- '-F', '--flaky',
- action='store_true',
- help='Include FLAKY_ tests')
- group.add_option(
- '-m', '--manual',
- action='store_true',
- help='Include MANUAL_ tests')
- group.add_option(
- '--run-manual',
- action='store_true',
- dest='manual',
- help='same as --manual')
- self.add_option_group(group)
-
- def parse_args(self, *args, **kwargs):
- options, args = OptionParserWithTestSharding.parse_args(
- self, *args, **kwargs)
-
- if options.gtest_filter:
- # Override any other option.
- # Based on UnitTestOptions::FilterMatchesTest() in
- # http://code.google.com/p/googletest/source/browse/#svn%2Ftrunk%2Fsrc
- if '-' in options.gtest_filter:
- options.whitelist, options.blacklist = options.gtest_filter.split('-',
- 1)
- else:
- options.whitelist = options.gtest_filter
- options.blacklist = ''
- options.whitelist = [i for i in options.whitelist.split(':') if i]
- options.blacklist = [i for i in options.blacklist.split(':') if i]
-
- return options, args
-
- @staticmethod
- def process_gtest_options(cmd, cwd, options):
- """Grabs the test cases."""
- if options.test_case_file:
- with open(options.test_case_file, 'r') as f:
- # Do not shuffle or alter the file in any way in that case except to
- # strip whitespaces.
- return [l for l in (l.strip() for l in f) if l]
- else:
- return get_test_cases(
- cmd,
- cwd,
- options.whitelist,
- options.blacklist,
- options.index,
- options.shards,
- options.seed,
- options.disabled,
- options.fails,
- options.flaky,
- options.manual)
-
-
-class OptionParserTestCases(OptionParserWithTestShardingAndFiltering):
- def __init__(self, *args, **kwargs):
- OptionParserWithTestShardingAndFiltering.__init__(self, *args, **kwargs)
- self.add_option(
- '-j', '--jobs',
- type='int',
- default=threading_utils.num_processors(),
- help='Number of parallel jobs; default=%default')
- self.add_option(
- '--use-less-jobs',
- action='store_const',
- const=max(1, threading_utils.num_processors() / 2),
- dest='jobs',
- help='Starts less parallel jobs than the default, used to help reduce'
- 'contention between threads if all the tests are very CPU heavy.')
- self.add_option(
- '-t', '--timeout',
- type='int',
- default=75,
- help='Timeout for a single test case, in seconds default:%default')
- self.add_option(
- '--clusters',
- type='int',
- help='Number of test cases to cluster together, clamped to '
- 'len(test_cases) / jobs; the default is automatic')
-
-
-def process_args(argv):
- parser = OptionParserTestCases(
- usage='%prog <options> [gtest]',
- verbose=int(os.environ.get('ISOLATE_DEBUG', 0)))
- parser.add_option(
- '--run-all',
- action='store_true',
- help='Do not fail early when a large number of test cases fail')
- parser.add_option(
- '--max-failures', type='int',
- help='Limit the number of failures before aborting')
- parser.add_option(
- '--retries', type='int', default=2,
- help='Number of times each test case should be retried in case of '
- 'failure.')
- parser.add_option(
- '--no-dump',
- action='store_true',
- help='do not generate a .run_test_cases file')
- parser.add_option(
- '--no-cr',
- action='store_true',
- help='Use LF instead of CR for status progress')
- parser.add_option(
- '--result',
- help='Override the default name of the generated .run_test_cases file')
-
- group = optparse.OptionGroup(parser, 'google-test compability flags')
- group.add_option(
- '--gtest_list_tests',
- action='store_true',
- help='List all the test cases unformatted. Keeps compatibility with the '
- 'executable itself.')
- group.add_option(
- '--gtest_output',
- default=os.environ.get('GTEST_OUTPUT', ''),
- help='XML output to generate')
- parser.add_option_group(group)
-
- options, args = parser.parse_args(argv)
-
- if not args:
- parser.error(
- 'Please provide the executable line to run, if you need fancy things '
- 'like xvfb, start this script from *inside* xvfb, it\'ll be much faster'
- '.')
-
- if options.run_all and options.max_failures is not None:
- parser.error('Use only one of --run-all or --max-failures')
- return parser, options, tools.fix_python_path(args)
-
-
-def main(argv):
- """CLI frontend to validate arguments."""
- tools.disable_buffering()
- parser, options, cmd = process_args(argv)
-
- if options.gtest_list_tests:
- # Special case, return the output of the target unmodified.
- return subprocess.call(cmd + ['--gtest_list_tests'])
-
- cwd = os.getcwd()
- test_cases = parser.process_gtest_options(cmd, cwd, options)
-
- if options.no_dump:
- result_file = None
- else:
- result_file = options.result
- if not result_file:
- if cmd[0] == sys.executable:
- result_file = '%s.run_test_cases' % cmd[1]
- else:
- result_file = '%s.run_test_cases' % cmd[0]
-
- if not test_cases:
- # The fact of not running any test is considered a failure. This is to
- # prevent silent failure with an invalid --gtest_filter argument or because
- # of a misconfigured unit test.
- if test_cases is not None:
- print('Found no test to run')
- if result_file:
- dump_results_as_json(result_file, {
- 'test_cases': [],
- 'expected': 0,
- 'success': [],
- 'flaky': [],
- 'fail': [],
- 'missing': [],
- 'duration': 0,
- })
- return 1
-
- if options.disabled:
- cmd.append('--gtest_also_run_disabled_tests')
- if options.manual:
- cmd.append('--run-manual')
-
- try:
- return run_test_cases(
- cmd,
- cwd,
- test_cases,
- options.jobs,
- options.timeout,
- options.clusters,
- options.retries,
- options.run_all,
- options.max_failures,
- options.no_cr,
- options.gtest_output,
- result_file,
- options.verbose)
- except Failure as e:
- print >> sys.stderr, e.args[0]
- return 1
-
-
-if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
« no previous file with comments | « swarm_client/googletest/list_test_cases.py ('k') | swarm_client/googletest/shard_test_cases.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698