Blob Blame History Raw
#!/usr/bin/python3
"""
Several packages with various Python interpreters *Supplement* tox.
*Supplements* is a reverse dependency to *Recommends*.

See https://lists.fedoraproject.org/archives/list/python-devel@lists.fedoraproject.org/thread/NVVUXSVSPFQOWIGBE2JNI67HEO7R63ZQ/

This script:

 1) figures out all packages in the enabled repositories supplementing tox
 2) ensures there is a venv.sh test for each of them in tests.yml

That way, when we change tox (update, patch, etc.),
we will always test it with all Pythons that supplement it.
"""

import shlex
import subprocess
import sys
import yaml


def parse_python_test_arg(command):
    tokens = shlex.split(command)
    for token in tokens:
        if token.startswith('PYTHON='):
            return token.removeprefix('PYTHON=')
    # only check VERSION if PYTHON was not found
    for token in tokens:
        if token.startswith('VERSION='):
            return 'python' + token.removeprefix('VERSION=')
    raise RuntimeError(f'Could not determine the Python version from `{command}`')


# First, construct a set of various Pythons we test, e.g. {python3.10, python3.7, pypy3.6, ...}
tested_pythons = set()
with open('tests.yml') as f:
    tests_yml = yaml.safe_load(f)
# this nested structure access is quite fragile,
# but at least it should fail the test if we reach to a wrong place
for test in tests_yml[-1]['roles'][0]['tests']:
    for value in test.values():
        run = value['run']
        if run.endswith('./venv.sh'):
            tested_pythons.add(parse_python_test_arg(run))
print('Tested Pythons found in tests.yml:', file=sys.stderr)
for python in sorted(tested_pythons):
    print('    ', python, file=sys.stderr)


# Get all packages that supplement tox,
# no repo explicitly specified means we use the enabled repos on the CI system which should be what we want
repoquery_result = subprocess.check_output(['dnf', 'repoquery', '--whatsupplements', 'tox'], text=True)
supplementing_pkgs = set(repoquery_result.splitlines())


# It gets quite tricky, since packages like "pypy" can supplement tox, we get a set of provides for all of them
supplementing_pkgs_provides = {}
for nvra in supplementing_pkgs:
    repoquery_result = subprocess.check_output(['dnf', '-q', 'repoquery', '--provides', nvra], text=True)
    provides = set(repoquery_result.splitlines())
    unversioned_provides = {provide.split(' ')[0] for provide in provides}
    supplementing_pkgs_provides[nvra.rsplit('-', 2)[0]] = unversioned_provides


# We use this hack to treat -devel and -libs packages as if they were not such
def normalize_name(pkgname):
    for suffix in '-devel', '-libs':
        if pkgname.endswith(suffix):
            return pkgname.removesuffix(suffix)
    return pkgname


# Now, for each package that supplements tox, we check if there is a tested Python that *is* it
exit_code = 0
for pkg, provides in supplementing_pkgs_provides.items():
    if normalize_name(pkg) in tested_pythons:
        print(f'{pkg} is tested', file=sys.stderr)
        continue
    for provide in provides:
        if normalize_name(provide) in tested_pythons:
            print(f'{pkg} is tested (via {provide})', file=sys.stderr)
            break
    else:
        print(f'{pkg} is NOT tested', file=sys.stderr)
        exit_code = 1

sys.exit(exit_code)