Blame tests/test_evals.py

985a805
import os
f5bea8c
import subprocess
06987f5
import platform
c2305ea
import re
f5bea8c
import sys
763d24c
import textwrap
f5bea8c
59abe83
import pytest
59abe83
5f3e4d6
X_Y = f'{sys.version_info[0]}.{sys.version_info[1]}'
985a805
XY = f'{sys.version_info[0]}{sys.version_info[1]}'
f5bea8c
2eb41fe
# Handy environment variable you can use to run the tests
2eb41fe
# with modified macros files. Multiple files should be
2eb41fe
# separated by colon.
230ce7f
# You can use * if you escape it from your Shell:
230ce7f
# TESTED_FILES='macros.*' pytest -v
2eb41fe
# Remember that some tests might need more macros files than just
187e049
# the local ones. You might need to use:
187e049
# TESTED_FILES='/usr/lib/rpm/macros:/usr/lib/rpm/platform/x86_64-linux/macros:macros.*'
2eb41fe
TESTED_FILES = os.getenv("TESTED_FILES", None)
2eb41fe
f5bea8c
985a805
def rpm_eval(expression, fails=False, **kwargs):
f5bea8c
    cmd = ['rpmbuild']
2eb41fe
    if TESTED_FILES:
2eb41fe
        cmd += ['--macros', TESTED_FILES]
f5bea8c
    for var, value in kwargs.items():
7237192
        if value is None:
7237192
            cmd += ['--undefine', var]
7237192
        else:
7237192
            cmd += ['--define', f'{var} {value}']
f5bea8c
    cmd += ['--eval', expression]
985a805
    cp = subprocess.run(cmd, text=True, env={**os.environ, 'LANG': 'C.utf-8'},
985a805
                        stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
985a805
    if fails:
985a805
        assert cp.returncode != 0, cp.stdout
985a805
    elif fails is not None:
985a805
        assert cp.returncode == 0, cp.stdout
f5bea8c
    return cp.stdout.strip().splitlines()
f5bea8c
f5bea8c
9d2fcef
@pytest.fixture(scope="session")
9d2fcef
def lib():
9d2fcef
    lib_eval = rpm_eval("%_lib")[0]
9d2fcef
    if lib_eval == "%_lib" and TESTED_FILES:
9d2fcef
        raise ValueError(
9d2fcef
            "%_lib is not resolved to an actual value. "
9d2fcef
            "You may want to include /usr/lib/rpm/platform/x86_64-linux/macros to TESTED_FILES."
9d2fcef
        )
9d2fcef
    return lib_eval
9d2fcef
9d2fcef
76209d7
def get_alt_x_y():
76209d7
    """
76209d7
    Some tests require alternate Python version to be installed.
76209d7
    In order to allow any Python version (or none at all),
76209d7
    this function/fixture exists.
76209d7
    You can control the behavior by setting the $ALTERNATE_PYTHON_VERSION
76209d7
    environment variable to X.Y (e.g. 3.6) or SKIP.
76209d7
    The environment variable must be set.
76209d7
    """
76209d7
    env_name = "ALTERNATE_PYTHON_VERSION"
76209d7
    alternate_python_version = os.getenv(env_name, "")
76209d7
    if alternate_python_version.upper() == "SKIP":
76209d7
        pytest.skip(f"${env_name} set to SKIP")
76209d7
    if not alternate_python_version:
76209d7
        raise ValueError(f"${env_name} must be set, "
76209d7
                         f"set it to SKIP if you want to skip tests that "
76209d7
                         f"require alternate Python version.")
76209d7
    if not re.match(r"^\d+\.\d+$", alternate_python_version):
76209d7
        raise ValueError(f"${env_name} must be X.Y")
76209d7
    return alternate_python_version
76209d7
76209d7
# We don't use the decorator, to be able to call the function itselef
76209d7
alt_x_y = pytest.fixture(scope="session")(get_alt_x_y)
76209d7
76209d7
e5429a7
def shell_stdout(script):
e5429a7
    return subprocess.check_output(script,
e5429a7
                                   env={**os.environ, 'LANG': 'C.utf-8'},
e5429a7
                                   text=True,
e5429a7
                                   shell=True).rstrip()
e5429a7
e5429a7
a8b2654
@pytest.mark.parametrize('macro', ['%__python3', '%python3'])
a8b2654
def test_python3(macro):
a8b2654
    assert rpm_eval(macro) == ['/usr/bin/python3']
a8b2654
a8b2654
a8b2654
@pytest.mark.parametrize('macro', ['%__python3', '%python3'])
a8b2654
@pytest.mark.parametrize('pkgversion', ['3', '3.9', '3.12'])
a8b2654
def test_python3_with_pkgversion(macro, pkgversion):
a8b2654
    assert rpm_eval(macro, python3_pkgversion=pkgversion) == [f'/usr/bin/python{pkgversion}']
a8b2654
a8b2654
59abe83
@pytest.mark.parametrize('argument, result', [
59abe83
    ('a', 'a'),
59abe83
    ('a-a', 'a-a'),
59abe83
    ('a_a', 'a-a'),
59abe83
    ('a.a', 'a-a'),
59abe83
    ('a---a', 'a-a'),
59abe83
    ('a-_-a', 'a-a'),
59abe83
    ('a-_-a', 'a-a'),
59abe83
    ('a[b]', 'a[b]'),
59abe83
    ('Aha[Boom]', 'aha[boom]'),
59abe83
    ('a.a[b.b]', 'a-a[b-b]'),
59abe83
])
59abe83
def test_pydist_name(argument, result):
59abe83
    assert rpm_eval(f'%py_dist_name {argument}') == [result]
59abe83
59abe83
59abe83
def test_py2_dist():
59abe83
    assert rpm_eval(f'%py2_dist Aha[Boom] a') == ['python2dist(aha[boom]) python2dist(a)']
59abe83
59abe83
59abe83
def test_py3_dist():
59abe83
    assert rpm_eval(f'%py3_dist Aha[Boom] a') == ['python3dist(aha[boom]) python3dist(a)']
59abe83
59abe83
76209d7
def test_py3_dist_with_python3_pkgversion_redefined(alt_x_y):
76209d7
    assert rpm_eval(f'%py3_dist Aha[Boom] a', python3_pkgversion=alt_x_y) == [f'python{alt_x_y}dist(aha[boom]) python{alt_x_y}dist(a)']
638f809
638f809
f5bea8c
def test_python_provide_python():
f5bea8c
    assert rpm_eval('%python_provide python-foo') == []
f5bea8c
f5bea8c
f5bea8c
def test_python_provide_python3():
f5bea8c
    lines = rpm_eval('%python_provide python3-foo', version='6', release='1.fc66')
f5bea8c
    assert 'Obsoletes: python-foo < 6-1.fc66' in lines
f5bea8c
    assert 'Provides: python-foo = 6-1.fc66' in lines
5f3e4d6
    assert f'Provides: python{X_Y}-foo = 6-1.fc66' in lines
f5bea8c
    assert len(lines) == 3
f5bea8c
f5bea8c
f5bea8c
def test_python_provide_python3_epoched():
f5bea8c
    lines = rpm_eval('%python_provide python3-foo', epoch='1', version='6', release='1.fc66')
f5bea8c
    assert 'Obsoletes: python-foo < 1:6-1.fc66' in lines
f5bea8c
    assert 'Provides: python-foo = 1:6-1.fc66' in lines
5f3e4d6
    assert f'Provides: python{X_Y}-foo = 1:6-1.fc66' in lines
f5bea8c
    assert len(lines) == 3
f5bea8c
f5bea8c
f5bea8c
def test_python_provide_python3X():
5f3e4d6
    lines = rpm_eval(f'%python_provide python{X_Y}-foo', version='6', release='1.fc66')
f5bea8c
    assert 'Obsoletes: python-foo < 6-1.fc66' in lines
f5bea8c
    assert 'Provides: python-foo = 6-1.fc66' in lines
f5bea8c
    assert 'Provides: python3-foo = 6-1.fc66' in lines
f5bea8c
    assert len(lines) == 3
f5bea8c
f5bea8c
f5bea8c
def test_python_provide_python3X_epoched():
5f3e4d6
    lines = rpm_eval(f'%python_provide python{X_Y}-foo', epoch='1', version='6', release='1.fc66')
f5bea8c
    assert 'Obsoletes: python-foo < 1:6-1.fc66' in lines
f5bea8c
    assert 'Provides: python-foo = 1:6-1.fc66' in lines
f5bea8c
    assert 'Provides: python3-foo = 1:6-1.fc66' in lines
f5bea8c
    assert len(lines) == 3
f5bea8c
f5bea8c
f5bea8c
def test_python_provide_doubleuse():
f5bea8c
    lines = rpm_eval('%{python_provide python3-foo}%{python_provide python3-foo}',
f5bea8c
                     version='6', release='1.fc66')
f5bea8c
    assert 'Obsoletes: python-foo < 6-1.fc66' in lines
f5bea8c
    assert 'Provides: python-foo = 6-1.fc66' in lines
5f3e4d6
    assert f'Provides: python{X_Y}-foo = 6-1.fc66' in lines
f5bea8c
    assert len(lines) == 6
f5bea8c
    assert len(set(lines)) == 3
f5bea8c
f5bea8c
5547a87
@pytest.mark.parametrize('rhel', [None, 10])
5547a87
def test_py_provides_python(rhel):
5547a87
    lines = rpm_eval('%py_provides python-foo', version='6', release='1.fc66', rhel=rhel)
f5bea8c
    assert 'Provides: python-foo = 6-1.fc66' in lines
f5bea8c
    assert len(lines) == 1
f5bea8c
f5bea8c
5547a87
@pytest.mark.parametrize('rhel', [None, 12])
5547a87
def test_py_provides_whatever(rhel):
5547a87
    lines = rpm_eval('%py_provides whatever', version='6', release='1.fc66', rhel=rhel)
f5bea8c
    assert 'Provides: whatever = 6-1.fc66' in lines
f5bea8c
    assert len(lines) == 1
f5bea8c
f5bea8c
5547a87
@pytest.mark.parametrize('rhel', [None, 9])
5547a87
def test_py_provides_python3(rhel):
5547a87
    lines = rpm_eval('%py_provides python3-foo', version='6', release='1.fc66', rhel=rhel)
f5bea8c
    assert 'Provides: python3-foo = 6-1.fc66' in lines
f5bea8c
    assert 'Provides: python-foo = 6-1.fc66' in lines
5f3e4d6
    assert f'Provides: python{X_Y}-foo = 6-1.fc66' in lines
5547a87
    if rhel:
5547a87
        assert f'Obsoletes: python{X_Y}-foo < 6-1.fc66' in lines
5547a87
        assert len(lines) == 4
5547a87
    else:
5547a87
        assert len(lines) == 3
f5bea8c
f5bea8c
e250f28
@pytest.mark.parametrize('rhel', [None, 9])
e250f28
def test_py_provides_python3_with_isa(rhel):
e250f28
    lines = rpm_eval('%py_provides python3-foo(x86_64)', version='6', release='1.fc66', rhel=rhel)
e250f28
    assert 'Provides: python3-foo(x86_64) = 6-1.fc66' in lines
e250f28
    assert 'Provides: python-foo(x86_64) = 6-1.fc66' in lines
e250f28
    assert f'Provides: python{X_Y}-foo(x86_64) = 6-1.fc66' in lines
e250f28
    assert f'Obsoletes: python{X_Y}-foo(x86_64) < 6-1.fc66' not in lines
e250f28
    assert len(lines) == 3
e250f28
e250f28
5547a87
@pytest.mark.parametrize('rhel', [None, 13])
5547a87
def test_py_provides_python3_epoched(rhel):
5547a87
    lines = rpm_eval('%py_provides python3-foo', epoch='1', version='6', release='1.fc66', rhel=rhel)
f5bea8c
    assert 'Provides: python3-foo = 1:6-1.fc66' in lines
f5bea8c
    assert 'Provides: python-foo = 1:6-1.fc66' in lines
5f3e4d6
    assert f'Provides: python{X_Y}-foo = 1:6-1.fc66' in lines
5547a87
    if rhel:
5547a87
        assert f'Obsoletes: python{X_Y}-foo < 1:6-1.fc66' in lines
5547a87
        assert len(lines) == 4
5547a87
    else:
5547a87
        assert len(lines) == 3
f5bea8c
f5bea8c
5547a87
@pytest.mark.parametrize('rhel', [None, 13])
5547a87
def test_py_provides_python3X(rhel):
5547a87
    lines = rpm_eval(f'%py_provides python{X_Y}-foo', version='6', release='1.fc66', rhel=rhel)
5f3e4d6
    assert f'Provides: python{X_Y}-foo = 6-1.fc66' in lines
f5bea8c
    assert 'Provides: python-foo = 6-1.fc66' in lines
f5bea8c
    assert 'Provides: python3-foo = 6-1.fc66' in lines
f5bea8c
    assert len(lines) == 3
f5bea8c
f5bea8c
5547a87
@pytest.mark.parametrize('rhel', [None, 27])
5547a87
def test_py_provides_python3X_epoched(rhel):
5547a87
    lines = rpm_eval(f'%py_provides python{X_Y}-foo', epoch='1', version='6', release='1.fc66', rhel=rhel)
5f3e4d6
    assert f'Provides: python{X_Y}-foo = 1:6-1.fc66' in lines
f5bea8c
    assert 'Provides: python-foo = 1:6-1.fc66' in lines
f5bea8c
    assert 'Provides: python3-foo = 1:6-1.fc66' in lines
f5bea8c
    assert len(lines) == 3
f5bea8c
f5bea8c
5547a87
@pytest.mark.parametrize('rhel', [None, 2])
5547a87
def test_py_provides_doubleuse(rhel):
f5bea8c
    lines = rpm_eval('%{py_provides python3-foo}%{py_provides python3-foo}',
5547a87
                     version='6', release='1.fc66', rhel=rhel)
f5bea8c
    assert 'Provides: python3-foo = 6-1.fc66' in lines
f5bea8c
    assert 'Provides: python-foo = 6-1.fc66' in lines
5f3e4d6
    assert f'Provides: python{X_Y}-foo = 6-1.fc66' in lines
5547a87
    if rhel:
5547a87
        assert f'Obsoletes: python{X_Y}-foo < 6-1.fc66' in lines
5547a87
        assert len(lines) == 8
5547a87
        assert len(set(lines)) == 4
5547a87
    else:
5547a87
        assert len(lines) == 6
5547a87
        assert len(set(lines)) == 3
f5bea8c
f5bea8c
5547a87
@pytest.mark.parametrize('rhel', [None, 2])
5547a87
def test_py_provides_with_evr(rhel):
f5bea8c
    lines = rpm_eval('%py_provides python3-foo 123',
5547a87
                     version='6', release='1.fc66', rhel=rhel)
f5bea8c
    assert 'Provides: python3-foo = 123' in lines
f5bea8c
    assert 'Provides: python-foo = 123' in lines
5f3e4d6
    assert f'Provides: python{X_Y}-foo = 123' in lines
5547a87
    if rhel:
5547a87
        assert f'Obsoletes: python{X_Y}-foo < 123' in lines
5547a87
        assert len(lines) == 4
5547a87
    else:
5547a87
        assert len(lines) == 3
0d3f1e6
0d3f1e6
9b797df
def test_python_wheel_pkg_prefix():
9b797df
    assert rpm_eval('%python_wheel_pkg_prefix', fedora='44', rhel=None, eln=None) == ['python']
9b797df
    assert rpm_eval('%python_wheel_pkg_prefix', fedora='44', rhel=None, eln=None, python3_pkgversion='3.9') == ['python']
9b797df
    assert rpm_eval('%python_wheel_pkg_prefix', fedora=None, rhel='1', eln='1') == ['python']
9b797df
    assert rpm_eval('%python_wheel_pkg_prefix', fedora=None, rhel='1', eln=None) == ['python3']
9b797df
    assert rpm_eval('%python_wheel_pkg_prefix', fedora=None, rhel='1', eln=None, python3_pkgversion='3.10') == ['python3.10']
9b797df
    assert rpm_eval('%python_wheel_pkg_prefix', fedora=None, rhel='1', eln=None, python3_pkgversion='3.11') == ['python3.11']
9b797df
9b797df
9b797df
def test_python_wheel_dir():
9b797df
    assert rpm_eval('%python_wheel_dir', fedora='44', rhel=None, eln=None) == ['/usr/share/python-wheels']
9b797df
    assert rpm_eval('%python_wheel_dir', fedora='44', rhel=None, eln=None, python3_pkgversion='3.9') == ['/usr/share/python-wheels']
9b797df
    assert rpm_eval('%python_wheel_dir', fedora=None, rhel='1', eln='1') == ['/usr/share/python-wheels']
9b797df
    assert rpm_eval('%python_wheel_dir', fedora=None, rhel='1', eln=None) == ['/usr/share/python3-wheels']
9b797df
    assert rpm_eval('%python_wheel_dir', fedora=None, rhel='1', eln=None, python3_pkgversion='3.10') == ['/usr/share/python3.10-wheels']
9b797df
    assert rpm_eval('%python_wheel_dir', fedora=None, rhel='1', eln=None, python3_pkgversion='3.11') == ['/usr/share/python3.11-wheels']
9b797df
9b797df
0d3f1e6
def test_pytest_passes_options_naturally():
0d3f1e6
    lines = rpm_eval('%pytest -k foo')
0d3f1e6
    assert '/usr/bin/pytest -k foo' in lines[-1]
0d3f1e6
0d3f1e6
0d3f1e6
def test_pytest_different_command():
0d3f1e6
    lines = rpm_eval('%pytest', __pytest='pytest-3')
0d3f1e6
    assert 'pytest-3' in lines[-1]
4569c61
4569c61
0253654
def test_pytest_command_suffix():
0253654
    lines = rpm_eval('%pytest -v')
0253654
    assert '/usr/bin/pytest -v' in lines[-1]
76209d7
76209d7
# this test does not require alternate Pythons to be installed
76209d7
@pytest.mark.parametrize('version', ['3.6', '3.7', '3.12'])
76209d7
def test_pytest_command_suffix_alternate_pkgversion(version):
76209d7
    lines = rpm_eval('%pytest -v', python3_pkgversion=version, python3_version=version)
76209d7
    assert f'/usr/bin/pytest-{version} -v' in lines[-1]
0253654
0253654
d905710
def test_pytest_undefined_addopts_are_not_set():
d905710
    lines = rpm_eval('%pytest', __pytest_addopts=None)
d905710
    assert 'PYTEST_ADDOPTS' not in '\n'.join(lines)
d905710
d905710
d905710
def test_pytest_defined_addopts_are_set():
d905710
    lines = rpm_eval('%pytest', __pytest_addopts="--ignore=stuff")
d905710
    assert 'PYTEST_ADDOPTS="${PYTEST_ADDOPTS:-} --ignore=stuff"' in '\n'.join(lines)
d905710
d905710
d905710
@pytest.mark.parametrize('__pytest_addopts', ['--macronized-option', 'x y z', None])
d905710
def test_pytest_addopts_preserves_envvar(__pytest_addopts):
d905710
    # this is the line a packager might put in the spec file before running %pytest:
d905710
    spec_line = 'export PYTEST_ADDOPTS="--exported-option1 --exported-option2"'
d905710
d905710
    # instead of actually running /usr/bin/pytest,
d905710
    # we run a small shell script that echoes the tested value for inspection
d905710
    lines = rpm_eval('%pytest', __pytest_addopts=__pytest_addopts,
d905710
                     __pytest="sh -c 'echo $PYTEST_ADDOPTS'")
d905710
d905710
    echoed = shell_stdout('\n'.join([spec_line] + lines))
d905710
d905710
    # assert all values were echoed
d905710
    assert '--exported-option1' in echoed
d905710
    assert '--exported-option2' in echoed
d905710
    if __pytest_addopts is not None:
d905710
        assert __pytest_addopts in echoed
d905710
d905710
    # assert the options are separated
d905710
    assert 'option--' not in echoed
d905710
    assert 'z--' not in echoed
d905710
d905710
4569c61
def test_pypi_source_default_name():
39166a7
    urls = rpm_eval('%pypi_source',
39166a7
                    name='foo', version='6')
39166a7
    assert urls == ['https://files.pythonhosted.org/packages/source/f/foo/foo-6.tar.gz']
4569c61
4569c61
4569c61
def test_pypi_source_default_srcname():
39166a7
    urls = rpm_eval('%pypi_source',
39166a7
                    name='python-foo', srcname='foo', version='6')
39166a7
    assert urls == ['https://files.pythonhosted.org/packages/source/f/foo/foo-6.tar.gz']
4569c61
4569c61
4569c61
def test_pypi_source_default_pypi_name():
39166a7
    urls = rpm_eval('%pypi_source',
39166a7
                    name='python-foo', pypi_name='foo', version='6')
39166a7
    assert urls == ['https://files.pythonhosted.org/packages/source/f/foo/foo-6.tar.gz']
4569c61
4569c61
4569c61
def test_pypi_source_default_name_uppercase():
39166a7
    urls = rpm_eval('%pypi_source',
39166a7
                    name='Foo', version='6')
39166a7
    assert urls == ['https://files.pythonhosted.org/packages/source/F/Foo/Foo-6.tar.gz']
4569c61
4569c61
4569c61
def test_pypi_source_provided_name():
39166a7
    urls = rpm_eval('%pypi_source foo',
39166a7
                    name='python-bar', pypi_name='bar', version='6')
39166a7
    assert urls == ['https://files.pythonhosted.org/packages/source/f/foo/foo-6.tar.gz']
4569c61
4569c61
4569c61
def test_pypi_source_provided_name_version():
39166a7
    urls = rpm_eval('%pypi_source foo 6',
39166a7
                    name='python-bar', pypi_name='bar', version='3')
39166a7
    assert urls == ['https://files.pythonhosted.org/packages/source/f/foo/foo-6.tar.gz']
4569c61
4569c61
4569c61
def test_pypi_source_provided_name_version_ext():
4569c61
    url = rpm_eval('%pypi_source foo 6 zip',
39166a7
                   name='python-bar', pypi_name='bar', version='3')
39166a7
    assert url == ['https://files.pythonhosted.org/packages/source/f/foo/foo-6.zip']
4569c61
4569c61
4569c61
def test_pypi_source_prerelease():
39166a7
    urls = rpm_eval('%pypi_source',
39166a7
                    name='python-foo', pypi_name='foo', version='6~b2')
39166a7
    assert urls == ['https://files.pythonhosted.org/packages/source/f/foo/foo-6b2.tar.gz']
4569c61
4569c61
4569c61
def test_pypi_source_explicit_tilde():
39166a7
    urls = rpm_eval('%pypi_source foo 6~6',
39166a7
                    name='python-foo', pypi_name='foo', version='6')
39166a7
    assert urls == ['https://files.pythonhosted.org/packages/source/f/foo/foo-6~6.tar.gz']
7237192
7237192
7237192
def test_py3_shebang_fix():
1979a78
    cmd = rpm_eval('%py3_shebang_fix arg1 arg2 arg3')[-1].strip()
e5429a7
    assert cmd == '$pathfix -pni /usr/bin/python3 $shebang_flags arg1 arg2 arg3'
e5429a7
e5429a7
e5429a7
def test_py3_shebang_fix_default_shebang_flags():
e5429a7
    lines = rpm_eval('%py3_shebang_fix arg1 arg2')
e5429a7
    lines[-1] = 'echo $shebang_flags'
e5429a7
    assert shell_stdout('\n'.join(lines)) == '-kas'
7237192
7237192
e5429a7
def test_py3_shebang_fix_custom_shebang_flags():
e5429a7
    lines = rpm_eval('%py3_shebang_fix arg1 arg2', py3_shebang_flags='Es')
e5429a7
    lines[-1] = 'echo $shebang_flags'
e5429a7
    assert shell_stdout('\n'.join(lines)) == '-kaEs'
7237192
7237192
e5429a7
@pytest.mark.parametrize('flags', [None, '%{nil}'])
e5429a7
def test_py3_shebang_fix_no_shebang_flags(flags):
e5429a7
    lines = rpm_eval('%py3_shebang_fix arg1 arg2', py3_shebang_flags=flags)
e5429a7
    lines[-1] = 'echo $shebang_flags'
e5429a7
    assert shell_stdout('\n'.join(lines)) == '-k'
7237192
7237192
e5429a7
def test_py_shebang_fix_custom_python():
1979a78
    cmd = rpm_eval('%py_shebang_fix arg1 arg2 arg3', __python='/usr/bin/pypy')[-1].strip()
e5429a7
    assert cmd == '$pathfix -pni /usr/bin/pypy $shebang_flags arg1 arg2 arg3'
985a805
985a805
985a805
def test_pycached_in_sitelib():
985a805
    lines = rpm_eval('%pycached %{python3_sitelib}/foo*.py')
985a805
    assert lines == [
985a805
        f'/usr/lib/python{X_Y}/site-packages/foo*.py',
985a805
        f'/usr/lib/python{X_Y}/site-packages/__pycache__/foo*.cpython-{XY}{{,.opt-?}}.pyc'
985a805
    ]
985a805
985a805
9d2fcef
def test_pycached_in_sitearch(lib):
985a805
    lines = rpm_eval('%pycached %{python3_sitearch}/foo*.py')
985a805
    assert lines == [
985a805
        f'/usr/{lib}/python{X_Y}/site-packages/foo*.py',
985a805
        f'/usr/{lib}/python{X_Y}/site-packages/__pycache__/foo*.cpython-{XY}{{,.opt-?}}.pyc'
985a805
    ]
985a805
985a805
76209d7
# this test does not require alternate Pythons to be installed
76209d7
@pytest.mark.parametrize('version', ['3.6', '3.7', '3.12'])
76209d7
def test_pycached_with_alternate_version(version):
76209d7
    version_nodot = version.replace('.', '')
76209d7
    lines = rpm_eval(f'%pycached /usr/lib/python{version}/site-packages/foo*.py')
985a805
    assert lines == [
76209d7
        f'/usr/lib/python{version}/site-packages/foo*.py',
76209d7
        f'/usr/lib/python{version}/site-packages/__pycache__/foo*.cpython-{version_nodot}{{,.opt-?}}.pyc'
985a805
    ]
985a805
985a805
985a805
def test_pycached_in_custom_dir():
985a805
    lines = rpm_eval('%pycached /bar/foo*.py')
985a805
    assert lines == [
985a805
        '/bar/foo*.py',
985a805
        '/bar/__pycache__/foo*.cpython-3*{,.opt-?}.pyc'
985a805
    ]
985a805
985a805
985a805
def test_pycached_with_exclude():
985a805
    lines = rpm_eval('%pycached %exclude %{python3_sitelib}/foo*.py')
985a805
    assert lines == [
985a805
        f'%exclude /usr/lib/python{X_Y}/site-packages/foo*.py',
985a805
        f'%exclude /usr/lib/python{X_Y}/site-packages/__pycache__/foo*.cpython-{XY}{{,.opt-?}}.pyc'
985a805
    ]
985a805
985a805
985a805
def test_pycached_fails_with_extension_glob():
985a805
    lines = rpm_eval('%pycached %{python3_sitelib}/foo.py*', fails=True)
985a805
    assert lines[0] == 'error: %pycached can only be used with paths explicitly ending with .py'
763d24c
763d24c
763d24c
def test_python_extras_subpkg_i():
763d24c
    lines = rpm_eval('%python_extras_subpkg -n python3-setuptools_scm -i %{python3_sitelib}/*.egg-info toml yaml',
763d24c
                     version='6', release='7')
763d24c
    expected = textwrap.dedent(f"""
763d24c
        %package -n python3-setuptools_scm+toml
763d24c
        Summary: Metapackage for python3-setuptools_scm: toml extras
763d24c
        Requires: python3-setuptools_scm = 6-7
763d24c
        %description -n python3-setuptools_scm+toml
c746b25
        This is a metapackage bringing in toml extras requires for
c746b25
        python3-setuptools_scm.
bc016cb
        It makes sure the dependencies are installed.
763d24c
763d24c
        %files -n python3-setuptools_scm+toml
763d24c
        %ghost /usr/lib/python{X_Y}/site-packages/*.egg-info
763d24c
763d24c
        %package -n python3-setuptools_scm+yaml
763d24c
        Summary: Metapackage for python3-setuptools_scm: yaml extras
763d24c
        Requires: python3-setuptools_scm = 6-7
763d24c
        %description -n python3-setuptools_scm+yaml
c746b25
        This is a metapackage bringing in yaml extras requires for
c746b25
        python3-setuptools_scm.
bc016cb
        It makes sure the dependencies are installed.
763d24c
763d24c
        %files -n python3-setuptools_scm+yaml
763d24c
        %ghost /usr/lib/python{X_Y}/site-packages/*.egg-info
763d24c
        """).lstrip().splitlines()
763d24c
    assert lines == expected
763d24c
763d24c
763d24c
def test_python_extras_subpkg_f():
763d24c
    lines = rpm_eval('%python_extras_subpkg -n python3-setuptools_scm -f ghost_filelist toml yaml',
763d24c
                     version='6', release='7')
763d24c
    expected = textwrap.dedent(f"""
763d24c
        %package -n python3-setuptools_scm+toml
763d24c
        Summary: Metapackage for python3-setuptools_scm: toml extras
763d24c
        Requires: python3-setuptools_scm = 6-7
763d24c
        %description -n python3-setuptools_scm+toml
c746b25
        This is a metapackage bringing in toml extras requires for
c746b25
        python3-setuptools_scm.
bc016cb
        It makes sure the dependencies are installed.
763d24c
763d24c
        %files -n python3-setuptools_scm+toml -f ghost_filelist
763d24c
763d24c
        %package -n python3-setuptools_scm+yaml
763d24c
        Summary: Metapackage for python3-setuptools_scm: yaml extras
763d24c
        Requires: python3-setuptools_scm = 6-7
763d24c
        %description -n python3-setuptools_scm+yaml
c746b25
        This is a metapackage bringing in yaml extras requires for
c746b25
        python3-setuptools_scm.
bc016cb
        It makes sure the dependencies are installed.
763d24c
763d24c
        %files -n python3-setuptools_scm+yaml -f ghost_filelist
763d24c
        """).lstrip().splitlines()
763d24c
    assert lines == expected
763d24c
763d24c
763d24c
def test_python_extras_subpkg_F():
763d24c
    lines = rpm_eval('%python_extras_subpkg -n python3-setuptools_scm -F toml yaml',
763d24c
                     version='6', release='7')
763d24c
    expected = textwrap.dedent(f"""
763d24c
        %package -n python3-setuptools_scm+toml
763d24c
        Summary: Metapackage for python3-setuptools_scm: toml extras
763d24c
        Requires: python3-setuptools_scm = 6-7
763d24c
        %description -n python3-setuptools_scm+toml
c746b25
        This is a metapackage bringing in toml extras requires for
c746b25
        python3-setuptools_scm.
bc016cb
        It makes sure the dependencies are installed.
763d24c
763d24c
763d24c
763d24c
        %package -n python3-setuptools_scm+yaml
763d24c
        Summary: Metapackage for python3-setuptools_scm: yaml extras
763d24c
        Requires: python3-setuptools_scm = 6-7
763d24c
        %description -n python3-setuptools_scm+yaml
c746b25
        This is a metapackage bringing in yaml extras requires for
c746b25
        python3-setuptools_scm.
bc016cb
        It makes sure the dependencies are installed.
763d24c
        """).lstrip().splitlines()
763d24c
    assert lines == expected
69b1b30
69b1b30
a6382f5
def test_python_extras_subpkg_underscores():
a6382f5
    lines = rpm_eval('%python_extras_subpkg -n python3-webscrapbook -F adhoc_ssl',
a6382f5
                     version='0.33.3', release='1.fc33')
a6382f5
    expected = textwrap.dedent(f"""
a6382f5
        %package -n python3-webscrapbook+adhoc_ssl
a6382f5
        Summary: Metapackage for python3-webscrapbook: adhoc_ssl extras
a6382f5
        Requires: python3-webscrapbook = 0.33.3-1.fc33
a6382f5
        %description -n python3-webscrapbook+adhoc_ssl
a6382f5
        This is a metapackage bringing in adhoc_ssl extras requires for
a6382f5
        python3-webscrapbook.
bc016cb
        It makes sure the dependencies are installed.
a6382f5
        """).lstrip().splitlines()
a6382f5
    assert lines == expected
a6382f5
a6382f5
a44ae31
@pytest.mark.parametrize('sep', [pytest.param(('', ' ', ' ', ''), id='spaces'),
a44ae31
                                 pytest.param(('', ',', ',', ''), id='commas'),
a44ae31
                                 pytest.param(('', ',', ',', ','), id='commas-trailing'),
a44ae31
                                 pytest.param((',', ',', ',', ''), id='commas-leading'),
a44ae31
                                 pytest.param((',', ',', ',', ','), id='commas-trailing-leading'),
a44ae31
                                 pytest.param(('', ',', ' ', ''), id='mixture'),
a44ae31
                                 pytest.param(('  ', '   ', '\t\t, ', '\t'), id='chaotic-good'),
a44ae31
                                 pytest.param(('', '\t ,, \t\r ', ',,\t  , ', ',,'), id='chaotic-evil')])
a44ae31
def test_python_extras_subpkg_arg_separators(sep):
a44ae31
    lines = rpm_eval('%python_extras_subpkg -n python3-hypothesis -F {}cli{}ghostwriter{}pytz{}'.format(*sep),
a44ae31
                     version='6.6.0', release='1.fc35')
a44ae31
    expected = textwrap.dedent(f"""
a44ae31
        %package -n python3-hypothesis+cli
a44ae31
        Summary: Metapackage for python3-hypothesis: cli extras
a44ae31
        Requires: python3-hypothesis = 6.6.0-1.fc35
a44ae31
        %description -n python3-hypothesis+cli
a44ae31
        This is a metapackage bringing in cli extras requires for python3-hypothesis.
a44ae31
        It makes sure the dependencies are installed.
a44ae31
a44ae31
a44ae31
a44ae31
        %package -n python3-hypothesis+ghostwriter
a44ae31
        Summary: Metapackage for python3-hypothesis: ghostwriter extras
a44ae31
        Requires: python3-hypothesis = 6.6.0-1.fc35
a44ae31
        %description -n python3-hypothesis+ghostwriter
a44ae31
        This is a metapackage bringing in ghostwriter extras requires for
a44ae31
        python3-hypothesis.
a44ae31
        It makes sure the dependencies are installed.
a44ae31
a44ae31
a44ae31
a44ae31
        %package -n python3-hypothesis+pytz
a44ae31
        Summary: Metapackage for python3-hypothesis: pytz extras
a44ae31
        Requires: python3-hypothesis = 6.6.0-1.fc35
a44ae31
        %description -n python3-hypothesis+pytz
a44ae31
        This is a metapackage bringing in pytz extras requires for python3-hypothesis.
a44ae31
        It makes sure the dependencies are installed.
a44ae31
        """).lstrip().splitlines()
a44ae31
    assert lines == expected
a44ae31
a44ae31
c746b25
@pytest.mark.parametrize('basename_len', [1, 10, 30, 45, 78])
c746b25
@pytest.mark.parametrize('extra_len', [1, 13, 28, 52, 78])
c746b25
def test_python_extras_subpkg_description_wrapping(basename_len, extra_len):
c746b25
    basename = 'x' * basename_len
c746b25
    extra = 'y' * extra_len
c746b25
    lines = rpm_eval(f'%python_extras_subpkg -n {basename} -F {extra}',
c746b25
                     version='6', release='7')
c746b25
    for idx, line in enumerate(lines):
c746b25
        if line.startswith('%description'):
c746b25
            start = idx + 1
c746b25
    lines = lines[start:]
c746b25
    assert all(len(l) < 80 for l in lines)
c746b25
    assert len(lines) < 6
c746b25
    if len(" ".join(lines[:-1])) < 80:
c746b25
        assert len(lines) == 2
c746b25
    expected_singleline = (f"This is a metapackage bringing in {extra} extras "
bc016cb
                           f"requires for {basename}. "
bc016cb
                           f"It makes sure the dependencies are installed.")
c746b25
    description_singleline = " ".join(lines)
c746b25
    assert description_singleline == expected_singleline
c746b25
c746b25
69b1b30
unversioned_macros = pytest.mark.parametrize('macro', [
69b1b30
    '%__python',
69b1b30
    '%python',
69b1b30
    '%python_version',
69b1b30
    '%python_version_nodots',
69b1b30
    '%python_sitelib',
69b1b30
    '%python_sitearch',
0086612
    '%python_platform',
06987f5
    '%python_platform_triplet',
06987f5
    '%python_ext_suffix',
69b1b30
    '%py_shebang_fix',
69b1b30
    '%py_build',
69b1b30
    '%py_build_egg',
69b1b30
    '%py_build_wheel',
69b1b30
    '%py_install',
69b1b30
    '%py_install_egg',
69b1b30
    '%py_install_wheel',
c2305ea
    '%py_check_import',
69b1b30
])
69b1b30
69b1b30
69b1b30
@unversioned_macros
69b1b30
def test_unversioned_python_errors(macro):
69b1b30
    lines = rpm_eval(macro, fails=True)
39166a7
    assert lines == ['error: attempt to use unversioned python, '
39166a7
                     'define %__python to /usr/bin/python2 or /usr/bin/python3 explicitly']
69b1b30
69b1b30
69b1b30
@unversioned_macros
69b1b30
def test_unversioned_python_works_when_defined(macro):
69b1b30
    macro3 = macro.replace('python', 'python3').replace('py_', 'py3_')
69b1b30
    assert rpm_eval(macro, __python='/usr/bin/python3') == rpm_eval(macro3)
06987f5
06987f5
06987f5
# we could rework the test for multiple architectures, but the Fedora CI currently only runs on x86_64
06987f5
x86_64_only = pytest.mark.skipif(platform.machine() != "x86_64", reason="works on x86_64 only")
06987f5
06987f5
06987f5
@x86_64_only
06987f5
def test_platform_triplet():
39166a7
    assert rpm_eval("%python3_platform_triplet") == ["x86_64-linux-gnu"]
06987f5
06987f5
06987f5
@x86_64_only
06987f5
def test_ext_suffix():
39166a7
    assert rpm_eval("%python3_ext_suffix") == [f".cpython-{XY}-x86_64-linux-gnu.so"]
9d2fcef
9d2fcef
76209d7
def test_python_sitelib_value_python3():
9d2fcef
    macro = '%python_sitelib'
39166a7
    assert rpm_eval(macro, __python='%__python3') == [f'/usr/lib/python{X_Y}/site-packages']
9d2fcef
9d2fcef
76209d7
def test_python_sitelib_value_alternate_python(alt_x_y):
76209d7
    macro = '%python_sitelib'
76209d7
    assert rpm_eval(macro, __python=f'/usr/bin/python{alt_x_y}') == [f'/usr/lib/python{alt_x_y}/site-packages']
76209d7
76209d7
76209d7
def test_python3_sitelib_value_default():
9d2fcef
    macro = '%python3_sitelib'
39166a7
    assert rpm_eval(macro) == [f'/usr/lib/python{X_Y}/site-packages']
9d2fcef
9d2fcef
76209d7
def test_python3_sitelib_value_alternate_python(alt_x_y):
76209d7
    macro = '%python3_sitelib'
a8b2654
    assert (rpm_eval(macro, __python3=f'/usr/bin/python{alt_x_y}') ==
a8b2654
            rpm_eval(macro, python3_pkgversion=alt_x_y) ==
a8b2654
            [f'/usr/lib/python{alt_x_y}/site-packages'])
76209d7
76209d7
76209d7
def test_python_sitearch_value_python3(lib):
9d2fcef
    macro = '%python_sitearch'
39166a7
    assert rpm_eval(macro, __python='%__python3') == [f'/usr/{lib}/python{X_Y}/site-packages']
9d2fcef
9d2fcef
76209d7
def test_python_sitearch_value_alternate_python(lib, alt_x_y):
76209d7
    macro = '%python_sitearch'
76209d7
    assert rpm_eval(macro, __python=f'/usr/bin/python{alt_x_y}') == [f'/usr/{lib}/python{alt_x_y}/site-packages']
76209d7
76209d7
76209d7
def test_python3_sitearch_value_default(lib):
9d2fcef
    macro = '%python3_sitearch'
39166a7
    assert rpm_eval(macro) == [f'/usr/{lib}/python{X_Y}/site-packages']
c2305ea
c2305ea
76209d7
def test_python3_sitearch_value_alternate_python(lib, alt_x_y):
76209d7
    macro = '%python3_sitearch'
a8b2654
    assert (rpm_eval(macro, __python3=f'/usr/bin/python{alt_x_y}') ==
a8b2654
            rpm_eval(macro, python3_pkgversion=alt_x_y) ==
a8b2654
            [f'/usr/{lib}/python{alt_x_y}/site-packages'])
76209d7
76209d7
c2305ea
@pytest.mark.parametrize(
b20d8aa
    'args, expected_args',
c2305ea
    [
b20d8aa
        ('six', 'six'),
b20d8aa
        ('-f foo.txt', '-f foo.txt'),
b20d8aa
        ('-t -f foo.txt six, seven', '-t -f foo.txt six, seven'),
b20d8aa
        ('-e "foo*" -f foo.txt six, seven', '-e "foo*" -f foo.txt six, seven'),
b20d8aa
        ('six.quarter six.half,, SIX', 'six.quarter six.half,, SIX'),
b20d8aa
        ('-f foo.txt six\nsix.half\nSIX', '-f foo.txt six six.half SIX'),
b20d8aa
        ('six \\ -e six.half', 'six -e six.half'),
c2305ea
    ]
c2305ea
)
76209d7
@pytest.mark.parametrize('__python3',
76209d7
                         [None,
76209d7
                          f'/usr/bin/python{X_Y}',
76209d7
                          '/usr/bin/pythonX.Y'])
b20d8aa
def test_py3_check_import(args, expected_args, __python3, lib):
c2305ea
    x_y = X_Y
2d0673a
    macros = {
c2305ea
        'buildroot': 'BUILDROOT',
2d0673a
        '_rpmconfigdir': 'RPMCONFIGDIR',
824ef3d
        'py3_shebang_flags': 's',
c2305ea
    }
c2305ea
    if __python3 is not None:
76209d7
        if 'X.Y' in __python3:
76209d7
            __python3 = __python3.replace('X.Y', get_alt_x_y())
2d0673a
        macros['__python3'] = __python3
c2305ea
        # If the __python3 command has version at the end, parse it and expect it.
c2305ea
        # Note that the command is used to determine %python3_sitelib and %python3_sitearch,
c2305ea
        # so we only test known CPython schemes here and not PyPy for simplicity.
c2305ea
        if (match := re.match(r'.+python(\d+\.\d+)$', __python3)):
c2305ea
            x_y = match.group(1)
c2305ea
b20d8aa
    invocation = '%{py3_check_import ' + args +'}'
b20d8aa
    lines = rpm_eval(invocation, **macros)
c2305ea
c2305ea
    # An equality check is a bit inflexible here,
c2305ea
    # every time we change the macro we need to change this test.
c2305ea
    # However actually executing it and verifying the result is much harder :/
c2305ea
    # At least, let's make the lines saner to check:
c2305ea
    lines = [line.rstrip('\\').strip() for line in lines]
c2305ea
    expected = textwrap.dedent(fr"""
c2305ea
        PATH="BUILDROOT/usr/bin:$PATH"
c2305ea
        PYTHONPATH="${{PYTHONPATH:-BUILDROOT/usr/{lib}/python{x_y}/site-packages:BUILDROOT/usr/lib/python{x_y}/site-packages}}"
9d81ad4
        _PYTHONSITE="BUILDROOT/usr/{lib}/python{x_y}/site-packages:BUILDROOT/usr/lib/python{x_y}/site-packages"
c2305ea
        PYTHONDONTWRITEBYTECODE=1
824ef3d
        {__python3 or '/usr/bin/python3'} -s RPMCONFIGDIR/redhat/import_all_modules.py {expected_args}
c2305ea
        """)
c2305ea
    assert lines == expected.splitlines()
824ef3d
824ef3d
824ef3d
@pytest.mark.parametrize(
824ef3d
    'shebang_flags_value, expected_shebang_flags',
824ef3d
    [
824ef3d
        ('s', '-s'),
824ef3d
        ('%{nil}', ''),
824ef3d
        (None, ''),
824ef3d
        ('Es', '-Es'),
824ef3d
    ]
824ef3d
)
824ef3d
def test_py3_check_import_respects_shebang_flags(shebang_flags_value, expected_shebang_flags, lib):
824ef3d
    macros = {
824ef3d
        '_rpmconfigdir': 'RPMCONFIGDIR',
824ef3d
        '__python3': '/usr/bin/python3',
824ef3d
        'py3_shebang_flags': shebang_flags_value,
824ef3d
    }
824ef3d
    lines = rpm_eval('%py3_check_import sys', **macros)
824ef3d
    # Compare the last line of the command, that's where lua part is evaluated
824ef3d
    expected = f'/usr/bin/python3 {expected_shebang_flags} RPMCONFIGDIR/redhat/import_all_modules.py sys'
824ef3d
    assert  lines[-1].strip() == expected