Initial commit.
This commit is contained in:
commit
235a5ac6ee
|
@ -0,0 +1,774 @@
|
||||||
|
"""
|
||||||
|
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-2006, 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.1"
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Changes in 0.8.1
|
||||||
|
* Thank you for Wolfgang Borgert's patch.
|
||||||
|
* Validated XHTML.
|
||||||
|
* Added description of test classes and test cases.
|
||||||
|
|
||||||
|
Changes in 0.8.0
|
||||||
|
* Define Template_mixin class for customization.
|
||||||
|
* Workaround a IE 6 bug that it does not treat <script> block as CDATA.
|
||||||
|
|
||||||
|
Changes in 0.7.1
|
||||||
|
* Back port to Python 2.3. Thank you Frank Horowitz.
|
||||||
|
* Fix missing scroll bars in detail log. Thank you 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 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
|
||||||
|
|
||||||
|
class Template_mixin(object):
|
||||||
|
"""
|
||||||
|
Define a HTML template for report customerization and generation.
|
||||||
|
|
||||||
|
Overall structure of an HTML report
|
||||||
|
|
||||||
|
HTML
|
||||||
|
+------------------------+
|
||||||
|
|<html> |
|
||||||
|
| <head> |
|
||||||
|
| |
|
||||||
|
| STYLESHEET |
|
||||||
|
| +----------------+ |
|
||||||
|
| | | |
|
||||||
|
| +----------------+ |
|
||||||
|
| |
|
||||||
|
| </head> |
|
||||||
|
| |
|
||||||
|
| <body> |
|
||||||
|
| |
|
||||||
|
| HEADING |
|
||||||
|
| +----------------+ |
|
||||||
|
| | | |
|
||||||
|
| +----------------+ |
|
||||||
|
| |
|
||||||
|
| REPORT |
|
||||||
|
| +----------------+ |
|
||||||
|
| | | |
|
||||||
|
| +----------------+ |
|
||||||
|
| |
|
||||||
|
| ENDING |
|
||||||
|
| +----------------+ |
|
||||||
|
| | | |
|
||||||
|
| +----------------+ |
|
||||||
|
| |
|
||||||
|
| </body> |
|
||||||
|
|</html> |
|
||||||
|
+------------------------+
|
||||||
|
"""
|
||||||
|
|
||||||
|
STATUS = {
|
||||||
|
0: 'pass',
|
||||||
|
1: 'fail',
|
||||||
|
2: 'error',
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_TITLE = 'Unit Test Report'
|
||||||
|
DEFAULT_DESCRIPTION = ''
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# HTML Template
|
||||||
|
|
||||||
|
HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<title>%(title)s</title>
|
||||||
|
<meta name="generator" content="%(generator)s"/>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
|
%(stylesheet)s
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="text/javascript"><!--
|
||||||
|
output_list = Array();
|
||||||
|
|
||||||
|
/* level - 0:Summary; 1:Failed; 2:All */
|
||||||
|
function showCase(level) {
|
||||||
|
trs = document.getElementsByTagName("tr");
|
||||||
|
for (var i = 0; i < trs.length; i++) {
|
||||||
|
tr = trs[i];
|
||||||
|
id = tr.id;
|
||||||
|
if (id.substr(0,2) == 'ft') {
|
||||||
|
if (level < 1) {
|
||||||
|
tr.className = 'hiddenRow';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tr.className = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (id.substr(0,2) == 'pt') {
|
||||||
|
if (level > 1) {
|
||||||
|
tr.className = '';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tr.className = 'hiddenRow';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showClassDetail(cid, count) {
|
||||||
|
var id_list = Array(count);
|
||||||
|
var toHide = 1;
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
tid0 = 't' + cid.substr(1) + '.' + (i+1);
|
||||||
|
tid = 'f' + tid0;
|
||||||
|
tr = document.getElementById(tid);
|
||||||
|
if (!tr) {
|
||||||
|
tid = 'p' + tid0;
|
||||||
|
tr = document.getElementById(tid);
|
||||||
|
}
|
||||||
|
id_list[i] = tid;
|
||||||
|
if (tr.className) {
|
||||||
|
toHide = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
tid = id_list[i];
|
||||||
|
if (toHide) {
|
||||||
|
document.getElementById(tid).className = 'hiddenRow';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document.getElementById(tid).className = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function html_escape(s) {
|
||||||
|
s = s.replace(/&/g,'&');
|
||||||
|
s = s.replace(/</g,'<');
|
||||||
|
s = s.replace(/>/g,'>');
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showOutput(id, name) {
|
||||||
|
var w = window.open("", //url
|
||||||
|
name,
|
||||||
|
"resizable,scrollbars,status,width=800,height=450");
|
||||||
|
d = w.document;
|
||||||
|
d.write("<pre>");
|
||||||
|
d.write(html_escape(output_list[id]));
|
||||||
|
d.write("\n");
|
||||||
|
d.write("<a href='javascript:window.close()'>close</a>\n");
|
||||||
|
d.write("</pre>\n");
|
||||||
|
d.close();
|
||||||
|
}
|
||||||
|
--></script>
|
||||||
|
|
||||||
|
%(heading)s
|
||||||
|
%(report)s
|
||||||
|
%(ending)s
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
# variables: (title, generator, stylesheet, heading, report, ending)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# Stylesheet
|
||||||
|
#
|
||||||
|
# alternatively use a <link> for external style sheet, e.g.
|
||||||
|
# <link rel="stylesheet" href="$url" type="text/css">
|
||||||
|
|
||||||
|
STYLESHEET_TMPL = """
|
||||||
|
<style type="text/css" media="screen">
|
||||||
|
body { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }
|
||||||
|
table { font-size: 100%; }
|
||||||
|
pre { }
|
||||||
|
|
||||||
|
/* -- heading ---------------------------------------------------------------------- */
|
||||||
|
h1 {
|
||||||
|
}
|
||||||
|
.heading {
|
||||||
|
margin-top: 0ex;
|
||||||
|
margin-bottom: 1ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading .attribute {
|
||||||
|
margin-top: 1ex;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading .description {
|
||||||
|
margin-top: 4ex;
|
||||||
|
margin-bottom: 6ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- report ------------------------------------------------------------------------ */
|
||||||
|
#show_detail_line {
|
||||||
|
margin-top: 3ex;
|
||||||
|
margin-bottom: 1ex;
|
||||||
|
}
|
||||||
|
#result_table {
|
||||||
|
width: 80%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: medium solid #777;
|
||||||
|
}
|
||||||
|
#header_row {
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
background-color: #777;
|
||||||
|
}
|
||||||
|
#result_table td {
|
||||||
|
border: thin solid #777;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
#total_row { font-weight: bold; }
|
||||||
|
.passClass { background-color: #6c6; }
|
||||||
|
.failClass { background-color: #c60; }
|
||||||
|
.errorClass { background-color: #c00; }
|
||||||
|
.passCase { color: #6c6; }
|
||||||
|
.failCase { color: #c60; font-weight: bold; }
|
||||||
|
.errorCase { color: #c00; font-weight: bold; }
|
||||||
|
.hiddenRow { display: none; }
|
||||||
|
.testcase { margin-left: 2em; }
|
||||||
|
|
||||||
|
|
||||||
|
/* -- ending ---------------------------------------------------------------------- */
|
||||||
|
#ending {
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# Heading
|
||||||
|
#
|
||||||
|
|
||||||
|
HEADING_TMPL = """<div class='heading'>
|
||||||
|
<h1>%(title)s</h1>
|
||||||
|
%(parameters)s
|
||||||
|
<p class='description'>%(description)s</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
""" # variables: (title, parameters, description)
|
||||||
|
|
||||||
|
HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
|
||||||
|
""" # variables: (name, value)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# Report
|
||||||
|
#
|
||||||
|
|
||||||
|
REPORT_TMPL = """
|
||||||
|
<p id='show_detail_line'>Show
|
||||||
|
<a href='javascript:showCase(0)'>Summary</a>
|
||||||
|
<a href='javascript:showCase(1)'>Failed</a>
|
||||||
|
<a href='javascript:showCase(2)'>All</a>
|
||||||
|
</p>
|
||||||
|
<table id='result_table'>
|
||||||
|
<colgroup>
|
||||||
|
<col align='left' />
|
||||||
|
<col align='right' />
|
||||||
|
<col align='right' />
|
||||||
|
<col align='right' />
|
||||||
|
<col align='right' />
|
||||||
|
<col align='right' />
|
||||||
|
</colgroup>
|
||||||
|
<tr id='header_row'>
|
||||||
|
<td>Test Group/Test case</td>
|
||||||
|
<td>Count</td>
|
||||||
|
<td>Pass</td>
|
||||||
|
<td>Fail</td>
|
||||||
|
<td>Error</td>
|
||||||
|
<td>View</td>
|
||||||
|
</tr>
|
||||||
|
%(test_list)s
|
||||||
|
<tr id='total_row'>
|
||||||
|
<td>Total</td>
|
||||||
|
<td>%(count)s</td>
|
||||||
|
<td>%(Pass)s</td>
|
||||||
|
<td>%(fail)s</td>
|
||||||
|
<td>%(error)s</td>
|
||||||
|
<td> </td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
""" # variables: (test_list, count, Pass, fail, error)
|
||||||
|
|
||||||
|
REPORT_CLASS_TMPL = r"""
|
||||||
|
<tr class='%(style)s'>
|
||||||
|
<td>%(desc)s</td>
|
||||||
|
<td>%(count)s</td>
|
||||||
|
<td>%(Pass)s</td>
|
||||||
|
<td>%(fail)s</td>
|
||||||
|
<td>%(error)s</td>
|
||||||
|
<td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</a></td>
|
||||||
|
</tr>
|
||||||
|
""" # variables: (style, desc, count, Pass, fail, error, cid)
|
||||||
|
|
||||||
|
|
||||||
|
REPORT_TEST_WITH_OUTPUT_TMPL = r"""
|
||||||
|
<tr id='%(tid)s' class='%(Class)s'>
|
||||||
|
<td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
|
||||||
|
<td colspan='5' align='center'><a href="javascript:showOutput('%(tid)s', '%(desc)s')">%(status)s</a>%(script)s</td>
|
||||||
|
</tr>
|
||||||
|
""" # variables: (tid, Class, style, desc, status)
|
||||||
|
|
||||||
|
|
||||||
|
REPORT_TEST_NO_OUTPUT_TMPL = r"""
|
||||||
|
<tr id='%(tid)s' class='%(Class)s'>
|
||||||
|
<td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
|
||||||
|
<td colspan='5' align='center'>%(status)s</td>
|
||||||
|
</tr>
|
||||||
|
""" # variables: (tid, Class, style, desc, status)
|
||||||
|
|
||||||
|
|
||||||
|
REPORT_TEST_OUTPUT_TMPL = r"""
|
||||||
|
<script language="javascript" type="text/javascript">output_list['%(id)s'] = '%(output)s';</script>
|
||||||
|
""" # variables: (id, output)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# ENDING
|
||||||
|
#
|
||||||
|
|
||||||
|
ENDING_TMPL = """<div id='ending'> </div>"""
|
||||||
|
|
||||||
|
# -------------------- The end of the Template class -------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def jsEscapeString(s):
|
||||||
|
""" Escape s for use as a Javascript String """
|
||||||
|
return s.replace('\\','\\\\') \
|
||||||
|
.replace('\r', '\\r') \
|
||||||
|
.replace('\n', '\\n') \
|
||||||
|
.replace('"', '\\"') \
|
||||||
|
.replace("'", "\\'") \
|
||||||
|
.replace("&", '\\x26') \
|
||||||
|
.replace("<", '\\x3C') \
|
||||||
|
.replace(">", '\\x3E')
|
||||||
|
# Note: non-ascii unicode characters do not need to be encoded
|
||||||
|
# Note: previously we encode < as <, etc. However IE6 fail to treat <script> block as CDATA.
|
||||||
|
|
||||||
|
|
||||||
|
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.success_count = 0
|
||||||
|
self.failure_count = 0
|
||||||
|
self.error_count = 0
|
||||||
|
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):
|
||||||
|
self.success_count += 1
|
||||||
|
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):
|
||||||
|
self.error_count += 1
|
||||||
|
TestResult.addError(self, test, err)
|
||||||
|
_, _exc_str = self.errors[-1]
|
||||||
|
output = self.complete_output()
|
||||||
|
self.result.append((2, test, output, _exc_str))
|
||||||
|
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):
|
||||||
|
self.failure_count += 1
|
||||||
|
TestResult.addFailure(self, test, err)
|
||||||
|
_, _exc_str = self.failures[-1]
|
||||||
|
output = self.complete_output()
|
||||||
|
self.result.append((1, test, output, _exc_str))
|
||||||
|
if self.verbosity > 1:
|
||||||
|
sys.stderr.write('F ')
|
||||||
|
sys.stderr.write(str(test))
|
||||||
|
sys.stderr.write('\n')
|
||||||
|
else:
|
||||||
|
sys.stderr.write('F')
|
||||||
|
|
||||||
|
|
||||||
|
class HTMLTestRunner(Template_mixin):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None):
|
||||||
|
self.stream = stream
|
||||||
|
self.verbosity = verbosity
|
||||||
|
if title is None:
|
||||||
|
self.title = self.DEFAULT_TITLE
|
||||||
|
else:
|
||||||
|
self.title = title
|
||||||
|
if description is None:
|
||||||
|
self.description = self.DEFAULT_DESCRIPTION
|
||||||
|
else:
|
||||||
|
self.description = description
|
||||||
|
|
||||||
|
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)
|
||||||
|
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 getReportAttributes(self, result):
|
||||||
|
"""
|
||||||
|
Return report attributes as a list of (name, value).
|
||||||
|
Override this to add custom attributes.
|
||||||
|
"""
|
||||||
|
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.failure_count: status.append('Failure %s' % result.failure_count)
|
||||||
|
if result.error_count: status.append('Error %s' % result.error_count )
|
||||||
|
if status:
|
||||||
|
status = ' '.join(status)
|
||||||
|
else:
|
||||||
|
status = 'none'
|
||||||
|
return [
|
||||||
|
('Start Time', startTime),
|
||||||
|
('Duration', duration),
|
||||||
|
('Status', status),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def generateReport(self, test, result):
|
||||||
|
report_attrs = self.getReportAttributes(result)
|
||||||
|
generator = 'HTMLTestRunner %s' % __version__
|
||||||
|
stylesheet = self._generate_stylesheet()
|
||||||
|
heading = self._generate_heading(report_attrs)
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
a_lines.append(line)
|
||||||
|
heading = self.HEADING_TMPL % dict(
|
||||||
|
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
|
||||||
|
|
||||||
|
# format class description
|
||||||
|
if cls.__module__ == "__main__":
|
||||||
|
name = cls.__name__
|
||||||
|
else:
|
||||||
|
name = "%s.%s" % (cls.__module__, cls.__name__)
|
||||||
|
doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
rows.append(row)
|
||||||
|
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
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):
|
||||||
|
# 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
|
||||||
|
|
||||||
|
script = self.REPORT_TEST_OUTPUT_TMPL % dict(
|
||||||
|
id = tid,
|
||||||
|
output = jsEscapeString(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],
|
||||||
|
)
|
||||||
|
rows.append(row)
|
||||||
|
if not has_output:
|
||||||
|
return
|
||||||
|
|
||||||
|
def _generate_ending(self):
|
||||||
|
return self.ENDING_TMPL
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# 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)
|
|
@ -0,0 +1,13 @@
|
||||||
|
To run the tests you should use runtests.py. For that to work you need to create
|
||||||
|
a config file at ~/.tryton-tests.cfg with the following sections::
|
||||||
|
|
||||||
|
[branch-name-1]
|
||||||
|
trytond = /path/to/trytond-1
|
||||||
|
proteus = /path/to/proteus-1
|
||||||
|
|
||||||
|
[branch-name-2]
|
||||||
|
trytond = /path/to/trytond-2
|
||||||
|
proteus = /path/to/proteus-2
|
||||||
|
|
||||||
|
Executing runtests.py will execute the tests against all branches. If you want
|
||||||
|
to run test against only one of them you can use the -b <branch> parameter.
|
|
@ -0,0 +1,91 @@
|
||||||
|
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||||
|
#this repository contains the full copyright notices and license terms.
|
||||||
|
[options]
|
||||||
|
|
||||||
|
# This is the hostname used when generating tryton URI
|
||||||
|
#hostname =
|
||||||
|
|
||||||
|
# Activate the json-rpc protocol
|
||||||
|
jsonrpc = localhost:8000
|
||||||
|
#ssl_jsonrpc = False
|
||||||
|
|
||||||
|
# Configure the path of json-rpc data
|
||||||
|
#jsondata_path = /var/www/localhost/tryton
|
||||||
|
|
||||||
|
# Activate the xml-rpc protocol
|
||||||
|
#xmlrpc = *:8069
|
||||||
|
#ssl_xmlrpc = False
|
||||||
|
|
||||||
|
# Activate the webdav protocol
|
||||||
|
#webdav = *:8080
|
||||||
|
#ssl_webdav = False
|
||||||
|
|
||||||
|
# Configure the database type
|
||||||
|
# allowed values are postgresql, sqlite, mysql
|
||||||
|
db_type = postgresql
|
||||||
|
|
||||||
|
# Configure the database connection
|
||||||
|
## Note: Only databases owned by db_user will be displayed in the connection dialog
|
||||||
|
## of the Tryton client. db_user must have create permission for new databases
|
||||||
|
## to be able to use automatic database creation with the Tryton client.
|
||||||
|
#db_host = False
|
||||||
|
#db_port = False
|
||||||
|
#db_user = False
|
||||||
|
#db_password = False
|
||||||
|
#db_minconn = 1
|
||||||
|
#db_maxconn = 64
|
||||||
|
|
||||||
|
# Configure the postgresql path for the executable
|
||||||
|
#pg_path = None
|
||||||
|
|
||||||
|
# Configure the Tryton server password
|
||||||
|
#admin_passwd = admin
|
||||||
|
|
||||||
|
# Configure the path of the files for the pid and the logs
|
||||||
|
#pidfile = False
|
||||||
|
#logfile = False
|
||||||
|
|
||||||
|
#privatekey = server.pem
|
||||||
|
#certificate = server.pem
|
||||||
|
|
||||||
|
# Configure the SMTP connection
|
||||||
|
#smtp_server = localhost
|
||||||
|
#smtp_port = 25
|
||||||
|
#smtp_ssl = False
|
||||||
|
#smtp_tls = False
|
||||||
|
#smtp_password = False
|
||||||
|
#smtp_user = False
|
||||||
|
|
||||||
|
# Configure the path to store attachments and sqlite database
|
||||||
|
data_path = /tmp
|
||||||
|
|
||||||
|
# Allow to run more than one instance of trytond
|
||||||
|
#multi_server = False
|
||||||
|
|
||||||
|
# Configure the session timeout (inactivity of the client in sec)
|
||||||
|
#session_timeout = 600
|
||||||
|
|
||||||
|
# Enable psyco module
|
||||||
|
# Need to have psyco installed http://psyco.sourceforge.net/
|
||||||
|
#psyco = False
|
||||||
|
|
||||||
|
# Enable auto-reload of modules if changed
|
||||||
|
#auto_reload = True
|
||||||
|
|
||||||
|
# Prevent database listing
|
||||||
|
#prevent_dblist = False
|
||||||
|
|
||||||
|
# Enable cron
|
||||||
|
# cron = True
|
||||||
|
|
||||||
|
# unoconv connection
|
||||||
|
#unoconv = pipe,name=trytond;urp;StarOffice.ComponentContext
|
||||||
|
|
||||||
|
# Number of retries on database operational error
|
||||||
|
# retry = 5
|
||||||
|
|
||||||
|
# Default database language code
|
||||||
|
# language = en_US
|
||||||
|
|
||||||
|
# Timezone of the server
|
||||||
|
# timezone = False
|
|
@ -0,0 +1,240 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
import subprocess
|
||||||
|
import getpass
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import optparse
|
||||||
|
import ConfigParser
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
parser = optparse.OptionParser()
|
||||||
|
parser.add_option("-b", "--branch", dest="branch", help="specify branch")
|
||||||
|
parser.add_option("-s", "--sqlite-only", action='store_true',
|
||||||
|
dest='sqlite_only')
|
||||||
|
parser.add_option("-p", "--pgsql-only", action='store_true', dest='pgsql_only')
|
||||||
|
parser.add_option("-c", "--coverage", action='store_true', dest='coverage')
|
||||||
|
|
||||||
|
(options, _) = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def get_settings(parser):
|
||||||
|
settings = {}
|
||||||
|
duplicates = []
|
||||||
|
for section in parser.sections():
|
||||||
|
usection = unicode(section, 'utf-8')
|
||||||
|
settings[usection] = {}
|
||||||
|
for name, value, in parser.items(section):
|
||||||
|
settings[usection][name] = value
|
||||||
|
return settings
|
||||||
|
|
||||||
|
exec_path = os.getcwd()
|
||||||
|
menu_path = os.path.split(__file__)[0]
|
||||||
|
rc_path = '%s/.tryton-tests.cfg' % os.getenv('HOME')
|
||||||
|
|
||||||
|
parser = ConfigParser.ConfigParser()
|
||||||
|
parser.read(rc_path)
|
||||||
|
settings = get_settings(parser)
|
||||||
|
|
||||||
|
|
||||||
|
def html_filename(branch, config):
|
||||||
|
filename = '%s-%s-coverage' % (branch, config)
|
||||||
|
filename = '/home/%s/public_html/%s.html' % (getpass.getuser(), filename)
|
||||||
|
return filename
|
||||||
|
|
||||||
|
def run(args, env):
|
||||||
|
process = subprocess.Popen(args, env=env)
|
||||||
|
process.wait()
|
||||||
|
|
||||||
|
def check_output(args, env=None):
|
||||||
|
process = subprocess.Popen(args, env=env, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE)
|
||||||
|
process.wait()
|
||||||
|
data = process.stdout.read()
|
||||||
|
return data
|
||||||
|
|
||||||
|
def runtest(path, branch, config, env, coverage):
|
||||||
|
parameters = ['python', 'test.py', '--name', branch, '--config',
|
||||||
|
'%s.conf' % config]
|
||||||
|
if coverage:
|
||||||
|
parameters.append('--coverage')
|
||||||
|
run(parameters, env)
|
||||||
|
if not coverage:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Process coverage information
|
||||||
|
output = check_output(['python-coverage', 'report'])
|
||||||
|
records = {}
|
||||||
|
total_lines = 0
|
||||||
|
total_covered = 0
|
||||||
|
for line in output.splitlines():
|
||||||
|
if 'trytond' in line:
|
||||||
|
item = re.split(' +', line)
|
||||||
|
filename = item[0]
|
||||||
|
try:
|
||||||
|
lines = int(item[1])
|
||||||
|
uncovered = int(item[2])
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
covered = lines - uncovered
|
||||||
|
key = filename
|
||||||
|
if key.startswith(trytond_path):
|
||||||
|
key = key[len(trytond_path):]
|
||||||
|
if not key in records:
|
||||||
|
records[key] = (0, 0)
|
||||||
|
lines += records[key][0]
|
||||||
|
covered += records[key][1]
|
||||||
|
if lines == 0.0:
|
||||||
|
coverage = 100.0
|
||||||
|
else:
|
||||||
|
coverage = 100.0 * float(covered) / float(lines)
|
||||||
|
records[key] = (lines, covered, coverage)
|
||||||
|
total_lines += lines
|
||||||
|
total_covered += covered
|
||||||
|
|
||||||
|
if total_lines == 0.0:
|
||||||
|
coverage = 100
|
||||||
|
else:
|
||||||
|
coverage = 100 * float(total_covered) / float(total_lines)
|
||||||
|
|
||||||
|
style = """
|
||||||
|
<style type="text/css" media="screen">
|
||||||
|
body { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }
|
||||||
|
table { font-size: 100%; }
|
||||||
|
pre { }
|
||||||
|
|
||||||
|
/* -- heading ---------------------------------------------------------------------- */
|
||||||
|
h1 {
|
||||||
|
}
|
||||||
|
.heading {
|
||||||
|
margin-top: 0ex;
|
||||||
|
margin-bottom: 1ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading .attribute {
|
||||||
|
margin-top: 1ex;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading .description {
|
||||||
|
margin-top: 4ex;
|
||||||
|
margin-bottom: 6ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- report ------------------------------------------------------------------------ */
|
||||||
|
#show_detail_line {
|
||||||
|
margin-top: 3ex;
|
||||||
|
margin-bottom: 1ex;
|
||||||
|
}
|
||||||
|
#result_table {
|
||||||
|
width: 80%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: medium solid #777;
|
||||||
|
}
|
||||||
|
#header_row {
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
background-color: #777;
|
||||||
|
}
|
||||||
|
#result_table td {
|
||||||
|
border: thin solid #777;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
#total_row { font-weight: bold; }
|
||||||
|
.passClass { background-color: #6c6; }
|
||||||
|
.failClass { background-color: #c60; }
|
||||||
|
.errorClass { background-color: #c00; }
|
||||||
|
.passCase { color: #6c6; }
|
||||||
|
.failCase { color: #c60; font-weight: bold; }
|
||||||
|
.errorCase { color: #c00; font-weight: bold; }
|
||||||
|
.hiddenRow { display: none; }
|
||||||
|
.testcase { margin-left: 2em; }
|
||||||
|
|
||||||
|
|
||||||
|
/* -- ending ---------------------------------------------------------------------- */
|
||||||
|
#ending {
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create HTML report
|
||||||
|
header = '<tr id="header_row">'
|
||||||
|
header += '<th>Module</th>'
|
||||||
|
header += '<th align="right">Total Lines</th>'
|
||||||
|
header += '<th align="right">Covered Lines</th>'
|
||||||
|
header += '<th align="right">Coverage</th>'
|
||||||
|
header += '</tr>'
|
||||||
|
|
||||||
|
row = '<tr class="%(class)s">'
|
||||||
|
row += '<td>%(module)s</td>'
|
||||||
|
row += '<td align="right">%(lines)s</td>'
|
||||||
|
row += '<td align="right">%(covered)s</td>'
|
||||||
|
row += '<td align="right">%(coverage).2f</td>'
|
||||||
|
row += '</tr>'
|
||||||
|
|
||||||
|
rows = ''
|
||||||
|
for key in sorted(records.keys()):
|
||||||
|
record = records[key]
|
||||||
|
if record[2] >= 80:
|
||||||
|
cls = 'passClass'
|
||||||
|
elif record[2] >= 40:
|
||||||
|
cls = 'failClass'
|
||||||
|
else:
|
||||||
|
cls = 'errorClass'
|
||||||
|
rows += row % {
|
||||||
|
'class': cls,
|
||||||
|
'module': key,
|
||||||
|
'lines': record[0],
|
||||||
|
'covered': record[1],
|
||||||
|
'coverage': record[2],
|
||||||
|
}
|
||||||
|
|
||||||
|
footer = '<tr>'
|
||||||
|
footer += '<th></th>'
|
||||||
|
footer += '<th align="right">%d</th>' % total_lines
|
||||||
|
footer += '<th align="right">%d</th>' % total_covered
|
||||||
|
footer += '<th align="right">%.2f</th>' % coverage
|
||||||
|
footer += '</tr>'
|
||||||
|
|
||||||
|
table = '<table id="result_table">%s%s%s</table>' % (header, rows, footer)
|
||||||
|
html = '<html>'
|
||||||
|
html += style
|
||||||
|
html += '<body>'
|
||||||
|
title = 'Tryton unittest %s' % env
|
||||||
|
html += '<title>%s</title>' % title
|
||||||
|
html += '<br/>'
|
||||||
|
html += table
|
||||||
|
html += '</body></html>'
|
||||||
|
|
||||||
|
f = open(html_filename(branch, config), 'w')
|
||||||
|
f.write(html)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
#print "TOTAL LINES:", total_lines
|
||||||
|
#print "TOTAL COVERED:", total_covered
|
||||||
|
#print "COVERAGE: %.2f" % coverage
|
||||||
|
|
||||||
|
|
||||||
|
#path = '/home/albert/d/tryton/master/server'
|
||||||
|
#proteus_path = '/home/albert/d/tryton/master/proteus'
|
||||||
|
#for trytond_path in glob.glob(os.path.join(path, '*')):
|
||||||
|
for branch, values in settings.iteritems():
|
||||||
|
if options.branch and branch != options.branch:
|
||||||
|
continue
|
||||||
|
trytond_path = values['trytond']
|
||||||
|
if not os.path.isdir(trytond_path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
print "%s %s" % (datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
branch)
|
||||||
|
pythonpath = [trytond_path]
|
||||||
|
if 'proteus' in values:
|
||||||
|
pythonpath.append(values['proteus'])
|
||||||
|
env = {
|
||||||
|
'PYTHONPATH': ':'.join(pythonpath)
|
||||||
|
}
|
||||||
|
if not options.pgsql_only:
|
||||||
|
runtest(trytond_path, branch, 'sqlite', env, options.coverage)
|
||||||
|
if not options.sqlite_only:
|
||||||
|
runtest(trytond_path, branch, 'postgres', env, options.coverage)
|
|
@ -0,0 +1,91 @@
|
||||||
|
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||||
|
#this repository contains the full copyright notices and license terms.
|
||||||
|
[options]
|
||||||
|
|
||||||
|
# This is the hostname used when generating tryton URI
|
||||||
|
#hostname =
|
||||||
|
|
||||||
|
# Activate the json-rpc protocol
|
||||||
|
jsonrpc = localhost:8000
|
||||||
|
#ssl_jsonrpc = False
|
||||||
|
|
||||||
|
# Configure the path of json-rpc data
|
||||||
|
#jsondata_path = /var/www/localhost/tryton
|
||||||
|
|
||||||
|
# Activate the xml-rpc protocol
|
||||||
|
#xmlrpc = *:8069
|
||||||
|
#ssl_xmlrpc = False
|
||||||
|
|
||||||
|
# Activate the webdav protocol
|
||||||
|
#webdav = *:8080
|
||||||
|
#ssl_webdav = False
|
||||||
|
|
||||||
|
# Configure the database type
|
||||||
|
# allowed values are postgresql, sqlite, mysql
|
||||||
|
db_type = sqlite
|
||||||
|
|
||||||
|
# Configure the database connection
|
||||||
|
## Note: Only databases owned by db_user will be displayed in the connection dialog
|
||||||
|
## of the Tryton client. db_user must have create permission for new databases
|
||||||
|
## to be able to use automatic database creation with the Tryton client.
|
||||||
|
#db_host = False
|
||||||
|
#db_port = False
|
||||||
|
#db_user = False
|
||||||
|
#db_password = False
|
||||||
|
#db_minconn = 1
|
||||||
|
#db_maxconn = 64
|
||||||
|
|
||||||
|
# Configure the postgresql path for the executable
|
||||||
|
#pg_path = None
|
||||||
|
|
||||||
|
# Configure the Tryton server password
|
||||||
|
#admin_passwd = admin
|
||||||
|
|
||||||
|
# Configure the path of the files for the pid and the logs
|
||||||
|
#pidfile = False
|
||||||
|
#logfile = False
|
||||||
|
|
||||||
|
#privatekey = server.pem
|
||||||
|
#certificate = server.pem
|
||||||
|
|
||||||
|
# Configure the SMTP connection
|
||||||
|
#smtp_server = localhost
|
||||||
|
#smtp_port = 25
|
||||||
|
#smtp_ssl = False
|
||||||
|
#smtp_tls = False
|
||||||
|
#smtp_password = False
|
||||||
|
#smtp_user = False
|
||||||
|
|
||||||
|
# Configure the path to store attachments and sqlite database
|
||||||
|
data_path = /tmp
|
||||||
|
|
||||||
|
# Allow to run more than one instance of trytond
|
||||||
|
#multi_server = False
|
||||||
|
|
||||||
|
# Configure the session timeout (inactivity of the client in sec)
|
||||||
|
#session_timeout = 600
|
||||||
|
|
||||||
|
# Enable psyco module
|
||||||
|
# Need to have psyco installed http://psyco.sourceforge.net/
|
||||||
|
#psyco = False
|
||||||
|
|
||||||
|
# Enable auto-reload of modules if changed
|
||||||
|
#auto_reload = True
|
||||||
|
|
||||||
|
# Prevent database listing
|
||||||
|
#prevent_dblist = False
|
||||||
|
|
||||||
|
# Enable cron
|
||||||
|
# cron = True
|
||||||
|
|
||||||
|
# unoconv connection
|
||||||
|
#unoconv = pipe,name=trytond;urp;StarOffice.ComponentContext
|
||||||
|
|
||||||
|
# Number of retries on database operational error
|
||||||
|
# retry = 5
|
||||||
|
|
||||||
|
# Default database language code
|
||||||
|
# language = en_US
|
||||||
|
|
||||||
|
# Timezone of the server
|
||||||
|
# timezone = False
|
|
@ -0,0 +1,79 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import logging
|
||||||
|
logging.basicConfig(level=logging.FATAL)
|
||||||
|
import HTMLTestRunner
|
||||||
|
import unittest
|
||||||
|
import getpass
|
||||||
|
import sys
|
||||||
|
import optparse
|
||||||
|
import functools
|
||||||
|
from coverage import coverage
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
sys.path.insert(0, 'trytond')
|
||||||
|
from trytond.config import CONFIG
|
||||||
|
|
||||||
|
options = {}
|
||||||
|
parser = optparse.OptionParser()
|
||||||
|
parser.add_option("-c", "--config", dest="config",
|
||||||
|
help="specify config file")
|
||||||
|
parser.add_option("-n", "--name", dest="name",
|
||||||
|
help="name to add to output file")
|
||||||
|
parser.add_option('', '--coverage', action='store_true', dest='coverage')
|
||||||
|
(opt, _) = parser.parse_args()
|
||||||
|
if opt.config:
|
||||||
|
options['configfile'] = opt.config
|
||||||
|
else:
|
||||||
|
# No config file speficified, it will be guessed
|
||||||
|
options['configfile'] = None
|
||||||
|
if opt.name:
|
||||||
|
options['name'] = opt.name
|
||||||
|
else:
|
||||||
|
options['name'] = None
|
||||||
|
options['coverage'] = opt.coverage
|
||||||
|
|
||||||
|
CONFIG.update_etc(options['configfile'])
|
||||||
|
update_etc = functools.partial(CONFIG.update_etc, options['configfile'])
|
||||||
|
CONFIG.update_etc = lambda *args, **kwargd: update_etc()
|
||||||
|
CONFIG.update_cmdline(options)
|
||||||
|
CONFIG.update_cmdline = lambda *args, **kwargs: None
|
||||||
|
|
||||||
|
import trytond.tests.test_tryton as test_tryton
|
||||||
|
|
||||||
|
sys.path.insert(0, 'proteus')
|
||||||
|
import proteus.tests
|
||||||
|
|
||||||
|
basename = ''
|
||||||
|
if options['name']:
|
||||||
|
basename += options['name'] + "-"
|
||||||
|
basename += CONFIG['db_type']
|
||||||
|
path = '/home/%s/public_html' % getpass.getuser()
|
||||||
|
filename = '/home/%s/public_html/%s.html' % (getpass.getuser(), basename)
|
||||||
|
title = 'Tryton unittest %s' % CONFIG['db_type']
|
||||||
|
|
||||||
|
fp = file(filename, 'wb')
|
||||||
|
runner = HTMLTestRunner.HTMLTestRunner(
|
||||||
|
stream=fp,
|
||||||
|
title=title,
|
||||||
|
)
|
||||||
|
suite = test_tryton.modules_suite()
|
||||||
|
suite.addTests(proteus.tests.test_suite())
|
||||||
|
#suite = proteus.tests.test_suite()
|
||||||
|
|
||||||
|
if options['coverage']:
|
||||||
|
cov = coverage()
|
||||||
|
cov.start()
|
||||||
|
runner.run(suite)
|
||||||
|
cov.stop()
|
||||||
|
cov.save()
|
||||||
|
if os.path.exists('coverage'):
|
||||||
|
shutil.rmtree('coverage')
|
||||||
|
cov.html_report(directory='%s/coverage' % path, title=title,
|
||||||
|
ignore_errors=True)
|
||||||
|
directory = '%s/%s-coverage' % (path, basename)
|
||||||
|
if os.path.exists(directory):
|
||||||
|
shutil.move('coverage', directory)
|
||||||
|
else:
|
||||||
|
runner.run(suite)
|
||||||
|
|
Loading…
Reference in New Issue