Back to index

apport  2.4
test_crash_digger.py
Go to the documentation of this file.
00001 '''Test crash-digger'''
00002 
00003 # Copyright (C) 2007 - 2009 Canonical Ltd.
00004 # Author: Martin Pitt <martin.pitt@ubuntu.com>
00005 #
00006 # This program is free software; you can redistribute it and/or modify it
00007 # under the terms of the GNU General Public License as published by the
00008 # Free Software Foundation; either version 2 of the License, or (at your
00009 # option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
00010 # the full text of the license.
00011 
00012 import unittest, subprocess, tempfile, os, shutil, os.path
00013 
00014 import apport.fileutils
00015 
00016 
00017 class T(unittest.TestCase):
00018     def setUp(self):
00019         '''Set up dummy config dir, crashdb.conf, and apport-retrace'''
00020 
00021         self.workdir = tempfile.mkdtemp()
00022 
00023         crashdb_conf = os.path.join(self.workdir, 'crashdb.conf')
00024         with open(crashdb_conf, 'w') as f:
00025             f.write('''default = 'memory'
00026 databases = {
00027     'memory': {'impl': 'memory', 'distro': 'Testux', 'dummy_data': '1',
00028                'dupdb_url': '%s'},
00029     'empty': {'impl': 'memory', 'distro': 'Foonux'},
00030 }''' % os.path.join(self.workdir, 'dupdb'))
00031 
00032         self.config_dir = os.path.join(self.workdir, 'config')
00033         os.mkdir(self.config_dir)
00034         os.mkdir(os.path.join(self.config_dir, 'Testux 1.0'))
00035         os.mkdir(os.path.join(self.config_dir, 'Testux 2.2'))
00036 
00037         self.apport_retrace_log = os.path.join(self.workdir, 'apport-retrace.log')
00038 
00039         self.apport_retrace = os.path.join(self.workdir, 'apport-retrace')
00040         with open(self.apport_retrace, 'w') as f:
00041             f.write('''#!/bin/sh
00042 echo "$@" >> %s''' % self.apport_retrace_log)
00043         os.chmod(self.apport_retrace, 0o755)
00044 
00045         self.lock_file = os.path.join(self.workdir, 'lock')
00046 
00047         os.environ['APPORT_CRASHDB_CONF'] = crashdb_conf
00048         os.environ['PYTHONPATH'] = '.'
00049 
00050         self.orig_report_dir = apport.fileutils.report_dir
00051         apport.fileutils.report_dir = os.path.join(self.workdir, 'crashes')
00052         os.mkdir(apport.fileutils.report_dir)
00053         os.environ['APPORT_REPORT_DIR'] = apport.fileutils.report_dir
00054 
00055     def tearDown(self):
00056         shutil.rmtree(self.workdir)
00057         apport.fileutils.report_dir = self.orig_report_dir
00058 
00059     def call(self, args):
00060         '''Call crash-digger with given arguments.
00061 
00062         Return a pair (stdout, stderr).
00063         '''
00064         s = subprocess.Popen(['crash-digger', '--apport-retrace',
00065                               self.apport_retrace] + args, stdout=subprocess.PIPE,
00066                              stderr=subprocess.PIPE)
00067         (out, err) = s.communicate()
00068         return (out.decode('UTF-8'), err.decode('UTF-8'))
00069 
00070     def test_crashes(self):
00071         '''Crash retracing'''
00072 
00073         (out, err) = self.call(['-c', self.config_dir, '-a', '/dev/zero', '-d',
00074                                 os.path.join(self.workdir, 'dup.db'), '-vl', self.lock_file])
00075         self.assertEqual(err, '', 'no error messages:\n' + err)
00076         self.assertTrue("Available releases: ['Testux 1.0', 'Testux 2.2']" in out)
00077         self.assertTrue('retracing #0' in out)
00078         self.assertTrue('retracing #1' in out)
00079         self.assertTrue('retracing #2' in out)
00080         self.assertTrue('crash is release FooLinux Pi/2 which does not have a config available' in out)
00081         self.assertFalse('failed with status' in out)
00082         self.assertFalse('#3' in out, 'dupcheck crashes are not retraced')
00083         self.assertFalse('#4' in out, 'dupcheck crashes are not retraced')
00084 
00085         with open(self.apport_retrace_log) as f:
00086             retrace_log = f.read()
00087         self.assertEqual(len(retrace_log.splitlines()), 2)
00088         self.assertFalse('dup.db -v 0\n' in retrace_log)
00089         self.assertTrue('dup.db -v 1\n' in retrace_log)
00090         self.assertTrue('dup.db -v 2\n' in retrace_log)
00091         self.assertFalse(os.path.exists(self.lock_file))
00092 
00093         self.assertFalse(os.path.isdir(os.path.join(self.workdir, 'dupdb', 'sig')))
00094 
00095     def test_crashes_error(self):
00096         '''Crash retracing if apport-retrace fails on bug #1'''
00097 
00098         # make apport-retrace fail on bug 1
00099         os.rename(self.apport_retrace, self.apport_retrace + '.bak')
00100         with open(self.apport_retrace, 'w') as f:
00101             f.write('''#!/bin/sh
00102 echo "$@" >> %s
00103 while [ -n "$2" ]; do shift; done
00104 if [ "$1" = 1 ]; then
00105     echo "cannot frobnicate bug" >&2
00106     exit 1
00107 fi
00108 ''' % self.apport_retrace_log)
00109         os.chmod(self.apport_retrace, 0o755)
00110 
00111         (out, err) = self.call(['-c', self.config_dir, '-a', '/dev/zero', '-d',
00112                                 os.path.join(self.workdir, 'dup.db'), '-vl', self.lock_file])
00113         self.assertTrue('Traceback' in err)
00114         self.assertTrue('SystemError: retracing #1 failed' in err)
00115         self.assertTrue("Available releases: ['Testux 1.0', 'Testux 2.2']" in out)
00116         self.assertTrue('retracing #0' in out)
00117         self.assertTrue('retracing #1' in out)
00118         self.assertFalse('retracing #2' in out, 'should not continue after errors')
00119         self.assertTrue('crash is release FooLinux Pi/2 which does not have a config available' in out)
00120         self.assertFalse('#0 failed with status' in out)
00121         self.assertTrue('#1 failed with status: 1' in out)
00122         self.assertFalse('#3' in out, 'dupcheck crashes are not retraced')
00123         self.assertFalse('#4' in out, 'dupcheck crashes are not retraced')
00124 
00125         with open(self.apport_retrace_log) as f:
00126             retrace_log = f.read()
00127         self.assertEqual(len(retrace_log.splitlines()), 1)
00128         self.assertFalse('dup.db -v 0\n' in retrace_log)
00129         self.assertTrue('dup.db -v 1\n' in retrace_log)
00130         # stops after failing #1
00131         self.assertFalse('dup.db -v 2\n' in retrace_log)
00132         self.assertTrue(os.path.exists(self.lock_file))
00133 
00134         os.rename(self.apport_retrace + '.bak', self.apport_retrace)
00135 
00136         # subsequent start should not do anything until the lock file is cleaned up
00137         (out, err) = self.call(['-c', self.config_dir, '-a', '/dev/zero', '-d',
00138                                 os.path.join(self.workdir, 'dup.db'), '-vl', self.lock_file])
00139         self.assertEqual(out, '')
00140         self.assertEqual(err, '')
00141 
00142         os.unlink(self.lock_file)
00143 
00144         # now it should run again
00145         (out, err) = self.call(['-c', self.config_dir, '-a', '/dev/zero', '-d',
00146                                 os.path.join(self.workdir, 'dup.db'), '-vl', self.lock_file])
00147         self.assertTrue('retracing #2' in out)
00148         self.assertEqual(err, '', 'no error messages:\n' + err)
00149         self.assertFalse(os.path.exists(self.lock_file))
00150 
00151     def test_crashes_transient_error(self):
00152         '''Crash retracing if apport-retrace reports a transient error'''
00153 
00154         # make apport-retrace fail on bug 1
00155         os.rename(self.apport_retrace, self.apport_retrace + '.bak')
00156         with open(self.apport_retrace, 'w') as f:
00157             f.write('''#!/bin/sh
00158 echo "$@" >> %s
00159 while [ -n "$2" ]; do shift; done
00160 if [ "$1" = 1 ]; then
00161     echo "cannot frobnicate crash db" >&2
00162     exit 99
00163 fi
00164 ''' % self.apport_retrace_log)
00165         os.chmod(self.apport_retrace, 0o755)
00166 
00167         (out, err) = self.call(['-c', self.config_dir, '-a', '/dev/zero', '-d',
00168                                 os.path.join(self.workdir, 'dup.db'), '-vl', self.lock_file])
00169         self.assertTrue("Available releases: ['Testux 1.0', 'Testux 2.2']" in out)
00170         self.assertTrue('retracing #0' in out)
00171         self.assertTrue('retracing #1' in out)
00172         self.assertFalse('retracing #2' in out, 'should not continue after errors')
00173         self.assertTrue('transient error reported; halting' in out)
00174 
00175         with open(self.apport_retrace_log) as f:
00176             retrace_log = f.read()
00177         self.assertTrue('dup.db -v 1\n' in retrace_log)
00178         # stops after failing #1
00179         self.assertFalse('dup.db -v 2\n' in retrace_log)
00180 
00181         self.assertFalse(os.path.exists(self.lock_file))
00182 
00183     def test_dupcheck(self):
00184         '''Duplicate checking'''
00185 
00186         (out, err) = self.call(['-a', '/dev/zero', '-d',
00187                                 os.path.join(self.workdir, 'dup.db'), '-vDl', self.lock_file])
00188         self.assertEqual(err, '', 'no error messages:\n' + err)
00189         self.assertFalse('#1' in out, 'signal crashes are not retraced')
00190         self.assertFalse('#2' in out, 'signal crashes are not retraced')
00191         self.assertTrue('checking #3 for duplicate' in out)
00192         self.assertTrue('checking #4 for duplicate' in out)
00193         self.assertTrue('Report is a duplicate of #3 (not fixed yet)' in out)
00194         self.assertFalse(os.path.exists(self.apport_retrace_log))
00195         self.assertFalse(os.path.exists(self.lock_file))
00196 
00197     def test_stderr_redirection(self):
00198         '''apport-retrace's stderr is redirected to stdout'''
00199 
00200         with open(self.apport_retrace, 'w') as f:
00201             f.write('''#!/bin/sh
00202 echo ApportRetraceError >&2''')
00203         (out, err) = self.call(['-c', self.config_dir, '-a', '/dev/zero', '-d',
00204                                 os.path.join(self.workdir, 'dup.db'), '-vl', self.lock_file])
00205         self.assertEqual(err, '', 'no error messages:\n' + err)
00206         self.assertTrue('ApportRetraceError' in out)
00207 
00208     def test_publish_db(self):
00209         '''Duplicate database publishing'''
00210 
00211         (out, err) = self.call(['-c', self.config_dir, '-a', '/dev/zero', '-d',
00212                                 os.path.join(self.workdir, 'dup.db'), '-vl', self.lock_file,
00213                                 '--publish-db', os.path.join(self.workdir, 'dupdb')])
00214         self.assertEqual(err, '', 'no error messages:\n' + err)
00215         self.assertTrue('retracing #0' in out)
00216 
00217         self.assertTrue(os.path.isdir(os.path.join(self.workdir, 'dupdb', 'sig')))
00218 
00219     def test_alternate_crashdb(self):
00220         '''Alternate crash database name'''
00221 
00222         # existing DB "empty" has no crashes
00223         (out, err) = self.call(['-c', self.config_dir, '-a', '/dev/zero',
00224                                 '-vl', self.lock_file, '--crash-db', 'empty'])
00225         self.assertEqual(err, '', 'no error messages:\n' + err)
00226         self.assertFalse('retracing #' in out)
00227         self.assertFalse('crash is' in out)
00228         self.assertFalse('failed with status' in out)
00229 
00230         # nonexisting DB
00231         (out, err) = self.call(['-c', self.config_dir, '-a', '/dev/zero',
00232                                 '-vl', self.lock_file, '--crash-db', 'nonexisting'])
00233         self.assertEqual(out, '', 'no output messages:\n' + out)
00234         self.assertFalse('Traceback' in err, err)
00235         self.assertTrue('nonexisting' in err, err)
00236 
00237 unittest.main()