@@ -197,6 +197,13 @@ def test_detects_connection_refused(self):
197197 )
198198 )
199199
200+ def test_detects_about_neterror_connection_failure (self ):
201+ self .assertTrue (
202+ _is_transient_failure (
203+ "Selenium loaded about:neterror?e=connectionFailure during test run"
204+ )
205+ )
206+
200207 def test_normal_test_failure_not_transient (self ):
201208 self .assertFalse (_is_transient_failure ("FAIL: test_login" ))
202209
@@ -252,15 +259,55 @@ def test_single_block_no_job_headers(self):
252259 self .assertFalse (tests_failed )
253260 self .assertFalse (transient )
254261
255- def test_transient_only_true_when_all_transient (self ):
262+ def test_pure_transient_error_allows_retry (self ):
263+ """Ensure a purely transient error correctly flags for a retry."""
256264 content = (
257- "===== JOB 100 =====\n "
258- "marionette.errors.MarionetteException: connection lost\n "
259- "===== JOB 200 =====\n "
260- "NS_ERROR_ABORT in browser\n "
265+ "===== JOB 1 =====\n "
266+ "Setting up Selenium...\n "
267+ "selenium.common.exceptions.InvalidSessionIdException: Message: Session is not active\n "
261268 )
262- _ , _ , transient = process_error_logs (content )
263- self .assertTrue (transient )
269+ text , tests_failed , transient_only = process_error_logs (content )
270+ self .assertFalse (tests_failed )
271+ self .assertTrue (transient_only )
272+
273+ def test_transient_forgives_generic_traceback (self ):
274+ """Ensure transient crashes with generic tracebacks don't falsely trigger test failures."""
275+ content = (
276+ "===== JOB 2 =====\n "
277+ "Traceback (most recent call last):\n "
278+ " File 'urllib3/connectionpool.py', line 703\n "
279+ "ERROR: Could not install packages due to an OSError: HTTPSConnectionPool\n "
280+ "Network is unreachable\n "
281+ )
282+ text , tests_failed , transient_only = process_error_logs (content )
283+ self .assertFalse (tests_failed )
284+ self .assertTrue (transient_only )
285+
286+ def test_strict_failure_blocks_transient_retry (self ):
287+ """Ensure a strict failure (AssertionError) blocks retry even if a transient error exists."""
288+ content = (
289+ "===== JOB 3 =====\n "
290+ "FAIL: test_user_login\n "
291+ "AssertionError: True is not False\n "
292+ "...\n "
293+ "Connection reset by peer\n "
294+ )
295+ text , tests_failed , transient_only = process_error_logs (content )
296+ self .assertTrue (tests_failed )
297+ self .assertFalse (transient_only )
298+
299+ def test_pure_generic_bug_blocks_retry (self ):
300+ """Ensure a generic traceback without a transient error is flagged as a real code bug."""
301+ content = (
302+ "===== JOB 4 =====\n "
303+ "Traceback (most recent call last):\n "
304+ " File 'app/models.py', line 45, in save\n "
305+ "TypeError: 'NoneType' object is not subscriptable\n "
306+ "ERROR: Process exited with code 1\n "
307+ )
308+ text , tests_failed , transient_only = process_error_logs (content )
309+ self .assertTrue (tests_failed )
310+ self .assertFalse (transient_only )
264311
265312 def test_transient_only_false_when_mixed (self ):
266313 content = (
@@ -281,6 +328,16 @@ def test_coveralls_only_is_transient(self):
281328 _ , _ , transient = process_error_logs (content )
282329 self .assertTrue (transient )
283330
331+ def test_transient_marker_containing_error_keyword (self ):
332+ content = (
333+ "===== JOB 100 =====\n "
334+ "ERROR: Could not install packages due to an OSError: HTTPSConnectionPool\n "
335+ "Network is unreachable\n "
336+ )
337+ text , tests_failed , transient_only = process_error_logs (content )
338+ self .assertTrue (transient_only )
339+ self .assertFalse (tests_failed )
340+
284341
285342class TestNormalizeForDedup (unittest .TestCase ):
286343 """Tests for _normalize_for_dedup."""
0 commit comments