commit 14fad281ab8df4c2242bae1f3db477536692c1ea
parent b04f143b82586e02dcc070e7b5087621b2734bc9
Author: Florian Quèze <florian@queze.net>
Date: Mon, 15 Dec 2025 21:20:08 +0000
Bug 2000636 - Track crash actions by test name so we can account for tests that produce multiple crash dumps when ending with expected CRASH status, r=ahal.
Differential Revision: https://phabricator.services.mozilla.com/D276164
Diffstat:
3 files changed, 87 insertions(+), 2 deletions(-)
diff --git a/testing/mozbase/mozlog/mozlog/handlers/statushandler.py b/testing/mozbase/mozlog/mozlog/handlers/statushandler.py
@@ -33,11 +33,24 @@ class StatusHandler:
self.log_level_counts = defaultdict(int)
# The count of "No tests run" error messages seen
self.no_tests_run_count = 0
+ # Track tests that have already had a crash counted to avoid counting
+ # multiple crashes for the same test
+ self.tests_with_counted_crash = set()
def __call__(self, data):
action = data["action"]
known_intermittent = data.get("known_intermittent", [])
- self.action_counts[action] += 1
+
+ # Handle crash actions specially to only count once per test
+ if action == "crash":
+ test_name = data.get("test")
+ # Only count the first crash per test, or always count if no test name
+ if test_name is None or test_name not in self.tests_with_counted_crash:
+ self.action_counts["crash"] += 1
+ if test_name is not None:
+ self.tests_with_counted_crash.add(test_name)
+ else:
+ self.action_counts[action] += 1
if action == "log":
if data["level"] == "ERROR" and data["message"] == "No tests ran":
@@ -55,6 +68,10 @@ class StatusHandler:
if status in known_intermittent:
self.known_intermittent_statuses[status] += 1
+ # Clean up crash tracking when test ends to handle retry scenarios
+ if action == "test_end":
+ self.tests_with_counted_crash.discard(data.get("test"))
+
if action == "assertion_count":
if data["count"] < data["min_expected"]:
self.unexpected_statuses["PASS"] += 1
diff --git a/testing/mozbase/mozlog/tests/test_structured.py b/testing/mozbase/mozlog/tests/test_structured.py
@@ -106,6 +106,75 @@ class TestStatusHandler(BaseStructuredTest):
self.assertIn("OK", summary.expected_statuses)
self.assertEqual(2, summary.expected_statuses["OK"])
+ def test_crash_with_expected_crash_status(self):
+ # Test that crashes are accounted for when test ends with expected CRASH status
+ self.logger.suite_start([])
+ self.logger.test_start("test1")
+ self.logger.crash(
+ test="test1",
+ process=1234,
+ signature="test_signature",
+ minidump_path="/path/to/dump",
+ )
+ self.logger.crash(
+ test="test1",
+ process=5678,
+ signature="test_signature2",
+ minidump_path="/path/to/dump2",
+ )
+ self.logger.test_end("test1", "CRASH", expected="CRASH")
+ self.logger.suite_end()
+ summary = self.handler.summarize()
+ # Extra crashes were subtracted, keeping 1 to match the CRASH status
+ self.assertEqual(1, summary.action_counts.get("crash", 0))
+ # Test had expected CRASH status
+ self.assertEqual(1, summary.expected_statuses["CRASH"])
+
+ def test_crash_with_retry_pass(self):
+ # Simulates xpcshell retry: test crashes producing 2 dumps, then passes on retry
+ self.logger.suite_start([])
+ # First run: test crashes
+ self.logger.test_start("test1")
+ self.logger.crash(
+ test="test1",
+ process=1234,
+ signature="test_signature",
+ minidump_path="/path/to/dump",
+ )
+ self.logger.crash(
+ test="test1",
+ process=5678,
+ signature="test_signature2",
+ minidump_path="/path/to/dump2",
+ )
+ self.logger.test_end("test1", "CRASH", expected="CRASH")
+ # Retry: test passes
+ self.logger.test_start("test1")
+ self.logger.test_end("test1", "PASS", expected="PASS")
+ self.logger.suite_end()
+ summary = self.handler.summarize()
+ # Extra crashes were subtracted, keeping 1 to match the CRASH status
+ self.assertEqual(1, summary.action_counts.get("crash", 0))
+ # Both test runs are counted
+ self.assertEqual(1, summary.expected_statuses["CRASH"])
+ self.assertEqual(1, summary.expected_statuses["PASS"])
+
+ def test_crash_without_test_name(self):
+ # Orphaned crash (e.g., shutdown crash) without associated test
+ self.logger.suite_start([])
+ self.logger.test_start("test1")
+ self.logger.test_end("test1", "PASS", expected="PASS")
+ self.logger.crash(
+ process=9999,
+ signature="shutdown_crash",
+ minidump_path="/path/to/dump",
+ )
+ self.logger.suite_end()
+ summary = self.handler.summarize()
+ # Crash without test name remains unaccounted
+ self.assertEqual(1, summary.action_counts["crash"])
+ self.assertEqual(1, summary.expected_statuses["PASS"])
+
class TestSummaryHandler(BaseStructuredTest):
def setUp(self):
diff --git a/toolkit/components/extensions/test/xpcshell/xpcshell-content.toml b/toolkit/components/extensions/test/xpcshell/xpcshell-content.toml
@@ -33,7 +33,6 @@ skip-if = [
skip-if = [
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && ccov", # Bug 1854843
"tsan", # Bug 1612707
- "true", # Bug 2000636
]
["test_ext_contentscript_xrays.js"]