test_setuptools.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. """Tests for the 'setuptools' package"""
  2. import os
  3. import re
  4. import sys
  5. from zipfile import ZipFile
  6. import pytest
  7. from packaging.version import Version
  8. import setuptools
  9. import setuptools.depends as dep
  10. import setuptools.dist
  11. from setuptools.depends import Require
  12. import distutils.cmd
  13. import distutils.core
  14. from distutils.core import Extension
  15. from distutils.errors import DistutilsSetupError
  16. @pytest.fixture(autouse=True)
  17. def isolated_dir(tmpdir_cwd):
  18. return
  19. def makeSetup(**args):
  20. """Return distribution from 'setup(**args)', without executing commands"""
  21. distutils.core._setup_stop_after = "commandline"
  22. # Don't let system command line leak into tests!
  23. args.setdefault('script_args', ['install'])
  24. try:
  25. return setuptools.setup(**args)
  26. finally:
  27. distutils.core._setup_stop_after = None
  28. needs_bytecode = pytest.mark.skipif(
  29. not hasattr(dep, 'get_module_constant'),
  30. reason="bytecode support not available",
  31. )
  32. class TestDepends:
  33. def testExtractConst(self):
  34. if not hasattr(dep, 'extract_constant'):
  35. # skip on non-bytecode platforms
  36. return
  37. def f1():
  38. global x, y, z
  39. x = "test"
  40. y = z # pyright: ignore[reportUnboundVariable] # Explicitly testing for this runtime issue
  41. fc = f1.__code__
  42. # unrecognized name
  43. assert dep.extract_constant(fc, 'q', -1) is None
  44. # constant assigned
  45. assert dep.extract_constant(fc, 'x', -1) == "test"
  46. # expression assigned
  47. assert dep.extract_constant(fc, 'y', -1) == -1
  48. # recognized name, not assigned
  49. assert dep.extract_constant(fc, 'z', -1) is None
  50. def testFindModule(self):
  51. with pytest.raises(ImportError):
  52. dep.find_module('no-such.-thing')
  53. with pytest.raises(ImportError):
  54. dep.find_module('setuptools.non-existent')
  55. f, _p, _i = dep.find_module('setuptools.tests')
  56. f.close()
  57. @pytest.fixture
  58. def sample_module(self, monkeypatch, tmp_path):
  59. monkeypatch.syspath_prepend(str(tmp_path))
  60. module = "mod_with_version"
  61. version = "2.0.9"
  62. file = tmp_path / f"{module}.py"
  63. file.write_text(f"__version__ = {version!r}", encoding="utf-8")
  64. return (module, version)
  65. @needs_bytecode
  66. def testModuleExtract(self, sample_module):
  67. (module, version) = sample_module
  68. assert dep.get_module_constant(module, '__version__') == version
  69. assert dep.get_module_constant('sys', 'version') == sys.version
  70. assert dep.get_module_constant(__name__, '__doc__') == __doc__
  71. @needs_bytecode
  72. def testRequire(self, sample_module):
  73. (module, version) = sample_module
  74. req = Require('GivenName', '1.0.3', module)
  75. assert req.name == 'GivenName'
  76. assert req.module == module
  77. assert req.requested_version == Version('1.0.3')
  78. assert req.attribute == '__version__'
  79. assert req.full_name() == 'GivenName-1.0.3'
  80. assert str(req.get_version()) == version
  81. assert req.version_ok('1.0.9')
  82. assert not req.version_ok('0.9.1')
  83. assert not req.version_ok('unknown')
  84. assert req.is_present()
  85. assert req.is_current()
  86. req = Require('Do-what-I-mean', '1.0', 'd-w-i-m')
  87. assert not req.is_present()
  88. assert not req.is_current()
  89. @needs_bytecode
  90. def test_require_present(self):
  91. # In #1896, this test was failing for months with the only
  92. # complaint coming from test runners (not end users).
  93. # TODO: Evaluate if this code is needed at all.
  94. req = Require('Tests', None, 'tests', homepage="http://example.com")
  95. assert req.format is None
  96. assert req.attribute is None
  97. assert req.requested_version is None
  98. assert req.full_name() == 'Tests'
  99. assert req.homepage == 'http://example.com'
  100. from setuptools.tests import __path__
  101. paths = [os.path.dirname(p) for p in __path__]
  102. assert req.is_present(paths)
  103. assert req.is_current(paths)
  104. class TestDistro:
  105. def setup_method(self, method):
  106. self.e1 = Extension('bar.ext', ['bar.c'])
  107. self.e2 = Extension('c.y', ['y.c'])
  108. self.dist = makeSetup(
  109. packages=['a', 'a.b', 'a.b.c', 'b', 'c'],
  110. py_modules=['b.d', 'x'],
  111. ext_modules=(self.e1, self.e2),
  112. package_dir={},
  113. )
  114. def testDistroType(self):
  115. assert isinstance(self.dist, setuptools.dist.Distribution)
  116. def testExcludePackage(self):
  117. self.dist.exclude_package('a')
  118. assert self.dist.packages == ['b', 'c']
  119. self.dist.exclude_package('b')
  120. assert self.dist.packages == ['c']
  121. assert self.dist.py_modules == ['x']
  122. assert self.dist.ext_modules == [self.e1, self.e2]
  123. self.dist.exclude_package('c')
  124. assert self.dist.packages == []
  125. assert self.dist.py_modules == ['x']
  126. assert self.dist.ext_modules == [self.e1]
  127. # test removals from unspecified options
  128. makeSetup().exclude_package('x')
  129. def testIncludeExclude(self):
  130. # remove an extension
  131. self.dist.exclude(ext_modules=[self.e1])
  132. assert self.dist.ext_modules == [self.e2]
  133. # add it back in
  134. self.dist.include(ext_modules=[self.e1])
  135. assert self.dist.ext_modules == [self.e2, self.e1]
  136. # should not add duplicate
  137. self.dist.include(ext_modules=[self.e1])
  138. assert self.dist.ext_modules == [self.e2, self.e1]
  139. def testExcludePackages(self):
  140. self.dist.exclude(packages=['c', 'b', 'a'])
  141. assert self.dist.packages == []
  142. assert self.dist.py_modules == ['x']
  143. assert self.dist.ext_modules == [self.e1]
  144. def testEmpty(self):
  145. dist = makeSetup()
  146. dist.include(packages=['a'], py_modules=['b'], ext_modules=[self.e2])
  147. dist = makeSetup()
  148. dist.exclude(packages=['a'], py_modules=['b'], ext_modules=[self.e2])
  149. def testContents(self):
  150. assert self.dist.has_contents_for('a')
  151. self.dist.exclude_package('a')
  152. assert not self.dist.has_contents_for('a')
  153. assert self.dist.has_contents_for('b')
  154. self.dist.exclude_package('b')
  155. assert not self.dist.has_contents_for('b')
  156. assert self.dist.has_contents_for('c')
  157. self.dist.exclude_package('c')
  158. assert not self.dist.has_contents_for('c')
  159. def testInvalidIncludeExclude(self):
  160. with pytest.raises(DistutilsSetupError):
  161. self.dist.include(nonexistent_option='x')
  162. with pytest.raises(DistutilsSetupError):
  163. self.dist.exclude(nonexistent_option='x')
  164. with pytest.raises(DistutilsSetupError):
  165. self.dist.include(packages={'x': 'y'})
  166. with pytest.raises(DistutilsSetupError):
  167. self.dist.exclude(packages={'x': 'y'})
  168. with pytest.raises(DistutilsSetupError):
  169. self.dist.include(ext_modules={'x': 'y'})
  170. with pytest.raises(DistutilsSetupError):
  171. self.dist.exclude(ext_modules={'x': 'y'})
  172. with pytest.raises(DistutilsSetupError):
  173. self.dist.include(package_dir=['q'])
  174. with pytest.raises(DistutilsSetupError):
  175. self.dist.exclude(package_dir=['q'])
  176. @pytest.fixture
  177. def example_source(tmpdir):
  178. tmpdir.mkdir('foo')
  179. (tmpdir / 'foo/bar.py').write('')
  180. (tmpdir / 'readme.txt').write('')
  181. return tmpdir
  182. def test_findall(example_source):
  183. found = list(setuptools.findall(str(example_source)))
  184. expected = ['readme.txt', 'foo/bar.py']
  185. expected = [example_source.join(fn) for fn in expected]
  186. assert found == expected
  187. def test_findall_curdir(example_source):
  188. with example_source.as_cwd():
  189. found = list(setuptools.findall())
  190. expected = ['readme.txt', os.path.join('foo', 'bar.py')]
  191. assert found == expected
  192. @pytest.fixture
  193. def can_symlink(tmpdir):
  194. """
  195. Skip if cannot create a symbolic link
  196. """
  197. link_fn = 'link'
  198. target_fn = 'target'
  199. try:
  200. os.symlink(target_fn, link_fn)
  201. except (OSError, NotImplementedError, AttributeError):
  202. pytest.skip("Cannot create symbolic links")
  203. os.remove(link_fn)
  204. @pytest.mark.usefixtures("can_symlink")
  205. def test_findall_missing_symlink(tmpdir):
  206. with tmpdir.as_cwd():
  207. os.symlink('foo', 'bar')
  208. found = list(setuptools.findall())
  209. assert found == []
  210. @pytest.mark.xfail(reason="unable to exclude tests; #4475 #3260")
  211. def test_its_own_wheel_does_not_contain_tests(setuptools_wheel):
  212. with ZipFile(setuptools_wheel) as zipfile:
  213. contents = [f.replace(os.sep, '/') for f in zipfile.namelist()]
  214. for member in contents:
  215. assert '/tests/' not in member
  216. def test_wheel_includes_cli_scripts(setuptools_wheel):
  217. with ZipFile(setuptools_wheel) as zipfile:
  218. contents = [f.replace(os.sep, '/') for f in zipfile.namelist()]
  219. assert any('cli-64.exe' in member for member in contents)
  220. def test_wheel_includes_vendored_metadata(setuptools_wheel):
  221. with ZipFile(setuptools_wheel) as zipfile:
  222. contents = [f.replace(os.sep, '/') for f in zipfile.namelist()]
  223. assert any(
  224. re.search(r'_vendor/.*\.dist-info/METADATA', member) for member in contents
  225. )