Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTMLTestRunner3 #17

Open
wants to merge 37 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
da89717
Updated for python3 (test_HTMLTestRunner.py still work in progress)
dash0002 Nov 12, 2015
c8864fc
no message
dash0002 Nov 12, 2015
acc6822
Update README
dash0002 Nov 12, 2015
e368abb
Updated content
dash0002 Nov 13, 2015
d96728e
Added version
dash0002 Dec 1, 2015
9c75c10
Added Vagrantfile representing environment
dash0002 Dec 1, 2015
0330af6
Refactored testing
dash0002 Dec 2, 2015
64c1df7
Updated for markdown
dash0002 Dec 2, 2015
3ede4b3
Merge pull request #1 from dash0002/python3
dash0002 Dec 3, 2015
7f660b4
Reduce namespace polution by only importing specific functions.
DhruvParanjape Aug 13, 2016
31e5b3e
Fix sys.stdout and sys.stderr import derp
DhruvParanjape Aug 13, 2016
57e2435
fix remaining sys import derp
DhruvParanjape Aug 13, 2016
5b45128
Update CHANGELOG.md
DhruvParanjape Aug 16, 2016
fefec8e
Update README.md
DhruvParanjape Aug 16, 2016
4e495bc
remove unused _ variable assignment and remove unessary string slice
DhruvParanjape Aug 17, 2016
1a55050
Update CHANGELOG.md
DhruvParanjape Aug 17, 2016
e92a5fe
Merge pull request #3 from Dark-Passenger/master
dash0002 Aug 19, 2016
11417ae
Update CHANGELOG.md
dash0002 Aug 19, 2016
002f6e6
Add support for reporting skipped tests
DhruvParanjape Aug 19, 2016
d7b35c5
Update CHANGELOG.md
DhruvParanjape Aug 19, 2016
8657457
Update README.md
DhruvParanjape Aug 19, 2016
dc7c652
Update README.md
DhruvParanjape Aug 19, 2016
67e2f5b
fix derp.
DhruvParanjape Aug 20, 2016
745a576
Update CHANGELOG.md
DhruvParanjape Aug 20, 2016
53fbeb3
Add a skip test
DhruvParanjape Aug 20, 2016
754b850
minor import cleanup and add skip testcase
DhruvParanjape Aug 20, 2016
85c4f72
Merge remote-tracking branch 'upstream/master'
DhruvParanjape Oct 25, 2016
abd5e4d
Add support for reporting skipped tests
eestrada Aug 19, 2016
5eef4b8
Update CHANGELOG.md
DhruvParanjape Aug 19, 2016
f01ddb2
Update README.md
DhruvParanjape Aug 19, 2016
e14cb22
Update README.md
DhruvParanjape Aug 19, 2016
7c97f9c
fix derp.
DhruvParanjape Aug 20, 2016
fe4b566
Update CHANGELOG.md
DhruvParanjape Aug 20, 2016
bb152d3
Add a skip test
DhruvParanjape Aug 20, 2016
97085a4
minor import cleanup and add skip testcase
DhruvParanjape Aug 20, 2016
25329c0
Merge branch 'master' of github.com:Dark-Passenger/HTMLTestRunner
DhruvParanjape Oct 25, 2016
dc18be2
Merge pull request #4 from Dark-Passenger/master
dash0002 Oct 25, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.pyc
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Change Log
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/) and http://keepachangelog.com/.

## [Unreleased]
### Added
- CHANGELOG.md from @dash0002

## [1.0.0]
### Changed
- Moved license content from python file to LICENSE
- Updated README to reflect current state of project
- Upgraded to support Python 3.4.3

## [1.0.1]
### Changed
- Imported select functions only rather than the entire module.
- remove unused variables.
- Remove string slice and use a datetime function instead.
- Now compatible with python 3.5.2
- Added Contributor Dark-Passenger to Readme

## [1.0.2]
### Added
- Added support for skip tests.
173 changes: 50 additions & 123 deletions HTMLTestRunner.py
Original file line number Diff line number Diff line change
@@ -1,110 +1,16 @@
"""
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()


For more customization options, instantiates a HTMLTestRunner object.
HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.

# output to a file
fp = file('my_report.html', 'wb')
runner = HTMLTestRunner.HTMLTestRunner(
stream=fp,
title='My unit test',
description='This demonstrates the report output by HTMLTestRunner.'
)

# Use an external stylesheet.
# See the Template_mixin class for more customizable options
runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'

# run the test
runner.run(my_test_suite)


------------------------------------------------------------------------
Copyright (c) 2004-2007, 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 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.
"""

# URL: http://tungwaiyip.info/software/HTMLTestRunner.html

__author__ = "Wai Yip Tung"
__version__ = "0.8.3"


"""
Change History
__version__ = "1.0.2"

Version 0.8.3
* Prevent crash on class or module-level exceptions (Darren Wurf).

Version 0.8.2
* Show output inline instead of popup window (Viorel Lupu).

Version in 0.8.1
* Validated XHTML (Wolfgang Borgert).
* Added description of test classes and test cases.

Version in 0.8.0
* Define Template_mixin class for customization.
* Workaround a IE 6 bug that it does not treat <script> block as CDATA.

Version in 0.7.1
* Back port to Python 2.3 (Frank Horowitz).
* Fix missing scroll bars in detail log (Podi).
"""

# TODO: color stderr
# TODO: simplify javascript using ,ore than 1 class in the class attribute?

import datetime
import StringIO
import sys
import time
import unittest
from datetime import datetime
from io import StringIO
from unittest import TestResult, TestProgram
from xml.sax import saxutils
import sys


# ------------------------------------------------------------------------
# The redirectors below are 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
# sent to stdout and stderr are automatically captured. However
# in some cases 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.
#
Expand All @@ -114,7 +20,7 @@

def to_unicode(s):
try:
return unicode(s)
return str(s)
except UnicodeDecodeError:
# s is non ascii byte string
return s.decode('unicode_escape')
Expand Down Expand Up @@ -186,6 +92,7 @@ class Template_mixin(object):
0: 'pass',
1: 'fail',
2: 'error',
3: 'skip',
}

DEFAULT_TITLE = 'Unit Test Report'
Expand Down Expand Up @@ -387,8 +294,10 @@ class Template_mixin(object):
.passClass { background-color: #6c6; }
.failClass { background-color: #c60; }
.errorClass { background-color: #c00; }
.skipClass { background-color: #ff0; }
.passCase { color: #6c6; }
.failCase { color: #c60; font-weight: bold; }
.skipCase { color: #ff0; font-weight: bold; }
.errorCase { color: #c00; font-weight: bold; }
.hiddenRow { display: none; }
.testcase { margin-left: 2em; }
Expand Down Expand Up @@ -443,6 +352,7 @@ class Template_mixin(object):
<td>Test Group/Test case</td>
<td>Count</td>
<td>Pass</td>
<td>Skip</td>
<td>Fail</td>
<td>Error</td>
<td>View</td>
Expand All @@ -452,6 +362,7 @@ class Template_mixin(object):
<td>Total</td>
<td>%(count)s</td>
<td>%(Pass)s</td>
<td>%(skip)s</td>
<td>%(fail)s</td>
<td>%(error)s</td>
<td>&nbsp;</td>
Expand All @@ -464,6 +375,7 @@ class Template_mixin(object):
<td>%(desc)s</td>
<td>%(count)s</td>
<td>%(Pass)s</td>
<td>%(skip)s</td>
<td>%(fail)s</td>
<td>%(error)s</td>
<td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</a></td>
Expand Down Expand Up @@ -519,18 +431,17 @@ class Template_mixin(object):
# -------------------- 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.

def __init__(self, verbosity=1):
TestResult.__init__(self)
self.outputBuffer = StringIO.StringIO()
self.outputBuffer = StringIO()
self.stdout0 = None
self.stderr0 = None
self.success_count = 0
self.skip_count = 0
self.failure_count = 0
self.error_count = 0
self.verbosity = verbosity
Expand Down Expand Up @@ -591,7 +502,7 @@ def addSuccess(self, test):
def addError(self, test, err):
self.error_count += 1
TestResult.addError(self, test, err)
_, _exc_str = self.errors[-1]
_exc_str = self.errors[-1][1]
output = self.complete_output()
self.result.append((2, test, output, _exc_str))
if self.verbosity > 1:
Expand All @@ -604,7 +515,7 @@ def addError(self, test, err):
def addFailure(self, test, err):
self.failure_count += 1
TestResult.addFailure(self, test, err)
_, _exc_str = self.failures[-1]
_exc_str = self.failures[-1][1]
output = self.complete_output()
self.result.append((1, test, output, _exc_str))
if self.verbosity > 1:
Expand All @@ -614,6 +525,19 @@ def addFailure(self, test, err):
else:
sys.stderr.write('F')

def addSkip(self, test, err):
self.skip_count += 1
TestResult.addSkip(self, test, err)
_exc_str = self.skipped[-1][1]
output = self.complete_output()
self.result.append((3, test, output, _exc_str))
if self.verbosity > 1:
sys.stderr.write('S ')
sys.stderr.write(str(test))
sys.stderr.write('\n')
else:
sys.stderr.write('S')


class HTMLTestRunner(Template_mixin):
"""
Expand All @@ -630,16 +554,16 @@ def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None)
else:
self.description = description

self.startTime = datetime.datetime.now()
self.startTime = datetime.now().replace(microsecond=0)


def run(self, test):
"Run the given test case or test suite."
result = _TestResult(self.verbosity)
test(result)
self.stopTime = datetime.datetime.now()
self.stopTime = datetime.now().replace(microsecond=0)
self.generateReport(test, result)
print >>sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)
print('Time Elapsed: {}'.format((self.stopTime-self.startTime)), file=sys.stderr)
return result


Expand All @@ -650,7 +574,7 @@ def sortResult(self, result_list):
classes = []
for n,t,o,e in result_list:
cls = t.__class__
if not rmap.has_key(cls):
if not cls in rmap:
rmap[cls] = []
classes.append(cls)
rmap[cls].append((n,t,o,e))
Expand All @@ -663,10 +587,11 @@ def getReportAttributes(self, result):
Return report attributes as a list of (name, value).
Override this to add custom attributes.
"""
startTime = str(self.startTime)[:19]
startTime = str(self.startTime)
duration = str(self.stopTime - self.startTime)
status = []
if result.success_count: status.append('Pass %s' % result.success_count)
if result.skip_count: status.append('Skip %s' % result.skip_count )
if result.failure_count: status.append('Failure %s' % result.failure_count)
if result.error_count: status.append('Error %s' % result.error_count )
if status:
Expand Down Expand Up @@ -695,7 +620,7 @@ def generateReport(self, test, result):
report = report,
ending = ending,
)
self.stream.write(output.encode('utf8'))
self.stream.write(output)


def _generate_stylesheet(self):
Expand Down Expand Up @@ -723,11 +648,12 @@ def _generate_report(self, result):
sortedResult = self.sortResult(result.result)
for cid, (cls, cls_results) in enumerate(sortedResult):
# subtotal for a class
np = nf = ne = 0
np = ns = nf = ne = 0
for n,t,o,e in cls_results:
if n == 0: np += 1
elif n == 1: nf += 1
else: ne += 1
elif n == 2: ne += 1
elif n == 3: ns += 1

# format class description
if cls.__module__ == "__main__":
Expand All @@ -738,9 +664,9 @@ 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',
style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or ns > 0 and 'skipClass' or 'passClass',
desc = desc,
count = np+nf+ne,
count = np+ns+nf+ne,
Pass = np,
fail = nf,
error = ne,
Expand All @@ -753,8 +679,9 @@ def _generate_report(self, result):

report = self.REPORT_TMPL % dict(
test_list = ''.join(rows),
count = str(result.success_count+result.failure_count+result.error_count),
count = str(result.success_count+result.failure_count+result.error_count+result.skip_count),
Pass = str(result.success_count),
skip = str(result.skip_count),
fail = str(result.failure_count),
error = str(result.error_count),
)
Expand All @@ -774,13 +701,13 @@ def _generate_report_test(self, rows, cid, tid, n, t, o, e):
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 = bytes(o, 'utf-8').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')
ue = bytes(e, 'utf-8').decode('latin-1')
else:
ue = e

Expand Down Expand Up @@ -812,7 +739,7 @@ def _generate_ending(self):
# 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):
class _TestProgram(TestProgram):
"""
A variation of the unittest.TestProgram. Please refer to the base
class for command line parameters.
Expand All @@ -823,9 +750,9 @@ def runTests(self):
# 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)
TestProgram.runTests(self)

main = TestProgram
main = _TestProgram

##############################################################################
# Executing this module from the command line
Expand Down
Loading