summaryrefslogtreecommitdiffstats
path: root/tests/test_integration.py
blob: bc2f4fffcaaea714a145baac8773d54f578bf6df (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# SPDX-License-Identifier: MIT

import os
import os.path
import platform
import re
import shutil
import subprocess
import sys
import tarfile
import urllib.request

import filelock
import pytest

import build.__main__


IS_WINDOWS = sys.platform.startswith('win')
IS_PYPY3 = platform.python_implementation() == 'PyPy'


INTEGRATION_SOURCES = {
    'dateutil': ('dateutil/dateutil', '2.8.1'),
    'pip': ('pypa/pip', '20.2.1'),
    'Solaar': ('pwr-Solaar/Solaar', '1.0.3'),
    'flit': ('takluyver/flit', '2.3.0'),
}

_SDIST = re.compile('.*.tar.gz')
_WHEEL = re.compile('.*.whl')
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


def get_project(name, tmp_path):
    dest = tmp_path / name
    if name == 'build':
        # our own project is available in-source, just ignore development files

        def _ignore_folder(base, filenames):
            ignore = [n for n in filenames if n in excl or any(n.endswith(i) for i in ('_cache', '.egg-info', '.pyc'))]
            if os.path.basename == ROOT and 'build' in filenames:  # ignore build only at root (our module is build too)
                ignore.append('build')
            return ignore

        excl = '.tox', 'dist', '.git', '__pycache__', '.integration-sources', '.github', 'tests', 'docs'
        shutil.copytree(ROOT, str(dest), ignore=_ignore_folder)
        return dest

    # for other projects download from github and cache it
    tar_store = os.path.join(ROOT, '.integration-sources')
    try:
        os.makedirs(tar_store)
    except OSError:  # python 2 has no exist_ok, and checking with exists is not parallel safe
        pass  # just ignore, if the creation failed we will have another failure soon that will notify the user

    github_org_repo, version = INTEGRATION_SOURCES[name]
    tar_filename = f'{name}-{version}.tar.gz'
    tarball = os.path.join(tar_store, tar_filename)
    with filelock.FileLock(os.path.join(tar_store, f'{tar_filename}.lock')):
        if not os.path.exists(tarball):
            url = f'https://github.com/{github_org_repo}/archive/{version}.tar.gz'
            with urllib.request.urlopen(url) as request, open(tarball, 'wb') as file_handler:
                shutil.copyfileobj(request, file_handler)
    with tarfile.open(tarball, 'r:gz') as tar_handler:
        tar_handler.extractall(str(dest))
    return dest / f'{name}-{version}'


@pytest.mark.parametrize(
    'call',
    [
        None,  # via code
        [sys.executable, '-m', 'build'],  # module
        ['pyproject-build'],  # entrypoint
    ],
    ids=['code', 'module', 'entrypoint'],
)
@pytest.mark.parametrize(
    'args',
    [[], ['-x', '--no-isolation']],
    ids=['isolated', 'no_isolation'],
)
@pytest.mark.parametrize(
    'project',
    [
        'build',
        'pip',
        'dateutil',
        'Solaar',
        'flit',
    ],
)
@pytest.mark.isolated
def test_build(monkeypatch, project, args, call, tmp_path):
    if project == 'flit' and '--no-isolation' in args:
        pytest.xfail("can't build flit without isolation due to missing dependencies")
    if project == 'Solaar' and IS_WINDOWS and IS_PYPY3:
        pytest.xfail('Solaar fails building wheels via sdists on Windows on PyPy 3')

    monkeypatch.chdir(tmp_path)
    monkeypatch.setenv('SETUPTOOLS_SCM_PRETEND_VERSION', '0+dummy')  # for the projects that use setuptools_scm

    if call and call[0] == 'pyproject-build':
        exe_name = f"pyproject-build{'.exe' if sys.platform.startswith('win') else ''}"
        exe = os.path.join(os.path.dirname(sys.executable), exe_name)
        if os.path.exists(exe):
            call[0] = exe
        else:
            pytest.skip('Running via PYTHONPATH, so the pyproject-build entrypoint is not available')
    path = get_project(project, tmp_path)
    pkgs = tmp_path / 'pkgs'
    args = [str(path), '-o', str(pkgs)] + args

    if call is None:
        build.__main__.main(args)
    else:
        subprocess.check_call(call + args)

    pkg_names = os.listdir(str(pkgs))
    assert list(filter(_SDIST.match, pkg_names))
    assert list(filter(_WHEEL.match, pkg_names))


def test_isolation(tmp_dir, package_test_flit, mocker):
    try:
        import flit_core  # noqa: F401
    except ModuleNotFoundError:
        pass
    else:
        pytest.xfail('flit_core is available -- we want it missing!')  # pragma: no cover

    mocker.patch('build.__main__._error')

    build.__main__.main([package_test_flit, '-o', tmp_dir, '--no-isolation'])
    build.__main__._error.assert_called_with("Backend 'flit_core.buildapi' is not available.")