__init__.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. from __future__ import absolute_import
  2. from contextlib import contextmanager
  3. import os
  4. import sys
  5. import re
  6. import textwrap
  7. import site
  8. import shutil
  9. import subprocess
  10. import pytest
  11. import scripttest
  12. import six
  13. import virtualenv
  14. from tests.lib.path import Path, curdir
  15. DATA_DIR = Path(__file__).folder.folder.join("data").abspath
  16. SRC_DIR = Path(__file__).abspath.folder.folder.folder
  17. pyversion = sys.version[:3]
  18. pyversion_tuple = sys.version_info
  19. def path_to_url(path):
  20. """
  21. Convert a path to URI. The path will be made absolute and
  22. will not have quoted path parts.
  23. (adapted from pip.util)
  24. """
  25. path = os.path.normpath(os.path.abspath(path))
  26. drive, path = os.path.splitdrive(path)
  27. filepath = path.split(os.path.sep)
  28. url = '/'.join(filepath)
  29. if drive:
  30. # Note: match urllib.request.pathname2url's
  31. # behavior: uppercase the drive letter.
  32. return 'file:///' + drive.upper() + url
  33. return 'file://' + url
  34. # workaround for https://github.com/pypa/virtualenv/issues/306
  35. def virtualenv_lib_path(venv_home, venv_lib):
  36. if not hasattr(sys, "pypy_version_info"):
  37. return venv_lib
  38. version_fmt = '{0}' if six.PY3 else '{0}.{1}'
  39. version_dir = version_fmt.format(*sys.version_info)
  40. return os.path.join(venv_home, 'lib-python', version_dir)
  41. def create_file(path, contents=None):
  42. """Create a file on the path, with the given contents
  43. """
  44. from pip._internal.utils.misc import ensure_dir
  45. ensure_dir(os.path.dirname(path))
  46. with open(path, "w") as f:
  47. if contents is not None:
  48. f.write(contents)
  49. else:
  50. f.write("\n")
  51. class TestData(object):
  52. """
  53. Represents a bundle of pre-created test data.
  54. This copies a pristine set of test data into a root location that is
  55. designed to be test specific. The reason for this is when running the tests
  56. concurrently errors can be generated because the related tooling uses
  57. the directory as a work space. This leads to two concurrent processes
  58. trampling over each other. This class gets around that by copying all
  59. data into a directory and operating on the copied data.
  60. """
  61. def __init__(self, root, source=None):
  62. self.source = source or DATA_DIR
  63. self.root = Path(root).abspath
  64. @classmethod
  65. def copy(cls, root):
  66. obj = cls(root)
  67. obj.reset()
  68. return obj
  69. def reset(self):
  70. self.root.rmtree()
  71. self.source.copytree(self.root)
  72. @property
  73. def packages(self):
  74. return self.root.join("packages")
  75. @property
  76. def packages2(self):
  77. return self.root.join("packages2")
  78. @property
  79. def packages3(self):
  80. return self.root.join("packages3")
  81. @property
  82. def src(self):
  83. return self.root.join("src")
  84. @property
  85. def indexes(self):
  86. return self.root.join("indexes")
  87. @property
  88. def reqfiles(self):
  89. return self.root.join("reqfiles")
  90. @property
  91. def completion_paths(self):
  92. return self.root.join("completion_paths")
  93. @property
  94. def find_links(self):
  95. return path_to_url(self.packages)
  96. @property
  97. def find_links2(self):
  98. return path_to_url(self.packages2)
  99. @property
  100. def find_links3(self):
  101. return path_to_url(self.packages3)
  102. @property
  103. def backends(self):
  104. return path_to_url(self.root.join("backends"))
  105. def index_url(self, index="simple"):
  106. return path_to_url(self.root.join("indexes", index))
  107. class TestFailure(AssertionError):
  108. """
  109. An "assertion" failed during testing.
  110. """
  111. pass
  112. class TestPipResult(object):
  113. def __init__(self, impl, verbose=False):
  114. self._impl = impl
  115. if verbose:
  116. print(self.stdout)
  117. if self.stderr:
  118. print('======= stderr ========')
  119. print(self.stderr)
  120. print('=======================')
  121. def __getattr__(self, attr):
  122. return getattr(self._impl, attr)
  123. if sys.platform == 'win32':
  124. @property
  125. def stdout(self):
  126. return self._impl.stdout.replace('\r\n', '\n')
  127. @property
  128. def stderr(self):
  129. return self._impl.stderr.replace('\r\n', '\n')
  130. def __str__(self):
  131. return str(self._impl).replace('\r\n', '\n')
  132. else:
  133. # Python doesn't automatically forward __str__ through __getattr__
  134. def __str__(self):
  135. return str(self._impl)
  136. def assert_installed(self, pkg_name, editable=True, with_files=[],
  137. without_files=[], without_egg_link=False,
  138. use_user_site=False, sub_dir=False):
  139. e = self.test_env
  140. if editable:
  141. pkg_dir = e.venv / 'src' / pkg_name.lower()
  142. # If package was installed in a sub directory
  143. if sub_dir:
  144. pkg_dir = pkg_dir / sub_dir
  145. else:
  146. without_egg_link = True
  147. pkg_dir = e.site_packages / pkg_name
  148. if use_user_site:
  149. egg_link_path = e.user_site / pkg_name + '.egg-link'
  150. else:
  151. egg_link_path = e.site_packages / pkg_name + '.egg-link'
  152. if without_egg_link:
  153. if egg_link_path in self.files_created:
  154. raise TestFailure(
  155. 'unexpected egg link file created: %r\n%s' %
  156. (egg_link_path, self)
  157. )
  158. else:
  159. if egg_link_path not in self.files_created:
  160. raise TestFailure(
  161. 'expected egg link file missing: %r\n%s' %
  162. (egg_link_path, self)
  163. )
  164. egg_link_file = self.files_created[egg_link_path]
  165. egg_link_contents = egg_link_file.bytes.replace(os.linesep, '\n')
  166. # FIXME: I don't understand why there's a trailing . here
  167. if not (egg_link_contents.endswith('\n.') and
  168. egg_link_contents[:-2].endswith(pkg_dir)):
  169. raise TestFailure(textwrap.dedent(u'''\
  170. Incorrect egg_link file %r
  171. Expected ending: %r
  172. ------- Actual contents -------
  173. %s
  174. -------------------------------''' % (
  175. egg_link_file,
  176. pkg_dir + '\n.',
  177. repr(egg_link_contents))
  178. ))
  179. if use_user_site:
  180. pth_file = e.user_site / 'easy-install.pth'
  181. else:
  182. pth_file = e.site_packages / 'easy-install.pth'
  183. if (pth_file in self.files_updated) == without_egg_link:
  184. raise TestFailure('%r unexpectedly %supdated by install' % (
  185. pth_file, (not without_egg_link and 'not ' or '')))
  186. if (pkg_dir in self.files_created) == (curdir in without_files):
  187. raise TestFailure(textwrap.dedent('''\
  188. expected package directory %r %sto be created
  189. actually created:
  190. %s
  191. ''') % (
  192. pkg_dir,
  193. (curdir in without_files and 'not ' or ''),
  194. sorted(self.files_created.keys())))
  195. for f in with_files:
  196. if not (pkg_dir / f).normpath in self.files_created:
  197. raise TestFailure(
  198. 'Package directory %r missing expected content %r' %
  199. (pkg_dir, f)
  200. )
  201. for f in without_files:
  202. if (pkg_dir / f).normpath in self.files_created:
  203. raise TestFailure(
  204. 'Package directory %r has unexpected content %f' %
  205. (pkg_dir, f)
  206. )
  207. class PipTestEnvironment(scripttest.TestFileEnvironment):
  208. """
  209. A specialized TestFileEnvironment for testing pip
  210. """
  211. #
  212. # Attribute naming convention
  213. # ---------------------------
  214. #
  215. # Instances of this class have many attributes representing paths
  216. # in the filesystem. To keep things straight, absolute paths have
  217. # a name of the form xxxx_path and relative paths have a name that
  218. # does not end in '_path'.
  219. exe = sys.platform == 'win32' and '.exe' or ''
  220. verbose = False
  221. def __init__(self, base_path, *args, **kwargs):
  222. # Make our base_path a test.lib.path.Path object
  223. base_path = Path(base_path)
  224. # Store paths related to the virtual environment
  225. _virtualenv = kwargs.pop("virtualenv")
  226. path_locations = virtualenv.path_locations(_virtualenv)
  227. # Make sure we have test.lib.path.Path objects
  228. venv, lib, include, bin = map(Path, path_locations)
  229. self.venv_path = venv
  230. self.lib_path = virtualenv_lib_path(venv, lib)
  231. self.include_path = include
  232. self.bin_path = bin
  233. if hasattr(sys, "pypy_version_info"):
  234. self.site_packages_path = self.venv_path.join("site-packages")
  235. else:
  236. self.site_packages_path = self.lib_path.join("site-packages")
  237. self.user_base_path = self.venv_path.join("user")
  238. self.user_site_path = self.venv_path.join(
  239. "user",
  240. site.USER_SITE[len(site.USER_BASE) + 1:],
  241. )
  242. if sys.platform == 'win32':
  243. if sys.version_info >= (3, 5):
  244. scripts_base = self.user_site_path.join('..').normpath
  245. else:
  246. scripts_base = self.user_base_path
  247. self.user_bin_path = scripts_base.join('Scripts')
  248. else:
  249. self.user_bin_path = self.user_base_path.join(
  250. self.bin_path - self.venv_path
  251. )
  252. # Create a Directory to use as a scratch pad
  253. self.scratch_path = base_path.join("scratch").mkdir()
  254. # Set our default working directory
  255. kwargs.setdefault("cwd", self.scratch_path)
  256. # Setup our environment
  257. environ = kwargs.get("environ")
  258. if environ is None:
  259. environ = os.environ.copy()
  260. environ["PATH"] = Path.pathsep.join(
  261. [self.bin_path] + [environ.get("PATH", [])],
  262. )
  263. environ["PYTHONUSERBASE"] = self.user_base_path
  264. # Writing bytecode can mess up updated file detection
  265. environ["PYTHONDONTWRITEBYTECODE"] = "1"
  266. # Make sure we get UTF-8 on output, even on Windows...
  267. environ["PYTHONIOENCODING"] = "UTF-8"
  268. kwargs["environ"] = environ
  269. # Call the TestFileEnvironment __init__
  270. super(PipTestEnvironment, self).__init__(base_path, *args, **kwargs)
  271. # Expand our absolute path directories into relative
  272. for name in ["base", "venv", "lib", "include", "bin", "site_packages",
  273. "user_base", "user_site", "user_bin", "scratch"]:
  274. real_name = "%s_path" % name
  275. setattr(self, name, getattr(self, real_name) - self.base_path)
  276. # Make sure temp_path is a Path object
  277. self.temp_path = Path(self.temp_path)
  278. # Ensure the tmp dir exists, things break horribly if it doesn't
  279. self.temp_path.mkdir()
  280. # create easy-install.pth in user_site, so we always have it updated
  281. # instead of created
  282. self.user_site_path.makedirs()
  283. self.user_site_path.join("easy-install.pth").touch()
  284. def _ignore_file(self, fn):
  285. if fn.endswith('__pycache__') or fn.endswith(".pyc"):
  286. result = True
  287. else:
  288. result = super(PipTestEnvironment, self)._ignore_file(fn)
  289. return result
  290. def run(self, *args, **kw):
  291. if self.verbose:
  292. print('>> running %s %s' % (args, kw))
  293. cwd = kw.pop('cwd', None)
  294. run_from = kw.pop('run_from', None)
  295. assert not cwd or not run_from, "Don't use run_from; it's going away"
  296. cwd = cwd or run_from or self.cwd
  297. if sys.platform == 'win32':
  298. # Partial fix for ScriptTest.run using `shell=True` on Windows.
  299. args = [str(a).replace('^', '^^').replace('&', '^&') for a in args]
  300. return TestPipResult(
  301. super(PipTestEnvironment, self).run(cwd=cwd, *args, **kw),
  302. verbose=self.verbose,
  303. )
  304. def pip(self, *args, **kwargs):
  305. # On old versions of Python, urllib3/requests will raise a warning
  306. # about the lack of an SSLContext. Expect it when running commands
  307. # that will touch the outside world.
  308. if (pyversion_tuple < (2, 7, 9) and
  309. args and args[0] in ('search', 'install', 'download')):
  310. kwargs['expect_stderr'] = True
  311. if kwargs.pop('use_module', False):
  312. exe = 'python'
  313. args = ('-m', 'pip') + args
  314. else:
  315. exe = 'pip'
  316. return self.run(exe, *args, **kwargs)
  317. def pip_install_local(self, *args, **kwargs):
  318. return self.pip(
  319. "install", "--no-index",
  320. "--find-links", path_to_url(os.path.join(DATA_DIR, "packages")),
  321. *args, **kwargs
  322. )
  323. # FIXME ScriptTest does something similar, but only within a single
  324. # ProcResult; this generalizes it so states can be compared across
  325. # multiple commands. Maybe should be rolled into ScriptTest?
  326. def diff_states(start, end, ignore=None):
  327. """
  328. Differences two "filesystem states" as represented by dictionaries
  329. of FoundFile and FoundDir objects.
  330. Returns a dictionary with following keys:
  331. ``deleted``
  332. Dictionary of files/directories found only in the start state.
  333. ``created``
  334. Dictionary of files/directories found only in the end state.
  335. ``updated``
  336. Dictionary of files whose size has changed (FIXME not entirely
  337. reliable, but comparing contents is not possible because
  338. FoundFile.bytes is lazy, and comparing mtime doesn't help if
  339. we want to know if a file has been returned to its earlier
  340. state).
  341. Ignores mtime and other file attributes; only presence/absence and
  342. size are considered.
  343. """
  344. ignore = ignore or []
  345. def prefix_match(path, prefix):
  346. if path == prefix:
  347. return True
  348. prefix = prefix.rstrip(os.path.sep) + os.path.sep
  349. return path.startswith(prefix)
  350. start_keys = {k for k in start.keys()
  351. if not any([prefix_match(k, i) for i in ignore])}
  352. end_keys = {k for k in end.keys()
  353. if not any([prefix_match(k, i) for i in ignore])}
  354. deleted = {k: start[k] for k in start_keys.difference(end_keys)}
  355. created = {k: end[k] for k in end_keys.difference(start_keys)}
  356. updated = {}
  357. for k in start_keys.intersection(end_keys):
  358. if (start[k].size != end[k].size):
  359. updated[k] = end[k]
  360. return dict(deleted=deleted, created=created, updated=updated)
  361. def assert_all_changes(start_state, end_state, expected_changes):
  362. """
  363. Fails if anything changed that isn't listed in the
  364. expected_changes.
  365. start_state is either a dict mapping paths to
  366. scripttest.[FoundFile|FoundDir] objects or a TestPipResult whose
  367. files_before we'll test. end_state is either a similar dict or a
  368. TestPipResult whose files_after we'll test.
  369. Note: listing a directory means anything below
  370. that directory can be expected to have changed.
  371. """
  372. __tracebackhide__ = True
  373. start_files = start_state
  374. end_files = end_state
  375. if isinstance(start_state, TestPipResult):
  376. start_files = start_state.files_before
  377. if isinstance(end_state, TestPipResult):
  378. end_files = end_state.files_after
  379. diff = diff_states(start_files, end_files, ignore=expected_changes)
  380. if list(diff.values()) != [{}, {}, {}]:
  381. raise TestFailure('Unexpected changes:\n' + '\n'.join(
  382. [k + ': ' + ', '.join(v.keys()) for k, v in diff.items()]))
  383. # Don't throw away this potentially useful information
  384. return diff
  385. def _create_test_package_with_subdirectory(script, subdirectory):
  386. script.scratch_path.join("version_pkg").mkdir()
  387. version_pkg_path = script.scratch_path / 'version_pkg'
  388. version_pkg_path.join("version_pkg.py").write(textwrap.dedent("""
  389. def main():
  390. print('0.1')
  391. """))
  392. version_pkg_path.join("setup.py").write(
  393. textwrap.dedent("""
  394. from setuptools import setup, find_packages
  395. setup(name='version_pkg',
  396. version='0.1',
  397. packages=find_packages(),
  398. py_modules=['version_pkg'],
  399. entry_points=dict(console_scripts=['version_pkg=version_pkg:main']))
  400. """))
  401. subdirectory_path = version_pkg_path.join(subdirectory)
  402. subdirectory_path.mkdir()
  403. subdirectory_path.join('version_subpkg.py').write(textwrap.dedent("""
  404. def main():
  405. print('0.1')
  406. """))
  407. subdirectory_path.join('setup.py').write(
  408. textwrap.dedent("""
  409. from setuptools import setup, find_packages
  410. setup(name='version_subpkg',
  411. version='0.1',
  412. packages=find_packages(),
  413. py_modules=['version_subpkg'],
  414. entry_points=dict(console_scripts=['version_pkg=version_subpkg:main']))
  415. """))
  416. script.run('git', 'init', cwd=version_pkg_path)
  417. script.run('git', 'add', '.', cwd=version_pkg_path)
  418. script.run(
  419. 'git', 'commit', '-q',
  420. '--author', 'pip <pypa-dev@googlegroups.com>',
  421. '-am', 'initial version', cwd=version_pkg_path
  422. )
  423. return version_pkg_path
  424. def _create_test_package_with_srcdir(script, name='version_pkg', vcs='git'):
  425. script.scratch_path.join(name).mkdir()
  426. version_pkg_path = script.scratch_path / name
  427. subdir_path = version_pkg_path.join('subdir')
  428. subdir_path.mkdir()
  429. src_path = subdir_path.join('src')
  430. src_path.mkdir()
  431. pkg_path = src_path.join('pkg')
  432. pkg_path.mkdir()
  433. pkg_path.join('__init__.py').write('')
  434. subdir_path.join("setup.py").write(textwrap.dedent("""
  435. from setuptools import setup, find_packages
  436. setup(
  437. name='{name}',
  438. version='0.1',
  439. packages=find_packages(),
  440. package_dir={{'': 'src'}},
  441. )
  442. """.format(name=name)))
  443. return _vcs_add(script, version_pkg_path, vcs)
  444. def _create_test_package(script, name='version_pkg', vcs='git'):
  445. script.scratch_path.join(name).mkdir()
  446. version_pkg_path = script.scratch_path / name
  447. version_pkg_path.join("%s.py" % name).write(textwrap.dedent("""
  448. def main():
  449. print('0.1')
  450. """))
  451. version_pkg_path.join("setup.py").write(textwrap.dedent("""
  452. from setuptools import setup, find_packages
  453. setup(
  454. name='{name}',
  455. version='0.1',
  456. packages=find_packages(),
  457. py_modules=['{name}'],
  458. entry_points=dict(console_scripts=['{name}={name}:main'])
  459. )
  460. """.format(name=name)))
  461. return _vcs_add(script, version_pkg_path, vcs)
  462. def _vcs_add(script, version_pkg_path, vcs='git'):
  463. if vcs == 'git':
  464. script.run('git', 'init', cwd=version_pkg_path)
  465. script.run('git', 'add', '.', cwd=version_pkg_path)
  466. script.run(
  467. 'git', 'commit', '-q',
  468. '--author', 'pip <pypa-dev@googlegroups.com>',
  469. '-am', 'initial version', cwd=version_pkg_path,
  470. )
  471. elif vcs == 'hg':
  472. script.run('hg', 'init', cwd=version_pkg_path)
  473. script.run('hg', 'add', '.', cwd=version_pkg_path)
  474. script.run(
  475. 'hg', 'commit', '-q',
  476. '--user', 'pip <pypa-dev@googlegroups.com>',
  477. '-m', 'initial version', cwd=version_pkg_path,
  478. )
  479. elif vcs == 'svn':
  480. repo_url = _create_svn_repo(script, version_pkg_path)
  481. script.run(
  482. 'svn', 'checkout', repo_url, 'pip-test-package',
  483. cwd=script.scratch_path
  484. )
  485. checkout_path = script.scratch_path / 'pip-test-package'
  486. # svn internally stores windows drives as uppercase; we'll match that.
  487. checkout_path = checkout_path.replace('c:', 'C:')
  488. version_pkg_path = checkout_path
  489. elif vcs == 'bazaar':
  490. script.run('bzr', 'init', cwd=version_pkg_path)
  491. script.run('bzr', 'add', '.', cwd=version_pkg_path)
  492. script.run(
  493. 'bzr', 'whoami', 'pip <pypa-dev@googlegroups.com>',
  494. cwd=version_pkg_path)
  495. script.run(
  496. 'bzr', 'commit', '-q',
  497. '--author', 'pip <pypa-dev@googlegroups.com>',
  498. '-m', 'initial version', cwd=version_pkg_path,
  499. )
  500. else:
  501. raise ValueError('Unknown vcs: %r' % vcs)
  502. return version_pkg_path
  503. def _create_svn_repo(script, version_pkg_path):
  504. repo_url = path_to_url(
  505. script.scratch_path / 'pip-test-package-repo' / 'trunk')
  506. script.run(
  507. 'svnadmin', 'create', 'pip-test-package-repo',
  508. cwd=script.scratch_path
  509. )
  510. script.run(
  511. 'svn', 'import', version_pkg_path, repo_url,
  512. '-m', 'Initial import of pip-test-package',
  513. cwd=script.scratch_path
  514. )
  515. return repo_url
  516. def _change_test_package_version(script, version_pkg_path):
  517. version_pkg_path.join("version_pkg.py").write(textwrap.dedent('''\
  518. def main():
  519. print("some different version")'''))
  520. script.run(
  521. 'git', 'clean', '-qfdx',
  522. cwd=version_pkg_path,
  523. expect_stderr=True,
  524. )
  525. script.run(
  526. 'git', 'commit', '-q',
  527. '--author', 'pip <pypa-dev@googlegroups.com>',
  528. '-am', 'messed version',
  529. cwd=version_pkg_path,
  530. expect_stderr=True,
  531. )
  532. def assert_raises_regexp(exception, reg, run, *args, **kwargs):
  533. """Like assertRaisesRegexp in unittest"""
  534. __tracebackhide__ = True
  535. try:
  536. run(*args, **kwargs)
  537. assert False, "%s should have been thrown" % exception
  538. except exception:
  539. e = sys.exc_info()[1]
  540. p = re.compile(reg)
  541. assert p.search(str(e)), str(e)
  542. @contextmanager
  543. def requirements_file(contents, tmpdir):
  544. """Return a Path to a requirements file of given contents.
  545. As long as the context manager is open, the requirements file will exist.
  546. :param tmpdir: A Path to the folder in which to create the file
  547. """
  548. path = tmpdir / 'reqs.txt'
  549. path.write(contents)
  550. yield path
  551. path.remove()
  552. def create_test_package_with_setup(script, **setup_kwargs):
  553. assert 'name' in setup_kwargs, setup_kwargs
  554. pkg_path = script.scratch_path / setup_kwargs['name']
  555. pkg_path.mkdir()
  556. pkg_path.join("setup.py").write(textwrap.dedent("""
  557. from setuptools import setup
  558. kwargs = %r
  559. setup(**kwargs)
  560. """) % setup_kwargs)
  561. return pkg_path
  562. def create_basic_wheel_for_package(script, name, version, depends, extras):
  563. files = {
  564. "{name}/__init__.py": """
  565. def hello():
  566. return "Hello From {name}"
  567. """,
  568. "{dist_info}/DESCRIPTION": """
  569. UNKNOWN
  570. """,
  571. "{dist_info}/WHEEL": """
  572. Wheel-Version: 1.0
  573. Generator: pip-test-suite
  574. Root-Is-Purelib: true
  575. Tag: py2-none-any
  576. Tag: py3-none-any
  577. """,
  578. "{dist_info}/METADATA": """
  579. Metadata-Version: 2.0
  580. Name: {name}
  581. Version: {version}
  582. Summary: UNKNOWN
  583. Home-page: UNKNOWN
  584. Author: UNKNOWN
  585. Author-email: UNKNOWN
  586. License: UNKNOWN
  587. Platform: UNKNOWN
  588. {requires_dist}
  589. UNKNOWN
  590. """,
  591. "{dist_info}/top_level.txt": """
  592. {name}
  593. """,
  594. # Have an empty RECORD because we don't want to be checking hashes.
  595. "{dist_info}/RECORD": ""
  596. }
  597. # Some useful shorthands
  598. archive_name = "{name}-{version}-py2.py3-none-any.whl".format(
  599. name=name, version=version
  600. )
  601. dist_info = "{name}-{version}.dist-info".format(
  602. name=name, version=version
  603. )
  604. requires_dist = "\n".join([
  605. "Requires-Dist: {}".format(pkg) for pkg in depends
  606. ] + [
  607. "Provides-Extra: {}".format(pkg) for pkg in extras.keys()
  608. ] + [
  609. "Requires-Dist: {}; extra == \"{}\"".format(pkg, extra)
  610. for extra in extras for pkg in extras[extra]
  611. ])
  612. # Replace key-values with formatted values
  613. for key, value in list(files.items()):
  614. del files[key]
  615. key = key.format(name=name, dist_info=dist_info)
  616. files[key] = textwrap.dedent(value).format(
  617. name=name, version=version, requires_dist=requires_dist
  618. ).strip()
  619. for fname in files:
  620. path = script.temp_path / fname
  621. path.folder.mkdir()
  622. path.write(files[fname])
  623. retval = script.scratch_path / archive_name
  624. generated = shutil.make_archive(retval, 'zip', script.temp_path)
  625. shutil.move(generated, retval)
  626. script.temp_path.rmtree()
  627. script.temp_path.mkdir()
  628. return retval
  629. def need_executable(name, check_cmd):
  630. def wrapper(fn):
  631. try:
  632. subprocess.check_output(check_cmd)
  633. except OSError:
  634. return pytest.mark.skip(reason='%s is not available' % name)(fn)
  635. return fn
  636. return wrapper
  637. def need_bzr(fn):
  638. return pytest.mark.bzr(need_executable(
  639. 'Bazaar', ('bzr', 'version', '--short')
  640. )(fn))
  641. def need_mercurial(fn):
  642. return pytest.mark.mercurial(need_executable(
  643. 'Mercurial', ('hg', 'version')
  644. )(fn))