Back to index

apport  2.3
test_report.py
Go to the documentation of this file.
00001 # coding: UTF-8
00002 import unittest, shutil, time, tempfile, os, subprocess, grp, atexit, re
00003 
00004 import apport.report
00005 import problem_report
00006 import apport.packaging
00007 
00008 
00009 class T(unittest.TestCase):
00010     def test_add_package_info(self):
00011         '''add_package_info().'''
00012 
00013         # determine bash version
00014         bashversion = apport.packaging.get_version('bash')
00015 
00016         pr = apport.report.Report()
00017         self.assertRaises(ValueError, pr.add_package_info, 'nonexistant_package')
00018 
00019         pr.add_package_info('bash')
00020         self.assertEqual(pr['Package'], 'bash ' + bashversion.strip())
00021         self.assertEqual(pr['SourcePackage'], 'bash')
00022         self.assertTrue('libc' in pr['Dependencies'])
00023 
00024         # test without specifying a package, but with ExecutablePath
00025         pr = apport.report.Report()
00026         self.assertRaises(KeyError, pr.add_package_info)
00027         pr['ExecutablePath'] = '/bin/bash'
00028         pr.add_package_info()
00029         self.assertEqual(pr['Package'], 'bash ' + bashversion.strip())
00030         self.assertEqual(pr['SourcePackage'], 'bash')
00031         self.assertTrue('libc' in pr['Dependencies'])
00032         # check for stray empty lines
00033         self.assertTrue('\n\n' not in pr['Dependencies'])
00034         self.assertTrue('PackageArchitecture' in pr)
00035 
00036         pr = apport.report.Report()
00037         pr['ExecutablePath'] = '/nonexisting'
00038         pr.add_package_info()
00039         self.assertTrue('Package' not in pr)
00040 
00041     def test_add_os_info(self):
00042         '''add_os_info().'''
00043 
00044         pr = apport.report.Report()
00045         pr.add_os_info()
00046         self.assertTrue(pr['Uname'].startswith('Linux'))
00047         self.assertTrue(hasattr(pr['DistroRelease'], 'startswith'))
00048         self.assertGreater(len(pr['DistroRelease']), 5)
00049         self.assertTrue(pr['Architecture'])
00050 
00051     def test_add_user_info(self):
00052         '''add_user_info().'''
00053 
00054         pr = apport.report.Report()
00055         pr.add_user_info()
00056         self.assertTrue('UserGroups' in pr)
00057 
00058         # double-check that user group names are removed
00059         for g in pr['UserGroups'].split():
00060             self.assertTrue(grp.getgrnam(g).gr_gid < 1000)
00061         self.assertTrue(grp.getgrgid(os.getgid()).gr_name not in pr['UserGroups'])
00062 
00063     def test_add_proc_info(self):
00064         '''add_proc_info().'''
00065 
00066         # set test environment
00067         assert 'LANG' in os.environ, 'please set $LANG for this test'
00068 
00069         # check without additional safe environment variables
00070         pr = apport.report.Report()
00071         self.assertEqual(pr.pid, None)
00072         pr.add_proc_info()
00073         self.assertEqual(pr.pid, os.getpid())
00074         self.assertTrue(set(['ProcEnviron', 'ProcMaps', 'ProcCmdline',
00075                              'ProcMaps']).issubset(set(pr.keys())), 'report has required fields')
00076         self.assertTrue('LANG=' + os.environ['LANG'] in pr['ProcEnviron'])
00077         self.assertTrue('USER' not in pr['ProcEnviron'])
00078         self.assertTrue('PWD' not in pr['ProcEnviron'])
00079         self.assertTrue('report.py' in pr['ExecutablePath'])
00080         self.assertEqual(int(pr['ExecutableTimestamp']),
00081                          int(os.stat(__file__).st_mtime))
00082 
00083         # check with one additional safe environment variable
00084         pr = apport.report.Report()
00085         pr.add_proc_info(extraenv=['PWD'])
00086         self.assertTrue('USER' not in pr['ProcEnviron'])
00087         if 'PWD' in os.environ:
00088             self.assertTrue('PWD=' + os.environ['PWD'] in pr['ProcEnviron'])
00089 
00090         # check process from other user
00091         restore_root = False
00092         if os.getuid() == 0:
00093             # temporarily drop to normal user "mail"
00094             os.setresuid(8, 8, -1)
00095             restore_root = True
00096         pr = apport.report.Report()
00097         self.assertRaises(OSError, pr.add_proc_info, 1)  # EPERM for init process
00098         if restore_root:
00099             os.setresuid(0, 0, -1)
00100 
00101         self.assertEqual(pr.pid, 1)
00102         self.assertTrue('init' in pr['ProcStatus'], pr['ProcStatus'])
00103         self.assertTrue(pr['ProcEnviron'].startswith('Error:'), pr['ProcEnviron'])
00104         self.assertTrue('InterpreterPath' not in pr)
00105 
00106         # check escaping of ProcCmdline
00107         p = subprocess.Popen(['cat', '/foo bar', '\\h', '\\ \\', '-'],
00108                              stdin=subprocess.PIPE, stdout=subprocess.PIPE,
00109                              stderr=subprocess.PIPE)
00110         assert p.pid
00111         # wait until /proc/pid/cmdline exists
00112         while True:
00113             with open('/proc/%i/cmdline' % p.pid) as fd:
00114                 if fd.read():
00115                     break
00116                 time.sleep(0.1)
00117         pr = apport.report.Report()
00118         pr.add_proc_info(pid=p.pid)
00119         self.assertEqual(pr.pid, p.pid)
00120         p.communicate(b'\n')
00121         self.assertEqual(pr['ProcCmdline'], 'cat /foo\ bar \\\\h \\\\\\ \\\\ -')
00122         self.assertEqual(pr['ExecutablePath'], '/bin/cat')
00123         self.assertTrue('InterpreterPath' not in pr)
00124         self.assertTrue('/bin/cat' in pr['ProcMaps'])
00125         self.assertTrue('[stack]' in pr['ProcMaps'])
00126 
00127         # check correct handling of executable symlinks
00128         assert os.path.islink('/bin/sh'), '/bin/sh needs to be a symlink for this test'
00129         p = subprocess.Popen(['sh'], stdin=subprocess.PIPE)
00130         assert p.pid
00131         # wait until /proc/pid/cmdline exists
00132         while True:
00133             with open('/proc/%i/cmdline' % p.pid) as fd:
00134                 if fd.read():
00135                     break
00136                 time.sleep(0.1)
00137         pr = apport.report.Report()
00138         pr.pid = p.pid
00139         pr.add_proc_info()
00140         p.communicate(b'exit\n')
00141         self.assertFalse('InterpreterPath' in pr, pr.get('InterpreterPath'))
00142         self.assertEqual(pr['ExecutablePath'], os.path.realpath('/bin/sh'))
00143         self.assertEqual(int(pr['ExecutableTimestamp']),
00144                          int(os.stat(os.path.realpath('/bin/sh')).st_mtime))
00145 
00146         # check correct handling of interpreted executables: shell
00147         p = subprocess.Popen(['zgrep', 'foo'], stdin=subprocess.PIPE)
00148         assert p.pid
00149         # wait until /proc/pid/cmdline exists
00150         while True:
00151             with open('/proc/%i/cmdline' % p.pid) as fd:
00152                 if fd.read():
00153                     break
00154                 time.sleep(0.1)
00155         pr = apport.report.Report()
00156         pr.add_proc_info(pid=p.pid)
00157         p.communicate(b'\n')
00158         self.assertTrue(pr['ExecutablePath'].endswith('bin/zgrep'))
00159         with open(pr['ExecutablePath']) as fd:
00160             self.assertEqual(pr['InterpreterPath'],
00161                              os.path.realpath(fd.readline().strip()[2:]))
00162         self.assertEqual(int(pr['ExecutableTimestamp']),
00163                          int(os.stat(pr['ExecutablePath']).st_mtime))
00164         self.assertTrue('[stack]' in pr['ProcMaps'])
00165 
00166         # check correct handling of interpreted executables: python
00167         (fd, testscript) = tempfile.mkstemp()
00168         os.write(fd, ('''#!/usr/bin/%s
00169 import sys
00170 sys.stdin.readline()
00171 ''' % os.getenv('PYTHON', 'python3')).encode('ascii'))
00172         os.close(fd)
00173         os.chmod(testscript, 0o755)
00174         p = subprocess.Popen([testscript], stdin=subprocess.PIPE,
00175                              stderr=subprocess.PIPE)
00176         assert p.pid
00177         # wait until /proc/pid/cmdline exists
00178         while True:
00179             with open('/proc/%i/cmdline' % p.pid) as fd:
00180                 if fd.read():
00181                     break
00182                 time.sleep(0.1)
00183         pr = apport.report.Report()
00184         pr.add_proc_info(pid=p.pid)
00185         p.communicate(b'\n')
00186         self.assertEqual(pr['ExecutablePath'], testscript)
00187         self.assertEqual(int(pr['ExecutableTimestamp']),
00188                          int(os.stat(testscript).st_mtime))
00189         os.unlink(testscript)
00190         self.assertTrue('python' in pr['InterpreterPath'])
00191         self.assertTrue('python' in pr['ProcMaps'])
00192         self.assertTrue('[stack]' in pr['ProcMaps'])
00193 
00194         # test process is gone, should complain about nonexisting PID
00195         self.assertRaises(ValueError, pr.add_proc_info, p.pid)
00196 
00197     def test_add_path_classification(self):
00198         '''classification of $PATH.'''
00199 
00200         # system default
00201         p = subprocess.Popen(['cat'], stdin=subprocess.PIPE,
00202                              env={'PATH': '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games'})
00203         time.sleep(0.1)
00204         r = apport.report.Report()
00205         r.add_proc_environ(pid=p.pid)
00206         p.communicate(b'')
00207         self.assertFalse('PATH' in r['ProcEnviron'],
00208                          'system default $PATH should be filtered out')
00209 
00210         # no user paths
00211         p = subprocess.Popen(['cat'], stdin=subprocess.PIPE,
00212                              env={'PATH': '/usr/sbin:/usr/bin:/sbin:/bin'})
00213         time.sleep(0.1)
00214         r = apport.report.Report()
00215         r.add_proc_environ(pid=p.pid)
00216         p.communicate(b'')
00217         self.assertTrue('PATH=(custom, no user)' in r['ProcEnviron'],
00218                         'PATH is customized without user paths')
00219 
00220         # user paths
00221         p = subprocess.Popen(['cat'], stdin=subprocess.PIPE,
00222                              env={'PATH': '/home/pitti:/usr/sbin:/usr/bin:/sbin:/bin'})
00223         time.sleep(0.1)
00224         r = apport.report.Report()
00225         r.add_proc_environ(pid=p.pid)
00226         p.communicate(b'')
00227         self.assertTrue('PATH=(custom, user)' in r['ProcEnviron'],
00228                         'PATH is customized with user paths')
00229 
00230     def test_check_interpreted(self):
00231         '''_check_interpreted().'''
00232 
00233         restore_root = False
00234         if os.getuid() == 0:
00235             # temporarily drop to normal user "mail"
00236             os.setresuid(8, 8, -1)
00237             restore_root = True
00238 
00239         try:
00240             # standard ELF binary
00241             f = tempfile.NamedTemporaryFile()
00242             pr = apport.report.Report()
00243             pr['ExecutablePath'] = '/usr/bin/gedit'
00244             pr['ProcStatus'] = 'Name:\tgedit'
00245             pr['ProcCmdline'] = 'gedit\0/' + f.name
00246             pr._check_interpreted()
00247             self.assertEqual(pr['ExecutablePath'], '/usr/bin/gedit')
00248             self.assertFalse('InterpreterPath' in pr)
00249             f.close()
00250 
00251             # bogus argv[0]
00252             pr = apport.report.Report()
00253             pr['ExecutablePath'] = '/bin/dash'
00254             pr['ProcStatus'] = 'Name:\tznonexisting'
00255             pr['ProcCmdline'] = 'nonexisting\0/foo'
00256             pr._check_interpreted()
00257             self.assertEqual(pr['ExecutablePath'], '/bin/dash')
00258             self.assertFalse('InterpreterPath' in pr)
00259 
00260             # standard sh script
00261             pr = apport.report.Report()
00262             pr['ExecutablePath'] = '/bin/dash'
00263             pr['ProcStatus'] = 'Name:\tzgrep'
00264             pr['ProcCmdline'] = '/bin/sh\0/bin/zgrep\0foo'
00265             pr._check_interpreted()
00266             self.assertEqual(pr['ExecutablePath'], '/bin/zgrep')
00267             self.assertEqual(pr['InterpreterPath'], '/bin/dash')
00268 
00269             # standard sh script when being called explicitly with interpreter
00270             pr = apport.report.Report()
00271             pr['ExecutablePath'] = '/bin/dash'
00272             pr['ProcStatus'] = 'Name:\tdash'
00273             pr['ProcCmdline'] = '/bin/sh\0/bin/zgrep\0foo'
00274             pr._check_interpreted()
00275             self.assertEqual(pr['ExecutablePath'], '/bin/zgrep')
00276             self.assertEqual(pr['InterpreterPath'], '/bin/dash')
00277 
00278             # special case mono scheme: beagled-helper (use zgrep to make the test
00279             # suite work if mono or beagle are not installed)
00280             pr = apport.report.Report()
00281             pr['ExecutablePath'] = '/usr/bin/mono'
00282             pr['ProcStatus'] = 'Name:\tzgrep'
00283             pr['ProcCmdline'] = 'zgrep\0--debug\0/bin/zgrep'
00284             pr._check_interpreted()
00285             self.assertEqual(pr['ExecutablePath'], '/bin/zgrep')
00286             self.assertEqual(pr['InterpreterPath'], '/usr/bin/mono')
00287 
00288             # special case mono scheme: banshee (use zgrep to make the test
00289             # suite work if mono or beagle are not installed)
00290             pr = apport.report.Report()
00291             pr['ExecutablePath'] = '/usr/bin/mono'
00292             pr['ProcStatus'] = 'Name:\tzgrep'
00293             pr['ProcCmdline'] = 'zgrep\0/bin/zgrep'
00294             pr._check_interpreted()
00295             self.assertEqual(pr['ExecutablePath'], '/bin/zgrep')
00296             self.assertEqual(pr['InterpreterPath'], '/usr/bin/mono')
00297 
00298             # fail on files we shouldn't have access to when name!=argv[0]
00299             pr = apport.report.Report()
00300             pr['ExecutablePath'] = '/usr/bin/python'
00301             pr['ProcStatus'] = 'Name:\tznonexisting'
00302             pr['ProcCmdline'] = 'python\0/etc/shadow'
00303             pr._check_interpreted()
00304             self.assertEqual(pr['ExecutablePath'], '/usr/bin/python')
00305             self.assertFalse('InterpreterPath' in pr)
00306 
00307             # succeed on files we should have access to when name!=argv[0]
00308             pr = apport.report.Report()
00309             pr['ExecutablePath'] = '/usr/bin/python'
00310             pr['ProcStatus'] = 'Name:\tznonexisting'
00311             pr['ProcCmdline'] = 'python\0/etc/passwd'
00312             pr._check_interpreted()
00313             self.assertEqual(pr['InterpreterPath'], '/usr/bin/python')
00314             self.assertEqual(pr['ExecutablePath'], '/etc/passwd')
00315 
00316             # fail on files we shouldn't have access to when name==argv[0]
00317             pr = apport.report.Report()
00318             pr['ExecutablePath'] = '/usr/bin/python'
00319             pr['ProcStatus'] = 'Name:\tshadow'
00320             pr['ProcCmdline'] = '../etc/shadow'
00321             pr._check_interpreted()
00322             self.assertEqual(pr['ExecutablePath'], '/usr/bin/python')
00323             self.assertFalse('InterpreterPath' in pr)
00324 
00325             # succeed on files we should have access to when name==argv[0]
00326             pr = apport.report.Report()
00327             pr['ExecutablePath'] = '/usr/bin/python'
00328             pr['ProcStatus'] = 'Name:\tpasswd'
00329             pr['ProcCmdline'] = '../etc/passwd'
00330             pr._check_interpreted()
00331             self.assertEqual(pr['InterpreterPath'], '/usr/bin/python')
00332             self.assertEqual(pr['ExecutablePath'], '/bin/../etc/passwd')
00333 
00334             # interactive python process
00335             pr = apport.report.Report()
00336             pr['ExecutablePath'] = '/usr/bin/python'
00337             pr['ProcStatus'] = 'Name:\tpython'
00338             pr['ProcCmdline'] = 'python'
00339             pr._check_interpreted()
00340             self.assertEqual(pr['ExecutablePath'], '/usr/bin/python')
00341             self.assertFalse('InterpreterPath' in pr)
00342 
00343             # python script (abuse /bin/bash since it must exist)
00344             pr = apport.report.Report()
00345             pr['ExecutablePath'] = '/usr/bin/python'
00346             pr['ProcStatus'] = 'Name:\tbash'
00347             pr['ProcCmdline'] = 'python\0/bin/bash'
00348             pr._check_interpreted()
00349             self.assertEqual(pr['InterpreterPath'], '/usr/bin/python')
00350             self.assertEqual(pr['ExecutablePath'], '/bin/bash')
00351 
00352             # python script with options (abuse /bin/bash since it must exist)
00353             pr = apport.report.Report()
00354             pr['ExecutablePath'] = '/usr/bin/python'
00355             pr['ProcStatus'] = 'Name:\tbash'
00356             pr['ProcCmdline'] = 'python\0-OO\0/bin/bash'
00357             pr._check_interpreted()
00358             self.assertEqual(pr['InterpreterPath'], '/usr/bin/python')
00359             self.assertEqual(pr['ExecutablePath'], '/bin/bash')
00360 
00361             # python script with a versioned interpreter
00362             pr = apport.report.Report()
00363             pr['ExecutablePath'] = '/usr/bin/python2.7'
00364             pr['ProcStatus'] = 'Name:\tbash'
00365             pr['ProcCmdline'] = '/usr/bin/python\0/bin/bash'
00366             pr._check_interpreted()
00367             self.assertEqual(pr['InterpreterPath'], '/usr/bin/python2.7')
00368             self.assertEqual(pr['ExecutablePath'], '/bin/bash')
00369 
00370             # python script through -m
00371             pr = apport.report.Report()
00372             pr['ExecutablePath'] = '/usr/bin/python2.7'
00373             pr['ProcStatus'] = 'Name:\tpython'
00374             pr['ProcCmdline'] = 'python\0-tt\0-m\0apport/report\0-v'
00375             pr._check_interpreted()
00376             self.assertEqual(pr['InterpreterPath'], '/usr/bin/python2.7')
00377             self.assertTrue('report' in pr['ExecutablePath'],
00378                             'expecting "report" in ExecutablePath "%s"' % pr['ExecutablePath'])
00379         finally:
00380             if restore_root:
00381                 os.setresuid(0, 0, -1)
00382 
00383     def test_check_interpreted_twistd(self):
00384         '''_check_interpreted() for programs ran through twistd'''
00385 
00386         # LP#761374
00387         pr = apport.report.Report()
00388         pr['ExecutablePath'] = '/usr/bin/python2.7'
00389         pr['ProcStatus'] = 'Name:\ttwistd'
00390         pr['ProcCmdline'] = '/usr/bin/python\0/usr/bin/twistd\0--uid\0root\0--gid\0root\0--pidfile\0/var/run/nanny.pid\0-r\0glib2\0--logfile\0/var/log/nanny.log\0-y\0/usr/share/nanny/daemon/nanny.tap'
00391         pr._check_interpreted()
00392         self.assertEqual(pr['ExecutablePath'], '/usr/share/nanny/daemon/nanny.tap')
00393         self.assertEqual(pr['InterpreterPath'], '/usr/bin/twistd')
00394 
00395         # LP#625039
00396         pr = apport.report.Report()
00397         pr['ExecutablePath'] = '/usr/bin/python2.7'
00398         pr['ProcStatus'] = 'Name:\ttwistd'
00399         pr['ProcCmdline'] = '/usr/bin/python\0/usr/bin/twistd\0--pidfile=/var/run/apt-p2p//apt-p2p.pid\0--rundir=/var/run/apt-p2p/\0--python=/usr/sbin/apt-p2p\0--logfile=/var/log/apt-p2p.log\0--no_save'
00400         pr._check_interpreted()
00401         self.assertEqual(pr['ExecutablePath'], '/usr/sbin/apt-p2p')
00402         self.assertEqual(pr['InterpreterPath'], '/usr/bin/twistd')
00403 
00404         # somewhere from LP#755025
00405         pr = apport.report.Report()
00406         pr['ExecutablePath'] = '/usr/bin/python2.7'
00407         pr['ProcStatus'] = 'Name:\ttwistd'
00408         pr['ProcCmdline'] = '/usr/bin/python\0/usr/bin/twistd\0-r\0gtk2\0--pidfile\0/tmp/vmc.pid\0-noy\0/usr/share/vodafone-mobile-connect/gtk-tap.py\0-l\0/dev/null'
00409         pr._check_interpreted()
00410         self.assertEqual(pr['ExecutablePath'], '/usr/share/vodafone-mobile-connect/gtk-tap.py')
00411         self.assertEqual(pr['InterpreterPath'], '/usr/bin/twistd')
00412 
00413         # LP#725383 -> not practical to determine file here
00414         pr = apport.report.Report()
00415         pr['ExecutablePath'] = '/usr/bin/python2.7'
00416         pr['ProcStatus'] = 'Name:\ttwistd'
00417         pr['ProcCmdline'] = '/usr/bin/python\0/usr/bin/twistd\0--pidfile=/var/run/poker-network-server.pid\0--logfile=/var/log/poker-network-server.log\0--no_save\0--reactor=poll\0pokerserver'
00418         pr._check_interpreted()
00419         self.assertTrue('ExecutablePath' in pr)
00420         self.assertTrue('UnreportableReason' in pr)
00421         self.assertEqual(pr['InterpreterPath'], '/usr/bin/twistd')
00422 
00423     @classmethod
00424     def _generate_sigsegv_report(klass, file=None, signal='11', code='''
00425 int f(x) {
00426     int* p = 0; *p = x;
00427     return x+1;
00428 }
00429 int main() { return f(42); }
00430 '''):
00431         '''Create a test executable which will die with a SIGSEGV, generate a
00432         core dump for it, create a problem report with those two arguments
00433         (ExecutablePath and CoreDump) and call add_gdb_info().
00434 
00435         If file is given, the report is written into it. Return the apport.report.Report.'''
00436 
00437         workdir = None
00438         orig_cwd = os.getcwd()
00439         pr = apport.report.Report()
00440         try:
00441             workdir = tempfile.mkdtemp()
00442             atexit.register(shutil.rmtree, workdir)
00443             os.chdir(workdir)
00444 
00445             # create a test executable
00446             with open('crash.c', 'w') as fd:
00447                 fd.write(code)
00448             assert subprocess.call(['gcc', '-g', 'crash.c', '-o', 'crash']) == 0
00449             assert os.path.exists('crash')
00450 
00451             # call it through gdb and dump core
00452             subprocess.call(['gdb', '--batch', '--ex', 'run', '--ex',
00453                              'generate-core-file core', './crash'], stdout=subprocess.PIPE)
00454             assert os.path.exists('core')
00455             assert subprocess.call(['readelf', '-n', 'core'],
00456                                    stdout=subprocess.PIPE) == 0
00457 
00458             pr['ExecutablePath'] = os.path.join(workdir, 'crash')
00459             pr['CoreDump'] = (os.path.join(workdir, 'core'),)
00460             pr['Signal'] = signal
00461 
00462             pr.add_gdb_info()
00463             if file:
00464                 pr.write(file)
00465                 file.flush()
00466         finally:
00467             os.chdir(orig_cwd)
00468 
00469         return pr
00470 
00471     def _validate_gdb_fields(self, pr):
00472         self.assertTrue('Stacktrace' in pr)
00473         self.assertTrue('ThreadStacktrace' in pr)
00474         self.assertTrue('StacktraceTop' in pr)
00475         self.assertTrue('Registers' in pr)
00476         self.assertTrue('Disassembly' in pr)
00477         self.assertTrue('(no debugging symbols found)' not in pr['Stacktrace'])
00478         self.assertTrue('Core was generated by' not in pr['Stacktrace'], pr['Stacktrace'])
00479         self.assertTrue(not re.match(r'(?s)(^|.*\n)#0  [^\n]+\n#0  ',
00480                                      pr['Stacktrace']))
00481         self.assertTrue('#0  0x' in pr['Stacktrace'])
00482         self.assertTrue('#1  0x' in pr['Stacktrace'])
00483         self.assertTrue('#0  0x' in pr['ThreadStacktrace'])
00484         self.assertTrue('#1  0x' in pr['ThreadStacktrace'])
00485         self.assertTrue('Thread 1 (' in pr['ThreadStacktrace'])
00486         self.assertTrue(len(pr['StacktraceTop'].splitlines()) <= 5)
00487 
00488     def test_add_gdb_info(self):
00489         '''add_gdb_info() with core dump file reference.'''
00490 
00491         pr = apport.report.Report()
00492         # should not throw an exception for missing fields
00493         pr.add_gdb_info()
00494 
00495         # normal crash
00496         pr = self._generate_sigsegv_report()
00497         self._validate_gdb_fields(pr)
00498         self.assertEqual(pr['StacktraceTop'], 'f (x=42) at crash.c:3\nmain () at crash.c:6', pr['StacktraceTop'])
00499         self.assertFalse('AssertionMessage' in pr)
00500 
00501         # crash where gdb generates output on stderr
00502         pr = self._generate_sigsegv_report(code='''
00503 int main() {
00504     void     (*function)(void);
00505     function = 0;
00506     function();
00507 }
00508 ''')
00509         self._validate_gdb_fields(pr)
00510         self.assertTrue('Cannot access memory at address 0x0' in pr['Disassembly'], pr['Disassembly'])
00511         self.assertFalse('AssertionMessage' in pr)
00512 
00513     def test_add_gdb_info_load(self):
00514         '''add_gdb_info() with inline core dump.'''
00515 
00516         rep = tempfile.NamedTemporaryFile()
00517         self._generate_sigsegv_report(rep)
00518         rep.seek(0)
00519 
00520         pr = apport.report.Report()
00521         with open(rep.name, 'rb') as f:
00522             pr.load(f)
00523         pr.add_gdb_info()
00524 
00525         self._validate_gdb_fields(pr)
00526 
00527     def test_add_zz_parse_segv_details(self):
00528         '''parse-segv produces sensible results'''
00529         rep = tempfile.NamedTemporaryFile()
00530         self._generate_sigsegv_report(rep)
00531         rep.seek(0)
00532 
00533         pr = apport.report.Report()
00534         with open(rep.name, 'rb') as f:
00535             pr.load(f)
00536         pr['Signal'] = '1'
00537         pr.add_hooks_info('fake_ui')
00538         self.assertTrue('SegvAnalysis' not in pr.keys())
00539 
00540         pr = apport.report.Report()
00541         with open(rep.name, 'rb') as f:
00542             pr.load(f)
00543         pr.add_hooks_info('fake_ui')
00544         self.assertTrue('Skipped: missing required field "Architecture"' in pr['SegvAnalysis'],
00545                         pr['SegvAnalysis'])
00546 
00547         pr.add_os_info()
00548         pr.add_hooks_info('fake_ui')
00549         self.assertTrue('Skipped: missing required field "ProcMaps"' in pr['SegvAnalysis'],
00550                         pr['SegvAnalysis'])
00551 
00552         pr.add_proc_info()
00553         pr.add_hooks_info('fake_ui')
00554         self.assertTrue('not located in a known VMA region' in pr['SegvAnalysis'],
00555                         pr['SegvAnalysis'])
00556 
00557     def test_add_gdb_info_script(self):
00558         '''add_gdb_info() with a script.'''
00559 
00560         (fd, script) = tempfile.mkstemp()
00561         coredump = os.path.join(os.path.dirname(script), 'core')
00562         assert not os.path.exists(coredump)
00563         try:
00564             os.close(fd)
00565 
00566             # create a test script which produces a core dump for us
00567             with open(script, 'w') as fd:
00568                 fd.write('''#!/bin/bash
00569 cd `dirname $0`
00570 ulimit -c unlimited
00571 kill -SEGV $$
00572 ''')
00573             os.chmod(script, 0o755)
00574 
00575             # call script and verify that it gives us a proper ELF core dump
00576             assert subprocess.call([script]) != 0
00577             subprocess.check_call(['sync'])
00578             assert subprocess.call(['readelf', '-n', coredump],
00579                                    stdout=subprocess.PIPE) == 0
00580 
00581             pr = apport.report.Report()
00582             pr['InterpreterPath'] = '/bin/bash'
00583             pr['ExecutablePath'] = script
00584             pr['CoreDump'] = (coredump,)
00585             pr.add_gdb_info()
00586         finally:
00587             os.unlink(coredump)
00588             os.unlink(script)
00589 
00590         self._validate_gdb_fields(pr)
00591         self.assertTrue('libc.so' in pr['Stacktrace'] or 'in execute_command' in pr['Stacktrace'])
00592 
00593     def test_add_gdb_info_abort(self):
00594         '''add_gdb_info() with SIGABRT/assert()
00595 
00596         If these come from an assert(), the report should have the assertion
00597         message. Otherwise it should be marked as not reportable.
00598         '''
00599         # abort with assert
00600         (fd, script) = tempfile.mkstemp()
00601         assert not os.path.exists('core')
00602         try:
00603             os.close(fd)
00604 
00605             # create a test script which produces a core dump for us
00606             with open(script, 'w') as fd:
00607                 fd.write('''#!/bin/sh
00608 gcc -o $0.bin -x c - <<EOF
00609 #include <assert.h>
00610 int main() { assert(1 < 0); }
00611 EOF
00612 ulimit -c unlimited
00613 $0.bin 2>/dev/null
00614 ''')
00615             os.chmod(script, 0o755)
00616 
00617             # call script and verify that it gives us a proper ELF core dump
00618             assert subprocess.call([script]) != 0
00619             subprocess.check_call(['sync'])
00620             assert subprocess.call(['readelf', '-n', 'core'],
00621                                    stdout=subprocess.PIPE) == 0
00622 
00623             pr = apport.report.Report()
00624             pr['ExecutablePath'] = script + '.bin'
00625             pr['CoreDump'] = ('core',)
00626             pr.add_gdb_info()
00627         finally:
00628             os.unlink(script)
00629             os.unlink(script + '.bin')
00630             os.unlink('core')
00631 
00632         self._validate_gdb_fields(pr)
00633         self.assertTrue("<stdin>:2: main: Assertion `1 < 0' failed." in
00634                         pr['AssertionMessage'], pr['AssertionMessage'])
00635         self.assertFalse(pr['AssertionMessage'].startswith('$'), pr['AssertionMessage'])
00636         self.assertFalse('= 0x' in pr['AssertionMessage'], pr['AssertionMessage'])
00637         self.assertFalse(pr['AssertionMessage'].endswith('\\n'), pr['AssertionMessage'])
00638 
00639         # abort with internal error
00640         (fd, script) = tempfile.mkstemp()
00641         assert not os.path.exists('core')
00642         try:
00643             os.close(fd)
00644 
00645             # create a test script which produces a core dump for us
00646             with open(script, 'w') as fd:
00647                 fd.write('''#!/bin/sh
00648 gcc -O2 -D_FORTIFY_SOURCE=2 -o $0.bin -x c - <<EOF
00649 #include <string.h>
00650 int main(int argc, char *argv[]) {
00651     char buf[8];
00652     strcpy(buf, argv[1]);
00653     return 0;
00654 }
00655 EOF
00656 ulimit -c unlimited
00657 LIBC_FATAL_STDERR_=1 $0.bin aaaaaaaaaaaaaaaa 2>/dev/null
00658 ''')
00659             os.chmod(script, 0o755)
00660 
00661             # call script and verify that it gives us a proper ELF core dump
00662             assert subprocess.call([script]) != 0
00663             subprocess.check_call(['sync'])
00664             assert subprocess.call(['readelf', '-n', 'core'],
00665                                    stdout=subprocess.PIPE) == 0
00666 
00667             pr = apport.report.Report()
00668             pr['ExecutablePath'] = script + '.bin'
00669             pr['CoreDump'] = ('core',)
00670             pr.add_gdb_info()
00671         finally:
00672             os.unlink(script)
00673             os.unlink(script + '.bin')
00674             os.unlink('core')
00675 
00676         self._validate_gdb_fields(pr)
00677         self.assertTrue("** buffer overflow detected ***: %s.bin terminated" % (script) in
00678                         pr['AssertionMessage'], pr['AssertionMessage'])
00679         self.assertFalse(pr['AssertionMessage'].startswith('$'), pr['AssertionMessage'])
00680         self.assertFalse('= 0x' in pr['AssertionMessage'], pr['AssertionMessage'])
00681         self.assertFalse(pr['AssertionMessage'].endswith('\\n'), pr['AssertionMessage'])
00682 
00683         # abort without assertion
00684         (fd, script) = tempfile.mkstemp()
00685         assert not os.path.exists('core')
00686         try:
00687             os.close(fd)
00688 
00689             # create a test script which produces a core dump for us
00690             with open(script, 'w') as fd:
00691                 fd.write('''#!/bin/sh
00692 gcc -o $0.bin -x c - <<EOF
00693 #include <stdlib.h>
00694 int main() { abort(); }
00695 EOF
00696 ulimit -c unlimited
00697 $0.bin 2>/dev/null
00698 ''')
00699             os.chmod(script, 0o755)
00700 
00701             # call script and verify that it gives us a proper ELF core dump
00702             assert subprocess.call([script]) != 0
00703             subprocess.check_call(['sync'])
00704             assert subprocess.call(['readelf', '-n', 'core'],
00705                                    stdout=subprocess.PIPE) == 0
00706 
00707             pr = apport.report.Report()
00708             pr['ExecutablePath'] = script + '.bin'
00709             pr['CoreDump'] = ('core',)
00710             pr.add_gdb_info()
00711         finally:
00712             os.unlink(script)
00713             os.unlink(script + '.bin')
00714             os.unlink('core')
00715 
00716         self._validate_gdb_fields(pr)
00717         self.assertFalse('AssertionMessage' in pr, pr.get('AssertionMessage'))
00718 
00719     def test_search_bug_patterns(self):
00720         '''search_bug_patterns().'''
00721 
00722         patterns = tempfile.NamedTemporaryFile(prefix='apport-')
00723         # create some test patterns
00724         patterns.write(b'''<?xml version="1.0"?>
00725 <patterns>
00726     <pattern url="http://bugtracker.net/bugs/1">
00727         <re key="Package">^bash </re>
00728         <re key="Foo">ba.*r</re>
00729     </pattern>
00730     <pattern url="http://bugtracker.net/bugs/2">
00731         <re key="Package">^bash 1-2$</re>
00732         <re key="Foo">write_(hello|goodbye)</re>
00733     </pattern>
00734     <pattern url="http://bugtracker.net/bugs/3">
00735         <re key="Package">^coreutils </re>
00736         <re key="Bar">^1$</re>
00737     </pattern>
00738     <pattern url="http://bugtracker.net/bugs/4">
00739         <re key="Package">^coreutils </re>
00740         <re></re>
00741         <re key="Bar">*</re> <!-- invalid RE -->
00742         <re key="broken">+[1^</re>
00743     </pattern>
00744     <pattern url="http://bugtracker.net/bugs/5">
00745         <re key="SourcePackage">^bazaar$</re>
00746         <re key="LogFile">AssertionError</re>
00747     </pattern>
00748 </patterns>''')
00749         patterns.flush()
00750 
00751         # invalid XML
00752         invalid = tempfile.NamedTemporaryFile(prefix='apport-')
00753         invalid.write(b'''<?xml version="1.0"?>
00754 </patterns>''')
00755         invalid.flush()
00756 
00757         # create some reports
00758         r_bash = apport.report.Report()
00759         r_bash['Package'] = 'bash 1-2'
00760         r_bash['Foo'] = 'bazaar'
00761 
00762         r_bazaar = apport.report.Report()
00763         r_bazaar['Package'] = 'bazaar 2-1'
00764         r_bazaar['SourcePackage'] = 'bazaar'
00765         r_bazaar['LogFile'] = 'AssertionError'
00766 
00767         r_coreutils = apport.report.Report()
00768         r_coreutils['Package'] = 'coreutils 1'
00769         r_coreutils['Bar'] = '1'
00770 
00771         r_invalid = apport.report.Report()
00772         r_invalid['Package'] = 'invalid 1'
00773 
00774         pattern_url = 'file://' + patterns.name
00775 
00776         # positive match cases
00777         self.assertEqual(r_bash.search_bug_patterns(pattern_url),
00778                          'http://bugtracker.net/bugs/1')
00779         r_bash['Foo'] = 'write_goodbye'
00780         self.assertEqual(r_bash.search_bug_patterns(pattern_url),
00781                          'http://bugtracker.net/bugs/2')
00782         self.assertEqual(r_coreutils.search_bug_patterns(pattern_url),
00783                          'http://bugtracker.net/bugs/3')
00784         self.assertEqual(r_bazaar.search_bug_patterns(pattern_url),
00785                          'http://bugtracker.net/bugs/5')
00786 
00787         # also works for CompressedValues
00788         r_bash_compressed = r_bash.copy()
00789         r_bash_compressed['Foo'] = problem_report.CompressedValue(b'bazaar')
00790         self.assertEqual(r_bash_compressed.search_bug_patterns(pattern_url),
00791                          'http://bugtracker.net/bugs/1')
00792 
00793         # negative match cases
00794         r_bash['Package'] = 'bash-static 1-2'
00795         self.assertEqual(r_bash.search_bug_patterns(pattern_url), None)
00796         r_bash['Package'] = 'bash 1-21'
00797         self.assertEqual(r_bash.search_bug_patterns(pattern_url), None,
00798                          'does not match on wrong bash version')
00799         r_bash['Foo'] = 'zz'
00800         self.assertEqual(r_bash.search_bug_patterns(pattern_url), None,
00801                          'does not match on wrong Foo value')
00802         r_coreutils['Bar'] = '11'
00803         self.assertEqual(r_coreutils.search_bug_patterns(pattern_url), None,
00804                          'does not match on wrong Bar value')
00805         r_bazaar['SourcePackage'] = 'launchpad'
00806         self.assertEqual(r_bazaar.search_bug_patterns(pattern_url), None,
00807                          'does not match on wrong source package')
00808         r_bazaar['LogFile'] = ''
00809         self.assertEqual(r_bazaar.search_bug_patterns(pattern_url), None,
00810                          'does not match on empty attribute')
00811 
00812         # various errors to check for robustness (no exceptions, just None
00813         # return value)
00814         del r_coreutils['Bar']
00815         self.assertEqual(r_coreutils.search_bug_patterns(pattern_url), None,
00816                          'does not match on nonexisting key')
00817         self.assertEqual(r_invalid.search_bug_patterns('file://' + invalid.name), None,
00818                          'gracefully handles invalid XML')
00819         r_coreutils['Package'] = 'other 2'
00820         self.assertEqual(r_bash.search_bug_patterns('file:///nonexisting/directory/'), None,
00821                          'gracefully handles nonexisting base path')
00822         # existing host, but no bug patterns
00823         self.assertEqual(r_bash.search_bug_patterns('http://security.ubuntu.com/'), None,
00824                          'gracefully handles base path without bug patterns')
00825         # nonexisting host
00826         self.assertEqual(r_bash.search_bug_patterns('http://nonexisting.domain/'), None,
00827                          'gracefully handles nonexisting URL domain')
00828 
00829     def test_add_hooks_info(self):
00830         '''add_hooks_info().'''
00831 
00832         orig_hook_dir = apport.report._hook_dir
00833         apport.report._hook_dir = tempfile.mkdtemp()
00834         orig_common_hook_dir = apport.report._common_hook_dir
00835         apport.report._common_hook_dir = tempfile.mkdtemp()
00836         try:
00837             with open(os.path.join(apport.report._hook_dir, 'foo.py'), 'w') as fd:
00838                 fd.write('''
00839 import sys
00840 def add_info(report):
00841     report['Field1'] = 'Field 1'
00842     report['Field2'] = 'Field 2\\nBla'
00843     if 'Spethial' in report:
00844         raise StopIteration
00845 ''')
00846 
00847             with open(os.path.join(apport.report._common_hook_dir, 'foo1.py'), 'w') as fd:
00848                 fd.write('''
00849 def add_info(report):
00850     report['CommonField1'] = 'CommonField 1'
00851     if report['Package'] == 'commonspethial':
00852         raise StopIteration
00853 ''')
00854             with open(os.path.join(apport.report._common_hook_dir, 'foo2.py'), 'w') as fd:
00855                 fd.write('''
00856 def add_info(report):
00857     report['CommonField2'] = 'CommonField 2'
00858 ''')
00859             with open(os.path.join(apport.report._common_hook_dir, 'foo3.py'), 'w') as fd:
00860                 fd.write('''
00861 def add_info(report, ui):
00862     report['CommonField3'] = str(ui)
00863 ''')
00864 
00865             # should only catch .py files
00866             with open(os.path.join(apport.report._common_hook_dir, 'notme'), 'w') as fd:
00867                 fd.write('''
00868 def add_info(report):
00869     report['BadField'] = 'XXX'
00870 ''')
00871             r = apport.report.Report()
00872             r['Package'] = 'bar'
00873             # should not throw any exceptions
00874             self.assertEqual(r.add_hooks_info('fake_ui'), False)
00875             self.assertEqual(set(r.keys()),
00876                              set(['ProblemType', 'Date', 'Package',
00877                                   'CommonField1', 'CommonField2',
00878                                   'CommonField3']),
00879                              'report has required fields')
00880 
00881             r = apport.report.Report()
00882             r['Package'] = 'baz 1.2-3'
00883             # should not throw any exceptions
00884             self.assertEqual(r.add_hooks_info('fake_ui'), False)
00885             self.assertEqual(set(r.keys()),
00886                              set(['ProblemType', 'Date', 'Package',
00887                                   'CommonField1', 'CommonField2',
00888                                   'CommonField3']),
00889                              'report has required fields')
00890 
00891             r = apport.report.Report()
00892             r['Package'] = 'foo'
00893             self.assertEqual(r.add_hooks_info('fake_ui'), False)
00894             self.assertEqual(set(r.keys()),
00895                              set(['ProblemType', 'Date', 'Package', 'Field1',
00896                                   'Field2', 'CommonField1', 'CommonField2',
00897                                   'CommonField3']),
00898                              'report has required fields')
00899             self.assertEqual(r['Field1'], 'Field 1')
00900             self.assertEqual(r['Field2'], 'Field 2\nBla')
00901             self.assertEqual(r['CommonField1'], 'CommonField 1')
00902             self.assertEqual(r['CommonField2'], 'CommonField 2')
00903             self.assertEqual(r['CommonField3'], 'fake_ui')
00904 
00905             r = apport.report.Report()
00906             r['Package'] = 'foo 4.5-6'
00907             self.assertEqual(r.add_hooks_info('fake_ui'), False)
00908             self.assertEqual(set(r.keys()),
00909                              set(['ProblemType', 'Date', 'Package', 'Field1',
00910                                   'Field2', 'CommonField1', 'CommonField2',
00911                                   'CommonField3']),
00912                              'report has required fields')
00913             self.assertEqual(r['Field1'], 'Field 1')
00914             self.assertEqual(r['Field2'], 'Field 2\nBla')
00915             self.assertEqual(r['CommonField1'], 'CommonField 1')
00916             self.assertEqual(r['CommonField2'], 'CommonField 2')
00917 
00918             # test hook abort
00919             r['Spethial'] = '1'
00920             self.assertEqual(r.add_hooks_info('fake_ui'), True)
00921             r = apport.report.Report()
00922             r['Package'] = 'commonspethial'
00923             self.assertEqual(r.add_hooks_info('fake_ui'), True)
00924 
00925             # source package hook
00926             with open(os.path.join(apport.report._hook_dir, 'source_foo.py'), 'w') as fd:
00927                 fd.write('''
00928 def add_info(report, ui):
00929     report['Field1'] = 'Field 1'
00930     report['Field2'] = 'Field 2\\nBla'
00931     if report['Package'] == 'spethial':
00932         raise StopIteration
00933 ''')
00934             r = apport.report.Report()
00935             r['SourcePackage'] = 'foo'
00936             r['Package'] = 'libfoo 3'
00937             self.assertEqual(r.add_hooks_info('fake_ui'), False)
00938             self.assertEqual(set(r.keys()),
00939                              set(['ProblemType', 'Date', 'Package',
00940                                   'SourcePackage', 'Field1', 'Field2',
00941                                   'CommonField1', 'CommonField2',
00942                                   'CommonField3']),
00943                              'report has required fields')
00944             self.assertEqual(r['Field1'], 'Field 1')
00945             self.assertEqual(r['Field2'], 'Field 2\nBla')
00946             self.assertEqual(r['CommonField1'], 'CommonField 1')
00947             self.assertEqual(r['CommonField2'], 'CommonField 2')
00948             self.assertEqual(r['CommonField3'], 'fake_ui')
00949 
00950             # test hook abort
00951             r['Package'] = 'spethial'
00952             self.assertEqual(r.add_hooks_info('fake_ui'), True)
00953 
00954         finally:
00955             shutil.rmtree(apport.report._hook_dir)
00956             shutil.rmtree(apport.report._common_hook_dir)
00957             apport.report._hook_dir = orig_hook_dir
00958             apport.report._common_hook_dir = orig_common_hook_dir
00959 
00960     def test_ignoring(self):
00961         '''mark_ignore() and check_ignored().'''
00962 
00963         orig_ignore_file = apport.report.apport.report._ignore_file
00964         workdir = tempfile.mkdtemp()
00965         apport.report.apport.report._ignore_file = os.path.join(workdir, 'ignore.xml')
00966         try:
00967             with open(os.path.join(workdir, 'bash'), 'w') as fd:
00968                 fd.write('bash')
00969             with open(os.path.join(workdir, 'crap'), 'w') as fd:
00970                 fd.write('crap')
00971 
00972             bash_rep = apport.report.Report()
00973             bash_rep['ExecutablePath'] = os.path.join(workdir, 'bash')
00974             crap_rep = apport.report.Report()
00975             crap_rep['ExecutablePath'] = os.path.join(workdir, 'crap')
00976             # must be able to deal with executables that do not exist any more
00977             cp_rep = apport.report.Report()
00978             cp_rep['ExecutablePath'] = os.path.join(workdir, 'cp')
00979 
00980             # no ignores initially
00981             self.assertEqual(bash_rep.check_ignored(), False)
00982             self.assertEqual(crap_rep.check_ignored(), False)
00983             self.assertEqual(cp_rep.check_ignored(), False)
00984 
00985             # ignore crap now
00986             crap_rep.mark_ignore()
00987             self.assertEqual(bash_rep.check_ignored(), False)
00988             self.assertEqual(crap_rep.check_ignored(), True)
00989             self.assertEqual(cp_rep.check_ignored(), False)
00990 
00991             # ignore bash now
00992             bash_rep.mark_ignore()
00993             self.assertEqual(bash_rep.check_ignored(), True)
00994             self.assertEqual(crap_rep.check_ignored(), True)
00995             self.assertEqual(cp_rep.check_ignored(), False)
00996 
00997             # poke crap so that it has a newer timestamp
00998             time.sleep(1)
00999             with open(os.path.join(workdir, 'crap'), 'w') as fd:
01000                 fd.write('crapnew')
01001             self.assertEqual(bash_rep.check_ignored(), True)
01002             self.assertEqual(crap_rep.check_ignored(), False)
01003             self.assertEqual(cp_rep.check_ignored(), False)
01004 
01005             # do not complain about an empty ignore file
01006             with open(apport.report.apport.report._ignore_file, 'w') as fd:
01007                 fd.write('')
01008             self.assertEqual(bash_rep.check_ignored(), False)
01009             self.assertEqual(crap_rep.check_ignored(), False)
01010             self.assertEqual(cp_rep.check_ignored(), False)
01011 
01012             # does not crash if the executable went away under our feet
01013             crap_rep['ExecutablePath'] = '/non existing'
01014             crap_rep.mark_ignore()
01015             self.assertEqual(os.path.getsize(apport.report.apport.report._ignore_file), 0)
01016         finally:
01017             shutil.rmtree(workdir)
01018             apport.report.apport.report._ignore_file = orig_ignore_file
01019 
01020     def test_blacklisting(self):
01021         '''check_ignored() for system-wise blacklist.'''
01022 
01023         orig_blacklist_dir = apport.report._blacklist_dir
01024         apport.report._blacklist_dir = tempfile.mkdtemp()
01025         orig_ignore_file = apport.report._ignore_file
01026         apport.report._ignore_file = '/nonexistant'
01027         try:
01028             bash_rep = apport.report.Report()
01029             bash_rep['ExecutablePath'] = '/bin/bash'
01030             crap_rep = apport.report.Report()
01031             crap_rep['ExecutablePath'] = '/bin/crap'
01032 
01033             # no ignores initially
01034             self.assertEqual(bash_rep.check_ignored(), False)
01035             self.assertEqual(crap_rep.check_ignored(), False)
01036 
01037             # should not stumble over comments
01038             with open(os.path.join(apport.report._blacklist_dir, 'README'), 'w') as fd:
01039                 fd.write('# Ignore file\n#/bin/bash\n')
01040 
01041             # no ignores on nonmatching paths
01042             with open(os.path.join(apport.report._blacklist_dir, 'bl1'), 'w') as fd:
01043                 fd.write('/bin/bas\n/bin/bashh\nbash\nbin/bash\n')
01044             self.assertEqual(bash_rep.check_ignored(), False)
01045             self.assertEqual(crap_rep.check_ignored(), False)
01046 
01047             # ignore crap now
01048             with open(os.path.join(apport.report._blacklist_dir, 'bl_2'), 'w') as fd:
01049                 fd.write('/bin/crap\n')
01050             self.assertEqual(bash_rep.check_ignored(), False)
01051             self.assertEqual(crap_rep.check_ignored(), True)
01052 
01053             # ignore bash now
01054             with open(os.path.join(apport.report._blacklist_dir, 'bl1'), 'a') as fd:
01055                 fd.write('/bin/bash\n')
01056             self.assertEqual(bash_rep.check_ignored(), True)
01057             self.assertEqual(crap_rep.check_ignored(), True)
01058         finally:
01059             shutil.rmtree(apport.report._blacklist_dir)
01060             apport.report._blacklist_dir = orig_blacklist_dir
01061             apport.report._ignore_file = orig_ignore_file
01062 
01063     def test_whitelisting(self):
01064         '''check_ignored() for system-wise whitelist.'''
01065 
01066         orig_whitelist_dir = apport.report._whitelist_dir
01067         apport.report._whitelist_dir = tempfile.mkdtemp()
01068         orig_ignore_file = apport.report.apport.report._ignore_file
01069         apport.report.apport.report._ignore_file = '/nonexistant'
01070         try:
01071             bash_rep = apport.report.Report()
01072             bash_rep['ExecutablePath'] = '/bin/bash'
01073             crap_rep = apport.report.Report()
01074             crap_rep['ExecutablePath'] = '/bin/crap'
01075 
01076             # no ignores without any whitelist
01077             self.assertEqual(bash_rep.check_ignored(), False)
01078             self.assertEqual(crap_rep.check_ignored(), False)
01079 
01080             # should not stumble over comments
01081             with open(os.path.join(apport.report._whitelist_dir, 'README'), 'w') as fd:
01082                 fd.write('# Ignore file\n#/bin/bash\n')
01083 
01084             # accepts matching paths
01085             with open(os.path.join(apport.report._whitelist_dir, 'wl1'), 'w') as fd:
01086                 fd.write('/bin/bash\n')
01087             self.assertEqual(bash_rep.check_ignored(), False)
01088             self.assertEqual(crap_rep.check_ignored(), True)
01089 
01090             # also accept crap now
01091             with open(os.path.join(apport.report._whitelist_dir, 'wl_2'), 'w') as fd:
01092                 fd.write('/bin/crap\n')
01093             self.assertEqual(bash_rep.check_ignored(), False)
01094             self.assertEqual(crap_rep.check_ignored(), False)
01095 
01096             # only complete matches accepted
01097             with open(os.path.join(apport.report._whitelist_dir, 'wl1'), 'w') as fd:
01098                 fd.write('/bin/bas\n/bin/bashh\nbash\n')
01099             self.assertEqual(bash_rep.check_ignored(), True)
01100             self.assertEqual(crap_rep.check_ignored(), False)
01101         finally:
01102             shutil.rmtree(apport.report._whitelist_dir)
01103             apport.report._whitelist_dir = orig_whitelist_dir
01104             apport.report.apport.report._ignore_file = orig_ignore_file
01105 
01106     def test_has_useful_stacktrace(self):
01107         '''has_useful_stacktrace().'''
01108 
01109         r = apport.report.Report()
01110         self.assertFalse(r.has_useful_stacktrace())
01111 
01112         r['StacktraceTop'] = ''
01113         self.assertFalse(r.has_useful_stacktrace())
01114 
01115         r['StacktraceTop'] = '?? ()'
01116         self.assertFalse(r.has_useful_stacktrace())
01117 
01118         r['StacktraceTop'] = '?? ()\n?? ()'
01119         self.assertFalse(r.has_useful_stacktrace())
01120 
01121         r['StacktraceTop'] = 'read () from /lib/libc.6.so\n?? ()'
01122         self.assertFalse(r.has_useful_stacktrace())
01123 
01124         r['StacktraceTop'] = 'read () from /lib/libc.6.so\n?? ()\n?? ()\n?? ()'
01125         self.assertFalse(r.has_useful_stacktrace())
01126 
01127         r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
01128         self.assertTrue(r.has_useful_stacktrace())
01129 
01130         r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so\n?? ()'
01131         self.assertTrue(r.has_useful_stacktrace())
01132 
01133         r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so\n?? ()\n?? ()'
01134         self.assertTrue(r.has_useful_stacktrace())
01135 
01136         r['StacktraceTop'] = 'read () from /lib/libc.6.so\n?? ()\nfoo (i=1) from /usr/lib/libfoo.so\n?? ()\n?? ()'
01137         self.assertFalse(r.has_useful_stacktrace())
01138 
01139     def test_standard_title(self):
01140         '''standard_title().'''
01141 
01142         report = apport.report.Report()
01143         self.assertEqual(report.standard_title(), None)
01144 
01145         # named signal crash
01146         report['Signal'] = '11'
01147         report['ExecutablePath'] = '/bin/bash'
01148         report['StacktraceTop'] = '''foo()
01149 bar(x=3)
01150 baz()
01151 '''
01152         self.assertEqual(report.standard_title(),
01153                          'bash crashed with SIGSEGV in foo()')
01154 
01155         # unnamed signal crash
01156         report['Signal'] = '42'
01157         self.assertEqual(report.standard_title(),
01158                          'bash crashed with signal 42 in foo()')
01159 
01160         # do not crash on empty StacktraceTop
01161         report['StacktraceTop'] = ''
01162         self.assertEqual(report.standard_title(),
01163                          'bash crashed with signal 42')
01164 
01165         # do not create bug title with unknown function name
01166         report['StacktraceTop'] = '??()\nfoo()'
01167         self.assertEqual(report.standard_title(),
01168                          'bash crashed with signal 42 in foo()')
01169 
01170         # if we do not know any function name, don't mention ??
01171         report['StacktraceTop'] = '??()\n??()'
01172         self.assertEqual(report.standard_title(),
01173                          'bash crashed with signal 42')
01174 
01175         # assertion message
01176         report['Signal'] = '6'
01177         report['ExecutablePath'] = '/bin/bash'
01178         report['AssertionMessage'] = 'foo.c:42 main: i > 0'
01179         self.assertEqual(report.standard_title(),
01180                          'bash assert failure: foo.c:42 main: i > 0')
01181 
01182         # Python crash
01183         report = apport.report.Report()
01184         report['ExecutablePath'] = '/usr/share/apport/apport-gtk'
01185         report['Traceback'] = '''Traceback (most recent call last):
01186 File "/usr/share/apport/apport-gtk", line 202, in <module>
01187 app.run_argv()
01188 File "/var/lib/python-support/python2.5/apport/ui.py", line 161, in run_argv
01189 self.run_crashes()
01190 File "/var/lib/python-support/python2.5/apport/ui.py", line 104, in run_crashes
01191 self.run_crash(f)
01192 File "/var/lib/python-support/python2.5/apport/ui.py", line 115, in run_crash
01193 response = self.ui_present_crash(desktop_entry)
01194 File "/usr/share/apport/apport-gtk", line 67, in ui_present_crash
01195 subprocess.call(['pgrep', '-x',
01196 NameError: global name 'subprocess' is not defined'''
01197         self.assertEqual(report.standard_title(),
01198                          "apport-gtk crashed with NameError in ui_present_crash(): global name 'subprocess' is not defined")
01199 
01200         # slightly weird Python crash
01201         report = apport.report.Report()
01202         report['ExecutablePath'] = '/usr/share/apport/apport-gtk'
01203         report['Traceback'] = '''TypeError: Cannot create a consistent method resolution
01204 order (MRO) for bases GObject, CanvasGroupableIface, CanvasGroupable'''
01205         self.assertEqual(report.standard_title(),
01206                          'apport-gtk crashed with TypeError: Cannot create a consistent method resolution')
01207 
01208         # Python crash with custom message
01209         report = apport.report.Report()
01210         report['ExecutablePath'] = '/usr/share/apport/apport-gtk'
01211         report['Traceback'] = '''Traceback (most recent call last):
01212   File "/x/foo.py", line 242, in setup_chooser
01213     raise "Moo"
01214 Mo?o[a-1]'''
01215 
01216         self.assertEqual(report.standard_title(), 'apport-gtk crashed with Mo?o[a-1] in setup_chooser()')
01217 
01218         # Python crash with custom message with newlines (LP #190947)
01219         report = apport.report.Report()
01220         report['ExecutablePath'] = '/usr/share/apport/apport-gtk'
01221         report['Traceback'] = '''Traceback (most recent call last):
01222   File "/x/foo.py", line 242, in setup_chooser
01223     raise "\nKey: "+key+" isn't set.\nRestarting AWN usually solves this issue\n"
01224 
01225 Key: /apps/avant-window-navigator/app/active_png isn't set.
01226 Restarting AWN usually solves this issue'''
01227 
01228         t = report.standard_title()
01229         self.assertTrue(t.startswith('apport-gtk crashed with'))
01230         self.assertTrue(t.endswith('setup_chooser()'))
01231 
01232         # Python crash at top level in module
01233         report = apport.report.Report()
01234         report['ExecutablePath'] = '/usr/bin/gnome-about'
01235         report['Traceback'] = '''Traceback (most recent call last):
01236   File "/usr/bin/gnome-about", line 30, in <module>
01237     import pygtk
01238   File "/usr/lib/pymodules/python2.6/pygtk.py", line 28, in <module>
01239     import nonexistent
01240 ImportError: No module named nonexistent
01241 '''
01242         self.assertEqual(report.standard_title(),
01243                          "gnome-about crashed with ImportError in /usr/lib/pymodules/python2.6/pygtk.py: No module named nonexistent")
01244 
01245         # Python crash at top level in main program
01246         report = apport.report.Report()
01247         report['ExecutablePath'] = '/usr/bin/dcut'
01248         report['Traceback'] = '''Traceback (most recent call last):
01249   File "/usr/bin/dcut", line 28, in <module>
01250     import nonexistent
01251 ImportError: No module named nonexistent
01252 '''
01253         self.assertEqual(report.standard_title(),
01254                          "dcut crashed with ImportError in __main__: No module named nonexistent")
01255 
01256         # package install problem
01257         report = apport.report.Report('Package')
01258         report['Package'] = 'bash'
01259 
01260         # no ErrorMessage
01261         self.assertEqual(report.standard_title(),
01262                          'package bash failed to install/upgrade')
01263 
01264         # empty ErrorMessage
01265         report['ErrorMessage'] = ''
01266         self.assertEqual(report.standard_title(),
01267                          'package bash failed to install/upgrade')
01268 
01269         # nonempty ErrorMessage
01270         report['ErrorMessage'] = 'botched\nnot found\n'
01271         self.assertEqual(report.standard_title(),
01272                          'package bash failed to install/upgrade: not found')
01273 
01274         # matching package/system architectures
01275         report['Signal'] = '11'
01276         report['ExecutablePath'] = '/bin/bash'
01277         report['StacktraceTop'] = '''foo()
01278 bar(x=3)
01279 baz()
01280 '''
01281         report['PackageArchitecture'] = 'amd64'
01282         report['Architecture'] = 'amd64'
01283         self.assertEqual(report.standard_title(),
01284                          'bash crashed with SIGSEGV in foo()')
01285 
01286         # non-native package (on multiarch)
01287         report['PackageArchitecture'] = 'i386'
01288         self.assertEqual(report.standard_title(),
01289                          'bash crashed with SIGSEGV in foo() [non-native i386 package]')
01290 
01291         # Arch: all package (matches every system architecture)
01292         report['PackageArchitecture'] = 'all'
01293         self.assertEqual(report.standard_title(),
01294                          'bash crashed with SIGSEGV in foo()')
01295 
01296         report = apport.report.Report('KernelOops')
01297         report['OopsText'] = '------------[ cut here ]------------\nkernel BUG at /tmp/oops.c:5!\ninvalid opcode: 0000 [#1] SMP'
01298         self.assertEqual(report.standard_title(), 'kernel BUG at /tmp/oops.c:5!')
01299 
01300     def test_obsolete_packages(self):
01301         '''obsolete_packages().'''
01302 
01303         report = apport.report.Report()
01304         self.assertEqual(report.obsolete_packages(), [])
01305 
01306         # should work without Dependencies
01307         report['Package'] = 'bash 0'
01308         self.assertEqual(report.obsolete_packages(), ['bash'])
01309         report['Package'] = 'bash 0 [modified: /bin/bash]'
01310         self.assertEqual(report.obsolete_packages(), ['bash'])
01311         report['Package'] = 'bash ' + apport.packaging.get_available_version('bash')
01312         self.assertEqual(report.obsolete_packages(), [])
01313 
01314         report['Dependencies'] = 'coreutils 0\ncron 0\n'
01315         self.assertEqual(report.obsolete_packages(), ['coreutils', 'cron'])
01316 
01317         report['Dependencies'] = 'coreutils %s [modified: /bin/mount]\ncron 0\n' % \
01318             apport.packaging.get_available_version('coreutils')
01319         self.assertEqual(report.obsolete_packages(), ['cron'])
01320 
01321         report['Dependencies'] = 'coreutils %s\ncron %s\n' % (
01322             apport.packaging.get_available_version('coreutils'),
01323             apport.packaging.get_available_version('cron'))
01324         self.assertEqual(report.obsolete_packages(), [])
01325 
01326     def test_gen_stacktrace_top(self):
01327         '''_gen_stacktrace_top().'''
01328 
01329         # nothing to chop off
01330         r = apport.report.Report()
01331         r['Stacktrace'] = '''#0  0x10000488 in h (p=0x0) at crash.c:25
01332 #1  0x100004c8 in g (x=1, y=42) at crash.c:26
01333 #2  0x10000514 in f (x=1) at crash.c:27
01334 #3  0x10000530 in e (x=1) at crash.c:28
01335 #4  0x10000530 in d (x=1) at crash.c:29
01336 #5  0x10000530 in c (x=1) at crash.c:30
01337 #6  0x10000550 in main () at crash.c:31
01338 '''
01339         r._gen_stacktrace_top()
01340         self.assertEqual(r['StacktraceTop'], '''h (p=0x0) at crash.c:25
01341 g (x=1, y=42) at crash.c:26
01342 f (x=1) at crash.c:27
01343 e (x=1) at crash.c:28
01344 d (x=1) at crash.c:29''')
01345 
01346         # nothing to chop off: some addresses missing (LP #269133)
01347         r = apport.report.Report()
01348         r['Stacktrace'] = '''#0 h (p=0x0) at crash.c:25
01349 #1  0x100004c8 in g (x=1, y=42) at crash.c:26
01350 #2 f (x=1) at crash.c:27
01351 #3  0x10000530 in e (x=1) at crash.c:28
01352 #4  0x10000530 in d (x=1) at crash.c:29
01353 #5  0x10000530 in c (x=1) at crash.c:30
01354 #6  0x10000550 in main () at crash.c:31
01355 '''
01356         r._gen_stacktrace_top()
01357         self.assertEqual(r['StacktraceTop'], '''h (p=0x0) at crash.c:25
01358 g (x=1, y=42) at crash.c:26
01359 f (x=1) at crash.c:27
01360 e (x=1) at crash.c:28
01361 d (x=1) at crash.c:29''')
01362 
01363         # single signal handler invocation
01364         r = apport.report.Report()
01365         r['Stacktrace'] = '''#0  0x10000488 in raise () from /lib/libpthread.so.0
01366 #1  0x100004c8 in ??
01367 #2  <signal handler called>
01368 #3  0x10000530 in e (x=1) at crash.c:28
01369 #4  0x10000530 in d (x=1) at crash.c:29
01370 #5  0x10000530 in c (x=1) at crash.c:30
01371 #6  0x10000550 in main () at crash.c:31
01372 '''
01373         r._gen_stacktrace_top()
01374         self.assertEqual(r['StacktraceTop'], '''e (x=1) at crash.c:28
01375 d (x=1) at crash.c:29
01376 c (x=1) at crash.c:30
01377 main () at crash.c:31''')
01378 
01379         # single signal handler invocation: some addresses missing
01380         r = apport.report.Report()
01381         r['Stacktrace'] = '''#0  0x10000488 in raise () from /lib/libpthread.so.0
01382 #1  ??
01383 #2  <signal handler called>
01384 #3  0x10000530 in e (x=1) at crash.c:28
01385 #4  d (x=1) at crash.c:29
01386 #5  0x10000530 in c (x=1) at crash.c:30
01387 #6  0x10000550 in main () at crash.c:31
01388 '''
01389         r._gen_stacktrace_top()
01390         self.assertEqual(r['StacktraceTop'], '''e (x=1) at crash.c:28
01391 d (x=1) at crash.c:29
01392 c (x=1) at crash.c:30
01393 main () at crash.c:31''')
01394 
01395         # stacked signal handler; should only cut the first one
01396         r = apport.report.Report()
01397         r['Stacktrace'] = '''#0  0x10000488 in raise () from /lib/libpthread.so.0
01398 #1  0x100004c8 in ??
01399 #2  <signal handler called>
01400 #3  0x10000530 in e (x=1) at crash.c:28
01401 #4  0x10000530 in d (x=1) at crash.c:29
01402 #5  0x10000123 in raise () from /lib/libpthread.so.0
01403 #6  <signal handler called>
01404 #7  0x10000530 in c (x=1) at crash.c:30
01405 #8  0x10000550 in main () at crash.c:31
01406 '''
01407         r._gen_stacktrace_top()
01408         self.assertEqual(r['StacktraceTop'], '''e (x=1) at crash.c:28
01409 d (x=1) at crash.c:29
01410 raise () from /lib/libpthread.so.0
01411 <signal handler called>
01412 c (x=1) at crash.c:30''')
01413 
01414         # Gnome assertion; should unwind the logs and assert call
01415         r = apport.report.Report()
01416         r['Stacktrace'] = '''#0  0xb7d39cab in IA__g_logv (log_domain=<value optimized out>, log_level=G_LOG_LEVEL_ERROR,
01417     format=0xb7d825f0 "file %s: line %d (%s): assertion failed: (%s)", args1=0xbfee8e3c "xxx") at /build/buildd/glib2.0-2.13.5/glib/gmessages.c:493
01418 #1  0xb7d39f29 in IA__g_log (log_domain=0xb7edbfd0 "libgnomevfs", log_level=G_LOG_LEVEL_ERROR,
01419     format=0xb7d825f0 "file %s: line %d (%s): assertion failed: (%s)") at /build/buildd/glib2.0-2.13.5/glib/gmessages.c:517
01420 #2  0xb7d39fa6 in IA__g_assert_warning (log_domain=0xb7edbfd0 "libgnomevfs", file=0xb7ee1a26 "gnome-vfs-volume.c", line=254,
01421     pretty_function=0xb7ee1920 "gnome_vfs_volume_unset_drive_private", expression=0xb7ee1a39 "volume->priv->drive == drive")
01422     at /build/buildd/glib2.0-2.13.5/glib/gmessages.c:552
01423 No locals.
01424 #3  0xb7ec6c11 in gnome_vfs_volume_unset_drive_private (volume=0x8081a30, drive=0x8078f00) at gnome-vfs-volume.c:254
01425         __PRETTY_FUNCTION__ = "gnome_vfs_volume_unset_drive_private"
01426 #4  0x08054db8 in _gnome_vfs_volume_monitor_disconnected (volume_monitor=0x8070400, drive=0x8078f00) at gnome-vfs-volume-monitor.c:963
01427         vol_list = (GList *) 0x8096d30
01428         current_vol = (GList *) 0x8097470
01429 #5  0x0805951e in _hal_device_removed (hal_ctx=0x8074da8, udi=0x8093be4 "/org/freedesktop/Hal/devices/volume_uuid_92FC9DFBFC9DDA35")
01430     at gnome-vfs-hal-mounts.c:1316
01431         backing_udi = <value optimized out>
01432 #6  0xb7ef1ead in filter_func (connection=0x8075288, message=0x80768d8, user_data=0x8074da8) at libhal.c:820
01433         udi = <value optimized out>
01434         object_path = 0x8076d40 "/org/freedesktop/Hal/Manager"
01435         error = {name = 0x0, message = 0x0, dummy1 = 1, dummy2 = 0, dummy3 = 0, dummy4 = 1, dummy5 = 0, padding1 = 0xb7e50c00}
01436 #7  0xb7e071d2 in dbus_connection_dispatch (connection=0x8075288) at dbus-connection.c:4267
01437 #8  0xb7e33dfd in ?? () from /usr/lib/libdbus-glib-1.so.2'''
01438         r._gen_stacktrace_top()
01439         self.assertEqual(r['StacktraceTop'], '''gnome_vfs_volume_unset_drive_private (volume=0x8081a30, drive=0x8078f00) at gnome-vfs-volume.c:254
01440 _gnome_vfs_volume_monitor_disconnected (volume_monitor=0x8070400, drive=0x8078f00) at gnome-vfs-volume-monitor.c:963
01441 _hal_device_removed (hal_ctx=0x8074da8, udi=0x8093be4 "/org/freedesktop/Hal/devices/volume_uuid_92FC9DFBFC9DDA35")
01442 filter_func (connection=0x8075288, message=0x80768d8, user_data=0x8074da8) at libhal.c:820
01443 dbus_connection_dispatch (connection=0x8075288) at dbus-connection.c:4267''')
01444 
01445         # XError (taken from LP#848808)
01446         r = apport.report.Report()
01447         r['Stacktrace'] = '''#0  0x007cf416 in __kernel_vsyscall ()
01448 No symbol table info available.
01449 #1  0x01017c8f in __GI_raise (sig=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
01450 #2  0x0101b2b5 in __GI_abort () at abort.c:92
01451 #3  0x0807daab in meta_bug (format=0x80b0c60 "Unexpected X error: %s serial %ld error_code %d request_code %d minor_code %d)\n") at core/util.c:398
01452 #4  0x0806989c in x_error_handler (error=0xbf924acc, xdisplay=0x9104b88) at core/errors.c:247
01453 #5  x_error_handler (xdisplay=0x9104b88, error=0xbf924acc) at core/errors.c:203
01454 #6  0x00e97d3b in _XError (dpy=0x9104b88, rep=0x9131840) at ../../src/XlibInt.c:1583
01455 #7  0x00e9490d in handle_error (dpy=0x9104b88, err=0x9131840, in_XReply=0) at ../../src/xcb_io.c:212
01456 #8  0x00e94967 in handle_response (dpy=0x9104b88, response=0x9131840, in_XReply=0) at ../../src/xcb_io.c:324
01457 #9  0x00e952fe in _XReadEvents (dpy=0x9104b88) at ../../src/xcb_io.c:425
01458 #10 0x00e93663 in XWindowEvent (dpy=0x9104b88, w=16777220, mask=4194304, event=0xbf924c6c) at ../../src/WinEvent.c:79
01459 #11 0x0806071c in meta_display_get_current_time_roundtrip (display=0x916d7d0) at core/display.c:1217
01460 #12 0x08089f64 in meta_window_show (window=0x91ccfc8) at core/window.c:2165
01461 #13 implement_showing (window=0x91ccfc8, showing=1) at core/window.c:1583
01462 #14 0x080879cc in meta_window_flush_calc_showing (window=0x91ccfc8) at core/window.c:1806'''
01463         r._gen_stacktrace_top()
01464         self.assertEqual(r['StacktraceTop'], '''meta_display_get_current_time_roundtrip (display=0x916d7d0) at core/display.c:1217
01465 meta_window_show (window=0x91ccfc8) at core/window.c:2165
01466 implement_showing (window=0x91ccfc8, showing=1) at core/window.c:1583
01467 meta_window_flush_calc_showing (window=0x91ccfc8) at core/window.c:1806''')
01468 
01469         # another XError (taken from LP#834403)
01470         r = apport.report.Report()
01471         r['Stacktrace'] = '''#0  g_logv (log_domain=0x7fd41db08a46 "Gdk", log_level=<optimized out>, format=0x7fd41db12e87 "%s", args1=0x7fff50bf0c18) at /build/buildd/glib2.0-2.29.16/./glib/gmessages.c:577
01472 #1  0x00007fd42006bb92 in g_log (log_domain=<optimized out>, log_level=<optimized out>, format=<optimized out>) at /build/buildd/glib2.0-2.29.16/./glib/gmessages.c:591
01473 #2  0x00007fd41dae86f3 in _gdk_x11_display_error_event (display=<optimized out>, error=<optimized out>) at /build/buildd/gtk+3.0-3.1.12/./gdk/x11/gdkdisplay-x11.c:2374
01474 #3  0x00007fd41daf5647 in gdk_x_error (error=0x7fff50bf0dc0, xdisplay=<optimized out>) at /build/buildd/gtk+3.0-3.1.12/./gdk/x11/gdkmain-x11.c:312
01475 #4  gdk_x_error (xdisplay=<optimized out>, error=0x7fff50bf0dc0) at /build/buildd/gtk+3.0-3.1.12/./gdk/x11/gdkmain-x11.c:275
01476 #5  0x00007fd41d5a301f in _XError (dpy=0x2425370, rep=<optimized out>) at ../../src/XlibInt.c:1583
01477 #6  0x00007fd41d59fdd1 in handle_error (dpy=0x2425370, err=0x7fd408707980, in_XReply=<optimized out>) at ../../src/xcb_io.c:212
01478 #7  0x00007fd41d5a0d27 in _XReply (dpy=0x2425370, rep=0x7fff50bf0f60, extra=0, discard=0) at ../../src/xcb_io.c:698
01479 #8  0x00007fd41d5852fb in XGetWindowProperty (dpy=0x2425370, w=0, property=348, offset=0, length=2, delete=<optimized out>, req_type=348, actual_type=0x7fff50bf1038, actual_format=0x7fff50bf105c, nitems=0x7fff50bf1040, bytesafter=0x7fff50bf1048, prop=0x7fff50bf1050) at ../../src/GetProp.c:61
01480 #9  0x00007fd41938269e in window_is_xembed (w=<optimized out>, d=<optimized out>) at canberra-gtk-module.c:373
01481 #10 dispatch_sound_event (d=0x32f6a30) at canberra-gtk-module.c:454
01482 #11 dispatch_queue () at canberra-gtk-module.c:815'''
01483         r._gen_stacktrace_top()
01484         self.assertEqual(r['StacktraceTop'], '''XGetWindowProperty (dpy=0x2425370, w=0, property=348, offset=0, length=2, delete=<optimized out>, req_type=348, actual_type=0x7fff50bf1038, actual_format=0x7fff50bf105c, nitems=0x7fff50bf1040, bytesafter=0x7fff50bf1048, prop=0x7fff50bf1050) at ../../src/GetProp.c:61
01485 window_is_xembed (w=<optimized out>, d=<optimized out>) at canberra-gtk-module.c:373
01486 dispatch_sound_event (d=0x32f6a30) at canberra-gtk-module.c:454
01487 dispatch_queue () at canberra-gtk-module.c:815''')
01488 
01489         # problem with too old gdb, only assertion, nothing else
01490         r = apport.report.Report()
01491         r['Stacktrace'] = '''#0  0x00987416 in __kernel_vsyscall ()
01492 No symbol table info available.
01493 #1  0x00ebecb1 in *__GI_raise (sig=6)
01494         selftid = 945
01495 #2  0x00ec218e in *__GI_abort () at abort.c:59
01496         save_stage = Unhandled dwarf expression opcode 0x9f
01497 '''
01498         r._gen_stacktrace_top()
01499         self.assertEqual(r['StacktraceTop'], '')
01500 
01501         # ignore uninteresting frames
01502         r = apport.report.Report()
01503         r['Stacktrace'] = '''#0  0x00987416 in __kernel_vsyscall ()
01504 #1  __strchr_sse42 () at strchr.S:97
01505 #2 h (p=0x0) at crash.c:25
01506 #3  0x100004c8 in g (x=1, y=42) at crash.c:26
01507 #4  0x10000999 in __memmove_ssse3 ()
01508 #5 f (x=1) at crash.c:27
01509 #6  0x10000530 in e (x=1) at crash.c:28
01510 #7  0x10000999 in __strlen_sse2_back () at strchr.S:42
01511 #8  0x10000530 in d (x=1) at crash.c:29
01512 #9  0x10000530 in c (x=1) at crash.c:30
01513 #10 0x10000550 in main () at crash.c:31
01514 '''
01515         r._gen_stacktrace_top()
01516         self.assertEqual(r['StacktraceTop'], '''h (p=0x0) at crash.c:25
01517 g (x=1, y=42) at crash.c:26
01518 f (x=1) at crash.c:27
01519 e (x=1) at crash.c:28
01520 d (x=1) at crash.c:29''')
01521 
01522     def test_crash_signature(self):
01523         '''crash_signature().'''
01524 
01525         r = apport.report.Report()
01526         self.assertEqual(r.crash_signature(), None)
01527 
01528         # signal crashes
01529         r['Signal'] = '42'
01530         r['ExecutablePath'] = '/bin/crash'
01531 
01532         r['StacktraceTop'] = '''foo_bar (x=1) at crash.c:28
01533 d01 (x=1) at crash.c:29
01534 raise () from /lib/libpthread.so.0
01535 <signal handler called>
01536 __frob::~frob (x=1) at crash.c:30'''
01537 
01538         self.assertEqual(r.crash_signature(), '/bin/crash:42:foo_bar:d01:raise:<signal handler called>:__frob::~frob')
01539 
01540         r['StacktraceTop'] = '''foo_bar (x=1) at crash.c:28
01541 ??
01542 raise () from /lib/libpthread.so.0
01543 <signal handler called>
01544 __frob (x=1) at crash.c:30'''
01545         self.assertEqual(r.crash_signature(), None)
01546 
01547         r['StacktraceTop'] = ''
01548         self.assertEqual(r.crash_signature(), None)
01549 
01550         # Python crashes
01551         del r['Signal']
01552         r['Traceback'] = '''Traceback (most recent call last):
01553   File "test.py", line 7, in <module>
01554     print(_f(5))
01555   File "test.py", line 5, in _f
01556     return g_foo00(x+1)
01557   File "test.py", line 2, in g_foo00
01558     return x/0
01559 ZeroDivisionError: integer division or modulo by zero'''
01560         self.assertEqual(r.crash_signature(), '/bin/crash:ZeroDivisionError:test.py@7:_f:g_foo00')
01561 
01562         # sometimes Python traces do not have file references
01563         r['Traceback'] = 'TypeError: function takes exactly 0 arguments (1 given)'
01564         self.assertEqual(r.crash_signature(), '/bin/crash:TypeError')
01565 
01566         r['Traceback'] = 'FooBar'
01567         self.assertEqual(r.crash_signature(), None)
01568 
01569         # kernel
01570         r['ProblemType'] = 'KernelCrash'
01571         r['Stacktrace'] = '''
01572 crash 4.0-8.9
01573 GNU gdb 6.1
01574 GDB is free software, covered by the GNU General Public License, and you are
01575 welcome to change it and/or distribute copies of it under certain conditions.
01576 Type "show copying" to see the conditions.
01577 There is absolutely no warranty for GDB.  Type "show warranty" for details.
01578 This GDB was configured as "i686-pc-linux-gnu"...
01579 
01580       KERNEL: /usr/lib/debug/boot/vmlinux-2.6.31-2-generic
01581     DUMPFILE: /tmp/tmpRJZy_O
01582         CPUS: 1
01583         DATE: Thu Jul  9 12:58:08 2009
01584       UPTIME: 00:00:57
01585 LOAD AVERAGE: 0.15, 0.05, 0.02
01586        TASKS: 173
01587     NODENAME: egon-desktop
01588      RELEASE: 2.6.31-2-generic
01589      VERSION: #16-Ubuntu SMP Mon Jul 6 20:38:51 UTC 2009
01590      MACHINE: i686  (2137 Mhz)
01591       MEMORY: 2 GB
01592        PANIC: "[   57.879776] Oops: 0002 [#1] SMP " (check log for details)
01593          PID: 0
01594      COMMAND: "swapper"
01595         TASK: c073c180  [THREAD_INFO: c0784000]
01596          CPU: 0
01597        STATE: TASK_RUNNING (PANIC)
01598 
01599 PID: 0      TASK: c073c180  CPU: 0   COMMAND: "swapper"
01600  #0 [c0785ba0] sysrq_handle_crash at c03917a3
01601     [RA: c03919c6  SP: c0785ba0  FP: c0785ba0  SIZE: 4]
01602     c0785ba0: c03919c6
01603  #1 [c0785ba0] __handle_sysrq at c03919c4
01604     [RA: c0391a91  SP: c0785ba4  FP: c0785bc8  SIZE: 40]
01605     c0785ba4: c06d4bab  c06d42d2  f6534000  00000004
01606     c0785bb4: 00000086  0000002e  00000001  f6534000
01607     c0785bc4: c0785bcc  c0391a91
01608  #2 [c0785bc8] handle_sysrq at c0391a8c
01609     [RA: c0389961  SP: c0785bcc  FP: c0785bd0  SIZE: 8]
01610     c0785bcc: c0785c0c  c0389961
01611  #3 [c0785bd0] kbd_keycode at c038995c
01612     [RA: c0389b8b  SP: c0785bd4  FP: c0785c10  SIZE: 64]
01613     c0785bd4: c056f96a  c0785be4  00000096  c07578c0
01614     c0785be4: 00000001  f6ac6e00  f6ac6e00  00000001
01615     c0785bf4: 00000000  00000000  0000002e  0000002e
01616     c0785c04: 00000001  f70d6850  c0785c1c  c0389b8b
01617  #4 [c0785c10] kbd_event at c0389b86
01618     [RA: c043140c  SP: c0785c14  FP: c0785c20  SIZE: 16]
01619     c0785c14: c0758040  f6910900  c0785c3c  c043140c
01620  #5 [c0785c20] input_pass_event at c0431409
01621     [RA: c04332ce  SP: c0785c24  FP: c0785c40  SIZE: 32]
01622     c0785c24: 00000001  0000002e  00000001  f70d6000
01623     c0785c34: 00000001  0000002e  c0785c64  c04332ce
01624  #6 [c0785c40] input_handle_event at c04332c9
01625     [RA: c0433ac6  SP: c0785c44  FP: c0785c68  SIZE: 40]
01626     c0785c44: 00000001  ffff138d  0000003d  00000001
01627     c0785c54: f70d6000  00000001  f70d6000  0000002e
01628     c0785c64: c0785c84  c0433ac6
01629  #7 [c0785c68] input_event at c0433ac1
01630     [RA: c0479806  SP: c0785c6c  FP: c0785c88  SIZE: 32]
01631     c0785c6c: 00000001  00000092  f70d677c  f70d70b4
01632     c0785c7c: 0000002e  f70d7000  c0785ca8  c0479806
01633  #8 [c0785c88] hidinput_hid_event at c0479801
01634     [RA: c0475b31  SP: c0785c8c  FP: c0785cac  SIZE: 36]
01635     c0785c8c: 00000001  00000007  c0785c00  f70d6000
01636     c0785c9c: f70d70b4  f70d5000  f70d7000  c0785cc4
01637     c0785cac: c0475b31
01638     [RA: 0  SP: c0785ffc  FP: c0785ffc  SIZE: 0]
01639    PID    PPID  CPU   TASK    ST  %MEM     VSZ    RSS  COMM
01640 >     0      0   0  c073c180  RU   0.0       0      0  [swapper]
01641       1      0   1  f7038000  IN   0.1    3096   1960  init
01642       2      0   0  f7038c90  IN   0.0       0      0  [kthreadd]
01643     271      2   1  f72bf110  IN   0.0       0      0  [bluetooth]
01644     325      2   1  f71c25b0  IN   0.0       0      0  [khungtaskd]
01645    1404      2   0  f6b5bed0  IN   0.0       0      0  [kpsmoused]
01646    1504      2   1  f649cb60  IN   0.0       0      0  [hd-audio0]
01647    2055      1   0  f6a18000  IN   0.0    1824    536  getty
01648    2056      1   0  f6a1d7f0  IN   0.0    1824    536  getty
01649    2061      1   0  f6a1f110  IN   0.1    3132   1604  login
01650    2062      1   1  f6a18c90  IN   0.0    1824    540  getty
01651    2063      1   1  f6b58c90  IN   0.0    1824    540  getty
01652    2130      1   0  f6b5f110  IN   0.0    2200   1032  acpid
01653    2169      1   0  f69ebed0  IN   0.0    2040    664  syslogd
01654    2192      1   1  f65b3ed0  IN   0.0    1976    532  dd
01655    2194      1   1  f6b5a5b0  IN   0.1    3996   2712  klogd
01656    2217      1   0  f6b74b60  IN   0.1    3008   1120  dbus-daemon
01657    2248      1   0  f65b7110  IN   0.2    6896   4304  hald
01658    2251      1   1  f65b3240  IN   0.1   19688   2604  console-kit-dae
01659 RUNQUEUES[0]: c6002320
01660  RT PRIO_ARRAY: c60023c0
01661  CFS RB_ROOT: c600237c
01662   PID: 9      TASK: f703f110  CPU: 0   COMMAND: "events/0"
01663 '''
01664         self.assertEqual(r.crash_signature(), 'kernel:sysrq_handle_crash:__handle_sysrq:handle_sysrq:kbd_keycode:kbd_event:input_pass_event:input_handle_event:input_event:hidinput_hid_event')
01665 
01666         # assertion failures
01667         r = apport.report.Report()
01668         r['Signal'] = '6'
01669         r['ExecutablePath'] = '/bin/bash'
01670         r['AssertionMessage'] = 'foo.c:42 main: i > 0'
01671         self.assertEqual(r.crash_signature(), '/bin/bash:foo.c:42 main: i > 0')
01672 
01673     def test_nonascii_data(self):
01674         '''methods get along with non-ASCII data'''
01675 
01676         # fake os.uname() into reporting a non-ASCII name
01677         uname = os.uname()
01678         uname = (uname[0], b't\xe2\x99\xaax'.decode('UTF-8'), uname[2], uname[3], uname[4])
01679         orig_uname = os.uname
01680         os.uname = lambda: uname
01681 
01682         try:
01683             pr = apport.report.Report()
01684             utf8_val = b'\xc3\xa4 ' + uname[1].encode('UTF-8') + b' \xe2\x99\xa5 '
01685             pr['ProcUnicodeValue'] = utf8_val.decode('UTF-8')
01686             pr['ProcByteArrayValue'] = utf8_val
01687 
01688             pr.anonymize()
01689 
01690             exp_utf8 = b'\xc3\xa4 hostname \xe2\x99\xa5 '
01691             self.assertEqual(pr['ProcUnicodeValue'], exp_utf8.decode('UTF-8'))
01692             self.assertEqual(pr['ProcByteArrayValue'], exp_utf8)
01693         finally:
01694             os.uname = orig_uname
01695 
01696     def test_address_to_offset(self):
01697         '''_address_to_offset()'''
01698 
01699         pr = apport.report.Report()
01700 
01701         self.assertRaises(AssertionError, pr._address_to_offset, 0)
01702 
01703         pr['ProcMaps'] = '''
01704 00400000-004df000 r-xp 00000000 08:02 1044485                            /bin/bash
01705 006de000-006df000 r--p 000de000 08:02 1044485                            /bin/bash
01706 01596000-01597000 rw-p 00000000 00:00 0
01707 01597000-015a4000 rw-p 00000000 00:00 0                                  [heap]
01708 7f491f868000-7f491f88a000 r-xp 00000000 08:02 526219                     /lib/x86_64-linux-gnu/libtinfo.so.5.9
01709 7f491fa8f000-7f491fc24000 r-xp 00000000 08:02 522605                     /lib/x86_64-linux-gnu/libc-2.13.so
01710 7f491fc24000-7f491fe23000 ---p 00195000 08:02 522605                     /lib/with spaces !/libfoo.so
01711 7fff6e57b000-7fff6e59c000 rw-p 00000000 00:00 0                          [stack]
01712 7fff6e5ff000-7fff6e600000 r-xp 00000000 00:00 0                          [vdso]
01713 ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
01714 '''
01715 
01716         self.assertEqual(pr._address_to_offset(0x41d703), '/bin/bash+1d703')
01717         self.assertEqual(pr._address_to_offset(0x00007f491fac5687),
01718                          '/lib/x86_64-linux-gnu/libc-2.13.so+36687')
01719 
01720         self.assertEqual(pr._address_to_offset(0x006ddfff), None)
01721         self.assertEqual(pr._address_to_offset(0x006de000), '/bin/bash+0')
01722         self.assertEqual(pr._address_to_offset(0x006df000), '/bin/bash+1000')
01723         self.assertEqual(pr._address_to_offset(0x006df001), None)
01724 
01725         self.assertEqual(pr._address_to_offset(0x7f491fc24010),
01726                          '/lib/with spaces !/libfoo.so+10')
01727 
01728     def test_address_to_offset_live(self):
01729         '''_address_to_offset() for current /proc/pid/maps'''
01730 
01731         # this primarily checks that the parser actually gets along with the
01732         # real /proc/pid/maps and not just with our static test case above
01733         pr = apport.report.Report()
01734         pr.add_proc_info()
01735         self.assertEqual(pr._address_to_offset(0), None)
01736         res = pr._address_to_offset(int(pr['ProcMaps'].split('-', 1)[0], 16) + 5)
01737         self.assertEqual(res.split('+', 1)[1], '5')
01738         self.assertTrue('python' in res.split('+', 1)[0])
01739 
01740     def test_crash_signature_addresses(self):
01741         '''crash_signature_addresses()'''
01742 
01743         pr = apport.report.Report()
01744         self.assertEqual(pr.crash_signature_addresses(), None)
01745 
01746         pr['ExecutablePath'] = '/bin/bash'
01747         pr['Signal'] = '42'
01748         pr['ProcMaps'] = '''
01749 00400000-004df000 r-xp 00000000 08:02 1044485                            /bin/bash
01750 006de000-006df000 r--p 000de000 08:02 1044485                            /bin/bash
01751 01596000-01597000 rw-p 00000000 00:00 0
01752 01597000-015a4000 rw-p 00000000 00:00 0                                  [heap]
01753 7f491f868000-7f491f88a000 r-xp 00000000 08:02 526219                     /lib/x86_64-linux-gnu/libtinfo.so.5.9
01754 7f491fa8f000-7f491fc24000 r-xp 00000000 08:02 522605                     /lib/x86_64-linux-gnu/libc-2.13.so
01755 7f491fc24000-7f491fe23000 ---p 00195000 08:02 522605                     /lib/with spaces !/libfoo.so
01756 7fff6e57b000-7fff6e59c000 rw-p 00000000 00:00 0                          [stack]
01757 7fff6e5ff000-7fff6e600000 r-xp 00000000 00:00 0                          [vdso]
01758 ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
01759 '''
01760 
01761         # no Stacktrace field
01762         self.assertEqual(pr.crash_signature_addresses(), None)
01763 
01764         # good stack trace
01765         pr['Stacktrace'] = '''
01766 #0  0x00007f491fac5687 in kill () at ../sysdeps/unix/syscall-template.S:82
01767 No locals.
01768 #1  0x000000000043fd51 in kill_pid ()
01769 #2  g_main_context_iterate (context=0x1731680) at gmain.c:3068
01770 #3  0x000000000042eb76 in ?? ()
01771 #4  0x00000000004324d8 in ??
01772 No symbol table info available.
01773 #5  0x00000000004707e3 in parse_and_execute ()
01774 #6  0x000000000041d703 in _start ()
01775 '''
01776         self.assertEqual(pr.crash_signature_addresses(),
01777                          '/bin/bash:42:%s:/lib/x86_64-linux-gnu/libc-2.13.so+36687:/bin/bash+3fd51:/bin/bash+2eb76:/bin/bash+324d8:/bin/bash+707e3:/bin/bash+1d703' % os.uname()[4])
01778 
01779         # all resolvable, but too short
01780         pr['Stacktrace'] = '#0  0x00007f491fac5687 in kill () at ../sysdeps/unix/syscall-template.S:82'
01781         self.assertEqual(pr.crash_signature_addresses(), None)
01782 
01783         # one unresolvable, but long enough
01784         pr['Stacktrace'] = '''
01785 #0  0x00007f491fac5687 in kill () at ../sysdeps/unix/syscall-template.S:82
01786 No locals.
01787 #1  0x000001000043fd51 in kill_pid ()
01788 #2  g_main_context_iterate (context=0x1731680) at gmain.c:3068
01789 #3  0x000000000042eb76 in ?? ()
01790 #4  0x00000000004324d8 in ??
01791 No symbol table info available.
01792 #5  0x00000000004707e3 in parse_and_execute ()
01793 #6  0x000000000041d715 in main ()
01794 #7  0x000000000041d703 in _start ()
01795 '''
01796         self.assertNotEqual(pr.crash_signature_addresses(), None)
01797 
01798         # two unresolvables, 2/7 is too much
01799         pr['Stacktrace'] = '''
01800 #0  0x00007f491fac5687 in kill () at ../sysdeps/unix/syscall-template.S:82
01801 No locals.
01802 #1  0x000001000043fd51 in kill_pid ()
01803 #2  g_main_context_iterate (context=0x1731680) at gmain.c:3068
01804 #3  0x000001000042eb76 in ?? ()
01805 #4  0x00000000004324d8 in ??
01806 No symbol table info available.
01807 #5  0x00000000004707e3 in parse_and_execute ()
01808 #6  0x000000000041d715 in main ()
01809 #7  0x000000000041d703 in _start ()
01810 '''
01811         self.assertEqual(pr.crash_signature_addresses(), None)
01812 
01813 if __name__ == '__main__':
01814     unittest.main()