File: test_resolvers_swift.py

package info (click to toggle)
python-resolvelib 1.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, trixie
  • size: 16,504 kB
  • sloc: python: 2,278; javascript: 102; sh: 9; makefile: 3
file content (150 lines) | stat: -rw-r--r-- 4,664 bytes parent folder | download | duplicates (2)
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
141
142
143
144
145
146
147
148
149
150
import collections
import json
import operator
import os

import pytest

from resolvelib import AbstractProvider, Resolver

Requirement = collections.namedtuple("Requirement", "container constraint")
Candidate = collections.namedtuple("Candidate", "container version")


INPUTS_DIR = os.path.abspath(os.path.join(__file__, "..", "inputs"))

INPUT_NAMES = [n for n in os.listdir(INPUTS_DIR) if n.endswith(".json")]


def _parse_version(s):
    major, minor, rest = s.split(".", 2)
    if "-" in rest:
        patch, rest = rest.split("-", 1)
    else:
        patch, rest = rest, ""
    return (int(major), int(minor), int(patch), rest)


def _is_version_allowed(version, ranges):
    """Check version compatibility with Sematic Versioning."""
    for r in ranges:
        r = _parse_version(r)
        if r[0] != version[0]:
            continue
        if r[0] == 0:
            if version[:2] == r[:2] and version[2] >= r[2]:
                return True
        else:
            if version[1:] >= r[1:]:
                return True
    return False


def _calculate_preference(parsed_version):
    """Calculate preference of a version with Minimal Version Selection."""
    if parsed_version[0] == 0:
        return (
            0,
            -parsed_version[1],
            parsed_version[2],
            -len(parsed_version[3][:1]),
            parsed_version[3],
        )
    return (
        -parsed_version[0],
        parsed_version[1],
        parsed_version[2],
        -len(parsed_version[3][:1]),
        parsed_version[3],
    )


class SwiftInputProvider(AbstractProvider):
    def __init__(self, filename):
        with open(filename) as f:
            input_data = json.load(f)

        self.containers = {
            container["identifier"]: container for container in input_data["containers"]
        }
        self.root_requirements = [
            Requirement(self.containers[constraint["identifier"]], constraint)
            for constraint in input_data["constraints"]
        ]
        self.expectation = input_data["result"]

    def identify(self, requirement_or_candidate):
        return requirement_or_candidate.container["identifier"]

    def get_preference(
        self,
        identifier,
        resolutions,
        candidates,
        information,
        backtrack_causes,
    ):
        return sum(1 for _ in candidates[identifier])

    def _iter_matches(self, identifier, requirements, incompatibilities):
        bad_versions = {c.version for c in incompatibilities[identifier]}
        container = next(requirements[identifier]).container
        for version in container["versions"]:
            if version in bad_versions:
                continue
            ver = _parse_version(version)
            satisfied = all(
                _is_version_allowed(ver, r.constraint["requirement"])
                for r in requirements[identifier]
            )
            if not satisfied:
                continue
            preference = _calculate_preference(ver)
            yield (preference, Candidate(container, version))

    def find_matches(self, identifier, requirements, incompatibilities):
        matches = sorted(
            self._iter_matches(identifier, requirements, incompatibilities),
            key=operator.itemgetter(0),
            reverse=True,
        )
        for _, candidate in matches:
            yield candidate

    def is_satisfied_by(self, requirement, candidate):
        return _is_version_allowed(
            _parse_version(candidate.version),
            requirement.constraint["requirement"],
        )

    def _iter_dependencies(self, candidate):
        for constraint in candidate.container["versions"][candidate.version]:
            identifier = constraint["identifier"]
            try:
                container = self.containers[identifier]
            except KeyError:
                # Package does not exist. Return a stub without candidates.
                container = {"identifier": identifier, "versions": {}}
            yield Requirement(container, constraint)

    def get_dependencies(self, candidate):
        return list(self._iter_dependencies(candidate))


@pytest.fixture(
    params=[os.path.join(INPUTS_DIR, n) for n in INPUT_NAMES],
    ids=[n[:-5] for n in INPUT_NAMES],
)
def provider(request):
    return SwiftInputProvider(request.param)


def test_resolver(provider, reporter):
    resolver = Resolver(provider, reporter)
    result = resolver.resolve(provider.root_requirements)

    display = {
        identifier: candidate.version
        for identifier, candidate in result.mapping.items()
    }
    assert display == provider.expectation