michael@0: #!/usr/bin/env python michael@0: # michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: # You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: import os, unittest, subprocess, tempfile, shutil, urlparse, zipfile, StringIO michael@0: import mozcrash, mozlog, mozhttpd michael@0: michael@0: # Make logs go away michael@0: log = mozlog.getLogger("mozcrash", handler=mozlog.FileHandler(os.devnull)) michael@0: michael@0: def popen_factory(stdouts): michael@0: """ michael@0: Generate a class that can mock subprocess.Popen. |stdouts| is an iterable that michael@0: should return an iterable for the stdout of each process in turn. michael@0: """ michael@0: class mock_popen(object): michael@0: def __init__(self, args, *args_rest, **kwargs): michael@0: self.stdout = stdouts.next() michael@0: self.returncode = 0 michael@0: michael@0: def wait(self): michael@0: return 0 michael@0: michael@0: def communicate(self): michael@0: return (self.stdout.next(), "") michael@0: michael@0: return mock_popen michael@0: michael@0: class TestCrash(unittest.TestCase): michael@0: def setUp(self): michael@0: self.tempdir = tempfile.mkdtemp() michael@0: # a fake file to use as a stackwalk binary michael@0: self.stackwalk = os.path.join(self.tempdir, "stackwalk") michael@0: open(self.stackwalk, "w").write("fake binary") michael@0: self._subprocess_popen = subprocess.Popen michael@0: subprocess.Popen = popen_factory(self.next_mock_stdout()) michael@0: self.stdouts = [] michael@0: michael@0: def tearDown(self): michael@0: subprocess.Popen = self._subprocess_popen michael@0: shutil.rmtree(self.tempdir) michael@0: michael@0: def next_mock_stdout(self): michael@0: if not self.stdouts: michael@0: yield iter([]) michael@0: for s in self.stdouts: michael@0: yield iter(s) michael@0: michael@0: def test_nodumps(self): michael@0: """ michael@0: Test that check_for_crashes returns False if no dumps are present. michael@0: """ michael@0: self.stdouts.append(["this is some output"]) michael@0: self.assertFalse(mozcrash.check_for_crashes(self.tempdir, michael@0: 'symbols_path', michael@0: stackwalk_binary=self.stackwalk, michael@0: quiet=True)) michael@0: michael@0: def test_simple(self): michael@0: """ michael@0: Test that check_for_crashes returns True if a dump is present. michael@0: """ michael@0: open(os.path.join(self.tempdir, "test.dmp"), "w").write("foo") michael@0: self.stdouts.append(["this is some output"]) michael@0: self.assert_(mozcrash.check_for_crashes(self.tempdir, michael@0: 'symbols_path', michael@0: stackwalk_binary=self.stackwalk, michael@0: quiet=True)) michael@0: michael@0: def test_stackwalk_envvar(self): michael@0: """ michael@0: Test that check_for_crashes uses the MINIDUMP_STACKWALK environment var. michael@0: """ michael@0: open(os.path.join(self.tempdir, "test.dmp"), "w").write("foo") michael@0: self.stdouts.append(["this is some output"]) michael@0: os.environ['MINIDUMP_STACKWALK'] = self.stackwalk michael@0: self.assert_(mozcrash.check_for_crashes(self.tempdir, michael@0: 'symbols_path', michael@0: quiet=True)) michael@0: del os.environ['MINIDUMP_STACKWALK'] michael@0: michael@0: def test_save_path(self): michael@0: """ michael@0: Test that dump_save_path works. michael@0: """ michael@0: open(os.path.join(self.tempdir, "test.dmp"), "w").write("foo") michael@0: open(os.path.join(self.tempdir, "test.extra"), "w").write("bar") michael@0: save_path = os.path.join(self.tempdir, "saved") michael@0: os.mkdir(save_path) michael@0: self.stdouts.append(["this is some output"]) michael@0: self.assert_(mozcrash.check_for_crashes(self.tempdir, michael@0: 'symbols_path', michael@0: stackwalk_binary=self.stackwalk, michael@0: dump_save_path=save_path, michael@0: quiet=True)) michael@0: self.assert_(os.path.isfile(os.path.join(save_path, "test.dmp"))) michael@0: self.assert_(os.path.isfile(os.path.join(save_path, "test.extra"))) michael@0: michael@0: def test_save_path_not_present(self): michael@0: """ michael@0: Test that dump_save_path works when the directory doesn't exist. michael@0: """ michael@0: open(os.path.join(self.tempdir, "test.dmp"), "w").write("foo") michael@0: open(os.path.join(self.tempdir, "test.extra"), "w").write("bar") michael@0: save_path = os.path.join(self.tempdir, "saved") michael@0: self.stdouts.append(["this is some output"]) michael@0: self.assert_(mozcrash.check_for_crashes(self.tempdir, michael@0: 'symbols_path', michael@0: stackwalk_binary=self.stackwalk, michael@0: dump_save_path=save_path, michael@0: quiet=True)) michael@0: self.assert_(os.path.isfile(os.path.join(save_path, "test.dmp"))) michael@0: self.assert_(os.path.isfile(os.path.join(save_path, "test.extra"))) michael@0: michael@0: def test_save_path_isfile(self): michael@0: """ michael@0: Test that dump_save_path works when the directory doesn't exist, michael@0: but a file with the same name exists. michael@0: """ michael@0: open(os.path.join(self.tempdir, "test.dmp"), "w").write("foo") michael@0: open(os.path.join(self.tempdir, "test.extra"), "w").write("bar") michael@0: save_path = os.path.join(self.tempdir, "saved") michael@0: open(save_path, "w").write("junk") michael@0: self.stdouts.append(["this is some output"]) michael@0: self.assert_(mozcrash.check_for_crashes(self.tempdir, michael@0: 'symbols_path', michael@0: stackwalk_binary=self.stackwalk, michael@0: dump_save_path=save_path, michael@0: quiet=True)) michael@0: self.assert_(os.path.isfile(os.path.join(save_path, "test.dmp"))) michael@0: self.assert_(os.path.isfile(os.path.join(save_path, "test.extra"))) michael@0: michael@0: def test_save_path_envvar(self): michael@0: """ michael@0: Test that the MINDUMP_SAVE_PATH environment variable works. michael@0: """ michael@0: open(os.path.join(self.tempdir, "test.dmp"), "w").write("foo") michael@0: open(os.path.join(self.tempdir, "test.extra"), "w").write("bar") michael@0: save_path = os.path.join(self.tempdir, "saved") michael@0: os.mkdir(save_path) michael@0: self.stdouts.append(["this is some output"]) michael@0: os.environ['MINIDUMP_SAVE_PATH'] = save_path michael@0: self.assert_(mozcrash.check_for_crashes(self.tempdir, michael@0: 'symbols_path', michael@0: stackwalk_binary=self.stackwalk, michael@0: quiet=True)) michael@0: del os.environ['MINIDUMP_SAVE_PATH'] michael@0: self.assert_(os.path.isfile(os.path.join(save_path, "test.dmp"))) michael@0: self.assert_(os.path.isfile(os.path.join(save_path, "test.extra"))) michael@0: michael@0: def test_symbol_path_url(self): michael@0: """ michael@0: Test that passing a URL as symbols_path correctly fetches the URL. michael@0: """ michael@0: open(os.path.join(self.tempdir, "test.dmp"), "w").write("foo") michael@0: self.stdouts.append(["this is some output"]) michael@0: michael@0: def make_zipfile(): michael@0: data = StringIO.StringIO() michael@0: z = zipfile.ZipFile(data, 'w') michael@0: z.writestr("symbols.txt", "abc/xyz") michael@0: z.close() michael@0: return data.getvalue() michael@0: def get_symbols(req): michael@0: headers = {} michael@0: return (200, headers, make_zipfile()) michael@0: httpd = mozhttpd.MozHttpd(port=0, michael@0: urlhandlers=[{'method':'GET', 'path':'/symbols', 'function':get_symbols}]) michael@0: httpd.start() michael@0: symbol_url = urlparse.urlunsplit(('http', '%s:%d' % httpd.httpd.server_address, michael@0: '/symbols','','')) michael@0: self.assert_(mozcrash.check_for_crashes(self.tempdir, michael@0: symbol_url, michael@0: stackwalk_binary=self.stackwalk, michael@0: quiet=True)) michael@0: michael@0: class TestJavaException(unittest.TestCase): michael@0: def setUp(self): michael@0: self.test_log = ["01-30 20:15:41.937 E/GeckoAppShell( 1703): >>> REPORTING UNCAUGHT EXCEPTION FROM THREAD 9 (\"GeckoBackgroundThread\")", michael@0: "01-30 20:15:41.937 E/GeckoAppShell( 1703): java.lang.NullPointerException", michael@0: "01-30 20:15:41.937 E/GeckoAppShell( 1703): at org.mozilla.gecko.GeckoApp$21.run(GeckoApp.java:1833)", michael@0: "01-30 20:15:41.937 E/GeckoAppShell( 1703): at android.os.Handler.handleCallback(Handler.java:587)"] michael@0: michael@0: def test_uncaught_exception(self): michael@0: """ michael@0: Test for an exception which should be caught michael@0: """ michael@0: self.assert_(mozcrash.check_for_java_exception(self.test_log, quiet=True)) michael@0: michael@0: def test_fatal_exception(self): michael@0: """ michael@0: Test for an exception which should be caught michael@0: """ michael@0: fatal_log = list(self.test_log) michael@0: fatal_log[0] = "01-30 20:15:41.937 E/GeckoAppShell( 1703): >>> FATAL EXCEPTION FROM THREAD 9 (\"GeckoBackgroundThread\")" michael@0: self.assert_(mozcrash.check_for_java_exception(fatal_log, quiet=True)) michael@0: michael@0: def test_truncated_exception(self): michael@0: """ michael@0: Test for an exception which should be caught which michael@0: was truncated michael@0: """ michael@0: truncated_log = list(self.test_log) michael@0: truncated_log[0], truncated_log[1] = truncated_log[1], truncated_log[0] michael@0: self.assert_(mozcrash.check_for_java_exception(truncated_log, quiet=True)) michael@0: michael@0: def test_unchecked_exception(self): michael@0: """ michael@0: Test for an exception which should not be caught michael@0: """ michael@0: passable_log = list(self.test_log) michael@0: passable_log[0] = "01-30 20:15:41.937 E/GeckoAppShell( 1703): >>> NOT-SO-BAD EXCEPTION FROM THREAD 9 (\"GeckoBackgroundThread\")" michael@0: self.assert_(not mozcrash.check_for_java_exception(passable_log, quiet=True)) michael@0: michael@0: if __name__ == '__main__': michael@0: unittest.main()