Skip to content

Commit

Permalink
Add --shards support to trace_test_cases.py.
Browse files Browse the repository at this point in the history
This permits much faster experimentation for very large test executable by using
--index 0 --shards 1000.

R=cmp@chromium.org
NOTRY=true
BUG=
TEST=


Review URL: https://chromiumcodereview.appspot.com/10553023

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@142892 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
maruel@chromium.org committed Jun 19, 2012
1 parent 78180d9 commit 9fb2958
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 18 deletions.
70 changes: 58 additions & 12 deletions tools/isolate/list_test_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,42 @@ def gtest_list_tests(executable):
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 parse_gtest_cases(out, disabled=False, fails=False, flaky=False):
def filter_bad_tests(tests, disabled=False, fails=False, flaky=False):
out = []
for test in tests:
fixture, case = test.split('.', 1)
if not disabled and _starts_with(fixture, case, 'DISABLED_'):
continue
if not fails and _starts_with(fixture, case, 'FAILS_'):
continue
if not flaky and _starts_with(fixture, case, 'FLAKY_'):
continue
out.append(test)
return out


def parse_gtest_cases(out):
"""Expected format is a concatenation of this:
TestFixture1
TestCase1
Expand All @@ -68,18 +99,18 @@ def parse_gtest_cases(out, disabled=False, fails=False, flaky=False):
# It's a 'YOU HAVE foo bar' line. We're done.
break
assert ' ' not in case

if not disabled and _starts_with(fixture, case, 'DISABLED_'):
continue
if not fails and _starts_with(fixture, case, 'FAILS_'):
continue
if not flaky and _starts_with(fixture, case, 'FLAKY_'):
continue

tests.append(fixture + case)
return tests


def list_test_cases(executable, index, shards, disabled, fails, flaky):
"""Retuns the list of test cases according to the specified criterias."""
tests = parse_gtest_cases(gtest_list_tests(executable))
if shards:
tests = filter_shards(tests, index, shards)
return filter_bad_tests(tests, disabled, fails, flaky)


def main():
"""CLI frontend to validate arguments."""
parser = optparse.OptionParser(
Expand All @@ -96,14 +127,29 @@ def main():
'-F', '--flaky',
action='store_true',
help='Include FLAKY_ tests')
parser.add_option(
'-i', '--index',
type='int',
help='Shard index to run')
parser.add_option(
'-s', '--shards',
type='int',
help='Total number of shards to calculate from the --index to run')
options, args = parser.parse_args()
if len(args) != 1:
parser.error('Please provide the executable to run')

if bool(options.shards) != bool(options.index is not None):
parser.error('Use both --index X --shards Y or none of them')

try:
out = gtest_list_tests(args[0])
tests = parse_gtest_cases(
out, options.disabled, options.fails, options.flaky)
tests = list_test_cases(
args[0],
options.index,
options.shards,
options.disabled,
options.fails,
options.flaky)
for test in tests:
print test
except Failure, e:
Expand Down
38 changes: 38 additions & 0 deletions tools/isolate/list_test_cases_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env python
# Copyright (c) 2012 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.

import unittest

import list_test_cases


class ListTestCasesTest(unittest.TestCase):
def test_shards(self):
test_cases = (
(range(10), 10, 0, 1),

([0, 1], 5, 0, 3),
([2, 3], 5, 1, 3),
([4 ], 5, 2, 3),

([0], 5, 0, 7),
([1], 5, 1, 7),
([2], 5, 2, 7),
([3], 5, 3, 7),
([4], 5, 4, 7),
([ ], 5, 5, 7),
([ ], 5, 6, 7),

([0, 1], 4, 0, 2),
([2, 3], 4, 1, 2),
)
for expected, range_length, index, shards in test_cases:
result = list_test_cases.filter_shards(range(range_length), index, shards)
self.assertEquals(
expected, result, (result, expected, range_length, index, shards))


if __name__ == '__main__':
unittest.main()
25 changes: 19 additions & 6 deletions tools/isolate/trace_test_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,18 @@ def map(self, test_case):
return out


def get_test_cases(executable, whitelist, blacklist):
def get_test_cases(executable, whitelist, blacklist, index, shards):
"""Returns the filtered list of test cases.
This is done synchronously.
"""
try:
out = list_test_cases.gtest_list_tests(executable)
tests = list_test_cases.list_test_cases(
executable, index, shards, False, False, False)
except list_test_cases.Failure, e:
print e.args[0]
return None

tests = list_test_cases.parse_gtest_cases(out)

# Filters the test cases with the two lists.
if blacklist:
tests = [
Expand All @@ -96,9 +95,9 @@ def get_test_cases(executable, whitelist, blacklist):

def trace_test_cases(
executable, root_dir, cwd_dir, variables, whitelist, blacklist, jobs,
output_file):
index, shards, output_file):
"""Traces test cases one by one."""
test_cases = get_test_cases(executable, whitelist, blacklist)
test_cases = get_test_cases(executable, whitelist, blacklist, index, shards)
if not test_cases:
return

Expand Down Expand Up @@ -237,6 +236,14 @@ def main():
'-j', '--jobs',
type='int',
help='number of parallel jobs')
parser.add_option(
'-i', '--index',
type='int',
help='Shard index to run')
parser.add_option(
'-s', '--shards',
type='int',
help='Total number of shards to calculate from the --index to run')
parser.add_option(
'-t', '--timeout',
default=120,
Expand All @@ -259,6 +266,10 @@ def main():
'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 bool(options.shards) != bool(options.index is not None):
parser.error('Use both --index X --shards Y or none of them')

executable = args[0]
if not os.path.isabs(executable):
executable = os.path.abspath(os.path.join(options.root_dir, executable))
Expand All @@ -273,6 +284,8 @@ def main():
options.blacklist,
options.jobs,
# TODO(maruel): options.timeout,
options.index,
options.shards,
options.out)


Expand Down

0 comments on commit 9fb2958

Please sign in to comment.