diff options
author | ?ric Araujo <merwok@netwok.org> | 2011-11-12 07:33:45 +0100 |
---|---|---|
committer | ?ric Araujo <merwok@netwok.org> | 2011-11-12 07:33:45 +0100 |
commit | ed71facf9298a2d12dc631f86e4ecdd33a117380 (patch) | |
tree | e31993c8b5ca3878fc6142db3fb2fea458806827 | |
parent | c00356888d4cce280d33dd86c7349bc9279a430d (diff) | |
parent | e65890db19e3b798752544632abd303625c4a7f7 (diff) | |
download | disutils2-ed71facf9298a2d12dc631f86e4ecdd33a117380.tar.gz |
Ye olde merge.
I broke test_mixin2to3 somehow; distutils2-default is okay and packaging too,
so I don?t see an obvious reason right now, I?ll investigate later.
57 files changed, 822 insertions, 674 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 5d42e27..d29edc3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -148,6 +148,15 @@ CONTRIBUTORS.txt for full names. Bug numbers refer to http://bugs.python.org/. - #13170: Revert one of Jeremy's changes to config to fix a bug, kludge around shlex not supporting unicode in 2.x, fix wrong shutil import [david, éric] - #13205: Fix and improve generated setup scripts [david, éric] +- #12386: Fix writing of the RESOURCES file [éric] +- #11751: Improve test coverage for manifest [justin] +- Byte compilation is now isolated from the calling Python -B or -O options + [éric] +- The signature of tests.support.LoggingCatcher.get_logs changed, see + docstring [éric] +- Rename get_reinitialized_command back to reinitialize_command [éric] +- Rename install_distinfo's option from distinfo-dir to the more usual + install_dir [éric] 1.0a3 - 2010-10-08 diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 9e6ef84..a777fd6 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -46,6 +46,7 @@ Thanks to: - Alain Leufroy - Martin von Löwis - Hugo Lopes Tavares +- Justin Love - Simon Mathieu - Carl Meyer - Alexis Métaireau diff --git a/distutils2/command/bdist.py b/distutils2/command/bdist.py index 0a39fb1..583041e 100644 --- a/distutils2/command/bdist.py +++ b/distutils2/command/bdist.py @@ -126,7 +126,7 @@ class bdist(Command): # Reinitialize and run each command. for i in range(len(self.formats)): cmd_name = commands[i] - sub_cmd = self.get_reinitialized_command(cmd_name) + sub_cmd = self.reinitialize_command(cmd_name) sub_cmd.format = self.formats[i] # passing the owner and group names for tar archiving diff --git a/distutils2/command/bdist_dumb.py b/distutils2/command/bdist_dumb.py index 677deb9..dfe80b9 100644 --- a/distutils2/command/bdist_dumb.py +++ b/distutils2/command/bdist_dumb.py @@ -80,8 +80,8 @@ class bdist_dumb(Command): if not self.skip_build: self.run_command('build') - install = self.get_reinitialized_command('install_dist', - reinit_subcommands=True) + install = self.reinitialize_command('install_dist', + reinit_subcommands=True) install.root = self.bdist_dir install.skip_build = self.skip_build install.warn_dir = False diff --git a/distutils2/command/bdist_msi.py b/distutils2/command/bdist_msi.py index 40e733f..704e695 100644 --- a/distutils2/command/bdist_msi.py +++ b/distutils2/command/bdist_msi.py @@ -35,7 +35,7 @@ class PyDialog(Dialog): def __init__(self, *args, **kw): """Dialog(database, name, x, y, w, h, attributes, title, first, default, cancel, bitmap=true)""" - Dialog.__init__(self, *args) + super(PyDialog, self).__init__(*args) ruler = self.h - 36 #if kw.get("bitmap", True): # self.bitmap("Bitmap", 0, 0, bmwidth, ruler, "PythonWin") @@ -183,13 +183,13 @@ class bdist_msi(Command): if not self.skip_build: self.run_command('build') - install = self.get_reinitialized_command('install_dist', - reinit_subcommands=True) + install = self.reinitialize_command('install_dist', + reinit_subcommands=True) install.prefix = self.bdist_dir install.skip_build = self.skip_build install.warn_dir = False - install_lib = self.get_reinitialized_command('install_lib') + install_lib = self.reinitialize_command('install_lib') # we do not want to include pyc or pyo files install_lib.compile = False install_lib.optimize = 0 diff --git a/distutils2/command/bdist_wininst.py b/distutils2/command/bdist_wininst.py index e4cde7a..fcd0955 100644 --- a/distutils2/command/bdist_wininst.py +++ b/distutils2/command/bdist_wininst.py @@ -2,7 +2,6 @@ import sys import os -import codecs from shutil import rmtree @@ -117,14 +116,13 @@ class bdist_wininst(Command): if not self.skip_build: self.run_command('build') - install = self.get_reinitialized_command('install', - reinit_subcommands=True) + install = self.reinitialize_command('install', reinit_subcommands=True) install.root = self.bdist_dir install.skip_build = self.skip_build install.warn_dir = False install.plat_name = self.plat_name - install_lib = self.get_reinitialized_command('install_lib') + install_lib = self.reinitialize_command('install_lib') # we do not want to include pyc or pyo files install_lib.compile = False install_lib.optimize = 0 diff --git a/distutils2/command/build.py b/distutils2/command/build.py index b2e5bf8..708b848 100644 --- a/distutils2/command/build.py +++ b/distutils2/command/build.py @@ -41,7 +41,7 @@ class build(Command): ('use-2to3', None, "use 2to3 to make source python 3.x compatible"), ('convert-2to3-doctests', None, - "use 2to3 to convert doctests in seperate text files"), + "use 2to3 to convert doctests in separate text files"), ('use-2to3-fixers', None, "list additional fixers opted for during 2to3 conversion"), ] diff --git a/distutils2/command/build_py.py b/distutils2/command/build_py.py index e27e271..f9b9bcd 100644 --- a/distutils2/command/build_py.py +++ b/distutils2/command/build_py.py @@ -1,22 +1,25 @@ """Build pure Python modules (just copy to build directory).""" import os -import sys from glob import glob from distutils2 import logger -from distutils2.command.cmd import Command -from distutils2.errors import PackagingOptionError, PackagingFileError from distutils2.util import convert_path -from distutils2.compat import Mixin2to3 +from distutils2.compat import Mixin2to3, cache_from_source +from distutils2.errors import PackagingOptionError, PackagingFileError +from distutils2.command.cmd import Command # marking public APIs __all__ = ['build_py'] + class build_py(Command, Mixin2to3): description = "build pure Python modules (copy to build directory)" + # The options for controlling byte compilation are two independent sets; + # more info in install_lib or the reST docs + user_options = [ ('build-lib=', 'd', "directory to build (copy) to"), ('compile', 'c', "compile .py to .pyc"), @@ -28,13 +31,14 @@ class build_py(Command, Mixin2to3): ('use-2to3', None, "use 2to3 to make source python 3.x compatible"), ('convert-2to3-doctests', None, - "use 2to3 to convert doctests in seperate text files"), + "use 2to3 to convert doctests in separate text files"), ('use-2to3-fixers', None, "list additional fixers opted for during 2to3 conversion"), ] boolean_options = ['compile', 'force'] - negative_opt = {'no-compile' : 'compile'} + + negative_opt = {'no-compile': 'compile'} def initialize_options(self): self.build_lib = None @@ -108,14 +112,15 @@ class build_py(Command, Mixin2to3): self.run_2to3(self._updated_files, self._doctests_2to3, self.use_2to3_fixers) - self.byte_compile(self.get_outputs(include_bytecode=False)) + self.byte_compile(self.get_outputs(include_bytecode=False), + prefix=self.build_lib) # -- Top-level worker functions ------------------------------------ def get_data_files(self): """Generate list of '(package,src_dir,build_dir,filenames)' tuples. - Helper function for `finalize_options()`. + Helper function for finalize_options. """ data = [] if not self.packages: @@ -130,7 +135,7 @@ class build_py(Command, Mixin2to3): # Length of path to strip from found files plen = 0 if src_dir: - plen = len(src_dir)+1 + plen = len(src_dir) + 1 # Strip directory from globbed filenames filenames = [ @@ -142,7 +147,7 @@ class build_py(Command, Mixin2to3): def find_data_files(self, package, src_dir): """Return filenames for package's data files in 'src_dir'. - Helper function for `get_data_files()`. + Helper function for get_data_files. """ globs = (self.package_data.get('', []) + self.package_data.get(package, [])) @@ -157,7 +162,7 @@ class build_py(Command, Mixin2to3): def build_package_data(self): """Copy data files into build directory. - Helper function for `run()`. + Helper function for run. """ # FIXME add tests for this method for package, src_dir, build_dir, filenames in self.data_files: @@ -167,16 +172,17 @@ class build_py(Command, Mixin2to3): self.mkpath(os.path.dirname(target)) outf, copied = self.copy_file(srcfile, target, preserve_mode=False) - if copied and srcfile in self.distribution.convert_2to3.doctests: + doctests = self.distribution.convert_2to3_doctests + if copied and srcfile in doctests: self._doctests_2to3.append(outf) # XXX - this should be moved to the Distribution class as it is not # only needed for build_py. It also has no dependencies on this class. def get_package_dir(self, package): """Return the directory, relative to the top of the source - distribution, where package 'package' should be found - (at least according to the 'package_dir' option, if any).""" - + distribution, where package 'package' should be found + (at least according to the 'package_dir' option, if any). + """ path = package.split('.') if self.package_dir is not None: path.insert(0, self.package_dir) @@ -187,8 +193,7 @@ class build_py(Command, Mixin2to3): return '' def check_package(self, package, package_dir): - """Helper function for `find_package_modules()` and `find_modules()'. - """ + """Helper function for find_package_modules and find_modules.""" # Empty dir name means current directory, which we can probably # assume exists. Also, os.path.exists and isdir don't know about # my "empty string means current dir" convention, so we have to @@ -208,8 +213,8 @@ class build_py(Command, Mixin2to3): if os.path.isfile(init_py): return init_py else: - logger.warning(("package init file '%s' not found " + - "(or not a regular file)"), init_py) + logger.warning("package init file %r not found " + "(or not a regular file)", init_py) # Either not in a package at all (__init__.py not expected), or # __init__.py doesn't exist -- so don't return the filename. @@ -217,7 +222,7 @@ class build_py(Command, Mixin2to3): def check_module(self, module, module_file): if not os.path.isfile(module_file): - logger.warning("file %s (for module %s) not found", + logger.warning("file %r (for module %r) not found", module_file, module) return False else: @@ -238,7 +243,7 @@ class build_py(Command, Mixin2to3): module = os.path.splitext(os.path.basename(f))[0] modules.append((package, module, f)) else: - logger.debug("excluding %s", setup_script) + logger.debug("excluding %r", setup_script) return modules def find_modules(self): @@ -330,9 +335,9 @@ class build_py(Command, Mixin2to3): outputs.append(filename) if include_bytecode: if self.compile: - outputs.append(filename + "c") - if self.optimize > 0: - outputs.append(filename + "o") + outputs.append(cache_from_source(filename, True)) + if self.optimize: + outputs.append(cache_from_source(filename, False)) outputs += [ os.path.join(build_dir, filename) @@ -359,7 +364,6 @@ class build_py(Command, Mixin2to3): def build_modules(self): modules = self.find_modules() for package, module, module_file in modules: - # Now "build" the module -- ie. copy the source file to # self.build_lib (the build directory for Python source). # (Actually, it gets copied to the directory for this package @@ -368,7 +372,6 @@ class build_py(Command, Mixin2to3): def build_packages(self): for package in self.packages: - # Get list of (package, module, module_file) tuples based on # scanning the package directory. 'package' is only included # in the tuple so that 'find_modules()' and @@ -386,25 +389,3 @@ class build_py(Command, Mixin2to3): for package_, module, module_file in modules: assert package == package_ self.build_module(module, module_file, package) - - def byte_compile(self, files): - if sys.dont_write_bytecode: - logger.warning('%s: byte-compiling is disabled, skipping.', - self.get_command_name()) - return - - from distutils2.util import byte_compile # FIXME use compileall - prefix = self.build_lib - if prefix[-1] != os.sep: - prefix = prefix + os.sep - - # XXX this code is essentially the same as the 'byte_compile() - # method of the "install_lib" command, except for the determination - # of the 'prefix' string. Hmmm. - - if self.compile: - byte_compile(files, optimize=0, - force=self.force, prefix=prefix, dry_run=self.dry_run) - if self.optimize > 0: - byte_compile(files, optimize=self.optimize, - force=self.force, prefix=prefix, dry_run=self.dry_run) diff --git a/distutils2/command/cmd.py b/distutils2/command/cmd.py index 6d76e7f..956623d 100644 --- a/distutils2/command/cmd.py +++ b/distutils2/command/cmd.py @@ -10,7 +10,7 @@ from distutils2._backport.shutil import copyfile, move, make_archive class Command: """Abstract base class for defining command classes, the "worker bees" - of the Packaging. A useful analogy for command classes is to think of + of Packaging. A useful analogy for command classes is to think of them as subroutines with local variables called "options". The options are "declared" in 'initialize_options()' and "defined" (given their final values, aka "finalized") in 'finalize_options()', both of which @@ -318,8 +318,8 @@ class Command: cmd_obj.ensure_finalized() return cmd_obj - def get_reinitialized_command(self, command, reinit_subcommands=False): - return self.distribution.get_reinitialized_command( + def reinitialize_command(self, command, reinit_subcommands=False): + return self.distribution.reinitialize_command( command, reinit_subcommands) def run_command(self, command): @@ -386,7 +386,6 @@ class Command: if self.dry_run: return # see if we want to display something - return util.copy_tree(infile, outfile, preserve_mode, preserve_times, preserve_symlinks, not self.force, dry_run=self.dry_run) @@ -439,3 +438,20 @@ class Command: # Otherwise, print the "skip" message else: logger.debug(skip_msg) + + def byte_compile(self, files, prefix=None): + """Byte-compile files to pyc and/or pyo files. + + This method requires that the calling class define compile and + optimize options, like build_py and install_lib. It also + automatically respects the force and dry-run options. + + prefix, if given, is a string that will be stripped off the + filenames encoded in bytecode files. + """ + if self.compile: + util.byte_compile(files, optimize=False, prefix=prefix, + force=self.force, dry_run=self.dry_run) + if self.optimize: + util.byte_compile(files, optimize=self.optimize, prefix=prefix, + force=self.force, dry_run=self.dry_run) diff --git a/distutils2/command/install_dist.py b/distutils2/command/install_dist.py index 159ccc2..8d8b675 100644 --- a/distutils2/command/install_dist.py +++ b/distutils2/command/install_dist.py @@ -55,9 +55,7 @@ class install_dist(Command): ('install-data=', None, "installation directory for data files"), - # Byte-compilation options -- see install_lib.py for details, as - # these are duplicated from there (but only install_lib does - # anything with them). + # Byte-compilation options -- see install_lib for details ('compile', 'c', "compile .py to .pyc [default]"), ('no-compile', None, "don't compile .py files"), ('optimize=', 'O', diff --git a/distutils2/command/install_distinfo.py b/distutils2/command/install_distinfo.py index 167844c..d495bc2 100644 --- a/distutils2/command/install_distinfo.py +++ b/distutils2/command/install_distinfo.py @@ -16,8 +16,8 @@ class install_distinfo(Command): description = 'create a .dist-info directory for the distribution' user_options = [ - ('distinfo-dir=', None, - "directory where the the .dist-info directory will be installed"), + ('install-dir=', None, + "directory where the the .dist-info directory will be created"), ('installer=', None, "the name of the installer"), ('requested', None, @@ -35,7 +35,7 @@ class install_distinfo(Command): negative_opt = {'no-requested': 'requested'} def initialize_options(self): - self.distinfo_dir = None + self.install_dir = None self.installer = None self.requested = None self.no_record = None @@ -46,8 +46,7 @@ class install_distinfo(Command): self.set_undefined_options('install_dist', 'installer', 'requested', 'no_record') - self.set_undefined_options('install_lib', - ('install_dir', 'distinfo_dir')) + self.set_undefined_options('install_lib', 'install_dir') if self.installer is None: # FIXME distutils or packaging or distutils2? @@ -64,26 +63,26 @@ class install_distinfo(Command): basename = metadata.get_fullname(filesafe=True) + ".dist-info" - self.distinfo_dir = os.path.join(self.distinfo_dir, basename) + self.install_dir = os.path.join(self.install_dir, basename) def run(self): - target = self.distinfo_dir + target = self.install_dir if os.path.isdir(target) and not os.path.islink(target): if not self.dry_run: rmtree(target) elif os.path.exists(target): - self.execute(os.unlink, (self.distinfo_dir,), + self.execute(os.unlink, (self.install_dir,), "removing " + target) self.execute(os.makedirs, (target,), "creating " + target) - metadata_path = os.path.join(self.distinfo_dir, 'METADATA') + metadata_path = os.path.join(self.install_dir, 'METADATA') self.execute(self.distribution.metadata.write, (metadata_path,), "creating " + metadata_path) self.outfiles.append(metadata_path) - installer_path = os.path.join(self.distinfo_dir, 'INSTALLER') + installer_path = os.path.join(self.install_dir, 'INSTALLER') logger.info('creating %s', installer_path) if not self.dry_run: with open(installer_path, 'w') as f: @@ -91,7 +90,7 @@ class install_distinfo(Command): self.outfiles.append(installer_path) if self.requested: - requested_path = os.path.join(self.distinfo_dir, 'REQUESTED') + requested_path = os.path.join(self.install_dir, 'REQUESTED') logger.info('creating %s', requested_path) if not self.dry_run: open(requested_path, 'wb').close() @@ -100,7 +99,7 @@ class install_distinfo(Command): if not self.no_resources: install_data = self.get_finalized_command('install_data') if install_data.get_resources_out() != []: - resources_path = os.path.join(self.distinfo_dir, + resources_path = os.path.join(self.install_dir, 'RESOURCES') logger.info('creating %s', resources_path) if not self.dry_run: @@ -114,7 +113,7 @@ class install_distinfo(Command): self.outfiles.append(resources_path) if not self.no_record: - record_path = os.path.join(self.distinfo_dir, 'RECORD') + record_path = os.path.join(self.install_dir, 'RECORD') logger.info('creating %s', record_path) if not self.dry_run: with open(record_path, 'w', encoding='utf-8') as f: diff --git a/distutils2/command/install_lib.py b/distutils2/command/install_lib.py index ea53eab..8ee1fd5 100644 --- a/distutils2/command/install_lib.py +++ b/distutils2/command/install_lib.py @@ -1,34 +1,26 @@ """Install all modules (extensions and pure Python).""" import os -import sys -import logging from distutils2 import logger +from distutils2.compat import cache_from_source from distutils2.command.cmd import Command from distutils2.errors import PackagingOptionError # Extension for Python source files. +# XXX dead code? most of the codebase checks for literal '.py' if hasattr(os, 'extsep'): PYTHON_SOURCE_EXTENSION = os.extsep + "py" else: PYTHON_SOURCE_EXTENSION = ".py" + class install_lib(Command): description = "install all modules (extensions and pure Python)" - # The byte-compilation options are a tad confusing. Here are the - # possible scenarios: - # 1) no compilation at all (--no-compile --no-optimize) - # 2) compile .pyc only (--compile --no-optimize; default) - # 3) compile .pyc and "level 1" .pyo (--compile --optimize) - # 4) compile "level 1" .pyo only (--no-compile --optimize) - # 5) compile .pyc and "level 2" .pyo (--compile --optimize-more) - # 6) compile "level 2" .pyo only (--no-compile --optimize-more) - # - # The UI for this is two option, 'compile' and 'optimize'. + # The options for controlling byte compilation are two independent sets: # 'compile' is strictly boolean, and only decides whether to # generate .pyc files. 'optimize' is three-way (0, 1, or 2), and # decides both whether to generate .pyo files and what level of @@ -36,7 +28,7 @@ class install_lib(Command): user_options = [ ('install-dir=', 'd', "directory to install to"), - ('build-dir=','b', "build directory (where to install from)"), + ('build-dir=', 'b', "build directory (where to install from)"), ('force', 'f', "force installation (overwrite existing files)"), ('compile', 'c', "compile .py to .pyc [default]"), ('no-compile', None, "don't compile .py files"), @@ -47,7 +39,8 @@ class install_lib(Command): ] boolean_options = ['force', 'compile', 'skip-build'] - negative_opt = {'no-compile' : 'compile'} + + negative_opt = {'no-compile': 'compile'} def initialize_options(self): # let the 'install_dist' command dictate our installation directory @@ -65,7 +58,8 @@ class install_lib(Command): self.set_undefined_options('install_dist', ('build_lib', 'build_dir'), ('install_lib', 'install_dir'), - 'force', 'compile', 'optimize', 'skip_build') + 'force', 'compile', 'optimize', + 'skip_build') if self.compile is None: self.compile = True @@ -89,9 +83,14 @@ class install_lib(Command): # having a build directory!) outfiles = self.install() - # (Optionally) compile .py to .pyc + # (Optionally) compile .py to .pyc and/or .pyo if outfiles is not None and self.distribution.has_pure_modules(): - self.byte_compile(outfiles) + # XXX comment from distutils: "This [prefix stripping] is far from + # complete, but it should at least generate usable bytecode in RPM + # distributions." -> need to find exact requirements for + # byte-compiled files and fix it + install_root = self.get_finalized_command('install_dist').root + self.byte_compile(outfiles, prefix=install_root) # -- Top-level worker functions ------------------------------------ # (called from 'run()') @@ -113,38 +112,6 @@ class install_lib(Command): return return outfiles - def byte_compile(self, files): - if sys.dont_write_bytecode: - # XXX do we want this? because a Python runs without bytecode - # doesn't mean that the *dists should not contain bytecode - #--or does it? - logger.warning('%s: byte-compiling is disabled, skipping.', - self.get_command_name()) - return - - from distutils2.util import byte_compile # FIXME use compileall - - # Get the "--root" directory supplied to the "install_dist" command, - # and use it as a prefix to strip off the purported filename - # encoded in bytecode files. This is far from complete, but it - # should at least generate usable bytecode in RPM distributions. - install_root = self.get_finalized_command('install_dist').root - - # Temporary kludge until we remove the verbose arguments and use - # logging everywhere - verbose = logger.getEffectiveLevel() >= logging.DEBUG - - if self.compile: - byte_compile(files, optimize=0, - force=self.force, prefix=install_root, - dry_run=self.dry_run) - if self.optimize > 0: - byte_compile(files, optimize=self.optimize, - force=self.force, prefix=install_root, - verbose=verbose, - dry_run=self.dry_run) - - # -- Utility methods ----------------------------------------------- def _mutate_outputs(self, has_any, build_cmd, cmd_option, output_dir): @@ -172,13 +139,12 @@ class install_lib(Command): if ext != PYTHON_SOURCE_EXTENSION: continue if self.compile: - bytecode_files.append(py_file + "c") - if self.optimize > 0: - bytecode_files.append(py_file + "o") + bytecode_files.append(cache_from_source(py_file, True)) + if self.optimize: + bytecode_files.append(cache_from_source(py_file, False)) return bytecode_files - # -- External interface -------------------------------------------- # (called by outsiders) diff --git a/distutils2/command/register.py b/distutils2/command/register.py index 79ec7b3..091918c 100644 --- a/distutils2/command/register.py +++ b/distutils2/command/register.py @@ -178,7 +178,7 @@ Your selection [default 1]: ''') 'will be faster.\n(the login will be stored in %s)', get_pypirc_path()) choice = 'X' - while choice.lower() not in 'yn': + while choice.lower() not in ('y', 'n'): choice = input('Save your login (y/N)?') if not choice: choice = 'n' diff --git a/distutils2/command/test.py b/distutils2/command/test.py index da5d141..09af3b6 100644 --- a/distutils2/command/test.py +++ b/distutils2/command/test.py @@ -56,7 +56,7 @@ class test(Command): prev_syspath = sys.path[:] try: # build release - build = self.get_reinitialized_command('build') + build = self.reinitialize_command('build') self.run_command('build') sys.path.insert(0, build.build_lib) diff --git a/distutils2/compat.py b/distutils2/compat.py index f7ac7a7..3927ea0 100644 --- a/distutils2/compat.py +++ b/distutils2/compat.py @@ -68,3 +68,20 @@ def fsencode(filename): else: raise TypeError("expect bytes or str, not %s" % type(filename).__name__) + + +try: + callable = callable +except NameError: + from collections import Callable + + def callable(obj): + return isinstance(obj, Callable) + + +try: + from imp import cache_from_source +except ImportError: + def cache_from_source(py_file, debug=__debug__): + ext = debug and 'c' or 'o' + return py_file + ext diff --git a/distutils2/compiler/bcppcompiler.py b/distutils2/compiler/bcppcompiler.py index 972c0f4..98855f8 100644 --- a/distutils2/compiler/bcppcompiler.py +++ b/distutils2/compiler/bcppcompiler.py @@ -48,7 +48,7 @@ class BCPPCompiler(CCompiler) : def __init__(self, verbose=0, dry_run=False, force=False): - CCompiler.__init__(self, verbose, dry_run, force) + super(BCPPCompiler, self).__init__(verbose, dry_run, force) # These executables are assumed to all be in the path. # Borland doesn't seem to use any special registry settings to diff --git a/distutils2/compiler/cygwinccompiler.py b/distutils2/compiler/cygwinccompiler.py index 4b0b786..720d1d6 100644 --- a/distutils2/compiler/cygwinccompiler.py +++ b/distutils2/compiler/cygwinccompiler.py @@ -93,8 +93,7 @@ class CygwinCCompiler(UnixCCompiler): exe_extension = ".exe" def __init__(self, verbose=0, dry_run=False, force=False): - - UnixCCompiler.__init__(self, verbose, dry_run, force) + super(CygwinCCompiler, self).__init__(verbose, dry_run, force) status, details = check_config_h() logger.debug("Python's GCC status: %s (details: %s)", status, details) @@ -234,12 +233,11 @@ class CygwinCCompiler(UnixCCompiler): if not debug: extra_preargs.append("-s") - UnixCCompiler.link(self, target_desc, objects, output_filename, - output_dir, libraries, library_dirs, - runtime_library_dirs, - None, # export_symbols, we do this in our def-file - debug, extra_preargs, extra_postargs, build_temp, - target_lang) + super(CygwinCCompiler, self).link( + target_desc, objects, output_filename, output_dir, libraries, + library_dirs, runtime_library_dirs, + None, # export_symbols, we do this in our def-file + debug, extra_preargs, extra_postargs, build_temp, target_lang) # -- Miscellaneous methods ----------------------------------------- @@ -255,14 +253,14 @@ class CygwinCCompiler(UnixCCompiler): if ext not in (self.src_extensions + ['.rc','.res']): raise UnknownFileError("unknown file type '%s' (from '%s')" % (ext, src_name)) if strip_dir: - base = os.path.basename (base) + base = os.path.basename(base) if ext in ('.res', '.rc'): # these need to be compiled to object files - obj_names.append (os.path.join(output_dir, + obj_names.append(os.path.join(output_dir, base + ext + self.obj_extension)) else: - obj_names.append (os.path.join(output_dir, - base + self.obj_extension)) + obj_names.append(os.path.join(output_dir, + base + self.obj_extension)) return obj_names # the same as cygwin plus some additional parameters @@ -273,8 +271,7 @@ class Mingw32CCompiler(CygwinCCompiler): description = 'MinGW32 compiler' def __init__(self, verbose=0, dry_run=False, force=False): - - CygwinCCompiler.__init__ (self, verbose, dry_run, force) + super(Mingw32CCompiler, self).__init__(verbose, dry_run, force) # ld_version >= "2.13" support -shared so use it instead of # -mdll -static diff --git a/distutils2/compiler/msvc9compiler.py b/distutils2/compiler/msvc9compiler.py index fb53d7d..0a86782 100644 --- a/distutils2/compiler/msvc9compiler.py +++ b/distutils2/compiler/msvc9compiler.py @@ -310,7 +310,7 @@ class MSVCCompiler(CCompiler) : exe_extension = '.exe' def __init__(self, verbose=0, dry_run=False, force=False): - CCompiler.__init__(self, verbose, dry_run, force) + super(MSVCCompiler, self).__init__(verbose, dry_run, force) self.__version = VERSION self.__root = r"Software\Microsoft\VisualStudio" # self.__macros = MACROS diff --git a/distutils2/compiler/msvccompiler.py b/distutils2/compiler/msvccompiler.py index 7a0d313..459cb02 100644 --- a/distutils2/compiler/msvccompiler.py +++ b/distutils2/compiler/msvccompiler.py @@ -237,7 +237,7 @@ class MSVCCompiler(CCompiler): exe_extension = '.exe' def __init__(self, verbose=0, dry_run=False, force=False): - CCompiler.__init__(self, verbose, dry_run, force) + super(MSVCCompiler, self).__init__(verbose, dry_run, force) self.__version = get_build_version() self.__arch = get_build_architecture() if self.__arch == "Intel": diff --git a/distutils2/create.py b/distutils2/create.py index 139cf6a..36b6b82 100644 --- a/distutils2/create.py +++ b/distutils2/create.py @@ -29,6 +29,7 @@ from textwrap import dedent from tokenize import detect_encoding from configparser import RawConfigParser +from distutils2 import logger # importing this with an underscore as it should be replaced by the # dict form or another structures for all purposes from distutils2._trove import all_classifiers as _CLASSIFIERS_LIST @@ -125,7 +126,7 @@ def ask_yn(question, default=None, helptext=None): if answer and answer[0].lower() in ('y', 'n'): return answer[0].lower() - print('\nERROR: You must select "Y" or "N".\n') + logger.error('You must select "Y" or "N".') # XXX use util.ask @@ -148,10 +149,7 @@ def ask(question, default=None, helptext=None, required=True, helptext = helptext.strip("\n") while True: - sys.stdout.write(prompt) - sys.stdout.flush() - - line = sys.stdin.readline().strip() + line = input(prompt).strip() if line == '?': print('=' * 70) print(helptext) @@ -272,9 +270,10 @@ class MainProgram: def _write_cfg(self): if os.path.exists(_FILENAME): if os.path.exists('%s.old' % _FILENAME): - print("ERROR: %(name)s.old backup exists, please check that " - "current %(name)s is correct and remove %(name)s.old" % - {'name': _FILENAME}) + message = ("ERROR: %(name)s.old backup exists, please check " + "that current %(name)s is correct and remove " + "%(name)s.old" % {'name': _FILENAME}) + logger.error(message) return shutil.move(_FILENAME, '%s.old' % _FILENAME) @@ -321,7 +320,7 @@ class MainProgram: fp.write('\n') os.chmod(_FILENAME, 0o644) - print('Wrote "%s".' % _FILENAME) + logger.info('Wrote "%s".' % _FILENAME) def convert_py_to_cfg(self): """Generate a setup.cfg from an existing setup.py. @@ -351,7 +350,6 @@ class MainProgram: ('long_description', 'description'), ('url', 'home_page'), ('platforms', 'platform'), - # backport only for 2.5+ ('provides', 'provides-dist'), ('obsoletes', 'obsoletes-dist'), ('requires', 'requires-dist')) @@ -615,8 +613,8 @@ class MainProgram: break if len(found_list) == 0: - print('ERROR: Could not find a matching license for "%s"' % - license) + logger.error('Could not find a matching license for "%s"' % + license) continue question = 'Matching licenses:\n\n' @@ -637,8 +635,8 @@ class MainProgram: try: index = found_list[int(choice) - 1] except ValueError: - print("ERROR: Invalid selection, type a number from the list " - "above.") + logger.error( + "Invalid selection, type a number from the list above.") classifiers.add(_CLASSIFIERS_LIST[index]) @@ -661,8 +659,8 @@ class MainProgram: classifiers.add(key) return except (IndexError, ValueError): - print("ERROR: Invalid selection, type a single digit " - "number.") + logger.error( + "Invalid selection, type a single digit number.") def main(): @@ -676,7 +674,3 @@ def main(): # program.write_setup_script() # distutils2.util.cfg_to_args() program() - - -if __name__ == '__main__': - main() diff --git a/distutils2/depgraph.py b/distutils2/depgraph.py index 5fc9bc2..f03ca58 100644 --- a/distutils2/depgraph.py +++ b/distutils2/depgraph.py @@ -224,6 +224,7 @@ def dependent_dists(dists, dist): def main(): + # XXX move to run._graph from distutils2.database import get_distributions tempout = StringIO() try: @@ -267,7 +268,3 @@ def main(): else: print('Supported option: -d [filename]') sys.exit(1) - - -if __name__ == '__main__': - main() diff --git a/distutils2/dist.py b/distutils2/dist.py index e2a4266..a17c950 100644 --- a/distutils2/dist.py +++ b/distutils2/dist.py @@ -5,9 +5,10 @@ import re from distutils2 import logger from distutils2.util import strtobool, resolve_name +from distutils2.compat import callable +from distutils2.config import Config from distutils2.errors import (PackagingOptionError, PackagingArgError, PackagingModuleError, PackagingClassError) -from distutils2.config import Config from distutils2.command import get_command_class, STANDARD_COMMANDS from distutils2.command.cmd import Command from distutils2.metadata import Metadata @@ -69,7 +70,7 @@ Common commands: (see '--help-commands' for more) ('use-2to3', None, "use 2to3 to make source python 3.x compatible"), ('convert-2to3-doctests', None, - "use 2to3 to convert doctests in seperate text files"), + "use 2to3 to convert doctests in separate text files"), ] display_option_names = [x[0].replace('-', '_') for x in display_options] @@ -409,13 +410,12 @@ Common commands: (see '--help-commands' for more) for help_option, short, desc, func in cmd_class.help_options: if hasattr(opts, help_option.replace('-', '_')): help_option_found = True - if hasattr(func, '__call__'): - func() - else: + if not callable(func): raise PackagingClassError( "invalid help function %r for help option %r: " "must be a callable object (function, etc.)" % (func, help_option)) + func() if help_option_found: return @@ -636,9 +636,9 @@ Common commands: (see '--help-commands' for more) except ValueError as msg: raise PackagingOptionError(msg) - def get_reinitialized_command(self, command, reinit_subcommands=False): + def reinitialize_command(self, command, reinit_subcommands=False): """Reinitializes a command to the state it was in when first - returned by 'get_command_obj()': ie., initialized but not yet + returned by 'get_command_obj()': i.e., initialized but not yet finalized. This provides the opportunity to sneak option values in programmatically, overriding or supplementing user-supplied values from the config files and command line. @@ -650,10 +650,11 @@ Common commands: (see '--help-commands' for more) 'reinit_subcommands' is true, also reinitializes the command's sub-commands, as declared by the 'sub_commands' class attribute (if it has one). See the "install_dist" command for an example. Only - reinitializes the sub-commands that actually matter, ie. those - whose test predicates return true. + reinitializes the sub-commands that actually matter, i.e. those + whose test predicate return true. - Returns the reinitialized command object. + Returns the reinitialized command object. It will be the same + object as the one stored in the self.command_obj attribute. """ if not isinstance(command, Command): command_name = command @@ -671,7 +672,7 @@ Common commands: (see '--help-commands' for more) if reinit_subcommands: for sub in command.get_sub_commands(): - self.get_reinitialized_command(sub, reinit_subcommands) + self.reinitialize_command(sub, reinit_subcommands) return command @@ -733,7 +734,7 @@ Common commands: (see '--help-commands' for more) else: hook_obj = hook - if not hasattr(hook_obj, '__call__'): + if not callable(hook_obj): raise PackagingOptionError('hook %r is not callable' % hook) logger.info('running %s %s for command %s', diff --git a/distutils2/errors.py b/distutils2/errors.py index cf58d09..e99116c 100644 --- a/distutils2/errors.py +++ b/distutils2/errors.py @@ -72,10 +72,6 @@ class PackagingTemplateError(PackagingError): """Syntax error in a file list template.""" -class PackagingByteCompileError(PackagingError): - """Byte compile error.""" - - class PackagingPyPIError(PackagingError): """Any problem occuring during using the indexes.""" diff --git a/distutils2/install.py b/distutils2/install.py index 832f5db..01f9090 100644 --- a/distutils2/install.py +++ b/distutils2/install.py @@ -528,12 +528,3 @@ def install(project): logger.info('%r conflicts with %s', project, ','.join(projects)) return True - - -def _main(**attrs): - if 'script_args' not in attrs: - attrs['requirements'] = sys.argv[1] - get_infos(**attrs) - -if __name__ == '__main__': - _main() diff --git a/distutils2/manifest.py b/distutils2/manifest.py index 64e8ebf..5c6c511 100644 --- a/distutils2/manifest.py +++ b/distutils2/manifest.py @@ -147,7 +147,9 @@ class Manifest: def _parse_template_line(self, line): words = line.split() - if len(words) == 1: + if len(words) == 1 and words[0] not in ( + 'include', 'exclude', 'global-include', 'global-exclude', + 'recursive-include', 'recursive-exclude', 'graft', 'prune'): # no action given, let's use the default 'include' words.insert(0, 'include') diff --git a/distutils2/metadata.py b/distutils2/metadata.py index 8db86e2..a08322a 100644 --- a/distutils2/metadata.py +++ b/distutils2/metadata.py @@ -28,8 +28,9 @@ try: def __init__(self, source, report_level, halt_level, stream=None, debug=0, encoding='ascii', error_handler='replace'): self.messages = [] - Reporter.__init__(self, source, report_level, halt_level, stream, - debug, encoding, error_handler) + super(SilentReporter, self).__init__( + source, report_level, halt_level, stream, + debug, encoding, error_handler) def system_message(self, level, message, *children, **kwargs): self.messages.append((level, message, children, kwargs)) @@ -184,6 +185,7 @@ _MISSING = object() _FILESAFE = re.compile('[^A-Za-z0-9.]+') + class Metadata: """The metadata of a release. @@ -227,10 +229,8 @@ class Metadata: def __delitem__(self, name): field_name = self._convert_name(name) - try: - del self._fields[field_name] - except KeyError: - raise KeyError(name) + # we let a KeyError propagate + del self._fields[field_name] self._set_best_version() def __contains__(self, name): diff --git a/distutils2/run.py b/distutils2/run.py index bebf070..d2f1eb6 100644 --- a/distutils2/run.py +++ b/distutils2/run.py @@ -9,6 +9,7 @@ import logging from distutils2 import logger from distutils2.dist import Distribution from distutils2.util import _is_archive_file, generate_setup_py +from distutils2.compat import callable from distutils2.command import get_command_class, STANDARD_COMMANDS from distutils2.install import install, install_local_project, remove from distutils2.database import get_distribution, get_distributions @@ -368,7 +369,7 @@ actions = [ ('list', 'List installed projects', _list), ('graph', 'Display a graph', _graph), ('create', 'Create a project', _create), - ('generate-setup', 'Generate a backward-comptatible setup.py', _generate), + ('generate-setup', 'Generate a backward-compatible setup.py', _generate), ] @@ -500,7 +501,7 @@ class Dispatcher: for help_option, short, desc, func in cmd_class.help_options: if hasattr(opts, help_option.replace('-', '_')): help_option_found = True - if hasattr(func, '__call__'): + if callable(func): func() else: raise PackagingClassError( diff --git a/distutils2/tests/__main__.py b/distutils2/tests/__main__.py index 3609c64..6ee5434 100644 --- a/distutils2/tests/__main__.py +++ b/distutils2/tests/__main__.py @@ -3,7 +3,6 @@ # Ripped from importlib tests, thanks Brett! import os -import sys from test.support import reap_children, reap_threads, run_unittest from distutils2.tests import unittest diff --git a/distutils2/tests/pypi_server.py b/distutils2/tests/pypi_server.py index 710e055..6e77e40 100644 --- a/distutils2/tests/pypi_server.py +++ b/distutils2/tests/pypi_server.py @@ -33,7 +33,6 @@ import os import queue import select import threading -import socketserver from functools import wraps from http.server import HTTPServer, SimpleHTTPRequestHandler from xmlrpc.server import SimpleXMLRPCServer @@ -103,7 +102,7 @@ class PyPIServer(threading.Thread): """ # we want to launch the server in a new dedicated thread, to not freeze # tests. - threading.Thread.__init__(self) + super(PyPIServer, self).__init__() self._run = True self._serve_xmlrpc = serve_xmlrpc if static_filesystem_paths is None: @@ -270,7 +269,7 @@ class PyPIRequestHandler(SimpleHTTPRequestHandler): class PyPIXMLRPCServer(SimpleXMLRPCServer): def server_bind(self): """Override server_bind to store the server name.""" - socketserver.TCPServer.server_bind(self) + super(PyPIXMLRPCServer, self).server_bind() host, port = self.socket.getsockname()[:2] self.server_port = port @@ -371,12 +370,13 @@ class MockDist: 'requires_python': self.requires_python, 'classifiers': [], 'name': self.name, - 'licence': self.licence, + 'licence': self.licence, # XXX licence or license? 'summary': self.summary, 'home_page': self.homepage, 'stable_version': self.stable_version, - 'provides_dist': self.provides_dist or "%s (%s)" % (self.name, - self.version), + # FIXME doesn't that reproduce the bug from 6527d3106e9f? + 'provides_dist': (self.provides_dist or + "%s (%s)" % (self.name, self.version)), 'requires': self.requires, 'cheesecake_installability_id': self.cheesecake_installability_id, } diff --git a/distutils2/tests/support.py b/distutils2/tests/support.py index 961780d..cacda42 100644 --- a/distutils2/tests/support.py +++ b/distutils2/tests/support.py @@ -36,7 +36,6 @@ import os import re import sys import errno -import codecs import shutil import logging import logging.handlers @@ -49,6 +48,9 @@ except ImportError: zlib = None from distutils2.dist import Distribution +from distutils2.util import resolve_name +from distutils2.command import set_command, _COMMANDS + from distutils2.tests import unittest from distutils2._backport import sysconfig @@ -57,11 +59,12 @@ __all__ = [ # TestCase mixins 'LoggingCatcher', 'TempdirManager', 'EnvironRestorer', # mocks - 'DummyCommand', 'TestDistribution', + 'DummyCommand', 'TestDistribution', 'Inputs', # misc. functions and decorators - 'fake_dec', 'create_distribution', 'copy_xxmodule_c', 'fixup_build_ext', + 'fake_dec', 'create_distribution', 'use_command', + 'copy_xxmodule_c', 'fixup_build_ext', # imported from this module for backport purposes - 'unittest', 'requires_zlib', 'skip_unless_symlink', + 'unittest', 'requires_zlib', 'skip_2to3_optimize', 'skip_unless_symlink', ] @@ -73,7 +76,7 @@ class _TestHandler(logging.handlers.BufferingHandler): # stolen and adapted from test.support def __init__(self): - logging.handlers.BufferingHandler.__init__(self, 0) + super(_TestHandler, self).__init__(0) self.setLevel(logging.DEBUG) def shouldFlush(self): @@ -90,10 +93,13 @@ class LoggingCatcher: configured to record all messages logged to the 'distutils2' logger. Use get_logs to retrieve messages and self.loghandler.flush to discard - them. get_logs automatically flushes the logs; if you test code that - generates logging messages but don't use get_logs, you have to flush - manually before doing other checks on logging message, otherwise you - will get irrelevant results. See example in test_command_check. + them. get_logs automatically flushes the logs, unless you pass + *flush=False*, for example to make multiple calls to the method with + different level arguments. If your test calls some code that generates + logging message and then you don't call get_logs, you will need to flush + manually before testing other code in the same test_* method, otherwise + get_logs in the next lines will see messages from the previous lines. + See example in test_command_check. """ def setUp(self): @@ -117,25 +123,23 @@ class LoggingCatcher: logger2to3.setLevel(self._old_levels[1]) super(LoggingCatcher, self).tearDown() - def get_logs(self, *levels): - """Return all log messages with level in *levels*. + def get_logs(self, level=logging.WARNING, flush=True): + """Return all log messages with given level. - Without explicit levels given, returns all messages. *levels* defaults - to all levels. For log calls with arguments (i.e. - logger.info('bla bla %r', arg)), the messages will be formatted before - being returned (e.g. "bla bla 'thing'"). + *level* defaults to logging.WARNING. - Returns a list. Automatically flushes the loghandler after being - called. + For log calls with arguments (i.e. logger.info('bla bla %r', arg)), + the messages will be formatted before being returned (e.g. "bla bla + 'thing'"). - Example: self.get_logs(logging.WARN, logging.DEBUG). + Returns a list. Automatically flushes the loghandler after being + called, unless *flush* is False (this is useful to get e.g. all + warnings then all info messages). """ - if not levels: - messages = [log.getMessage() for log in self.loghandler.buffer] - else: - messages = [log.getMessage() for log in self.loghandler.buffer - if log.levelno in levels] - self.loghandler.flush() + messages = [log.getMessage() for log in self.loghandler.buffer + if log.levelno == level] + if flush: + self.loghandler.flush() return messages @@ -254,7 +258,7 @@ class DummyCommand: Useful for mocking one dependency command in the tests for another command, see e.g. the dummy build command in test_build_scripts. """ - # XXX does not work with dist.get_reinitialized_command, which typechecks + # XXX does not work with dist.reinitialize_command, which typechecks # and wants a finalized attribute def __init__(self, **kwargs): @@ -277,6 +281,22 @@ class TestDistribution(Distribution): return self._config_files +class Inputs: + """Fakes user inputs.""" + # TODO document usage + # TODO use context manager or something for auto cleanup + + def __init__(self, *answers): + self.answers = answers + self.index = 0 + + def __call__(self, prompt=''): + try: + return self.answers[self.index] + finally: + self.index += 1 + + def create_distribution(configfiles=()): """Prepares a distribution with given config files parsed.""" d = TestDistribution() @@ -287,6 +307,15 @@ def create_distribution(configfiles=()): return d +def use_command(testcase, fullname): + """Register command at *fullname* for the duration of a test.""" + set_command(fullname) + # XXX maybe set_command should return the class object + name = resolve_name(fullname).get_command_name() + # XXX maybe we need a public API to remove commands + testcase.addCleanup(_COMMANDS.__delitem__, name) + + def fake_dec(*args, **kw): """Fake decorator""" def _wrap(func): @@ -369,6 +398,10 @@ except ImportError: 'requires test.support.skip_unless_symlink') +skip_2to3_optimize = unittest.skipIf(sys.flags.optimize, + "2to3 doesn't work under -O") + + requires_zlib = unittest.skipUnless(zlib, 'requires zlib') diff --git a/distutils2/tests/test_command_bdist_dumb.py b/distutils2/tests/test_command_bdist_dumb.py index a31d14e..f77e279 100644 --- a/distutils2/tests/test_command_bdist_dumb.py +++ b/distutils2/tests/test_command_bdist_dumb.py @@ -1,6 +1,9 @@ """Tests for distutils.command.bdist_dumb.""" import os +import imp +import sys +import zipfile import distutils2.util from distutils2.dist import Distribution @@ -49,15 +52,27 @@ class BuildDumbTestCase(support.TempdirManager, # see what we have dist_created = os.listdir(os.path.join(pkg_dir, 'dist')) - base = "%s.%s" % (dist.get_fullname(), cmd.plat_name) + base = "%s.%s.zip" % (dist.get_fullname(), cmd.plat_name) if os.name == 'os2': base = base.replace(':', '-') - wanted = ['%s.zip' % base] - self.assertEqual(dist_created, wanted) + self.assertEqual(dist_created, [base]) # now let's check what we have in the zip file - # XXX to be done + fp = zipfile.ZipFile(os.path.join('dist', base)) + try: + contents = fp.namelist() + finally: + fp.close + + if sys.version_info[1] == 1: + pyc = 'foo.pyc' + else: + pyc = 'foo.%s.pyc' % imp.get_tag() + contents = sorted(os.path.basename(fn) for fn in contents) + wanted = ['foo.py', pyc, + 'METADATA', 'INSTALLER', 'REQUESTED', 'RECORD'] + self.assertEqual(contents, sorted(wanted)) def test_finalize_options(self): pkg_dir, dist = self.create_dist() diff --git a/distutils2/tests/test_command_build_ext.py b/distutils2/tests/test_command_build_ext.py index 06b5992..b9f8cc2 100644 --- a/distutils2/tests/test_command_build_ext.py +++ b/distutils2/tests/test_command_build_ext.py @@ -1,9 +1,7 @@ import os import sys import site -import shutil import textwrap -from io import StringIO from distutils2.dist import Distribution from distutils2.errors import (UnknownFileError, CompileError, PackagingPlatformError) @@ -11,7 +9,7 @@ from distutils2.command.build_ext import build_ext from distutils2.compiler.extension import Extension from distutils2._backport import sysconfig -from distutils2.tests import support, unittest, verbose +from distutils2.tests import support, unittest from distutils2.tests.support import assert_python_ok @@ -38,18 +36,10 @@ class BuildExtTestCase(support.TempdirManager, support.fixup_build_ext(cmd) cmd.build_lib = self.tmp_dir cmd.build_temp = self.tmp_dir + cmd.ensure_finalized() + cmd.run() - old_stdout = sys.stdout - if not verbose: - # silence compiler output - sys.stdout = StringIO() - try: - cmd.ensure_finalized() - cmd.run() - finally: - sys.stdout = old_stdout - - code = """if 1: + code = textwrap.dedent("""\ import sys sys.path.insert(0, %r) @@ -64,7 +54,8 @@ class BuildExtTestCase(support.TempdirManager, doc = 'This is a template module just for instruction.' assert xx.__doc__ == doc assert isinstance(xx.Null(), xx.Null) - assert isinstance(xx.Str(), xx.Str)""" + assert isinstance(xx.Str(), xx.Str) + """) code = code % self.tmp_dir assert_python_ok('-c', code) @@ -389,16 +380,8 @@ class BuildExtTestCase(support.TempdirManager, cmd.build_temp = self.tmp_dir try: - old_stdout = sys.stdout - if not verbose: - # silence compiler output - sys.stdout = StringIO() - try: - cmd.ensure_finalized() - cmd.run() - finally: - sys.stdout = old_stdout - + cmd.ensure_finalized() + cmd.run() except CompileError: self.fail("Wrong deployment target during compilation") diff --git a/distutils2/tests/test_command_build_py.py b/distutils2/tests/test_command_build_py.py index fa152ec..4b6c6aa 100644 --- a/distutils2/tests/test_command_build_py.py +++ b/distutils2/tests/test_command_build_py.py @@ -55,30 +55,20 @@ class BuildPyTestCase(support.TempdirManager, # This makes sure the list of outputs includes byte-compiled # files for Python modules but not for package data files # (there shouldn't *be* byte-code files for those!). - # self.assertEqual(len(cmd.get_outputs()), 3) pkgdest = os.path.join(destination, "pkg") files = os.listdir(pkgdest) - pycache_dir = os.path.join(pkgdest, "__pycache__") self.assertIn("__init__.py", files) self.assertIn("README.txt", files) - if sys.dont_write_bytecode: - if sys.version_info[1] == 1: - self.assertNotIn("__init__.pyc", files) - else: - self.assertFalse(os.path.exists(pycache_dir)) + if sys.version_info[1] == 1: + self.assertIn("__init__.pyc", files) else: - # XXX even with -O, distutils2 writes pyc, not pyo; bug? - if sys.version_info[1] == 1: - self.assertIn("__init__.pyc", files) - else: - pyc_files = os.listdir(pycache_dir) - self.assertIn("__init__.%s.pyc" % imp.get_tag(), pyc_files) + pycache_dir = os.path.join(pkgdest, "__pycache__") + pyc_files = os.listdir(pycache_dir) + self.assertIn("__init__.%s.pyc" % imp.get_tag(), pyc_files) def test_empty_package_dir(self): # See SF 1668596/1720897. - cwd = os.getcwd() - # create the distribution files. sources = self.mkdtemp() pkg = os.path.join(sources, 'pkg') @@ -89,40 +79,66 @@ class BuildPyTestCase(support.TempdirManager, open(os.path.join(testdir, "testfile"), "wb").close() os.chdir(sources) - old_stdout = sys.stdout - #sys.stdout = StringIO.StringIO() + dist = Distribution({"packages": ["pkg"], + "package_dir": sources, + "package_data": {"pkg": ["doc/*"]}}) + dist.script_args = ["build"] + dist.parse_command_line() try: - dist = Distribution({"packages": ["pkg"], - "package_dir": sources, - "package_data": {"pkg": ["doc/*"]}}) - dist.script_args = ["build"] - dist.parse_command_line() - - try: - dist.run_commands() - except PackagingFileError: - self.fail("failed package_data test when package_dir is ''") - finally: - # Restore state. - os.chdir(cwd) - sys.stdout = old_stdout + dist.run_commands() + except PackagingFileError: + self.fail("failed package_data test when package_dir is ''") + + def test_byte_compile(self): + project_dir, dist = self.create_dist(py_modules=['boiledeggs']) + os.chdir(project_dir) + self.write_file('boiledeggs.py', 'import antigravity') + cmd = build_py(dist) + cmd.compile = True + cmd.build_lib = 'here' + cmd.finalize_options() + cmd.run() - def test_dont_write_bytecode(self): - # makes sure byte_compile is not used - pkg_dir, dist = self.create_dist() + found = os.listdir(cmd.build_lib) + if sys.version_info[1] == 1: + self.assertEqual(sorted(found), + ['boiledeggs.py', 'boiledeggs.pyc']) + else: + self.assertEqual(sorted(found), ['__pycache__', 'boiledeggs.py']) + found = os.listdir(os.path.join(cmd.build_lib, '__pycache__')) + self.assertEqual(found, ['boiledeggs.%s.pyc' % imp.get_tag()]) + + def test_byte_compile_optimized(self): + project_dir, dist = self.create_dist(py_modules=['boiledeggs']) + os.chdir(project_dir) + self.write_file('boiledeggs.py', 'import antigravity') cmd = build_py(dist) cmd.compile = True cmd.optimize = 1 + cmd.build_lib = 'here' + cmd.finalize_options() + cmd.run() - old_dont_write_bytecode = sys.dont_write_bytecode + found = os.listdir(cmd.build_lib) + if sys.version_info[1] == 1: + self.assertEqual(sorted(found), ['boiledeggs.py', 'boiledeggs.pyc', + 'boiledeggs.pyo']) + else: + self.assertEqual(sorted(found), ['__pycache__', 'boiledeggs.py']) + found = os.listdir(os.path.join(cmd.build_lib, '__pycache__')) + self.assertEqual(sorted(found), + ['boiledeggs.%s.pyc' % imp.get_tag(), + 'boiledeggs.%s.pyo' % imp.get_tag()]) + + def test_byte_compile_under_B(self): + # make sure byte compilation works under -B (dont_write_bytecode) + self.addCleanup(setattr, sys, 'dont_write_bytecode', + sys.dont_write_bytecode) sys.dont_write_bytecode = True - try: - cmd.byte_compile([]) - finally: - sys.dont_write_bytecode = old_dont_write_bytecode + self.test_byte_compile() + self.test_byte_compile_optimized() - self.assertIn('byte-compiling is disabled', self.get_logs()[0]) def test_suite(): return unittest.makeSuite(BuildPyTestCase) diff --git a/distutils2/tests/test_command_check.py b/distutils2/tests/test_command_check.py index 638bdaf..e4413b3 100644 --- a/distutils2/tests/test_command_check.py +++ b/distutils2/tests/test_command_check.py @@ -1,6 +1,5 @@ """Tests for distutils.command.check.""" -import logging from distutils2.command.check import check from distutils2.metadata import _HAS_DOCUTILS from distutils2.errors import PackagingSetupError, MetadataMissingError @@ -27,11 +26,11 @@ class CheckTestCase(support.LoggingCatcher, # let's run the command with no metadata at all # by default, check is checking the metadata # should have some warnings - cmd = self._run() + self._run() # trick: using assertNotEqual with an empty list will give us a more # useful error message than assertGreater(.., 0) when the code change # and the test fails - self.assertNotEqual([], self.get_logs(logging.WARNING)) + self.assertNotEqual(self.get_logs(), []) # now let's add the required fields # and run it again, to make sure we don't get @@ -40,8 +39,8 @@ class CheckTestCase(support.LoggingCatcher, 'author_email': 'xxx', 'name': 'xxx', 'version': '4.2', } - cmd = self._run(metadata) - self.assertEqual([], self.get_logs(logging.WARNING)) + self._run(metadata) + self.assertEqual(self.get_logs(), []) # now with the strict mode, we should # get an error if there are missing metadata @@ -53,8 +52,8 @@ class CheckTestCase(support.LoggingCatcher, self.loghandler.flush() # and of course, no error when all metadata fields are present - cmd = self._run(metadata, strict=True) - self.assertEqual([], self.get_logs(logging.WARNING)) + self._run(metadata, strict=True) + self.assertEqual(self.get_logs(), []) # now a test with non-ASCII characters metadata = {'home_page': 'xxx', 'author': '\u00c9ric', @@ -62,15 +61,15 @@ class CheckTestCase(support.LoggingCatcher, 'version': '1.2', 'summary': 'Something about esszet \u00df', 'description': 'More things about esszet \u00df'} - cmd = self._run(metadata) - self.assertEqual([], self.get_logs(logging.WARNING)) + self._run(metadata) + self.assertEqual(self.get_logs(), []) def test_check_metadata_1_2(self): # let's run the command with no metadata at all # by default, check is checking the metadata # should have some warnings - cmd = self._run() - self.assertNotEqual([], self.get_logs(logging.WARNING)) + self._run() + self.assertNotEqual(self.get_logs(), []) # now let's add the required fields and run it again, to make sure we # don't get any warning anymore let's use requires_python as a marker @@ -80,8 +79,8 @@ class CheckTestCase(support.LoggingCatcher, 'name': 'xxx', 'version': '4.2', 'requires_python': '2.4', } - cmd = self._run(metadata) - self.assertEqual([], self.get_logs(logging.WARNING)) + self._run(metadata) + self.assertEqual(self.get_logs(), []) # now with the strict mode, we should # get an error if there are missing metadata @@ -99,8 +98,8 @@ class CheckTestCase(support.LoggingCatcher, # now with correct version format again metadata['version'] = '4.2' - cmd = self._run(metadata, strict=True) - self.assertEqual([], self.get_logs(logging.WARNING)) + self._run(metadata, strict=True) + self.assertEqual(self.get_logs(), []) @unittest.skipUnless(_HAS_DOCUTILS, "requires docutils") def test_check_restructuredtext(self): @@ -109,9 +108,7 @@ class CheckTestCase(support.LoggingCatcher, pkg_info, dist = self.create_dist(description=broken_rest) cmd = check(dist) cmd.check_restructuredtext() - self.assertEqual(len(self.get_logs(logging.WARNING)), 1) - # clear warnings from the previous call - self.loghandler.flush() + self.assertEqual(len(self.get_logs()), 1) # let's see if we have an error with strict=1 metadata = {'home_page': 'xxx', 'author': 'xxx', @@ -126,7 +123,7 @@ class CheckTestCase(support.LoggingCatcher, dist = self.create_dist(description='title\n=====\n\ntest \u00df')[1] cmd = check(dist) cmd.check_restructuredtext() - self.assertEqual([], self.get_logs(logging.WARNING)) + self.assertEqual(self.get_logs(), []) def test_check_all(self): self.assertRaises(PackagingSetupError, self._run, @@ -143,18 +140,18 @@ class CheckTestCase(support.LoggingCatcher, } cmd = check(dist) cmd.check_hooks_resolvable() - self.assertEqual(len(self.get_logs(logging.WARNING)), 1) + self.assertEqual(len(self.get_logs()), 1) def test_warn(self): _, dist = self.create_dist() cmd = check(dist) - self.assertEqual([], self.get_logs()) + self.assertEqual(self.get_logs(), []) cmd.warn('hello') - self.assertEqual(['check: hello'], self.get_logs()) + self.assertEqual(self.get_logs(), ['check: hello']) cmd.warn('hello %s', 'world') - self.assertEqual(['check: hello world'], self.get_logs()) + self.assertEqual(self.get_logs(), ['check: hello world']) cmd.warn('hello %s %s', 'beautiful', 'world') - self.assertEqual(['check: hello beautiful world'], self.get_logs()) + self.assertEqual(self.get_logs(), ['check: hello beautiful world']) def test_suite(): diff --git a/distutils2/tests/test_command_clean.py b/distutils2/tests/test_command_clean.py index 7b8effa..910628a 100644 --- a/distutils2/tests/test_command_clean.py +++ b/distutils2/tests/test_command_clean.py @@ -5,7 +5,8 @@ from distutils2.command.clean import clean from distutils2.tests import unittest, support -class cleanTestCase(support.TempdirManager, support.LoggingCatcher, +class CleanTestCase(support.TempdirManager, + support.LoggingCatcher, unittest.TestCase): def test_simple_run(self): @@ -23,7 +24,7 @@ class cleanTestCase(support.TempdirManager, support.LoggingCatcher, if name == 'build_base': continue for f in ('one', 'two', 'three'): - self.write_file(os.path.join(path, f)) + self.write_file((path, f)) # let's run the command cmd.all = True @@ -36,13 +37,11 @@ class cleanTestCase(support.TempdirManager, support.LoggingCatcher, '%r was not removed' % path) # let's run the command again (should spit warnings but succeed) - cmd.all = True - cmd.ensure_finalized() cmd.run() def test_suite(): - return unittest.makeSuite(cleanTestCase) + return unittest.makeSuite(CleanTestCase) if __name__ == "__main__": unittest.main(defaultTest="test_suite") diff --git a/distutils2/tests/test_command_cmd.py b/distutils2/tests/test_command_cmd.py index 1b0f622..c29f0de 100644 --- a/distutils2/tests/test_command_cmd.py +++ b/distutils2/tests/test_command_cmd.py @@ -1,5 +1,6 @@ """Tests for distutils.cmd.""" import os +import logging from distutils2.command.cmd import Command from distutils2.dist import Distribution @@ -43,7 +44,7 @@ class CommandTestCase(support.LoggingCatcher, wanted = ["command options for 'MyCmd':", ' option1 = 1', ' option2 = 1'] - msgs = self.get_logs() + msgs = self.get_logs(logging.INFO) self.assertEqual(msgs, wanted) def test_ensure_string(self): diff --git a/distutils2/tests/test_command_install_data.py b/distutils2/tests/test_command_install_data.py index ba93cce..c169b87 100644 --- a/distutils2/tests/test_command_install_data.py +++ b/distutils2/tests/test_command_install_data.py @@ -62,6 +62,7 @@ class InstallDataTestCase(support.TempdirManager, # let's try with warn_dir one cmd.warn_dir = True + cmd.finalized = False cmd.ensure_finalized() cmd.run() @@ -80,6 +81,7 @@ class InstallDataTestCase(support.TempdirManager, cmd.data_files = {one: '{inst}/one', two: '{inst2}/two', three: '{inst3}/three'} + cmd.finalized = False cmd.ensure_finalized() cmd.run() @@ -125,22 +127,16 @@ class InstallDataTestCase(support.TempdirManager, # now the real test fn = os.path.join(install_dir, 'Spamlib-0.1.dist-info', 'RESOURCES') - fp = open(fn) - try: + with open(fn, encoding='utf-8') as fp: content = fp.read().strip() - finally: - fp.close() expected = 'spamd,%s' % os.path.join(scripts_dir, 'spamd') self.assertEqual(content, expected) # just to be sure, we also test that get_file works here, even though # packaging.database has its own test file - fp = distutils2.database.get_file('Spamlib', 'spamd') - try: + with distutils2.database.get_file('Spamlib', 'spamd') as fp: content = fp.read() - finally: - fp.close() self.assertEqual('# Python script', content) diff --git a/distutils2/tests/test_command_install_dist.py b/distutils2/tests/test_command_install_dist.py index c477299..597e10a 100644 --- a/distutils2/tests/test_command_install_dist.py +++ b/distutils2/tests/test_command_install_dist.py @@ -1,6 +1,7 @@ """Tests for distutils2.command.install.""" import os +import imp import sys from distutils2.command.build_ext import build_ext @@ -92,21 +93,20 @@ class InstallTestCase(support.TempdirManager, self.old_expand = os.path.expanduser os.path.expanduser = _expanduser - try: - # this is the actual test - self._test_user_site() - finally: + def cleanup(): _CONFIG_VARS['userbase'] = self.old_user_base _SCHEMES.set(scheme, 'purelib', self.old_user_site) os.path.expanduser = self.old_expand - def _test_user_site(self): + self.addCleanup(cleanup) + schemes = get_scheme_names() for key in ('nt_user', 'posix_user', 'os2_home'): self.assertIn(key, schemes) dist = Distribution({'name': 'xx'}) cmd = install_dist(dist) + # making sure the user option is there options = [name for name, short, lable in cmd.user_options] @@ -181,9 +181,11 @@ class InstallTestCase(support.TempdirManager, def test_old_record(self): # test pre-PEP 376 --record option (outside dist-info dir) install_dir = self.mkdtemp() - project_dir, dist = self.create_dist(scripts=['hello']) + project_dir, dist = self.create_dist(py_modules=['hello'], + scripts=['sayhi']) os.chdir(project_dir) - self.write_file('hello', "print('o hai')") + self.write_file('hello.py', "def main(): print('o hai')") + self.write_file('sayhi', 'from hello import main; main()') cmd = install_dist(dist) dist.command_obj['install_dist'] = cmd @@ -195,9 +197,14 @@ class InstallTestCase(support.TempdirManager, with open(cmd.record) as f: content = f.read() + if sys.version_info[1] == 1: + pyc = 'hello.pyc' + else: + pyc = 'hello.%s.pyc' % imp.get_tag() found = [os.path.basename(line) for line in content.splitlines()] - expected = ['hello', 'METADATA', 'INSTALLER', 'REQUESTED', 'RECORD'] - self.assertEqual(found, expected) + expected = ['hello.py', pyc, 'sayhi', + 'METADATA', 'INSTALLER', 'REQUESTED', 'RECORD'] + self.assertEqual(sorted(found), sorted(expected)) # XXX test that fancy_getopt is okay with options named # record and no-record but unrelated diff --git a/distutils2/tests/test_command_install_distinfo.py b/distutils2/tests/test_command_install_distinfo.py index 9898c2f..3985e0b 100644 --- a/distutils2/tests/test_command_install_distinfo.py +++ b/distutils2/tests/test_command_install_distinfo.py @@ -49,7 +49,7 @@ class InstallDistinfoTestCase(support.TempdirManager, cmd = install_distinfo(dist) dist.command_obj['install_distinfo'] = cmd - cmd.distinfo_dir = install_dir + cmd.install_dir = install_dir cmd.ensure_finalized() cmd.run() @@ -60,6 +60,7 @@ class InstallDistinfoTestCase(support.TempdirManager, ['METADATA', 'RECORD', 'REQUESTED', 'INSTALLER']) with open(os.path.join(dist_info, 'INSTALLER')) as fp: self.assertEqual(fp.read(), 'distutils') + with open(os.path.join(dist_info, 'REQUESTED')) as fp: self.assertEqual(fp.read(), '') meta_path = os.path.join(dist_info, 'METADATA') @@ -76,7 +77,7 @@ class InstallDistinfoTestCase(support.TempdirManager, cmd = install_distinfo(dist) dist.command_obj['install_distinfo'] = cmd - cmd.distinfo_dir = install_dir + cmd.install_dir = install_dir cmd.installer = 'bacon-python' cmd.ensure_finalized() cmd.run() @@ -96,7 +97,7 @@ class InstallDistinfoTestCase(support.TempdirManager, cmd = install_distinfo(dist) dist.command_obj['install_distinfo'] = cmd - cmd.distinfo_dir = install_dir + cmd.install_dir = install_dir cmd.requested = False cmd.ensure_finalized() cmd.run() @@ -116,7 +117,7 @@ class InstallDistinfoTestCase(support.TempdirManager, cmd = install_distinfo(dist) dist.command_obj['install_distinfo'] = cmd - cmd.distinfo_dir = install_dir + cmd.install_dir = install_dir cmd.no_record = True cmd.ensure_finalized() cmd.run() @@ -214,7 +215,7 @@ class InstallDistinfoTestCase(support.TempdirManager, cmd = install_distinfo(dist) dist.command_obj['install_distinfo'] = cmd - cmd.distinfo_dir = install_dir + cmd.install_dir = install_dir cmd.ensure_finalized() cmd.run() diff --git a/distutils2/tests/test_command_install_lib.py b/distutils2/tests/test_command_install_lib.py index 78b539c..160e62e 100644 --- a/distutils2/tests/test_command_install_lib.py +++ b/distutils2/tests/test_command_install_lib.py @@ -17,7 +17,7 @@ class InstallLibTestCase(support.TempdirManager, restore_environ = ['PYTHONPATH'] def test_finalize_options(self): - pkg_dir, dist = self.create_dist() + dist = self.create_dist()[1] cmd = install_lib(dist) cmd.finalize_options() @@ -34,75 +34,77 @@ class InstallLibTestCase(support.TempdirManager, cmd.finalize_options() self.assertEqual(cmd.optimize, 2) - @unittest.skipIf(sys.dont_write_bytecode, 'byte-compile disabled') def test_byte_compile(self): - pkg_dir, dist = self.create_dist() - os.chdir(pkg_dir) + project_dir, dist = self.create_dist() + os.chdir(project_dir) cmd = install_lib(dist) cmd.compile = True cmd.optimize = 1 - f = os.path.join(pkg_dir, 'foo.py') + f = os.path.join(project_dir, 'foo.py') self.write_file(f, '# python file') cmd.byte_compile([f]) if sys.version_info[1] == 1: pyc_file = 'foo.pyc' pyo_file = 'foo.pyo' else: - pyc_file = imp.cache_from_source('foo.py') - pyo_file = imp.cache_from_source('foo.py', debug_override=False) + pyc_file = imp.cache_from_source('foo.py', True) + pyo_file = imp.cache_from_source('foo.py', False) self.assertTrue(os.path.exists(pyc_file)) self.assertTrue(os.path.exists(pyo_file)) + def test_byte_compile_under_B(self): + # make sure byte compilation works under -B (dont_write_bytecode) + self.addCleanup(setattr, sys, 'dont_write_bytecode', + sys.dont_write_bytecode) + sys.dont_write_bytecode = True + self.test_byte_compile() + def test_get_outputs(self): - pkg_dir, dist = self.create_dist() + project_dir, dist = self.create_dist() + os.chdir(project_dir) + os.mkdir('spam') cmd = install_lib(dist) # setting up a dist environment cmd.compile = True cmd.optimize = 1 - cmd.install_dir = pkg_dir - f = os.path.join(pkg_dir, '__init__.py') + cmd.install_dir = self.mkdtemp() + f = os.path.join(project_dir, 'spam', '__init__.py') self.write_file(f, '# python package') cmd.distribution.ext_modules = [Extension('foo', ['xxx'])] - cmd.distribution.packages = [pkg_dir] + cmd.distribution.packages = ['spam'] - # make sure the build_lib is set the temp dir - build_dir = os.path.split(pkg_dir)[0] + # make sure the build_lib is set the temp dir # XXX what? this is not + # needed in the same distutils test and should work without manual + # intervention + build_dir = os.path.split(project_dir)[0] cmd.get_finalized_command('build_py').build_lib = build_dir - # get_output should return 4 elements - self.assertEqual(len(cmd.get_outputs()), 4) + # get_outputs should return 4 elements: spam/__init__.py, .pyc and + # .pyo, foo*.so / foo.pyd + outputs = cmd.get_outputs() + self.assertEqual(len(outputs), 4, outputs) def test_get_inputs(self): - pkg_dir, dist = self.create_dist() + project_dir, dist = self.create_dist() + os.chdir(project_dir) + os.mkdir('spam') cmd = install_lib(dist) # setting up a dist environment cmd.compile = True cmd.optimize = 1 - cmd.install_dir = pkg_dir - f = os.path.join(pkg_dir, '__init__.py') + cmd.install_dir = self.mkdtemp() + f = os.path.join(project_dir, 'spam', '__init__.py') self.write_file(f, '# python package') cmd.distribution.ext_modules = [Extension('foo', ['xxx'])] - cmd.distribution.packages = [pkg_dir] - - # get_input should return 2 elements - self.assertEqual(len(cmd.get_inputs()), 2) - - def test_dont_write_bytecode(self): - # makes sure byte_compile is not used - pkg_dir, dist = self.create_dist() - cmd = install_lib(dist) - cmd.compile = True - cmd.optimize = 1 - - self.addCleanup(setattr, sys, 'dont_write_bytecode', - sys.dont_write_bytecode) - sys.dont_write_bytecode = True - cmd.byte_compile([]) + cmd.distribution.packages = ['spam'] - self.assertIn('byte-compiling is disabled', self.get_logs()[0]) + # get_inputs should return 2 elements: spam/__init__.py and + # foo*.so / foo.pyd + inputs = cmd.get_inputs() + self.assertEqual(len(inputs), 2, inputs) def test_suite(): diff --git a/distutils2/tests/test_command_register.py b/distutils2/tests/test_command_register.py index 2cc9f70..4e6c1de 100644 --- a/distutils2/tests/test_command_register.py +++ b/distutils2/tests/test_command_register.py @@ -12,6 +12,7 @@ except ImportError: DOCUTILS_SUPPORT = False from distutils2.tests import unittest, support +from distutils2.tests.support import Inputs from distutils2.command import register as register_module from distutils2.command.register import register from distutils2.errors import PackagingSetupError @@ -38,19 +39,6 @@ password:password """ -class Inputs: - """Fakes user inputs.""" - def __init__(self, *answers): - self.answers = answers - self.index = 0 - - def __call__(self, prompt=''): - try: - return self.answers[self.index] - finally: - self.index += 1 - - class FakeOpener: """Fakes a PyPI server""" def __init__(self): @@ -143,6 +131,7 @@ class RegisterTestCase(support.TempdirManager, register_module.input = _no_way cmd.show_response = True + cmd.finalized = False cmd.ensure_finalized() cmd.run() @@ -200,12 +189,10 @@ class RegisterTestCase(support.TempdirManager, @unittest.skipUnless(DOCUTILS_SUPPORT, 'needs docutils') def test_strict(self): - # testing the script option - # when on, the register command stops if - # the metadata is incomplete or if - # long_description is not reSt compliant + # testing the strict option: when on, the register command stops if the + # metadata is incomplete or if description contains bad reST - # empty metadata + # empty metadata # XXX this is not really empty.. cmd = self._get_cmd({'name': 'xxx', 'version': 'xxx'}) cmd.ensure_finalized() cmd.strict = True @@ -213,16 +200,15 @@ class RegisterTestCase(support.TempdirManager, register_module.input = inputs self.assertRaises(PackagingSetupError, cmd.run) - # metadata is OK but long_description is broken + # metadata is OK but description is broken metadata = {'home_page': 'xxx', 'author': 'xxx', 'author_email': 'éxéxé', - 'name': 'xxx', 'version': 'xxx', + 'name': 'xxx', 'version': '4.2', 'description': 'title\n==\n\ntext'} cmd = self._get_cmd(metadata) cmd.ensure_finalized() cmd.strict = True - self.assertRaises(PackagingSetupError, cmd.run) # now something that works diff --git a/distutils2/tests/test_command_sdist.py b/distutils2/tests/test_command_sdist.py index 9bf1fcf..0df3728 100644 --- a/distutils2/tests/test_command_sdist.py +++ b/distutils2/tests/test_command_sdist.py @@ -1,9 +1,6 @@ """Tests for distutils2.command.sdist.""" import os import zipfile -import logging - -from distutils2.tests.support import requires_zlib try: import grp @@ -13,17 +10,17 @@ except ImportError: UID_GID_SUPPORT = False from os.path import join -from distutils2.tests import captured_stdout -from distutils2.command.sdist import sdist -from distutils2.command.sdist import show_formats from distutils2.dist import Distribution -from distutils2.tests import unittest -from distutils2.errors import PackagingOptionError from distutils2.util import find_executable -from distutils2.tests import support +from distutils2.errors import PackagingOptionError +from distutils2.command.sdist import sdist, show_formats from distutils2._backport import tarfile from distutils2._backport.shutil import get_archive_formats +from distutils2.tests import support, unittest +from distutils2.tests import captured_stdout +from distutils2.tests.support import requires_zlib + MANIFEST = """\ # file GENERATED by distutils2, do NOT edit @@ -89,7 +86,6 @@ class SDistTestCase(support.TempdirManager, # creating VCS directories with some files in them os.mkdir(join(self.tmp_dir, 'somecode', '.svn')) - self.write_file((self.tmp_dir, 'somecode', '.svn', 'ok.py'), 'xxx') os.mkdir(join(self.tmp_dir, 'somecode', '.hg')) @@ -147,7 +143,7 @@ class SDistTestCase(support.TempdirManager, # now trying a tar then a gztar cmd.formats = ['tar', 'gztar'] - + cmd.finalized = False cmd.ensure_finalized() cmd.run() @@ -223,12 +219,14 @@ class SDistTestCase(support.TempdirManager, # testing the `check-metadata` option dist, cmd = self.get_cmd(metadata={'name': 'xxx', 'version': 'xxx'}) - # this should raise some warnings - # with the check subcommand + # this should cause the check subcommand to log two warnings: + # version is invalid, home-page and author are missing cmd.ensure_finalized() cmd.run() - warnings = self.get_logs(logging.WARN) - self.assertEqual(len(warnings), 4) + warnings = self.get_logs() + check_warnings = [msg for msg in warnings if + not msg.startswith('sdist:')] + self.assertEqual(len(check_warnings), 2, warnings) # trying with a complete set of metadata self.loghandler.flush() @@ -236,13 +234,10 @@ class SDistTestCase(support.TempdirManager, cmd.ensure_finalized() cmd.metadata_check = False cmd.run() - warnings = self.get_logs(logging.WARN) - # removing manifest generated warnings - warnings = [warn for warn in warnings if - not warn.endswith('-- skipping')] - # the remaining warnings are about the use of the default file list and - # the absence of setup.cfg + warnings = self.get_logs() self.assertEqual(len(warnings), 2) + self.assertIn('using default file list', warnings[0]) + self.assertIn("'setup.cfg' file not found", warnings[1]) def test_show_formats(self): __, stdout = captured_stdout(show_formats) @@ -254,7 +249,6 @@ class SDistTestCase(support.TempdirManager, self.assertEqual(len(output), num_formats) def test_finalize_options(self): - dist, cmd = self.get_cmd() cmd.finalize_options() @@ -274,6 +268,18 @@ class SDistTestCase(support.TempdirManager, self.assertRaises(PackagingOptionError, cmd.finalize_options) @requires_zlib + def test_template(self): + dist, cmd = self.get_cmd() + dist.extra_files = ['include yeah'] + cmd.ensure_finalized() + self.write_file((self.tmp_dir, 'yeah'), 'xxx') + cmd.run() + with open(cmd.manifest) as f: + content = f.read() + + self.assertIn('yeah', content) + + @requires_zlib @unittest.skipUnless(UID_GID_SUPPORT, "requires grp and pwd support") @unittest.skipIf(find_executable('tar') is None or find_executable('gzip') is None, @@ -291,13 +297,10 @@ class SDistTestCase(support.TempdirManager, # making sure we have the good rights archive_name = join(self.tmp_dir, 'dist', 'fake-1.0.tar.gz') - archive = tarfile.open(archive_name) - try: + with tarfile.open(archive_name) as archive: for member in archive.getmembers(): self.assertEqual(member.uid, 0) self.assertEqual(member.gid, 0) - finally: - archive.close() # building a sdist again dist, cmd = self.get_cmd() @@ -309,15 +312,12 @@ class SDistTestCase(support.TempdirManager, # making sure we have the good rights archive_name = join(self.tmp_dir, 'dist', 'fake-1.0.tar.gz') - archive = tarfile.open(archive_name) - try: + with tarfile.open(archive_name) as archive: # note that we are not testing the group ownership here # because, depending on the platforms and the container # rights (see #7408) for member in archive.getmembers(): self.assertEqual(member.uid, os.getuid()) - finally: - archive.close() @requires_zlib def test_get_file_list(self): @@ -383,18 +383,6 @@ class SDistTestCase(support.TempdirManager, self.assertEqual(manifest, ['README.manual']) @requires_zlib - def test_template(self): - dist, cmd = self.get_cmd() - dist.extra_files = ['include yeah'] - cmd.ensure_finalized() - self.write_file((self.tmp_dir, 'yeah'), 'xxx') - cmd.run() - with open(cmd.manifest) as f: - content = f.read() - - self.assertIn('yeah', content) - - @requires_zlib def test_manifest_builder(self): dist, cmd = self.get_cmd() cmd.manifest_builders = 'distutils2.tests.test_command_sdist.builder' diff --git a/distutils2/tests/test_command_test.py b/distutils2/tests/test_command_test.py index 9fb6fb9..f2dcd22 100644 --- a/distutils2/tests/test_command_test.py +++ b/distutils2/tests/test_command_test.py @@ -2,7 +2,6 @@ import os import re import sys import shutil -import logging import unittest as ut1 import distutils2.database @@ -140,7 +139,8 @@ class TestTest(TempdirManager, cmd.run() self.assertEqual(['build has run'], record) - def _test_works_with_2to3(self): + @unittest.skip('needs to be written') + def test_works_with_2to3(self): pass def test_checks_requires(self): @@ -149,7 +149,7 @@ class TestTest(TempdirManager, phony_project = 'ohno_ohno-impossible_1234-name_stop-that!' cmd.tests_require = [phony_project] cmd.ensure_finalized() - logs = self.get_logs(logging.WARNING) + logs = self.get_logs() self.assertIn(phony_project, logs[-1]) def prepare_a_module(self): diff --git a/distutils2/tests/test_command_upload.py b/distutils2/tests/test_command_upload.py index 5c58879..61c6654 100644 --- a/distutils2/tests/test_command_upload.py +++ b/distutils2/tests/test_command_upload.py @@ -129,7 +129,7 @@ class UploadTestCase(support.TempdirManager, support.EnvironRestorer, dist_files = [(command, pyversion, filename)] docs_path = os.path.join(self.tmp_dir, "build", "docs") os.makedirs(docs_path) - self.write_file(os.path.join(docs_path, "index.html"), "yellow") + self.write_file((docs_path, "index.html"), "yellow") self.write_file(self.rc, PYPIRC) # let's run it diff --git a/distutils2/tests/test_command_upload_docs.py b/distutils2/tests/test_command_upload_docs.py index cf69af9..95eec0c 100644 --- a/distutils2/tests/test_command_upload_docs.py +++ b/distutils2/tests/test_command_upload_docs.py @@ -1,6 +1,7 @@ """Tests for distutils2.command.upload_docs.""" import os import shutil +import logging import zipfile try: import _ssl @@ -70,9 +71,8 @@ class UploadDocsTestCase(support.TempdirManager, if sample_dir is None: sample_dir = self.mkdtemp() os.mkdir(os.path.join(sample_dir, "docs")) - self.write_file(os.path.join(sample_dir, "docs", "index.html"), - "Ce mortel ennui") - self.write_file(os.path.join(sample_dir, "index.html"), "Oh la la") + self.write_file((sample_dir, "docs", "index.html"), "Ce mortel ennui") + self.write_file((sample_dir, "index.html"), "Oh la la") return sample_dir def test_zip_dir(self): @@ -141,13 +141,16 @@ class UploadDocsTestCase(support.TempdirManager, self.pypi.default_response_status = '403 Forbidden' self.prepare_command() self.cmd.run() - self.assertIn('Upload failed (403): Forbidden', self.get_logs()[-1]) + errors = self.get_logs(logging.ERROR) + self.assertEqual(len(errors), 1) + self.assertIn('Upload failed (403): Forbidden', errors[0]) self.pypi.default_response_status = '301 Moved Permanently' self.pypi.default_response_headers.append( ("Location", "brand_new_location")) self.cmd.run() - self.assertIn('brand_new_location', self.get_logs()[-1]) + lastlog = self.get_logs(logging.INFO)[-1] + self.assertIn('brand_new_location', lastlog) def test_reads_pypirc_data(self): self.write_file(self.rc, PYPIRC % self.pypi.full_address) @@ -171,7 +174,7 @@ class UploadDocsTestCase(support.TempdirManager, self.prepare_command() self.cmd.show_response = True self.cmd.run() - record = self.get_logs()[-1] + record = self.get_logs(logging.INFO)[-1] self.assertTrue(record, "should report the response") self.assertIn(self.pypi.default_response_data, record) diff --git a/distutils2/tests/test_config.py b/distutils2/tests/test_config.py index 7c913ee..dc61daa 100644 --- a/distutils2/tests/test_config.py +++ b/distutils2/tests/test_config.py @@ -1,8 +1,6 @@ """Tests for distutils2.config.""" import os import sys -import logging -from io import StringIO from distutils2 import command from distutils2.dist import Distribution @@ -184,13 +182,14 @@ class FooBarBazTest: def __init__(self, dist): self.distribution = dist + self._record = [] @classmethod def get_command_name(cls): return 'foo' def run(self): - self.distribution.foo_was_here = True + self._record.append('foo has run') def nothing(self): pass @@ -210,21 +209,11 @@ class ConfigTestCase(support.TempdirManager, def setUp(self): super(ConfigTestCase, self).setUp() - self.addCleanup(setattr, sys, 'stdout', sys.stdout) - self.addCleanup(setattr, sys, 'stderr', sys.stderr) - sys.stdout = StringIO() - sys.stderr = StringIO() - - self.addCleanup(os.chdir, os.getcwd()) tempdir = self.mkdtemp() self.working_dir = os.getcwd() os.chdir(tempdir) self.tempdir = tempdir - def tearDown(self): - os.chdir(self.working_dir) - super(ConfigTestCase, self).tearDown() - def write_setup(self, kwargs=None): opts = {'description-file': 'README', 'extra-files': '', 'setup-hooks': 'distutils2.tests.test_config.version_hook'} @@ -375,15 +364,14 @@ class ConfigTestCase(support.TempdirManager, self.write_file('README', 'yeah') self.write_file('hooks.py', HOOKS_MODULE) self.get_dist() - logs = self.get_logs(logging.WARNING) - self.assertEqual(['logging_hook called'], logs) + self.assertEqual(['logging_hook called'], self.get_logs()) self.assertIn('hooks', sys.modules) def test_missing_setup_hook_warns(self): - self.write_setup({'setup-hooks': 'this.does._not.exist'}) + self.write_setup({'setup-hooks': 'does._not.exist'}) self.write_file('README', 'yeah') self.get_dist() - logs = self.get_logs(logging.WARNING) + logs = self.get_logs() self.assertEqual(1, len(logs)) self.assertIn('cannot find setup hook', logs[0]) @@ -397,7 +385,7 @@ class ConfigTestCase(support.TempdirManager, dist = self.get_dist() self.assertEqual(['haven', 'first', 'third'], dist.py_modules) - logs = self.get_logs(logging.WARNING) + logs = self.get_logs() self.assertEqual(1, len(logs)) self.assertIn('cannot find setup hook', logs[0]) @@ -493,10 +481,12 @@ class ConfigTestCase(support.TempdirManager, self.write_file((pkg, '__init__.py'), '#') # try to run the install command to see if foo is called + self.addCleanup(command._COMMANDS.__delitem__, 'foo') dist = self.get_dist() - self.assertIn('foo', command.get_command_names()) - self.assertEqual('FooBarBazTest', - dist.get_command_obj('foo').__class__.__name__) + dist.run_command('install_dist') + cmd = dist.get_command_obj('foo') + self.assertEqual(cmd.__class__.__name__, 'FooBarBazTest') + self.assertEqual(cmd._record, ['foo has run']) def test_suite(): diff --git a/distutils2/tests/test_create.py b/distutils2/tests/test_create.py index 7a0aa0d..83d2f50 100644 --- a/distutils2/tests/test_create.py +++ b/distutils2/tests/test_create.py @@ -1,16 +1,18 @@ """Tests for distutils2.create.""" import os import sys -from io import StringIO from textwrap import dedent +from distutils2 import create from distutils2.create import MainProgram, ask_yn, ask, main from distutils2._backport import sysconfig from distutils2.tests import support, unittest +from distutils2.tests.support import Inputs class CreateTestCase(support.TempdirManager, support.EnvironRestorer, + support.LoggingCatcher, unittest.TestCase): maxDiff = None @@ -18,11 +20,6 @@ class CreateTestCase(support.TempdirManager, def setUp(self): super(CreateTestCase, self).setUp() - self._stdin = sys.stdin # TODO use Inputs - self._stdout = sys.stdout - sys.stdin = StringIO() - sys.stdout = StringIO() - self._cwd = os.getcwd() self.wdir = self.mkdtemp() os.chdir(self.wdir) # patch sysconfig @@ -32,29 +29,24 @@ class CreateTestCase(support.TempdirManager, 'doc': sys.prefix + '/share/doc/pyxfoil', } def tearDown(self): - sys.stdin = self._stdin - sys.stdout = self._stdout - os.chdir(self._cwd) sysconfig.get_paths = self._old_get_paths + if hasattr(create, 'input'): + del create.input super(CreateTestCase, self).tearDown() def test_ask_yn(self): - sys.stdin.write('y\n') - sys.stdin.seek(0) + create.input = Inputs('y') self.assertEqual('y', ask_yn('is this a test')) def test_ask(self): - sys.stdin.write('a\n') - sys.stdin.write('b\n') - sys.stdin.seek(0) + create.input = Inputs('a', 'b') self.assertEqual('a', ask('is this a test')) self.assertEqual('b', ask(str(list(range(0, 70))), default='c', lengthy=True)) def test_set_multi(self): mainprogram = MainProgram() - sys.stdin.write('aaaaa\n') - sys.stdin.seek(0) + create.input = Inputs('aaaaa') mainprogram.data['author'] = [] mainprogram._set_multi('_set_multi test', 'author') self.assertEqual(['aaaaa'], mainprogram.data['author']) @@ -80,8 +72,7 @@ class CreateTestCase(support.TempdirManager, os.mkdir(os.path.join(tempdir, dir_)) for file_ in files: - path = os.path.join(tempdir, file_) - self.write_file(path, 'xxx') + self.write_file((tempdir, file_), 'xxx') mainprogram._find_files() mainprogram.data['packages'].sort() @@ -131,8 +122,7 @@ class CreateTestCase(support.TempdirManager, scripts=['my_script', 'bin/run'], ) """), encoding='utf-8') - sys.stdin.write('y\n') - sys.stdin.seek(0) + create.input = Inputs('y') main() path = os.path.join(self.wdir, 'setup.cfg') @@ -207,9 +197,7 @@ My super Death-scription barbar is now in the public domain, ho, baby! ''')) - sys.stdin.write('y\n') - sys.stdin.seek(0) - # FIXME Out of memory error. + create.input = Inputs('y') main() path = os.path.join(self.wdir, 'setup.cfg') diff --git a/distutils2/tests/test_dist.py b/distutils2/tests/test_dist.py index 22fd37b..1e9eb9c 100644 --- a/distutils2/tests/test_dist.py +++ b/distutils2/tests/test_dist.py @@ -1,33 +1,37 @@ """Tests for distutils2.dist.""" import os import sys -import logging import textwrap import distutils2.dist from distutils2.dist import Distribution -from distutils2.command import set_command from distutils2.command.cmd import Command from distutils2.errors import PackagingModuleError, PackagingOptionError from distutils2.tests import captured_stdout from distutils2.tests import support, unittest -from distutils2.tests.support import create_distribution +from distutils2.tests.support import create_distribution, use_command from distutils2.tests.support import unload class test_dist(Command): - """Sample distutils2 extension command.""" + """Custom command used for testing.""" user_options = [ - ("sample-option=", "S", "help text"), + ('sample-option=', 'S', + "help text"), ] def initialize_options(self): self.sample_option = None + self._record = [] def finalize_options(self): - pass + if self.sample_option is None: + self.sample_option = 'default value' + + def run(self): + self._record.append('test_dist has run') class DistributionTestCase(support.TempdirManager, @@ -39,6 +43,8 @@ class DistributionTestCase(support.TempdirManager, def setUp(self): super(DistributionTestCase, self).setUp() + # XXX this is ugly, we should fix the functions to accept args + # (defaulting to sys.argv) self.argv = sys.argv, sys.argv[:] del sys.argv[1:] @@ -74,7 +80,7 @@ class DistributionTestCase(support.TempdirManager, 'version': '1.2', 'home_page': 'xxxx', 'badoptname': 'xxx'}) - logs = self.get_logs(logging.WARNING) + logs = self.get_logs() self.assertEqual(len(logs), 1) self.assertIn('unknown argument', logs[0]) @@ -85,7 +91,7 @@ class DistributionTestCase(support.TempdirManager, 'version': '1.2', 'home_page': 'xxxx', 'options': {}}) - self.assertEqual([], self.get_logs(logging.WARNING)) + self.assertEqual(self.get_logs(), []) self.assertNotIn('options', dir(dist)) def test_non_empty_options(self): @@ -173,7 +179,8 @@ class DistributionTestCase(support.TempdirManager, self.write_file((temp_home, "config2.cfg"), '[test_dist]\npre-hook.b = type') - set_command('distutils2.tests.test_dist.test_dist') + use_command(self, 'distutils2.tests.test_dist.test_dist') + dist = create_distribution(config_files) cmd = dist.get_command_obj("test_dist") self.assertEqual(cmd.pre_hook, {"a": 'type', "b": 'type'}) @@ -201,7 +208,7 @@ class DistributionTestCase(support.TempdirManager, record.append('post-%s' % cmd.get_command_name()) ''')) - set_command('distutils2.tests.test_dist.test_dist') + use_command(self, 'distutils2.tests.test_dist.test_dist') d = create_distribution([config_file]) cmd = d.get_command_obj("test_dist") @@ -228,7 +235,7 @@ class DistributionTestCase(support.TempdirManager, [test_dist] pre-hook.test = nonexistent.dotted.name''')) - set_command('distutils2.tests.test_dist.test_dist') + use_command(self, 'distutils2.tests.test_dist.test_dist') d = create_distribution([config_file]) cmd = d.get_command_obj("test_dist") cmd.ensure_finalized() @@ -243,7 +250,7 @@ class DistributionTestCase(support.TempdirManager, [test_dist] pre-hook.test = distutils2.tests.test_dist.__doc__''')) - set_command('distutils2.tests.test_dist.test_dist') + use_command(self, 'distutils2.tests.test_dist.test_dist') d = create_distribution([config_file]) cmd = d.get_command_obj("test_dist") cmd.ensure_finalized() diff --git a/distutils2/tests/test_manifest.py b/distutils2/tests/test_manifest.py index 24f0510..e829fa4 100644 --- a/distutils2/tests/test_manifest.py +++ b/distutils2/tests/test_manifest.py @@ -1,8 +1,9 @@ """Tests for distutils2.manifest.""" import os -import logging +import re from io import StringIO -from distutils2.manifest import Manifest +from distutils2.errors import PackagingTemplateError +from distutils2.manifest import Manifest, _translate_pattern, _glob_to_re from distutils2.tests import unittest, support @@ -26,13 +27,11 @@ class ManifestTestCase(support.TempdirManager, support.LoggingCatcher, unittest.TestCase): - def setUp(self): - super(ManifestTestCase, self).setUp() - self.cwd = os.getcwd() + def assertNoWarnings(self): + self.assertEqual(self.get_logs(), []) - def tearDown(self): - os.chdir(self.cwd) - super(ManifestTestCase, self).tearDown() + def assertWarnings(self): + self.assertNotEqual(self.get_logs(), []) def test_manifest_reader(self): tmpdir = self.mkdtemp() @@ -43,7 +42,7 @@ class ManifestTestCase(support.TempdirManager, manifest = Manifest() manifest.read_template(MANIFEST) - warnings = self.get_logs(logging.WARNING) + warnings = self.get_logs() # the manifest should have been read and 3 warnings issued # (we didn't provide the files) self.assertEqual(3, len(warnings)) @@ -69,6 +68,193 @@ class ManifestTestCase(support.TempdirManager, manifest.read_template(content) self.assertEqual(['README', 'file1'], manifest.files) + def test_glob_to_re(self): + # simple cases + self.assertEqual(_glob_to_re('foo*'), 'foo[^/]*\\Z(?ms)') + self.assertEqual(_glob_to_re('foo?'), 'foo[^/]\\Z(?ms)') + self.assertEqual(_glob_to_re('foo??'), 'foo[^/][^/]\\Z(?ms)') + + # special cases + self.assertEqual(_glob_to_re(r'foo\\*'), r'foo\\\\[^/]*\Z(?ms)') + self.assertEqual(_glob_to_re(r'foo\\\*'), r'foo\\\\\\[^/]*\Z(?ms)') + self.assertEqual(_glob_to_re('foo????'), r'foo[^/][^/][^/][^/]\Z(?ms)') + self.assertEqual(_glob_to_re(r'foo\\??'), r'foo\\\\[^/][^/]\Z(?ms)') + + def test_remove_duplicates(self): + manifest = Manifest() + manifest.files = ['a', 'b', 'a', 'g', 'c', 'g'] + # files must be sorted beforehand + manifest.sort() + manifest.remove_duplicates() + self.assertEqual(manifest.files, ['a', 'b', 'c', 'g']) + + def test_translate_pattern(self): + # blackbox test of a private function + + # not regex + pattern = _translate_pattern('a', anchor=True, is_regex=False) + self.assertTrue(hasattr(pattern, 'search')) + + # is a regex + regex = re.compile('a') + pattern = _translate_pattern(regex, anchor=True, is_regex=True) + self.assertEqual(pattern, regex) + + # plain string flagged as regex + pattern = _translate_pattern('a', anchor=True, is_regex=True) + self.assertTrue(hasattr(pattern, 'search')) + + # glob support + pattern = _translate_pattern('*.py', anchor=True, is_regex=False) + self.assertTrue(pattern.search('filelist.py')) + + def test_exclude_pattern(self): + # return False if no match + manifest = Manifest() + self.assertFalse(manifest.exclude_pattern('*.py')) + + # return True if files match + manifest = Manifest() + manifest.files = ['a.py', 'b.py'] + self.assertTrue(manifest.exclude_pattern('*.py')) + + # test excludes + manifest = Manifest() + manifest.files = ['a.py', 'a.txt'] + manifest.exclude_pattern('*.py') + self.assertEqual(manifest.files, ['a.txt']) + + def test_include_pattern(self): + # return False if no match + manifest = Manifest() + manifest.allfiles = [] + self.assertFalse(manifest._include_pattern('*.py')) + + # return True if files match + manifest = Manifest() + manifest.allfiles = ['a.py', 'b.txt'] + self.assertTrue(manifest._include_pattern('*.py')) + + # test * matches all files + manifest = Manifest() + self.assertIsNone(manifest.allfiles) + manifest.allfiles = ['a.py', 'b.txt'] + manifest._include_pattern('*') + self.assertEqual(manifest.allfiles, ['a.py', 'b.txt']) + + def test_process_template(self): + # invalid lines + manifest = Manifest() + for action in ('include', 'exclude', 'global-include', + 'global-exclude', 'recursive-include', + 'recursive-exclude', 'graft', 'prune'): + self.assertRaises(PackagingTemplateError, + manifest._process_template_line, action) + + # implicit include + manifest = Manifest() + manifest.allfiles = ['a.py', 'b.txt', 'd/c.py'] + + manifest._process_template_line('*.py') + self.assertEqual(manifest.files, ['a.py']) + self.assertNoWarnings() + + # include + manifest = Manifest() + manifest.allfiles = ['a.py', 'b.txt', 'd/c.py'] + + manifest._process_template_line('include *.py') + self.assertEqual(manifest.files, ['a.py']) + self.assertNoWarnings() + + manifest._process_template_line('include *.rb') + self.assertEqual(manifest.files, ['a.py']) + self.assertWarnings() + + # exclude + manifest = Manifest() + manifest.files = ['a.py', 'b.txt', 'd/c.py'] + + manifest._process_template_line('exclude *.py') + self.assertEqual(manifest.files, ['b.txt', 'd/c.py']) + self.assertNoWarnings() + + manifest._process_template_line('exclude *.rb') + self.assertEqual(manifest.files, ['b.txt', 'd/c.py']) + self.assertWarnings() + + # global-include + manifest = Manifest() + manifest.allfiles = ['a.py', 'b.txt', 'd/c.py'] + + manifest._process_template_line('global-include *.py') + self.assertEqual(manifest.files, ['a.py', 'd/c.py']) + self.assertNoWarnings() + + manifest._process_template_line('global-include *.rb') + self.assertEqual(manifest.files, ['a.py', 'd/c.py']) + self.assertWarnings() + + # global-exclude + manifest = Manifest() + manifest.files = ['a.py', 'b.txt', 'd/c.py'] + + manifest._process_template_line('global-exclude *.py') + self.assertEqual(manifest.files, ['b.txt']) + self.assertNoWarnings() + + manifest._process_template_line('global-exclude *.rb') + self.assertEqual(manifest.files, ['b.txt']) + self.assertWarnings() + + # recursive-include + manifest = Manifest() + manifest.allfiles = ['a.py', 'd/b.py', 'd/c.txt', 'd/d/e.py'] + + manifest._process_template_line('recursive-include d *.py') + self.assertEqual(manifest.files, ['d/b.py', 'd/d/e.py']) + self.assertNoWarnings() + + manifest._process_template_line('recursive-include e *.py') + self.assertEqual(manifest.files, ['d/b.py', 'd/d/e.py']) + self.assertWarnings() + + # recursive-exclude + manifest = Manifest() + manifest.files = ['a.py', 'd/b.py', 'd/c.txt', 'd/d/e.py'] + + manifest._process_template_line('recursive-exclude d *.py') + self.assertEqual(manifest.files, ['a.py', 'd/c.txt']) + self.assertNoWarnings() + + manifest._process_template_line('recursive-exclude e *.py') + self.assertEqual(manifest.files, ['a.py', 'd/c.txt']) + self.assertWarnings() + + # graft + manifest = Manifest() + manifest.allfiles = ['a.py', 'd/b.py', 'd/d/e.py', 'f/f.py'] + + manifest._process_template_line('graft d') + self.assertEqual(manifest.files, ['d/b.py', 'd/d/e.py']) + self.assertNoWarnings() + + manifest._process_template_line('graft e') + self.assertEqual(manifest.files, ['d/b.py', 'd/d/e.py']) + self.assertWarnings() + + # prune + manifest = Manifest() + manifest.files = ['a.py', 'd/b.py', 'd/d/e.py', 'f/f.py'] + + manifest._process_template_line('prune d') + self.assertEqual(manifest.files, ['a.py', 'f/f.py']) + self.assertNoWarnings() + + manifest._process_template_line('prune e') + self.assertEqual(manifest.files, ['a.py', 'f/f.py']) + self.assertWarnings() + def test_suite(): return unittest.makeSuite(ManifestTestCase) diff --git a/distutils2/tests/test_metadata.py b/distutils2/tests/test_metadata.py index 82f5212..e19af0d 100644 --- a/distutils2/tests/test_metadata.py +++ b/distutils2/tests/test_metadata.py @@ -1,7 +1,6 @@ """Tests for distutils2.metadata.""" import os import sys -import logging from textwrap import dedent from io import StringIO @@ -302,7 +301,7 @@ class MetadataTestCase(LoggingCatcher, 'name': 'xxx', 'version': 'xxx', 'home_page': 'xxxx'}) - logs = self.get_logs(logging.WARNING) + logs = self.get_logs() self.assertEqual(1, len(logs)) self.assertIn('not a valid version', logs[0]) @@ -418,7 +417,7 @@ class MetadataTestCase(LoggingCatcher, # XXX check PEP and see if 3 == 3.0 metadata['Requires-Python'] = '>=2.6, <3.0' metadata['Requires-Dist'] = ['Foo (>=2.6, <3.0)'] - self.assertEqual([], self.get_logs(logging.WARNING)) + self.assertEqual(self.get_logs(), []) @unittest.skip('needs to be implemented') def test_requires_illegal(self): diff --git a/distutils2/tests/test_mixin2to3.py b/distutils2/tests/test_mixin2to3.py index 4d1584b..76e14d6 100644 --- a/distutils2/tests/test_mixin2to3.py +++ b/distutils2/tests/test_mixin2to3.py @@ -1,4 +1,3 @@ -import sys import textwrap from distutils2.tests import unittest, support @@ -9,6 +8,7 @@ class Mixin2to3TestCase(support.TempdirManager, support.LoggingCatcher, unittest.TestCase): + @support.skip_2to3_optimize def test_convert_code_only(self): # used to check if code gets converted properly. code = "print 'test'" diff --git a/distutils2/tests/test_pypi_simple.py b/distutils2/tests/test_pypi_simple.py index c587701..70c3ba3 100644 --- a/distutils2/tests/test_pypi_simple.py +++ b/distutils2/tests/test_pypi_simple.py @@ -87,7 +87,7 @@ class SimpleCrawlerTestCase(TempdirManager, try: crawler._open_url(url) except Exception as v: - if sys.version_info[:2] < (3, 3): + if sys.version_info[:2] < (3, 2, 3): wanted = 'nonnumeric port' else: wanted = 'Download error' diff --git a/distutils2/tests/test_run.py b/distutils2/tests/test_run.py index 8645f63..7433731 100644 --- a/distutils2/tests/test_run.py +++ b/distutils2/tests/test_run.py @@ -33,11 +33,9 @@ class RunTestCase(support.TempdirManager, def setUp(self): super(RunTestCase, self).setUp() - self.old_stdout = sys.stdout self.old_argv = sys.argv, sys.argv[:] def tearDown(self): - sys.stdout = self.old_stdout sys.argv = self.old_argv[0] sys.argv[:] = self.old_argv[1] super(RunTestCase, self).tearDown() @@ -67,7 +65,7 @@ class RunTestCase(support.TempdirManager, pythonpath = os.environ.get('PYTHONPATH') d2parent = os.path.dirname(os.path.dirname(__file__)) if pythonpath is not None: - pythonpath = os.pathsep.join((pythonpath, d2parent)) + pythonpath = os.pathsep.join((pythonpath, d2parent)) else: pythonpath = d2parent diff --git a/distutils2/tests/test_uninstall.py b/distutils2/tests/test_uninstall.py index fc664a3..4b66f03 100644 --- a/distutils2/tests/test_uninstall.py +++ b/distutils2/tests/test_uninstall.py @@ -1,6 +1,5 @@ """Tests for the distutils2.uninstall module.""" import os -import sys import logging import distutils2.util @@ -31,19 +30,10 @@ class UninstallTestCase(support.TempdirManager, def setUp(self): super(UninstallTestCase, self).setUp() - self.addCleanup(setattr, sys, 'stdout', sys.stdout) - self.addCleanup(setattr, sys, 'stderr', sys.stderr) - self.addCleanup(os.chdir, os.getcwd()) self.addCleanup(enable_cache) - self.root_dir = self.mkdtemp() - self.cwd = os.getcwd() + self.addCleanup(distutils2.util._path_created.clear) disable_cache() - def tearDown(self): - os.chdir(self.cwd) - distutils2.util._path_created.clear() - super(UninstallTestCase, self).tearDown() - def get_path(self, dist, name): # the dist argument must contain an install_dist command correctly # initialized with a prefix option and finalized befored this method @@ -61,8 +51,7 @@ class UninstallTestCase(support.TempdirManager, kw['pkg'] = pkg pkg_dir = os.path.join(project_dir, pkg) - os.mkdir(pkg_dir) - os.mkdir(os.path.join(pkg_dir, 'sub')) + os.makedirs(os.path.join(pkg_dir, 'sub')) self.write_file((project_dir, 'setup.cfg'), SETUP_CFG % kw) self.write_file((pkg_dir, '__init__.py'), '#') @@ -85,7 +74,7 @@ class UninstallTestCase(support.TempdirManager, dist.parse_config_files() dist.finalize_options() dist.run_command('install_dist', - {'prefix': ('command line', self.root_dir)}) + {'prefix': ('command line', self.mkdtemp())}) site_packages = self.get_path(dist, 'purelib') return dist, site_packages diff --git a/distutils2/tests/test_util.py b/distutils2/tests/test_util.py index f3432ab..0edb402 100644 --- a/distutils2/tests/test_util.py +++ b/distutils2/tests/test_util.py @@ -10,7 +10,7 @@ import subprocess from io import StringIO from distutils2.errors import ( - PackagingPlatformError, PackagingByteCompileError, PackagingFileError, + PackagingPlatformError, PackagingFileError, PackagingExecError, InstallationException) from distutils2 import util from distutils2.dist import Distribution @@ -138,15 +138,8 @@ class UtilTestCase(support.EnvironRestorer, self._uname = None os.uname = self._get_uname - # patching POpen - self.old_find_executable = util.find_executable - util.find_executable = self._find_executable - self._exes = {} - self.old_popen = subprocess.Popen - self.old_stdout = sys.stdout - self.old_stderr = sys.stderr - FakePopen.test_class = self - subprocess.Popen = FakePopen + def _get_uname(self): + return self._uname def tearDown(self): # getting back the environment @@ -161,17 +154,24 @@ class UtilTestCase(support.EnvironRestorer, os.uname = self.uname else: del os.uname - util.find_executable = self.old_find_executable - subprocess.Popen = self.old_popen - sys.old_stdout = self.old_stdout - sys.old_stderr = self.old_stderr super(UtilTestCase, self).tearDown() - def _set_uname(self, uname): - self._uname = uname + def mock_popen(self): + self.old_find_executable = util.find_executable + util.find_executable = self._find_executable + self._exes = {} + self.old_popen = subprocess.Popen + self.old_stdout = sys.stdout + self.old_stderr = sys.stderr + FakePopen.test_class = self + subprocess.Popen = FakePopen + self.addCleanup(self.unmock_popen) - def _get_uname(self): - return self._uname + def unmock_popen(self): + util.find_executable = self.old_find_executable + subprocess.Popen = self.old_popen + sys.stdout = self.old_stdout + sys.stderr = self.old_stderr def test_convert_path(self): # linux/mac @@ -283,6 +283,7 @@ class UtilTestCase(support.EnvironRestorer, return None def test_get_compiler_versions(self): + self.mock_popen() # get_versions calls distutils.spawn.find_executable on # 'gcc', 'ld' and 'dllwrap' self.assertEqual(get_compiler_versions(), (None, None, None)) @@ -323,15 +324,12 @@ class UtilTestCase(support.EnvironRestorer, res = get_compiler_versions() self.assertEqual(res[2], None) - def test_dont_write_bytecode(self): - # makes sure byte_compile raise a PackagingError - # if sys.dont_write_bytecode is True - old_dont_write_bytecode = sys.dont_write_bytecode + def test_byte_compile_under_B(self): + # make sure byte compilation works under -B (dont_write_bytecode) + self.addCleanup(setattr, sys, 'dont_write_bytecode', + sys.dont_write_bytecode) sys.dont_write_bytecode = True - try: - self.assertRaises(PackagingByteCompileError, byte_compile, []) - finally: - sys.dont_write_bytecode = old_dont_write_bytecode + byte_compile([]) def test_newer(self): self.assertRaises(PackagingFileError, util.newer, 'xxx', 'xxx') @@ -359,56 +357,66 @@ class UtilTestCase(support.EnvironRestorer, # root = self.mkdtemp() pkg1 = os.path.join(root, 'pkg1') - os.mkdir(pkg1) - self.write_file(os.path.join(pkg1, '__init__.py')) - os.mkdir(os.path.join(pkg1, 'pkg2')) - self.write_file(os.path.join(pkg1, 'pkg2', '__init__.py')) - os.mkdir(os.path.join(pkg1, 'pkg3')) - self.write_file(os.path.join(pkg1, 'pkg3', '__init__.py')) - os.mkdir(os.path.join(pkg1, 'pkg3', 'pkg6')) - self.write_file(os.path.join(pkg1, 'pkg3', 'pkg6', '__init__.py')) - os.mkdir(os.path.join(pkg1, 'pkg4')) - os.mkdir(os.path.join(pkg1, 'pkg4', 'pkg8')) - self.write_file(os.path.join(pkg1, 'pkg4', 'pkg8', '__init__.py')) - pkg5 = os.path.join(root, 'pkg5') - os.mkdir(pkg5) - self.write_file(os.path.join(pkg5, '__init__.py')) + os.makedirs(os.path.join(pkg1, 'pkg2')) + os.makedirs(os.path.join(pkg1, 'pkg3', 'pkg6')) + os.makedirs(os.path.join(pkg1, 'pkg4', 'pkg8')) + os.makedirs(os.path.join(root, 'pkg5')) + self.write_file((pkg1, '__init__.py')) + self.write_file((pkg1, 'pkg2', '__init__.py')) + self.write_file((pkg1, 'pkg3', '__init__.py')) + self.write_file((pkg1, 'pkg3', 'pkg6', '__init__.py')) + self.write_file((pkg1, 'pkg4', 'pkg8', '__init__.py')) + self.write_file((root, 'pkg5', '__init__.py')) res = find_packages([root], ['pkg1.pkg2']) - self.assertEqual(set(res), set(['pkg1', 'pkg5', 'pkg1.pkg3', - 'pkg1.pkg3.pkg6'])) + self.assertEqual(sorted(res), + ['pkg1', 'pkg1.pkg3', 'pkg1.pkg3.pkg6', 'pkg5']) def test_resolve_name(self): - self.assertIs(str, resolve_name('builtins.str')) - self.assertEqual( - UtilTestCase.__name__, - resolve_name("distutils2.tests.test_util.UtilTestCase").__name__) - self.assertEqual( - UtilTestCase.test_resolve_name.__name__, - resolve_name("distutils2.tests.test_util.UtilTestCase." - "test_resolve_name").__name__) - - self.assertRaises(ImportError, resolve_name, - "distutils2.tests.test_util.UtilTestCaseNot") - self.assertRaises(ImportError, resolve_name, - "distutils2.tests.test_util.UtilTestCase." - "nonexistent_attribute") - - def test_import_nested_first_time(self): - tmp_dir = self.mkdtemp() - os.makedirs(os.path.join(tmp_dir, 'a', 'b')) - self.write_file(os.path.join(tmp_dir, 'a', '__init__.py'), '') - self.write_file(os.path.join(tmp_dir, 'a', 'b', '__init__.py'), '') - self.write_file(os.path.join(tmp_dir, 'a', 'b', 'c.py'), - 'class Foo: pass') - - try: - sys.path.append(tmp_dir) - resolve_name("a.b.c.Foo") - # assert nothing raised - finally: - sys.path.remove(tmp_dir) - + # test raw module name + tmpdir = self.mkdtemp() + sys.path.append(tmpdir) + self.addCleanup(sys.path.remove, tmpdir) + self.write_file((tmpdir, 'hello.py'), '') + + os.makedirs(os.path.join(tmpdir, 'a', 'b')) + self.write_file((tmpdir, 'a', '__init__.py'), '') + self.write_file((tmpdir, 'a', 'b', '__init__.py'), '') + self.write_file((tmpdir, 'a', 'b', 'c.py'), 'class Foo: pass') + self.write_file((tmpdir, 'a', 'b', 'd.py'), textwrap.dedent("""\ + class FooBar: + class Bar: + def baz(self): + pass + """)) + + # check Python, C and built-in module + self.assertEqual(resolve_name('hello').__name__, 'hello') + self.assertEqual(resolve_name('_csv').__name__, '_csv') + self.assertEqual(resolve_name('sys').__name__, 'sys') + + # test module.attr + self.assertIs(resolve_name('builtins.str'), str) + self.assertIsNone(resolve_name('hello.__doc__')) + self.assertEqual(resolve_name('a.b.c.Foo').__name__, 'Foo') + self.assertEqual(resolve_name('a.b.d.FooBar.Bar.baz').__name__, 'baz') + + # error if module not found + self.assertRaises(ImportError, resolve_name, 'nonexistent') + self.assertRaises(ImportError, resolve_name, 'non.existent') + self.assertRaises(ImportError, resolve_name, 'a.no') + self.assertRaises(ImportError, resolve_name, 'a.b.no') + self.assertRaises(ImportError, resolve_name, 'a.b.no.no') + self.assertRaises(ImportError, resolve_name, 'inva-lid') + + # looking up built-in names is not supported + self.assertRaises(ImportError, resolve_name, 'str') + + # error if module found but not attr + self.assertRaises(ImportError, resolve_name, 'a.b.Spam') + self.assertRaises(ImportError, resolve_name, 'a.b.c.Spam') + + @support.skip_2to3_optimize def test_run_2to3_on_code(self): content = "print 'test'" converted_content = "print('test')" @@ -422,6 +430,7 @@ class UtilTestCase(support.EnvironRestorer, file_handle.close() self.assertEqual(new_content, converted_content) + @support.skip_2to3_optimize def test_run_2to3_on_doctests(self): # to check if text files containing doctests only get converted. content = ">>> print 'test'\ntest\n" @@ -439,8 +448,6 @@ class UtilTestCase(support.EnvironRestorer, @unittest.skipUnless(os.name in ('nt', 'posix'), 'runs only under posix or nt') def test_spawn(self): - # no patching of Popen here - subprocess.Popen = self.old_popen tmpdir = self.mkdtemp() # creating something executable @@ -538,8 +545,6 @@ class UtilTestCase(support.EnvironRestorer, self.assertEqual(args['py_modules'], dist.py_modules) def test_generate_setup_py(self): - # undo subprocess.Popen monkey-patching before using assert_python_* - subprocess.Popen = self.old_popen os.chdir(self.mkdtemp()) self.write_file('setup.cfg', textwrap.dedent("""\ [metadata] @@ -599,14 +604,6 @@ class GlobTestCaseBase(support.TempdirManager, class GlobTestCase(GlobTestCaseBase): - def setUp(self): - super(GlobTestCase, self).setUp() - self.cwd = os.getcwd() - - def tearDown(self): - os.chdir(self.cwd) - super(GlobTestCase, self).tearDown() - def assertGlobMatch(self, glob, spec): tempdir = self.build_files_tree(spec) expected = self.clean_tree(spec) diff --git a/distutils2/tests/test_version.py b/distutils2/tests/test_version.py index 1928bbd..ab4ab58 100644 --- a/distutils2/tests/test_version.py +++ b/distutils2/tests/test_version.py @@ -1,6 +1,5 @@ """Tests for distutils2.version.""" import doctest -import os from distutils2.version import NormalizedVersion as V from distutils2.version import HugeMajorVersionNumError, IrrationalVersionError @@ -46,7 +45,6 @@ class VersionTestCase(unittest.TestCase): def test_from_parts(self): for v, s in self.versions: - parts = v.parts v2 = V.from_parts(*v.parts) self.assertEqual(v, v2) self.assertEqual(str(v), str(v2)) @@ -192,7 +190,7 @@ class VersionTestCase(unittest.TestCase): 'Hey (>=2.5,<2.7)') for predicate in predicates: - v = VersionPredicate(predicate) + VersionPredicate(predicate) self.assertTrue(VersionPredicate('Hey (>=2.5,<2.7)').match('2.6')) self.assertTrue(VersionPredicate('Ho').match('2.6')) diff --git a/distutils2/util.py b/distutils2/util.py index a5111b2..0f723a2 100644 --- a/distutils2/util.py +++ b/distutils2/util.py @@ -18,9 +18,10 @@ from inspect import getsource from configparser import RawConfigParser from distutils2 import logger +from distutils2.compat import cache_from_source from distutils2.errors import (PackagingPlatformError, PackagingFileError, - PackagingByteCompileError, PackagingExecError, - InstallationException, PackagingInternalError) + PackagingExecError, InstallationException, + PackagingInternalError) from distutils2._backport import sysconfig __all__ = [ @@ -296,7 +297,7 @@ def strtobool(val): def byte_compile(py_files, optimize=0, force=False, prefix=None, - base_dir=None, verbose=0, dry_run=False, direct=None): + base_dir=None, dry_run=False, direct=None): """Byte-compile a collection of Python source files to either .pyc or .pyo files in the same directory (Python 3.1) or in a __pycache__ subdirectory (3.2 and newer). @@ -306,6 +307,9 @@ def byte_compile(py_files, optimize=0, force=False, prefix=None, 0 - don't optimize (generate .pyc) 1 - normal optimization (like "python -O") 2 - extra optimization (like "python -OO") + This function is independent from the running Python's -O or -B options; + it is fully controlled by the parameters passed in. + If 'force' is true, all files are recompiled regardless of timestamps. @@ -327,10 +331,7 @@ def byte_compile(py_files, optimize=0, force=False, prefix=None, generated in indirect mode; unless you know what you're doing, leave it set to None. """ - # nothing is done if sys.dont_write_bytecode is True - # FIXME this should not raise an error - if sys.dont_write_bytecode: - raise PackagingByteCompileError('byte-compiling is disabled.') + # FIXME use compileall + remove direct/indirect shenanigans # First, if the caller didn't force us into direct or indirect mode, # figure out which mode we should be in. We take a conservative @@ -384,15 +385,11 @@ files = [ script.write(""" byte_compile(files, optimize=%r, force=%r, prefix=%r, base_dir=%r, - verbose=%r, dry_run=False, + dry_run=False, direct=True) -""" % (optimize, force, prefix, base_dir, verbose)) +""" % (optimize, force, prefix, base_dir)) cmd = [sys.executable, script_name] - if optimize == 1: - cmd.insert(1, "-O") - elif optimize == 2: - cmd.insert(1, "-OO") env = os.environ.copy() env['PYTHONPATH'] = os.path.pathsep.join(sys.path) @@ -418,17 +415,12 @@ byte_compile(files, optimize=%r, force=%r, # Terminology from the py_compile module: # cfile - byte-compiled file # dfile - purported source filename (same as 'file' by default) - if sys.version_info[1] == 1: - cfile = file + (__debug__ and "c" or "o") - else: - # comply with PEP 3147 in 3.2+ - if optimize >= 0: - cfile = imp.cache_from_source(file, - debug_override=not optimize) - else: - cfile = imp.cache_from_source(file) - + # The second argument to cache_from_source forces the extension to + # be .pyc (if true) or .pyo (if false); without it, the extension + # would depend on the calling Python's -O option + cfile = cache_from_source(file, not optimize) dfile = file + if prefix: if file[:len(prefix)] != prefix: raise ValueError("invalid prefix: filename %r doesn't " @@ -638,22 +630,35 @@ def find_packages(paths=(os.curdir,), exclude=()): def resolve_name(name): """Resolve a name like ``module.object`` to an object and return it. - Raise ImportError if the module or name is not found. + This functions supports packages and attributes without depth limitation: + ``package.package.module.class.class.function.attr`` is valid input. + However, looking up builtins is not directly supported: use + ``builtins.name``. + + Raises ImportError if importing the module fails or if one requested + attribute is not found. """ + if '.' not in name: + # shortcut + __import__(name) + return sys.modules[name] + + # FIXME clean up this code! parts = name.split('.') cursor = len(parts) module_name = parts[:cursor] + ret = '' while cursor > 0: try: ret = __import__('.'.join(module_name)) break except ImportError: - if cursor == 0: - raise cursor -= 1 module_name = parts[:cursor] - ret = '' + + if ret == '': + raise ImportError(parts[0]) for part in parts[1:]: try: @@ -1331,6 +1336,8 @@ def get_install_method(path): def copy_tree(src, dst, preserve_mode=True, preserve_times=True, preserve_symlinks=False, update=False, verbose=True, dry_run=False): + # FIXME use of this function is why we get spurious logging message on + # stdout when tests run; kill and replace by shutil! from distutils.file_util import copy_file if not dry_run and not os.path.isdir(src): @@ -1444,8 +1451,7 @@ def encode_multipart(fields, files, boundary=None): Returns (content_type: bytes, body: bytes) ready for http.client.HTTP. """ - # Taken from - # http://code.activestate.com/recipes/146306-http-client-to-post-using-multipartform-data/ + # Taken from http://code.activestate.com/recipes/146306 if boundary is None: boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' |