import inspect import re import textwrap import functools import pytest import pkg_resources from .test_resources import Metadata def strip_comments(s): return '\n'.join( l for l in s.split('\n') if l.strip() and not l.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('\n(?=[^\s])', s): if not spec: continue fields = spec.split('\n', 1) assert 1 <= len(fields) <= 2 name, version = fields.pop(0).split('-') 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(object): def __init__(self, installable_dists): 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('\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 ''', ) 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)