tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_crash.py (7465B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this
      3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      4 
      5 import glob
      6 import os
      7 import shutil
      8 import sys
      9 
     10 from io import StringIO
     11 
     12 from marionette_driver import Wait
     13 from marionette_driver.errors import (
     14    InvalidSessionIdException,
     15    NoSuchWindowException,
     16    TimeoutException,
     17 )
     18 
     19 from marionette_harness import MarionetteTestCase, expectedFailure
     20 
     21 # Import runner module to monkey patch mozcrash module
     22 from mozrunner.base import runner
     23 
     24 
     25 class MockMozCrash(object):
     26    """Mock object to replace original mozcrash methods."""
     27 
     28    def __init__(self, marionette):
     29        self.marionette = marionette
     30 
     31        with self.marionette.using_context("chrome"):
     32            self.crash_reporter_enabled = self.marionette.execute_script(
     33                """
     34                const { AppConstants } = ChromeUtils.importESModule(
     35                  "resource://gre/modules/AppConstants.sys.mjs"
     36                );
     37                return AppConstants.MOZ_CRASHREPORTER;
     38            """
     39            )
     40 
     41    def check_for_crashes(self, dump_directory, *args, **kwargs):
     42        if self.crash_reporter_enabled:
     43            # Workaround until bug 1376795 has been fixed
     44            # Wait at maximum 5s for the minidump files being created
     45            # minidump_files = glob.glob('{}/*.dmp'.format(dump_directory))
     46            try:
     47                minidump_files = Wait(None, timeout=5).until(
     48                    lambda _: glob.glob("{}/*.dmp".format(dump_directory))
     49                )
     50            except TimeoutException:
     51                minidump_files = []
     52 
     53            if os.path.isdir(dump_directory):
     54                shutil.rmtree(dump_directory)
     55 
     56            return len(minidump_files)
     57        else:
     58            return len(minidump_files) == 0
     59 
     60    def log_crashes(self, logger, dump_directory, *args, **kwargs):
     61        return self.check_for_crashes(dump_directory, *args, **kwargs)
     62 
     63 
     64 class BaseCrashTestCase(MarionetteTestCase):
     65    # Reduce the timeout for faster processing of the tests
     66    socket_timeout = 10
     67 
     68    def setUp(self):
     69        super(BaseCrashTestCase, self).setUp()
     70 
     71        # Monkey patch mozcrash to avoid crash info output only for our triggered crashes.
     72        mozcrash_mock = MockMozCrash(self.marionette)
     73        if not mozcrash_mock.crash_reporter_enabled:
     74            self.skipTest("Crash reporter disabled")
     75            return
     76 
     77        self.mozcrash = runner.mozcrash
     78        runner.mozcrash = mozcrash_mock
     79 
     80        self.crash_count = self.marionette.crashed
     81        self.pid = self.marionette.process_id
     82 
     83    def tearDown(self):
     84        # Replace mockup with original mozcrash instance
     85        runner.mozcrash = self.mozcrash
     86 
     87        self.marionette.crashed = self.crash_count
     88 
     89        super(BaseCrashTestCase, self).tearDown()
     90 
     91    def crash(self, parent=True):
     92        socket_timeout = self.marionette.client.socket_timeout
     93        self.marionette.client.socket_timeout = self.socket_timeout
     94 
     95        self.marionette.set_context("content")
     96        try:
     97            self.marionette.navigate(
     98                "about:crash{}".format("parent" if parent else "content")
     99            )
    100        finally:
    101            self.marionette.client.socket_timeout = socket_timeout
    102 
    103 
    104 class TestCrash(BaseCrashTestCase):
    105    def setUp(self):
    106        if os.environ.get("MOZ_AUTOMATION"):
    107            # Capture stdout, otherwise the Gecko output causes mozharness to fail
    108            # the task due to "A content process has crashed" appearing in the log.
    109            # To view stdout for debugging, use `print(self.new_out.getvalue())`
    110            print(
    111                "Suppressing GECKO output. To view, add `print(self.new_out.getvalue())` "
    112                "to the end of this test."
    113            )
    114            self.new_out, self.new_err = StringIO(), StringIO()
    115            self.old_out, self.old_err = sys.stdout, sys.stderr
    116            sys.stdout, sys.stderr = self.new_out, self.new_err
    117 
    118        super(TestCrash, self).setUp()
    119 
    120    def tearDown(self):
    121        super(TestCrash, self).tearDown()
    122 
    123        if os.environ.get("MOZ_AUTOMATION"):
    124            sys.stdout, sys.stderr = self.old_out, self.old_err
    125 
    126    def test_crash_chrome_process(self):
    127        self.assertRaisesRegex(IOError, "Process crashed", self.crash, parent=True)
    128 
    129        # A crash results in a non zero exit code
    130        self.assertNotIn(self.marionette.instance.runner.returncode, (None, 0))
    131 
    132        self.assertEqual(self.marionette.crashed, 1)
    133        self.assertIsNone(self.marionette.session)
    134        with self.assertRaisesRegex(
    135            InvalidSessionIdException, "Please start a session"
    136        ):
    137            self.marionette.get_url()
    138 
    139        self.marionette.start_session()
    140        self.assertNotEqual(self.marionette.process_id, self.pid)
    141 
    142        self.marionette.get_url()
    143 
    144    def test_crash_content_process(self):
    145        # For a content process crash and MOZ_CRASHREPORTER_SHUTDOWN set the top
    146        # browsing context will be gone first. As such the raised NoSuchWindowException
    147        # has to be ignored. To check for the IOError, further commands have to
    148        # be executed until the process is gone.
    149        with self.assertRaisesRegex(IOError, "Content process crashed"):
    150            self.crash(parent=False)
    151            Wait(
    152                self.marionette,
    153                timeout=self.socket_timeout,
    154                ignored_exceptions=NoSuchWindowException,
    155            ).until(
    156                lambda _: self.marionette.get_url(),
    157                message="Expected IOError exception for content crash not raised.",
    158            )
    159 
    160        # A crash when loading about:crashcontent results in a SIGUSR1 exit code.
    161        self.assertEqual(self.marionette.instance.runner.returncode, 245)
    162 
    163        self.assertEqual(self.marionette.crashed, 1)
    164        self.assertIsNone(self.marionette.session)
    165        with self.assertRaisesRegex(
    166            InvalidSessionIdException, "Please start a session"
    167        ):
    168            self.marionette.get_url()
    169 
    170        self.marionette.start_session()
    171        self.assertNotEqual(self.marionette.process_id, self.pid)
    172        self.marionette.get_url()
    173 
    174    @expectedFailure
    175    def test_unexpected_crash(self):
    176        self.crash(parent=True)
    177 
    178 
    179 class TestCrashInSetUp(BaseCrashTestCase):
    180    def setUp(self):
    181        super(TestCrashInSetUp, self).setUp()
    182 
    183        self.assertRaisesRegex(IOError, "Process crashed", self.crash, parent=True)
    184 
    185        # A crash results in a non zero exit code
    186        self.assertNotIn(self.marionette.instance.runner.returncode, (None, 0))
    187 
    188        self.assertEqual(self.marionette.crashed, 1)
    189        self.assertIsNone(self.marionette.session)
    190 
    191    def test_crash_in_setup(self):
    192        self.marionette.start_session()
    193        self.assertNotEqual(self.marionette.process_id, self.pid)
    194 
    195 
    196 class TestCrashInTearDown(BaseCrashTestCase):
    197    def tearDown(self):
    198        try:
    199            self.assertRaisesRegex(IOError, "Process crashed", self.crash, parent=True)
    200 
    201            # A crash results in a non zero exit code
    202            self.assertNotIn(self.marionette.instance.runner.returncode, (None, 0))
    203 
    204            self.assertEqual(self.marionette.crashed, 1)
    205            self.assertIsNone(self.marionette.session)
    206 
    207        finally:
    208            super(TestCrashInTearDown, self).tearDown()
    209 
    210    def test_crash_in_teardown(self):
    211        pass