Commit 76fefe98 authored by Jason R. Coombs's avatar Jason R. Coombs

Merge branch 'master' into pr1145

parents a5add17b ff9bc490
v36.5.1 v36.6.1
------- -------
* #170: When working with Mercurial checkouts, use Windows-friendly * #1132: Removed redundant and costly serialization/parsing step
syntax for suppressing output. in ``EntryPoint.__init__``.
v36.6.0
-------
* #1143: Added ``setuptools.build_meta`` module, an implementation
of PEP-517 for Setuptools-defined packages.
* #1143: Added ``dist_info`` command for producing dist_info
metadata.
v36.5.0 v36.5.0
------- -------
* #170: When working with Mercurial checkouts, use Windows-friendly
syntax for suppressing output.
* Inspired by #1134, performed substantial refactoring of * Inspired by #1134, performed substantial refactoring of
``pkg_resources.find_on_path`` to facilitate an optimization ``pkg_resources.find_on_path`` to facilitate an optimization
for paths with many non-version entries. for paths with many non-version entries.
......
...@@ -480,8 +480,10 @@ def get_build_platform(): ...@@ -480,8 +480,10 @@ def get_build_platform():
try: try:
version = _macosx_vers() version = _macosx_vers()
machine = os.uname()[4].replace(" ", "_") machine = os.uname()[4].replace(" ", "_")
return "macosx-%d.%d-%s" % (int(version[0]), int(version[1]), return "macosx-%d.%d-%s" % (
_macosx_arch(machine)) int(version[0]), int(version[1]),
_macosx_arch(machine),
)
except ValueError: except ValueError:
# if someone is running a non-Mac darwin system, this will fall # if someone is running a non-Mac darwin system, this will fall
# through to the default implementation # through to the default implementation
...@@ -806,7 +808,8 @@ class WorkingSet(object): ...@@ -806,7 +808,8 @@ class WorkingSet(object):
already-installed distribution; it should return a ``Distribution`` or already-installed distribution; it should return a ``Distribution`` or
``None``. ``None``.
Unless `replace_conflicting=True`, raises a VersionConflict exception if Unless `replace_conflicting=True`, raises a VersionConflict exception
if
any requirements are found on the path that have the correct name but any requirements are found on the path that have the correct name but
the wrong version. Otherwise, if an `installer` is supplied it will be the wrong version. Otherwise, if an `installer` is supplied it will be
invoked to obtain the correct version of the requirement and activate invoked to obtain the correct version of the requirement and activate
...@@ -885,8 +888,8 @@ class WorkingSet(object): ...@@ -885,8 +888,8 @@ class WorkingSet(object):
# return list of distros to activate # return list of distros to activate
return to_activate return to_activate
def find_plugins(self, plugin_env, full_env=None, installer=None, def find_plugins(
fallback=True): self, plugin_env, full_env=None, installer=None, fallback=True):
"""Find all activatable distributions in `plugin_env` """Find all activatable distributions in `plugin_env`
Example usage:: Example usage::
...@@ -1040,7 +1043,8 @@ class _ReqExtras(dict): ...@@ -1040,7 +1043,8 @@ class _ReqExtras(dict):
class Environment(object): class Environment(object):
"""Searchable snapshot of distributions on a search path""" """Searchable snapshot of distributions on a search path"""
def __init__(self, search_path=None, platform=get_supported_platform(), def __init__(
self, search_path=None, platform=get_supported_platform(),
python=PY_MAJOR): python=PY_MAJOR):
"""Snapshot distributions available on a search path """Snapshot distributions available on a search path
...@@ -1113,7 +1117,8 @@ class Environment(object): ...@@ -1113,7 +1117,8 @@ class Environment(object):
dists.append(dist) dists.append(dist)
dists.sort(key=operator.attrgetter('hashcmp'), reverse=True) dists.sort(key=operator.attrgetter('hashcmp'), reverse=True)
def best_match(self, req, working_set, installer=None, replace_conflicting=False): def best_match(
self, req, working_set, installer=None, replace_conflicting=False):
"""Find distribution best matching `req` and usable on `working_set` """Find distribution best matching `req` and usable on `working_set`
This calls the ``find(req)`` method of the `working_set` to see if a This calls the ``find(req)`` method of the `working_set` to see if a
...@@ -1248,8 +1253,8 @@ class ResourceManager: ...@@ -1248,8 +1253,8 @@ class ResourceManager:
tmpl = textwrap.dedent(""" tmpl = textwrap.dedent("""
Can't extract file(s) to egg cache Can't extract file(s) to egg cache
The following error occurred while trying to extract file(s) to the Python egg The following error occurred while trying to extract file(s)
cache: to the Python egg cache:
{old_exc} {old_exc}
...@@ -1257,9 +1262,9 @@ class ResourceManager: ...@@ -1257,9 +1262,9 @@ class ResourceManager:
{cache_path} {cache_path}
Perhaps your account does not have write access to this directory? You can Perhaps your account does not have write access to this directory?
change the cache directory by setting the PYTHON_EGG_CACHE environment You can change the cache directory by setting the PYTHON_EGG_CACHE
variable to point to an accessible directory. environment variable to point to an accessible directory.
""").lstrip() """).lstrip()
err = ExtractionError(tmpl.format(**locals())) err = ExtractionError(tmpl.format(**locals()))
err.manager = self err.manager = self
...@@ -1309,11 +1314,13 @@ class ResourceManager: ...@@ -1309,11 +1314,13 @@ class ResourceManager:
return return
mode = os.stat(path).st_mode mode = os.stat(path).st_mode
if mode & stat.S_IWOTH or mode & stat.S_IWGRP: if mode & stat.S_IWOTH or mode & stat.S_IWGRP:
msg = ("%s is writable by group/others and vulnerable to attack " msg = (
"%s is writable by group/others and vulnerable to attack "
"when " "when "
"used with get_resource_filename. Consider a more secure " "used with get_resource_filename. Consider a more secure "
"location (set with .set_extraction_path or the " "location (set with .set_extraction_path or the "
"PYTHON_EGG_CACHE environment variable)." % path) "PYTHON_EGG_CACHE environment variable)." % path
)
warnings.warn(msg, UserWarning) warnings.warn(msg, UserWarning)
def postprocess(self, tempname, filename): def postprocess(self, tempname, filename):
...@@ -1597,8 +1604,11 @@ class DefaultProvider(EggProvider): ...@@ -1597,8 +1604,11 @@ class DefaultProvider(EggProvider):
@classmethod @classmethod
def _register(cls): def _register(cls):
loader_cls = getattr(importlib_machinery, 'SourceFileLoader', loader_cls = getattr(
type(None)) importlib_machinery,
'SourceFileLoader',
type(None),
)
register_loader_type(loader_cls, cls) register_loader_type(loader_cls, cls)
...@@ -1766,7 +1776,10 @@ class ZipProvider(EggProvider): ...@@ -1766,7 +1776,10 @@ class ZipProvider(EggProvider):
if self._is_current(real_path, zip_path): if self._is_current(real_path, zip_path):
return real_path return real_path
outf, tmpnam = _mkstemp(".$extract", dir=os.path.dirname(real_path)) outf, tmpnam = _mkstemp(
".$extract",
dir=os.path.dirname(real_path),
)
os.write(outf, self.loader.get_data(zip_path)) os.write(outf, self.loader.get_data(zip_path))
os.close(outf) os.close(outf)
utime(tmpnam, (timestamp, timestamp)) utime(tmpnam, (timestamp, timestamp))
...@@ -1972,7 +1985,8 @@ def find_eggs_in_zip(importer, path_item, only=False): ...@@ -1972,7 +1985,8 @@ def find_eggs_in_zip(importer, path_item, only=False):
for subitem in metadata.resource_listdir('/'): for subitem in metadata.resource_listdir('/'):
if _is_egg_path(subitem): if _is_egg_path(subitem):
subpath = os.path.join(path_item, subitem) subpath = os.path.join(path_item, subitem)
for dist in find_eggs_in_zip(zipimport.zipimporter(subpath), subpath): dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath)
for dist in dists:
yield dist yield dist
elif subitem.lower().endswith('.dist-info'): elif subitem.lower().endswith('.dist-info'):
subpath = os.path.join(path_item, subitem) subpath = os.path.join(path_item, subitem)
...@@ -1981,7 +1995,6 @@ def find_eggs_in_zip(importer, path_item, only=False): ...@@ -1981,7 +1995,6 @@ def find_eggs_in_zip(importer, path_item, only=False):
yield Distribution.from_location(path_item, subitem, submeta) yield Distribution.from_location(path_item, subitem, submeta)
register_finder(zipimport.zipimporter, find_eggs_in_zip) register_finder(zipimport.zipimporter, find_eggs_in_zip)
...@@ -2124,7 +2137,11 @@ def non_empty_lines(path): ...@@ -2124,7 +2137,11 @@ def non_empty_lines(path):
""" """
Yield non-empty lines from file at path Yield non-empty lines from file at path
""" """
return (line.rstrip() for line in open(path) if line.strip()) with open(path) as f:
for line in f:
line = line.strip()
if line:
yield line
def resolve_egg_link(path): def resolve_egg_link(path):
...@@ -2375,7 +2392,7 @@ class EntryPoint(object): ...@@ -2375,7 +2392,7 @@ class EntryPoint(object):
self.name = name self.name = name
self.module_name = module_name self.module_name = module_name
self.attrs = tuple(attrs) self.attrs = tuple(attrs)
self.extras = Requirement.parse(("x[%s]" % ','.join(extras))).extras self.extras = tuple(extras)
self.dist = dist self.dist = dist
def __str__(self): def __str__(self):
...@@ -2523,7 +2540,8 @@ class Distribution(object): ...@@ -2523,7 +2540,8 @@ class Distribution(object):
"""Wrap an actual or potential sys.path entry w/metadata""" """Wrap an actual or potential sys.path entry w/metadata"""
PKG_INFO = 'PKG-INFO' PKG_INFO = 'PKG-INFO'
def __init__(self, location=None, metadata=None, project_name=None, def __init__(
self, location=None, metadata=None, project_name=None,
version=None, py_version=PY_MAJOR, platform=None, version=None, py_version=PY_MAJOR, platform=None,
precedence=EGG_DIST): precedence=EGG_DIST):
self.project_name = safe_name(project_name or 'Unknown') self.project_name = safe_name(project_name or 'Unknown')
...@@ -2799,7 +2817,8 @@ class Distribution(object): ...@@ -2799,7 +2817,8 @@ class Distribution(object):
if replace: if replace:
break break
else: else:
# don't modify path (even removing duplicates) if found and not replace # don't modify path (even removing duplicates) if
# found and not replace
return return
elif item == bdir and self.precedence == EGG_DIST: elif item == bdir and self.precedence == EGG_DIST:
# if it's an .egg, give it precedence over its directory # if it's an .egg, give it precedence over its directory
...@@ -2896,7 +2915,10 @@ class EggInfoDistribution(Distribution): ...@@ -2896,7 +2915,10 @@ class EggInfoDistribution(Distribution):
class DistInfoDistribution(Distribution): class DistInfoDistribution(Distribution):
"""Wrap an actual or potential sys.path entry w/metadata, .dist-info style""" """
Wrap an actual or potential sys.path entry
w/metadata, .dist-info style.
"""
PKG_INFO = 'METADATA' PKG_INFO = 'METADATA'
EQEQ = re.compile(r"([\(,])\s*(\d.*?)\s*([,\)])") EQEQ = re.compile(r"([\(,])\s*(\d.*?)\s*([,\)])")
...@@ -2946,7 +2968,7 @@ _distributionImpl = { ...@@ -2946,7 +2968,7 @@ _distributionImpl = {
'.egg': Distribution, '.egg': Distribution,
'.egg-info': EggInfoDistribution, '.egg-info': EggInfoDistribution,
'.dist-info': DistInfoDistribution, '.dist-info': DistInfoDistribution,
} }
def issue_warning(*args, **kw): def issue_warning(*args, **kw):
...@@ -3031,7 +3053,8 @@ class Requirement(packaging.requirements.Requirement): ...@@ -3031,7 +3053,8 @@ class Requirement(packaging.requirements.Requirement):
def __hash__(self): def __hash__(self):
return self.__hash return self.__hash
def __repr__(self): return "Requirement.parse(%r)" % str(self) def __repr__(self):
return "Requirement.parse(%r)" % str(self)
@staticmethod @staticmethod
def parse(s): def parse(s):
...@@ -3165,7 +3188,10 @@ def _initialize_master_working_set(): ...@@ -3165,7 +3188,10 @@ def _initialize_master_working_set():
dist.activate(replace=False) dist.activate(replace=False)
for dist in working_set for dist in working_set
) )
add_activation_listener(lambda dist: dist.activate(replace=True), existing=False) add_activation_listener(
lambda dist: dist.activate(replace=True),
existing=False,
)
working_set.entries = [] working_set.entries = []
# match order # match order
list(map(working_set.add_entry, sys.path)) list(map(working_set.add_entry, sys.path))
......
...@@ -92,8 +92,8 @@ class TestZipProvider(object): ...@@ -92,8 +92,8 @@ class TestZipProvider(object):
ts = timestamp(self.ref_time) ts = timestamp(self.ref_time)
os.utime(filename, (ts, ts)) os.utime(filename, (ts, ts))
filename = zp.get_resource_filename(manager, 'data.dat') filename = zp.get_resource_filename(manager, 'data.dat')
f = open(filename) with open(filename) as f:
assert f.read() == 'hello, world!' assert f.read() == 'hello, world!'
manager.cleanup_resources() manager.cleanup_resources()
......
[bumpversion] [bumpversion]
current_version = 36.4.0 current_version = 36.6.0
commit = True commit = True
tag = True tag = True
...@@ -22,5 +22,7 @@ formats = zip ...@@ -22,5 +22,7 @@ formats = zip
[bdist_wheel] [bdist_wheel]
universal = 1 universal = 1
[bumpversion:file:setup.py] [metadata]
license_file = LICENSE
[bumpversion:file:setup.py]
...@@ -89,7 +89,7 @@ def pypi_link(pkg_filename): ...@@ -89,7 +89,7 @@ def pypi_link(pkg_filename):
setup_params = dict( setup_params = dict(
name="setuptools", name="setuptools",
version="36.4.0", version="36.6.0",
description="Easily download, build, install, upgrade, and uninstall " description="Easily download, build, install, upgrade, and uninstall "
"Python packages", "Python packages",
author="Python Packaging Authority", author="Python Packaging Authority",
......
"""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
f = getattr(tokenize, 'open', open)(__file__)
code = f.read().replace('\\r\\n', '\\n')
f.close()
exec(compile(code, __file__, 'exec'))
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_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_infos = [f for f in os.listdir(metadata_directory)
if f.endswith('.dist-info')]
assert len(dist_infos) == 1
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]
...@@ -3,6 +3,7 @@ __all__ = [ ...@@ -3,6 +3,7 @@ __all__ = [
'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts', 'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts',
'sdist', 'setopt', 'test', 'install_egg_info', 'install_scripts', 'sdist', 'setopt', 'test', 'install_egg_info', 'install_scripts',
'register', 'bdist_wininst', 'upload_docs', 'upload', 'build_clib', 'register', 'bdist_wininst', 'upload_docs', 'upload', 'build_clib',
'dist_info',
] ]
from distutils.command.bdist import bdist from distutils.command.bdist import bdist
......
...@@ -95,7 +95,9 @@ class develop(namespaces.DevelopInstaller, easy_install): ...@@ -95,7 +95,9 @@ class develop(namespaces.DevelopInstaller, easy_install):
path_to_setup = egg_base.replace(os.sep, '/').rstrip('/') path_to_setup = egg_base.replace(os.sep, '/').rstrip('/')
if path_to_setup != os.curdir: if path_to_setup != os.curdir:
path_to_setup = '../' * (path_to_setup.count('/') + 1) 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): if resolved != normalize_path(os.curdir):
raise DistutilsOptionError( raise DistutilsOptionError(
"Can't get a consistent path to setup script from" "Can't get a consistent path to setup script from"
......
"""
Create a dist_info directory
As defined in the wheel specification
"""
import os
import shutil
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.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)
if self.egg_base:
destination = os.path.join(self.egg_base, dist_info_dir)
log.info("creating '{}'".format(os.path.abspath(destination)))
shutil.move(dist_info_dir, destination)
...@@ -1817,7 +1817,7 @@ def _update_zipimporter_cache(normalized_path, cache, updater=None): ...@@ -1817,7 +1817,7 @@ def _update_zipimporter_cache(normalized_path, cache, updater=None):
# get/del patterns instead. For more detailed information see the # get/del patterns instead. For more detailed information see the
# following links: # following links:
# https://github.com/pypa/setuptools/issues/202#issuecomment-202913420 # 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] old_entry = cache[p]
del cache[p] del cache[p]
new_entry = updater and updater(p, old_entry) new_entry = updater and updater(p, old_entry)
......
"""Tests for the 'setuptools' package"""
import locale 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 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' is_ascii = locale.getpreferredencoding() == 'ANSI_X3.4-1968'
fail_on_ascii = pytest.mark.xfail(is_ascii, reason="Test fails in this locale") 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
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)
@pytest.fixture
def build_backend(tmpdir):
defn = {
'setup.py': DALS("""
__import__('setuptools').setup(
name='foo',
py_modules=['hello'],
setup_requires=['six'],
)
"""),
'hello.py': DALS("""
def run():
print('hello')
"""),
}
build_files(defn, 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'))
...@@ -167,7 +167,9 @@ class TestNamespaces: ...@@ -167,7 +167,9 @@ class TestNamespaces:
target = tmpdir / 'packages' target = tmpdir / 'packages'
# use pip to install to the target directory # use pip to install to the target directory
install_cmd = [ install_cmd = [
'pip', sys.executable,
'-m',
'pip.__main__',
'install', 'install',
str(pkg_A), str(pkg_A),
'-t', str(target), '-t', str(target),
......
...@@ -164,7 +164,8 @@ class TestEggInfo(object): ...@@ -164,7 +164,8 @@ class TestEggInfo(object):
self._run_install_command(tmpdir_cwd, env) self._run_install_command(tmpdir_cwd, env)
egg_info_dir = self._find_egg_info_files(env.paths['lib']).base egg_info_dir = self._find_egg_info_files(env.paths['lib']).base
sources_txt = os.path.join(egg_info_dir, 'SOURCES.txt') 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): def _setup_script_with_requires(self, requires, use_setup_cfg=False):
setup_script = DALS( setup_script = DALS(
...@@ -447,7 +448,8 @@ class TestEggInfo(object): ...@@ -447,7 +448,8 @@ class TestEggInfo(object):
self._run_install_command(tmpdir_cwd, env) self._run_install_command(tmpdir_cwd, env)
egg_info_dir = self._find_egg_info_files(env.paths['lib']).base egg_info_dir = self._find_egg_info_files(env.paths['lib']).base
pkginfo = os.path.join(egg_info_dir, 'PKG-INFO') 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): def test_manifest_maker_warning_suppression(self):
fixtures = [ fixtures = [
......
...@@ -30,7 +30,9 @@ class TestNamespaces: ...@@ -30,7 +30,9 @@ class TestNamespaces:
targets = site_packages, path_packages targets = site_packages, path_packages
# use pip to install to the target directory # use pip to install to the target directory
install_cmd = [ install_cmd = [
'pip', sys.executable,
'-m',
'pip.__main__',
'install', 'install',
str(pkg_A), str(pkg_A),
'-t', str(site_packages), '-t', str(site_packages),
...@@ -38,7 +40,9 @@ class TestNamespaces: ...@@ -38,7 +40,9 @@ class TestNamespaces:
subprocess.check_call(install_cmd) subprocess.check_call(install_cmd)
namespaces.make_site_dir(site_packages) namespaces.make_site_dir(site_packages)
install_cmd = [ install_cmd = [
'pip', sys.executable,
'-m',
'pip.__main__',
'install', 'install',
str(pkg_B), str(pkg_B),
'-t', str(path_packages), '-t', str(path_packages),
...@@ -88,7 +92,9 @@ class TestNamespaces: ...@@ -88,7 +92,9 @@ class TestNamespaces:
target = tmpdir / 'packages' target = tmpdir / 'packages'
# use pip to install to the target directory # use pip to install to the target directory
install_cmd = [ install_cmd = [
'pip', sys.executable,
'-m',
'pip.__main__',
'install', 'install',
str(pkg_A), str(pkg_A),
'-t', str(target), '-t', str(target),
......
...@@ -19,6 +19,7 @@ from setuptools.command.sdist import sdist ...@@ -19,6 +19,7 @@ from setuptools.command.sdist import sdist
from setuptools.command.egg_info import manifest_maker from setuptools.command.egg_info import manifest_maker
from setuptools.dist import Distribution from setuptools.dist import Distribution
from setuptools.tests import fail_on_ascii 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") py3_only = pytest.mark.xfail(six.PY2, reason="Test runs on Python 3 only")
...@@ -36,13 +37,7 @@ from setuptools import setup ...@@ -36,13 +37,7 @@ from setuptools import setup
setup(**%r) setup(**%r)
""" % SETUP_ATTRS """ % 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 @contextlib.contextmanager
def quiet(): def quiet():
old_stdout, old_stderr = sys.stdout, sys.stderr old_stdout, old_stderr = sys.stdout, sys.stderr
...@@ -53,17 +48,10 @@ def quiet(): ...@@ -53,17 +48,10 @@ def quiet():
sys.stdout, sys.stderr = old_stdout, old_stderr 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 # Convert to POSIX path
def posix(path): def posix(path):
if six.PY3 and not isinstance(path, str): 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: else:
return path.replace(os.sep, '/') return path.replace(os.sep, '/')
...@@ -86,6 +74,21 @@ def read_all_bytes(filename): ...@@ -86,6 +74,21 @@ def read_all_bytes(filename):
return fp.read() 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: class TestSdistTest:
def setup_method(self, method): def setup_method(self, method):
self.temp_dir = tempfile.mkdtemp() self.temp_dir = tempfile.mkdtemp()
...@@ -134,8 +137,8 @@ class TestSdistTest: ...@@ -134,8 +137,8 @@ class TestSdistTest:
def test_defaults_case_sensitivity(self): def test_defaults_case_sensitivity(self):
""" """
Make sure default files (README.*, etc.) are added in a case-sensitive Make sure default files (README.*, etc.) are added in a case-sensitive
way to avoid problems with packages built on Windows. way to avoid problems with packages built on Windows.
""" """
open(os.path.join(self.temp_dir, 'readme.rst'), 'w').close() open(os.path.join(self.temp_dir, 'readme.rst'), 'w').close()
...@@ -152,7 +155,9 @@ class TestSdistTest: ...@@ -152,7 +155,9 @@ class TestSdistTest:
with quiet(): with quiet():
cmd.run() 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) manifest = map(lambda x: x.lower(), cmd.filelist.files)
assert 'readme.rst' not in manifest, manifest assert 'readme.rst' not in manifest, manifest
assert 'setup.py' not in manifest, manifest assert 'setup.py' not in manifest, manifest
...@@ -201,8 +206,7 @@ class TestSdistTest: ...@@ -201,8 +206,7 @@ class TestSdistTest:
mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
os.mkdir('sdist_test.egg-info') os.mkdir('sdist_test.egg-info')
# UTF-8 filename filename = os.path.join(b'sdist_test', Filenames.utf_8)
filename = os.path.join(b('sdist_test'), b('smörbröd.py'))
# Must touch the file or risk removal # Must touch the file or risk removal
open(filename, "w").close() open(filename, "w").close()
...@@ -241,7 +245,7 @@ class TestSdistTest: ...@@ -241,7 +245,7 @@ class TestSdistTest:
os.mkdir('sdist_test.egg-info') os.mkdir('sdist_test.egg-info')
# Latin-1 filename # 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 # Add filename with surrogates and write manifest
with quiet(): with quiet():
...@@ -275,10 +279,10 @@ class TestSdistTest: ...@@ -275,10 +279,10 @@ class TestSdistTest:
cmd.run() cmd.run()
# Add UTF-8 filename to manifest # 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') cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
manifest = open(cmd.manifest, 'ab') manifest = open(cmd.manifest, 'ab')
manifest.write(b('\n') + filename) manifest.write(b'\n' + filename)
manifest.close() manifest.close()
# The file must exist to be included in the filelist # The file must exist to be included in the filelist
...@@ -295,6 +299,7 @@ class TestSdistTest: ...@@ -295,6 +299,7 @@ class TestSdistTest:
assert filename in cmd.filelist.files assert filename in cmd.filelist.files
@py3_only @py3_only
@fail_on_latin1_encoded_filenames
def test_read_manifest_skips_non_utf8_filenames(self): def test_read_manifest_skips_non_utf8_filenames(self):
# Test for #303. # Test for #303.
dist = Distribution(SETUP_ATTRS) dist = Distribution(SETUP_ATTRS)
...@@ -307,10 +312,10 @@ class TestSdistTest: ...@@ -307,10 +312,10 @@ class TestSdistTest:
cmd.run() cmd.run()
# Add Latin-1 filename to manifest # 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') cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
manifest = open(cmd.manifest, 'ab') manifest = open(cmd.manifest, 'ab')
manifest.write(b('\n') + filename) manifest.write(b'\n' + filename)
manifest.close() manifest.close()
# The file must exist to be included in the filelist # The file must exist to be included in the filelist
...@@ -326,6 +331,7 @@ class TestSdistTest: ...@@ -326,6 +331,7 @@ class TestSdistTest:
assert filename not in cmd.filelist.files assert filename not in cmd.filelist.files
@fail_on_ascii @fail_on_ascii
@fail_on_latin1_encoded_filenames
def test_sdist_with_utf8_encoded_filename(self): def test_sdist_with_utf8_encoded_filename(self):
# Test for #303. # Test for #303.
dist = Distribution(SETUP_ATTRS) dist = Distribution(SETUP_ATTRS)
...@@ -333,8 +339,7 @@ class TestSdistTest: ...@@ -333,8 +339,7 @@ class TestSdistTest:
cmd = sdist(dist) cmd = sdist(dist)
cmd.ensure_finalized() cmd.ensure_finalized()
# UTF-8 filename filename = os.path.join(b'sdist_test', Filenames.utf_8)
filename = os.path.join(b('sdist_test'), b('smörbröd.py'))
open(filename, 'w').close() open(filename, 'w').close()
with quiet(): with quiet():
...@@ -360,6 +365,7 @@ class TestSdistTest: ...@@ -360,6 +365,7 @@ class TestSdistTest:
else: else:
assert filename in cmd.filelist.files assert filename in cmd.filelist.files
@fail_on_latin1_encoded_filenames
def test_sdist_with_latin1_encoded_filename(self): def test_sdist_with_latin1_encoded_filename(self):
# Test for #303. # Test for #303.
dist = Distribution(SETUP_ATTRS) dist = Distribution(SETUP_ATTRS)
...@@ -368,7 +374,7 @@ class TestSdistTest: ...@@ -368,7 +374,7 @@ class TestSdistTest:
cmd.ensure_finalized() cmd.ensure_finalized()
# Latin-1 filename # 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() open(filename, 'w').close()
assert os.path.isfile(filename) assert os.path.isfile(filename)
...@@ -381,10 +387,9 @@ class TestSdistTest: ...@@ -381,10 +387,9 @@ class TestSdistTest:
# Latin-1 is similar to Windows-1252 however # Latin-1 is similar to Windows-1252 however
# on mbcs filesys it is not in latin-1 encoding # on mbcs filesys it is not in latin-1 encoding
fs_enc = sys.getfilesystemencoding() fs_enc = sys.getfilesystemencoding()
if fs_enc == 'mbcs': if fs_enc != 'mbcs':
filename = filename.decode('mbcs') fs_enc = 'latin-1'
else: filename = filename.decode(fs_enc)
filename = filename.decode('latin-1')
assert filename in cmd.filelist.files assert filename in cmd.filelist.files
else: else:
......
"""Tests for the 'setuptools' package"""
import sys
import os 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 pytest
import setuptools 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 @pytest.fixture
......
# -*- 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')
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment