Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
erp5
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
7
Merge Requests
7
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Jobs
Commits
Open sidebar
Jérome Perrin
erp5
Commits
9ee7066a
Commit
9ee7066a
authored
Jun 19, 2013
by
Jérome Perrin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add xunit output support to tests
parent
f0e09c54
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
300 additions
and
4 deletions
+300
-4
product/ERP5Type/tests/runUnitTest.py
product/ERP5Type/tests/runUnitTest.py
+36
-4
product/ERP5Type/tests/xunit.py
product/ERP5Type/tests/xunit.py
+264
-0
No files found.
product/ERP5Type/tests/runUnitTest.py
View file @
9ee7066a
...
@@ -16,6 +16,7 @@ try:
...
@@ -16,6 +16,7 @@ try:
from
coverage
import
coverage
from
coverage
import
coverage
except
ImportError
:
except
ImportError
:
coverage
=
None
coverage
=
None
import
xunit
WIN
=
os
.
name
==
'nt'
WIN
=
os
.
name
==
'nt'
...
@@ -143,6 +144,7 @@ Options:
...
@@ -143,6 +144,7 @@ Options:
--sys_path=path,path Comma-separated list of paths which will be used to
--sys_path=path,path Comma-separated list of paths which will be used to
extend sys.path
extend sys.path
--instance_home=PATH Create/use test instance in given path
--instance_home=PATH Create/use test instance in given path
--xunit_file=PATH File for xunit xml report
When no unit test is specified, only activities are processed.
When no unit test is specified, only activities are processed.
"""
"""
...
@@ -427,10 +429,16 @@ unittest.loader.TestLoader = ERP5TypeTestLoader
...
@@ -427,10 +429,16 @@ unittest.loader.TestLoader = ERP5TypeTestLoader
class
DebugTestResult
:
class
DebugTestResult
:
"""Wrap an unittest.TestResult, invoking pdb on errors / failures
"""Wrap an unittest.TestResult, invoking pdb on errors / failures
"""
"""
def
__init__
(
self
,
result
):
xunit
=
None
def
__init__
(
self
,
result
,
debug
=
True
,
xunit_file
=
None
):
self
.
result
=
result
self
.
result
=
result
if
xunit_file
:
self
.
xunit
=
xunit
.
Xunit
(
xunit_file
)
self
.
_debug
=
debug
def
_start_debugger
(
self
,
tb
):
def
_start_debugger
(
self
,
tb
):
if
not
self
.
_debug
:
return
import
Lifetime
import
Lifetime
if
Lifetime
.
_shutdown_phase
:
if
Lifetime
.
_shutdown_phase
:
return
return
...
@@ -453,17 +461,36 @@ class DebugTestResult:
...
@@ -453,17 +461,36 @@ class DebugTestResult:
def
addError
(
self
,
test
,
err
):
def
addError
(
self
,
test
,
err
):
self
.
_start_debugger
(
err
[
2
])
self
.
_start_debugger
(
err
[
2
])
self
.
result
.
addError
(
test
,
err
)
self
.
result
.
addError
(
test
,
err
)
if
self
.
xunit
:
self
.
xunit
.
addError
(
test
,
err
)
def
addFailure
(
self
,
test
,
err
):
def
addFailure
(
self
,
test
,
err
):
self
.
_start_debugger
(
err
[
2
])
self
.
_start_debugger
(
err
[
2
])
self
.
result
.
addFailure
(
test
,
err
)
self
.
result
.
addFailure
(
test
,
err
)
if
self
.
xunit
:
self
.
xunit
.
addFailure
(
test
,
err
)
def
addSuccess
(
self
,
test
):
self
.
result
.
addSuccess
(
test
)
if
self
.
xunit
:
self
.
xunit
.
addSuccess
(
test
)
def
startTest
(
self
,
test
):
self
.
result
.
startTest
(
test
)
if
self
.
xunit
:
self
.
xunit
.
startTest
(
test
)
def
printErrors
(
self
):
self
.
result
.
printErrors
()
if
self
.
xunit
:
self
.
xunit
.
report
(
unittest
.
_WritelnDecorator
(
sys
.
stdout
))
def
__getattr__
(
self
,
attr
):
def
__getattr__
(
self
,
attr
):
return
getattr
(
self
.
result
,
attr
)
return
getattr
(
self
.
result
,
attr
)
_print
=
sys
.
stderr
.
write
_print
=
sys
.
stderr
.
write
def
runUnitTestList
(
test_list
,
verbosity
=
1
,
debug
=
0
,
run_only
=
None
):
def
runUnitTestList
(
test_list
,
verbosity
=
1
,
debug
=
0
,
xunit_file
=
None
,
run_only
=
None
):
if
"zeo_client"
in
os
.
environ
and
"zeo_server"
in
os
.
environ
:
if
"zeo_client"
in
os
.
environ
and
"zeo_server"
in
os
.
environ
:
_print
(
"conflicting options: --zeo_client and --zeo_server"
)
_print
(
"conflicting options: --zeo_client and --zeo_server"
)
sys
.
exit
(
1
)
sys
.
exit
(
1
)
...
@@ -608,13 +635,13 @@ def runUnitTestList(test_list, verbosity=1, debug=0, run_only=None):
...
@@ -608,13 +635,13 @@ def runUnitTestList(test_list, verbosity=1, debug=0, run_only=None):
ERP5TypeTestLoader
.
_testMethodPrefix
=
'dummy_test'
ERP5TypeTestLoader
.
_testMethodPrefix
=
'dummy_test'
PortalTestCase
.
setUp
=
dummy_setUp
PortalTestCase
.
setUp
=
dummy_setUp
PortalTestCase
.
tearDown
=
dummy_tearDown
PortalTestCase
.
tearDown
=
dummy_tearDown
el
if
debug
:
el
se
:
# Hack the profiler to run only specified test methods,
# Hack the profiler to run only specified test methods,
# and wrap results when running in debug mode.
# and wrap results when running in debug mode.
class
DebugTextTestRunner
(
TestRunner
):
class
DebugTextTestRunner
(
TestRunner
):
def
_makeResult
(
self
):
def
_makeResult
(
self
):
result
=
super
(
DebugTextTestRunner
,
self
).
_makeResult
()
result
=
super
(
DebugTextTestRunner
,
self
).
_makeResult
()
return
DebugTestResult
(
result
)
return
DebugTestResult
(
result
,
debug
,
xunit_file
)
TestRunner
=
DebugTextTestRunner
TestRunner
=
DebugTextTestRunner
loader
=
ERP5TypeTestLoader
()
loader
=
ERP5TypeTestLoader
()
if
run_only
:
if
run_only
:
...
@@ -715,6 +742,7 @@ def main(argument_list=None):
...
@@ -715,6 +742,7 @@ def main(argument_list=None):
"products_path="
,
"products_path="
,
"sys_path="
,
"sys_path="
,
"instance_home="
,
"instance_home="
,
"xunit_file="
,
])
])
except
getopt
.
GetoptError
,
msg
:
except
getopt
.
GetoptError
,
msg
:
usage
(
sys
.
stderr
,
msg
)
usage
(
sys
.
stderr
,
msg
)
...
@@ -724,6 +752,7 @@ def main(argument_list=None):
...
@@ -724,6 +752,7 @@ def main(argument_list=None):
verbosity
=
1
verbosity
=
1
debug
=
0
debug
=
0
run_only
=
None
run_only
=
None
xunit_file
=
None
instance_home
=
os
.
path
.
join
(
real_instance_home
,
'unit_test'
)
instance_home
=
os
.
path
.
join
(
real_instance_home
,
'unit_test'
)
bt5_path_list
=
[]
bt5_path_list
=
[]
...
@@ -827,6 +856,8 @@ def main(argument_list=None):
...
@@ -827,6 +856,8 @@ def main(argument_list=None):
sys
.
path
.
extend
(
arg
.
split
(
','
))
sys
.
path
.
extend
(
arg
.
split
(
','
))
elif
opt
==
"--instance_home"
:
elif
opt
==
"--instance_home"
:
instance_home
=
os
.
path
.
abspath
(
arg
)
instance_home
=
os
.
path
.
abspath
(
arg
)
elif
opt
==
"--xunit_file"
:
xunit_file
=
os
.
path
.
abspath
(
arg
)
bt5_path_list
+=
filter
(
None
,
bt5_path_list
+=
filter
(
None
,
os
.
environ
.
get
(
"erp5_tests_bt5_path"
,
""
).
split
(
','
))
os
.
environ
.
get
(
"erp5_tests_bt5_path"
,
""
).
split
(
','
))
...
@@ -849,6 +880,7 @@ def main(argument_list=None):
...
@@ -849,6 +880,7 @@ def main(argument_list=None):
result
=
runUnitTestList
(
test_list
=
args
,
result
=
runUnitTestList
(
test_list
=
args
,
verbosity
=
verbosity
,
verbosity
=
verbosity
,
debug
=
debug
,
debug
=
debug
,
xunit_file
=
xunit_file
,
run_only
=
run_only
,
run_only
=
run_only
,
)
)
return
result
and
not
result
.
wasSuccessful
()
return
result
and
not
result
.
wasSuccessful
()
...
...
product/ERP5Type/tests/xunit.py
0 → 100644
View file @
9ee7066a
"""This plugin provides test results in the standard XUnit XML format.
It's designed for the `Jenkins`_ (previously Hudson) continuous build
system, but will probably work for anything else that understands an
XUnit-formatted XML representation of test results.
Add this shell command to your builder ::
nosetests --with-xunit
And by default a file named nosetests.xml will be written to the
working directory.
In a Jenkins builder, tick the box named "Publish JUnit test result report"
under the Post-build Actions and enter this value for Test report XMLs::
**/nosetests.xml
If you need to change the name or location of the file, you can set the
``--xunit-file`` option.
Here is an abbreviated version of what an XML test report might look like::
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="nosetests" tests="1" errors="1" failures="0" skip="0">
<testcase classname="path_to_test_suite.TestSomething"
name="test_it" time="0">
<error type="exceptions.TypeError" message="oops, wrong type">
Traceback (most recent call last):
...
TypeError: oops, wrong type
</error>
</testcase>
</testsuite>
.. _Jenkins: http://jenkins-ci.org/
"""
import
codecs
import
doctest
import
os
import
traceback
import
re
import
inspect
from
time
import
time
from
xml.sax
import
saxutils
from
unittest
import
SkipTest
UNICODE_STRINGS
=
False
# Invalid XML characters, control characters 0-31 sans \t, \n and \r
CONTROL_CHARACTERS
=
re
.
compile
(
r"[\000-\010\013\014\016-\037]"
)
TEST_ID
=
re
.
compile
(
r'^(.*?)(\
(.*
\))$'
)
def
xml_safe
(
value
):
"""Replaces invalid XML characters with '?'."""
return
CONTROL_CHARACTERS
.
sub
(
'?'
,
value
)
def
escape_cdata
(
cdata
):
"""Escape a string for an XML CDATA section."""
return
xml_safe
(
cdata
).
replace
(
']]>'
,
']]>]]><![CDATA['
)
def
id_split
(
idval
):
m
=
TEST_ID
.
match
(
idval
)
if
m
:
name
,
fargs
=
m
.
groups
()
head
,
tail
=
name
.
rsplit
(
"."
,
1
)
return
[
head
,
tail
+
fargs
]
else
:
return
idval
.
rsplit
(
"."
,
1
)
def
nice_classname
(
obj
):
"""Returns a nice name for class object or class instance.
>>> nice_classname(Exception()) # doctest: +ELLIPSIS
'...Exception'
>>> nice_classname(Exception) # doctest: +ELLIPSIS
'...Exception'
"""
if
inspect
.
isclass
(
obj
):
cls_name
=
obj
.
__name__
else
:
cls_name
=
obj
.
__class__
.
__name__
mod
=
inspect
.
getmodule
(
obj
)
if
mod
:
name
=
mod
.
__name__
# jython
if
name
.
startswith
(
'org.python.core.'
):
name
=
name
[
len
(
'org.python.core.'
):]
return
"%s.%s"
%
(
name
,
cls_name
)
else
:
return
cls_name
def
exc_message
(
exc_info
):
"""Return the exception's message."""
exc
=
exc_info
[
1
]
if
exc
is
None
:
# str exception
result
=
exc_info
[
0
]
else
:
try
:
result
=
str
(
exc
)
except
UnicodeEncodeError
:
try
:
result
=
unicode
(
exc
)
except
UnicodeError
:
# Fallback to args as neither str nor
# unicode(Exception(u'\xe6')) work in Python < 2.6
result
=
exc
.
args
[
0
]
return
xml_safe
(
result
)
class
Xunit
(
object
):
"""This plugin provides test results in the standard XUnit XML format."""
name
=
'xunit'
score
=
2000
encoding
=
'UTF-8'
error_report_file
=
None
def
_timeTaken
(
self
):
if
hasattr
(
self
,
'_timer'
):
taken
=
time
()
-
self
.
_timer
else
:
# test died before it ran (probably error in setup())
# or success/failure added before test started probably
# due to custom TestResult munging
taken
=
0.0
return
taken
def
_quoteattr
(
self
,
attr
):
"""Escape an XML attribute. Value can be unicode."""
attr
=
xml_safe
(
attr
)
if
isinstance
(
attr
,
unicode
)
and
not
UNICODE_STRINGS
:
attr
=
attr
.
encode
(
self
.
encoding
)
return
saxutils
.
quoteattr
(
attr
)
def
options
(
self
,
parser
,
env
):
"""Sets additional command line options."""
Plugin
.
options
(
self
,
parser
,
env
)
parser
.
add_option
(
'--xunit-file'
,
action
=
'store'
,
dest
=
'xunit_file'
,
metavar
=
"FILE"
,
default
=
env
.
get
(
'NOSE_XUNIT_FILE'
,
'nosetests.xml'
),
help
=
(
"Path to xml file to store the xunit report in. "
"Default is nosetests.xml in the working directory "
"[NOSE_XUNIT_FILE]"
))
def
__init__
(
self
,
xunit_file
):
""" We don't call configure.
"""
self
.
stats
=
{
'errors'
:
0
,
'failures'
:
0
,
'passes'
:
0
,
'skipped'
:
0
}
self
.
errorlist
=
[]
self
.
error_report_file
=
codecs
.
open
(
xunit_file
,
'w'
,
self
.
encoding
,
'replace'
)
def
configure
(
self
,
options
,
config
):
"""Configures the xunit plugin."""
Plugin
.
configure
(
self
,
options
,
config
)
self
.
config
=
config
if
self
.
enabled
:
self
.
stats
=
{
'errors'
:
0
,
'failures'
:
0
,
'passes'
:
0
,
'skipped'
:
0
}
self
.
errorlist
=
[]
self
.
error_report_file
=
codecs
.
open
(
options
.
xunit_file
,
'w'
,
self
.
encoding
,
'replace'
)
def
report
(
self
,
stream
):
"""Writes an Xunit-formatted XML file
The file includes a report of test errors and failures.
"""
self
.
stats
[
'encoding'
]
=
self
.
encoding
self
.
stats
[
'total'
]
=
(
self
.
stats
[
'errors'
]
+
self
.
stats
[
'failures'
]
+
self
.
stats
[
'passes'
]
+
self
.
stats
[
'skipped'
])
self
.
error_report_file
.
write
(
u'<?xml version="1.0" encoding="%(encoding)s"?>'
u'<testsuite name="nosetests" tests="%(total)d" '
u'errors="%(errors)d" failures="%(failures)d" '
u'skip="%(skipped)d">'
%
self
.
stats
)
self
.
error_report_file
.
write
(
u''
.
join
([
self
.
_forceUnicode
(
e
)
for
e
in
self
.
errorlist
]))
self
.
error_report_file
.
write
(
u'</testsuite>'
)
self
.
error_report_file
.
close
()
if
1
:
stream
.
writeln
(
"-"
*
70
)
stream
.
writeln
(
"XML: %s"
%
self
.
error_report_file
.
name
)
def
startTest
(
self
,
test
):
"""Initializes a timer before starting a test."""
self
.
_timer
=
time
()
def
addError
(
self
,
test
,
err
,
capt
=
None
):
"""Add error output to Xunit report.
"""
taken
=
self
.
_timeTaken
()
if
issubclass
(
err
[
0
],
SkipTest
):
type
=
'skipped'
self
.
stats
[
'skipped'
]
+=
1
else
:
type
=
'error'
self
.
stats
[
'errors'
]
+=
1
tb
=
''
.
join
(
traceback
.
format_exception
(
*
err
))
id
=
test
.
id
()
self
.
errorlist
.
append
(
'<testcase classname=%(cls)s name=%(name)s time="%(taken).3f">'
'<%(type)s type=%(errtype)s message=%(message)s><![CDATA[%(tb)s]]>'
'</%(type)s></testcase>'
%
{
'cls'
:
self
.
_quoteattr
(
id_split
(
id
)[
0
]),
'name'
:
self
.
_quoteattr
(
id_split
(
id
)[
-
1
]),
'taken'
:
taken
,
'type'
:
type
,
'errtype'
:
self
.
_quoteattr
(
nice_classname
(
err
[
0
])),
'message'
:
self
.
_quoteattr
(
exc_message
(
err
)),
'tb'
:
escape_cdata
(
tb
),
})
def
addFailure
(
self
,
test
,
err
,
capt
=
None
,
tb_info
=
None
):
"""Add failure output to Xunit report.
"""
taken
=
self
.
_timeTaken
()
tb
=
''
.
join
(
traceback
.
format_exception
(
*
err
))
self
.
stats
[
'failures'
]
+=
1
id
=
test
.
id
()
self
.
errorlist
.
append
(
'<testcase classname=%(cls)s name=%(name)s time="%(taken).3f">'
'<failure type=%(errtype)s message=%(message)s><![CDATA[%(tb)s]]>'
'</failure></testcase>'
%
{
'cls'
:
self
.
_quoteattr
(
id_split
(
id
)[
0
]),
'name'
:
self
.
_quoteattr
(
id_split
(
id
)[
-
1
]),
'taken'
:
taken
,
'errtype'
:
self
.
_quoteattr
(
nice_classname
(
err
[
0
])),
'message'
:
self
.
_quoteattr
(
exc_message
(
err
)),
'tb'
:
escape_cdata
(
tb
),
})
def
addSuccess
(
self
,
test
,
capt
=
None
):
"""Add success output to Xunit report.
"""
taken
=
self
.
_timeTaken
()
self
.
stats
[
'passes'
]
+=
1
id
=
test
.
id
()
self
.
errorlist
.
append
(
'<testcase classname=%(cls)s name=%(name)s '
'time="%(taken).3f" />'
%
{
'cls'
:
self
.
_quoteattr
(
id_split
(
id
)[
0
]),
'name'
:
self
.
_quoteattr
(
id_split
(
id
)[
-
1
]),
'taken'
:
taken
,
})
def
_forceUnicode
(
self
,
s
):
if
not
UNICODE_STRINGS
:
if
isinstance
(
s
,
str
):
s
=
s
.
decode
(
self
.
encoding
,
'replace'
)
return
s
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment