testtools: Import latest upstream.
[nivanova/samba-autobuild/.git] / lib / testtools / testtools / run.py
index c4f461ecfb8cec1b16dd9bef3368a2a517b04f27..b6f2c491cd5abe3b66a63d92bb281e4e96370995 100755 (executable)
@@ -8,10 +8,27 @@ For instance, to run the testtools test suite.
  $ python -m testtools.run testtools.tests.test_suite
 """
 
+import os
+import unittest
 import sys
 
-from testtools.tests import test_suite
 from testtools import TextTestResult
+from testtools.compat import classtypes, istext, unicode_output_stream
+
+
+defaultTestLoader = unittest.defaultTestLoader
+defaultTestLoaderCls = unittest.TestLoader
+
+if getattr(defaultTestLoader, 'discover', None) is None:
+    try:
+        import discover
+        defaultTestLoader = discover.DiscoveringTestLoader()
+        defaultTestLoaderCls = discover.DiscoveringTestLoader
+        have_discover = True
+    except ImportError:
+        have_discover = False
+else:
+    have_discover = True
 
 
 class TestToolsTestRunner(object):
@@ -19,7 +36,7 @@ class TestToolsTestRunner(object):
 
     def run(self, test):
         "Run the given test case or test suite."
-        result = TextTestResult(sys.stdout)
+        result = TextTestResult(unicode_output_stream(sys.stdout))
         result.startTestRun()
         try:
             return test.run(result)
@@ -27,13 +44,239 @@ class TestToolsTestRunner(object):
             result.stopTestRun()
 
 
+####################
+# Taken from python 2.7 and slightly modified for compatibility with
+# older versions. Delete when 2.7 is the oldest supported version.
+# Modifications:
+#  - Use have_discover to raise an error if the user tries to use
+#    discovery on an old version and doesn't have discover installed.
+#  - If --catch is given check that installHandler is available, as
+#    it won't be on old python versions.
+#  - print calls have been been made single-source python3 compatibile.
+#  - exception handling likewise.
+#  - The default help has been changed to USAGE_AS_MAIN and USAGE_FROM_MODULE
+#    removed.
+#  - A tweak has been added to detect 'python -m *.run' and use a
+#    better progName in that case.
+
+FAILFAST     = "  -f, --failfast   Stop on first failure\n"
+CATCHBREAK   = "  -c, --catch      Catch control-C and display results\n"
+BUFFEROUTPUT = "  -b, --buffer     Buffer stdout and stderr during test runs\n"
+
+USAGE_AS_MAIN = """\
+Usage: %(progName)s [options] [tests]
+
+Options:
+  -h, --help       Show this message
+  -v, --verbose    Verbose output
+  -q, --quiet      Minimal output
+%(failfast)s%(catchbreak)s%(buffer)s
+Examples:
+  %(progName)s test_module               - run tests from test_module
+  %(progName)s module.TestClass          - run tests from module.TestClass
+  %(progName)s module.Class.test_method  - run specified test method
+
+[tests] can be a list of any number of test modules, classes and test
+methods.
+
+Alternative Usage: %(progName)s discover [options]
+
+Options:
+  -v, --verbose    Verbose output
+%(failfast)s%(catchbreak)s%(buffer)s  -s directory     Directory to start discovery ('.' default)
+  -p pattern       Pattern to match test files ('test*.py' default)
+  -t directory     Top level directory of project (default to
+                   start directory)
+
+For test discovery all test modules must be importable from the top
+level directory of the project.
+"""
+
+
+class TestProgram(object):
+    """A command-line program that runs a set of tests; this is primarily
+       for making test modules conveniently executable.
+    """
+    USAGE = USAGE_AS_MAIN
+
+    # defaults for testing
+    failfast = catchbreak = buffer = progName = None
+
+    def __init__(self, module='__main__', defaultTest=None, argv=None,
+                    testRunner=None, testLoader=defaultTestLoader,
+                    exit=True, verbosity=1, failfast=None, catchbreak=None,
+                    buffer=None):
+        if istext(module):
+            self.module = __import__(module)
+            for part in module.split('.')[1:]:
+                self.module = getattr(self.module, part)
+        else:
+            self.module = module
+        if argv is None:
+            argv = sys.argv
+
+        self.exit = exit
+        self.failfast = failfast
+        self.catchbreak = catchbreak
+        self.verbosity = verbosity
+        self.buffer = buffer
+        self.defaultTest = defaultTest
+        self.testRunner = testRunner
+        self.testLoader = testLoader
+        progName = argv[0]
+        if progName.endswith('%srun.py' % os.path.sep):
+            elements = progName.split(os.path.sep)
+            progName = '%s.run' % elements[-2]
+        else:
+            progName = os.path.basename(argv[0])
+        self.progName = progName
+        self.parseArgs(argv)
+        self.runTests()
+
+    def usageExit(self, msg=None):
+        if msg:
+            print(msg)
+        usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '',
+                 'buffer': ''}
+        if self.failfast != False:
+            usage['failfast'] = FAILFAST
+        if self.catchbreak != False:
+            usage['catchbreak'] = CATCHBREAK
+        if self.buffer != False:
+            usage['buffer'] = BUFFEROUTPUT
+        print(self.USAGE % usage)
+        sys.exit(2)
+
+    def parseArgs(self, argv):
+        if len(argv) > 1 and argv[1].lower() == 'discover':
+            self._do_discovery(argv[2:])
+            return
+
+        import getopt
+        long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer']
+        try:
+            options, args = getopt.getopt(argv[1:], 'hHvqfcb', long_opts)
+            for opt, value in options:
+                if opt in ('-h','-H','--help'):
+                    self.usageExit()
+                if opt in ('-q','--quiet'):
+                    self.verbosity = 0
+                if opt in ('-v','--verbose'):
+                    self.verbosity = 2
+                if opt in ('-f','--failfast'):
+                    if self.failfast is None:
+                        self.failfast = True
+                    # Should this raise an exception if -f is not valid?
+                if opt in ('-c','--catch'):
+                    if self.catchbreak is None:
+                        self.catchbreak = True
+                    # Should this raise an exception if -c is not valid?
+                if opt in ('-b','--buffer'):
+                    if self.buffer is None:
+                        self.buffer = True
+                    # Should this raise an exception if -b is not valid?
+            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()
+        except getopt.error:
+            exc_info = sys.exc_info()
+            msg = exc_info[1]
+            self.usageExit(msg)
+
+    def createTests(self):
+        if self.testNames is None:
+            self.test = self.testLoader.loadTestsFromModule(self.module)
+        else:
+            self.test = self.testLoader.loadTestsFromNames(self.testNames,
+                                                           self.module)
+
+    def _do_discovery(self, argv, Loader=defaultTestLoaderCls):
+        # handle command line args for test discovery
+        if not have_discover:
+            raise AssertionError("Unable to use discovery, must use python 2.7 "
+                    "or greater, or install the discover package.")
+        self.progName = '%s discover' % self.progName
+        import optparse
+        parser = optparse.OptionParser()
+        parser.prog = self.progName
+        parser.add_option('-v', '--verbose', dest='verbose', default=False,
+                          help='Verbose output', action='store_true')
+        if self.failfast != False:
+            parser.add_option('-f', '--failfast', dest='failfast', default=False,
+                              help='Stop on first fail or error',
+                              action='store_true')
+        if self.catchbreak != False:
+            parser.add_option('-c', '--catch', dest='catchbreak', default=False,
+                              help='Catch ctrl-C and display results so far',
+                              action='store_true')
+        if self.buffer != False:
+            parser.add_option('-b', '--buffer', dest='buffer', default=False,
+                              help='Buffer stdout and stderr during tests',
+                              action='store_true')
+        parser.add_option('-s', '--start-directory', dest='start', default='.',
+                          help="Directory to start discovery ('.' default)")
+        parser.add_option('-p', '--pattern', dest='pattern', default='test*.py',
+                          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)')
+
+        options, args = parser.parse_args(argv)
+        if len(args) > 3:
+            self.usageExit()
+
+        for name, value in zip(('start', 'pattern', 'top'), args):
+            setattr(options, name, value)
+
+        # only set options from the parsing here
+        # if they weren't set explicitly in the constructor
+        if self.failfast is None:
+            self.failfast = options.failfast
+        if self.catchbreak is None:
+            self.catchbreak = options.catchbreak
+        if self.buffer is None:
+            self.buffer = options.buffer
+
+        if options.verbose:
+            self.verbosity = 2
+
+        start_dir = options.start
+        pattern = options.pattern
+        top_level_dir = options.top
+
+        loader = Loader()
+        self.test = loader.discover(start_dir, pattern, top_level_dir)
+
+    def runTests(self):
+        if (self.catchbreak
+            and getattr(unittest, 'installHandler', None) is not None):
+            unittest.installHandler()
+        if self.testRunner is None:
+            self.testRunner = runner.TextTestRunner
+        if isinstance(self.testRunner, classtypes()):
+            try:
+                testRunner = self.testRunner(verbosity=self.verbosity,
+                                             failfast=self.failfast,
+                                             buffer=self.buffer)
+            except TypeError:
+                # didn't accept the verbosity, buffer or failfast arguments
+                testRunner = self.testRunner()
+        else:
+            # it is assumed to be a TestRunner instance
+            testRunner = self.testRunner
+        self.result = testRunner.run(self.test)
+        if self.exit:
+            sys.exit(not self.result.wasSuccessful())
+################
+
+
 if __name__ == '__main__':
-    import optparse
-    from unittest import TestProgram
-    parser = optparse.OptionParser(__doc__)
-    args = parser.parse_args()[1]
-    if not args:
-        parser.error("No testspecs given.")
     runner = TestToolsTestRunner()
-    program = TestProgram(module=None, argv=[sys.argv[0]] + args,
-        testRunner=runner)
+    program = TestProgram(argv=sys.argv, testRunner=runner)