1"""Tests for the 'setuptools' package""" 2 3import sys 4import os 5import distutils.core 6import distutils.cmd 7from distutils.errors import DistutilsOptionError, DistutilsPlatformError 8from distutils.errors import DistutilsSetupError 9from distutils.core import Extension 10from distutils.version import LooseVersion 11 12import pytest 13 14import setuptools 15import setuptools.dist 16import setuptools.depends as dep 17from setuptools import Feature 18from setuptools.depends import Require 19from setuptools.extern import six 20 21 22def makeSetup(**args): 23 """Return distribution from 'setup(**args)', without executing commands""" 24 25 distutils.core._setup_stop_after = "commandline" 26 27 # Don't let system command line leak into tests! 28 args.setdefault('script_args', ['install']) 29 30 try: 31 return setuptools.setup(**args) 32 finally: 33 distutils.core._setup_stop_after = None 34 35 36needs_bytecode = pytest.mark.skipif( 37 not hasattr(dep, 'get_module_constant'), 38 reason="bytecode support not available", 39) 40 41 42class TestDepends: 43 def testExtractConst(self): 44 if not hasattr(dep, 'extract_constant'): 45 # skip on non-bytecode platforms 46 return 47 48 def f1(): 49 global x, y, z 50 x = "test" 51 y = z 52 53 fc = six.get_function_code(f1) 54 55 # unrecognized name 56 assert dep.extract_constant(fc, 'q', -1) is None 57 58 # constant assigned 59 dep.extract_constant(fc, 'x', -1) == "test" 60 61 # expression assigned 62 dep.extract_constant(fc, 'y', -1) == -1 63 64 # recognized name, not assigned 65 dep.extract_constant(fc, 'z', -1) is None 66 67 def testFindModule(self): 68 with pytest.raises(ImportError): 69 dep.find_module('no-such.-thing') 70 with pytest.raises(ImportError): 71 dep.find_module('setuptools.non-existent') 72 f, p, i = dep.find_module('setuptools.tests') 73 f.close() 74 75 @needs_bytecode 76 def testModuleExtract(self): 77 from json import __version__ 78 assert dep.get_module_constant('json', '__version__') == __version__ 79 assert dep.get_module_constant('sys', 'version') == sys.version 80 assert dep.get_module_constant('setuptools.tests.test_setuptools', '__doc__') == __doc__ 81 82 @needs_bytecode 83 def testRequire(self): 84 req = Require('Json', '1.0.3', 'json') 85 86 assert req.name == 'Json' 87 assert req.module == 'json' 88 assert req.requested_version == '1.0.3' 89 assert req.attribute == '__version__' 90 assert req.full_name() == 'Json-1.0.3' 91 92 from json import __version__ 93 assert req.get_version() == __version__ 94 assert req.version_ok('1.0.9') 95 assert not req.version_ok('0.9.1') 96 assert not req.version_ok('unknown') 97 98 assert req.is_present() 99 assert req.is_current() 100 101 req = Require('Json 3000', '03000', 'json', format=LooseVersion) 102 assert req.is_present() 103 assert not req.is_current() 104 assert not req.version_ok('unknown') 105 106 req = Require('Do-what-I-mean', '1.0', 'd-w-i-m') 107 assert not req.is_present() 108 assert not req.is_current() 109 110 req = Require('Tests', None, 'tests', homepage="http://example.com") 111 assert req.format is None 112 assert req.attribute is None 113 assert req.requested_version is None 114 assert req.full_name() == 'Tests' 115 assert req.homepage == 'http://example.com' 116 117 from setuptools.tests import __path__ 118 paths = [os.path.dirname(p) for p in __path__] 119 assert req.is_present(paths) 120 assert req.is_current(paths) 121 122 123class TestDistro: 124 def setup_method(self, method): 125 self.e1 = Extension('bar.ext', ['bar.c']) 126 self.e2 = Extension('c.y', ['y.c']) 127 128 self.dist = makeSetup( 129 packages=['a', 'a.b', 'a.b.c', 'b', 'c'], 130 py_modules=['b.d', 'x'], 131 ext_modules=(self.e1, self.e2), 132 package_dir={}, 133 ) 134 135 def testDistroType(self): 136 assert isinstance(self.dist, setuptools.dist.Distribution) 137 138 def testExcludePackage(self): 139 self.dist.exclude_package('a') 140 assert self.dist.packages == ['b', 'c'] 141 142 self.dist.exclude_package('b') 143 assert self.dist.packages == ['c'] 144 assert self.dist.py_modules == ['x'] 145 assert self.dist.ext_modules == [self.e1, self.e2] 146 147 self.dist.exclude_package('c') 148 assert self.dist.packages == [] 149 assert self.dist.py_modules == ['x'] 150 assert self.dist.ext_modules == [self.e1] 151 152 # test removals from unspecified options 153 makeSetup().exclude_package('x') 154 155 def testIncludeExclude(self): 156 # remove an extension 157 self.dist.exclude(ext_modules=[self.e1]) 158 assert self.dist.ext_modules == [self.e2] 159 160 # add it back in 161 self.dist.include(ext_modules=[self.e1]) 162 assert self.dist.ext_modules == [self.e2, self.e1] 163 164 # should not add duplicate 165 self.dist.include(ext_modules=[self.e1]) 166 assert self.dist.ext_modules == [self.e2, self.e1] 167 168 def testExcludePackages(self): 169 self.dist.exclude(packages=['c', 'b', 'a']) 170 assert self.dist.packages == [] 171 assert self.dist.py_modules == ['x'] 172 assert self.dist.ext_modules == [self.e1] 173 174 def testEmpty(self): 175 dist = makeSetup() 176 dist.include(packages=['a'], py_modules=['b'], ext_modules=[self.e2]) 177 dist = makeSetup() 178 dist.exclude(packages=['a'], py_modules=['b'], ext_modules=[self.e2]) 179 180 def testContents(self): 181 assert self.dist.has_contents_for('a') 182 self.dist.exclude_package('a') 183 assert not self.dist.has_contents_for('a') 184 185 assert self.dist.has_contents_for('b') 186 self.dist.exclude_package('b') 187 assert not self.dist.has_contents_for('b') 188 189 assert self.dist.has_contents_for('c') 190 self.dist.exclude_package('c') 191 assert not self.dist.has_contents_for('c') 192 193 def testInvalidIncludeExclude(self): 194 with pytest.raises(DistutilsSetupError): 195 self.dist.include(nonexistent_option='x') 196 with pytest.raises(DistutilsSetupError): 197 self.dist.exclude(nonexistent_option='x') 198 with pytest.raises(DistutilsSetupError): 199 self.dist.include(packages={'x': 'y'}) 200 with pytest.raises(DistutilsSetupError): 201 self.dist.exclude(packages={'x': 'y'}) 202 with pytest.raises(DistutilsSetupError): 203 self.dist.include(ext_modules={'x': 'y'}) 204 with pytest.raises(DistutilsSetupError): 205 self.dist.exclude(ext_modules={'x': 'y'}) 206 207 with pytest.raises(DistutilsSetupError): 208 self.dist.include(package_dir=['q']) 209 with pytest.raises(DistutilsSetupError): 210 self.dist.exclude(package_dir=['q']) 211 212 213class TestFeatures: 214 def setup_method(self, method): 215 self.req = Require('Distutils', '1.0.3', 'distutils') 216 self.dist = makeSetup( 217 features={ 218 'foo': Feature("foo", standard=True, require_features=['baz', self.req]), 219 'bar': Feature("bar", standard=True, packages=['pkg.bar'], 220 py_modules=['bar_et'], remove=['bar.ext'], 221 ), 222 'baz': Feature( 223 "baz", optional=False, packages=['pkg.baz'], 224 scripts=['scripts/baz_it'], 225 libraries=[('libfoo', 'foo/foofoo.c')] 226 ), 227 'dwim': Feature("DWIM", available=False, remove='bazish'), 228 }, 229 script_args=['--without-bar', 'install'], 230 packages=['pkg.bar', 'pkg.foo'], 231 py_modules=['bar_et', 'bazish'], 232 ext_modules=[Extension('bar.ext', ['bar.c'])] 233 ) 234 235 def testDefaults(self): 236 assert not Feature( 237 "test", standard=True, remove='x', available=False 238 ).include_by_default() 239 assert Feature("test", standard=True, remove='x').include_by_default() 240 # Feature must have either kwargs, removes, or require_features 241 with pytest.raises(DistutilsSetupError): 242 Feature("test") 243 244 def testAvailability(self): 245 with pytest.raises(DistutilsPlatformError): 246 self.dist.features['dwim'].include_in(self.dist) 247 248 def testFeatureOptions(self): 249 dist = self.dist 250 assert ( 251 ('with-dwim', None, 'include DWIM') in dist.feature_options 252 ) 253 assert ( 254 ('without-dwim', None, 'exclude DWIM (default)') in dist.feature_options 255 ) 256 assert ( 257 ('with-bar', None, 'include bar (default)') in dist.feature_options 258 ) 259 assert ( 260 ('without-bar', None, 'exclude bar') in dist.feature_options 261 ) 262 assert dist.feature_negopt['without-foo'] == 'with-foo' 263 assert dist.feature_negopt['without-bar'] == 'with-bar' 264 assert dist.feature_negopt['without-dwim'] == 'with-dwim' 265 assert ('without-baz' not in dist.feature_negopt) 266 267 def testUseFeatures(self): 268 dist = self.dist 269 assert dist.with_foo == 1 270 assert dist.with_bar == 0 271 assert dist.with_baz == 1 272 assert ('bar_et' not in dist.py_modules) 273 assert ('pkg.bar' not in dist.packages) 274 assert ('pkg.baz' in dist.packages) 275 assert ('scripts/baz_it' in dist.scripts) 276 assert (('libfoo', 'foo/foofoo.c') in dist.libraries) 277 assert dist.ext_modules == [] 278 assert dist.require_features == [self.req] 279 280 # If we ask for bar, it should fail because we explicitly disabled 281 # it on the command line 282 with pytest.raises(DistutilsOptionError): 283 dist.include_feature('bar') 284 285 def testFeatureWithInvalidRemove(self): 286 with pytest.raises(SystemExit): 287 makeSetup(features={'x': Feature('x', remove='y')}) 288 289 290class TestCommandTests: 291 def testTestIsCommand(self): 292 test_cmd = makeSetup().get_command_obj('test') 293 assert (isinstance(test_cmd, distutils.cmd.Command)) 294 295 def testLongOptSuiteWNoDefault(self): 296 ts1 = makeSetup(script_args=['test', '--test-suite=foo.tests.suite']) 297 ts1 = ts1.get_command_obj('test') 298 ts1.ensure_finalized() 299 assert ts1.test_suite == 'foo.tests.suite' 300 301 def testDefaultSuite(self): 302 ts2 = makeSetup(test_suite='bar.tests.suite').get_command_obj('test') 303 ts2.ensure_finalized() 304 assert ts2.test_suite == 'bar.tests.suite' 305 306 def testDefaultWModuleOnCmdLine(self): 307 ts3 = makeSetup( 308 test_suite='bar.tests', 309 script_args=['test', '-m', 'foo.tests'] 310 ).get_command_obj('test') 311 ts3.ensure_finalized() 312 assert ts3.test_module == 'foo.tests' 313 assert ts3.test_suite == 'foo.tests.test_suite' 314 315 def testConflictingOptions(self): 316 ts4 = makeSetup( 317 script_args=['test', '-m', 'bar.tests', '-s', 'foo.tests.suite'] 318 ).get_command_obj('test') 319 with pytest.raises(DistutilsOptionError): 320 ts4.ensure_finalized() 321 322 def testNoSuite(self): 323 ts5 = makeSetup().get_command_obj('test') 324 ts5.ensure_finalized() 325 assert ts5.test_suite is None 326 327 328@pytest.fixture 329def example_source(tmpdir): 330 tmpdir.mkdir('foo') 331 (tmpdir / 'foo/bar.py').write('') 332 (tmpdir / 'readme.txt').write('') 333 return tmpdir 334 335 336def test_findall(example_source): 337 found = list(setuptools.findall(str(example_source))) 338 expected = ['readme.txt', 'foo/bar.py'] 339 expected = [example_source.join(fn) for fn in expected] 340 assert found == expected 341 342 343def test_findall_curdir(example_source): 344 with example_source.as_cwd(): 345 found = list(setuptools.findall()) 346 expected = ['readme.txt', os.path.join('foo', 'bar.py')] 347 assert found == expected 348 349 350@pytest.fixture 351def can_symlink(tmpdir): 352 """ 353 Skip if cannot create a symbolic link 354 """ 355 link_fn = 'link' 356 target_fn = 'target' 357 try: 358 os.symlink(target_fn, link_fn) 359 except (OSError, NotImplementedError, AttributeError): 360 pytest.skip("Cannot create symbolic links") 361 os.remove(link_fn) 362 363 364def test_findall_missing_symlink(tmpdir, can_symlink): 365 with tmpdir.as_cwd(): 366 os.symlink('foo', 'bar') 367 found = list(setuptools.findall()) 368 assert found == [] 369