testing/marionette/harness/marionette_harness/tests/unit/test_crash.py
author Vedant Chakravadhanula <vedantc98@gmail.com>
Tue, 05 Dec 2017 20:01:55 +0530
changeset 708671 240069ba65d7d460c4443110bb38112ff34fbc69
parent 706187 c6d4860fcd0a97c99cedf4757048eb39e1903a4d
child 717186 31851b97e541cdbe46534c24ca7cd7b08a94816a
child 717187 3cd7fc68ed05a035135c18a77bd8c43ab0b85e00
permissions -rw-r--r--
Bug 1422697 - Changed crash reporter detection in unit test r=whimboo Since the crash reporter interface was made available all the time, the check for the enabled state of the crash reporter had to be made via AppConstants.jsm MozReview-Commit-ID: LPkBVEjBB5o

# 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 shutil

from marionette_driver import Wait
from marionette_driver.errors import (
    MarionetteException,
    NoSuchWindowException,
    TimeoutException
)

from marionette_harness import MarionetteTestCase, expectedFailure, run_if_e10s

# 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("""
                Cu.import("resource://gre/modules/AppConstants.jsm");
                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 = []

            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, chrome=True):
        context = 'chrome' if chrome else 'content'
        sandbox = None if chrome else 'system'

        socket_timeout = self.marionette.client.socket_timeout
        self.marionette.client.socket_timeout = self.socket_timeout

        self.marionette.set_context(context)
        try:
            self.marionette.execute_script("""
              // Copied from crash me simple
              Components.utils.import("resource://gre/modules/ctypes.jsm");

              // ctypes checks for NULL pointer derefs, so just go near-NULL.
              var zero = new ctypes.intptr_t(8);
              var badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
              var crash = badptr.contents;
            """, sandbox=sandbox)
        finally:
            self.marionette.client.socket_timeout = socket_timeout


class TestCrash(BaseCrashTestCase):

    def test_crash_chrome_process(self):
        with self.assertRaises(IOError):
            self.crash(chrome=True)
            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 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)
        self.assertRaisesRegexp(MarionetteException, 'Please start a session',
                                self.marionette.get_url)

        self.marionette.start_session()
        self.assertNotEqual(self.marionette.process_id, self.pid)

        self.marionette.get_url()

    @run_if_e10s("Content crashes only exist in e10s mode")
    def test_crash_content_process(self):
        # With MOZ_CRASHREPORTER_SHUTDOWN each window of the browser will be
        # closed in case of a content crash. So the "unload" handler fires for
        # the current window, which causes the command to return early.
        # To check for an IOError, further commands have to be executed until
        # Firefox has been shutdown.
        with self.assertRaises(IOError):
            self.crash(chrome=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."
            )

        # In the case of a content crash Firefox will be closed and its
        # returncode will report 0 (this will change with 1370520).
        self.assertEqual(self.marionette.instance.runner.returncode, 0)

        self.assertEqual(self.marionette.crashed, 1)
        self.assertIsNone(self.marionette.session)
        self.assertRaisesRegexp(MarionetteException, '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(chrome=True)


class TestCrashInSetUp(BaseCrashTestCase):

    def setUp(self):
        super(TestCrashInSetUp, self).setUp()

        with self.assertRaises(IOError):
            self.crash(chrome=True)
            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 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:
            with self.assertRaises(IOError):
                self.crash(chrome=True)
                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 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