[GRASS-SVN] r61104 - sandbox/wenzeslaus/gunittest

svn_grass at osgeo.org svn_grass at osgeo.org
Tue Jul 1 08:36:55 PDT 2014


Author: wenzeslaus
Date: 2014-07-01 08:36:55 -0700 (Tue, 01 Jul 2014)
New Revision: 61104

Added:
   sandbox/wenzeslaus/gunittest/loader.py
   sandbox/wenzeslaus/gunittest/main.py
   sandbox/wenzeslaus/gunittest/runner.py
   sandbox/wenzeslaus/gunittest/suite.py
Log:
gunittest: initial grass versions of loader, runner, suite and main (except for discover method mostly copied from Python unittest 2.7.4)

Added: sandbox/wenzeslaus/gunittest/loader.py
===================================================================
--- sandbox/wenzeslaus/gunittest/loader.py	                        (rev 0)
+++ sandbox/wenzeslaus/gunittest/loader.py	2014-07-01 15:36:55 UTC (rev 61104)
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+"""Loading unittests."""
+
+import os
+import sys
+import fnmatch
+import unittest
+import collections
+
+import suite
+
+GrassTestDir = collections.namedtuple('GrassTestDir', ['modules'])
+GrassTestPythonModule = collections.namedtuple('GrassTestPythonModule',
+                                               ['name', 'module',
+                                                'tested_dir',
+                                                'file_dir'])
+
+
+class GrassTestLoader(unittest.TestLoader):
+
+    skip_dirs = ['.svn', 'dist.*', 'bin.*', 'OBJ.*']
+    testsuite_dir = 'testsuite'
+    files_in_testsuite = '*.py'
+    suiteClass = suite.GrassTestSuite
+
+    # TODO: we ignore all parameters
+    def discover(self, start_dir, pattern='test*.py', top_level_dir=None):
+        modules = []
+        tests = []
+        for root, dirs, files in os.walk('.'):
+            for pattern in self.skip_dirs:
+                to_skip = fnmatch.filter(dirs, pattern)
+                for skip in to_skip:
+                    dirs.remove(skip)
+
+            if self.testsuite_dir in dirs:
+                full = os.path.join(root, self.testsuite_dir)
+                files = fnmatch.filter(os.listdir(full), self.files_in_testsuite)
+                module_names = [f[:-3] for f in files if not f == '__init__.py']
+                for name in module_names:
+                    sys.path.insert(0, full)
+                    try:
+                        import importlib
+                        m = importlib.import_module(name)
+                        #__import__(name)
+                        modules.append(GrassTestPythonModule(name=name,
+                                                                  module=m,
+                                                                  tested_dir=root,
+                                                                  file_dir=full))
+                        tests.append(self.loadTestsFromModule(m))
+                    except ImportError as e:
+                        raise ImportError('No module named %s in %s' % (name, full))
+                        # alternative is to create TestClass which will raise
+                        # see unittest.loader
+        return self.suiteClass(tests)
+
+
+if __name__ == '__main__':
+    GrassTestLoader().discoverGrassTestDirs()


Property changes on: sandbox/wenzeslaus/gunittest/loader.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: sandbox/wenzeslaus/gunittest/main.py
===================================================================
--- sandbox/wenzeslaus/gunittest/main.py	                        (rev 0)
+++ sandbox/wenzeslaus/gunittest/main.py	2014-07-01 15:36:55 UTC (rev 61104)
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+
+
+from unittest.main import TestProgram, USAGE_AS_MAIN
+TestProgram.USAGE = USAGE_AS_MAIN
+
+from loader import GrassTestLoader
+from runner import GrassTestRunner
+
+
+class GrassTestProgram(TestProgram):
+
+    def _do_discovery(self, argv, Loader=None):
+        """
+
+        Taken from Python's ``unittest.main.TestProgram._do_discovery()``.
+        Line::
+
+            Loader = lambda: self.testLoader
+
+        changed to::
+
+            Loader = self.testLoader
+        """
+        if Loader is None:
+            Loader = self.testLoader
+
+        # handle command line args for test discovery
+        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)
+
+
+if __name__ == '__main__':
+    GrassTestProgram(module=None, testLoader=GrassTestLoader, testRunner=GrassTestRunner)


Property changes on: sandbox/wenzeslaus/gunittest/main.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: sandbox/wenzeslaus/gunittest/runner.py
===================================================================
--- sandbox/wenzeslaus/gunittest/runner.py	                        (rev 0)
+++ sandbox/wenzeslaus/gunittest/runner.py	2014-07-01 15:36:55 UTC (rev 61104)
@@ -0,0 +1,202 @@
+# -*- coding: utf-8 -*-
+
+"""Running tests
+
+File content taken from Python's  ``unittest.runner``, it will be used as
+a template. It is not expected that something will left.
+"""
+
+import sys
+import time
+
+from unittest import result
+from unittest.signals import registerResult
+
+__unittest = True
+
+
+class _WritelnDecorator(object):
+    """Used to decorate file-like objects with a handy 'writeln' method"""
+    def __init__(self,stream):
+        self.stream = stream
+
+    def __getattr__(self, attr):
+        if attr in ('stream', '__getstate__'):
+            raise AttributeError(attr)
+        return getattr(self.stream,attr)
+
+    def writeln(self, arg=None):
+        if arg:
+            self.write(arg)
+        self.write('\n') # text-mode streams translate to \r\n if needed
+
+
+class TextTestResult(result.TestResult):
+    """A test result class that can print formatted text results to a stream.
+
+    Used by TextTestRunner.
+    """
+    separator1 = '=' * 70
+    separator2 = '-' * 70
+
+    def __init__(self, stream, descriptions, verbosity):
+        super(TextTestResult, self).__init__(stream, descriptions, verbosity)
+        self.stream = stream
+        self.showAll = verbosity > 1
+        self.dots = verbosity == 1
+        self.descriptions = descriptions
+
+    def getDescription(self, test):
+        doc_first_line = test.shortDescription()
+        if self.descriptions and doc_first_line:
+            return '\n'.join((str(test), doc_first_line))
+        else:
+            return str(test)
+
+    def startTest(self, test):
+        super(TextTestResult, self).startTest(test)
+        if self.showAll:
+            self.stream.write(self.getDescription(test))
+            self.stream.write(" ... ")
+            self.stream.flush()
+
+    def addSuccess(self, test):
+        super(TextTestResult, self).addSuccess(test)
+        if self.showAll:
+            self.stream.writeln("ok")
+        elif self.dots:
+            self.stream.write('.')
+            self.stream.flush()
+
+    def addError(self, test, err):
+        super(TextTestResult, self).addError(test, err)
+        if self.showAll:
+            self.stream.writeln("ERROR")
+        elif self.dots:
+            self.stream.write('E')
+            self.stream.flush()
+
+    def addFailure(self, test, err):
+        super(TextTestResult, self).addFailure(test, err)
+        if self.showAll:
+            self.stream.writeln("FAIL")
+        elif self.dots:
+            self.stream.write('F')
+            self.stream.flush()
+
+    def addSkip(self, test, reason):
+        super(TextTestResult, self).addSkip(test, reason)
+        if self.showAll:
+            self.stream.writeln("skipped {0!r}".format(reason))
+        elif self.dots:
+            self.stream.write("s")
+            self.stream.flush()
+
+    def addExpectedFailure(self, test, err):
+        super(TextTestResult, self).addExpectedFailure(test, err)
+        if self.showAll:
+            self.stream.writeln("expected failure")
+        elif self.dots:
+            self.stream.write("x")
+            self.stream.flush()
+
+    def addUnexpectedSuccess(self, test):
+        super(TextTestResult, self).addUnexpectedSuccess(test)
+        if self.showAll:
+            self.stream.writeln("unexpected success")
+        elif self.dots:
+            self.stream.write("u")
+            self.stream.flush()
+
+    def printErrors(self):
+        if self.dots or self.showAll:
+            self.stream.writeln()
+        self.printErrorList('ERROR', self.errors)
+        self.printErrorList('FAIL', self.failures)
+
+    def printErrorList(self, flavour, errors):
+        for test, err in errors:
+            self.stream.writeln(self.separator1)
+            self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
+            self.stream.writeln(self.separator2)
+            self.stream.writeln("%s" % err)
+
+
+class GrassTestRunner(object):
+    """A test runner class that displays results in textual form.
+
+    It prints out the names of tests as they are run, errors as they
+    occur, and a summary of the results at the end of the test run.
+    """
+    resultclass = TextTestResult
+
+    def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1,
+                 failfast=False, buffer=False, resultclass=None):
+        self.stream = _WritelnDecorator(stream)
+        self.descriptions = descriptions
+        self.verbosity = verbosity
+        self.failfast = failfast
+        self.buffer = buffer
+        if resultclass is not None:
+            self.resultclass = resultclass
+
+    def _makeResult(self):
+        return self.resultclass(self.stream, self.descriptions, self.verbosity)
+
+    def run(self, test):
+        "Run the given test case or test suite."
+        result = self._makeResult()
+        registerResult(result)
+        result.failfast = self.failfast
+        result.buffer = self.buffer
+        startTime = time.time()
+        startTestRun = getattr(result, 'startTestRun', None)
+        if startTestRun is not None:
+            startTestRun()
+        try:
+            test(result)
+        finally:
+            stopTestRun = getattr(result, 'stopTestRun', None)
+            if stopTestRun is not None:
+                stopTestRun()
+        stopTime = time.time()
+        timeTaken = stopTime - startTime
+        result.printErrors()
+        if hasattr(result, 'separator2'):
+            self.stream.writeln(result.separator2)
+        run = result.testsRun
+        self.stream.writeln("Ran %d test%s in %.3fs" %
+                            (run, run != 1 and "s" or "", timeTaken))
+        self.stream.writeln()
+
+        expectedFails = unexpectedSuccesses = skipped = 0
+        try:
+            results = map(len, (result.expectedFailures,
+                                result.unexpectedSuccesses,
+                                result.skipped))
+        except AttributeError:
+            pass
+        else:
+            expectedFails, unexpectedSuccesses, skipped = results
+
+        infos = []
+        if not result.wasSuccessful():
+            self.stream.write("FAILED")
+            failed, errored = map(len, (result.failures, result.errors))
+            if failed:
+                infos.append("failures=%d" % failed)
+            if errored:
+                infos.append("errors=%d" % errored)
+        else:
+            self.stream.write("OK")
+        if skipped:
+            infos.append("skipped=%d" % skipped)
+        if expectedFails:
+            infos.append("expected failures=%d" % expectedFails)
+        if unexpectedSuccesses:
+            infos.append("unexpected successes=%d" % unexpectedSuccesses)
+        if infos:
+            self.stream.writeln(" (%s)" % (", ".join(infos),))
+        else:
+            self.stream.write("\n")
+        return result


Property changes on: sandbox/wenzeslaus/gunittest/runner.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: sandbox/wenzeslaus/gunittest/suite.py
===================================================================
--- sandbox/wenzeslaus/gunittest/suite.py	                        (rev 0)
+++ sandbox/wenzeslaus/gunittest/suite.py	2014-07-01 15:36:55 UTC (rev 61104)
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+import unittest
+
+
+class GrassTestSuite(unittest.TestSuite):
+
+    def run(self, result, debug=False):
+        """
+
+        Original function implementation taken from Python's ``unittest.suite``.
+        """
+        topLevel = False
+        if getattr(result, '_testRunEntered', False) is False:
+            result._testRunEntered = topLevel = True
+
+        for test in self:
+            if result.shouldStop:
+                break
+
+            if _isnotsuite(test):
+                self._tearDownPreviousClass(test, result)
+                self._handleModuleFixture(test, result)
+                self._handleClassSetUp(test, result)
+                result._previousTestClass = test.__class__
+
+                if (getattr(test.__class__, '_classSetupFailed', False) or
+                   getattr(result, '_moduleSetUpFailed', False)):
+                    continue
+
+            if not debug:
+                test(result)
+            else:
+                test.debug()
+
+        if topLevel:
+            self._tearDownPreviousClass(None, result)
+            self._handleModuleTearDown(result)
+            result._testRunEntered = False
+        return result
+
+
+# helper from unittest.suite for the original implementation of run
+def _isnotsuite(test):
+    "A crude way to tell apart testcases and suites with duck-typing"
+    try:
+        iter(test)
+    except TypeError:
+        return True
+    return False


Property changes on: sandbox/wenzeslaus/gunittest/suite.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native



More information about the grass-commit mailing list