Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import glob
import os
import shutil
import sys
from io import StringIO
from marionette_driver import Wait
from marionette_driver.errors import (
InvalidSessionIdException,
NoSuchWindowException,
TimeoutException,
)
from marionette_harness import MarionetteTestCase, expectedFailure
# Import runner module to monkey patch mozcrash module
from mozrunner.base import runner
class MockMozCrash(object):
"""Mock object to replace original mozcrash methods."""
def __init__(self, marionette):
self.marionette = marionette
with self.marionette.using_context("chrome"):
self.crash_reporter_enabled = self.marionette.execute_script(
"""
const { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
return AppConstants.MOZ_CRASHREPORTER;
"""
)
def check_for_crashes(self, dump_directory, *args, **kwargs):
if self.crash_reporter_enabled:
# Workaround until bug 1376795 has been fixed
# Wait at maximum 5s for the minidump files being created
# minidump_files = glob.glob('{}/*.dmp'.format(dump_directory))
try:
minidump_files = Wait(None, timeout=5).until(
lambda _: glob.glob("{}/*.dmp".format(dump_directory))
)
except TimeoutException:
minidump_files = []
if os.path.isdir(dump_directory):
shutil.rmtree(dump_directory)
return len(minidump_files)
else:
return len(minidump_files) == 0
def log_crashes(self, logger, dump_directory, *args, **kwargs):
return self.check_for_crashes(dump_directory, *args, **kwargs)
class BaseCrashTestCase(MarionetteTestCase):
# Reduce the timeout for faster processing of the tests
socket_timeout = 10
def setUp(self):
super(BaseCrashTestCase, self).setUp()
# Monkey patch mozcrash to avoid crash info output only for our triggered crashes.
mozcrash_mock = MockMozCrash(self.marionette)
if not mozcrash_mock.crash_reporter_enabled:
self.skipTest("Crash reporter disabled")
return
self.mozcrash = runner.mozcrash
runner.mozcrash = mozcrash_mock
self.crash_count = self.marionette.crashed
self.pid = self.marionette.process_id
def tearDown(self):
# Replace mockup with original mozcrash instance
runner.mozcrash = self.mozcrash
self.marionette.crashed = self.crash_count
super(BaseCrashTestCase, self).tearDown()
def crash(self, parent=True):
socket_timeout = self.marionette.client.socket_timeout
self.marionette.client.socket_timeout = self.socket_timeout
self.marionette.set_context("content")
try:
self.marionette.navigate(
"about:crash{}".format("parent" if parent else "content")
)
finally:
self.marionette.client.socket_timeout = socket_timeout
class TestCrash(BaseCrashTestCase):
def setUp(self):
if os.environ.get("MOZ_AUTOMATION"):
# Capture stdout, otherwise the Gecko output causes mozharness to fail
# the task due to "A content process has crashed" appearing in the log.
# To view stdout for debugging, use `print(self.new_out.getvalue())`
print(
"Suppressing GECKO output. To view, add `print(self.new_out.getvalue())` "
"to the end of this test."
)
self.new_out, self.new_err = StringIO(), StringIO()
self.old_out, self.old_err = sys.stdout, sys.stderr
sys.stdout, sys.stderr = self.new_out, self.new_err
super(TestCrash, self).setUp()
def tearDown(self):
super(TestCrash, self).tearDown()
if os.environ.get("MOZ_AUTOMATION"):
sys.stdout, sys.stderr = self.old_out, self.old_err
def test_crash_chrome_process(self):
self.assertRaisesRegex(IOError, "Process crashed", self.crash, parent=True)
# A crash results in a non zero exit code
self.assertNotIn(self.marionette.instance.runner.returncode, (None, 0))
self.assertEqual(self.marionette.crashed, 1)
self.assertIsNone(self.marionette.session)
with self.assertRaisesRegex(
InvalidSessionIdException, "Please start a session"
):
self.marionette.get_url()
self.marionette.start_session()
self.assertNotEqual(self.marionette.process_id, self.pid)
self.marionette.get_url()
def test_crash_content_process(self):
# For a content process crash and MOZ_CRASHREPORTER_SHUTDOWN set the top
# browsing context will be gone first. As such the raised NoSuchWindowException
# has to be ignored. To check for the IOError, further commands have to
# be executed until the process is gone.
with self.assertRaisesRegex(IOError, "Content process crashed"):
self.crash(parent=False)
Wait(
self.marionette,
timeout=self.socket_timeout,
ignored_exceptions=NoSuchWindowException,
).until(
lambda _: self.marionette.get_url(),
message="Expected IOError exception for content crash not raised.",
)
# A crash when loading about:crashcontent results in a SIGUSR1 exit code.
self.assertEqual(self.marionette.instance.runner.returncode, 245)
self.assertEqual(self.marionette.crashed, 1)
self.assertIsNone(self.marionette.session)
with self.assertRaisesRegex(
InvalidSessionIdException, "Please start a session"
):
self.marionette.get_url()
self.marionette.start_session()
self.assertNotEqual(self.marionette.process_id, self.pid)
self.marionette.get_url()
@expectedFailure
def test_unexpected_crash(self):
self.crash(parent=True)
class TestCrashInSetUp(BaseCrashTestCase):
def setUp(self):
super(TestCrashInSetUp, self).setUp()
self.assertRaisesRegex(IOError, "Process crashed", self.crash, parent=True)
# A crash results in a non zero exit code
self.assertNotIn(self.marionette.instance.runner.returncode, (None, 0))
self.assertEqual(self.marionette.crashed, 1)
self.assertIsNone(self.marionette.session)
def test_crash_in_setup(self):
self.marionette.start_session()
self.assertNotEqual(self.marionette.process_id, self.pid)
class TestCrashInTearDown(BaseCrashTestCase):
def tearDown(self):
try:
self.assertRaisesRegex(IOError, "Process crashed", self.crash, parent=True)
# A crash results in a non zero exit code
self.assertNotIn(self.marionette.instance.runner.returncode, (None, 0))
self.assertEqual(self.marionette.crashed, 1)
self.assertIsNone(self.marionette.session)
finally:
super(TestCrashInTearDown, self).tearDown()
def test_crash_in_teardown(self):
pass