aboutsummaryrefslogtreecommitdiffstats
path: root/testing
diff options
context:
space:
mode:
Diffstat (limited to 'testing')
-rw-r--r--testing/__init__.py50
-rw-r--r--testing/blacklist.py61
-rw-r--r--testing/buildlog.py97
-rw-r--r--testing/command.py240
-rw-r--r--testing/helper.py69
-rw-r--r--testing/parser.py68
-rw-r--r--testing/runner.py159
-rw-r--r--testing/wheel_tester.py403
8 files changed, 510 insertions, 637 deletions
diff --git a/testing/__init__.py b/testing/__init__.py
index 03c590604..62614c438 100644
--- a/testing/__init__.py
+++ b/testing/__init__.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
"""
testing/__init__.py
@@ -46,19 +8,23 @@ testing/__init__.py
- define command.main as entry point
"""
+import builtins
import sys
+
from . import command
main = command.main
# modify print so that it always flushes
-__builtins__["orig_print"] = __builtins__["print"]
+builtins.orig_print = builtins.print
+
def print_flushed(*args, **kw):
orig_print(*args, **kw)
sys.stdout.flush()
-__builtins__["print"] = print_flushed
+
+builtins.print = print_flushed
print = print_flushed
diff --git a/testing/blacklist.py b/testing/blacklist.py
index f82f2b5e7..eacb51cbb 100644
--- a/testing/blacklist.py
+++ b/testing/blacklist.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
"""
testing/blacklist.py
@@ -47,13 +9,15 @@ Take a blacklist file and build classifiers for all tests.
find_matching_line() adds info using classifiers.
"""
-from .helper import decorate, StringIO
+from io import StringIO
+
from .buildlog import builds
+from .helper import decorate
class BlackList(object):
def __init__(self, blname):
- if blname == None:
+ if not blname:
f = StringIO()
self.raw_data = []
else:
@@ -63,8 +27,8 @@ class BlackList(object):
lines = self.raw_data[:]
def filtered_line(line):
- if '#' in line:
- line = line[0 : line.index('#')]
+ if "#" in line:
+ line = line[:line.index("#")]
return line.split()
# now put every bracketed line in a test
@@ -78,7 +42,6 @@ class BlackList(object):
# nothing supplied
return
- self.index = {}
for idx, line in enumerate(lines):
fline = filtered_line(line)
if not fline:
@@ -86,15 +49,15 @@ class BlackList(object):
if is_test(fline):
break
# we have a global section
- name = ''
+ name = ""
self.tests[name] = []
for idx, line in enumerate(lines):
fline = filtered_line(line)
if is_test(fline):
# a new name
name = decorate(fline[0][1:-1])
- self.tests[name] = []
- self.index[name] = idx
+ # Allow for repeated sections
+ self.tests.setdefault(name, [])
elif fline:
# a known name with a new entry
self.tests[name].append(fline)
@@ -129,4 +92,4 @@ class BlackList(object):
# found a match!
return line
else:
- return None # nothing found
+ return None # nothing found
diff --git a/testing/buildlog.py b/testing/buildlog.py
index 216282b4c..f82191f91 100644
--- a/testing/buildlog.py
+++ b/testing/buildlog.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
"""
testing/buildlog.py
@@ -48,8 +10,9 @@ BuildLog.classifiers finds the set of classifier strings.
"""
import os
-import sys
+import platform
import shutil
+import sys
from collections import namedtuple
from textwrap import dedent
@@ -67,14 +30,15 @@ class BuildLog(object):
For simplicity and readability, the log entries are named tuples.
"""
+
def __init__(self):
- history_dir = os.path.join(script_dir, 'build_history')
+ history_dir = os.path.join(script_dir, "build_history")
build_history = []
for timestamp in os.listdir(history_dir):
log_dir = os.path.join(history_dir, timestamp)
if not os.path.isdir(log_dir):
continue
- fpath = os.path.join(log_dir, 'build_dir.txt')
+ fpath = os.path.join(log_dir, "build_dir.txt")
if not os.path.exists(fpath):
continue
with open(fpath) as f:
@@ -88,10 +52,14 @@ class BuildLog(object):
build_dir = f_contents_split[0]
build_classifiers = ""
except IndexError:
- print(dedent("""
+ print(
+ dedent(
+ f"""
Error: There was an issue finding the build dir and its
- characteristics, in the following considered file: '{}'
- """.format(fpath)))
+ characteristics, in the following considered file: '{fpath}'
+ """
+ )
+ )
sys.exit(1)
if not os.path.exists(build_dir):
@@ -102,13 +70,14 @@ class BuildLog(object):
if os.path.exists(build_dir):
print("Note: build_dir was probably moved.")
else:
- print("Warning: missing build dir %s" % build_dir)
+ print(f"Warning: missing build dir {build_dir}")
continue
entry = LogEntry(log_dir, build_dir, build_classifiers)
build_history.append(entry)
# we take the latest build for now.
build_history.sort()
self.history = build_history
+ self.python_version = None
self._buildno = None
if not is_ci:
# there seems to be a timing problem in RHel 7.6, so we better don't touch it
@@ -122,11 +91,13 @@ class BuildLog(object):
continue
lst.append(log_dir)
if lst:
+
def warn_problem(func, path, exc_info):
- cls, ins, tb = exc_info
- print("rmtree({}) warning: problem with {}:\n {}: {}".format(
- func.__name__, path,
- cls.__name__, ins.args))
+ cls, ins, _ = exc_info
+ print(
+ f"rmtree({func.__name__}) warning: problem with "
+ f"{path}:\n {cls.__name__}: {ins.args}"
+ )
lst.sort()
log_dir = lst[-1]
@@ -139,7 +110,7 @@ class BuildLog(object):
shutil.rmtree(log_dir, onerror=warn_problem)
def set_buildno(self, buildno):
- self.history[buildno] # test
+ self.history[buildno] # test
self._buildno = buildno
@property
@@ -153,18 +124,23 @@ class BuildLog(object):
@property
def classifiers(self):
if not self.selected:
- raise ValueError('+++ No build with the configuration found!')
+ raise ValueError("+++ No build with the configuration found!")
# Python2 legacy: Correct 'linux2' to 'linux', recommended way.
- platform = 'linux' if sys.platform.startswith('linux') else sys.platform
- res = [platform]
+ plat_name = "linux" if sys.platform.startswith("linux") else sys.platform
+ res = [plat_name, "qt6"]
+ if is_ci:
+ res.append("ci")
if self.selected.build_classifiers:
# Use classifier string encoded into build_dir.txt file.
- res.extend(self.selected.build_classifiers.split('-'))
+ res.extend(self.selected.build_classifiers.split("-"))
else:
# the rest must be guessed from the given filename
path = self.selected.build_dir
base = os.path.basename(path)
- res.extend(base.split('-'))
+ res.extend(base.split("-"))
+ # add exact Python version
+ if self.python_version:
+ res.append("py" + ".".join(map(str, self.python_version)))
# add all the python and qt subkeys
for entry in res:
parts = entry.split(".")
@@ -172,6 +148,13 @@ class BuildLog(object):
key = ".".join(parts[:idx])
if key not in res:
res.append(key)
+ # Allow to check the processor.
+ # This gives "i386" or "arm" on macOS.
+ res.append(platform.processor())
return res
+ def set_python_version(self, version_triple):
+ self.python_version = version_triple
+
+
builds = BuildLog()
diff --git a/testing/command.py b/testing/command.py
index 886ab3e74..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
@@ -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,12 +91,11 @@ 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)
+ runner.run(f"RUN {idx + 1}:", rerun, TIMEOUT)
results = TestParser(runner.logfile)
r = 5 * [0]
rerun_list = []
@@ -130,12 +103,12 @@ def test_project(project, args, blacklist, runs):
fatal = False
for item in results.iter_blacklist(blacklist):
res = item.rich_result
- sharp = "#" + str(item.sharp)
+ sharp = f"#{item.sharp}"
mod_name = decorate(item.mod_name)
- print("RES {index}: Test {sharp:>4}: {res:<6} {mod_name}()".format(**locals()))
+ 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"):
@@ -144,67 +117,90 @@ def test_project(project, args, blacklist, runs):
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
+ return ret, fatal, runs
+
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":
@@ -212,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()
@@ -240,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}
+ Platform={sys.platform}
+ Executable={sys.executable}
+ Version={version}
+ API version={sys.api_version}
- Environment:""")
- .format(version_lf=sys.version.replace("\n", " "), **sys.__dict__))
+ Environment:"""
+ )
+ )
for key, value in sorted(os.environ.items()):
- print(" {}={}".format(key, value))
+ print(f" {key}={value}")
print()
q = 5 * [0]
@@ -260,16 +260,17 @@ def main():
# now loop over the projects and accumulate
fatal = False
for project in args.projects:
- res, fatal = 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()
@@ -279,7 +280,7 @@ def main():
runner = TestRunner(builds.selected, project, index)
results = TestParser(runner.logfile)
for item in results.iter_blacklist(bl):
- key = project + ":" + item.mod_name
+ key = f"{project}:{item.mod_name}"
tot_res.setdefault(key, [])
tot_res[key].append(item.rich_result)
tot_flaky = 0
@@ -312,8 +313,8 @@ def main():
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)) * " "
@@ -325,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.")
@@ -334,7 +335,7 @@ 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()
@@ -342,16 +343,23 @@ def main():
# Now create an error if the criterion is met:
try:
if fatal:
- raise ValueError("FATAL format error:", fatal)
- err_crit = "'FAIL! >= {}'".format(fail_crit)
+ 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
diff --git a/testing/helper.py b/testing/helper.py
index 88da48c6e..d89f0d849 100644
--- a/testing/helper.py
+++ b/testing/helper.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
"""
testing/helper.py
@@ -46,34 +8,17 @@ Some tools that do not fit elsewhere.
"""
import os
-import sys
-from collections import namedtuple
-
-PY3 = sys.version_info[0] == 3 # from the six module
-from subprocess import PIPE
-if PY3:
- from subprocess import TimeoutExpired
- from io import StringIO
-else:
- class SubprocessError(Exception): pass
- # this is a fake, just to keep the source compatible.
- # timeout support is in python 3.3 and above.
- class TimeoutExpired(SubprocessError): pass
- from StringIO import StringIO
-
script_dir = os.path.dirname(os.path.dirname(__file__))
+
def decorate(mod_name):
"""
Write the combination of "modulename_funcname"
in the Qt-like form "modulename::funcname"
"""
- if "_" not in mod_name:
- return mod_name
- if "::" in mod_name:
+ if "_" not in mod_name or "::" in mod_name:
return mod_name
- name, rest = mod_name.split("_", 1)
- return name + "::" + rest
-
-#eof
+ else:
+ name, rest = mod_name.split("_", 1)
+ return f"{name}::{rest}"
diff --git a/testing/parser.py b/testing/parser.py
index 1d5c34680..a01c4d029 100644
--- a/testing/parser.py
+++ b/testing/parser.py
@@ -1,48 +1,10 @@
-#############################################################################
-##
-## 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
import os
import re
from collections import namedtuple
-from .helper import StringIO
+from io import StringIO
"""
testing/parser.py
@@ -82,7 +44,9 @@ _TEST_PAT_PRE = r"""
([0-9]+) # sharp
: # colon symbol ':'
"""
-_TEST_PAT = _TEST_PAT_PRE + r"""
+_TEST_PAT = (
+ _TEST_PAT_PRE
+ + r"""
\s+ # some WS
([\w-]+) # mod_name
.*? # whatever (non greedy)
@@ -98,14 +62,17 @@ _TEST_PAT = _TEST_PAT_PRE + r"""
\s* # any WS
$ # end
"""
+)
# validation of our pattern:
assert re.match(_TEST_PAT, _EXAMPLE.splitlines()[5], re.VERBOSE)
assert len(re.match(_TEST_PAT, _EXAMPLE.splitlines()[5], re.VERBOSE).groups()) == 8
assert len(re.match(_TEST_PAT, _EXAMPLE.splitlines()[7], re.VERBOSE).groups()) == 8
-TestResult = namedtuple("TestResult", "idx n sharp mod_name passed "
- "code time fatal rich_result".split())
+TestResult = namedtuple(
+ "TestResult", "idx n sharp mod_name passed " "code time fatal rich_result".split()
+)
+
def _parse_tests(test_log):
"""
@@ -126,13 +93,13 @@ def _parse_tests(test_log):
match = re.match(pat, line, re.VERBOSE)
if match and line.split()[-1] != "sec":
# don't change the number of lines
- lines[idx : idx + 2] = [line.rstrip() + lines[idx + 1], ""]
+ lines[idx:idx + 2] = [line.rstrip() + lines[idx + 1], ""]
pat = _TEST_PAT
for line in lines:
match = re.match(pat, line, re.VERBOSE)
if match:
- idx, n, sharp, mod_name, much_stuff, code1, code2, tim = tup = match.groups()
+ idx, n, sharp, mod_name, much_stuff, code1, code2, tim = match.groups()
# either code1 or code2 is None
code = code1 or code2
idx, n, sharp, code, tim = int(idx), int(n), int(sharp), code.lower(), float(tim)
@@ -145,11 +112,10 @@ def _parse_tests(test_log):
# Use "if idx + 1 != item.idx or idx == 42:"
if idx + 1 != item.idx:
# The numbering is disrupted. Provoke an error in this line!
- passed = False
- code += ", but lines are disrupted!"
- result[idx] = item._replace(passed=False,
- code=item.code + ", but lines are disrupted!",
- fatal=True)
+ code = f"{code}, but lines are disrupted!"
+ result[idx] = item._replace(
+ passed=False, code=f"{item.code}, but lines are disrupted!", fatal=True
+ )
break
return result
diff --git a/testing/runner.py b/testing/runner.py
index 83b7b08e6..b52ac4937 100644
--- a/testing/runner.py
+++ b/testing/runner.py
@@ -1,56 +1,14 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python project.
-##
-## $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
+import inspect
import os
-import sys
import re
import subprocess
-import inspect
-
-from collections import namedtuple
+import sys
+from subprocess import TimeoutExpired
from textwrap import dedent
-from .buildlog import builds
-from .helper import decorate, PY3, TimeoutExpired
-
# Get the dir path to the utils module
try:
this_file = __file__
@@ -58,10 +16,11 @@ except NameError:
this_file = sys.argv[0]
this_file = os.path.abspath(this_file)
this_dir = os.path.dirname(this_file)
-build_scripts_dir = os.path.abspath(os.path.join(this_dir, '../build_scripts'))
+build_scripts_dir = os.path.abspath(os.path.join(this_dir, ".."))
sys.path.append(build_scripts_dir)
-from utils import detect_clang
+from build_scripts.utils import detect_clang
+
class TestRunner(object):
def __init__(self, log_entry, project, index):
@@ -70,23 +29,48 @@ class TestRunner(object):
self.test_dir = os.path.join(built_path, project)
log_dir = log_entry.log_dir
if index is not None:
- self.logfile = os.path.join(log_dir, project + ".{}.log".format(index))
+ self.logfile = os.path.join(log_dir, f"{project}.{index}.log")
else:
- self.logfile = os.path.join(log_dir, project + ".log")
- os.environ['CTEST_OUTPUT_ON_FAILURE'] = '1'
+ self.logfile = os.path.join(log_dir, f"{project}.log")
+ os.environ["CTEST_OUTPUT_ON_FAILURE"] = "1"
self._setup_clang()
self._setup()
+ def get_python_version(self):
+ """
+ Finding the exact Python version.
+ ---------------------------------
+
+ This is done by asking the interpreter, because it cannot reliably
+ be found from any file name parsing as a triple.
+
+ Note: We need to look into the CMakeCache.txt file to find out
+ what CMake has found as the Python interpreter to use.
+ This is *not* necessarily the same Python that runs this script,
+ otherwise we could use the version info directly.
+ """
+ look_python = os.path.join(self.test_dir, "CMakeCache.txt")
+ look_for = "PYTHON_EXECUTABLE:FILEPATH="
+ with open(look_python) as f:
+ for line in f:
+ if line.startswith(look_for):
+ python_exec = line.split("=")[-1].strip()
+ res = subprocess.run([python_exec, "-c",
+ "import sys;print(sys.version_info[:3])"],
+ capture_output=True)
+ return eval(res.stdout.decode("utf-8"))
+ return None
+
def _setup_clang(self):
if sys.platform != "win32":
return
clang_dir = detect_clang()
if clang_dir[0]:
- clang_bin_dir = os.path.join(clang_dir[0], 'bin')
- path = os.environ.get('PATH')
- if not clang_bin_dir in path:
- os.environ['PATH'] = clang_bin_dir + os.pathsep + path
- print("Adding %s as detected by %s to PATH" % (clang_bin_dir, clang_dir[1]))
+ clang_bin_dir = os.path.join(clang_dir[0], "bin")
+ path = os.environ.get("PATH")
+ if clang_bin_dir not in path:
+ os.environ["PATH"] = clang_bin_dir + os.pathsep + path
+ print(f"Adding {clang_bin_dir} as detected by {clang_dir[1]} to PATH")
def _find_ctest_in_file(self, file_name):
"""
@@ -103,14 +87,16 @@ class TestRunner(object):
# We have probably forgotten to build the tests.
# Give a nice error message with a shortened but exact path.
rel_path = os.path.relpath(file_name)
- msg = dedent("""\n
- {line}
- ** ctest is not in '{}'.
+ msg = dedent(
+ f"""\n
+ {'*' * 79}
+ ** ctest is not in '{rel_path}'.
* Did you forget to build the tests with '--build-tests' in setup.py?
- """).format(rel_path, line=79 * "*")
+ """
+ )
raise RuntimeError(msg)
# the ctest program is on the left to look_for
- assert line, "Did not find {}".format(look_for)
+ assert line, f"Did not find {look_for}"
ctest = re.search(r'(\S+|"([^"]+)")\s+' + look_for, line).groups()
return ctest[1] or ctest[0]
@@ -130,8 +116,9 @@ class TestRunner(object):
path = os.path.join(self.test_dir, candidate)
if os.path.exists(path):
return self._find_ctest_in_file(path)
- raise RuntimeError('Cannot find any of the build system files {}.'.format(
- ', '.join(candidate_files)))
+ raise RuntimeError(
+ "Cannot find any of the build system files " f"{', '.join(candidate_files)}."
+ )
def _setup(self):
self.ctestCommand = self._find_ctest()
@@ -155,16 +142,20 @@ class TestRunner(object):
# without a caret are interpreted as such which leads to weirdness.
# Since we have all commands with explicit paths and don't use shell
# commands, this should work fine.
- print(dedent("""\
- running {cmd}
- in {test_dir}
- """).format(**self.__dict__))
- ctest_process = subprocess.Popen(self.cmd,
- cwd=self.test_dir,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
+ print(
+ dedent(
+ f"""\
+ running {self.cmd}
+ in {self.test_dir}
+ """
+ )
+ )
+ ctest_process = subprocess.Popen(
+ self.cmd, cwd=self.test_dir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
+ )
+
def py_tee(input, output, label):
- '''
+ """
A simple (incomplete) tee command in Python
This script simply logs everything from input to output
@@ -179,7 +170,8 @@ class TestRunner(object):
The special escape is for the case of an embedded file in
the output.
- '''
+ """
+
def xprint(*args, **kw):
print(*args, file=output, **kw)
@@ -199,26 +191,20 @@ class TestRunner(object):
if line.startswith(text_z):
labelled = True
- tee_src = dedent("""\
- from __future__ import print_function
- import sys
- {}
- py_tee(sys.stdin, sys.stdout, '{label}')
- """).format(dedent(inspect.getsource(py_tee)), label=label)
+ tee_src = dedent(inspect.getsource(py_tee))
+ tee_src = f"import sys\n{tee_src}\npy_tee(sys.stdin, sys.stdout, '{label}')"
tee_cmd = (sys.executable, "-E", "-u", "-c", tee_src)
- tee_process = subprocess.Popen(tee_cmd,
- cwd=self.test_dir,
- stdin=ctest_process.stdout)
+ tee_process = subprocess.Popen(tee_cmd, cwd=self.test_dir, stdin=ctest_process.stdout)
try:
comm = tee_process.communicate
- output = (comm(timeout=timeout) if PY3 else comm())[0]
+ _ = comm(timeout=timeout)[0]
except (TimeoutExpired, KeyboardInterrupt):
print()
print("aborted, partial result")
ctest_process.kill()
- outs, errs = ctest_process.communicate()
+ _ = ctest_process.communicate()
# ctest lists to a temp file. Move it to the log
- tmp_name = self.logfile + ".tmp"
+ tmp_name = f"{self.logfile}.tmp"
if os.path.exists(tmp_name):
if os.path.exists(self.logfile):
os.unlink(self.logfile)
@@ -240,4 +226,3 @@ class TestRunner(object):
words = "^(" + "|".join(rerun) + ")$"
cmd += ("--tests-regex", words)
self._run(cmd, label, timeout)
-# eof
diff --git a/testing/wheel_tester.py b/testing/wheel_tester.py
index 100fd18f7..b36ee55a4 100644
--- a/testing/wheel_tester.py
+++ b/testing/wheel_tester.py
@@ -1,41 +1,5 @@
-#############################################################################
-##
-## Copyright (C) 2019 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$
-##
-#############################################################################
+# 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
"""
This script is used by Coin (coin_test_instructions.py specifically) to
@@ -51,10 +15,16 @@ looked up in your PATH.
Make sure that some generated wheels already exist in the dist/
directory (e.g. setup.py bdist_wheel was already executed).
"""
-from __future__ import print_function, absolute_import
+import os
+import platform
+import shutil
+import sys
+import tempfile
+import logging
from argparse import ArgumentParser, RawTextHelpFormatter
-import os, sys
+from pathlib import Path
+from configparser import ConfigParser
try:
this_file = __file__
@@ -62,38 +32,37 @@ except NameError:
this_file = sys.argv[0]
this_file = os.path.abspath(this_file)
this_dir = os.path.dirname(this_file)
-setup_script_dir = os.path.abspath(os.path.join(this_dir, '..'))
+setup_script_dir = os.path.abspath(os.path.join(this_dir, ".."))
sys.path.append(setup_script_dir)
-from build_scripts.utils import find_files_using_glob
-from build_scripts.utils import find_glob_in_path
-from build_scripts.utils import run_process, run_process_output
-from build_scripts.utils import rmtree
-import distutils.log as log
-import platform
+from build_scripts.utils import (find_files_using_glob, find_glob_in_path, # noqa: E402
+ remove_tree, run_process, run_process_output)
+from build_scripts.log import log # noqa: E402
-log.set_verbosity(1)
+log.setLevel(logging.DEBUG)
+
+NEW_WHEELS = False
def find_executable(executable, command_line_value):
value = command_line_value
- option_str = '--{}'.format(executable)
+ option_str = f"--{executable}"
if value:
- log.info("{} option given: {}".format(option_str, value))
+ log.info(f"{option_str} option given: {value}")
if not os.path.exists(value):
- raise RuntimeError("No executable exists at: {}".format(value))
+ raise RuntimeError(f"No executable exists at: {value}")
else:
- log.info("No {} option given, trying to find {} in PATH.".format(option_str, executable))
+ log.info(f"No {option_str} option given, trying to find {executable} in PATH.")
paths = find_glob_in_path(executable)
- log.info("{} executables found in PATH: {}".format(executable, paths))
+ log.info(f"{executable} executables found in PATH: {paths}")
if not paths:
raise RuntimeError(
- "No {} option was specified and no {} was found "
- "in PATH.".format(option_str, executable))
+ f"No {option_str} option was specified and no {executable} was " "found in PATH."
+ )
else:
value = paths[0]
- log.info("Using {} found in PATH: {}".format(executable, value))
+ log.info(f"Using {executable} found in PATH: {value}")
log.info("")
return value
@@ -102,8 +71,8 @@ QMAKE_PATH = None
CMAKE_PATH = None
-def get_wheels_dir():
- return os.path.join(setup_script_dir, "dist")
+def get_wheels_dir(dir_name):
+ return os.path.join(setup_script_dir, dir_name)
def get_examples_dir():
@@ -111,9 +80,13 @@ def get_examples_dir():
def package_prefix_names():
- # Note: shiboken2_generator is not needed for compile_using_pyinstaller,
+ # Note: shiboken6_generator is not needed for compile_using_nuitka,
# but building modules with cmake needs it.
- return ["shiboken2", "shiboken2_generator", "PySide2"]
+ if NEW_WHEELS:
+ return ["shiboken6", "shiboken6_generator", "PySide6_Essentials", "PySide6_Addons",
+ "PySide6"]
+ else:
+ return ["shiboken6", "shiboken6_generator", "PySide6"]
def clean_egg_info():
@@ -125,16 +98,16 @@ def clean_egg_info():
# safe to do so.
paths = find_files_using_glob(setup_script_dir, "*.egg-info")
for p in paths:
- log.info("Removing {}".format(p))
- rmtree(p)
+ log.info(f"Removing {p}")
+ remove_tree(p)
def install_wheel(wheel_path):
- log.info("Installing wheel: {}".format(wheel_path))
+ log.info(f"Installing wheel: {wheel_path}")
exit_code = run_process([sys.executable, "-m", "pip", "install", wheel_path])
log.info("")
if exit_code:
- raise RuntimeError("Error while installing wheel {}".format(wheel_path))
+ raise RuntimeError(f"Error while installing wheel {wheel_path}")
def try_install_wheels(wheels_dir, py_version):
@@ -143,115 +116,162 @@ def try_install_wheels(wheels_dir, py_version):
all_wheels = find_files_using_glob(wheels_dir, all_wheels_pattern)
if len(all_wheels) > 1:
- log.info("Found the following wheels in {}: ".format(wheels_dir))
+ log.info(f"Found the following wheels in {wheels_dir}: ")
for wheel in all_wheels:
log.info(wheel)
else:
- log.info("No wheels found in {}".format(wheels_dir))
+ log.info(f"No wheels found in {wheels_dir}")
log.info("")
for p in package_prefix_names():
- log.info("Trying to install {p}:".format(**locals()))
- pattern = "{}-*cp{}*.whl".format(p, int(float(py_version)))
+ log.info(f"Trying to install {p}:")
+ pattern = f"{p}-*cp{int(float(py_version))}*.whl"
files = find_files_using_glob(wheels_dir, pattern)
if files and len(files) == 1:
wheel_path = files[0]
install_wheel(wheel_path)
elif len(files) > 1:
- raise RuntimeError("More than one wheel found for specific {p} version."
- .format(**locals()))
+ raise RuntimeError(f"More than one wheel found for specific {p} version.")
else:
- raise RuntimeError("No {p} wheels compatible with Python {py_version} found "
- "for testing.".format(**locals()))
-
-
-def is_unix():
- if sys.platform.startswith("linux") or sys.platform == "darwin":
- return True
- return False
+ raise RuntimeError(
+ f"No {p} wheels compatible with Python {py_version} found " f"for testing."
+ )
def generate_build_cmake():
- args = [CMAKE_PATH]
- if is_unix():
- args.extend(["-G", "Unix Makefiles"])
- else:
- args.extend(["-G", "NMake Makefiles"])
- args.append("-DCMAKE_BUILD_TYPE=Release")
- args.append("-Dpython_interpreter={}".format(sys.executable))
-
- # Specify prefix path so find_package(Qt5) works.
- qmake_dir = os.path.abspath(os.path.join(os.path.dirname(QMAKE_PATH), ".."))
- args.append("-DCMAKE_PREFIX_PATH={}".format(qmake_dir))
-
- args.append("..")
-
+ # Specify prefix path so find_package(Qt6) works.
+ qmake_dir = Path(QMAKE_PATH).resolve().parent.parent
+ args = [CMAKE_PATH, "-G", "Ninja", "-DCMAKE_BUILD_TYPE=Release",
+ f"-Dpython_interpreter={sys.executable}",
+ f"-DCMAKE_PREFIX_PATH={qmake_dir}",
+ ".."]
exit_code = run_process(args)
if exit_code:
raise RuntimeError("Failure while running cmake.")
log.info("")
-def generate_build_qmake():
- exit_code = run_process([QMAKE_PATH, "..", "python_interpreter={}".format(sys.executable)])
- if exit_code:
- raise RuntimeError("Failure while running qmake.")
- log.info("")
-
-
def raise_error_pyinstaller(msg):
print()
- print("PYINST: {msg}".format(**locals()))
- print("PYINST: sys.version = {}".format(sys.version.splitlines()[0]))
- print("PYINST: platform.platform() = {}".format(platform.platform()))
+ print(f"PYINST: {msg}")
+ print(f"PYINST: sys.version = {sys.version.splitlines()[0]}")
+ print(f"PYINST: platform.platform() = {platform.platform()}")
print("PYINST: See the error message above.")
print()
for line in run_process_output([sys.executable, "-m", "pip", "list"]):
- print("PyInstaller pip list: ", line)
+ print(f"PyInstaller pip list: {line}")
print()
- raise(RuntimeError(msg))
+ raise (RuntimeError(msg))
def compile_using_pyinstaller():
- src_path = os.path.join("..", "hello.py")
+ _ = os.path.join("..", "hello.py")
spec_path = os.path.join("..", "hello_app.spec")
exit_code = run_process([sys.executable, "-m", "PyInstaller", spec_path])
- # to create the spec file, this setting was used:
- #"--name=hello_app", "--console", "--log-level=DEBUG", src_path])
- # By using a spec file, we avoid all the probing that might disturb certain
- # platforms and also save some analysis time.
+ # to create the spec file, this setting was used:
+ # "--name=hello_app", "--console", "--log-level=DEBUG", src_path])
+ # By using a spec file, we avoid all the probing that might disturb certain
+ # platforms and also save some analysis time.
if exit_code:
# 2019-04-28 Raising on error is again enabled
raise_error_pyinstaller("Failure while compiling script using PyInstaller.")
log.info("")
-def run_make():
- args = []
- if is_unix():
- executable = "make"
+def test_nuitka(example):
+ testprog = "Nuitka"
+ name = os.path.splitext(os.path.basename(example))[0]
+ print(f"Running {testprog} test of {name}")
+ current_dir = os.getcwd()
+ result = False
+ tmpdirname = tempfile.mkdtemp()
+ try:
+ os.chdir(tmpdirname)
+ cmd = [sys.executable, "-m", "nuitka", "--run", example] # , "--standalone"]
+ _ = run_process(cmd)
+ result = True
+ except RuntimeError as e:
+ print(str(e))
+ finally:
+ os.chdir(current_dir)
+ print(f"Executable is in {tmpdirname}")
+ return result
+
+
+def run_nuitka_test(example):
+ if test_nuitka(example):
+ log.info("")
else:
- executable = "nmake"
- args.append(executable)
-
+ raise RuntimeError(f"Failure running {example} with Nuitka.")
+
+
+def _run_deploy_test(example, tmpdirname):
+ """Helper for running deployment and example."""
+ main_file = None
+ for py_file in example.glob("*.py"):
+ shutil.copy(py_file, tmpdirname)
+ if not main_file or py_file.name == "main.py":
+ main_file = py_file
+ deploy_tool = Path(sys.executable).parent / "pyside6-deploy"
+ cmd = [os.fspath(deploy_tool), "-f", main_file.name, "--init"]
+ if run_process(cmd) != 0:
+ raise RuntimeError("Error creating pysidedeploy.spec")
+
+ config_file = Path(tmpdirname) / "pysidedeploy.spec"
+ parser = ConfigParser(comment_prefixes="/", allow_no_value=True)
+ parser.read(config_file)
+ parser.set("nuitka", "extra_args", "--verbose --assume-yes-for-downloads")
+ with open(config_file, "w+") as config_file_writer:
+ parser.write(config_file_writer, space_around_delimiters=True)
+
+ cmd = [os.fspath(deploy_tool), "-f", "-c", os.fspath(config_file)]
+ if run_process(cmd) != 0:
+ raise RuntimeError("Error deploying")
+
+ suffix = "exe" if sys.platform == "win32" else "bin"
+
+ if sys.platform != "darwin":
+ binary = f"{tmpdirname}/{main_file.stem}.{suffix}"
+ else:
+ binary = f"{tmpdirname}/pyside_app_demo.app/Contents/MacOS/{main_file.stem}"
+
+ if run_process([binary]) != 0:
+ raise RuntimeError("Error running the deployed example")
+ return True
+
+
+def run_deploy_test(example):
+ """Test pyside6-deploy."""
+ log.info(f"Running deploy test of {example}")
+ current_dir = Path.cwd()
+ result = False
+ with tempfile.TemporaryDirectory() as tmpdirname:
+ try:
+ os.chdir(tmpdirname)
+ result = _run_deploy_test(example, tmpdirname)
+ except RuntimeError as e:
+ log.error(str(e))
+ raise e
+ finally:
+ os.chdir(os.fspath(current_dir))
+ state = "succeeded" if result else "failed"
+ log.info(f"Deploy test {state}")
+ return result
+
+
+def run_ninja():
+ args = ["ninja"]
exit_code = run_process(args)
if exit_code:
- raise RuntimeError("Failure while running {}.".format(executable))
+ raise RuntimeError(f"Failure while running {' '.join(args)}.")
log.info("")
-def run_make_install():
- args = []
- if is_unix():
- executable = "make"
- else:
- executable = "nmake"
- args.append(executable)
- args.append("install")
-
+def run_ninja_install():
+ args = ["ninja", "install"]
exit_code = run_process(args)
if exit_code:
- raise RuntimeError("Failed while running {} install.".format(executable))
+ raise RuntimeError(f"Failed while running {' '.join(args)} install.")
log.info("")
@@ -259,28 +279,28 @@ def run_compiled_script(binary_path):
args = [binary_path]
exit_code = run_process(args)
if exit_code:
- raise_error_pyinstaller("Failure while executing compiled script: {}".format(binary_path))
+ raise_error_pyinstaller(f"Failure while executing compiled script: {binary_path}")
log.info("")
-def execute_script(script_path):
- args = [sys.executable, script_path]
+def execute_script(script_path, *extra):
+ args = list(map(str, (sys.executable, script_path) + extra))
exit_code = run_process(args)
if exit_code:
- raise RuntimeError("Failure while executing script: {}".format(script_path))
+ raise RuntimeError(f"Failure while executing script: {script_path}")
log.info("")
def prepare_build_folder(src_path, build_folder_name):
build_path = os.path.join(src_path, build_folder_name)
- # The script can be called for both Python 2 and Python 3 wheels, so
+ # The script can be called for Python 3 wheels, so
# preparing a build folder should clean any previous existing build.
if os.path.exists(build_path):
- log.info("Removing {}".format(build_path))
- rmtree(build_path)
+ log.info(f"Removing {build_path}")
+ remove_tree(build_path)
- log.info("Creating {}".format(build_path))
+ log.info(f"Creating {build_path}")
os.makedirs(build_path)
os.chdir(build_path)
@@ -288,65 +308,102 @@ def prepare_build_folder(src_path, build_folder_name):
def try_build_examples():
examples_dir = get_examples_dir()
- # This script should better go to the last place, here.
- # But because it is most likely to break, we put it here for now.
- log.info("Attempting to build hello.py using PyInstaller.")
- # PyInstaller is loaded by coin_build_instructions.py, but not when
- # testing directly this script.
- src_path = os.path.join(examples_dir, "installer_test")
- prepare_build_folder(src_path, "pyinstaller")
-
- compile_using_pyinstaller()
- run_compiled_script(os.path.join(src_path,
- "pyinstaller", "dist", "hello_app", "hello_app"))
+ # Disabled PyInstaller until it supports PySide 6
+ if False:
+ # But because it is most likely to break, we put it here for now.
+ log.info("Attempting to build hello.py using PyInstaller.")
+ # PyInstaller is loaded by coin_build_instructions.py, but not when
+ # testing directly this script.
+ src_path = os.path.join(examples_dir, "installer_test")
+ prepare_build_folder(src_path, "pyinstaller")
+ compile_using_pyinstaller()
+ run_compiled_script(os.path.join(src_path, "pyinstaller", "dist", "hello_app", "hello_app"))
+
+ log.info("Attempting to build hello.py using Nuitka.")
+ src_path = Path(examples_dir) / "installer_test"
+
+ # disable for windows as it Nuitka --onefile deployment in Windows
+ # requires DependencyWalker. Download and installing will slow down
+ # Coin
+ if sys.platform != "win32":
+ run_deploy_test(src_path)
+
+ if False: # pre 6.4.1, kept for reference
+ # Nuitka is loaded by coin_build_instructions.py, but not when
+ # testing directly this script.
+ run_nuitka_test(os.fspath(src_path / "hello.py"))
log.info("Attempting to build and run samplebinding using cmake.")
src_path = os.path.join(examples_dir, "samplebinding")
prepare_build_folder(src_path, "cmake")
generate_build_cmake()
- run_make()
- run_make_install()
+ run_ninja()
+ run_ninja_install()
execute_script(os.path.join(src_path, "main.py"))
log.info("Attempting to build scriptableapplication using cmake.")
src_path = os.path.join(examples_dir, "scriptableapplication")
prepare_build_folder(src_path, "cmake")
generate_build_cmake()
- run_make()
-
- log.info("Attempting to build scriptableapplication using qmake.")
- src_path = os.path.join(examples_dir, "scriptableapplication")
- prepare_build_folder(src_path, "qmake")
- generate_build_qmake()
- run_make()
-
-
-def run_wheel_tests(install_wheels):
- wheels_dir = get_wheels_dir()
- py_version = "{v.major}.{v.minor}".format(v=sys.version_info)
+ run_ninja()
+
+ if sys.version_info[:2] >= (3, 7):
+ log.info("Checking Python Interface Files in Python 3 with all features selected")
+ with tempfile.TemporaryDirectory() as tmpdirname:
+ src_path = Path(tmpdirname) / "pyi_test"
+ pyi_script_dir = Path(setup_script_dir) / "sources" / "pyside6" / "PySide6" / "support"
+ execute_script(
+ pyi_script_dir / "generate_pyi.py",
+ "all",
+ "--outpath",
+ src_path,
+ "--feature",
+ "snake_case",
+ "true_property",
+ )
+ from PySide6 import __all__ as modules
+
+ for modname in modules:
+ # PYSIDE-1735: pyi files are no longer compatible with Python.
+ # XXX Maybe add a test with Mypy here?
+ pass # execute_script(src_path / f"{modname}.pyi")
+
+
+def run_wheel_tests(install_wheels, wheels_dir_name):
+ wheels_dir = get_wheels_dir(wheels_dir_name)
+ py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
if install_wheels:
log.info("Attempting to install wheels.\n")
try_install_wheels(wheels_dir, py_version)
log.info("Attempting to build examples.\n")
- try_build_examples()
+ bin_dir = os.fspath(Path(sys.executable).parent)
+ path = os.environ["PATH"]
+ if bin_dir not in path:
+ log.info(f"Adding {bin_dir} to PATH...")
+ os.environ["PATH"] = f"{bin_dir}{os.pathsep}{path}"
+ try_build_examples()
log.info("All tests passed!")
if __name__ == "__main__":
- parser = ArgumentParser(description="wheel_tester",
- formatter_class=RawTextHelpFormatter)
- parser.add_argument('--no-install-wheels', '-n', action='store_true',
- help='Do not install wheels'
- ' (for developer builds with virtualenv)')
- parser.add_argument("--qmake", type=str,
- help="Path to qmake")
- parser.add_argument("--cmake", type=str,
- help="Path to cmake")
+ parser = ArgumentParser(description="wheel_tester", formatter_class=RawTextHelpFormatter)
+ parser.add_argument(
+ "--no-install-wheels",
+ "-n",
+ action="store_true",
+ help="Do not install wheels" " (for developer builds with virtualenv)",
+ )
+ parser.add_argument("--qmake", type=str, help="Path to qmake")
+ parser.add_argument("--cmake", type=str, help="Path to cmake")
+ parser.add_argument("--wheels-dir", type=str, help="Path to where the wheels are",
+ default="dist")
+ parser.add_argument("--new", action="store_true", help="Option to test new wheels")
options = parser.parse_args()
- QMAKE_PATH = find_executable('qmake', options.qmake)
- CMAKE_PATH = find_executable('cmake', options.cmake)
+ QMAKE_PATH = find_executable("qmake", options.qmake)
+ CMAKE_PATH = find_executable("cmake", options.cmake)
+ NEW_WHEELS = options.new
- run_wheel_tests(not options.no_install_wheels)
+ run_wheel_tests(not options.no_install_wheels, options.wheels_dir)