From 1e58bea3a371a6a26fa9a08d24c2eb902f162dc1 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 20 2018 11:41:22 +0000 Subject: [PATCH 1/12] Replace the old shell script by a new runner more performant Signed-off-by: Pierre-Yves Chibon --- diff --git a/runtests.py b/runtests.py new file mode 100755 index 0000000..e08455e --- /dev/null +++ b/runtests.py @@ -0,0 +1,550 @@ +#!/bin/python3 + +from __future__ import unicode_literals + +import argparse +import coverage +import json +import logging +import multiprocessing +import os +import shutil +import subprocess +import sys +import threading +import time + + +RUNNER_PY2 = "nosetests-2" +RUNNER_PY3 = "nosetests-3" + +COVER_PY2 = "coverage2" +COVER_PY3 = "coverage3" + +LASTLEN = None +NUMREMAINING = None +PRINTLOCK = None +RUNNING = [] +FAILED = [] +NUMPROCS = multiprocessing.cpu_count() - 1 + + +def setup_parser(): + """ Set up the command line arguments supported and return the arguments + """ + + parser = argparse.ArgumentParser(description="Run the Pagure tests") + parser.add_argument( + "--debug", + dest="debug", + action="store_true", + default=False, + help="Increase the level of data logged.", + ) + + subparsers = parser.add_subparsers(title="actions") + + # RUN + parser_run = subparsers.add_parser("run", help="Run the tests") + parser_run.add_argument( + "--py2", + dest="py2", + action="store_true", + default=False, + help="Runs the tests only in python2 instead of both python2 and python3", + ) + parser_run.add_argument( + "--py3", + dest="py3", + action="store_true", + default=False, + help="Runs the tests only in python3 instead of both python2 and python3", + ) + parser_run.add_argument( + "--results", + default="results", + help="Specify a folder in which the results should be placed " + "(defaults to `results`)", + ) + parser_run.add_argument( + "-f", + "--force", + default=False, + action="store_true", + help="Override the results and newfailed file without asking you", + ) + parser_run.add_argument( + "--with-coverage", + default=False, + action="store_true", + help="Also build coverage report", + ) + parser_run.add_argument( + "failed_tests", + nargs="?", + help="File containing a JSON list of the failed tests to run or " + "pointing to a test file to run.", + ) + parser_run.set_defaults(func=do_run) + + # RERUN + parser_run = subparsers.add_parser("rerun", help="Run failed tests") + parser_run.add_argument( + "--debug", + dest="debug", + action="store_true", + default=False, + help="Expand the level of data returned.", + ) + parser_run.add_argument( + "--py2", + dest="py2", + action="store_true", + default=False, + help="Runs the tests only in python2 instead of both python2 and python3", + ) + parser_run.add_argument( + "--py3", + dest="py3", + action="store_true", + default=False, + help="Runs the tests only in python3 instead of both python2 and python3", + ) + parser_run.add_argument( + "--results", + default="results", + help="Specify a folder in which the results should be placed " + "(defaults to `results`)", + ) + parser_run.add_argument( + "--with-coverage", + default=False, + action="store_true", + help="Also build coverage report", + ) + parser_run.set_defaults(func=do_rerun) + + # LIST + parser_run = subparsers.add_parser("list", help="List failed tests") + parser_run.add_argument( + "--results", + default="results", + help="Specify a folder in which the results should be placed " + "(defaults to `results`)", + ) + parser_run.add_argument( + "--show", + default=False, + action="store_true", + help="Show the error files using `less`", + ) + parser_run.add_argument( + "-n", default=None, nargs="?", type=int, + help="Number of failed test to show", + ) + parser_run.set_defaults(func=do_list) + + # SHOW-COVERAGE + parser_run = subparsers.add_parser( + "show-coverage", + help="Shows the coverage report from the data in the results folder") + parser_run.add_argument( + "--debug", + dest="debug", + action="store_true", + default=False, + help="Expand the level of data returned.", + ) + parser_run.add_argument( + "--py2", + dest="py2", + action="store_true", + default=False, + help="Runs the tests only in python2 instead of both python2 and python3", + ) + parser_run.add_argument( + "--py3", + dest="py3", + action="store_true", + default=False, + help="Runs the tests only in python3 instead of both python2 and python3", + ) + parser_run.add_argument( + "--results", + default="results", + help="Specify a folder in which the results should be placed " + "(defaults to `results`)", + ) + parser_run.set_defaults(func=do_show_coverage) + + return parser + + +def clean_line(): + global LASTLEN + + with PRINTLOCK: + if LASTLEN is not None: + print(" " * LASTLEN, end="\r") + LASTLEN = None + + +def print_running(): + global LASTLEN + + with PRINTLOCK: + msg = "Running %d suites: %d remaining, %d failed" % ( + len(RUNNING), + NUMREMAINING, + len(FAILED), + ) + LASTLEN = len(msg) + print(msg, end="\r") + + +def add_running(suite): + global NUMREMAINING + + with PRINTLOCK: + NUMREMAINING -= 1 + RUNNING.append(suite) + clean_line() + print_running() + + +def remove_running(suite, failed): + with PRINTLOCK: + RUNNING.remove(suite) + clean_line() + status = 'passed' + if failed: + status = 'FAILED' + print("Test suite %s: %s" % (status, suite)) + print_running() + + +class WorkerThread(threading.Thread): + def __init__(self, sem, pyver, suite, results, with_cover): + name = "py%d-%s" % (pyver, suite) + super(WorkerThread, self).__init__(name="worker-%s" % name) + self.name = name + self.sem = sem + self.pyver = pyver + self.suite = suite + self.failed = None + self.results = results + self.with_cover = with_cover + + def run(self): + with self.sem: + add_running(self.name) + with open(os.path.join(self.results, self.name), "w") as resfile: + if self.pyver == 2: + runner = RUNNER_PY2 + elif self.pyver == 3: + runner = RUNNER_PY3 + cmd = [runner, "-v", "tests.%s" % self.suite] + if self.with_cover: + cmd.append("--with-cover") + env = { + "PAGURE_CONFIG": "../tests/test_config", + "COVERAGE_FILE": os.path.join( + self.results, "%s.coverage" % self.name + ), + "LANG": "en_US.UTF-8", + } + proc = subprocess.Popen( + cmd, cwd="..", stdout=resfile, stderr=subprocess.STDOUT, env=env + ) + res = proc.wait() + if res == 0: + self.failed = False + else: + self.failed = True + if not self.failed is not True: + with PRINTLOCK: + FAILED.append(self.name) + remove_running(self.name, self.failed) + + +def do_run(args): + """ Performs some checks and runs the tests. + """ + + # Some pre-flight checks + if not os.path.exists("../.git") or not os.path.exists("../nosetests3"): + print("Please run from a single level into the Pagure codebase") + return 1 + + if os.path.exists(args.results): + if not args.force: + print( + "Results folder exists, please remove it so we do not clobber" + " or use --force" + ) + return 1 + else: + shutil.rmtree(args.results) + + os.mkdir(args.results) + + print("Pre-flight checks passed") + + suites = [] + + if args.failed_tests: + here = os.path.join(os.path.dirname(os.path.abspath(__file__))) + failed_tests_fullpath = os.path.join(here, args.failed_tests) + if not os.path.exists(failed_tests_fullpath): + print("Could not find the specified file:%s" % failed_tests_fullpath) + return 1 + print("Loading failed tests") + try: + with open(failed_tests_fullpath, "r") as ffile: + suites = json.loads(ffile.read()) + except json.decoder.JSONDecodeError: + bname = os.path.basename(args.failed_tests) + if bname.endswith(".py") and bname.startswith("test_"): + suites.append(bname.replace(".py", "")) + + if len(suites) == 0: + print("Loading all tests") + for fname in os.listdir("../tests"): + if not fname.endswith(".py"): + continue + if not fname.startswith("test_"): + continue + suites.append(fname.replace(".py", "")) + + _run_test_suites(args, suites) + + +def do_rerun(args): + """ Re-run tests that failed the last/specified run. + """ + + # Some pre-flight checks + if not os.path.exists("../.git") or not os.path.exists("../nosetests3"): + print("Please run from a single level into the Pagure codebase") + return 1 + + if not os.path.exists(args.results): + print("Could not find an existing results folder at: %s" % args.results) + return 1 + + if not os.path.exists(os.path.join(args.results, "newfailed")): + print( + "Could not find an failed tests in the results folder at: %s" % args.results + ) + return 1 + + print("Pre-flight checks passed") + + suites = [] + tmp = [] + + print("Loading failed tests") + try: + with open(os.path.join(args.results, "newfailed"), "r") as ffile: + tmp = json.loads(ffile.read()) + except json.decoder.JSONDecodeError: + print("File containing the failed tests is not JSON") + return 1 + + for suite in tmp: + if suite.startswith(("py2-", "py3-")): + suites.append(suite[4:]) + + _run_test_suites(args, set(suites)) + + +def _run_test_suites(args, suites): + print("Using %d processes" % NUMPROCS) + print("Start timing") + start = time.time() + + global PRINTLOCK + PRINTLOCK = threading.RLock() + global NUMREMAINING + NUMREMAINING = 0 + + sem = threading.BoundedSemaphore(NUMPROCS) + + # Create a worker per test + workers = {} + pyvers = (2, 3) + if args.py2: + pyvers = (2,) + elif args.py3: + pyvers = (3,) + + for suite in suites: + for pyver in pyvers: + NUMREMAINING += 1 + workers["py%d-%s" % (pyver, suite)] = WorkerThread( + sem, pyver, suite, args.results, args.with_coverage + ) + + # Start the workers + print("Starting the workers") + print() + print() + for worker in workers.values(): + worker.start() + + # Wait for them to terminate + for worker in workers: + workers[worker].join() + print_running() + print() + print("All work done") + + # Gather results + print() + print() + print("Failed tests:") + for worker in workers: + if not workers[worker].failed: + continue + print("FAILED test: %s" % (worker)) + + # Write failed + if FAILED: + with open(os.path.join(args.results, "newfailed"), "w") as ffile: + ffile.write(json.dumps(FAILED)) + + # Stats + end = time.time() + print() + print() + print( + "Ran %d tests in %f seconds, of which %d failed" + % (len(workers), (end - start), len(FAILED)) + ) + + # Exit + if len(FAILED) == 0: + print("ALL PASSED! CONGRATULATIONS!") + else: + return 1 + + if args.with_coverage: + do_show_coverage(args) + + return 0 + + +def do_list(args): + """ List tests that failed the last/specified run. + """ + + # Some pre-flight checks + if not os.path.exists("../.git") or not os.path.exists("../nosetests3"): + print("Please run from a single level into the Pagure codebase") + return 1 + + if not os.path.exists(args.results): + print("Could not find an existing results folder at: %s" % args.results) + return 1 + + if not os.path.exists(os.path.join(args.results, "newfailed")): + print( + "Could not find an failed tests in the results folder at: %s" % args.results + ) + return 1 + + print("Pre-flight checks passed") + + suites = [] + tmp = [] + + print("Loading failed tests") + try: + with open(os.path.join(args.results, "newfailed"), "r") as ffile: + suites = json.loads(ffile.read()) + except json.decoder.JSONDecodeError: + print("File containing the failed tests is not JSON") + return 1 + + print("Failed tests") + failed_tests = len(suites) + + if args.n: + suites = suites[:args.n] + print("- " + "\n- ".join(suites)) + print("Total: %s test failed" % failed_tests) + + if args.show: + for suite in suites: + cmd = ["less", os.path.join(args.results, suite)] + subprocess.check_call(cmd) + + +def do_show_coverage(args): + print() + print("Combining coverage results...") + pyvers = (2, 3) + if args.py2: + pyvers = (2,) + elif args.py3: + pyvers = (3,) + + for pyver in pyvers: + coverfiles = [] + for fname in os.listdir(args.results): + if fname.endswith(".coverage") and fname.startswith("py%d-" % pyver): + coverfiles.append(os.path.join(args.results, fname)) + + cover = None + if pyver == 2: + cover = COVER_PY2 + elif pyver == 3: + cover = COVER_PY3 + + env = {"COVERAGE_FILE": os.path.join(args.results, "combined.coverage")} + cmd = [cover, "combine"] + coverfiles + subprocess.check_call(cmd, env=env) + print() + print("Python %d coverage: " % pyver) + cmd = [cover, "report", "--include=../pagure/*"] + subprocess.check_call(cmd, env=env) + + +def main(): + """ Main function """ + # Set up parser for global args + parser = setup_parser() + # Parse the commandline + try: + arg = parser.parse_args() + except argparse.ArgumentTypeError as err: + print("\nError: {0}".format(err)) + return 2 + + logging.basicConfig() + if arg.debug: + LOG.setLevel(logging.DEBUG) + + if "func" not in arg: + parser.print_help() + return 1 + + arg.results = os.path.abspath(arg.results) + + return_code = 0 + + try: + return_code = arg.func(arg) + except KeyboardInterrupt: + print("\nInterrupted by user.") + return_code = 1 + except Exception as err: + print("Error: {0}".format(err)) + logging.exception("Generic error caught:") + return_code = 5 + + return return_code + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/runtests.sh b/runtests.sh deleted file mode 100755 index 1a84849..0000000 --- a/runtests.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -PAGURE_CONFIG=`pwd`/tests/test_config \ -PYTHONPATH=pagure \ -./nosetests --with-coverage --cover-erase --cover-package=pagure --with-pagureperf $* diff --git a/runtests3.sh b/runtests3.sh deleted file mode 100755 index cfafed5..0000000 --- a/runtests3.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -PAGURE_CONFIG=`pwd`/tests/test_config \ -PYTHONPATH=pagure \ -./nosetests3 --with-coverage --cover-erase --cover-package=pagure $* diff --git a/tox.ini b/tox.ini index 693f3fc..ba54220 100644 --- a/tox.ini +++ b/tox.ini @@ -19,7 +19,8 @@ setenv = PYTHONPATH={toxinidir} commands = #nosetests --with-coverage --cover-erase --cover-package=pagure --with-pagureperf {posargs} - nosetests {posargs} + #nosetests {posargs} + {toxinidir}/runtests.py run {posargs} [testenv:timetests] From c1d6fa9dd8da899767cb962694836e0764e84c9a Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 20 2018 12:00:45 +0000 Subject: [PATCH 2/12] Call the runner directly in tox Signed-off-by: Pierre-Yves Chibon --- diff --git a/tox.ini b/tox.ini index ba54220..466b7b6 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,7 @@ setenv = commands = #nosetests --with-coverage --cover-erase --cover-package=pagure --with-pagureperf {posargs} #nosetests {posargs} - {toxinidir}/runtests.py run {posargs} + python {toxinidir}/runtests.py run {posargs} [testenv:timetests] From 67871314dfb7ab49285cb7fa5c9dc0c1e9737354 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 20 2018 12:01:02 +0000 Subject: [PATCH 3/12] Check if there is the corresponding runner before running py2 or py3 Signed-off-by: Pierre-Yves Chibon --- diff --git a/runtests.py b/runtests.py index e08455e..416f4ff 100755 --- a/runtests.py +++ b/runtests.py @@ -378,6 +378,26 @@ def _run_test_suites(args, suites): elif args.py3: pyvers = (3,) + if 2 in pyvers: + try: + subprocess.check_call(["which", RUNNER_PY2]) + except subprocess.CalledProcessError: + print("No %s found, removing python 2" % RUNNER_PY2) + if 3 in pyvers: + pyvers = (3,) + else: + return 1 + + if 3 in pyvers: + try: + subprocess.check_call(["which", RUNNER_PY3]) + except subprocess.CalledProcessError: + print("No %s found, removing python 3" % RUNNER_PY3) + if 2 in pyvers: + pyvers = (2,) + else: + return 1 + for suite in suites: for pyver in pyvers: NUMREMAINING += 1 From 60eeb6dda73609b3bb6ae1eede73df2eb1ae8c39 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 20 2018 12:01:41 +0000 Subject: [PATCH 4/12] Include coverage in the list of requirements for the tests Signed-off-by: Pierre-Yves Chibon --- diff --git a/tests_requirements.txt b/tests_requirements.txt index beb0a12..494f448 100644 --- a/tests_requirements.txt +++ b/tests_requirements.txt @@ -1,6 +1,7 @@ bcrypt beautifulsoup4 black; python_version >= '3.6' # Only available on py3.6+ +coverage cryptography eventlet fedmsg From b75122e246e2d8843ed6b4f9bf21ef76468782fd Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 20 2018 12:13:38 +0000 Subject: [PATCH 5/12] Make the new runner work from the top level directory Signed-off-by: Pierre-Yves Chibon --- diff --git a/runtests.py b/runtests.py index 416f4ff..b76cff2 100755 --- a/runtests.py +++ b/runtests.py @@ -17,7 +17,6 @@ import time RUNNER_PY2 = "nosetests-2" RUNNER_PY3 = "nosetests-3" - COVER_PY2 = "coverage2" COVER_PY3 = "coverage3" @@ -254,7 +253,7 @@ class WorkerThread(threading.Thread): "LANG": "en_US.UTF-8", } proc = subprocess.Popen( - cmd, cwd="..", stdout=resfile, stderr=subprocess.STDOUT, env=env + cmd, cwd=".", stdout=resfile, stderr=subprocess.STDOUT, env=env ) res = proc.wait() if res == 0: @@ -272,7 +271,7 @@ def do_run(args): """ # Some pre-flight checks - if not os.path.exists("../.git") or not os.path.exists("../nosetests3"): + if not os.path.exists("./.git") or not os.path.exists("./nosetests3"): print("Please run from a single level into the Pagure codebase") return 1 @@ -309,7 +308,7 @@ def do_run(args): if len(suites) == 0: print("Loading all tests") - for fname in os.listdir("../tests"): + for fname in os.listdir("./tests"): if not fname.endswith(".py"): continue if not fname.startswith("test_"): @@ -324,7 +323,7 @@ def do_rerun(args): """ # Some pre-flight checks - if not os.path.exists("../.git") or not os.path.exists("../nosetests3"): + if not os.path.exists("./.git") or not os.path.exists("./pagure"): print("Please run from a single level into the Pagure codebase") return 1 @@ -459,7 +458,7 @@ def do_list(args): """ # Some pre-flight checks - if not os.path.exists("../.git") or not os.path.exists("../nosetests3"): + if not os.path.exists("./.git") or not os.path.exists("./pagure"): print("Please run from a single level into the Pagure codebase") return 1 @@ -526,7 +525,7 @@ def do_show_coverage(args): subprocess.check_call(cmd, env=env) print() print("Python %d coverage: " % pyver) - cmd = [cover, "report", "--include=../pagure/*"] + cmd = [cover, "report", "--include=./pagure/*"] subprocess.check_call(cmd, env=env) From 8e4bfc57217a5b0e8ca0b9d6b49536424eed03f9 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 20 2018 12:14:00 +0000 Subject: [PATCH 6/12] Drop the extra arguments when calling the runner, pylint and flake8 Signed-off-by: Pierre-Yves Chibon --- diff --git a/run_ci_tests.sh b/run_ci_tests.sh index c55759e..d7be518 100755 --- a/run_ci_tests.sh +++ b/run_ci_tests.sh @@ -39,11 +39,6 @@ pip install --upgrade tox trollius pip install --upgrade --force-reinstall chardet pip3 install "pygit2 <= `rpm -q libgit2 --queryformat='%{version}'`" parallel -v ::: \ -"tox --sitepackages -e 'py27-flask011-ci' -- -v --with-xcoverage --cover-erase --cover-package=pagure" \ -"tox --sitepackages -e 'py34-flask011-ci' -- -v --with-xcoverage --cover-erase --cover-package=pagure" +"tox --sitepackages -e 'py27-flask011-ci' " \ +"tox --sitepackages -e 'py34-flask011-ci' " - -set +e - -tox --sitepackages -e pylint -- -f parseable | tee pylint.out -tox --sitepackages -e lint | tee flake8.out From d70640b9e8292d1b4e8fa4ff5ff535d66f382b78 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 20 2018 12:18:29 +0000 Subject: [PATCH 7/12] Use the print function, even in py2 Signed-off-by: Pierre-Yves Chibon --- diff --git a/runtests.py b/runtests.py index b76cff2..1957394 100755 --- a/runtests.py +++ b/runtests.py @@ -1,6 +1,6 @@ #!/bin/python3 -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import argparse import coverage From 842dfc7a2605f3c23e35813586645979b09daa5f Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 20 2018 12:19:50 +0000 Subject: [PATCH 8/12] Drop the positional arguments until the change to run_ci_tests apply Signed-off-by: Pierre-Yves Chibon --- diff --git a/tox.ini b/tox.ini index 466b7b6..1706e9b 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,8 @@ setenv = commands = #nosetests --with-coverage --cover-erase --cover-package=pagure --with-pagureperf {posargs} #nosetests {posargs} - python {toxinidir}/runtests.py run {posargs} + python {toxinidir}/runtests.py run +#{posargs} [testenv:timetests] From 84e668c67e0fbab1b704e1502c3d825f2d0882b2 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 20 2018 12:32:27 +0000 Subject: [PATCH 9/12] Support an un-version nosetests and coverage runners Signed-off-by: Pierre-Yves Chibon --- diff --git a/runtests.py b/runtests.py index 1957394..6fb2db9 100755 --- a/runtests.py +++ b/runtests.py @@ -15,8 +15,10 @@ import threading import time +RUNNER_PY = "nosetests" RUNNER_PY2 = "nosetests-2" RUNNER_PY3 = "nosetests-3" +COVER_PY = "coverage" COVER_PY2 = "coverage2" COVER_PY3 = "coverage3" @@ -242,6 +244,8 @@ class WorkerThread(threading.Thread): runner = RUNNER_PY2 elif self.pyver == 3: runner = RUNNER_PY3 + else: + runner = RUNNER_PY cmd = [runner, "-v", "tests.%s" % self.suite] if self.with_cover: cmd.append("--with-cover") @@ -357,6 +361,42 @@ def do_rerun(args): _run_test_suites(args, set(suites)) +def _get_pyvers(args): + pyvers = [2, 3] + if args.py2: + pyvers = [2,] + elif args.py3: + pyvers = [3,] + + un_versioned = False + try: + subprocess.check_call(["which", RUNNER_PY]) + un_versioned = True + except subprocess.CalledProcessError: + print("No %s found no unversioned runner" % RUNNER_PY) + + if 2 in pyvers: + nopy2 = False + try: + subprocess.check_call(["which", RUNNER_PY2]) + except subprocess.CalledProcessError: + print("No %s found, removing python 2" % RUNNER_PY2) + del pyvers[pyvers.index(2)] + + if 3 in pyvers: + nopy3 = False + try: + subprocess.check_call(["which", RUNNER_PY3]) + except subprocess.CalledProcessError: + print("No %s found, removing python 3" % RUNNER_PY3) + del pyvers[pyvers.index(3)] + + if not pyvers and un_versioned: + pyvers = [""] + + return pyvers + + def _run_test_suites(args, suites): print("Using %d processes" % NUMPROCS) print("Start timing") @@ -371,31 +411,11 @@ def _run_test_suites(args, suites): # Create a worker per test workers = {} - pyvers = (2, 3) - if args.py2: - pyvers = (2,) - elif args.py3: - pyvers = (3,) - if 2 in pyvers: - try: - subprocess.check_call(["which", RUNNER_PY2]) - except subprocess.CalledProcessError: - print("No %s found, removing python 2" % RUNNER_PY2) - if 3 in pyvers: - pyvers = (3,) - else: - return 1 + pyvers = _get_pyvers(args) - if 3 in pyvers: - try: - subprocess.check_call(["which", RUNNER_PY3]) - except subprocess.CalledProcessError: - print("No %s found, removing python 3" % RUNNER_PY3) - if 2 in pyvers: - pyvers = (2,) - else: - return 1 + if no pyvers: + return 1 for suite in suites: for pyver in pyvers: @@ -502,11 +522,8 @@ def do_list(args): def do_show_coverage(args): print() print("Combining coverage results...") - pyvers = (2, 3) - if args.py2: - pyvers = (2,) - elif args.py3: - pyvers = (3,) + + pyvers = _get_pyvers(args) for pyver in pyvers: coverfiles = [] @@ -519,6 +536,8 @@ def do_show_coverage(args): cover = COVER_PY2 elif pyver == 3: cover = COVER_PY3 + else: + cover = COVER_PY env = {"COVERAGE_FILE": os.path.join(args.results, "combined.coverage")} cmd = [cover, "combine"] + coverfiles From cab6b6213aaedbef87b580cdbc879e1b674dcdfd Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 20 2018 12:33:38 +0000 Subject: [PATCH 10/12] Ignore existing results Signed-off-by: Pierre-Yves Chibon --- diff --git a/tox.ini b/tox.ini index 1706e9b..c3445df 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,7 @@ setenv = commands = #nosetests --with-coverage --cover-erase --cover-package=pagure --with-pagureperf {posargs} #nosetests {posargs} - python {toxinidir}/runtests.py run + python {toxinidir}/runtests.py run -f #{posargs} From c04da26b931e6418db7109a85e69b180b3c7d447 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 20 2018 12:37:29 +0000 Subject: [PATCH 11/12] typi typo Signed-off-by: Pierre-Yves Chibon --- diff --git a/runtests.py b/runtests.py index 6fb2db9..22afc01 100755 --- a/runtests.py +++ b/runtests.py @@ -414,7 +414,7 @@ def _run_test_suites(args, suites): pyvers = _get_pyvers(args) - if no pyvers: + if not pyvers: return 1 for suite in suites: From 05cd046763e2017dbca54f64e284be2e6d8a1452 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 20 2018 12:42:17 +0000 Subject: [PATCH 12/12] pyver can now be a string Signed-off-by: Pierre-Yves Chibon --- diff --git a/runtests.py b/runtests.py index 22afc01..b54d261 100755 --- a/runtests.py +++ b/runtests.py @@ -226,7 +226,7 @@ def remove_running(suite, failed): class WorkerThread(threading.Thread): def __init__(self, sem, pyver, suite, results, with_cover): - name = "py%d-%s" % (pyver, suite) + name = "py%s-%s" % (pyver, suite) super(WorkerThread, self).__init__(name="worker-%s" % name) self.name = name self.sem = sem @@ -420,7 +420,7 @@ def _run_test_suites(args, suites): for suite in suites: for pyver in pyvers: NUMREMAINING += 1 - workers["py%d-%s" % (pyver, suite)] = WorkerThread( + workers["py%s-%s" % (pyver, suite)] = WorkerThread( sem, pyver, suite, args.results, args.with_coverage ) @@ -528,7 +528,7 @@ def do_show_coverage(args): for pyver in pyvers: coverfiles = [] for fname in os.listdir(args.results): - if fname.endswith(".coverage") and fname.startswith("py%d-" % pyver): + if fname.endswith(".coverage") and fname.startswith("py%s-" % pyver): coverfiles.append(os.path.join(args.results, fname)) cover = None @@ -543,7 +543,7 @@ def do_show_coverage(args): cmd = [cover, "combine"] + coverfiles subprocess.check_call(cmd, env=env) print() - print("Python %d coverage: " % pyver) + print("Python %s coverage: " % pyver) cmd = [cover, "report", "--include=./pagure/*"] subprocess.check_call(cmd, env=env)