Commit fcdf12ee authored by Jason R. Coombs's avatar Jason R. Coombs

Merge branch 'master' into feature/re-vendor-sadface

parents 3d0cc355 4dc2c76b
......@@ -13,3 +13,4 @@ setuptools.egg-info
*.swp
*~
.hg*
.cache
......@@ -15,6 +15,7 @@ matrix:
- python: 2.7
env: LC_ALL=C LC_CTYPE=C
script:
# need tox to get started
- pip install tox
# Output the env, to verify behavior
......
v35.0.2
-------
* #1015: Fix test failures on Python 3.7.
* #1024: Add workaround for Jython #2581 in monkey module.
v35.0.1
-------
* #992: Revert change introduced in v34.4.1, now
considered invalid.
* #1016: Revert change introduced in v35.0.0 per #1014,
referencing #436. The approach had unintended
consequences, causing sdist installs to be missing
files.
v35.0.0
-------
* #436: In egg_info.manifest_maker, no longer read
the file list from the manifest file, and instead
re-build it on each build. In this way, files removed
from the specification will not linger in the manifest.
As a result, any files manually added to the manifest
will be removed on subsequent egg_info invocations.
No projects should be manually adding files to the
manifest and should instead use MANIFEST.in or SCM
file finders to force inclusion of files in the manifest.
v34.4.1
-------
* #1008: In MSVC support, use always the last version available for Windows SDK and UCRT SDK.
* #1008: In MSVC support, fix "vcruntime140.dll" returned path with Visual Studio 2017.
* #992: In msvc.msvc9_query_vcvarsall, ensure the
returned dicts have str values and not Unicode for
compatibilty with os.environ.
v34.4.0
-------
* #995: In MSVC support, add support for "Microsoft Visual Studio 2017" and "Microsoft Visual Studio Build Tools 2017".
* #999 via #1007: Extend support for declarative package
config in a setup.cfg file to include the options
``python_requires`` and ``py_modules``.
v34.3.3
-------
* #967 (and #997): Explicitly import submodules of
packaging to account for environments where the imports
of those submodules is not implied by other behavior.
v34.3.2
-------
* #993: Fix documentation upload by correcting
rendering of content-type in _build_multipart
on Python 3.
v34.3.1
-------
* #988: Trap ``os.unlink`` same as ``os.remove`` in
``auto_chmod`` error handler.
* #983: Fixes to invalid escape sequence deprecations on
Python 3.6.
v34.3.0
-------
......
......@@ -18,18 +18,23 @@
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
# Allow Sphinx to find the setup command that is imported below, as referenced above.
import os
import subprocess
import sys
sys.path.append(os.path.abspath('..'))
import os
import setup as setup_script
# hack to run the bootstrap script so that jaraco.packaging.sphinx
# can invoke setup.py
'READTHEDOCS' in os.environ and subprocess.check_call(
[sys.executable, 'bootstrap.py'],
cwd=os.path.join(os.path.dirname(__file__), os.path.pardir),
)
# -- General configuration -----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['rst.linker', 'sphinx.ext.autosectionlabel']
extensions = ['jaraco.packaging.sphinx', 'rst.linker', 'sphinx.ext.autosectionlabel']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
......@@ -40,19 +45,6 @@ source_suffix = '.txt'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'Setuptools'
copyright = '2009-2014, The fellowship of the packaging'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = setup_script.setup_params['version']
# The full version, including alpha/beta/rc tags.
release = setup_script.setup_params['version']
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = []
......@@ -69,13 +61,6 @@ html_theme = 'nature'
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_theme']
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
html_title = "Setuptools documentation"
# A shorter title for the navigation bar. Default is the same as html_title.
html_short_title = "Setuptools"
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
html_use_smartypants = True
......@@ -89,9 +74,6 @@ html_use_modindex = False
# If false, no index is generated.
html_use_index = False
# Output file base name for HTML help builder.
htmlhelp_basename = 'Setuptoolsdoc'
# -- Options for LaTeX output --------------------------------------------------
# Grouping the document tree into LaTeX files. List of tuples
......@@ -109,60 +91,60 @@ link_files = {
),
replace=[
dict(
pattern=r"(Issue )?#(?P<issue>\d+)",
url='{GH}/pypa/setuptools/issues/{issue}',
pattern=r'(Issue )?#(?P<issue>\d+)',
url='{package_url}/issues/{issue}',
),
dict(
pattern=r"BB Pull Request ?#(?P<bb_pull_request>\d+)",
pattern=r'BB Pull Request ?#(?P<bb_pull_request>\d+)',
url='{BB}/pypa/setuptools/pull-request/{bb_pull_request}',
),
dict(
pattern=r"Distribute #(?P<distribute>\d+)",
pattern=r'Distribute #(?P<distribute>\d+)',
url='{BB}/tarek/distribute/issue/{distribute}',
),
dict(
pattern=r"Buildout #(?P<buildout>\d+)",
pattern=r'Buildout #(?P<buildout>\d+)',
url='{GH}/buildout/buildout/issues/{buildout}',
),
dict(
pattern=r"Old Setuptools #(?P<old_setuptools>\d+)",
pattern=r'Old Setuptools #(?P<old_setuptools>\d+)',
url='http://bugs.python.org/setuptools/issue{old_setuptools}',
),
dict(
pattern=r"Jython #(?P<jython>\d+)",
pattern=r'Jython #(?P<jython>\d+)',
url='http://bugs.jython.org/issue{jython}',
),
dict(
pattern=r"Python #(?P<python>\d+)",
pattern=r'Python #(?P<python>\d+)',
url='http://bugs.python.org/issue{python}',
),
dict(
pattern=r"Interop #(?P<interop>\d+)",
pattern=r'Interop #(?P<interop>\d+)',
url='{GH}/pypa/interoperability-peps/issues/{interop}',
),
dict(
pattern=r"Pip #(?P<pip>\d+)",
pattern=r'Pip #(?P<pip>\d+)',
url='{GH}/pypa/pip/issues/{pip}',
),
dict(
pattern=r"Packaging #(?P<packaging>\d+)",
pattern=r'Packaging #(?P<packaging>\d+)',
url='{GH}/pypa/packaging/issues/{packaging}',
),
dict(
pattern=r"[Pp]ackaging (?P<packaging_ver>\d+(\.\d+)+)",
pattern=r'[Pp]ackaging (?P<packaging_ver>\d+(\.\d+)+)',
url='{GH}/pypa/packaging/blob/{packaging_ver}/CHANGELOG.rst',
),
dict(
pattern=r"PEP[- ](?P<pep_number>\d+)",
pattern=r'PEP[- ](?P<pep_number>\d+)',
url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/',
),
dict(
pattern=r"setuptools_svn #(?P<setuptools_svn>\d+)",
pattern=r'setuptools_svn #(?P<setuptools_svn>\d+)',
url='{GH}/jaraco/setuptools_svn/issues/{setuptools_svn}',
),
dict(
pattern=r"^(?m)((?P<scm_version>v?\d+(\.\d+){1,2}))\n[-=]+\n",
with_scm="{text}\n{rev[timestamp]:%d %b %Y}\n",
pattern=r'^(?m)((?P<scm_version>v?\d+(\.\d+){1,2}))\n[-=]+\n',
with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n',
),
],
),
......
rst.linker>=1.6.1
sphinx>=1.4
sphinx
rst.linker>=1.9
jaraco.packaging>=3.2
setuptools>=34
......@@ -1176,6 +1176,8 @@ Distributing a ``setuptools``-based project
Using ``setuptools``... Without bundling it!
---------------------------------------------
.. warning:: **ez_setup** is deprecated in favor of PIP with **PEP-518** support.
Your users might not have ``setuptools`` installed on their machines, or even
if they do, it might not be the right version. Fixing this is easy; just
download `ez_setup.py`_, and put it in the same directory as your ``setup.py``
......@@ -2277,6 +2279,11 @@ New in 20.1: Added keyring support.
Configuring setup() using setup.cfg files
-----------------------------------------
.. note:: New in 30.3.0 (8 Dec 2016).
.. important:: ``setup.py`` with ``setup()`` function call is still required even
if your configuration resides in ``setup.cfg``.
``Setuptools`` allows using configuration files (usually `setup.cfg`)
to define package’s metadata and other options which are normally supplied
to ``setup()`` function.
......@@ -2425,6 +2432,7 @@ zip_safe bool
setup_requires list-semi
install_requires list-semi
extras_require section
python_requires str
entry_points file:, section
use_2to3 bool
use_2to3_fixers list-comma
......@@ -2440,6 +2448,7 @@ package_dir dict
package_data section
exclude_package_data section
namespace_packages list-comma
py_modules list-comma
======================= =====
.. note::
......
......@@ -593,7 +593,7 @@ class TestParsing:
[Requirement('Twis-Ted>=1.2-1')]
)
assert (
list(parse_requirements('Twisted >=1.2, \ # more\n<2.0'))
list(parse_requirements('Twisted >=1.2, \\ # more\n<2.0'))
==
[Requirement('Twisted>=1.2,<2.0')]
)
......
[bumpversion]
current_version = 34.3.0
current_version = 35.0.2
commit = True
tag = True
......
......@@ -15,8 +15,12 @@ here = os.path.dirname(__file__)
def require_metadata():
"Prevent improper installs without necessary metadata. See #659"
if not os.path.exists('setuptools.egg-info'):
msg = "Cannot build setuptools without metadata. Run bootstrap.py"
egg_info_dir = os.path.join(here, 'setuptools.egg-info')
if not os.path.exists(egg_info_dir):
msg = (
"Cannot build setuptools without metadata. "
"Run `bootstrap.py`."
)
raise RuntimeError(msg)
......@@ -85,7 +89,7 @@ def pypi_link(pkg_filename):
setup_params = dict(
name="setuptools",
version="34.3.0",
version="35.0.2",
description="Easily download, build, install, upgrade, and uninstall "
"Python packages",
author="Python Packaging Authority",
......
......@@ -474,8 +474,7 @@ class easy_install(Command):
else:
self.pth_file = None
PYTHONPATH = os.environ.get('PYTHONPATH', '').split(os.pathsep)
if instdir not in map(normalize_path, filter(None, PYTHONPATH)):
if instdir not in map(normalize_path, _pythonpath()):
# only PYTHONPATH dirs need a site.py, so pretend it's there
self.sitepy_installed = True
elif self.multi_version and not os.path.exists(pth_file):
......@@ -1348,10 +1347,21 @@ class easy_install(Command):
setattr(self, attr, val)
def _pythonpath():
items = os.environ.get('PYTHONPATH', '').split(os.pathsep)
return filter(None, items)
def get_site_dirs():
# return a list of 'site' dirs
sitedirs = [_f for _f in os.environ.get('PYTHONPATH',
'').split(os.pathsep) if _f]
"""
Return a list of 'site' dirs
"""
sitedirs = []
# start with PYTHONPATH
sitedirs.extend(_pythonpath())
prefixes = [sys.prefix]
if sys.exec_prefix != sys.prefix:
prefixes.append(sys.exec_prefix)
......@@ -1675,7 +1685,7 @@ def _first_line_re():
def auto_chmod(func, arg, exc):
if func is os.remove and os.name == 'nt':
if func in [os.unlink, os.remove] and os.name == 'nt':
chmod(arg, stat.S_IWRITE)
return func(arg)
et, ev, _ = sys.exc_info()
......@@ -2013,7 +2023,7 @@ class ScriptWriter(object):
gui apps.
"""
template = textwrap.dedent("""
template = textwrap.dedent(r"""
# EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r
__requires__ = %(spec)r
import re
......
......@@ -112,7 +112,8 @@ def translate_pattern(glob):
if not last_chunk:
pat += sep
return re.compile(pat + r'\Z(?ms)')
pat += r'\Z'
return re.compile(pat, flags=re.MULTILINE|re.DOTALL)
class egg_info(Command):
......
......@@ -67,7 +67,7 @@ class test(Command):
user_options = [
('test-module=', 'm', "Run 'test_suite' in specified module"),
('test-suite=', 's',
"Test suite to run (e.g. 'some_module.test_suite')"),
"Run single test, case or suite (e.g. 'module.test_suite')"),
('test-runner=', 'r', "Test runner to use"),
]
......
......@@ -77,9 +77,8 @@ class upload_docs(upload):
self.mkpath(self.target_dir) # just in case
for root, dirs, files in os.walk(self.target_dir):
if root == self.target_dir and not files:
raise DistutilsOptionError(
"no files found in upload directory '%s'"
% self.target_dir)
tmpl = "no files found in upload directory '%s'"
raise DistutilsOptionError(tmpl % self.target_dir)
for name in files:
full = os.path.join(root, name)
relative = root[len(self.target_dir):].lstrip(os.path.sep)
......@@ -138,7 +137,7 @@ class upload_docs(upload):
part_groups = map(builder, data.items())
parts = itertools.chain.from_iterable(part_groups)
body_items = itertools.chain(parts, end_items)
content_type = 'multipart/form-data; boundary=%s' % boundary
content_type = 'multipart/form-data; boundary=%s' % boundary.decode('ascii')
return b''.join(body_items), content_type
def upload_file(self, filename):
......@@ -159,8 +158,8 @@ class upload_docs(upload):
body, ct = self._build_multipart(data)
self.announce("Submitting documentation to %s" % (self.repository),
log.INFO)
msg = "Submitting documentation to %s" % (self.repository)
self.announce(msg, log.INFO)
# build the Request
# We can't use urllib2 since we need to send the Basic
......@@ -191,16 +190,16 @@ class upload_docs(upload):
r = conn.getresponse()
if r.status == 200:
self.announce('Server response (%s): %s' % (r.status, r.reason),
log.INFO)
msg = 'Server response (%s): %s' % (r.status, r.reason)
self.announce(msg, log.INFO)
elif r.status == 301:
location = r.getheader('Location')
if location is None:
location = 'https://pythonhosted.org/%s/' % meta.get_name()
self.announce('Upload successful. Visit %s' % location,
log.INFO)
msg = 'Upload successful. Visit %s' % location
self.announce(msg, log.INFO)
else:
self.announce('Upload failed (%s): %s' % (r.status, r.reason),
log.ERROR)
msg = 'Upload failed (%s): %s' % (r.status, r.reason)
self.announce(msg, log.ERROR)
if self.show_response:
print('-' * 75, r.read(), '-' * 75)
......@@ -462,6 +462,7 @@ class ConfigOptionsHandler(ConfigHandler):
'tests_require': parse_list_semicolon,
'packages': self._parse_packages,
'entry_points': self._parse_file,
'py_modules': parse_list,
}
def _parse_packages(self, value):
......
......@@ -16,6 +16,9 @@ from setuptools.extern import six
from setuptools.extern.six.moves import map
from pkg_resources.extern import packaging
__import__('pkg_resources.extern.packaging.specifiers')
__import__('pkg_resources.extern.packaging.version')
from setuptools.depends import Require
from setuptools import windows_support
from setuptools.monkey import get_unpatched
......@@ -165,7 +168,7 @@ def check_specifier(dist, attr, value):
packaging.specifiers.SpecifierSet(value)
except packaging.specifiers.InvalidSpecifier as error:
tmpl = (
"{attr!r} must be a string or list of strings "
"{attr!r} must be a string "
"containing valid version specifiers; {error}"
)
raise DistutilsSetupError(tmpl.format(attr=attr, error=error))
......@@ -352,6 +355,8 @@ class Distribution(Distribution_parse_config_files, _Distribution):
_Distribution.parse_config_files(self, filenames=filenames)
parse_configuration(self, self.command_options)
if getattr(self, 'python_requires', None):
self.metadata.python_requires = self.python_requires
def parse_command_line(self):
"""Process features after parsing command line options"""
......
......@@ -21,6 +21,20 @@ if you think you need this functionality.
"""
def _get_mro(cls):
"""
Returns the bases classes for cls sorted by the MRO.
Works around an issue on Jython where inspect.getmro will not return all
base classes if multiple classes share the same name. Instead, this
function will return a tuple containing the class itself, and the contents
of cls.__bases__. See https://github.com/pypa/setuptools/issues/1024.
"""
if platform.python_implementation() == "Jython":
return (cls,) + cls.__bases__
return inspect.getmro(cls)
def get_unpatched(item):
lookup = (
get_unpatched_class if isinstance(item, six.class_types) else
......@@ -38,7 +52,7 @@ def get_unpatched_class(cls):
"""
external_bases = (
cls
for cls in inspect.getmro(cls)
for cls in _get_mro(cls)
if not cls.__module__.startswith('setuptools')
)
base = next(external_bases)
......
This diff is collapsed.
......@@ -34,8 +34,8 @@ EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.+!]+)$')
HREF = re.compile("""href\\s*=\\s*['"]?([^'"> ]+)""", re.I)
# this is here to fix emacs' cruddy broken syntax highlighting
PYPI_MD5 = re.compile(
'<a href="([^"#]+)">([^<]+)</a>\n\s+\\(<a (?:title="MD5 hash"\n\s+)'
'href="[^?]+\?:action=show_md5&amp;digest=([0-9a-f]{32})">md5</a>\\)'
'<a href="([^"#]+)">([^<]+)</a>\n\\s+\\(<a (?:title="MD5 hash"\n\\s+)'
'href="[^?]+\\?:action=show_md5&amp;digest=([0-9a-f]{32})">md5</a>\\)'
)
URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):', re.I).match
EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz".split()
......@@ -161,7 +161,7 @@ def interpret_distro_name(
# versions in distribution archive names (sdist and bdist).
parts = basename.split('-')
if not py_version and any(re.match('py\d\.\d$', p) for p in parts[2:]):
if not py_version and any(re.match(r'py\d\.\d$', p) for p in parts[2:]):
# it is a bdist_dumb, not an sdist -- bail out
return
......@@ -205,7 +205,7 @@ def unique_values(func):
return wrapper
REL = re.compile("""<([^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*)>""", re.I)
REL = re.compile(r"""<([^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*)>""", re.I)
# this line is here to fix emacs' cruddy broken syntax highlighting
......
......@@ -2,9 +2,10 @@
Compatibility Support for Python 2.7 and earlier
"""
import sys
import platform
import six
def get_all_headers(message, key):
"""
......@@ -13,15 +14,14 @@ def get_all_headers(message, key):
return message.get_all(key)
if sys.version_info < (3,):
if six.PY2:
def get_all_headers(message, key):
return message.getheaders(key)
linux_py2_ascii = (
platform.system() == 'Linux' and
sys.version_info < (3,)
platform.system() == 'Linux' and
six.PY2
)
rmtree_safe = str if linux_py2_ascii else lambda x: x
......
......@@ -7,6 +7,7 @@ import itertools
import re
import contextlib
import pickle
import textwrap
from setuptools.extern import six
from setuptools.extern.six.moves import builtins, map
......@@ -215,7 +216,7 @@ def _needs_hiding(mod_name):
>>> _needs_hiding('Cython')
True
"""
pattern = re.compile('(setuptools|pkg_resources|distutils|Cython)(\.|$)')
pattern = re.compile(r'(setuptools|pkg_resources|distutils|Cython)(\.|$)')
return bool(pattern.match(mod_name))
......@@ -248,11 +249,9 @@ def run_setup(setup_script, args):
setup_script.encode(sys.getfilesystemencoding())
)
def runner():
with DirectorySandbox(setup_dir):
ns = dict(__file__=dunder_file, __name__='__main__')
_execfile(setup_script, ns)
DirectorySandbox(setup_dir).run(runner)
except SystemExit as v:
if v.args and v.args[0]:
raise
......@@ -274,21 +273,24 @@ class AbstractSandbox:
for name in self._attrs:
setattr(os, name, getattr(source, name))
def __enter__(self):
self._copy(self)
if _file:
builtins.file = self._file
builtins.open = self._open
self._active = True
def __exit__(self, exc_type, exc_value, traceback):
self._active = False
if _file:
builtins.file = _file
builtins.open = _open
self._copy(_os)
def run(self, func):
"""Run 'func' under os sandboxing"""
try:
self._copy(self)
if _file:
builtins.file = self._file
builtins.open = self._open
self._active = True
with self:
return func()
finally:
self._active = False
if _file:
builtins.file = _file
builtins.open = _open
self._copy(_os)
def _mk_dual_path_wrapper(name):
original = getattr(_os, name)
......@@ -391,7 +393,7 @@ class DirectorySandbox(AbstractSandbox):
_exception_patterns = [
# Allow lib2to3 to attempt to save a pickled grammar object (#121)
'.*lib2to3.*\.pickle$',
r'.*lib2to3.*\.pickle$',
]
"exempt writing to paths that match the pattern"
......@@ -476,16 +478,18 @@ WRITE_FLAGS = functools.reduce(
class SandboxViolation(DistutilsError):
"""A setup script attempted to modify the filesystem outside the sandbox"""
def __str__(self):
return """SandboxViolation: %s%r %s
The package setup script has attempted to modify files on your system
that are not within the EasyInstall build area, and has been aborted.
tmpl = textwrap.dedent("""
SandboxViolation: {cmd}{args!r} {kwargs}
This package cannot be safely installed by EasyInstall, and may not
support alternate installation locations even if you run its setup
script by hand. Please inform the package's author and the EasyInstall
maintainers to find out if a fix or workaround is available.""" % self.args
The package setup script has attempted to modify files on your system
that are not within the EasyInstall build area, and has been aborted.
This package cannot be safely installed by EasyInstall, and may not
support alternate installation locations even if you run its setup
script by hand. Please inform the package's author and the EasyInstall
maintainers to find out if a fix or workaround is available.
""").lstrip()
#
def __str__(self):
cmd, args, kwargs = self.args
return self.tmpl.format(**locals())
......@@ -41,4 +41,4 @@ class Test:
# let's see if we got our egg link at the right place
[content] = os.listdir('dist')
assert re.match('foo-0.0.0-py[23].\d.egg$', content)
assert re.match(r'foo-0.0.0-py[23].\d.egg$', content)
......@@ -312,6 +312,8 @@ class TestOptions:
'setup_requires = docutils>=0.3; spack ==1.1, ==1.3; there\n'
'dependency_links = http://some.com/here/1, '
'http://some.com/there/2\n'
'python_requires = >=1.0, !=2.8\n'
'py_modules = module1, module2\n'
)
with get_dist(tmpdir) as dist:
assert dist.zip_safe
......@@ -340,6 +342,8 @@ class TestOptions:
'there'
])
assert dist.tests_require == ['mock==0.7.2', 'pytest']
assert dist.python_requires == '>=1.0, !=2.8'
assert dist.py_modules == ['module1', 'module2']
def test_multiline(self, tmpdir):
fake_env(
......
......@@ -65,7 +65,7 @@ class TestEasyInstallTest:
def test_get_script_args(self):
header = ei.CommandSpec.best().from_environment().as_header()
expected = header + DALS("""
expected = header + DALS(r"""
# EASY-INSTALL-ENTRY-SCRIPT: 'spec','console_scripts','name'
__requires__ = 'spec'
import re
......
......@@ -98,3 +98,68 @@ def test_pbr(install_context):
def test_python_novaclient(install_context):
_install_one('python-novaclient', install_context,
'novaclient', 'base.py')
def test_pyuri(install_context):
"""
Install the pyuri package (version 0.3.1 at the time of writing).
This is also a regression test for issue #1016.
"""
_install_one('pyuri', install_context, 'pyuri', 'uri.py')
pyuri = install_context.installed_projects['pyuri']
# The package data should be installed.
assert os.path.exists(os.path.join(pyuri.location, 'pyuri', 'uri.regex'))
import re
import subprocess
import functools
import tarfile, zipfile
build_deps = ['appdirs', 'packaging', 'pyparsing', 'six']
@pytest.mark.parametrize("build_dep", build_deps)
@pytest.mark.skipif(sys.version_info < (3, 6), reason='run only on late versions')
def test_build_deps_on_distutils(request, tmpdir_factory, build_dep):
"""
All setuptools build dependencies must build without
setuptools.
"""
if 'pyparsing' in build_dep:
pytest.xfail(reason="Project imports setuptools unconditionally")
build_target = tmpdir_factory.mktemp('source')
build_dir = download_and_extract(request, build_dep, build_target)
install_target = tmpdir_factory.mktemp('target')
output = install(build_dir, install_target)
for line in output.splitlines():
match = re.search('Unknown distribution option: (.*)', line)
allowed_unknowns = [
'test_suite',
'tests_require',
'install_requires',
]
assert not match or match.group(1).strip('"\'') in allowed_unknowns
def install(pkg_dir, install_dir):
with open(os.path.join(pkg_dir, 'setuptools.py'), 'w') as breaker:
breaker.write('raise ImportError()')
cmd = [sys.executable, 'setup.py', 'install', '--prefix', install_dir]
env = dict(os.environ, PYTHONPATH=pkg_dir)
output = subprocess.check_output(cmd, cwd=pkg_dir, env=env, stderr=subprocess.STDOUT)
return output.decode('utf-8')
def download_and_extract(request, req, target):
cmd = [sys.executable, '-m', 'pip', 'download', '--no-deps',
'--no-binary', ':all:', req]
output = subprocess.check_output(cmd, encoding='utf-8')
filename = re.search('Saved (.*)', output).group(1)
request.addfinalizer(functools.partial(os.remove, filename))
opener = zipfile.ZipFile if filename.endswith('.zip') else tarfile.open
with opener(filename) as archive:
archive.extractall(target)
return os.path.join(target, os.listdir(target)[0])
......@@ -6,6 +6,7 @@ import os
import shutil
import sys
import tempfile
import itertools
from distutils import log
from distutils.errors import DistutilsTemplateError
......@@ -65,32 +66,94 @@ default_files = frozenset(map(make_local_path, [
]))
def get_pattern(glob):
return translate_pattern(make_local_path(glob)).pattern
def test_translated_pattern_test():
l = make_local_path
assert get_pattern('foo') == r'foo\Z(?ms)'
assert get_pattern(l('foo/bar')) == l(r'foo\/bar\Z(?ms)')
translate_specs = [
('foo', ['foo'], ['bar', 'foobar']),
('foo/bar', ['foo/bar'], ['foo/bar/baz', './foo/bar', 'foo']),
# Glob matching
assert get_pattern('*.txt') == l(r'[^\/]*\.txt\Z(?ms)')
assert get_pattern('dir/*.txt') == l(r'dir\/[^\/]*\.txt\Z(?ms)')
assert get_pattern('*/*.py') == l(r'[^\/]*\/[^\/]*\.py\Z(?ms)')
assert get_pattern('docs/page-?.txt') \
== l(r'docs\/page\-[^\/]\.txt\Z(?ms)')
('*.txt', ['foo.txt', 'bar.txt'], ['foo/foo.txt']),
('dir/*.txt', ['dir/foo.txt', 'dir/bar.txt', 'dir/.txt'], ['notdir/foo.txt']),
('*/*.py', ['bin/start.py'], []),
('docs/page-?.txt', ['docs/page-9.txt'], ['docs/page-10.txt']),
# Globstars change what they mean depending upon where they are
assert get_pattern(l('foo/**/bar')) == l(r'foo\/(?:[^\/]+\/)*bar\Z(?ms)')
assert get_pattern(l('foo/**')) == l(r'foo\/.*\Z(?ms)')
assert get_pattern(l('**')) == r'.*\Z(?ms)'
(
'foo/**/bar',
['foo/bing/bar', 'foo/bing/bang/bar', 'foo/bar'],
['foo/abar'],
),
(
'foo/**',
['foo/bar/bing.py', 'foo/x'],
['/foo/x'],
),
(
'**',
['x', 'abc/xyz', '@nything'],
[],
),
# Character classes
assert get_pattern('pre[one]post') == r'pre[one]post\Z(?ms)'
assert get_pattern('hello[!one]world') == r'hello[^one]world\Z(?ms)'
assert get_pattern('[]one].txt') == r'[\]one]\.txt\Z(?ms)'
assert get_pattern('foo[!]one]bar') == r'foo[^\]one]bar\Z(?ms)'
(
'pre[one]post',
['preopost', 'prenpost', 'preepost'],
['prepost', 'preonepost'],
),
(
'hello[!one]world',
['helloxworld', 'helloyworld'],
['hellooworld', 'helloworld', 'hellooneworld'],
),
(
'[]one].txt',
['o.txt', '].txt', 'e.txt'],
['one].txt'],
),
(
'foo[!]one]bar',
['fooybar'],
['foo]bar', 'fooobar', 'fooebar'],
),
]
"""
A spec of inputs for 'translate_pattern' and matches and mismatches
for that input.
"""
match_params = itertools.chain.from_iterable(
zip(itertools.repeat(pattern), matches)
for pattern, matches, mismatches in translate_specs
)
@pytest.fixture(params=match_params)
def pattern_match(request):
return map(make_local_path, request.param)
mismatch_params = itertools.chain.from_iterable(
zip(itertools.repeat(pattern), mismatches)
for pattern, matches, mismatches in translate_specs
)
@pytest.fixture(params=mismatch_params)
def pattern_mismatch(request):
return map(make_local_path, request.param)
def test_translated_pattern_match(pattern_match):
pattern, target = pattern_match
assert translate_pattern(pattern).match(target)
def test_translated_pattern_mismatch(pattern_mismatch):
pattern, target = pattern_mismatch
assert not translate_pattern(pattern).match(target)
class TempDirTestCase(object):
......
......@@ -7,13 +7,12 @@ import pytest
import pkg_resources
import setuptools.sandbox
from setuptools.sandbox import DirectorySandbox
class TestSandbox:
def test_devnull(self, tmpdir):
sandbox = DirectorySandbox(str(tmpdir))
sandbox.run(self._file_writer(os.devnull))
with setuptools.sandbox.DirectorySandbox(str(tmpdir)):
self._file_writer(os.devnull)
@staticmethod
def _file_writer(path):
......@@ -116,13 +115,17 @@ class TestExceptionSaver:
with open('/etc/foo', 'w'):
pass
sandbox = DirectorySandbox(str(tmpdir))
with pytest.raises(setuptools.sandbox.SandboxViolation) as caught:
with setuptools.sandbox.save_modules():
setuptools.sandbox.hide_setuptools()
sandbox.run(write_file)
with setuptools.sandbox.DirectorySandbox(str(tmpdir)):
write_file()
cmd, args, kwargs = caught.value.args
assert cmd == 'open'
assert args == ('/etc/foo', 'w')
assert kwargs == {}
msg = str(caught.value)
assert 'open' in msg
assert "('/etc/foo', 'w')" in msg
......@@ -64,6 +64,8 @@ class TestUploadDocsTest:
)
body, content_type = upload_docs._build_multipart(data)
assert 'form-data' in content_type
assert "b'" not in content_type
assert 'b"' not in content_type
assert isinstance(body, bytes)
assert b'foo' in body
assert b'content' in body
pytest-flake8
pytest>=3.0.2
setuptools[ssl]
backports.unittest_mock>=1.2
# pinned to 1.2 as temporary workaround for #1038
backports.unittest_mock>=1.2,<1.3
# Note: Run "python bootstrap.py" before running Tox, to generate metadata.
#
# To run Tox against all supported Python interpreters, you can set:
#
# export TOXENV='py2{6,7},py3{3,4,5,6},pypy'
[testenv]
deps=-rtests/requirements.txt
passenv=APPDATA USERPROFILE HOMEDRIVE HOMEPATH windir APPVEYOR
......
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