@@ -1314,6 +1314,148 @@ def test_empty_detail_status_correct(self):
13141314 log ._events ,
13151315 )
13161316
1317+ def test_subtest_failure (self ):
1318+ # Test that addSubTest collects failures and reports them in stopTest
1319+
1320+ # Create a mock subtest that mimics unittest's _SubTest
1321+ class MockSubTest :
1322+ def __init__ (self , parent , description ):
1323+ self ._parent = parent
1324+ self ._description = description
1325+
1326+ def id (self ):
1327+ return f"{ self ._parent .id ()} { self ._description } "
1328+
1329+ def _subDescription (self ):
1330+ return self ._description
1331+
1332+ log = LoggingStreamResult ()
1333+ result = ExtendedToStreamDecorator (log )
1334+ result .startTestRun ()
1335+ now = datetime .datetime .now (utc )
1336+ result .time (now )
1337+ result .startTest (self )
1338+
1339+ # Simulate a failing subtest
1340+ subtest = MockSubTest (self , "(i=1)" )
1341+ try :
1342+ raise AssertionError ("subtest failed" )
1343+ except AssertionError :
1344+ err = sys .exc_info ()
1345+ result .addSubTest (self , subtest , err )
1346+
1347+ result .stopTest (self )
1348+ result .stopTestRun ()
1349+
1350+ # Filter events to check structure
1351+ test_id = self .id ()
1352+ events = log ._events
1353+
1354+ # Should have: startTestRun, inprogress, traceback attachment, fail, stopTestRun
1355+ self .assertEqual (events [0 ], ("startTestRun" ,))
1356+ self .assertEqual (events [1 ].test_id , test_id )
1357+ self .assertEqual (events [1 ].test_status , "inprogress" )
1358+
1359+ # The traceback attachment for the subtest
1360+ self .assertEqual (events [2 ].test_id , test_id )
1361+ self .assertEqual (events [2 ].file_name , "traceback (i=1)" )
1362+ self .assertIn (b"AssertionError: subtest failed" , events [2 ].file_bytes )
1363+
1364+ # The final fail status
1365+ self .assertEqual (events [3 ].test_id , test_id )
1366+ self .assertEqual (events [3 ].test_status , "fail" )
1367+
1368+ self .assertEqual (events [4 ], ("stopTestRun" ,))
1369+
1370+ def test_subtest_success_no_events (self ):
1371+ # Test that successful subtests don't generate events
1372+ class MockSubTest :
1373+ def __init__ (self , parent , description ):
1374+ self ._parent = parent
1375+ self ._description = description
1376+
1377+ def id (self ):
1378+ return f"{ self ._parent .id ()} { self ._description } "
1379+
1380+ def _subDescription (self ):
1381+ return self ._description
1382+
1383+ log = LoggingStreamResult ()
1384+ result = ExtendedToStreamDecorator (log )
1385+ result .startTestRun ()
1386+ now = datetime .datetime .now (utc )
1387+ result .time (now )
1388+ result .startTest (self )
1389+
1390+ # Simulate a passing subtest (err=None)
1391+ subtest = MockSubTest (self , "(i=0)" )
1392+ result .addSubTest (self , subtest , None )
1393+
1394+ # Simulate the success callback that unittest sends when all subtests pass
1395+ result .addSuccess (self )
1396+ result .stopTest (self )
1397+ result .stopTestRun ()
1398+
1399+ test_id = self .id ()
1400+ events = log ._events
1401+
1402+ # Should have: startTestRun, inprogress, success, stopTestRun
1403+ # No subtest-specific events since it passed
1404+ self .assertEqual (events [0 ], ("startTestRun" ,))
1405+ self .assertEqual (events [1 ].test_id , test_id )
1406+ self .assertEqual (events [1 ].test_status , "inprogress" )
1407+ self .assertEqual (events [2 ].test_id , test_id )
1408+ self .assertEqual (events [2 ].test_status , "success" )
1409+ self .assertEqual (events [3 ], ("stopTestRun" ,))
1410+
1411+ def test_multiple_subtest_failures (self ):
1412+ # Test that multiple subtest failures are all reported
1413+ class MockSubTest :
1414+ def __init__ (self , parent , description ):
1415+ self ._parent = parent
1416+ self ._description = description
1417+
1418+ def id (self ):
1419+ return f"{ self ._parent .id ()} { self ._description } "
1420+
1421+ def _subDescription (self ):
1422+ return self ._description
1423+
1424+ log = LoggingStreamResult ()
1425+ result = ExtendedToStreamDecorator (log )
1426+ result .startTestRun ()
1427+ now = datetime .datetime .now (utc )
1428+ result .time (now )
1429+ result .startTest (self )
1430+
1431+ # Simulate two failing subtests
1432+ for i in [1 , 2 ]:
1433+ subtest = MockSubTest (self , f"(i={ i } )" )
1434+ try :
1435+ raise AssertionError (f"subtest { i } failed" )
1436+ except AssertionError :
1437+ err = sys .exc_info ()
1438+ result .addSubTest (self , subtest , err )
1439+
1440+ result .stopTest (self )
1441+ result .stopTestRun ()
1442+
1443+ events = log ._events
1444+
1445+ # Should have: startTestRun, inprogress, 2x traceback, fail, stopTestRun
1446+ self .assertEqual (events [0 ], ("startTestRun" ,))
1447+ self .assertEqual (events [1 ].test_status , "inprogress" )
1448+
1449+ # Two traceback attachments
1450+ self .assertEqual (events [2 ].file_name , "traceback (i=1)" )
1451+ self .assertIn (b"subtest 1 failed" , events [2 ].file_bytes )
1452+ self .assertEqual (events [3 ].file_name , "traceback (i=2)" )
1453+ self .assertIn (b"subtest 2 failed" , events [3 ].file_bytes )
1454+
1455+ # Final fail status
1456+ self .assertEqual (events [4 ].test_status , "fail" )
1457+ self .assertEqual (events [5 ], ("stopTestRun" ,))
1458+
13171459
13181460class TestResourcedToStreamDecorator (TestCase ):
13191461 def setUp (self ):
0 commit comments