summaryrefslogtreecommitdiff
path: root/setuptools
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2017-11-19 21:39:41 -0500
committerJason R. Coombs <jaraco@jaraco.com>2017-11-19 21:39:41 -0500
commite4572e8c828cf5c82e072e01672283b5f27396a4 (patch)
treee8cc8df65484f1f29d9e6bd0a27a362eadddd068 /setuptools
parentdcb24ad15465c266a3f258471766fbbe8fc8a42e (diff)
parentd1cef0c5aa0ca473d4b5fef6b11e5508a37663a2 (diff)
downloadpython-setuptools-git-e4572e8c828cf5c82e072e01672283b5f27396a4.tar.gz
Merge branch 'master' into drop-py26
Diffstat (limited to 'setuptools')
-rw-r--r--setuptools/__init__.py22
-rw-r--r--setuptools/build_meta.py172
-rw-r--r--setuptools/command/__init__.py1
-rw-r--r--setuptools/command/bdist_egg.py18
-rwxr-xr-xsetuptools/command/develop.py4
-rw-r--r--setuptools/command/dist_info.py36
-rwxr-xr-xsetuptools/command/easy_install.py2
-rw-r--r--setuptools/command/test.py11
-rw-r--r--setuptools/dist.py40
-rwxr-xr-xsetuptools/package_index.py45
-rw-r--r--setuptools/ssl_support.py11
-rw-r--r--setuptools/tests/__init__.py320
-rw-r--r--setuptools/tests/test_bdist_egg.py26
-rw-r--r--setuptools/tests/test_build_meta.py126
-rw-r--r--setuptools/tests/test_develop.py4
-rw-r--r--setuptools/tests/test_dist.py1
-rw-r--r--setuptools/tests/test_easy_install.py85
-rw-r--r--setuptools/tests/test_egg_info.py6
-rw-r--r--setuptools/tests/test_namespaces.py12
-rw-r--r--setuptools/tests/test_sdist.py67
-rw-r--r--setuptools/tests/test_setuptools.py320
-rw-r--r--setuptools/tests/test_test.py90
-rw-r--r--setuptools/tests/test_virtualenv.py4
-rw-r--r--setuptools/tests/text.py9
24 files changed, 996 insertions, 436 deletions
diff --git a/setuptools/__init__.py b/setuptools/__init__.py
index 04f76740..7da47fbe 100644
--- a/setuptools/__init__.py
+++ b/setuptools/__init__.py
@@ -109,7 +109,27 @@ class PEP420PackageFinder(PackageFinder):
find_packages = PackageFinder.find
-setup = distutils.core.setup
+
+def _install_setup_requires(attrs):
+ # Note: do not use `setuptools.Distribution` directly, as
+ # our PEP 517 backend patch `distutils.core.Distribution`.
+ dist = distutils.core.Distribution(dict(
+ (k, v) for k, v in attrs.items()
+ if k in ('dependency_links', 'setup_requires')
+ ))
+ # Honor setup.cfg's options.
+ dist.parse_config_files(ignore_option_errors=True)
+ if dist.setup_requires:
+ dist.fetch_build_eggs(dist.setup_requires)
+
+
+def setup(**attrs):
+ # Make sure we have any requirements needed to interpret 'attrs'.
+ _install_setup_requires(attrs)
+ return distutils.core.setup(**attrs)
+
+setup.__doc__ = distutils.core.setup.__doc__
+
_Command = monkey.get_unpatched(distutils.core.Command)
diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py
new file mode 100644
index 00000000..609ea1e5
--- /dev/null
+++ b/setuptools/build_meta.py
@@ -0,0 +1,172 @@
+"""A PEP 517 interface to setuptools
+
+Previously, when a user or a command line tool (let's call it a "frontend")
+needed to make a request of setuptools to take a certain action, for
+example, generating a list of installation requirements, the frontend would
+would call "setup.py egg_info" or "setup.py bdist_wheel" on the command line.
+
+PEP 517 defines a different method of interfacing with setuptools. Rather
+than calling "setup.py" directly, the frontend should:
+
+ 1. Set the current directory to the directory with a setup.py file
+ 2. Import this module into a safe python interpreter (one in which
+ setuptools can potentially set global variables or crash hard).
+ 3. Call one of the functions defined in PEP 517.
+
+What each function does is defined in PEP 517. However, here is a "casual"
+definition of the functions (this definition should not be relied on for
+bug reports or API stability):
+
+ - `build_wheel`: build a wheel in the folder and return the basename
+ - `get_requires_for_build_wheel`: get the `setup_requires` to build
+ - `prepare_metadata_for_build_wheel`: get the `install_requires`
+ - `build_sdist`: build an sdist in the folder and return the basename
+ - `get_requires_for_build_sdist`: get the `setup_requires` to build
+
+Again, this is not a formal definition! Just a "taste" of the module.
+"""
+
+import os
+import sys
+import tokenize
+import shutil
+import contextlib
+
+import setuptools
+import distutils
+
+
+class SetupRequirementsError(BaseException):
+ def __init__(self, specifiers):
+ self.specifiers = specifiers
+
+
+class Distribution(setuptools.dist.Distribution):
+ def fetch_build_eggs(self, specifiers):
+ raise SetupRequirementsError(specifiers)
+
+ @classmethod
+ @contextlib.contextmanager
+ def patch(cls):
+ """
+ Replace
+ distutils.dist.Distribution with this class
+ for the duration of this context.
+ """
+ orig = distutils.core.Distribution
+ distutils.core.Distribution = cls
+ try:
+ yield
+ finally:
+ distutils.core.Distribution = orig
+
+
+def _run_setup(setup_script='setup.py'):
+ # Note that we can reuse our build directory between calls
+ # Correctness comes first, then optimization later
+ __file__ = setup_script
+ __name__ = '__main__'
+ f = getattr(tokenize, 'open', open)(__file__)
+ code = f.read().replace('\\r\\n', '\\n')
+ f.close()
+ exec(compile(code, __file__, 'exec'), locals())
+
+
+def _fix_config(config_settings):
+ config_settings = config_settings or {}
+ config_settings.setdefault('--global-option', [])
+ return config_settings
+
+
+def _get_build_requires(config_settings):
+ config_settings = _fix_config(config_settings)
+ requirements = ['setuptools', 'wheel']
+
+ sys.argv = sys.argv[:1] + ['egg_info'] + \
+ config_settings["--global-option"]
+ try:
+ with Distribution.patch():
+ _run_setup()
+ except SetupRequirementsError as e:
+ requirements += e.specifiers
+
+ return requirements
+
+
+def _get_immediate_subdirectories(a_dir):
+ return [name for name in os.listdir(a_dir)
+ if os.path.isdir(os.path.join(a_dir, name))]
+
+
+def get_requires_for_build_wheel(config_settings=None):
+ config_settings = _fix_config(config_settings)
+ return _get_build_requires(config_settings)
+
+
+def get_requires_for_build_sdist(config_settings=None):
+ config_settings = _fix_config(config_settings)
+ return _get_build_requires(config_settings)
+
+
+def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
+ sys.argv = sys.argv[:1] + ['dist_info', '--egg-base', metadata_directory]
+ _run_setup()
+
+ dist_info_directory = metadata_directory
+ while True:
+ dist_infos = [f for f in os.listdir(dist_info_directory)
+ if f.endswith('.dist-info')]
+
+ if len(dist_infos) == 0 and \
+ len(_get_immediate_subdirectories(dist_info_directory)) == 1:
+ dist_info_directory = os.path.join(
+ dist_info_directory, os.listdir(dist_info_directory)[0])
+ continue
+
+ assert len(dist_infos) == 1
+ break
+
+ # PEP 517 requires that the .dist-info directory be placed in the
+ # metadata_directory. To comply, we MUST copy the directory to the root
+ if dist_info_directory != metadata_directory:
+ shutil.move(
+ os.path.join(dist_info_directory, dist_infos[0]),
+ metadata_directory)
+ shutil.rmtree(dist_info_directory, ignore_errors=True)
+
+ return dist_infos[0]
+
+
+def build_wheel(wheel_directory, config_settings=None,
+ metadata_directory=None):
+ config_settings = _fix_config(config_settings)
+ wheel_directory = os.path.abspath(wheel_directory)
+ sys.argv = sys.argv[:1] + ['bdist_wheel'] + \
+ config_settings["--global-option"]
+ _run_setup()
+ if wheel_directory != 'dist':
+ shutil.rmtree(wheel_directory)
+ shutil.copytree('dist', wheel_directory)
+
+ wheels = [f for f in os.listdir(wheel_directory)
+ if f.endswith('.whl')]
+
+ assert len(wheels) == 1
+ return wheels[0]
+
+
+def build_sdist(sdist_directory, config_settings=None):
+ config_settings = _fix_config(config_settings)
+ sdist_directory = os.path.abspath(sdist_directory)
+ sys.argv = sys.argv[:1] + ['sdist'] + \
+ config_settings["--global-option"]
+ _run_setup()
+ if sdist_directory != 'dist':
+ shutil.rmtree(sdist_directory)
+ shutil.copytree('dist', sdist_directory)
+
+ sdists = [f for f in os.listdir(sdist_directory)
+ if f.endswith('.tar.gz')]
+
+ assert len(sdists) == 1
+ return sdists[0]
diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py
index c96d33c2..fe619e2e 100644
--- a/setuptools/command/__init__.py
+++ b/setuptools/command/__init__.py
@@ -3,6 +3,7 @@ __all__ = [
'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts',
'sdist', 'setopt', 'test', 'install_egg_info', 'install_scripts',
'register', 'bdist_wininst', 'upload_docs', 'upload', 'build_clib',
+ 'dist_info',
]
from distutils.command.bdist import bdist
diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py
index 51755d52..5fdb62d9 100644
--- a/setuptools/command/bdist_egg.py
+++ b/setuptools/command/bdist_egg.py
@@ -8,6 +8,7 @@ from distutils import log
from types import CodeType
import sys
import os
+import re
import textwrap
import marshal
@@ -240,11 +241,26 @@ class bdist_egg(Command):
log.info("Removing .py files from temporary directory")
for base, dirs, files in walk_egg(self.bdist_dir):
for name in files:
+ path = os.path.join(base, name)
+
if name.endswith('.py'):
- path = os.path.join(base, name)
log.debug("Deleting %s", path)
os.unlink(path)
+ if base.endswith('__pycache__'):
+ path_old = path
+
+ pattern = r'(?P<name>.+)\.(?P<magic>[^.]+)\.pyc'
+ m = re.match(pattern, name)
+ path_new = os.path.join(base, os.pardir, m.group('name') + '.pyc')
+ log.info("Renaming file from [%s] to [%s]" % (path_old, path_new))
+ try:
+ os.remove(path_new)
+ except OSError:
+ pass
+ os.rename(path_old, path_new)
+
+
def zip_safe(self):
safe = getattr(self.distribution, 'zip_safe', None)
if safe is not None:
diff --git a/setuptools/command/develop.py b/setuptools/command/develop.py
index 85b23c60..959c932a 100755
--- a/setuptools/command/develop.py
+++ b/setuptools/command/develop.py
@@ -95,7 +95,9 @@ class develop(namespaces.DevelopInstaller, easy_install):
path_to_setup = egg_base.replace(os.sep, '/').rstrip('/')
if path_to_setup != os.curdir:
path_to_setup = '../' * (path_to_setup.count('/') + 1)
- resolved = normalize_path(os.path.join(install_dir, egg_path, path_to_setup))
+ resolved = normalize_path(
+ os.path.join(install_dir, egg_path, path_to_setup)
+ )
if resolved != normalize_path(os.curdir):
raise DistutilsOptionError(
"Can't get a consistent path to setup script from"
diff --git a/setuptools/command/dist_info.py b/setuptools/command/dist_info.py
new file mode 100644
index 00000000..c45258fa
--- /dev/null
+++ b/setuptools/command/dist_info.py
@@ -0,0 +1,36 @@
+"""
+Create a dist_info directory
+As defined in the wheel specification
+"""
+
+import os
+
+from distutils.core import Command
+from distutils import log
+
+
+class dist_info(Command):
+
+ description = 'create a .dist-info directory'
+
+ user_options = [
+ ('egg-base=', 'e', "directory containing .egg-info directories"
+ " (default: top of the source tree)"),
+ ]
+
+ def initialize_options(self):
+ self.egg_base = None
+
+ def finalize_options(self):
+ pass
+
+ def run(self):
+ egg_info = self.get_finalized_command('egg_info')
+ egg_info.egg_base = self.egg_base
+ egg_info.finalize_options()
+ egg_info.run()
+ dist_info_dir = egg_info.egg_info[:-len('.egg-info')] + '.dist-info'
+ log.info("creating '{}'".format(os.path.abspath(dist_info_dir)))
+
+ bdist_wheel = self.get_finalized_command('bdist_wheel')
+ bdist_wheel.egg2dist(egg_info.egg_info, dist_info_dir)
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py
index 8fba7b41..71991efa 100755
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -1817,7 +1817,7 @@ def _update_zipimporter_cache(normalized_path, cache, updater=None):
# get/del patterns instead. For more detailed information see the
# following links:
# https://github.com/pypa/setuptools/issues/202#issuecomment-202913420
- # https://bitbucket.org/pypy/pypy/src/dd07756a34a41f674c0cacfbc8ae1d4cc9ea2ae4/pypy/module/zipimport/interp_zipimport.py#cl-99
+ # http://bit.ly/2h9itJX
old_entry = cache[p]
del cache[p]
new_entry = updater and updater(p, old_entry)
diff --git a/setuptools/command/test.py b/setuptools/command/test.py
index f00d6794..51aee1f7 100644
--- a/setuptools/command/test.py
+++ b/setuptools/command/test.py
@@ -18,6 +18,11 @@ from setuptools import Command
class ScanningLoader(TestLoader):
+
+ def __init__(self):
+ TestLoader.__init__(self)
+ self._visited = set()
+
def loadTestsFromModule(self, module, pattern=None):
"""Return a suite of all tests cases contained in the given module
@@ -25,6 +30,10 @@ class ScanningLoader(TestLoader):
If the module has an ``additional_tests`` function, call it and add
the return value to the tests.
"""
+ if module in self._visited:
+ return None
+ self._visited.add(module)
+
tests = []
tests.append(TestLoader.loadTestsFromModule(self, module))
@@ -101,6 +110,8 @@ class test(Command):
return list(self._test_args())
def _test_args(self):
+ if not self.test_suite and sys.version_info >= (2, 7):
+ yield 'discover'
if self.verbose:
yield '--verbose'
if self.test_suite:
diff --git a/setuptools/dist.py b/setuptools/dist.py
index a2ca8795..aa304500 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -316,23 +316,19 @@ class Distribution(Distribution_parse_config_files, _Distribution):
have_package_data = hasattr(self, "package_data")
if not have_package_data:
self.package_data = {}
- _attrs_dict = attrs or {}
- if 'features' in _attrs_dict or 'require_features' in _attrs_dict:
+ attrs = attrs or {}
+ if 'features' in attrs or 'require_features' in attrs:
Feature.warn_deprecated()
self.require_features = []
self.features = {}
self.dist_files = []
- self.src_root = attrs and attrs.pop("src_root", None)
+ self.src_root = attrs.pop("src_root", None)
self.patch_missing_pkg_info(attrs)
- self.long_description_content_type = _attrs_dict.get(
+ self.long_description_content_type = attrs.get(
'long_description_content_type'
)
- # Make sure we have any eggs needed to interpret 'attrs'
- if attrs is not None:
- self.dependency_links = attrs.pop('dependency_links', [])
- assert_string_list(self, 'dependency_links', self.dependency_links)
- if attrs and 'setup_requires' in attrs:
- self.fetch_build_eggs(attrs['setup_requires'])
+ self.dependency_links = attrs.pop('dependency_links', [])
+ self.setup_requires = attrs.pop('setup_requires', [])
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
vars(self).setdefault(ep.name, None)
_Distribution.__init__(self, attrs)
@@ -427,14 +423,15 @@ class Distribution(Distribution_parse_config_files, _Distribution):
req.marker = None
return req
- def parse_config_files(self, filenames=None):
+ def parse_config_files(self, filenames=None, ignore_option_errors=False):
"""Parses configuration files from various levels
and loads configuration.
"""
_Distribution.parse_config_files(self, filenames=filenames)
- parse_configuration(self, self.command_options)
+ parse_configuration(self, self.command_options,
+ ignore_option_errors=ignore_option_errors)
self._finalize_requires()
def parse_command_line(self):
@@ -497,19 +494,20 @@ class Distribution(Distribution_parse_config_files, _Distribution):
"""Fetch an egg needed for building"""
from setuptools.command.easy_install import easy_install
dist = self.__class__({'script_args': ['easy_install']})
- dist.parse_config_files()
opts = dist.get_option_dict('easy_install')
- keep = (
- 'find_links', 'site_dirs', 'index_url', 'optimize',
- 'site_dirs', 'allow_hosts'
- )
- for key in list(opts):
- if key not in keep:
- del opts[key] # don't use any other settings
+ opts.clear()
+ opts.update(
+ (k, v)
+ for k, v in self.get_option_dict('easy_install').items()
+ if k in (
+ # don't use any other settings
+ 'find_links', 'site_dirs', 'index_url',
+ 'optimize', 'site_dirs', 'allow_hosts',
+ ))
if self.dependency_links:
links = self.dependency_links[:]
if 'find_links' in opts:
- links = opts['find_links'][1].split() + links
+ links = opts['find_links'][1] + links
opts['find_links'] = ('setup', links)
install_dir = self.get_egg_cache_dir()
cmd = easy_install(
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
index 4f610e0e..fe2ef50f 100755
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -140,7 +140,7 @@ def distros_for_filename(filename, metadata=None):
def interpret_distro_name(
location, basename, metadata, py_version=None, precedence=SOURCE_DIST,
platform=None
- ):
+):
"""Generate alternative interpretations of a source distro name
Note: if `location` is a filesystem filename, you should call
@@ -291,7 +291,7 @@ class PackageIndex(Environment):
def __init__(
self, index_url="https://pypi.python.org/simple", hosts=('*',),
ca_bundle=None, verify_ssl=True, *args, **kw
- ):
+ ):
Environment.__init__(self, *args, **kw)
self.index_url = index_url + "/" [:not index_url.endswith('/')]
self.scanned_urls = {}
@@ -345,7 +345,8 @@ class PackageIndex(Environment):
base = f.url # handle redirects
page = f.read()
- if not isinstance(page, str): # We are in Python 3 and got bytes. We want str.
+ if not isinstance(page, str):
+ # In Python 3 and got bytes but want str.
if isinstance(f, urllib.error.HTTPError):
# Errors have no charset, assume latin1:
charset = 'latin-1'
@@ -380,8 +381,9 @@ class PackageIndex(Environment):
is_file = s and s.group(1).lower() == 'file'
if is_file or self.allows(urllib.parse.urlparse(url)[1]):
return True
- msg = ("\nNote: Bypassing %s (disallowed host; see "
- "http://bit.ly/1dg9ijs for details).\n")
+ msg = (
+ "\nNote: Bypassing %s (disallowed host; see "
+ "http://bit.ly/2hrImnY for details).\n")
if fatal:
raise DistutilsError(msg % url)
else:
@@ -499,15 +501,16 @@ class PackageIndex(Environment):
"""
checker is a ContentChecker
"""
- checker.report(self.debug,
+ checker.report(
+ self.debug,
"Validating %%s checksum for %s" % filename)
if not checker.is_valid():
tfp.close()
os.unlink(filename)
raise DistutilsError(
"%s validation failed for %s; "
- "possible download problem?" % (
- checker.hash.name, os.path.basename(filename))
+ "possible download problem?"
+ % (checker.hash.name, os.path.basename(filename))
)
def add_find_links(self, urls):
@@ -535,7 +538,8 @@ class PackageIndex(Environment):
if self[requirement.key]: # we've seen at least one distro
meth, msg = self.info, "Couldn't retrieve index page for %r"
else: # no distros seen for this name, might be misspelled
- meth, msg = (self.warn,
+ meth, msg = (
+ self.warn,
"Couldn't find index page for %r (maybe misspelled?)")
meth(msg, requirement.unsafe_name)
self.scan_all()
@@ -576,8 +580,7 @@ class PackageIndex(Environment):
def fetch_distribution(
self, requirement, tmpdir, force_scan=False, source=False,
- develop_ok=False, local_index=None
- ):
+ develop_ok=False, local_index=None):
"""Obtain a distribution suitable for fulfilling `requirement`
`requirement` must be a ``pkg_resources.Requirement`` instance.
@@ -608,12 +611,19 @@ class PackageIndex(Environment):
if dist.precedence == DEVELOP_DIST and not develop_ok:
if dist not in skipped:
- self.warn("Skipping development or system egg: %s", dist)
+ self.warn(
+ "Skipping development or system egg: %s", dist,
+ )
skipped[dist] = 1
continue
- if dist in req and (dist.precedence <= SOURCE_DIST or not source):
- dist.download_location = self.download(dist.location, tmpdir)
+ test = (
+ dist in req
+ and (dist.precedence <= SOURCE_DIST or not source)
+ )
+ if test:
+ loc = self.download(dist.location, tmpdir)
+ dist.download_location = loc
if os.path.exists(dist.download_location):
return dist
@@ -703,7 +713,7 @@ class PackageIndex(Environment):
def _download_to(self, url, filename):
self.info("Downloading %s", url)
# Download the file
- fp, info = None, None
+ fp = None
try:
checker = HashChecker.from_url(url)
fp = self.open_url(url)
@@ -892,7 +902,7 @@ class PackageIndex(Environment):
if rev is not None:
self.info("Updating to %s", rev)
- os.system("(cd %s && hg up -C -r %s >&-)" % (
+ os.system("(cd %s && hg up -C -r %s -q)" % (
filename,
rev,
))
@@ -1102,7 +1112,8 @@ def local_open(url):
f += '/'
files.append('<a href="{name}">{name}</a>'.format(name=f))
else:
- tmpl = ("<html><head><title>{url}</title>"
+ tmpl = (
+ "<html><head><title>{url}</title>"
"</head><body>{files}</body></html>")
body = tmpl.format(url=url, files='\n'.join(files))
status, message = 200, "OK"
diff --git a/setuptools/ssl_support.py b/setuptools/ssl_support.py
index 72b18ef2..6362f1f4 100644
--- a/setuptools/ssl_support.py
+++ b/setuptools/ssl_support.py
@@ -186,9 +186,14 @@ class VerifyingHTTPSConn(HTTPSConnection):
else:
actual_host = self.host
- self.sock = ssl.wrap_socket(
- sock, cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.ca_bundle
- )
+ if hasattr(ssl, 'create_default_context'):
+ ctx = ssl.create_default_context(cafile=self.ca_bundle)
+ self.sock = ctx.wrap_socket(sock, server_hostname=actual_host)
+ else:
+ # This is for python < 2.7.9 and < 3.4?
+ self.sock = ssl.wrap_socket(
+ sock, cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.ca_bundle
+ )
try:
match_hostname(self.sock.getpeercert(), actual_host)
except CertificateError:
diff --git a/setuptools/tests/__init__.py b/setuptools/tests/__init__.py
index 8ae4402d..54dd7d2b 100644
--- a/setuptools/tests/__init__.py
+++ b/setuptools/tests/__init__.py
@@ -1,326 +1,6 @@
-"""Tests for the 'setuptools' package"""
import locale
-import sys
-import os
-import distutils.core
-import distutils.cmd
-from distutils.errors import DistutilsOptionError, DistutilsPlatformError
-from distutils.errors import DistutilsSetupError
-from distutils.core import Extension
-from distutils.version import LooseVersion
-from setuptools.extern import six
import pytest
-import setuptools.dist
-import setuptools.depends as dep
-from setuptools import Feature
-from setuptools.depends import Require
-
is_ascii = locale.getpreferredencoding() == 'ANSI_X3.4-1968'
fail_on_ascii = pytest.mark.xfail(is_ascii, reason="Test fails in this locale")
-
-
-def makeSetup(**args):
- """Return distribution from 'setup(**args)', without executing commands"""
-
- distutils.core._setup_stop_after = "commandline"
-
- # Don't let system command line leak into tests!
- args.setdefault('script_args', ['install'])
-
- try:
- return setuptools.setup(**args)
- finally:
- distutils.core._setup_stop_after = None
-
-
-needs_bytecode = pytest.mark.skipif(
- not hasattr(dep, 'get_module_constant'),
- reason="bytecode support not available",
-)
-
-
-class TestDepends:
- def testExtractConst(self):
- if not hasattr(dep, 'extract_constant'):
- # skip on non-bytecode platforms
- return
-
- def f1():
- global x, y, z
- x = "test"
- y = z
-
- fc = six.get_function_code(f1)
-
- # unrecognized name
- assert dep.extract_constant(fc, 'q', -1) is None
-
- # constant assigned
- dep.extract_constant(fc, 'x', -1) == "test"
-
- # expression assigned
- dep.extract_constant(fc, 'y', -1) == -1
-
- # recognized name, not assigned
- dep.extract_constant(fc, 'z', -1) is None
-
- def testFindModule(self):
- with pytest.raises(ImportError):
- dep.find_module('no-such.-thing')
- with pytest.raises(ImportError):
- dep.find_module('setuptools.non-existent')
- f, p, i = dep.find_module('setuptools.tests')
- f.close()
-
- @needs_bytecode
- def testModuleExtract(self):
- from email import __version__
- assert dep.get_module_constant('email', '__version__') == __version__
- assert dep.get_module_constant('sys', 'version') == sys.version
- assert dep.get_module_constant('setuptools.tests', '__doc__') == __doc__
-
- @needs_bytecode
- def testRequire(self):
- req = Require('Email', '1.0.3', 'email')
-
- assert req.name == 'Email'
- assert req.module == 'email'
- assert req.requested_version == '1.0.3'
- assert req.attribute == '__version__'
- assert req.full_name() == 'Email-1.0.3'
-
- from email import __version__
- assert req.get_version() == __version__
- assert req.version_ok('1.0.9')
- assert not req.version_ok('0.9.1')
- assert not req.version_ok('unknown')
-
- assert req.is_present()
- assert req.is_current()
-
- req = Require('Email 3000', '03000', 'email', format=LooseVersion)
- assert req.is_present()
- assert not req.is_current()
- assert not req.version_ok('unknown')
-
- req = Require('Do-what-I-mean', '1.0', 'd-w-i-m')
- assert not req.is_present()
- assert not req.is_current()
-
- req = Require('Tests', None, 'tests', homepage="http://example.com")
- assert req.format is None
- assert req.attribute is None
- assert req.requested_version is None
- assert req.full_name() == 'Tests'
- assert req.homepage == 'http://example.com'
-
- paths = [os.path.dirname(p) for p in __path__]
- assert req.is_present(paths)
- assert req.is_current(paths)
-
-
-class TestDistro:
- def setup_method(self, method):
- self.e1 = Extension('bar.ext', ['bar.c'])
- self.e2 = Extension('c.y', ['y.c'])
-
- self.dist = makeSetup(
- packages=['a', 'a.b', 'a.b.c', 'b', 'c'],
- py_modules=['b.d', 'x'],
- ext_modules=(self.e1, self.e2),
- package_dir={},
- )
-
- def testDistroType(self):
- assert isinstance(self.dist, setuptools.dist.Distribution)
-
- def testExcludePackage(self):
- self.dist.exclude_package('a')
- assert self.dist.packages == ['b', 'c']
-
- self.dist.exclude_package('b')
- assert self.dist.packages == ['c']
- assert self.dist.py_modules == ['x']
- assert self.dist.ext_modules == [self.e1, self.e2]
-
- self.dist.exclude_package('c')
- assert self.dist.packages == []
- assert self.dist.py_modules == ['x']
- assert self.dist.ext_modules == [self.e1]
-
- # test removals from unspecified options
- makeSetup().exclude_package('x')
-
- def testIncludeExclude(self):
- # remove an extension
- self.dist.exclude(ext_modules=[self.e1])
- assert self.dist.ext_modules == [self.e2]
-
- # add it back in
- self.dist.include(ext_modules=[self.e1])
- assert self.dist.ext_modules == [self.e2, self.e1]
-
- # should not add duplicate
- self.dist.include(ext_modules=[self.e1])
- assert self.dist.ext_modules == [self.e2, self.e1]
-
- def testExcludePackages(self):
- self.dist.exclude(packages=['c', 'b', 'a'])
- assert self.dist.packages == []
- assert self.dist.py_modules == ['x']
- assert self.dist.ext_modules == [self.e1]
-
- def testEmpty(self):
- dist = makeSetup()
- dist.include(packages=['a'], py_modules=['b'], ext_modules=[self.e2])
- dist = makeSetup()
- dist.exclude(packages=['a'], py_modules=['b'], ext_modules=[self.e2])
-
- def testContents(self):
- assert self.dist.has_contents_for('a')
- self.dist.exclude_package('a')
- assert not self.dist.has_contents_for('a')
-
- assert self.dist.has_contents_for('b')
- self.dist.exclude_package('b')
- assert not self.dist.has_contents_for('b')
-
- assert self.dist.has_contents_for('c')
- self.dist.exclude_package('c')
- assert not self.dist.has_contents_for('c')
-
- def testInvalidIncludeExclude(self):
- with pytest.raises(DistutilsSetupError):
- self.dist.include(nonexistent_option='x')
- with pytest.raises(DistutilsSetupError):
- self.dist.exclude(nonexistent_option='x')
- with pytest.raises(DistutilsSetupError):
- self.dist.include(packages={'x': 'y'})
- with pytest.raises(DistutilsSetupError):
- self.dist.exclude(packages={'x': 'y'})
- with pytest.raises(DistutilsSetupError):
- self.dist.include(ext_modules={'x': 'y'})
- with pytest.raises(DistutilsSetupError):
- self.dist.exclude(ext_modules={'x': 'y'})
-
- with pytest.raises(DistutilsSetupError):
- self.dist.include(package_dir=['q'])
- with pytest.raises(DistutilsSetupError):
- self.dist.exclude(package_dir=['q'])
-
-
-class TestFeatures:
- def setup_method(self, method):
- self.req = Require('Distutils', '1.0.3', 'distutils')
- self.dist = makeSetup(
- features={
- 'foo': Feature("foo", standard=True, require_features=['baz', self.req]),
- 'bar': Feature("bar", standard=True, packages=['pkg.bar'],
- py_modules=['bar_et'], remove=['bar.ext'],
- ),
- 'baz': Feature(
- "baz", optional=False, packages=['pkg.baz'],
- scripts=['scripts/baz_it'],
- libraries=[('libfoo', 'foo/foofoo.c')]
- ),
- 'dwim': Feature("DWIM", available=False, remove='bazish'),
- },
- script_args=['--without-bar', 'install'],
- packages=['pkg.bar', 'pkg.foo'],
- py_modules=['bar_et', 'bazish'],
- ext_modules=[Extension('bar.ext', ['bar.c'])]
- )
-
- def testDefaults(self):
- assert not Feature(
- "test", standard=True, remove='x', available=False
- ).include_by_default()
- assert Feature("test", standard=True, remove='x').include_by_default()
- # Feature must have either kwargs, removes, or require_features
- with pytest.raises(DistutilsSetupError):
- Feature("test")
-
- def testAvailability(self):
- with pytest.raises(DistutilsPlatformError):
- self.dist.features['dwim'].include_in(self.dist)
-
- def testFeatureOptions(self):
- dist = self.dist
- assert (
- ('with-dwim', None, 'include DWIM') in dist.feature_options
- )
- assert (
- ('without-dwim', None, 'exclude DWIM (default)') in dist.feature_options
- )
- assert (
- ('with-bar', None, 'include bar (default)') in dist.feature_options
- )
- assert (
- ('without-bar', None, 'exclude bar') in dist.feature_options
- )
- assert dist.feature_negopt['without-foo'] == 'with-foo'
- assert dist.feature_negopt['without-bar'] == 'with-bar'
- assert dist.feature_negopt['without-dwim'] == 'with-dwim'
- assert ('without-baz' not in dist.feature_negopt)
-
- def testUseFeatures(self):
- dist = self.dist
- assert dist.with_foo == 1
- assert dist.with_bar == 0
- assert dist.with_baz == 1
- assert ('bar_et' not in dist.py_modules)
- assert ('pkg.bar' not in dist.packages)
- assert ('pkg.baz' in dist.packages)
- assert ('scripts/baz_it' in dist.scripts)
- assert (('libfoo', 'foo/foofoo.c') in dist.libraries)
- assert dist.ext_modules == []
- assert dist.require_features == [self.req]
-
- # If we ask for bar, it should fail because we explicitly disabled
- # it on the command line
- with pytest.raises(DistutilsOptionError):
- dist.include_feature('bar')
-
- def testFeatureWithInvalidRemove(self):
- with pytest.raises(SystemExit):
- makeSetup(features={'x': Feature('x', remove='y')})
-
-
-class TestCommandTests:
- def testTestIsCommand(self):
- test_cmd = makeSetup().get_command_obj('test')
- assert (isinstance(test_cmd, distutils.cmd.Command))
-
- def testLongOptSuiteWNoDefault(self):
- ts1 = makeSetup(script_args=['test', '--test-suite=foo.tests.suite'])
- ts1 = ts1.get_command_obj('test')
- ts1.ensure_finalized()
- assert ts1.test_suite == 'foo.tests.suite'
-
- def testDefaultSuite(self):
- ts2 = makeSetup(test_suite='bar.tests.suite').get_command_obj('test')
- ts2.ensure_finalized()
- assert ts2.test_suite == 'bar.tests.suite'
-
- def testDefaultWModuleOnCmdLine(self):
- ts3 = makeSetup(
- test_suite='bar.tests',
- script_args=['test', '-m', 'foo.tests']
- ).get_command_obj('test')
- ts3.ensure_finalized()
- assert ts3.test_module == 'foo.tests'
- assert ts3.test_suite == 'foo.tests.test_suite'
-
- def testConflictingOptions(self):
- ts4 = makeSetup(
- script_args=['test', '-m', 'bar.tests', '-s', 'foo.tests.suite']
- ).get_command_obj('test')
- with pytest.raises(DistutilsOptionError):
- ts4.ensure_finalized()
-
- def testNoSuite(self):
- ts5 = makeSetup().get_command_obj('test')
- ts5.ensure_finalized()
- assert ts5.test_suite is None
diff --git a/setuptools/tests/test_bdist_egg.py b/setuptools/tests/test_bdist_egg.py
index d24aa366..54742aa6 100644
--- a/setuptools/tests/test_bdist_egg.py
+++ b/setuptools/tests/test_bdist_egg.py
@@ -2,6 +2,7 @@
"""
import os
import re
+import zipfile
import pytest
@@ -16,7 +17,7 @@ setup(name='foo', py_modules=['hi'])
"""
-@pytest.yield_fixture
+@pytest.fixture(scope='function')
def setup_context(tmpdir):
with (tmpdir / 'setup.py').open('w') as f:
f.write(SETUP_PY)
@@ -32,7 +33,7 @@ class Test:
script_name='setup.py',
script_args=['bdist_egg'],
name='foo',
- py_modules=['hi']
+ py_modules=['hi'],
))
os.makedirs(os.path.join('build', 'src'))
with contexts.quiet():
@@ -42,3 +43,24 @@ class Test:
# let's see if we got our egg link at the right place
[content] = os.listdir('dist')
assert re.match(r'foo-0.0.0-py[23].\d.egg$', content)
+
+ @pytest.mark.xfail(
+ os.environ.get('PYTHONDONTWRITEBYTECODE'),
+ reason="Byte code disabled",
+ )
+ def test_exclude_source_files(self, setup_context, user_override):
+ dist = Distribution(dict(
+ script_name='setup.py',
+ script_args=['bdist_egg', '--exclude-source-files'],
+ name='foo',
+ py_modules=['hi'],
+ ))
+ with contexts.quiet():
+ dist.parse_command_line()
+ dist.run_commands()
+ [dist_name] = os.listdir('dist')
+ dist_filename = os.path.join('dist', dist_name)
+ zip = zipfile.ZipFile(dist_filename)
+ names = list(zi.filename for zi in zip.filelist)
+ assert 'hi.pyc' in names
+ assert 'hi.py' not in names
diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py
new file mode 100644
index 00000000..659c1a65
--- /dev/null
+++ b/setuptools/tests/test_build_meta.py
@@ -0,0 +1,126 @@
+import os
+
+import pytest
+
+from .files import build_files
+from .textwrap import DALS
+
+
+futures = pytest.importorskip('concurrent.futures')
+importlib = pytest.importorskip('importlib')
+
+
+class BuildBackendBase(object):
+ def __init__(self, cwd=None, env={}, backend_name='setuptools.build_meta'):
+ self.cwd = cwd
+ self.env = env
+ self.backend_name = backend_name
+
+
+class BuildBackend(BuildBackendBase):
+ """PEP 517 Build Backend"""
+ def __init__(self, *args, **kwargs):
+ super(BuildBackend, self).__init__(*args, **kwargs)
+ self.pool = futures.ProcessPoolExecutor()
+
+ def __getattr__(self, name):
+ """Handles aribrary function invocations on the build backend."""
+ def method(*args, **kw):
+ root = os.path.abspath(self.cwd)
+ caller = BuildBackendCaller(root, self.env, self.backend_name)
+ return self.pool.submit(caller, name, *args, **kw).result()
+
+ return method
+
+
+class BuildBackendCaller(BuildBackendBase):
+ def __call__(self, name, *args, **kw):
+ """Handles aribrary function invocations on the build backend."""
+ os.chdir(self.cwd)
+ os.environ.update(self.env)
+ mod = importlib.import_module(self.backend_name)
+ return getattr(mod, name)(*args, **kw)
+
+
+defns = [{
+ 'setup.py': DALS("""
+ __import__('setuptools').setup(
+ name='foo',
+ py_modules=['hello'],
+ setup_requires=['six'],
+ )
+ """),
+ 'hello.py': DALS("""
+ def run():
+ print('hello')
+ """),
+ },
+ {
+ 'setup.py': DALS("""
+ assert __name__ == '__main__'
+ __import__('setuptools').setup(
+ name='foo',
+ py_modules=['hello'],
+ setup_requires=['six'],
+ )
+ """),
+ 'hello.py': DALS("""
+ def run():
+ print('hello')
+ """),
+ },
+ {
+ 'setup.py': DALS("""
+ variable = True
+ def function():
+ return variable
+ assert variable
+ __import__('setuptools').setup(
+ name='foo',
+ py_modules=['hello'],
+ setup_requires=['six'],
+ )
+ """),
+ 'hello.py': DALS("""
+ def run():
+ print('hello')
+ """),
+ }]
+
+
+@pytest.fixture(params=defns)
+def build_backend(tmpdir, request):
+ build_files(request.param, prefix=str(tmpdir))
+ with tmpdir.as_cwd():
+ yield BuildBackend(cwd='.')
+
+
+def test_get_requires_for_build_wheel(build_backend):
+ actual = build_backend.get_requires_for_build_wheel()
+ expected = ['six', 'setuptools', 'wheel']
+ assert sorted(actual) == sorted(expected)
+
+
+def test_build_wheel(build_backend):
+ dist_dir = os.path.abspath('pip-wheel')
+ os.makedirs(dist_dir)
+ wheel_name = build_backend.build_wheel(dist_dir)
+
+ assert os.path.isfile(os.path.join(dist_dir, wheel_name))
+
+
+def test_build_sdist(build_backend):
+ dist_dir = os.path.abspath('pip-sdist')
+ os.makedirs(dist_dir)
+ sdist_name = build_backend.build_sdist(dist_dir)
+
+ assert os.path.isfile(os.path.join(dist_dir, sdist_name))
+
+
+def test_prepare_metadata_for_build_wheel(build_backend):
+ dist_dir = os.path.abspath('pip-dist-info')
+ os.makedirs(dist_dir)
+
+ dist_info = build_backend.prepare_metadata_for_build_wheel(dist_dir)
+
+ assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
diff --git a/setuptools/tests/test_develop.py b/setuptools/tests/test_develop.py
index ad7cfa05..cb4ff4b4 100644
--- a/setuptools/tests/test_develop.py
+++ b/setuptools/tests/test_develop.py
@@ -167,7 +167,9 @@ class TestNamespaces:
target = tmpdir / 'packages'
# use pip to install to the target directory
install_cmd = [
- 'pip',
+ sys.executable,
+ '-m',
+ 'pip.__main__',
'install',
str(pkg_A),
'-t', str(target),
diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py
index 435ffec0..c4c9bd03 100644
--- a/setuptools/tests/test_dist.py
+++ b/setuptools/tests/test_dist.py
@@ -39,6 +39,7 @@ def test_dist_fetch_build_egg(tmpdir):
'''.split()
with tmpdir.as_cwd():
dist = Distribution()
+ dist.parse_config_files()
resolved_dists = [
dist.fetch_build_egg(r)
for r in reqs
diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py
index e4ed556f..834710ef 100644
--- a/setuptools/tests/test_easy_install.py
+++ b/setuptools/tests/test_easy_install.py
@@ -380,7 +380,15 @@ class TestSetupRequires:
"""))])
yield dist_path
- def test_setup_requires_overrides_version_conflict(self):
+ use_setup_cfg = (
+ (),
+ ('dependency_links',),
+ ('setup_requires',),
+ ('dependency_links', 'setup_requires'),
+ )
+
+ @pytest.mark.parametrize('use_setup_cfg', use_setup_cfg)
+ def test_setup_requires_overrides_version_conflict(self, use_setup_cfg):
"""
Regression test for distribution issue 323:
https://bitbucket.org/tarek/distribute/issues/323
@@ -396,7 +404,7 @@ class TestSetupRequires:
with contexts.save_pkg_resources_state():
with contexts.tempdir() as temp_dir:
- test_pkg = create_setup_requires_package(temp_dir)
+ test_pkg = create_setup_requires_package(temp_dir, use_setup_cfg=use_setup_cfg)
test_setup_py = os.path.join(test_pkg, 'setup.py')
with contexts.quiet() as (stdout, stderr):
# Don't even need to install the package, just
@@ -405,9 +413,10 @@ class TestSetupRequires:
lines = stdout.readlines()
assert len(lines) > 0
- assert lines[-1].strip(), 'test_pkg'
+ assert lines[-1].strip() == 'test_pkg'
- def test_setup_requires_override_nspkg(self):
+ @pytest.mark.parametrize('use_setup_cfg', use_setup_cfg)
+ def test_setup_requires_override_nspkg(self, use_setup_cfg):
"""
Like ``test_setup_requires_overrides_version_conflict`` but where the
``setup_requires`` package is part of a namespace package that has
@@ -445,7 +454,8 @@ class TestSetupRequires:
""")
test_pkg = create_setup_requires_package(
- temp_dir, 'foo.bar', '0.2', make_nspkg_sdist, template)
+ temp_dir, 'foo.bar', '0.2', make_nspkg_sdist, template,
+ use_setup_cfg=use_setup_cfg)
test_setup_py = os.path.join(test_pkg, 'setup.py')
@@ -463,6 +473,38 @@ class TestSetupRequires:
assert len(lines) > 0
assert lines[-1].strip() == 'test_pkg'
+ @pytest.mark.parametrize('use_setup_cfg', use_setup_cfg)
+ def test_setup_requires_with_attr_version(self, use_setup_cfg):
+ def make_dependency_sdist(dist_path, distname, version):
+ make_sdist(dist_path, [
+ ('setup.py',
+ DALS("""
+ import setuptools
+ setuptools.setup(
+ name={name!r},
+ version={version!r},
+ py_modules=[{name!r}],
+ )
+ """.format(name=distname, version=version))),
+ (distname + '.py',
+ DALS("""
+ version = 42
+ """
+ ))])
+ with contexts.save_pkg_resources_state():
+ with contexts.tempdir() as temp_dir:
+ test_pkg = create_setup_requires_package(
+ temp_dir, setup_attrs=dict(version='attr: foobar.version'),
+ make_package=make_dependency_sdist,
+ use_setup_cfg=use_setup_cfg+('version',),
+ )
+ test_setup_py = os.path.join(test_pkg, 'setup.py')
+ with contexts.quiet() as (stdout, stderr):
+ run_setup(test_setup_py, ['--version'])
+ lines = stdout.readlines()
+ assert len(lines) > 0
+ assert lines[-1].strip() == '42'
+
def make_trivial_sdist(dist_path, distname, version):
"""
@@ -531,7 +573,8 @@ def make_sdist(dist_path, files):
def create_setup_requires_package(path, distname='foobar', version='0.1',
make_package=make_trivial_sdist,
- setup_py_template=None):
+ setup_py_template=None, setup_attrs={},
+ use_setup_cfg=()):
"""Creates a source tree under path for a trivial test package that has a
single requirement in setup_requires--a tarball for that requirement is
also created and added to the dependency_links argument.
@@ -546,11 +589,39 @@ def create_setup_requires_package(path, distname='foobar', version='0.1',
'setup_requires': ['%s==%s' % (distname, version)],
'dependency_links': [os.path.abspath(path)]
}
+ test_setup_attrs.update(setup_attrs)
test_pkg = os.path.join(path, 'test_pkg')
- test_setup_py = os.path.join(test_pkg, 'setup.py')
os.mkdir(test_pkg)
+ if use_setup_cfg:
+ test_setup_cfg = os.path.join(test_pkg, 'setup.cfg')
+ options = []
+ metadata = []
+ for name in use_setup_cfg:
+ value = test_setup_attrs.pop(name)
+ if name in 'name version'.split():
+ section = metadata
+ else:
+ section = options
+ if isinstance(value, (tuple, list)):
+ value = ';'.join(value)
+ section.append('%s: %s' % (name, value))
+ with open(test_setup_cfg, 'w') as f:
+ f.write(DALS(
+ """
+ [metadata]
+ {metadata}
+ [options]
+ {options}
+ """
+ ).format(
+ options='\n'.join(options),
+ metadata='\n'.join(metadata),
+ ))
+
+ test_setup_py = os.path.join(test_pkg, 'setup.py')
+
if setup_py_template is None:
setup_py_template = DALS("""\
import setuptools
diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py
index 4c04d298..a97d0c84 100644
--- a/setuptools/tests/test_egg_info.py
+++ b/setuptools/tests/test_egg_info.py
@@ -157,7 +157,8 @@ class TestEggInfo(object):
self._run_install_command(tmpdir_cwd, env)
egg_info_dir = self._find_egg_info_files(env.paths['lib']).base
sources_txt = os.path.join(egg_info_dir, 'SOURCES.txt')
- assert 'docs/usage.rst' in open(sources_txt).read().split('\n')
+ with open(sources_txt) as f:
+ assert 'docs/usage.rst' in f.read().split('\n')
def _setup_script_with_requires(self, requires, use_setup_cfg=False):
setup_script = DALS(
@@ -440,7 +441,8 @@ class TestEggInfo(object):
self._run_install_command(tmpdir_cwd, env)
egg_info_dir = self._find_egg_info_files(env.paths['lib']).base
pkginfo = os.path.join(egg_info_dir, 'PKG-INFO')
- assert 'Requires-Python: >=1.2.3' in open(pkginfo).read().split('\n')
+ with open(pkginfo) as f:
+ assert 'Requires-Python: >=1.2.3' in f.read().split('\n')
def test_manifest_maker_warning_suppression(self):
fixtures = [
diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py
index 721cad1e..1ac1b35e 100644
--- a/setuptools/tests/test_namespaces.py
+++ b/setuptools/tests/test_namespaces.py
@@ -30,7 +30,9 @@ class TestNamespaces:
targets = site_packages, path_packages
# use pip to install to the target directory
install_cmd = [
- 'pip',
+ sys.executable,
+ '-m',
+ 'pip.__main__',
'install',
str(pkg_A),
'-t', str(site_packages),
@@ -38,7 +40,9 @@ class TestNamespaces:
subprocess.check_call(install_cmd)
namespaces.make_site_dir(site_packages)
install_cmd = [
- 'pip',
+ sys.executable,
+ '-m',
+ 'pip.__main__',
'install',
str(pkg_B),
'-t', str(path_packages),
@@ -88,7 +92,9 @@ class TestNamespaces:
target = tmpdir / 'packages'
# use pip to install to the target directory
install_cmd = [
- 'pip',
+ sys.executable,
+ '-m',
+ 'pip.__main__',
'install',
str(pkg_A),
'-t', str(target),
diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py
index f34068dc..02222da5 100644
--- a/setuptools/tests/test_sdist.py
+++ b/setuptools/tests/test_sdist.py
@@ -19,6 +19,7 @@ from setuptools.command.sdist import sdist
from setuptools.command.egg_info import manifest_maker
from setuptools.dist import Distribution
from setuptools.tests import fail_on_ascii
+from .text import Filenames
py3_only = pytest.mark.xfail(six.PY2, reason="Test runs on Python 3 only")
@@ -36,13 +37,7 @@ from setuptools import setup
setup(**%r)
""" % SETUP_ATTRS
-if six.PY3:
- LATIN1_FILENAME = 'smörbröd.py'.encode('latin-1')
-else:
- LATIN1_FILENAME = 'sm\xf6rbr\xf6d.py'
-
-# Cannot use context manager because of Python 2.4
@contextlib.contextmanager
def quiet():
old_stdout, old_stderr = sys.stdout, sys.stderr
@@ -53,17 +48,10 @@ def quiet():
sys.stdout, sys.stderr = old_stdout, old_stderr
-# Fake byte literals for Python <= 2.5
-def b(s, encoding='utf-8'):
- if six.PY3:
- return s.encode(encoding)
- return s
-
-
# Convert to POSIX path
def posix(path):
if six.PY3 and not isinstance(path, str):
- return path.replace(os.sep.encode('ascii'), b('/'))
+ return path.replace(os.sep.encode('ascii'), b'/')
else:
return path.replace(os.sep, '/')
@@ -86,6 +74,21 @@ def read_all_bytes(filename):
return fp.read()
+def latin1_fail():
+ try:
+ desc, filename = tempfile.mkstemp(suffix=Filenames.latin_1)
+ os.close(desc)
+ os.remove(filename)
+ except Exception:
+ return True
+
+
+fail_on_latin1_encoded_filenames = pytest.mark.xfail(
+ latin1_fail(),
+ reason="System does not support latin-1 filenames",
+)
+
+
class TestSdistTest:
def setup_method(self, method):
self.temp_dir = tempfile.mkdtemp()
@@ -134,8 +137,8 @@ class TestSdistTest:
def test_defaults_case_sensitivity(self):
"""
- Make sure default files (README.*, etc.) are added in a case-sensitive
- way to avoid problems with packages built on Windows.
+ Make sure default files (README.*, etc.) are added in a case-sensitive
+ way to avoid problems with packages built on Windows.
"""
open(os.path.join(self.temp_dir, 'readme.rst'), 'w').close()
@@ -152,7 +155,9 @@ class TestSdistTest:
with quiet():
cmd.run()
- # lowercase all names so we can test in a case-insensitive way to make sure the files are not included
+ # lowercase all names so we can test in a
+ # case-insensitive way to make sure the files
+ # are not included.
manifest = map(lambda x: x.lower(), cmd.filelist.files)
assert 'readme.rst' not in manifest, manifest
assert 'setup.py' not in manifest, manifest
@@ -201,8 +206,7 @@ class TestSdistTest:
mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
os.mkdir('sdist_test.egg-info')
- # UTF-8 filename
- filename = os.path.join(b('sdist_test'), b('smörbröd.py'))
+ filename = os.path.join(b'sdist_test', Filenames.utf_8)
# Must touch the file or risk removal
open(filename, "w").close()
@@ -241,7 +245,7 @@ class TestSdistTest:
os.mkdir('sdist_test.egg-info')
# Latin-1 filename
- filename = os.path.join(b('sdist_test'), LATIN1_FILENAME)
+ filename = os.path.join(b'sdist_test', Filenames.latin_1)
# Add filename with surrogates and write manifest
with quiet():
@@ -275,10 +279,10 @@ class TestSdistTest:
cmd.run()
# Add UTF-8 filename to manifest
- filename = os.path.join(b('sdist_test'), b('smörbröd.py'))
+ filename = os.path.join(b'sdist_test', Filenames.utf_8)
cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
manifest = open(cmd.manifest, 'ab')
- manifest.write(b('\n') + filename)
+ manifest.write(b'\n' + filename)
manifest.close()
# The file must exist to be included in the filelist
@@ -295,6 +299,7 @@ class TestSdistTest:
assert filename in cmd.filelist.files
@py3_only
+ @fail_on_latin1_encoded_filenames
def test_read_manifest_skips_non_utf8_filenames(self):
# Test for #303.
dist = Distribution(SETUP_ATTRS)
@@ -307,10 +312,10 @@ class TestSdistTest:
cmd.run()
# Add Latin-1 filename to manifest
- filename = os.path.join(b('sdist_test'), LATIN1_FILENAME)
+ filename = os.path.join(b'sdist_test', Filenames.latin_1)
cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
manifest = open(cmd.manifest, 'ab')
- manifest.write(b('\n') + filename)
+ manifest.write(b'\n' + filename)
manifest.close()
# The file must exist to be included in the filelist
@@ -326,6 +331,7 @@ class TestSdistTest:
assert filename not in cmd.filelist.files
@fail_on_ascii
+ @fail_on_latin1_encoded_filenames
def test_sdist_with_utf8_encoded_filename(self):
# Test for #303.
dist = Distribution(SETUP_ATTRS)
@@ -333,8 +339,7 @@ class TestSdistTest:
cmd = sdist(dist)
cmd.ensure_finalized()
- # UTF-8 filename
- filename = os.path.join(b('sdist_test'), b('smörbröd.py'))
+ filename = os.path.join(b'sdist_test', Filenames.utf_8)
open(filename, 'w').close()
with quiet():
@@ -360,6 +365,7 @@ class TestSdistTest:
else:
assert filename in cmd.filelist.files
+ @fail_on_latin1_encoded_filenames
def test_sdist_with_latin1_encoded_filename(self):
# Test for #303.
dist = Distribution(SETUP_ATTRS)
@@ -368,7 +374,7 @@ class TestSdistTest:
cmd.ensure_finalized()
# Latin-1 filename
- filename = os.path.join(b('sdist_test'), LATIN1_FILENAME)
+ filename = os.path.join(b'sdist_test', Filenames.latin_1)
open(filename, 'w').close()
assert os.path.isfile(filename)
@@ -381,10 +387,9 @@ class TestSdistTest:
# Latin-1 is similar to Windows-1252 however
# on mbcs filesys it is not in latin-1 encoding
fs_enc = sys.getfilesystemencoding()
- if fs_enc == 'mbcs':
- filename = filename.decode('mbcs')
- else:
- filename = filename.decode('latin-1')
+ if fs_enc != 'mbcs':
+ fs_enc = 'latin-1'
+ filename = filename.decode(fs_enc)
assert filename in cmd.filelist.files
else:
diff --git a/setuptools/tests/test_setuptools.py b/setuptools/tests/test_setuptools.py
index e59800d2..26e37a6c 100644
--- a/setuptools/tests/test_setuptools.py
+++ b/setuptools/tests/test_setuptools.py
@@ -1,8 +1,328 @@
+"""Tests for the 'setuptools' package"""
+
+import sys
import os
+import distutils.core
+import distutils.cmd
+from distutils.errors import DistutilsOptionError, DistutilsPlatformError
+from distutils.errors import DistutilsSetupError
+from distutils.core import Extension
+from distutils.version import LooseVersion
import pytest
import setuptools
+import setuptools.dist
+import setuptools.depends as dep
+from setuptools import Feature
+from setuptools.depends import Require
+from setuptools.extern import six
+
+
+def makeSetup(**args):
+ """Return distribution from 'setup(**args)', without executing commands"""
+
+ distutils.core._setup_stop_after = "commandline"
+
+ # Don't let system command line leak into tests!
+ args.setdefault('script_args', ['install'])
+
+ try:
+ return setuptools.setup(**args)
+ finally:
+ distutils.core._setup_stop_after = None
+
+
+needs_bytecode = pytest.mark.skipif(
+ not hasattr(dep, 'get_module_constant'),
+ reason="bytecode support not available",
+)
+
+
+class TestDepends:
+ def testExtractConst(self):
+ if not hasattr(dep, 'extract_constant'):
+ # skip on non-bytecode platforms
+ return
+
+ def f1():
+ global x, y, z
+ x = "test"
+ y = z
+
+ fc = six.get_function_code(f1)
+
+ # unrecognized name
+ assert dep.extract_constant(fc, 'q', -1) is None
+
+ # constant assigned
+ dep.extract_constant(fc, 'x', -1) == "test"
+
+ # expression assigned
+ dep.extract_constant(fc, 'y', -1) == -1
+
+ # recognized name, not assigned
+ dep.extract_constant(fc, 'z', -1) is None
+
+ def testFindModule(self):
+ with pytest.raises(ImportError):
+ dep.find_module('no-such.-thing')
+ with pytest.raises(ImportError):
+ dep.find_module('setuptools.non-existent')
+ f, p, i = dep.find_module('setuptools.tests')
+ f.close()
+
+ @needs_bytecode
+ def testModuleExtract(self):
+ from json import __version__
+ assert dep.get_module_constant('json', '__version__') == __version__
+ assert dep.get_module_constant('sys', 'version') == sys.version
+ assert dep.get_module_constant('setuptools.tests.test_setuptools', '__doc__') == __doc__
+
+ @needs_bytecode
+ def testRequire(self):
+ req = Require('Json', '1.0.3', 'json')
+
+ assert req.name == 'Json'
+ assert req.module == 'json'
+ assert req.requested_version == '1.0.3'
+ assert req.attribute == '__version__'
+ assert req.full_name() == 'Json-1.0.3'
+
+ from json import __version__
+ assert req.get_version() == __version__
+ assert req.version_ok('1.0.9')
+ assert not req.version_ok('0.9.1')
+ assert not req.version_ok('unknown')
+
+ assert req.is_present()
+ assert req.is_current()
+
+ req = Require('Json 3000', '03000', 'json', format=LooseVersion)
+ assert req.is_present()
+ assert not req.is_current()
+ assert not req.version_ok('unknown')
+
+ req = Require('Do-what-I-mean', '1.0', 'd-w-i-m')
+ assert not req.is_present()
+ assert not req.is_current()
+
+ req = Require('Tests', None, 'tests', homepage="http://example.com")
+ assert req.format is None
+ assert req.attribute is None
+ assert req.requested_version is None
+ assert req.full_name() == 'Tests'
+ assert req.homepage == 'http://example.com'
+
+ from setuptools.tests import __path__
+ paths = [os.path.dirname(p) for p in __path__]
+ assert req.is_present(paths)
+ assert req.is_current(paths)
+
+
+class TestDistro:
+ def setup_method(self, method):
+ self.e1 = Extension('bar.ext', ['bar.c'])
+ self.e2 = Extension('c.y', ['y.c'])
+
+ self.dist = makeSetup(
+ packages=['a', 'a.b', 'a.b.c', 'b', 'c'],
+ py_modules=['b.d', 'x'],
+ ext_modules=(self.e1, self.e2),
+ package_dir={},
+ )
+
+ def testDistroType(self):
+ assert isinstance(self.dist, setuptools.dist.Distribution)
+
+ def testExcludePackage(self):
+ self.dist.exclude_package('a')
+ assert self.dist.packages == ['b', 'c']
+
+ self.dist.exclude_package('b')
+ assert self.dist.packages == ['c']
+ assert self.dist.py_modules == ['x']
+ assert self.dist.ext_modules == [self.e1, self.e2]
+
+ self.dist.exclude_package('c')
+ assert self.dist.packages == []
+ assert self.dist.py_modules == ['x']
+ assert self.dist.ext_modules == [self.e1]
+
+ # test removals from unspecified options
+ makeSetup().exclude_package('x')
+
+ def testIncludeExclude(self):
+ # remove an extension
+ self.dist.exclude(ext_modules=[self.e1])
+ assert self.dist.ext_modules == [self.e2]
+
+ # add it back in
+ self.dist.include(ext_modules=[self.e1])
+ assert self.dist.ext_modules == [self.e2, self.e1]
+
+ # should not add duplicate
+ self.dist.include(ext_modules=[self.e1])
+ assert self.dist.ext_modules == [self.e2, self.e1]
+
+ def testExcludePackages(self):
+ self.dist.exclude(packages=['c', 'b', 'a'])
+ assert self.dist.packages == []
+ assert self.dist.py_modules == ['x']
+ assert self.dist.ext_modules == [self.e1]
+
+ def testEmpty(self):
+ dist = makeSetup()
+ dist.include(packages=['a'], py_modules=['b'], ext_modules=[self.e2])
+ dist = makeSetup()
+ dist.exclude(packages=['a'], py_modules=['b'], ext_modules=[self.e2])
+
+ def testContents(self):
+ assert self.dist.has_contents_for('a')
+ self.dist.exclude_package('a')
+ assert not self.dist.has_contents_for('a')
+
+ assert self.dist.has_contents_for('b')
+ self.dist.exclude_package('b')
+ assert not self.dist.has_contents_for('b')
+
+ assert self.dist.has_contents_for('c')
+ self.dist.exclude_package('c')
+ assert not self.dist.has_contents_for('c')
+
+ def testInvalidIncludeExclude(self):
+ with pytest.raises(DistutilsSetupError):
+ self.dist.include(nonexistent_option='x')
+ with pytest.raises(DistutilsSetupError):
+ self.dist.exclude(nonexistent_option='x')
+ with pytest.raises(DistutilsSetupError):
+ self.dist.include(packages={'x': 'y'})
+ with pytest.raises(DistutilsSetupError):
+ self.dist.exclude(packages={'x': 'y'})
+ with pytest.raises(DistutilsSetupError):
+ self.dist.include(ext_modules={'x': 'y'})
+ with pytest.raises(DistutilsSetupError):
+ self.dist.exclude(ext_modules={'x': 'y'})
+
+ with pytest.raises(DistutilsSetupError):
+ self.dist.include(package_dir=['q'])
+ with pytest.raises(DistutilsSetupError):
+ self.dist.exclude(package_dir=['q'])
+
+
+class TestFeatures:
+ def setup_method(self, method):
+ self.req = Require('Distutils', '1.0.3', 'distutils')
+ self.dist = makeSetup(
+ features={
+ 'foo': Feature("foo", standard=True, require_features=['baz', self.req]),
+ 'bar': Feature("bar", standard=True, packages=['pkg.bar'],
+ py_modules=['bar_et'], remove=['bar.ext'],
+ ),
+ 'baz': Feature(
+ "baz", optional=False, packages=['pkg.baz'],
+ scripts=['scripts/baz_it'],
+ libraries=[('libfoo', 'foo/foofoo.c')]
+ ),
+ 'dwim': Feature("DWIM", available=False, remove='bazish'),
+ },
+ script_args=['--without-bar', 'install'],
+ packages=['pkg.bar', 'pkg.foo'],
+ py_modules=['bar_et', 'bazish'],
+ ext_modules=[Extension('bar.ext', ['bar.c'])]
+ )
+
+ def testDefaults(self):
+ assert not Feature(
+ "test", standard=True, remove='x', available=False
+ ).include_by_default()
+ assert Feature("test", standard=True, remove='x').include_by_default()
+ # Feature must have either kwargs, removes, or require_features
+ with pytest.raises(DistutilsSetupError):
+ Feature("test")
+
+ def testAvailability(self):
+ with pytest.raises(DistutilsPlatformError):
+ self.dist.features['dwim'].include_in(self.dist)
+
+ def testFeatureOptions(self):
+ dist = self.dist
+ assert (
+ ('with-dwim', None, 'include DWIM') in dist.feature_options
+ )
+ assert (
+ ('without-dwim', None, 'exclude DWIM (default)') in dist.feature_options
+ )
+ assert (
+ ('with-bar', None, 'include bar (default)') in dist.feature_options
+ )
+ assert (
+ ('without-bar', None, 'exclude bar') in dist.feature_options
+ )
+ assert dist.feature_negopt['without-foo'] == 'with-foo'
+ assert dist.feature_negopt['without-bar'] == 'with-bar'
+ assert dist.feature_negopt['without-dwim'] == 'with-dwim'
+ assert ('without-baz' not in dist.feature_negopt)
+
+ def testUseFeatures(self):
+ dist = self.dist
+ assert dist.with_foo == 1
+ assert dist.with_bar == 0
+ assert dist.with_baz == 1
+ assert ('bar_et' not in dist.py_modules)
+ assert ('pkg.bar' not in dist.packages)
+ assert ('pkg.baz' in dist.packages)
+ assert ('scripts/baz_it' in dist.scripts)
+ assert (('libfoo', 'foo/foofoo.c') in dist.libraries)
+ assert dist.ext_modules == []
+ assert dist.require_features == [self.req]
+
+ # If we ask for bar, it should fail because we explicitly disabled
+ # it on the command line
+ with pytest.raises(DistutilsOptionError):
+ dist.include_feature('bar')
+
+ def testFeatureWithInvalidRemove(self):
+ with pytest.raises(SystemExit):
+ makeSetup(features={'x': Feature('x', remove='y')})
+
+
+class TestCommandTests:
+ def testTestIsCommand(self):
+ test_cmd = makeSetup().get_command_obj('test')
+ assert (isinstance(test_cmd, distutils.cmd.Command))
+
+ def testLongOptSuiteWNoDefault(self):
+ ts1 = makeSetup(script_args=['test', '--test-suite=foo.tests.suite'])
+ ts1 = ts1.get_command_obj('test')
+ ts1.ensure_finalized()
+ assert ts1.test_suite == 'foo.tests.suite'
+
+ def testDefaultSuite(self):
+ ts2 = makeSetup(test_suite='bar.tests.suite').get_command_obj('test')
+ ts2.ensure_finalized()
+ assert ts2.test_suite == 'bar.tests.suite'
+
+ def testDefaultWModuleOnCmdLine(self):
+ ts3 = makeSetup(
+ test_suite='bar.tests',
+ script_args=['test', '-m', 'foo.tests']
+ ).get_command_obj('test')
+ ts3.ensure_finalized()
+ assert ts3.test_module == 'foo.tests'
+ assert ts3.test_suite == 'foo.tests.test_suite'
+
+ def testConflictingOptions(self):
+ ts4 = makeSetup(
+ script_args=['test', '-m', 'bar.tests', '-s', 'foo.tests.suite']
+ ).get_command_obj('test')
+ with pytest.raises(DistutilsOptionError):
+ ts4.ensure_finalized()
+
+ def testNoSuite(self):
+ ts5 = makeSetup().get_command_obj('test')
+ ts5.ensure_finalized()
+ assert ts5.test_suite is None
@pytest.fixture
diff --git a/setuptools/tests/test_test.py b/setuptools/tests/test_test.py
index 7ea43c57..960527bc 100644
--- a/setuptools/tests/test_test.py
+++ b/setuptools/tests/test_test.py
@@ -2,9 +2,9 @@
from __future__ import unicode_literals
+from distutils import log
import os
-import site
-from distutils.errors import DistutilsError
+import sys
import pytest
@@ -66,26 +66,66 @@ def sample_test(tmpdir_cwd):
f.write(TEST_PY)
-@pytest.mark.skipif('hasattr(sys, "real_prefix")')
-@pytest.mark.usefixtures('user_override')
-@pytest.mark.usefixtures('sample_test')
-class TestTestTest:
- def test_test(self):
- params = dict(
- name='foo',
- packages=['name', 'name.space', 'name.space.tests'],
- namespace_packages=['name'],
- test_suite='name.space.tests.test_suite',
- use_2to3=True,
- )
- dist = Distribution(params)
- dist.script_name = 'setup.py'
- cmd = test(dist)
- cmd.user = 1
- cmd.ensure_finalized()
- cmd.install_dir = site.USER_SITE
- cmd.user = 1
- with contexts.quiet():
- # The test runner calls sys.exit
- with contexts.suppress_exceptions(SystemExit):
- cmd.run()
+@pytest.fixture
+def quiet_log():
+ # Running some of the other tests will automatically
+ # change the log level to info, messing our output.
+ log.set_verbosity(0)
+
+
+@pytest.mark.usefixtures('sample_test', 'quiet_log')
+def test_test(capfd):
+ params = dict(
+ name='foo',
+ packages=['name', 'name.space', 'name.space.tests'],
+ namespace_packages=['name'],
+ test_suite='name.space.tests.test_suite',
+ use_2to3=True,
+ )
+ dist = Distribution(params)
+ dist.script_name = 'setup.py'
+ cmd = test(dist)
+ cmd.ensure_finalized()
+ # The test runner calls sys.exit
+ with contexts.suppress_exceptions(SystemExit):
+ cmd.run()
+ out, err = capfd.readouterr()
+ assert out == 'Foo\n'
+
+
+@pytest.mark.xfail(
+ sys.version_info < (2, 7),
+ reason="No discover support for unittest on Python 2.6",
+)
+@pytest.mark.usefixtures('tmpdir_cwd', 'quiet_log')
+def test_tests_are_run_once(capfd):
+ params = dict(
+ name='foo',
+ packages=['dummy'],
+ )
+ with open('setup.py', 'wt') as f:
+ f.write('from setuptools import setup; setup(\n')
+ for k, v in sorted(params.items()):
+ f.write(' %s=%r,\n' % (k, v))
+ f.write(')\n')
+ os.makedirs('dummy')
+ with open('dummy/__init__.py', 'wt'):
+ pass
+ with open('dummy/test_dummy.py', 'wt') as f:
+ f.write(DALS(
+ """
+ from __future__ import print_function
+ import unittest
+ class TestTest(unittest.TestCase):
+ def test_test(self):
+ print('Foo')
+ """))
+ dist = Distribution(params)
+ dist.script_name = 'setup.py'
+ cmd = test(dist)
+ cmd.ensure_finalized()
+ # The test runner calls sys.exit
+ with contexts.suppress_exceptions(SystemExit):
+ cmd.run()
+ out, err = capfd.readouterr()
+ assert out == 'Foo\n'
diff --git a/setuptools/tests/test_virtualenv.py b/setuptools/tests/test_virtualenv.py
index 17b8793c..9dbd3c86 100644
--- a/setuptools/tests/test_virtualenv.py
+++ b/setuptools/tests/test_virtualenv.py
@@ -1,5 +1,6 @@
import glob
import os
+import sys
from pytest import yield_fixture
from pytest_fixture_config import yield_requires_config
@@ -39,6 +40,9 @@ def test_pip_upgrade_from_source(virtualenv):
Check pip can upgrade setuptools from source.
"""
dist_dir = virtualenv.workspace
+ if sys.version_info < (2, 7):
+ # Python 2.6 support was dropped in wheel 0.30.0.
+ virtualenv.run('pip install -U "wheel<0.30.0"')
# Generate source distribution / wheel.
virtualenv.run(' && '.join((
'cd {source}',
diff --git a/setuptools/tests/text.py b/setuptools/tests/text.py
new file mode 100644
index 00000000..ad2c6249
--- /dev/null
+++ b/setuptools/tests/text.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import unicode_literals
+
+
+class Filenames:
+ unicode = 'smörbröd.py'
+ latin_1 = unicode.encode('latin-1')
+ utf_8 = unicode.encode('utf-8')