mirror of
https://github.com/Nuitka/Nuitka.git
synced 2026-02-01 11:33:19 +01:00
336 lines
11 KiB
Python
336 lines
11 KiB
Python
#!/usr/bin/env python
|
|
# Copyright 2025, Tommy Li, mailto:<tommyli3318@gmail.com> find license text at end of file
|
|
|
|
|
|
"""Runner for PyPI Pytest comparison
|
|
|
|
This script automates the comparing of pytest results of a nuitka compiled wheel
|
|
using `python setup.py bdist_nuitka` to the pytest results of an uncompiled wheel
|
|
built using `python setup.py bdist_wheel` for the most popular PyPI packages.
|
|
Testing is done to ensure that nuitka is building the wheel correctly. If the
|
|
pytest run passes/fails in the same way, that means Nuitka built the wheel properly.
|
|
Else if the tests differ, then something is wrong.
|
|
Virtualenv is used to create a clean environment with no outside pollution.
|
|
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
# Find nuitka package relative to us.
|
|
sys.path.insert(
|
|
0,
|
|
os.path.normpath(
|
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..")
|
|
),
|
|
)
|
|
|
|
# isort:start
|
|
|
|
import nuitka
|
|
from nuitka.tools.environments.Virtualenv import withVirtualenv
|
|
from nuitka.tools.testing.Common import (
|
|
createSearchMode,
|
|
my_print,
|
|
reportSkip,
|
|
setup,
|
|
test_logger,
|
|
)
|
|
from nuitka.tools.testing.OutputComparison import compareOutput
|
|
from nuitka.utils.AppDirs import getCacheDir
|
|
from nuitka.utils.FileOperations import getFileContents
|
|
|
|
|
|
def executeCommand(command):
|
|
test_logger.info("Executing: %s" % command, style="blue")
|
|
|
|
return os.system(command) == 0
|
|
|
|
|
|
def gitClone(package, url, directory):
|
|
"""
|
|
Update package with git if already existing in directory
|
|
else git clone the package into directory
|
|
"""
|
|
|
|
os.chdir(directory)
|
|
if not executeCommand(
|
|
"cd %s && git fetch -q && git reset -q --hard origin && git clean -q -dfx"
|
|
% package
|
|
):
|
|
assert executeCommand(
|
|
"git clone %s %s --depth 1 --single-branch --no-tags" % (url, package)
|
|
), ("Error while git cloning package %s, aborting..." % package)
|
|
|
|
|
|
def main():
|
|
# pylint: disable=broad-except,too-many-branches,too-many-locals,too-many-statements
|
|
|
|
setup(suite="pypi")
|
|
|
|
# cache_dir is where the git clones are cached
|
|
cache_dir = getCacheDir("pypi-git-clones")
|
|
base_dir = os.getcwd()
|
|
|
|
if not os.path.isdir(cache_dir):
|
|
os.mkdir(cache_dir)
|
|
|
|
search_mode = createSearchMode()
|
|
|
|
results = []
|
|
|
|
# load json
|
|
packages = json.load(getFileContents("packages.json"))
|
|
|
|
for package_name, details in sorted(packages.items()):
|
|
active = search_mode.consider(dirname=None, filename=package_name)
|
|
|
|
if not active:
|
|
continue
|
|
|
|
if str is not bytes:
|
|
# running on python3
|
|
if package_name in ("futures", "future"):
|
|
reportSkip("Does not run on Python3", ".", package_name)
|
|
continue
|
|
|
|
if os.name == "nt":
|
|
if package_name in ("cryptography",):
|
|
reportSkip("Not working on Windows", ".", package_name)
|
|
continue
|
|
|
|
if package_name == "pyyaml":
|
|
reportSkip("Not yet supported, see Issue #476", ".", package_name)
|
|
continue
|
|
|
|
if package_name in ("pycparser", "numpy"):
|
|
reportSkip("Not yet supported, see Issue #477", ".", package_name)
|
|
continue
|
|
|
|
if package_name in (
|
|
"google-auth", # bdist_nuitka fails AttributeError: single_version_externally_managed
|
|
"jinja2", # ModuleNotFoundError: No module named 'jinja2.tests'
|
|
"pandas", # ModuleNotFoundError: No module named 'Cython'
|
|
"pytz", # need to 'make build'
|
|
"rsa", # Now uses Poetry (no setup.py)
|
|
):
|
|
continue
|
|
|
|
package_dir = os.path.join(cache_dir, package_name)
|
|
|
|
try:
|
|
gitClone(package_name, details["url"], cache_dir)
|
|
|
|
os.chdir(base_dir)
|
|
with withVirtualenv(
|
|
"venv_%s" % package_name, logger=test_logger, delete=False, style="blue"
|
|
) as venv:
|
|
dist_dir = os.path.join(package_dir, "dist")
|
|
|
|
# delete ignored tests if any
|
|
if details["ignored_tests"]:
|
|
for test in details["ignored_tests"]:
|
|
venv.runCommand("rm -rf %s" % os.path.join(package_dir, test))
|
|
|
|
# setup for pytest
|
|
cmds = [
|
|
"python -m pip install pytest",
|
|
"cd %s" % os.path.join(os.path.dirname(nuitka.__file__), ".."),
|
|
"python setup.py develop",
|
|
"cd %s" % package_dir,
|
|
]
|
|
|
|
if details["requirements_file"]:
|
|
cmds.append(
|
|
"python -m pip install -r %s" % details["requirements_file"]
|
|
)
|
|
|
|
if details.get("extra_commands"):
|
|
cmds += details["extra_commands"]
|
|
|
|
# build uncompiled .whl
|
|
cmds.append("python setup.py bdist_wheel")
|
|
|
|
venv.runCommand(commands=cmds)
|
|
|
|
# install and print out if the active .whl is compiled or not
|
|
venv.runCommand(
|
|
commands=[
|
|
"python -m pip install -U %s"
|
|
% os.path.join(dist_dir, os.listdir(dist_dir)[0]),
|
|
# use triple quotes for linux
|
|
"""python -c "print(getattr(__import__('%s'),'__compiled__','__uncompiled_version__'))" """
|
|
% details.get("package_name", package_name),
|
|
]
|
|
)
|
|
|
|
# get uncompiled pytest results
|
|
uncompiled_stdout, uncompiled_stderr = venv.runCommandWithOutput(
|
|
commands=[
|
|
"cd %s" % package_dir,
|
|
"python -m pytest --disable-warnings",
|
|
],
|
|
style="blue",
|
|
)
|
|
|
|
# clean up before building compiled .whl
|
|
cmds = ["cd %s" % package_dir, "git clean -dfx"]
|
|
|
|
if details.get("extra_commands"):
|
|
cmds += details["extra_commands"]
|
|
|
|
# build nuitka compiled .whl
|
|
cmds.append("python setup.py bdist_nuitka")
|
|
|
|
venv.runCommand(commands=cmds)
|
|
|
|
# install and print out if the active .whl is compiled or not
|
|
venv.runCommand(
|
|
commands=[
|
|
"python -m pip install -U %s"
|
|
% os.path.join(dist_dir, os.listdir(dist_dir)[0]),
|
|
# use triple quotes for linux
|
|
"""python -c "print(getattr(__import__('%s'),'__compiled__','__uncompiled_version__'))" """
|
|
% details.get("package_name", package_name),
|
|
]
|
|
)
|
|
|
|
# get compiled pytest results, may fail some tests.
|
|
(
|
|
compiled_stdout,
|
|
compiled_stderr,
|
|
_exit_code,
|
|
) = venv.runCommandWithOutput(
|
|
commands=[
|
|
"cd %s" % package_dir,
|
|
"python -m pytest --disable-warnings",
|
|
],
|
|
style="blue",
|
|
)
|
|
|
|
venv.runCommand(commands=["cd %s" % package_dir, "git clean -q -dfx"])
|
|
|
|
except Exception as e:
|
|
test_logger.warning(
|
|
"Package",
|
|
package_name,
|
|
"ran into an exception during execution, traceback: ",
|
|
)
|
|
test_logger.info(repr(e))
|
|
results.append((package_name, "ERROR", "ERROR"))
|
|
|
|
continue
|
|
|
|
# compare outputs
|
|
stdout_diff = compareOutput(
|
|
"stdout",
|
|
uncompiled_stdout,
|
|
compiled_stdout,
|
|
ignore_warnings=True,
|
|
syntax_errors=True,
|
|
)
|
|
|
|
stderr_diff = compareOutput(
|
|
"stderr",
|
|
uncompiled_stderr,
|
|
compiled_stderr,
|
|
ignore_warnings=True,
|
|
syntax_errors=True,
|
|
)
|
|
|
|
results.append((package_name, stdout_diff, stderr_diff))
|
|
|
|
exit_code = stdout_diff or stderr_diff
|
|
|
|
my_print(
|
|
"\n=================================================================================",
|
|
"\n--- %s ---" % package_name,
|
|
"exit_stdout:",
|
|
stdout_diff,
|
|
"exit_stderr:",
|
|
stderr_diff,
|
|
(
|
|
"\nError, outputs differed for package %s." % package_name
|
|
if exit_code
|
|
else "\nNo differences found for package %s." % package_name
|
|
),
|
|
"\n=================================================================================\n",
|
|
style="red" if exit_code else "green",
|
|
)
|
|
|
|
if exit_code != 0 and search_mode.abortOnFinding(
|
|
dirname=None, filename=package_name
|
|
):
|
|
break
|
|
|
|
search_mode.finish()
|
|
|
|
# give a summary of all packages
|
|
my_print(
|
|
"\n\n=====================================SUMMARY=====================================",
|
|
style="yellow",
|
|
)
|
|
|
|
for package_name, stdout_diff, stderr_diff in results:
|
|
my_print(
|
|
package_name,
|
|
"-",
|
|
end=" ",
|
|
style="red" if (stdout_diff or stderr_diff) else "green",
|
|
)
|
|
|
|
my_print(
|
|
"stdout:", stdout_diff, end=" ", style="red" if stdout_diff else "green"
|
|
)
|
|
|
|
my_print(
|
|
"stderr:", stderr_diff, end="", style="red" if stderr_diff else "green"
|
|
)
|
|
|
|
my_print(
|
|
"\n---------------------------------------------------------------------------------"
|
|
)
|
|
|
|
my_print("TOTAL NUMBER OF PACKAGES TESTED: %s" % len(results), style="yellow")
|
|
|
|
num_failed = 0
|
|
num_errors = 0
|
|
|
|
# tally the number of errors and failed
|
|
for _, y, z in results:
|
|
if type(y) is str:
|
|
# this means the package ran into an exception
|
|
num_errors += 1
|
|
elif y or z:
|
|
num_failed += 1
|
|
|
|
my_print(
|
|
"TOTAL PASSED: %s" % (len(results) - num_failed - num_errors), style="green"
|
|
)
|
|
|
|
my_print("TOTAL FAILED (differences): %s" % num_failed, style="red")
|
|
|
|
my_print("TOTAL ERRORS (exceptions): %s" % num_errors, style="red")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|
|
# Python test originally created or extracted from other peoples work. The
|
|
# parts from me are licensed as below. It is at least Free Software where
|
|
# it's copied from other people. In these cases, that will normally be
|
|
# indicated.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|