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 17, 2017
1 parent 6ed76cb commit 15ccd05
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 2 deletions.
7 changes: 5 additions & 2 deletions pex/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,11 @@ 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
23 changes: 23 additions & 0 deletions tests/test_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from pex.resolver import Resolver, Unsatisfiable, _ResolvableSet, resolve
from pex.resolver_options import ResolverOptionsBuilder
from pex.testing import make_sdist
from pex.crawler import Crawler


def test_empty_resolve():
Expand Down Expand Up @@ -47,6 +48,28 @@ def test_diamond_local_resolve_cached():
dists = resolve(['project1', 'project2'], fetchers=fetchers, cache=cd, cache_ttl=1000)
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')
Expand Down

0 comments on commit 15ccd05

Please sign in to comment.