1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
|
# 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.assertRaisesRegexp(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.assertRaisesRegexp(
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.assertRaisesRegexp(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.assertRaisesRegexp(
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.assertRaisesRegexp(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.assertRaisesRegexp(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
|