From 4ce303060cf3966067e2026f56ce61aef90385e4 Mon Sep 17 00:00:00 2001 From: Fredrik Westermark Date: Fri, 19 Jul 2019 17:40:51 +0200 Subject: [PATCH 01/21] Tox travis python support (#53) * Drop support for py26, py32, py33, and py34. Add support for py35, py36, py37. * Use shallow clone in travis --- .travis.yml | 31 +++++++++++++++++++++++-------- tox.ini | 2 +- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7897ec3..fa0a982 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,33 @@ 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 + env: - - TOXENV=py26 - - TOXENV=py27 - - TOXENV=pypy - - TOXENV=py33 - - TOXENV=py34 - - TOXENV=cover + - TOXENV=py27 + - TOXENV=py36 + - TOXENV=cover 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/tox.ini b/tox.ini index 4ccdde0..5884bca 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, cover, flake8 sitepackages = False [testenv] From 5ea26cd620ba40589fc398f55441f5f39a37cded Mon Sep 17 00:00:00 2001 From: Fredrik Westermark Date: Mon, 29 Jul 2019 16:06:24 +0200 Subject: [PATCH 02/21] Fix pytest warnings for test module (#52) --- test_junit_xml.py | 194 +++++++++++++++++++++++----------------------- 1 file changed, 99 insertions(+), 95 deletions(-) diff --git a/test_junit_xml.py b/test_junit_xml.py index 1c59aed..3587e91 100644 --- a/test_junit_xml.py +++ b/test_junit_xml.py @@ -9,7 +9,9 @@ from six import u, PY2 -from junit_xml import TestCase, TestSuite, decode +from junit_xml import TestCase as Case +from junit_xml import TestSuite as Suite +from junit_xml import decode def serialize_and_read(test_suites, to_file=False, prettyprint=False, encoding=None): @@ -24,12 +26,12 @@ def serialize_and_read(test_suites, to_file=False, prettyprint=False, encoding=N 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) + Suite.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( + xml_string = Suite.to_xml_string( test_suites, prettyprint=prettyprint, encoding=encoding) if PY2: assert isinstance(xml_string, unicode) @@ -60,7 +62,7 @@ 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] + Suite('test', Case('Test1')), to_file=True)[0] self.fail("This should've raised an exeception") # pragma: nocover except Exception as exc: self.assertEqual( @@ -72,7 +74,7 @@ def test_single_suite_no_test_cases(self): timestamp = 1398382805 (ts, tcs) = serialize_and_read( - TestSuite( + Suite( name='test', test_cases=[], hostname='localhost', @@ -99,7 +101,7 @@ def test_single_suite_no_test_cases_utf8(self): package = 'mypäckage' timestamp = 1398382805 - test_suite = TestSuite( + test_suite = Suite( name='äöü', test_cases=[], hostname='löcalhost', @@ -130,7 +132,7 @@ def test_single_suite_no_test_cases_unicode(self): timestamp = 1398382805 (ts, tcs) = serialize_and_read( - TestSuite( + Suite( name=decode('äöü', 'utf-8'), test_cases=[], hostname=decode('löcalhost', 'utf-8'), @@ -155,29 +157,29 @@ def test_single_suite_no_test_cases_unicode(self): def test_single_suite_to_file(self): (ts, tcs) = serialize_and_read( - TestSuite('test', [TestCase('Test1')]), to_file=True)[0] + Suite('test', [Case('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] + (ts, tcs) = serialize_and_read(Suite( + 'test', [Case('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')]), + Suite('test', [Case('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')]), + Suite('test', [Case('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')])] + tss = [Suite('suite1', [Case('Test1')]), + Suite('suite2', [Case('Test2')])] suites = serialize_and_read(tss, to_file=True) self.assertEqual('suite1', suites[0][0].attributes['name'].value) @@ -187,8 +189,8 @@ def test_multiple_suites_to_file(self): 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')])] + tss = [Suite('suite1', [Case('Test1')]), + Suite('suite2', [Case('Test2')])] suites = serialize_and_read(tss) self.assertEqual('suite1', suites[0][0].attributes['name'].value) @@ -198,9 +200,9 @@ def test_multiple_suites_to_string(self): 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')])] + 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) self.assertEqual('suite1', suites[0][0].attributes['name'].value) @@ -212,25 +214,25 @@ def test_attribute_time(self): self.assertEqual('0', suites[1][0].attributes['time'].value) def test_attribute_disable(self): - tc = TestCase('Disabled-Test') + tc = Case('Disabled-Test') tc.is_enabled = False - tss = [TestSuite('suite1', [tc])] + tss = [Suite('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] + Suite(name='test', stderr='I am stderr!', + test_cases=[Case(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] + Suite(name='test', stdout='I am stdout!', + stderr='I am stderr!', + test_cases=[Case(name='Test1')]))[0] self.assertEqual('I am stderr!', suites[0].getElementsByTagName('system-err')[0].firstChild.data) self.assertEqual('I am stdout!', @@ -238,24 +240,26 @@ def test_stdout_stderr(self): def test_no_assertions(self): suites = serialize_and_read( - TestSuite(name='test', - test_cases=[TestCase(name='Test1')]))[0] + Suite(name='test', + test_cases=[Case(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) + Suite(name='test', + test_cases=[Case(name='Test1', + assertions=5)]))[0] + self.assertEqual( + '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) + test_suites = [Suite(name='suite1', test_cases=[Case(name='Test1')]), + Suite(name='suite2', test_cases=[Case(name='Test2')])] + xml_string = Suite.to_xml_string(test_suites) if PY2: self.assertTrue(isinstance(xml_string, unicode)) expected_xml_string = textwrap.dedent(""" @@ -272,10 +276,10 @@ def test_to_xml_string(self): self.assertEqual(xml_string, expected_xml_string) def test_to_xml_string_test_suites_not_a_list(self): - test_suites = TestSuite('suite1', [TestCase('Test1')]) + test_suites = Suite('suite1', [Case('Test1')]) try: - TestSuite.to_xml_string(test_suites) + Suite.to_xml_string(test_suites) except Exception as exc: self.assertEqual( str(exc), 'test_suites must be a list of test suites') @@ -284,30 +288,30 @@ def test_to_xml_string_test_suites_not_a_list(self): class TestCaseTests(unittest.TestCase): def test_init(self): (ts, tcs) = serialize_and_read( - TestSuite('test', [TestCase('Test1')]))[0] + Suite('test', [Case('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] + Suite('test', + [Case(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] + Suite('test', + [Case(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] + Suite('test', + [Case(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), @@ -315,9 +319,9 @@ def test_init_classname_time_timestamp(self): 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] + Suite( + 'test', [Case(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', @@ -325,8 +329,8 @@ def test_init_stderr(self): def test_init_stdout_stderr(self): (ts, tcs) = serialize_and_read( - TestSuite( - 'test', [TestCase( + Suite( + 'test', [Case( name='Test1', classname='some.class.name', elapsed_sec=123.345, stdout='I am stdout!', stderr='I am stderr!')]))[0] @@ -337,107 +341,107 @@ def test_init_stdout_stderr(self): stdout='I am stdout!', stderr='I am stderr!') def test_init_disable(self): - tc = TestCase('Disabled-Test') + tc = Case('Disabled-Test') tc.is_enabled = False - (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0] + (ts, tcs) = serialize_and_read(Suite('test', [tc]))[0] verify_test_case(self, tcs[0], {'name': 'Disabled-Test'}) def test_init_failure_message(self): - tc = TestCase('Failure-Message') + tc = Case('Failure-Message') tc.add_failure_info("failure message") - (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0] + (ts, tcs) = serialize_and_read(Suite('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 = Case('Failure-Output') tc.add_failure_info(output="I failed!") - (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0] + (ts, tcs) = serialize_and_read(Suite('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 = Case('Failure-Type') tc.add_failure_info(failure_type='com.example.Error') - (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0] + (ts, tcs) = serialize_and_read(Suite('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] + (ts, tcs) = serialize_and_read(Suite('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 = Case('Failure-Message-and-Output') tc.add_failure_info("failure message", "I failed!") - (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0] + (ts, tcs) = serialize_and_read(Suite('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 = Case('Error-Message') tc.add_error_info("error message") - (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0] + (ts, tcs) = serialize_and_read(Suite('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 = Case('Error-Output') tc.add_error_info(output="I errored!") - (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0] + (ts, tcs) = serialize_and_read(Suite('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 = Case('Error-Type') tc.add_error_info(error_type='com.example.Error') - (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0] + (ts, tcs) = serialize_and_read(Suite('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] + (ts, tcs) = serialize_and_read(Suite('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 = Case('Error-Message-and-Output') tc.add_error_info("error message", "I errored!") - (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0] + (ts, tcs) = serialize_and_read(Suite('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 = Case('Skipped-Message') tc.add_skipped_info("skipped message") - (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0] + (ts, tcs) = serialize_and_read(Suite('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 = Case('Skipped-Output') tc.add_skipped_info(output="I skipped!") - (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0] + (ts, tcs) = serialize_and_read(Suite('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 = 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(TestSuite('test', [tc]))[0] + (ts, tcs) = serialize_and_read(Suite('test', [tc]))[0] verify_test_case( self, tcs[0], {'name': 'Skipped-Output'}, @@ -445,39 +449,39 @@ def test_init_skipped_err_output(self): error_output="I skipped with an error!") def test_init_skipped(self): - tc = TestCase('Skipped-Message-and-Output') + tc = Case('Skipped-Message-and-Output') tc.add_skipped_info("skipped message", "I skipped!") - (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0] + (ts, tcs) = serialize_and_read(Suite('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 = Case('Failure-Message') tc.add_failure_info( u("failure message with legal unicode char: [\x22]")) - (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0] + (ts, tcs) = serialize_and_read(Suite('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 = Case('Failure-Message') tc.add_failure_info( u("failure message with illegal unicode char: [\x02]")) - (ts, tcs) = serialize_and_read(TestSuite('test', [tc]))[0] + (ts, tcs) = serialize_and_read(Suite('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 = 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 = TestSuite('Test UTF-8', [tc]) + test_suite = Suite('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'), @@ -489,18 +493,18 @@ def test_init_utf8(self): 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 = 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(TestSuite('Test Unicode', - [tc]))[0] + (ts, tcs) = serialize_and_read(Suite('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)}, From c6c3c90db98ddb82fa07c95519280a54f67c354c Mon Sep 17 00:00:00 2001 From: Fredrik Westermark Date: Fri, 19 Jul 2019 21:26:54 +0200 Subject: [PATCH 03/21] Convert tests to pytest style. Separate suite and case test modules. Break out test helpers. Fix flake8 issues. Fix formatting. --- .travis.yml | 1 + asserts.py | 62 +++++ junit_xml/__init__.py | 6 +- serializer.py | 53 ++++ setup.cfg | 3 + setup.py | 1 + test_junit_xml.py | 583 ------------------------------------------ test_test_case.py | 271 ++++++++++++++++++++ test_test_suite.py | 222 ++++++++++++++++ 9 files changed, 616 insertions(+), 586 deletions(-) create mode 100644 asserts.py create mode 100644 serializer.py create mode 100644 setup.cfg delete mode 100644 test_junit_xml.py create mode 100644 test_test_case.py create mode 100644 test_test_suite.py diff --git a/.travis.yml b/.travis.yml index fa0a982..e7c094a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ env: - TOXENV=py27 - TOXENV=py36 - TOXENV=cover + - TOXENV=flake8 install: - travis_retry pip install tox==3.13.2 diff --git a/asserts.py b/asserts.py new file mode 100644 index 0000000..5a3e8ec --- /dev/null +++ b/asserts.py @@ -0,0 +1,62 @@ + + +def verify_test_case( + 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(): + 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 + 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 + + failures = test_case_element.getElementsByTagName('failure') + if failure_message or failure_output: + assert len(failures) > 0 + 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 + + skipped = test_case_element.getElementsByTagName('skipped') + if skipped_message or skipped_output: + assert len(skipped) > 0 + else: + assert len(skipped) == 0 diff --git a/junit_xml/__init__.py b/junit_xml/__init__.py index 91838df..69ecae7 100644 --- a/junit_xml/__init__.py +++ b/junit_xml/__init__.py @@ -59,15 +59,15 @@ def decode(var, encoding): If not already unicode, decode it. """ if PY2: - if isinstance(var, unicode): + if isinstance(var, unicode): # NOQA ret = var elif isinstance(var, str): if encoding: ret = var.decode(encoding) else: - ret = unicode(var) + ret = unicode(var) # NOQA else: - ret = unicode(var) + ret = unicode(var) # NOQA else: ret = str(var) return ret diff --git a/serializer.py b/serializer.py new file mode 100644 index 0000000..94d2d10 --- /dev/null +++ b/serializer.py @@ -0,0 +1,53 @@ +import codecs +import os +import tempfile +from xml.dom import minidom + +from six import PY2 + +from junit_xml import TestSuite as Suite + + +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: + Suite.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 = Suite.to_xml_string( + test_suites, prettyprint=prettyprint, encoding=encoding) + if PY2: + assert isinstance(xml_string, unicode) # NOQA + 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/setup.cfg b/setup.cfg new file mode 100644 index 0000000..f3b3562 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 120 +exclude = .git,.tox,build,dist,venv \ No newline at end of file diff --git a/setup.py b/setup.py index 3553f2c..4ade60e 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,7 @@ def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() + setup( name='junit-xml', author='Brian Beyer', diff --git a/test_junit_xml.py b/test_junit_xml.py deleted file mode 100644 index 3587e91..0000000 --- a/test_junit_xml.py +++ /dev/null @@ -1,583 +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 as Case -from junit_xml import TestSuite as Suite -from junit_xml import 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: - Suite.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 = Suite.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( - Suite('test', Case('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( - Suite( - 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 = 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] - 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( - 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] - 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( - Suite('test', [Case('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(Suite( - 'test', [Case('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( - Suite('test', [Case('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( - Suite('test', [Case('Test1')]), - to_file=True, prettyprint=False)[0] - verify_test_case(self, tcs[0], {'name': 'Test1'}) - - def test_multiple_suites_to_file(self): - tss = [Suite('suite1', [Case('Test1')]), - Suite('suite2', [Case('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 = [Suite('suite1', [Case('Test1')]), - Suite('suite2', [Case('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 = [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) - - 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 = Case('Disabled-Test') - tc.is_enabled = False - tss = [Suite('suite1', [tc])] - suites = serialize_and_read(tss) - - self.assertEqual('1', suites[0][0].attributes['disabled'].value) - - def test_stderr(self): - suites = serialize_and_read( - Suite(name='test', stderr='I am stderr!', - test_cases=[Case(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( - Suite(name='test', stdout='I am stdout!', - stderr='I am stderr!', - test_cases=[Case(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( - Suite(name='test', - test_cases=[Case(name='Test1')]))[0] - self.assertFalse(suites[0].getElementsByTagName('testcase')[0].hasAttribute('assertions')) - - def test_assertions(self): - suites = serialize_and_read( - Suite(name='test', - test_cases=[Case(name='Test1', - assertions=5)]))[0] - self.assertEqual( - '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 = [Suite(name='suite1', test_cases=[Case(name='Test1')]), - Suite(name='suite2', test_cases=[Case(name='Test2')])] - xml_string = Suite.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 = Suite('suite1', [Case('Test1')]) - - try: - Suite.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( - Suite('test', [Case('Test1')]))[0] - verify_test_case(self, tcs[0], {'name': 'Test1'}) - - def test_init_classname(self): - (ts, tcs) = serialize_and_read( - Suite('test', - [Case(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( - Suite('test', - [Case(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( - Suite('test', - [Case(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( - Suite( - 'test', [Case(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( - 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( - 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 = Case('Disabled-Test') - tc.is_enabled = False - (ts, tcs) = serialize_and_read(Suite('test', [tc]))[0] - verify_test_case(self, tcs[0], {'name': 'Disabled-Test'}) - - def test_init_failure_message(self): - tc = Case('Failure-Message') - tc.add_failure_info("failure message") - (ts, tcs) = serialize_and_read(Suite('test', [tc]))[0] - verify_test_case( - self, tcs[0], {'name': 'Failure-Message'}, - failure_message="failure message") - - def test_init_failure_output(self): - tc = Case('Failure-Output') - tc.add_failure_info(output="I failed!") - (ts, tcs) = serialize_and_read(Suite('test', [tc]))[0] - verify_test_case( - self, tcs[0], {'name': 'Failure-Output'}, - failure_output="I failed!") - - def test_init_failure_type(self): - 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(self, tcs[0], {'name': 'Failure-Type'}) - - tc.add_failure_info("failure message") - (ts, tcs) = serialize_and_read(Suite('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 = 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( - 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 = Case('Error-Message') - tc.add_error_info("error message") - (ts, tcs) = serialize_and_read(Suite('test', [tc]))[0] - verify_test_case( - self, tcs[0], {'name': 'Error-Message'}, - error_message="error message") - - def test_init_error_output(self): - tc = Case('Error-Output') - tc.add_error_info(output="I errored!") - (ts, tcs) = serialize_and_read(Suite('test', [tc]))[0] - verify_test_case( - self, tcs[0], {'name': 'Error-Output'}, error_output="I errored!") - - def test_init_error_type(self): - 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(self, tcs[0], {'name': 'Error-Type'}) - - tc.add_error_info("error message") - (ts, tcs) = serialize_and_read(Suite('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 = 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( - 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 = Case('Skipped-Message') - tc.add_skipped_info("skipped message") - (ts, tcs) = serialize_and_read(Suite('test', [tc]))[0] - verify_test_case( - self, tcs[0], {'name': 'Skipped-Message'}, - skipped_message="skipped message") - - def test_init_skipped_output(self): - tc = Case('Skipped-Output') - tc.add_skipped_info(output="I skipped!") - (ts, tcs) = serialize_and_read(Suite('test', [tc]))[0] - verify_test_case( - self, tcs[0], {'name': 'Skipped-Output'}, - skipped_output="I skipped!") - - def test_init_skipped_err_output(self): - 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( - self, tcs[0], - {'name': 'Skipped-Output'}, - skipped_output="I skipped!", - error_output="I skipped with an error!") - - def test_init_skipped(self): - 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( - self, tcs[0], {'name': 'Skipped-Message-and-Output'}, - skipped_message="skipped message", skipped_output="I skipped!") - - def test_init_legal_unicode_char(self): - 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( - self, tcs[0], {'name': 'Failure-Message'}, failure_message=u( - "failure message with legal unicode char: [\x22]")) - - def test_init_illegal_unicode_char(self): - 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( - self, tcs[0], {'name': 'Failure-Message'}, failure_message=u( - "failure message with illegal unicode char: []")) - - def test_init_utf8(self): - 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(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 = 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(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/test_test_case.py b/test_test_case.py new file mode 100644 index 0000000..29d99de --- /dev/null +++ b/test_test_case.py @@ -0,0 +1,271 @@ +# -*- 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') + ) diff --git a/test_test_suite.py b/test_test_suite.py new file mode 100644 index 0000000..0b09ab8 --- /dev/null +++ b/test_test_suite.py @@ -0,0 +1,222 @@ +# -*- coding: UTF-8 -*- +from __future__ import with_statement +import textwrap + +import pytest +from six import PY2 + +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_single_suite_single_test_case(): + with pytest.raises(Exception) 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 = Suite.to_xml_string(test_suites) + if PY2: + assert isinstance(xml_string, unicode) # NOQA + expected_xml_string = textwrap.dedent(""" + + + \t + \t\t + \t + \t + \t\t + \t + + """.strip("\n")) # NOQA + 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(Exception) as excinfo: + Suite.to_xml_string(test_suites) + assert str(excinfo.value) == 'test_suites must be a list of test suites' From b1578067c7e07a93ec663f2b705ccde054b1cb57 Mon Sep 17 00:00:00 2001 From: Fredrik Westermark Date: Mon, 22 Jul 2019 09:49:23 +0200 Subject: [PATCH 04/21] Move tests and helpers to tests package --- tests/__init__.py | 0 asserts.py => tests/asserts.py | 0 serializer.py => tests/serializer.py | 0 test_test_case.py => tests/test_test_case.py | 4 ++-- test_test_suite.py => tests/test_test_suite.py | 4 ++-- 5 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 tests/__init__.py rename asserts.py => tests/asserts.py (100%) rename serializer.py => tests/serializer.py (100%) rename test_test_case.py => tests/test_test_case.py (99%) rename test_test_suite.py => tests/test_test_suite.py (98%) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asserts.py b/tests/asserts.py similarity index 100% rename from asserts.py rename to tests/asserts.py diff --git a/serializer.py b/tests/serializer.py similarity index 100% rename from serializer.py rename to tests/serializer.py diff --git a/test_test_case.py b/tests/test_test_case.py similarity index 99% rename from test_test_case.py rename to tests/test_test_case.py index 29d99de..cc2ac97 100644 --- a/test_test_case.py +++ b/tests/test_test_case.py @@ -3,11 +3,11 @@ from six import u -from asserts import verify_test_case +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 +from .serializer import serialize_and_read def test_init(): diff --git a/test_test_suite.py b/tests/test_test_suite.py similarity index 98% rename from test_test_suite.py rename to tests/test_test_suite.py index 0b09ab8..dc5524e 100644 --- a/test_test_suite.py +++ b/tests/test_test_suite.py @@ -5,11 +5,11 @@ import pytest from six import PY2 -from asserts import verify_test_case +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 +from .serializer import serialize_and_read def test_single_suite_single_test_case(): From 214bef1222e5ad28d5dfc8a92858cca07420f126 Mon Sep 17 00:00:00 2001 From: Fredrik Westermark Date: Mon, 22 Jul 2019 10:48:31 +0200 Subject: [PATCH 05/21] Delete MANIFEST.in file. It does not add anything to setuptools --- MANIFEST.in | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 MANIFEST.in 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 From 113ca32ada6b622bb0ad926b395d1af7b4b5128f Mon Sep 17 00:00:00 2001 From: Fredrik Westermark Date: Mon, 22 Jul 2019 10:50:57 +0200 Subject: [PATCH 06/21] Exclude tests package for setuptools. Remove test_suite from setuptools configuration. Better to just run the tests using tox and pytest than through setuptools --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 4ade60e..1a761d9 100644 --- a/setup.py +++ b/setup.py @@ -13,8 +13,7 @@ def read(fname): author_email='brian@kyr.us', url='https://github.com/kyrus/python-junit-xml', license='MIT', - packages=find_packages(), - test_suite='test_junit_xml', + 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'), From 7c25ed4cd1dc4c8a9d9f023b8b34198e9a477d31 Mon Sep 17 00:00:00 2001 From: Fredrik Westermark Date: Thu, 25 Jul 2019 13:15:39 +0200 Subject: [PATCH 07/21] Be more specific in flake8 rules that are ignored for specific parts of the code --- junit_xml/__init__.py | 6 +++--- tests/serializer.py | 2 +- tests/test_test_suite.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/junit_xml/__init__.py b/junit_xml/__init__.py index 69ecae7..92543f2 100644 --- a/junit_xml/__init__.py +++ b/junit_xml/__init__.py @@ -59,15 +59,15 @@ def decode(var, encoding): If not already unicode, decode it. """ if PY2: - if isinstance(var, unicode): # NOQA + if isinstance(var, unicode): # noqa: F821 ret = var elif isinstance(var, str): if encoding: ret = var.decode(encoding) else: - ret = unicode(var) # NOQA + ret = unicode(var) # noqa: F821 else: - ret = unicode(var) # NOQA + ret = unicode(var) # noqa: F821 else: ret = str(var) return ret diff --git a/tests/serializer.py b/tests/serializer.py index 94d2d10..a741228 100644 --- a/tests/serializer.py +++ b/tests/serializer.py @@ -28,7 +28,7 @@ def serialize_and_read(test_suites, to_file=False, prettyprint=False, encoding=N xml_string = Suite.to_xml_string( test_suites, prettyprint=prettyprint, encoding=encoding) if PY2: - assert isinstance(xml_string, unicode) # NOQA + assert isinstance(xml_string, unicode) # noqa: F821 print("Serialized XML to string:\n%s" % xml_string) if encoding: xml_string = xml_string.encode(encoding) diff --git a/tests/test_test_suite.py b/tests/test_test_suite.py index dc5524e..0d45bac 100644 --- a/tests/test_test_suite.py +++ b/tests/test_test_suite.py @@ -199,7 +199,7 @@ def test_to_xml_string(): Suite(name='suite2', test_cases=[Case(name='Test2')])] xml_string = Suite.to_xml_string(test_suites) if PY2: - assert isinstance(xml_string, unicode) # NOQA + assert isinstance(xml_string, unicode) # noqa: F821 expected_xml_string = textwrap.dedent(""" @@ -210,7 +210,7 @@ def test_to_xml_string(): \t\t \t - """.strip("\n")) # NOQA + """.strip("\n")) assert xml_string == expected_xml_string From 311c0396c34116e03452a7feed6596d1f648e385 Mon Sep 17 00:00:00 2001 From: Fredrik Westermark Date: Mon, 29 Jul 2019 13:53:54 +0200 Subject: [PATCH 08/21] Do not throw Exception exceptions. Use TypeError that is more specific --- junit_xml/__init__.py | 4 ++-- tests/test_test_suite.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/junit_xml/__init__.py b/junit_xml/__init__.py index 92543f2..6b53a64 100644 --- a/junit_xml/__init__.py +++ b/junit_xml/__init__.py @@ -88,7 +88,7 @@ def __init__(self, name, test_cases=None, hostname=None, id=None, 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 @@ -249,7 +249,7 @@ def to_xml_string(test_suites, prettyprint=True, encoding=None): try: iter(test_suites) except TypeError: - raise Exception('test_suites must be a list of test suites') + raise TypeError('test_suites must be a list of test suites') xml_element = ET.Element("testsuites") attributes = defaultdict(int) diff --git a/tests/test_test_suite.py b/tests/test_test_suite.py index 0d45bac..8b280cf 100644 --- a/tests/test_test_suite.py +++ b/tests/test_test_suite.py @@ -13,7 +13,7 @@ def test_single_suite_single_test_case(): - with pytest.raises(Exception) as excinfo: + 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' @@ -217,6 +217,6 @@ def test_to_xml_string(): def test_to_xml_string_test_suites_not_a_list(): test_suites = Suite('suite1', [Case('Test1')]) - with pytest.raises(Exception) as excinfo: + with pytest.raises(TypeError) as excinfo: Suite.to_xml_string(test_suites) assert str(excinfo.value) == 'test_suites must be a list of test suites' From 87b063421a44ce225b1f7c89509877ede7b0ee83 Mon Sep 17 00:00:00 2001 From: Fredrik Westermark Date: Mon, 29 Jul 2019 14:28:41 +0200 Subject: [PATCH 09/21] Use black to format the code. Use flake8-black plugin to test black violations --- junit_xml/__init__.py | 180 ++++++++++++++++------------ pyproject.toml | 2 + setup.py | 45 ++++--- tests/asserts.py | 46 ++++---- tests/serializer.py | 5 +- tests/test_test_case.py | 249 +++++++++++++++++++-------------------- tests/test_test_suite.py | 185 ++++++++++++++--------------- tox.ini | 1 + 8 files changed, 366 insertions(+), 347 deletions(-) create mode 100644 pyproject.toml diff --git a/junit_xml/__init__.py b/junit_xml/__init__.py index 6b53a64..4123da7 100644 --- a/junit_xml/__init__.py +++ b/junit_xml/__init__.py @@ -79,16 +79,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 TypeError('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 +123,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) + 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["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)) 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 +154,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,39 +170,38 @@ 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'} + attrs = {"type": "failure"} if case.failure_message: - attrs['message'] = decode(case.failure_message, encoding) + attrs["message"] = decode(case.failure_message, encoding) if case.failure_type: - attrs['type'] = decode(case.failure_type, encoding) + 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) @@ -204,11 +209,11 @@ def build_xml_doc(self, encoding=None): # errors if case.is_error(): - attrs = {'type': 'error'} + attrs = {"type": "error"} if case.error_message: - attrs['message'] = decode(case.error_message, encoding) + attrs["message"] = decode(case.error_message, encoding) if case.error_type: - attrs['type'] = decode(case.error_type, encoding) + 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) @@ -216,9 +221,9 @@ def build_xml_doc(self, encoding=None): # skippeds if case.is_skipped(): - attrs = {'type': 'skipped'} + attrs = {"type": "skipped"} if case.skipped_message: - attrs['message'] = decode(case.skipped_message, encoding) + attrs["message"] = decode(case.skipped_message, encoding) skipped_element = ET.Element("skipped", attrs) if case.skipped_output: skipped_element.text = decode(case.skipped_output, encoding) @@ -249,15 +254,15 @@ def to_xml_string(test_suites, prettyprint=True, encoding=None): try: iter(test_suites) except TypeError: - raise TypeError('test_suites must be a list of test suites') + 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 ['failures', 'errors', 'tests', 'disabled']: + for key in ["failures", "errors", "tests", "disabled"]: attributes[key] += int(ts_xml.get(key, 0)) - for key in ['time']: + for key in ["time"]: attributes[key] += float(ts_xml.get(key, 0)) xml_element.append(ts_xml) for key, value in iteritems(attributes): @@ -265,13 +270,12 @@ def to_xml_string(test_suites, prettyprint=True, encoding=None): 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')) + 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_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) @@ -285,8 +289,7 @@ 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) + 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) @@ -299,30 +302,59 @@ def _clean_illegal_xml_chars(string_to_clean): """ 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) + (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, + group=None, + url=None, + ): self.name = name self.assertions = assertions self.elapsed_sec = elapsed_sec 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/setup.py b/setup.py index 1a761d9..e5f2c96 100644 --- a/setup.py +++ b/setup.py @@ -8,28 +8,25 @@ def read(fname): 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(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.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.8", 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/tests/asserts.py b/tests/asserts.py index 5a3e8ec..d4f5ff2 100644 --- a/tests/asserts.py +++ b/tests/asserts.py @@ -1,18 +1,16 @@ - - -def verify_test_case( - 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 +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, ): for k, v in expected_attributes.items(): assert test_case_element.attributes[k].value == v @@ -21,41 +19,41 @@ def verify_test_case( assert k in expected_attributes.keys() if stderr: - assert test_case_element.getElementsByTagName('system-err')[0].firstChild.nodeValue.strip() == 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 + assert test_case_element.getElementsByTagName("system-out")[0].firstChild.nodeValue.strip() == stdout - errors = test_case_element.getElementsByTagName('error') + errors = test_case_element.getElementsByTagName("error") if error_message or error_output: assert len(errors) > 0 else: assert len(errors) == 0 if error_message: - assert errors[0].attributes['message'].value == error_message + assert errors[0].attributes["message"].value == error_message if error_type and errors: - assert errors[0].attributes['type'].value == error_type + assert errors[0].attributes["type"].value == error_type if error_output: assert errors[0].firstChild.nodeValue.strip() == error_output - failures = test_case_element.getElementsByTagName('failure') + failures = test_case_element.getElementsByTagName("failure") if failure_message or failure_output: assert len(failures) > 0 else: assert len(failures) == 0 if failure_message: - assert failures[0].attributes['message'].value == failure_message + assert failures[0].attributes["message"].value == failure_message if failure_type and failures: - assert failures[0].attributes['type'].value == failure_type + assert failures[0].attributes["type"].value == failure_type if failure_output: assert failures[0].firstChild.nodeValue.strip() == failure_output - skipped = test_case_element.getElementsByTagName('skipped') + skipped = test_case_element.getElementsByTagName("skipped") if skipped_message or skipped_output: assert len(skipped) > 0 else: diff --git a/tests/serializer.py b/tests/serializer.py index a741228..f1fcb39 100644 --- a/tests/serializer.py +++ b/tests/serializer.py @@ -19,14 +19,13 @@ def serialize_and_read(test_suites, to_file=False, prettyprint=False, encoding=N if to_file: fd, filename = tempfile.mkstemp(text=True) os.close(fd) - with codecs.open(filename, mode='w', encoding=encoding) as f: + with codecs.open(filename, mode="w", encoding=encoding) as f: Suite.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 = Suite.to_xml_string( - test_suites, prettyprint=prettyprint, encoding=encoding) + xml_string = Suite.to_xml_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) diff --git a/tests/test_test_case.py b/tests/test_test_case.py index cc2ac97..692562b 100644 --- a/tests/test_test_case.py +++ b/tests/test_test_case.py @@ -11,261 +11,250 @@ def test_init(): - ts, tcs = serialize_and_read(Suite('test', [Case('Test1')]))[0] - verify_test_case(tcs[0], {'name': 'Test1'}) + 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'}) + 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)}) + 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] + 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)} + 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] + 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!' + 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] + 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!' + {"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 = Case("Disabled-Test") tc.is_enabled = False - ts, tcs = serialize_and_read(Suite('test', [tc]))[0] - verify_test_case(tcs[0], {'name': 'Disabled-Test'}) + 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 = 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") + 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 = 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!") + 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 = 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] + 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' + 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 = Case("Failure-Message-and-Output") tc.add_failure_info("failure message", "I failed!") - ts, tcs = serialize_and_read(Suite('test', [tc]))[0] + ts, tcs = serialize_and_read(Suite("test", [tc]))[0] verify_test_case( tcs[0], - {'name': 'Failure-Message-and-Output'}, + {"name": "Failure-Message-and-Output"}, failure_message="failure message", failure_output="I failed!", - failure_type='failure' + failure_type="failure", ) def test_init_error_message(): - tc = Case('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") + 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 = 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!") + 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 = 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') + 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 = Case("Error-Message-and-Output") tc.add_error_info("error message", "I errored!") - ts, tcs = serialize_and_read(Suite('test', [tc]))[0] + ts, tcs = serialize_and_read(Suite("test", [tc]))[0] verify_test_case( tcs[0], - {'name': 'Error-Message-and-Output'}, + {"name": "Error-Message-and-Output"}, error_message="error message", error_output="I errored!", - error_type="error" + error_type="error", ) def test_init_skipped_message(): - tc = Case('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") + 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 = 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!") + 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 = 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] + 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!" + 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 = Case("Skipped-Message-and-Output") tc.add_skipped_info("skipped message", "I skipped!") - ts, tcs = serialize_and_read(Suite('test', [tc]))[0] + 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!" + 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 = Case("Failure-Message") tc.add_failure_info(u("failure message with legal unicode char: [\x22]")) - ts, tcs = serialize_and_read(Suite('test', [tc]))[0] + 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]") + 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 = Case("Failure-Message") tc.add_failure_info(u("failure message with illegal unicode char: [\x02]")) - ts, tcs = serialize_and_read(Suite('test', [tc]))[0] + 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: []") + 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.äöü', + name="Test äöü", + classname="some.class.name.äöü", elapsed_sec=123.345, - stdout='I am stdöüt!', - stderr='I am stdärr!' + 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] + 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) + "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') + 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'), + 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') + 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')) + 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] + 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) + "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') + 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"), ) diff --git a/tests/test_test_suite.py b/tests/test_test_suite.py index 8b280cf..9ea3d56 100644 --- a/tests/test_test_suite.py +++ b/tests/test_test_suite.py @@ -14,193 +14,191 @@ 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' + 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' + properties = {"foo": "bar"} + package = "mypackage" timestamp = 1398382805 ts, tcs = serialize_and_read( Suite( - name='test', + name="test", test_cases=[], - hostname='localhost', + hostname="localhost", id=1, properties=properties, package=package, - timestamp=timestamp + timestamp=timestamp, ), to_file=True, - prettyprint=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' + 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' + properties = {"foö": "bär"} + package = "mypäckage" timestamp = 1398382805 test_suite = Suite( - name='äöü', + name="äöü", test_cases=[], - hostname='löcalhost', - id='äöü', + hostname="löcalhost", + id="äöü", properties=properties, package=package, - timestamp=timestamp + 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') + 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') + 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'), + name=decode("äöü", "utf-8"), test_cases=[], - hostname=decode('löcalhost', 'utf-8'), - id=decode('äöü', 'utf-8'), + hostname=decode("löcalhost", "utf-8"), + id=decode("äöü", "utf-8"), properties=properties, package=package, - timestamp=timestamp + timestamp=timestamp, ), to_file=True, prettyprint=True, - encoding='utf-8' + 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') + 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'}) + 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'}) + 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'}) + 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'}) + 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')])] + 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[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'}) + 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')])] + 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[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'}) + 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')])] + 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[0][0].attributes["name"].value == "suite1" + assert suites[0][0].attributes["time"].value == "246.69" - assert suites[1][0].attributes['name'].value == 'suite2' + 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' + assert suites[1][0].attributes["time"].value == "0" def test_attribute_disable(): - tc = Case('Disabled-Test') + tc = Case("Disabled-Test") tc.is_enabled = False - tss = [Suite('suite1', [tc])] + tss = [Suite("suite1", [tc])] suites = serialize_and_read(tss) - assert suites[0][0].attributes['disabled'].value == '1' + 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!' + 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!' + 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') + 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' + 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')])] + test_suites = [ + Suite(name="suite1", test_cases=[Case(name="Test1")]), + Suite(name="suite2", test_cases=[Case(name="Test2")]), + ] xml_string = Suite.to_xml_string(test_suites) if PY2: assert isinstance(xml_string, unicode) # noqa: F821 - expected_xml_string = textwrap.dedent(""" + expected_xml_string = textwrap.dedent( + """ \t @@ -210,13 +208,16 @@ def test_to_xml_string(): \t\t \t - """.strip("\n")) + """.strip( + "\n" + ) + ) assert xml_string == expected_xml_string def test_to_xml_string_test_suites_not_a_list(): - test_suites = Suite('suite1', [Case('Test1')]) + test_suites = Suite("suite1", [Case("Test1")]) with pytest.raises(TypeError) as excinfo: Suite.to_xml_string(test_suites) - assert str(excinfo.value) == 'test_suites must be a list of test suites' + assert str(excinfo.value) == "test_suites must be a list of test suites" diff --git a/tox.ini b/tox.ini index 5884bca..ab95ab1 100644 --- a/tox.ini +++ b/tox.ini @@ -28,6 +28,7 @@ commands = [testenv:flake8] deps = + flake8-black pytest pytest-sugar pytest-flake8 From 70baa4aaff0ced192b0245e83ba49af70781df25 Mon Sep 17 00:00:00 2001 From: Fredrik Westermark Date: Sat, 3 Aug 2019 09:16:34 +0200 Subject: [PATCH 10/21] Move TestSuite.to_xml_string and TestSuite.to_file to functions that are not part of the TestSuite class. Add deprecation warnings for old interface --- junit_xml/__init__.py | 172 ++++++++++++++++++++++----------------- tests/serializer.py | 6 +- tests/test_test_suite.py | 26 +++++- 3 files changed, 123 insertions(+), 81 deletions(-) diff --git a/junit_xml/__init__.py b/junit_xml/__init__.py index 4123da7..f6c8ea9 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 @@ -250,89 +251,112 @@ 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 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 ["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 + """ + + 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 ["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 = _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 + + +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) + 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): diff --git a/tests/serializer.py b/tests/serializer.py index f1fcb39..8a06200 100644 --- a/tests/serializer.py +++ b/tests/serializer.py @@ -5,7 +5,7 @@ from six import PY2 -from junit_xml import TestSuite as Suite +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): @@ -20,12 +20,12 @@ def serialize_and_read(test_suites, to_file=False, prettyprint=False, encoding=N fd, filename = tempfile.mkstemp(text=True) os.close(fd) with codecs.open(filename, mode="w", encoding=encoding) as f: - Suite.to_file(f, test_suites, prettyprint=prettyprint, encoding=encoding) + 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 = Suite.to_xml_string(test_suites, prettyprint=prettyprint, encoding=encoding) + 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) diff --git a/tests/test_test_suite.py b/tests/test_test_suite.py index 9ea3d56..a6591fe 100644 --- a/tests/test_test_suite.py +++ b/tests/test_test_suite.py @@ -1,14 +1,16 @@ # -*- coding: UTF-8 -*- from __future__ import with_statement + import textwrap +import warnings import pytest -from six import PY2 +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 +from junit_xml import decode, to_xml_report_string from .serializer import serialize_and_read @@ -194,7 +196,7 @@ def test_to_xml_string(): Suite(name="suite1", test_cases=[Case(name="Test1")]), Suite(name="suite2", test_cases=[Case(name="Test2")]), ] - xml_string = Suite.to_xml_string(test_suites) + xml_string = to_xml_report_string(test_suites) if PY2: assert isinstance(xml_string, unicode) # noqa: F821 expected_xml_string = textwrap.dedent( @@ -219,5 +221,21 @@ def test_to_xml_string_test_suites_not_a_list(): test_suites = Suite("suite1", [Case("Test1")]) with pytest.raises(TypeError) as excinfo: - Suite.to_xml_string(test_suites) + 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) From 4369306c59ecaf2128873a4c30bcf32d404ae2b2 Mon Sep 17 00:00:00 2001 From: Fredrik Westermark Date: Wed, 31 Jul 2019 14:06:55 +0200 Subject: [PATCH 11/21] Add configuration for universal wheel --- setup.cfg | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index f3b3562..3fcc1e7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,6 @@ [flake8] max-line-length = 120 -exclude = .git,.tox,build,dist,venv \ No newline at end of file +exclude = .git,.tox,build,dist,venv + +[wheel] +universal = 1 From 4aec9cd63d315ed57db9fb24c744590a6559a8c1 Mon Sep 17 00:00:00 2001 From: Fredrik Westermark Date: Wed, 31 Jul 2019 14:18:28 +0200 Subject: [PATCH 12/21] Add venv and Makefile for development --- .gitignore | 1 + Makefile | 17 +++++++++++++++++ pytest.ini | 2 ++ requirements.txt | 1 + requirements_dev.txt | 4 ++++ 5 files changed, 25 insertions(+) create mode 100644 Makefile create mode 100644 pytest.ini create mode 100644 requirements.txt create mode 100644 requirements_dev.txt 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/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/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 From cde48232a172d17b8ab6ba448d32c4bcecaddc17 Mon Sep 17 00:00:00 2001 From: Fredrik Westermark Date: Wed, 31 Jul 2019 14:37:48 +0200 Subject: [PATCH 13/21] Remove unused parameter --- junit_xml/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/junit_xml/__init__.py b/junit_xml/__init__.py index f6c8ea9..3ca3450 100644 --- a/junit_xml/__init__.py +++ b/junit_xml/__init__.py @@ -376,7 +376,6 @@ def __init__( file=None, line=None, log=None, - group=None, url=None, ): self.name = name From 8c5aba98b9dac38e6dc9c25d05472895fce4dd00 Mon Sep 17 00:00:00 2001 From: Martin Rakovec Date: Mon, 1 Apr 2019 13:16:26 +0200 Subject: [PATCH 14/21] Add support for more failure/error/skipped elements in one TestCase Signed-off-by: Martin Rakovec --- junit_xml/__init__.py | 88 +++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/junit_xml/__init__.py b/junit_xml/__init__.py index 3ca3450..321fa27 100644 --- a/junit_xml/__init__.py +++ b/junit_xml/__init__.py @@ -197,37 +197,37 @@ def build_xml_doc(self, encoding=None): test_case_element = ET.SubElement(xml_element, "testcase", test_case_attributes) # failures - if case.is_failure(): + for failure in case.failures: 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) + 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 case.failure_output: - failure_element.text = decode(case.failure_output, encoding) + if failure['output']: + failure_element.text = decode(failure['output'], encoding) test_case_element.append(failure_element) # errors - if case.is_error(): + for error in case.errors: 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) + 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 case.error_output: - error_element.text = decode(case.error_output, encoding) + if error['output']: + error_element.text = decode(error['output'], encoding) test_case_element.append(error_element) # skippeds - if case.is_skipped(): + for skipped in case.skipped: attrs = {"type": "skipped"} - if case.skipped_message: - attrs["message"] = decode(case.skipped_message, encoding) + 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 @@ -393,48 +393,44 @@ def __init__( 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 = [] 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 + if message or output or error_type: + error = {} + error['message'] = message + error['output'] = output + error['type'] = error_type + self.errors.append(error) 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 + if message or output or failure_type: + failure = {} + failure['message'] = message + failure['output'] = output + failure['type'] = failure_type + self.failures.append(failure) 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 + if message or output: + skipped = {} + skipped['message'] = message + skipped['output'] = output + self.skipped.append(skipped) def is_failure(self): """returns true if this test case is a failure""" - return self.failure_output or self.failure_message + return (len(self.failures) > 0) def is_error(self): """returns true if this test case is an error""" - return self.error_output or self.error_message + return (len(self.errors) > 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) From fb2c8cb7221412de06644337a892ad01603714d9 Mon Sep 17 00:00:00 2001 From: Martin Rakovec Date: Mon, 1 Apr 2019 13:16:57 +0200 Subject: [PATCH 15/21] Modify README.rst (Bamboo is also supported) Signed-off-by: Martin Rakovec --- README.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 46b2804..470cef1 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 ------------ From f7d5463875f84e4f1763ca78dec7de51beb0e767 Mon Sep 17 00:00:00 2001 From: Martin Rakovec Date: Mon, 1 Apr 2019 15:34:47 +0200 Subject: [PATCH 16/21] Increase version Signed-off-by: Martin Rakovec --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e5f2c96..5d8a46e 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ def read(fname): 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.8", + version="1.8.1", classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", From 63e95fd20edf7b0b62d6a45bcf857b3c57d212a1 Mon Sep 17 00:00:00 2001 From: Martin Rakovec Date: Fri, 12 Apr 2019 20:45:25 +0200 Subject: [PATCH 17/21] Add support for multiple sub-elements (failure/error/skipped) in testcase Signed-off-by: Martin Rakovec --- junit_xml/__init__.py | 110 ++++++++++++++++++++++++++-------------- tests/asserts.py | 57 ++++++++++++++------- tests/test_test_case.py | 64 +++++++++++++++++++++++ 3 files changed, 175 insertions(+), 56 deletions(-) diff --git a/junit_xml/__init__.py b/junit_xml/__init__.py index 321fa27..78a153e 100644 --- a/junit_xml/__init__.py +++ b/junit_xml/__init__.py @@ -198,36 +198,38 @@ def build_xml_doc(self, encoding=None): # failures for failure in case.failures: - 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) + 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 for error in case.errors: - 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) + 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 for skipped in case.skipped: attrs = {"type": "skipped"} - if skipped['message']: - attrs['message'] = decode(skipped['message'], encoding) + if skipped["message"]: + attrs["message"] = decode(skipped["message"], encoding) skipped_element = ET.Element("skipped", attrs) - if skipped['output']: - skipped_element.text = decode(skipped['output'], encoding) + if skipped["output"]: + skipped_element.text = decode(skipped["output"], encoding) test_case_element.append(skipped_element) # test stdout @@ -377,6 +379,7 @@ def __init__( line=None, log=None, url=None, + allow_multiple_subelements=False, ): self.name = name self.assertions = assertions @@ -396,41 +399,70 @@ def __init__( 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 or output or error_type: - error = {} - error['message'] = message - error['output'] = output - 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 or output or failure_type: - failure = {} - failure['message'] = message - failure['output'] = output - 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 or output: - skipped = {} - skipped['message'] = message - 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 (len(self.failures) > 0) + 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 (len(self.errors) > 0) + 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 (len(self.skipped) > 0) + return len(self.skipped) > 0 diff --git a/tests/asserts.py b/tests/asserts.py index d4f5ff2..82c48b4 100644 --- a/tests/asserts.py +++ b/tests/asserts.py @@ -11,6 +11,9 @@ def verify_test_case( # noqa: E302 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 @@ -23,38 +26,58 @@ def verify_test_case( # noqa: E302 if stdout: assert test_case_element.getElementsByTagName("system-out")[0].firstChild.nodeValue.strip() == stdout - errors = test_case_element.getElementsByTagName("error") + _errors = test_case_element.getElementsByTagName("error") if error_message or error_output: - assert len(errors) > 0 + assert len(_errors) > 0 + elif errors: + assert len(errors) == len(_errors) else: - assert len(errors) == 0 + assert len(_errors) == 0 if error_message: - assert errors[0].attributes["message"].value == 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_type and _errors: + assert _errors[0].attributes["type"].value == error_type if error_output: - assert errors[0].firstChild.nodeValue.strip() == error_output + assert _errors[0].firstChild.nodeValue.strip() == error_output - failures = test_case_element.getElementsByTagName("failure") + 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 + assert len(_failures) > 0 + elif failures: + assert len(failures) == len(_failures) else: - assert len(failures) == 0 + assert len(_failures) == 0 if failure_message: - assert failures[0].attributes["message"].value == 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_type and _failures: + assert _failures[0].attributes["type"].value == failure_type if failure_output: - assert failures[0].firstChild.nodeValue.strip() == 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") + _skipped = test_case_element.getElementsByTagName("skipped") if skipped_message or skipped_output: - assert len(skipped) > 0 + assert len(_skipped) > 0 + elif skipped: + assert len(skipped) == len(_skipped) else: - assert len(skipped) == 0 + 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/test_test_case.py b/tests/test_test_case.py index 692562b..b9817de 100644 --- a/tests/test_test_case.py +++ b/tests/test_test_case.py @@ -258,3 +258,67 @@ def test_init_unicode(): 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"}, + ], + ) From 63db26da353790500642fd02cae1543eb41aab8b Mon Sep 17 00:00:00 2001 From: Radostin Stoyanov Date: Wed, 11 Sep 2019 12:45:12 +0100 Subject: [PATCH 18/21] Run tests with Python 3.8 Signed-off-by: Radostin Stoyanov --- .travis.yml | 4 ++++ junit_xml/__init__.py | 8 ++++---- tox.ini | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index e7c094a..f15766e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,10 @@ matrix: dist: xenial sudo: true env: TOXENV=py37 + - python: 3.8-dev + dist: xenial + sudo: true + env: TOXENV=py38 env: - TOXENV=py27 diff --git a/junit_xml/__init__.py b/junit_xml/__init__.py index 78a153e..0cfef29 100644 --- a/junit_xml/__init__.py +++ b/junit_xml/__init__.py @@ -124,15 +124,15 @@ 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["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["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["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) @@ -288,7 +288,7 @@ def to_xml_report_string(test_suites, prettyprint=True, encoding=None): attributes = defaultdict(int) for ts in test_suites: ts_xml = ts.build_xml_doc(encoding=encoding) - for key in ["failures", "errors", "tests", "disabled"]: + 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)) diff --git a/tox.ini b/tox.ini index ab95ab1..3fdb993 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27, pypy, py35, py36, py37, cover, flake8 +envlist = py27, pypy, py35, py36, py37, py38, cover, flake8 sitepackages = False [testenv] From 19d3cc333d35dfd2d17d75c506336c15e5c6685a Mon Sep 17 00:00:00 2001 From: Brian Beyer Date: Sat, 22 Feb 2020 13:17:57 -0700 Subject: [PATCH 19/21] bump to version 1.9 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5d8a46e..8aba068 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ def read(fname): 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.8.1", + version="1.9", classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", From 856414648cbab3f64e69b856bc25cea8b9aa0377 Mon Sep 17 00:00:00 2001 From: Brian Beyer Date: Sat, 22 Feb 2020 13:25:34 -0700 Subject: [PATCH 20/21] change to 3.8 branch --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f15766e..8d42e75 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ matrix: dist: xenial sudo: true env: TOXENV=py37 - - python: 3.8-dev + - python: 3.8 dist: xenial sudo: true env: TOXENV=py38 From 4bd08a272f059998cedf9b7779f944d49eba13a6 Mon Sep 17 00:00:00 2001 From: Brian Beyer Date: Sat, 22 Feb 2020 13:43:21 -0700 Subject: [PATCH 21/21] update readme --- README.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.rst b/README.rst index 470cef1..b585af1 100644 --- a/README.rst +++ b/README.rst @@ -89,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