Hello community,

here is the log from the commit of package python-unittest-xml-reporting for 
openSUSE:Factory checked in at 2019-04-14 12:22:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-unittest-xml-reporting (Old)
 and      /work/SRC/openSUSE:Factory/.python-unittest-xml-reporting.new.27019 
(New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-unittest-xml-reporting"

Sun Apr 14 12:22:58 2019 rev:16 rq:693688 version:2.5.1

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-unittest-xml-reporting/python-unittest-xml-reporting.changes
      2019-03-05 12:26:38.888833304 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-unittest-xml-reporting.new.27019/python-unittest-xml-reporting.changes
   2019-04-14 12:23:01.935805035 +0200
@@ -1,0 +2,6 @@
+Fri Apr 12 09:06:13 UTC 2019 - Marketa Calabkova <[email protected]>
+
+- update to version 2.5.1
+  * no changelog available
+
+-------------------------------------------------------------------

Old:
----
  2.2.1.tar.gz

New:
----
  2.5.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-unittest-xml-reporting.spec ++++++
--- /var/tmp/diff_new_pack.deHCPU/_old  2019-04-14 12:23:02.655805899 +0200
+++ /var/tmp/diff_new_pack.deHCPU/_new  2019-04-14 12:23:02.655805899 +0200
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-unittest-xml-reporting
-Version:        2.2.1
+Version:        2.5.1
 Release:        0
 Summary:        PyUnit-based test runner with JUnit like XML reporting
 License:        LGPL-3.0-or-later

++++++ 2.2.1.tar.gz -> 2.5.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/unittest-xml-reporting-2.2.1/.landscape.yml 
new/unittest-xml-reporting-2.5.1/.landscape.yml
--- old/unittest-xml-reporting-2.2.1/.landscape.yml     1970-01-01 
01:00:00.000000000 +0100
+++ new/unittest-xml-reporting-2.5.1/.landscape.yml     2019-03-25 
06:05:10.000000000 +0100
@@ -0,0 +1,8 @@
+doc-warnings: true
+test-warnings: false
+strictness: veryhigh
+max-line-length: 80
+autodetect: true
+python-targets:
+  - 2
+  - 3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/unittest-xml-reporting-2.2.1/.travis.yml 
new/unittest-xml-reporting-2.5.1/.travis.yml
--- old/unittest-xml-reporting-2.2.1/.travis.yml        2019-01-09 
07:52:55.000000000 +0100
+++ new/unittest-xml-reporting-2.5.1/.travis.yml        2019-03-25 
06:05:10.000000000 +0100
@@ -1,34 +1,53 @@
-sudo: false
-
 language: python
+sudo: required
+cache: pip
 
 matrix:
   include:
-    - python: "2.7"
+    - python: 2.7
       env: TOXENV=py27
-    - python: "2.7"
+    - python: 2.7
       env: TOXENV=py27-djangolts
-    - python: "2.7"
+    - python: 2.7
       env: TOXENV=py27-djangocurr
-    - python: "2.7"
+    - python: 2.7
       env: TOXENV=quality
-    - python: "3.4"
-      env: TOXENV=py34
-    - python: "3.5"
+    - python: 3.5
       env: TOXENV=py35
-    - python: "3.6"
+    - python: 3.6
       env: TOXENV=py36
-    - python: "3.7-dev"
+    - python: 3.7-dev
       env: TOXENV=py37
-    - python: "pypy"
+    - python: 3.7-dev
+      env: TOXENV=pytest
+    - os: linux
+      dist: xenial
+      python: pypy2.7-6.0
       env: TOXENV=pypy
-    - python: "pypy3"
+      services:
+        - docker
+    - os: linux
+      dist: xenial
+      python: pypy3.5-6.0
       env: TOXENV=pypy3
+      services:
+        - docker
 
+before_install:
+  - python --version
+  - uname -a
+  - lsb_release -a
 install:
   - pip install tox codecov coveralls
+  - virtualenv --version
+  - easy_install --version
+  - pip --version
+  - tox --version
 script:
-  - tox
+  - tox -v
+after_failure:
+  - more .tox/log/* | cat
+  - more .tox/*/log/* | cat
 after_success:
   - codecov
   - coveralls
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/unittest-xml-reporting-2.2.1/README.md 
new/unittest-xml-reporting-2.5.1/README.md
--- old/unittest-xml-reporting-2.2.1/README.md  2019-01-09 07:52:55.000000000 
+0100
+++ new/unittest-xml-reporting-2.5.1/README.md  2019-03-25 06:05:10.000000000 
+0100
@@ -4,16 +4,36 @@
 [![Documentation 
Status](https://readthedocs.org/projects/unittest-xml-reporting/badge/?version=latest)](http://unittest-xml-reporting.readthedocs.io/en/latest/?badge=latest)
 
 [![Build 
Status](https://travis-ci.org/xmlrunner/unittest-xml-reporting.svg?branch=master)](https://travis-ci.org/xmlrunner/unittest-xml-reporting)
-[![Code 
Health](https://landscape.io/github/xmlrunner/unittest-xml-reporting/master/landscape.png)](https://landscape.io/github/xmlrunner/unittest-xml-reporting/master)
+[![Code 
Health](https://landscape.io/github/xmlrunner/unittest-xml-reporting/master/landscape.svg?style=flat)](https://landscape.io/github/xmlrunner/unittest-xml-reporting/master)
 [![codecov.io Coverage 
Status](https://codecov.io/github/xmlrunner/unittest-xml-reporting/coverage.svg?branch=master)](https://codecov.io/github/xmlrunner/unittest-xml-reporting?branch=master)
 [![Coveralls Coverage 
Status](https://coveralls.io/repos/xmlrunner/unittest-xml-reporting/badge.svg?branch=master&service=github)](https://coveralls.io/github/xmlrunner/unittest-xml-reporting?branch=master)
 [![Requirements 
Status](https://requires.io/github/xmlrunner/unittest-xml-reporting/requirements.svg?branch=master)](https://requires.io/github/xmlrunner/unittest-xml-reporting/requirements/?branch=master)
 
-# unittest-xml-reporting
+# unittest-xml-reporting (aka xmlrunner)
 
-unittest-xml-reporting is a unittest test runner that can save test results
-to XML files that can be consumed by a wide range of tools, such as build
-systems, IDEs and continuous integration servers.
+A unittest test runner that can save test results to XML files in xUnit format.
+The files can be consumed by a wide range of tools, such as build systems, IDEs
+and continuous integration servers.
+
+## Schema
+
+There are many schemas with minor differences.
+We use one that is compatible with Jenkins xUnit plugin, a copy is
+available under `tests/vendor/jenkins/xunit-plugin/junit-10.xsd` (see attached 
license).
+You may also find these resources useful:
+
+- 
https://stackoverflow.com/questions/4922867/what-is-the-junit-xml-format-specification-that-hudson-supports
+- https://stackoverflow.com/questions/11241781/python-unittests-in-jenkins
+- [Jenkins 
(junit-10.xsd)](https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd)
+- [JUnit-Schema 
(JUnit.xsd)](https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd)
+- [Windyroad (JUnit.xsd)](http://windyroad.com.au/dl/Open%20Source/JUnit.xsd)
+- [a gist (Jenkins xUnit test result 
schema)](https://gist.github.com/erikd/4192748)
+
+## Things that are somewhat broken
+
+Python 3 has the concept of sub-tests for a `unittest.TestCase`; this doesn't 
map well to an existing
+xUnit concept, so you won't find it in the schema. What that means, is that 
you lose some granularity
+in the reports for sub-tests.
 
 ## Requirements
 
@@ -50,6 +70,21 @@
 [PyPI](https://pypi.python.org/pypi/unittest-xml-reporting/).
 
 
+## Command-line
+
+````bash
+python -m xmlrunner [options]
+python -m xmlrunner discover [options]
+
+# help
+python -m xmlrunner -h
+````
+
+e.g. 
+````bash
+python -m xmlrunner discover -t ~/mycode/tests -o /tmp/build/junit-reports
+````
+
 ## Usage
 
 The script below, adapted from the
@@ -136,7 +171,7 @@
     xmlrunner.XMLTestRunner().run(suite)
 ````
 
-### Django
+### Django support
 
 In order to plug `XMLTestRunner` to a Django project, add the following
 to your `settings.py`:
@@ -147,32 +182,12 @@
 
 Also, the following settings are provided so you can fine tune the reports:
 
-**TEST_OUTPUT_VERBOSE** (Default: `1`, choose between `0`,`1`, and `2`)
-
-Besides the XML reports generated by the test runner, a bunch of useful
-information is printed to the `sys.stderr` stream, just like the
-`TextTestRunner` does. Use this setting to choose between a verbose and a
-non-verbose output.
-
-**TEST_OUTPUT_DESCRIPTIONS** (Default: `False`)
-
-If your test methods contains docstrings, you can display such docstrings
-instead of display the test name (ex: `module.TestCase.test_method`). In
-order to use this feature, you have to enable verbose output by setting
-`TEST_OUTPUT_VERBOSE = 2`.
-
-**TEST_OUTPUT_DIR** (Default: `"."`)
-
-Tells the test runner where to put the XML reports. If the directory
-couldn't be found, the test runner will try to create it before
-generate the XML files.
-
-**TEST_OUTPUT_FILE_NAME** (Default: `None`)
-
-Tells the test runner to output a single XML report with this filename
-under `os.path.join(TEST_OUTPUT_DIR, TEST_OUTPUT_FILE_NAME)`. Please note
-that for long running tests, this will keep the results in memory for
-a longer time than multiple reports, and may use up more resources.
+|setting|default|values|description|
+|-|-|-|-|
+|`TEST_OUTPUT_VERBOSE`|`1`|`0\|1\|2`|Besides the XML reports generated by the 
test runner, a bunch of useful information is printed to the `sys.stderr` 
stream, just like the `TextTestRunner` does. Use this setting to choose between 
a verbose and a non-verbose output.|
+|`TEST_OUTPUT_DESCRIPTIONS`|`False`|`True\|False`|If your test methods 
contains docstrings, you can display such docstrings instead of display the 
test name (ex: `module.TestCase.test_method`).<br>In order to use this feature, 
you have to enable verbose output by setting `TEST_OUTPUT_VERBOSE = 2`.<br>Only 
effects stdout and not XML output.|
+|`TEST_OUTPUT_DIR`|`"."`|`<str>`|Tells the test runner where to put the XML 
reports. If the directory couldn't be found, the test runner will try to create 
it before generate the XML files.|
+|`TEST_OUTPUT_FILE_NAME`|`None`|`<str>`|Tells the test runner to output a 
single XML report with this filename under `os.path.join(TEST_OUTPUT_DIR, 
TEST_OUTPUT_FILE_NAME)`.<br>Please note that for long running tests, this will 
keep the results in memory for a longer time than multiple reports, and may use 
up more resources.|
 
 
 ## Contributing
@@ -191,6 +206,11 @@
 
 ```bash
 $ pip install tox
+
+# basic sanity test, friendly output
+$ tox -e pytest
+
+# all combinations
 $ tox
 ```
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/unittest-xml-reporting-2.2.1/setup.py 
new/unittest-xml-reporting-2.5.1/setup.py
--- old/unittest-xml-reporting-2.2.1/setup.py   2019-01-09 07:52:55.000000000 
+0100
+++ new/unittest-xml-reporting-2.5.1/setup.py   2019-03-25 06:05:10.000000000 
+0100
@@ -10,6 +10,11 @@
 with codecs.open(ver_path, 'rb', 'utf8') as ver_file:
     exec(ver_file.read(), main_ns)
 
+# Load README.md
+readme_path = convert_path('README.md')
+with codecs.open(readme_path, 'rb', 'utf8') as readme_file:
+    long_description = readme_file.read()
+
 install_requires = ['six>=1.4.0']
 
 # this is for sdist to work.
@@ -21,13 +26,14 @@
 setup(
     name = 'unittest-xml-reporting',
     version = main_ns['__version__'],
-    author = 'Daniel Fernandes Martins',
-    author_email = '[email protected]',
+    author = 'Daniel Fernandes Martins, Damien Nozay',
     description = 'unittest-based test runner with Ant/JUnit like XML 
reporting.',
+    long_description = long_description,
+    long_description_content_type = 'text/markdown',
     license = 'BSD',
     platforms = ['Any'],
     keywords = [
-        'pyunit', 'unittest', 'junit xml', 'report', 'testrunner', 'xmlrunner'
+        'pyunit', 'unittest', 'junit xml', 'xunit', 'report', 'testrunner', 
'xmlrunner'
     ],
     url = 'http://github.com/xmlrunner/unittest-xml-reporting/tree/master/',
     classifiers = [
@@ -40,10 +46,9 @@
         'Programming Language :: Python :: 2',
         'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.3',
-        'Programming Language :: Python :: 3.4',
         'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
+        'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: Implementation :: CPython',
         'Programming Language :: Python :: Implementation :: PyPy',
         'Topic :: Software Development :: Libraries :: Python Modules',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/unittest-xml-reporting-2.2.1/tests/testsuite.py 
new/unittest-xml-reporting-2.5.1/tests/testsuite.py
--- old/unittest-xml-reporting-2.2.1/tests/testsuite.py 2019-01-09 
07:52:55.000000000 +0100
+++ new/unittest-xml-reporting-2.5.1/tests/testsuite.py 2019-03-25 
06:05:10.000000000 +0100
@@ -3,6 +3,8 @@
 
 """Executable module to test unittest-xml-reporting.
 """
+from __future__ import print_function
+
 import contextlib
 import io
 import sys
@@ -48,6 +50,20 @@
     JUnitSchema.assertValid(document)
 
 
+class TestCaseSubclassWithNoSuper(unittest.TestCase):
+    def __init__(self, description):
+        # no super, see #189
+        pass
+
+    def run(self, result):
+        result = _XMLTestResult()
+        result.startTest(self)
+        result.stopTest(self)
+
+    def test_something(self):
+        pass
+
+
 class DoctestTest(unittest.TestCase):
 
     def test_doctest_example(self):
@@ -59,10 +75,10 @@
         runner.run(suite)
         outdir.seek(0)
         output = outdir.read()
-        self.assertIn('classname="tests.doctest_example.Multiplicator" '
-                      'name="threetimes"'.encode('utf8'), output)
-        self.assertIn('classname="tests.doctest_example" '
-                      'name="twice"'.encode('utf8'), output)
+        
self.assertIn('classname="tests.doctest_example.Multiplicator"'.encode('utf8'), 
output)
+        self.assertIn('name="threetimes"'.encode('utf8'), output)
+        self.assertIn('classname="tests.doctest_example"'.encode('utf8'), 
output)
+        self.assertIn('name="twice"'.encode('utf8'), output)
 
 
 @contextlib.contextmanager
@@ -81,6 +97,16 @@
         sys.stderr = orig_stderr
 
 
+def _strip_xml(xml, changes):
+    doc = etree.fromstring(xml)
+    for xpath, attributes in changes.items():
+        for node in doc.xpath(xpath):
+            for attrib in node.attrib.keys():
+                if attrib not in attributes:
+                    del node.attrib[attrib]
+    return etree.tostring(doc)
+
+
 class XMLTestRunnerTestCase(unittest.TestCase):
     """
     XMLTestRunner test case.
@@ -121,6 +147,10 @@
         def test_unsafe_unicode(self):
             print(u"A\x00B\x08C\x0BD\x0C")
 
+        def test_output_stdout_and_stderr(self):
+            print('test on stdout')
+            print('test on stderr', file=sys.stderr)
+
         def test_runner_buffer_output_pass(self):
             print('should not be printed')
 
@@ -152,6 +182,11 @@
                 with self.subTest(i=i):
                     raise Exception('this is a subtest')
 
+        def test_subTest_mixed(self):
+            for i in range(2):
+                with self.subTest(i=i):
+                    self.assertLess(i, 1, msg='this is a subtest.')
+
     class DummyErrorInCallTest(unittest.TestCase):
 
         def __call__(self, result):
@@ -179,8 +214,9 @@
         self.runner_kwargs = {}
         self.addCleanup(rmtree, self.outdir)
 
-    def _test_xmlrunner(self, suite, runner=None):
-        outdir = self.outdir
+    def _test_xmlrunner(self, suite, runner=None, outdir=None):
+        if outdir is None:
+            outdir = self.outdir
         stream = self.stream
         verbosity = self.verbosity
         runner_kwargs = self.runner_kwargs
@@ -188,9 +224,15 @@
             runner = xmlrunner.XMLTestRunner(
                 stream=stream, output=outdir, verbosity=verbosity,
                 **runner_kwargs)
-        self.assertEqual(0, len(glob(os.path.join(outdir, '*xml'))))
+        if isinstance(outdir, BytesIO):
+            self.assertFalse(outdir.getvalue())
+        else:
+            self.assertEqual(0, len(glob(os.path.join(outdir, '*xml'))))
         runner.run(suite)
-        self.assertEqual(1, len(glob(os.path.join(outdir, '*xml'))))
+        if isinstance(outdir, BytesIO):
+            self.assertTrue(outdir.getvalue())
+        else:
+            self.assertEqual(1, len(glob(os.path.join(outdir, '*xml'))))
         return runner
 
     def test_basic_unittest_constructs(self):
@@ -214,6 +256,11 @@
         runner.run(suite)
         outdir.seek(0)
         output = outdir.read()
+        output = _strip_xml(output, {
+            '//testsuite': (),
+            '//testcase': ('classname', 'name'),
+            '//failure': ('message',),
+        })
         self.assertRegexpMatches(
             output,
             r'classname="tests\.testsuite\.(XMLTestRunnerTestCase\.)?'
@@ -222,7 +269,30 @@
         self.assertRegexpMatches(
             output,
             r'classname="tests\.testsuite\.(XMLTestRunnerTestCase\.)?'
-            r'DummySubTest" name="test_subTest_pass"'.encode('utf8'))
+            r'DummySubTest" name="test_subTest_pass"'.encode('utf8'),
+        )
+
+    def test_expected_failure(self):
+        suite = unittest.TestSuite()
+        suite.addTest(self.DummyTest('test_expected_failure'))
+        outdir = BytesIO()
+
+        self._test_xmlrunner(suite, outdir=outdir)
+
+        self.assertNotIn(b'<failure', outdir.getvalue())
+        self.assertNotIn(b'<error', outdir.getvalue())
+        self.assertIn(b'<skip', outdir.getvalue())
+
+    def test_unexpected_success(self):
+        suite = unittest.TestSuite()
+        suite.addTest(self.DummyTest('test_unexpected_success'))
+        outdir = BytesIO()
+
+        self._test_xmlrunner(suite, outdir=outdir)
+
+        self.assertNotIn(b'<failure', outdir.getvalue())
+        self.assertIn(b'<error', outdir.getvalue())
+        self.assertNotIn(b'<skip', outdir.getvalue())
 
     def test_xmlrunner_non_ascii(self):
         suite = unittest.TestSuite()
@@ -410,6 +480,11 @@
         runner.run(suite)
         outdir.seek(0)
         output = outdir.read()
+        output = _strip_xml(output, {
+            '//testsuite': (),
+            '//testcase': ('classname', 'name'),
+            '//failure': ('message',),
+        })
         self.assertRegexpMatches(
             output,
             br'<testcase classname="tests\.testsuite\.'
@@ -434,6 +509,11 @@
         runner.run(suite)
         outdir.seek(0)
         output = outdir.read()
+        output = _strip_xml(output, {
+            '//testsuite': (),
+            '//testcase': ('classname', 'name'),
+            '//failure': ('message',),
+        })
         self.assertRegexpMatches(
             output,
             br'<testcase classname="tests\.testsuite\.'
@@ -445,6 +525,32 @@
             br'(XMLTestRunnerTestCase\.)?DummySubTest" '
             br'name="test_subTest_error \(i=1\)"')
 
+
+    @unittest.skipIf(not hasattr(unittest.TestCase, 'subTest'),
+                     'unittest.TestCase.subTest not present.')
+    def test_unittest_subTest_mixed(self):
+        # test for issue #155
+        outdir = BytesIO()
+        runner = xmlrunner.XMLTestRunner(
+            stream=self.stream, output=outdir, verbosity=self.verbosity,
+            **self.runner_kwargs)
+        suite = unittest.TestSuite()
+        suite.addTest(self.DummySubTest('test_subTest_mixed'))
+        runner.run(suite)
+        outdir.seek(0)
+        output = outdir.read()
+        output = _strip_xml(output, {
+            '//testsuite': (),
+            '//testcase': ('classname', 'name'),
+            '//failure': ('message',),
+        })
+        self.assertNotIn(
+            'name="test_subTest_mixed (i=0)"'.encode('utf8'),
+            output)
+        self.assertIn(
+            'name="test_subTest_mixed (i=1)"'.encode('utf8'),
+            output)
+
     @unittest.skipIf(not hasattr(unittest.TestCase, 'subTest'),
                      'unittest.TestCase.subTest not present.')
     def test_unittest_subTest_pass(self):
@@ -517,6 +623,7 @@
         suite = unittest.TestSuite()
         suite.addTest(self.DummyTest('test_fail'))
         suite.addTest(self.DummyTest('test_pass'))
+        suite.addTest(self.DummyTest('test_output_stdout_and_stderr'))
         suite.properties = dict(key='value')
         outdir = BytesIO()
         runner = xmlrunner.XMLTestRunner(
@@ -575,6 +682,15 @@
         suite.addTest(self.DummyTest('test_pass'))
         runner.run(suite)
 
+    def test_xmlrunner_stream_empty_testsuite(self):
+        stream = self.stream
+        output = BytesIO()
+        runner = xmlrunner.XMLTestRunner(
+            stream=stream, output=output, verbosity=self.verbosity,
+            **self.runner_kwargs)
+        suite = unittest.TestSuite()
+        runner.run(suite)
+
     def test_xmlrunner_output_subdir(self):
         stream = self.stream
         output = os.path.join(self.outdir, 'subdir')
@@ -689,6 +805,25 @@
         self.assertIn('should be printed', r[0].getvalue())
         self.assertNotIn('should be printed', r[1].getvalue())
 
+    def test_partialmethod(self):
+        try:
+            from functools import partialmethod
+        except ImportError:
+            raise unittest.SkipTest('functools.partialmethod is not 
available.')
+        def test_partialmethod(test):
+            pass
+        class TestWithPartialmethod(unittest.TestCase):
+            pass
+        setattr(
+            TestWithPartialmethod,
+            'test_partialmethod',
+            partialmethod(test_partialmethod),
+        )
+        suite = unittest.TestSuite()
+        suite.addTest(TestWithPartialmethod('test_partialmethod'))
+        self._test_xmlrunner(suite)
+
+
 
 class DuplicateWriterTestCase(unittest.TestCase):
     def setUp(self):
@@ -735,7 +870,6 @@
         self.assertEqual(wrote, len(self.getSecondContent()))
 
 
[email protected](sys.version_info[0] < 3, 'Python 3 required')
 class XMLProgramTestCase(unittest.TestCase):
     @mock.patch('sys.argv', ['xmlrunner', '-o', 'flaf'])
     @mock.patch('xmlrunner.runner.XMLTestRunner')
@@ -744,16 +878,39 @@
         xmlrunner.runner.XMLTestProgram()
 
         kwargs = dict(
-            buffer=False,
-            failfast=False,
-            verbosity=1,
-            warnings='default',
+            buffer=mock.ANY,
+            failfast=mock.ANY,
+            verbosity=mock.ANY,
+            warnings=mock.ANY,
             output='flaf',
         )
 
         if sys.version_info[:2] > (3, 4):
-            kwargs.update(tb_locals=False)
+            kwargs.update(tb_locals=mock.ANY)
 
         testrunner.assert_called_once_with(**kwargs)
+        exiter.assert_called_once_with(False)
 
+    @mock.patch('sys.argv', ['xmlrunner', '--output-file', 'test.xml'])
+    @mock.patch('xmlrunner.runner.open')
+    @mock.patch('xmlrunner.runner.XMLTestRunner')
+    @mock.patch('sys.exit')
+    def test_xmlrunner_output_file(self, exiter, testrunner, opener):
+        xmlrunner.runner.XMLTestProgram()
+        opener.assert_called_once_with('test.xml', 'wb')
+        open_file = opener()
+        open_file.close.assert_called_with()
+
+        kwargs = dict(
+            buffer=mock.ANY,
+            failfast=mock.ANY,
+            verbosity=mock.ANY,
+            warnings=mock.ANY,
+            output=open_file,
+        )
+
+        if sys.version_info[:2] > (3, 4):
+            kwargs.update(tb_locals=mock.ANY)
+
+        testrunner.assert_called_once_with(**kwargs)
         exiter.assert_called_once_with(False)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/unittest-xml-reporting-2.2.1/tox.ini 
new/unittest-xml-reporting-2.5.1/tox.ini
--- old/unittest-xml-reporting-2.2.1/tox.ini    2019-01-09 07:52:55.000000000 
+0100
+++ new/unittest-xml-reporting-2.5.1/tox.ini    2019-03-25 06:05:10.000000000 
+0100
@@ -1,29 +1,38 @@
+[pytest]
+python_files = *_test.py test*.py
+testpaths = tests
+norecursedirs = tests/django_example
+
 [tox]
-envlist = begin,py{27,py,py3,34,35,36,37},py27-django{lts,curr},end,quality
+envlist = begin,py{27,py,py3,35,36,37},pytest,py27-django{lts,curr},end,quality
 
 [tox:travis]
 2.7 = begin,py27,py27-django{lts,curr},end,quality
-3.4 = py34
 3.5 = py35
 3.6 = py36
-3.7 = py37
+3.7 = py37,pytest
 
 [testenv]
 deps =
     coverage
     codecov>=1.4.0
     coveralls
-    djangolts: django>=1.8.8,<1.9.0
+    djangolts,pytest: django>=1.8.8,<1.9.0
     djangocurr: django>=1.9.1
+    pytest: pytest
     lxml>=3.6.0
     mock
 commands =
     coverage run --append setup.py test
     coverage report --omit='.tox/*'
+    python -m xmlrunner discover -p test_xmlrunner_output
     codecov -e TOXENV
     -coveralls
 passenv = CI TRAVIS_BUILD_ID TRAVIS TRAVIS_BRANCH TRAVIS_JOB_NUMBER 
TRAVIS_PULL_REQUEST TRAVIS_JOB_ID TRAVIS_REPO_SLUG TRAVIS_COMMIT
 
+[testenv:pytest]
+commands = pytest
+
 [testenv:begin]
 commands = coverage erase
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/unittest-xml-reporting-2.2.1/xmlrunner/result.py 
new/unittest-xml-reporting-2.5.1/xmlrunner/result.py
--- old/unittest-xml-reporting-2.2.1/xmlrunner/result.py        2019-01-09 
07:52:55.000000000 +0100
+++ new/unittest-xml-reporting-2.5.1/xmlrunner/result.py        2019-03-25 
06:05:10.000000000 +0100
@@ -1,4 +1,5 @@
 
+import inspect
 import io
 import os
 import sys
@@ -10,6 +11,9 @@
 from os import path
 from six.moves import StringIO
 
+# use direct import to bypass freezegun
+from time import time
+
 from .unittest import TestResult, _TextTestResult, failfast
 
 
@@ -46,18 +50,7 @@
 STDERR_LINE = '\nStderr:\n%s'
 
 
-def xml_safe_unicode(base, encoding='utf-8'):
-    """Return a unicode string containing only valid XML characters.
-
-    encoding - if base is a byte string it is first decoded to unicode
-        using this encoding.
-    """
-    if isinstance(base, six.binary_type):
-        base = base.decode(encoding)
-    return INVALID_XML_1_0_UNICODE_RE.sub('', base)
-
-
-def to_unicode(data):
+def _to_unicode(data):
     """Returns unicode in Python2 and str in Python3"""
     if six.PY3:
         return six.text_type(data)
@@ -68,8 +61,17 @@
         return repr(data).decode('utf8', 'replace')
 
 
-def safe_unicode(data, encoding=None):
-    return xml_safe_unicode(to_unicode(data), encoding)
+def safe_unicode(data, encoding='utf8'):
+    """Return a unicode string containing only valid XML characters.
+
+    encoding - if data is a byte string it is first decoded to unicode
+        using this encoding.
+    """
+    data = _to_unicode(data)
+    if isinstance(data, six.binary_type):
+        # e.g. IronPython, see #182
+        data = data.decode(encoding)
+    return INVALID_XML_1_0_UNICODE_RE.sub('', data)
 
 
 def testcase_name(test_method):
@@ -132,7 +134,14 @@
     # Possible test outcomes
     (SUCCESS, FAILURE, ERROR, SKIP) = range(4)
 
-    def __init__(self, test_result, test_method, outcome=SUCCESS, err=None, 
subTest=None):
+    OUTCOME_ELEMENTS = {
+        SUCCESS: None,
+        FAILURE: 'failure',
+        ERROR: 'error',
+        SKIP: 'skipped',
+    }
+
+    def __init__(self, test_result, test_method, outcome=SUCCESS, err=None, 
subTest=None, filename=None, lineno=None):
         self.test_result = test_result
         self.outcome = outcome
         self.elapsed_time = 0
@@ -156,10 +165,13 @@
 
         self.test_name = testcase_name(test_method)
         self.test_id = test_method.id()
-        self.subDescription = None
+
         if subTest:
             self.test_id = subTest.id()
-            self.subDescription = subTest._subDescription()
+            self.test_description = self.test_result.getDescription(subTest)
+
+        self.filename = filename
+        self.lineno = lineno
 
     def id(self):
         return self.test_id
@@ -172,17 +184,6 @@
         timestamp = datetime.datetime.fromtimestamp(self.test_result.stop_time)
         self.timestamp = timestamp.replace(microsecond=0).isoformat()
 
-    def get_description(self):
-        """
-        Return a text representation of the test method.
-        """
-        description = self.test_description
-
-        if self.subDescription is not None:
-            description += ' ' + self.subDescription
-
-        return description
-
     def get_error_info(self):
         """
         Return a text representation of an exception thrown by a test
@@ -210,6 +211,8 @@
         self.callback = None
         self.elapsed_times = elapsed_times
         self.properties = properties  # junit testsuite properties
+        self.filename = None
+        self.lineno = None
         if infoclass is None:
             self.infoclass = _TestInfo
         else:
@@ -221,6 +224,8 @@
         Appends a `infoclass` to the given target list and sets a callback
         method to be called by stopTest method.
         """
+        test_info.filename = self.filename
+        test_info.lineno = self.lineno
         target_list.append(test_info)
 
         def callback():
@@ -249,9 +254,27 @@
         """
         Called before execute each test method.
         """
-        self.start_time = time.time()
+        self.start_time = time()
         TestResult.startTest(self, test)
 
+        try:
+            if getattr(test, '_dt_test', None) is not None:
+                # doctest.DocTestCase
+                self.filename = test._dt_test.filename
+                self.lineno = test._dt_test.lineno
+            else:
+                # regular unittest.TestCase?
+                test_method = getattr(test, test._testMethodName)
+                test_class = type(test)
+                # Note: inspect can get confused with decorators, so use class.
+                self.filename = inspect.getsourcefile(test_class)
+                # Handle partial and partialmethod objects.
+                test_method = getattr(test_method, 'func', test_method)
+                _, self.lineno = inspect.getsourcelines(test_method)
+        except (AttributeError, TypeError):
+            # issue #188, #189, some frameworks can make test method opaque.
+            pass
+
         if self.showAll:
             self.stream.write('  ' + self.getDescription(test))
             self.stream.write(" ... ")
@@ -296,7 +319,7 @@
         # self._stderr_data = sys.stderr.getvalue()
 
         _TextTestResult.stopTest(self, test)
-        self.stop_time = time.time()
+        self.stop_time = time()
 
         if self.callback and callable(self.callback):
             self.callback()
@@ -308,7 +331,7 @@
         """
         self._save_output_data()
         self._prepare_callback(
-            self.infoclass(self, test), self.successes, 'OK', '.'
+            self.infoclass(self, test), self.successes, 'ok', '.'
         )
 
     @failfast
@@ -375,8 +398,10 @@
         self._save_output_data()
         testinfo = self.infoclass(
             self, test, self.infoclass.SKIP, reason)
+        testinfo.test_exception_name = 'skip'
+        testinfo.test_exception_message = reason
         self.skipped.append((testinfo, reason))
-        self._prepare_callback(testinfo, [], 'SKIP', 'S')
+        self._prepare_callback(testinfo, [], 'skip', 's')
 
     def addExpectedFailure(self, test, err):
         """
@@ -384,12 +409,12 @@
         """
         self._save_output_data()
 
-        testinfo = self.infoclass(self, test, self.infoclass.ERROR, err)
-        testinfo.test_exception_name = 'ExpectedFailure'
-        testinfo.test_exception_message = 'EXPECTED FAILURE: 
{}'.format(testinfo.test_exception_message)
+        testinfo = self.infoclass(self, test, self.infoclass.SKIP, err)
+        testinfo.test_exception_name = 'XFAIL'
+        testinfo.test_exception_message = 'expected failure: 
{}'.format(testinfo.test_exception_message)
 
         self.expectedFailures.append((testinfo, self._exc_info_to_string(err, 
test)))
-        self._prepare_callback(testinfo, [], 'EXPECTED FAILURE', 'X')
+        self._prepare_callback(testinfo, [], 'expected failure', 'x')
 
     @failfast
     def addUnexpectedSuccess(self, test):
@@ -402,11 +427,11 @@
         testinfo.outcome = self.infoclass.ERROR
         # But since we want to have error outcome, we need to provide 
additional fields:
         testinfo.test_exception_name = 'UnexpectedSuccess'
-        testinfo.test_exception_message = ('UNEXPECTED SUCCESS: This test was 
marked as expected failure but passed, '
+        testinfo.test_exception_message = ('Unexpected success: This test was 
marked as expected failure but passed, '
                                            'please review it')
 
-        self.unexpectedSuccesses.append(testinfo)
-        self._prepare_callback(testinfo, [], 'UNEXPECTED SUCCESS', 'U')
+        self.unexpectedSuccesses.append((testinfo, 'unexpected success'))
+        self._prepare_callback(testinfo, [], 'unexpected success', 'u')
 
     def printErrorList(self, flavour, errors):
         """
@@ -416,7 +441,7 @@
             self.stream.writeln(self.separator1)
             self.stream.writeln(
                 '%s [%.3fs]: %s' % (flavour, test_info.elapsed_time,
-                                    test_info.get_description())
+                                    test_info.test_description)
             )
             self.stream.writeln(self.separator2)
             self.stream.writeln('%s' % test_info.get_error_info())
@@ -431,7 +456,7 @@
         tests_by_testcase = {}
 
         for tests in (self.successes, self.failures, self.errors,
-                      self.skipped):
+                      self.skipped, self.expectedFailures, 
self.unexpectedSuccesses):
             for test_info in tests:
                 if isinstance(test_info, tuple):
                     # This is a skipped, error or a failure test case
@@ -462,9 +487,12 @@
         """
         testsuite = xml_document.createElement('testsuite')
         parentElement.appendChild(testsuite)
+        module_name = suite_name.rpartition('.')[0]
+        file_name = module_name.replace('.', '/') + '.py'
 
         testsuite.setAttribute('name', suite_name)
         testsuite.setAttribute('tests', str(len(tests)))
+        testsuite.setAttribute('file', file_name)
 
         testsuite.setAttribute(
             'time', '%.3f' % sum(map(lambda e: e.elapsed_time, tests))
@@ -488,28 +516,6 @@
         for test in tests:
             _XMLTestResult._report_testcase(test, testsuite, xml_document)
 
-        systemout = xml_document.createElement('system-out')
-        testsuite.appendChild(systemout)
-
-        stdout = StringIO()
-        for test in tests:
-            # Merge the stdout from the tests in a class
-            if test.stdout is not None:
-                stdout.write(test.stdout)
-        _XMLTestResult._createCDATAsections(
-            xml_document, systemout, stdout.getvalue())
-
-        systemerr = xml_document.createElement('system-err')
-        testsuite.appendChild(systemerr)
-
-        stderr = StringIO()
-        for test in tests:
-            # Merge the stderr from the tests in a class
-            if test.stderr is not None:
-                stderr.write(test.stderr)
-        _XMLTestResult._createCDATAsections(
-            xml_document, systemerr, stderr.getvalue())
-
         return testsuite
 
     _report_testsuite = staticmethod(_report_testsuite)
@@ -552,25 +558,45 @@
         testcase.setAttribute('time', '%.3f' % test_result.elapsed_time)
         testcase.setAttribute('timestamp', test_result.timestamp)
 
-        if (test_result.outcome != test_result.SUCCESS):
-            elem_name = ('failure', 'error', 'skipped')[test_result.outcome-1]
-            failure = xml_document.createElement(elem_name)
-            testcase.appendChild(failure)
-            if test_result.outcome != test_result.SKIP:
-                failure.setAttribute(
-                    'type',
-                    test_result.test_exception_name
-                )
-                failure.setAttribute(
-                    'message',
-                    test_result.test_exception_message
-                )
+        if test_result.filename is not None:
+            # Try to make filename relative to current directory.
+            filename = os.path.relpath(test_result.filename)
+            filename = test_result.filename if filename.startswith('../') else 
filename
+            testcase.setAttribute('file', filename)
+
+        if test_result.lineno is not None:
+            testcase.setAttribute('line', str(test_result.lineno))
+
+        result_elem_name = test_result.OUTCOME_ELEMENTS[test_result.outcome]
+
+        if result_elem_name is not None:
+            result_elem = xml_document.createElement(result_elem_name)
+            testcase.appendChild(result_elem)
+
+            result_elem.setAttribute(
+                'type',
+                test_result.test_exception_name
+            )
+            result_elem.setAttribute(
+                'message',
+                test_result.test_exception_message
+            )
+            if test_result.get_error_info():
                 error_info = safe_unicode(test_result.get_error_info())
                 _XMLTestResult._createCDATAsections(
-                    xml_document, failure, error_info)
-            else:
-                failure.setAttribute('type', 'skip')
-                failure.setAttribute('message', 
test_result.test_exception_message)
+                    xml_document, result_elem, error_info)
+
+        if test_result.stdout is not None:
+            systemout = xml_document.createElement('system-out')
+            testcase.appendChild(systemout)
+            _XMLTestResult._createCDATAsections(
+                xml_document, systemout, test_result.stdout)
+
+        if test_result.stderr is not None:
+            systemout = xml_document.createElement('system-err')
+            testcase.appendChild(systemout)
+            _XMLTestResult._createCDATAsections(
+                xml_document, systemout, test_result.stderr)
 
     _report_testcase = staticmethod(_report_testcase)
 
@@ -607,22 +633,27 @@
             testsuite = _XMLTestResult._report_testsuite(
                 suite_name, tests, doc, parentElement, self.properties
             )
-            xml_content = doc.toprettyxml(
-                indent='\t',
-                encoding=test_runner.encoding
-            )
 
             if outputHandledAsString:
+                xml_content = doc.toprettyxml(
+                    indent='\t',
+                    encoding=test_runner.encoding
+                )
                 filename = path.join(
                     test_runner.output,
                     'TEST-%s.xml' % suite_name)
                 with open(filename, 'wb') as report_file:
                     report_file.write(xml_content)
 
-                self.stream.writeln('Generated XML report: 
{}'.format(filename))
+                if self.showAll:
+                    self.stream.writeln('Generated XML report: 
{}'.format(filename))
 
         if not outputHandledAsString:
             # Assume that test_runner.output is a stream
+            xml_content = doc.toprettyxml(
+                indent='\t',
+                encoding=test_runner.encoding
+            )
             test_runner.output.write(xml_content)
 
     def _exc_info_to_string(self, err, test):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/unittest-xml-reporting-2.2.1/xmlrunner/runner.py 
new/unittest-xml-reporting-2.5.1/xmlrunner/runner.py
--- old/unittest-xml-reporting-2.2.1/xmlrunner/runner.py        2019-01-09 
07:52:55.000000000 +0100
+++ new/unittest-xml-reporting-2.5.1/xmlrunner/runner.py        2019-03-25 
06:05:10.000000000 +0100
@@ -1,7 +1,10 @@
 
+import argparse
 import sys
 import time
 
+import six
+
 from .unittest import TextTestRunner, TestProgram
 from .result import _XMLTestResult
 
@@ -16,8 +19,10 @@
     """
     def __init__(self, output='.', outsuffix=None, 
                  elapsed_times=True, encoding=UTF8,
-                 resultclass=None,
+                 resultclass=None, warnings=None,
                  **kwargs):
+        if six.PY3:
+            kwargs['warnings'] = warnings
         super(XMLTestRunner, self).__init__(**kwargs)
         self.output = output
         self.encoding = encoding
@@ -116,31 +121,65 @@
 
 
 class XMLTestProgram(TestProgram):
-    output = None
 
     def __init__(self, *args, **kwargs):
         kwargs.setdefault('testRunner', XMLTestRunner)
+        self.warnings = None  # python2 fix
+        self._parseKnownArgs(kwargs)
         super(XMLTestProgram, self).__init__(*args, **kwargs)
 
+    def _parseKnownArgs(self, kwargs):
+        argv = kwargs.get('argv')
+        if argv is None:
+            argv = sys.argv
+
+        # python2 argparse fix
+        parser = argparse.ArgumentParser(prog='xmlrunner')
+        group = parser.add_mutually_exclusive_group()
+        group.add_argument(
+            '-o', '--output', metavar='DIR',
+            help='Directory for storing XML reports (\'.\' default)')
+        group.add_argument(
+            '--output-file', metavar='FILENAME',
+            help='Filename for storing XML report')
+        namespace, argv = parser.parse_known_args(argv)
+        self.output = namespace.output
+        self.output_file = namespace.output_file
+        kwargs['argv'] = argv
+
     def _initArgParsers(self):
+        # this code path is only called in python3 (optparse vs argparse)
         super(XMLTestProgram, self)._initArgParsers()
 
         for parser in (self._main_parser, self._discovery_parser):
-            parser.add_argument('-o', '--output', metavar='DIR',
-                                help='Directory for storing XML reports '
-                                "('.' default)")
+            group = parser.add_mutually_exclusive_group()
+            group.add_argument(
+                '-o', '--output', metavar='DIR', nargs=1,
+                help='Directory for storing XML reports (\'.\' default)')
+            group.add_argument(
+                '--output-file', metavar='FILENAME', nargs=1,
+                help='Filename for storing XML report')
 
     def runTests(self):
-        if self.output is not None:
-            kwargs = dict(verbosity=self.verbosity,
-                          failfast=self.failfast,
-                          buffer=self.buffer,
-                          warnings=self.warnings,
-                          output=self.output)
+        kwargs = dict(
+            verbosity=self.verbosity,
+            failfast=self.failfast,
+            buffer=self.buffer,
+            warnings=self.warnings,
+        )
+        if sys.version_info[:2] > (3, 4):
+            kwargs.update(tb_locals=self.tb_locals)
 
-            if sys.version_info[:2] > (3, 4):
-                kwargs.update(tb_locals=self.tb_locals)
+        output_file = None
+        try:
+            if self.output_file is not None:
+                output_file = open(self.output_file, 'wb')
+                kwargs.update(output=output_file)
+            elif self.output is not None:
+                kwargs.update(output=self.output)
 
             self.testRunner = self.testRunner(**kwargs)
-
-        super(XMLTestProgram, self).runTests()
+            super(XMLTestProgram, self).runTests()
+        finally:
+            if output_file is not None:
+                output_file.close()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/unittest-xml-reporting-2.2.1/xmlrunner/version.py 
new/unittest-xml-reporting-2.5.1/xmlrunner/version.py
--- old/unittest-xml-reporting-2.2.1/xmlrunner/version.py       2019-01-09 
07:52:55.000000000 +0100
+++ new/unittest-xml-reporting-2.5.1/xmlrunner/version.py       2019-03-25 
06:05:10.000000000 +0100
@@ -1,2 +1,2 @@
 
-__version__ = '2.2.1'
+__version__ = '2.5.1'


Reply via email to