Imported Upstream version 36.6.0 upstream/36.6.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 14 Jan 2019 01:35:05 +0000 (10:35 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 14 Jan 2019 01:35:05 +0000 (10:35 +0900)
16 files changed:
CHANGES.rst
pkg_resources/__init__.py
pkg_resources/tests/test_pkg_resources.py
setup.cfg
setup.py
setuptools/build_meta.py [new file with mode: 0644]
setuptools/command/__init__.py
setuptools/command/dist_info.py [new file with mode: 0644]
setuptools/tests/__init__.py
setuptools/tests/test_build_meta.py [new file with mode: 0644]
setuptools/tests/test_develop.py
setuptools/tests/test_egg_info.py
setuptools/tests/test_namespaces.py
setuptools/tests/test_sdist.py
setuptools/tests/test_setuptools.py
setuptools/tests/text.py [new file with mode: 0644]

index 8217aabad43442b0d20e0c53a6d22d70ffea2009..3dbda9dc4be40bab4467459dfe4e43a53d657e13 100644 (file)
@@ -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
 -------
 
index 68349df44e642f60ffcd744be189d31f56843fd3..049b8a5458e1bdfef65d2b7c08a8b248aac032bd 100644 (file)
@@ -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):
index 49bf7a04b805832dfb88891d98d180112121e40e..c6a7ac97e93467b60dbd8e419bf53142881c8cc8 100644 (file)
@@ -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()
 
 
index e5ebf4b8dfaf6be5fffdffd6ff603a303ae85809..c1ee1ba49572de7f0b2af1ea86641c7c5f253ffa 100755 (executable)
--- 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
 
index ee968d9a89c885bf5165dc2da1ddfea7b2bd0377..33003465a142b1f66babbc967185e51b6ce44896 100755 (executable)
--- 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 (file)
index 0000000..54f2987
--- /dev/null
@@ -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]
index c96d33c23d6f79f96ec262b1144a1b71ce3eedeb..4fe3bb5684634986c14ff1cfe328c18ea4c537e7 100644 (file)
@@ -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 (file)
index 0000000..c8dc659
--- /dev/null
@@ -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))
index 8ae4402dde9f2cfaf3a82b54f17abc296fa881d5..54dd7d2b2069fb5463325000112783caf39a9b2b 100644 (file)
@@ -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 (file)
index 0000000..69a700c
--- /dev/null
@@ -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'))
index ad7cfa05c3548eea67e765b55262388736771311..cb4ff4b40101480052966fd31c2bc6894ff9d31d 100644 (file)
@@ -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),
index e454694d1143d53e8b6d783754caf6616726ad13..1411f93c0d99ff0d6ffd23d99749a16003029e46 100644 (file)
@@ -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 = [
index 721cad1e6348a794b020c5c09d52d5ad1af5c7c9..1ac1b35e8b91970749c1b09e14c6ad78522ff30d 100644 (file)
@@ -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),
index f34068dcd39c7ad1655a5930e2e39f350a7f3331..02222da5a62472ba894e292fd6b3372a95a779db 100644 (file)
@@ -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:
index e59800d2ece9fd6930bf62ef3e3f48b0d374c289..26e37a6c14ea1416debcf31d753dc4cb7140f45f 100644 (file)
@@ -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 (file)
index 0000000..ad2c624
--- /dev/null
@@ -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')