diff options
54 files changed, 1593 insertions, 1692 deletions
@@ -179,3 +179,7 @@ fa069bf2411a150c9379d31a04d1c3836e2d3027 9.0.1 0c4d18a747a6d39bff8e194a58af949a960d674a 10.0 4c41e2cdd70beb0da556d71f46a67734c14f2bc2 10.0.1 26b00011ec65b8f7b4f3d51078ec0a694701a45c 10.1 +651d41db58849d4fc50e466f4dc458d448480c4e 10.2 +1f5de53c079d577ead9d80265c9e006503b16457 10.2.1 +b4b92805bc0e9802da0b597d00df4fa42b30bc40 11.0 +6cd2b18f4be2a9c188fa505b34505b32f4a4554b 11.1 diff --git a/.travis.yml b/.travis.yml index 5f644e00..0e648b38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,8 @@ python: - pypy # command to run tests script: - # invoke bootstrap and override egg_info based on setup.py in checkout + # update egg_info based on setup.py in checkout - python bootstrap.py - - python setup.py egg_info - - python setup.py ptr - - python ez_setup.py --version 7.0 + - python setup.py ptr --addopts='-rs' + - python ez_setup.py --version 10.2.1 diff --git a/CHANGES.txt b/CHANGES.txt index efbb43f8..8fc9e981 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -3,10 +3,36 @@ CHANGES ======= ---- -next +11.1 +---- + +* Issue #281: Since Setuptools 6.1 (Issue #268), a ValueError would be raised + in certain cases where VersionConflict was raised with two arguments, which + occurred in ``pkg_resources.WorkingSet.find``. This release adds support + for indicating the dependent packages while maintaining support for + a VersionConflict when no dependent package context is known. New unit tests + now capture the expected interface. + +---- +11.0 +---- + +* Interop #3: Upgrade to Packaging 15.0; updates to PEP 440 so that >1.7 does + not exclude 1.7.1 but does exclude 1.7.0 and 1.7.0.post1. + +------ +10.2.1 +------ + +* Issue #323: Fix regression in entry point name parsing. + +---- +10.2 ---- * Deprecated use of EntryPoint.load(require=False). +* Substantial refactoring of all unit tests. Tests are now much leaner and + re-use a lot of fixtures and contexts for better clarity of purpose. ---- 10.1 diff --git a/MANIFEST.in b/MANIFEST.in index f6caf61a..ed60948b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,6 @@ -recursive-include setuptools *.py *.txt *.exe *.xml -recursive-include tests *.py *.c *.pyx *.txt -recursive-include setuptools/tests *.html entries* -recursive-include setuptools/tests/svn_data *.zip +recursive-include setuptools *.py *.exe *.xml +recursive-include tests *.py *.c *.pyx +recursive-include setuptools/tests *.html recursive-include docs *.py *.txt *.conf *.css *.css_t Makefile indexsidebar.html recursive-include _markerlib *.py recursive-include pkg_resources *.py *.txt diff --git a/bootstrap.py b/bootstrap.py index cbc1ca9d..70f96258 100644 --- a/bootstrap.py +++ b/bootstrap.py @@ -28,8 +28,10 @@ minimal_egg_info = textwrap.dedent(""" """) def ensure_egg_info(): - if not os.path.exists('setuptools.egg-info'): - build_egg_info() + if os.path.exists('setuptools.egg-info'): + return + print("adding minimal entry_points") + build_egg_info() def build_egg_info(): @@ -43,7 +45,11 @@ def build_egg_info(): def run_egg_info(): - subprocess.check_call([sys.executable, 'setup.py', 'egg_info']) + cmd = [sys.executable, 'setup.py', 'egg_info'] + print("Regenerating egg_info") + subprocess.check_call(cmd) + print("...and again.") + subprocess.check_call(cmd) if __name__ == '__main__': diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..a513bb9e --- /dev/null +++ b/conftest.py @@ -0,0 +1 @@ +pytest_plugins = 'setuptools.tests.fixtures' diff --git a/ez_setup.py b/ez_setup.py index 62ead23a..388ff490 100644 --- a/ez_setup.py +++ b/ez_setup.py @@ -36,7 +36,7 @@ try: except ImportError: USER_SITE = None -DEFAULT_VERSION = "10.2" +DEFAULT_VERSION = "11.2" DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" def _python_cmd(*args): @@ -14,6 +14,7 @@ link_patterns = [ r"Old Setuptools #(?P<old_setuptools>\d+)", r"Jython #(?P<jython>\d+)", r"Python #(?P<python>\d+)", + r"Interop #(?P<interop>\d+)", ] issue_urls = dict( @@ -25,6 +26,7 @@ issue_urls = dict( old_setuptools='http://bugs.python.org/setuptools/issue{old_setuptools}', jython='http://bugs.jython.org/issue{jython}', python='http://bugs.python.org/issue{python}', + interop='https://github.com/pypa/interoperability-peps/issues/{interop}', ) diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 0e6223de..b31ae532 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -52,6 +52,8 @@ if PY3: else: string_types = str, eval('unicode') +iteritems = (lambda i: i.items()) if PY3 else lambda i: i.iteritems() + # capture these to bypass sandboxing from os import utime try: @@ -315,8 +317,51 @@ class ResolutionError(Exception): def __repr__(self): return self.__class__.__name__+repr(self.args) + class VersionConflict(ResolutionError): - """An already-installed version conflicts with the requested version""" + """ + An already-installed version conflicts with the requested version. + + Should be initialized with the installed Distribution and the requested + Requirement. + """ + + _template = "{self.dist} is installed but {self.req} is required" + + @property + def dist(self): + return self.args[0] + + @property + def req(self): + return self.args[1] + + def report(self): + return self._template.format(**locals()) + + def with_context(self, required_by): + """ + If required_by is non-empty, return a version of self that is a + ContextualVersionConflict. + """ + if not required_by: + return self + args = self.args + (required_by,) + return ContextualVersionConflict(*args) + + +class ContextualVersionConflict(VersionConflict): + """ + A VersionConflict that accepts a third parameter, the set of the + requirements that required the installed Distribution. + """ + + _template = VersionConflict._template + ' by {self.required_by}' + + @property + def required_by(self): + return self.args[2] + class DistributionNotFound(ResolutionError): """A requested distribution was not found""" @@ -625,8 +670,7 @@ class WorkingSet(object): if dist is not None and dist not in req: # XXX add more info raise VersionConflict(dist, req) - else: - return dist + return dist def iter_entry_points(self, group, name=None): """Yield entry point objects from `group` matching `name` @@ -762,9 +806,8 @@ class WorkingSet(object): to_activate.append(dist) if dist not in req: # Oops, the "best" so far conflicts with a dependency - tmpl = "%s is installed but %s is required by %s" - args = dist, req, list(required_by.get(req, [])) - raise VersionConflict(tmpl % args) + dependent_req = required_by[req] + raise VersionConflict(dist, req).with_context(dependent_req) # push the new requirements onto the stack new_requirements = dist.requires(req.extras)[::-1] @@ -841,8 +884,7 @@ class WorkingSet(object): try: resolvees = shadow_set.resolve(req, env, installer) - except ResolutionError: - v = sys.exc_info()[1] + except ResolutionError as v: # save error info error_info[dist] = v if fallback: @@ -1338,8 +1380,8 @@ class MarkerEvaluation(object): """ try: cls.evaluate_marker(text) - except SyntaxError: - return cls.normalize_exception(sys.exc_info()[1]) + except SyntaxError as e: + return cls.normalize_exception(e) return False @staticmethod @@ -1454,8 +1496,7 @@ class MarkerEvaluation(object): env[new_key] = env.pop(key) try: result = _markerlib.interpret(text, env) - except NameError: - e = sys.exc_info()[1] + except NameError as e: raise SyntaxError(e.args[0]) return result @@ -2281,7 +2322,7 @@ class EntryPoint(object): pattern = re.compile( r'\s*' - r'(?P<name>[\w. -]+?)\s*' + r'(?P<name>[+\w. -]+?)\s*' r'=\s*' r'(?P<module>[\w.]+)\s*' r'(:\s*(?P<attr>[\w.]+))?\s*' diff --git a/pkg_resources/_vendor/packaging/__about__.py b/pkg_resources/_vendor/packaging/__about__.py index d3e50f1e..36f1a35c 100644 --- a/pkg_resources/_vendor/packaging/__about__.py +++ b/pkg_resources/_vendor/packaging/__about__.py @@ -22,7 +22,7 @@ __title__ = "packaging" __summary__ = "Core utilities for Python packages" __uri__ = "https://github.com/pypa/packaging" -__version__ = "14.5" +__version__ = "15.0" __author__ = "Donald Stufft" __email__ = "donald@stufft.io" diff --git a/pkg_resources/_vendor/packaging/specifiers.py b/pkg_resources/_vendor/packaging/specifiers.py index 80225786..9ad0a635 100644 --- a/pkg_resources/_vendor/packaging/specifiers.py +++ b/pkg_resources/_vendor/packaging/specifiers.py @@ -458,21 +458,59 @@ class Specifier(_IndividualSpecifier): @_require_version_compare def _compare_less_than(self, prospective, spec): - # Less than are defined as exclusive operators, this implies that - # pre-releases do not match for the same series as the spec. This is - # implemented by making <V imply !=V.*. + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. spec = Version(spec) - return (prospective < spec - and self._get_operator("!=")(prospective, str(spec) + ".*")) + + # Check to see if the prospective version is less than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective < spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a pre-release version, that we do not accept pre-release + # versions for the version mentioned in the specifier (e.g. <3.1 should + # not match 3.1.dev0, but should match 3.0.dev0). + if not spec.is_prerelease and prospective.is_prerelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # less than the spec version *and* it's not a pre-release of the same + # version in the spec. + return True @_require_version_compare def _compare_greater_than(self, prospective, spec): - # Greater than are defined as exclusive operators, this implies that - # pre-releases do not match for the same series as the spec. This is - # implemented by making >V imply !=V.*. + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. spec = Version(spec) - return (prospective > spec - and self._get_operator("!=")(prospective, str(spec) + ".*")) + + # Check to see if the prospective version is greater than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective > spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a post-release version, that we do not accept + # post-release versions for the version mentioned in the specifier + # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). + if not spec.is_postrelease and prospective.is_postrelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # Ensure that we do not allow a local version of the version mentioned + # in the specifier, which is techincally greater than, to match. + if prospective.local is not None: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # greater than the spec version *and* it's not a pre-release of the + # same version in the spec. + return True def _compare_arbitrary(self, prospective, spec): return str(prospective).lower() == str(spec).lower() diff --git a/pkg_resources/_vendor/packaging/version.py b/pkg_resources/_vendor/packaging/version.py index 8d779a48..cf8afb16 100644 --- a/pkg_resources/_vendor/packaging/version.py +++ b/pkg_resources/_vendor/packaging/version.py @@ -96,6 +96,10 @@ class LegacyVersion(_BaseVersion): return self._version @property + def base_version(self): + return self._version + + @property def local(self): return None @@ -103,6 +107,10 @@ class LegacyVersion(_BaseVersion): def is_prerelease(self): return False + @property + def is_postrelease(self): + return False + _legacy_version_component_re = re.compile( r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE, @@ -270,6 +278,19 @@ class Version(_BaseVersion): return str(self).split("+", 1)[0] @property + def base_version(self): + parts = [] + + # Epoch + if self._version.epoch != 0: + parts.append("{0}!".format(self._version.epoch)) + + # Release segment + parts.append(".".join(str(x) for x in self._version.release)) + + return "".join(parts) + + @property def local(self): version_string = str(self) if "+" in version_string: @@ -279,6 +300,10 @@ class Version(_BaseVersion): def is_prerelease(self): return bool(self._version.dev or self._version.pre) + @property + def is_postrelease(self): + return bool(self._version.post) + def _parse_letter_version(letter, number): if letter: diff --git a/pkg_resources/_vendor/vendored.txt b/pkg_resources/_vendor/vendored.txt index 723e026b..75a31670 100644 --- a/pkg_resources/_vendor/vendored.txt +++ b/pkg_resources/_vendor/vendored.txt @@ -1 +1 @@ -packaging==14.5 +packaging==15.0 diff --git a/pkg_resources/api_tests.txt b/pkg_resources/api_tests.txt index 50e04b87..a6c25a37 100644 --- a/pkg_resources/api_tests.txt +++ b/pkg_resources/api_tests.txt @@ -210,8 +210,7 @@ working set triggers a ``pkg_resources.VersionConflict`` error: >>> try: ... ws.find(Requirement.parse("Bar==1.0")) - ... except pkg_resources.VersionConflict: - ... exc = sys.exc_info()[1] + ... except pkg_resources.VersionConflict as exc: ... print(str(exc)) ... else: ... raise AssertionError("VersionConflict was not raised") diff --git a/pkg_resources/tests/__init__.py b/pkg_resources/tests/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/pkg_resources/tests/__init__.py diff --git a/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py index 564d7cec..564d7cec 100644 --- a/tests/test_pkg_resources.py +++ b/pkg_resources/tests/test_pkg_resources.py diff --git a/setuptools/tests/test_resources.py b/pkg_resources/tests/test_resources.py index f9f2e459..f252ddff 100644 --- a/setuptools/tests/test_resources.py +++ b/pkg_resources/tests/test_resources.py @@ -1,12 +1,9 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# NOTE: the shebang and encoding lines are for ScriptHeaderTests do not remove - import os import sys import tempfile import shutil -from unittest import TestCase + +import pytest import pkg_resources from pkg_resources import (parse_requirements, VersionConflict, parse_version, @@ -15,10 +12,6 @@ from pkg_resources import (parse_requirements, VersionConflict, parse_version, packaging = pkg_resources.packaging -from setuptools.command.easy_install import (get_script_header, is_sh, - nt_quote_arg) -from setuptools.compat import StringIO, iteritems, PY3 -from .py26compat import skipIf def safe_repr(obj, short=False): """ copied from Python2.7""" @@ -30,53 +23,51 @@ def safe_repr(obj, short=False): return result return result[:pkg_resources._MAX_LENGTH] + ' [truncated]...' + class Metadata(pkg_resources.EmptyProvider): """Mock object to return metadata as if from an on-disk distribution""" - def __init__(self,*pairs): + def __init__(self, *pairs): self.metadata = dict(pairs) - def has_metadata(self,name): + def has_metadata(self, name): return name in self.metadata - def get_metadata(self,name): + def get_metadata(self, name): return self.metadata[name] - def get_metadata_lines(self,name): + def get_metadata_lines(self, name): return pkg_resources.yield_lines(self.get_metadata(name)) + dist_from_fn = pkg_resources.Distribution.from_filename -class DistroTests(TestCase): +class TestDistro: def testCollection(self): # empty path should produce no distributions ad = pkg_resources.Environment([], platform=None, python=None) - self.assertEqual(list(ad), []) - self.assertEqual(ad['FooPkg'],[]) + assert list(ad) == [] + assert ad['FooPkg'] == [] ad.add(dist_from_fn("FooPkg-1.3_1.egg")) ad.add(dist_from_fn("FooPkg-1.4-py2.4-win32.egg")) ad.add(dist_from_fn("FooPkg-1.2-py2.4.egg")) # Name is in there now - self.assertTrue(ad['FooPkg']) + assert ad['FooPkg'] # But only 1 package - self.assertEqual(list(ad), ['foopkg']) + assert list(ad) == ['foopkg'] # Distributions sort by version - self.assertEqual( - [dist.version for dist in ad['FooPkg']], ['1.4','1.3-1','1.2'] - ) + assert [dist.version for dist in ad['FooPkg']] == ['1.4','1.3-1','1.2'] + # Removing a distribution leaves sequence alone ad.remove(ad['FooPkg'][1]) - self.assertEqual( - [dist.version for dist in ad['FooPkg']], ['1.4','1.2'] - ) + assert [dist.version for dist in ad['FooPkg']] == ['1.4','1.2'] + # And inserting adds them in order ad.add(dist_from_fn("FooPkg-1.9.egg")) - self.assertEqual( - [dist.version for dist in ad['FooPkg']], ['1.9','1.4','1.2'] - ) + assert [dist.version for dist in ad['FooPkg']] == ['1.9','1.4','1.2'] ws = WorkingSet([]) foo12 = dist_from_fn("FooPkg-1.2-py2.4.egg") @@ -84,31 +75,32 @@ class DistroTests(TestCase): req, = parse_requirements("FooPkg>=1.3") # Nominal case: no distros on path, should yield all applicable - self.assertEqual(ad.best_match(req,ws).version, '1.9') + assert ad.best_match(req, ws).version == '1.9' # If a matching distro is already installed, should return only that ws.add(foo14) - self.assertEqual(ad.best_match(req,ws).version, '1.4') + assert ad.best_match(req, ws).version == '1.4' # If the first matching distro is unsuitable, it's a version conflict ws = WorkingSet([]) ws.add(foo12) ws.add(foo14) - self.assertRaises(VersionConflict, ad.best_match, req, ws) + with pytest.raises(VersionConflict): + ad.best_match(req, ws) # If more than one match on the path, the first one takes precedence ws = WorkingSet([]) ws.add(foo14) ws.add(foo12) ws.add(foo14) - self.assertEqual(ad.best_match(req,ws).version, '1.4') + assert ad.best_match(req, ws).version == '1.4' def checkFooPkg(self,d): - self.assertEqual(d.project_name, "FooPkg") - self.assertEqual(d.key, "foopkg") - self.assertEqual(d.version, "1.3.post1") - self.assertEqual(d.py_version, "2.4") - self.assertEqual(d.platform, "win32") - self.assertEqual(d.parsed_version, parse_version("1.3-1")) + assert d.project_name == "FooPkg" + assert d.key == "foopkg" + assert d.version == "1.3.post1" + assert d.py_version == "2.4" + assert d.platform == "win32" + assert d.parsed_version == parse_version("1.3-1") def testDistroBasics(self): d = Distribution( @@ -118,8 +110,8 @@ class DistroTests(TestCase): self.checkFooPkg(d) d = Distribution("/some/path") - self.assertEqual(d.py_version, sys.version[:3]) - self.assertEqual(d.platform, None) + assert d.py_version == sys.version[:3] + assert d.platform == None def testDistroParse(self): d = dist_from_fn("FooPkg-1.3.post1-py2.4-win32.egg") @@ -140,10 +132,7 @@ class DistroTests(TestCase): return Distribution("/foo", metadata=Metadata(('depends.txt', txt))) def checkRequires(self, dist, txt, extras=()): - self.assertEqual( - list(dist.requires(extras)), - list(parse_requirements(txt)) - ) + assert list(dist.requires(extras)) == list(parse_requirements(txt)) def testDistroDependsSimple(self): for v in "Twisted>=1.5", "Twisted>=1.5\nZConfig>=2.0": @@ -153,11 +142,11 @@ class DistroTests(TestCase): ad = pkg_resources.Environment([]) ws = WorkingSet([]) # Resolving no requirements -> nothing to install - self.assertEqual(list(ws.resolve([],ad)), []) + assert list(ws.resolve([], ad)) == [] # Request something not in the collection -> DistributionNotFound - self.assertRaises( - pkg_resources.DistributionNotFound, ws.resolve, parse_requirements("Foo"), ad - ) + with pytest.raises(pkg_resources.DistributionNotFound): + ws.resolve(parse_requirements("Foo"), ad) + Foo = Distribution.from_filename( "/foo_dir/Foo-1.2.egg", metadata=Metadata(('depends.txt', "[bar]\nBaz>=2.0")) @@ -168,28 +157,28 @@ class DistroTests(TestCase): # Request thing(s) that are available -> list to activate for i in range(3): targets = list(ws.resolve(parse_requirements("Foo"), ad)) - self.assertEqual(targets, [Foo]) + assert targets == [Foo] list(map(ws.add,targets)) - self.assertRaises(VersionConflict, ws.resolve, - parse_requirements("Foo==0.9"), ad) + with pytest.raises(VersionConflict): + ws.resolve(parse_requirements("Foo==0.9"), ad) ws = WorkingSet([]) # reset # Request an extra that causes an unresolved dependency for "Baz" - self.assertRaises( - pkg_resources.DistributionNotFound, ws.resolve,parse_requirements("Foo[bar]"), ad - ) + with pytest.raises(pkg_resources.DistributionNotFound): + ws.resolve(parse_requirements("Foo[bar]"), ad) Baz = Distribution.from_filename( "/foo_dir/Baz-2.1.egg", metadata=Metadata(('depends.txt', "Foo")) ) ad.add(Baz) # Activation list now includes resolved dependency - self.assertEqual( - list(ws.resolve(parse_requirements("Foo[bar]"), ad)), [Foo,Baz] - ) + assert list(ws.resolve(parse_requirements("Foo[bar]"), ad)) ==[Foo,Baz] # Requests for conflicting versions produce VersionConflict - self.assertRaises(VersionConflict, - ws.resolve, parse_requirements("Foo==1.2\nFoo!=1.2"), ad) + with pytest.raises(VersionConflict) as vc: + ws.resolve(parse_requirements("Foo==1.2\nFoo!=1.2"), ad) + + msg = 'Foo 0.9 is installed but Foo==1.2 is required' + assert vc.value.report() == msg def testDistroDependsOptions(self): d = self.distRequires(""" @@ -214,49 +203,96 @@ class DistroTests(TestCase): d,"Twisted>=1.5 fcgiapp>=0.1 ZConfig>=2.0 docutils>=0.3".split(), ["fastcgi", "docgen"] ) - self.assertRaises(pkg_resources.UnknownExtra, d.requires, ["foo"]) + with pytest.raises(pkg_resources.UnknownExtra): + d.requires(["foo"]) -class EntryPointTests(TestCase): +class TestWorkingSet: + def test_find_conflicting(self): + ws = WorkingSet([]) + Foo = Distribution.from_filename("/foo_dir/Foo-1.2.egg") + ws.add(Foo) - def assertfields(self, ep): - self.assertEqual(ep.name,"foo") - self.assertEqual(ep.module_name,"setuptools.tests.test_resources") - self.assertEqual(ep.attrs, ("EntryPointTests",)) - self.assertEqual(ep.extras, ("x",)) - self.assertTrue(ep.load() is EntryPointTests) - self.assertEqual( - str(ep), - "foo = setuptools.tests.test_resources:EntryPointTests [x]" - ) + # create a requirement that conflicts with Foo 1.2 + req = next(parse_requirements("Foo<1.2")) + + with pytest.raises(VersionConflict) as vc: + ws.find(req) + + msg = 'Foo 1.2 is installed but Foo<1.2 is required' + assert vc.value.report() == msg + + def test_resolve_conflicts_with_prior(self): + """ + A ContextualVersionConflict should be raised when a requirement + conflicts with a prior requirement for a different package. + """ + # Create installation where Foo depends on Baz 1.0 and Bar depends on + # Baz 2.0. + ws = WorkingSet([]) + md = Metadata(('depends.txt', "Baz==1.0")) + Foo = Distribution.from_filename("/foo_dir/Foo-1.0.egg", metadata=md) + ws.add(Foo) + md = Metadata(('depends.txt', "Baz==2.0")) + Bar = Distribution.from_filename("/foo_dir/Bar-1.0.egg", metadata=md) + ws.add(Bar) + Baz = Distribution.from_filename("/foo_dir/Baz-1.0.egg") + ws.add(Baz) + Baz = Distribution.from_filename("/foo_dir/Baz-2.0.egg") + ws.add(Baz) - def setUp(self): + with pytest.raises(VersionConflict) as vc: + ws.resolve(parse_requirements("Foo\nBar\n")) + + msg = "Baz 1.0 is installed but Baz==2.0 is required by {'Bar'}" + if pkg_resources.PY2: + msg = msg.replace("{'Bar'}", "set(['Bar'])") + assert vc.value.report() == msg + + +class TestEntryPoints: + + def assertfields(self, ep): + assert ep.name == "foo" + assert ep.module_name == "pkg_resources.tests.test_resources" + assert ep.attrs == ("TestEntryPoints",) + assert ep.extras == ("x",) + assert ep.load() is TestEntryPoints + expect = "foo = pkg_resources.tests.test_resources:TestEntryPoints [x]" + assert str(ep) == expect + + def setup_method(self, method): self.dist = Distribution.from_filename( "FooPkg-1.2-py2.4.egg", metadata=Metadata(('requires.txt','[x]'))) def testBasics(self): ep = EntryPoint( - "foo", "setuptools.tests.test_resources", ["EntryPointTests"], + "foo", "pkg_resources.tests.test_resources", ["TestEntryPoints"], ["x"], self.dist ) self.assertfields(ep) def testParse(self): - s = "foo = setuptools.tests.test_resources:EntryPointTests [x]" + s = "foo = pkg_resources.tests.test_resources:TestEntryPoints [x]" ep = EntryPoint.parse(s, self.dist) self.assertfields(ep) ep = EntryPoint.parse("bar baz= spammity[PING]") - self.assertEqual(ep.name,"bar baz") - self.assertEqual(ep.module_name,"spammity") - self.assertEqual(ep.attrs, ()) - self.assertEqual(ep.extras, ("ping",)) + assert ep.name == "bar baz" + assert ep.module_name == "spammity" + assert ep.attrs == () + assert ep.extras == ("ping",) ep = EntryPoint.parse(" fizzly = wocka:foo") - self.assertEqual(ep.name,"fizzly") - self.assertEqual(ep.module_name,"wocka") - self.assertEqual(ep.attrs, ("foo",)) - self.assertEqual(ep.extras, ()) + assert ep.name == "fizzly" + assert ep.module_name == "wocka" + assert ep.attrs == ("foo",) + assert ep.extras == () + + # plus in the name + spec = "html+mako = mako.ext.pygmentplugin:MakoHtmlLexer" + ep = EntryPoint.parse(spec) + assert ep.name == 'html+mako' def testRejects(self): for ep in [ @@ -267,9 +303,9 @@ class EntryPointTests(TestCase): else: raise AssertionError("Should've been bad", ep) def checkSubMap(self, m): - self.assertEqual(len(m), len(self.submap_expect)) - for key, ep in iteritems(self.submap_expect): - self.assertEqual(repr(m.get(key)), repr(ep)) + assert len(m) == len(self.submap_expect) + for key, ep in pkg_resources.iteritems(self.submap_expect): + assert repr(m.get(key)) == repr(ep) submap_expect = dict( feature1=EntryPoint('feature1', 'somemodule', ['somefunction']), @@ -285,63 +321,71 @@ class EntryPointTests(TestCase): def testParseList(self): self.checkSubMap(EntryPoint.parse_group("xyz", self.submap_str)) - self.assertRaises(ValueError, EntryPoint.parse_group, "x a", "foo=bar") - self.assertRaises(ValueError, EntryPoint.parse_group, "x", - ["foo=baz", "foo=bar"]) + with pytest.raises(ValueError): + EntryPoint.parse_group("x a", "foo=bar") + with pytest.raises(ValueError): + EntryPoint.parse_group("x", ["foo=baz", "foo=bar"]) def testParseMap(self): m = EntryPoint.parse_map({'xyz':self.submap_str}) self.checkSubMap(m['xyz']) - self.assertEqual(list(m.keys()),['xyz']) + assert list(m.keys()) == ['xyz'] m = EntryPoint.parse_map("[xyz]\n"+self.submap_str) self.checkSubMap(m['xyz']) - self.assertEqual(list(m.keys()),['xyz']) - self.assertRaises(ValueError, EntryPoint.parse_map, ["[xyz]", "[xyz]"]) - self.assertRaises(ValueError, EntryPoint.parse_map, self.submap_str) + assert list(m.keys()) == ['xyz'] + with pytest.raises(ValueError): + EntryPoint.parse_map(["[xyz]", "[xyz]"]) + with pytest.raises(ValueError): + EntryPoint.parse_map(self.submap_str) -class RequirementsTests(TestCase): +class TestRequirements: def testBasics(self): r = Requirement.parse("Twisted>=1.2") - self.assertEqual(str(r),"Twisted>=1.2") - self.assertEqual(repr(r),"Requirement.parse('Twisted>=1.2')") - self.assertEqual(r, Requirement("Twisted", [('>=','1.2')], ())) - self.assertEqual(r, Requirement("twisTed", [('>=','1.2')], ())) - self.assertNotEqual(r, Requirement("Twisted", [('>=','2.0')], ())) - self.assertNotEqual(r, Requirement("Zope", [('>=','1.2')], ())) - self.assertNotEqual(r, Requirement("Zope", [('>=','3.0')], ())) - self.assertNotEqual(r, Requirement.parse("Twisted[extras]>=1.2")) + assert str(r) == "Twisted>=1.2" + assert repr(r) == "Requirement.parse('Twisted>=1.2')" + assert r == Requirement("Twisted", [('>=','1.2')], ()) + assert r == Requirement("twisTed", [('>=','1.2')], ()) + assert r != Requirement("Twisted", [('>=','2.0')], ()) + assert r != Requirement("Zope", [('>=','1.2')], ()) + assert r != Requirement("Zope", [('>=','3.0')], ()) + assert r != Requirement.parse("Twisted[extras]>=1.2") def testOrdering(self): r1 = Requirement("Twisted", [('==','1.2c1'),('>=','1.2')], ()) r2 = Requirement("Twisted", [('>=','1.2'),('==','1.2c1')], ()) - self.assertEqual(r1,r2) - self.assertEqual(str(r1),str(r2)) - self.assertEqual(str(r2),"Twisted==1.2c1,>=1.2") + assert r1 == r2 + assert str(r1) == str(r2) + assert str(r2) == "Twisted==1.2c1,>=1.2" def testBasicContains(self): r = Requirement("Twisted", [('>=','1.2')], ()) foo_dist = Distribution.from_filename("FooPkg-1.3_1.egg") twist11 = Distribution.from_filename("Twisted-1.1.egg") twist12 = Distribution.from_filename("Twisted-1.2.egg") - self.assertTrue(parse_version('1.2') in r) - self.assertTrue(parse_version('1.1') not in r) - self.assertTrue('1.2' in r) - self.assertTrue('1.1' not in r) - self.assertTrue(foo_dist not in r) - self.assertTrue(twist11 not in r) - self.assertTrue(twist12 in r) + assert parse_version('1.2') in r + assert parse_version('1.1') not in r + assert '1.2' in r + assert '1.1' not in r + assert foo_dist not in r + assert twist11 not in r + assert twist12 in r def testOptionsAndHashing(self): r1 = Requirement.parse("Twisted[foo,bar]>=1.2") r2 = Requirement.parse("Twisted[bar,FOO]>=1.2") - self.assertEqual(r1,r2) - self.assertEqual(r1.extras, ("foo","bar")) - self.assertEqual(r2.extras, ("bar","foo")) # extras are normalized - self.assertEqual(hash(r1), hash(r2)) - self.assertEqual( - hash(r1), hash(("twisted", packaging.specifiers.SpecifierSet(">=1.2"), - frozenset(["foo","bar"]))) + assert r1 == r2 + assert r1.extras == ("foo","bar") + assert r2.extras == ("bar","foo") # extras are normalized + assert hash(r1) == hash(r2) + assert ( + hash(r1) + == + hash(( + "twisted", + packaging.specifiers.SpecifierSet(">=1.2"), + frozenset(["foo","bar"]), + )) ) def testVersionEquality(self): @@ -349,42 +393,42 @@ class RequirementsTests(TestCase): r2 = Requirement.parse("foo!=0.3a4") d = Distribution.from_filename - self.assertTrue(d("foo-0.3a4.egg") not in r1) - self.assertTrue(d("foo-0.3a1.egg") not in r1) - self.assertTrue(d("foo-0.3a4.egg") not in r2) + assert d("foo-0.3a4.egg") not in r1 + assert d("foo-0.3a1.egg") not in r1 + assert d("foo-0.3a4.egg") not in r2 - self.assertTrue(d("foo-0.3a2.egg") in r1) - self.assertTrue(d("foo-0.3a2.egg") in r2) - self.assertTrue(d("foo-0.3a3.egg") in r2) - self.assertTrue(d("foo-0.3a5.egg") in r2) + assert d("foo-0.3a2.egg") in r1 + assert d("foo-0.3a2.egg") in r2 + assert d("foo-0.3a3.egg") in r2 + assert d("foo-0.3a5.egg") in r2 def testSetuptoolsProjectName(self): """ The setuptools project should implement the setuptools package. """ - self.assertEqual( - Requirement.parse('setuptools').project_name, 'setuptools') + assert ( + Requirement.parse('setuptools').project_name == 'setuptools') # setuptools 0.7 and higher means setuptools. - self.assertEqual( - Requirement.parse('setuptools == 0.7').project_name, 'setuptools') - self.assertEqual( - Requirement.parse('setuptools == 0.7a1').project_name, 'setuptools') - self.assertEqual( - Requirement.parse('setuptools >= 0.7').project_name, 'setuptools') + assert ( + Requirement.parse('setuptools == 0.7').project_name == 'setuptools') + assert ( + Requirement.parse('setuptools == 0.7a1').project_name == 'setuptools') + assert ( + Requirement.parse('setuptools >= 0.7').project_name == 'setuptools') -class ParseTests(TestCase): +class TestParsing: def testEmptyParse(self): - self.assertEqual(list(parse_requirements('')), []) + assert list(parse_requirements('')) == [] def testYielding(self): for inp,out in [ ([], []), ('x',['x']), ([[]],[]), (' x\n y', ['x','y']), (['x\n\n','y'], ['x','y']), ]: - self.assertEqual(list(pkg_resources.yield_lines(inp)),out) + assert list(pkg_resources.yield_lines(inp)) == out def testSplitting(self): sample = """ @@ -400,48 +444,65 @@ class ParseTests(TestCase): [q] v """ - self.assertEqual(list(pkg_resources.split_sections(sample)), - [(None,["x"]), ("Y",["z","a"]), ("b",["c"]), ("d",[]), ("q",["v"])] + assert ( + list(pkg_resources.split_sections(sample)) + == + [ + (None, ["x"]), + ("Y", ["z", "a"]), + ("b", ["c"]), + ("d", []), + ("q", ["v"]), + ] ) - self.assertRaises(ValueError,list,pkg_resources.split_sections("[foo")) + with pytest.raises(ValueError): + list(pkg_resources.split_sections("[foo")) def testSafeName(self): - self.assertEqual(safe_name("adns-python"), "adns-python") - self.assertEqual(safe_name("WSGI Utils"), "WSGI-Utils") - self.assertEqual(safe_name("WSGI Utils"), "WSGI-Utils") - self.assertEqual(safe_name("Money$$$Maker"), "Money-Maker") - self.assertNotEqual(safe_name("peak.web"), "peak-web") + assert safe_name("adns-python") == "adns-python" + assert safe_name("WSGI Utils") == "WSGI-Utils" + assert safe_name("WSGI Utils") == "WSGI-Utils" + assert safe_name("Money$$$Maker") == "Money-Maker" + assert safe_name("peak.web") != "peak-web" def testSafeVersion(self): - self.assertEqual(safe_version("1.2-1"), "1.2.post1") - self.assertEqual(safe_version("1.2 alpha"), "1.2.alpha") - self.assertEqual(safe_version("2.3.4 20050521"), "2.3.4.20050521") - self.assertEqual(safe_version("Money$$$Maker"), "Money-Maker") - self.assertEqual(safe_version("peak.web"), "peak.web") + assert safe_version("1.2-1") == "1.2.post1" + assert safe_version("1.2 alpha") == "1.2.alpha" + assert safe_version("2.3.4 20050521") == "2.3.4.20050521" + assert safe_version("Money$$$Maker") == "Money-Maker" + assert safe_version("peak.web") == "peak.web" def testSimpleRequirements(self): - self.assertEqual( - list(parse_requirements('Twis-Ted>=1.2-1')), + assert ( + list(parse_requirements('Twis-Ted>=1.2-1')) + == [Requirement('Twis-Ted',[('>=','1.2-1')], ())] ) - self.assertEqual( - list(parse_requirements('Twisted >=1.2, \ # more\n<2.0')), + assert ( + list(parse_requirements('Twisted >=1.2, \ # more\n<2.0')) + == [Requirement('Twisted',[('>=','1.2'),('<','2.0')], ())] ) - self.assertEqual( - Requirement.parse("FooBar==1.99a3"), + assert ( + Requirement.parse("FooBar==1.99a3") + == Requirement("FooBar", [('==','1.99a3')], ()) ) - self.assertRaises(ValueError,Requirement.parse,">=2.3") - self.assertRaises(ValueError,Requirement.parse,"x\\") - self.assertRaises(ValueError,Requirement.parse,"x==2 q") - self.assertRaises(ValueError,Requirement.parse,"X==1\nY==2") - self.assertRaises(ValueError,Requirement.parse,"#") + with pytest.raises(ValueError): + Requirement.parse(">=2.3") + with pytest.raises(ValueError): + Requirement.parse("x\\") + with pytest.raises(ValueError): + Requirement.parse("x==2 q") + with pytest.raises(ValueError): + Requirement.parse("X==1\nY==2") + with pytest.raises(ValueError): + Requirement.parse("#") def testVersionEquality(self): def c(s1,s2): p1, p2 = parse_version(s1),parse_version(s2) - self.assertEqual(p1,p2, (s1,s2,p1,p2)) + assert p1 == p2, (s1,s2,p1,p2) c('1.2-rc1', '1.2rc1') c('0.4', '0.4.0') @@ -457,7 +518,7 @@ class ParseTests(TestCase): def testVersionOrdering(self): def c(s1,s2): p1, p2 = parse_version(s1),parse_version(s2) - self.assertTrue(p1<p2, (s1,s2,p1,p2)) + assert p1<p2, (s1,s2,p1,p2) c('2.1','2.1.1') c('2a1','2b0') @@ -502,15 +563,15 @@ class ParseTests(TestCase): return True return _final_version(parsed_version) - self.assertTrue(buildout(parse_version("1.0"))) - self.assertFalse(buildout(parse_version("1.0a1"))) + assert buildout(parse_version("1.0")) + assert not buildout(parse_version("1.0a1")) def testVersionIndexable(self): """ Some projects were doing things like parse_version("v")[0], so we'll support indexing the same as we support iterating. """ - self.assertEqual(parse_version("1.0")[0], "00000001") + assert parse_version("1.0")[0] == "00000001" def testVersionTupleSort(self): """ @@ -518,102 +579,42 @@ class ParseTests(TestCase): value of parse_version. So again we'll add a warning enabled shim to make this possible. """ - self.assertTrue(parse_version("1.0") < tuple(parse_version("2.0"))) - self.assertTrue(parse_version("1.0") <= tuple(parse_version("2.0"))) - self.assertTrue(parse_version("1.0") == tuple(parse_version("1.0"))) - self.assertTrue(parse_version("3.0") > tuple(parse_version("2.0"))) - self.assertTrue(parse_version("3.0") >= tuple(parse_version("2.0"))) - self.assertTrue(parse_version("3.0") != tuple(parse_version("2.0"))) - self.assertFalse(parse_version("3.0") != tuple(parse_version("3.0"))) + assert parse_version("1.0") < tuple(parse_version("2.0")) + assert parse_version("1.0") <= tuple(parse_version("2.0")) + assert parse_version("1.0") == tuple(parse_version("1.0")) + assert parse_version("3.0") > tuple(parse_version("2.0")) + assert parse_version("3.0") >= tuple(parse_version("2.0")) + assert parse_version("3.0") != tuple(parse_version("2.0")) + assert not (parse_version("3.0") != tuple(parse_version("3.0"))) def testVersionHashable(self): """ Ensure that our versions stay hashable even though we've subclassed them and added some shim code to them. """ - self.assertEqual( - hash(parse_version("1.0")), - hash(parse_version("1.0")), + assert ( + hash(parse_version("1.0")) + == + hash(parse_version("1.0")) ) -class ScriptHeaderTests(TestCase): - non_ascii_exe = '/Users/José/bin/python' - exe_with_spaces = r'C:\Program Files\Python33\python.exe' - - def test_get_script_header(self): - if not sys.platform.startswith('java') or not is_sh(sys.executable): - # This test is for non-Jython platforms - expected = '#!%s\n' % nt_quote_arg(os.path.normpath(sys.executable)) - self.assertEqual(get_script_header('#!/usr/local/bin/python'), - expected) - expected = '#!%s -x\n' % nt_quote_arg(os.path.normpath(sys.executable)) - self.assertEqual(get_script_header('#!/usr/bin/python -x'), - expected) - self.assertEqual(get_script_header('#!/usr/bin/python', - executable=self.non_ascii_exe), - '#!%s -x\n' % self.non_ascii_exe) - candidate = get_script_header('#!/usr/bin/python', - executable=self.exe_with_spaces) - self.assertEqual(candidate, '#!"%s"\n' % self.exe_with_spaces) - - def test_get_script_header_jython_workaround(self): - # This test doesn't work with Python 3 in some locales - if PY3 and os.environ.get("LC_CTYPE") in (None, "C", "POSIX"): - return - - class java: - class lang: - class System: - @staticmethod - def getProperty(property): - return "" - sys.modules["java"] = java - - platform = sys.platform - sys.platform = 'java1.5.0_13' - stdout, stderr = sys.stdout, sys.stderr - try: - # A mock sys.executable that uses a shebang line (this file) - exe = os.path.normpath(os.path.splitext(__file__)[0] + '.py') - self.assertEqual( - get_script_header('#!/usr/local/bin/python', executable=exe), - '#!/usr/bin/env %s\n' % exe) - - # Ensure we generate what is basically a broken shebang line - # when there's options, with a warning emitted - sys.stdout = sys.stderr = StringIO() - self.assertEqual(get_script_header('#!/usr/bin/python -x', - executable=exe), - '#!%s -x\n' % exe) - self.assertTrue('Unable to adapt shebang line' in sys.stdout.getvalue()) - sys.stdout = sys.stderr = StringIO() - self.assertEqual(get_script_header('#!/usr/bin/python', - executable=self.non_ascii_exe), - '#!%s -x\n' % self.non_ascii_exe) - self.assertTrue('Unable to adapt shebang line' in sys.stdout.getvalue()) - finally: - del sys.modules["java"] - sys.platform = platform - sys.stdout, sys.stderr = stdout, stderr - - -class NamespaceTests(TestCase): - - def setUp(self): +class TestNamespaces: + + def setup_method(self, method): self._ns_pkgs = pkg_resources._namespace_packages.copy() self._tmpdir = tempfile.mkdtemp(prefix="tests-setuptools-") os.makedirs(os.path.join(self._tmpdir, "site-pkgs")) self._prev_sys_path = sys.path[:] sys.path.append(os.path.join(self._tmpdir, "site-pkgs")) - def tearDown(self): + def teardown_method(self, method): shutil.rmtree(self._tmpdir) pkg_resources._namespace_packages = self._ns_pkgs.copy() sys.path = self._prev_sys_path[:] - msg = "Test fails when /tmp is a symlink. See #231" - @skipIf(os.path.islink(tempfile.gettempdir()), msg) + @pytest.mark.skipif(os.path.islink(tempfile.gettempdir()), + reason="Test fails when /tmp is a symlink. See #231") def test_two_levels_deep(self): """ Test nested namespace packages @@ -638,10 +639,8 @@ class NamespaceTests(TestCase): pkg2_init.close() import pkg1 assert "pkg1" in pkg_resources._namespace_packages - try: - import pkg1.pkg2 - except ImportError: - self.fail("Setuptools tried to import the parent namespace package") + # attempt to import pkg2 from site-pkgs2 + import pkg1.pkg2 # check the _namespace_packages dict assert "pkg1.pkg2" in pkg_resources._namespace_packages assert pkg_resources._namespace_packages["pkg1"] == ["pkg1.pkg2"] diff --git a/setuptools/archive_util.py b/setuptools/archive_util.py index 67a67e23..b3c9fa56 100755 --- a/setuptools/archive_util.py +++ b/setuptools/archive_util.py @@ -64,20 +64,23 @@ def unpack_directory(filename, extract_dir, progress_filter=default_filter): Raises ``UnrecognizedFormat`` if `filename` is not a directory """ if not os.path.isdir(filename): - raise UnrecognizedFormat("%s is not a directory" % (filename,)) + raise UnrecognizedFormat("%s is not a directory" % filename) - paths = {filename:('',extract_dir)} + paths = { + filename: ('', extract_dir), + } for base, dirs, files in os.walk(filename): - src,dst = paths[base] + src, dst = paths[base] for d in dirs: - paths[os.path.join(base,d)] = src+d+'/', os.path.join(dst,d) + paths[os.path.join(base, d)] = src + d + '/', os.path.join(dst, d) for f in files: - target = os.path.join(dst,f) - target = progress_filter(src+f, target) + target = os.path.join(dst, f) + target = progress_filter(src + f, target) if not target: - continue # skip non-files + # skip non-files + continue ensure_directory(target) - f = os.path.join(base,f) + f = os.path.join(base, f) shutil.copyfile(f, target) shutil.copystat(f, target) @@ -112,12 +115,8 @@ def unpack_zipfile(filename, extract_dir, progress_filter=default_filter): # file ensure_directory(target) data = z.read(info.filename) - f = open(target,'wb') - try: + with open(target, 'wb') as f: f.write(data) - finally: - f.close() - del data unix_attributes = info.external_attr >> 16 if unix_attributes: os.chmod(target, unix_attributes) @@ -137,18 +136,21 @@ def unpack_tarfile(filename, extract_dir, progress_filter=default_filter): "%s is not a compressed or uncompressed tar file" % (filename,) ) with contextlib.closing(tarobj): - tarobj.chown = lambda *args: None # don't do any chowning! + # don't do any chowning! + tarobj.chown = lambda *args: None for member in tarobj: name = member.name # don't extract absolute paths or ones with .. in them if not name.startswith('/') and '..' not in name.split('/'): prelim_dst = os.path.join(extract_dir, *name.split('/')) - # resolve any links and to extract the link targets as normal files + # resolve any links and to extract the link targets as normal + # files while member is not None and (member.islnk() or member.issym()): linkpath = member.linkname if member.issym(): - linkpath = posixpath.join(posixpath.dirname(member.name), linkpath) + base = posixpath.dirname(member.name) + linkpath = posixpath.join(base, linkpath) linkpath = posixpath.normpath(linkpath) member = tarobj._getmember(linkpath) @@ -158,9 +160,11 @@ def unpack_tarfile(filename, extract_dir, progress_filter=default_filter): if final_dst.endswith(os.sep): final_dst = final_dst[:-1] try: - tarobj._extract_member(member, final_dst) # XXX Ugh + # XXX Ugh + tarobj._extract_member(member, final_dst) except tarfile.ExtractError: - pass # chown/chmod/mkfifo/mknode/makedev failed + # chown/chmod/mkfifo/mknode/makedev failed + pass return True extraction_drivers = unpack_directory, unpack_zipfile, unpack_tarfile diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 1a2f56ae..d05f4c65 100755 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -698,17 +698,12 @@ Please make the appropriate changes for your system and try again. distros = WorkingSet([]).resolve( [requirement], self.local_index, self.easy_install ) - except DistributionNotFound: - e = sys.exc_info()[1] + except DistributionNotFound as e: raise DistutilsError( "Could not find required distribution %s" % e.args ) - except VersionConflict: - e = sys.exc_info()[1] - raise DistutilsError( - "Installed distribution %s conflicts with requirement %s" - % e.args - ) + except VersionConflict as e: + raise DistutilsError(e.report()) if self.always_copy or self.always_copy_from: # Force all the relevant distros to be copied or activated for dist in distros: @@ -1044,8 +1039,7 @@ See the setuptools documentation for the "develop" command for more info. ) try: run_setup(setup_script, args) - except SystemExit: - v = sys.exc_info()[1] + except SystemExit as v: raise DistutilsError("Setup script exited with %s" % (v.args[0],)) def build_and_install(self, setup_script, setup_base): @@ -1889,8 +1883,7 @@ def chmod(path, mode): log.debug("changing mode of %s to %o", path, mode) try: _chmod(path, mode) - except os.error: - e = sys.exc_info()[1] + except os.error as e: log.debug("chmod failed: %s", e) diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index 3d33df80..851a1775 100755 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -70,7 +70,8 @@ class sdist(orig.sdist): try: orig.sdist.read_template(self) except: - sys.exc_info()[2].tb_next.tb_frame.f_locals['template'].close() + _, _, tb = sys.exc_info() + tb.tb_next.tb_frame.f_locals['template'].close() raise # Beginning with Python 2.7.2, 3.1.4, and 3.2.1, this leaky file handle diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py index cd6c300c..001ee936 100644 --- a/setuptools/command/upload_docs.py +++ b/setuptools/command/upload_docs.py @@ -169,8 +169,7 @@ class upload_docs(upload): conn.putheader('Authorization', auth) conn.endheaders() conn.send(body) - except socket.error: - e = sys.exc_info()[1] + except socket.error as e: self.announce(str(e), log.ERROR) return diff --git a/setuptools/dist.py b/setuptools/dist.py index eb146444..1917a610 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -131,8 +131,7 @@ def check_entry_points(dist, attr, value): """Verify that entry_points map is parseable""" try: pkg_resources.EntryPoint.parse_map(value) - except ValueError: - e = sys.exc_info()[1] + except ValueError as e: raise DistutilsSetupError(e) def check_test_suite(dist, attr, value): diff --git a/setuptools/msvc9_support.py b/setuptools/msvc9_support.py index d0be70e2..e76d70f0 100644 --- a/setuptools/msvc9_support.py +++ b/setuptools/msvc9_support.py @@ -50,8 +50,7 @@ def find_vcvarsall(version): def query_vcvarsall(version, *args, **kwargs): try: return unpatched['query_vcvarsall'](version, *args, **kwargs) - except distutils.errors.DistutilsPlatformError: - exc = sys.exc_info()[1] + except distutils.errors.DistutilsPlatformError as exc: if exc and "vcvarsall.bat" in exc.args[0]: message = 'Microsoft Visual C++ %0.1f is required (%s).' % (version, exc.args[0]) if int(version) == 9: diff --git a/setuptools/package_index.py b/setuptools/package_index.py index 58572ce6..5ed19130 100755 --- a/setuptools/package_index.py +++ b/setuptools/package_index.py @@ -699,25 +699,21 @@ class PackageIndex(Environment): return local_open(url) try: return open_with_auth(url, self.opener) - except (ValueError, httplib.InvalidURL): - v = sys.exc_info()[1] + except (ValueError, httplib.InvalidURL) as v: msg = ' '.join([str(arg) for arg in v.args]) if warning: self.warn(warning, msg) else: raise DistutilsError('%s %s' % (url, msg)) - except urllib2.HTTPError: - v = sys.exc_info()[1] + except urllib2.HTTPError as v: return v - except urllib2.URLError: - v = sys.exc_info()[1] + except urllib2.URLError as v: if warning: self.warn(warning, v.reason) else: raise DistutilsError("Download error for %s: %s" % (url, v.reason)) - except httplib.BadStatusLine: - v = sys.exc_info()[1] + except httplib.BadStatusLine as v: if warning: self.warn(warning, v.line) else: @@ -726,8 +722,7 @@ class PackageIndex(Environment): 'down, %s' % (url, v.line) ) - except httplib.HTTPException: - v = sys.exc_info()[1] + except httplib.HTTPException as v: if warning: self.warn(warning, v) else: diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py index b90d1e1b..7971f42c 100755 --- a/setuptools/sandbox.py +++ b/setuptools/sandbox.py @@ -199,8 +199,7 @@ def run_setup(setup_script, args): ns = dict(__file__=setup_script, __name__='__main__') _execfile(setup_script, ns) DirectorySandbox(setup_dir).run(runner) - except SystemExit: - v = sys.exc_info()[1] + except SystemExit as v: if v.args and v.args[0]: raise # Normal exit, just return diff --git a/setuptools/tests/__init__.py b/setuptools/tests/__init__.py index e5d6545a..b8a29cba 100644 --- a/setuptools/tests/__init__.py +++ b/setuptools/tests/__init__.py @@ -1,8 +1,6 @@ """Tests for the 'setuptools' package""" import sys import os -import unittest -import doctest import distutils.core import distutils.cmd from distutils.errors import DistutilsOptionError, DistutilsPlatformError @@ -11,22 +9,13 @@ from distutils.core import Extension from distutils.version import LooseVersion from setuptools.compat import func_code +import pytest + import setuptools.dist import setuptools.depends as dep from setuptools import Feature from setuptools.depends import Require -def additional_tests(): - suite = unittest.TestSuite(( - doctest.DocFileSuite( - 'api_tests.txt', - optionflags=doctest.ELLIPSIS, package='pkg_resources', - ), - )) - if sys.platform == 'win32': - suite.addTest(doctest.DocFileSuite('win_script_wrapper.txt')) - return suite - def makeSetup(**args): """Return distribution from 'setup(**args)', without executing commands""" @@ -41,7 +30,12 @@ def makeSetup(**args): distutils.core._setup_stop_after = None -class DependsTests(unittest.TestCase): +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'): @@ -54,86 +48,77 @@ class DependsTests(unittest.TestCase): y = z fc = func_code(f1) + # unrecognized name - self.assertEqual(dep.extract_constant(fc,'q', -1), None) + assert dep.extract_constant(fc,'q', -1) is None # constant assigned - self.assertEqual(dep.extract_constant(fc,'x', -1), "test") + dep.extract_constant(fc,'x', -1) == "test" # expression assigned - self.assertEqual(dep.extract_constant(fc,'y', -1), -1) + dep.extract_constant(fc,'y', -1) == -1 # recognized name, not assigned - self.assertEqual(dep.extract_constant(fc,'z', -1), None) + dep.extract_constant(fc,'z', -1) is None def testFindModule(self): - self.assertRaises(ImportError, dep.find_module, 'no-such.-thing') - self.assertRaises(ImportError, dep.find_module, 'setuptools.non-existent') + 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): - if not hasattr(dep, 'get_module_constant'): - # skip on non-bytecode platforms - return - from email import __version__ - self.assertEqual( - dep.get_module_constant('email','__version__'), __version__ - ) - self.assertEqual( - dep.get_module_constant('sys','version'), sys.version - ) - self.assertEqual( - dep.get_module_constant('setuptools.tests','__doc__'),__doc__ - ) + 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): - if not hasattr(dep, 'extract_constant'): - # skip on non-bytecode platformsh - return - req = Require('Email','1.0.3','email') - self.assertEqual(req.name, 'Email') - self.assertEqual(req.module, 'email') - self.assertEqual(req.requested_version, '1.0.3') - self.assertEqual(req.attribute, '__version__') - self.assertEqual(req.full_name(), 'Email-1.0.3') + 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__ - self.assertEqual(req.get_version(), __version__) - self.assertTrue(req.version_ok('1.0.9')) - self.assertTrue(not req.version_ok('0.9.1')) - self.assertTrue(not req.version_ok('unknown')) + 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') - self.assertTrue(req.is_present()) - self.assertTrue(req.is_current()) + assert req.is_present() + assert req.is_current() req = Require('Email 3000','03000','email',format=LooseVersion) - self.assertTrue(req.is_present()) - self.assertTrue(not req.is_current()) - self.assertTrue(not req.version_ok('unknown')) + 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') - self.assertTrue(not req.is_present()) - self.assertTrue(not req.is_current()) + assert not req.is_present() + assert not req.is_current() req = Require('Tests', None, 'tests', homepage="http://example.com") - self.assertEqual(req.format, None) - self.assertEqual(req.attribute, None) - self.assertEqual(req.requested_version, None) - self.assertEqual(req.full_name(), 'Tests') - self.assertEqual(req.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__] - self.assertTrue(req.is_present(paths)) - self.assertTrue(req.is_current(paths)) + assert req.is_present(paths) + assert req.is_current(paths) -class DistroTests(unittest.TestCase): +class TestDistro: - def setUp(self): + def setup_method(self, method): self.e1 = Extension('bar.ext',['bar.c']) self.e2 = Extension('c.y', ['y.c']) @@ -145,21 +130,21 @@ class DistroTests(unittest.TestCase): ) def testDistroType(self): - self.assertTrue(isinstance(self.dist,setuptools.dist.Distribution)) + assert isinstance(self.dist,setuptools.dist.Distribution) def testExcludePackage(self): self.dist.exclude_package('a') - self.assertEqual(self.dist.packages, ['b','c']) + assert self.dist.packages == ['b','c'] self.dist.exclude_package('b') - self.assertEqual(self.dist.packages, ['c']) - self.assertEqual(self.dist.py_modules, ['x']) - self.assertEqual(self.dist.ext_modules, [self.e1, self.e2]) + 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') - self.assertEqual(self.dist.packages, []) - self.assertEqual(self.dist.py_modules, ['x']) - self.assertEqual(self.dist.ext_modules, [self.e1]) + 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') @@ -167,21 +152,21 @@ class DistroTests(unittest.TestCase): def testIncludeExclude(self): # remove an extension self.dist.exclude(ext_modules=[self.e1]) - self.assertEqual(self.dist.ext_modules, [self.e2]) + assert self.dist.ext_modules == [self.e2] # add it back in self.dist.include(ext_modules=[self.e1]) - self.assertEqual(self.dist.ext_modules, [self.e2, self.e1]) + assert self.dist.ext_modules == [self.e2, self.e1] # should not add duplicate self.dist.include(ext_modules=[self.e1]) - self.assertEqual(self.dist.ext_modules, [self.e2, self.e1]) + assert self.dist.ext_modules == [self.e2, self.e1] def testExcludePackages(self): self.dist.exclude(packages=['c','b','a']) - self.assertEqual(self.dist.packages, []) - self.assertEqual(self.dist.py_modules, ['x']) - self.assertEqual(self.dist.ext_modules, [self.e1]) + assert self.dist.packages == [] + assert self.dist.py_modules == ['x'] + assert self.dist.ext_modules == [self.e1] def testEmpty(self): dist = makeSetup() @@ -190,49 +175,41 @@ class DistroTests(unittest.TestCase): dist.exclude(packages=['a'], py_modules=['b'], ext_modules=[self.e2]) def testContents(self): - self.assertTrue(self.dist.has_contents_for('a')) + assert self.dist.has_contents_for('a') self.dist.exclude_package('a') - self.assertTrue(not self.dist.has_contents_for('a')) + assert not self.dist.has_contents_for('a') - self.assertTrue(self.dist.has_contents_for('b')) + assert self.dist.has_contents_for('b') self.dist.exclude_package('b') - self.assertTrue(not self.dist.has_contents_for('b')) + assert not self.dist.has_contents_for('b') - self.assertTrue(self.dist.has_contents_for('c')) + assert self.dist.has_contents_for('c') self.dist.exclude_package('c') - self.assertTrue(not self.dist.has_contents_for('c')) + assert not self.dist.has_contents_for('c') def testInvalidIncludeExclude(self): - self.assertRaises(DistutilsSetupError, - self.dist.include, nonexistent_option='x' - ) - self.assertRaises(DistutilsSetupError, - self.dist.exclude, nonexistent_option='x' - ) - self.assertRaises(DistutilsSetupError, - self.dist.include, packages={'x':'y'} - ) - self.assertRaises(DistutilsSetupError, - self.dist.exclude, packages={'x':'y'} - ) - self.assertRaises(DistutilsSetupError, - self.dist.include, ext_modules={'x':'y'} - ) - self.assertRaises(DistutilsSetupError, - self.dist.exclude, ext_modules={'x':'y'} - ) - - self.assertRaises(DistutilsSetupError, - self.dist.include, package_dir=['q'] - ) - self.assertRaises(DistutilsSetupError, - self.dist.exclude, package_dir=['q'] - ) - - -class FeatureTests(unittest.TestCase): - - def setUp(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={ @@ -254,80 +231,75 @@ class FeatureTests(unittest.TestCase): ) def testDefaults(self): - self.assertTrue(not - Feature( - "test",standard=True,remove='x',available=False - ).include_by_default() - ) - self.assertTrue( - Feature("test",standard=True,remove='x').include_by_default() - ) + 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 - self.assertRaises(DistutilsSetupError, Feature, "test") + with pytest.raises(DistutilsSetupError): + Feature("test") def testAvailability(self): - self.assertRaises( - DistutilsPlatformError, - self.dist.features['dwim'].include_in, self.dist - ) + with pytest.raises(DistutilsPlatformError): + self.dist.features['dwim'].include_in(self.dist) def testFeatureOptions(self): dist = self.dist - self.assertTrue( + assert ( ('with-dwim',None,'include DWIM') in dist.feature_options ) - self.assertTrue( + assert ( ('without-dwim',None,'exclude DWIM (default)') in dist.feature_options ) - self.assertTrue( + assert ( ('with-bar',None,'include bar (default)') in dist.feature_options ) - self.assertTrue( + assert ( ('without-bar',None,'exclude bar') in dist.feature_options ) - self.assertEqual(dist.feature_negopt['without-foo'],'with-foo') - self.assertEqual(dist.feature_negopt['without-bar'],'with-bar') - self.assertEqual(dist.feature_negopt['without-dwim'],'with-dwim') - self.assertTrue(not 'without-baz' in dist.feature_negopt) + 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 (not 'without-baz' in dist.feature_negopt) def testUseFeatures(self): dist = self.dist - self.assertEqual(dist.with_foo,1) - self.assertEqual(dist.with_bar,0) - self.assertEqual(dist.with_baz,1) - self.assertTrue(not 'bar_et' in dist.py_modules) - self.assertTrue(not 'pkg.bar' in dist.packages) - self.assertTrue('pkg.baz' in dist.packages) - self.assertTrue('scripts/baz_it' in dist.scripts) - self.assertTrue(('libfoo','foo/foofoo.c') in dist.libraries) - self.assertEqual(dist.ext_modules,[]) - self.assertEqual(dist.require_features, [self.req]) + assert dist.with_foo == 1 + assert dist.with_bar == 0 + assert dist.with_baz == 1 + assert (not 'bar_et' in dist.py_modules) + assert (not 'pkg.bar' 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 - self.assertRaises(DistutilsOptionError, dist.include_feature, 'bar') + with pytest.raises(DistutilsOptionError): + dist.include_feature('bar') def testFeatureWithInvalidRemove(self): - self.assertRaises( - SystemExit, makeSetup, features = {'x':Feature('x', remove='y')} - ) + with pytest.raises(SystemExit): + makeSetup(features={'x':Feature('x', remove='y')}) -class TestCommandTests(unittest.TestCase): +class TestCommandTests: def testTestIsCommand(self): test_cmd = makeSetup().get_command_obj('test') - self.assertTrue(isinstance(test_cmd, distutils.cmd.Command)) + 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() - self.assertEqual(ts1.test_suite, 'foo.tests.suite') + assert ts1.test_suite == 'foo.tests.suite' def testDefaultSuite(self): ts2 = makeSetup(test_suite='bar.tests.suite').get_command_obj('test') ts2.ensure_finalized() - self.assertEqual(ts2.test_suite, 'bar.tests.suite') + assert ts2.test_suite == 'bar.tests.suite' def testDefaultWModuleOnCmdLine(self): ts3 = makeSetup( @@ -335,16 +307,17 @@ class TestCommandTests(unittest.TestCase): script_args=['test','-m','foo.tests'] ).get_command_obj('test') ts3.ensure_finalized() - self.assertEqual(ts3.test_module, 'foo.tests') - self.assertEqual(ts3.test_suite, 'foo.tests.test_suite') + 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') - self.assertRaises(DistutilsOptionError, ts4.ensure_finalized) + with pytest.raises(DistutilsOptionError): + ts4.ensure_finalized() def testNoSuite(self): ts5 = makeSetup().get_command_obj('test') ts5.ensure_finalized() - self.assertEqual(ts5.test_suite, None) + assert ts5.test_suite == None diff --git a/setuptools/tests/contexts.py b/setuptools/tests/contexts.py new file mode 100644 index 00000000..d06a333f --- /dev/null +++ b/setuptools/tests/contexts.py @@ -0,0 +1,93 @@ +import tempfile +import os +import shutil +import sys +import contextlib +import site + +from ..compat import StringIO + + +@contextlib.contextmanager +def tempdir(cd=lambda dir:None, **kwargs): + temp_dir = tempfile.mkdtemp(**kwargs) + orig_dir = os.getcwd() + try: + cd(temp_dir) + yield temp_dir + finally: + cd(orig_dir) + shutil.rmtree(temp_dir) + + +@contextlib.contextmanager +def environment(**replacements): + """ + In a context, patch the environment with replacements. Pass None values + to clear the values. + """ + saved = dict( + (key, os.environ['key']) + for key in replacements + if key in os.environ + ) + + # remove values that are null + remove = (key for (key, value) in replacements.items() if value is None) + for key in list(remove): + os.environ.pop(key, None) + replacements.pop(key) + + os.environ.update(replacements) + + try: + yield saved + finally: + for key in replacements: + os.environ.pop(key, None) + os.environ.update(saved) + + +@contextlib.contextmanager +def argv(repl): + old_argv = sys.argv[:] + sys.argv[:] = repl + yield + sys.argv[:] = old_argv + + +@contextlib.contextmanager +def quiet(): + """ + Redirect stdout/stderr to StringIO objects to prevent console output from + distutils commands. + """ + + old_stdout = sys.stdout + old_stderr = sys.stderr + new_stdout = sys.stdout = StringIO() + new_stderr = sys.stderr = StringIO() + try: + yield new_stdout, new_stderr + finally: + new_stdout.seek(0) + new_stderr.seek(0) + sys.stdout = old_stdout + sys.stderr = old_stderr + + +@contextlib.contextmanager +def save_user_site_setting(): + saved = site.ENABLE_USER_SITE + try: + yield saved + finally: + site.ENABLE_USER_SITE = saved + + +@contextlib.contextmanager +def suppress_exceptions(*excs): + try: + yield + except excs: + pass diff --git a/setuptools/tests/fixtures.py b/setuptools/tests/fixtures.py new file mode 100644 index 00000000..0b1eaf5f --- /dev/null +++ b/setuptools/tests/fixtures.py @@ -0,0 +1,24 @@ +import mock +import pytest + +from . import contexts + + +@pytest.yield_fixture +def user_override(): + """ + Override site.USER_BASE and site.USER_SITE with temporary directories in + a context. + """ + with contexts.tempdir() as user_base: + with mock.patch('site.USER_BASE', user_base): + with contexts.tempdir() as user_site: + with mock.patch('site.USER_SITE', user_site): + with contexts.save_user_site_setting(): + yield + + +@pytest.yield_fixture +def tmpdir_cwd(tmpdir): + with tmpdir.as_cwd() as orig: + yield orig diff --git a/setuptools/tests/py26compat.py b/setuptools/tests/py26compat.py index e120e744..c53b4809 100644 --- a/setuptools/tests/py26compat.py +++ b/setuptools/tests/py26compat.py @@ -1,27 +1,11 @@ import sys -import unittest import tarfile - -try: - # provide skipIf for Python 2.4-2.6 - skipIf = unittest.skipIf -except AttributeError: - def skipIf(condition, reason): - def skipper(func): - def skip(*args, **kwargs): - return - if condition: - return skip - return func - return skipper +import contextlib def _tarfile_open_ex(*args, **kwargs): """ Extend result as a context manager. """ - res = tarfile.open(*args, **kwargs) - res.__exit__ = lambda exc_type, exc_value, traceback: res.close() - res.__enter__ = lambda: res - return res + return contextlib.closing(tarfile.open(*args, **kwargs)) tarfile_open = _tarfile_open_ex if sys.version_info < (2,7) else tarfile.open diff --git a/setuptools/tests/server.py b/setuptools/tests/server.py index ae2381e3..6b214279 100644 --- a/setuptools/tests/server.py +++ b/setuptools/tests/server.py @@ -1,11 +1,10 @@ """Basic http server for tests to simulate PyPI or custom indexes """ -import sys + import time import threading from setuptools.compat import BaseHTTPRequestHandler -from setuptools.compat import (urllib2, URLError, HTTPServer, - SimpleHTTPRequestHandler) +from setuptools.compat import HTTPServer, SimpleHTTPRequestHandler class IndexServer(HTTPServer): """Basic single-threaded http server simulating a package index @@ -23,12 +22,8 @@ class IndexServer(HTTPServer): HTTPServer.__init__(self, server_address, RequestHandlerClass) self._run = True - def serve(self): - while self._run: - self.handle_request() - def start(self): - self.thread = threading.Thread(target=self.serve) + self.thread = threading.Thread(target=self.serve_forever) self.thread.start() def stop(self): @@ -37,19 +32,7 @@ class IndexServer(HTTPServer): # Let the server finish the last request and wait for a new one. time.sleep(0.1) - # self.shutdown is not supported on python < 2.6, so just - # set _run to false, and make a request, causing it to - # terminate. - self._run = False - url = 'http://127.0.0.1:%(server_port)s/' % vars(self) - try: - if sys.version_info >= (2, 6): - urllib2.urlopen(url, timeout=5) - else: - urllib2.urlopen(url) - except URLError: - # ignore any errors; all that's important is the request - pass + self.shutdown() self.thread.join() self.socket.close() @@ -77,6 +60,6 @@ class MockServer(HTTPServer, threading.Thread): def run(self): self.serve_forever() + @property def url(self): return 'http://localhost:%(server_port)s/' % vars(self) - url = property(url) diff --git a/setuptools/tests/test_bdist_egg.py b/setuptools/tests/test_bdist_egg.py index cf4bcd11..ccfb2ea7 100644 --- a/setuptools/tests/test_bdist_egg.py +++ b/setuptools/tests/test_bdist_egg.py @@ -2,52 +2,31 @@ """ import os import re -import shutil -import site -import sys -import tempfile -import unittest -from distutils.errors import DistutilsError -from setuptools.compat import StringIO -from setuptools.command.bdist_egg import bdist_egg -from setuptools.command import easy_install as easy_install_pkg +import pytest + from setuptools.dist import Distribution +from . import contexts + SETUP_PY = """\ from setuptools import setup setup(name='foo', py_modules=['hi']) """ -class TestDevelopTest(unittest.TestCase): - - def setUp(self): - self.dir = tempfile.mkdtemp() - self.old_cwd = os.getcwd() - os.chdir(self.dir) - f = open('setup.py', 'w') +@pytest.yield_fixture +def setup_context(tmpdir): + with (tmpdir/'setup.py').open('w') as f: f.write(SETUP_PY) - f.close() - f = open('hi.py', 'w') + with (tmpdir/'hi.py').open('w') as f: f.write('1\n') - f.close() - if sys.version >= "2.6": - self.old_base = site.USER_BASE - site.USER_BASE = tempfile.mkdtemp() - self.old_site = site.USER_SITE - site.USER_SITE = tempfile.mkdtemp() + with tmpdir.as_cwd(): + yield tmpdir - def tearDown(self): - os.chdir(self.old_cwd) - shutil.rmtree(self.dir) - if sys.version >= "2.6": - shutil.rmtree(site.USER_BASE) - shutil.rmtree(site.USER_SITE) - site.USER_BASE = self.old_base - site.USER_SITE = self.old_site - def test_bdist_egg(self): +class Test: + def test_bdist_egg(self, setup_context, user_override): dist = Distribution(dict( script_name='setup.py', script_args=['bdist_egg'], @@ -55,18 +34,10 @@ class TestDevelopTest(unittest.TestCase): py_modules=['hi'] )) os.makedirs(os.path.join('build', 'src')) - old_stdout = sys.stdout - sys.stdout = o = StringIO() - try: + with contexts.quiet(): dist.parse_command_line() dist.run_commands() - finally: - sys.stdout = old_stdout # let's see if we got our egg link at the right place [content] = os.listdir('dist') - self.assertTrue(re.match('foo-0.0.0-py[23].\d.egg$', content)) - -def test_suite(): - return unittest.makeSuite(TestDevelopTest) - + assert re.match('foo-0.0.0-py[23].\d.egg$', content) diff --git a/setuptools/tests/test_build_ext.py b/setuptools/tests/test_build_ext.py index a92e53ae..0719ba44 100644 --- a/setuptools/tests/test_build_ext.py +++ b/setuptools/tests/test_build_ext.py @@ -1,19 +1,18 @@ -"""build_ext tests -""" -import unittest -from distutils.command.build_ext import build_ext as distutils_build_ext +import distutils.command.build_ext as orig + from setuptools.command.build_ext import build_ext from setuptools.dist import Distribution -class TestBuildExtTest(unittest.TestCase): - +class TestBuildExt: def test_get_ext_filename(self): - # setuptools needs to give back the same - # result than distutils, even if the fullname - # is not in ext_map + """ + Setuptools needs to give back the same + result as distutils, even if the fullname + is not in ext_map. + """ dist = Distribution() cmd = build_ext(dist) cmd.ext_map['foo/bar'] = '' res = cmd.get_ext_filename('foo') - wanted = distutils_build_ext.get_ext_filename(cmd, 'foo') + wanted = orig.build_ext.get_ext_filename(cmd, 'foo') assert res == wanted diff --git a/setuptools/tests/test_develop.py b/setuptools/tests/test_develop.py index 66d182eb..ed1b194a 100644 --- a/setuptools/tests/test_develop.py +++ b/setuptools/tests/test_develop.py @@ -5,9 +5,7 @@ import shutil import site import sys import tempfile -import unittest -from distutils.errors import DistutilsError from setuptools.command.develop import develop from setuptools.dist import Distribution @@ -23,10 +21,10 @@ setup(name='foo', INIT_PY = """print "foo" """ -class TestDevelopTest(unittest.TestCase): +class TestDevelopTest: - def setUp(self): - if sys.version < "2.6" or hasattr(sys, 'real_prefix'): + def setup_method(self, method): + if hasattr(sys, 'real_prefix'): return # Directory structure @@ -50,8 +48,8 @@ class TestDevelopTest(unittest.TestCase): self.old_site = site.USER_SITE site.USER_SITE = tempfile.mkdtemp() - def tearDown(self): - if sys.version < "2.6" or hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix): + def teardown_method(self, method): + if hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix): return os.chdir(self.old_cwd) @@ -62,7 +60,7 @@ class TestDevelopTest(unittest.TestCase): site.USER_SITE = self.old_site def test_develop(self): - if sys.version < "2.6" or hasattr(sys, 'real_prefix'): + if hasattr(sys, 'real_prefix'): return dist = Distribution( dict(name='foo', @@ -86,7 +84,7 @@ class TestDevelopTest(unittest.TestCase): # let's see if we got our egg link at the right place content = os.listdir(site.USER_SITE) content.sort() - self.assertEqual(content, ['easy-install.pth', 'foo.egg-link']) + assert content == ['easy-install.pth', 'foo.egg-link'] # Check that we are using the right code. egg_link_file = open(os.path.join(site.USER_SITE, 'foo.egg-link'), 'rt') @@ -100,23 +98,6 @@ class TestDevelopTest(unittest.TestCase): finally: init_file.close() if sys.version < "3": - self.assertEqual(init, 'print "foo"') + assert init == 'print "foo"' else: - self.assertEqual(init, 'print("foo")') - - def notest_develop_with_setup_requires(self): - - wanted = ("Could not find suitable distribution for " - "Requirement.parse('I-DONT-EXIST')") - old_dir = os.getcwd() - os.chdir(self.dir) - try: - try: - Distribution({'setup_requires': ['I_DONT_EXIST']}) - except DistutilsError: - e = sys.exc_info()[1] - error = str(e) - if error == wanted: - pass - finally: - os.chdir(old_dir) + assert init == 'print("foo")' diff --git a/setuptools/tests/test_dist_info.py b/setuptools/tests/test_dist_info.py index a8adb68c..6d0ab587 100644 --- a/setuptools/tests/test_dist_info.py +++ b/setuptools/tests/test_dist_info.py @@ -3,28 +3,20 @@ import os import shutil import tempfile -import unittest -import textwrap -try: - import ast -except: - pass +import pytest import pkg_resources +from .textwrap import DALS -from setuptools.tests.py26compat import skipIf -def DALS(s): - "dedent and left-strip" - return textwrap.dedent(s).lstrip() - -class TestDistInfo(unittest.TestCase): +class TestDistInfo: def test_distinfo(self): - dists = {} - for d in pkg_resources.find_distributions(self.tmpdir): - dists[d.project_name] = d + dists = dict( + (d.project_name, d) + for d in pkg_resources.find_distributions(self.tmpdir) + ) assert len(dists) == 2, dists @@ -34,50 +26,45 @@ class TestDistInfo(unittest.TestCase): assert versioned.version == '2.718' # from filename assert unversioned.version == '0.3' # from METADATA - @skipIf('ast' not in globals(), - "ast is used to test conditional dependencies (Python >= 2.6)") + @pytest.mark.importorskip('ast') def test_conditional_dependencies(self): - requires = [pkg_resources.Requirement.parse('splort==4'), - pkg_resources.Requirement.parse('quux>=1.1')] + specs = 'splort==4', 'quux>=1.1' + requires = list(map(pkg_resources.Requirement.parse, specs)) for d in pkg_resources.find_distributions(self.tmpdir): - self.assertEqual(d.requires(), requires[:1]) - self.assertEqual(d.requires(extras=('baz',)), requires) - self.assertEqual(d.extras, ['baz']) + assert d.requires() == requires[:1] + assert d.requires(extras=('baz',)) == requires + assert d.extras == ['baz'] + + metadata_template = DALS(""" + Metadata-Version: 1.2 + Name: {name} + {version} + Requires-Dist: splort (==4) + Provides-Extra: baz + Requires-Dist: quux (>=1.1); extra == 'baz' + """) - def setUp(self): + def setup_method(self, method): self.tmpdir = tempfile.mkdtemp() - versioned = os.path.join(self.tmpdir, - 'VersionedDistribution-2.718.dist-info') + dist_info_name = 'VersionedDistribution-2.718.dist-info' + versioned = os.path.join(self.tmpdir, dist_info_name) os.mkdir(versioned) - metadata_file = open(os.path.join(versioned, 'METADATA'), 'w+') - try: - metadata_file.write(DALS( - """ - Metadata-Version: 1.2 - Name: VersionedDistribution - Requires-Dist: splort (4) - Provides-Extra: baz - Requires-Dist: quux (>=1.1); extra == 'baz' - """)) - finally: - metadata_file.close() - unversioned = os.path.join(self.tmpdir, - 'UnversionedDistribution.dist-info') + with open(os.path.join(versioned, 'METADATA'), 'w+') as metadata_file: + metadata = self.metadata_template.format( + name='VersionedDistribution', + version='', + ).replace('\n\n', '\n') + metadata_file.write(metadata) + dist_info_name = 'UnversionedDistribution.dist-info' + unversioned = os.path.join(self.tmpdir, dist_info_name) os.mkdir(unversioned) - metadata_file = open(os.path.join(unversioned, 'METADATA'), 'w+') - try: - metadata_file.write(DALS( - """ - Metadata-Version: 1.2 - Name: UnversionedDistribution - Version: 0.3 - Requires-Dist: splort (==4) - Provides-Extra: baz - Requires-Dist: quux (>=1.1); extra == 'baz' - """)) - finally: - metadata_file.close() + with open(os.path.join(unversioned, 'METADATA'), 'w+') as metadata_file: + metadata = self.metadata_template.format( + name='UnversionedDistribution', + version='Version: 0.3', + ) + metadata_file.write(metadata) - def tearDown(self): + def teardown_method(self, method): shutil.rmtree(self.tmpdir) diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index 46c2df2c..7baa989a 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -1,13 +1,15 @@ +#! -*- coding: utf-8 -*- + """Easy install Tests """ +from __future__ import absolute_import + import sys import os import shutil import tempfile -import unittest import site import contextlib -import textwrap import tarfile import logging import itertools @@ -16,10 +18,13 @@ import pytest import mock from setuptools import sandbox +from setuptools import compat from setuptools.compat import StringIO, BytesIO, urlparse from setuptools.sandbox import run_setup, SandboxViolation from setuptools.command.easy_install import ( - easy_install, fix_jython_executable, get_script_args, nt_quote_arg) + easy_install, fix_jython_executable, get_script_args, nt_quote_arg, + get_script_header, is_sh, +) from setuptools.command.easy_install import PthDistributions from setuptools.command import easy_install as easy_install_pkg from setuptools.dist import Distribution @@ -29,13 +34,8 @@ import setuptools.tests.server import pkg_resources from .py26compat import tarfile_open - - -def DALS(input): - """ - Dedent and left-strip - """ - return textwrap.dedent(input).lstrip() +from . import contexts +from .textwrap import DALS class FakeDist(object): @@ -66,7 +66,7 @@ SETUP_PY = DALS(""" setup(name='foo') """) -class TestEasyInstallTest(unittest.TestCase): +class TestEasyInstallTest: def test_install_site_py(self): dist = Distribution() @@ -76,7 +76,7 @@ class TestEasyInstallTest(unittest.TestCase): try: cmd.install_site_py() sitepy = os.path.join(cmd.install_dir, 'site.py') - self.assertTrue(os.path.exists(sitepy)) + assert os.path.exists(sitepy) finally: shutil.rmtree(cmd.install_dir) @@ -86,7 +86,7 @@ class TestEasyInstallTest(unittest.TestCase): args = next(get_script_args(dist)) name, script = itertools.islice(args, 2) - self.assertEqual(script, WANTED) + assert script == WANTED def test_no_find_links(self): # new option '--no-find-links', that blocks find-links added at @@ -99,7 +99,7 @@ class TestEasyInstallTest(unittest.TestCase): cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok') cmd.args = ['ok'] cmd.ensure_finalized() - self.assertEqual(cmd.package_index.scanned_urls, {}) + assert cmd.package_index.scanned_urls == {} # let's try without it (default behavior) cmd = easy_install(dist) @@ -109,59 +109,44 @@ class TestEasyInstallTest(unittest.TestCase): cmd.args = ['ok'] cmd.ensure_finalized() keys = sorted(cmd.package_index.scanned_urls.keys()) - self.assertEqual(keys, ['link1', 'link2']) + assert keys == ['link1', 'link2'] -class TestPTHFileWriter(unittest.TestCase): +class TestPTHFileWriter: def test_add_from_cwd_site_sets_dirty(self): '''a pth file manager should set dirty if a distribution is in site but also the cwd ''' pth = PthDistributions('does-not_exist', [os.getcwd()]) - self.assertTrue(not pth.dirty) + assert not pth.dirty pth.add(PRDistribution(os.getcwd())) - self.assertTrue(pth.dirty) + assert pth.dirty def test_add_from_site_is_ignored(self): location = '/test/location/does-not-have-to-exist' # PthDistributions expects all locations to be normalized location = pkg_resources.normalize_path(location) pth = PthDistributions('does-not_exist', [location, ]) - self.assertTrue(not pth.dirty) + assert not pth.dirty pth.add(PRDistribution(location)) - self.assertTrue(not pth.dirty) + assert not pth.dirty -class TestUserInstallTest(unittest.TestCase): - - def setUp(self): - self.dir = tempfile.mkdtemp() - setup = os.path.join(self.dir, 'setup.py') - with open(setup, 'w') as f: - f.write(SETUP_PY) - self.old_cwd = os.getcwd() - os.chdir(self.dir) - - self.old_enable_site = site.ENABLE_USER_SITE - self.old_file = easy_install_pkg.__file__ - self.old_base = site.USER_BASE - site.USER_BASE = tempfile.mkdtemp() - self.old_site = site.USER_SITE - site.USER_SITE = tempfile.mkdtemp() - easy_install_pkg.__file__ = site.USER_SITE +@pytest.yield_fixture +def setup_context(tmpdir): + with (tmpdir/'setup.py').open('w') as f: + f.write(SETUP_PY) + with tmpdir.as_cwd(): + yield tmpdir - def tearDown(self): - os.chdir(self.old_cwd) - shutil.rmtree(self.dir) - shutil.rmtree(site.USER_BASE) - shutil.rmtree(site.USER_SITE) - site.USER_BASE = self.old_base - site.USER_SITE = self.old_site - site.ENABLE_USER_SITE = self.old_enable_site - easy_install_pkg.__file__ = self.old_file +@pytest.mark.usefixtures("user_override") +@pytest.mark.usefixtures("setup_context") +class TestUserInstallTest: + @mock.patch('setuptools.command.easy_install.__file__', None) def test_user_install_implied(self): + easy_install_pkg.__file__ = site.USER_SITE site.ENABLE_USER_SITE = True # disabled sometimes #XXX: replace with something meaningfull dist = Distribution() @@ -169,7 +154,7 @@ class TestUserInstallTest(unittest.TestCase): cmd = easy_install(dist) cmd.args = ['py'] cmd.ensure_finalized() - self.assertTrue(cmd.user, 'user should be implied') + assert cmd.user, 'user should be implied' def test_multiproc_atexit(self): try: @@ -190,7 +175,7 @@ class TestUserInstallTest(unittest.TestCase): cmd = easy_install(dist) cmd.args = ['py'] cmd.initialize_options() - self.assertFalse(cmd.user, 'NOT user should be implied') + assert not cmd.user, 'NOT user should be implied' def test_local_index(self): # make sure the local index is used @@ -216,7 +201,7 @@ class TestUserInstallTest(unittest.TestCase): res = cmd.easy_install('foo') actual = os.path.normcase(os.path.realpath(res.location)) expected = os.path.normcase(os.path.realpath(new_location)) - self.assertEqual(actual, expected) + assert actual == expected finally: sys.path.remove(target) for basedir in [new_location, target, ]: @@ -242,7 +227,6 @@ class TestUserInstallTest(unittest.TestCase): ei.__file__ = site.USER_SITE yield - def patched_setup_context(self): self.orig_context = sandbox.setup_context @@ -251,7 +235,6 @@ class TestUserInstallTest(unittest.TestCase): self.user_install_setup_context, ) - def test_setup_requires(self): """Regression test for Distribute issue #318 @@ -260,15 +243,13 @@ class TestUserInstallTest(unittest.TestCase): SandboxViolation. """ - test_pkg = create_setup_requires_package(self.dir) + test_pkg = create_setup_requires_package(os.getcwd()) test_setup_py = os.path.join(test_pkg, 'setup.py') try: - with quiet_context(): + with contexts.quiet(): with self.patched_setup_context(): run_setup(test_setup_py, ['install']) - except SandboxViolation: - self.fail('Installation caused SandboxViolation') except IndexError: # Test fails in some cases due to bugs in Python # See https://bitbucket.org/pypa/setuptools/issue/201 @@ -281,7 +262,7 @@ def distutils_package(): 'from setuptools import setup', 'from distutils.core import setup', ) - with tempdir_context(cd=os.chdir): + with contexts.tempdir(cd=os.chdir): with open('setup.py', 'w') as f: f.write(distutils_setup_py) yield @@ -292,7 +273,7 @@ class TestDistutilsPackage: run_setup('setup.py', ['bdist_egg']) -class TestSetupRequires(unittest.TestCase): +class TestSetupRequires: def test_setup_requires_honors_fetch_params(self): """ @@ -309,11 +290,11 @@ class TestSetupRequires(unittest.TestCase): # Some platforms (Jython) don't find a port to which to bind, # so skip this test for them. return - with quiet_context(): + with contexts.quiet(): # create an sdist that has a build-time dependency. with TestSetupRequires.create_sdist() as dist_file: - with tempdir_context() as temp_install_dir: - with environment_context(PYTHONPATH=temp_install_dir): + with contexts.tempdir() as temp_install_dir: + with contexts.environment(PYTHONPATH=temp_install_dir): ei_params = [ '--index-url', p_index.url, '--allow-hosts', p_index_loc, @@ -321,15 +302,15 @@ class TestSetupRequires(unittest.TestCase): '--install-dir', temp_install_dir, dist_file, ] - with argv_context(['easy_install']): + with contexts.argv(['easy_install']): # attempt to install the dist. It should fail because # it doesn't exist. with pytest.raises(SystemExit): easy_install_pkg.main(ei_params) # there should have been two or three requests to the server # (three happens on Python 3.3a) - self.assertTrue(2 <= len(p_index.requests) <= 3) - self.assertEqual(p_index.requests[0].path, '/does-not-exist/') + assert 2 <= len(p_index.requests) <= 3 + assert p_index.requests[0].path == '/does-not-exist/' @staticmethod @contextlib.contextmanager @@ -338,7 +319,7 @@ class TestSetupRequires(unittest.TestCase): Return an sdist with a setup_requires dependency (of something that doesn't exist) """ - with tempdir_context() as dir: + with contexts.tempdir() as dir: dist_path = os.path.join(dir, 'setuptools-test-fetcher-1.0.tar.gz') script = DALS(""" import setuptools @@ -366,21 +347,17 @@ class TestSetupRequires(unittest.TestCase): working_set.add(fake_dist) try: - with tempdir_context() as temp_dir: + with contexts.tempdir() as temp_dir: test_pkg = create_setup_requires_package(temp_dir) test_setup_py = os.path.join(test_pkg, 'setup.py') - with quiet_context() as (stdout, stderr): - try: - # Don't even need to install the package, just - # running the setup.py at all is sufficient - run_setup(test_setup_py, ['--name']) - except VersionConflict: - self.fail('Installing setup.py requirements ' - 'caused a VersionConflict') + with contexts.quiet() as (stdout, stderr): + # Don't even need to install the package, just + # running the setup.py at all is sufficient + run_setup(test_setup_py, ['--name']) lines = stdout.readlines() - self.assertTrue(len(lines) > 0) - self.assertTrue(lines[-1].strip(), 'test_pkg') + assert len(lines) > 0 + assert lines[-1].strip(), 'test_pkg' finally: pkg_resources.__setstate__(pr_state) @@ -438,51 +415,61 @@ def make_trivial_sdist(dist_path, setup_py): dist.addfile(setup_py_file, fileobj=setup_py_bytes) -@contextlib.contextmanager -def tempdir_context(cd=lambda dir:None): - temp_dir = tempfile.mkdtemp() - orig_dir = os.getcwd() - try: - cd(temp_dir) - yield temp_dir - finally: - cd(orig_dir) - shutil.rmtree(temp_dir) - -@contextlib.contextmanager -def environment_context(**updates): - old_env = os.environ.copy() - os.environ.update(updates) - try: - yield - finally: - for key in updates: - del os.environ[key] - os.environ.update(old_env) - -@contextlib.contextmanager -def argv_context(repl): - old_argv = sys.argv[:] - sys.argv[:] = repl - yield - sys.argv[:] = old_argv - +class TestScriptHeader: + non_ascii_exe = '/Users/José/bin/python' + exe_with_spaces = r'C:\Program Files\Python33\python.exe' -@contextlib.contextmanager -def quiet_context(): - """ - Redirect stdout/stderr to StringIO objects to prevent console output from - distutils commands. - """ - - old_stdout = sys.stdout - old_stderr = sys.stderr - new_stdout = sys.stdout = StringIO() - new_stderr = sys.stderr = StringIO() - try: - yield new_stdout, new_stderr - finally: - new_stdout.seek(0) - new_stderr.seek(0) - sys.stdout = old_stdout - sys.stderr = old_stderr + @pytest.mark.skipif( + sys.platform.startswith('java') and is_sh(sys.executable), + reason="Test cannot run under java when executable is sh" + ) + def test_get_script_header(self): + expected = '#!%s\n' % nt_quote_arg(os.path.normpath(sys.executable)) + assert get_script_header('#!/usr/local/bin/python') == expected + expected = '#!%s -x\n' % nt_quote_arg(os.path.normpath(sys.executable)) + assert get_script_header('#!/usr/bin/python -x') == expected + candidate = get_script_header('#!/usr/bin/python', + executable=self.non_ascii_exe) + assert candidate == '#!%s -x\n' % self.non_ascii_exe + candidate = get_script_header('#!/usr/bin/python', + executable=self.exe_with_spaces) + assert candidate == '#!"%s"\n' % self.exe_with_spaces + + @pytest.mark.xfail( + compat.PY3 and os.environ.get("LC_CTYPE") in ("C", "POSIX"), + reason="Test fails in this locale on Python 3" + ) + @mock.patch.dict(sys.modules, java=mock.Mock(lang=mock.Mock(System= + mock.Mock(getProperty=mock.Mock(return_value=""))))) + @mock.patch('sys.platform', 'java1.5.0_13') + def test_get_script_header_jython_workaround(self, tmpdir): + # Create a mock sys.executable that uses a shebang line + header = DALS(""" + #!/usr/bin/python + # -*- coding: utf-8 -*- + """) + exe = tmpdir / 'exe.py' + with exe.open('w') as f: + f.write(header) + exe = str(exe) + + header = get_script_header('#!/usr/local/bin/python', executable=exe) + assert header == '#!/usr/bin/env %s\n' % exe + + expect_out = 'stdout' if sys.version_info < (2,7) else 'stderr' + + with contexts.quiet() as (stdout, stderr): + # When options are included, generate a broken shebang line + # with a warning emitted + candidate = get_script_header('#!/usr/bin/python -x', + executable=exe) + assert candidate == '#!%s -x\n' % exe + output = locals()[expect_out] + assert 'Unable to adapt shebang line' in output.getvalue() + + with contexts.quiet() as (stdout, stderr): + candidate = get_script_header('#!/usr/bin/python', + executable=self.non_ascii_exe) + assert candidate == '#!%s -x\n' % self.non_ascii_exe + output = locals()[expect_out] + assert 'Unable to adapt shebang line' in output.getvalue() diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index 6b4d917f..a1caf9fd 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -1,79 +1,98 @@ import os -import tempfile -import shutil import stat -import unittest -from . import environment +import pytest +from . import environment +from .textwrap import DALS +from . import contexts -class TestEggInfo(unittest.TestCase): - def setUp(self): - self.test_dir = tempfile.mkdtemp() +class TestEggInfo: - self.old_cwd = os.getcwd() - os.chdir(self.test_dir) + setup_script = DALS(""" + from setuptools import setup - def tearDown(self): - os.chdir(self.old_cwd) - shutil.rmtree(self.test_dir) + setup( + name='foo', + py_modules=['hello'], + entry_points={'console_scripts': ['hi = hello.run']}, + zip_safe=False, + ) + """) def _create_project(self): with open('setup.py', 'w') as f: - f.write('from setuptools import setup\n') - f.write('\n') - f.write('setup(\n') - f.write(" name='foo',\n") - f.write(" py_modules=['hello'],\n") - f.write(" entry_points={'console_scripts': ['hi = hello.run']},\n") - f.write(' zip_safe=False,\n') - f.write(' )\n') + f.write(self.setup_script) + with open('hello.py', 'w') as f: - f.write('def run():\n') - f.write(" print('hello')\n") + f.write(DALS(""" + def run(): + print('hello') + """)) - def test_egg_base_installed_egg_info(self): - self._create_project() - temp_dir = tempfile.mkdtemp(prefix='setuptools-test.') - os.chmod(temp_dir, stat.S_IRWXU) - try: - paths = {} - for dirname in ['home', 'lib', 'scripts', 'data', 'egg-base']: - paths[dirname] = os.path.join(temp_dir, dirname) - os.mkdir(paths[dirname]) - config = os.path.join(paths['home'], '.pydistutils.cfg') + @pytest.yield_fixture + def env(self): + class Environment(str): pass + + with contexts.tempdir(prefix='setuptools-test.') as env_dir: + env = Environment(env_dir) + os.chmod(env_dir, stat.S_IRWXU) + subs = 'home', 'lib', 'scripts', 'data', 'egg-base' + env.paths = dict( + (dirname, os.path.join(env_dir, dirname)) + for dirname in subs + ) + list(map(os.mkdir, env.paths.values())) + config = os.path.join(env.paths['home'], '.pydistutils.cfg') with open(config, 'w') as f: - f.write('[egg_info]\n') - f.write('egg-base = %s\n' % paths['egg-base']) - environ = os.environ.copy() - environ['HOME'] = paths['home'] - code, data = environment.run_setup_py( - cmd=[ - 'install', '--home', paths['home'], - '--install-lib', paths['lib'], - '--install-scripts', paths['scripts'], - '--install-data', paths['data']], - pypath=os.pathsep.join([paths['lib'], self.old_cwd]), - data_stream=1, - env=environ) - if code: - raise AssertionError(data) - egg_info = None - for dirpath, dirnames, filenames in os.walk(paths['lib']): - if os.path.basename(dirpath) == 'EGG-INFO': - egg_info = sorted(filenames) - self.assertEqual( - egg_info, - ['PKG-INFO', - 'SOURCES.txt', - 'dependency_links.txt', - 'entry_points.txt', - 'not-zip-safe', - 'top_level.txt']) - finally: - shutil.rmtree(temp_dir) + f.write(DALS(""" + [egg_info] + egg-base = %(egg-base)s + """ % env.paths + )) + yield env + + def test_egg_base_installed_egg_info(self, tmpdir_cwd, env): + self._create_project() + + environ = os.environ.copy().update( + HOME=env.paths['home'], + ) + cmd = [ + 'install', + '--home', env.paths['home'], + '--install-lib', env.paths['lib'], + '--install-scripts', env.paths['scripts'], + '--install-data', env.paths['data'], + ] + code, data = environment.run_setup_py( + cmd=cmd, + pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]), + data_stream=1, + env=environ, + ) + if code: + raise AssertionError(data) + + actual = self._find_egg_info_files(env.paths['lib']) + expected = [ + 'PKG-INFO', + 'SOURCES.txt', + 'dependency_links.txt', + 'entry_points.txt', + 'not-zip-safe', + 'top_level.txt', + ] + assert sorted(actual) == expected -def test_suite(): - return unittest.defaultTestLoader.loadTestsFromName(__name__) + def _find_egg_info_files(self, root): + results = ( + filenames + for dirpath, dirnames, filenames in os.walk(root) + if os.path.basename(dirpath) == 'EGG-INFO' + ) + # expect exactly one result + result, = results + return result diff --git a/setuptools/tests/test_find_packages.py b/setuptools/tests/test_find_packages.py index fe390728..06a7c02e 100644 --- a/setuptools/tests/test_find_packages.py +++ b/setuptools/tests/test_find_packages.py @@ -3,12 +3,12 @@ import os import sys import shutil import tempfile -import unittest import platform +import pytest + import setuptools from setuptools import find_packages -from setuptools.tests.py26compat import skipIf find_420_packages = setuptools.PEP420PackageFinder.find @@ -33,13 +33,13 @@ def has_symlink(): ) return can_symlink() and not bad_symlink -class TestFindPackages(unittest.TestCase): +class TestFindPackages: - def setUp(self): + def setup_method(self, method): self.dist_dir = tempfile.mkdtemp() self._make_pkg_structure() - def tearDown(self): + def teardown_method(self, method): shutil.rmtree(self.dist_dir) def _make_pkg_structure(self): @@ -87,7 +87,7 @@ class TestFindPackages(unittest.TestCase): def test_regular_package(self): self._touch('__init__.py', self.pkg_dir) packages = find_packages(self.dist_dir) - self.assertEqual(packages, ['pkg', 'pkg.subpkg']) + assert packages == ['pkg', 'pkg.subpkg'] def test_exclude(self): self._touch('__init__.py', self.pkg_dir) @@ -102,7 +102,7 @@ class TestFindPackages(unittest.TestCase): alt_dir = self._mkdir('other_pkg', self.dist_dir) self._touch('__init__.py', alt_dir) packages = find_packages(self.dist_dir, include=['other_pkg']) - self.assertEqual(packages, ['other_pkg']) + assert packages == ['other_pkg'] def test_dir_with_dot_is_skipped(self): shutil.rmtree(os.path.join(self.dist_dir, 'pkg/subpkg/assets')) @@ -110,7 +110,7 @@ class TestFindPackages(unittest.TestCase): self._touch('__init__.py', data_dir) self._touch('file.dat', data_dir) packages = find_packages(self.dist_dir) - self.assertTrue('pkg.some.data' not in packages) + assert 'pkg.some.data' not in packages def test_dir_with_packages_in_subdir_is_excluded(self): """ @@ -121,9 +121,9 @@ class TestFindPackages(unittest.TestCase): build_pkg_dir = self._mkdir('pkg', build_dir) self._touch('__init__.py', build_pkg_dir) packages = find_packages(self.dist_dir) - self.assertTrue('build.pkg' not in packages) + assert 'build.pkg' not in packages - @skipIf(not has_symlink(), 'Symlink support required') + @pytest.mark.skipif(not has_symlink(), reason='Symlink support required') def test_symlinked_packages_are_included(self): """ A symbolically-linked directory should be treated like any other @@ -136,10 +136,10 @@ class TestFindPackages(unittest.TestCase): os.symlink('pkg', linked_pkg) assert os.path.isdir(linked_pkg) packages = find_packages(self.dist_dir) - self.assertTrue('lpkg' in packages) + assert 'lpkg' in packages def _assert_packages(self, actual, expected): - self.assertEqual(set(actual), set(expected)) + assert set(actual) == set(expected) def test_pep420_ns_package(self): packages = find_420_packages( diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py index 8d6c1e55..92a27080 100644 --- a/setuptools/tests/test_integration.py +++ b/setuptools/tests/test_integration.py @@ -14,6 +14,17 @@ from setuptools.command import easy_install as easy_install_pkg from setuptools.dist import Distribution +def setup_module(module): + packages = 'stevedore', 'virtualenvwrapper', 'pbr', 'novaclient' + for pkg in packages: + try: + __import__(pkg) + tmpl = "Integration tests cannot run when {pkg} is installed" + pytest.skip(tmpl.format(**locals())) + except ImportError: + pass + + @pytest.fixture def install_context(request, tmpdir, monkeypatch): """Fixture to set up temporary installation directory. @@ -27,7 +38,7 @@ def install_context(request, tmpdir, monkeypatch): def fin(): # undo the monkeypatch, particularly needed under # windows because of kept handle on cwd - monkeypatch.undo() + monkeypatch.undo() new_cwd.remove() user_base.remove() user_site.remove() @@ -71,7 +82,6 @@ def test_virtualenvwrapper(install_context): 'virtualenvwrapper', 'hook_loader.py') -@pytest.mark.xfail def test_pbr(install_context): _install_one('pbr', install_context, 'pbr', 'core.py') diff --git a/setuptools/tests/test_markerlib.py b/setuptools/tests/test_markerlib.py index dae71cba..8197b49d 100644 --- a/setuptools/tests/test_markerlib.py +++ b/setuptools/tests/test_markerlib.py @@ -1,48 +1,43 @@ import os -import unittest -from setuptools.tests.py26compat import skipIf -try: - import ast -except ImportError: - pass +import pytest -class TestMarkerlib(unittest.TestCase): - @skipIf('ast' not in globals(), - "ast not available (Python < 2.6?)") +class TestMarkerlib: + + @pytest.mark.importorskip('ast') def test_markers(self): from _markerlib import interpret, default_environment, compile - + os_name = os.name - - self.assertTrue(interpret("")) - - self.assertTrue(interpret("os.name != 'buuuu'")) - self.assertTrue(interpret("os_name != 'buuuu'")) - self.assertTrue(interpret("python_version > '1.0'")) - self.assertTrue(interpret("python_version < '5.0'")) - self.assertTrue(interpret("python_version <= '5.0'")) - self.assertTrue(interpret("python_version >= '1.0'")) - self.assertTrue(interpret("'%s' in os.name" % os_name)) - self.assertTrue(interpret("'%s' in os_name" % os_name)) - self.assertTrue(interpret("'buuuu' not in os.name")) - - self.assertFalse(interpret("os.name == 'buuuu'")) - self.assertFalse(interpret("os_name == 'buuuu'")) - self.assertFalse(interpret("python_version < '1.0'")) - self.assertFalse(interpret("python_version > '5.0'")) - self.assertFalse(interpret("python_version >= '5.0'")) - self.assertFalse(interpret("python_version <= '1.0'")) - self.assertFalse(interpret("'%s' not in os.name" % os_name)) - self.assertFalse(interpret("'buuuu' in os.name and python_version >= '5.0'")) - self.assertFalse(interpret("'buuuu' in os_name and python_version >= '5.0'")) - + + assert interpret("") + + assert interpret("os.name != 'buuuu'") + assert interpret("os_name != 'buuuu'") + assert interpret("python_version > '1.0'") + assert interpret("python_version < '5.0'") + assert interpret("python_version <= '5.0'") + assert interpret("python_version >= '1.0'") + assert interpret("'%s' in os.name" % os_name) + assert interpret("'%s' in os_name" % os_name) + assert interpret("'buuuu' not in os.name") + + assert not interpret("os.name == 'buuuu'") + assert not interpret("os_name == 'buuuu'") + assert not interpret("python_version < '1.0'") + assert not interpret("python_version > '5.0'") + assert not interpret("python_version >= '5.0'") + assert not interpret("python_version <= '1.0'") + assert not interpret("'%s' not in os.name" % os_name) + assert not interpret("'buuuu' in os.name and python_version >= '5.0'") + assert not interpret("'buuuu' in os_name and python_version >= '5.0'") + environment = default_environment() environment['extra'] = 'test' - self.assertTrue(interpret("extra == 'test'", environment)) - self.assertFalse(interpret("extra == 'doc'", environment)) - + assert interpret("extra == 'test'", environment) + assert not interpret("extra == 'doc'", environment) + def raises_nameError(): try: interpret("python.version == '42'") @@ -50,9 +45,9 @@ class TestMarkerlib(unittest.TestCase): pass else: raise Exception("Expected NameError") - + raises_nameError() - + def raises_syntaxError(): try: interpret("(x for x in (4,))") @@ -60,9 +55,9 @@ class TestMarkerlib(unittest.TestCase): pass else: raise Exception("Expected SyntaxError") - + raises_syntaxError() - + statement = "python_version == '5'" - self.assertEqual(compile(statement).__doc__, statement) - + assert compile(statement).__doc__ == statement + diff --git a/setuptools/tests/test_msvc9compiler.py b/setuptools/tests/test_msvc9compiler.py index 970f7679..a0820fff 100644 --- a/setuptools/tests/test_msvc9compiler.py +++ b/setuptools/tests/test_msvc9compiler.py @@ -1,157 +1,173 @@ -"""msvc9compiler monkey patch test - -This test ensures that importing setuptools is sufficient to replace -the standard find_vcvarsall function with our patched version that -finds the Visual C++ for Python package. +""" +Tests for msvc9compiler. """ import os -import shutil -import sys -import tempfile -import unittest -import distutils.errors import contextlib +import distutils.errors -# importing only setuptools should apply the patch -__import__('setuptools') - -class MockReg: - """Mock for distutils.msvc9compiler.Reg. We patch it - with an instance of this class that mocks out the - functions that access the registry. - """ - - def __init__(self, hkey_local_machine={}, hkey_current_user={}): - self.hklm = hkey_local_machine - self.hkcu = hkey_current_user +import pytest +import mock - def __enter__(self): - self.original_read_keys = distutils.msvc9compiler.Reg.read_keys - self.original_read_values = distutils.msvc9compiler.Reg.read_values +from . import contexts - _winreg = getattr(distutils.msvc9compiler, '_winreg', None) - winreg = getattr(distutils.msvc9compiler, 'winreg', _winreg) +# importing only setuptools should apply the patch +__import__('setuptools') - hives = { - winreg.HKEY_CURRENT_USER: self.hkcu, - winreg.HKEY_LOCAL_MACHINE: self.hklm, - } +pytest.importorskip("distutils.msvc9compiler") - def read_keys(cls, base, key): - """Return list of registry keys.""" - hive = hives.get(base, {}) - return [k.rpartition('\\')[2] - for k in hive if k.startswith(key.lower())] - def read_values(cls, base, key): - """Return dict of registry keys and values.""" - hive = hives.get(base, {}) - return dict((k.rpartition('\\')[2], hive[k]) - for k in hive if k.startswith(key.lower())) +def mock_reg(hkcu=None, hklm=None): + """ + Return a mock for distutils.msvc9compiler.Reg, patched + to mock out the functions that access the registry. + """ - distutils.msvc9compiler.Reg.read_keys = classmethod(read_keys) - distutils.msvc9compiler.Reg.read_values = classmethod(read_values) + _winreg = getattr(distutils.msvc9compiler, '_winreg', None) + winreg = getattr(distutils.msvc9compiler, 'winreg', _winreg) + + hives = { + winreg.HKEY_CURRENT_USER: hkcu or {}, + winreg.HKEY_LOCAL_MACHINE: hklm or {}, + } + + @classmethod + def read_keys(cls, base, key): + """Return list of registry keys.""" + hive = hives.get(base, {}) + return [ + k.rpartition('\\')[2] + for k in hive if k.startswith(key.lower()) + ] + + @classmethod + def read_values(cls, base, key): + """Return dict of registry keys and values.""" + hive = hives.get(base, {}) + return dict( + (k.rpartition('\\')[2], hive[k]) + for k in hive if k.startswith(key.lower()) + ) - return self + return mock.patch.multiple(distutils.msvc9compiler.Reg, + read_keys=read_keys, read_values=read_values) - def __exit__(self, exc_type, exc_value, exc_tb): - distutils.msvc9compiler.Reg.read_keys = self.original_read_keys - distutils.msvc9compiler.Reg.read_values = self.original_read_values -@contextlib.contextmanager -def patch_env(**replacements): +class TestModulePatch: """ - In a context, patch the environment with replacements. Pass None values - to clear the values. + Ensure that importing setuptools is sufficient to replace + the standard find_vcvarsall function with a version that + recognizes the "Visual C++ for Python" package. """ - saved = dict( - (key, os.environ['key']) - for key in replacements - if key in os.environ - ) - - # remove values that are null - remove = (key for (key, value) in replacements.items() if value is None) - for key in list(remove): - os.environ.pop(key, None) - replacements.pop(key) - - os.environ.update(replacements) - - try: - yield saved - finally: - for key in replacements: - os.environ.pop(key, None) - os.environ.update(saved) - -class TestMSVC9Compiler(unittest.TestCase): - - def test_find_vcvarsall_patch(self): - if not hasattr(distutils, 'msvc9compiler'): - # skip - return - - self.assertEqual( - "setuptools.msvc9_support", - distutils.msvc9compiler.find_vcvarsall.__module__, - "find_vcvarsall was not patched" - ) + key_32 = r'software\microsoft\devdiv\vcforpython\9.0\installdir' + key_64 = r'software\wow6432node\microsoft\devdiv\vcforpython\9.0\installdir' + + def test_patched(self): + "Test the module is actually patched" + mod_name = distutils.msvc9compiler.find_vcvarsall.__module__ + assert mod_name == "setuptools.msvc9_support", "find_vcvarsall unpatched" + + def test_no_registry_entryies_means_nothing_found(self): + """ + No registry entries or environment variable should lead to an error + directing the user to download vcpython27. + """ find_vcvarsall = distutils.msvc9compiler.find_vcvarsall query_vcvarsall = distutils.msvc9compiler.query_vcvarsall - # No registry entries or environment variable means we should - # not find anything - with patch_env(VS90COMNTOOLS=None): - with MockReg(): - self.assertIsNone(find_vcvarsall(9.0)) + with contexts.environment(VS90COMNTOOLS=None): + with mock_reg(): + assert find_vcvarsall(9.0) is None - try: + expected = distutils.errors.DistutilsPlatformError + with pytest.raises(expected) as exc: query_vcvarsall(9.0) - self.fail('Expected DistutilsPlatformError from query_vcvarsall()') - except distutils.errors.DistutilsPlatformError: - exc_message = str(sys.exc_info()[1]) - self.assertIn('aka.ms/vcpython27', exc_message) - - key_32 = r'software\microsoft\devdiv\vcforpython\9.0\installdir' - key_64 = r'software\wow6432node\microsoft\devdiv\vcforpython\9.0\installdir' - - # Make two mock files so we can tell whether HCKU entries are - # preferred to HKLM entries. - mock_installdir_1 = tempfile.mkdtemp() - mock_vcvarsall_bat_1 = os.path.join(mock_installdir_1, 'vcvarsall.bat') - open(mock_vcvarsall_bat_1, 'w').close() - mock_installdir_2 = tempfile.mkdtemp() - mock_vcvarsall_bat_2 = os.path.join(mock_installdir_2, 'vcvarsall.bat') - open(mock_vcvarsall_bat_2, 'w').close() - try: - # Ensure we get the current user's setting first - with MockReg( - hkey_current_user={key_32: mock_installdir_1}, - hkey_local_machine={ - key_32: mock_installdir_2, - key_64: mock_installdir_2, - } - ): - self.assertEqual(mock_vcvarsall_bat_1, find_vcvarsall(9.0)) - - # Ensure we get the local machine value if it's there - with MockReg(hkey_local_machine={key_32: mock_installdir_2}): - self.assertEqual(mock_vcvarsall_bat_2, find_vcvarsall(9.0)) - - # Ensure we prefer the 64-bit local machine key - # (*not* the Wow6432Node key) - with MockReg( - hkey_local_machine={ - # This *should* only exist on 32-bit machines - key_32: mock_installdir_1, - # This *should* only exist on 64-bit machines - key_64: mock_installdir_2, - } - ): - self.assertEqual(mock_vcvarsall_bat_1, find_vcvarsall(9.0)) - finally: - shutil.rmtree(mock_installdir_1) - shutil.rmtree(mock_installdir_2) + assert 'aka.ms/vcpython27' in str(exc) + + @pytest.yield_fixture + def user_preferred_setting(self): + """ + Set up environment with different install dirs for user vs. system + and yield the user_install_dir for the expected result. + """ + with self.mock_install_dir() as user_install_dir: + with self.mock_install_dir() as system_install_dir: + reg = mock_reg( + hkcu={ + self.key_32: user_install_dir, + }, + hklm={ + self.key_32: system_install_dir, + self.key_64: system_install_dir, + }, + ) + with reg: + yield user_install_dir + + def test_prefer_current_user(self, user_preferred_setting): + """ + Ensure user's settings are preferred. + """ + result = distutils.msvc9compiler.find_vcvarsall(9.0) + assert user_preferred_setting == result + + @pytest.yield_fixture + def local_machine_setting(self): + """ + Set up environment with only the system environment configured. + """ + with self.mock_install_dir() as system_install_dir: + reg = mock_reg( + hklm={ + self.key_32: system_install_dir, + }, + ) + with reg: + yield system_install_dir + + def test_local_machine_recognized(self, local_machine_setting): + """ + Ensure machine setting is honored if user settings are not present. + """ + result = distutils.msvc9compiler.find_vcvarsall(9.0) + assert local_machine_setting == result + + @pytest.yield_fixture + def x64_preferred_setting(self): + """ + Set up environment with 64-bit and 32-bit system settings configured + and yield the 64-bit location. + """ + with self.mock_install_dir() as x32_dir: + with self.mock_install_dir() as x64_dir: + reg = mock_reg( + hklm={ + # This *should* only exist on 32-bit machines + self.key_32: x32_dir, + # This *should* only exist on 64-bit machines + self.key_64: x64_dir, + }, + ) + with reg: + yield x64_dir + + def test_ensure_64_bit_preferred(self, x64_preferred_setting): + """ + Ensure 64-bit system key is preferred. + """ + result = distutils.msvc9compiler.find_vcvarsall(9.0) + assert x64_preferred_setting == result + + @staticmethod + @contextlib.contextmanager + def mock_install_dir(): + """ + Make a mock install dir in a unique location so that tests can + distinguish which dir was detected in a given scenario. + """ + with contexts.tempdir() as result: + vcvarsall = os.path.join(result, 'vcvarsall.bat') + with open(vcvarsall, 'w'): + pass + yield diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py index 664566a3..dcd90d6f 100644 --- a/setuptools/tests/test_packageindex.py +++ b/setuptools/tests/test_packageindex.py @@ -1,26 +1,24 @@ -"""Package Index Tests -""" import sys -import os -import unittest -import pkg_resources -from setuptools.compat import urllib2, httplib, HTTPError, unicode, pathname2url import distutils.errors + +from setuptools.compat import httplib, HTTPError, unicode, pathname2url + +import pkg_resources import setuptools.package_index from setuptools.tests.server import IndexServer -class TestPackageIndex(unittest.TestCase): + +class TestPackageIndex: def test_bad_url_bad_port(self): index = setuptools.package_index.PackageIndex() url = 'http://127.0.0.1:0/nonesuch/test_package_index' try: v = index.open_url(url) - except Exception: - v = sys.exc_info()[1] - self.assertTrue(url in str(v)) + except Exception as v: + assert url in str(v) else: - self.assertTrue(isinstance(v, HTTPError)) + assert isinstance(v, HTTPError) def test_bad_url_typo(self): # issue 16 @@ -33,11 +31,10 @@ class TestPackageIndex(unittest.TestCase): url = 'url:%20https://svn.plone.org/svn/collective/inquant.contentmirror.plone/trunk' try: v = index.open_url(url) - except Exception: - v = sys.exc_info()[1] - self.assertTrue(url in str(v)) + except Exception as v: + assert url in str(v) else: - self.assertTrue(isinstance(v, HTTPError)) + assert isinstance(v, HTTPError) def test_bad_url_bad_status_line(self): index = setuptools.package_index.PackageIndex( @@ -51,9 +48,8 @@ class TestPackageIndex(unittest.TestCase): url = 'http://example.com' try: v = index.open_url(url) - except Exception: - v = sys.exc_info()[1] - self.assertTrue('line' in str(v)) + except Exception as v: + assert 'line' in str(v) else: raise AssertionError('Should have raise here!') @@ -69,8 +65,7 @@ class TestPackageIndex(unittest.TestCase): url = 'http://http://svn.pythonpaste.org/Paste/wphp/trunk' try: index.open_url(url) - except distutils.errors.DistutilsError: - error = sys.exc_info()[1] + except distutils.errors.DistutilsError as error: msg = unicode(error) assert 'nonnumeric port' in msg or 'getaddrinfo failed' in msg or 'Name or service not known' in msg return @@ -94,7 +89,7 @@ class TestPackageIndex(unittest.TestCase): hosts=('www.example.com',) ) url = 'file:///tmp/test_package_index' - self.assertTrue(index.url_ok(url, True)) + assert index.url_ok(url, True) def test_links_priority(self): """ @@ -127,21 +122,30 @@ class TestPackageIndex(unittest.TestCase): server.stop() # the distribution has been found - self.assertTrue('foobar' in pi) + assert 'foobar' in pi # we have only one link, because links are compared without md5 - self.assertTrue(len(pi['foobar'])==1) + assert len(pi['foobar'])==1 # the link should be from the index - self.assertTrue('correct_md5' in pi['foobar'][0].location) + assert 'correct_md5' in pi['foobar'][0].location def test_parse_bdist_wininst(self): - self.assertEqual(setuptools.package_index.parse_bdist_wininst( - 'reportlab-2.5.win32-py2.4.exe'), ('reportlab-2.5', '2.4', 'win32')) - self.assertEqual(setuptools.package_index.parse_bdist_wininst( - 'reportlab-2.5.win32.exe'), ('reportlab-2.5', None, 'win32')) - self.assertEqual(setuptools.package_index.parse_bdist_wininst( - 'reportlab-2.5.win-amd64-py2.7.exe'), ('reportlab-2.5', '2.7', 'win-amd64')) - self.assertEqual(setuptools.package_index.parse_bdist_wininst( - 'reportlab-2.5.win-amd64.exe'), ('reportlab-2.5', None, 'win-amd64')) + parse = setuptools.package_index.parse_bdist_wininst + + actual = parse('reportlab-2.5.win32-py2.4.exe') + expected = 'reportlab-2.5', '2.4', 'win32' + assert actual == expected + + actual = parse('reportlab-2.5.win32.exe') + expected = 'reportlab-2.5', None, 'win32' + assert actual == expected + + actual = parse('reportlab-2.5.win-amd64-py2.7.exe') + expected = 'reportlab-2.5', '2.7', 'win-amd64' + assert actual == expected + + actual = parse('reportlab-2.5.win-amd64.exe') + expected = 'reportlab-2.5', None, 'win-amd64' + assert actual == expected def test__vcs_split_rev_from_url(self): """ @@ -149,55 +153,51 @@ class TestPackageIndex(unittest.TestCase): """ vsrfu = setuptools.package_index.PackageIndex._vcs_split_rev_from_url url, rev = vsrfu('https://example.com/bar@2995') - self.assertEqual(url, 'https://example.com/bar') - self.assertEqual(rev, '2995') + assert url == 'https://example.com/bar' + assert rev == '2995' - def test_local_index(self): + def test_local_index(self, tmpdir): """ local_open should be able to read an index from the file system. """ - f = open('index.html', 'w') - f.write('<div>content</div>') - f.close() - try: - url = 'file:' + pathname2url(os.getcwd()) + '/' - res = setuptools.package_index.local_open(url) - finally: - os.remove('index.html') + index_file = tmpdir / 'index.html' + with index_file.open('w') as f: + f.write('<div>content</div>') + url = 'file:' + pathname2url(str(tmpdir)) + '/' + res = setuptools.package_index.local_open(url) assert 'content' in res.read() -class TestContentCheckers(unittest.TestCase): +class TestContentCheckers: def test_md5(self): checker = setuptools.package_index.HashChecker.from_url( 'http://foo/bar#md5=f12895fdffbd45007040d2e44df98478') checker.feed('You should probably not be using MD5'.encode('ascii')) - self.assertEqual(checker.hash.hexdigest(), - 'f12895fdffbd45007040d2e44df98478') - self.assertTrue(checker.is_valid()) + assert checker.hash.hexdigest() == 'f12895fdffbd45007040d2e44df98478' + assert checker.is_valid() def test_other_fragment(self): "Content checks should succeed silently if no hash is present" checker = setuptools.package_index.HashChecker.from_url( 'http://foo/bar#something%20completely%20different') checker.feed('anything'.encode('ascii')) - self.assertTrue(checker.is_valid()) + assert checker.is_valid() def test_blank_md5(self): "Content checks should succeed if a hash is empty" checker = setuptools.package_index.HashChecker.from_url( 'http://foo/bar#md5=') checker.feed('anything'.encode('ascii')) - self.assertTrue(checker.is_valid()) + assert checker.is_valid() def test_get_hash_name_md5(self): checker = setuptools.package_index.HashChecker.from_url( 'http://foo/bar#md5=f12895fdffbd45007040d2e44df98478') - self.assertEqual(checker.hash_name, 'md5') + assert checker.hash_name == 'md5' def test_report(self): checker = setuptools.package_index.HashChecker.from_url( 'http://foo/bar#md5=f12895fdffbd45007040d2e44df98478') rep = checker.report(lambda x: x, 'My message about %s') - self.assertEqual(rep, 'My message about md5') + assert rep == 'My message about md5' diff --git a/setuptools/tests/test_sandbox.py b/setuptools/tests/test_sandbox.py index 6a890ebc..cadc4812 100644 --- a/setuptools/tests/test_sandbox.py +++ b/setuptools/tests/test_sandbox.py @@ -1,67 +1,43 @@ """develop tests """ -import sys import os -import shutil -import unittest -import tempfile import types +import pytest + import pkg_resources import setuptools.sandbox from setuptools.sandbox import DirectorySandbox, SandboxViolation -def has_win32com(): - """ - Run this to determine if the local machine has win32com, and if it - does, include additional tests. - """ - if not sys.platform.startswith('win32'): - return False - try: - __import__('win32com') - except ImportError: - return False - return True - -class TestSandbox(unittest.TestCase): - def setUp(self): - self.dir = tempfile.mkdtemp() +class TestSandbox: - def tearDown(self): - shutil.rmtree(self.dir) - - def test_devnull(self): - sandbox = DirectorySandbox(self.dir) + def test_devnull(self, tmpdir): + sandbox = DirectorySandbox(str(tmpdir)) sandbox.run(self._file_writer(os.devnull)) + @staticmethod def _file_writer(path): def do_write(): - f = open(path, 'w') - f.write('xxx') - f.close() + with open(path, 'w') as f: + f.write('xxx') return do_write - _file_writer = staticmethod(_file_writer) - - if has_win32com(): - def test_win32com(self): - """ - win32com should not be prevented from caching COM interfaces - in gen_py. - """ - import win32com - gen_py = win32com.__gen_path__ - target = os.path.join(gen_py, 'test_write') - sandbox = DirectorySandbox(self.dir) - try: - try: - sandbox.run(self._file_writer(target)) - except SandboxViolation: - self.fail("Could not create gen_py file due to SandboxViolation") - finally: - if os.path.exists(target): os.remove(target) + def test_win32com(self, tmpdir): + """ + win32com should not be prevented from caching COM interfaces + in gen_py. + """ + win32com = pytest.importorskip('win32com') + gen_py = win32com.__gen_path__ + target = os.path.join(gen_py, 'test_write') + sandbox = DirectorySandbox(str(tmpdir)) + try: + # attempt to create gen_py file + sandbox.run(self._file_writer(target)) + finally: + if os.path.exists(target): + os.remove(target) def test_setup_py_with_BOM(self): """ @@ -73,11 +49,8 @@ class TestSandbox(unittest.TestCase): setuptools.sandbox._execfile(target, vars(namespace)) assert namespace.result == 'passed' - def test_setup_py_with_CRLF(self): - setup_py = os.path.join(self.dir, 'setup.py') - with open(setup_py, 'wb') as stream: + def test_setup_py_with_CRLF(self, tmpdir): + setup_py = tmpdir / 'setup.py' + with setup_py.open('wb') as stream: stream.write(b'"degenerate script"\r\n') - setuptools.sandbox._execfile(setup_py, globals()) - -if __name__ == '__main__': - unittest.main() + setuptools.sandbox._execfile(str(setup_py), globals()) diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index 123e3ea9..d3494d7a 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -6,10 +6,10 @@ import os import shutil import sys import tempfile -import unittest import unicodedata import contextlib -from setuptools.tests.py26compat import skipIf + +import pytest import pkg_resources from setuptools.compat import StringIO, unicode, PY3, PY2 @@ -77,9 +77,9 @@ def decompose(path): return path -class TestSdistTest(unittest.TestCase): +class TestSdistTest: - def setUp(self): + 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) @@ -97,7 +97,7 @@ class TestSdistTest(unittest.TestCase): self.old_cwd = os.getcwd() os.chdir(self.temp_dir) - def tearDown(self): + def teardown_method(self, method): os.chdir(self.old_cwd) shutil.rmtree(self.temp_dir) @@ -116,9 +116,9 @@ class TestSdistTest(unittest.TestCase): cmd.run() manifest = cmd.filelist.files - self.assertTrue(os.path.join('sdist_test', 'a.txt') in manifest) - self.assertTrue(os.path.join('sdist_test', 'b.txt') in manifest) - self.assertTrue(os.path.join('sdist_test', 'c.rst') not in manifest) + assert os.path.join('sdist_test', 'a.txt') in manifest + assert os.path.join('sdist_test', 'b.txt') in manifest + assert os.path.join('sdist_test', 'c.rst') not in manifest def test_defaults_case_sensitivity(self): @@ -143,9 +143,9 @@ class TestSdistTest(unittest.TestCase): # 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) - self.assertFalse('readme.rst' in manifest, manifest) - self.assertFalse('setup.py' in manifest, manifest) - self.assertFalse('setup.cfg' in manifest, manifest) + assert 'readme.rst' not in manifest, manifest + assert 'setup.py' not in manifest, manifest + assert 'setup.cfg' not in manifest, manifest def test_manifest_is_written_with_utf8_encoding(self): # Test for #303. @@ -172,18 +172,14 @@ class TestSdistTest(unittest.TestCase): manifest.close() # The manifest should be UTF-8 encoded - try: - u_contents = contents.decode('UTF-8') - except UnicodeDecodeError: - e = sys.exc_info()[1] - self.fail(e) + u_contents = contents.decode('UTF-8') # The manifest should contain the UTF-8 filename if PY2: fs_enc = sys.getfilesystemencoding() filename = filename.decode(fs_enc) - self.assertTrue(posix(filename) in u_contents) + assert posix(filename) in u_contents # Python 3 only if PY3: @@ -215,17 +211,13 @@ class TestSdistTest(unittest.TestCase): manifest.close() # The manifest should be UTF-8 encoded - try: - contents.decode('UTF-8') - except UnicodeDecodeError: - e = sys.exc_info()[1] - self.fail(e) + contents.decode('UTF-8') # The manifest should contain the UTF-8 filename - self.assertTrue(posix(filename) in contents) + assert posix(filename) in contents # The filelist should have been updated as well - self.assertTrue(u_filename in mm.filelist.files) + assert u_filename in mm.filelist.files def test_write_manifest_skips_non_utf8_filenames(self): """ @@ -256,17 +248,13 @@ class TestSdistTest(unittest.TestCase): manifest.close() # The manifest should be UTF-8 encoded - try: - contents.decode('UTF-8') - except UnicodeDecodeError: - e = sys.exc_info()[1] - self.fail(e) + contents.decode('UTF-8') # The Latin-1 filename should have been skipped - self.assertFalse(posix(filename) in contents) + assert posix(filename) not in contents # The filelist should have been updated as well - self.assertFalse(u_filename in mm.filelist.files) + assert u_filename not in mm.filelist.files def test_manifest_is_read_with_utf8_encoding(self): # Test for #303. @@ -297,7 +285,7 @@ class TestSdistTest(unittest.TestCase): # The filelist should contain the UTF-8 filename if PY3: filename = filename.decode('utf-8') - self.assertTrue(filename in cmd.filelist.files) + assert filename in cmd.filelist.files # Python 3 only if PY3: @@ -326,18 +314,15 @@ class TestSdistTest(unittest.TestCase): # Re-read manifest cmd.filelist.files = [] with quiet(): - try: - cmd.read_manifest() - except UnicodeDecodeError: - e = sys.exc_info()[1] - self.fail(e) + cmd.read_manifest() # The Latin-1 filename should have been skipped filename = filename.decode('latin-1') - self.assertFalse(filename in cmd.filelist.files) + assert filename not in cmd.filelist.files - @skipIf(PY3 and locale.getpreferredencoding() != 'UTF-8', - 'Unittest fails if locale is not utf-8 but the manifests is recorded correctly') + @pytest.mark.skipif(PY3 and locale.getpreferredencoding() != 'UTF-8', + reason='Unittest fails if locale is not utf-8 but the manifests is ' + 'recorded correctly') def test_sdist_with_utf8_encoded_filename(self): # Test for #303. dist = Distribution(SETUP_ATTRS) @@ -362,15 +347,15 @@ class TestSdistTest(unittest.TestCase): if fs_enc == 'cp1252': # Python 3 mangles the UTF-8 filename filename = filename.decode('cp1252') - self.assertTrue(filename in cmd.filelist.files) + assert filename in cmd.filelist.files else: filename = filename.decode('mbcs') - self.assertTrue(filename in cmd.filelist.files) + assert filename in cmd.filelist.files else: filename = filename.decode('utf-8') - self.assertTrue(filename in cmd.filelist.files) + assert filename in cmd.filelist.files else: - self.assertTrue(filename in cmd.filelist.files) + assert filename in cmd.filelist.files def test_sdist_with_latin1_encoded_filename(self): # Test for #303. @@ -382,7 +367,7 @@ class TestSdistTest(unittest.TestCase): # Latin-1 filename filename = os.path.join(b('sdist_test'), LATIN1_FILENAME) open(filename, 'w').close() - self.assertTrue(os.path.isfile(filename)) + assert os.path.isfile(filename) with quiet(): cmd.run() @@ -398,11 +383,11 @@ class TestSdistTest(unittest.TestCase): else: filename = filename.decode('latin-1') - self.assertTrue(filename in cmd.filelist.files) + assert filename in cmd.filelist.files else: # The Latin-1 filename should have been skipped filename = filename.decode('latin-1') - self.assertFalse(filename in cmd.filelist.files) + filename not in cmd.filelist.files else: # Under Python 2 there seems to be no decoded string in the # filelist. However, due to decode and encoding of the @@ -412,9 +397,9 @@ class TestSdistTest(unittest.TestCase): # be proformed for the manifest output. fs_enc = sys.getfilesystemencoding() filename.decode(fs_enc) - self.assertTrue(filename in cmd.filelist.files) + assert filename in cmd.filelist.files except UnicodeDecodeError: - self.assertFalse(filename in cmd.filelist.files) + filename not in cmd.filelist.files def test_default_revctrl(): @@ -432,7 +417,3 @@ def test_default_revctrl(): ep = pkg_resources.EntryPoint.parse(ep_def) res = ep._load() assert hasattr(res, '__iter__') - - -def test_suite(): - return unittest.defaultTestLoader.loadTestsFromName(__name__) diff --git a/setuptools/tests/test_test.py b/setuptools/tests/test_test.py index df92085e..a66294c9 100644 --- a/setuptools/tests/test_test.py +++ b/setuptools/tests/test_test.py @@ -1,126 +1,91 @@ # -*- coding: UTF-8 -*- -"""develop tests -""" +from __future__ import unicode_literals + import os -import shutil import site -import sys -import tempfile -import unittest -from distutils.errors import DistutilsError -from setuptools.compat import StringIO, PY2 +import pytest + from setuptools.command.test import test -from setuptools.command import easy_install as easy_install_pkg from setuptools.dist import Distribution -SETUP_PY = """\ -from setuptools import setup - -setup(name='foo', - packages=['name', 'name.space', 'name.space.tests'], - namespace_packages=['name'], - test_suite='name.space.tests.test_suite', -) -""" - -NS_INIT = """# -*- coding: Latin-1 -*- -# Söme Arbiträry Ünicode to test Issüé 310 -try: - __import__('pkg_resources').declare_namespace(__name__) -except ImportError: - from pkgutil import extend_path - __path__ = extend_path(__path__, __name__) -""" -# Make sure this is Latin-1 binary, before writing: -if PY2: - NS_INIT = NS_INIT.decode('UTF-8') -NS_INIT = NS_INIT.encode('Latin-1') - -TEST_PY = """import unittest - -class TestTest(unittest.TestCase): - def test_test(self): - print "Foo" # Should fail under Python 3 unless 2to3 is used +from .textwrap import DALS +from . import contexts + +SETUP_PY = DALS(""" + from setuptools import setup + + setup(name='foo', + packages=['name', 'name.space', 'name.space.tests'], + namespace_packages=['name'], + test_suite='name.space.tests.test_suite', + ) + """) + +NS_INIT = DALS(""" + # -*- coding: Latin-1 -*- + # Söme Arbiträry Ünicode to test Distribute Issüé 310 + try: + __import__('pkg_resources').declare_namespace(__name__) + except ImportError: + from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) + """) + +TEST_PY = DALS(""" + import unittest -test_suite = unittest.makeSuite(TestTest) -""" + class TestTest(unittest.TestCase): + def test_test(self): + print "Foo" # Should fail under Python 3 unless 2to3 is used -class TestTestTest(unittest.TestCase): + test_suite = unittest.makeSuite(TestTest) + """) - def setUp(self): - if sys.version < "2.6" or hasattr(sys, 'real_prefix'): - return - # Directory structure - self.dir = tempfile.mkdtemp() - os.mkdir(os.path.join(self.dir, 'name')) - os.mkdir(os.path.join(self.dir, 'name', 'space')) - os.mkdir(os.path.join(self.dir, 'name', 'space', 'tests')) - # setup.py - setup = os.path.join(self.dir, 'setup.py') - f = open(setup, 'wt') +@pytest.fixture +def sample_test(tmpdir_cwd): + os.makedirs('name/space/tests') + + # setup.py + with open('setup.py', 'wt') as f: f.write(SETUP_PY) - f.close() - self.old_cwd = os.getcwd() - # name/__init__.py - init = os.path.join(self.dir, 'name', '__init__.py') - f = open(init, 'wb') - f.write(NS_INIT) - f.close() - # name/space/__init__.py - init = os.path.join(self.dir, 'name', 'space', '__init__.py') - f = open(init, 'wt') + + # name/__init__.py + with open('name/__init__.py', 'wb') as f: + f.write(NS_INIT.encode('Latin-1')) + + # name/space/__init__.py + with open('name/space/__init__.py', 'wt') as f: f.write('#empty\n') - f.close() - # name/space/tests/__init__.py - init = os.path.join(self.dir, 'name', 'space', 'tests', '__init__.py') - f = open(init, 'wt') - f.write(TEST_PY) - f.close() - os.chdir(self.dir) - self.old_base = site.USER_BASE - site.USER_BASE = tempfile.mkdtemp() - self.old_site = site.USER_SITE - site.USER_SITE = tempfile.mkdtemp() + # name/space/tests/__init__.py + with open('name/space/tests/__init__.py', 'wt') as f: + f.write(TEST_PY) - def tearDown(self): - if sys.version < "2.6" or hasattr(sys, 'real_prefix'): - return - os.chdir(self.old_cwd) - shutil.rmtree(self.dir) - shutil.rmtree(site.USER_BASE) - shutil.rmtree(site.USER_SITE) - site.USER_BASE = self.old_base - site.USER_SITE = self.old_site +@pytest.mark.skipif('hasattr(sys, "real_prefix")') +@pytest.mark.usefixtures('user_override') +@pytest.mark.usefixtures('sample_test') +class TestTestTest: def test_test(self): - if sys.version < "2.6" or hasattr(sys, 'real_prefix'): - return - - dist = Distribution(dict( + params = dict( name='foo', packages=['name', 'name.space', 'name.space.tests'], namespace_packages=['name'], test_suite='name.space.tests.test_suite', use_2to3=True, - )) + ) + dist = Distribution(params) dist.script_name = 'setup.py' cmd = test(dist) cmd.user = 1 cmd.ensure_finalized() cmd.install_dir = site.USER_SITE cmd.user = 1 - old_stdout = sys.stdout - sys.stdout = StringIO() - try: - try: # try/except/finally doesn't work in Python 2.4, so we need nested try-statements. + with contexts.quiet(): + # The test runner calls sys.exit + with contexts.suppress_exceptions(SystemExit): cmd.run() - except SystemExit: # The test runner calls sys.exit, stop that making an error. - pass - finally: - sys.stdout = old_stdout - diff --git a/setuptools/tests/test_upload_docs.py b/setuptools/tests/test_upload_docs.py index 769f16cc..cc71cadb 100644 --- a/setuptools/tests/test_upload_docs.py +++ b/setuptools/tests/test_upload_docs.py @@ -1,72 +1,59 @@ -"""build_ext tests -""" -import sys, os, shutil, tempfile, unittest, site, zipfile +import os +import zipfile +import contextlib + +import pytest + from setuptools.command.upload_docs import upload_docs from setuptools.dist import Distribution -SETUP_PY = """\ -from setuptools import setup +from .textwrap import DALS +from . import contexts + -setup(name='foo') -""" +SETUP_PY = DALS( + """ + from setuptools import setup -class TestUploadDocsTest(unittest.TestCase): - def setUp(self): - self.dir = tempfile.mkdtemp() - setup = os.path.join(self.dir, 'setup.py') - f = open(setup, 'w') + setup(name='foo') + """) + + +@pytest.fixture +def sample_project(tmpdir_cwd): + # setup.py + with open('setup.py', 'wt') as f: f.write(SETUP_PY) - f.close() - self.old_cwd = os.getcwd() - os.chdir(self.dir) - self.upload_dir = os.path.join(self.dir, 'build') - os.mkdir(self.upload_dir) + os.mkdir('build') - # A test document. - f = open(os.path.join(self.upload_dir, 'index.html'), 'w') + # A test document. + with open('build/index.html', 'w') as f: f.write("Hello world.") - f.close() - - # An empty folder. - os.mkdir(os.path.join(self.upload_dir, 'empty')) - - if sys.version >= "2.6": - self.old_base = site.USER_BASE - site.USER_BASE = upload_docs.USER_BASE = tempfile.mkdtemp() - self.old_site = site.USER_SITE - site.USER_SITE = upload_docs.USER_SITE = tempfile.mkdtemp() - - def tearDown(self): - os.chdir(self.old_cwd) - shutil.rmtree(self.dir) - if sys.version >= "2.6": - shutil.rmtree(site.USER_BASE) - shutil.rmtree(site.USER_SITE) - site.USER_BASE = self.old_base - site.USER_SITE = self.old_site + + # An empty folder. + os.mkdir('build/empty') + + +@pytest.mark.usefixtures('sample_project') +@pytest.mark.usefixtures('user_override') +class TestUploadDocsTest: def test_create_zipfile(self): - # Test to make sure zipfile creation handles common cases. - # This explicitly includes a folder containing an empty folder. + """ + Ensure zipfile creation handles common cases, including a folder + containing an empty folder. + """ dist = Distribution() cmd = upload_docs(dist) - cmd.upload_dir = self.upload_dir - cmd.target_dir = self.upload_dir - tmp_dir = tempfile.mkdtemp() - tmp_file = os.path.join(tmp_dir, 'foo.zip') - try: + cmd.target_dir = cmd.upload_dir = 'build' + with contexts.tempdir() as tmp_dir: + tmp_file = os.path.join(tmp_dir, 'foo.zip') zip_file = cmd.create_zipfile(tmp_file) assert zipfile.is_zipfile(tmp_file) - zip_file = zipfile.ZipFile(tmp_file) # woh... - - assert zip_file.namelist() == ['index.html'] - - zip_file.close() - finally: - shutil.rmtree(tmp_dir) - + with contextlib.closing(zipfile.ZipFile(tmp_file)) as zip_file: + assert zip_file.namelist() == ['index.html'] diff --git a/setuptools/tests/test_windows_wrappers.py b/setuptools/tests/test_windows_wrappers.py new file mode 100644 index 00000000..5b14d07b --- /dev/null +++ b/setuptools/tests/test_windows_wrappers.py @@ -0,0 +1,183 @@ +""" +Python Script Wrapper for Windows +================================= + +setuptools includes wrappers for Python scripts that allows them to be +executed like regular windows programs. There are 2 wrappers, one +for command-line programs, cli.exe, and one for graphical programs, +gui.exe. These programs are almost identical, function pretty much +the same way, and are generated from the same source file. The +wrapper programs are used by copying them to the directory containing +the script they are to wrap and with the same name as the script they +are to wrap. +""" + +from __future__ import absolute_import + +import sys +import textwrap +import subprocess + +import pytest + +from setuptools.command.easy_install import nt_quote_arg +import pkg_resources + + +pytestmark = pytest.mark.skipif(sys.platform != 'win32', reason="Windows only") + + +class WrapperTester: + + @classmethod + def prep_script(cls, template): + python_exe = nt_quote_arg(sys.executable) + return template % locals() + + @classmethod + def create_script(cls, tmpdir): + """ + Create a simple script, foo-script.py + + Note that the script starts with a Unix-style '#!' line saying which + Python executable to run. The wrapper will use this line to find the + correct Python executable. + """ + + script = cls.prep_script(cls.script_tmpl) + + with (tmpdir / cls.script_name).open('w') as f: + f.write(script) + + # also copy cli.exe to the sample directory + with (tmpdir / cls.wrapper_name).open('wb') as f: + w = pkg_resources.resource_string('setuptools', cls.wrapper_source) + f.write(w) + + +class TestCLI(WrapperTester): + script_name = 'foo-script.py' + wrapper_source = 'cli-32.exe' + wrapper_name = 'foo.exe' + script_tmpl = textwrap.dedent(""" + #!%(python_exe)s + import sys + input = repr(sys.stdin.read()) + print(sys.argv[0][-14:]) + print(sys.argv[1:]) + print(input) + if __debug__: + print('non-optimized') + """).lstrip() + + def test_basic(self, tmpdir): + """ + When the copy of cli.exe, foo.exe in this example, runs, it examines + the path name it was run with and computes a Python script path name + by removing the '.exe' suffix and adding the '-script.py' suffix. (For + GUI programs, the suffix '-script.pyw' is added.) This is why we + named out script the way we did. Now we can run out script by running + the wrapper: + + This example was a little pathological in that it exercised windows + (MS C runtime) quoting rules: + + - Strings containing spaces are surrounded by double quotes. + + - Double quotes in strings need to be escaped by preceding them with + back slashes. + + - One or more backslashes preceding double quotes need to be escaped + by preceding each of them with back slashes. + """ + self.create_script(tmpdir) + cmd = [ + str(tmpdir / 'foo.exe'), + 'arg1', + 'arg 2', + 'arg "2\\"', + 'arg 4\\', + 'arg5 a\\\\b', + ] + 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""" + \foo-script.py + ['arg1', 'arg 2', 'arg "2\\"', 'arg 4\\', 'arg5 a\\\\b'] + 'hello\nworld\n' + non-optimized + """).lstrip() + assert actual == expected + + def test_with_options(self, tmpdir): + """ + Specifying Python Command-line Options + -------------------------------------- + + You can specify a single argument on the '#!' line. This can be used + to specify Python options like -O, to run in optimized mode or -i + to start the interactive interpreter. You can combine multiple + options as usual. For example, to run in optimized mode and + enter the interpreter after running the script, you could use -Oi: + """ + self.create_script(tmpdir) + tmpl = textwrap.dedent(""" + #!%(python_exe)s -Oi + import sys + input = repr(sys.stdin.read()) + print(sys.argv[0][-14:]) + print(sys.argv[1:]) + print(input) + if __debug__: + print('non-optimized') + sys.ps1 = '---' + """).lstrip() + 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) + stdout, stderr = proc.communicate() + actual = stdout.decode('ascii').replace('\r\n', '\n') + expected = textwrap.dedent(r""" + \foo-script.py + [] + '' + --- + """).lstrip() + assert actual == expected + + +class TestGUI(WrapperTester): + """ + Testing the GUI Version + ----------------------- + """ + script_name = 'bar-script.pyw' + wrapper_source = 'gui-32.exe' + wrapper_name = 'bar.exe' + + script_tmpl = textwrap.dedent(""" + #!%(python_exe)s + import sys + f = open(sys.argv[1], 'wb') + bytes_written = f.write(repr(sys.argv[2]).encode('utf-8')) + f.close() + """).strip() + + def test_basic(self, tmpdir): + """Test the GUI version with the simple scipt, bar-script.py""" + self.create_script(tmpdir) + + cmd = [ + str(tmpdir / 'bar.exe'), + str(tmpdir / 'test_output.txt'), + 'Test Argument', + ] + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) + stdout, stderr = proc.communicate() + assert not stdout + assert not stderr + with (tmpdir / 'test_output.txt').open('rb') as f_out: + actual = f_out.read().decode('ascii') + assert actual == repr('Test Argument') diff --git a/setuptools/tests/textwrap.py b/setuptools/tests/textwrap.py new file mode 100644 index 00000000..5cd9e5bc --- /dev/null +++ b/setuptools/tests/textwrap.py @@ -0,0 +1,8 @@ +from __future__ import absolute_import + +import textwrap + + +def DALS(s): + "dedent and left-strip" + return textwrap.dedent(s).lstrip() diff --git a/setuptools/tests/win_script_wrapper.txt b/setuptools/tests/win_script_wrapper.txt deleted file mode 100644 index b3a52e0a..00000000 --- a/setuptools/tests/win_script_wrapper.txt +++ /dev/null @@ -1,154 +0,0 @@ -Python Script Wrapper for Windows -================================= - -setuptools includes wrappers for Python scripts that allows them to be -executed like regular windows programs. There are 2 wrappers, once -for command-line programs, cli.exe, and one for graphical programs, -gui.exe. These programs are almost identical, function pretty much -the same way, and are generated from the same source file. The -wrapper programs are used by copying them to the directory containing -the script they are to wrap and with the same name as the script they -are to wrap. In the rest of this document, we'll give an example that -will illustrate this. - -Let's create a simple script, foo-script.py: - - >>> import os, sys, tempfile - >>> from setuptools.command.easy_install import nt_quote_arg - >>> sample_directory = tempfile.mkdtemp() - >>> f = open(os.path.join(sample_directory, 'foo-script.py'), 'w') - >>> bytes_written = f.write( - ... """#!%(python_exe)s - ... import sys - ... input = repr(sys.stdin.read()) - ... print(sys.argv[0][-14:]) - ... print(sys.argv[1:]) - ... print(input) - ... if __debug__: - ... print('non-optimized') - ... """ % dict(python_exe=nt_quote_arg(sys.executable))) - >>> f.close() - -Note that the script starts with a Unix-style '#!' line saying which -Python executable to run. The wrapper will use this to find the -correct Python executable. - -We'll also copy cli.exe to the sample-directory with the name foo.exe: - - >>> import pkg_resources - >>> f = open(os.path.join(sample_directory, 'foo.exe'), 'wb') - >>> bytes_written = f.write( - ... pkg_resources.resource_string('setuptools', 'cli-32.exe') - ... ) - >>> f.close() - -When the copy of cli.exe, foo.exe in this example, runs, it examines -the path name it was run with and computes a Python script path name -by removing the '.exe' suffix and adding the '-script.py' suffix. (For -GUI programs, the suffix '-script-pyw' is added.) This is why we -named out script the way we did. Now we can run out script by running -the wrapper: - - >>> import subprocess - >>> cmd = [os.path.join(sample_directory, 'foo.exe'), 'arg1', 'arg 2', - ... 'arg "2\\"', 'arg 4\\', 'arg5 a\\\\b'] - >>> proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE) - >>> stdout, stderr = proc.communicate('hello\nworld\n'.encode('ascii')) - >>> bytes = sys.stdout.write(stdout.decode('ascii').replace('\r\n', '\n')) - \foo-script.py - ['arg1', 'arg 2', 'arg "2\\"', 'arg 4\\', 'arg5 a\\\\b'] - 'hello\nworld\n' - non-optimized - -This example was a little pathological in that it exercised windows -(MS C runtime) quoting rules: - -- Strings containing spaces are surrounded by double quotes. - -- Double quotes in strings need to be escaped by preceding them with - back slashes. - -- One or more backslashes preceding double quotes need to be escaped - by preceding each of them with back slashes. - - -Specifying Python Command-line Options --------------------------------------- - -You can specify a single argument on the '#!' line. This can be used -to specify Python options like -O, to run in optimized mode or -i -to start the interactive interpreter. You can combine multiple -options as usual. For example, to run in optimized mode and -enter the interpreter after running the script, you could use -Oi: - - >>> f = open(os.path.join(sample_directory, 'foo-script.py'), 'w') - >>> bytes_written = f.write( - ... """#!%(python_exe)s -Oi - ... import sys - ... input = repr(sys.stdin.read()) - ... print(sys.argv[0][-14:]) - ... print(sys.argv[1:]) - ... print(input) - ... if __debug__: - ... print('non-optimized') - ... sys.ps1 = '---' - ... """ % dict(python_exe=nt_quote_arg(sys.executable))) - >>> f.close() - >>> cmd = [os.path.join(sample_directory, 'foo.exe')] - >>> proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) - >>> stdout, stderr = proc.communicate() - >>> bytes = sys.stdout.write(stdout.decode('ascii').replace('\r\n', '\n')) - \foo-script.py - [] - '' - --- - -Testing the GUI Version ------------------------ - -Now let's test the GUI version with the simple scipt, bar-script.py: - - >>> import os, sys, tempfile - >>> from setuptools.command.easy_install import nt_quote_arg - >>> sample_directory = tempfile.mkdtemp() - >>> f = open(os.path.join(sample_directory, 'bar-script.pyw'), 'w') - >>> bytes_written = f.write( - ... """#!%(python_exe)s - ... import sys - ... f = open(sys.argv[1], 'wb') - ... bytes_written = f.write(repr(sys.argv[2]).encode('utf-8')) - ... f.close() - ... """ % dict(python_exe=nt_quote_arg(sys.executable))) - >>> f.close() - -We'll also copy gui.exe to the sample-directory with the name bar.exe: - - >>> import pkg_resources - >>> f = open(os.path.join(sample_directory, 'bar.exe'), 'wb') - >>> bytes_written = f.write( - ... pkg_resources.resource_string('setuptools', 'gui-32.exe') - ... ) - >>> f.close() - -Finally, we'll run the script and check the result: - - >>> cmd = [ - ... os.path.join(sample_directory, 'bar.exe'), - ... os.path.join(sample_directory, 'test_output.txt'), - ... 'Test Argument', - ... ] - >>> proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) - >>> stdout, stderr = proc.communicate() - >>> print(stdout.decode('ascii')) - <BLANKLINE> - >>> f_out = open(os.path.join(sample_directory, 'test_output.txt'), 'rb') - >>> print(f_out.read().decode('ascii')) - 'Test Argument' - >>> f_out.close() - - -We're done with the sample_directory: - - >>> import shutil - >>> shutil.rmtree(sample_directory) - diff --git a/setuptools/version.py b/setuptools/version.py index 6b917706..a6fd3e36 100644 --- a/setuptools/version.py +++ b/setuptools/version.py @@ -1 +1 @@ -__version__ = '10.2' +__version__ = '11.2' diff --git a/tests/shlib_test/hello.c b/tests/shlib_test/hello.c deleted file mode 100755 index 9998372c..00000000 --- a/tests/shlib_test/hello.c +++ /dev/null @@ -1,168 +0,0 @@ -/* Generated by Pyrex 0.9.3 on Thu Jan 05 17:47:12 2006 */ - -#include "Python.h" -#include "structmember.h" -#ifndef PY_LONG_LONG - #define PY_LONG_LONG LONG_LONG -#endif - - -typedef struct {PyObject **p; char *s;} __Pyx_InternTabEntry; /*proto*/ -typedef struct {PyObject **p; char *s; long n;} __Pyx_StringTabEntry; /*proto*/ -static PyObject *__Pyx_UnpackItem(PyObject *, int); /*proto*/ -static int __Pyx_EndUnpack(PyObject *, int); /*proto*/ -static int __Pyx_PrintItem(PyObject *); /*proto*/ -static int __Pyx_PrintNewline(void); /*proto*/ -static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb); /*proto*/ -static void __Pyx_ReRaise(void); /*proto*/ -static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list); /*proto*/ -static PyObject *__Pyx_GetExcValue(void); /*proto*/ -static int __Pyx_ArgTypeTest(PyObject *obj, PyTypeObject *type, int none_allowed, char *name); /*proto*/ -static int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type); /*proto*/ -static int __Pyx_GetStarArgs(PyObject **args, PyObject **kwds, char *kwd_list[], int nargs, PyObject **args2, PyObject **kwds2); /*proto*/ -static void __Pyx_WriteUnraisable(char *name); /*proto*/ -static void __Pyx_AddTraceback(char *funcname); /*proto*/ -static PyTypeObject *__Pyx_ImportType(char *module_name, char *class_name, long size); /*proto*/ -static int __Pyx_SetVtable(PyObject *dict, void *vtable); /*proto*/ -static int __Pyx_GetVtable(PyObject *dict, void *vtabptr); /*proto*/ -static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name, char *modname); /*proto*/ -static int __Pyx_InternStrings(__Pyx_InternTabEntry *t); /*proto*/ -static int __Pyx_InitStrings(__Pyx_StringTabEntry *t); /*proto*/ -static PyObject *__Pyx_GetName(PyObject *dict, PyObject *name); /*proto*/ - -static PyObject *__pyx_m; -static PyObject *__pyx_b; -static int __pyx_lineno; -static char *__pyx_filename; -staticforward char **__pyx_f; - -/* Declarations from hello */ - -char (*(get_hello_msg(void))); /*proto*/ - -/* Implementation of hello */ - -static PyObject *__pyx_n_hello; - -static PyObject *__pyx_f_5hello_hello(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ -static PyObject *__pyx_f_5hello_hello(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { - PyObject *__pyx_r; - PyObject *__pyx_1 = 0; - static char *__pyx_argnames[] = {0}; - if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0; - - /* "C:\cygwin\home\pje\setuptools\tests\shlib_test\hello.pyx":4 */ - __pyx_1 = PyString_FromString(get_hello_msg()); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 4; goto __pyx_L1;} - __pyx_r = __pyx_1; - __pyx_1 = 0; - goto __pyx_L0; - - __pyx_r = Py_None; Py_INCREF(__pyx_r); - goto __pyx_L0; - __pyx_L1:; - Py_XDECREF(__pyx_1); - __Pyx_AddTraceback("hello.hello"); - __pyx_r = 0; - __pyx_L0:; - return __pyx_r; -} - -static __Pyx_InternTabEntry __pyx_intern_tab[] = { - {&__pyx_n_hello, "hello"}, - {0, 0} -}; - -static struct PyMethodDef __pyx_methods[] = { - {"hello", (PyCFunction)__pyx_f_5hello_hello, METH_VARARGS|METH_KEYWORDS, 0}, - {0, 0, 0, 0} -}; - -DL_EXPORT(void) inithello(void); /*proto*/ -DL_EXPORT(void) inithello(void) { - __pyx_m = Py_InitModule4("hello", __pyx_methods, 0, 0, PYTHON_API_VERSION); - if (!__pyx_m) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1; goto __pyx_L1;}; - __pyx_b = PyImport_AddModule("__builtin__"); - if (!__pyx_b) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1; goto __pyx_L1;}; - if (PyObject_SetAttrString(__pyx_m, "__builtins__", __pyx_b) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1; goto __pyx_L1;}; - if (__Pyx_InternStrings(__pyx_intern_tab) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1; goto __pyx_L1;}; - - /* "C:\cygwin\home\pje\setuptools\tests\shlib_test\hello.pyx":3 */ - return; - __pyx_L1:; - __Pyx_AddTraceback("hello"); -} - -static char *__pyx_filenames[] = { - "hello.pyx", -}; -statichere char **__pyx_f = __pyx_filenames; - -/* Runtime support code */ - -static int __Pyx_InternStrings(__Pyx_InternTabEntry *t) { - while (t->p) { - *t->p = PyString_InternFromString(t->s); - if (!*t->p) - return -1; - ++t; - } - return 0; -} - -#include "compile.h" -#include "frameobject.h" -#include "traceback.h" - -static void __Pyx_AddTraceback(char *funcname) { - PyObject *py_srcfile = 0; - PyObject *py_funcname = 0; - PyObject *py_globals = 0; - PyObject *empty_tuple = 0; - PyObject *empty_string = 0; - PyCodeObject *py_code = 0; - PyFrameObject *py_frame = 0; - - py_srcfile = PyString_FromString(__pyx_filename); - if (!py_srcfile) goto bad; - py_funcname = PyString_FromString(funcname); - if (!py_funcname) goto bad; - py_globals = PyModule_GetDict(__pyx_m); - if (!py_globals) goto bad; - empty_tuple = PyTuple_New(0); - if (!empty_tuple) goto bad; - empty_string = PyString_FromString(""); - if (!empty_string) goto bad; - py_code = PyCode_New( - 0, /*int argcount,*/ - 0, /*int nlocals,*/ - 0, /*int stacksize,*/ - 0, /*int flags,*/ - empty_string, /*PyObject *code,*/ - empty_tuple, /*PyObject *consts,*/ - empty_tuple, /*PyObject *names,*/ - empty_tuple, /*PyObject *varnames,*/ - empty_tuple, /*PyObject *freevars,*/ - empty_tuple, /*PyObject *cellvars,*/ - py_srcfile, /*PyObject *filename,*/ - py_funcname, /*PyObject *name,*/ - __pyx_lineno, /*int firstlineno,*/ - empty_string /*PyObject *lnotab*/ - ); - if (!py_code) goto bad; - py_frame = PyFrame_New( - PyThreadState_Get(), /*PyThreadState *tstate,*/ - py_code, /*PyCodeObject *code,*/ - py_globals, /*PyObject *globals,*/ - 0 /*PyObject *locals*/ - ); - if (!py_frame) goto bad; - py_frame->f_lineno = __pyx_lineno; - PyTraceBack_Here(py_frame); -bad: - Py_XDECREF(py_srcfile); - Py_XDECREF(py_funcname); - Py_XDECREF(empty_tuple); - Py_XDECREF(empty_string); - Py_XDECREF(py_code); - Py_XDECREF(py_frame); -} diff --git a/tests/shlib_test/hello.pyx b/tests/shlib_test/hello.pyx deleted file mode 100755 index 58ce6919..00000000 --- a/tests/shlib_test/hello.pyx +++ /dev/null @@ -1,4 +0,0 @@ -cdef extern char *get_hello_msg() - -def hello(): - return get_hello_msg() diff --git a/tests/shlib_test/hellolib.c b/tests/shlib_test/hellolib.c deleted file mode 100755 index 88d65cee..00000000 --- a/tests/shlib_test/hellolib.c +++ /dev/null @@ -1,3 +0,0 @@ -extern char* get_hello_msg() { - return "Hello, world!"; -} diff --git a/tests/shlib_test/setup.py b/tests/shlib_test/setup.py deleted file mode 100755 index b0c93996..00000000 --- a/tests/shlib_test/setup.py +++ /dev/null @@ -1,10 +0,0 @@ -from setuptools import setup, Extension, Library - -setup( - name="shlib_test", - ext_modules = [ - Library("hellolib", ["hellolib.c"]), - Extension("hello", ["hello.pyx"], libraries=["hellolib"]) - ], - test_suite="test_hello.HelloWorldTest", -) diff --git a/tests/shlib_test/test_hello.py b/tests/shlib_test/test_hello.py deleted file mode 100755 index 6da02e31..00000000 --- a/tests/shlib_test/test_hello.py +++ /dev/null @@ -1,7 +0,0 @@ -from unittest import TestCase - -class HelloWorldTest(TestCase): - def testHelloMsg(self): - from hello import hello - self.assertEqual(hello(), "Hello, world!") - |
