diff --git a/.gitignore b/.gitignore
index b204a6b..3a5c919 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ eggs
parts
bin
var
+venv
sdist
develop-eggs
.installed.cfg
diff --git a/.travis.yml b/.travis.yml
index 7897ec3..8d42e75 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,18 +1,38 @@
language: python
+matrix:
+ include:
+ - python: pypy
+ dist: xenial
+ sudo: true
+ env: TOXENV=pypy
+ - python: 3.5
+ dist: xenial
+ sudo: true
+ env: TOXENV=py35
+ - python: 3.7
+ dist: xenial
+ sudo: true
+ env: TOXENV=py37
+ - python: 3.8
+ dist: xenial
+ sudo: true
+ env: TOXENV=py38
+
env:
- - TOXENV=py26
- - TOXENV=py27
- - TOXENV=pypy
- - TOXENV=py33
- - TOXENV=py34
- - TOXENV=cover
+ - TOXENV=py27
+ - TOXENV=py36
+ - TOXENV=cover
+ - TOXENV=flake8
install:
- - travis_retry pip install tox==1.6.1
+ - travis_retry pip install tox==3.13.2
script:
- - travis_retry tox
+ - travis_retry tox
+
+git:
+ depth: 1
notifications:
email:
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 2ef3470..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,5 +0,0 @@
-include LICENSE.txt
-include README.rst
-include setup.py
-include test_junit_xml.py
-include tox.ini
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..121ce12
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,17 @@
+ACTIVATE=venv/bin/activate
+
+venv: $(ACTIVATE)
+$(ACTIVATE): requirements.txt requirements_dev.txt
+ test -d venv || virtualenv venv
+ . $(ACTIVATE); pip install -r requirements_dev.txt
+
+.PHONY : dist
+dist:
+ python setup.py sdist bdist_wheel
+
+.PHONY : clean
+clean:
+ find . -name "*.pyc" -delete
+ find . -name "__pycache__" -delete
+ rm -rf build
+ rm -rf dist
diff --git a/README.rst b/README.rst
index 46b2804..b585af1 100644
--- a/README.rst
+++ b/README.rst
@@ -6,15 +6,16 @@ About
-----
A Python module for creating JUnit XML test result documents that can be
-read by tools such as Jenkins. If you are ever working with test tool or
-test suite written in Python and want to take advantage of Jenkins'
+read by tools such as Jenkins or Bamboo. If you are ever working with test tool or
+test suite written in Python and want to take advantage of Jenkins' or Bamboo's
pretty graphs and test reporting capabilities, this module will let you
generate the XML test reports.
*As there is no definitive Jenkins JUnit XSD that I could find, the XML
documents created by this module support a schema based on Google
searches and the Jenkins JUnit XML reader source code. File a bug if
-something doesn't work like you expect it to.*
+something doesn't work like you expect it to.
+For Bamboo situation is the same.*
Installation
------------
@@ -88,3 +89,10 @@ Running the tests
pip install tox
tox
+Releasing a new version
+-----------------------
+
+1. Bump version in `setup.py`
+2. Build distribution with `python setup.py sdist bdist_wheel`
+3. Upload to Pypi with `twine upload dist/*`
+4. Verify the new version was uploaded at https://pypi.org/project/junit-xml/#history
diff --git a/junit_xml/__init__.py b/junit_xml/__init__.py
index 91838df..0cfef29 100644
--- a/junit_xml/__init__.py
+++ b/junit_xml/__init__.py
@@ -1,5 +1,6 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
+import warnings
from collections import defaultdict
import sys
import re
@@ -59,15 +60,15 @@ def decode(var, encoding):
If not already unicode, decode it.
"""
if PY2:
- if isinstance(var, unicode):
+ if isinstance(var, unicode): # noqa: F821
ret = var
elif isinstance(var, str):
if encoding:
ret = var.decode(encoding)
else:
- ret = unicode(var)
+ ret = unicode(var) # noqa: F821
else:
- ret = unicode(var)
+ ret = unicode(var) # noqa: F821
else:
ret = str(var)
return ret
@@ -79,16 +80,28 @@ class TestSuite(object):
Can handle unicode strings or binary strings if their encoding is provided.
"""
- def __init__(self, name, test_cases=None, hostname=None, id=None,
- package=None, timestamp=None, properties=None, file=None,
- log=None, url=None, stdout=None, stderr=None):
+ def __init__(
+ self,
+ name,
+ test_cases=None,
+ hostname=None,
+ id=None,
+ package=None,
+ timestamp=None,
+ properties=None,
+ file=None,
+ log=None,
+ url=None,
+ stdout=None,
+ stderr=None,
+ ):
self.name = name
if not test_cases:
test_cases = []
try:
iter(test_cases)
except TypeError:
- raise Exception('test_cases must be a list of test cases')
+ raise TypeError("test_cases must be a list of test cases")
self.test_cases = test_cases
self.timestamp = timestamp
self.hostname = hostname
@@ -111,36 +124,30 @@ def build_xml_doc(self, encoding=None):
# build the test suite element
test_suite_attributes = dict()
- test_suite_attributes['name'] = decode(self.name, encoding)
if any(c.assertions for c in self.test_cases):
- test_suite_attributes['assertions'] = \
- str(sum([int(c.assertions) for c in self.test_cases if c.assertions]))
- test_suite_attributes['disabled'] = \
- str(len([c for c in self.test_cases if not c.is_enabled]))
- test_suite_attributes['failures'] = \
- str(len([c for c in self.test_cases if c.is_failure()]))
- test_suite_attributes['errors'] = \
- str(len([c for c in self.test_cases if c.is_error()]))
- test_suite_attributes['skipped'] = \
- str(len([c for c in self.test_cases if c.is_skipped()]))
- test_suite_attributes['time'] = \
- str(sum(c.elapsed_sec for c in self.test_cases if c.elapsed_sec))
- test_suite_attributes['tests'] = str(len(self.test_cases))
+ test_suite_attributes["assertions"] = str(sum([int(c.assertions) for c in self.test_cases if c.assertions]))
+ test_suite_attributes["disabled"] = str(len([c for c in self.test_cases if not c.is_enabled]))
+ test_suite_attributes["errors"] = str(len([c for c in self.test_cases if c.is_error()]))
+ test_suite_attributes["failures"] = str(len([c for c in self.test_cases if c.is_failure()]))
+ test_suite_attributes["name"] = decode(self.name, encoding)
+ test_suite_attributes["skipped"] = str(len([c for c in self.test_cases if c.is_skipped()]))
+ test_suite_attributes["tests"] = str(len(self.test_cases))
+ test_suite_attributes["time"] = str(sum(c.elapsed_sec for c in self.test_cases if c.elapsed_sec))
if self.hostname:
- test_suite_attributes['hostname'] = decode(self.hostname, encoding)
+ test_suite_attributes["hostname"] = decode(self.hostname, encoding)
if self.id:
- test_suite_attributes['id'] = decode(self.id, encoding)
+ test_suite_attributes["id"] = decode(self.id, encoding)
if self.package:
- test_suite_attributes['package'] = decode(self.package, encoding)
+ test_suite_attributes["package"] = decode(self.package, encoding)
if self.timestamp:
- test_suite_attributes['timestamp'] = decode(self.timestamp, encoding)
+ test_suite_attributes["timestamp"] = decode(self.timestamp, encoding)
if self.file:
- test_suite_attributes['file'] = decode(self.file, encoding)
+ test_suite_attributes["file"] = decode(self.file, encoding)
if self.log:
- test_suite_attributes['log'] = decode(self.log, encoding)
+ test_suite_attributes["log"] = decode(self.log, encoding)
if self.url:
- test_suite_attributes['url'] = decode(self.url, encoding)
+ test_suite_attributes["url"] = decode(self.url, encoding)
xml_element = ET.Element("testsuite", test_suite_attributes)
@@ -148,7 +155,7 @@ def build_xml_doc(self, encoding=None):
if self.properties:
props_element = ET.SubElement(xml_element, "properties")
for k, v in self.properties.items():
- attrs = {'name': decode(k, encoding), 'value': decode(v, encoding)}
+ attrs = {"name": decode(k, encoding), "value": decode(v, encoding)}
ET.SubElement(props_element, "property", attrs)
# add test suite stdout
@@ -164,64 +171,65 @@ def build_xml_doc(self, encoding=None):
# test cases
for case in self.test_cases:
test_case_attributes = dict()
- test_case_attributes['name'] = decode(case.name, encoding)
+ test_case_attributes["name"] = decode(case.name, encoding)
if case.assertions:
# Number of assertions in the test case
- test_case_attributes['assertions'] = "%d" % case.assertions
+ test_case_attributes["assertions"] = "%d" % case.assertions
if case.elapsed_sec:
- test_case_attributes['time'] = "%f" % case.elapsed_sec
+ test_case_attributes["time"] = "%f" % case.elapsed_sec
if case.timestamp:
- test_case_attributes['timestamp'] = decode(case.timestamp, encoding)
+ test_case_attributes["timestamp"] = decode(case.timestamp, encoding)
if case.classname:
- test_case_attributes['classname'] = decode(case.classname, encoding)
+ test_case_attributes["classname"] = decode(case.classname, encoding)
if case.status:
- test_case_attributes['status'] = decode(case.status, encoding)
+ test_case_attributes["status"] = decode(case.status, encoding)
if case.category:
- test_case_attributes['class'] = decode(case.category, encoding)
+ test_case_attributes["class"] = decode(case.category, encoding)
if case.file:
- test_case_attributes['file'] = decode(case.file, encoding)
+ test_case_attributes["file"] = decode(case.file, encoding)
if case.line:
- test_case_attributes['line'] = decode(case.line, encoding)
+ test_case_attributes["line"] = decode(case.line, encoding)
if case.log:
- test_case_attributes['log'] = decode(case.log, encoding)
+ test_case_attributes["log"] = decode(case.log, encoding)
if case.url:
- test_case_attributes['url'] = decode(case.url, encoding)
+ test_case_attributes["url"] = decode(case.url, encoding)
- test_case_element = ET.SubElement(
- xml_element, "testcase", test_case_attributes)
+ test_case_element = ET.SubElement(xml_element, "testcase", test_case_attributes)
# failures
- if case.is_failure():
- attrs = {'type': 'failure'}
- if case.failure_message:
- attrs['message'] = decode(case.failure_message, encoding)
- if case.failure_type:
- attrs['type'] = decode(case.failure_type, encoding)
- failure_element = ET.Element("failure", attrs)
- if case.failure_output:
- failure_element.text = decode(case.failure_output, encoding)
- test_case_element.append(failure_element)
+ for failure in case.failures:
+ if failure["output"] or failure["message"]:
+ attrs = {"type": "failure"}
+ if failure["message"]:
+ attrs["message"] = decode(failure["message"], encoding)
+ if failure["type"]:
+ attrs["type"] = decode(failure["type"], encoding)
+ failure_element = ET.Element("failure", attrs)
+ if failure["output"]:
+ failure_element.text = decode(failure["output"], encoding)
+ test_case_element.append(failure_element)
# errors
- if case.is_error():
- attrs = {'type': 'error'}
- if case.error_message:
- attrs['message'] = decode(case.error_message, encoding)
- if case.error_type:
- attrs['type'] = decode(case.error_type, encoding)
- error_element = ET.Element("error", attrs)
- if case.error_output:
- error_element.text = decode(case.error_output, encoding)
- test_case_element.append(error_element)
+ for error in case.errors:
+ if error["message"] or error["output"]:
+ attrs = {"type": "error"}
+ if error["message"]:
+ attrs["message"] = decode(error["message"], encoding)
+ if error["type"]:
+ attrs["type"] = decode(error["type"], encoding)
+ error_element = ET.Element("error", attrs)
+ if error["output"]:
+ error_element.text = decode(error["output"], encoding)
+ test_case_element.append(error_element)
# skippeds
- if case.is_skipped():
- attrs = {'type': 'skipped'}
- if case.skipped_message:
- attrs['message'] = decode(case.skipped_message, encoding)
+ for skipped in case.skipped:
+ attrs = {"type": "skipped"}
+ if skipped["message"]:
+ attrs["message"] = decode(skipped["message"], encoding)
skipped_element = ET.Element("skipped", attrs)
- if case.skipped_output:
- skipped_element.text = decode(case.skipped_output, encoding)
+ if skipped["output"]:
+ skipped_element.text = decode(skipped["output"], encoding)
test_case_element.append(skipped_element)
# test stdout
@@ -245,84 +253,134 @@ def to_xml_string(test_suites, prettyprint=True, encoding=None):
@param encoding: The encoding of the input.
@return: unicode string
"""
-
- try:
- iter(test_suites)
- except TypeError:
- raise Exception('test_suites must be a list of test suites')
-
- xml_element = ET.Element("testsuites")
- attributes = defaultdict(int)
- for ts in test_suites:
- ts_xml = ts.build_xml_doc(encoding=encoding)
- for key in ['failures', 'errors', 'tests', 'disabled']:
- attributes[key] += int(ts_xml.get(key, 0))
- for key in ['time']:
- attributes[key] += float(ts_xml.get(key, 0))
- xml_element.append(ts_xml)
- for key, value in iteritems(attributes):
- xml_element.set(key, str(value))
-
- xml_string = ET.tostring(xml_element, encoding=encoding)
- # is encoded now
- xml_string = TestSuite._clean_illegal_xml_chars(
- xml_string.decode(encoding or 'utf-8'))
- # is unicode now
-
- if prettyprint:
- # minidom.parseString() works just on correctly encoded binary strings
- xml_string = xml_string.encode(encoding or 'utf-8')
- xml_string = xml.dom.minidom.parseString(xml_string)
- # toprettyxml() produces unicode if no encoding is being passed or binary string with an encoding
- xml_string = xml_string.toprettyxml(encoding=encoding)
- if encoding:
- xml_string = xml_string.decode(encoding)
- # is unicode now
- return xml_string
+ warnings.warn(
+ "Testsuite.to_xml_string is deprecated. It will be removed in version 2.0.0. "
+ "Use function to_xml_report_string",
+ DeprecationWarning,
+ )
+ return to_xml_report_string(test_suites, prettyprint, encoding)
@staticmethod
def to_file(file_descriptor, test_suites, prettyprint=True, encoding=None):
"""
Writes the JUnit XML document to a file.
"""
- xml_string = TestSuite.to_xml_string(
- test_suites, prettyprint=prettyprint, encoding=encoding)
- # has problems with encoded str with non-ASCII (non-default-encoding) characters!
- file_descriptor.write(xml_string)
+ warnings.warn(
+ "Testsuite.to_file is deprecated. It will be removed in version 2.0.0. Use function to_xml_report_file",
+ DeprecationWarning,
+ )
+ to_xml_report_file(file_descriptor, test_suites, prettyprint, encoding)
- @staticmethod
- def _clean_illegal_xml_chars(string_to_clean):
- """
- Removes any illegal unicode characters from the given XML string.
- @see: http://stackoverflow.com/questions/1707890/fast-way-to-filter-illegal-xml-unicode-chars-in-python
- """
+def to_xml_report_string(test_suites, prettyprint=True, encoding=None):
+ """
+ Returns the string representation of the JUnit XML document.
+ @param encoding: The encoding of the input.
+ @return: unicode string
+ """
- illegal_unichrs = [
- (0x00, 0x08), (0x0B, 0x1F), (0x7F, 0x84), (0x86, 0x9F),
- (0xD800, 0xDFFF), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF),
- (0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF),
- (0x4FFFE, 0x4FFFF), (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
- (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), (0x9FFFE, 0x9FFFF),
- (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
- (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF),
- (0x10FFFE, 0x10FFFF)]
+ try:
+ iter(test_suites)
+ except TypeError:
+ raise TypeError("test_suites must be a list of test suites")
+
+ xml_element = ET.Element("testsuites")
+ attributes = defaultdict(int)
+ for ts in test_suites:
+ ts_xml = ts.build_xml_doc(encoding=encoding)
+ for key in ["disabled", "errors", "failures", "tests"]:
+ attributes[key] += int(ts_xml.get(key, 0))
+ for key in ["time"]:
+ attributes[key] += float(ts_xml.get(key, 0))
+ xml_element.append(ts_xml)
+ for key, value in iteritems(attributes):
+ xml_element.set(key, str(value))
+
+ xml_string = ET.tostring(xml_element, encoding=encoding)
+ # is encoded now
+ xml_string = _clean_illegal_xml_chars(xml_string.decode(encoding or "utf-8"))
+ # is unicode now
+
+ if prettyprint:
+ # minidom.parseString() works just on correctly encoded binary strings
+ xml_string = xml_string.encode(encoding or "utf-8")
+ xml_string = xml.dom.minidom.parseString(xml_string)
+ # toprettyxml() produces unicode if no encoding is being passed or binary string with an encoding
+ xml_string = xml_string.toprettyxml(encoding=encoding)
+ if encoding:
+ xml_string = xml_string.decode(encoding)
+ # is unicode now
+ return xml_string
- illegal_ranges = ["%s-%s" % (unichr(low), unichr(high))
- for (low, high) in illegal_unichrs
- if low < sys.maxunicode]
- illegal_xml_re = re.compile(u('[%s]') % u('').join(illegal_ranges))
- return illegal_xml_re.sub('', string_to_clean)
+def to_xml_report_file(file_descriptor, test_suites, prettyprint=True, encoding=None):
+ """
+ Writes the JUnit XML document to a file.
+ """
+ xml_string = to_xml_report_string(test_suites, prettyprint=prettyprint, encoding=encoding)
+ # has problems with encoded str with non-ASCII (non-default-encoding) characters!
+ file_descriptor.write(xml_string)
+
+
+def _clean_illegal_xml_chars(string_to_clean):
+ """
+ Removes any illegal unicode characters from the given XML string.
+
+ @see: http://stackoverflow.com/questions/1707890/fast-way-to-filter-illegal-xml-unicode-chars-in-python
+ """
+
+ illegal_unichrs = [
+ (0x00, 0x08),
+ (0x0B, 0x1F),
+ (0x7F, 0x84),
+ (0x86, 0x9F),
+ (0xD800, 0xDFFF),
+ (0xFDD0, 0xFDDF),
+ (0xFFFE, 0xFFFF),
+ (0x1FFFE, 0x1FFFF),
+ (0x2FFFE, 0x2FFFF),
+ (0x3FFFE, 0x3FFFF),
+ (0x4FFFE, 0x4FFFF),
+ (0x5FFFE, 0x5FFFF),
+ (0x6FFFE, 0x6FFFF),
+ (0x7FFFE, 0x7FFFF),
+ (0x8FFFE, 0x8FFFF),
+ (0x9FFFE, 0x9FFFF),
+ (0xAFFFE, 0xAFFFF),
+ (0xBFFFE, 0xBFFFF),
+ (0xCFFFE, 0xCFFFF),
+ (0xDFFFE, 0xDFFFF),
+ (0xEFFFE, 0xEFFFF),
+ (0xFFFFE, 0xFFFFF),
+ (0x10FFFE, 0x10FFFF),
+ ]
+
+ illegal_ranges = ["%s-%s" % (unichr(low), unichr(high)) for (low, high) in illegal_unichrs if low < sys.maxunicode]
+
+ illegal_xml_re = re.compile(u("[%s]") % u("").join(illegal_ranges))
+ return illegal_xml_re.sub("", string_to_clean)
class TestCase(object):
"""A JUnit test case with a result and possibly some stdout or stderr"""
- def __init__(self, name, classname=None, elapsed_sec=None, stdout=None,
- stderr=None, assertions=None, timestamp=None, status=None,
- category=None, file=None, line=None, log=None, group=None,
- url=None):
+ def __init__(
+ self,
+ name,
+ classname=None,
+ elapsed_sec=None,
+ stdout=None,
+ stderr=None,
+ assertions=None,
+ timestamp=None,
+ status=None,
+ category=None,
+ file=None,
+ line=None,
+ log=None,
+ url=None,
+ allow_multiple_subelements=False,
+ ):
self.name = name
self.assertions = assertions
self.elapsed_sec = elapsed_sec
@@ -338,48 +396,73 @@ def __init__(self, name, classname=None, elapsed_sec=None, stdout=None,
self.stderr = stderr
self.is_enabled = True
- self.error_message = None
- self.error_output = None
- self.error_type = None
- self.failure_message = None
- self.failure_output = None
- self.failure_type = None
- self.skipped_message = None
- self.skipped_output = None
+ self.errors = []
+ self.failures = []
+ self.skipped = []
+ self.allow_multiple_subalements = allow_multiple_subelements
def add_error_info(self, message=None, output=None, error_type=None):
"""Adds an error message, output, or both to the test case"""
- if message:
- self.error_message = message
- if output:
- self.error_output = output
- if error_type:
- self.error_type = error_type
+ error = {}
+ error["message"] = message
+ error["output"] = output
+ error["type"] = error_type
+ if self.allow_multiple_subalements:
+ if message or output:
+ self.errors.append(error)
+ elif not len(self.errors):
+ self.errors.append(error)
+ else:
+ if message:
+ self.errors[0]["message"] = message
+ if output:
+ self.errors[0]["output"] = output
+ if error_type:
+ self.errors[0]["type"] = error_type
def add_failure_info(self, message=None, output=None, failure_type=None):
"""Adds a failure message, output, or both to the test case"""
- if message:
- self.failure_message = message
- if output:
- self.failure_output = output
- if failure_type:
- self.failure_type = failure_type
+ failure = {}
+ failure["message"] = message
+ failure["output"] = output
+ failure["type"] = failure_type
+ if self.allow_multiple_subalements:
+ if message or output:
+ self.failures.append(failure)
+ elif not len(self.failures):
+ self.failures.append(failure)
+ else:
+ if message:
+ self.failures[0]["message"] = message
+ if output:
+ self.failures[0]["output"] = output
+ if failure_type:
+ self.failures[0]["type"] = failure_type
def add_skipped_info(self, message=None, output=None):
"""Adds a skipped message, output, or both to the test case"""
- if message:
- self.skipped_message = message
- if output:
- self.skipped_output = output
+ skipped = {}
+ skipped["message"] = message
+ skipped["output"] = output
+ if self.allow_multiple_subalements:
+ if message or output:
+ self.skipped.append(skipped)
+ elif not len(self.skipped):
+ self.skipped.append(skipped)
+ else:
+ if message:
+ self.skipped[0]["message"] = message
+ if output:
+ self.skipped[0]["output"] = output
def is_failure(self):
"""returns true if this test case is a failure"""
- return self.failure_output or self.failure_message
+ return sum(1 for f in self.failures if f["message"] or f["output"]) > 0
def is_error(self):
"""returns true if this test case is an error"""
- return self.error_output or self.error_message
+ return sum(1 for e in self.errors if e["message"] or e["output"]) > 0
def is_skipped(self):
"""returns true if this test case has been skipped"""
- return self.skipped_output or self.skipped_message
+ return len(self.skipped) > 0
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..55ec8d7
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,2 @@
+[tool.black]
+line-length = 120
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000..74ddadb
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+norecursedirs = .git .tox build dist venv
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..ffe2fce
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+six
diff --git a/requirements_dev.txt b/requirements_dev.txt
new file mode 100644
index 0000000..69269df
--- /dev/null
+++ b/requirements_dev.txt
@@ -0,0 +1,4 @@
+-r requirements.txt
+flake8-black
+pytest-sugar
+pytest-flake8
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..3fcc1e7
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,6 @@
+[flake8]
+max-line-length = 120
+exclude = .git,.tox,build,dist,venv
+
+[wheel]
+universal = 1
diff --git a/setup.py b/setup.py
index 3553f2c..8aba068 100644
--- a/setup.py
+++ b/setup.py
@@ -6,30 +6,27 @@
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
+
setup(
- name='junit-xml',
- author='Brian Beyer',
- author_email='brian@kyr.us',
- url='https://github.com/kyrus/python-junit-xml',
- license='MIT',
- packages=find_packages(),
- test_suite='test_junit_xml',
- description='Creates JUnit XML test result documents that can be read by '
- 'tools such as Jenkins',
- long_description=read('README.rst'),
- version='1.8',
+ name="junit-xml",
+ author="Brian Beyer",
+ author_email="brian@kyr.us",
+ url="https://github.com/kyrus/python-junit-xml",
+ license="MIT",
+ packages=find_packages(exclude=["tests"]),
+ description="Creates JUnit XML test result documents that can be read by tools such as Jenkins",
+ long_description=read("README.rst"),
+ version="1.9",
classifiers=[
- 'Development Status :: 5 - Production/Stable',
- 'Intended Audience :: Developers',
- 'License :: Freely Distributable',
- 'License :: OSI Approved :: MIT License',
- 'Operating System :: OS Independent',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 3',
- 'Topic :: Software Development :: Build Tools',
- 'Topic :: Software Development :: Testing',
- ],
- install_requires=[
- 'six'
- ]
- )
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers",
+ "License :: Freely Distributable",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Topic :: Software Development :: Build Tools",
+ "Topic :: Software Development :: Testing",
+ ],
+ install_requires=["six"],
+)
diff --git a/test_junit_xml.py b/test_junit_xml.py
deleted file mode 100644
index 1c59aed..0000000
--- a/test_junit_xml.py
+++ /dev/null
@@ -1,579 +0,0 @@
-# -*- coding: UTF-8 -*-
-from __future__ import with_statement
-import unittest
-import os
-import tempfile
-import textwrap
-from xml.dom import minidom
-import codecs
-
-from six import u, PY2
-
-from junit_xml import TestCase, TestSuite, decode
-
-
-def serialize_and_read(test_suites, to_file=False, prettyprint=False, encoding=None):
- """writes the test suite to an XML string and then re-reads it using minidom,
- returning => (test suite element, list of test case elements)"""
- try:
- iter(test_suites)
- except TypeError:
- test_suites = [test_suites]
-
- if to_file:
- fd, filename = tempfile.mkstemp(text=True)
- os.close(fd)
- with codecs.open(filename, mode='w', encoding=encoding) as f:
- TestSuite.to_file(f, test_suites, prettyprint=prettyprint, encoding=encoding)
- print("Serialized XML to temp file [%s]" % filename)
- xmldoc = minidom.parse(filename)
- os.remove(filename)
- else:
- xml_string = TestSuite.to_xml_string(
- test_suites, prettyprint=prettyprint, encoding=encoding)
- if PY2:
- assert isinstance(xml_string, unicode)
- print("Serialized XML to string:\n%s" % xml_string)
- if encoding:
- xml_string = xml_string.encode(encoding)
- xmldoc = minidom.parseString(xml_string)
-
- def remove_blanks(node):
- for x in node.childNodes:
- if x.nodeType == minidom.Node.TEXT_NODE:
- if x.nodeValue:
- x.nodeValue = x.nodeValue.strip()
- elif x.nodeType == minidom.Node.ELEMENT_NODE:
- remove_blanks(x)
- remove_blanks(xmldoc)
- xmldoc.normalize()
-
- ret = []
- suites = xmldoc.getElementsByTagName("testsuites")[0]
- for suite in suites.getElementsByTagName("testsuite"):
- cases = suite.getElementsByTagName("testcase")
- ret.append((suite, cases))
- return ret
-
-
-class TestSuiteTests(unittest.TestCase):
- def test_single_suite_single_test_case(self):
- try:
- (ts, tcs) = serialize_and_read(
- TestSuite('test', TestCase('Test1')), to_file=True)[0]
- self.fail("This should've raised an exeception") # pragma: nocover
- except Exception as exc:
- self.assertEqual(
- str(exc), 'test_cases must be a list of test cases')
-
- def test_single_suite_no_test_cases(self):
- properties = {'foo': 'bar'}
- package = 'mypackage'
- timestamp = 1398382805
-
- (ts, tcs) = serialize_and_read(
- TestSuite(
- name='test',
- test_cases=[],
- hostname='localhost',
- id=1,
- properties=properties,
- package=package,
- timestamp=timestamp
- ),
- to_file=True,
- prettyprint=True
- )[0]
- self.assertEqual(ts.tagName, 'testsuite')
- self.assertEqual(ts.attributes['package'].value, package)
- self.assertEqual(ts.attributes['timestamp'].value, str(timestamp))
- self.assertEqual(
- ts.childNodes[0].childNodes[0].attributes['name'].value,
- 'foo')
- self.assertEqual(
- ts.childNodes[0].childNodes[0].attributes['value'].value,
- 'bar')
-
- def test_single_suite_no_test_cases_utf8(self):
- properties = {'foö': 'bär'}
- package = 'mypäckage'
- timestamp = 1398382805
-
- test_suite = TestSuite(
- name='äöü',
- test_cases=[],
- hostname='löcalhost',
- id='äöü',
- properties=properties,
- package=package,
- timestamp=timestamp
- )
- (ts, tcs) = serialize_and_read(
- test_suite,
- to_file=True,
- prettyprint=True,
- encoding='utf-8'
- )[0]
- self.assertEqual(ts.tagName, 'testsuite')
- self.assertEqual(ts.attributes['package'].value, decode(package, 'utf-8'))
- self.assertEqual(ts.attributes['timestamp'].value, str(timestamp))
- self.assertEqual(
- ts.childNodes[0].childNodes[0].attributes['name'].value,
- decode('foö', 'utf-8'))
- self.assertEqual(
- ts.childNodes[0].childNodes[0].attributes['value'].value,
- decode('bär', 'utf-8'))
-
- def test_single_suite_no_test_cases_unicode(self):
- properties = {decode('foö', 'utf-8'): decode('bär', 'utf-8')}
- package = decode('mypäckage', 'utf-8')
- timestamp = 1398382805
-
- (ts, tcs) = serialize_and_read(
- TestSuite(
- name=decode('äöü', 'utf-8'),
- test_cases=[],
- hostname=decode('löcalhost', 'utf-8'),
- id=decode('äöü', 'utf-8'),
- properties=properties,
- package=package,
- timestamp=timestamp
- ),
- to_file=True,
- prettyprint=True,
- encoding='utf-8'
- )[0]
- self.assertEqual(ts.tagName, 'testsuite')
- self.assertEqual(ts.attributes['package'].value, package)
- self.assertEqual(ts.attributes['timestamp'].value, str(timestamp))
- self.assertEqual(
- ts.childNodes[0].childNodes[0].attributes['name'].value,
- decode('foö', 'utf-8'))
- self.assertEqual(
- ts.childNodes[0].childNodes[0].attributes['value'].value,
- decode('bär', 'utf-8'))
-
- def test_single_suite_to_file(self):
- (ts, tcs) = serialize_and_read(
- TestSuite('test', [TestCase('Test1')]), to_file=True)[0]
- verify_test_case(self, tcs[0], {'name': 'Test1'})
-
- def test_single_suite_to_file_prettyprint(self):
- (ts, tcs) = serialize_and_read(TestSuite(
- 'test', [TestCase('Test1')]), to_file=True, prettyprint=True)[0]
- verify_test_case(self, tcs[0], {'name': 'Test1'})
-
- def test_single_suite_prettyprint(self):
- (ts, tcs) = serialize_and_read(
- TestSuite('test', [TestCase('Test1')]),
- to_file=False, prettyprint=True)[0]
- verify_test_case(self, tcs[0], {'name': 'Test1'})
-
- def test_single_suite_to_file_no_prettyprint(self):
- (ts, tcs) = serialize_and_read(
- TestSuite('test', [TestCase('Test1')]),
- to_file=True, prettyprint=False)[0]
- verify_test_case(self, tcs[0], {'name': 'Test1'})
-
- def test_multiple_suites_to_file(self):
- tss = [TestSuite('suite1', [TestCase('Test1')]),
- TestSuite('suite2', [TestCase('Test2')])]
- suites = serialize_and_read(tss, to_file=True)
-
- self.assertEqual('suite1', suites[0][0].attributes['name'].value)
- verify_test_case(self, suites[0][1][0], {'name': 'Test1'})
-
- self.assertEqual('suite2', suites[1][0].attributes['name'].value)
- verify_test_case(self, suites[1][1][0], {'name': 'Test2'})
-
- def test_multiple_suites_to_string(self):
- tss = [TestSuite('suite1', [TestCase('Test1')]),
- TestSuite('suite2', [TestCase('Test2')])]
- suites = serialize_and_read(tss)
-
- self.assertEqual('suite1', suites[0][0].attributes['name'].value)
- verify_test_case(self, suites[0][1][0], {'name': 'Test1'})
-
- self.assertEqual('suite2', suites[1][0].attributes['name'].value)
- verify_test_case(self, suites[1][1][0], {'name': 'Test2'})
-
- def test_attribute_time(self):
- tss = [TestSuite('suite1', [TestCase(name='Test1', classname='some.class.name', elapsed_sec=123.345),
- TestCase(name='Test2', classname='some2.class.name', elapsed_sec=123.345)]),
- TestSuite('suite2', [TestCase('Test2')])]
- suites = serialize_and_read(tss)
-
- self.assertEqual('suite1', suites[0][0].attributes['name'].value)
- self.assertEqual('246.69', suites[0][0].attributes['time'].value)
-
- self.assertEqual('suite2', suites[1][0].attributes['name'].value)
- # here the time in testsuite is "0" even there is no attribute time for
- # testcase
- self.assertEqual('0', suites[1][0].attributes['time'].value)
-
- def test_attribute_disable(self):
- tc = TestCase('Disabled-Test')
- tc.is_enabled = False
- tss = [TestSuite('suite1', [tc])]
- suites = serialize_and_read(tss)
-
- self.assertEqual('1', suites[0][0].attributes['disabled'].value)
-
- def test_stderr(self):
- suites = serialize_and_read(
- TestSuite(name='test', stderr='I am stderr!',
- test_cases=[TestCase(name='Test1')]))[0]
- self.assertEqual('I am stderr!',
- suites[0].getElementsByTagName('system-err')[0].firstChild.data)
-
- def test_stdout_stderr(self):
- suites = serialize_and_read(
- TestSuite(name='test', stdout='I am stdout!',
- stderr='I am stderr!',
- test_cases=[TestCase(name='Test1')]))[0]
- self.assertEqual('I am stderr!',
- suites[0].getElementsByTagName('system-err')[0].firstChild.data)
- self.assertEqual('I am stdout!',
- suites[0].getElementsByTagName('system-out')[0].firstChild.data)
-
- def test_no_assertions(self):
- suites = serialize_and_read(
- TestSuite(name='test',
- test_cases=[TestCase(name='Test1')]))[0]
- self.assertFalse(suites[0].getElementsByTagName('testcase')[0].hasAttribute('assertions'))
-
- def test_assertions(self):
- suites = serialize_and_read(
- TestSuite(name='test',
- test_cases=[TestCase(name='Test1',
- assertions=5)]))[0]
- self.assertEquals('5',
- suites[0].getElementsByTagName('testcase')[0].attributes['assertions'].value)
-
- # @todo: add more tests for the other attributes and properties
-
- def test_to_xml_string(self):
- test_suites = [TestSuite(name='suite1', test_cases=[TestCase(name='Test1')]),
- TestSuite(name='suite2', test_cases=[TestCase(name='Test2')])]
- xml_string = TestSuite.to_xml_string(test_suites)
- if PY2:
- self.assertTrue(isinstance(xml_string, unicode))
- expected_xml_string = textwrap.dedent("""
-
-
- \t
- \t\t
- \t
- \t
- \t\t
- \t
-
- """.strip("\n")) # NOQA
- self.assertEqual(xml_string, expected_xml_string)
-
- def test_to_xml_string_test_suites_not_a_list(self):
- test_suites = TestSuite('suite1', [TestCase('Test1')])
-
- try:
- TestSuite.to_xml_string(test_suites)
- except Exception as exc:
- self.assertEqual(
- str(exc), 'test_suites must be a list of test suites')
-
-
-class TestCaseTests(unittest.TestCase):
- def test_init(self):
- (ts, tcs) = serialize_and_read(
- TestSuite('test', [TestCase('Test1')]))[0]
- verify_test_case(self, tcs[0], {'name': 'Test1'})
-
- def test_init_classname(self):
- (ts, tcs) = serialize_and_read(
- TestSuite('test',
- [TestCase(name='Test1', classname='some.class.name')]))[0]
- verify_test_case(
- self, tcs[0], {'name': 'Test1', 'classname': 'some.class.name'})
-
- def test_init_classname_time(self):
- (ts, tcs) = serialize_and_read(
- TestSuite('test',
- [TestCase(name='Test1', classname='some.class.name',
- elapsed_sec=123.345)]))[0]
- verify_test_case(
- self, tcs[0], {'name': 'Test1', 'classname': 'some.class.name',
- 'time': ("%f" % 123.345)})
-
- def test_init_classname_time_timestamp(self):
- (ts, tcs) = serialize_and_read(
- TestSuite('test',
- [TestCase(name='Test1', classname='some.class.name',
- elapsed_sec=123.345, timestamp=99999)]))[0]
- verify_test_case(
- self, tcs[0], {'name': 'Test1', 'classname': 'some.class.name',
- 'time': ("%f" % 123.345),
- 'timestamp': ("%s" % 99999)})
-
- def test_init_stderr(self):
- (ts, tcs) = serialize_and_read(
- TestSuite(
- 'test', [TestCase(name='Test1', classname='some.class.name',
- elapsed_sec=123.345, stderr='I am stderr!')]))[0]
- verify_test_case(
- self, tcs[0],
- {'name': 'Test1', 'classname': 'some.class.name',
- 'time': ("%f" % 123.345)}, stderr='I am stderr!')
-
- def test_init_stdout_stderr(self):
- (ts, tcs) = serialize_and_read(
- TestSuite(
- 'test', [TestCase(
- name='Test1', classname='some.class.name',
- elapsed_sec=123.345, stdout='I am stdout!',
- stderr='I am stderr!')]))[0]
- verify_test_case(
- self, tcs[0],
- {'name': 'Test1', 'classname': 'some.class.name',
- 'time': ("%f" % 123.345)},
- stdout='I am stdout!', stderr='I am stderr!')
-
- def test_init_disable(self):
- tc = TestCase('Disabled-Test')
- tc.is_enabled = False
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(self, tcs[0], {'name': 'Disabled-Test'})
-
- def test_init_failure_message(self):
- tc = TestCase('Failure-Message')
- tc.add_failure_info("failure message")
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(
- self, tcs[0], {'name': 'Failure-Message'},
- failure_message="failure message")
-
- def test_init_failure_output(self):
- tc = TestCase('Failure-Output')
- tc.add_failure_info(output="I failed!")
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(
- self, tcs[0], {'name': 'Failure-Output'},
- failure_output="I failed!")
-
- def test_init_failure_type(self):
- tc = TestCase('Failure-Type')
- tc.add_failure_info(failure_type='com.example.Error')
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(self, tcs[0], {'name': 'Failure-Type'})
-
- tc.add_failure_info("failure message")
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(
- self, tcs[0], {'name': 'Failure-Type'},
- failure_message="failure message",
- failure_type='com.example.Error')
-
- def test_init_failure(self):
- tc = TestCase('Failure-Message-and-Output')
- tc.add_failure_info("failure message", "I failed!")
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(
- self, tcs[0], {'name': 'Failure-Message-and-Output'},
- failure_message="failure message", failure_output="I failed!",
- failure_type='failure')
-
- def test_init_error_message(self):
- tc = TestCase('Error-Message')
- tc.add_error_info("error message")
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(
- self, tcs[0], {'name': 'Error-Message'},
- error_message="error message")
-
- def test_init_error_output(self):
- tc = TestCase('Error-Output')
- tc.add_error_info(output="I errored!")
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(
- self, tcs[0], {'name': 'Error-Output'}, error_output="I errored!")
-
- def test_init_error_type(self):
- tc = TestCase('Error-Type')
- tc.add_error_info(error_type='com.example.Error')
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(self, tcs[0], {'name': 'Error-Type'})
-
- tc.add_error_info("error message")
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(
- self, tcs[0], {'name': 'Error-Type'},
- error_message="error message",
- error_type='com.example.Error')
-
- def test_init_error(self):
- tc = TestCase('Error-Message-and-Output')
- tc.add_error_info("error message", "I errored!")
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(
- self, tcs[0], {'name': 'Error-Message-and-Output'},
- error_message="error message", error_output="I errored!",
- error_type="error")
-
- def test_init_skipped_message(self):
- tc = TestCase('Skipped-Message')
- tc.add_skipped_info("skipped message")
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(
- self, tcs[0], {'name': 'Skipped-Message'},
- skipped_message="skipped message")
-
- def test_init_skipped_output(self):
- tc = TestCase('Skipped-Output')
- tc.add_skipped_info(output="I skipped!")
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(
- self, tcs[0], {'name': 'Skipped-Output'},
- skipped_output="I skipped!")
-
- def test_init_skipped_err_output(self):
- tc = TestCase('Skipped-Output')
- tc.add_skipped_info(output="I skipped!")
- tc.add_error_info(output="I skipped with an error!")
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(
- self, tcs[0],
- {'name': 'Skipped-Output'},
- skipped_output="I skipped!",
- error_output="I skipped with an error!")
-
- def test_init_skipped(self):
- tc = TestCase('Skipped-Message-and-Output')
- tc.add_skipped_info("skipped message", "I skipped!")
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(
- self, tcs[0], {'name': 'Skipped-Message-and-Output'},
- skipped_message="skipped message", skipped_output="I skipped!")
-
- def test_init_legal_unicode_char(self):
- tc = TestCase('Failure-Message')
- tc.add_failure_info(
- u("failure message with legal unicode char: [\x22]"))
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(
- self, tcs[0], {'name': 'Failure-Message'}, failure_message=u(
- "failure message with legal unicode char: [\x22]"))
-
- def test_init_illegal_unicode_char(self):
- tc = TestCase('Failure-Message')
- tc.add_failure_info(
- u("failure message with illegal unicode char: [\x02]"))
- (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0]
- verify_test_case(
- self, tcs[0], {'name': 'Failure-Message'}, failure_message=u(
- "failure message with illegal unicode char: []"))
-
- def test_init_utf8(self):
- tc = TestCase(name='Test äöü', classname='some.class.name.äöü',
- elapsed_sec=123.345, stdout='I am stdöüt!',
- stderr='I am stdärr!')
- tc.add_skipped_info(message='Skipped äöü', output="I skippäd!")
- tc.add_error_info(message='Skipped error äöü',
- output="I skippäd with an error!")
- test_suite = TestSuite('Test UTF-8', [tc])
- (ts, tcs) = serialize_and_read(test_suite, encoding='utf-8')[0]
- verify_test_case(self, tcs[0], {'name': decode('Test äöü', 'utf-8'),
- 'classname': decode('some.class.name.äöü', 'utf-8'),
- 'time': ("%f" % 123.345)},
- stdout=decode('I am stdöüt!', 'utf-8'), stderr=decode('I am stdärr!', 'utf-8'),
- skipped_message=decode('Skipped äöü', 'utf-8'),
- skipped_output=decode('I skippäd!', 'utf-8'),
- error_message=decode('Skipped error äöü', 'utf-8'),
- error_output=decode('I skippäd with an error!', 'utf-8'))
-
- def test_init_unicode(self):
- tc = TestCase(name=decode('Test äöü', 'utf-8'),
- classname=decode('some.class.name.äöü', 'utf-8'),
- elapsed_sec=123.345,
- stdout=decode('I am stdöüt!', 'utf-8'),
- stderr=decode('I am stdärr!', 'utf-8'))
- tc.add_skipped_info(message=decode('Skipped äöü', 'utf-8'),
- output=decode('I skippäd!', 'utf-8'))
- tc.add_error_info(message=decode('Skipped error äöü', 'utf-8'),
- output=decode('I skippäd with an error!', 'utf-8'))
-
- (ts, tcs) = serialize_and_read(TestSuite('Test Unicode',
- [tc]))[0]
- verify_test_case(self, tcs[0], {'name': decode('Test äöü', 'utf-8'),
- 'classname': decode('some.class.name.äöü', 'utf-8'),
- 'time': ("%f" % 123.345)},
- stdout=decode('I am stdöüt!', 'utf-8'),
- stderr=decode('I am stdärr!', 'utf-8'),
- skipped_message=decode('Skipped äöü', 'utf-8'),
- skipped_output=decode('I skippäd!', 'utf-8'),
- error_message=decode('Skipped error äöü', 'utf-8'),
- error_output=decode('I skippäd with an error!', 'utf-8'))
-
-
-def verify_test_case(tc, test_case_element, expected_attributes,
- error_message=None, error_output=None, error_type=None,
- failure_message=None, failure_output=None,
- failure_type=None,
- skipped_message=None, skipped_output=None,
- stdout=None, stderr=None):
- for k, v in expected_attributes.items():
- tc.assertEqual(v, test_case_element.attributes[k].value)
-
- for k in test_case_element.attributes.keys():
- tc.assertTrue(k in expected_attributes.keys())
-
- if stderr:
- tc.assertEqual(
- stderr, test_case_element.getElementsByTagName(
- 'system-err')[0].firstChild.nodeValue.strip())
- if stdout:
- tc.assertEqual(
- stdout, test_case_element.getElementsByTagName(
- 'system-out')[0].firstChild.nodeValue.strip())
-
- errors = test_case_element.getElementsByTagName('error')
- if error_message or error_output:
- tc.assertTrue(len(errors) > 0)
- else:
- tc.assertEqual(0, len(errors))
-
- if error_message:
- tc.assertEqual(
- error_message, errors[0].attributes['message'].value)
-
- if error_type and errors:
- tc.assertEqual(
- error_type, errors[0].attributes['type'].value)
-
- if error_output:
- tc.assertEqual(
- error_output, errors[0].firstChild.nodeValue.strip())
-
- failures = test_case_element.getElementsByTagName('failure')
- if failure_message or failure_output:
- tc.assertTrue(len(failures) > 0)
- else:
- tc.assertEqual(0, len(failures))
-
- if failure_message:
- tc.assertEqual(
- failure_message, failures[0].attributes['message'].value)
-
- if failure_type and failures:
- tc.assertEqual(
- failure_type, failures[0].attributes['type'].value)
-
- if failure_output:
- tc.assertEqual(
- failure_output, failures[0].firstChild.nodeValue.strip())
-
- skipped = test_case_element.getElementsByTagName('skipped')
- if skipped_message or skipped_output:
- tc.assertTrue(len(skipped) > 0)
- else:
- tc.assertEqual(0, len(skipped))
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/asserts.py b/tests/asserts.py
new file mode 100644
index 0000000..82c48b4
--- /dev/null
+++ b/tests/asserts.py
@@ -0,0 +1,83 @@
+def verify_test_case( # noqa: E302
+ test_case_element,
+ expected_attributes,
+ error_message=None,
+ error_output=None,
+ error_type=None,
+ failure_message=None,
+ failure_output=None,
+ failure_type=None,
+ skipped_message=None,
+ skipped_output=None,
+ stdout=None,
+ stderr=None,
+ errors=None,
+ failures=None,
+ skipped=None,
+):
+ for k, v in expected_attributes.items():
+ assert test_case_element.attributes[k].value == v
+
+ for k in test_case_element.attributes.keys():
+ assert k in expected_attributes.keys()
+
+ if stderr:
+ assert test_case_element.getElementsByTagName("system-err")[0].firstChild.nodeValue.strip() == stderr
+ if stdout:
+ assert test_case_element.getElementsByTagName("system-out")[0].firstChild.nodeValue.strip() == stdout
+
+ _errors = test_case_element.getElementsByTagName("error")
+ if error_message or error_output:
+ assert len(_errors) > 0
+ elif errors:
+ assert len(errors) == len(_errors)
+ else:
+ assert len(_errors) == 0
+
+ if error_message:
+ assert _errors[0].attributes["message"].value == error_message
+
+ if error_type and _errors:
+ assert _errors[0].attributes["type"].value == error_type
+
+ if error_output:
+ assert _errors[0].firstChild.nodeValue.strip() == error_output
+
+ for error_exp, error_r in zip(errors or [], _errors):
+ assert error_r.attributes["message"].value == error_exp["message"]
+ assert error_r.firstChild.nodeValue.strip() == error_exp["output"]
+ assert error_r.attributes["type"].value == error_exp["type"]
+
+ _failures = test_case_element.getElementsByTagName("failure")
+ if failure_message or failure_output:
+ assert len(_failures) > 0
+ elif failures:
+ assert len(failures) == len(_failures)
+ else:
+ assert len(_failures) == 0
+
+ if failure_message:
+ assert _failures[0].attributes["message"].value == failure_message
+
+ if failure_type and _failures:
+ assert _failures[0].attributes["type"].value == failure_type
+
+ if failure_output:
+ assert _failures[0].firstChild.nodeValue.strip() == failure_output
+
+ for failure_exp, failure_r in zip(failures or [], _failures):
+ assert failure_r.attributes["message"].value == failure_exp["message"]
+ assert failure_r.firstChild.nodeValue.strip() == failure_exp["output"]
+ assert failure_r.attributes["type"].value == failure_exp["type"]
+
+ _skipped = test_case_element.getElementsByTagName("skipped")
+ if skipped_message or skipped_output:
+ assert len(_skipped) > 0
+ elif skipped:
+ assert len(skipped) == len(_skipped)
+ else:
+ assert len(_skipped) == 0
+
+ for skipped_exp, skipped_r in zip(skipped or [], _skipped):
+ assert skipped_r.attributes["message"].value == skipped_exp["message"]
+ assert skipped_r.firstChild.nodeValue.strip() == skipped_exp["output"]
diff --git a/tests/serializer.py b/tests/serializer.py
new file mode 100644
index 0000000..8a06200
--- /dev/null
+++ b/tests/serializer.py
@@ -0,0 +1,52 @@
+import codecs
+import os
+import tempfile
+from xml.dom import minidom
+
+from six import PY2
+
+from junit_xml import to_xml_report_file, to_xml_report_string
+
+
+def serialize_and_read(test_suites, to_file=False, prettyprint=False, encoding=None):
+ """writes the test suite to an XML string and then re-reads it using minidom,
+ returning => (test suite element, list of test case elements)"""
+ try:
+ iter(test_suites)
+ except TypeError:
+ test_suites = [test_suites]
+
+ if to_file:
+ fd, filename = tempfile.mkstemp(text=True)
+ os.close(fd)
+ with codecs.open(filename, mode="w", encoding=encoding) as f:
+ to_xml_report_file(f, test_suites, prettyprint=prettyprint, encoding=encoding)
+ print("Serialized XML to temp file [%s]" % filename)
+ xmldoc = minidom.parse(filename)
+ os.remove(filename)
+ else:
+ xml_string = to_xml_report_string(test_suites, prettyprint=prettyprint, encoding=encoding)
+ if PY2:
+ assert isinstance(xml_string, unicode) # noqa: F821
+ print("Serialized XML to string:\n%s" % xml_string)
+ if encoding:
+ xml_string = xml_string.encode(encoding)
+ xmldoc = minidom.parseString(xml_string)
+
+ def remove_blanks(node):
+ for x in node.childNodes:
+ if x.nodeType == minidom.Node.TEXT_NODE:
+ if x.nodeValue:
+ x.nodeValue = x.nodeValue.strip()
+ elif x.nodeType == minidom.Node.ELEMENT_NODE:
+ remove_blanks(x)
+
+ remove_blanks(xmldoc)
+ xmldoc.normalize()
+
+ ret = []
+ suites = xmldoc.getElementsByTagName("testsuites")[0]
+ for suite in suites.getElementsByTagName("testsuite"):
+ cases = suite.getElementsByTagName("testcase")
+ ret.append((suite, cases))
+ return ret
diff --git a/tests/test_test_case.py b/tests/test_test_case.py
new file mode 100644
index 0000000..b9817de
--- /dev/null
+++ b/tests/test_test_case.py
@@ -0,0 +1,324 @@
+# -*- coding: UTF-8 -*-
+from __future__ import with_statement
+
+from six import u
+
+from .asserts import verify_test_case
+from junit_xml import TestCase as Case
+from junit_xml import TestSuite as Suite
+from junit_xml import decode
+from .serializer import serialize_and_read
+
+
+def test_init():
+ ts, tcs = serialize_and_read(Suite("test", [Case("Test1")]))[0]
+ verify_test_case(tcs[0], {"name": "Test1"})
+
+
+def test_init_classname():
+ ts, tcs = serialize_and_read(Suite("test", [Case(name="Test1", classname="some.class.name")]))[0]
+ verify_test_case(tcs[0], {"name": "Test1", "classname": "some.class.name"})
+
+
+def test_init_classname_time():
+ ts, tcs = serialize_and_read(Suite("test", [Case(name="Test1", classname="some.class.name", elapsed_sec=123.345)]))[
+ 0
+ ]
+ verify_test_case(tcs[0], {"name": "Test1", "classname": "some.class.name", "time": ("%f" % 123.345)})
+
+
+def test_init_classname_time_timestamp():
+ ts, tcs = serialize_and_read(
+ Suite("test", [Case(name="Test1", classname="some.class.name", elapsed_sec=123.345, timestamp=99999)])
+ )[0]
+ verify_test_case(
+ tcs[0], {"name": "Test1", "classname": "some.class.name", "time": ("%f" % 123.345), "timestamp": ("%s" % 99999)}
+ )
+
+
+def test_init_stderr():
+ ts, tcs = serialize_and_read(
+ Suite("test", [Case(name="Test1", classname="some.class.name", elapsed_sec=123.345, stderr="I am stderr!")])
+ )[0]
+ verify_test_case(
+ tcs[0], {"name": "Test1", "classname": "some.class.name", "time": ("%f" % 123.345)}, stderr="I am stderr!"
+ )
+
+
+def test_init_stdout_stderr():
+ ts, tcs = serialize_and_read(
+ Suite(
+ "test",
+ [
+ Case(
+ name="Test1",
+ classname="some.class.name",
+ elapsed_sec=123.345,
+ stdout="I am stdout!",
+ stderr="I am stderr!",
+ )
+ ],
+ )
+ )[0]
+ verify_test_case(
+ tcs[0],
+ {"name": "Test1", "classname": "some.class.name", "time": ("%f" % 123.345)},
+ stdout="I am stdout!",
+ stderr="I am stderr!",
+ )
+
+
+def test_init_disable():
+ tc = Case("Disabled-Test")
+ tc.is_enabled = False
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(tcs[0], {"name": "Disabled-Test"})
+
+
+def test_init_failure_message():
+ tc = Case("Failure-Message")
+ tc.add_failure_info("failure message")
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(tcs[0], {"name": "Failure-Message"}, failure_message="failure message")
+
+
+def test_init_failure_output():
+ tc = Case("Failure-Output")
+ tc.add_failure_info(output="I failed!")
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(tcs[0], {"name": "Failure-Output"}, failure_output="I failed!")
+
+
+def test_init_failure_type():
+ tc = Case("Failure-Type")
+ tc.add_failure_info(failure_type="com.example.Error")
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(tcs[0], {"name": "Failure-Type"})
+
+ tc.add_failure_info("failure message")
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(
+ tcs[0], {"name": "Failure-Type"}, failure_message="failure message", failure_type="com.example.Error"
+ )
+
+
+def test_init_failure():
+ tc = Case("Failure-Message-and-Output")
+ tc.add_failure_info("failure message", "I failed!")
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(
+ tcs[0],
+ {"name": "Failure-Message-and-Output"},
+ failure_message="failure message",
+ failure_output="I failed!",
+ failure_type="failure",
+ )
+
+
+def test_init_error_message():
+ tc = Case("Error-Message")
+ tc.add_error_info("error message")
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(tcs[0], {"name": "Error-Message"}, error_message="error message")
+
+
+def test_init_error_output():
+ tc = Case("Error-Output")
+ tc.add_error_info(output="I errored!")
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(tcs[0], {"name": "Error-Output"}, error_output="I errored!")
+
+
+def test_init_error_type():
+ tc = Case("Error-Type")
+ tc.add_error_info(error_type="com.example.Error")
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(tcs[0], {"name": "Error-Type"})
+
+ tc.add_error_info("error message")
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(tcs[0], {"name": "Error-Type"}, error_message="error message", error_type="com.example.Error")
+
+
+def test_init_error():
+ tc = Case("Error-Message-and-Output")
+ tc.add_error_info("error message", "I errored!")
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(
+ tcs[0],
+ {"name": "Error-Message-and-Output"},
+ error_message="error message",
+ error_output="I errored!",
+ error_type="error",
+ )
+
+
+def test_init_skipped_message():
+ tc = Case("Skipped-Message")
+ tc.add_skipped_info("skipped message")
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(tcs[0], {"name": "Skipped-Message"}, skipped_message="skipped message")
+
+
+def test_init_skipped_output():
+ tc = Case("Skipped-Output")
+ tc.add_skipped_info(output="I skipped!")
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(tcs[0], {"name": "Skipped-Output"}, skipped_output="I skipped!")
+
+
+def test_init_skipped_err_output():
+ tc = Case("Skipped-Output")
+ tc.add_skipped_info(output="I skipped!")
+ tc.add_error_info(output="I skipped with an error!")
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(
+ tcs[0], {"name": "Skipped-Output"}, skipped_output="I skipped!", error_output="I skipped with an error!"
+ )
+
+
+def test_init_skipped():
+ tc = Case("Skipped-Message-and-Output")
+ tc.add_skipped_info("skipped message", "I skipped!")
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(
+ tcs[0], {"name": "Skipped-Message-and-Output"}, skipped_message="skipped message", skipped_output="I skipped!"
+ )
+
+
+def test_init_legal_unicode_char():
+ tc = Case("Failure-Message")
+ tc.add_failure_info(u("failure message with legal unicode char: [\x22]"))
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(
+ tcs[0], {"name": "Failure-Message"}, failure_message=u("failure message with legal unicode char: [\x22]")
+ )
+
+
+def test_init_illegal_unicode_char():
+ tc = Case("Failure-Message")
+ tc.add_failure_info(u("failure message with illegal unicode char: [\x02]"))
+ ts, tcs = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(
+ tcs[0], {"name": "Failure-Message"}, failure_message=u("failure message with illegal unicode char: []")
+ )
+
+
+def test_init_utf8():
+ tc = Case(
+ name="Test äöü",
+ classname="some.class.name.äöü",
+ elapsed_sec=123.345,
+ stdout="I am stdöüt!",
+ stderr="I am stdärr!",
+ )
+ tc.add_skipped_info(message="Skipped äöü", output="I skippäd!")
+ tc.add_error_info(message="Skipped error äöü", output="I skippäd with an error!")
+ test_suite = Suite("Test UTF-8", [tc])
+ ts, tcs = serialize_and_read(test_suite, encoding="utf-8")[0]
+ verify_test_case(
+ tcs[0],
+ {
+ "name": decode("Test äöü", "utf-8"),
+ "classname": decode("some.class.name.äöü", "utf-8"),
+ "time": ("%f" % 123.345),
+ },
+ stdout=decode("I am stdöüt!", "utf-8"),
+ stderr=decode("I am stdärr!", "utf-8"),
+ skipped_message=decode("Skipped äöü", "utf-8"),
+ skipped_output=decode("I skippäd!", "utf-8"),
+ error_message=decode("Skipped error äöü", "utf-8"),
+ error_output=decode("I skippäd with an error!", "utf-8"),
+ )
+
+
+def test_init_unicode():
+ tc = Case(
+ name=decode("Test äöü", "utf-8"),
+ classname=decode("some.class.name.äöü", "utf-8"),
+ elapsed_sec=123.345,
+ stdout=decode("I am stdöüt!", "utf-8"),
+ stderr=decode("I am stdärr!", "utf-8"),
+ )
+ tc.add_skipped_info(message=decode("Skipped äöü", "utf-8"), output=decode("I skippäd!", "utf-8"))
+ tc.add_error_info(message=decode("Skipped error äöü", "utf-8"), output=decode("I skippäd with an error!", "utf-8"))
+
+ ts, tcs = serialize_and_read(Suite("Test Unicode", [tc]))[0]
+ verify_test_case(
+ tcs[0],
+ {
+ "name": decode("Test äöü", "utf-8"),
+ "classname": decode("some.class.name.äöü", "utf-8"),
+ "time": ("%f" % 123.345),
+ },
+ stdout=decode("I am stdöüt!", "utf-8"),
+ stderr=decode("I am stdärr!", "utf-8"),
+ skipped_message=decode("Skipped äöü", "utf-8"),
+ skipped_output=decode("I skippäd!", "utf-8"),
+ error_message=decode("Skipped error äöü", "utf-8"),
+ error_output=decode("I skippäd with an error!", "utf-8"),
+ )
+
+
+def test_multiple_errors():
+ """Tests multiple errors in one test case"""
+ tc = Case("Multiple error", allow_multiple_subelements=True)
+ tc.add_error_info("First error", "First error message")
+ (_, tcs) = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(
+ tcs[0],
+ {"name": "Multiple error"},
+ errors=[{"message": "First error", "output": "First error message", "type": "error"}],
+ )
+ tc.add_error_info("Second error", "Second error message")
+ (_, tcs) = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(
+ tcs[0],
+ {"name": "Multiple error"},
+ errors=[
+ {"message": "First error", "output": "First error message", "type": "error"},
+ {"message": "Second error", "output": "Second error message", "type": "error"},
+ ],
+ )
+
+
+def test_multiple_failures():
+ """Tests multiple failures in one test case"""
+ tc = Case("Multiple failures", allow_multiple_subelements=True)
+ tc.add_failure_info("First failure", "First failure message")
+ (_, tcs) = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(
+ tcs[0],
+ {"name": "Multiple failures"},
+ failures=[{"message": "First failure", "output": "First failure message", "type": "failure"}],
+ )
+ tc.add_failure_info("Second failure", "Second failure message")
+ (_, tcs) = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(
+ tcs[0],
+ {"name": "Multiple failures"},
+ failures=[
+ {"message": "First failure", "output": "First failure message", "type": "failure"},
+ {"message": "Second failure", "output": "Second failure message", "type": "failure"},
+ ],
+ )
+
+
+def test_multiple_skipped():
+ """Tests multiple skipped messages in one test case"""
+ tc = Case("Multiple skipped", allow_multiple_subelements=True)
+ tc.add_skipped_info("First skipped", "First skipped message")
+ (_, tcs) = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(
+ tcs[0], {"name": "Multiple skipped"}, skipped=[{"message": "First skipped", "output": "First skipped message"}]
+ )
+ tc.add_skipped_info("Second skipped", "Second skipped message")
+ (_, tcs) = serialize_and_read(Suite("test", [tc]))[0]
+ verify_test_case(
+ tcs[0],
+ {"name": "Multiple skipped"},
+ skipped=[
+ {"message": "First skipped", "output": "First skipped message"},
+ {"message": "Second skipped", "output": "Second skipped message"},
+ ],
+ )
diff --git a/tests/test_test_suite.py b/tests/test_test_suite.py
new file mode 100644
index 0000000..a6591fe
--- /dev/null
+++ b/tests/test_test_suite.py
@@ -0,0 +1,241 @@
+# -*- coding: UTF-8 -*-
+from __future__ import with_statement
+
+import textwrap
+import warnings
+
+import pytest
+from six import PY2, StringIO
+
+from .asserts import verify_test_case
+from junit_xml import TestCase as Case
+from junit_xml import TestSuite as Suite
+from junit_xml import decode, to_xml_report_string
+from .serializer import serialize_and_read
+
+
+def test_single_suite_single_test_case():
+ with pytest.raises(TypeError) as excinfo:
+ serialize_and_read(Suite("test", Case("Test1")), to_file=True)[0]
+ assert str(excinfo.value) == "test_cases must be a list of test cases"
+
+
+def test_single_suite_no_test_cases():
+ properties = {"foo": "bar"}
+ package = "mypackage"
+ timestamp = 1398382805
+
+ ts, tcs = serialize_and_read(
+ Suite(
+ name="test",
+ test_cases=[],
+ hostname="localhost",
+ id=1,
+ properties=properties,
+ package=package,
+ timestamp=timestamp,
+ ),
+ to_file=True,
+ prettyprint=True,
+ )[0]
+ assert ts.tagName == "testsuite"
+ assert ts.attributes["package"].value == package
+ assert ts.attributes["timestamp"].value == str(timestamp)
+ assert ts.childNodes[0].childNodes[0].attributes["name"].value == "foo"
+ assert ts.childNodes[0].childNodes[0].attributes["value"].value == "bar"
+
+
+def test_single_suite_no_test_cases_utf8():
+ properties = {"foö": "bär"}
+ package = "mypäckage"
+ timestamp = 1398382805
+
+ test_suite = Suite(
+ name="äöü",
+ test_cases=[],
+ hostname="löcalhost",
+ id="äöü",
+ properties=properties,
+ package=package,
+ timestamp=timestamp,
+ )
+ ts, tcs = serialize_and_read(test_suite, to_file=True, prettyprint=True, encoding="utf-8")[0]
+ assert ts.tagName == "testsuite"
+ assert ts.attributes["package"].value == decode(package, "utf-8")
+ assert ts.attributes["timestamp"].value == str(timestamp)
+ assert ts.childNodes[0].childNodes[0].attributes["name"].value == decode("foö", "utf-8")
+ assert ts.childNodes[0].childNodes[0].attributes["value"].value == decode("bär", "utf-8")
+
+
+def test_single_suite_no_test_cases_unicode():
+ properties = {decode("foö", "utf-8"): decode("bär", "utf-8")}
+ package = decode("mypäckage", "utf-8")
+ timestamp = 1398382805
+
+ ts, tcs = serialize_and_read(
+ Suite(
+ name=decode("äöü", "utf-8"),
+ test_cases=[],
+ hostname=decode("löcalhost", "utf-8"),
+ id=decode("äöü", "utf-8"),
+ properties=properties,
+ package=package,
+ timestamp=timestamp,
+ ),
+ to_file=True,
+ prettyprint=True,
+ encoding="utf-8",
+ )[0]
+ assert ts.tagName == "testsuite"
+ assert ts.attributes["package"].value == package
+ assert ts.attributes["timestamp"].value, str(timestamp)
+ assert ts.childNodes[0].childNodes[0].attributes["name"].value == decode("foö", "utf-8")
+ assert ts.childNodes[0].childNodes[0].attributes["value"].value == decode("bär", "utf-8")
+
+
+def test_single_suite_to_file():
+ ts, tcs = serialize_and_read(Suite("test", [Case("Test1")]), to_file=True)[0]
+ verify_test_case(tcs[0], {"name": "Test1"})
+
+
+def test_single_suite_to_file_prettyprint():
+ ts, tcs = serialize_and_read(Suite("test", [Case("Test1")]), to_file=True, prettyprint=True)[0]
+ verify_test_case(tcs[0], {"name": "Test1"})
+
+
+def test_single_suite_prettyprint():
+ ts, tcs = serialize_and_read(Suite("test", [Case("Test1")]), to_file=False, prettyprint=True)[0]
+ verify_test_case(tcs[0], {"name": "Test1"})
+
+
+def test_single_suite_to_file_no_prettyprint():
+ ts, tcs = serialize_and_read(Suite("test", [Case("Test1")]), to_file=True, prettyprint=False)[0]
+ verify_test_case(tcs[0], {"name": "Test1"})
+
+
+def test_multiple_suites_to_file():
+ tss = [Suite("suite1", [Case("Test1")]), Suite("suite2", [Case("Test2")])]
+ suites = serialize_and_read(tss, to_file=True)
+
+ assert suites[0][0].attributes["name"].value == "suite1"
+ verify_test_case(suites[0][1][0], {"name": "Test1"})
+
+ assert suites[1][0].attributes["name"].value == "suite2"
+ verify_test_case(suites[1][1][0], {"name": "Test2"})
+
+
+def test_multiple_suites_to_string():
+ tss = [Suite("suite1", [Case("Test1")]), Suite("suite2", [Case("Test2")])]
+ suites = serialize_and_read(tss)
+
+ assert suites[0][0].attributes["name"].value == "suite1"
+ verify_test_case(suites[0][1][0], {"name": "Test1"})
+
+ assert suites[1][0].attributes["name"].value == "suite2"
+ verify_test_case(suites[1][1][0], {"name": "Test2"})
+
+
+def test_attribute_time():
+ tss = [
+ Suite(
+ "suite1",
+ [
+ Case(name="Test1", classname="some.class.name", elapsed_sec=123.345),
+ Case(name="Test2", classname="some2.class.name", elapsed_sec=123.345),
+ ],
+ ),
+ Suite("suite2", [Case("Test2")]),
+ ]
+ suites = serialize_and_read(tss)
+
+ assert suites[0][0].attributes["name"].value == "suite1"
+ assert suites[0][0].attributes["time"].value == "246.69"
+
+ assert suites[1][0].attributes["name"].value == "suite2"
+ # here the time in testsuite is "0" even there is no attribute time for
+ # testcase
+ assert suites[1][0].attributes["time"].value == "0"
+
+
+def test_attribute_disable():
+ tc = Case("Disabled-Test")
+ tc.is_enabled = False
+ tss = [Suite("suite1", [tc])]
+ suites = serialize_and_read(tss)
+
+ assert suites[0][0].attributes["disabled"].value == "1"
+
+
+def test_stderr():
+ suites = serialize_and_read(Suite(name="test", stderr="I am stderr!", test_cases=[Case(name="Test1")]))[0]
+ assert suites[0].getElementsByTagName("system-err")[0].firstChild.data == "I am stderr!"
+
+
+def test_stdout_stderr():
+ suites = serialize_and_read(
+ Suite(name="test", stdout="I am stdout!", stderr="I am stderr!", test_cases=[Case(name="Test1")])
+ )[0]
+ assert suites[0].getElementsByTagName("system-err")[0].firstChild.data == "I am stderr!"
+ assert suites[0].getElementsByTagName("system-out")[0].firstChild.data == "I am stdout!"
+
+
+def test_no_assertions():
+ suites = serialize_and_read(Suite(name="test", test_cases=[Case(name="Test1")]))[0]
+ assert not suites[0].getElementsByTagName("testcase")[0].hasAttribute("assertions")
+
+
+def test_assertions():
+ suites = serialize_and_read(Suite(name="test", test_cases=[Case(name="Test1", assertions=5)]))[0]
+ assert suites[0].getElementsByTagName("testcase")[0].attributes["assertions"].value == "5"
+
+ # @todo: add more tests for the other attributes and properties
+
+
+def test_to_xml_string():
+ test_suites = [
+ Suite(name="suite1", test_cases=[Case(name="Test1")]),
+ Suite(name="suite2", test_cases=[Case(name="Test2")]),
+ ]
+ xml_string = to_xml_report_string(test_suites)
+ if PY2:
+ assert isinstance(xml_string, unicode) # noqa: F821
+ expected_xml_string = textwrap.dedent(
+ """
+
+
+ \t
+ \t\t
+ \t
+ \t
+ \t\t
+ \t
+
+ """.strip(
+ "\n"
+ )
+ )
+ assert xml_string == expected_xml_string
+
+
+def test_to_xml_string_test_suites_not_a_list():
+ test_suites = Suite("suite1", [Case("Test1")])
+
+ with pytest.raises(TypeError) as excinfo:
+ to_xml_report_string(test_suites)
+ assert str(excinfo.value) == "test_suites must be a list of test suites"
+
+
+def test_deprecated_to_xml_string():
+ with warnings.catch_warnings(record=True) as w:
+ Suite.to_xml_string([])
+ assert len(w) == 1
+ assert issubclass(w[0].category, DeprecationWarning)
+ assert "Testsuite.to_xml_string is deprecated" in str(w[0].message)
+
+
+def test_deprecated_to_file():
+ with warnings.catch_warnings(record=True) as w:
+ Suite.to_file(StringIO(), [])
+ assert len(w) == 1
+ assert issubclass(w[0].category, DeprecationWarning)
+ assert "Testsuite.to_file is deprecated" in str(w[0].message)
diff --git a/tox.ini b/tox.ini
index 4ccdde0..3fdb993 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py26, py27, pypy, py32, py33, py34, cover, flake8
+envlist = py27, pypy, py35, py36, py37, py38, cover, flake8
sitepackages = False
[testenv]
@@ -28,6 +28,7 @@ commands =
[testenv:flake8]
deps =
+ flake8-black
pytest
pytest-sugar
pytest-flake8