diff options
Diffstat (limited to 'setuptools/tests')
25 files changed, 862 insertions, 157 deletions
diff --git a/setuptools/tests/files.py b/setuptools/tests/files.py index 465a6b41..bad2189d 100644 --- a/setuptools/tests/files.py +++ b/setuptools/tests/files.py @@ -6,10 +6,13 @@ import pkg_resources.py31compat def build_files(file_defs, prefix=""): """ - Build a set of files/directories, as described by the file_defs dictionary. + Build a set of files/directories, as described by the + file_defs dictionary. - Each key/value pair in the dictionary is interpreted as a filename/contents - pair. If the contents value is a dictionary, a directory is created, and the + Each key/value pair in the dictionary is interpreted as + a filename/contents + pair. If the contents value is a dictionary, a directory + is created, and the dictionary interpreted as the files within it, recursively. For example: diff --git a/setuptools/tests/server.py b/setuptools/tests/server.py index 35312120..fc3a5975 100644 --- a/setuptools/tests/server.py +++ b/setuptools/tests/server.py @@ -19,10 +19,11 @@ class IndexServer(BaseHTTPServer.HTTPServer): s.stop() """ - def __init__(self, server_address=('', 0), + def __init__( + self, server_address=('', 0), RequestHandlerClass=SimpleHTTPServer.SimpleHTTPRequestHandler): - BaseHTTPServer.HTTPServer.__init__(self, server_address, - RequestHandlerClass) + BaseHTTPServer.HTTPServer.__init__( + self, server_address, RequestHandlerClass) self._run = True def start(self): @@ -56,10 +57,11 @@ class MockServer(BaseHTTPServer.HTTPServer, threading.Thread): A simple HTTP Server that records the requests made to it. """ - def __init__(self, server_address=('', 0), + def __init__( + self, server_address=('', 0), RequestHandlerClass=RequestRecorder): - BaseHTTPServer.HTTPServer.__init__(self, server_address, - RequestHandlerClass) + BaseHTTPServer.HTTPServer.__init__( + self, server_address, RequestHandlerClass) threading.Thread.__init__(self) self.setDaemon(True) self.requests = [] diff --git a/setuptools/tests/test_build_clib.py b/setuptools/tests/test_build_clib.py index aebcc350..3779e679 100644 --- a/setuptools/tests/test_build_clib.py +++ b/setuptools/tests/test_build_clib.py @@ -1,6 +1,4 @@ import pytest -import os -import shutil import mock from distutils.errors import DistutilsSetupError @@ -40,13 +38,14 @@ class TestBuildCLib: # with that out of the way, let's see if the crude dependency # system works cmd.compiler = mock.MagicMock(spec=cmd.compiler) - mock_newer.return_value = ([],[]) + mock_newer.return_value = ([], []) obj_deps = {'': ('global.h',), 'example.c': ('example.h',)} - libs = [('example', {'sources': ['example.c'] ,'obj_deps': obj_deps})] + libs = [('example', {'sources': ['example.c'], 'obj_deps': obj_deps})] cmd.build_libraries(libs) - assert [['example.c', 'global.h', 'example.h']] in mock_newer.call_args[0] + assert [['example.c', 'global.h', 'example.h']] in \ + mock_newer.call_args[0] assert not cmd.compiler.compile.called assert cmd.compiler.create_static_lib.call_count == 1 diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index 7b195e2c..82b44c89 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -2,16 +2,20 @@ from __future__ import unicode_literals import os import shutil +import tarfile import pytest +from setuptools.build_meta import build_sdist from .files import build_files from .textwrap import DALS +from . import py2_only __metaclass__ = type -futures = pytest.importorskip('concurrent.futures') -importlib = pytest.importorskip('importlib') +# Backports on Python 2.7 +import importlib +from concurrent import futures class BuildBackendBase: @@ -108,13 +112,13 @@ def build_backend(tmpdir, request): def test_get_requires_for_build_wheel(build_backend): actual = build_backend.get_requires_for_build_wheel() - expected = ['six', 'setuptools', 'wheel'] + expected = ['six', 'wheel'] assert sorted(actual) == sorted(expected) def test_get_requires_for_build_sdist(build_backend): actual = build_backend.get_requires_for_build_sdist() - expected = ['six', 'setuptools'] + expected = ['six'] assert sorted(actual) == sorted(expected) @@ -143,7 +147,7 @@ def test_prepare_metadata_for_build_wheel(build_backend): assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA')) -@pytest.mark.skipif('sys.version_info > (3,)') +@py2_only def test_prepare_metadata_for_build_wheel_with_str(build_backend): dist_dir = os.path.abspath(str('pip-dist-info')) os.makedirs(dist_dir) @@ -168,15 +172,67 @@ def test_build_sdist_version_change(build_backend): sdist_name = build_backend.build_sdist(sdist_into_directory) assert os.path.isfile(os.path.join(sdist_into_directory, sdist_name)) - # if the setup.py changes subsequent call of the build meta should still succeed, given the + # if the setup.py changes subsequent call of the build meta + # should still succeed, given the # sdist_directory the frontend specifies is empty with open(os.path.abspath("setup.py"), 'rt') as file_handler: content = file_handler.read() with open(os.path.abspath("setup.py"), 'wt') as file_handler: - file_handler.write(content.replace("version='0.0.0'", "version='0.0.1'")) + file_handler.write( + content.replace("version='0.0.0'", "version='0.0.1'")) shutil.rmtree(sdist_into_directory) os.makedirs(sdist_into_directory) sdist_name = build_backend.build_sdist("out_sdist") - assert os.path.isfile(os.path.join(os.path.abspath("out_sdist"), sdist_name)) + assert os.path.isfile( + os.path.join(os.path.abspath("out_sdist"), sdist_name)) + + +def test_build_sdist_setup_py_exists(tmpdir_cwd): + # If build_sdist is called from a script other than setup.py, + # ensure setup.py is include + build_files(defns[0]) + targz_path = build_sdist("temp") + with tarfile.open(os.path.join("temp", targz_path)) as tar: + assert any('setup.py' in name for name in tar.getnames()) + + +def test_build_sdist_setup_py_manifest_excluded(tmpdir_cwd): + # Ensure that MANIFEST.in can exclude setup.py + files = { + 'setup.py': DALS(""" + __import__('setuptools').setup( + name='foo', + version='0.0.0', + py_modules=['hello'] + )"""), + 'hello.py': '', + 'MANIFEST.in': DALS(""" + exclude setup.py + """) + } + + build_files(files) + targz_path = build_sdist("temp") + with tarfile.open(os.path.join("temp", targz_path)) as tar: + assert not any('setup.py' in name for name in tar.getnames()) + + +def test_build_sdist_builds_targz_even_if_zip_indicated(tmpdir_cwd): + files = { + 'setup.py': DALS(""" + __import__('setuptools').setup( + name='foo', + version='0.0.0', + py_modules=['hello'] + )"""), + 'hello.py': '', + 'setup.cfg': DALS(""" + [sdist] + formats=zip + """) + } + + build_files(files) + build_sdist("temp") diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index 76759ec5..6b177709 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -1,10 +1,18 @@ +# -*- coding: UTF-8 -*- +from __future__ import unicode_literals + import contextlib import pytest + from distutils.errors import DistutilsOptionError, DistutilsFileError from mock import patch from setuptools.dist import Distribution, _Distribution from setuptools.config import ConfigHandler, read_configuration +from setuptools.extern.six.moves.configparser import InterpolationMissingOptionError +from setuptools.tests import is_ascii from . import py2_only, py3_only +from .textwrap import DALS + class ErrConfigHandler(ConfigHandler): """Erroneous handler. Fails to implement required methods.""" @@ -16,12 +24,12 @@ def make_package_dir(name, base_dir, ns=False): dir_package = dir_package.mkdir(dir_name) init_file = None if not ns: - init_file = dir_package.join('__init__.py') - init_file.write('') + init_file = dir_package.join('__init__.py') + init_file.write('') return dir_package, init_file -def fake_env(tmpdir, setup_cfg, setup_py=None, package_path='fake_package'): +def fake_env(tmpdir, setup_cfg, setup_py=None, encoding='ascii', package_path='fake_package'): if setup_py is None: setup_py = ( @@ -31,7 +39,7 @@ def fake_env(tmpdir, setup_cfg, setup_py=None, package_path='fake_package'): tmpdir.join('setup.py').write(setup_py) config = tmpdir.join('setup.cfg') - config.write(setup_cfg) + config.write(setup_cfg.encode(encoding), mode='wb') package_dir, init_file = make_package_dir(package_path, tmpdir) @@ -146,6 +154,24 @@ class TestMetadata: assert metadata.download_url == 'http://test.test.com/test/' assert metadata.maintainer_email == 'test@test.com' + def test_license_cfg(self, tmpdir): + fake_env( + tmpdir, + DALS(""" + [metadata] + name=foo + version=0.0.1 + license=Apache 2.0 + """) + ) + + with get_dist(tmpdir) as dist: + metadata = dist.metadata + + assert metadata.name == "foo" + assert metadata.version == "0.0.1" + assert metadata.license == "Apache 2.0" + def test_file_mixed(self, tmpdir): fake_env( @@ -288,7 +314,7 @@ class TestMetadata: tmpdir.join('fake_package', 'version.txt').write('1.2.3\n4.5.6\n') with pytest.raises(DistutilsOptionError): with get_dist(tmpdir) as dist: - _ = dist.metadata.version + dist.metadata.version def test_version_with_package_dir_simple(self, tmpdir): @@ -391,6 +417,89 @@ class TestMetadata: with get_dist(tmpdir) as dist: assert set(dist.metadata.classifiers) == expected + def test_deprecated_config_handlers(self, tmpdir): + fake_env( + tmpdir, + '[metadata]\n' + 'version = 10.1.1\n' + 'description = Some description\n' + 'requires = some, requirement\n' + ) + + with pytest.deprecated_call(): + with get_dist(tmpdir) as dist: + metadata = dist.metadata + + assert metadata.version == '10.1.1' + assert metadata.description == 'Some description' + assert metadata.requires == ['some', 'requirement'] + + def test_interpolation(self, tmpdir): + fake_env( + tmpdir, + '[metadata]\n' + 'description = %(message)s\n' + ) + with pytest.raises(InterpolationMissingOptionError): + with get_dist(tmpdir): + pass + + skip_if_not_ascii = pytest.mark.skipif(not is_ascii, reason='Test not supported with this locale') + + @skip_if_not_ascii + def test_non_ascii_1(self, tmpdir): + fake_env( + tmpdir, + '[metadata]\n' + 'description = éàïôñ\n', + encoding='utf-8' + ) + with pytest.raises(UnicodeDecodeError): + with get_dist(tmpdir): + pass + + def test_non_ascii_2(self, tmpdir): + fake_env( + tmpdir, + '# -*- coding: invalid\n' + ) + with pytest.raises(LookupError): + with get_dist(tmpdir): + pass + + def test_non_ascii_3(self, tmpdir): + fake_env( + tmpdir, + '\n' + '# -*- coding: invalid\n' + ) + with get_dist(tmpdir): + pass + + @skip_if_not_ascii + def test_non_ascii_4(self, tmpdir): + fake_env( + tmpdir, + '# -*- coding: utf-8\n' + '[metadata]\n' + 'description = éàïôñ\n', + encoding='utf-8' + ) + with get_dist(tmpdir) as dist: + assert dist.metadata.description == 'éàïôñ' + + @skip_if_not_ascii + def test_non_ascii_5(self, tmpdir): + fake_env( + tmpdir, + '# vim: set fileencoding=iso-8859-15 :\n' + '[metadata]\n' + 'description = éàïôñ\n', + encoding='iso-8859-15' + ) + with get_dist(tmpdir) as dist: + assert dist.metadata.description == 'éàïôñ' + class TestOptions: @@ -414,7 +523,7 @@ class TestOptions: 'tests_require = mock==0.7.2; pytest\n' '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' + 'http://some.com/there/2\n' 'python_requires = >=1.0, !=2.8\n' 'py_modules = module1, module2\n' ) @@ -622,7 +731,7 @@ class TestOptions: dir_sub_two, _ = make_package_dir('sub_two', dir_package, ns=True) with get_dist(tmpdir) as dist: - assert set(dist.packages) == { + assert set(dist.packages) == { 'fake_package', 'fake_package.sub_two', 'fake_package.sub_one' } @@ -674,7 +783,7 @@ class TestOptions: tmpdir, '[options.entry_points]\n' 'group1 = point1 = pack.module:func, ' - '.point2 = pack.module2:func_rest [rest]\n' + '.point2 = pack.module2:func_rest [rest]\n' 'group2 = point3 = pack.module:func2\n' ) @@ -720,7 +829,10 @@ class TestOptions: ] assert sorted(dist.data_files) == sorted(expected) + saved_dist_init = _Distribution.__init__ + + class TestExternalSetters: # During creation of the setuptools Distribution() object, we call # the init of the parent distutils Distribution object via diff --git a/setuptools/tests/test_depends.py b/setuptools/tests/test_depends.py index e0cfa880..bff1dfb1 100644 --- a/setuptools/tests/test_depends.py +++ b/setuptools/tests/test_depends.py @@ -5,12 +5,12 @@ from setuptools import depends class TestGetModuleConstant: - def test_basic(self): - """ - Invoke get_module_constant on a module in - the test package. - """ - mod_name = 'setuptools.tests.mod_with_constant' - val = depends.get_module_constant(mod_name, 'value') - assert val == 'three, sir!' - assert 'setuptools.tests.mod_with_constant' not in sys.modules + def test_basic(self): + """ + Invoke get_module_constant on a module in + the test package. + """ + mod_name = 'setuptools.tests.mod_with_constant' + val = depends.get_module_constant(mod_name, 'value') + assert val == 'three, sir!' + assert 'setuptools.tests.mod_with_constant' not in sys.modules diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py index 5162e1c9..390c3dfc 100644 --- a/setuptools/tests/test_dist.py +++ b/setuptools/tests/test_dist.py @@ -3,10 +3,11 @@ from __future__ import unicode_literals import io - +from setuptools.dist import DistDeprecationWarning, _get_unpatched from setuptools import Distribution from setuptools.extern.six.moves.urllib.request import pathname2url from setuptools.extern.six.moves.urllib_parse import urljoin +from setuptools.extern import six from .textwrap import DALS from .test_easy_install import make_nspkg_sdist @@ -56,6 +57,125 @@ def test_dist_fetch_build_egg(tmpdir): assert [dist.key for dist in resolved_dists if dist] == reqs +def test_dist__get_unpatched_deprecated(): + pytest.warns(DistDeprecationWarning, _get_unpatched, [""]) + + +def __read_test_cases(): + # Metadata version 1.0 + base_attrs = { + "name": "package", + "version": "0.0.1", + "author": "Foo Bar", + "author_email": "foo@bar.net", + "long_description": "Long\ndescription", + "description": "Short description", + "keywords": ["one", "two"] + } + + def merge_dicts(d1, d2): + d1 = d1.copy() + d1.update(d2) + + return d1 + + test_cases = [ + ('Metadata version 1.0', base_attrs.copy()), + ('Metadata version 1.1: Provides', merge_dicts(base_attrs, { + 'provides': ['package'] + })), + ('Metadata version 1.1: Obsoletes', merge_dicts(base_attrs, { + 'obsoletes': ['foo'] + })), + ('Metadata version 1.1: Classifiers', merge_dicts(base_attrs, { + 'classifiers': [ + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.7', + 'License :: OSI Approved :: MIT License', + ]})), + ('Metadata version 1.1: Download URL', merge_dicts(base_attrs, { + 'download_url': 'https://example.com' + })), + ('Metadata Version 1.2: Requires-Python', merge_dicts(base_attrs, { + 'python_requires': '>=3.7' + })), + pytest.param( + 'Metadata Version 1.2: Project-Url', + merge_dicts(base_attrs, { + 'project_urls': { + 'Foo': 'https://example.bar' + } + }), marks=pytest.mark.xfail( + reason="Issue #1578: project_urls not read" + )), + ('Metadata Version 2.1: Long Description Content Type', + merge_dicts(base_attrs, { + 'long_description_content_type': 'text/x-rst; charset=UTF-8' + })), + pytest.param( + 'Metadata Version 2.1: Provides Extra', + merge_dicts(base_attrs, { + 'provides_extras': ['foo', 'bar'] + }), marks=pytest.mark.xfail(reason="provides_extras not read")), + ('Missing author, missing author e-mail', + {'name': 'foo', 'version': '1.0.0'}), + ('Missing author', + {'name': 'foo', + 'version': '1.0.0', + 'author_email': 'snorri@sturluson.name'}), + ('Missing author e-mail', + {'name': 'foo', + 'version': '1.0.0', + 'author': 'Snorri Sturluson'}), + ('Missing author', + {'name': 'foo', + 'version': '1.0.0', + 'author': 'Snorri Sturluson'}), + ] + + return test_cases + + +@pytest.mark.parametrize('name,attrs', __read_test_cases()) +def test_read_metadata(name, attrs): + dist = Distribution(attrs) + metadata_out = dist.metadata + dist_class = metadata_out.__class__ + + # Write to PKG_INFO and then load into a new metadata object + if six.PY2: + PKG_INFO = io.BytesIO() + else: + PKG_INFO = io.StringIO() + + metadata_out.write_pkg_file(PKG_INFO) + + PKG_INFO.seek(0) + metadata_in = dist_class() + metadata_in.read_pkg_file(PKG_INFO) + + tested_attrs = [ + ('name', dist_class.get_name), + ('version', dist_class.get_version), + ('author', dist_class.get_contact), + ('author_email', dist_class.get_contact_email), + ('metadata_version', dist_class.get_metadata_version), + ('provides', dist_class.get_provides), + ('description', dist_class.get_description), + ('download_url', dist_class.get_download_url), + ('keywords', dist_class.get_keywords), + ('platforms', dist_class.get_platforms), + ('obsoletes', dist_class.get_obsoletes), + ('requires', dist_class.get_requires), + ('classifiers', dist_class.get_classifiers), + ('project_urls', lambda s: getattr(s, 'project_urls', {})), + ('provides_extras', lambda s: getattr(s, 'provides_extras', set())), + ] + + for attr, getter in tested_attrs: + assert getter(metadata_in) == getter(metadata_out) + + def __maintainer_test_cases(): attrs = {"name": "package", "version": "1.0", diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index b0cc4c9f..c3fd1c6e 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -15,7 +15,9 @@ import distutils.errors import io import zipfile import mock - +from setuptools.command.easy_install import ( + EasyInstallDeprecationWarning, ScriptWriter, WindowsScriptWriter, +) import time from setuptools.extern import six from setuptools.extern.six.moves import urllib @@ -287,6 +289,22 @@ class TestEasyInstallTest: cmd.easy_install(sdist_script) assert (target / 'mypkg_script').exists() + def test_dist_get_script_args_deprecated(self): + with pytest.warns(EasyInstallDeprecationWarning): + ScriptWriter.get_script_args(None, None) + + def test_dist_get_script_header_deprecated(self): + with pytest.warns(EasyInstallDeprecationWarning): + ScriptWriter.get_script_header("") + + def test_dist_get_writer_deprecated(self): + with pytest.warns(EasyInstallDeprecationWarning): + ScriptWriter.get_writer(None) + + def test_dist_WindowsScriptWriter_get_writer_deprecated(self): + with pytest.warns(EasyInstallDeprecationWarning): + WindowsScriptWriter.get_writer() + @pytest.mark.filterwarnings('ignore:Unbuilt egg') class TestPTHFileWriter: diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index 7c862e61..db9c3873 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -1,4 +1,3 @@ -import datetime import sys import ast import os @@ -7,7 +6,9 @@ import re import stat import time -from setuptools.command.egg_info import egg_info, manifest_maker +from setuptools.command.egg_info import ( + egg_info, manifest_maker, EggInfoDeprecationWarning, get_pkg_info_revision, +) from setuptools.dist import Distribution from setuptools.extern.six.moves import map @@ -148,6 +149,37 @@ class TestEggInfo: ] assert sorted(actual) == expected + def test_license_is_a_string(self, tmpdir_cwd, env): + setup_config = DALS(""" + [metadata] + name=foo + version=0.0.1 + license=file:MIT + """) + + setup_script = DALS(""" + from setuptools import setup + + setup() + """) + + build_files({'setup.py': setup_script, + 'setup.cfg': setup_config}) + + # This command should fail with a ValueError, but because it's + # currently configured to use a subprocess, the actual traceback + # object is lost and we need to parse it from stderr + with pytest.raises(AssertionError) as exc: + self._run_egg_info_command(tmpdir_cwd, env) + + # Hopefully this is not too fragile: the only argument to the + # assertion error should be a traceback, ending with: + # ValueError: .... + # + # assert not 1 + tb = exc.value.args[0].split('\n') + assert tb[-3].lstrip().startswith('ValueError') + def test_rebuilt(self, tmpdir_cwd, env): """Ensure timestamps are updated when the command is re-run.""" self._create_project() @@ -618,6 +650,20 @@ class TestEggInfo: for msg in fixtures: assert manifest_maker._should_suppress_warning(msg) + def test_egg_info_includes_setup_py(self, tmpdir_cwd): + self._create_project() + dist = Distribution({"name": "foo", "version": "0.0.1"}) + dist.script_name = "non_setup.py" + egg_info_instance = egg_info(dist) + egg_info_instance.finalize_options() + egg_info_instance.run() + + assert 'setup.py' in egg_info_instance.filelist.files + + with open(egg_info_instance.egg_info + "/SOURCES.txt") as f: + sources = f.read().split('\n') + assert 'setup.py' in sources + def _run_egg_info_command(self, tmpdir_cwd, env, cmd=None, output=None): environ = os.environ.copy().update( HOME=env.paths['home'], @@ -632,8 +678,8 @@ class TestEggInfo: data_stream=1, env=environ, ) - if code: - raise AssertionError(data) + assert not code, data + if output: assert output in data @@ -652,3 +698,52 @@ class TestEggInfo: with open(os.path.join(egg_info_dir, 'PKG-INFO')) as pkginfo_file: pkg_info_lines = pkginfo_file.read().split('\n') assert 'Version: 0.0.0.dev0' in pkg_info_lines + + def test_get_pkg_info_revision_deprecated(self): + pytest.warns(EggInfoDeprecationWarning, get_pkg_info_revision) + + EGG_INFO_TESTS = ( + # Check for issue #1136: invalid string type when + # reading declarative `setup.cfg` under Python 2. + { + 'setup.py': DALS( + """ + from setuptools import setup + setup( + name="foo", + ) + """), + 'setup.cfg': DALS( + """ + [options] + package_dir = + = src + """), + 'src': {}, + }, + # Check Unicode can be used in `setup.py` under Python 2. + { + 'setup.py': DALS( + """ + # -*- coding: utf-8 -*- + from __future__ import unicode_literals + from setuptools import setup, find_packages + setup( + name="foo", + package_dir={'': 'src'}, + ) + """), + 'src': {}, + } + ) + + @pytest.mark.parametrize('package_files', EGG_INFO_TESTS) + def test_egg_info(self, tmpdir_cwd, env, package_files): + """ + """ + build_files(package_files) + code, data = environment.run_setup_py( + cmd=['egg_info'], + data_stream=1, + ) + assert not code, data diff --git a/setuptools/tests/test_find_packages.py b/setuptools/tests/test_find_packages.py index b08f91c7..ab26b4f1 100644 --- a/setuptools/tests/test_find_packages.py +++ b/setuptools/tests/test_find_packages.py @@ -12,10 +12,10 @@ from . import py3_only from setuptools.extern.six import PY3 from setuptools import find_packages if PY3: - from setuptools import find_namespace_packages + from setuptools import find_namespace_packages -# modeled after CPython's test.support.can_symlink +# modeled after CPython's test.support.can_symlink def can_symlink(): TESTFN = tempfile.mktemp() symlink_path = TESTFN + "can_symlink" @@ -164,12 +164,14 @@ class TestFindPackages: def test_pep420_ns_package_no_includes(self): packages = find_namespace_packages( self.dist_dir, exclude=['pkg.subpkg.assets']) - self._assert_packages(packages, ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg']) + self._assert_packages( + packages, ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg']) @py3_only def test_pep420_ns_package_no_includes_or_excludes(self): packages = find_namespace_packages(self.dist_dir) - expected = ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets'] + expected = [ + 'docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets'] self._assert_packages(packages, expected) @py3_only @@ -185,4 +187,3 @@ class TestFindPackages: shutil.rmtree(os.path.join(self.dist_dir, 'pkg/subpkg/assets')) packages = find_namespace_packages(self.dist_dir) self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg']) - diff --git a/setuptools/tests/test_install_scripts.py b/setuptools/tests/test_install_scripts.py index 727ad65b..4338c792 100644 --- a/setuptools/tests/test_install_scripts.py +++ b/setuptools/tests/test_install_scripts.py @@ -64,7 +64,8 @@ class TestInstallScripts: @pytest.mark.skipif(sys.platform == 'win32', reason='non-Windows only') def test_executable_with_spaces_escaping_unix(self, tmpdir): """ - Ensure that shebang on Unix is not quoted, even when a value with spaces + Ensure that shebang on Unix is not quoted, even when + a value with spaces is specified using --executable. """ expected = '#!%s\n' % self.unix_spaces_exe @@ -77,7 +78,8 @@ class TestInstallScripts: @pytest.mark.skipif(sys.platform != 'win32', reason='Windows only') def test_executable_arg_escaping_win32(self, tmpdir): """ - Ensure that shebang on Windows is quoted when getting a path with spaces + Ensure that shebang on Windows is quoted when + getting a path with spaces from --executable, that is itself properly quoted. """ expected = '#!"%s"\n' % self.win32_exe diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py index 3a9a6c50..e54f3209 100644 --- a/setuptools/tests/test_integration.py +++ b/setuptools/tests/test_integration.py @@ -6,6 +6,11 @@ Try to install a few packages. import glob import os import sys +import re +import subprocess +import functools +import tarfile +import zipfile from setuptools.extern.six.moves import urllib import pytest @@ -114,15 +119,12 @@ def test_pyuri(install_context): 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'] -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') +@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 @@ -149,13 +151,16 @@ def install(pkg_dir, install_dir): 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) + 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] + 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)) diff --git a/setuptools/tests/test_manifest.py b/setuptools/tests/test_manifest.py index c9533dda..2a0e9c86 100644 --- a/setuptools/tests/test_manifest.py +++ b/setuptools/tests/test_manifest.py @@ -20,8 +20,6 @@ import pytest __metaclass__ = type -py3_only = pytest.mark.xfail(six.PY2, reason="Test runs on Python 3 only") - def make_local_path(s): """Converts '/' in a string to os.sep""" @@ -75,7 +73,9 @@ translate_specs = [ # Glob matching ('*.txt', ['foo.txt', 'bar.txt'], ['foo/foo.txt']), - ('dir/*.txt', ['dir/foo.txt', 'dir/bar.txt', 'dir/.txt'], ['notdir/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']), @@ -244,77 +244,77 @@ class TestManifestTest(TempDirTestCase): def test_exclude(self): """Include everything in app/ except the text files""" - l = make_local_path + ml = make_local_path self.make_manifest( """ include app/* exclude app/*.txt """) - files = default_files | set([l('app/c.rst')]) + files = default_files | set([ml('app/c.rst')]) assert files == self.get_files() def test_include_multiple(self): """Include with multiple patterns.""" - l = make_local_path + ml = make_local_path self.make_manifest("include app/*.txt app/static/*") files = default_files | set([ - l('app/a.txt'), l('app/b.txt'), - l('app/static/app.js'), l('app/static/app.js.map'), - l('app/static/app.css'), l('app/static/app.css.map')]) + ml('app/a.txt'), ml('app/b.txt'), + ml('app/static/app.js'), ml('app/static/app.js.map'), + ml('app/static/app.css'), ml('app/static/app.css.map')]) assert files == self.get_files() def test_graft(self): """Include the whole app/static/ directory.""" - l = make_local_path + ml = make_local_path self.make_manifest("graft app/static") files = default_files | set([ - l('app/static/app.js'), l('app/static/app.js.map'), - l('app/static/app.css'), l('app/static/app.css.map')]) + ml('app/static/app.js'), ml('app/static/app.js.map'), + ml('app/static/app.css'), ml('app/static/app.css.map')]) assert files == self.get_files() def test_graft_glob_syntax(self): """Include the whole app/static/ directory.""" - l = make_local_path + ml = make_local_path self.make_manifest("graft */static") files = default_files | set([ - l('app/static/app.js'), l('app/static/app.js.map'), - l('app/static/app.css'), l('app/static/app.css.map')]) + ml('app/static/app.js'), ml('app/static/app.js.map'), + ml('app/static/app.css'), ml('app/static/app.css.map')]) assert files == self.get_files() def test_graft_global_exclude(self): """Exclude all *.map files in the project.""" - l = make_local_path + ml = make_local_path self.make_manifest( """ graft app/static global-exclude *.map """) files = default_files | set([ - l('app/static/app.js'), l('app/static/app.css')]) + ml('app/static/app.js'), ml('app/static/app.css')]) assert files == self.get_files() def test_global_include(self): """Include all *.rst, *.js, and *.css files in the whole tree.""" - l = make_local_path + ml = make_local_path self.make_manifest( """ global-include *.rst *.js *.css """) files = default_files | set([ - '.hidden.rst', 'testing.rst', l('app/c.rst'), - l('app/static/app.js'), l('app/static/app.css')]) + '.hidden.rst', 'testing.rst', ml('app/c.rst'), + ml('app/static/app.js'), ml('app/static/app.css')]) assert files == self.get_files() def test_graft_prune(self): """Include all files in app/, except for the whole app/static/ dir.""" - l = make_local_path + ml = make_local_path self.make_manifest( """ graft app prune app/static """) files = default_files | set([ - l('app/a.txt'), l('app/b.txt'), l('app/c.rst')]) + ml('app/a.txt'), ml('app/b.txt'), ml('app/c.rst')]) assert files == self.get_files() @@ -370,7 +370,7 @@ class TestFileListTest(TempDirTestCase): def test_process_template_line(self): # testing all MANIFEST.in template patterns file_list = FileList() - l = make_local_path + ml = make_local_path # simulated file list self.make_files([ @@ -378,16 +378,16 @@ class TestFileListTest(TempDirTestCase): 'buildout.cfg', # filelist does not filter out VCS directories, # it's sdist that does - l('.hg/last-message.txt'), - l('global/one.txt'), - l('global/two.txt'), - l('global/files.x'), - l('global/here.tmp'), - l('f/o/f.oo'), - l('dir/graft-one'), - l('dir/dir2/graft2'), - l('dir3/ok'), - l('dir3/sub/ok.txt'), + ml('.hg/last-message.txt'), + ml('global/one.txt'), + ml('global/two.txt'), + ml('global/files.x'), + ml('global/here.tmp'), + ml('f/o/f.oo'), + ml('dir/graft-one'), + ml('dir/dir2/graft2'), + ml('dir3/ok'), + ml('dir3/sub/ok.txt'), ]) MANIFEST_IN = DALS("""\ @@ -414,12 +414,12 @@ class TestFileListTest(TempDirTestCase): 'buildout.cfg', 'four.txt', 'ok', - l('.hg/last-message.txt'), - l('dir/graft-one'), - l('dir/dir2/graft2'), - l('f/o/f.oo'), - l('global/one.txt'), - l('global/two.txt'), + ml('.hg/last-message.txt'), + ml('dir/graft-one'), + ml('dir/dir2/graft2'), + ml('f/o/f.oo'), + ml('global/one.txt'), + ml('global/two.txt'), ] file_list.sort() @@ -476,10 +476,10 @@ class TestFileListTest(TempDirTestCase): assert False, "Should have thrown an error" def test_include(self): - l = make_local_path + ml = make_local_path # include file_list = FileList() - self.make_files(['a.py', 'b.txt', l('d/c.py')]) + self.make_files(['a.py', 'b.txt', ml('d/c.py')]) file_list.process_template_line('include *.py') file_list.sort() @@ -492,42 +492,42 @@ class TestFileListTest(TempDirTestCase): self.assertWarnings() def test_exclude(self): - l = make_local_path + ml = make_local_path # exclude file_list = FileList() - file_list.files = ['a.py', 'b.txt', l('d/c.py')] + file_list.files = ['a.py', 'b.txt', ml('d/c.py')] file_list.process_template_line('exclude *.py') file_list.sort() - assert file_list.files == ['b.txt', l('d/c.py')] + assert file_list.files == ['b.txt', ml('d/c.py')] self.assertNoWarnings() file_list.process_template_line('exclude *.rb') file_list.sort() - assert file_list.files == ['b.txt', l('d/c.py')] + assert file_list.files == ['b.txt', ml('d/c.py')] self.assertWarnings() def test_global_include(self): - l = make_local_path + ml = make_local_path # global-include file_list = FileList() - self.make_files(['a.py', 'b.txt', l('d/c.py')]) + self.make_files(['a.py', 'b.txt', ml('d/c.py')]) file_list.process_template_line('global-include *.py') file_list.sort() - assert file_list.files == ['a.py', l('d/c.py')] + assert file_list.files == ['a.py', ml('d/c.py')] self.assertNoWarnings() file_list.process_template_line('global-include *.rb') file_list.sort() - assert file_list.files == ['a.py', l('d/c.py')] + assert file_list.files == ['a.py', ml('d/c.py')] self.assertWarnings() def test_global_exclude(self): - l = make_local_path + ml = make_local_path # global-exclude file_list = FileList() - file_list.files = ['a.py', 'b.txt', l('d/c.py')] + file_list.files = ['a.py', 'b.txt', ml('d/c.py')] file_list.process_template_line('global-exclude *.py') file_list.sort() @@ -540,65 +540,65 @@ class TestFileListTest(TempDirTestCase): self.assertWarnings() def test_recursive_include(self): - l = make_local_path + ml = make_local_path # recursive-include file_list = FileList() - self.make_files(['a.py', l('d/b.py'), l('d/c.txt'), l('d/d/e.py')]) + self.make_files(['a.py', ml('d/b.py'), ml('d/c.txt'), ml('d/d/e.py')]) file_list.process_template_line('recursive-include d *.py') file_list.sort() - assert file_list.files == [l('d/b.py'), l('d/d/e.py')] + assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')] self.assertNoWarnings() file_list.process_template_line('recursive-include e *.py') file_list.sort() - assert file_list.files == [l('d/b.py'), l('d/d/e.py')] + assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')] self.assertWarnings() def test_recursive_exclude(self): - l = make_local_path + ml = make_local_path # recursive-exclude file_list = FileList() - file_list.files = ['a.py', l('d/b.py'), l('d/c.txt'), l('d/d/e.py')] + file_list.files = ['a.py', ml('d/b.py'), ml('d/c.txt'), ml('d/d/e.py')] file_list.process_template_line('recursive-exclude d *.py') file_list.sort() - assert file_list.files == ['a.py', l('d/c.txt')] + assert file_list.files == ['a.py', ml('d/c.txt')] self.assertNoWarnings() file_list.process_template_line('recursive-exclude e *.py') file_list.sort() - assert file_list.files == ['a.py', l('d/c.txt')] + assert file_list.files == ['a.py', ml('d/c.txt')] self.assertWarnings() def test_graft(self): - l = make_local_path + ml = make_local_path # graft file_list = FileList() - self.make_files(['a.py', l('d/b.py'), l('d/d/e.py'), l('f/f.py')]) + self.make_files(['a.py', ml('d/b.py'), ml('d/d/e.py'), ml('f/f.py')]) file_list.process_template_line('graft d') file_list.sort() - assert file_list.files == [l('d/b.py'), l('d/d/e.py')] + assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')] self.assertNoWarnings() file_list.process_template_line('graft e') file_list.sort() - assert file_list.files == [l('d/b.py'), l('d/d/e.py')] + assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')] self.assertWarnings() def test_prune(self): - l = make_local_path + ml = make_local_path # prune file_list = FileList() - file_list.files = ['a.py', l('d/b.py'), l('d/d/e.py'), l('f/f.py')] + file_list.files = ['a.py', ml('d/b.py'), ml('d/d/e.py'), ml('f/f.py')] file_list.process_template_line('prune d') file_list.sort() - assert file_list.files == ['a.py', l('f/f.py')] + assert file_list.files == ['a.py', ml('f/f.py')] self.assertNoWarnings() file_list.process_template_line('prune e') file_list.sort() - assert file_list.files == ['a.py', l('f/f.py')] + assert file_list.files == ['a.py', ml('f/f.py')] self.assertWarnings() diff --git a/setuptools/tests/test_msvc.py b/setuptools/tests/test_msvc.py index 32d7a907..24e38ea8 100644 --- a/setuptools/tests/test_msvc.py +++ b/setuptools/tests/test_msvc.py @@ -49,7 +49,8 @@ def mock_reg(hkcu=None, hklm=None): for k in hive if k.startswith(key.lower()) ) - return mock.patch.multiple(distutils.msvc9compiler.Reg, + return mock.patch.multiple( + distutils.msvc9compiler.Reg, read_keys=read_keys, read_values=read_values) @@ -61,7 +62,7 @@ class TestModulePatch: """ key_32 = r'software\microsoft\devdiv\vcforpython\9.0\installdir' - key_64 = r'software\wow6432node\microsoft\devdiv\vcforpython\9.0\installdir' + key_64 = key_32.replace(r'\microsoft', r'\wow6432node\microsoft') def test_patched(self): "Test the module is actually patched" diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py index da19bd79..f937d981 100644 --- a/setuptools/tests/test_namespaces.py +++ b/setuptools/tests/test_namespaces.py @@ -1,6 +1,5 @@ from __future__ import absolute_import, unicode_literals -import os import sys import subprocess @@ -12,7 +11,7 @@ from setuptools.command import test class TestNamespaces: - @pytest.mark.xfail( + @pytest.mark.skipif( sys.version_info < (3, 5), reason="Requires importlib.util.module_from_spec", ) diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py index 63b92946..ab371884 100644 --- a/setuptools/tests/test_packageindex.py +++ b/setuptools/tests/test_packageindex.py @@ -6,6 +6,8 @@ import distutils.errors from setuptools.extern import six from setuptools.extern.six.moves import urllib, http_client +import mock +import pytest import pkg_resources import setuptools.package_index @@ -42,7 +44,10 @@ class TestPackageIndex: hosts=('www.example.com',) ) - url = 'url:%20https://svn.plone.org/svn/collective/inquant.contentmirror.plone/trunk' + url = ( + 'url:%20https://svn.plone.org/svn' + '/collective/inquant.contentmirror.plone/trunk' + ) try: v = index.open_url(url) except Exception as v: @@ -61,9 +66,9 @@ class TestPackageIndex: index.opener = _urlopen url = 'http://example.com' try: - v = index.open_url(url) - except Exception as v: - assert 'line' in str(v) + index.open_url(url) + except Exception as exc: + assert 'line' in str(exc) else: raise AssertionError('Should have raise here!') @@ -81,7 +86,11 @@ class TestPackageIndex: index.open_url(url) except distutils.errors.DistutilsError as error: msg = six.text_type(error) - assert 'nonnumeric port' in msg or 'getaddrinfo failed' in msg or 'Name or service not known' in msg + assert ( + 'nonnumeric port' in msg + or 'getaddrinfo failed' in msg + or 'Name or service not known' in msg + ) return raise RuntimeError("Did not raise") @@ -223,6 +232,61 @@ class TestPackageIndex: assert dists[0].version == '' assert dists[1].version == vc + def test_download_git_with_rev(self, tmpdir): + url = 'git+https://github.example/group/project@master#egg=foo' + index = setuptools.package_index.PackageIndex() + + with mock.patch("os.system") as os_system_mock: + result = index.download(url, str(tmpdir)) + + os_system_mock.assert_called() + + expected_dir = str(tmpdir / 'project@master') + expected = ( + 'git clone --quiet ' + 'https://github.example/group/project {expected_dir}' + ).format(**locals()) + first_call_args = os_system_mock.call_args_list[0][0] + assert first_call_args == (expected,) + + tmpl = '(cd {expected_dir} && git checkout --quiet master)' + expected = tmpl.format(**locals()) + assert os_system_mock.call_args_list[1][0] == (expected,) + assert result == expected_dir + + def test_download_git_no_rev(self, tmpdir): + url = 'git+https://github.example/group/project#egg=foo' + index = setuptools.package_index.PackageIndex() + + with mock.patch("os.system") as os_system_mock: + result = index.download(url, str(tmpdir)) + + os_system_mock.assert_called() + + expected_dir = str(tmpdir / 'project') + expected = ( + 'git clone --quiet ' + 'https://github.example/group/project {expected_dir}' + ).format(**locals()) + os_system_mock.assert_called_once_with(expected) + + def test_download_svn(self, tmpdir): + url = 'svn+https://svn.example/project#egg=foo' + index = setuptools.package_index.PackageIndex() + + with pytest.warns(UserWarning): + with mock.patch("os.system") as os_system_mock: + result = index.download(url, str(tmpdir)) + + os_system_mock.assert_called() + + expected_dir = str(tmpdir / 'project') + expected = ( + 'svn checkout -q ' + 'svn+https://svn.example/project {expected_dir}' + ).format(**locals()) + os_system_mock.assert_called_once_with(expected) + class TestContentCheckers: def test_md5(self): diff --git a/setuptools/tests/test_pep425tags.py b/setuptools/tests/test_pep425tags.py index f558a0d8..30afdec7 100644 --- a/setuptools/tests/test_pep425tags.py +++ b/setuptools/tests/test_pep425tags.py @@ -32,7 +32,9 @@ class TestPEP425Tags: if sys.version_info < (3, 3): config_vars.update({'Py_UNICODE_SIZE': 2}) mock_gcf = self.mock_get_config_var(**config_vars) - with patch('setuptools.pep425tags.sysconfig.get_config_var', mock_gcf): + with patch( + 'setuptools.pep425tags.sysconfig.get_config_var', + mock_gcf): abi_tag = pep425tags.get_abi_tag() assert abi_tag == base + flags diff --git a/setuptools/tests/test_sandbox.py b/setuptools/tests/test_sandbox.py index d8675422..99398cdb 100644 --- a/setuptools/tests/test_sandbox.py +++ b/setuptools/tests/test_sandbox.py @@ -26,7 +26,8 @@ class TestSandbox: """ It should be possible to execute a setup.py with a Byte Order Mark """ - target = pkg_resources.resource_filename(__name__, + target = pkg_resources.resource_filename( + __name__, 'script-with-bom.py') namespace = types.ModuleType('namespace') setuptools.sandbox._execfile(target, vars(namespace)) diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index 02222da5..d2c4e0cf 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -20,8 +20,8 @@ from setuptools.command.egg_info import manifest_maker from setuptools.dist import Distribution from setuptools.tests import fail_on_ascii from .text import Filenames +from . import py3_only -py3_only = pytest.mark.xfail(six.PY2, reason="Test runs on Python 3 only") SETUP_ATTRS = { 'name': 'sdist_test', @@ -92,9 +92,8 @@ fail_on_latin1_encoded_filenames = pytest.mark.xfail( class TestSdistTest: def setup_method(self, method): self.temp_dir = tempfile.mkdtemp() - f = open(os.path.join(self.temp_dir, 'setup.py'), 'w') - f.write(SETUP_PY) - f.close() + with open(os.path.join(self.temp_dir, 'setup.py'), 'w') as f: + f.write(SETUP_PY) # Set up the rest of the test package test_pkg = os.path.join(self.temp_dir, 'sdist_test') @@ -135,6 +134,47 @@ class TestSdistTest: assert os.path.join('sdist_test', 'c.rst') not in manifest assert os.path.join('d', 'e.dat') in manifest + def test_setup_py_exists(self): + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'foo.py' + cmd = sdist(dist) + cmd.ensure_finalized() + + with quiet(): + cmd.run() + + manifest = cmd.filelist.files + assert 'setup.py' in manifest + + def test_setup_py_missing(self): + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'foo.py' + cmd = sdist(dist) + cmd.ensure_finalized() + + if os.path.exists("setup.py"): + os.remove("setup.py") + with quiet(): + cmd.run() + + manifest = cmd.filelist.files + assert 'setup.py' not in manifest + + def test_setup_py_excluded(self): + with open("MANIFEST.in", "w") as manifest_file: + manifest_file.write("exclude setup.py") + + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'foo.py' + cmd = sdist(dist) + cmd.ensure_finalized() + + with quiet(): + cmd.run() + + manifest = cmd.filelist.files + assert 'setup.py' not in manifest + def test_defaults_case_sensitivity(self): """ Make sure default files (README.*, etc.) are added in a case-sensitive diff --git a/setuptools/tests/test_setuptools.py b/setuptools/tests/test_setuptools.py index 7aae3a16..5896a69a 100644 --- a/setuptools/tests/test_setuptools.py +++ b/setuptools/tests/test_setuptools.py @@ -77,7 +77,8 @@ class TestDepends: 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__ + assert dep.get_module_constant( + 'setuptools.tests.test_setuptools', '__doc__') == __doc__ @needs_bytecode def testRequire(self): @@ -216,7 +217,8 @@ class TestFeatures: self.req = Require('Distutils', '1.0.3', 'distutils') self.dist = makeSetup( features={ - 'foo': Feature("foo", standard=True, require_features=['baz', self.req]), + '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'], ), @@ -252,7 +254,8 @@ class TestFeatures: ('with-dwim', None, 'include DWIM') in dist.feature_options ) assert ( - ('without-dwim', None, 'exclude DWIM (default)') in dist.feature_options + ('without-dwim', None, 'exclude DWIM (default)') + in dist.feature_options ) assert ( ('with-bar', None, 'include bar (default)') in dist.feature_options diff --git a/setuptools/tests/test_test.py b/setuptools/tests/test_test.py index 960527bc..faaa6ba9 100644 --- a/setuptools/tests/test_test.py +++ b/setuptools/tests/test_test.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals from distutils import log import os -import sys import pytest @@ -93,10 +92,6 @@ def test_test(capfd): assert out == 'Foo\n' -@pytest.mark.xfail( - sys.version_info < (2, 7), - reason="No discover support for unittest on Python 2.6", -) @pytest.mark.usefixtures('tmpdir_cwd', 'quiet_log') def test_tests_are_run_once(capfd): params = dict( diff --git a/setuptools/tests/test_upload.py b/setuptools/tests/test_upload.py index 95a8d16b..320c6959 100644 --- a/setuptools/tests/test_upload.py +++ b/setuptools/tests/test_upload.py @@ -1,13 +1,100 @@ import mock +import os +import re + from distutils import log +from distutils.errors import DistutilsError import pytest from setuptools.command.upload import upload from setuptools.dist import Distribution +from setuptools.extern import six + + +def _parse_upload_body(body): + boundary = u'\r\n----------------GHSKFJDLGDS7543FJKLFHRE75642756743254' + entries = [] + name_re = re.compile(u'^Content-Disposition: form-data; name="([^\"]+)"') + + for entry in body.split(boundary): + pair = entry.split(u'\r\n\r\n') + if not len(pair) == 2: + continue + + key, value = map(six.text_type.strip, pair) + m = name_re.match(key) + if m is not None: + key = m.group(1) + + entries.append((key, value)) + + return entries + + +@pytest.fixture +def patched_upload(tmpdir): + class Fix: + def __init__(self, cmd, urlopen): + self.cmd = cmd + self.urlopen = urlopen + + def __iter__(self): + return iter((self.cmd, self.urlopen)) + + def get_uploaded_metadata(self): + request = self.urlopen.call_args_list[0][0][0] + body = request.data.decode('utf-8') + entries = dict(_parse_upload_body(body)) + + return entries + + class ResponseMock(mock.Mock): + def getheader(self, name, default=None): + """Mocked getheader method for response object""" + return { + 'content-type': 'text/plain; charset=utf-8', + }.get(name.lower(), default) + + with mock.patch('setuptools.command.upload.urlopen') as urlopen: + urlopen.return_value = ResponseMock() + urlopen.return_value.getcode.return_value = 200 + urlopen.return_value.read.return_value = b'' + + content = os.path.join(str(tmpdir), "content_data") + + with open(content, 'w') as f: + f.write("Some content") + + dist = Distribution() + dist.dist_files = [('sdist', '3.7.0', content)] + + cmd = upload(dist) + cmd.announce = mock.Mock() + cmd.username = 'user' + cmd.password = 'hunter2' + + yield Fix(cmd, urlopen) class TestUploadTest: + def test_upload_metadata(self, patched_upload): + cmd, patch = patched_upload + + # Set the metadata version to 2.1 + cmd.distribution.metadata.metadata_version = '2.1' + + # Run the command + cmd.ensure_finalized() + cmd.run() + + # Make sure we did the upload + patch.assert_called_once() + + # Make sure the metadata version is correct in the headers + entries = patched_upload.get_uploaded_metadata() + assert entries['metadata_version'] == '2.1' + def test_warns_deprecation(self): dist = Distribution() dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())] @@ -41,3 +128,86 @@ class TestUploadTest: "upload instead (https://pypi.org/p/twine/)", log.WARN ) + + @pytest.mark.parametrize('url', [ + 'https://example.com/a;parameter', # Has parameters + 'https://example.com/a?query', # Has query + 'https://example.com/a#fragment', # Has fragment + 'ftp://example.com', # Invalid scheme + + ]) + def test_upload_file_invalid_url(self, url, patched_upload): + patched_upload.urlopen.side_effect = Exception("Should not be reached") + + cmd = patched_upload.cmd + cmd.repository = url + + cmd.ensure_finalized() + with pytest.raises(AssertionError): + cmd.run() + + def test_upload_file_http_error(self, patched_upload): + patched_upload.urlopen.side_effect = six.moves.urllib.error.HTTPError( + 'https://example.com', + 404, + 'File not found', + None, + None + ) + + cmd = patched_upload.cmd + cmd.ensure_finalized() + + with pytest.raises(DistutilsError): + cmd.run() + + cmd.announce.assert_any_call( + 'Upload failed (404): File not found', + log.ERROR) + + def test_upload_file_os_error(self, patched_upload): + patched_upload.urlopen.side_effect = OSError("Invalid") + + cmd = patched_upload.cmd + cmd.ensure_finalized() + + with pytest.raises(OSError): + cmd.run() + + cmd.announce.assert_any_call('Invalid', log.ERROR) + + @mock.patch('setuptools.command.upload.spawn') + def test_upload_file_gpg(self, spawn, patched_upload): + cmd, urlopen = patched_upload + + cmd.sign = True + cmd.identity = "Alice" + cmd.dry_run = True + content_fname = cmd.distribution.dist_files[0][2] + signed_file = content_fname + '.asc' + + with open(signed_file, 'wb') as f: + f.write("signed-data".encode('utf-8')) + + cmd.ensure_finalized() + cmd.run() + + # Make sure that GPG was called + spawn.assert_called_once_with([ + "gpg", "--detach-sign", "--local-user", "Alice", "-a", + content_fname + ], dry_run=True) + + # Read the 'signed' data that was transmitted + entries = patched_upload.get_uploaded_metadata() + assert entries['gpg_signature'] == 'signed-data' + + def test_show_response_no_error(self, patched_upload): + # This test is just that show_response doesn't throw an error + # It is not really important what the printed response looks like + # in a deprecated command, but we don't want to introduce new + # errors when importing this function from distutils + + patched_upload.cmd.show_response = True + patched_upload.cmd.ensure_finalized() + patched_upload.cmd.run() diff --git a/setuptools/tests/test_virtualenv.py b/setuptools/tests/test_virtualenv.py index b66a311d..3d5c84b0 100644 --- a/setuptools/tests/test_virtualenv.py +++ b/setuptools/tests/test_virtualenv.py @@ -57,9 +57,6 @@ def test_pip_upgrade_from_source(virtualenv): Check pip can upgrade setuptools from source. """ dist_dir = virtualenv.workspace - if sys.version_info < (2, 7): - # Python 2.6 support was dropped in wheel 0.30.0. - virtualenv.run('pip install -U "wheel<0.30.0"') # Generate source distribution / wheel. virtualenv.run(' && '.join(( 'cd {source}', @@ -137,3 +134,14 @@ def test_test_command_install_requirements(bare_virtualenv, tmpdir): 'python setup.py test -s test', )).format(tmpdir=tmpdir)) assert tmpdir.join('success').check() + + +def test_no_missing_dependencies(bare_virtualenv): + """ + Quick and dirty test to ensure all external dependencies are vendored. + """ + for command in ('upload',): # sorted(distutils.command.__all__): + bare_virtualenv.run(' && '.join(( + 'cd {source}', + 'python setup.py {command} -h', + )).format(command=command, source=SOURCE_DIR)) diff --git a/setuptools/tests/test_wheel.py b/setuptools/tests/test_wheel.py index 6db5fa11..e85a4a7e 100644 --- a/setuptools/tests/test_wheel.py +++ b/setuptools/tests/test_wheel.py @@ -63,6 +63,7 @@ WHEEL_INFO_TESTS = ( }), ) + @pytest.mark.parametrize( ('filename', 'info'), WHEEL_INFO_TESTS, ids=[t[0] for t in WHEEL_INFO_TESTS] @@ -487,6 +488,7 @@ WHEEL_INSTALL_TESTS = ( ) + @pytest.mark.parametrize( 'params', WHEEL_INSTALL_TESTS, ids=list(params['id'] for params in WHEEL_INSTALL_TESTS), diff --git a/setuptools/tests/test_windows_wrappers.py b/setuptools/tests/test_windows_wrappers.py index d2871c0f..2553394a 100644 --- a/setuptools/tests/test_windows_wrappers.py +++ b/setuptools/tests/test_windows_wrappers.py @@ -97,7 +97,8 @@ class TestCLI(WrapperTester): 'arg 4\\', 'arg5 a\\\\b', ] - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE) + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE) stdout, stderr = proc.communicate('hello\nworld\n'.encode('ascii')) actual = stdout.decode('ascii').replace('\r\n', '\n') expected = textwrap.dedent(r""" @@ -134,7 +135,11 @@ class TestCLI(WrapperTester): with (tmpdir / 'foo-script.py').open('w') as f: f.write(self.prep_script(tmpl)) cmd = [str(tmpdir / 'foo.exe')] - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) + proc = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + stderr=subprocess.STDOUT) stdout, stderr = proc.communicate() actual = stdout.decode('ascii').replace('\r\n', '\n') expected = textwrap.dedent(r""" @@ -172,7 +177,9 @@ class TestGUI(WrapperTester): str(tmpdir / 'test_output.txt'), 'Test Argument', ] - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, + stderr=subprocess.STDOUT) stdout, stderr = proc.communicate() assert not stdout assert not stderr |