← Back to team overview

testtools-dev team mailing list archive

[Merge] lp:~lifeless/testtools/listtests into lp:testtools

 

Robert Collins has proposed merging lp:~lifeless/testtools/listtests into lp:testtools.

Requested reviews:
  testtools developers (testtools-dev)


Support test listing. Not tested with discover, but I expect any damage is going to be minimal.
-- 
https://code.launchpad.net/~lifeless/testtools/listtests/+merge/42166
Your team testtools developers is requested to review the proposed merge of lp:~lifeless/testtools/listtests into lp:testtools.
=== modified file 'NEWS'
--- NEWS	2010-11-29 00:32:53 +0000
+++ NEWS	2010-11-29 20:36:11 +0000
@@ -42,6 +42,11 @@
 * ``MatchesException`` added to the ``testtools.matchers`` module - matches
   an exception class and parameters. (Robert Collins)
 
+* New ``KeysEqual`` matcher.  (Jonathan Lange)
+
+* New helpers for conditionally importing modules, ``try_import`` and
+  ``try_imports``.  (Jonathan Lange)
+
 * ``Raises`` added to the ``testtools.matchers`` module - matches if the
   supplied callable raises, and delegates to an optional matcher for validation
   of the exception. (Robert Collins)
@@ -53,16 +58,16 @@
 * ``testools.TestCase.useFixture`` has been added to glue with fixtures nicely.
   (Robert Collins)
 
+* ``testtools.run`` now supports ``-l`` to list tests rather than executing
+  them. This is useful for integration with external test analysis/processing
+  tools like subunit and testrepository. (Robert Collins)
+
 * Update documentation to say how to use testtools.run() on Python 2.4.
   (Jonathan Lange, #501174)
 
 * ``text_content`` conveniently converts a Python string to a Content object.
   (Jonathan Lange, James Westby)
 
-* New ``KeysEqual`` matcher.  (Jonathan Lange)
-
-* New helpers for conditionally importing modules, ``try_import`` and
-  ``try_imports``.  (Jonathan Lange)
 
 
 0.9.7

=== modified file 'testtools/run.py'
--- testtools/run.py	2010-08-15 23:18:59 +0000
+++ testtools/run.py	2010-11-29 20:36:11 +0000
@@ -14,6 +14,7 @@
 
 from testtools import TextTestResult
 from testtools.compat import classtypes, istext, unicode_output_stream
+from testtools.testsuite import iterate_tests
 
 
 defaultTestLoader = unittest.defaultTestLoader
@@ -34,9 +35,12 @@
 class TestToolsTestRunner(object):
     """ A thunk object to support unittest.TestProgram."""
 
+    def __init__(self, stdout):
+        self.stdout = stdout
+
     def run(self, test):
         "Run the given test case or test suite."
-        result = TextTestResult(unicode_output_stream(sys.stdout))
+        result = TextTestResult(unicode_output_stream(self.stdout))
         result.startTestRun()
         try:
             return test.run(result)
@@ -70,6 +74,7 @@
   -h, --help       Show this message
   -v, --verbose    Verbose output
   -q, --quiet      Minimal output
+  -l, --list       List tests rather than executing them.
 %(failfast)s%(catchbreak)s%(buffer)s
 Examples:
   %(progName)s test_module               - run tests from test_module
@@ -87,6 +92,7 @@
   -p pattern       Pattern to match test files ('test*.py' default)
   -t directory     Top level directory of project (default to
                    start directory)
+  -l, --list       List tests rather than executing them.
 
 For test discovery all test modules must be importable from the top
 level directory of the project.
@@ -102,11 +108,13 @@
     # defaults for testing
     failfast = catchbreak = buffer = progName = None
 
-    def __init__(self, module='__main__', defaultTest=None, argv=None,
+    def __init__(self, module=__name__, defaultTest=None, argv=None,
                     testRunner=None, testLoader=defaultTestLoader,
                     exit=True, verbosity=1, failfast=None, catchbreak=None,
-                    buffer=None):
-        if istext(module):
+                    buffer=None, stdout=None):
+        if module == __name__:
+            self.module = None
+        elif istext(module):
             self.module = __import__(module)
             for part in module.split('.')[1:]:
                 self.module = getattr(self.module, part)
@@ -121,6 +129,7 @@
         self.verbosity = verbosity
         self.buffer = buffer
         self.defaultTest = defaultTest
+        self.listtests = False
         self.testRunner = testRunner
         self.testLoader = testLoader
         progName = argv[0]
@@ -131,7 +140,11 @@
             progName = os.path.basename(argv[0])
         self.progName = progName
         self.parseArgs(argv)
-        self.runTests()
+        if not self.listtests:
+            self.runTests()
+        else:
+            for test in iterate_tests(self.test):
+                stdout.write('%s\n' % test.id())
 
     def usageExit(self, msg=None):
         if msg:
@@ -153,9 +166,10 @@
             return
 
         import getopt
-        long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer']
+        long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer',
+            'list']
         try:
-            options, args = getopt.getopt(argv[1:], 'hHvqfcb', long_opts)
+            options, args = getopt.getopt(argv[1:], 'hHvqfcbl', long_opts)
             for opt, value in options:
                 if opt in ('-h','-H','--help'):
                     self.usageExit()
@@ -175,14 +189,13 @@
                     if self.buffer is None:
                         self.buffer = True
                     # Should this raise an exception if -b is not valid?
+                if opt in ('-l', '--list'):
+                    self.listtests = True
             if len(args) == 0 and self.defaultTest is None:
                 # createTests will load tests from self.module
                 self.testNames = None
             elif len(args) > 0:
                 self.testNames = args
-                if __name__ == '__main__':
-                    # to support python -m unittest ...
-                    self.module = None
             else:
                 self.testNames = (self.defaultTest,)
             self.createTests()
@@ -225,6 +238,8 @@
                           help="Pattern to match tests ('test*.py' default)")
         parser.add_option('-t', '--top-level-directory', dest='top', default=None,
                           help='Top level directory of project (defaults to start directory)')
+        parser.add_option('-l', '--list', dest='listtests', default=False,
+                          help='List tests rather than running them.')
 
         options, args = parser.parse_args(argv)
         if len(args) > 3:
@@ -241,6 +256,7 @@
             self.catchbreak = options.catchbreak
         if self.buffer is None:
             self.buffer = options.buffer
+        self.listtests = options.listtests
 
         if options.verbose:
             self.verbosity = 2
@@ -274,7 +290,9 @@
             sys.exit(not self.result.wasSuccessful())
 ################
 
+def main(argv, stdout):
+    runner = TestToolsTestRunner(stdout)
+    program = TestProgram(argv=argv, testRunner=runner, stdout=stdout)
 
 if __name__ == '__main__':
-    runner = TestToolsTestRunner()
-    program = TestProgram(argv=sys.argv, testRunner=runner)
+    main(sys.argv, sys.stdout)

=== modified file 'testtools/tests/__init__.py'
--- testtools/tests/__init__.py	2010-11-27 11:25:12 +0000
+++ testtools/tests/__init__.py	2010-11-29 20:36:11 +0000
@@ -15,13 +15,13 @@
         test_helpers,
         test_matchers,
         test_monkey,
+        test_run,
         test_runtest,
         test_spinner,
         test_testtools,
         test_testresult,
         test_testsuite,
         )
-    suites = []
     modules = [
         test_compat,
         test_content,
@@ -31,12 +31,11 @@
         test_helpers,
         test_matchers,
         test_monkey,
-        test_runtest,
+        test_run,
         test_spinner,
         test_testresult,
         test_testsuite,
         test_testtools,
         ]
-    for module in modules:
-        suites.append(getattr(module, 'test_suite')())
+    suites = map(lambda x:x.test_suite(), modules)
     return unittest.TestSuite(suites)

=== added file 'testtools/tests/test_run.py'
--- testtools/tests/test_run.py	1970-01-01 00:00:00 +0000
+++ testtools/tests/test_run.py	2010-11-29 20:36:11 +0000
@@ -0,0 +1,39 @@
+# Copyright (c) 2010 Testtools authors. See LICENSE for details.
+
+"""Tests for the test runner logic."""
+
+import StringIO
+
+from testtools.helpers import try_import
+fixtures = try_import('fixtures')
+
+import testtools
+from testtools import TestCase, run
+
+
+class TestRun(TestCase):
+
+    def test_run_list(self):
+        if fixtures is None:
+            self.skipTest("Need fixtures")
+        package = self.useFixture(fixtures.PythonPackage(
+            'runexample', [('__init__.py', """
+from testtools import TestCase
+
+class TestFoo(TestCase):
+    def test_bar(self):
+        pass
+    def test_quux(self):
+        pass
+def test_suite():
+    from unittest import TestLoader
+    return TestLoader().loadTestsFromName(__name__)
+""")]))
+        testtools.__path__.append(package.base)
+        self.addCleanup(testtools.__path__.remove, package.base)
+        out = StringIO.StringIO()
+        run.main(['-l', 'testtools.runexample.test_suite'], out)
+
+def test_suite():
+    from unittest import TestLoader
+    return TestLoader().loadTestsFromName(__name__)


Follow ups