|
1 #!/usr/bin/env python |
|
2 # |
|
3 # This Source Code Form is subject to the terms of the Mozilla Public |
|
4 # License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
5 # You can obtain one at http://mozilla.org/MPL/2.0/. |
|
6 |
|
7 import os, unittest, subprocess, tempfile, shutil, urlparse, zipfile, StringIO |
|
8 import mozcrash, mozlog, mozhttpd |
|
9 |
|
10 # Make logs go away |
|
11 log = mozlog.getLogger("mozcrash", handler=mozlog.FileHandler(os.devnull)) |
|
12 |
|
13 def popen_factory(stdouts): |
|
14 """ |
|
15 Generate a class that can mock subprocess.Popen. |stdouts| is an iterable that |
|
16 should return an iterable for the stdout of each process in turn. |
|
17 """ |
|
18 class mock_popen(object): |
|
19 def __init__(self, args, *args_rest, **kwargs): |
|
20 self.stdout = stdouts.next() |
|
21 self.returncode = 0 |
|
22 |
|
23 def wait(self): |
|
24 return 0 |
|
25 |
|
26 def communicate(self): |
|
27 return (self.stdout.next(), "") |
|
28 |
|
29 return mock_popen |
|
30 |
|
31 class TestCrash(unittest.TestCase): |
|
32 def setUp(self): |
|
33 self.tempdir = tempfile.mkdtemp() |
|
34 # a fake file to use as a stackwalk binary |
|
35 self.stackwalk = os.path.join(self.tempdir, "stackwalk") |
|
36 open(self.stackwalk, "w").write("fake binary") |
|
37 self._subprocess_popen = subprocess.Popen |
|
38 subprocess.Popen = popen_factory(self.next_mock_stdout()) |
|
39 self.stdouts = [] |
|
40 |
|
41 def tearDown(self): |
|
42 subprocess.Popen = self._subprocess_popen |
|
43 shutil.rmtree(self.tempdir) |
|
44 |
|
45 def next_mock_stdout(self): |
|
46 if not self.stdouts: |
|
47 yield iter([]) |
|
48 for s in self.stdouts: |
|
49 yield iter(s) |
|
50 |
|
51 def test_nodumps(self): |
|
52 """ |
|
53 Test that check_for_crashes returns False if no dumps are present. |
|
54 """ |
|
55 self.stdouts.append(["this is some output"]) |
|
56 self.assertFalse(mozcrash.check_for_crashes(self.tempdir, |
|
57 'symbols_path', |
|
58 stackwalk_binary=self.stackwalk, |
|
59 quiet=True)) |
|
60 |
|
61 def test_simple(self): |
|
62 """ |
|
63 Test that check_for_crashes returns True if a dump is present. |
|
64 """ |
|
65 open(os.path.join(self.tempdir, "test.dmp"), "w").write("foo") |
|
66 self.stdouts.append(["this is some output"]) |
|
67 self.assert_(mozcrash.check_for_crashes(self.tempdir, |
|
68 'symbols_path', |
|
69 stackwalk_binary=self.stackwalk, |
|
70 quiet=True)) |
|
71 |
|
72 def test_stackwalk_envvar(self): |
|
73 """ |
|
74 Test that check_for_crashes uses the MINIDUMP_STACKWALK environment var. |
|
75 """ |
|
76 open(os.path.join(self.tempdir, "test.dmp"), "w").write("foo") |
|
77 self.stdouts.append(["this is some output"]) |
|
78 os.environ['MINIDUMP_STACKWALK'] = self.stackwalk |
|
79 self.assert_(mozcrash.check_for_crashes(self.tempdir, |
|
80 'symbols_path', |
|
81 quiet=True)) |
|
82 del os.environ['MINIDUMP_STACKWALK'] |
|
83 |
|
84 def test_save_path(self): |
|
85 """ |
|
86 Test that dump_save_path works. |
|
87 """ |
|
88 open(os.path.join(self.tempdir, "test.dmp"), "w").write("foo") |
|
89 open(os.path.join(self.tempdir, "test.extra"), "w").write("bar") |
|
90 save_path = os.path.join(self.tempdir, "saved") |
|
91 os.mkdir(save_path) |
|
92 self.stdouts.append(["this is some output"]) |
|
93 self.assert_(mozcrash.check_for_crashes(self.tempdir, |
|
94 'symbols_path', |
|
95 stackwalk_binary=self.stackwalk, |
|
96 dump_save_path=save_path, |
|
97 quiet=True)) |
|
98 self.assert_(os.path.isfile(os.path.join(save_path, "test.dmp"))) |
|
99 self.assert_(os.path.isfile(os.path.join(save_path, "test.extra"))) |
|
100 |
|
101 def test_save_path_not_present(self): |
|
102 """ |
|
103 Test that dump_save_path works when the directory doesn't exist. |
|
104 """ |
|
105 open(os.path.join(self.tempdir, "test.dmp"), "w").write("foo") |
|
106 open(os.path.join(self.tempdir, "test.extra"), "w").write("bar") |
|
107 save_path = os.path.join(self.tempdir, "saved") |
|
108 self.stdouts.append(["this is some output"]) |
|
109 self.assert_(mozcrash.check_for_crashes(self.tempdir, |
|
110 'symbols_path', |
|
111 stackwalk_binary=self.stackwalk, |
|
112 dump_save_path=save_path, |
|
113 quiet=True)) |
|
114 self.assert_(os.path.isfile(os.path.join(save_path, "test.dmp"))) |
|
115 self.assert_(os.path.isfile(os.path.join(save_path, "test.extra"))) |
|
116 |
|
117 def test_save_path_isfile(self): |
|
118 """ |
|
119 Test that dump_save_path works when the directory doesn't exist, |
|
120 but a file with the same name exists. |
|
121 """ |
|
122 open(os.path.join(self.tempdir, "test.dmp"), "w").write("foo") |
|
123 open(os.path.join(self.tempdir, "test.extra"), "w").write("bar") |
|
124 save_path = os.path.join(self.tempdir, "saved") |
|
125 open(save_path, "w").write("junk") |
|
126 self.stdouts.append(["this is some output"]) |
|
127 self.assert_(mozcrash.check_for_crashes(self.tempdir, |
|
128 'symbols_path', |
|
129 stackwalk_binary=self.stackwalk, |
|
130 dump_save_path=save_path, |
|
131 quiet=True)) |
|
132 self.assert_(os.path.isfile(os.path.join(save_path, "test.dmp"))) |
|
133 self.assert_(os.path.isfile(os.path.join(save_path, "test.extra"))) |
|
134 |
|
135 def test_save_path_envvar(self): |
|
136 """ |
|
137 Test that the MINDUMP_SAVE_PATH environment variable works. |
|
138 """ |
|
139 open(os.path.join(self.tempdir, "test.dmp"), "w").write("foo") |
|
140 open(os.path.join(self.tempdir, "test.extra"), "w").write("bar") |
|
141 save_path = os.path.join(self.tempdir, "saved") |
|
142 os.mkdir(save_path) |
|
143 self.stdouts.append(["this is some output"]) |
|
144 os.environ['MINIDUMP_SAVE_PATH'] = save_path |
|
145 self.assert_(mozcrash.check_for_crashes(self.tempdir, |
|
146 'symbols_path', |
|
147 stackwalk_binary=self.stackwalk, |
|
148 quiet=True)) |
|
149 del os.environ['MINIDUMP_SAVE_PATH'] |
|
150 self.assert_(os.path.isfile(os.path.join(save_path, "test.dmp"))) |
|
151 self.assert_(os.path.isfile(os.path.join(save_path, "test.extra"))) |
|
152 |
|
153 def test_symbol_path_url(self): |
|
154 """ |
|
155 Test that passing a URL as symbols_path correctly fetches the URL. |
|
156 """ |
|
157 open(os.path.join(self.tempdir, "test.dmp"), "w").write("foo") |
|
158 self.stdouts.append(["this is some output"]) |
|
159 |
|
160 def make_zipfile(): |
|
161 data = StringIO.StringIO() |
|
162 z = zipfile.ZipFile(data, 'w') |
|
163 z.writestr("symbols.txt", "abc/xyz") |
|
164 z.close() |
|
165 return data.getvalue() |
|
166 def get_symbols(req): |
|
167 headers = {} |
|
168 return (200, headers, make_zipfile()) |
|
169 httpd = mozhttpd.MozHttpd(port=0, |
|
170 urlhandlers=[{'method':'GET', 'path':'/symbols', 'function':get_symbols}]) |
|
171 httpd.start() |
|
172 symbol_url = urlparse.urlunsplit(('http', '%s:%d' % httpd.httpd.server_address, |
|
173 '/symbols','','')) |
|
174 self.assert_(mozcrash.check_for_crashes(self.tempdir, |
|
175 symbol_url, |
|
176 stackwalk_binary=self.stackwalk, |
|
177 quiet=True)) |
|
178 |
|
179 class TestJavaException(unittest.TestCase): |
|
180 def setUp(self): |
|
181 self.test_log = ["01-30 20:15:41.937 E/GeckoAppShell( 1703): >>> REPORTING UNCAUGHT EXCEPTION FROM THREAD 9 (\"GeckoBackgroundThread\")", |
|
182 "01-30 20:15:41.937 E/GeckoAppShell( 1703): java.lang.NullPointerException", |
|
183 "01-30 20:15:41.937 E/GeckoAppShell( 1703): at org.mozilla.gecko.GeckoApp$21.run(GeckoApp.java:1833)", |
|
184 "01-30 20:15:41.937 E/GeckoAppShell( 1703): at android.os.Handler.handleCallback(Handler.java:587)"] |
|
185 |
|
186 def test_uncaught_exception(self): |
|
187 """ |
|
188 Test for an exception which should be caught |
|
189 """ |
|
190 self.assert_(mozcrash.check_for_java_exception(self.test_log, quiet=True)) |
|
191 |
|
192 def test_fatal_exception(self): |
|
193 """ |
|
194 Test for an exception which should be caught |
|
195 """ |
|
196 fatal_log = list(self.test_log) |
|
197 fatal_log[0] = "01-30 20:15:41.937 E/GeckoAppShell( 1703): >>> FATAL EXCEPTION FROM THREAD 9 (\"GeckoBackgroundThread\")" |
|
198 self.assert_(mozcrash.check_for_java_exception(fatal_log, quiet=True)) |
|
199 |
|
200 def test_truncated_exception(self): |
|
201 """ |
|
202 Test for an exception which should be caught which |
|
203 was truncated |
|
204 """ |
|
205 truncated_log = list(self.test_log) |
|
206 truncated_log[0], truncated_log[1] = truncated_log[1], truncated_log[0] |
|
207 self.assert_(mozcrash.check_for_java_exception(truncated_log, quiet=True)) |
|
208 |
|
209 def test_unchecked_exception(self): |
|
210 """ |
|
211 Test for an exception which should not be caught |
|
212 """ |
|
213 passable_log = list(self.test_log) |
|
214 passable_log[0] = "01-30 20:15:41.937 E/GeckoAppShell( 1703): >>> NOT-SO-BAD EXCEPTION FROM THREAD 9 (\"GeckoBackgroundThread\")" |
|
215 self.assert_(not mozcrash.check_for_java_exception(passable_log, quiet=True)) |
|
216 |
|
217 if __name__ == '__main__': |
|
218 unittest.main() |