[Python-checkins] distutils2: merged from upstream

tarek.ziade python-checkins at python.org
Sun Sep 19 10:20:21 CEST 2010


tarek.ziade pushed 10da588ede3e to distutils2:

http://hg.python.org/distutils2/rev/10da588ede3e
changeset:   608:10da588ede3e
parent:      479:7debb069fc21
parent:      607:59d10bf5a35c
user:        Konrad Delong <konryd at gmail.com>
date:        Mon Aug 09 13:09:23 2010 +0200
summary:     merged from upstream
files:       docs/source/commands.rst, src/distutils2/command/upload_docs.py, src/distutils2/dist.py

diff --git a/docs/source/commands.rst b/docs/source/commands.rst
--- a/docs/source/commands.rst
+++ b/docs/source/commands.rst
@@ -6,6 +6,55 @@
 You might recognize some of them from other projects, like Distribute or
 Setuptools.
 
+``test`` - Build package and run a unittest suite
+=================================================
+
+When doing test-driven development, or running automated builds that need
+testing before they are deployed for downloading or use, it's often useful
+to be able to run a project's unit tests without actually deploying the project
+anywhere, even using the ``develop`` command.  The ``test`` command runs a
+project's unit tests without actually deploying it, by temporarily putting the
+project's source on ``sys.path``, after first running ``build_ext -i`` and
+``egg_info`` to ensure that any C extensions and project metadata are
+up-to-date.
+
+To use this command, your project's tests must be wrapped in a ``unittest``
+test suite by either a function, a ``TestCase`` class or method, or a module
+or package containing ``TestCase`` classes.  If the named suite is a module,
+and the module has an ``additional_tests()`` function, it is called and the
+result (which must be a ``unittest.TestSuite``) is added to the tests to be
+run.  If the named suite is a package, any submodules and subpackages are
+recursively added to the overall test suite.  (Note: if your project specifies
+a ``test_loader``, the rules for processing the chosen ``test_suite`` may
+differ; see the `test_loader`_ documentation for more details.)
+
+Note that many test systems including ``doctest`` support wrapping their
+non-``unittest`` tests in ``TestSuite`` objects.  So, if you are using a test
+package that does not support this, we suggest you encourage its developers to
+implement test suite support, as this is a convenient and standard way to
+aggregate a collection of tests to be run under a common test harness.
+
+By default, tests will be run in the "verbose" mode of the ``unittest``
+package's text test runner, but you can get the "quiet" mode (just dots) if
+you supply the ``-q`` or ``--quiet`` option, either as a global option to
+the setup script (e.g. ``setup.py -q test``) or as an option for the ``test``
+command itself (e.g. ``setup.py test -q``).  There is one other option
+available:
+
+``--test-suite=NAME, -s NAME``
+    Specify the test suite (or module, class, or method) to be run
+    (e.g. ``some_module.test_suite``).  The default for this option can be
+    set by giving a ``test_suite`` argument to the ``setup()`` function, e.g.::
+
+        setup(
+            # ...
+            test_suite = "my_package.tests.test_all"
+        )
+
+    If you did not set a ``test_suite`` in your ``setup()`` call, and do not
+    provide a ``--test-suite`` option, an error will occur.
+
+
 ``upload`` - Upload source and/or binary distributions to PyPI
 ==============================================================
 
@@ -84,7 +133,7 @@
 
     python setup.py upload_docs --upload-dir=docs/build/html
 
-As with any other command, you can define useful
+As with any other ``setuptools`` based command, you can define useful
 defaults in the ``setup.cfg`` of your Python project, e.g.:
 
 .. code-block:: ini
diff --git a/src/distutils2/_backport/__init__.py b/src/distutils2/_backport/__init__.py
--- a/src/distutils2/_backport/__init__.py
+++ b/src/distutils2/_backport/__init__.py
@@ -1,2 +1,8 @@
 """Things that will land in the Python 3.3 std lib but which we must drag along
 with us for now to support 2.x."""
+
+def any(seq):
+    for elem in seq:
+        if elem:
+            return elem
+    return False
diff --git a/src/distutils2/command/test.py b/src/distutils2/command/test.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/command/test.py
@@ -0,0 +1,65 @@
+import os, sys
+from distutils2.core import Command 
+from distutils2._backport.pkgutil import get_distribution
+import unittest
+import warnings
+
+def get_loader_instance(dotted_path):
+    if dotted_path is None:
+        return None
+    module_name, rest = dotted_path.split('.')[0], dotted_path.split('.')[1:]
+    while True:
+        try:
+            ret = __import__(module_name)
+            break
+        except ImportError:
+            if rest == []:
+                return None
+            module_name += ('.' + rest[0])
+            rest = rest[1:]
+    while rest:
+        try:
+            ret = getattr(ret, rest.pop(0))
+        except AttributeError:
+            return None
+    return ret()
+
+class test(Command):
+
+    description = "" # TODO
+    user_options = [
+        ('test-suite=', 's',
+            "Test suite to run (e.g. 'some_module.test_suite')"),
+        ('test-loader=', None,
+            "Test loader to be used to load the test suite."),
+    ]
+    
+    def initialize_options(self):
+        self.test_suite  = None
+        self.test_loader = None
+    
+    def finalize_options(self):
+        self.build_lib = self.get_finalized_command("build").build_lib
+        if self.distribution.tests_require:
+            for requirement in self.distribution.tests_require:
+                if get_distribution(requirement) is None:
+                    warnings.warn("The test dependency %s is not installed which may couse the tests to fail.",
+                                  RuntimeWarning)
+
+    def run(self):
+        prev_cwd = os.getcwd()
+        try:
+            if self.distribution.has_ext_modules():
+                build = self.get_reinitialized_command('build')
+                build.inplace = 1 # TODO - remove make sure it's needed
+                self.run_command('build')
+                os.chdir(self.build_lib)
+            args = {"module": self.test_suite,
+                    "argv": sys.argv[:1],
+                    "testLoader": get_loader_instance(self.test_loader)
+            }
+            if args['testLoader'] is None:
+                del args['testLoader']
+            unittest.main(**args)
+        finally:
+            os.chdir(prev_cwd)
diff --git a/src/distutils2/command/upload_docs.py b/src/distutils2/command/upload_docs.py
--- a/src/distutils2/command/upload_docs.py
+++ b/src/distutils2/command/upload_docs.py
@@ -94,9 +94,10 @@
 
     def run(self):
         name = self.distribution.metadata['Name']
+        version = self.distribution.metadata['Version']
         zip_file = zip_dir(self.upload_dir)
 
-        fields = {':action': 'doc_upload', 'name': name}.items()
+        fields = [(':action', 'doc_upload'), ('name', name), ('version', version)]
         files = [('content', name, zip_file.getvalue())]
         content_type, body = encode_multipart(fields, files)
 
diff --git a/src/distutils2/dist.py b/src/distutils2/dist.py
--- a/src/distutils2/dist.py
+++ b/src/distutils2/dist.py
@@ -110,6 +110,8 @@
          "print the list of packages/modules provided"),
         ('requires', None,
          "print the list of packages/modules required"),
+        ('tests-require', None,
+         "print the list of packages/modules required to run the test suite"),
         ('obsoletes', None,
          "print the list of packages/modules made obsolete"),
         ('use-2to3', None,
diff --git a/src/distutils2/tests/dists/custom_loader/myowntestmodule.py b/src/distutils2/tests/dists/custom_loader/myowntestmodule.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/tests/dists/custom_loader/myowntestmodule.py
@@ -0,0 +1,9 @@
+import unittest
+class Loader(object):
+    def loadTestsFromModule(self, names, module=None):
+        class SomeTest(unittest.TestCase):
+            def test_blah(self):
+                self.fail("horribly")
+        return unittest.makeSuite(SomeTest)
+    def __repr__(self):
+        return 'YO'
diff --git a/src/distutils2/tests/dists/custom_loader/setup.cfg b/src/distutils2/tests/dists/custom_loader/setup.cfg
new file mode 100644
--- /dev/null
+++ b/src/distutils2/tests/dists/custom_loader/setup.cfg
@@ -0,0 +1,2 @@
+[test]
+test-loader = myowntestmodule.Loader
diff --git a/src/distutils2/tests/dists/custom_loader/setup.py b/src/distutils2/tests/dists/custom_loader/setup.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/tests/dists/custom_loader/setup.py
@@ -0,0 +1,5 @@
+from distutils2.core import setup
+setup(name='somedist',
+      version='0.1',
+      py_modules=['myowntestmodule', 'somemod'],
+)
diff --git a/src/distutils2/tests/dists/extensions_test/myowntestmodule.py b/src/distutils2/tests/dists/extensions_test/myowntestmodule.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/tests/dists/extensions_test/myowntestmodule.py
@@ -0,0 +1,4 @@
+import unittest
+class SomeTest(unittest.TestCase):
+    def test_blah(self):
+        self.fail("horribly")
diff --git a/src/distutils2/tests/dists/extensions_test/setup.cfg b/src/distutils2/tests/dists/extensions_test/setup.cfg
new file mode 100644
--- /dev/null
+++ b/src/distutils2/tests/dists/extensions_test/setup.cfg
@@ -0,0 +1,2 @@
+[test]
+test-suite = myowntestmodule
diff --git a/src/distutils2/tests/dists/extensions_test/setup.py b/src/distutils2/tests/dists/extensions_test/setup.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/tests/dists/extensions_test/setup.py
@@ -0,0 +1,6 @@
+from distutils2.core import setup, Extension
+setup(name='somedist',
+      version='0.1',
+      py_modules=['myowntestmodule', 'somemod'],
+      ext_modules=[Extension('spam', ['spammodule.c'])],
+)
diff --git a/src/distutils2/tests/dists/extensions_test/spammodule.c b/src/distutils2/tests/dists/extensions_test/spammodule.c
new file mode 100644
--- /dev/null
+++ b/src/distutils2/tests/dists/extensions_test/spammodule.c
@@ -0,0 +1,11 @@
+#include <Python.h>
+
+static PyMethodDef SpamMethods[] = {
+    {NULL, NULL, 0, NULL}        /* Sentinel */
+};
+
+PyMODINIT_FUNC
+initspam(void)
+{
+    (void) Py_InitModule("spam", SpamMethods);
+}
diff --git a/src/distutils2/tests/dists/simple_test/myowntestmodule.py b/src/distutils2/tests/dists/simple_test/myowntestmodule.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/tests/dists/simple_test/myowntestmodule.py
@@ -0,0 +1,4 @@
+import unittest
+class SomeTest(unittest.TestCase):
+    def test_blah(self):
+        self.fail("horribly")
diff --git a/src/distutils2/tests/dists/simple_test/setup.cfg b/src/distutils2/tests/dists/simple_test/setup.cfg
new file mode 100644
--- /dev/null
+++ b/src/distutils2/tests/dists/simple_test/setup.cfg
@@ -0,0 +1,2 @@
+[test]
+test-suite = myowntestmodule
diff --git a/src/distutils2/tests/dists/simple_test/setup.py b/src/distutils2/tests/dists/simple_test/setup.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/tests/dists/simple_test/setup.py
@@ -0,0 +1,5 @@
+from distutils2.core import setup
+setup(name='somedist',
+      version='0.1',
+      py_modules=['myowntestmodule', 'somemod'],
+)
diff --git a/src/distutils2/tests/test_test.py b/src/distutils2/tests/test_test.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/tests/test_test.py
@@ -0,0 +1,102 @@
+import os, sys, shutil, re
+from copy import copy
+from os.path import join
+from StringIO import StringIO
+from distutils2.tests.support import unittest, TempdirManager
+from distutils2.command.test import test
+from distutils2.dist import Distribution
+import subprocess
+
+try:
+    any
+except NameError:
+    from distutils2._backport import any
+
+EXPECTED_OUTPUT_RE = r'''FAIL: test_blah \(myowntestmodule.SomeTest\)
+----------------------------------------------------------------------
+Traceback \(most recent call last\):
+  File ".+/myowntestmodule.py", line \d+, in test_blah
+    self.fail\("horribly"\)
+AssertionError: horribly
+'''
+
+here = os.path.dirname(os.path.abspath(__file__))
+
+
+class TestTest(TempdirManager, unittest.TestCase):
+
+    def setUp(self):
+        super(TestTest, self).setUp()
+
+        distutils2path = os.path.dirname(os.path.dirname(here))
+        self.old_pythonpath = os.environ.get('PYTHONPATH', '')
+        os.environ['PYTHONPATH'] = distutils2path + os.pathsep + self.old_pythonpath
+
+    def tearDown(self):
+        os.environ['PYTHONPATH'] = self.old_pythonpath
+        super(TestTest, self).tearDown()
+
+    def assert_re_match(self, pattern, string):
+        def quote(s):
+            lines = ['## ' + line for line in s.split('\n')]
+            sep = ["#" * 60]
+            return [''] + sep + lines + sep
+        msg = quote(pattern) + ["didn't match"] + quote(string)
+        msg = "\n".join(msg)
+        if not re.search(pattern, string):
+            self.fail(msg)
+
+    def run_with_dist_cwd(self, pkg_dir):
+        cwd = os.getcwd()
+        command = [sys.executable, "setup.py", "test"]
+        try:
+            os.chdir(pkg_dir)
+            test_proc = subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+            _, errors = test_proc.communicate()
+            return errors
+        finally:
+            os.chdir(cwd)
+
+    def prepare_dist(self, dist_name):
+        pkg_dir = join(os.path.dirname(__file__), "dists", dist_name)
+        temp_pkg_dir = join(self.mkdtemp(), dist_name)
+        shutil.copytree(pkg_dir, temp_pkg_dir)
+        return temp_pkg_dir
+        
+    def test_runs_simple_tests(self):
+        self.pkg_dir = self.prepare_dist('simple_test')
+        output = self.run_with_dist_cwd(self.pkg_dir)
+        self.assert_re_match(EXPECTED_OUTPUT_RE, output)
+        self.assertFalse(os.path.exists(join(self.pkg_dir, 'build')))
+
+    def test_builds_extensions(self):
+        self.pkg_dir = self.prepare_dist("extensions_test")
+        output = self.run_with_dist_cwd(self.pkg_dir)
+        self.assert_re_match(EXPECTED_OUTPUT_RE, output)
+        self.assertTrue(os.path.exists(join(self.pkg_dir, 'build')))
+        self.assertTrue(any(x.startswith('lib') for x in os.listdir(join(self.pkg_dir, 'build'))))
+
+    def test_custom_test_loader(self):
+        self.pkg_dir = self.prepare_dist("custom_loader")
+        output = self.run_with_dist_cwd(self.pkg_dir)
+        self.assert_re_match(EXPECTED_OUTPUT_RE, output)
+
+    def _test_works_with_2to3(self):
+        pass
+
+    @unittest.skipUnless(sys.version > '2.6', 'Need >= 2.6')
+    def test_checks_requires(self):
+        from distutils2.tests.with_support import examine_warnings
+        dist = Distribution()
+        dist.tests_require = ['ohno_ohno-impossible_1234-name_stop-that!']
+        cmd = test(dist)
+        def examinator(ws):
+            cmd.ensure_finalized()
+            self.assertEqual(1, len(ws))
+            warning = ws[0]
+            self.assertIs(warning.category, RuntimeWarning)
+
+        examine_warnings(examinator)
+
+def test_suite():
+    return unittest.makeSuite(TestTest)
diff --git a/src/distutils2/tests/test_upload_docs.py b/src/distutils2/tests/test_upload_docs.py
--- a/src/distutils2/tests/test_upload_docs.py
+++ b/src/distutils2/tests/test_upload_docs.py
@@ -116,12 +116,13 @@
         self.assertTrue(handler.headers.dict['content-type']
             .startswith('multipart/form-data;'))
 
-        action, name, content =\
-            request_data.split("----------------GHSKFJDLGDS7543FJKLFHRE75642756743254")[1:4]
+        action, name, version, content =\
+            request_data.split("----------------GHSKFJDLGDS7543FJKLFHRE75642756743254")[1:5]
 
         # check that we picked the right chunks
         self.assertIn('name=":action"', action)
         self.assertIn('name="name"', name)
+        self.assertIn('name="version"', version)
         self.assertIn('name="content"', content)
 
         # check their contents
diff --git a/src/distutils2/tests/with_support.py b/src/distutils2/tests/with_support.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/tests/with_support.py
@@ -0,0 +1,7 @@
+def examine_warnings(examinator):
+    """Given an examinator function calls it as if the code was under with
+    catch_warnings block. Useful for testing older Python versions"""
+    import warnings
+    warnings.simplefilter('default')
+    with warnings.catch_warnings(record=True) as ws:
+        examinator(ws)

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


More information about the Python-checkins mailing list