@@ -250,7 +250,8 @@ def test_construct_xml(self):
250250 assert case [0 ].attrib ["name" ] == "case1"
251251
252252 def test_add_testsuite_keeps_live_reference (self ):
253- """Regression test for #180."""
253+ # issue 180: add_testsuite used to deepcopy, so cases added afterwards
254+ # went nowhere
254255 report = JUnitXml ()
255256 suite = TestSuite ("suite" )
256257 report .add_testsuite (suite )
@@ -271,15 +272,15 @@ def test_add_testsuite_keeps_live_reference(self):
271272 assert report .failures == 2
272273
273274 def test_add_testsuite_merge_keeps_live_reference (self ):
274- """Regression test for # 180, merge branch of ``add_testsuite``."""
275+ # issue 180, this time down the merge branch
275276 report = JUnitXml ()
276277 suite = TestSuite ("suite" )
277278 report .add_testsuite (suite )
278- # An equal suite is merged into the existing one instead of appended.
279+ # equal suites get merged, not appended
279280 other = TestSuite ("suite" )
280281 other .add_testcase (TestCase ("case 1" ))
281282 report .add_testsuite (other )
282- # The caller's original suite object still points into the report.
283+ # suite is still the one in the report
283284 suite .add_testcase (TestCase ("case 2" ))
284285 report .update_statistics ()
285286
@@ -402,6 +403,90 @@ def test_iadd_two_different_suites(self):
402403 assert isinstance (suite1 , JUnitXml )
403404 assert len (list (iter (suite1 ))) == 2
404405
406+ def _xml_with_stats (self , name , passes , failures ):
407+ # one suite, counts already written out
408+ xml = JUnitXml ()
409+ suite = TestSuite (name )
410+ for i in range (passes ):
411+ suite .add_testcase (TestCase (f"{ name } _pass{ i } " ))
412+ for i in range (failures ):
413+ case = TestCase (f"{ name } _fail{ i } " )
414+ case .result = [Failure ("boom" )]
415+ suite .add_testcase (case )
416+ xml .add_testsuite (suite )
417+ xml .update_statistics ()
418+ return xml
419+
420+ def test_iadd_refreshes_stale_statistics (self ):
421+ # issue 177: IntAttr only counts when the attr is missing, so += has to
422+ # refresh it by hand
423+ a = self ._xml_with_stats ("A" , 5 , 2 )
424+ b = self ._xml_with_stats ("B" , 3 , 1 )
425+ assert (a .tests , a .failures ) == (7 , 2 )
426+
427+ a += b
428+
429+ assert a .tests == 11
430+ assert a .failures == 3
431+ assert sum (1 for suite in a for _ in suite ) == 11
432+
433+ def test_iadd_self_merge_refreshes_statistics (self ):
434+ # issue 177, x += x
435+ xml = self ._xml_with_stats ("A" , 4 , 0 )
436+ xml += xml
437+ assert sum (1 for suite in xml for _ in suite ) == 8
438+ assert xml .tests == 8
439+
440+ def test_add_and_iadd_agree (self ):
441+ # issue 177: + and += must give the same thing
442+ added = self ._xml_with_stats ("A" , 5 , 2 ) + self ._xml_with_stats ("B" , 3 , 1 )
443+ inplace = self ._xml_with_stats ("A" , 5 , 2 )
444+ inplace += self ._xml_with_stats ("B" , 3 , 1 )
445+
446+ assert (added .tests , added .failures ) == (inplace .tests , inplace .failures )
447+ assert len (added ) == len (inplace )
448+ assert [suite .name for suite in added ] == [suite .name for suite in inplace ]
449+
450+ def test_add_bare_testsuite_matches_iadd (self ):
451+ # += takes a bare testsuite, + used to iterate it as if it were a
452+ # testsuites and drop the cases under the root
453+ suite = TestSuite ("B" )
454+ suite .add_testcase (TestCase ("b0" ))
455+ suite .add_testcase (TestCase ("b1" ))
456+
457+ added = self ._xml_with_stats ("A" , 1 , 0 ) + suite
458+ inplace = self ._xml_with_stats ("A" , 1 , 0 )
459+ inplace += deepcopy (suite )
460+
461+ assert len (added ) == 2
462+ assert added .tests == 3
463+ assert (added .tests , len (added )) == (inplace .tests , len (inplace ))
464+ # no testcase directly under testsuites
465+ assert added ._elem .find ("testcase" ) is None
466+
467+ def test_add_iterable_of_suites (self ):
468+ # + never looked at the operand's element, so a plain list works.
469+ # += takes one now too.
470+ suites = [TestSuite ("B" ), TestSuite ("C" )]
471+ for i , suite in enumerate (suites ):
472+ suite .add_testcase (TestCase (f"case{ i } " ))
473+
474+ added = self ._xml_with_stats ("A" , 2 , 0 ) + suites
475+ inplace = self ._xml_with_stats ("A" , 2 , 0 )
476+ inplace += deepcopy (suites )
477+
478+ assert len (added ) == 3
479+ assert added .tests == 4
480+ assert (len (added ), added .tests ) == (len (inplace ), inplace .tests )
481+
482+ def test_add_generator_of_suites (self ):
483+ # NOTE: single pass, don't iterate the operand twice
484+ suite = TestSuite ("B" )
485+ suite .add_testcase (TestCase ("b0" ))
486+ result = self ._xml_with_stats ("A" , 2 , 0 ) + (s for s in [suite ])
487+ assert len (result ) == 2
488+ assert result .tests == 3
489+
405490 def test_xml_statistics (self ):
406491 result1 = JUnitXml ()
407492 suite1 = TestSuite ()
0 commit comments