Switch to stestr under the covers

This commit switches the ostestr command to use stestr under the covers.
This simplifies the majority of the logic, eventually ostestr will
be deprecated as a result of this migration. (since almost all the logic
here is contained in stestr already)

Depends-On: I2c7618a742439fd2ed26879f3114f0f66fd6337f
Change-Id: Id7cb2a39a8308f1413608dcf19273a1d7f33592e
This commit is contained in:
Matthew Treinish 2017-07-28 10:43:53 -04:00
parent 8ca44080d7
commit 7dd678e372
10 changed files with 91 additions and 139 deletions

1
.gitignore vendored
View File

@ -27,6 +27,7 @@ cover/
.tox .tox
nosetests.xml nosetests.xml
.testrepository .testrepository
.stestr
.venv .venv
# Translations # Translations

3
.stestr.conf Normal file
View File

@ -0,0 +1,3 @@
[DEFAULT]
test_path=os_testr/tests
top_dir=./

View File

@ -1,7 +0,0 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@ -14,21 +14,21 @@
# under the License. # under the License.
import argparse import argparse
import atexit
import copy import copy
import io import io
import os import os
import subprocess import subprocess
import sys import sys
import tempfile import warnings
import pbr.version import pbr.version
import six.moves
from stestr import commands
from subunit import run as subunit_run from subunit import run as subunit_run
from testtools import run as testtools_run from testtools import run as testtools_run
from os_testr import regex_builder as rb from os_testr import regex_builder as rb
from os_testr import testlist_builder as tlb
__version__ = pbr.version.VersionInfo('os_testr').version_string() __version__ = pbr.version.VersionInfo('os_testr').version_string()
@ -107,92 +107,68 @@ def get_parser(args):
def call_testr(regex, subunit, pretty, list_tests, slowest, parallel, concur, def call_testr(regex, subunit, pretty, list_tests, slowest, parallel, concur,
until_failure, color, list_of_tests=None, others=None): until_failure, color, others=None, blacklist_file=None,
others = others or [] whitelist_file=None, black_regex=None, load_list=None):
if parallel: # Handle missing .stestr.conf from users from before stestr migration
cmd = ['testr', 'run', '--parallel'] test_dir = None
if concur: top_dir = None
cmd.append('--concurrency=%s' % concur) group_regex = None
else: if not os.path.isfile('.stestr.conf') and os.path.isfile('.testr.conf'):
cmd = ['testr', 'run'] msg = ('No .stestr.conf file found in the CWD. Please create one to '
'to replace the .testr.conf. You can find a script to do this '
'in the stestr git repository.')
warnings.warn(msg)
with open('.testr.conf', 'r') as testr_conf_file:
config = six.moves.configparser.ConfigParser()
config.readfp(testr_conf_file)
test_command = config.get('DEFAULT', 'test_command')
group_regex = None
if config.has_option('DEFAULT', 'group_regex'):
group_regex = config.get('DEFAULT', 'group_regex')
for line in test_command.split('\n'):
if 'subunit.run discover' in line:
command_parts = line.split(' ')
top_dir_present = '-t' in line
for idx, val in enumerate(command_parts):
if top_dir_present:
if val == '-t':
top_dir = command_parts[idx + 1]
test_dir = command_parts[idx + 2]
else:
if val == 'discover':
test_dir = command_parts[idx + 2]
elif not os.path.isfile(
'.testr.conf') and not os.path.isfile('.stestr.conf'):
msg = ('No .stestr.conf found, please create one.')
print(msg)
sys.exit(1)
regexes = None
if regex:
regexes = regex.split()
serial = not parallel
if list_tests: if list_tests:
cmd = ['testr', 'list-tests'] # TODO(mtreinish): remove init call after list command detects and
elif (subunit or pretty) and not until_failure: # autocreates the repository
cmd.append('--subunit') if not os.path.isdir('.stestr'):
elif not (subunit or pretty) and until_failure: commands.init_command()
cmd.append('--until-failure') return commands.list_command(filters=regexes)
if list_of_tests: return_code = commands.run_command(filters=regexes, subunit_out=subunit,
test_fd, test_file_name = tempfile.mkstemp() concurrency=concur, test_path=test_dir,
atexit.register(os.remove, test_file_name) top_dir=top_dir,
test_file = os.fdopen(test_fd, 'w') group_regex=group_regex,
test_file.write('\n'.join(list_of_tests) + '\n') until_failure=until_failure,
test_file.close() serial=serial, pretty_out=pretty,
cmd.extend(('--load-list', test_file_name)) load_list=load_list,
elif regex: blacklist_file=blacklist_file,
cmd.append(regex) whitelist_file=whitelist_file,
black_regex=black_regex)
env = copy.deepcopy(os.environ) if slowest:
sys.stdout.write("\nSlowest Tests:\n")
if pretty: commands.slowest_command()
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 = rb._get_test_list(regex, env)
count = 0
failed = False
if not test_list:
print("No tests to run")
return 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)
return 1
else:
raise
count = count + 1
if failed:
print("Ran %s tests without failure" % count)
return 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 return return_code
@ -224,19 +200,16 @@ def call_subunit_run(test_id, pretty, subunit):
testtools_run.main([sys.argv[0], test_id], sys.stdout) testtools_run.main([sys.argv[0], test_id], sys.stdout)
def _ensure_testr():
if not os.path.isdir('.testrepository'):
subprocess.call(['testr', 'init'])
def _select_and_call_runner(opts, exclude_regex, others): def _select_and_call_runner(opts, exclude_regex, others):
ec = 1 ec = 1
_ensure_testr()
if not opts.no_discover and not opts.pdb: if not opts.no_discover and not opts.pdb:
ec = call_testr(exclude_regex, opts.subunit, opts.pretty, opts.list, ec = call_testr(exclude_regex, opts.subunit, opts.pretty, opts.list,
opts.slowest, opts.parallel, opts.concurrency, opts.slowest, opts.parallel, opts.concurrency,
opts.until_failure, opts.color, None, others) opts.until_failure, opts.color, others,
blacklist_file=opts.blacklist_file,
whitelist_file=opts.whitelist_file,
black_regex=opts.black_regex)
else: else:
if others: if others:
print('Unexpected arguments: ' + ' '.join(others)) print('Unexpected arguments: ' + ' '.join(others))
@ -248,24 +221,6 @@ def _select_and_call_runner(opts, exclude_regex, others):
return ec return ec
def _call_testr_with_list(opts, test_list, others):
ec = 1
_ensure_testr()
if opts.list:
print("\n".join(test_list))
return 0
if not test_list:
print("No testcase selected to run")
return 8
ec = call_testr(None, opts.subunit, opts.pretty, opts.list,
opts.slowest, opts.parallel, opts.concurrency,
opts.until_failure, opts.color, test_list, others)
return ec
def ostestr(args): def ostestr(args):
opts, others = get_parser(args) opts, others = get_parser(args)
if opts.pretty and opts.subunit: if opts.pretty and opts.subunit:
@ -301,15 +256,7 @@ def ostestr(args):
else: else:
regex = opts.regex regex = opts.regex
if opts.blacklist_file or opts.whitelist_file or opts.black_regex: return _select_and_call_runner(opts, regex, others)
list_of_tests = tlb.construct_list(opts.blacklist_file,
opts.whitelist_file,
regex,
opts.black_regex,
opts.print_exclude)
return (_call_testr_with_list(opts, list_of_tests, others))
else:
return (_select_and_call_runner(opts, regex, others))
def main(): def main():

View File

@ -19,7 +19,7 @@ import subprocess
def _get_test_list(regex, env=None): def _get_test_list(regex, env=None):
env = env or copy.deepcopy(os.environ) env = env or copy.deepcopy(os.environ)
testr_args = ['testr', 'list-tests'] testr_args = ['stestr', 'list']
if regex: if regex:
testr_args.append(regex) testr_args.append(regex)
proc = subprocess.Popen(testr_args, env=env, proc = subprocess.Popen(testr_args, env=env,

View File

@ -0,0 +1,3 @@
[DEFAULT]
test_path=./tests
group_regex=([^\.]*\.)*

View File

@ -1,5 +0,0 @@
[DEFAULT]
test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list
group_regex=([^\.]*\.)*

View File

@ -34,13 +34,13 @@ class TestReturnCodes(base.TestCase):
self.test_dir = os.path.join(self.directory, 'tests') self.test_dir = os.path.join(self.directory, 'tests')
os.mkdir(self.test_dir) os.mkdir(self.test_dir)
# Setup Test files # Setup Test files
self.testr_conf_file = os.path.join(self.directory, '.testr.conf') self.testr_conf_file = os.path.join(self.directory, '.stestr.conf')
self.setup_cfg_file = os.path.join(self.directory, 'setup.cfg') self.setup_cfg_file = os.path.join(self.directory, 'setup.cfg')
self.passing_file = os.path.join(self.test_dir, 'test_passing.py') self.passing_file = os.path.join(self.test_dir, 'test_passing.py')
self.failing_file = os.path.join(self.test_dir, 'test_failing.py') self.failing_file = os.path.join(self.test_dir, 'test_failing.py')
self.init_file = os.path.join(self.test_dir, '__init__.py') self.init_file = os.path.join(self.test_dir, '__init__.py')
self.setup_py = os.path.join(self.directory, 'setup.py') self.setup_py = os.path.join(self.directory, 'setup.py')
shutil.copy('os_testr/tests/files/testr-conf', self.testr_conf_file) shutil.copy('os_testr/tests/files/stestr-conf', self.testr_conf_file)
shutil.copy('os_testr/tests/files/passing-tests', self.passing_file) shutil.copy('os_testr/tests/files/passing-tests', self.passing_file)
shutil.copy('os_testr/tests/files/failing-tests', self.failing_file) shutil.copy('os_testr/tests/files/failing-tests', self.failing_file)
shutil.copy('setup.py', self.setup_py) shutil.copy('setup.py', self.setup_py)
@ -104,4 +104,4 @@ class TestReturnCodes(base.TestCase):
self.assertRunExit('ostestr --list', 0) self.assertRunExit('ostestr --list', 0)
def test_no_test(self): def test_no_test(self):
self.assertRunExit('ostestr --regex a --black-regex a', 8) self.assertRunExit('ostestr --regex a --black-regex a', 1)

View File

@ -3,6 +3,6 @@
# process, which may cause wedges in the gate later. # process, which may cause wedges in the gate later.
pbr!=2.1.0,>=2.0.0 # Apache-2.0 pbr!=2.1.0,>=2.0.0 # Apache-2.0
testrepository>=0.0.18 # Apache-2.0/BSD stestr>=1.0.0 # Apache-2.0
python-subunit>=0.0.18 # Apache-2.0/BSD python-subunit>=0.0.18 # Apache-2.0/BSD
testtools>=1.4.0 # MIT testtools>=1.4.0 # MIT

12
tox.ini
View File

@ -10,6 +10,9 @@ setenv =
VIRTUAL_ENV={envdir} VIRTUAL_ENV={envdir}
BRANCH_NAME=master BRANCH_NAME=master
CLIENT_NAME=os-testr CLIENT_NAME=os-testr
OS_STDOUT_CAPTURE=1
OS_STDERR_CAPTURE=1
OS_TEST_TIMEOUT=500
whitelist_externals = find whitelist_externals = find
deps = -r{toxinidir}/requirements.txt deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt -r{toxinidir}/test-requirements.txt
@ -24,7 +27,14 @@ commands = flake8
commands = {posargs} commands = {posargs}
[testenv:cover] [testenv:cover]
commands = python setup.py test --coverage --coverage-package-name='os_testr' --testr-args='{posargs}' setenv =
VIRTUAL_ENV={envdir}
PYTHON=coverage run --source os_testr --parallel-mode
commands =
ostestr {posargs}
coverage combine
coverage html -d cover
coverage xml -o cover/coverage.xml
[testenv:docs] [testenv:docs]
commands = python setup.py build_sphinx commands = python setup.py build_sphinx