summaryrefslogtreecommitdiff
path: root/tests/unit/test_utils_wheel.py
blob: 4e8e72be64a1dfa937ed31da6b000b7900cba028 (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
137
138
139
140
import os
from contextlib import ExitStack
from email import message_from_string
from io import BytesIO
from pathlib import Path
from typing import Callable, Iterator
from zipfile import ZipFile

import pytest

from pip._internal.exceptions import UnsupportedWheel
from pip._internal.utils import wheel
from tests.lib import TestData

_ZipDir = Callable[[Path], ZipFile]


@pytest.fixture
def zip_dir() -> Iterator[_ZipDir]:
    def make_zip(path: Path) -> ZipFile:
        buf = BytesIO()
        with ZipFile(buf, "w", allowZip64=True) as z:
            for dirpath, _, filenames in os.walk(path):
                for filename in filenames:
                    file_path = os.path.join(path, dirpath, filename)
                    # Zip files must always have / as path separator
                    archive_path = os.path.relpath(file_path, path).replace(
                        os.pathsep, "/"
                    )
                    z.write(file_path, archive_path)

        return stack.enter_context(ZipFile(buf, "r", allowZip64=True))

    stack = ExitStack()
    with stack:
        yield make_zip


def test_wheel_dist_info_dir_found(tmpdir: Path, zip_dir: _ZipDir) -> None:
    expected = "simple-0.1.dist-info"
    dist_info_dir = tmpdir / expected
    dist_info_dir.mkdir()
    dist_info_dir.joinpath("WHEEL").touch()
    assert wheel.wheel_dist_info_dir(zip_dir(tmpdir), "simple") == expected


def test_wheel_dist_info_dir_multiple(tmpdir: Path, zip_dir: _ZipDir) -> None:
    dist_info_dir_1 = tmpdir / "simple-0.1.dist-info"
    dist_info_dir_1.mkdir()
    dist_info_dir_1.joinpath("WHEEL").touch()
    dist_info_dir_2 = tmpdir / "unrelated-0.1.dist-info"
    dist_info_dir_2.mkdir()
    dist_info_dir_2.joinpath("WHEEL").touch()
    with pytest.raises(UnsupportedWheel) as e:
        wheel.wheel_dist_info_dir(zip_dir(tmpdir), "simple")
    assert "multiple .dist-info directories found" in str(e.value)


def test_wheel_dist_info_dir_none(tmpdir: Path, zip_dir: _ZipDir) -> None:
    with pytest.raises(UnsupportedWheel) as e:
        wheel.wheel_dist_info_dir(zip_dir(tmpdir), "simple")
    assert "directory not found" in str(e.value)


def test_wheel_dist_info_dir_wrong_name(tmpdir: Path, zip_dir: _ZipDir) -> None:
    dist_info_dir = tmpdir / "unrelated-0.1.dist-info"
    dist_info_dir.mkdir()
    dist_info_dir.joinpath("WHEEL").touch()
    with pytest.raises(UnsupportedWheel) as e:
        wheel.wheel_dist_info_dir(zip_dir(tmpdir), "simple")
    assert "does not start with 'simple'" in str(e.value)


def test_wheel_version_ok(data: TestData) -> None:
    assert wheel.wheel_version(message_from_string("Wheel-Version: 1.9")) == (1, 9)


def test_wheel_metadata_fails_missing_wheel(tmpdir: Path, zip_dir: _ZipDir) -> None:
    dist_info_dir = tmpdir / "simple-0.1.0.dist-info"
    dist_info_dir.mkdir()
    dist_info_dir.joinpath("METADATA").touch()

    with pytest.raises(UnsupportedWheel) as e:
        wheel.wheel_metadata(zip_dir(tmpdir), dist_info_dir.name)
    assert "could not read" in str(e.value)


def test_wheel_metadata_fails_on_bad_encoding(tmpdir: Path, zip_dir: _ZipDir) -> None:
    dist_info_dir = tmpdir / "simple-0.1.0.dist-info"
    dist_info_dir.mkdir()
    dist_info_dir.joinpath("METADATA").touch()
    dist_info_dir.joinpath("WHEEL").write_bytes(b"\xff")

    with pytest.raises(UnsupportedWheel) as e:
        wheel.wheel_metadata(zip_dir(tmpdir), dist_info_dir.name)
    assert "error decoding" in str(e.value)


def test_wheel_version_fails_on_no_wheel_version() -> None:
    with pytest.raises(UnsupportedWheel) as e:
        wheel.wheel_version(message_from_string(""))
    assert "missing Wheel-Version" in str(e.value)


@pytest.mark.parametrize(
    "version",
    [
        ("",),
        ("1.b",),
        ("1.",),
    ],
)
def test_wheel_version_fails_on_bad_wheel_version(version: str) -> None:
    with pytest.raises(UnsupportedWheel) as e:
        wheel.wheel_version(message_from_string(f"Wheel-Version: {version}"))
    assert "invalid Wheel-Version" in str(e.value)


def test_check_compatibility() -> None:
    name = "test"
    vc = wheel.VERSION_COMPATIBLE

    # Major version is higher - should be incompatible
    higher_v = (vc[0] + 1, vc[1])

    # test raises with correct error
    with pytest.raises(UnsupportedWheel) as e:
        wheel.check_compatibility(higher_v, name)
    assert "is not compatible" in str(e)

    # Should only log.warning - minor version is greater
    higher_v = (vc[0], vc[1] + 1)
    wheel.check_compatibility(higher_v, name)

    # These should work fine
    wheel.check_compatibility(wheel.VERSION_COMPATIBLE, name)

    # E.g if wheel to install is 1.0 and we support up to 1.2
    lower_v = (vc[0], max(0, vc[1] - 1))
    wheel.check_compatibility(lower_v, name)