diff --git a/tests/testsuite.py b/tests/testsuite.py index 68616e3..99b5e7c 100755 --- a/tests/testsuite.py +++ b/tests/testsuite.py @@ -240,8 +240,18 @@ def _test_xmlrunner(self, suite, runner=None, outdir=None): runner.run(suite) if isinstance(outdir, BytesIO): self.assertTrue(outdir.getvalue()) + xml_content = outdir.getvalue() else: - self.assertEqual(1, len(glob(os.path.join(outdir, '*xml')))) + xml_files = glob(os.path.join(outdir, '*xml')) + self.assertEqual(1, len(xml_files)) + with open(xml_files[0], 'rb') as f: + xml_content = f.read() + + doc = minidom.parseString(xml_content) + for testcase in doc.getElementsByTagName('testcase'): + elapsed = float(testcase.getAttribute('time')) + self.assertGreaterEqual(elapsed, 0.0) + return runner def test_basic_unittest_constructs(self): @@ -903,6 +913,15 @@ class TestWithPartialmethod(unittest.TestCase): suite.addTest(TestWithPartialmethod('test_partialmethod')) self._test_xmlrunner(suite) + def test_elapsed_time_with_docstring(self): + class DocstringTest(unittest.TestCase): + def test_with_doc(self): + """This test has a docstring.""" + pass + + suite = unittest.TestSuite() + suite.addTest(DocstringTest('test_with_doc')) + self._test_xmlrunner(suite) class DuplicateWriterTestCase(unittest.TestCase): diff --git a/xmlrunner/result.py b/xmlrunner/result.py index a759555..156220d 100644 --- a/xmlrunner/result.py +++ b/xmlrunner/result.py @@ -167,10 +167,12 @@ def __init__(self, test_result, test_method, outcome=SUCCESS, err=None, subTest= self.test_name = testcase_name(test_method) self.test_id = test_method.id() + self.test_str = str(test_method) if subTest: self.test_id = subTest.id() self.test_description = self.test_result.getDescription(subTest) + self.test_str = str(subTest) self.filename = filename self.lineno = lineno @@ -179,11 +181,25 @@ def __init__(self, test_result, test_method, outcome=SUCCESS, err=None, subTest= def id(self): return self.test_id + def _get_elapsed_time(self): + """Return elapsed time from addDuration (3.12+) or start/stop delta. + + In parallel test runners like Django's, startTest/stopTest events are + replayed in the parent process, making stop_time - start_time near-zero. + Python 3.12's addDuration carries the real worker-measured elapsed time + correctly allowing us to prevent this issue for 3.12+. + """ + collected = getattr(self.test_result, 'collectedDurations', []) + for test_repr, elapsed in reversed(collected): + if test_repr == self.test_str: + return elapsed + + return self.test_result.stop_time - self.test_result.start_time + def test_finished(self): """Save info that can only be calculated once a test has run. """ - self.elapsed_time = \ - self.test_result.stop_time - self.test_result.start_time + self.elapsed_time = self._get_elapsed_time() timestamp = datetime.datetime.fromtimestamp(self.test_result.stop_time) self.timestamp = timestamp.replace(microsecond=0).isoformat()