Your IP : 172.28.240.42


Current Path : /usr/share/apport/testsuite/
Upload File :
Current File : //usr/share/apport/testsuite/test_python_crashes.py

# Test apport_python_hook.py
#
# Copyright (c) 2006 - 2011 Canonical Ltd.
# Authors: Robert Collins <robert@ubuntu.com>
#          Martin Pitt <martin.pitt@ubuntu.com>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
# the full text of the license.

import unittest, tempfile, subprocess, os, stat, sys, shutil, atexit
import dbus

temp_report_dir = tempfile.mkdtemp()
os.environ['APPORT_REPORT_DIR'] = temp_report_dir
atexit.register(shutil.rmtree, temp_report_dir)

import apport.fileutils, problem_report

class T(unittest.TestCase):
    def tearDown(self):
        for f in apport.fileutils.get_all_reports():
            os.unlink(f)

    def _test_crash(self, extracode='', scriptname=None):
        '''Create a test crash.'''

        # put the script into /var/tmp, since that isn't ignored in the
        # hook
        if scriptname:
            script = scriptname
            fd = os.open(scriptname, os.O_CREAT|os.O_WRONLY)
        else:
            (fd, script) = tempfile.mkstemp(dir='/var/tmp')
        try:
            os.write(fd, '''#!/usr/bin/python
import apport_python_hook
apport_python_hook.install()

def func(x):
    raise Exception(b'This should happen. \\xe2\\x99\\xa5'.decode('UTF-8'))

%s
func(42)
''' % extracode)
            os.close(fd)
            os.chmod(script, 0o755)

            p = subprocess.Popen([script, 'testarg1', 'testarg2'],
                stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=os.environ)
            err = p.communicate()[1]
            self.assertEqual(p.returncode, 1,
                'crashing test python program exits with failure code')
            if not extracode:
                self.assertTrue('This should happen.' in err, err)
            self.assertFalse('OSError' in err, err)
        finally:
            os.unlink(script)

        return script

    def test_general(self):
        '''general operation of the Python crash hook.'''

        script = self._test_crash()

        # did we get a report?
        reports = apport.fileutils.get_new_reports()
        pr = None
        self.assertEqual(len(reports), 1, 'crashed Python program produced a report')
        self.assertEqual(stat.S_IMODE(os.stat(reports[0]).st_mode),
            0o640, 'report has correct permissions')

        pr = problem_report.ProblemReport()
        pr.load(open(reports[0]))

        # check report contents
        expected_keys = ['InterpreterPath', 'PythonArgs',
            'Traceback', 'ProblemType', 'ProcEnviron', 'ProcStatus',
            'ProcCmdline', 'Date', 'ExecutablePath', 'ProcMaps',
            'UserGroups']
        self.assertTrue(set(expected_keys).issubset(set(pr.keys())),
            'report has necessary fields')
        self.assertTrue('bin/python' in pr['InterpreterPath'])
        self.assertEqual(pr['ExecutablePath'], script)
        self.assertEqual(pr['PythonArgs'], "['%s', 'testarg1', 'testarg2']" % script)
        self.assertTrue(pr['Traceback'].startswith('Traceback'))
        self.assertTrue("func\n    raise Exception(b'This should happen." in
                        pr['Traceback'], pr['Traceback'])

    def test_existing(self):
        '''Python crash hook overwrites seen existing files.'''

        script = self._test_crash()

        # did we get a report?
        reports = apport.fileutils.get_new_reports()
        self.assertEqual(len(reports), 1, 'crashed Python program produced a report')
        self.assertEqual(stat.S_IMODE(os.stat(reports[0]).st_mode),
            0o640, 'report has correct permissions')

        # touch report -> "seen" case
        apport.fileutils.mark_report_seen(reports[0])

        reports = apport.fileutils.get_new_reports()
        self.assertEqual(len(reports), 0)

        script = self._test_crash(scriptname=script)
        reports = apport.fileutils.get_new_reports()
        self.assertEqual(len(reports), 1)

        # "unseen" case
        script = self._test_crash(scriptname=script)
        reports = apport.fileutils.get_new_reports()
        self.assertEqual(len(reports), 1)

    def test_no_argv(self):
        '''with zapped sys.argv.'''

        self._test_crash('import sys\nsys.argv = None')

        # did we get a report?
        reports = apport.fileutils.get_new_reports()
        pr = None
        self.assertEqual(len(reports), 1, 'crashed Python program produced a report')
        self.assertEqual(stat.S_IMODE(os.stat(reports[0]).st_mode),
            0o640, 'report has correct permissions')

        pr = problem_report.ProblemReport()
        pr.load(open(reports[0]))

        # check report contents
        expected_keys = ['InterpreterPath',
            'Traceback', 'ProblemType', 'ProcEnviron', 'ProcStatus',
            'ProcCmdline', 'Date', 'ExecutablePath', 'ProcMaps',
            'UserGroups']
        self.assertTrue(set(expected_keys).issubset(set(pr.keys())),
            'report has necessary fields')
        self.assertTrue('bin/python' in pr['InterpreterPath'])
        self.assertTrue(pr['Traceback'].startswith('Traceback'))

    def _assert_no_reports(self):
        '''Assert that there are no crash reports.'''

        reports = apport.fileutils.get_new_reports()
        self.assertEqual(len(reports), 0,
            'no crash reports present (cwd: %s)' % os.getcwd())

    def test_interactive(self):
        '''interactive Python sessions never generate a report.'''

        orig_cwd = os.getcwd()
        try:
            for d in ('/tmp', '/usr/local', '/usr'):
                os.chdir(d)
                p = subprocess.Popen(['python'], stdin=subprocess.PIPE,
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                (out, err) = p.communicate('raise ValueError')
                assert p.returncode != 0
                assert out == ''
                assert 'ValueError' in err
                self._assert_no_reports()
        finally:
            os.chdir(orig_cwd)

    def test_ignoring(self):
        '''the Python crash hook respects the ignore list.'''

        # put the script into /var/crash, since that isn't ignored in the
        # hook
        (fd, script) = tempfile.mkstemp(dir=apport.fileutils.report_dir)
        ifpath = os.path.expanduser(apport.report._ignore_file)
        orig_ignore_file = None
        try:
            os.write(fd, '''#!/usr/bin/python
import apport_python_hook
apport_python_hook.install()

def func(x):
    raise Exception, 'This should happen.'

func(42)
''')
            os.close(fd)
            os.chmod(script, 0o755)

            # move aside current ignore file
            if os.path.exists(ifpath):
                orig_ignore_file = ifpath + '.apporttest'
                os.rename(ifpath, orig_ignore_file)

            # ignore
            r = apport.report.Report()
            r['ExecutablePath'] = script
            r.mark_ignore()
            r = None

            p = subprocess.Popen([script, 'testarg1', 'testarg2'],
                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            err = p.communicate()[1]
            self.assertEqual(p.returncode, 1,
                'crashing test python program exits with failure code')
            self.assertTrue('Exception: This should happen.' in err)

        finally:
            os.unlink(script)
            # clean up our ignore file
            if os.path.exists(ifpath):
                os.unlink(ifpath)
            if orig_ignore_file:
                os.rename(orig_ignore_file, ifpath)

        # did we get a report?
        reports = apport.fileutils.get_new_reports()
        self.assertEqual(len(reports), 0)

    def test_no_flooding(self):
        '''limit successive reports'''

        count = 0
        limit = 5
        while count < limit:
            self._test_crash(scriptname='/var/tmp/pytestcrash')
            reports = apport.fileutils.get_new_reports()
            if not reports:
                break
            self.assertEqual(len(reports), 1, 'crashed Python program produced one report')
            apport.fileutils.mark_report_seen(reports[0])
            count += 1

        self.assertGreater(count, 1)
        self.assertLess(count, limit)

    def test_dbus_service_timeout_running(self):
        '''DBus.Error.NoReply with a running service'''

        # ensure the service is running
        metadata_obj = dbus.SessionBus().get_object('org.gtk.vfs.Metadata', '/org/gtk/vfs/metadata')
        self.assertNotEqual(metadata_obj, None)

        # timeout of zero will always fail with NoReply
        try:
            subprocess.call(['killall', '-STOP', 'gvfsd-metadata'])
            self._test_crash(extracode='''import dbus
obj = dbus.SessionBus().get_object('org.gtk.vfs.Metadata', '/org/gtk/vfs/metadata')
assert obj
i = dbus.Interface(obj, 'org.freedesktop.DBus.Peer')
i.Ping(timeout=1)
''')
        finally:
            subprocess.call(['killall', '-CONT', 'gvfsd-metadata'])

        # check report contents
        reports = apport.fileutils.get_new_reports()
        self.assertEqual(len(reports), 0, 'NoReply is an useless exception and should not create a report')

unittest.main()