[Python-checkins] distutils2 (merge default -> python3): Ye olde merge.

eric.araujo python-checkins at python.org
Mon Nov 14 15:24:08 CET 2011


http://hg.python.org/distutils2/rev/1c4fe27018fd
changeset:   1248:1c4fe27018fd
branch:      python3
parent:      1230:2d469ccfe30e
parent:      1246:2657c291ec81
user:        Éric Araujo <merwok at netwok.org>
date:        Sat Nov 12 07:33:45 2011 +0100
summary:
  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.

files:
  CHANGES.txt                                       |    9 +
  CONTRIBUTORS.txt                                  |    1 +
  distutils2/command/bdist.py                       |    2 +-
  distutils2/command/bdist_dumb.py                  |    4 +-
  distutils2/command/bdist_msi.py                   |    8 +-
  distutils2/command/bdist_wininst.py               |    6 +-
  distutils2/command/build.py                       |    2 +-
  distutils2/command/build_py.py                    |   77 +--
  distutils2/command/cmd.py                         |   24 +-
  distutils2/command/install_dist.py                |    4 +-
  distutils2/command/install_distinfo.py            |   25 +-
  distutils2/command/install_lib.py                 |   72 +--
  distutils2/command/register.py                    |    2 +-
  distutils2/command/test.py                        |    2 +-
  distutils2/compat.py                              |   17 +
  distutils2/compiler/bcppcompiler.py               |    2 +-
  distutils2/compiler/cygwinccompiler.py            |   25 +-
  distutils2/compiler/msvc9compiler.py              |    2 +-
  distutils2/compiler/msvccompiler.py               |    2 +-
  distutils2/create.py                              |   34 +-
  distutils2/depgraph.py                            |    5 +-
  distutils2/dist.py                                |   25 +-
  distutils2/errors.py                              |    4 -
  distutils2/install.py                             |    9 -
  distutils2/manifest.py                            |    4 +-
  distutils2/metadata.py                            |   12 +-
  distutils2/run.py                                 |    5 +-
  distutils2/tests/__main__.py                      |    1 -
  distutils2/tests/pypi_server.py                   |   12 +-
  distutils2/tests/support.py                       |   83 ++-
  distutils2/tests/test_command_bdist_dumb.py       |   23 +-
  distutils2/tests/test_command_build_ext.py        |   33 +-
  distutils2/tests/test_command_build_py.py         |   94 ++-
  distutils2/tests/test_command_check.py            |   45 +-
  distutils2/tests/test_command_clean.py            |    9 +-
  distutils2/tests/test_command_cmd.py              |    3 +-
  distutils2/tests/test_command_install_data.py     |   12 +-
  distutils2/tests/test_command_install_dist.py     |   25 +-
  distutils2/tests/test_command_install_distinfo.py |   11 +-
  distutils2/tests/test_command_install_lib.py      |   72 +-
  distutils2/tests/test_command_register.py         |   28 +-
  distutils2/tests/test_command_sdist.py            |   72 +--
  distutils2/tests/test_command_test.py             |    6 +-
  distutils2/tests/test_command_upload.py           |    2 +-
  distutils2/tests/test_command_upload_docs.py      |   15 +-
  distutils2/tests/test_config.py                   |   32 +-
  distutils2/tests/test_create.py                   |   34 +-
  distutils2/tests/test_dist.py                     |   31 +-
  distutils2/tests/test_manifest.py                 |  204 +++++++++-
  distutils2/tests/test_metadata.py                 |    5 +-
  distutils2/tests/test_mixin2to3.py                |    2 +-
  distutils2/tests/test_pypi_simple.py              |    2 +-
  distutils2/tests/test_run.py                      |    4 +-
  distutils2/tests/test_uninstall.py                |   17 +-
  distutils2/tests/test_util.py                     |  159 +++---
  distutils2/tests/test_version.py                  |    4 +-
  distutils2/util.py                                |   64 +-
  57 files changed, 818 insertions(+), 670 deletions(-)


diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -148,6 +148,15 @@
 - #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
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -46,6 +46,7 @@
 - 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
--- a/distutils2/command/bdist.py
+++ b/distutils2/command/bdist.py
@@ -126,7 +126,7 @@
         # 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
--- a/distutils2/command/bdist_dumb.py
+++ b/distutils2/command/bdist_dumb.py
@@ -80,8 +80,8 @@
         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
--- a/distutils2/command/bdist_msi.py
+++ b/distutils2/command/bdist_msi.py
@@ -35,7 +35,7 @@
     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 @@
         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
--- 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 @@
         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
--- a/distutils2/command/build.py
+++ b/distutils2/command/build.py
@@ -41,7 +41,7 @@
         ('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
--- 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.util import convert_path
+from distutils2.compat import Mixin2to3, cache_from_source
+from distutils2.errors import PackagingOptionError, PackagingFileError
 from distutils2.command.cmd import Command
-from distutils2.errors import PackagingOptionError, PackagingFileError
-from distutils2.util import convert_path
-from distutils2.compat import Mixin2to3
 
 # 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 @@
         ('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 @@
             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 @@
             # 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 @@
     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 @@
     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 @@
                 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 @@
         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 @@
             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 @@
 
     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 @@
                 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 @@
             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 @@
     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 @@
 
     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 @@
             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
--- a/distutils2/command/cmd.py
+++ b/distutils2/command/cmd.py
@@ -10,7 +10,7 @@
 
 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 @@
         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 @@
         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 @@
         # 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
--- a/distutils2/command/install_dist.py
+++ b/distutils2/command/install_dist.py
@@ -55,9 +55,7 @@
         ('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
--- a/distutils2/command/install_distinfo.py
+++ b/distutils2/command/install_distinfo.py
@@ -16,8 +16,8 @@
     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 @@
     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 @@
         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 @@
 
         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 @@
         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 @@
         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 @@
                 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
--- 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 @@
 
     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 @@
         ]
 
     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 @@
         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 @@
         # 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 @@
             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 @@
             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
--- a/distutils2/command/register.py
+++ b/distutils2/command/register.py
@@ -178,7 +178,7 @@
                         '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
--- a/distutils2/command/test.py
+++ b/distutils2/command/test.py
@@ -56,7 +56,7 @@
         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
--- a/distutils2/compat.py
+++ b/distutils2/compat.py
@@ -68,3 +68,20 @@
     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
--- a/distutils2/compiler/bcppcompiler.py
+++ b/distutils2/compiler/bcppcompiler.py
@@ -48,7 +48,7 @@
 
 
     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
--- a/distutils2/compiler/cygwinccompiler.py
+++ b/distutils2/compiler/cygwinccompiler.py
@@ -93,8 +93,7 @@
     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 @@
         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 @@
             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 @@
     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
--- a/distutils2/compiler/msvc9compiler.py
+++ b/distutils2/compiler/msvc9compiler.py
@@ -310,7 +310,7 @@
     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
--- a/distutils2/compiler/msvccompiler.py
+++ b/distutils2/compiler/msvccompiler.py
@@ -237,7 +237,7 @@
     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
--- a/distutils2/create.py
+++ b/distutils2/create.py
@@ -29,6 +29,7 @@
 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 @@
         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 @@
     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 @@
     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 @@
             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 @@
                       ('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 @@
                         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 @@
             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 @@
                     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 @@
     # program.write_setup_script()
     # distutils2.util.cfg_to_args()
     program()
-
-
-if __name__ == '__main__':
-    main()
diff --git a/distutils2/depgraph.py b/distutils2/depgraph.py
--- a/distutils2/depgraph.py
+++ b/distutils2/depgraph.py
@@ -224,6 +224,7 @@
 
 
 def main():
+    # XXX move to run._graph
     from distutils2.database import get_distributions
     tempout = StringIO()
     try:
@@ -267,7 +268,3 @@
     else:
         print('Supported option: -d [filename]')
         sys.exit(1)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/distutils2/dist.py b/distutils2/dist.py
--- a/distutils2/dist.py
+++ b/distutils2/dist.py
@@ -5,9 +5,10 @@
 
 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 @@
         ('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 @@
             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 @@
             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 @@
         '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 @@
 
         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 @@
             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
--- a/distutils2/errors.py
+++ b/distutils2/errors.py
@@ -72,10 +72,6 @@
     """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
--- a/distutils2/install.py
+++ b/distutils2/install.py
@@ -528,12 +528,3 @@
             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
--- a/distutils2/manifest.py
+++ b/distutils2/manifest.py
@@ -147,7 +147,9 @@
 
     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
--- a/distutils2/metadata.py
+++ b/distutils2/metadata.py
@@ -28,8 +28,9 @@
         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 @@
 
 _FILESAFE = re.compile('[^A-Za-z0-9.]+')
 
+
 class Metadata:
     """The metadata of a release.
 
@@ -227,10 +229,8 @@
 
     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
--- a/distutils2/run.py
+++ b/distutils2/run.py
@@ -9,6 +9,7 @@
 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 @@
     ('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 @@
             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
--- 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
--- a/distutils2/tests/pypi_server.py
+++ b/distutils2/tests/pypi_server.py
@@ -33,7 +33,6 @@
 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 @@
         """
         # 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 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 @@
             '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
--- a/distutils2/tests/support.py
+++ b/distutils2/tests/support.py
@@ -36,7 +36,6 @@
 import re
 import sys
 import errno
-import codecs
 import shutil
 import logging
 import logging.handlers
@@ -49,6 +48,9 @@
     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 @@
     # 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 @@
     # 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 @@
     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 @@
         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.
+
+        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'").
 
         Returns a list.  Automatically flushes the loghandler after being
-        called.
-
-        Example: self.get_logs(logging.WARN, logging.DEBUG).
+        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 @@
     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 @@
         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 @@
     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 @@
         '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
--- 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 @@
 
         # 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
--- 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.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 @@
         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 @@
             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 @@
         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
--- a/distutils2/tests/test_command_build_py.py
+++ b/distutils2/tests/test_command_build_py.py
@@ -55,30 +55,20 @@
         # 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 @@
         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()
+            dist.run_commands()
+        except PackagingFileError:
+            self.fail("failed package_data test when package_dir is ''")
 
-            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
+    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
--- 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 @@
         # 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 @@
                     '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 @@
         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 @@
                     '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 @@
                     '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 @@
 
         # 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 @@
         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 @@
         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 @@
         }
         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
--- a/distutils2/tests/test_command_clean.py
+++ b/distutils2/tests/test_command_clean.py
@@ -5,7 +5,8 @@
 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 @@
             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 @@
                              '%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
--- 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 @@
 
         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
--- a/distutils2/tests/test_command_install_data.py
+++ b/distutils2/tests/test_command_install_data.py
@@ -62,6 +62,7 @@
 
         # let's try with warn_dir one
         cmd.warn_dir = True
+        cmd.finalized = False
         cmd.ensure_finalized()
         cmd.run()
 
@@ -80,6 +81,7 @@
 
         cmd.data_files = {one: '{inst}/one', two: '{inst2}/two',
                           three: '{inst3}/three'}
+        cmd.finalized = False
         cmd.ensure_finalized()
         cmd.run()
 
@@ -125,22 +127,16 @@
 
         # 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
--- 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 @@
         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 @@
     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 @@
         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
--- a/distutils2/tests/test_command_install_distinfo.py
+++ b/distutils2/tests/test_command_install_distinfo.py
@@ -49,7 +49,7 @@
         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 @@
                         ['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 @@
         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 @@
         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 @@
         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 @@
         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
--- a/distutils2/tests/test_command_install_lib.py
+++ b/distutils2/tests/test_command_install_lib.py
@@ -17,7 +17,7 @@
     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 @@
         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]
+        cmd.distribution.packages = ['spam']
 
-        # 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([])
-
-        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
--- a/distutils2/tests/test_command_register.py
+++ b/distutils2/tests/test_command_register.py
@@ -12,6 +12,7 @@
     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 @@
 """
 
 
-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 @@
 
         register_module.input = _no_way
         cmd.show_response = True
+        cmd.finalized = False
         cmd.ensure_finalized()
         cmd.run()
 
@@ -200,12 +189,10 @@
 
     @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 @@
         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
--- 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 @@
     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.util import find_executable
 from distutils2.errors import PackagingOptionError
-from distutils2.util import find_executable
-from distutils2.tests import support
+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 @@
 
         # 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 @@
 
         # now trying a tar then a gztar
         cmd.formats = ['tar', 'gztar']
-
+        cmd.finalized = False
         cmd.ensure_finalized()
         cmd.run()
 
@@ -223,12 +219,14 @@
         # 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 @@
         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 @@
         self.assertEqual(len(output), num_formats)
 
     def test_finalize_options(self):
-
         dist, cmd = self.get_cmd()
         cmd.finalize_options()
 
@@ -274,6 +268,18 @@
         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 @@
 
         # 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 @@
 
         # 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 @@
         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
--- a/distutils2/tests/test_command_test.py
+++ b/distutils2/tests/test_command_test.py
@@ -2,7 +2,6 @@
 import re
 import sys
 import shutil
-import logging
 import unittest as ut1
 import distutils2.database
 
@@ -140,7 +139,8 @@
         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 @@
         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
--- a/distutils2/tests/test_command_upload.py
+++ b/distutils2/tests/test_command_upload.py
@@ -129,7 +129,7 @@
         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
--- 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 @@
         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 @@
         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 @@
         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
--- 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 @@
 
     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 @@
 
     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 @@
         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 @@
         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 @@
             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
--- 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 @@
 
     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 @@
             '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 @@
             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 @@
               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 @@
 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
--- 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 @@
 
     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 @@
                             '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 @@
                                    '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 @@
         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 @@
                 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 @@
             [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 @@
             [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
--- 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 @@
                        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 @@
         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 @@
         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
--- 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 @@
                           '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 @@
         # 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
--- 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 @@
                         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
--- a/distutils2/tests/test_pypi_simple.py
+++ b/distutils2/tests/test_pypi_simple.py
@@ -87,7 +87,7 @@
         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
--- a/distutils2/tests/test_run.py
+++ b/distutils2/tests/test_run.py
@@ -33,11 +33,9 @@
 
     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 @@
         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
--- 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 @@
 
     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 @@
         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 @@
         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
--- a/distutils2/tests/test_util.py
+++ b/distutils2/tests/test_util.py
@@ -10,7 +10,7 @@
 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 @@
             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 @@
             os.uname = self.uname
         else:
             del os.uname
+        super(UtilTestCase, self).tearDown()
+
+    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 unmock_popen(self):
         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 _get_uname(self):
-        return self._uname
+        sys.stdout = self.old_stdout
+        sys.stderr = self.old_stderr
 
     def test_convert_path(self):
         # linux/mac
@@ -283,6 +283,7 @@
         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 @@
         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 @@
         #
         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__)
+        # test raw module name
+        tmpdir = self.mkdtemp()
+        sys.path.append(tmpdir)
+        self.addCleanup(sys.path.remove, tmpdir)
+        self.write_file((tmpdir, 'hello.py'), '')
 
-        self.assertRaises(ImportError, resolve_name,
-                          "distutils2.tests.test_util.UtilTestCaseNot")
-        self.assertRaises(ImportError, resolve_name,
-                          "distutils2.tests.test_util.UtilTestCase."
-                          "nonexistent_attribute")
+        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
+                            """))
 
-    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')
+        # 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')
 
-        try:
-            sys.path.append(tmp_dir)
-            resolve_name("a.b.c.Foo")
-            # assert nothing raised
-        finally:
-            sys.path.remove(tmp_dir)
+        # 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 @@
         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 @@
     @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 @@
         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 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
--- 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 @@
     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 @@
                       '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
--- a/distutils2/util.py
+++ b/distutils2/util.py
@@ -18,9 +18,10 @@
 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 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 @@
       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 @@
     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 @@
                 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 @@
             # 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
 
-            dfile = file
             if prefix:
                 if file[:len(prefix)] != prefix:
                     raise ValueError("invalid prefix: filename %r doesn't "
@@ -638,22 +630,35 @@
 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 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 @@
 
     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'

-- 
Repository URL: http://hg.python.org/distutils2


More information about the Python-checkins mailing list