332 lines
13 KiB
Python
Executable File
332 lines
13 KiB
Python
Executable File
#!/usr/bin/env python2
|
|
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import argparse
|
|
import copy
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
import pbr.version
|
|
from subunit import run as subunit_run
|
|
from testtools import run as testtools_run
|
|
|
|
|
|
__version__ = pbr.version.VersionInfo('os_testr').version_string()
|
|
|
|
|
|
def get_parser(args):
|
|
parser = argparse.ArgumentParser(
|
|
description='Tool to run openstack tests')
|
|
parser.add_argument('--version', action='version',
|
|
version='%s' % __version__)
|
|
list_files = parser.add_mutually_exclusive_group()
|
|
list_files.add_argument('--blacklist_file', '-b',
|
|
help='Path to a blacklist file, this file '
|
|
'contains a separate regex exclude on each '
|
|
'newline')
|
|
list_files.add_argument('--whitelist_file', '-w',
|
|
help='Path to a whitelist file, this file '
|
|
'contains a separate regex on each newline.')
|
|
group = parser.add_mutually_exclusive_group()
|
|
group.add_argument('--regex', '-r',
|
|
help='A normal testr selection regex. If a blacklist '
|
|
'file is specified, the regex will be appended '
|
|
'to the end of the generated regex from that '
|
|
'file.')
|
|
group.add_argument('--path', metavar='FILE_OR_DIRECTORY',
|
|
help='A file name or directory of tests to run.')
|
|
group.add_argument('--no-discover', '-n', metavar='TEST_ID',
|
|
help="Takes in a single test to bypasses test "
|
|
"discover and just execute the test specified. "
|
|
"A file name may be used in place of a test "
|
|
"name.")
|
|
pretty = parser.add_mutually_exclusive_group()
|
|
pretty.add_argument('--pretty', '-p', dest='pretty', action='store_true',
|
|
help='Print pretty output from subunit-trace. This is '
|
|
'mutually exclusive with --subunit')
|
|
pretty.add_argument('--no-pretty', dest='pretty', action='store_false',
|
|
help='Disable the pretty output with subunit-trace')
|
|
parser.add_argument('--subunit', '-s', action='store_true',
|
|
help='output the raw subunit v2 from the test run '
|
|
'this is mutually exclusive with --pretty')
|
|
parser.add_argument('--list', '-l', action='store_true',
|
|
help='List all the tests which will be run.')
|
|
parser.add_argument('--color', action='store_true',
|
|
help='Use color in the pretty output')
|
|
slowest = parser.add_mutually_exclusive_group()
|
|
slowest.add_argument('--slowest', dest='slowest', action='store_true',
|
|
help="after the test run print the slowest tests")
|
|
slowest.add_argument('--no-slowest', dest='slowest', action='store_false',
|
|
help="after the test run don't print the slowest "
|
|
"tests")
|
|
parser.add_argument('--pdb', metavar='TEST_ID',
|
|
help='Run a single test that has pdb traces added')
|
|
parallel = parser.add_mutually_exclusive_group()
|
|
parallel.add_argument('--parallel', dest='parallel', action='store_true',
|
|
help='Run tests in parallel (this is the default)')
|
|
parallel.add_argument('--serial', dest='parallel', action='store_false',
|
|
help='Run tests serially')
|
|
parser.add_argument('--concurrency', '-c', type=int, metavar='WORKERS',
|
|
help='The number of workers to use when running in '
|
|
'parallel. By default this is the number of cpus')
|
|
parser.add_argument('--until-failure', action='store_true',
|
|
help='Run the tests in a loop until a failure is '
|
|
'encountered. Running with subunit or pretty'
|
|
'output enable will force the loop to run tests'
|
|
'serially')
|
|
parser.add_argument('--print-exclude', action='store_true',
|
|
help='If an exclude file is used this option will '
|
|
'prints the comment from the same line and all '
|
|
'skipped tests before the test run')
|
|
parser.set_defaults(pretty=True, slowest=True, parallel=True)
|
|
return parser.parse_known_args(args)
|
|
|
|
|
|
def _get_test_list(regex, env=None):
|
|
env = env or copy.deepcopy(os.environ)
|
|
proc = subprocess.Popen(['testr', 'list-tests', regex], env=env,
|
|
stdout=subprocess.PIPE)
|
|
out = proc.communicate()[0]
|
|
raw_test_list = out.split('\n')
|
|
bad = False
|
|
test_list = []
|
|
exclude_list = ['OS_', 'CAPTURE', 'TEST_TIMEOUT', 'PYTHON',
|
|
'subunit.run discover']
|
|
for line in raw_test_list:
|
|
for exclude in exclude_list:
|
|
if exclude in line:
|
|
bad = True
|
|
break
|
|
elif not line:
|
|
bad = True
|
|
break
|
|
if not bad:
|
|
test_list.append(line)
|
|
bad = False
|
|
return test_list
|
|
|
|
|
|
def print_skips(regex, message):
|
|
test_list = _get_test_list(regex)
|
|
if test_list:
|
|
if message:
|
|
print(message)
|
|
else:
|
|
print('Skipped because of regex %s:' % regex)
|
|
for test in test_list:
|
|
print(test)
|
|
# Extra whitespace to separate
|
|
print('\n')
|
|
|
|
|
|
def path_to_regex(path):
|
|
root, _ = os.path.splitext(path)
|
|
return root.replace('/', '.')
|
|
|
|
|
|
def get_regex_from_whitelist_file(file_path):
|
|
lines = []
|
|
for line in open(file_path).read().splitlines():
|
|
split_line = line.strip().split('#')
|
|
# Before the # is the regex
|
|
line_regex = split_line[0].strip()
|
|
if line_regex:
|
|
lines.append(line_regex)
|
|
return '|'.join(lines)
|
|
|
|
|
|
def construct_regex(blacklist_file, whitelist_file, regex, print_exclude):
|
|
if not blacklist_file:
|
|
exclude_regex = ''
|
|
else:
|
|
black_file = open(blacklist_file, 'r')
|
|
exclude_regex = ''
|
|
for line in black_file:
|
|
raw_line = line.strip()
|
|
split_line = raw_line.split('#')
|
|
# Before the # is the regex
|
|
line_regex = split_line[0].strip()
|
|
if len(split_line) > 1:
|
|
# After the # is a comment
|
|
comment = split_line[1].strip()
|
|
else:
|
|
comment = ''
|
|
if line_regex:
|
|
if print_exclude:
|
|
print_skips(line_regex, comment)
|
|
if exclude_regex:
|
|
exclude_regex = '|'.join([line_regex, exclude_regex])
|
|
else:
|
|
exclude_regex = line_regex
|
|
if exclude_regex:
|
|
exclude_regex = "^((?!" + exclude_regex + ").)*$"
|
|
if regex:
|
|
exclude_regex += regex
|
|
if whitelist_file:
|
|
exclude_regex += '%s' % get_regex_from_whitelist_file(whitelist_file)
|
|
return exclude_regex
|
|
|
|
|
|
def call_testr(regex, subunit, pretty, list_tests, slowest, parallel, concur,
|
|
until_failure, color, others=None):
|
|
others = others or []
|
|
if parallel:
|
|
cmd = ['testr', 'run', '--parallel']
|
|
if concur:
|
|
cmd.append('--concurrency=%s' % concur)
|
|
else:
|
|
cmd = ['testr', 'run']
|
|
if list_tests:
|
|
cmd = ['testr', 'list-tests']
|
|
elif (subunit or pretty) and not until_failure:
|
|
cmd.append('--subunit')
|
|
elif not (subunit or pretty) and until_failure:
|
|
cmd.append('--until-failure')
|
|
cmd.append(regex)
|
|
env = copy.deepcopy(os.environ)
|
|
|
|
if pretty:
|
|
subunit_trace_cmd = ['subunit-trace', '--no-failure-debug', '-f']
|
|
if color:
|
|
subunit_trace_cmd.append('--color')
|
|
|
|
# This workaround is necessary because of lp bug 1411804 it's super hacky
|
|
# and makes tons of unfounded assumptions, but it works for the most part
|
|
if (subunit or pretty) and until_failure:
|
|
test_list = _get_test_list(regex, env)
|
|
count = 0
|
|
failed = False
|
|
if not test_list:
|
|
print("No tests to run")
|
|
exit(1)
|
|
# If pretty or subunit output is desired manually loop forever over
|
|
# test individually and generate the desired output in a linear series
|
|
# this avoids 1411804 while retaining most of the desired behavior
|
|
while True:
|
|
for test in test_list:
|
|
if pretty:
|
|
cmd = ['python', '-m', 'subunit.run', test]
|
|
ps = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE)
|
|
subunit_trace_cmd.append('--no-summary')
|
|
proc = subprocess.Popen(subunit_trace_cmd,
|
|
env=env,
|
|
stdin=ps.stdout)
|
|
ps.stdout.close()
|
|
proc.communicate()
|
|
if proc.returncode > 0:
|
|
failed = True
|
|
break
|
|
else:
|
|
try:
|
|
subunit_run.main([sys.argv[0], test], sys.stdout)
|
|
except SystemExit as e:
|
|
if e > 0:
|
|
print("Ran %s tests without failure" % count)
|
|
exit(1)
|
|
else:
|
|
raise
|
|
count = count + 1
|
|
if failed:
|
|
print("Ran %s tests without failure" % count)
|
|
exit(0)
|
|
# If not until-failure special case call testr like normal
|
|
elif pretty and not list_tests:
|
|
cmd.extend(others)
|
|
ps = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE)
|
|
proc = subprocess.Popen(subunit_trace_cmd,
|
|
env=env, stdin=ps.stdout)
|
|
ps.stdout.close()
|
|
else:
|
|
cmd.extend(others)
|
|
proc = subprocess.Popen(cmd, env=env)
|
|
proc.communicate()
|
|
return_code = proc.returncode
|
|
if slowest and not list_tests:
|
|
print("\nSlowest Tests:\n")
|
|
slow_proc = subprocess.Popen(['testr', 'slowest'], env=env)
|
|
slow_proc.communicate()
|
|
return return_code
|
|
|
|
|
|
def call_subunit_run(test_id, pretty, subunit):
|
|
if pretty:
|
|
env = copy.deepcopy(os.environ)
|
|
cmd = ['python', '-m', 'subunit.run', test_id]
|
|
ps = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE)
|
|
proc = subprocess.Popen(['subunit-trace', '--no-failure-debug', '-f'],
|
|
env=env, stdin=ps.stdout)
|
|
ps.stdout.close()
|
|
proc.communicate()
|
|
return proc.returncode
|
|
elif subunit:
|
|
subunit_run.main([sys.argv[0], test_id], sys.stdout)
|
|
else:
|
|
testtools_run.main([sys.argv[0], test_id], sys.stdout)
|
|
|
|
|
|
def _select_and_call_runner(opts, exclude_regex, others):
|
|
ec = 1
|
|
if not os.path.isdir('.testrepository'):
|
|
subprocess.call(['testr', 'init'])
|
|
|
|
if not opts.no_discover and not opts.pdb:
|
|
ec = call_testr(exclude_regex, opts.subunit, opts.pretty, opts.list,
|
|
opts.slowest, opts.parallel, opts.concurrency,
|
|
opts.until_failure, opts.color, others)
|
|
else:
|
|
if others:
|
|
print('Unexpected arguments: ' + ' '.join(others))
|
|
return 2
|
|
test_to_run = opts.no_discover or opts.pdb
|
|
if test_to_run.find('/') != -1:
|
|
test_to_run = path_to_regex(test_to_run)
|
|
ec = call_subunit_run(test_to_run, opts.pretty, opts.subunit)
|
|
return ec
|
|
|
|
|
|
def main():
|
|
opts, others = get_parser(sys.argv[1:])
|
|
if opts.pretty and opts.subunit:
|
|
msg = ('Subunit output and pretty output cannot be specified at the '
|
|
'same time')
|
|
print(msg)
|
|
exit(2)
|
|
if opts.list and opts.no_discover:
|
|
msg = ('you can not list tests when you are bypassing discovery to '
|
|
'run a single test')
|
|
print(msg)
|
|
exit(3)
|
|
if not opts.parallel and opts.concurrency:
|
|
msg = "You can't specify a concurrency to use when running serially"
|
|
print(msg)
|
|
exit(4)
|
|
if (opts.pdb or opts.no_discover) and opts.until_failure:
|
|
msg = "You can not use until_failure mode with pdb or no-discover"
|
|
print(msg)
|
|
exit(5)
|
|
if opts.path:
|
|
regex = path_to_regex(opts.path)
|
|
else:
|
|
regex = opts.regex
|
|
exclude_regex = construct_regex(opts.blacklist_file,
|
|
opts.whitelist_file,
|
|
regex,
|
|
opts.print_exclude)
|
|
exit(_select_and_call_runner(opts, exclude_regex, others))
|
|
|
|
if __name__ == '__main__':
|
|
main()
|