test_sdist.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. # -*- coding: utf-8 -*-
  2. """sdist tests"""
  3. import locale
  4. import os
  5. import shutil
  6. import sys
  7. import tempfile
  8. import unicodedata
  9. import contextlib
  10. import pytest
  11. import pkg_resources
  12. from setuptools.compat import StringIO, unicode, PY3, PY2
  13. from setuptools.command.sdist import sdist
  14. from setuptools.command.egg_info import manifest_maker
  15. from setuptools.dist import Distribution
  16. SETUP_ATTRS = {
  17. 'name': 'sdist_test',
  18. 'version': '0.0',
  19. 'packages': ['sdist_test'],
  20. 'package_data': {'sdist_test': ['*.txt']}
  21. }
  22. SETUP_PY = """\
  23. from setuptools import setup
  24. setup(**%r)
  25. """ % SETUP_ATTRS
  26. if PY3:
  27. LATIN1_FILENAME = 'smörbröd.py'.encode('latin-1')
  28. else:
  29. LATIN1_FILENAME = 'sm\xf6rbr\xf6d.py'
  30. # Cannot use context manager because of Python 2.4
  31. @contextlib.contextmanager
  32. def quiet():
  33. old_stdout, old_stderr = sys.stdout, sys.stderr
  34. sys.stdout, sys.stderr = StringIO(), StringIO()
  35. try:
  36. yield
  37. finally:
  38. sys.stdout, sys.stderr = old_stdout, old_stderr
  39. # Fake byte literals for Python <= 2.5
  40. def b(s, encoding='utf-8'):
  41. if PY3:
  42. return s.encode(encoding)
  43. return s
  44. # Convert to POSIX path
  45. def posix(path):
  46. if PY3 and not isinstance(path, str):
  47. return path.replace(os.sep.encode('ascii'), b('/'))
  48. else:
  49. return path.replace(os.sep, '/')
  50. # HFS Plus uses decomposed UTF-8
  51. def decompose(path):
  52. if isinstance(path, unicode):
  53. return unicodedata.normalize('NFD', path)
  54. try:
  55. path = path.decode('utf-8')
  56. path = unicodedata.normalize('NFD', path)
  57. path = path.encode('utf-8')
  58. except UnicodeError:
  59. pass # Not UTF-8
  60. return path
  61. class TestSdistTest:
  62. def setup_method(self, method):
  63. self.temp_dir = tempfile.mkdtemp()
  64. f = open(os.path.join(self.temp_dir, 'setup.py'), 'w')
  65. f.write(SETUP_PY)
  66. f.close()
  67. # Set up the rest of the test package
  68. test_pkg = os.path.join(self.temp_dir, 'sdist_test')
  69. os.mkdir(test_pkg)
  70. # *.rst was not included in package_data, so c.rst should not be
  71. # automatically added to the manifest when not under version control
  72. for fname in ['__init__.py', 'a.txt', 'b.txt', 'c.rst']:
  73. # Just touch the files; their contents are irrelevant
  74. open(os.path.join(test_pkg, fname), 'w').close()
  75. self.old_cwd = os.getcwd()
  76. os.chdir(self.temp_dir)
  77. def teardown_method(self, method):
  78. os.chdir(self.old_cwd)
  79. shutil.rmtree(self.temp_dir)
  80. def test_package_data_in_sdist(self):
  81. """Regression test for pull request #4: ensures that files listed in
  82. package_data are included in the manifest even if they're not added to
  83. version control.
  84. """
  85. dist = Distribution(SETUP_ATTRS)
  86. dist.script_name = 'setup.py'
  87. cmd = sdist(dist)
  88. cmd.ensure_finalized()
  89. with quiet():
  90. cmd.run()
  91. manifest = cmd.filelist.files
  92. assert os.path.join('sdist_test', 'a.txt') in manifest
  93. assert os.path.join('sdist_test', 'b.txt') in manifest
  94. assert os.path.join('sdist_test', 'c.rst') not in manifest
  95. def test_defaults_case_sensitivity(self):
  96. """
  97. Make sure default files (README.*, etc.) are added in a case-sensitive
  98. way to avoid problems with packages built on Windows.
  99. """
  100. open(os.path.join(self.temp_dir, 'readme.rst'), 'w').close()
  101. open(os.path.join(self.temp_dir, 'SETUP.cfg'), 'w').close()
  102. dist = Distribution(SETUP_ATTRS)
  103. # the extension deliberately capitalized for this test
  104. # to make sure the actual filename (not capitalized) gets added
  105. # to the manifest
  106. dist.script_name = 'setup.PY'
  107. cmd = sdist(dist)
  108. cmd.ensure_finalized()
  109. with quiet():
  110. cmd.run()
  111. # lowercase all names so we can test in a case-insensitive way to make sure the files are not included
  112. manifest = map(lambda x: x.lower(), cmd.filelist.files)
  113. assert 'readme.rst' not in manifest, manifest
  114. assert 'setup.py' not in manifest, manifest
  115. assert 'setup.cfg' not in manifest, manifest
  116. def test_manifest_is_written_with_utf8_encoding(self):
  117. # Test for #303.
  118. dist = Distribution(SETUP_ATTRS)
  119. dist.script_name = 'setup.py'
  120. mm = manifest_maker(dist)
  121. mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
  122. os.mkdir('sdist_test.egg-info')
  123. # UTF-8 filename
  124. filename = os.path.join('sdist_test', 'smörbröd.py')
  125. # Must create the file or it will get stripped.
  126. open(filename, 'w').close()
  127. # Add UTF-8 filename and write manifest
  128. with quiet():
  129. mm.run()
  130. mm.filelist.append(filename)
  131. mm.write_manifest()
  132. manifest = open(mm.manifest, 'rbU')
  133. contents = manifest.read()
  134. manifest.close()
  135. # The manifest should be UTF-8 encoded
  136. u_contents = contents.decode('UTF-8')
  137. # The manifest should contain the UTF-8 filename
  138. if PY2:
  139. fs_enc = sys.getfilesystemencoding()
  140. filename = filename.decode(fs_enc)
  141. assert posix(filename) in u_contents
  142. # Python 3 only
  143. if PY3:
  144. def test_write_manifest_allows_utf8_filenames(self):
  145. # Test for #303.
  146. dist = Distribution(SETUP_ATTRS)
  147. dist.script_name = 'setup.py'
  148. mm = manifest_maker(dist)
  149. mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
  150. os.mkdir('sdist_test.egg-info')
  151. # UTF-8 filename
  152. filename = os.path.join(b('sdist_test'), b('smörbröd.py'))
  153. # Must touch the file or risk removal
  154. open(filename, "w").close()
  155. # Add filename and write manifest
  156. with quiet():
  157. mm.run()
  158. u_filename = filename.decode('utf-8')
  159. mm.filelist.files.append(u_filename)
  160. # Re-write manifest
  161. mm.write_manifest()
  162. manifest = open(mm.manifest, 'rbU')
  163. contents = manifest.read()
  164. manifest.close()
  165. # The manifest should be UTF-8 encoded
  166. contents.decode('UTF-8')
  167. # The manifest should contain the UTF-8 filename
  168. assert posix(filename) in contents
  169. # The filelist should have been updated as well
  170. assert u_filename in mm.filelist.files
  171. def test_write_manifest_skips_non_utf8_filenames(self):
  172. """
  173. Files that cannot be encoded to UTF-8 (specifically, those that
  174. weren't originally successfully decoded and have surrogate
  175. escapes) should be omitted from the manifest.
  176. See https://bitbucket.org/tarek/distribute/issue/303 for history.
  177. """
  178. dist = Distribution(SETUP_ATTRS)
  179. dist.script_name = 'setup.py'
  180. mm = manifest_maker(dist)
  181. mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
  182. os.mkdir('sdist_test.egg-info')
  183. # Latin-1 filename
  184. filename = os.path.join(b('sdist_test'), LATIN1_FILENAME)
  185. # Add filename with surrogates and write manifest
  186. with quiet():
  187. mm.run()
  188. u_filename = filename.decode('utf-8', 'surrogateescape')
  189. mm.filelist.append(u_filename)
  190. # Re-write manifest
  191. mm.write_manifest()
  192. manifest = open(mm.manifest, 'rbU')
  193. contents = manifest.read()
  194. manifest.close()
  195. # The manifest should be UTF-8 encoded
  196. contents.decode('UTF-8')
  197. # The Latin-1 filename should have been skipped
  198. assert posix(filename) not in contents
  199. # The filelist should have been updated as well
  200. assert u_filename not in mm.filelist.files
  201. def test_manifest_is_read_with_utf8_encoding(self):
  202. # Test for #303.
  203. dist = Distribution(SETUP_ATTRS)
  204. dist.script_name = 'setup.py'
  205. cmd = sdist(dist)
  206. cmd.ensure_finalized()
  207. # Create manifest
  208. with quiet():
  209. cmd.run()
  210. # Add UTF-8 filename to manifest
  211. filename = os.path.join(b('sdist_test'), b('smörbröd.py'))
  212. cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
  213. manifest = open(cmd.manifest, 'ab')
  214. manifest.write(b('\n') + filename)
  215. manifest.close()
  216. # The file must exist to be included in the filelist
  217. open(filename, 'w').close()
  218. # Re-read manifest
  219. cmd.filelist.files = []
  220. with quiet():
  221. cmd.read_manifest()
  222. # The filelist should contain the UTF-8 filename
  223. if PY3:
  224. filename = filename.decode('utf-8')
  225. assert filename in cmd.filelist.files
  226. # Python 3 only
  227. if PY3:
  228. def test_read_manifest_skips_non_utf8_filenames(self):
  229. # Test for #303.
  230. dist = Distribution(SETUP_ATTRS)
  231. dist.script_name = 'setup.py'
  232. cmd = sdist(dist)
  233. cmd.ensure_finalized()
  234. # Create manifest
  235. with quiet():
  236. cmd.run()
  237. # Add Latin-1 filename to manifest
  238. filename = os.path.join(b('sdist_test'), LATIN1_FILENAME)
  239. cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
  240. manifest = open(cmd.manifest, 'ab')
  241. manifest.write(b('\n') + filename)
  242. manifest.close()
  243. # The file must exist to be included in the filelist
  244. open(filename, 'w').close()
  245. # Re-read manifest
  246. cmd.filelist.files = []
  247. with quiet():
  248. cmd.read_manifest()
  249. # The Latin-1 filename should have been skipped
  250. filename = filename.decode('latin-1')
  251. assert filename not in cmd.filelist.files
  252. @pytest.mark.skipif(PY3 and locale.getpreferredencoding() != 'UTF-8',
  253. reason='Unittest fails if locale is not utf-8 but the manifests is '
  254. 'recorded correctly')
  255. def test_sdist_with_utf8_encoded_filename(self):
  256. # Test for #303.
  257. dist = Distribution(SETUP_ATTRS)
  258. dist.script_name = 'setup.py'
  259. cmd = sdist(dist)
  260. cmd.ensure_finalized()
  261. # UTF-8 filename
  262. filename = os.path.join(b('sdist_test'), b('smörbröd.py'))
  263. open(filename, 'w').close()
  264. with quiet():
  265. cmd.run()
  266. if sys.platform == 'darwin':
  267. filename = decompose(filename)
  268. if PY3:
  269. fs_enc = sys.getfilesystemencoding()
  270. if sys.platform == 'win32':
  271. if fs_enc == 'cp1252':
  272. # Python 3 mangles the UTF-8 filename
  273. filename = filename.decode('cp1252')
  274. assert filename in cmd.filelist.files
  275. else:
  276. filename = filename.decode('mbcs')
  277. assert filename in cmd.filelist.files
  278. else:
  279. filename = filename.decode('utf-8')
  280. assert filename in cmd.filelist.files
  281. else:
  282. assert filename in cmd.filelist.files
  283. def test_sdist_with_latin1_encoded_filename(self):
  284. # Test for #303.
  285. dist = Distribution(SETUP_ATTRS)
  286. dist.script_name = 'setup.py'
  287. cmd = sdist(dist)
  288. cmd.ensure_finalized()
  289. # Latin-1 filename
  290. filename = os.path.join(b('sdist_test'), LATIN1_FILENAME)
  291. open(filename, 'w').close()
  292. assert os.path.isfile(filename)
  293. with quiet():
  294. cmd.run()
  295. if PY3:
  296. # not all windows systems have a default FS encoding of cp1252
  297. if sys.platform == 'win32':
  298. # Latin-1 is similar to Windows-1252 however
  299. # on mbcs filesys it is not in latin-1 encoding
  300. fs_enc = sys.getfilesystemencoding()
  301. if fs_enc == 'mbcs':
  302. filename = filename.decode('mbcs')
  303. else:
  304. filename = filename.decode('latin-1')
  305. assert filename in cmd.filelist.files
  306. else:
  307. # The Latin-1 filename should have been skipped
  308. filename = filename.decode('latin-1')
  309. filename not in cmd.filelist.files
  310. else:
  311. # Under Python 2 there seems to be no decoded string in the
  312. # filelist. However, due to decode and encoding of the
  313. # file name to get utf-8 Manifest the latin1 maybe excluded
  314. try:
  315. # fs_enc should match how one is expect the decoding to
  316. # be proformed for the manifest output.
  317. fs_enc = sys.getfilesystemencoding()
  318. filename.decode(fs_enc)
  319. assert filename in cmd.filelist.files
  320. except UnicodeDecodeError:
  321. filename not in cmd.filelist.files
  322. def test_default_revctrl():
  323. """
  324. When _default_revctrl was removed from the `setuptools.command.sdist`
  325. module in 10.0, it broke some systems which keep an old install of
  326. setuptools (Distribute) around. Those old versions require that the
  327. setuptools package continue to implement that interface, so this
  328. function provides that interface, stubbed. See #320 for details.
  329. This interface must be maintained until Ubuntu 12.04 is no longer
  330. supported (by Setuptools).
  331. """
  332. ep_def = 'svn_cvs = setuptools.command.sdist:_default_revctrl'
  333. ep = pkg_resources.EntryPoint.parse(ep_def)
  334. res = ep.resolve()
  335. assert hasattr(res, '__iter__')