Branch merge
diff --git a/Lib/distutils/tests/support.py b/Lib/distutils/tests/support.py
index e258d2e..0e33827 100644
--- a/Lib/distutils/tests/support.py
+++ b/Lib/distutils/tests/support.py
@@ -2,12 +2,15 @@
 import os
 import shutil
 import tempfile
+import unittest
+import sysconfig
 from copy import deepcopy
 
 from distutils import log
 from distutils.log import DEBUG, INFO, WARN, ERROR, FATAL
 from distutils.core import Distribution
 
+
 class LoggingSilencer(object):
 
     def setUp(self):
@@ -41,6 +44,7 @@
     def clear_logs(self):
         self.logs = []
 
+
 class TempdirManager(object):
     """Mix-in class that handles temporary directories for test cases.
 
@@ -97,6 +101,7 @@
 
         return pkg_dir, dist
 
+
 class DummyCommand:
     """Class to store options for retrieval via set_undefined_options()."""
 
@@ -107,6 +112,7 @@
     def ensure_finalized(self):
         pass
 
+
 class EnvironGuard(object):
 
     def setUp(self):
@@ -123,3 +129,39 @@
                 del os.environ[key]
 
         super(EnvironGuard, self).tearDown()
+
+
+def copy_xxmodule_c(directory):
+    """Helper for tests that need the xxmodule.c source file.
+
+    Example use:
+
+            def test_compile(self):
+                copy_xxmodule_c(self.tmpdir)
+                self.assertIn('xxmodule.c', os.listdir(self.tmpdir)
+
+    If the source file can be found, it will be copied to *directory*.  If not,
+    the test will be skipped.  Errors during copy are not caught.
+    """
+    filename = _get_xxmodule_path()
+    if filename is None:
+        raise unittest.SkipTest('cannot find xxmodule.c (test must run in '
+                                'the python build dir)')
+    shutil.copy(filename, directory)
+
+
+def _get_xxmodule_path():
+    srcdir = sysconfig.get_config_var('srcdir')
+    candidates = [
+        # use installed copy if available
+        os.path.join(os.path.dirname(__file__), 'xxmodule.c'),
+        # otherwise try using copy from build directory
+        os.path.join(srcdir, 'Modules', 'xxmodule.c'),
+        # srcdir mysteriously can be $srcdir/Lib/distutils/tests when
+        # this file is run from its parent directory, so walk up the
+        # tree to find the real srcdir
+        os.path.join(srcdir, '..', '..', '..', 'Modules', 'xxmodule.c'),
+    ]
+    for path in candidates:
+        if os.path.exists(path):
+            return path
diff --git a/Lib/distutils/tests/test_build_ext.py b/Lib/distutils/tests/test_build_ext.py
index 0ce7f0f..de53afb 100644
--- a/Lib/distutils/tests/test_build_ext.py
+++ b/Lib/distutils/tests/test_build_ext.py
@@ -1,14 +1,13 @@
 import sys
 import os
-import shutil
 from io import StringIO
 import textwrap
 
 from distutils.core import Distribution
 from distutils.command.build_ext import build_ext
 from distutils import sysconfig
-from distutils.tests.support import TempdirManager
-from distutils.tests.support import LoggingSilencer
+from distutils.tests.support import (TempdirManager, LoggingSilencer,
+                                     copy_xxmodule_c)
 from distutils.extension import Extension
 from distutils.errors import (
     CompileError, DistutilsPlatformError, DistutilsSetupError,
@@ -16,20 +15,11 @@
 
 import unittest
 from test import support
-from test.support import run_unittest
 
 # http://bugs.python.org/issue4373
 # Don't load the xx module more than once.
 ALREADY_TESTED = False
 
-def _get_source_filename():
-    # use installed copy if available
-    tests_f = os.path.join(os.path.dirname(__file__), 'xxmodule.c')
-    if os.path.exists(tests_f):
-        return tests_f
-    # otherwise try using copy from build directory
-    srcdir = sysconfig.get_config_var('srcdir')
-    return os.path.join(srcdir, 'Modules', 'xxmodule.c')
 
 class BuildExtTestCase(TempdirManager,
                        LoggingSilencer,
@@ -41,9 +31,6 @@
         self.tmp_dir = self.mkdtemp()
         self.sys_path = sys.path, sys.path[:]
         sys.path.append(self.tmp_dir)
-        filename = _get_source_filename()
-        if os.path.exists(filename):
-            shutil.copy(filename, self.tmp_dir)
         if sys.version > "2.6":
             import site
             self.old_user_base = site.USER_BASE
@@ -72,9 +59,8 @@
 
     def test_build_ext(self):
         global ALREADY_TESTED
+        copy_xxmodule_c(self.tmp_dir)
         xx_c = os.path.join(self.tmp_dir, 'xxmodule.c')
-        if not os.path.exists(xx_c):
-            return
         xx_ext = Extension('xx', [xx_c])
         dist = Distribution({'name': 'xx', 'ext_modules': [xx_ext]})
         dist.package_dir = self.tmp_dir
@@ -518,13 +504,7 @@
 
 
 def test_suite():
-    src = _get_source_filename()
-    if not os.path.exists(src):
-        if support.verbose:
-            print('test_build_ext: Cannot find source code (test'
-                  ' must run in python build dir)')
-        return unittest.TestSuite()
-    else: return unittest.makeSuite(BuildExtTestCase)
+    return unittest.makeSuite(BuildExtTestCase)
 
 if __name__ == '__main__':
     support.run_unittest(test_suite())
diff --git a/Lib/distutils/tests/test_install.py b/Lib/distutils/tests/test_install.py
index ed69b0c..2133fa7 100644
--- a/Lib/distutils/tests/test_install.py
+++ b/Lib/distutils/tests/test_install.py
@@ -1,18 +1,20 @@
 """Tests for distutils.command.install."""
 
 import os
-import os.path
 import sys
 import unittest
 import site
 
 from test.support import captured_stdout, run_unittest
 
+from distutils import sysconfig
 from distutils.command.install import install
 from distutils.command import install as install_module
+from distutils.command.build_ext import build_ext
 from distutils.command.install import INSTALL_SCHEMES
 from distutils.core import Distribution
 from distutils.errors import DistutilsOptionError
+from distutils.extension import Extension
 
 from distutils.tests import support
 
@@ -167,33 +169,66 @@
         self.assertRaises(DistutilsOptionError, cmd.finalize_options)
 
     def test_record(self):
-
         install_dir = self.mkdtemp()
-        pkgdir, dist = self.create_dist()
+        project_dir, dist = self.create_dist(scripts=['hello'])
+        self.addCleanup(os.chdir, os.getcwd())
+        os.chdir(project_dir)
+        self.write_file('hello', "print('o hai')")
 
-        dist = Distribution()
         cmd = install(dist)
         dist.command_obj['install'] = cmd
         cmd.root = install_dir
-        cmd.record = os.path.join(pkgdir, 'RECORD')
+        cmd.record = os.path.join(project_dir, 'RECORD')
         cmd.ensure_finalized()
-
         cmd.run()
 
-        # let's check the RECORD file was created with one
-        # line (the egg info file)
         f = open(cmd.record)
         try:
-            self.assertEqual(len(f.readlines()), 1)
+            content = f.read()
         finally:
             f.close()
 
+        found = [os.path.basename(line) for line in content.splitlines()]
+        expected = ['hello',
+                    'UNKNOWN-0.0.0-py%s.%s.egg-info' % sys.version_info[:2]]
+        self.assertEqual(found, expected)
+
+    def test_record_extensions(self):
+        install_dir = self.mkdtemp()
+        project_dir, dist = self.create_dist(ext_modules=[
+            Extension('xx', ['xxmodule.c'])])
+        self.addCleanup(os.chdir, os.getcwd())
+        os.chdir(project_dir)
+        support.copy_xxmodule_c(project_dir)
+
+        buildcmd = build_ext(dist)
+        buildcmd.ensure_finalized()
+        buildcmd.run()
+
+        cmd = install(dist)
+        dist.command_obj['install'] = cmd
+        cmd.root = install_dir
+        cmd.record = os.path.join(project_dir, 'RECORD')
+        cmd.ensure_finalized()
+        cmd.run()
+
+        f = open(cmd.record)
+        try:
+            content = f.read()
+        finally:
+            f.close()
+
+        found = [os.path.basename(line) for line in content.splitlines()]
+        expected = ['xx%s' % sysconfig.get_config_var('SO'),
+                    'UNKNOWN-0.0.0-py%s.%s.egg-info' % sys.version_info[:2]]
+        self.assertEqual(found, expected)
+
     def test_debug_mode(self):
         # this covers the code called when DEBUG is set
         old_logs_len = len(self.logs)
         install_module.DEBUG = True
         try:
-            with captured_stdout() as stdout:
+            with captured_stdout():
                 self.test_record()
         finally:
             install_module.DEBUG = False
diff --git a/Lib/distutils/tests/test_sdist.py b/Lib/distutils/tests/test_sdist.py
index 440af98..f34f786 100644
--- a/Lib/distutils/tests/test_sdist.py
+++ b/Lib/distutils/tests/test_sdist.py
@@ -365,6 +365,7 @@
     def test_manual_manifest(self):
         # check that a MANIFEST without a marker is left alone
         dist, cmd = self.get_cmd()
+        cmd.formats = ['gztar']
         cmd.ensure_finalized()
         self.write_file((self.tmp_dir, cmd.manifest), 'README.manual')
         self.write_file((self.tmp_dir, 'README.manual'),
diff --git a/Lib/packaging/command/install_distinfo.py b/Lib/packaging/command/install_distinfo.py
index 3390a1f..0e1577e 100644
--- a/Lib/packaging/command/install_distinfo.py
+++ b/Lib/packaging/command/install_distinfo.py
@@ -41,6 +41,7 @@
         self.requested = None
         self.no_record = None
         self.no_resources = None
+        self.outfiles = []
 
     def finalize_options(self):
         self.set_undefined_options('install_dist',
@@ -67,7 +68,6 @@
             to_filename(safe_version(metadata['Version'])))
 
         self.distinfo_dir = os.path.join(self.distinfo_dir, basename)
-        self.outputs = []
 
     def run(self):
         # FIXME dry-run should be used at a finer level, so that people get
@@ -87,19 +87,19 @@
             metadata_path = os.path.join(self.distinfo_dir, 'METADATA')
             logger.info('creating %s', metadata_path)
             self.distribution.metadata.write(metadata_path)
-            self.outputs.append(metadata_path)
+            self.outfiles.append(metadata_path)
 
             installer_path = os.path.join(self.distinfo_dir, 'INSTALLER')
             logger.info('creating %s', installer_path)
             with open(installer_path, 'w') as f:
                 f.write(self.installer)
-            self.outputs.append(installer_path)
+            self.outfiles.append(installer_path)
 
             if self.requested:
                 requested_path = os.path.join(self.distinfo_dir, 'REQUESTED')
                 logger.info('creating %s', requested_path)
                 open(requested_path, 'wb').close()
-                self.outputs.append(requested_path)
+                self.outfiles.append(requested_path)
 
 
             if not self.no_resources:
@@ -115,7 +115,7 @@
                         for tuple in install_data.get_resources_out():
                             writer.writerow(tuple)
 
-                        self.outputs.append(resources_path)
+                        self.outfiles.append(resources_path)
 
             if not self.no_record:
                 record_path = os.path.join(self.distinfo_dir, 'RECORD')
@@ -141,10 +141,10 @@
 
                     # add the RECORD file itself
                     writer.writerow((record_path, '', ''))
-                    self.outputs.append(record_path)
+                    self.outfiles.append(record_path)
 
     def get_outputs(self):
-        return self.outputs
+        return self.outfiles
 
 
 # The following functions are taken from setuptools' pkg_resources module.
diff --git a/Lib/packaging/tests/support.py b/Lib/packaging/tests/support.py
index c6e3f72..a9535ab 100644
--- a/Lib/packaging/tests/support.py
+++ b/Lib/packaging/tests/support.py
@@ -32,6 +32,7 @@
 import logging
 import weakref
 import tempfile
+import sysconfig
 
 from packaging.dist import Distribution
 from packaging.tests import unittest
@@ -39,7 +40,7 @@
 
 __all__ = ['LoggingCatcher', 'TempdirManager', 'EnvironRestorer',
            'DummyCommand', 'unittest', 'create_distribution',
-           'skip_unless_symlink', 'requires_zlib']
+           'skip_unless_symlink', 'requires_zlib', 'copy_xxmodule_c']
 
 
 logger = logging.getLogger('packaging')
@@ -271,6 +272,38 @@
     return _wrap
 
 
+def copy_xxmodule_c(directory):
+    """Helper for tests that need the xxmodule.c source file.
+
+    Example use:
+
+        def test_compile(self):
+            copy_xxmodule_c(self.tmpdir)
+            self.assertIn('xxmodule.c', os.listdir(self.tmpdir)
+
+    If the source file can be found, it will be copied to *directory*.  If not,
+    the test will be skipped.  Errors during copy are not caught.
+    """
+    filename = _get_xxmodule_path()
+    if filename is None:
+        raise unittest.SkipTest('cannot find xxmodule.c (test must run in '
+                                'the python build dir)')
+    shutil.copy(filename, directory)
+
+
+def _get_xxmodule_path():
+    srcdir = sysconfig.get_config_var('srcdir')
+    candidates = [
+        # use installed copy if available
+        os.path.join(os.path.dirname(__file__), 'xxmodule.c'),
+        # otherwise try using copy from build directory
+        os.path.join(srcdir, 'Modules', 'xxmodule.c'),
+    ]
+    for path in candidates:
+        if os.path.exists(path):
+            return path
+
+
 try:
     from test.support import skip_unless_symlink
 except ImportError:
diff --git a/Lib/packaging/tests/test_command_build_ext.py b/Lib/packaging/tests/test_command_build_ext.py
index e144fd0..9521adc 100644
--- a/Lib/packaging/tests/test_command_build_ext.py
+++ b/Lib/packaging/tests/test_command_build_ext.py
@@ -1,7 +1,6 @@
 import os
 import sys
 import site
-import shutil
 import sysconfig
 import textwrap
 from io import StringIO
@@ -12,17 +11,7 @@
 from packaging.compiler.extension import Extension
 from test.script_helper import assert_python_ok
 
-from packaging.tests import support, unittest, verbose, unload
-
-
-def _get_source_filename():
-    # use installed copy if available
-    tests_f = os.path.join(os.path.dirname(__file__), 'xxmodule.c')
-    if os.path.exists(tests_f):
-        return tests_f
-    # otherwise try using copy from build directory
-    srcdir = sysconfig.get_config_var('srcdir')
-    return os.path.join(srcdir, 'Modules', 'xxmodule.c')
+from packaging.tests import support, unittest, verbose
 
 
 class BuildExtTestCase(support.TempdirManager,
@@ -33,9 +22,6 @@
         # Note that we're making changes to sys.path
         super(BuildExtTestCase, self).setUp()
         self.tmp_dir = self.mkdtemp()
-        filename = _get_source_filename()
-        if os.path.exists(filename):
-            shutil.copy(filename, self.tmp_dir)
         self.old_user_base = site.USER_BASE
         site.USER_BASE = self.mkdtemp()
         build_ext.USER_BASE = site.USER_BASE
@@ -68,10 +54,8 @@
                 cmd.library_dirs = value.split(os.pathsep)
 
     def test_build_ext(self):
+        support.copy_xxmodule_c(self.tmp_dir)
         xx_c = os.path.join(self.tmp_dir, 'xxmodule.c')
-        if not os.path.exists(xx_c):
-            # skipping if we cannot find it
-            return
         xx_ext = Extension('xx', [xx_c])
         dist = Distribution({'name': 'xx', 'ext_modules': [xx_ext]})
         dist.package_dir = self.tmp_dir
@@ -455,14 +439,7 @@
 
 
 def test_suite():
-    src = _get_source_filename()
-    if not os.path.exists(src):
-        if verbose:
-            print('test_command_build_ext: Cannot find source code (test'
-                  ' must run in python build dir)')
-        return unittest.TestSuite()
-    else:
-        return unittest.makeSuite(BuildExtTestCase)
+    return unittest.makeSuite(BuildExtTestCase)
 
 if __name__ == '__main__':
     unittest.main(defaultTest='test_suite')
diff --git a/Lib/packaging/tests/test_command_install_dist.py b/Lib/packaging/tests/test_command_install_dist.py
index 7821a3a..ea7c3d3 100644
--- a/Lib/packaging/tests/test_command_install_dist.py
+++ b/Lib/packaging/tests/test_command_install_dist.py
@@ -2,22 +2,20 @@
 
 import os
 import sys
-
 from sysconfig import (get_scheme_names, get_config_vars,
                        _SCHEMES, get_config_var, get_path)
 
-_CONFIG_VARS = get_config_vars()
-
-from packaging.tests import captured_stdout
-
 from packaging.command.install_dist import install_dist
-from packaging.command import install_dist as install_module
+from packaging.compiler.extension import Extension
 from packaging.dist import Distribution
 from packaging.errors import PackagingOptionError
 
 from packaging.tests import unittest, support
 
 
+_CONFIG_VARS = get_config_vars()
+
+
 class InstallTestCase(support.TempdirManager,
                       support.LoggingCatcher,
                       unittest.TestCase):
@@ -178,25 +176,50 @@
     def test_old_record(self):
         # test pre-PEP 376 --record option (outside dist-info dir)
         install_dir = self.mkdtemp()
-        pkgdir, dist = self.create_dist()
+        project_dir, dist = self.create_dist(scripts=['hello'])
+        os.chdir(project_dir)
+        self.write_file('hello', "print('o hai')")
 
-        dist = Distribution()
         cmd = install_dist(dist)
         dist.command_obj['install_dist'] = cmd
         cmd.root = install_dir
-        cmd.record = os.path.join(pkgdir, 'filelist')
+        cmd.record = os.path.join(project_dir, 'filelist')
         cmd.ensure_finalized()
         cmd.run()
 
-        # let's check the record file was created with four
-        # lines, one for each .dist-info entry: METADATA,
-        # INSTALLER, REQUSTED, RECORD
         with open(cmd.record) as f:
-            self.assertEqual(len(f.readlines()), 4)
+            content = f.read()
+
+        found = [os.path.basename(line) for line in content.splitlines()]
+        expected = ['hello', 'METADATA', 'INSTALLER', 'REQUESTED', 'RECORD']
+        self.assertEqual(found, expected)
 
         # XXX test that fancy_getopt is okay with options named
         # record and no-record but unrelated
 
+    def test_old_record_extensions(self):
+        # test pre-PEP 376 --record option with ext modules
+        install_dir = self.mkdtemp()
+        project_dir, dist = self.create_dist(ext_modules=[
+            Extension('xx', ['xxmodule.c'])])
+        os.chdir(project_dir)
+        support.copy_xxmodule_c(project_dir)
+
+        cmd = install_dist(dist)
+        dist.command_obj['install_dist'] = cmd
+        cmd.root = install_dir
+        cmd.record = os.path.join(project_dir, 'filelist')
+        cmd.ensure_finalized()
+        cmd.run()
+
+        with open(cmd.record) as f:
+            content = f.read()
+
+        found = [os.path.basename(line) for line in content.splitlines()]
+        expected = ['xx%s' % get_config_var('SO'),
+                    'METADATA', 'INSTALLER', 'REQUESTED', 'RECORD']
+        self.assertEqual(found, expected)
+
 
 def test_suite():
     return unittest.makeSuite(InstallTestCase)
diff --git a/Lib/packaging/tests/test_command_install_distinfo.py b/Lib/packaging/tests/test_command_install_distinfo.py
index ade191c..994cdd8 100644
--- a/Lib/packaging/tests/test_command_install_distinfo.py
+++ b/Lib/packaging/tests/test_command_install_distinfo.py
@@ -3,10 +3,11 @@
 import os
 import csv
 import hashlib
-import sys
+import sysconfig
 
 from packaging.command.install_distinfo import install_distinfo
 from packaging.command.cmd import Command
+from packaging.compiler.extension import Extension
 from packaging.metadata import Metadata
 from packaging.tests import unittest, support
 
@@ -45,7 +46,6 @@
         cmd = install_distinfo(dist)
         dist.command_obj['install_distinfo'] = cmd
 
-        cmd.initialize_options()
         cmd.distinfo_dir = install_dir
         cmd.ensure_finalized()
         cmd.run()
@@ -73,7 +73,6 @@
         cmd = install_distinfo(dist)
         dist.command_obj['install_distinfo'] = cmd
 
-        cmd.initialize_options()
         cmd.distinfo_dir = install_dir
         cmd.installer = 'bacon-python'
         cmd.ensure_finalized()
@@ -94,7 +93,6 @@
         cmd = install_distinfo(dist)
         dist.command_obj['install_distinfo'] = cmd
 
-        cmd.initialize_options()
         cmd.distinfo_dir = install_dir
         cmd.requested = False
         cmd.ensure_finalized()
@@ -115,7 +113,6 @@
         cmd = install_distinfo(dist)
         dist.command_obj['install_distinfo'] = cmd
 
-        cmd.initialize_options()
         cmd.distinfo_dir = install_dir
         cmd.no_record = True
         cmd.ensure_finalized()
@@ -125,6 +122,57 @@
         self.checkLists(os.listdir(dist_info),
                         ['METADATA', 'REQUESTED', 'INSTALLER'])
 
+    def test_record_basic(self):
+        install_dir = self.mkdtemp()
+        modules_dest = os.path.join(install_dir, 'lib')
+        scripts_dest = os.path.join(install_dir, 'bin')
+        project_dir, dist = self.create_dist(
+            name='Spamlib', version='0.1',
+            py_modules=['spam'], scripts=['spamd'],
+            ext_modules=[Extension('_speedspam', ['_speedspam.c'])])
+
+        # using a real install_dist command is too painful, so we use a mock
+        # class that's only a holder for options to be used by install_distinfo
+        # and we create placeholder files manually instead of using build_*.
+        # the install_* commands will still be consulted by install_distinfo.
+        os.chdir(project_dir)
+        self.write_file('spam', '# Python module')
+        self.write_file('spamd', '# Python script')
+        extmod = '_speedspam%s' % sysconfig.get_config_var('SO')
+        self.write_file(extmod, '')
+
+        install = DummyInstallCmd(dist)
+        install.outputs = ['spam', 'spamd', extmod]
+        install.install_lib = modules_dest
+        install.install_scripts = scripts_dest
+        dist.command_obj['install_dist'] = install
+
+        cmd = install_distinfo(dist)
+        cmd.ensure_finalized()
+        dist.command_obj['install_distinfo'] = cmd
+        cmd.run()
+
+        record = os.path.join(modules_dest, 'Spamlib-0.1.dist-info', 'RECORD')
+        with open(record, encoding='utf-8') as fp:
+            content = fp.read()
+
+        found = []
+        for line in content.splitlines():
+            filename, checksum, size = line.split(',')
+            filename = os.path.basename(filename)
+            found.append((filename, checksum, size))
+
+        expected = [
+            ('spam', '6ab2f288ef2545868effe68757448b45', '15'),
+            ('spamd','d13e6156ce78919a981e424b2fdcd974', '15'),
+            (extmod, 'd41d8cd98f00b204e9800998ecf8427e', '0'),
+            ('METADATA', '846de67e49c3b92c81fb1ebd7bc07046', '172'),
+            ('INSTALLER', '44e3fde05f3f537ed85831969acf396d', '9'),
+            ('REQUESTED', 'd41d8cd98f00b204e9800998ecf8427e', '0'),
+            ('RECORD', '', ''),
+        ]
+        self.assertEqual(found, expected)
+
     def test_record(self):
         pkg_dir, dist = self.create_dist(name='foo',
                                          version='1.0')
@@ -153,7 +201,6 @@
         cmd = install_distinfo(dist)
         dist.command_obj['install_distinfo'] = cmd
 
-        cmd.initialize_options()
         cmd.distinfo_dir = install_dir
         cmd.ensure_finalized()
         cmd.run()