1# coding: utf-8 2from __future__ import unicode_literals 3 4import sys 5import tempfile 6import os 7import zipfile 8import datetime 9import time 10import subprocess 11import stat 12import distutils.dist 13import distutils.command.install_egg_info 14 15from pkg_resources.extern.six.moves import map 16 17import pytest 18 19import pkg_resources 20 21try: 22 unicode 23except NameError: 24 unicode = str 25 26 27def timestamp(dt): 28 """ 29 Return a timestamp for a local, naive datetime instance. 30 """ 31 try: 32 return dt.timestamp() 33 except AttributeError: 34 # Python 3.2 and earlier 35 return time.mktime(dt.timetuple()) 36 37 38class EggRemover(unicode): 39 def __call__(self): 40 if self in sys.path: 41 sys.path.remove(self) 42 if os.path.exists(self): 43 os.remove(self) 44 45 46class TestZipProvider(object): 47 finalizers = [] 48 49 ref_time = datetime.datetime(2013, 5, 12, 13, 25, 0) 50 "A reference time for a file modification" 51 52 @classmethod 53 def setup_class(cls): 54 "create a zip egg and add it to sys.path" 55 egg = tempfile.NamedTemporaryFile(suffix='.egg', delete=False) 56 zip_egg = zipfile.ZipFile(egg, 'w') 57 zip_info = zipfile.ZipInfo() 58 zip_info.filename = 'mod.py' 59 zip_info.date_time = cls.ref_time.timetuple() 60 zip_egg.writestr(zip_info, 'x = 3\n') 61 zip_info = zipfile.ZipInfo() 62 zip_info.filename = 'data.dat' 63 zip_info.date_time = cls.ref_time.timetuple() 64 zip_egg.writestr(zip_info, 'hello, world!') 65 zip_info = zipfile.ZipInfo() 66 zip_info.filename = 'subdir/mod2.py' 67 zip_info.date_time = cls.ref_time.timetuple() 68 zip_egg.writestr(zip_info, 'x = 6\n') 69 zip_info = zipfile.ZipInfo() 70 zip_info.filename = 'subdir/data2.dat' 71 zip_info.date_time = cls.ref_time.timetuple() 72 zip_egg.writestr(zip_info, 'goodbye, world!') 73 zip_egg.close() 74 egg.close() 75 76 sys.path.append(egg.name) 77 subdir = os.path.join(egg.name, 'subdir') 78 sys.path.append(subdir) 79 cls.finalizers.append(EggRemover(subdir)) 80 cls.finalizers.append(EggRemover(egg.name)) 81 82 @classmethod 83 def teardown_class(cls): 84 for finalizer in cls.finalizers: 85 finalizer() 86 87 def test_resource_listdir(self): 88 import mod 89 zp = pkg_resources.ZipProvider(mod) 90 91 expected_root = ['data.dat', 'mod.py', 'subdir'] 92 assert sorted(zp.resource_listdir('')) == expected_root 93 assert sorted(zp.resource_listdir('/')) == expected_root 94 95 expected_subdir = ['data2.dat', 'mod2.py'] 96 assert sorted(zp.resource_listdir('subdir')) == expected_subdir 97 assert sorted(zp.resource_listdir('subdir/')) == expected_subdir 98 99 assert zp.resource_listdir('nonexistent') == [] 100 assert zp.resource_listdir('nonexistent/') == [] 101 102 import mod2 103 zp2 = pkg_resources.ZipProvider(mod2) 104 105 assert sorted(zp2.resource_listdir('')) == expected_subdir 106 assert sorted(zp2.resource_listdir('/')) == expected_subdir 107 108 assert zp2.resource_listdir('subdir') == [] 109 assert zp2.resource_listdir('subdir/') == [] 110 111 def test_resource_filename_rewrites_on_change(self): 112 """ 113 If a previous call to get_resource_filename has saved the file, but 114 the file has been subsequently mutated with different file of the 115 same size and modification time, it should not be overwritten on a 116 subsequent call to get_resource_filename. 117 """ 118 import mod 119 manager = pkg_resources.ResourceManager() 120 zp = pkg_resources.ZipProvider(mod) 121 filename = zp.get_resource_filename(manager, 'data.dat') 122 actual = datetime.datetime.fromtimestamp(os.stat(filename).st_mtime) 123 assert actual == self.ref_time 124 f = open(filename, 'w') 125 f.write('hello, world?') 126 f.close() 127 ts = timestamp(self.ref_time) 128 os.utime(filename, (ts, ts)) 129 filename = zp.get_resource_filename(manager, 'data.dat') 130 with open(filename) as f: 131 assert f.read() == 'hello, world!' 132 manager.cleanup_resources() 133 134 135class TestResourceManager(object): 136 def test_get_cache_path(self): 137 mgr = pkg_resources.ResourceManager() 138 path = mgr.get_cache_path('foo') 139 type_ = str(type(path)) 140 message = "Unexpected type from get_cache_path: " + type_ 141 assert isinstance(path, (unicode, str)), message 142 143 144class TestIndependence: 145 """ 146 Tests to ensure that pkg_resources runs independently from setuptools. 147 """ 148 149 def test_setuptools_not_imported(self): 150 """ 151 In a separate Python environment, import pkg_resources and assert 152 that action doesn't cause setuptools to be imported. 153 """ 154 lines = ( 155 'import pkg_resources', 156 'import sys', 157 ( 158 'assert "setuptools" not in sys.modules, ' 159 '"setuptools was imported"' 160 ), 161 ) 162 cmd = [sys.executable, '-c', '; '.join(lines)] 163 subprocess.check_call(cmd) 164 165 166class TestDeepVersionLookupDistutils(object): 167 @pytest.fixture 168 def env(self, tmpdir): 169 """ 170 Create a package environment, similar to a virtualenv, 171 in which packages are installed. 172 """ 173 174 class Environment(str): 175 pass 176 177 env = Environment(tmpdir) 178 tmpdir.chmod(stat.S_IRWXU) 179 subs = 'home', 'lib', 'scripts', 'data', 'egg-base' 180 env.paths = dict( 181 (dirname, str(tmpdir / dirname)) 182 for dirname in subs 183 ) 184 list(map(os.mkdir, env.paths.values())) 185 return env 186 187 def create_foo_pkg(self, env, version): 188 """ 189 Create a foo package installed (distutils-style) to env.paths['lib'] 190 as version. 191 """ 192 ld = "This package has unicode metadata! ❄" 193 attrs = dict(name='foo', version=version, long_description=ld) 194 dist = distutils.dist.Distribution(attrs) 195 iei_cmd = distutils.command.install_egg_info.install_egg_info(dist) 196 iei_cmd.initialize_options() 197 iei_cmd.install_dir = env.paths['lib'] 198 iei_cmd.finalize_options() 199 iei_cmd.run() 200 201 def test_version_resolved_from_egg_info(self, env): 202 version = '1.11.0.dev0+2329eae' 203 self.create_foo_pkg(env, version) 204 205 # this requirement parsing will raise a VersionConflict unless the 206 # .egg-info file is parsed (see #419 on BitBucket) 207 req = pkg_resources.Requirement.parse('foo>=1.9') 208 dist = pkg_resources.WorkingSet([env.paths['lib']]).find(req) 209 assert dist.version == version 210