From 2a4e7bccb1d9a9e8f67437770ffe8007efcd1409 Mon Sep 17 00:00:00 2001 From: Weiqiang Bu Date: Wed, 12 Apr 2017 18:06:05 +0800 Subject: [PATCH 1/2] update to 0.8.3(by Darren Wurf) and fix output accumulation issue --- HTMLTestRunner.py | 157 ++++++++++++++++++++++------------------------ 1 file changed, 76 insertions(+), 81 deletions(-) mode change 100644 => 100755 HTMLTestRunner.py diff --git a/HTMLTestRunner.py b/HTMLTestRunner.py old mode 100644 new mode 100755 index 8d60600..f58d98d --- a/HTMLTestRunner.py +++ b/HTMLTestRunner.py @@ -67,7 +67,6 @@ __author__ = "Wai Yip Tung" __version__ = "0.8.3" - """ Change History @@ -119,8 +118,10 @@ def to_unicode(s): # s is non ascii byte string return s.decode('unicode_escape') + class OutputRedirector(object): """ Wrapper to redirect stdout or stderr """ + def __init__(self, fp): self.fp = fp @@ -134,11 +135,11 @@ def writelines(self, lines): def flush(self): self.fp.flush() + stdout_redirector = OutputRedirector(sys.stdout) stderr_redirector = OutputRedirector(sys.stderr) - # ---------------------------------------------------------------------- # Template @@ -183,9 +184,9 @@ class Template_mixin(object): """ STATUS = { - 0: 'pass', - 1: 'fail', - 2: 'error', + 0: 'pass', + 1: 'fail', + 2: 'error', } DEFAULT_TITLE = 'Unit Test Report' @@ -401,8 +402,6 @@ class Template_mixin(object): """ - - # ------------------------------------------------------------------------ # Heading # @@ -413,12 +412,10 @@ class Template_mixin(object):

%(description)s

-""" # variables: (title, parameters, description) +""" # variables: (title, parameters, description) HEADING_ATTRIBUTE_TMPL = """

%(name)s: %(value)s

-""" # variables: (name, value) - - +""" # variables: (name, value) # ------------------------------------------------------------------------ # Report @@ -457,7 +454,7 @@ class Template_mixin(object):   -""" # variables: (test_list, count, Pass, fail, error) +""" # variables: (test_list, count, Pass, fail, error) REPORT_CLASS_TMPL = r""" @@ -468,8 +465,7 @@ class Template_mixin(object): %(error)s Detail -""" # variables: (style, desc, count, Pass, fail, error, cid) - +""" # variables: (style, desc, count, Pass, fail, error, cid) REPORT_TEST_WITH_OUTPUT_TMPL = r""" @@ -493,22 +489,18 @@ class Template_mixin(object): -""" # variables: (tid, Class, style, desc, status) - +""" # variables: (tid, Class, style, desc, status) REPORT_TEST_NO_OUTPUT_TMPL = r"""
%(desc)s
%(status)s -""" # variables: (tid, Class, style, desc, status) - +""" # variables: (tid, Class, style, desc, status) REPORT_TEST_OUTPUT_TMPL = r""" %(id)s: %(output)s -""" # variables: (id, output) - - +""" # variables: (id, output) # ------------------------------------------------------------------------ # ENDING @@ -516,11 +508,13 @@ class Template_mixin(object): ENDING_TMPL = """
 
""" + # -------------------- The end of the Template class ------------------- 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. @@ -544,7 +538,6 @@ def __init__(self, verbosity=1): # ) self.result = [] - def startTest(self, test): TestResult.startTest(self, test) # just one buffer for both stdout and stderr @@ -555,7 +548,6 @@ def startTest(self, test): sys.stdout = stdout_redirector sys.stderr = stderr_redirector - def complete_output(self): """ Disconnect output redirection and return buffer. @@ -566,8 +558,12 @@ def complete_output(self): sys.stderr = self.stderr0 self.stdout0 = None self.stderr0 = None - return self.outputBuffer.getvalue() - + output = self.outputBuffer.getvalue() + self.outputBuffer.truncate(0) + self.outputBuffer.seek(0) + # self.outputBuffer.close() + # self.outputBuffer = StringIO.StringIO() + return output def stopTest(self, test): # Usually one of addSuccess, addError or addFailure would have been called. @@ -575,7 +571,6 @@ def stopTest(self, test): # We must disconnect stdout in stopTest(), which is guaranteed to be called. self.complete_output() - def addSuccess(self, test): self.success_count += 1 TestResult.addSuccess(self, test) @@ -618,6 +613,7 @@ def addFailure(self, test, err): class HTMLTestRunner(Template_mixin): """ """ + def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None): self.stream = stream self.verbosity = verbosity @@ -632,32 +628,29 @@ def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None) self.startTime = datetime.datetime.now() - 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) + 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: + 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)) + rmap[cls].append((n, t, o, e)) r = [(cls, rmap[cls]) for cls in classes] return r - def getReportAttributes(self, result): """ Return report attributes as a list of (name, value). @@ -666,9 +659,9 @@ def getReportAttributes(self, result): startTime = str(self.startTime)[:19] duration = str(self.stopTime - self.startTime) status = [] - if result.success_count: status.append('Pass %s' % result.success_count) + if result.success_count: status.append('Pass %s' % result.success_count) if result.failure_count: status.append('Failure %s' % result.failure_count) - if result.error_count: status.append('Error %s' % result.error_count ) + if result.error_count: status.append('Error %s' % result.error_count) if status: status = ' '.join(status) else: @@ -679,7 +672,6 @@ def getReportAttributes(self, result): ('Status', status), ] - def generateReport(self, test, result): report_attrs = self.getReportAttributes(result) generator = 'HTMLTestRunner %s' % __version__ @@ -688,46 +680,46 @@ def generateReport(self, test, result): report = self._generate_report(result) ending = self._generate_ending() output = self.HTML_TMPL % dict( - title = saxutils.escape(self.title), - generator = generator, - stylesheet = stylesheet, - heading = heading, - report = report, - ending = ending, + title=saxutils.escape(self.title), + generator=generator, + stylesheet=stylesheet, + heading=heading, + report=report, + ending=ending, ) self.stream.write(output.encode('utf8')) - def _generate_stylesheet(self): return self.STYLESHEET_TMPL - def _generate_heading(self, report_attrs): a_lines = [] for name, value in report_attrs: line = self.HEADING_ATTRIBUTE_TMPL % dict( - name = saxutils.escape(name), - value = saxutils.escape(value), - ) + name=saxutils.escape(name), + value=saxutils.escape(value), + ) a_lines.append(line) heading = self.HEADING_TMPL % dict( - title = saxutils.escape(self.title), - parameters = ''.join(a_lines), - description = saxutils.escape(self.description), + title=saxutils.escape(self.title), + parameters=''.join(a_lines), + description=saxutils.escape(self.description), ) return heading - def _generate_report(self, result): rows = [] sortedResult = self.sortResult(result.result) for cid, (cls, cls_results) in enumerate(sortedResult): # subtotal for a class 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 + for n, t, o, e in cls_results: + if n == 0: + np += 1 + elif n == 1: + nf += 1 + else: + ne += 1 # format class description if cls.__module__ == "__main__": @@ -738,64 +730,65 @@ def _generate_report(self, result): desc = doc and '%s: %s' % (name, doc) or name row = self.REPORT_CLASS_TMPL % dict( - style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass', - desc = desc, - count = np+nf+ne, - Pass = np, - fail = nf, - error = ne, - cid = 'c%s' % (cid+1), + style=ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass', + desc=desc, + 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): + for tid, (n, t, o, e) in enumerate(cls_results): self._generate_report_test(rows, cid, tid, n, t, o, e) report = self.REPORT_TMPL % dict( - test_list = ''.join(rows), - count = str(result.success_count+result.failure_count+result.error_count), - Pass = str(result.success_count), - fail = str(result.failure_count), - error = str(result.error_count), + test_list=''.join(rows), + count=str(result.success_count + result.failure_count + result.error_count), + Pass=str(result.success_count), + fail=str(result.failure_count), + error=str(result.error_count), ) return report - def _generate_report_test(self, rows, cid, tid, n, t, o, e): # 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) + tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid + 1, tid + 1) name = t.id().split('.')[-1] doc = t.shortDescription() or "" desc = doc and ('%s: %s' % (name, doc)) or name tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL # o and e should be byte string because they are collected from stdout and stderr? - if isinstance(o,str): + 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') + # uo = o.decode('latin-1') + uo = o.decode('utf-8') else: uo = o - if isinstance(e,str): + 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') + # ue = e.decode('latin-1') + ue = e.decode('utf-8') else: ue = e script = self.REPORT_TEST_OUTPUT_TMPL % dict( - id = tid, - output = saxutils.escape(uo+ue), + id=tid, + output=saxutils.escape(uo + ue), ) row = tmpl % dict( - tid = tid, - Class = (n == 0 and 'hiddenRow' or 'none'), - style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'), - desc = desc, - script = script, - status = self.STATUS[n], + tid=tid, + Class=(n == 0 and 'hiddenRow' or 'none'), + style=n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'), + desc=desc, + script=script, + status=self.STATUS[n], ) rows.append(row) if not has_output: @@ -817,6 +810,7 @@ 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 @@ -825,6 +819,7 @@ def runTests(self): self.testRunner = HTMLTestRunner(verbosity=self.verbosity) unittest.TestProgram.runTests(self) + main = TestProgram ############################################################################## From 26cab08d1abf831768b936739aa7d31b929f2dd4 Mon Sep 17 00:00:00 2001 From: buweiqiang Date: Mon, 7 May 2018 13:41:03 +0800 Subject: [PATCH 2/2] 0.8.4: collect and show time cost of each test case --- HTMLTestRunner.py | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/HTMLTestRunner.py b/HTMLTestRunner.py index f58d98d..696ee16 100755 --- a/HTMLTestRunner.py +++ b/HTMLTestRunner.py @@ -65,10 +65,12 @@ # URL: http://tungwaiyip.info/software/HTMLTestRunner.html __author__ = "Wai Yip Tung" -__version__ = "0.8.3" +__version__ = "0.8.4" """ Change History +Version 0.8.4 +* Collect and show time cost of each test case (Weiqiang Bu). Version 0.8.3 * Prevent crash on class or module-level exceptions (Darren Wurf). @@ -470,7 +472,7 @@ class Template_mixin(object): REPORT_TEST_WITH_OUTPUT_TMPL = r"""
%(desc)s
- + @@ -488,15 +490,17 @@ class Template_mixin(object): + %(cost)s """ # variables: (tid, Class, style, desc, status) REPORT_TEST_NO_OUTPUT_TMPL = r"""
%(desc)s
- %(status)s + %(status)s + %(cost)s -""" # variables: (tid, Class, style, desc, status) +""" # variables: (tid, Class, style, desc, status,cost) REPORT_TEST_OUTPUT_TMPL = r""" %(id)s: %(output)s @@ -528,6 +532,8 @@ def __init__(self, verbosity=1): self.failure_count = 0 self.error_count = 0 self.verbosity = verbosity + self.start_time = 0 + self.time_cost = 0 # result is a list of result in 4 tuple # ( @@ -547,6 +553,7 @@ def startTest(self, test): self.stderr0 = sys.stderr sys.stdout = stdout_redirector sys.stderr = stderr_redirector + self.start_time = time.time() def complete_output(self): """ @@ -559,6 +566,7 @@ def complete_output(self): self.stdout0 = None self.stderr0 = None output = self.outputBuffer.getvalue() + # need clear buffer after each test self.outputBuffer.truncate(0) self.outputBuffer.seek(0) # self.outputBuffer.close() @@ -575,7 +583,8 @@ def addSuccess(self, test): self.success_count += 1 TestResult.addSuccess(self, test) output = self.complete_output() - self.result.append((0, test, output, '')) + self.time_cost = '{0} s'.format(round(time.time() - self.start_time, 2)) + self.result.append((0, test, output, '', self.time_cost)) if self.verbosity > 1: sys.stderr.write('ok ') sys.stderr.write(str(test)) @@ -588,7 +597,8 @@ def addError(self, test, err): TestResult.addError(self, test, err) _, _exc_str = self.errors[-1] output = self.complete_output() - self.result.append((2, test, output, _exc_str)) + self.time_cost = '{0} s'.format(round(time.time() - self.start_time, 2)) + self.result.append((2, test, output, _exc_str, self.time_cost)) if self.verbosity > 1: sys.stderr.write('E ') sys.stderr.write(str(test)) @@ -601,7 +611,8 @@ def addFailure(self, test, err): TestResult.addFailure(self, test, err) _, _exc_str = self.failures[-1] output = self.complete_output() - self.result.append((1, test, output, _exc_str)) + self.time_cost = '{0} s'.format(round(time.time() - self.start_time, 2)) + self.result.append((1, test, output, _exc_str, self.time_cost)) if self.verbosity > 1: sys.stderr.write('F ') sys.stderr.write(str(test)) @@ -642,12 +653,12 @@ def sortResult(self, result_list): # Here at least we want to group them together by class. rmap = {} classes = [] - for n, t, o, e in result_list: + for n, t, o, e, c in result_list: cls = t.__class__ if not rmap.has_key(cls): rmap[cls] = [] classes.append(cls) - rmap[cls].append((n, t, o, e)) + rmap[cls].append((n, t, o, e, c)) r = [(cls, rmap[cls]) for cls in classes] return r @@ -713,7 +724,7 @@ def _generate_report(self, result): for cid, (cls, cls_results) in enumerate(sortedResult): # subtotal for a class np = nf = ne = 0 - for n, t, o, e in cls_results: + for n, t, o, e, c in cls_results: if n == 0: np += 1 elif n == 1: @@ -740,8 +751,8 @@ def _generate_report(self, result): ) rows.append(row) - for tid, (n, t, o, e) in enumerate(cls_results): - self._generate_report_test(rows, cid, tid, n, t, o, e) + for tid, (n, t, o, e, cost) in enumerate(cls_results): + self._generate_report_test(rows, cid, tid, n, t, o, e, cost) report = self.REPORT_TMPL % dict( test_list=''.join(rows), @@ -752,7 +763,7 @@ def _generate_report(self, result): ) return report - def _generate_report_test(self, rows, cid, tid, n, t, o, e): + def _generate_report_test(self, rows, cid, tid, n, t, o, e, cost=0): # 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) @@ -789,6 +800,7 @@ def _generate_report_test(self, rows, cid, tid, n, t, o, e): desc=desc, script=script, status=self.STATUS[n], + cost=cost ) rows.append(row) if not has_output: