| import functools |
| import inspect |
| import re |
| import textwrap |
|
|
| import pytest |
|
|
| import pkg_resources |
|
|
| from .test_resources import Metadata |
|
|
|
|
| def strip_comments(s): |
| return '\n'.join( |
| line |
| for line in s.split('\n') |
| if line.strip() and not line.strip().startswith('#') |
| ) |
|
|
|
|
| def parse_distributions(s): |
| """ |
| Parse a series of distribution specs of the form: |
| {project_name}-{version} |
| [optional, indented requirements specification] |
| |
| Example: |
| |
| foo-0.2 |
| bar-1.0 |
| foo>=3.0 |
| [feature] |
| baz |
| |
| yield 2 distributions: |
| - project_name=foo, version=0.2 |
| - project_name=bar, version=1.0, |
| requires=['foo>=3.0', 'baz; extra=="feature"'] |
| """ |
| s = s.strip() |
| for spec in re.split(r'\n(?=[^\s])', s): |
| if not spec: |
| continue |
| fields = spec.split('\n', 1) |
| assert 1 <= len(fields) <= 2 |
| name, version = fields.pop(0).rsplit('-', 1) |
| if fields: |
| requires = textwrap.dedent(fields.pop(0)) |
| metadata = Metadata(('requires.txt', requires)) |
| else: |
| metadata = None |
| dist = pkg_resources.Distribution( |
| project_name=name, version=version, metadata=metadata |
| ) |
| yield dist |
|
|
|
|
| class FakeInstaller: |
| def __init__(self, installable_dists) -> None: |
| self._installable_dists = installable_dists |
|
|
| def __call__(self, req): |
| return next( |
| iter(filter(lambda dist: dist in req, self._installable_dists)), None |
| ) |
|
|
|
|
| def parametrize_test_working_set_resolve(*test_list): |
| idlist = [] |
| argvalues = [] |
| for test in test_list: |
| ( |
| name, |
| installed_dists, |
| installable_dists, |
| requirements, |
| expected1, |
| expected2, |
| ) = ( |
| strip_comments(s.lstrip()) |
| for s in textwrap.dedent(test).lstrip().split('\n\n', 5) |
| ) |
| installed_dists = list(parse_distributions(installed_dists)) |
| installable_dists = list(parse_distributions(installable_dists)) |
| requirements = list(pkg_resources.parse_requirements(requirements)) |
| for id_, replace_conflicting, expected in ( |
| (name, False, expected1), |
| (name + '_replace_conflicting', True, expected2), |
| ): |
| idlist.append(id_) |
| expected = strip_comments(expected.strip()) |
| if re.match(r'\w+$', expected): |
| expected = getattr(pkg_resources, expected) |
| assert issubclass(expected, Exception) |
| else: |
| expected = list(parse_distributions(expected)) |
| argvalues.append( |
| pytest.param( |
| installed_dists, |
| installable_dists, |
| requirements, |
| replace_conflicting, |
| expected, |
| ) |
| ) |
| return pytest.mark.parametrize( |
| 'installed_dists,installable_dists,' |
| 'requirements,replace_conflicting,' |
| 'resolved_dists_or_exception', |
| argvalues, |
| ids=idlist, |
| ) |
|
|
|
|
| @parametrize_test_working_set_resolve( |
| """ |
| # id |
| noop |
| |
| # installed |
| |
| # installable |
| |
| # wanted |
| |
| # resolved |
| |
| # resolved [replace conflicting] |
| """, |
| """ |
| # id |
| already_installed |
| |
| # installed |
| foo-3.0 |
| |
| # installable |
| |
| # wanted |
| foo>=2.1,!=3.1,<4 |
| |
| # resolved |
| foo-3.0 |
| |
| # resolved [replace conflicting] |
| foo-3.0 |
| """, |
| """ |
| # id |
| installable_not_installed |
| |
| # installed |
| |
| # installable |
| foo-3.0 |
| foo-4.0 |
| |
| # wanted |
| foo>=2.1,!=3.1,<4 |
| |
| # resolved |
| foo-3.0 |
| |
| # resolved [replace conflicting] |
| foo-3.0 |
| """, |
| """ |
| # id |
| not_installable |
| |
| # installed |
| |
| # installable |
| |
| # wanted |
| foo>=2.1,!=3.1,<4 |
| |
| # resolved |
| DistributionNotFound |
| |
| # resolved [replace conflicting] |
| DistributionNotFound |
| """, |
| """ |
| # id |
| no_matching_version |
| |
| # installed |
| |
| # installable |
| foo-3.1 |
| |
| # wanted |
| foo>=2.1,!=3.1,<4 |
| |
| # resolved |
| DistributionNotFound |
| |
| # resolved [replace conflicting] |
| DistributionNotFound |
| """, |
| """ |
| # id |
| installable_with_installed_conflict |
| |
| # installed |
| foo-3.1 |
| |
| # installable |
| foo-3.5 |
| |
| # wanted |
| foo>=2.1,!=3.1,<4 |
| |
| # resolved |
| VersionConflict |
| |
| # resolved [replace conflicting] |
| foo-3.5 |
| """, |
| """ |
| # id |
| not_installable_with_installed_conflict |
| |
| # installed |
| foo-3.1 |
| |
| # installable |
| |
| # wanted |
| foo>=2.1,!=3.1,<4 |
| |
| # resolved |
| VersionConflict |
| |
| # resolved [replace conflicting] |
| DistributionNotFound |
| """, |
| """ |
| # id |
| installed_with_installed_require |
| |
| # installed |
| foo-3.9 |
| baz-0.1 |
| foo>=2.1,!=3.1,<4 |
| |
| # installable |
| |
| # wanted |
| baz |
| |
| # resolved |
| foo-3.9 |
| baz-0.1 |
| |
| # resolved [replace conflicting] |
| foo-3.9 |
| baz-0.1 |
| """, |
| """ |
| # id |
| installed_with_conflicting_installed_require |
| |
| # installed |
| foo-5 |
| baz-0.1 |
| foo>=2.1,!=3.1,<4 |
| |
| # installable |
| |
| # wanted |
| baz |
| |
| # resolved |
| VersionConflict |
| |
| # resolved [replace conflicting] |
| DistributionNotFound |
| """, |
| """ |
| # id |
| installed_with_installable_conflicting_require |
| |
| # installed |
| foo-5 |
| baz-0.1 |
| foo>=2.1,!=3.1,<4 |
| |
| # installable |
| foo-2.9 |
| |
| # wanted |
| baz |
| |
| # resolved |
| VersionConflict |
| |
| # resolved [replace conflicting] |
| baz-0.1 |
| foo-2.9 |
| """, |
| """ |
| # id |
| installed_with_installable_require |
| |
| # installed |
| baz-0.1 |
| foo>=2.1,!=3.1,<4 |
| |
| # installable |
| foo-3.9 |
| |
| # wanted |
| baz |
| |
| # resolved |
| foo-3.9 |
| baz-0.1 |
| |
| # resolved [replace conflicting] |
| foo-3.9 |
| baz-0.1 |
| """, |
| """ |
| # id |
| installable_with_installed_require |
| |
| # installed |
| foo-3.9 |
| |
| # installable |
| baz-0.1 |
| foo>=2.1,!=3.1,<4 |
| |
| # wanted |
| baz |
| |
| # resolved |
| foo-3.9 |
| baz-0.1 |
| |
| # resolved [replace conflicting] |
| foo-3.9 |
| baz-0.1 |
| """, |
| """ |
| # id |
| installable_with_installable_require |
| |
| # installed |
| |
| # installable |
| foo-3.9 |
| baz-0.1 |
| foo>=2.1,!=3.1,<4 |
| |
| # wanted |
| baz |
| |
| # resolved |
| foo-3.9 |
| baz-0.1 |
| |
| # resolved [replace conflicting] |
| foo-3.9 |
| baz-0.1 |
| """, |
| """ |
| # id |
| installable_with_conflicting_installable_require |
| |
| # installed |
| foo-5 |
| |
| # installable |
| foo-2.9 |
| baz-0.1 |
| foo>=2.1,!=3.1,<4 |
| |
| # wanted |
| baz |
| |
| # resolved |
| VersionConflict |
| |
| # resolved [replace conflicting] |
| baz-0.1 |
| foo-2.9 |
| """, |
| """ |
| # id |
| conflicting_installables |
| |
| # installed |
| |
| # installable |
| foo-2.9 |
| foo-5.0 |
| |
| # wanted |
| foo>=2.1,!=3.1,<4 |
| foo>=4 |
| |
| # resolved |
| VersionConflict |
| |
| # resolved [replace conflicting] |
| VersionConflict |
| """, |
| """ |
| # id |
| installables_with_conflicting_requires |
| |
| # installed |
| |
| # installable |
| foo-2.9 |
| dep==1.0 |
| baz-5.0 |
| dep==2.0 |
| dep-1.0 |
| dep-2.0 |
| |
| # wanted |
| foo |
| baz |
| |
| # resolved |
| VersionConflict |
| |
| # resolved [replace conflicting] |
| VersionConflict |
| """, |
| """ |
| # id |
| installables_with_conflicting_nested_requires |
| |
| # installed |
| |
| # installable |
| foo-2.9 |
| dep1 |
| dep1-1.0 |
| subdep<1.0 |
| baz-5.0 |
| dep2 |
| dep2-1.0 |
| subdep>1.0 |
| subdep-0.9 |
| subdep-1.1 |
| |
| # wanted |
| foo |
| baz |
| |
| # resolved |
| VersionConflict |
| |
| # resolved [replace conflicting] |
| VersionConflict |
| """, |
| """ |
| # id |
| wanted_normalized_name_installed_canonical |
| |
| # installed |
| foo.bar-3.6 |
| |
| # installable |
| |
| # wanted |
| foo-bar==3.6 |
| |
| # resolved |
| foo.bar-3.6 |
| |
| # resolved [replace conflicting] |
| foo.bar-3.6 |
| """, |
| ) |
| def test_working_set_resolve( |
| installed_dists, |
| installable_dists, |
| requirements, |
| replace_conflicting, |
| resolved_dists_or_exception, |
| ): |
| ws = pkg_resources.WorkingSet([]) |
| list(map(ws.add, installed_dists)) |
| resolve_call = functools.partial( |
| ws.resolve, |
| requirements, |
| installer=FakeInstaller(installable_dists), |
| replace_conflicting=replace_conflicting, |
| ) |
| if inspect.isclass(resolved_dists_or_exception): |
| with pytest.raises(resolved_dists_or_exception): |
| resolve_call() |
| else: |
| assert sorted(resolve_call()) == sorted(resolved_dists_or_exception) |
|
|