diff options
| author | Jason R. Coombs <jaraco@jaraco.com> | 2015-03-06 17:04:35 -0500 |
|---|---|---|
| committer | Jason R. Coombs <jaraco@jaraco.com> | 2015-03-06 17:04:35 -0500 |
| commit | dbb11565d5880616866a608ce04f93b2b3efedd8 (patch) | |
| tree | 1ed31bcaf743f05ad91e4ef9742b0ea5245b0c22 /setuptools | |
| parent | a979a4494369abbe40fcf65738fc78fc5ae88d7d (diff) | |
| parent | 4334bd87fdb0e60ed6e019ad2eb3ee103032d316 (diff) | |
| download | python-setuptools-bitbucket-dbb11565d5880616866a608ce04f93b2b3efedd8.tar.gz | |
Merge pull request #25 from dhellmann/fix-tox
Fix tox settings so they work
Diffstat (limited to 'setuptools')
| -rw-r--r-- | setuptools/command/bdist_egg.py | 13 | ||||
| -rw-r--r-- | setuptools/command/build_py.py | 17 | ||||
| -rwxr-xr-x | setuptools/command/easy_install.py | 282 | ||||
| -rwxr-xr-x | setuptools/command/install_scripts.py | 17 | ||||
| -rw-r--r-- | setuptools/dist.py | 5 | ||||
| -rw-r--r-- | setuptools/msvc9_support.py | 8 | ||||
| -rwxr-xr-x | setuptools/sandbox.py | 88 | ||||
| -rw-r--r-- | setuptools/tests/contexts.py | 10 | ||||
| -rw-r--r-- | setuptools/tests/fixtures.py | 5 | ||||
| -rw-r--r-- | setuptools/tests/py26compat.py | 5 | ||||
| -rw-r--r-- | setuptools/tests/test_easy_install.py | 214 | ||||
| -rw-r--r-- | setuptools/tests/test_integration.py | 6 | ||||
| -rw-r--r-- | setuptools/tests/test_msvc9compiler.py | 20 | ||||
| -rw-r--r-- | setuptools/tests/test_sandbox.py | 48 | ||||
| -rw-r--r-- | setuptools/version.py | 2 |
15 files changed, 429 insertions, 311 deletions
diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py index 34fdeec2..87dce882 100644 --- a/setuptools/command/bdist_egg.py +++ b/setuptools/command/bdist_egg.py @@ -2,7 +2,6 @@ Build .egg distributions""" -# This module should be kept compatible with Python 2.3 from distutils.errors import DistutilsSetupError from distutils.dir_util import remove_tree, mkpath from distutils import log @@ -406,10 +405,6 @@ def scan_module(egg_dir, base, name, stubs): if bad in symbols: log.warn("%s: module MAY be using inspect.%s", module, bad) safe = False - if '__name__' in symbols and '__main__' in symbols and '.' not in module: - if sys.version[:3] == "2.4": # -m works w/zipfiles in 2.5 - log.warn("%s: top-level module may be 'python -m' script", module) - safe = False return safe @@ -441,7 +436,7 @@ INSTALL_DIRECTORY_ATTRS = [ ] -def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=None, +def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=True, mode='w'): """Create a zip file from all the files under 'base_dir'. The output zip file will be named 'base_dir' + ".zip". Uses either the "zipfile" @@ -463,11 +458,7 @@ def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=None, z.write(path, p) log.debug("adding '%s'" % p) - if compress is None: - # avoid 2.3 zipimport bug when 64 bits - compress = (sys.version >= "2.4") - - compression = [zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED][bool(compress)] + compression = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED if not dry_run: z = zipfile.ZipFile(zip_filename, mode, compression=compression) for dirname, dirs, files in os.walk(base_dir): diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index 98080694..a873d54b 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -136,22 +136,7 @@ class build_py(orig.build_py, Mixin2to3): mf.setdefault(src_dirs[d], []).append(path) def get_data_files(self): - pass # kludge 2.4 for lazy computation - - if sys.version < "2.4": # Python 2.4 already has this code - def get_outputs(self, include_bytecode=1): - """Return complete list of files copied to the build directory - - This includes both '.py' files and data files, as well as '.pyc' - and '.pyo' files if 'include_bytecode' is true. (This method is - needed for the 'install_lib' command to do its job properly, and to - generate a correct installation manifest.) - """ - return orig.build_py.get_outputs(self, include_bytecode) + [ - os.path.join(build_dir, filename) - for package, src_dir, build_dir, filenames in self.data_files - for filename in filenames - ] + pass # Lazily compute data files in _get_data_files() function. def check_package(self, package, package_dir): """Check namespace packages' __init__ for declare_namespace""" diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 340b1fac..4e841520 100755 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -37,6 +37,7 @@ import struct import contextlib import subprocess import shlex +import io from setuptools import Command from setuptools.sandbox import run_setup @@ -56,7 +57,6 @@ from pkg_resources import ( ) import pkg_resources - # Turn on PEP440Warnings warnings.filterwarnings("default", category=pkg_resources.PEP440Warning) @@ -152,12 +152,9 @@ class easy_install(Command): create_index = PackageIndex def initialize_options(self): - if site.ENABLE_USER_SITE: - whereami = os.path.abspath(__file__) - self.user = whereami.startswith(site.USER_SITE) - else: - self.user = 0 - + # the --user option seemst to be an opt-in one, + # so the default should be False. + self.user = 0 self.zip_ok = self.local_snapshots_ok = None self.install_dir = self.script_dir = self.exclude_scripts = None self.index_url = None @@ -446,43 +443,49 @@ class easy_install(Command): self.pth_file = None # and don't create a .pth file self.install_dir = instdir - def cant_write_to_target(self): - template = """can't create or remove files in install directory + __cant_write_msg = textwrap.dedent(""" + can't create or remove files in install directory -The following error occurred while trying to add or remove files in the -installation directory: + The following error occurred while trying to add or remove files in the + installation directory: - %s + %s -The installation directory you specified (via --install-dir, --prefix, or -the distutils default setting) was: + The installation directory you specified (via --install-dir, --prefix, or + the distutils default setting) was: - %s -""" - msg = template % (sys.exc_info()[1], self.install_dir,) + %s + """).lstrip() - if not os.path.exists(self.install_dir): - msg += """ -This directory does not currently exist. Please create it and try again, or -choose a different installation directory (using the -d or --install-dir -option). -""" - else: - msg += """ -Perhaps your account does not have write access to this directory? If the -installation directory is a system-owned directory, you may need to sign in -as the administrator or "root" account. If you do not have administrative -access to this machine, you may wish to choose a different installation -directory, preferably one that is listed in your PYTHONPATH environment -variable. + __not_exists_id = textwrap.dedent(""" + This directory does not currently exist. Please create it and try again, or + choose a different installation directory (using the -d or --install-dir + option). + """).lstrip() -For information on other options, you may wish to consult the -documentation at: + __access_msg = textwrap.dedent(""" + Perhaps your account does not have write access to this directory? If the + installation directory is a system-owned directory, you may need to sign in + as the administrator or "root" account. If you do not have administrative + access to this machine, you may wish to choose a different installation + directory, preferably one that is listed in your PYTHONPATH environment + variable. - https://pythonhosted.org/setuptools/easy_install.html + For information on other options, you may wish to consult the + documentation at: -Please make the appropriate changes for your system and try again. -""" + https://pythonhosted.org/setuptools/easy_install.html + + Please make the appropriate changes for your system and try again. + """).lstrip() + + def cant_write_to_target(self): + msg = self.__cant_write_msg % (sys.exc_info()[1], self.install_dir,) + + if not os.path.exists(self.install_dir): + msg += '\n' + self.__not_exists_id + else: + msg += '\n' + self.__access_msg raise DistutilsError(msg) def check_pth_processing(self): @@ -742,7 +745,7 @@ Please make the appropriate changes for your system and try again. def install_wrapper_scripts(self, dist): if not self.exclude_scripts: - for args in ScriptWriter.get_args(dist): + for args in ScriptWriter.best().get_args(dist): self.write_script(*args) def install_script(self, dist, script_name, script_text, dev_path=None): @@ -980,46 +983,52 @@ Please make the appropriate changes for your system and try again. f.write('\n'.join(locals()[name]) + '\n') f.close() + __mv_warning = textwrap.dedent(""" + Because this distribution was installed --multi-version, before you can + import modules from this package in an application, you will need to + 'import pkg_resources' and then use a 'require()' call similar to one of + these examples, in order to select the desired version: + + pkg_resources.require("%(name)s") # latest installed version + pkg_resources.require("%(name)s==%(version)s") # this exact version + pkg_resources.require("%(name)s>=%(version)s") # this version or higher + """).lstrip() + + __id_warning = textwrap.dedent(""" + Note also that the installation directory must be on sys.path at runtime for + this to work. (e.g. by being the application's script directory, by being on + PYTHONPATH, or by being added to sys.path by your code.) + """) + def installation_report(self, req, dist, what="Installed"): """Helpful installation message for display to package users""" msg = "\n%(what)s %(eggloc)s%(extras)s" if self.multi_version and not self.no_report: - msg += """ - -Because this distribution was installed --multi-version, before you can -import modules from this package in an application, you will need to -'import pkg_resources' and then use a 'require()' call similar to one of -these examples, in order to select the desired version: - - pkg_resources.require("%(name)s") # latest installed version - pkg_resources.require("%(name)s==%(version)s") # this exact version - pkg_resources.require("%(name)s>=%(version)s") # this version or higher -""" + msg += '\n' + self.__mv_warning if self.install_dir not in map(normalize_path, sys.path): - msg += """ + msg += '\n' + self.__id_warning -Note also that the installation directory must be on sys.path at runtime for -this to work. (e.g. by being the application's script directory, by being on -PYTHONPATH, or by being added to sys.path by your code.) -""" eggloc = dist.location name = dist.project_name version = dist.version extras = '' # TODO: self.report_extras(req, dist) return msg % locals() - def report_editable(self, spec, setup_script): - dirname = os.path.dirname(setup_script) - python = sys.executable - return """\nExtracted editable version of %(spec)s to %(dirname)s + __editable_msg = textwrap.dedent(""" + Extracted editable version of %(spec)s to %(dirname)s -If it uses setuptools in its setup script, you can activate it in -"development" mode by going to that directory and running:: + If it uses setuptools in its setup script, you can activate it in + "development" mode by going to that directory and running:: - %(python)s setup.py develop + %(python)s setup.py develop -See the setuptools documentation for the "develop" command for more info. -""" % locals() + See the setuptools documentation for the "develop" command for more info. + """).lstrip() + + def report_editable(self, spec, setup_script): + dirname = os.path.dirname(setup_script) + python = sys.executable + return '\n' + self.__editable_msg % locals() def run_setup(self, setup_script, setup_base, args): sys.modules.setdefault('distutils.command.bdist_egg', bdist_egg) @@ -1170,35 +1179,38 @@ See the setuptools documentation for the "develop" command for more info. finally: log.set_verbosity(self.verbose) # restore original verbosity - def no_default_version_msg(self): - template = """bad install directory or PYTHONPATH + __no_default_msg = textwrap.dedent(""" + bad install directory or PYTHONPATH + + You are attempting to install a package to a directory that is not + on PYTHONPATH and which Python does not read ".pth" files from. The + installation directory you specified (via --install-dir, --prefix, or + the distutils default setting) was: -You are attempting to install a package to a directory that is not -on PYTHONPATH and which Python does not read ".pth" files from. The -installation directory you specified (via --install-dir, --prefix, or -the distutils default setting) was: + %s - %s + and your PYTHONPATH environment variable currently contains: -and your PYTHONPATH environment variable currently contains: + %r - %r + Here are some of your options for correcting the problem: -Here are some of your options for correcting the problem: + * You can choose a different installation directory, i.e., one that is + on PYTHONPATH or supports .pth files -* You can choose a different installation directory, i.e., one that is - on PYTHONPATH or supports .pth files + * You can add the installation directory to the PYTHONPATH environment + variable. (It must then also be on PYTHONPATH whenever you run + Python and want to use the package(s) you are installing.) -* You can add the installation directory to the PYTHONPATH environment - variable. (It must then also be on PYTHONPATH whenever you run - Python and want to use the package(s) you are installing.) + * You can set up the installation directory to support ".pth" files by + using one of the approaches described here: -* You can set up the installation directory to support ".pth" files by - using one of the approaches described here: + https://pythonhosted.org/setuptools/easy_install.html#custom-installation-locations - https://pythonhosted.org/setuptools/easy_install.html#custom-installation-locations + Please make the appropriate changes for your system and try again.""").lstrip() -Please make the appropriate changes for your system and try again.""" + def no_default_version_msg(self): + template = self.__no_default_msg return template % (self.install_dir, os.environ.get('PYTHONPATH', '')) def install_site_py(self): @@ -1398,13 +1410,8 @@ def extract_wininst_cfg(dist_filename): {'version': '', 'target_version': ''}) try: part = f.read(cfglen) - # part is in bytes, but we need to read up to the first null - # byte. - if sys.version_info >= (2, 6): - null_byte = bytes([0]) - else: - null_byte = chr(0) - config = part.split(null_byte, 1)[0] + # Read up to the first null byte. + config = part.split(b'\0', 1)[0] # Now the config is in bytes, but for RawConfigParser, it should # be text, so decode it. config = config.decode(sys.getfilesystemencoding()) @@ -1787,9 +1794,8 @@ def is_python(text, filename='<string>'): def is_sh(executable): """Determine if the specified executable is a .sh (contains a #! line)""" try: - fp = open(executable) - magic = fp.read(2) - fp.close() + with io.open(executable, encoding='latin-1') as fp: + magic = fp.read(2) except (OSError, IOError): return executable return magic == '#!' @@ -1831,25 +1837,14 @@ def chmod(path, mode): def fix_jython_executable(executable, options): - if sys.platform.startswith('java') and is_sh(executable): - # Workaround for Jython is not needed on Linux systems. - import java + warnings.warn("Use JythonCommandSpec", DeprecationWarning, stacklevel=2) - if java.lang.System.getProperty("os.name") == "Linux": - return executable + if not JythonCommandSpec.relevant(): + return executable - # Workaround Jython's sys.executable being a .sh (an invalid - # shebang line interpreter) - if options: - # Can't apply the workaround, leave it broken - log.warn( - "WARNING: Unable to adapt shebang line for Jython," - " the following script is NOT executable\n" - " see http://bugs.jython.org/issue1112 for" - " more information.") - else: - return '/usr/bin/env %s' % executable - return executable + cmd = CommandSpec.best().from_param(executable) + cmd.install_options(options) + return cmd.as_header().lstrip('#!').rstrip('\n') class CommandSpec(list): @@ -1859,6 +1854,14 @@ class CommandSpec(list): """ options = [] + split_args = dict() + + @classmethod + def best(cls): + """ + Choose the best CommandSpec class based on environmental conditions. + """ + return cls if not JythonCommandSpec.relevant() else JythonCommandSpec @classmethod def _sys_executable(cls): @@ -1882,7 +1885,7 @@ class CommandSpec(list): @classmethod def from_environment(cls): - return cls.from_string('"' + cls._sys_executable() + '"') + return cls([cls._sys_executable()]) @classmethod def from_string(cls, string): @@ -1890,8 +1893,8 @@ class CommandSpec(list): Construct a command spec from a simple string representing a command line parseable by shlex.split. """ - items = shlex.split(string) - return JythonCommandSpec.from_string(string) or cls(items) + items = shlex.split(string, **cls.split_args) + return cls(items) def install_options(self, script_text): self.options = shlex.split(self._extract_options(script_text)) @@ -1917,20 +1920,31 @@ class CommandSpec(list): cmdline = subprocess.list2cmdline(items) return '#!' + cmdline + '\n' +# For pbr compat; will be removed in a future version. +sys_executable = CommandSpec._sys_executable() + + +class WindowsCommandSpec(CommandSpec): + split_args = dict(posix=False) + class JythonCommandSpec(CommandSpec): @classmethod - def from_string(cls, string): - """ - On Jython, construct an instance of this class. - On platforms other than Jython, return None. - """ - needs_jython_spec = ( + def relevant(cls): + return ( sys.platform.startswith('java') and __import__('java').lang.System.getProperty('os.name') != 'Linux' ) - return cls([string]) if needs_jython_spec else None + + @classmethod + def from_environment(cls): + string = '"' + cls._sys_executable() + '"' + return cls.from_string(string) + + @classmethod + def from_string(cls, string): + return cls([string]) def as_header(self): """ @@ -1971,11 +1985,13 @@ class ScriptWriter(object): ) """).lstrip() + command_spec_class = CommandSpec + @classmethod def get_script_args(cls, dist, executable=None, wininst=False): # for backward compatibility warnings.warn("Use get_args", DeprecationWarning) - writer = cls.get_writer(wininst) + writer = (WindowsScriptWriter if wininst else ScriptWriter).best() header = cls.get_script_header("", executable, wininst) return writer.get_args(dist, header) @@ -1985,7 +2001,7 @@ class ScriptWriter(object): warnings.warn("Use get_header", DeprecationWarning) if wininst: executable = "python.exe" - cmd = CommandSpec.from_param(executable) + cmd = cls.command_spec_class.best().from_param(executable) cmd.install_options(script_text) return cmd.as_header() @@ -2007,9 +2023,16 @@ class ScriptWriter(object): @classmethod def get_writer(cls, force_windows): - if force_windows or sys.platform == 'win32': - return WindowsScriptWriter.get_writer() - return cls + # for backward compatibility + warnings.warn("Use best", DeprecationWarning) + return WindowsScriptWriter.best() if force_windows else cls.best() + + @classmethod + def best(cls): + """ + Select the best ScriptWriter for this environment. + """ + return WindowsScriptWriter.best() if sys.platform == 'win32' else cls @classmethod def _get_script_args(cls, type_, name, header, script_text): @@ -2019,16 +2042,24 @@ class ScriptWriter(object): @classmethod def get_header(cls, script_text="", executable=None): """Create a #! line, getting options (if any) from script_text""" - cmd = CommandSpec.from_param(executable) + cmd = cls.command_spec_class.best().from_param(executable) cmd.install_options(script_text) return cmd.as_header() class WindowsScriptWriter(ScriptWriter): + command_spec_class = WindowsCommandSpec + @classmethod def get_writer(cls): + # for backward compatibility + warnings.warn("Use best", DeprecationWarning) + return cls.best() + + @classmethod + def best(cls): """ - Get a script writer suitable for Windows + Select the best ScriptWriter suitable for Windows """ writer_lookup = dict( executable=WindowsExecutableLauncherWriter, @@ -2225,4 +2256,3 @@ def _patch_usage(): yield finally: distutils.core.gen_usage = saved - diff --git a/setuptools/command/install_scripts.py b/setuptools/command/install_scripts.py index 722b0566..be66cb22 100755 --- a/setuptools/command/install_scripts.py +++ b/setuptools/command/install_scripts.py @@ -13,7 +13,7 @@ class install_scripts(orig.install_scripts): self.no_ep = False def run(self): - from setuptools.command.easy_install import ScriptWriter, CommandSpec + import setuptools.command.easy_install as ei self.run_command("egg_info") if self.distribution.scripts: @@ -30,13 +30,16 @@ class install_scripts(orig.install_scripts): ei_cmd.egg_name, ei_cmd.egg_version, ) bs_cmd = self.get_finalized_command('build_scripts') - cmd = CommandSpec.from_param(getattr(bs_cmd, 'executable', None)) - is_wininst = getattr( - self.get_finalized_command("bdist_wininst"), '_is_running', False - ) + exec_param = getattr(bs_cmd, 'executable', None) + bw_cmd = self.get_finalized_command("bdist_wininst") + is_wininst = getattr(bw_cmd, '_is_running', False) + writer = ei.ScriptWriter if is_wininst: - cmd = CommandSpec.from_string("python.exe") - writer = ScriptWriter.get_writer(force_windows=is_wininst) + exec_param = "python.exe" + writer = ei.WindowsScriptWriter + # resolve the writer to the environment + writer = writer.best() + cmd = writer.command_spec_class.best().from_param(exec_param) for args in writer.get_args(dist, cmd.as_header()): self.write_script(*args) diff --git a/setuptools/dist.py b/setuptools/dist.py index bc29b131..ffbc7c48 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -277,10 +277,9 @@ class Distribution(_Distribution): normalized_version = str(ver) if self.metadata.version != normalized_version: warnings.warn( - "The version specified requires normalization, " - "consider using '%s' instead of '%s'." % ( - normalized_version, + "Normalizing '%s' to '%s'" % ( self.metadata.version, + normalized_version, ) ) self.metadata.version = normalized_version diff --git a/setuptools/msvc9_support.py b/setuptools/msvc9_support.py index e76d70f0..a69c7474 100644 --- a/setuptools/msvc9_support.py +++ b/setuptools/msvc9_support.py @@ -1,5 +1,3 @@ -import sys - try: import distutils.msvc9compiler except ImportError: @@ -29,13 +27,15 @@ def patch_for_specialized_compiler(): def find_vcvarsall(version): Reg = distutils.msvc9compiler.Reg VC_BASE = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f' + key = VC_BASE % ('', version) try: # Per-user installs register the compiler path here - productdir = Reg.get_value(VC_BASE % ('', version), "installdir") + productdir = Reg.get_value(key, "installdir") except KeyError: try: # All-user installs on a 64-bit system register here - productdir = Reg.get_value(VC_BASE % ('Wow6432Node\\', version), "installdir") + key = VC_BASE % ('Wow6432Node\\', version) + productdir = Reg.get_value(key, "installdir") except KeyError: productdir = None diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py index 7971f42c..67255123 100755 --- a/setuptools/sandbox.py +++ b/setuptools/sandbox.py @@ -47,8 +47,10 @@ def _execfile(filename, globals, locals=None): @contextlib.contextmanager -def save_argv(): +def save_argv(repl=None): saved = sys.argv[:] + if repl is not None: + sys.argv[:] = repl try: yield saved finally: @@ -92,6 +94,51 @@ def pushd(target): os.chdir(saved) +class UnpickleableException(Exception): + """ + An exception representing another Exception that could not be pickled. + """ + @classmethod + def dump(cls, type, exc): + """ + Always return a dumped (pickled) type and exc. If exc can't be pickled, + wrap it in UnpickleableException first. + """ + try: + return pickle.dumps(type), pickle.dumps(exc) + except Exception: + return cls.dump(cls, cls(repr(exc))) + + +class ExceptionSaver: + """ + A Context Manager that will save an exception, serialized, and restore it + later. + """ + def __enter__(self): + return self + + def __exit__(self, type, exc, tb): + if not exc: + return + + # dump the exception + self._saved = UnpickleableException.dump(type, exc) + self._tb = tb + + # suppress the exception + return True + + def resume(self): + "restore and re-raise any exception" + + if '_saved' not in vars(self): + return + + type, exc = map(pickle.loads, self._saved) + compat.reraise(type, exc, self._tb) + + @contextlib.contextmanager def save_modules(): """ @@ -101,31 +148,20 @@ def save_modules(): outside the context. """ saved = sys.modules.copy() - try: - try: - yield saved - except: - # dump any exception - class_, exc, tb = sys.exc_info() - saved_cls = pickle.dumps(class_) - saved_exc = pickle.dumps(exc) - raise - finally: - sys.modules.update(saved) - # remove any modules imported since - del_modules = ( - mod_name for mod_name in sys.modules - if mod_name not in saved - # exclude any encodings modules. See #285 - and not mod_name.startswith('encodings.') - ) - _clear_modules(del_modules) - except: - # reload and re-raise any exception, using restored modules - class_, exc, tb = sys.exc_info() - new_cls = pickle.loads(saved_cls) - new_exc = pickle.loads(saved_exc) - compat.reraise(new_cls, new_exc, tb) + with ExceptionSaver() as saved_exc: + yield saved + + sys.modules.update(saved) + # remove any modules imported since + del_modules = ( + mod_name for mod_name in sys.modules + if mod_name not in saved + # exclude any encodings modules. See #285 + and not mod_name.startswith('encodings.') + ) + _clear_modules(del_modules) + + saved_exc.resume() def _clear_modules(module_names): diff --git a/setuptools/tests/contexts.py b/setuptools/tests/contexts.py index d06a333f..1d29284b 100644 --- a/setuptools/tests/contexts.py +++ b/setuptools/tests/contexts.py @@ -27,7 +27,7 @@ def environment(**replacements): to clear the values. """ saved = dict( - (key, os.environ['key']) + (key, os.environ[key]) for key in replacements if key in os.environ ) @@ -49,14 +49,6 @@ def environment(**replacements): @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 diff --git a/setuptools/tests/fixtures.py b/setuptools/tests/fixtures.py index 0b1eaf5f..c70c38cb 100644 --- a/setuptools/tests/fixtures.py +++ b/setuptools/tests/fixtures.py @@ -1,4 +1,7 @@ -import mock +try: + from unittest import mock +except ImportError: + import mock import pytest from . import contexts diff --git a/setuptools/tests/py26compat.py b/setuptools/tests/py26compat.py index c53b4809..c5680881 100644 --- a/setuptools/tests/py26compat.py +++ b/setuptools/tests/py26compat.py @@ -8,4 +8,7 @@ def _tarfile_open_ex(*args, **kwargs): """ return contextlib.closing(tarfile.open(*args, **kwargs)) -tarfile_open = _tarfile_open_ex if sys.version_info < (2,7) else tarfile.open +if sys.version_info[:2] < (2, 7) or (3, 0) <= sys.version_info[:2] < (3, 2): + tarfile_open = _tarfile_open_ex +else: + tarfile_open = tarfile.open diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index 72b040e1..7d61fb83 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -1,4 +1,4 @@ -#! -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- """Easy install Tests """ @@ -13,18 +13,19 @@ import contextlib import tarfile import logging import itertools +import distutils.errors import pytest -import mock +try: + from unittest import mock +except ImportError: + import mock from setuptools import sandbox from setuptools import compat from setuptools.compat import StringIO, BytesIO, urlparse from setuptools.sandbox import run_setup -from setuptools.command.easy_install import ( - easy_install, fix_jython_executable, nt_quote_arg, - is_sh, ScriptWriter, CommandSpec, -) +import setuptools.command.easy_install as ei from setuptools.command.easy_install import PthDistributions from setuptools.command import easy_install as easy_install_pkg from setuptools.dist import Distribution @@ -47,19 +48,6 @@ class FakeDist(object): def as_requirement(self): return 'spec' -WANTED = DALS(""" - #!%s - # EASY-INSTALL-ENTRY-SCRIPT: 'spec','console_scripts','name' - __requires__ = 'spec' - import sys - from pkg_resources import load_entry_point - - if __name__ == '__main__': - sys.exit( - load_entry_point('spec', 'console_scripts', 'name')() - ) - """) % nt_quote_arg(fix_jython_executable(sys.executable, "")) - SETUP_PY = DALS(""" from setuptools import setup @@ -70,7 +58,7 @@ class TestEasyInstallTest: def test_install_site_py(self): dist = Distribution() - cmd = easy_install(dist) + cmd = ei.easy_install(dist) cmd.sitepy_installed = False cmd.install_dir = tempfile.mkdtemp() try: @@ -81,18 +69,30 @@ class TestEasyInstallTest: shutil.rmtree(cmd.install_dir) def test_get_script_args(self): + header = ei.CommandSpec.best().from_environment().as_header() + expected = header + DALS(""" + # EASY-INSTALL-ENTRY-SCRIPT: 'spec','console_scripts','name' + __requires__ = 'spec' + import sys + from pkg_resources import load_entry_point + + if __name__ == '__main__': + sys.exit( + load_entry_point('spec', 'console_scripts', 'name')() + ) + """) dist = FakeDist() - args = next(ScriptWriter.get_args(dist)) + args = next(ei.ScriptWriter.get_args(dist)) name, script = itertools.islice(args, 2) - assert script == WANTED + assert script == expected def test_no_find_links(self): # new option '--no-find-links', that blocks find-links added at # the project level dist = Distribution() - cmd = easy_install(dist) + cmd = ei.easy_install(dist) cmd.check_pth_processing = lambda: True cmd.no_find_links = True cmd.find_links = ['link1', 'link2'] @@ -102,7 +102,7 @@ class TestEasyInstallTest: assert cmd.package_index.scanned_urls == {} # let's try without it (default behavior) - cmd = easy_install(dist) + cmd = ei.easy_install(dist) cmd.check_pth_processing = lambda: True cmd.find_links = ['link1', 'link2'] cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok') @@ -111,6 +111,16 @@ class TestEasyInstallTest: keys = sorted(cmd.package_index.scanned_urls.keys()) assert keys == ['link1', 'link2'] + def test_write_exception(self): + """ + Test that `cant_write_to_target` is rendered as a DistutilsError. + """ + dist = Distribution() + cmd = ei.easy_install(dist) + cmd.install_dir = os.getcwd() + with pytest.raises(distutils.errors.DistutilsError): + cmd.cant_write_to_target() + class TestPTHFileWriter: def test_add_from_cwd_site_sets_dirty(self): @@ -144,77 +154,64 @@ def setup_context(tmpdir): @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 + # simulate setuptools installed in user site packages + @mock.patch('setuptools.command.easy_install.__file__', site.USER_SITE) + @mock.patch('site.ENABLE_USER_SITE', True) + def test_user_install_not_implied_user_site_enabled(self): + self.assert_not_user_site() + + @mock.patch('site.ENABLE_USER_SITE', False) + def test_user_install_not_implied_user_site_disabled(self): + self.assert_not_user_site() + + @staticmethod + def assert_not_user_site(): + # create a finalized easy_install command dist = Distribution() dist.script_name = 'setup.py' - cmd = easy_install(dist) + cmd = ei.easy_install(dist) cmd.args = ['py'] cmd.ensure_finalized() - assert cmd.user, 'user should be implied' + assert not cmd.user, 'user should not be implied' def test_multiproc_atexit(self): - try: - __import__('multiprocessing') - except ImportError: - # skip the test if multiprocessing is not available - return + pytest.importorskip('multiprocessing') log = logging.getLogger('test_easy_install') logging.basicConfig(level=logging.INFO, stream=sys.stderr) log.info('this should not break') - def test_user_install_not_implied_without_usersite_enabled(self): - site.ENABLE_USER_SITE = False # usually enabled - #XXX: replace with something meaningfull - dist = Distribution() - dist.script_name = 'setup.py' - cmd = easy_install(dist) - cmd.args = ['py'] - cmd.initialize_options() - assert not cmd.user, 'NOT user should be implied' - - def test_local_index(self): - # make sure the local index is used - # when easy_install looks for installed - # packages - new_location = tempfile.mkdtemp() - target = tempfile.mkdtemp() - egg_file = os.path.join(new_location, 'foo-1.0.egg-info') - with open(egg_file, 'w') as f: + @pytest.fixture() + def foo_package(self, tmpdir): + egg_file = tmpdir / 'foo-1.0.egg-info' + with egg_file.open('w') as f: f.write('Name: foo\n') + return str(tmpdir) - sys.path.append(target) - old_ppath = os.environ.get('PYTHONPATH') - os.environ['PYTHONPATH'] = os.path.pathsep.join(sys.path) - try: - dist = Distribution() - dist.script_name = 'setup.py' - cmd = easy_install(dist) - cmd.install_dir = target - cmd.args = ['foo'] - cmd.ensure_finalized() - cmd.local_index.scan([new_location]) - res = cmd.easy_install('foo') - actual = os.path.normcase(os.path.realpath(res.location)) - expected = os.path.normcase(os.path.realpath(new_location)) - assert actual == expected - finally: - sys.path.remove(target) - for basedir in [new_location, target, ]: - if not os.path.exists(basedir) or not os.path.isdir(basedir): - continue - try: - shutil.rmtree(basedir) - except: - pass - if old_ppath is not None: - os.environ['PYTHONPATH'] = old_ppath - else: - del os.environ['PYTHONPATH'] + @pytest.yield_fixture() + def install_target(self, tmpdir): + target = str(tmpdir) + with mock.patch('sys.path', sys.path + [target]): + python_path = os.path.pathsep.join(sys.path) + with mock.patch.dict(os.environ, PYTHONPATH=python_path): + yield target + + def test_local_index(self, foo_package, install_target): + """ + The local index must be used when easy_install locates installed + packages. + """ + dist = Distribution() + dist.script_name = 'setup.py' + cmd = ei.easy_install(dist) + cmd.install_dir = install_target + cmd.args = ['foo'] + cmd.ensure_finalized() + cmd.local_index.scan([foo_package]) + res = cmd.easy_install('foo') + actual = os.path.normcase(os.path.realpath(res.location)) + expected = os.path.normcase(os.path.realpath(foo_package)) + assert actual == expected @contextlib.contextmanager def user_install_setup_context(self, *args, **kwargs): @@ -302,7 +299,7 @@ class TestSetupRequires: '--install-dir', temp_install_dir, dist_file, ] - with contexts.argv(['easy_install']): + with sandbox.save_argv(['easy_install']): # attempt to install the dist. It should fail because # it doesn't exist. with pytest.raises(SystemExit): @@ -420,24 +417,25 @@ class TestScriptHeader: exe_with_spaces = r'C:\Program Files\Python33\python.exe' @pytest.mark.skipif( - sys.platform.startswith('java') and is_sh(sys.executable), + sys.platform.startswith('java') and ei.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)) - actual = ScriptWriter.get_script_header('#!/usr/local/bin/python') + expected = '#!%s\n' % ei.nt_quote_arg(os.path.normpath(sys.executable)) + actual = ei.ScriptWriter.get_script_header('#!/usr/local/bin/python') assert actual == expected - expected = '#!%s -x\n' % nt_quote_arg(os.path.normpath(sys.executable)) - actual = ScriptWriter.get_script_header('#!/usr/bin/python -x') + expected = '#!%s -x\n' % ei.nt_quote_arg(os.path.normpath + (sys.executable)) + actual = ei.ScriptWriter.get_script_header('#!/usr/bin/python -x') assert actual == expected - actual = ScriptWriter.get_script_header('#!/usr/bin/python', + actual = ei.ScriptWriter.get_script_header('#!/usr/bin/python', executable=self.non_ascii_exe) expected = '#!%s -x\n' % self.non_ascii_exe assert actual == expected - actual = ScriptWriter.get_script_header('#!/usr/bin/python', + actual = ei.ScriptWriter.get_script_header('#!/usr/bin/python', executable='"'+self.exe_with_spaces+'"') expected = '#!"%s"\n' % self.exe_with_spaces assert actual == expected @@ -460,7 +458,7 @@ class TestScriptHeader: f.write(header) exe = str(exe) - header = ScriptWriter.get_script_header('#!/usr/local/bin/python', + header = ei.ScriptWriter.get_script_header('#!/usr/local/bin/python', executable=exe) assert header == '#!/usr/bin/env %s\n' % exe @@ -469,14 +467,14 @@ class TestScriptHeader: with contexts.quiet() as (stdout, stderr): # When options are included, generate a broken shebang line # with a warning emitted - candidate = ScriptWriter.get_script_header('#!/usr/bin/python -x', + candidate = ei.ScriptWriter.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 = ScriptWriter.get_script_header('#!/usr/bin/python', + candidate = ei.ScriptWriter.get_script_header('#!/usr/bin/python', executable=self.non_ascii_exe) assert candidate == '#!%s -x\n' % self.non_ascii_exe output = locals()[expect_out] @@ -489,20 +487,20 @@ class TestCommandSpec: Show how a custom CommandSpec could be used to specify a #! executable which takes parameters. """ - cmd = CommandSpec(['/usr/bin/env', 'python3']) + cmd = ei.CommandSpec(['/usr/bin/env', 'python3']) assert cmd.as_header() == '#!/usr/bin/env python3\n' def test_from_param_for_CommandSpec_is_passthrough(self): """ from_param should return an instance of a CommandSpec """ - cmd = CommandSpec(['python']) - cmd_new = CommandSpec.from_param(cmd) + cmd = ei.CommandSpec(['python']) + cmd_new = ei.CommandSpec.from_param(cmd) assert cmd is cmd_new def test_from_environment_with_spaces_in_executable(self): with mock.patch('sys.executable', TestScriptHeader.exe_with_spaces): - cmd = CommandSpec.from_environment() + cmd = ei.CommandSpec.from_environment() assert len(cmd) == 1 assert cmd.as_header().startswith('#!"') @@ -511,6 +509,26 @@ class TestCommandSpec: In order to support `executable = /usr/bin/env my-python`, make sure from_param invokes shlex on that input. """ - cmd = CommandSpec.from_param('/usr/bin/env my-python') + cmd = ei.CommandSpec.from_param('/usr/bin/env my-python') assert len(cmd) == 2 assert '"' not in cmd.as_header() + + def test_sys_executable(self): + """ + CommandSpec.from_string(sys.executable) should contain just that param. + """ + writer = ei.ScriptWriter.best() + cmd = writer.command_spec_class.from_string(sys.executable) + assert len(cmd) == 1 + assert cmd[0] == sys.executable + + +class TestWindowsScriptWriter: + def test_header(self): + hdr = ei.WindowsScriptWriter.get_script_header('') + assert hdr.startswith('#!') + assert hdr.endswith('\n') + hdr = hdr.lstrip('#!') + hdr = hdr.rstrip('\n') + # header should not start with an escaped quote + assert not hdr.startswith('\\"') diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py index 92a27080..90bb4313 100644 --- a/setuptools/tests/test_integration.py +++ b/setuptools/tests/test_integration.py @@ -12,6 +12,7 @@ import pytest from setuptools.command.easy_install import easy_install from setuptools.command import easy_install as easy_install_pkg from setuptools.dist import Distribution +from setuptools.compat import urlopen def setup_module(module): @@ -24,6 +25,11 @@ def setup_module(module): except ImportError: pass + try: + urlopen('https://pypi.python.org/pypi') + except Exception as exc: + pytest.skip(reason=str(exc)) + @pytest.fixture def install_context(request, tmpdir, monkeypatch): diff --git a/setuptools/tests/test_msvc9compiler.py b/setuptools/tests/test_msvc9compiler.py index a0820fff..09e0460c 100644 --- a/setuptools/tests/test_msvc9compiler.py +++ b/setuptools/tests/test_msvc9compiler.py @@ -7,7 +7,10 @@ import contextlib import distutils.errors import pytest -import mock +try: + from unittest import mock +except ImportError: + import mock from . import contexts @@ -110,7 +113,8 @@ class TestModulePatch: Ensure user's settings are preferred. """ result = distutils.msvc9compiler.find_vcvarsall(9.0) - assert user_preferred_setting == result + expected = os.path.join(user_preferred_setting, 'vcvarsall.bat') + assert expected == result @pytest.yield_fixture def local_machine_setting(self): @@ -131,13 +135,14 @@ class TestModulePatch: Ensure machine setting is honored if user settings are not present. """ result = distutils.msvc9compiler.find_vcvarsall(9.0) - assert local_machine_setting == result + expected = os.path.join(local_machine_setting, 'vcvarsall.bat') + assert expected == 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. + and yield the canonical location. """ with self.mock_install_dir() as x32_dir: with self.mock_install_dir() as x64_dir: @@ -150,14 +155,15 @@ class TestModulePatch: }, ) with reg: - yield x64_dir + yield x32_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 + expected = os.path.join(x64_preferred_setting, 'vcvarsall.bat') + assert expected == result @staticmethod @contextlib.contextmanager @@ -170,4 +176,4 @@ class TestModulePatch: vcvarsall = os.path.join(result, 'vcvarsall.bat') with open(vcvarsall, 'w'): pass - yield + yield result diff --git a/setuptools/tests/test_sandbox.py b/setuptools/tests/test_sandbox.py index cadc4812..6e1e9e1c 100644 --- a/setuptools/tests/test_sandbox.py +++ b/setuptools/tests/test_sandbox.py @@ -7,7 +7,7 @@ import pytest import pkg_resources import setuptools.sandbox -from setuptools.sandbox import DirectorySandbox, SandboxViolation +from setuptools.sandbox import DirectorySandbox class TestSandbox: @@ -54,3 +54,49 @@ class TestSandbox: with setup_py.open('wb') as stream: stream.write(b'"degenerate script"\r\n') setuptools.sandbox._execfile(str(setup_py), globals()) + + +class TestExceptionSaver: + def test_exception_trapped(self): + with setuptools.sandbox.ExceptionSaver(): + raise ValueError("details") + + def test_exception_resumed(self): + with setuptools.sandbox.ExceptionSaver() as saved_exc: + raise ValueError("details") + + with pytest.raises(ValueError) as caught: + saved_exc.resume() + + assert isinstance(caught.value, ValueError) + assert str(caught.value) == 'details' + + def test_exception_reconstructed(self): + orig_exc = ValueError("details") + + with setuptools.sandbox.ExceptionSaver() as saved_exc: + raise orig_exc + + with pytest.raises(ValueError) as caught: + saved_exc.resume() + + assert isinstance(caught.value, ValueError) + assert caught.value is not orig_exc + + def test_no_exception_passes_quietly(self): + with setuptools.sandbox.ExceptionSaver() as saved_exc: + pass + + saved_exc.resume() + + def test_unpickleable_exception(self): + class CantPickleThis(Exception): + "This Exception is unpickleable because it's not in globals" + + with setuptools.sandbox.ExceptionSaver() as saved_exc: + raise CantPickleThis('detail') + + with pytest.raises(setuptools.sandbox.UnpickleableException) as caught: + saved_exc.resume() + + assert str(caught.value) == "CantPickleThis('detail',)" diff --git a/setuptools/version.py b/setuptools/version.py index 1e71f92d..525a47ea 100644 --- a/setuptools/version.py +++ b/setuptools/version.py @@ -1 +1 @@ -__version__ = '11.3.2' +__version__ = '13.0.2' |
