Compare commits

..

4 commits

Author SHA1 Message Date
Petr Šplíchal
9358fa66bc Add python-tox to the list of relevant components 2018-09-19 13:57:20 +02:00
Petr Šplíchal
b3c9a16a9d Update the list of components 2018-09-19 10:33:46 +02:00
Petr Šplíchal
3903c0b861 Incorporate pull request feedback 2018-09-19 10:21:05 +02:00
Petr Šplíchal
7c561e2e11 Initial example of the Flexible Metadata Format 2018-09-18 12:17:36 +02:00
14 changed files with 33 additions and 549 deletions

View file

@ -1,48 +0,0 @@
"""
This script asserts that Python config vars with compiler options including
one or more -O flags have the last specified -O flag equal to the script's
first argument.
We use it to check that the debug build (as well as extension modules) was
built with a desired optimization level (usually -Og or -O0).
About -O flags: https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
"If you use multiple -O options, with or without level numbers,
the last such option is the one that is effective."
"""
import sys
import sysconfig
# We will check all flags if none were requested
KEYS_TO_CHECK = sys.argv[2:] or list(sysconfig.get_config_vars().keys())
# For backwards compatibility, if no flags were provided, we assume flags without -O are to be skipped
# But when we provide explicit list of flags, we assert they get the options,
# so we can assert things like "CFLAGS has -O3" vs. "CFLAGS has no -O at all"
NO_FLAG_FAILS = bool(sys.argv[2:])
# The flags that currently don't have the -Og flag on the debug build
# and we consider it OK, because we don't know any better :)
SKIP = [
'CONFIGURE_CFLAGS',
'CONFIGURE_CFLAGS_NODIST',
'CONFIG_ARGS',
'OPT',
]
print('Expecting that {} is the last -O flag:\n'.format(sys.argv[1]))
ret = 0
for key in KEYS_TO_CHECK:
if key in SKIP:
continue
flags = sysconfig.get_config_vars()[key]
if isinstance(flags, str):
oflags = [f for f in flags.split(' ') if f.startswith('-O')]
if (oflags and oflags[-1] != sys.argv[1]) or (not oflags and NO_FLAG_FAILS):
print('Problem in {} -O flags: {}'.format(key, ' '.join(oflags) or '<empty>'))
ret = 1
elif oflags:
print('{} are OK'.format(key))
sys.exit(ret)

View file

@ -1,4 +1,4 @@
contact: contact:
- Petr Šplíchal <psplicha@redhat.com> - Petr Šplíchal <psplicha@redhat.com>
- Miro Hrončok <mhroncok@redhat.com> - Miro Hrončok <miro@hroncok.cz>
component: [python] component: [python]

View file

@ -1,32 +0,0 @@
#!/bin/sh -eux
# Marshalparser helps us to uniform byte-compiled Python files
# so it has to be able to also parse pyc files from
# the main python packages.
# Moreover, it has its own database of magic numbers so this test
# checks that the parser is compatible with the latest python before
# we ship it.
# Test the latest version by default because that's the place
# where magic number incompatibility might happen with
# the largest probability
VERSION=${VERSION:-3.9}
# How many randomly-selected pyc files to check?
SAMPLE=${SAMPLE:-50}
# This allows us to test marshalparser optionally: on Fedora 33+ only
INSTALL_OR_SKIP=${INSTALL_OR_SKIP:-false}
if [ "$INSTALL_OR_SKIP" == "true" ] && [ ! -f "/usr/bin/marshalparser" ]; then
dnf -y install marshalparser || :
if [ ! -f "/usr/bin/marshalparser" ]; then
echo "marshalparser not installable, skipping this test"
exit 0
fi
fi
path="/usr/lib*/python$VERSION/"
for file in `find $path -name "*.pyc" | shuf | head -n $SAMPLE`; do
marshalparser $file || exit 1
done

View file

@ -1,92 +0,0 @@
#!/bin/bash
# Compare required external symbols of compiled Python extension modules between
# the PR build (pre-installed by Testing Farm) and the latest stable version
# available in the distribution repositories.
#
# The PR build is already installed before this test runs (via the Testing Farm
# artifact mechanism). We save its .so files, downgrade to the stable repo version,
# collect symbols from both, and compare.
#
# Output:
# + [module] symbol — PR build adds a new required symbol (potential issue possibly requiring update of Requires)
# - [module] symbol — PR build drops a previously required symbol (generally OK)
#
# Exit codes:
# 0 — no changes, or no stable version available to compare against (skipped)
# 1 — symbol differences detected between PR build and stable release
set -eo pipefail
PYVER=${VERSION}
SCRIPT_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
WORK_DIR=""
PKGS_MODIFIED=false
cleanup() {
if [[ "$PKGS_MODIFIED" == true ]]; then
echo ""
echo "=== Reverting package changes ==="
dnf history undo last -y 2>&1 || echo "WARNING: dnf history undo failed" >&2
fi
[[ -n "$WORK_DIR" ]] && rm -rf "$WORK_DIR"
}
trap cleanup EXIT
# Directories containing compiled extension modules
SCAN_DIRS=()
for d in "/usr/lib64/python${PYVER}" "/usr/lib64/python${PYVER}t"; do
[ -d "$d" ] && SCAN_DIRS+=("$d")
done
if [ ${#SCAN_DIRS[@]} -eq 0 ]; then
echo "ERROR: No Python extension directories found for python${PYVER}" >&2
exit 1
fi
SCAN_ARGS=("${SCAN_DIRS[@]}")
for f in /usr/lib64/libpython${PYVER}*.so.*; do
[[ -f "$f" && ! -L "$f" ]] && SCAN_ARGS+=("$f")
done
WORK_DIR=$(mktemp -d)
echo "=== Collecting PR build symbols ==="
python${PYVER} "$SCRIPT_DIR/collect_symbols.py" "${SCAN_ARGS[@]}" > "$WORK_DIR/new.json"
echo ""
echo "=== Available repos ==="
dnf repolist --all
# Discover packages dynamically from the installed source RPM so we don't need
# to hardcode subpackage names — works regardless of which subpackages were built.
readarray -t PKGS < <(
rpm -qa --qf '%{SOURCERPM} %{NAME}\n' |
awk -v src="python${PYVER}" '$1 ~ "^" src "-[0-9]" { print $2 }' |
sort -u
)
echo "Packages to install from stable repos: ${PKGS[*]}"
# Install the stable version from distribution repos, excluding the artifact repo
# that carries the PR build, so we always compare against what is publicly available.
#
# Strategy:
# 1. dnf reinstall — works when the PR did not bump NVR (same version in stable repos)
# 2. dnf distro-sync — fallback when the PR bumped NVR; installs the latest stable NVR
echo ""
echo "=== Installing stable version from repos ==="
if ! dnf reinstall -y --disablerepo=test-artifacts "${PKGS[@]}" 2>&1; then
echo "INFO: reinstall failed (NVR not in stable repos), falling back to distro-sync" >&2
if ! dnf distro-sync -y --disablerepo=test-artifacts "${PKGS[@]}" 2>&1; then
echo "INFO: distro-sync failed; no stable version available, skipping comparison." >&2
exit 0
fi
fi
PKGS_MODIFIED=true
echo ""
echo "=== Collecting stable version symbols ==="
python${PYVER} "$SCRIPT_DIR/collect_symbols.py" "${SCAN_ARGS[@]}" > "$WORK_DIR/old.json"
echo ""
echo "=== Comparing PR build against stable ==="
python${PYVER} "$SCRIPT_DIR/compare_symbols.py" "$WORK_DIR/old.json" "$WORK_DIR/new.json"

View file

@ -1,67 +0,0 @@
#!/usr/bin/env python3
"""
Collect undefined non-boring external symbols from compiled Python extension modules
and libpython shared libraries.
Usage: collect_symbols.py <path> [<path>...]
Each path may be a directory (scanned recursively for *.cpython-*.so) or a file
(scanned directly; intended for libpython*.so.* passed explicitly from the caller).
Symbols are unioned across all variants of each module (regular/debug/freethreading),
and the result is printed as JSON to stdout.
"""
import json
import re
import subprocess
import sys
from pathlib import Path
BORING = re.compile(
r"@GLIBC_" # glibc versioned symbols (stable by definition)
r"|@GCC_" # GCC built-ins
r"|^_?Py[A-Za-z_]" # Python C API (resolved from libpython at runtime)
r"|^__" # C runtime internals
r"|^_ITM_" # Intel transactional memory
)
def module_name(so: Path) -> str:
# _ssl.cpython-314td-x86_64-linux-gnu.so → _ssl
if m := re.match(r"(.+?)\.cpython-", so.name):
return m.group(1)
# libpython3.14.so.1.0 → libpython3.14
return re.sub(r"\.so.*$", "", so.name)
def external_symbols(so: Path) -> list[str]:
result = subprocess.run(["nm", "-D", str(so)], capture_output=True, text=True)
return sorted(
parts[-1]
for line in result.stdout.splitlines()
if len(parts := line.split()) >= 2
and parts[-2] == "U"
and not BORING.search(parts[-1])
)
modules: dict[str, set[str]] = {}
for path_arg in sys.argv[1:]:
p = Path(path_arg)
if p.is_file():
print(f"Scanning: {p.name}", file=sys.stderr)
if syms := external_symbols(p):
modules.setdefault(module_name(p), set()).update(syms)
else:
print(f"Scanning: {path_arg}", file=sys.stderr)
for so in sorted(p.rglob("*.cpython-*.so")):
if so.is_symlink():
continue
if syms := external_symbols(so):
modules.setdefault(module_name(so), set()).update(syms)
total_symbols = sum(len(v) for v in modules.values())
print(f"Found {len(modules)} modules, {total_symbols} tracked symbols", file=sys.stderr)
json.dump({k: sorted(v) for k, v in sorted(modules.items())}, sys.stdout, indent=2)
print()

View file

@ -1,41 +0,0 @@
#!/usr/bin/env python3
"""
Compare two symbol JSON files produced by collect_symbols.py.
Usage: compare_symbols.py <old.json> <new.json>
Output:
+ [module] symbol new build adds a required symbol the old build did not have
- [module] symbol new build drops a required symbol the old build had
Exit codes:
0 no differences
1 differences found
"""
import json
import sys
from pathlib import Path
old = json.loads(Path(sys.argv[1]).read_text())
new = json.loads(Path(sys.argv[2]).read_text())
all_mods = sorted(set(old) | set(new))
changes = []
for mod in all_mods:
added = sorted(set(new.get(mod, [])) - set(old.get(mod, [])))
removed = sorted(set(old.get(mod, [])) - set(new.get(mod, [])))
if added or removed:
changes.append((mod, added, removed))
if not changes:
print("OK: no symbol changes between stable and PR build")
sys.exit(0)
print("Symbol changes detected (+ new requirement in PR build, - dropped by PR build):")
for mod, added, removed in changes:
for sym in added:
print(f" + [{mod}] {sym}")
for sym in removed:
print(f" - [{mod}] {sym}")
sys.exit(1)

View file

@ -1 +0,0 @@
test: ./findleaks.sh

View file

@ -1,21 +0,0 @@
#!/bin/sh -eux
# set python version
VERSION=${VERSION:-3.7}
PYTHON=${PYTHON:-python${VERSION}dm}
# what to skip
# test_socket swaps and kills the machine https://bugs.python.org/issue34587
X=${X:-"-x test_socket"}
# Fedora sets explicit minimum/maximum TLS versions.
# Python's test suite assumes that the minimum/maximum version is set to
# a magic marker. We workaround the test problem by setting:
export OPENSSL_CONF=/non-existing-file
# https://bugzilla.redhat.com/show_bug.cgi?id=1618753
# https://bugzilla.redhat.com/show_bug.cgi?id=1778357
# https://bugs.python.org/issue35045
# https://bugs.python.org/issue38815
$PYTHON -m test.pythoninfo
$PYTHON -m test -wW --findleaks $X

View file

@ -1,4 +0,0 @@
require:
- python3-test
- python3-rpm-macros
- python3-tkinter

View file

@ -1,4 +0,0 @@
test: ./parallel.sh
duration: 30m
recommend:
- /usr/bin/dtrace

View file

@ -1,41 +0,0 @@
#!/bin/sh -eux
# set default version to %{python3_version} if available
VERSION=${VERSION:-$(rpm --eval '%{?python3_version}')} || true
# ...or 3.8 if that macro is not available or rpm fails for any reason
VERSION=${VERSION:-3.8}
PYTHON=${PYTHON:-python$VERSION}
# what to skip
# test_socket swaps and kills the machine https://bugs.python.org/issue34587
X=${X:-"-x test_socket"}
TIMEOUT=${TIMEOUT:-1800}
# parallel jobs, 0 lets Python decide what's best
JOBS=${JOBS:-0}
# Fedora sets explicit minimum/maximum TLS versions.
# Python's test suite assumes that the minimum/maximum version is set to
# a magic marker. We workaround the test problem by setting:
export OPENSSL_CONF=/non-existing-file
# https://bugzilla.redhat.com/show_bug.cgi?id=1618753
# https://bugzilla.redhat.com/show_bug.cgi?id=1778357
# https://bugs.python.org/issue35045
# https://bugs.python.org/issue38815
# setuptools 60+ uses its own copy of distutils by default
# this setting must be overriden with the environment variable for
# Python tests to use the standard library's distutils
# We only do this if stdlib distutils actually exists
$PYTHON -c 'import distutils' 2>/dev/null && export SETUPTOOLS_USE_DISTUTILS=stdlib || true
$PYTHON -m test.pythoninfo
$PYTHON -m test -wW -j$JOBS --timeout=$TIMEOUT $X && e=0 || e=$?
# https://tmt.readthedocs.io/en/stable/spec/tests.html#framework says only exit code 1 is failure, rest is error
# https://github.com/python/cpython/blob/v3.14.0/Lib/test/libregrtest/results.py#L18 says a bad test exits with 2
if [ $e -eq 2 ]; then
exit 1
fi
exit $e

View file

@ -21,21 +21,16 @@ description: |
* METHOD ... virtual environment creation method (venv, virtualenv) * METHOD ... virtual environment creation method (venv, virtualenv)
* TOX ... enable or disable the tox test (true, false) * TOX ... enable or disable the tox test (true, false)
path: /smoke path: smoke
test: ./venv.sh test: ./venv.sh
tier: 1 tier: 1
tag: [venv] tags: [venv]
require+:
- gcc
- gcc-c++
environment: environment:
VERSION: 3.7 VERSION: 3.7
METHOD: venv METHOD: venv
TOX: true TOX: true
TOX_REQUIRES: ""
duration: 10m duration: 10m
component+: component+:
- python2 - python2

View file

@ -1,56 +1,24 @@
#!/bin/sh -eux #!/bin/sh -eux
# set default version to %{python3_version} if available # set python version
VERSION=${VERSION:-$(rpm --eval '%{?python3_version}')} || true VERSION=${VERSION:-3.7}
# ...or 3.8 if that macro is not available or rpm fails for any reason
VERSION=${VERSION:-3.8}
PYTHON=${PYTHON:-python$VERSION} PYTHON=${PYTHON:-python$VERSION}
METHOD=${METHOD:-venv} METHOD=${METHOD:-venv}
TOX=${TOX:-true} TOX=${TOX:-true}
TOX_REQUIRES=${TOX_REQUIRES:-}
CYTHON=${CYTHON:-true}
INSTALL_OR_SKIP=${INSTALL_OR_SKIP:-false}
# Work from a custom directory # clean from possible older runs
workdir="$(mktemp --directory)" rm -rf venv .tox __pycache__ .pytest* test_*.py *.pyx *.c *.so || :
cd "$workdir"
if [ "$INSTALL_OR_SKIP" == "true" ]; then
[ ! -f "/usr/bin/$PYTHON" ] && dnf -y install "/usr/bin/$PYTHON" || :
[ ! -f "/usr/bin/${PYTHON}-config" ] && dnf -y install "/usr/bin/${PYTHON}-config" || :
dnf -y install "$PYTHON-devel" || :
if [ ! -f "/usr/bin/$PYTHON" ]; then
echo "/usr/bin/$PYTHON not installable, skipping this test"
exit 0
fi
fi
# check the we can create the venv # check the we can create the venv
if [ "$METHOD" == "venv" ]; then if [ "$METHOD" == "venv" ]; then
$PYTHON -m venv venv $PYTHON -m venv venv
elif [ "$METHOD" == "virtualenv" ]; then elif [ "$METHOD" == "virtualenv" ]; then
virtualenv --python=$PYTHON venv virtualenv --python=$PYTHON venv
elif [ "$METHOD" == "virtualenv-download" ]; then
virtualenv --download --python=$PYTHON venv
elif [ "$METHOD" == "virtualenv-no-download" ]; then
virtualenv --no-download --python=$PYTHON venv
elif [ "$METHOD" == "virtualenv-seeder-pip" ]; then
virtualenv --seeder pip --python=$PYTHON venv
elif [ "$METHOD" == "none" ]; then
echo 'Skipping plain virtual environment tests' >&2
else else
echo 'Unsupported $METHOD' $METHOD >&2 echo 'Unsupported $METHOD' $METHOD >&2
exit 1 exit 1
fi fi
# create tests
cat > test_foo.py << EOF
def test_foo():
assert True
EOF
if [ "$METHOD" != "none" ]; then
# and activate it # and activate it
# unset variables on 3.5, it's known # unset variables on 3.5, it's known
@ -59,15 +27,29 @@ source venv/bin/activate
set -u set -u
# run python in it # run python in it
python -c 'import sys; print(sys.version)' | head -n1 | grep ${VERSION/t} python -c 'import sys; print(sys.version)' | head -n1 | grep $VERSION
# install packages with pip # install packages with pip
python -m pip install pytest if [ "$VERSION" == "2.6" ]; then
pip install pytest
pip install Cython --install-option="--no-cython-compile"
else
python -m pip install pytest
if [ "$PYTHON" != "jython" ]; then
python -m pip install Cython
fi
fi
# run tests # run tests
cat > test_foo.py << EOF
def test_foo():
assert True
EOF
python -m pytest -v test_foo.py python -m pytest -v test_foo.py
if [ "$CYTHON" == "true" ]; then # check that we can do extension modules
if [ "$PYTHON" != "jython" ]; then
cat > module.pyx << EOF cat > module.pyx << EOF
cdef int add(int a, int b): cdef int add(int a, int b):
return a + b return a + b
@ -78,12 +60,6 @@ def two():
return add(a, b) return add(a, b)
EOF EOF
cat > pyproject.toml << EOF
[build-system]
requires = ["setuptools", "cython"]
build-backend = "setuptools.build_meta"
EOF
cat > setup.py << EOF cat > setup.py << EOF
from setuptools import setup from setuptools import setup
from Cython.Build import cythonize from Cython.Build import cythonize
@ -91,7 +67,7 @@ from Cython.Build import cythonize
setup(ext_modules = cythonize('module.pyx')) setup(ext_modules = cythonize('module.pyx'))
EOF EOF
python -m pip install . python setup.py build_ext --inplace
python -c 'import module; print(module.two())' | grep '^2$' python -c 'import module; print(module.two())' | grep '^2$'
fi fi
@ -101,50 +77,18 @@ set +u
deactivate deactivate
set -u set -u
fi # METHOD != none
# ensurepip test (when testing virtualenv, this was not covered)
# with the main Python, this will say "Requirement already satisfied", but that's OK
$PYTHON -m ensurepip --root ensurepiptestroot
# use it with tox # use it with tox
[[ "$TOX" != "true" ]] && exit 0 [[ "$TOX" != "true" ]] && exit 0
if [[ $PYTHON == python* ]]; then cat > tox.ini << EOF
export TOXENV=py${VERSION/./} [tox]
elif [[ $PYTHON == pypy* ]]; then
export TOXENV=pypy${VERSION/./}
else
export TOXENV=$PYTHON
fi
echo '[tox]' > tox.ini
if [ -n "$TOX_REQUIRES" ]; then
echo 'requires = '"$TOX_REQUIRES" >> tox.ini
fi
cat >> tox.ini << EOF
skipsdist = True
[testenv]
commands = python -c 'import sys; print(sys.version)'
EOF
# Tests that the Python version used in tox is really the Python version we want
tox | tee toxlog
grep "^${VERSION/t}\.[0-9]" toxlog
echo '[tox]' > tox.ini
if [ -n "$TOX_REQUIRES" ]; then
echo 'requires = '"$TOX_REQUIRES" >> tox.ini
fi
cat >> tox.ini << EOF
skipsdist = True skipsdist = True
[testenv] [testenv]
deps = pytest deps = pytest
commands = python -m pytest -v test_foo.py commands = python -m pytest -v test_foo.py
EOF EOF
# A more complex example with pytest if [[ $PYTHON == python* ]]; then
tox tox -e py${VERSION/./}
else
# Clean up if everything else went well tox -e $PYTHON
cd - fi
rm -rf "$workdir"

104
tests.yml
View file

@ -1,104 +0,0 @@
---
- hosts: localhost
tags:
- classic
tasks:
- dnf:
name: "*"
state: latest
- ansible.builtin.user:
name: testuser
- hosts: localhost
roles:
- role: standard-test-basic
tags:
- classic
tests:
- smoke_default:
dir: smoke
run: runuser testuser -c './venv.sh'
- smoke27:
dir: smoke
run: VERSION=2.7 INSTALL_OR_SKIP=true METHOD=virtualenv TOX_REQUIRES="virtualenv<20.22.0" ./venv.sh
- smoke27_seeder_pip:
dir: smoke
run: VERSION=2.7 INSTALL_OR_SKIP=true METHOD=virtualenv-seeder-pip TOX=false ./venv.sh
- smoke36:
dir: smoke
run: runuser testuser -c 'VERSION=3.6 METHOD=venv TOX_REQUIRES="virtualenv<20.22.0" ./venv.sh'
- smoke37:
dir: smoke
run: VERSION=3.7 METHOD=venv INSTALL_OR_SKIP=true ./venv.sh
- smoke38:
dir: smoke
run: VERSION=3.8 METHOD=venv INSTALL_OR_SKIP=true ./venv.sh
- smoke39:
dir: smoke
run: runuser testuser -c 'VERSION=3.9 METHOD=venv ./venv.sh'
- smoke311:
dir: smoke
run: runuser testuser -c 'VERSION=3.11 METHOD=venv CYTHON=false ./venv.sh'
- smoke312:
dir: smoke
run: runuser testuser -c 'VERSION=3.12 METHOD=venv ./venv.sh'
- smoke314t_optional:
dir: smoke
run: VERSION=3.14t METHOD=venv INSTALL_OR_SKIP=true ./venv.sh
- smoke39_seeder_pip:
dir: smoke
run: runuser testuser -c 'VERSION=3.9 METHOD=virtualenv-seeder-pip ./venv.sh'
- smoke34_optional:
dir: smoke
run: VERSION=3.4 METHOD=venv INSTALL_OR_SKIP=true ./venv.sh
- smoke310_optional:
dir: smoke
run: VERSION=3.10 METHOD=venv TOX=false INSTALL_OR_SKIP=true ./venv.sh
- smoke311_virtualenv_download:
dir: smoke
run: VERSION=3.10 METHOD=virtualenv-download TOX=false ./venv.sh
- smokepypy:
dir: smoke
run: runuser testuser -c 'VERSION=2.7 METHOD=none PYTHON=pypy TOX_REQUIRES="virtualenv<20.22.0" ./venv.sh'
- smokepypy310:
dir: smoke
run: runuser testuser -c 'VERSION=3.10 METHOD=venv PYTHON=pypy3.10 ./venv.sh'
- selftest39:
dir: selftest
run: runuser testuser -c 'VERSION=3.9 X="-x test_socket -x test_asyncgen -x test_asyncio -x test_compile -x test_concurrent_futures -x test_itertools -x test_multiprocessing_fork -x test_multiprocessing_forkserver -x test_shutil -x test_time -x test_multiprocessing_spawn -x test_threading -x test_wsgiref" ./parallel.sh'
- debugtest310:
dir: selftest
run: runuser testuser -c 'VERSION=3.10 PYTHON="python3.10d" X="test_ssl" ./parallel.sh'
- debugflags:
dir: flags
run: runuser testuser -c 'python3-debug ./assertflags.py -O0'
- debugflags_some:
dir: flags
run: runuser testuser -c 'python3-debug ./assertflags.py -O0 PY_STDMODULE_CFLAGS'
- optflags_o3:
dir: flags
run: runuser testuser -c 'python3 ./assertflags.py -O3 CFLAGS'
- marshalparser_compatibility:
dir: marshalparser
run: runuser testuser -c 'SAMPLE=10 test_marshalparser_compatibility.sh'
required_packages:
- util-linux # for runuser
- dnf
- gcc
- gcc-c++
- virtualenv
- python3.6
- python3.9
- python3.11
- python3.12
- pypy
- pypy3.10-devel
- python3-devel
- pypy-devel
- python3-tox
- python3-tkinter
- python3-test
- python3-debug
- python3.10-debug
- python3-rpm-macros
- marshalparser