Skip to content

Commit

Permalink
Fix corner case in cached dependency resolution
Browse files Browse the repository at this point in the history
  Ref: #178

  The error is as follows:

  1. Two resolvables, one pinned, the other not.
  2. First pex run with clean cache works.
  3. Change the pinned version.
  4. Second pex run fails with Unsatisfiable.

  The problem is that the non-pinned resolvable locates the cached dep
  and only returns it. Since that version doesn't match the pinned
  version, we fail. This fixes that behavior by returning both the
  cached version and all uncached pypi packages for unpinned
  resolvables.

  We change the pinned version to test the use case where you have a
  cached version of a dep and then you transitively pull in another
  requirement that either has a tighter bound or a pin on a version not
  found in the cache.
  • Loading branch information
butlern committed Feb 18, 2017
1 parent 6ed76cb commit f18543b
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 2 deletions.
8 changes: 6 additions & 2 deletions pex/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,12 @@ def package_iterator(self, resolvable, existing=None):

if self.__cache_ttl:
packages = self.filter_packages_by_ttl(packages, self.__cache_ttl)
if packages:
return packages
# Return both cached packages and the pypi packages. The edge case is if a inexact
# resolvable finds a cached version, it needs to return that cached package AND all pypi
# packages because the cached version might not match another resolvable which has a tighter
# bound or an exact version.
return packages + super(CachingResolver, self).package_iterator(resolvable,
existing=existing)

return super(CachingResolver, self).package_iterator(resolvable, existing=existing)

Expand Down
24 changes: 24 additions & 0 deletions tests/test_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from twitter.common.contextutil import temporary_dir

from pex.common import safe_copy
from pex.crawler import Crawler
from pex.fetcher import Fetcher
from pex.package import EggPackage, SourcePackage
from pex.resolvable import ResolvableRequirement
Expand Down Expand Up @@ -48,6 +49,29 @@ def test_diamond_local_resolve_cached():
assert len(dists) == 2


def test_cached_dependency_pinned_unpinned_resolution_multi_run():
# This exercises the issue described here: https://github.com/pantsbuild/pex/issues/178
project1_0_0 = make_sdist(name='project', version='1.0.0')
project1_1_0 = make_sdist(name='project', version='1.1.0')

with temporary_dir() as td:
for sdist in (project1_0_0, project1_1_0):
safe_copy(sdist, os.path.join(td, os.path.basename(sdist)))
fetchers = [Fetcher([td])]
with temporary_dir() as cd:
# First run, pinning 1.0.0 in the cache
dists = resolve(['project', 'project==1.0.0'], fetchers=fetchers, cache=cd, cache_ttl=1000)
assert len(dists) == 1
assert dists[0].version == '1.0.0'
# This simulates separate invocations of pex but allows us to keep the same tmp cache dir
Crawler.reset_cache()
# Second, run, the unbounded 'project' req will find the 1.0.0 in the cache. But should also
# return SourcePackages found in td
dists = resolve(['project', 'project==1.1.0'], fetchers=fetchers, cache=cd, cache_ttl=1000)
assert len(dists) == 1
assert dists[0].version == '1.1.0'


def test_resolve_prereleases():
stable_dep = make_sdist(name='dep', version='2.0.0')
prerelease_dep = make_sdist(name='dep', version='3.0.0rc3')
Expand Down

0 comments on commit f18543b

Please sign in to comment.