From e1271b6bf1e512c6cafc707423887a68678293b6 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 17 2018 15:29:32 +0000 Subject: Some UI changes to the runner This commit does a few things: - Improve the UX by using argparse which gives us a nice --help - Support py2 and py3 (default to py3, switches via --py2) - Allows running a single file (./runpaguretests.py --py2 ../tests/test_style.py) - Adds a -f/--force option to clear up the results folder if present - Move the list of failed tests into the results folder (easier to clean afterward) - Allows specifying a results folder - Run black on the code (sorry this should have been a separate commit :() - Adds back the configuration file used by pagure's tests Signed-off-by: Pierre-Yves Chibon --- diff --git a/runpaguretests.py b/runpaguretests.py index 68f34be..046cc5f 100755 --- a/runpaguretests.py +++ b/runpaguretests.py @@ -1,52 +1,110 @@ #!/bin/python3 + +import argparse import json import os import threading import time import multiprocessing +import shutil import subprocess import sys -# 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') - sys.exit(1) +RUNNER = "nosetests-3" +RUNNER_PY2 = "nosetests-2" + + +parser = argparse.ArgumentParser(description="Run the Pagure tests") +parser.add_argument( + "--debug", + dest="debug", + action="store_true", + default=False, + help="Expand the level of data returned.", +) +parser.add_argument( + "--py2", + dest="py2", + action="store_true", + default=False, + help="Runs the tests in python2 instead of python3", +) +parser.add_argument( + "--results", + default="results", + help="Specify a folder in which the results should be placed " + "(defaults to `results`)", +) +parser.add_argument( + "-f", + "--force", + default=False, + action="store_true", + help="Override the results and newfailed file without asking you", +) +parser.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.", +) + +args = parser.parse_args() -if os.path.exists('results'): - print('Results folder exists, please move so we do not clobber') +# 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") sys.exit(1) -if os.path.exists('newfailed'): - print('newfailed exists, please move so we do not clobber') - sys.exit(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" + ) + sys.exit(1) + else: + shutil.rmtree(args.results) -os.mkdir('results') +os.mkdir(args.results) -print('Pre-flight checks passed') +print("Pre-flight checks passed") NUMPROCS = multiprocessing.cpu_count() - 1 -print('Using %d processes' % NUMPROCS) +print("Using %d processes" % NUMPROCS) -print('Start timing') +print("Start timing") start = time.time() +if args.py2: + RUNNER = RUNNER_PY2 + suites = [] -if os.path.exists('failed'): - print('Loading failed tests') - with open('failed', 'r') as ffile: - suites = json.loads(ffile.read()) +if args.failed_tests: + here = os.path.join(os.path.dirname(os.path.abspath(__file__))) + failed_tests = os.path.join(here, args.failed_tests) + if not os.path.exists(failed_tests): + print("Could not find the specified file:%s" % args.failed_tests) + sys.exit(1) + print("Loading failed tests") + try: + with open(failed_tests, "r") as ffile: + suites = json.loads(ffile.read()) + except json.decoder.JSONDecodeError: + bname = os.path.basename(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'): + print("Loading all tests") + for fname in os.listdir("../tests"): + if not fname.endswith(".py"): continue - if not fname.startswith('test_'): + if not fname.startswith("test_"): continue - suites.append(fname.replace('.py', '')) + suites.append(fname.replace(".py", "")) printlock = threading.RLock() @@ -60,7 +118,7 @@ def clean_line(): with printlock: if lastlen is not None: - print(' ' * lastlen, end='\r') + print(" " * lastlen, end="\r") lastlen = None @@ -68,11 +126,13 @@ def print_running(): global lastlen with printlock: - msg = 'Running %d suites (%d remaining): %s' % (len(running), - numremaining, - ', '.join(running)) + msg = "Running %d suites (%d remaining): %s" % ( + len(running), + numremaining, + ", ".join(running), + ) lastlen = len(msg) - print(msg, end='\r') + print(msg, end="\r") def add_running(suite): @@ -89,13 +149,13 @@ def remove_running(suite, failed): with printlock: running.remove(suite) clean_line() - print('Test suite %s passed: %s' % (suite, not failed)) + print("Test suite %s passed: %s" % (suite, not failed)) print_running() class WorkerThread(threading.Thread): def __init__(self, sem, suite): - super(WorkerThread, self).__init__(name='worker-%s' % suite) + super(WorkerThread, self).__init__(name="worker-%s" % suite) self.sem = sem self.suite = suite self.failed = None @@ -103,13 +163,12 @@ class WorkerThread(threading.Thread): def run(self): with self.sem: add_running(self.suite) - with open('results/%s' % self.suite, 'w') as resfile: - cmd = ['nosetests-3', '-v', 'tests.%s' % self.suite] + with open(os.path.join(args.results, self.suite), "w") as resfile: + cmd = [RUNNER, "-v", "tests.%s" % self.suite] + env = {'PAGURE_CONFIG': '../tests/test_config'} proc = subprocess.Popen( - cmd, - cwd='..', - stdout=resfile, - stderr=subprocess.STDOUT, + cmd, cwd="..", stdout=resfile, stderr=subprocess.STDOUT, + env=env ) res = proc.wait() if res == 0: @@ -128,7 +187,7 @@ for suite in suites: # Start the workers -print('Starting the workers') +print("Starting the workers") print() print() for worker in workers.values(): @@ -140,26 +199,26 @@ for worker in workers: workers[worker].join() print_running() print() -print('All work done') +print("All work done") # Gather results print() print() -print('RESULTS:') +print("RESULTS:") failed = [] for worker in workers: if not workers[worker].failed: - result = 'Success' + result = "Success" else: - result = 'FAILED' + result = "FAILED" failed.append(worker) - print('Test %s result: %s' % (worker, result)) + print("Test %s result: %s" % (worker, result)) # Write failed if failed: - with open('newfailed', 'w') as ffile: + with open(os.path.join(args.results, "newfailed"), "w") as ffile: ffile.write(json.dumps(failed)) @@ -167,12 +226,14 @@ if failed: end = time.time() print() print() -print('Ran %d tests in %f seconds, of which %d failed' - % (len(workers), (end - start), len(failed))) +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!') + print("ALL PASSED! CONGRATULATIONS!") else: sys.exit(1)