From: eficas <>
Date: Sun, 26 Feb 2006 14:08:44 +0000 (+0000)
Subject: CCAR: ajout HTMLTestRunner
X-Git-Tag: merge_mars_06~8
X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=767ea6d0f9f23957072ec6d3b855653afa37783a;p=tools%2Feficas.git
CCAR: ajout HTMLTestRunner
---
diff --git a/Tests/HTMLTestRunner.py b/Tests/HTMLTestRunner.py
new file mode 100644
index 00000000..e59a8efa
--- /dev/null
+++ b/Tests/HTMLTestRunner.py
@@ -0,0 +1,538 @@
+"""
+Copyright (c) 2004, Wai Yip Tung
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+* Neither the name of the Wai Yip Tung nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+A TestRunner for use with the Python unit testing framework. It
+generates a HTML report to show the result at a glance.
+
+The simplest way to use this is to invoke its main method. E.g.
+
+ import unittest
+ import HTMLTestRunner
+
+ ... define your tests ...
+
+ if __name__ == '__main__':
+ HTMLTestRunner.main()
+
+It defines the class HTMLTestRunner, which is a counterpart of unittest's
+TextTestRunner. You can also instantiates a HTMLTestRunner object for
+finer control.
+"""
+
+# URL: http://tungwaiyip.info/software
+
+__author__ = "Wai Yip Tung"
+__version__ = "0.7"
+
+
+# TOOD: need to make sure all HTML and JavaScript blocks are properly escaped!
+# TODO: allow link to custom CSS
+# TODO: color stderr
+# TODO: simplify javascript using ,ore than 1 class in the class attribute?
+
+import datetime
+import string
+import StringIO
+import sys
+import time
+import unittest
+from xml.sax import saxutils
+
+
+# ------------------------------------------------------------------------
+# The redirectors below is used to capture output during testing. Output
+# sent to sys.stdout and sys.stderr are automatically captured. However
+# in some cases sys.stdout is already cached before HTMLTestRunner is
+# invoked (e.g. calling logging.basicConfig). In order to capture those
+# output, use the redirectors for the cached stream.
+#
+# e.g.
+# >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
+# >>>
+
+class OutputRedirector(object):
+ """ Wrapper to redirect stdout or stderr """
+ def __init__(self, fp):
+ self.fp = fp
+
+ def write(self, s):
+ self.fp.write(s)
+
+ def writelines(self, lines):
+ self.fp.writelines(lines)
+
+ def flush(self):
+ self.fp.flush()
+
+stdout_redirector = OutputRedirector(sys.stdout)
+stderr_redirector = OutputRedirector(sys.stderr)
+
+
+
+# ----------------------------------------------------------------------
+# Template
+
+STATUS = {
+0: 'pass',
+1: 'fail',
+2: 'error',
+}
+
+
+CSS = """
+
+"""
+
+# currently not used
+CSS_LINK = '\n'
+
+
+HTML_TMPL = string.Template(r"""
+
+
+
+""")
+
+TEST_OUTPUT_TMPL = string.Template(r"""
+
+""")
+
+
+# ----------------------------------------------------------------------
+
+TestResult = unittest.TestResult
+
+class _TestResult(TestResult):
+ # note: _TestResult is a pure representation of results.
+ # It lacks the output and reporting ability compares to unittest._TextTestResult.
+
+ def __init__(self, verbosity=1):
+ TestResult.__init__(self)
+ self.stdout0 = None
+ self.stderr0 = None
+ self.verbosity = verbosity
+
+ # result is a list of result in 4 tuple
+ # (
+ # result code (0: success; 1: fail; 2: error),
+ # TestCase object,
+ # Test output (byte string),
+ # stack trace,
+ # )
+ self.result = []
+
+
+ def startTest(self, test):
+ TestResult.startTest(self, test)
+ # just one buffer for both stdout and stderr
+ self.outputBuffer = StringIO.StringIO()
+ stdout_redirector.fp = self.outputBuffer
+ stderr_redirector.fp = self.outputBuffer
+ self.stdout0 = sys.stdout
+ self.stderr0 = sys.stderr
+ sys.stdout = stdout_redirector
+ sys.stderr = stderr_redirector
+
+
+ def complete_output(self):
+ """
+ Disconnect output redirection and return buffer.
+ Safe to call multiple times.
+ """
+ if self.stdout0:
+ sys.stdout = self.stdout0
+ sys.stderr = self.stderr0
+ self.stdout0 = None
+ self.stderr0 = None
+ return self.outputBuffer.getvalue()
+
+
+ def stopTest(self, test):
+ # Usually one of addSuccess, addError or addFailure would have been called.
+ # But there are some path in unittest that would bypass this.
+ # We must disconnect stdout in stopTest(), which is guaranteed to be called.
+ self.complete_output()
+
+
+ def addSuccess(self, test):
+ TestResult.addSuccess(self, test)
+ output = self.complete_output()
+ self.result.append((0, test, output, ''))
+ if self.verbosity > 1:
+ sys.stderr.write('ok ')
+ sys.stderr.write(str(test))
+ sys.stderr.write('\n')
+ else:
+ sys.stderr.write('.')
+
+ def addError(self, test, err):
+ TestResult.addError(self, test, err)
+ output = self.complete_output()
+ self.result.append((2, test, output, self._exc_info_to_string(err, test)))
+ if self.verbosity > 1:
+ sys.stderr.write('E ')
+ sys.stderr.write(str(test))
+ sys.stderr.write('\n')
+ else:
+ sys.stderr.write('E')
+
+ def addFailure(self, test, err):
+ TestResult.addFailure(self, test, err)
+ output = self.complete_output()
+ self.result.append((1, test, output, self._exc_info_to_string(err, test)))
+ if self.verbosity > 1:
+ sys.stderr.write('F ')
+ sys.stderr.write(str(test))
+ sys.stderr.write('\n')
+ else:
+ sys.stderr.write('F')
+
+
+class HTMLTestRunner:
+ """
+ """
+ def __init__(self, stream=sys.stdout, descriptions=1, verbosity=1, description='Test'):
+ # unittest itself has no good mechanism for user to define a
+ # description neither in TestCase nor TestSuite. Allow user to
+ # pass in the description as a parameter.
+
+ # note: this is different from unittest.TextTestRunner's
+ # 'descrpitions' parameter, which is an integer flag.
+
+ self.stream = stream
+ self.startTime = datetime.datetime.now()
+ self.description = description
+ self.verbosity = verbosity
+
+ def run(self, test):
+ "Run the given test case or test suite."
+ result = _TestResult(self.verbosity)
+ test(result)
+ self.stopTime = datetime.datetime.now()
+ self.generateReport(test, result)
+ print >>sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)
+ return result
+
+ def sortResult(self, result_list):
+ # unittest does not seems to run in any particular order.
+ # Here at least we want to group them together by class.
+ rmap = {}
+ classes = []
+ for n,t,o,e in result_list:
+ cls = t.__class__
+ if not rmap.has_key(cls):
+ rmap[cls] = []
+ classes.append(cls)
+ rmap[cls].append((n,t,o,e))
+ r = [(cls, rmap[cls]) for cls in classes]
+ return r
+
+ def generateReport(self, test, result):
+ rows = []
+ npAll = nfAll = neAll = 0
+ sortedResult = self.sortResult(result.result)
+ for cid, (cls, cls_results) in enumerate(sortedResult):
+ # update counts
+ np = nf = ne = 0
+ for n,t,o,e in cls_results:
+ if n == 0: np += 1
+ elif n == 1: nf += 1
+ else: ne += 1
+ npAll += np
+ nfAll += nf
+ neAll += ne
+
+ row = CLASS_TMPL.safe_substitute(
+ style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
+ name = "%s.%s" % (cls.__module__, cls.__name__),
+ count = np+nf+ne,
+ Pass = np,
+ fail = nf,
+ error = ne,
+ cid = 'c%s' % (cid+1),
+ )
+ rows.append(row)
+
+ for tid, (n,t,o,e) in enumerate(cls_results):
+ # e.g. 'pt1.1', 'ft1.1', etc
+ has_output = bool(o or e)
+ tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1)
+ name = t.id().split('.')[-1]
+ tmpl = has_output and TEST_TMPL or TEST_TMPL_NO_OUTPUT
+ row = tmpl.safe_substitute(
+ tid = tid,
+ Class = (n == 0 and 'hiddenRow' or ''),
+ style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or ''),
+ name = name,
+ status = STATUS[n],
+ )
+ rows.append(row)
+ if has_output:
+ # o and e should be byte string because they are collected from stdout and stderr?
+ if isinstance(o,str):
+# TODO: some problem with 'string_escape': it escape \n and mess up formating
+# uo = unicode(o.encode('string_escape'))
+ uo = o.decode('latin-1')
+ else:
+ uo = o
+ if isinstance(e,str):
+# TODO: some problem with 'string_escape': it escape \n and mess up formating
+# ue = unicode(e.encode('string_escape'))
+ ue = e.decode('latin-1')
+ else:
+ ue = e
+ row = TEST_OUTPUT_TMPL.safe_substitute(
+ id = tid,
+ output = saxutils.escape(uo+ue) \
+ .replace("'", ''') \
+ .replace('"', '"') \
+ .replace('\\','\\\\') \
+ .replace('\r','\\r') \
+ .replace('\n','\\n'),
+ )
+ rows.append(row)
+
+ report = HTML_TMPL.safe_substitute(
+ title = self.description,
+ css = CSS,
+ description = self.description,
+ time = str(self.startTime)[:19],
+ status = result.wasSuccessful() and 'Passed' or 'Failed',
+ tests = ''.join(rows),
+ count = str(npAll+nfAll+neAll),
+ Pass = str(npAll),
+ fail = str(nfAll),
+ error = str(neAll),
+ )
+ self.stream.write(report.encode('utf8'))
+
+
+##############################################################################
+# Facilities for running tests from the command line
+##############################################################################
+
+# Note: Reuse unittest.TestProgram to launch test. In the future we may
+# build our own launcher to support more specific command line
+# parameters like test title, CSS, etc.
+class TestProgram(unittest.TestProgram):
+ """
+ A variation of the unittest.TestProgram. Please refer to the base
+ class for command line parameters.
+ """
+ def runTests(self):
+ # Pick HTMLTestRunner as the default test runner.
+ # base class's testRunner parameter is not useful because it means
+ # we have to instantiate HTMLTestRunner before we know self.verbosity.
+ if self.testRunner is None:
+ self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
+ unittest.TestProgram.runTests(self)
+
+main = TestProgram
+
+##############################################################################
+# Executing this module from the command line
+##############################################################################
+
+if __name__ == "__main__":
+ main(module=None)
diff --git a/Tests/run.py b/Tests/run.py
index de323f1e..36e548ad 100644
--- a/Tests/run.py
+++ b/Tests/run.py
@@ -1,3 +1,21 @@
+"""
+This program executes all unitest tests that are found in
+ - directories with name test* or Test*
+ - files with name test* or Test*
+
+unitest tests are :
+ - functions and class with names test* or Test*
+ - methods with name test* or Test* from classes with name test* or Test*
+
+Typical uses are :
+
+ - execute all tests with text output : python2.4 run.py
+ - execute all tests with html output : python2.4 run.py --html
+ - execute some tests with text output : python2.4 run.py testelem
+ - execute one test with text output : python2.4 run.py testelem/testsimp1.py
+ - execute all tests with verbosity and html output : python2.4 run.py -v --html
+"""
+
import sys,types,os
import sre
import unittest
@@ -142,13 +160,18 @@ class ModuleTestSuite(TestSuite):
for test in func_tests ]
return tests
+
class TestProgram(unittest.TestProgram):
USAGE="""
"""
- def __init__(self,testRunner=None):
- self.testRunner = testRunner
+ def __init__(self):
+ self.testRunner = None
self.verbosity = 1
+ self.html=0
self.parseArgs(sys.argv)
+ if self.html:
+ import HTMLTestRunner
+ self.testRunner = HTMLTestRunner.HTMLTestRunner(verbosity=self.verbosity)
self.createTests()
self.runTests()
@@ -157,9 +180,13 @@ class TestProgram(unittest.TestProgram):
parser.add_option("-v","--verbose",action="count",
dest="verbosity",default=1,
help="Be more verbose. ")
+ parser.add_option("--html",action="store_true",
+ dest="html",default=0,
+ help="Produce HTML output ")
options, args = parser.parse_args(argv)
self.verbosity = options.verbosity
+ self.html=options.html
if args:
self.names = list(args)
@@ -169,9 +196,6 @@ class TestProgram(unittest.TestProgram):
def createTests(self):
self.test = TestSuite(self.names)
-
-main = TestProgram
-
if __name__ == "__main__":
- main()
+ TestProgram()