From 7dd678e3720c326afb3ca7adfd01ec7324d5f5c7 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 28 Jul 2017 10:43:53 -0400 Subject: [PATCH] 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 --- .gitignore | 1 + .stestr.conf | 3 + .testr.conf | 7 - os_testr/ostestr.py | 191 ++++++++++------------------ os_testr/regex_builder.py | 2 +- os_testr/tests/files/stestr-conf | 3 + os_testr/tests/files/testr-conf | 5 - os_testr/tests/test_return_codes.py | 6 +- requirements.txt | 2 +- tox.ini | 12 +- 10 files changed, 92 insertions(+), 140 deletions(-) create mode 100644 .stestr.conf delete mode 100644 .testr.conf create mode 100644 os_testr/tests/files/stestr-conf delete mode 100644 os_testr/tests/files/testr-conf diff --git a/.gitignore b/.gitignore index d6ae633..ed8e4b5 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ cover/ .tox nosetests.xml .testrepository +.stestr .venv # Translations diff --git a/.stestr.conf b/.stestr.conf new file mode 100644 index 0000000..2b84f4f --- /dev/null +++ b/.stestr.conf @@ -0,0 +1,3 @@ +[DEFAULT] +test_path=os_testr/tests +top_dir=./ diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 6d83b3c..0000000 --- a/.testr.conf +++ /dev/null @@ -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 diff --git a/os_testr/ostestr.py b/os_testr/ostestr.py index 1c41079..7d18068 100755 --- a/os_testr/ostestr.py +++ b/os_testr/ostestr.py @@ -14,21 +14,21 @@ # under the License. import argparse -import atexit import copy import io import os import subprocess import sys -import tempfile +import warnings import pbr.version +import six.moves +from stestr import commands from subunit import run as subunit_run from testtools import run as testtools_run from os_testr import regex_builder as rb -from os_testr import testlist_builder as tlb __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, - until_failure, color, list_of_tests=None, 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') - if list_of_tests: - test_fd, test_file_name = tempfile.mkstemp() - atexit.register(os.remove, test_file_name) - test_file = os.fdopen(test_fd, 'w') - test_file.write('\n'.join(list_of_tests) + '\n') - test_file.close() - cmd.extend(('--load-list', test_file_name)) - elif regex: - 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') + until_failure, color, others=None, blacklist_file=None, + whitelist_file=None, black_regex=None, load_list=None): + # Handle missing .stestr.conf from users from before stestr migration + test_dir = None + top_dir = None + group_regex = None + if not os.path.isfile('.stestr.conf') and os.path.isfile('.testr.conf'): + 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) - # 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() + regexes = None + if regex: + regexes = regex.split() + serial = not parallel + if list_tests: + # TODO(mtreinish): remove init call after list command detects and + # autocreates the repository + if not os.path.isdir('.stestr'): + commands.init_command() + return commands.list_command(filters=regexes) + return_code = commands.run_command(filters=regexes, subunit_out=subunit, + concurrency=concur, test_path=test_dir, + top_dir=top_dir, + group_regex=group_regex, + until_failure=until_failure, + serial=serial, pretty_out=pretty, + load_list=load_list, + blacklist_file=blacklist_file, + whitelist_file=whitelist_file, + black_regex=black_regex) + + if slowest: + sys.stdout.write("\nSlowest Tests:\n") + commands.slowest_command() 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) -def _ensure_testr(): - if not os.path.isdir('.testrepository'): - subprocess.call(['testr', 'init']) - - def _select_and_call_runner(opts, exclude_regex, others): ec = 1 - _ensure_testr() 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, None, others) + opts.until_failure, opts.color, others, + blacklist_file=opts.blacklist_file, + whitelist_file=opts.whitelist_file, + black_regex=opts.black_regex) else: if others: print('Unexpected arguments: ' + ' '.join(others)) @@ -248,24 +221,6 @@ def _select_and_call_runner(opts, exclude_regex, others): 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): opts, others = get_parser(args) if opts.pretty and opts.subunit: @@ -301,15 +256,7 @@ def ostestr(args): else: regex = opts.regex - if opts.blacklist_file or opts.whitelist_file or opts.black_regex: - 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)) + return _select_and_call_runner(opts, regex, others) def main(): diff --git a/os_testr/regex_builder.py b/os_testr/regex_builder.py index 81a917a..6c373d4 100644 --- a/os_testr/regex_builder.py +++ b/os_testr/regex_builder.py @@ -19,7 +19,7 @@ import subprocess def _get_test_list(regex, env=None): env = env or copy.deepcopy(os.environ) - testr_args = ['testr', 'list-tests'] + testr_args = ['stestr', 'list'] if regex: testr_args.append(regex) proc = subprocess.Popen(testr_args, env=env, diff --git a/os_testr/tests/files/stestr-conf b/os_testr/tests/files/stestr-conf new file mode 100644 index 0000000..63b3c44 --- /dev/null +++ b/os_testr/tests/files/stestr-conf @@ -0,0 +1,3 @@ +[DEFAULT] +test_path=./tests +group_regex=([^\.]*\.)* diff --git a/os_testr/tests/files/testr-conf b/os_testr/tests/files/testr-conf deleted file mode 100644 index d5ad083..0000000 --- a/os_testr/tests/files/testr-conf +++ /dev/null @@ -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=([^\.]*\.)* diff --git a/os_testr/tests/test_return_codes.py b/os_testr/tests/test_return_codes.py index 4c336b1..2595aa8 100644 --- a/os_testr/tests/test_return_codes.py +++ b/os_testr/tests/test_return_codes.py @@ -34,13 +34,13 @@ class TestReturnCodes(base.TestCase): self.test_dir = os.path.join(self.directory, 'tests') os.mkdir(self.test_dir) # 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.passing_file = os.path.join(self.test_dir, 'test_passing.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.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/failing-tests', self.failing_file) shutil.copy('setup.py', self.setup_py) @@ -104,4 +104,4 @@ class TestReturnCodes(base.TestCase): self.assertRunExit('ostestr --list', 0) def test_no_test(self): - self.assertRunExit('ostestr --regex a --black-regex a', 8) + self.assertRunExit('ostestr --regex a --black-regex a', 1) diff --git a/requirements.txt b/requirements.txt index 4fc7eb7..95cd081 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,6 @@ # process, which may cause wedges in the gate later. 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 testtools>=1.4.0 # MIT diff --git a/tox.ini b/tox.ini index 08fca43..c7f210e 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,9 @@ setenv = VIRTUAL_ENV={envdir} BRANCH_NAME=master CLIENT_NAME=os-testr + OS_STDOUT_CAPTURE=1 + OS_STDERR_CAPTURE=1 + OS_TEST_TIMEOUT=500 whitelist_externals = find deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt @@ -24,7 +27,14 @@ commands = flake8 commands = {posargs} [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] commands = python setup.py build_sphinx