mirror of
https://github.com/AdaCore/git-hooks.git
synced 2026-02-12 12:43:11 -08:00
distutils.version is now deprecated, so transition to packaging.version instead. Unfortunately, the Version object from packaging.version is not as convenient to use as distutils.version, as the comparison operators do not support the use of strings as the righ-hand-side. So users of those objects are updated accordingly. Change-Id: I72aeb40050e5da25ead616b3d9475218ba88e403 TN: V304-012
237 lines
7.9 KiB
Python
Executable File
237 lines
7.9 KiB
Python
Executable File
#! /usr/bin/env python3
|
|
|
|
from argparse import ArgumentParser
|
|
from packaging.version import Version
|
|
from e3.os.process import command_line_image, PIPE, Run
|
|
import json
|
|
import os
|
|
import shutil
|
|
import sys
|
|
|
|
# The minimum version of Python we need to run the validation testing.
|
|
MINIMUM_PYTHON_VERSION = "3.8"
|
|
|
|
# The testsuite's root directory. By construction, it is the directory
|
|
# containing this script.
|
|
TESTSUITE_ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
# The location where to store the html coverage output.
|
|
HTML_COV_DIR = os.path.join(TESTSUITE_ROOT_DIR, "htmlcov")
|
|
|
|
# The root directory of this repository. By construction, it is
|
|
# the parent directory of the TESTSUITE_ROOT_DIR.
|
|
REPOSITORY_ROOT_DIR = os.path.dirname(TESTSUITE_ROOT_DIR)
|
|
|
|
|
|
def run(cmds, **kwargs):
|
|
"""Run a program from the root of the style_checker repository.
|
|
|
|
This function first prints on stdout the command being executed,
|
|
and then executes the given command using e3.os.process.Run.
|
|
|
|
The API is the same as e3.os.process.Run.__init__ with the following
|
|
exceptions:
|
|
- The default value for the "cwd" paramater is the root of
|
|
this repository (because this is where the Python tools are
|
|
expected to be run from in order to find the various pieces).
|
|
- The default value for "output" is None (no redirection).
|
|
- The default value for "error" is None (no redirection).
|
|
|
|
:return: The e3.os.process.Run object.
|
|
"""
|
|
print("Running: {}".format(command_line_image(cmds)))
|
|
kwargs.setdefault("cwd", REPOSITORY_ROOT_DIR)
|
|
kwargs.setdefault("output", None)
|
|
kwargs.setdefault("error", None)
|
|
|
|
return Run(cmds, **kwargs)
|
|
|
|
|
|
def check_dependencies(args):
|
|
"""Check that all necessary dependencies for running the testsuite are met.
|
|
|
|
This includes dependencies coming from the style_checker itself,
|
|
as well as dependencies coming from the testsuite framework.
|
|
|
|
:param args: The object returned by ArgumentParser.parse_args.
|
|
"""
|
|
missing_deps = []
|
|
|
|
# The list of programs we need to be installed and accessible
|
|
# through the PATH.
|
|
required_programs = [
|
|
("pip3", "pip3"),
|
|
]
|
|
|
|
# The list of packages we need to be available in the Python
|
|
# distribution.
|
|
required_packages = [
|
|
"black",
|
|
"e3-core",
|
|
"pre-commit",
|
|
"pytest",
|
|
"pytest-cov",
|
|
"pytest-xdist",
|
|
]
|
|
if args.verify_style_conformance:
|
|
required_packages.append("flake8")
|
|
|
|
# First, check that the Python being used is recent enough.
|
|
python_version = Version("{v.major}.{v.minor}".format(v=sys.version_info))
|
|
if python_version < Version(MINIMUM_PYTHON_VERSION):
|
|
print(
|
|
"ERROR: Your version of Python is too old: "
|
|
"({v.major}.{v.minor}.{v.micro}-{v.releaselevel})".format(
|
|
v=sys.version_info
|
|
)
|
|
)
|
|
print(" Minimum version required: {}".format(MINIMUM_PYTHON_VERSION))
|
|
print("Aborting.")
|
|
sys.exit(1)
|
|
|
|
# Next, check that all required dependencies are there.
|
|
#
|
|
# Check the required programs are there first, as one of
|
|
# those programs (pip3) is used to check that all required
|
|
# packages have been installed.
|
|
|
|
for exe, description in required_programs:
|
|
if shutil.which(exe) is None:
|
|
missing_deps.append(description)
|
|
|
|
# If pip3 is available, use it to get the list of packages installed,
|
|
# and compare it against our list of required_packages.
|
|
|
|
if "pip3" not in missing_deps:
|
|
p = Run(["pip3", "list", "--format=json"], error=PIPE)
|
|
if p.status != 0:
|
|
print(f"ERROR: pip3 command returned nonzero: {p.status}")
|
|
print("$ " + p.command_line_image())
|
|
print(f"{p.out}")
|
|
print(f"{p.err}")
|
|
sys.exit(1)
|
|
|
|
# Load the JSON data returned by pip3, and post process it
|
|
# a little to make it more convenient to search it: pip3
|
|
# returns a list where each item is a dict representing
|
|
# a package (with "name", and "version" info provided).
|
|
#
|
|
# For our purposes, we only need the name...
|
|
package_list_from_pip3 = json.loads(p.out)
|
|
all_packages = [pkg_info["name"] for pkg_info in package_list_from_pip3]
|
|
|
|
for package_name in required_packages:
|
|
if package_name not in all_packages:
|
|
missing_deps.append(f"Python package: {package_name}")
|
|
|
|
# If anything was missing, report it and abort.
|
|
if missing_deps:
|
|
print("ERROR: The testing environment is missing the following:")
|
|
for dep in missing_deps:
|
|
print(f" - {dep}")
|
|
sys.exit(1)
|
|
|
|
|
|
def run_testsuite(args):
|
|
"""Run the testsuite part of the testing.
|
|
|
|
:param args: The object returned by ArgumentParser.parse_args.
|
|
"""
|
|
testsuite_cmd = ["python3", "-m", "pytest", "-v", "-vv"]
|
|
|
|
# Run the testsuite with --import-mode=importlib (new in pytest-6.0):
|
|
# This is an enhancement which, for our purposes, allows each testcase,
|
|
# which is implemented as one directory with one test module, to have
|
|
# the same name for that module...
|
|
#
|
|
# Note that, according to the pytest documentation, the pytest project's
|
|
# intent is to make this the default at some point.
|
|
testsuite_cmd.append("--import-mode=importlib")
|
|
|
|
if args.jobs != "1":
|
|
# We only pass the number of jobs when parallelism is actually
|
|
# requested. It avoids the pytest-xdist plugin being unnecessarily
|
|
# activated.
|
|
testsuite_cmd.extend(["-n", args.jobs])
|
|
if args.include_coverage:
|
|
testsuite_cmd.extend(
|
|
[f"--cov={REPOSITORY_ROOT_DIR}", f"--cov-report=html:{HTML_COV_DIR}"]
|
|
)
|
|
if args.testsuite_filter is not None:
|
|
testsuite_cmd.extend(["-k", args.testsuite_filter])
|
|
|
|
run(testsuite_cmd)
|
|
if args.include_coverage:
|
|
run(["python3", "-m", "coverage", "report"])
|
|
|
|
|
|
def run_style_conformance_checks(args):
|
|
"""Perform style-conformance testing.
|
|
|
|
:param args: The object returned by ArgumentParser.parse_args.
|
|
"""
|
|
return run(["pre-commit", "run", "--all-files"])
|
|
|
|
|
|
def main():
|
|
"""Implement the main subprogram for this script."""
|
|
parser = ArgumentParser(description="Run the style_checker testsuite")
|
|
parser.add_argument(
|
|
"--jobs",
|
|
"-j",
|
|
metavar="N",
|
|
default="auto",
|
|
help="Run the testsuite with N jobs in parallel."
|
|
" If not provided, the system determines the number"
|
|
" of jobs based on the number of CPU cores available.",
|
|
)
|
|
parser.add_argument(
|
|
"--no-testsuite",
|
|
dest="run_testsuite",
|
|
default=True,
|
|
action="store_false",
|
|
help="Skip running the testsuite (useful when"
|
|
" only trying to perform coding style conformance"
|
|
" checks",
|
|
)
|
|
parser.add_argument(
|
|
"--no-coverage",
|
|
dest="include_coverage",
|
|
default=True,
|
|
action="store_false",
|
|
help="Run the testsuite with coverage analysis",
|
|
)
|
|
parser.add_argument(
|
|
"--no-style-checking",
|
|
dest="verify_style_conformance",
|
|
default=True,
|
|
action="store_false",
|
|
help="Skip the coding style conformance checks",
|
|
)
|
|
parser.add_argument(
|
|
"testsuite_filter",
|
|
metavar="EXPRESSION",
|
|
nargs="?",
|
|
help="Ask pytest to restring the testing to the tests"
|
|
" matching the given substring expression (passed"
|
|
" to pytest -via -k)",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
check_dependencies(args)
|
|
|
|
print(f"Repository root dir: {REPOSITORY_ROOT_DIR}")
|
|
if args.verify_style_conformance:
|
|
p = run_style_conformance_checks(args)
|
|
if p.status != 0:
|
|
print(f"ERROR: style violations detected (status: {p.status}).")
|
|
print(" Aborting")
|
|
sys.exit(p.status)
|
|
|
|
if args.run_testsuite:
|
|
run_testsuite(args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|