diff options
Diffstat (limited to 'testing/command.py')
-rw-r--r-- | testing/command.py | 277 |
1 files changed, 151 insertions, 126 deletions
diff --git a/testing/command.py b/testing/command.py index dd71eab56..31a48f87c 100644 --- a/testing/command.py +++ b/testing/command.py @@ -1,43 +1,5 @@ -############################################################################# -## -## Copyright (C) 2017 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# - -from __future__ import print_function +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only """ testrunner @@ -46,7 +8,7 @@ testrunner Provide an interface to the pyside tests. ----------------------------------------- -This program can only be run if PySide was build with tests enabled. +This program can only be run if PySide was built with tests enabled. All tests are run in a single pass, and if not blacklisted, an error is raised at the end of the run. @@ -57,7 +19,7 @@ Building the project with something like python setup.py build --build-tests --qmake=<qmakepath> --ignore-git --debug is sufficient. The tests are run by changing into the latest build dir and there -into pyside2, then 'make test'. +into pyside6, then 'make test'. New testing policy: @@ -72,31 +34,40 @@ The full mode can be tested locally by setting export COIN_RERUN_FAILED_ONLY=0 """ +import argparse import os import sys -import argparse -from textwrap import dedent from collections import OrderedDict +from textwrap import dedent from timeit import default_timer as timer -from .helper import script_dir, decorate -from .buildlog import builds from .blacklist import BlackList -from .runner import TestRunner +from .buildlog import builds +from .helper import decorate, script_dir from .parser import TestParser +from .runner import TestRunner # Should we repeat only failed tests? COIN_RERUN_FAILED_ONLY = True -COIN_THRESHOLD = 3 # report error if >= -COIN_TESTING = 5 # number of runs +COIN_THRESHOLD = 3 # report error if >= +COIN_TESTING = 5 # number of runs +TIMEOUT = 20 * 60 -if (os.environ.get("COIN_RERUN_FAILED_ONLY", "1").lower() in - "0 f false n no".split()): +if os.environ.get("COIN_RERUN_FAILED_ONLY", "1").lower() in "0 f false n no".split(): COIN_RERUN_FAILED_ONLY = False + def test_project(project, args, blacklist, runs): ret = [] + if "pypy" in builds.classifiers: + # As long as PyPy has so many bugs, we use 1 test only... + global COIN_TESTING + COIN_TESTING = runs = 1 + # ...and extend the timeout. + global TIMEOUT + TIMEOUT = 100 * 60 + # remove files from a former run for idx in range(runs): index = idx + 1 @@ -104,11 +75,14 @@ def test_project(project, args, blacklist, runs): if os.path.exists(runner.logfile) and not args.skip: os.unlink(runner.logfile) # now start the real run + rerun_list = None for idx in range(runs): index = idx + 1 runner = TestRunner(builds.selected, project, index) + # For the full Python version we need to ask the TestRunner. + builds.set_python_version(runner.get_python_version()) print() - print("********* Start testing of %s *********" % project) + print(f"********* Start testing of {project} *********") print("Config: Using", " ".join(builds.classifiers)) print() if os.path.exists(runner.logfile) and args.skip: @@ -117,85 +91,116 @@ def test_project(project, args, blacklist, runs): if index > 1 and COIN_RERUN_FAILED_ONLY: rerun = rerun_list if not rerun: - print("--- no re-runs found, stopping before test {} ---" - .format(index)) + print(f"--- no re-runs found, stopping before test {index} ---") break else: rerun = None - runner.run("RUN {}:".format(idx + 1), rerun, 10 * 60) - result = TestParser(runner.logfile) + runner.run(f"RUN {idx + 1}:", rerun, TIMEOUT) + results = TestParser(runner.logfile) r = 5 * [0] rerun_list = [] print() - for test, res in result.iter_blacklist(blacklist): - print("RES {}:".format(index), end=" ") - print("%-6s" % res, decorate(test) + "()") + fatal = False + for item in results.iter_blacklist(blacklist): + res = item.rich_result + sharp = f"#{item.sharp}" + mod_name = decorate(item.mod_name) + print(f"RES {index}: Test {sharp:>4}: {res:<6} {mod_name}()") r[0] += 1 if res == "PASS" else 0 r[1] += 1 if res == "FAIL!" else 0 - r[2] += 1 if res == "SKIPPED" else 0 # not yet supported + r[2] += 1 if res == "SKIPPED" else 0 # not yet supported r[3] += 1 if res == "BFAIL" else 0 r[4] += 1 if res == "BPASS" else 0 if res not in ("PASS", "BPASS"): - rerun_list.append(test) + rerun_list.append(item.mod_name) + # PYSIDE-1229: When a fatal error happens, bail out immediately! + if item.fatal: + fatal = item print() - print("Totals:", sum(r), "tests.", - "{} passed, {} failed, {} skipped, {} blacklisted, {} bpassed." - .format(*r)) + print( + f"Totals: {sum(r)} tests. " + f"{r[0]} passed, {r[1]} failed, {r[2]} skipped, {r[3]} blacklisted, {r[4]} bpassed." + ) print() - print("********* Finished testing of %s *********" % project) + print(f"********* Finished testing of {project} *********") print() ret.append(r) + if fatal: + print("FATAL ERROR:", fatal) + print("Repetitions cancelled!") + break + return ret, fatal, runs - return ret def main(): # create the top-level command parser start_time = timer() - all_projects = "shiboken2 pyside2 pyside2-tools".split() - tested_projects = "shiboken2 pyside2".split() + all_projects = "shiboken6 pyside6".split() + tested_projects = "shiboken6 pyside6".split() + tested_projects_quoted = " ".join("'i'" for i in tested_projects) parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, - description=dedent("""\ - Run the tests for some projects, default = '{}'. + description=dedent( + """\ + Run the tests for some projects, default = {tested_projects_quoted}. - Testing is now repeated up to {rep} times, and errors are - only reported if they occur {thr} or more times. + Testing is now repeated up to {COIN_TESTING} times, and errors are + only reported if they occur {COIN_THRESHOLD} or more times. The environment variable COIN_RERUN_FAILED_ONLY controls if errors are only repeated if there are errors. The default is "1". - """.format("' '".join(tested_projects), thr=COIN_THRESHOLD, rep=COIN_TESTING))) + """ + ), + ) subparsers = parser.add_subparsers(dest="subparser_name") # create the parser for the "test" command parser_test = subparsers.add_parser("test") group = parser_test.add_mutually_exclusive_group(required=False) - blacklist_default = os.path.join(script_dir, 'build_history', 'blacklist.txt') - group.add_argument("--blacklist", "-b", type=argparse.FileType('r'), - default=blacklist_default, - help='a Qt blacklist file (default: {})'.format(blacklist_default)) - parser_test.add_argument("--skip", action='store_true', - help="skip the tests if they were run before") - parser_test.add_argument("--environ", nargs='+', - help="use name=value ... to set environment variables") - parser_test.add_argument("--buildno", default=-1, type=int, - help="use build number n (0-based), latest = -1 (default)") - parser_test.add_argument("--projects", nargs='+', type=str, - default=tested_projects, - choices=all_projects, - help="use '{}'' (default) or other projects" - .format("' '".join(tested_projects))) + blacklist_default = os.path.join(script_dir, "build_history", "blacklist.txt") + group.add_argument( + "--blacklist", + "-b", + type=argparse.FileType("r"), + default=blacklist_default, + help=f"a Qt blacklist file (default: {blacklist_default})", + ) + parser_test.add_argument( + "--skip", action="store_true", help="skip the tests if they were run before" + ) + parser_test.add_argument( + "--environ", nargs="+", help="use name=value ... to set environment variables" + ) + parser_test.add_argument( + "--buildno", + default=-1, + type=int, + help="use build number n (0-based), latest = -1 (default)", + ) + parser_test.add_argument( + "--projects", + nargs="+", + type=str, + default=tested_projects, + choices=all_projects, + help=f"use {tested_projects_quoted} (default) or other projects", + ) parser_getcwd = subparsers.add_parser("getcwd") - parser_getcwd.add_argument("filename", type=argparse.FileType('w'), - help="write the build dir name into a file") - parser_getcwd.add_argument("--buildno", default=-1, type=int, - help="use build number n (0-based), latest = -1 (default)") - parser_list = subparsers.add_parser("list") + parser_getcwd.add_argument( + "filename", type=argparse.FileType("w"), help="write the build dir name into a file" + ) + parser_getcwd.add_argument( + "--buildno", + default=-1, + type=int, + help="use build number n (0-based), latest = -1 (default)", + ) args = parser.parse_args() if hasattr(args, "buildno"): try: builds.set_buildno(args.buildno) except IndexError: - print("history out of range. Try '%s list'" % __file__) + print(f"history out of range. Try '{__file__} list'") sys.exit(1) if args.subparser_name == "getcwd": @@ -203,7 +208,7 @@ def main(): print(builds.selected.build_dir, "written to file", args.filename.name) sys.exit(0) elif args.subparser_name == "test": - pass # we do it afterwards + pass # we do it afterwards elif args.subparser_name == "list": rp = os.path.relpath print() @@ -231,17 +236,21 @@ def main(): key, value = things os.environ[key] = value - print(dedent("""\ + version = sys.version.replace("\n", " ") + print( + dedent( + f"""\ System: - Platform={platform} - Executable={executable} - Version={version_lf} - API version={api_version} - - Environment:""") - .format(version_lf=sys.version.replace("\n", " "), **sys.__dict__)) + Platform={sys.platform} + Executable={sys.executable} + Version={version} + API version={sys.api_version} + + Environment:""" + ) + ) for key, value in sorted(os.environ.items()): - print(" {}={}".format(key, value)) + print(f" {key}={value}") print() q = 5 * [0] @@ -249,15 +258,19 @@ def main(): runs = COIN_TESTING fail_crit = COIN_THRESHOLD # now loop over the projects and accumulate + fatal = False for project in args.projects: - res = test_project(project, args, bl, runs) + res, fatal, runs = test_project(project, args, bl, runs) + if fatal: + runs = 1 for idx, r in enumerate(res): - q = list(map(lambda x, y: x+y, r, q)) + q = list(map(lambda x, y: x + y, r, q)) if len(args.projects) > 1: - print("All above projects:", sum(q), "tests.", - "{} passed, {} failed, {} skipped, {} blacklisted, {} bpassed." - .format(*q)) + print( + f"All above projects: {sum(q)} tests. " + f"{q[0]} passed, {q[1]} failed, {q[2]} skipped, {q[3]} blacklisted, {q[4]} bpassed." + ) print() tot_res = OrderedDict() @@ -265,11 +278,11 @@ def main(): for idx in range(runs): index = idx + 1 runner = TestRunner(builds.selected, project, index) - result = TestParser(runner.logfile) - for test, res in result.iter_blacklist(bl): - key = project + ":" + test + results = TestParser(runner.logfile) + for item in results.iter_blacklist(bl): + key = f"{project}:{item.mod_name}" tot_res.setdefault(key, []) - tot_res[key].append(res) + tot_res[key].append(item.rich_result) tot_flaky = 0 print("*" * 79) print("**") @@ -282,23 +295,26 @@ def main(): fail__c = res.count("FAIL!") bfail_c = res.count("BFAIL") fail2_c = fail__c + bfail_c + fatal_c = res.count("FATAL") if pass__c == len(res): continue - elif bpass_c == runs and runs > 1: + elif bpass_c >= runs and runs > 1: msg = "Remove blacklisting; test passes" - elif fail__c == runs: + elif fail__c >= runs: msg = "Newly detected Real test failure!" - elif bfail_c == runs: + elif bfail_c >= runs: msg = "Keep blacklisting ;-(" elif fail2_c > 0 and fail2_c < len(res): msg = "Flaky test" tot_flaky += 1 + elif fatal_c: + msg = "FATAL format error, repetitions aborted!" else: continue empty = False padding = 6 * runs - txt = " ".join(("{:<{width}}".format(piece, width=5) for piece in res)) - txt = (txt + padding * " ")[:padding] + txt = " ".join((f"{piece:<5}" for piece in res)) + txt = (f"{txt}{padding * ' '}")[:padding] testpad = 36 if len(test) < testpad: test += (testpad - len(test)) * " " @@ -310,7 +326,7 @@ def main(): print("*" * 79) print() if runs > 1: - print("Total flaky tests: errors but not always = {}".format(tot_flaky)) + print(f"Total flaky tests: errors but not always = {tot_flaky}") print() else: print("For info about flaky tests, we need to perform more than one run.") @@ -319,22 +335,31 @@ def main(): # nag us about unsupported projects ap, tp = set(all_projects), set(tested_projects) if ap != tp: - print("+++++ Note: please support", " ".join(ap-tp), "+++++") + print("+++++ Note: please support", " ".join(ap - tp), "+++++") print() stop_time = timer() used_time = stop_time - start_time # Now create an error if the criterion is met: try: - err_crit = "'FAIL! >= {}'".format(fail_crit) + if fatal: + raise ValueError(f"FATAL format error: {fatal}") + err_crit = f"'FAIL! >= {fail_crit}'" + fail_count = 0 for res in tot_res.values(): if res.count("FAIL!") >= fail_crit: - raise ValueError("At least one failure was not blacklisted " - "and met the criterion {}" - .format(err_crit)) - print("No test met the error criterion {}".format(err_crit)) + fail_count += 1 + if fail_count == 1: + raise ValueError(f"A test was not blacklisted and met the criterion {err_crit}") + elif fail_count > 1: + raise ValueError( + f"{fail_count} failures were not blacklisted " f"and met the criterion {err_crit}" + ) + print(f"No test met the error criterion {err_crit}") finally: print() - print("Total time of whole Python script = {:0.2f} sec".format(used_time)) + print(f"Total time of whole Python script = {used_time:0.2f} sec") print() + + # eof |