From e3e91947dd3ecdf2919c411b55dcb1fc13656aba Mon Sep 17 00:00:00 2001 From: DongHun Kwak Date: Mon, 14 Jan 2019 10:35:05 +0900 Subject: [PATCH] Imported Upstream version 36.6.0 --- CHANGES.rst | 9 + pkg_resources/__init__.py | 6 +- pkg_resources/tests/test_pkg_resources.py | 4 +- setup.cfg | 2 +- setup.py | 2 +- setuptools/build_meta.py | 148 ++++++++++ setuptools/command/__init__.py | 2 +- setuptools/command/dist_info.py | 37 +++ setuptools/tests/__init__.py | 320 ---------------------- setuptools/tests/test_build_meta.py | 93 +++++++ setuptools/tests/test_develop.py | 4 +- setuptools/tests/test_egg_info.py | 6 +- setuptools/tests/test_namespaces.py | 12 +- setuptools/tests/test_sdist.py | 67 ++--- setuptools/tests/test_setuptools.py | 320 ++++++++++++++++++++++ setuptools/tests/text.py | 9 + 16 files changed, 678 insertions(+), 363 deletions(-) create mode 100644 setuptools/build_meta.py create mode 100644 setuptools/command/dist_info.py create mode 100644 setuptools/tests/test_build_meta.py create mode 100644 setuptools/tests/text.py diff --git a/CHANGES.rst b/CHANGES.rst index 8217aab..3dbda9d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,12 @@ +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 ------- diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 68349df..049b8a5 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -2124,7 +2124,11 @@ def non_empty_lines(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): diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py index 49bf7a0..c6a7ac9 100644 --- a/pkg_resources/tests/test_pkg_resources.py +++ b/pkg_resources/tests/test_pkg_resources.py @@ -92,8 +92,8 @@ class TestZipProvider(object): ts = timestamp(self.ref_time) os.utime(filename, (ts, ts)) filename = zp.get_resource_filename(manager, 'data.dat') - f = open(filename) - assert f.read() == 'hello, world!' + with open(filename) as f: + assert f.read() == 'hello, world!' manager.cleanup_resources() diff --git a/setup.cfg b/setup.cfg index e5ebf4b..c1ee1ba 100755 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 36.5.0 +current_version = 36.6.0 commit = True tag = True diff --git a/setup.py b/setup.py index ee968d9..3300346 100755 --- a/setup.py +++ b/setup.py @@ -89,7 +89,7 @@ def pypi_link(pkg_filename): setup_params = dict( name="setuptools", - version="36.5.0", + version="36.6.0", description="Easily download, build, install, upgrade, and uninstall " "Python packages", author="Python Packaging Authority", diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py new file mode 100644 index 0000000..54f2987 --- /dev/null +++ b/setuptools/build_meta.py @@ -0,0 +1,148 @@ +"""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] diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py index c96d33c..4fe3bb5 100644 --- a/setuptools/command/__init__.py +++ b/setuptools/command/__init__.py @@ -2,7 +2,7 @@ __all__ = [ 'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop', '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', + 'register', 'bdist_wininst', 'upload_docs', 'upload', 'build_clib', 'dist_info', ] from distutils.command.bdist import bdist diff --git a/setuptools/command/dist_info.py b/setuptools/command/dist_info.py new file mode 100644 index 0000000..c8dc659 --- /dev/null +++ b/setuptools/command/dist_info.py @@ -0,0 +1,37 @@ +""" +Create a dist_info directory +As defined in the wheel specification +""" + +import os +import shutil + +from distutils.core import Command + + +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' + + bdist_wheel = self.get_finalized_command('bdist_wheel') + bdist_wheel.egg2dist(egg_info.egg_info, dist_info_dir) + + if self.egg_base: + shutil.move(dist_info_dir, os.path.join( + self.egg_base, dist_info_dir)) diff --git a/setuptools/tests/__init__.py b/setuptools/tests/__init__.py index 8ae4402..54dd7d2 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_build_meta.py b/setuptools/tests/test_build_meta.py new file mode 100644 index 0000000..69a700c --- /dev/null +++ b/setuptools/tests/test_build_meta.py @@ -0,0 +1,93 @@ +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')) diff --git a/setuptools/tests/test_develop.py b/setuptools/tests/test_develop.py index ad7cfa0..cb4ff4b 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_egg_info.py b/setuptools/tests/test_egg_info.py index e454694..1411f93 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -164,7 +164,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( @@ -447,7 +448,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 721cad1..1ac1b35 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 f34068d..02222da 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 e59800d..26e37a6 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/text.py b/setuptools/tests/text.py new file mode 100644 index 0000000..ad2c624 --- /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') -- 2.34.1