From 67bc432f7d7108702aac4c346d5a5ab41c9c2b19 Mon Sep 17 00:00:00 2001 From: Damien Nozay Date: Mon, 17 Feb 2020 16:29:39 -0800 Subject: [PATCH 01/29] info / fixes about xUnit plugin (#215) - see #209; add a utility function for XSLT transformation. - minor doc fixes. - fix incorrect version, I called out xunit plugin 2.2.4 earlier and I must not have looked at the version properly. --- README.md | 83 +++++++--- tests/testsuite.py | 44 ++++-- .../junit-10.xsd | 0 .../junit-10.xsd | 147 ++++++++++++++++++ xmlrunner/extra/xunit_plugin.py | 28 ++++ 5 files changed, 273 insertions(+), 29 deletions(-) rename tests/vendor/jenkins/xunit-plugin/{ => 14c6e39c38408b9ed6280361484a13c6f5becca7}/junit-10.xsd (100%) create mode 100644 tests/vendor/jenkins/xunit-plugin/ae25da5089d4f94ac6c4669bf736e4d416cc4665/junit-10.xsd create mode 100644 xmlrunner/extra/xunit_plugin.py diff --git a/README.md b/README.md index 3af5835..7bdb9f3 100644 --- a/README.md +++ b/README.md @@ -14,35 +14,82 @@ A unittest test runner that can save test results to XML files in xUnit format. The files can be consumed by a wide range of tools, such as build systems, IDEs and continuous integration servers. -## Schema -There are many schemas with minor differences. -We use one that is compatible with Jenkins xUnit plugin, a copy is -available under `tests/vendor/jenkins/xunit-plugin/junit-10.xsd` (see attached license). +## Requirements + +* Python 3.5+ +* Please note Python 2.7 end-of-life was in Jan 2020, last version supporting 2.7 was 2.5.2 +* Please note Python 3.4 end-of-life was in Mar 2019, last version supporting 3.4 was 2.5.2 +* Please note Python 2.6 end-of-life was in Oct 2013, last version supporting 2.6 was 1.14.0 + + +## Limited support for `unittest.TestCase.subTest` + +https://docs.python.org/3/library/unittest.html#unittest.TestCase.subTest + +`unittest` has the concept of sub-tests for a `unittest.TestCase`; this doesn't map well to an existing xUnit concept, so you won't find it in the schema. What that means, is that you lose some granularity +in the reports for sub-tests. + +`unittest` also does not report successful sub-tests, so the accounting won't be exact. -- [Jenkins (junit-10.xsd), xunit plugin (2014-2018)](https://github.com/jenkinsci/xunit-plugin/blob/14c6e39c38408b9ed6280361484a13c6f5becca7/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd), please note the latest versions (2.2.4 and above are not backwards compatible) +## Jenkins plugins + +- Jenkins JUnit plugin : https://plugins.jenkins.io/junit/ +- Jenkins xUnit plugin : https://plugins.jenkins.io/xunit/ + +### Jenkins JUnit plugin + +This plugin does not perform XSD validation (at time of writing) and should parse the XML file without issues. + +### Jenkins xUnit plugin version 1.100 + +- [Jenkins (junit-10.xsd), xunit plugin (2014-2018)](https://github.com/jenkinsci/xunit-plugin/blob/14c6e39c38408b9ed6280361484a13c6f5becca7/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd), version `1.100`. + +This plugin does perfom XSD validation and uses the more lax XSD. This should parse the XML file without issues. + +### Jenkins xUnit plugin version 1.104+ + +- [Jenkins (junit-10.xsd), xunit plugin (2018-current)](https://github.com/jenkinsci/xunit-plugin/blob/ae25da5089d4f94ac6c4669bf736e4d416cc4665/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd), version `1.104`+. + +This plugin does perfom XSD validation and uses the more strict XSD. + +See https://github.com/xmlrunner/unittest-xml-reporting/issues/209 + +``` +import io +import unittest +import xmlrunner + +# run the tests storing results in memory +out = io.BytesIO() +unittest.main( + testRunner=xmlrunner.XMLTestRunner(output=out), + failfast=False, buffer=False, catchbreak=False, exit=False) +``` + +Transform the results removing extra attributes. +``` +from xmlrunner.extra.xunit_plugin import transform + +with open('TEST-report.xml', 'wb') as report: + report.write(transform(out.getvalue())) + +``` + +## JUnit Schema ? + +There are many tools claiming to write JUnit reports, so you will find many schemas with minor differences. + +We used the XSD that was available in the Jenkins xUnit plugin version `1.100`; a copy is available under `tests/vendor/jenkins/xunit-plugin/.../junit-10.xsd` (see attached license). You may also find these resources useful: - https://stackoverflow.com/questions/4922867/what-is-the-junit-xml-format-specification-that-hudson-supports - https://stackoverflow.com/questions/11241781/python-unittests-in-jenkins -- [Jenkins (junit-10.xsd), xunit plugin 2.2.4+](https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd) - [JUnit-Schema (JUnit.xsd)](https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd) - [Windyroad (JUnit.xsd)](http://windyroad.com.au/dl/Open%20Source/JUnit.xsd) - [a gist (Jenkins xUnit test result schema)](https://gist.github.com/erikd/4192748) -## Things that are somewhat broken - -Python 3 has the concept of sub-tests for a `unittest.TestCase`; this doesn't map well to an existing -xUnit concept, so you won't find it in the schema. What that means, is that you lose some granularity -in the reports for sub-tests. - -## Requirements - -* Python 3.5+ -* Please note Python 2.7 end-of-life was in Jan 2020, last version supporting 2.7 was 2.5.2 -* Please note Python 3.4 end-of-life was in Mar 2019, last version supporting 3.4 was 2.5.2 -* Please note Python 2.6 end-of-life was in Oct 2013, last version supporting 2.6 was 1.14.0 ## Installation diff --git a/tests/testsuite.py b/tests/testsuite.py index 9e87353..431547b 100755 --- a/tests/testsuite.py +++ b/tests/testsuite.py @@ -27,10 +27,10 @@ from unittest import mock -def _load_schema(): - path = os.path.join(os.path.dirname(__file__), - 'vendor/jenkins/xunit-plugin', - 'junit-10.xsd') +def _load_schema(version): + path = os.path.join( + os.path.dirname(__file__), + 'vendor/jenkins/xunit-plugin', version, 'junit-10.xsd') with open(path, 'r') as schema_file: schema_doc = etree.parse(schema_file) schema = etree.XMLSchema(schema_doc) @@ -38,12 +38,10 @@ def _load_schema(): raise RuntimeError('Could not load JUnit schema') # pragma: no cover -JUnitSchema = _load_schema() - - -def validate_junit_report(text): +def validate_junit_report(version, text): document = etree.parse(BytesIO(text)) - JUnitSchema.assertValid(document) + schema = _load_schema(version) + schema.assertValid(document) class TestCaseSubclassWithNoSuper(unittest.TestCase): @@ -650,7 +648,7 @@ def test_junitxml_xsd_validation_order(self): self.assertTrue(i_properties < i_testcase < i_system_out < i_system_err) # XSD validation - for good measure. - validate_junit_report(output) + validate_junit_report('14c6e39c38408b9ed6280361484a13c6f5becca7', output) def test_junitxml_xsd_validation_empty_properties(self): suite = unittest.TestSuite() @@ -665,7 +663,31 @@ def test_junitxml_xsd_validation_empty_properties(self): outdir.seek(0) output = outdir.read() self.assertNotIn(''.encode('utf8'), output) - validate_junit_report(output) + validate_junit_report('14c6e39c38408b9ed6280361484a13c6f5becca7', output) + + def test_xunit_plugin_transform(self): + suite = unittest.TestSuite() + suite.addTest(self.DummyTest('test_fail')) + suite.addTest(self.DummyTest('test_pass')) + suite.properties = None + outdir = BytesIO() + runner = xmlrunner.XMLTestRunner( + stream=self.stream, output=outdir, verbosity=self.verbosity, + **self.runner_kwargs) + runner.run(suite) + outdir.seek(0) + output = outdir.read() + + validate_junit_report('14c6e39c38408b9ed6280361484a13c6f5becca7', output) + with self.assertRaises(etree.DocumentInvalid): + validate_junit_report('ae25da5089d4f94ac6c4669bf736e4d416cc4665', output) + + from xmlrunner.extra.xunit_plugin import transform + transformed = transform(output) + validate_junit_report('14c6e39c38408b9ed6280361484a13c6f5becca7', transformed) + validate_junit_report('ae25da5089d4f94ac6c4669bf736e4d416cc4665', transformed) + self.assertIn('test_pass'.encode('utf8'), transformed) + self.assertIn('test_fail'.encode('utf8'), transformed) def test_xmlrunner_elapsed_times(self): self.runner_kwargs['elapsed_times'] = False diff --git a/tests/vendor/jenkins/xunit-plugin/junit-10.xsd b/tests/vendor/jenkins/xunit-plugin/14c6e39c38408b9ed6280361484a13c6f5becca7/junit-10.xsd similarity index 100% rename from tests/vendor/jenkins/xunit-plugin/junit-10.xsd rename to tests/vendor/jenkins/xunit-plugin/14c6e39c38408b9ed6280361484a13c6f5becca7/junit-10.xsd diff --git a/tests/vendor/jenkins/xunit-plugin/ae25da5089d4f94ac6c4669bf736e4d416cc4665/junit-10.xsd b/tests/vendor/jenkins/xunit-plugin/ae25da5089d4f94ac6c4669bf736e4d416cc4665/junit-10.xsd new file mode 100644 index 0000000..286fbf7 --- /dev/null +++ b/tests/vendor/jenkins/xunit-plugin/ae25da5089d4f94ac6c4669bf736e4d416cc4665/junit-10.xsd @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xmlrunner/extra/xunit_plugin.py b/xmlrunner/extra/xunit_plugin.py new file mode 100644 index 0000000..46b4cf4 --- /dev/null +++ b/xmlrunner/extra/xunit_plugin.py @@ -0,0 +1,28 @@ +import io +import lxml.etree as etree + + +TRANSFORM = etree.XSLT(etree.XML('''\ + + + + + + + + + + + + + + +''')) + + +def transform(xml_data): + out = io.BytesIO() + xml_doc = etree.XML(xml_data) + result = TRANSFORM(xml_doc) + result.write(out) + return out.getvalue() From 6556d194e78e2ddc41ecc2958afcd35317160a04 Mon Sep 17 00:00:00 2001 From: Olivier Martin Date: Thu, 20 Feb 2020 03:29:07 +0100 Subject: [PATCH 02/29] xmlrunner: Expose python docstrings as comments in the XML result file (#214) * xmlrunner: Expose python docstrings as comments in the XML result file These comments could for instance be retrieved by XSLT to add description to test title. * tests: Added Python Docstring to test new support --- tests/django_example/app/tests.py | 9 +++++++++ tests/django_test.py | 4 ++++ xmlrunner/result.py | 13 ++++++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/django_example/app/tests.py b/tests/django_example/app/tests.py index 232d365..663a858 100644 --- a/tests/django_example/app/tests.py +++ b/tests/django_example/app/tests.py @@ -4,4 +4,13 @@ # Create your tests here. class DummyTestCase(TestCase): def test_pass(self): + """Test Pass""" + pass + + def test_negative_comment1(self): + """Use a close comment XML tag -->""" + pass + + def test_negative_comment2(self): + """Check XML tag """ pass diff --git a/tests/django_test.py b/tests/django_test.py index be5feaa..4695c24 100644 --- a/tests/django_test.py +++ b/tests/django_test.py @@ -57,12 +57,16 @@ def _check_runner(self, runner): test_ids = [test.id() for test in suite] self.assertEqual(test_ids, [ 'app2.tests.DummyTestCase.test_pass', + 'app.tests.DummyTestCase.test_negative_comment1', + 'app.tests.DummyTestCase.test_negative_comment2', 'app.tests.DummyTestCase.test_pass', ]) suite = runner.build_suite(test_labels=[]) test_ids = [test.id() for test in suite] self.assertEqual(set(test_ids), set([ 'app.tests.DummyTestCase.test_pass', + 'app.tests.DummyTestCase.test_negative_comment1', + 'app.tests.DummyTestCase.test_negative_comment2', 'app2.tests.DummyTestCase.test_pass', ])) diff --git a/xmlrunner/result.py b/xmlrunner/result.py index d3b7722..fb81fd6 100644 --- a/xmlrunner/result.py +++ b/xmlrunner/result.py @@ -128,7 +128,7 @@ class _TestInfo(object): SKIP: 'skipped', } - def __init__(self, test_result, test_method, outcome=SUCCESS, err=None, subTest=None, filename=None, lineno=None): + def __init__(self, test_result, test_method, outcome=SUCCESS, err=None, subTest=None, filename=None, lineno=None, doc=None): self.test_result = test_result self.outcome = outcome self.elapsed_time = 0 @@ -159,6 +159,7 @@ def __init__(self, test_result, test_method, outcome=SUCCESS, err=None, subTest= self.filename = filename self.lineno = lineno + self.doc = doc def id(self): return self.test_id @@ -200,6 +201,7 @@ def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1, self.properties = properties # junit testsuite properties self.filename = None self.lineno = None + self.doc = None if infoclass is None: self.infoclass = _TestInfo else: @@ -213,6 +215,7 @@ def _prepare_callback(self, test_info, target_list, verbose_str, """ test_info.filename = self.filename test_info.lineno = self.lineno + test_info.doc = self.doc target_list.append(test_info) def callback(): @@ -258,6 +261,8 @@ def startTest(self, test): # Handle partial and partialmethod objects. test_method = getattr(test_method, 'func', test_method) _, self.lineno = inspect.getsourcelines(test_method) + + self.doc = test_method.__doc__ except (AttributeError, IOError, TypeError): # issue #188, #189, #195 # some frameworks can make test method opaque. @@ -555,6 +560,12 @@ def _report_testcase(test_result, xml_testsuite, xml_document): if test_result.lineno is not None: testcase.setAttribute('line', str(test_result.lineno)) + if test_result.doc is not None: + comment = str(test_result.doc) + # The use of '--' is forbidden in XML comments + comment = comment.replace('--', '--') + testcase.appendChild(xml_document.createComment(comment)) + result_elem_name = test_result.OUTCOME_ELEMENTS[test_result.outcome] if result_elem_name is not None: From 6ad83e2e5c20ec3735280666c352642ca3915a97 Mon Sep 17 00:00:00 2001 From: Damien Nozay Date: Wed, 19 Feb 2020 18:41:41 -0800 Subject: [PATCH 03/29] try to fix pypy build (#216) --- .travis.yml | 9 +++------ setup.cfg | 2 +- tests/testsuite.py | 2 ++ tox.ini | 2 +- xmlrunner/extra/xunit_plugin.py | 3 ++- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5089cb0..2daad5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,9 @@ cache: pip matrix: include: + - os: linux + python: pypy3.5-6.0 + env: TOXENV=pypy3 - python: 3.5 env: TOXENV=py35 - python: 3.6 @@ -20,12 +23,6 @@ matrix: env: TOXENV=quality - python: 3.8-dev env: TOXENV=pytest - - os: linux - dist: xenial - python: pypy3.5-6.0 - env: TOXENV=pypy3 - services: - - docker before_install: - python --version diff --git a/setup.cfg b/setup.cfg index 97cc555..65bed92 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [bdist_wheel] universal = 1 -python-tag = py2.py3 +python-tag = py3 diff --git a/tests/testsuite.py b/tests/testsuite.py index 431547b..f075f0f 100755 --- a/tests/testsuite.py +++ b/tests/testsuite.py @@ -665,6 +665,8 @@ def test_junitxml_xsd_validation_empty_properties(self): self.assertNotIn(''.encode('utf8'), output) validate_junit_report('14c6e39c38408b9ed6280361484a13c6f5becca7', output) + @unittest.skipIf(hasattr(sys, 'pypy_version_info'), + 'skip - PyPy + lxml seems to be hanging') def test_xunit_plugin_transform(self): suite = unittest.TestSuite() suite.addTest(self.DummyTest('test_fail')) diff --git a/tox.ini b/tox.ini index 9ae0910..e278917 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ testpaths = tests norecursedirs = tests/django_example [tox] -envlist = begin,py{py,py3,35,36,37,38},pytest,py38-django{lts,curr},end,quality +envlist = begin,py{py3,35,36,37,38},pytest,py38-django{lts,curr},end,quality [tox:travis] 3.5 = py35 diff --git a/xmlrunner/extra/xunit_plugin.py b/xmlrunner/extra/xunit_plugin.py index 46b4cf4..2c8cf80 100644 --- a/xmlrunner/extra/xunit_plugin.py +++ b/xmlrunner/extra/xunit_plugin.py @@ -2,7 +2,8 @@ import lxml.etree as etree -TRANSFORM = etree.XSLT(etree.XML('''\ +TRANSFORM = etree.XSLT(etree.XML(b'''\ + From e8a337c2597a4124f290feb28b2eeb3991c93567 Mon Sep 17 00:00:00 2001 From: Damien Nozay Date: Wed, 19 Feb 2020 18:44:01 -0800 Subject: [PATCH 04/29] Bump version to 3.0.2 --- xmlrunner/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmlrunner/version.py b/xmlrunner/version.py index aac1167..b888be1 100644 --- a/xmlrunner/version.py +++ b/xmlrunner/version.py @@ -1,2 +1,2 @@ -__version__ = '3.0.1' +__version__ = '3.0.2' From 7723be8c1f5a9f0d78d52c32cf9de25a6f4086bd Mon Sep 17 00:00:00 2001 From: Damien Nozay Date: Thu, 30 Apr 2020 21:53:50 -0700 Subject: [PATCH 05/29] remove broken test remove broken test, see #220 and #189 ``` If it is necessary to override the __init__ method, the base class __init__ method must always be called. It is important that subclasses should not change the signature of their __init__ method, since instances of the classes are instantiated automatically by parts of the framework in order to be run. ``` --- tests/testsuite.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tests/testsuite.py b/tests/testsuite.py index f075f0f..32ab157 100755 --- a/tests/testsuite.py +++ b/tests/testsuite.py @@ -44,20 +44,6 @@ def validate_junit_report(version, text): schema.assertValid(document) -class TestCaseSubclassWithNoSuper(unittest.TestCase): - def __init__(self, description): - # no super, see #189 - pass - - def run(self, result): - result = _XMLTestResult() - result.startTest(self) - result.stopTest(self) - - def test_something(self): - pass - - class DoctestTest(unittest.TestCase): def test_doctest_example(self): From aaa2d371db5665c081046585a0fe63b3ce7430a3 Mon Sep 17 00:00:00 2001 From: XonqNopp Date: Thu, 7 May 2020 23:16:25 +0200 Subject: [PATCH 06/29] [result] fix subtest referencing objects (#220) * [result] fix subtest referencing objects * [tests] reproducing #219 --- tests/testsuite.py | 26 ++++++++++++++++++++++++++ xmlrunner/result.py | 12 +++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/tests/testsuite.py b/tests/testsuite.py index 32ab157..ad8b9ce 100755 --- a/tests/testsuite.py +++ b/tests/testsuite.py @@ -178,6 +178,11 @@ def test_subTest_mixed(self): with self.subTest(i=i): self.assertLess(i, 1, msg='this is a subtest.') + def test_subTest_with_dots(self): + for i in range(2): + with self.subTest(module='hello.world.subTest{}'.format(i)): + self.fail('this is a subtest.') + class DecoratedUnitTest(unittest.TestCase): @some_decorator @@ -553,6 +558,27 @@ def test_unittest_subTest_pass(self): suite.addTest(self.DummySubTest('test_subTest_pass')) self._test_xmlrunner(suite) + @unittest.skipIf(not hasattr(unittest.TestCase, 'subTest'), + 'unittest.TestCase.subTest not present.') + def test_unittest_subTest_with_dots(self): + # Test for issue #85 + suite = unittest.TestSuite() + suite.addTest(self.DummySubTest('test_subTest_with_dots')) + outdir = BytesIO() + + self._test_xmlrunner(suite, outdir=outdir) + + xmlcontent = outdir.getvalue().decode() + + # Method name + self.assertNotIn('name="subTest', xmlcontent, 'parsing of test method name is not done correctly') + self.assertIn('name="test_subTest_with_dots (module=\'hello.world.subTest', xmlcontent) + + # Class name + matchString = 'classname="tests.testsuite.XMLTestRunnerTestCase.DummySubTest.test_subTest_with_dots (module=\'hello.world"' + self.assertNotIn(matchString, xmlcontent, 'parsing of class name is not done correctly') + self.assertIn('classname="tests.testsuite.XMLTestRunnerTestCase.DummySubTest"', xmlcontent) + def test_xmlrunner_pass(self): suite = unittest.TestSuite() suite.addTest(self.DummyTest('test_pass')) diff --git a/xmlrunner/result.py b/xmlrunner/result.py index fb81fd6..99e2f7f 100644 --- a/xmlrunner/result.py +++ b/xmlrunner/result.py @@ -4,7 +4,6 @@ import os import sys import datetime -import time import traceback import re from os import path @@ -517,7 +516,11 @@ def _test_method_name(test_id): """ Returns the test method name. """ - return test_id.split('.')[-1] + # Trick subtest referencing objects + subtest_parts = test_id.split(' ') + test_method_name = subtest_parts[0].split('.')[-1] + subtest_method_name = [test_method_name] + subtest_parts[1:] + return ' '.join(subtest_method_name) _test_method_name = staticmethod(_test_method_name) @@ -543,7 +546,10 @@ def _report_testcase(test_result, xml_testsuite, xml_document): xml_testsuite.appendChild(testcase) class_name = re.sub(r'^__main__.', '', test_result.id()) - class_name = class_name.rpartition('.')[0] + + # Trick subtest referencing objects + class_name = class_name.split(' ')[0].rpartition('.')[0] + testcase.setAttribute('classname', class_name) testcase.setAttribute( 'name', _XMLTestResult._test_method_name(test_result.test_id) From 75d61fc0534176e89ab068cccd67c6deadeeb9af Mon Sep 17 00:00:00 2001 From: Jacob Svensson Date: Thu, 14 May 2020 02:00:19 +0200 Subject: [PATCH 07/29] Avoid including empty system-out and system-err when reporting testcases (#223) --- xmlrunner/result.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xmlrunner/result.py b/xmlrunner/result.py index 99e2f7f..40669a1 100644 --- a/xmlrunner/result.py +++ b/xmlrunner/result.py @@ -591,13 +591,13 @@ def _report_testcase(test_result, xml_testsuite, xml_document): _XMLTestResult._createCDATAsections( xml_document, result_elem, error_info) - if test_result.stdout is not None: + if test_result.stdout: systemout = xml_document.createElement('system-out') testcase.appendChild(systemout) _XMLTestResult._createCDATAsections( xml_document, systemout, test_result.stdout) - if test_result.stderr is not None: + if test_result.stderr: systemout = xml_document.createElement('system-err') testcase.appendChild(systemout) _XMLTestResult._createCDATAsections( From fb417d3e7ddf567a7d94034c15728151e25c22ee Mon Sep 17 00:00:00 2001 From: Damien Nozay Date: Fri, 14 Aug 2020 15:38:30 -0700 Subject: [PATCH 08/29] fix #222 --- tests/testsuite.py | 21 +++++++++++++++++++++ xmlrunner/result.py | 13 +++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/tests/testsuite.py b/tests/testsuite.py index ad8b9ce..34bde7e 100755 --- a/tests/testsuite.py +++ b/tests/testsuite.py @@ -13,6 +13,7 @@ import xmlrunner from xmlrunner.result import _DuplicateWriter from xmlrunner.result import _XMLTestResult +from xmlrunner.result import resolve_filename import doctest import tests.doctest_example from io import StringIO, BytesIO @@ -964,3 +965,23 @@ def test_xmlrunner_output_file(self, exiter, testrunner, opener): testrunner.assert_called_once_with(**kwargs) exiter.assert_called_once_with(False) + + +class ResolveFilenameTestCase(unittest.TestCase): + @mock.patch('os.path.relpath') + def test_resolve_filename_relative(self, relpath): + relpath.return_value = 'somefile.py' + filename = resolve_filename('/path/to/somefile.py') + self.assertEqual(filename, 'somefile.py') + + @mock.patch('os.path.relpath') + def test_resolve_filename_outside(self, relpath): + relpath.return_value = '../../../tmp/somefile.py' + filename = resolve_filename('/tmp/somefile.py') + self.assertEqual(filename, '/tmp/somefile.py') + + @mock.patch('os.path.relpath') + def test_resolve_filename_error(self, relpath): + relpath.side_effect = ValueError("ValueError: path is on mount 'C:', start on mount 'D:'") + filename = resolve_filename('C:\\path\\to\\somefile.py') + self.assertEqual(filename, 'C:\\path\\to\\somefile.py') diff --git a/xmlrunner/result.py b/xmlrunner/result.py index 40669a1..b740c8d 100644 --- a/xmlrunner/result.py +++ b/xmlrunner/result.py @@ -69,6 +69,16 @@ def testcase_name(test_method): return result +def resolve_filename(filename): + # Try to make filename relative to current directory. + try: + rel_filename = os.path.relpath(filename) + except ValueError: + return filename + # if not inside folder, keep as-is + return filename if rel_filename.startswith('../') else rel_filename + + class _DuplicateWriter(io.TextIOBase): """ Duplicate output from the first handle to the second handle @@ -559,8 +569,7 @@ def _report_testcase(test_result, xml_testsuite, xml_document): if test_result.filename is not None: # Try to make filename relative to current directory. - filename = os.path.relpath(test_result.filename) - filename = test_result.filename if filename.startswith('../') else filename + filename = resolve_filename(test_result.filename) testcase.setAttribute('file', filename) if test_result.lineno is not None: From 5dd7fbbae657eb111871a259d0d7ba5af9ceebd3 Mon Sep 17 00:00:00 2001 From: Damien Nozay Date: Fri, 14 Aug 2020 15:38:53 -0700 Subject: [PATCH 09/29] virtualenv -> python3 -m venv --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index cea30b6..eef0abf 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ build/tox/bin: - virtualenv build/tox + python3 -m venv build/tox build/tox/bin/pip install tox build/publish/bin: - virtualenv build/publish + python3 -m venv build/publish build/publish/bin/pip install wheel twine checkversion: From 5086830ba7756b908198f4a03ab76bbcb1bec7c8 Mon Sep 17 00:00:00 2001 From: Damien Nozay Date: Fri, 14 Aug 2020 15:40:40 -0700 Subject: [PATCH 10/29] Bump version to 3.0.3 --- xmlrunner/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmlrunner/version.py b/xmlrunner/version.py index b888be1..ed56a68 100644 --- a/xmlrunner/version.py +++ b/xmlrunner/version.py @@ -1,2 +1,2 @@ -__version__ = '3.0.2' +__version__ = '3.0.3' From 03e5328c6b680f589d6c23e277eb9e7326943ce8 Mon Sep 17 00:00:00 2001 From: Damien Nozay Date: Mon, 17 Aug 2020 17:40:32 -0700 Subject: [PATCH 11/29] fix #217 --- tests/testsuite.py | 23 +++++++++++++++++++++++ xmlrunner/result.py | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/testsuite.py b/tests/testsuite.py index 34bde7e..437c69f 100755 --- a/tests/testsuite.py +++ b/tests/testsuite.py @@ -133,6 +133,12 @@ def test_error(self): def test_cdata_section(self): print('') + def test_invalid_xml_chars_in_doc(self): + """ + Testing comments, -- is not allowed, or invalid xml 1.0 chars such as \x0c + """ + pass + def test_non_ascii_error(self): self.assertEqual(u"éçà", 42) @@ -617,6 +623,23 @@ def test_xmlrunner_cdata_section(self): suite.addTest(self.DummyTest('test_cdata_section')) self._test_xmlrunner(suite) + def test_xmlrunner_invalid_xml_chars_in_doc(self): + suite = unittest.TestSuite() + suite.addTest(self.DummyTest('test_invalid_xml_chars_in_doc')) + outdir = BytesIO() + runner = xmlrunner.XMLTestRunner( + stream=self.stream, output=outdir, verbosity=self.verbosity, + **self.runner_kwargs) + runner.run(suite) + outdir.seek(0) + output = outdir.read() + # Finally check if we have a valid XML document or not. + try: + minidom.parseString(output) + except Exception as e: # pragma: no cover + # note: we could remove the try/except, but it's more crude. + self.fail(e) + def test_xmlrunner_outsuffix(self): self.runner_kwargs['outsuffix'] = '.somesuffix' suite = unittest.TestSuite() diff --git a/xmlrunner/result.py b/xmlrunner/result.py index b740c8d..ab49078 100644 --- a/xmlrunner/result.py +++ b/xmlrunner/result.py @@ -579,7 +579,7 @@ def _report_testcase(test_result, xml_testsuite, xml_document): comment = str(test_result.doc) # The use of '--' is forbidden in XML comments comment = comment.replace('--', '--') - testcase.appendChild(xml_document.createComment(comment)) + testcase.appendChild(xml_document.createComment(safe_unicode(comment))) result_elem_name = test_result.OUTCOME_ELEMENTS[test_result.outcome] From 7d0a0e7e946f5c44593f53dcc3cd24d641db93de Mon Sep 17 00:00:00 2001 From: Damien Nozay Date: Mon, 17 Aug 2020 17:41:11 -0700 Subject: [PATCH 12/29] Bump version to 3.0.4 --- xmlrunner/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmlrunner/version.py b/xmlrunner/version.py index ed56a68..aac2c2b 100644 --- a/xmlrunner/version.py +++ b/xmlrunner/version.py @@ -1,2 +1,2 @@ -__version__ = '3.0.3' +__version__ = '3.0.4' From 80ba55825b3ffa70269d64aa30ae31397c049bf3 Mon Sep 17 00:00:00 2001 From: Damien Nozay Date: Mon, 23 Nov 2020 11:18:21 -0800 Subject: [PATCH 13/29] explicitly require lxml (#232) --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 9cbfbff..33f7c30 100755 --- a/setup.py +++ b/setup.py @@ -27,6 +27,7 @@ description = 'unittest-based test runner with Ant/JUnit like XML reporting.', long_description = long_description, long_description_content_type = 'text/markdown', + install_requires = ['lxml'], license = 'BSD', platforms = ['Any'], python_requires='>=3.5', From cf7bbe4bc2a106aaedc2831bc6c6215aa5a71878 Mon Sep 17 00:00:00 2001 From: Konboi Date: Tue, 3 Aug 2021 18:54:13 +0900 Subject: [PATCH 14/29] Add github actions configuration --- .github/workflows/tests.yml | 62 +++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..431e599 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,62 @@ +name: Tests + +on: + pull_request: + push: + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - python-version: 3.5 + toxenv: py35 + - python-version: 3.6 + toxenv: py36 + - python-version: 3.7 + toxenv: py37 + - python-version: 3.8 + toxenv: py38 + - python-version: 3.8 + toxenv: py38-djangolts + - python-version: 3.8 + toxenv: py38-djangocurr + - python-version: 3.8 + toxenv: py38-quality + - python-version: 3.9 + toxenv: py39 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Before Install + run: | + python --version + uname -a + lsb_release -a + - name: Install + env: + TOXENV: ${{ matrix.toxenv }} + run: | + pip install tox codecov coveralls + virtualenv --version + easy_install --version + pip --version + tox --version + - name: Script + run: | + tox -v + - name: After Failure + if: ${{ failure() }} + run: | + more .tox/log/* | cat + more .tox/*/log/* | cat + - name: After Success + if: ${{ success() }} + run: | + codecov + coveralls From 14c8cc0ded46e6f78f0f3d25c44c27b7fda4e161 Mon Sep 17 00:00:00 2001 From: Konboi Date: Tue, 3 Aug 2021 18:54:23 +0900 Subject: [PATCH 15/29] rm travis configuration --- .travis.yml | 44 -------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2daad5b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,44 +0,0 @@ -language: python -sudo: required -cache: pip - -matrix: - include: - - os: linux - python: pypy3.5-6.0 - env: TOXENV=pypy3 - - python: 3.5 - env: TOXENV=py35 - - python: 3.6 - env: TOXENV=py36 - - python: 3.7 - env: TOXENV=py37 - - python: 3.8 - env: TOXENV=py38 - - python: 3.8 - env: TOXENV=py38-djangolts - - python: 3.8 - env: TOXENV=py38-djangocurr - - python: 3.8 - env: TOXENV=quality - - python: 3.8-dev - env: TOXENV=pytest - -before_install: - - python --version - - uname -a - - lsb_release -a -install: - - pip install tox codecov coveralls - - virtualenv --version - - easy_install --version - - pip --version - - tox --version -script: - - tox -v -after_failure: - - more .tox/log/* | cat - - more .tox/*/log/* | cat -after_success: - - codecov - - coveralls From 74014dbace969bc34fbf450bd303fd4f44c016ce Mon Sep 17 00:00:00 2001 From: Konboi Date: Tue, 3 Aug 2021 18:56:47 +0900 Subject: [PATCH 16/29] rm unused command --- .github/workflows/tests.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 431e599..ca38eaa 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -43,8 +43,6 @@ jobs: TOXENV: ${{ matrix.toxenv }} run: | pip install tox codecov coveralls - virtualenv --version - easy_install --version pip --version tox --version - name: Script From 8e99918467d124a202fec20d1a8b36d372a1b060 Mon Sep 17 00:00:00 2001 From: Damien Nozay Date: Tue, 14 Sep 2021 10:13:50 -0700 Subject: [PATCH 17/29] use tox-gh-actions (#256) see https://github.com/ymyzk/tox-gh-actions * add CODECOV_TOKEN * add COVERALLS_REPO_TOKEN * codecov / coveralls run as part of tox * pass env to tox --- .github/workflows/tests.yml | 14 +++++++------- MANIFEST.in | 2 ++ tox.ini | 14 ++++++++------ 3 files changed, 17 insertions(+), 13 deletions(-) create mode 100644 MANIFEST.in diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ca38eaa..a2b4ea1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,8 +1,10 @@ name: Tests on: - pull_request: push: + branches: + - master + pull_request: jobs: build: @@ -42,19 +44,17 @@ jobs: env: TOXENV: ${{ matrix.toxenv }} run: | - pip install tox codecov coveralls + pip install tox-gh-actions codecov coveralls pip --version tox --version - name: Script run: | tox -v + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} - name: After Failure if: ${{ failure() }} run: | more .tox/log/* | cat more .tox/*/log/* | cat - - name: After Success - if: ${{ success() }} - run: | - codecov - coveralls diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..74215c3 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include README.md +include LICENSE \ No newline at end of file diff --git a/tox.ini b/tox.ini index e278917..c9d2df4 100644 --- a/tox.ini +++ b/tox.ini @@ -6,11 +6,12 @@ norecursedirs = tests/django_example [tox] envlist = begin,py{py3,35,36,37,38},pytest,py38-django{lts,curr},end,quality -[tox:travis] -3.5 = py35 -3.6 = py36 -3.7 = py37,pytest -3.8 = begin,py38,py38-django{lts,curr},end,quality +[gh-actions] +python = + 3.5: py35 + 3.6: py36 + 3.7: py37,pytest + 3.8: begin,py38,py38-django{lts,curr},end,quality [testenv] deps = @@ -27,7 +28,8 @@ commands = python -m xmlrunner discover -p test_xmlrunner_output codecov -e TOXENV -coveralls -passenv = CI TRAVIS_BUILD_ID TRAVIS TRAVIS_BRANCH TRAVIS_JOB_NUMBER TRAVIS_PULL_REQUEST TRAVIS_JOB_ID TRAVIS_REPO_SLUG TRAVIS_COMMIT +passenv = CI TRAVIS_BUILD_ID TRAVIS TRAVIS_BRANCH TRAVIS_JOB_NUMBER TRAVIS_PULL_REQUEST TRAVIS_JOB_ID TRAVIS_REPO_SLUG TRAVIS_COMMIT CODECOV_TOKEN COVERALLS_REPO_TOKEN GITHUB_ACTION GITHUB_HEAD_REF GITHUB_REF GITHUB_REPOSITORY GITHUB_RUN_ID GITHUB_SHA + [testenv:pytest] commands = pytest From 531da782b7be89c85cef671eceb744eefb833f58 Mon Sep 17 00:00:00 2001 From: Damien Nozay Date: Tue, 14 Sep 2021 10:14:16 -0700 Subject: [PATCH 18/29] make time measurements monotonic (#231) - `time.time()` is the system time. "it's great" (tm) however it is not reliable for measuring time spent. it gives you the "current" time, but oh... time can move backwards (thanks NTP). - `time.monotonic()` is a monotonic time source. "it's great" (tm) it only goes forward, making measurements a peach. however it cannot be used as a reference point. --- xmlrunner/builder.py | 11 ++++++++--- xmlrunner/runner.py | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/xmlrunner/builder.py b/xmlrunner/builder.py index 50c469a..3eff61d 100644 --- a/xmlrunner/builder.py +++ b/xmlrunner/builder.py @@ -58,7 +58,9 @@ def __init__(self, xml_doc, parent_context=None): """ self.xml_doc = xml_doc self.parent = parent_context - self._start_time = self._stop_time = 0 + self._start_time_m = 0 + self._stop_time_m = 0 + self._stop_time = 0 self.counters = {} def element_tag(self): @@ -72,12 +74,15 @@ def begin(self, tag, name): """ self.element = self.xml_doc.createElement(tag) self.element.setAttribute('name', replace_nontext(name)) - self._start_time = time.time() + self._start_time = time.monotonic() def end(self): """Closes this context (started with a call to `begin`) and creates an attribute for each counter and another for the elapsed time. """ + # time.monotonic is reliable for measuring differences, not affected by NTP + self._stop_time_m = time.monotonic() + # time.time is used for reference point self._stop_time = time.time() self.element.setAttribute('time', self.elapsed_time()) self.element.setAttribute('timestamp', self.timestamp()) @@ -120,7 +125,7 @@ def elapsed_time(self): """Returns the time the context took to run between the calls to `begin()` and `end()`, in seconds. """ - return format(self._stop_time - self._start_time, '.3f') + return format(self._stop_time_m - self._start_time_m, '.3f') def timestamp(self): """Returns the time the context ended as ISO-8601-formatted timestamp. diff --git a/xmlrunner/runner.py b/xmlrunner/runner.py index 538f187..dec0a66 100644 --- a/xmlrunner/runner.py +++ b/xmlrunner/runner.py @@ -62,9 +62,9 @@ def run(self, test): self.stream.writeln(result.separator2) # Execute tests - start_time = time.time() + start_time = time.monotonic() test(result) - stop_time = time.time() + stop_time = time.monotonic() time_taken = stop_time - start_time # Print results From 0ef90a6f2565430c4e8c19b4b4741a971a8b4041 Mon Sep 17 00:00:00 2001 From: Ryosuke Yabuki Date: Wed, 29 Sep 2021 23:32:32 +0900 Subject: [PATCH 19/29] Add outsuffix option (#238) --- tests/testsuite.py | 21 +++++++++++++++++++++ xmlrunner/runner.py | 13 ++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/testsuite.py b/tests/testsuite.py index 437c69f..1c47ee6 100755 --- a/tests/testsuite.py +++ b/tests/testsuite.py @@ -989,6 +989,27 @@ def test_xmlrunner_output_file(self, exiter, testrunner, opener): testrunner.assert_called_once_with(**kwargs) exiter.assert_called_once_with(False) + @mock.patch('sys.argv', ['xmlrunner', '--outsuffix', '']) + @mock.patch('xmlrunner.runner.open') + @mock.patch('xmlrunner.runner.XMLTestRunner') + @mock.patch('sys.exit') + def test_xmlrunner_outsuffix(self, exiter, testrunner, opener): + xmlrunner.runner.XMLTestProgram() + + kwargs = dict( + buffer=mock.ANY, + failfast=mock.ANY, + verbosity=mock.ANY, + warnings=mock.ANY, + outsuffix='', + ) + + if sys.version_info[:2] > (3, 4): + kwargs.update(tb_locals=mock.ANY) + + testrunner.assert_called_once_with(**kwargs) + exiter.assert_called_once_with(False) + class ResolveFilenameTestCase(unittest.TestCase): @mock.patch('os.path.relpath') diff --git a/xmlrunner/runner.py b/xmlrunner/runner.py index dec0a66..6b6588b 100644 --- a/xmlrunner/runner.py +++ b/xmlrunner/runner.py @@ -15,7 +15,8 @@ class XMLTestRunner(TextTestRunner): """ A test runner class that outputs the results in JUnit like XML files. """ - def __init__(self, output='.', outsuffix=None, + + def __init__(self, output='.', outsuffix=None, elapsed_times=True, encoding=UTF8, resultclass=None, **kwargs): @@ -138,9 +139,13 @@ def _parseKnownArgs(self, kwargs): group.add_argument( '--output-file', metavar='FILENAME', help='Filename for storing XML report') + parser.add_argument( + '--outsuffix', metavar='STRING', + help='Output suffix (timestamp is default)') namespace, argv = parser.parse_known_args(argv) self.output = namespace.output self.output_file = namespace.output_file + self.outsuffix = namespace.outsuffix kwargs['argv'] = argv def _initArgParsers(self): @@ -155,6 +160,9 @@ def _initArgParsers(self): group.add_argument( '--output-file', metavar='FILENAME', nargs=1, help='Filename for storing XML report') + group.add_argument( + '--outsuffix', metavar='STRING', nargs=1, + help='Output suffix (timestamp is default)') def runTests(self): kwargs = dict( @@ -174,6 +182,9 @@ def runTests(self): elif self.output is not None: kwargs.update(output=self.output) + if self.outsuffix is not None: + kwargs.update(outsuffix=self.outsuffix) + self.testRunner = self.testRunner(**kwargs) super(XMLTestProgram, self).runTests() finally: From 658a75b487f48d8dacf01ad563a8b3d53185f1ac Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 6 Oct 2021 08:19:56 +0200 Subject: [PATCH 20/29] Used unittest.TextTestResult instead of an alias _TextTestResult removed in Python 3.11. --- xmlrunner/result.py | 8 ++++---- xmlrunner/unittest.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/xmlrunner/result.py b/xmlrunner/result.py index ab49078..96fc675 100644 --- a/xmlrunner/result.py +++ b/xmlrunner/result.py @@ -12,7 +12,7 @@ # use direct import to bypass freezegun from time import time -from .unittest import TestResult, _TextTestResult, failfast +from .unittest import TestResult, TextTestResult, failfast # Matches invalid XML1.0 unicode characters, like control characters: @@ -189,7 +189,7 @@ def get_error_info(self): return self.test_exception_info -class _XMLTestResult(_TextTestResult): +class _XMLTestResult(TextTestResult): """ A test result class that can express test results in a XML report. @@ -197,7 +197,7 @@ class _XMLTestResult(_TextTestResult): """ def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1, elapsed_times=True, properties=None, infoclass=None): - _TextTestResult.__init__(self, stream, descriptions, verbosity) + TextTestResult.__init__(self, stream, descriptions, verbosity) self._stdout_data = None self._stderr_data = None self._stdout_capture = StringIO() @@ -320,7 +320,7 @@ def stopTest(self, test): # self._stdout_data = sys.stdout.getvalue() # self._stderr_data = sys.stderr.getvalue() - _TextTestResult.stopTest(self, test) + TextTestResult.stopTest(self, test) self.stop_time = time() if self.callback and callable(self.callback): diff --git a/xmlrunner/unittest.py b/xmlrunner/unittest.py index 5f3a1b2..a172048 100644 --- a/xmlrunner/unittest.py +++ b/xmlrunner/unittest.py @@ -5,11 +5,11 @@ # pylint: disable-msg=W0611 import unittest from unittest import TextTestRunner -from unittest import TestResult, _TextTestResult +from unittest import TestResult, TextTestResult from unittest.result import failfast from unittest.main import TestProgram __all__ = ( - 'unittest', 'TextTestRunner', 'TestResult', '_TextTestResult', + 'unittest', 'TextTestRunner', 'TestResult', 'TextTestResult', 'TestProgram', 'failfast') From 8e8ec9d7810952ab31f47f813ca975d37daa37a9 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 6 Oct 2021 08:25:20 +0200 Subject: [PATCH 21/29] Used assertRegex() instead of an alias assertRegexpMatches() removed in Python 3.11. --- tests/testsuite.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/testsuite.py b/tests/testsuite.py index 1c47ee6..76db902 100755 --- a/tests/testsuite.py +++ b/tests/testsuite.py @@ -270,12 +270,12 @@ def test_classnames(self): '//testcase': ('classname', 'name'), '//failure': ('message',), }) - self.assertRegexpMatches( + self.assertRegex( output, r'classname="tests\.testsuite\.(XMLTestRunnerTestCase\.)?' r'DummyTest" name="test_pass"'.encode('utf8'), ) - self.assertRegexpMatches( + self.assertRegex( output, r'classname="tests\.testsuite\.(XMLTestRunnerTestCase\.)?' r'DummySubTest" name="test_subTest_pass"'.encode('utf8'), @@ -491,12 +491,12 @@ def test_unittest_subTest_fail(self): '//testcase': ('classname', 'name'), '//failure': ('message',), }) - self.assertRegexpMatches( + self.assertRegex( output, br' Date: Tue, 19 Oct 2021 09:14:02 +0200 Subject: [PATCH 22/29] Fix typo in tests/django_test.py. --- tests/django_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/django_test.py b/tests/django_test.py index 4695c24..16b4a72 100644 --- a/tests/django_test.py +++ b/tests/django_test.py @@ -37,7 +37,7 @@ def setUp(self): settings.INSTALLED_APPS # load settings on first access settings.DATABASES['default'] = {} settings.DATABASES['default']['NAME'] = path.join( - self.tmpdir, 'db.sqlilte3') + self.tmpdir, 'db.sqlite3') # this goes around the "settings already loaded" issue. self.override = UserSettingsHolder(settings._wrapped) settings._wrapped = self.override From 522ef16901d34c8751c04a136b16651e7fa81252 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 19 Oct 2021 09:20:32 +0200 Subject: [PATCH 23/29] Update Django testing. --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index c9d2df4..92013fd 100644 --- a/tox.ini +++ b/tox.ini @@ -18,8 +18,8 @@ deps = coverage codecov>=1.4.0 coveralls - djangolts,pytest: django>=1.11.0,<1.12.0 - djangocurr: django>=2.2.0 + djangolts,pytest: django>=3.2.0,<4.0.0 + djangocurr: django>=4.0a1,<4.1.0 pytest: pytest lxml>=3.6.0 commands = From 9ec8a26db14165aa1416335a9740bd44ecf03534 Mon Sep 17 00:00:00 2001 From: Damien Nozay Date: Wed, 19 Jan 2022 07:34:24 -0800 Subject: [PATCH 24/29] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 7bdb9f3..7b07656 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Development Status](https://img.shields.io/pypi/status/unittest-xml-reporting.svg)](https://pypi.python.org/pypi/unittest-xml-reporting/) [![Documentation Status](https://readthedocs.org/projects/unittest-xml-reporting/badge/?version=latest)](http://unittest-xml-reporting.readthedocs.io/en/latest/?badge=latest) -[![Build Status](https://travis-ci.org/xmlrunner/unittest-xml-reporting.svg?branch=master)](https://travis-ci.org/xmlrunner/unittest-xml-reporting) [![codecov.io Coverage Status](https://codecov.io/github/xmlrunner/unittest-xml-reporting/coverage.svg?branch=master)](https://codecov.io/github/xmlrunner/unittest-xml-reporting?branch=master) [![Coveralls Coverage Status](https://coveralls.io/repos/xmlrunner/unittest-xml-reporting/badge.svg?branch=master&service=github)](https://coveralls.io/github/xmlrunner/unittest-xml-reporting?branch=master) [![Requirements Status](https://requires.io/github/xmlrunner/unittest-xml-reporting/requirements.svg?branch=master)](https://requires.io/github/xmlrunner/unittest-xml-reporting/requirements/?branch=master) From c43427611390fba83ca13fbb5311bd8fece5048f Mon Sep 17 00:00:00 2001 From: Damien Nozay Date: Wed, 19 Jan 2022 07:41:25 -0800 Subject: [PATCH 25/29] Add LICENSE to tarball should fix #259 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 33f7c30..0fb37f8 100755 --- a/setup.py +++ b/setup.py @@ -27,6 +27,7 @@ description = 'unittest-based test runner with Ant/JUnit like XML reporting.', long_description = long_description, long_description_content_type = 'text/markdown', + data_files = [('', ['LICENSE'])], install_requires = ['lxml'], license = 'BSD', platforms = ['Any'], From 04f4cd3253ffd72c1b710b0311011073fd95fd20 Mon Sep 17 00:00:00 2001 From: Damien Nozay Date: Wed, 19 Jan 2022 07:45:20 -0800 Subject: [PATCH 26/29] Version 3.1.0 --- xmlrunner/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmlrunner/version.py b/xmlrunner/version.py index aac2c2b..310e871 100644 --- a/xmlrunner/version.py +++ b/xmlrunner/version.py @@ -1,2 +1,2 @@ -__version__ = '3.0.4' +__version__ = '3.1.0' From 5f54e2c3efaa8a6edc7e9daff16da94c819bc1c7 Mon Sep 17 00:00:00 2001 From: Damien Nozay <205466+dnozay@users.noreply.github.com> Date: Wed, 19 Jan 2022 07:47:12 -0800 Subject: [PATCH 27/29] Bump version to 3.1.0 From 0d3fc4322660d6e0111a85b72ea28a1028bdd688 Mon Sep 17 00:00:00 2001 From: Paolo Melchiorre Date: Wed, 19 Jan 2022 17:59:33 +0100 Subject: [PATCH 28/29] Fix #261 # 262 Update Python and Django versions - Removed Python 3.5 and 3.6 - Added Python 3.10 - Updated Django 4 version --- .github/workflows/tests.yml | 6 ++---- README.md | 4 +++- setup.py | 12 +++++++----- tox.ini | 10 +++++----- xmlrunner/version.py | 2 +- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a2b4ea1..6d220fc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,10 +12,6 @@ jobs: strategy: matrix: include: - - python-version: 3.5 - toxenv: py35 - - python-version: 3.6 - toxenv: py36 - python-version: 3.7 toxenv: py37 - python-version: 3.8 @@ -28,6 +24,8 @@ jobs: toxenv: py38-quality - python-version: 3.9 toxenv: py39 + - python-version: "3.10" + toxenv: py310 steps: - name: Checkout uses: actions/checkout@v2 diff --git a/README.md b/README.md index 7b07656..b9d3102 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ and continuous integration servers. ## Requirements -* Python 3.5+ +* Python 3.7+ +* Please note Python 3.6 end-of-life was in Dec 2021, last version supporting 3.6 was 3.1.0 +* Please note Python 3.5 end-of-life was in Sep 2020, last version supporting 3.5 was 3.1.0 * Please note Python 2.7 end-of-life was in Jan 2020, last version supporting 2.7 was 2.5.2 * Please note Python 3.4 end-of-life was in Mar 2019, last version supporting 3.4 was 2.5.2 * Please note Python 2.6 end-of-life was in Oct 2013, last version supporting 2.6 was 1.14.0 diff --git a/setup.py b/setup.py index 0fb37f8..c524ed4 100755 --- a/setup.py +++ b/setup.py @@ -17,8 +17,8 @@ # this is for sdist to work. import sys -if sys.version_info < (3, 5): - raise RuntimeError('This version requires Python 3.5+') # pragma: no cover +if sys.version_info < (3, 7): + raise RuntimeError('This version requires Python 3.7+') # pragma: no cover setup( name = 'unittest-xml-reporting', @@ -31,7 +31,7 @@ install_requires = ['lxml'], license = 'BSD', platforms = ['Any'], - python_requires='>=3.5', + python_requires='>=3.7', keywords = [ 'pyunit', 'unittest', 'junit xml', 'xunit', 'report', 'testrunner', 'xmlrunner' ], @@ -44,9 +44,11 @@ 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules', diff --git a/tox.ini b/tox.ini index 92013fd..c290205 100644 --- a/tox.ini +++ b/tox.ini @@ -4,22 +4,22 @@ testpaths = tests norecursedirs = tests/django_example [tox] -envlist = begin,py{py3,35,36,37,38},pytest,py38-django{lts,curr},end,quality +envlist = begin,py{py3,37,38,39,310},pytest,py38-django{lts,curr},end,quality [gh-actions] python = - 3.5: py35 - 3.6: py36 3.7: py37,pytest 3.8: begin,py38,py38-django{lts,curr},end,quality + 3.9: py39 + 3.10: py310 [testenv] deps = coverage codecov>=1.4.0 coveralls - djangolts,pytest: django>=3.2.0,<4.0.0 - djangocurr: django>=4.0a1,<4.1.0 + djangolts,pytest: django~=3.2.0 + djangocurr: django~=4.0.0 pytest: pytest lxml>=3.6.0 commands = diff --git a/xmlrunner/version.py b/xmlrunner/version.py index 310e871..bb42b14 100644 --- a/xmlrunner/version.py +++ b/xmlrunner/version.py @@ -1,2 +1,2 @@ -__version__ = '3.1.0' +__version__ = '3.2.0' From 3a2be20177ec6e396d23eecb68d38d410c2ef7a5 Mon Sep 17 00:00:00 2001 From: Damien Nozay <205466+dnozay@users.noreply.github.com> Date: Thu, 20 Jan 2022 11:08:29 -0800 Subject: [PATCH 29/29] Bump version to 3.2.0