Back to index

apport  2.4
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             gdb = subprocess.Popen(['gdb', '--batch', '--ex', 'run', '--ex',
00453                                     'generate-core-file core', './crash'], stdout=subprocess.PIPE)
00454             gdb.communicate()
00455             klass._validate_core('core')
00456 
00457             pr['ExecutablePath'] = os.path.join(workdir, 'crash')
00458             pr['CoreDump'] = (os.path.join(workdir, 'core'),)
00459             pr['Signal'] = signal
00460 
00461             pr.add_gdb_info()
00462             if file:
00463                 pr.write(file)
00464                 file.flush()
00465         finally:
00466             os.chdir(orig_cwd)
00467 
00468         return pr
00469 
00470     @classmethod
00471     def _validate_core(klass, core_path):
00472         subprocess.check_call(['sync'])
00473         assert os.path.exists(core_path)
00474         readelf = subprocess.Popen(['readelf', '-n', core_path], stdout=subprocess.PIPE)
00475         readelf.communicate()
00476         assert readelf.returncode == 0
00477 
00478     def _validate_gdb_fields(self, pr):
00479         self.assertTrue('Stacktrace' in pr)
00480         self.assertTrue('ThreadStacktrace' in pr)
00481         self.assertTrue('StacktraceTop' in pr)
00482         self.assertTrue('Registers' in pr)
00483         self.assertTrue('Disassembly' in pr)
00484         self.assertTrue('(no debugging symbols found)' not in pr['Stacktrace'])
00485         self.assertTrue('Core was generated by' not in pr['Stacktrace'], pr['Stacktrace'])
00486         self.assertTrue(not re.match(r'(?s)(^|.*\n)#0  [^\n]+\n#0  ',
00487                                      pr['Stacktrace']))
00488         self.assertTrue('#0  0x' in pr['Stacktrace'])
00489         self.assertTrue('#1  0x' in pr['Stacktrace'])
00490         self.assertTrue('#0  0x' in pr['ThreadStacktrace'])
00491         self.assertTrue('#1  0x' in pr['ThreadStacktrace'])
00492         self.assertTrue('Thread 1 (' in pr['ThreadStacktrace'])
00493         self.assertTrue(len(pr['StacktraceTop'].splitlines()) <= 5)
00494 
00495     def test_add_gdb_info(self):
00496         '''add_gdb_info() with core dump file reference.'''
00497 
00498         pr = apport.report.Report()
00499         # should not throw an exception for missing fields
00500         pr.add_gdb_info()
00501 
00502         # normal crash
00503         pr = self._generate_sigsegv_report()
00504         self._validate_gdb_fields(pr)
00505         self.assertEqual(pr['StacktraceTop'], 'f (x=42) at crash.c:3\nmain () at crash.c:6', pr['StacktraceTop'])
00506         self.assertFalse('AssertionMessage' in pr)
00507 
00508         # crash where gdb generates output on stderr
00509         pr = self._generate_sigsegv_report(code='''
00510 int main() {
00511     void     (*function)(void);
00512     function = 0;
00513     function();
00514 }
00515 ''')
00516         self._validate_gdb_fields(pr)
00517         self.assertTrue('Cannot access memory at address 0x0' in pr['Disassembly'], pr['Disassembly'])
00518         self.assertFalse('AssertionMessage' in pr)
00519 
00520     def test_add_gdb_info_load(self):
00521         '''add_gdb_info() with inline core dump.'''
00522 
00523         rep = tempfile.NamedTemporaryFile()
00524         self._generate_sigsegv_report(rep)
00525         rep.seek(0)
00526 
00527         pr = apport.report.Report()
00528         with open(rep.name, 'rb') as f:
00529             pr.load(f)
00530         pr.add_gdb_info()
00531 
00532         self._validate_gdb_fields(pr)
00533 
00534     def test_add_zz_parse_segv_details(self):
00535         '''parse-segv produces sensible results'''
00536         rep = tempfile.NamedTemporaryFile()
00537         self._generate_sigsegv_report(rep)
00538         rep.seek(0)
00539 
00540         pr = apport.report.Report()
00541         with open(rep.name, 'rb') as f:
00542             pr.load(f)
00543         pr['Signal'] = '1'
00544         pr.add_hooks_info('fake_ui')
00545         self.assertTrue('SegvAnalysis' not in pr.keys())
00546 
00547         pr = apport.report.Report()
00548         with open(rep.name, 'rb') as f:
00549             pr.load(f)
00550         pr.add_hooks_info('fake_ui')
00551         self.assertTrue('Skipped: missing required field "Architecture"' in pr['SegvAnalysis'],
00552                         pr['SegvAnalysis'])
00553 
00554         pr.add_os_info()
00555         pr.add_hooks_info('fake_ui')
00556         self.assertTrue('Skipped: missing required field "ProcMaps"' in pr['SegvAnalysis'],
00557                         pr['SegvAnalysis'])
00558 
00559         pr.add_proc_info()
00560         pr.add_hooks_info('fake_ui')
00561         self.assertTrue('not located in a known VMA region' in pr['SegvAnalysis'],
00562                         pr['SegvAnalysis'])
00563 
00564     def test_add_gdb_info_script(self):
00565         '''add_gdb_info() with a script.'''
00566 
00567         (fd, script) = tempfile.mkstemp()
00568         coredump = os.path.join(os.path.dirname(script), 'core')
00569         assert not os.path.exists(coredump)
00570         try:
00571             os.close(fd)
00572 
00573             # create a test script which produces a core dump for us
00574             with open(script, 'w') as fd:
00575                 fd.write('''#!/bin/bash
00576 cd `dirname $0`
00577 ulimit -c unlimited
00578 kill -SEGV $$
00579 ''')
00580             os.chmod(script, 0o755)
00581 
00582             # call script and verify that it gives us a proper ELF core dump
00583             assert subprocess.call([script]) != 0
00584             self._validate_core(coredump)
00585 
00586             pr = apport.report.Report()
00587             pr['InterpreterPath'] = '/bin/bash'
00588             pr['ExecutablePath'] = script
00589             pr['CoreDump'] = (coredump,)
00590             pr.add_gdb_info()
00591         finally:
00592             os.unlink(coredump)
00593             os.unlink(script)
00594 
00595         self._validate_gdb_fields(pr)
00596         self.assertTrue('libc.so' in pr['Stacktrace'] or 'in execute_command' in pr['Stacktrace'])
00597 
00598     def test_add_gdb_info_abort(self):
00599         '''add_gdb_info() with SIGABRT/assert()
00600 
00601         If these come from an assert(), the report should have the assertion
00602         message. Otherwise it should be marked as not reportable.
00603         '''
00604         # abort with assert
00605         (fd, script) = tempfile.mkstemp()
00606         assert not os.path.exists('core')
00607         try:
00608             os.close(fd)
00609 
00610             # create a test script which produces a core dump for us
00611             with open(script, 'w') as fd:
00612                 fd.write('''#!/bin/sh
00613 gcc -o $0.bin -x c - <<EOF
00614 #include <assert.h>
00615 int main() { assert(1 < 0); }
00616 EOF
00617 ulimit -c unlimited
00618 $0.bin 2>/dev/null
00619 ''')
00620             os.chmod(script, 0o755)
00621 
00622             # call script and verify that it gives us a proper ELF core dump
00623             assert subprocess.call([script]) != 0
00624             self._validate_core('core')
00625 
00626             pr = apport.report.Report()
00627             pr['ExecutablePath'] = script + '.bin'
00628             pr['CoreDump'] = ('core',)
00629             pr.add_gdb_info()
00630         finally:
00631             os.unlink(script)
00632             os.unlink(script + '.bin')
00633             os.unlink('core')
00634 
00635         self._validate_gdb_fields(pr)
00636         self.assertTrue("<stdin>:2: main: Assertion `1 < 0' failed." in
00637                         pr['AssertionMessage'], pr['AssertionMessage'])
00638         self.assertFalse(pr['AssertionMessage'].startswith('$'), pr['AssertionMessage'])
00639         self.assertFalse('= 0x' in pr['AssertionMessage'], pr['AssertionMessage'])
00640         self.assertFalse(pr['AssertionMessage'].endswith('\\n'), pr['AssertionMessage'])
00641 
00642         # abort with internal error
00643         (fd, script) = tempfile.mkstemp()
00644         assert not os.path.exists('core')
00645         try:
00646             os.close(fd)
00647 
00648             # create a test script which produces a core dump for us
00649             with open(script, 'w') as fd:
00650                 fd.write('''#!/bin/sh
00651 gcc -O2 -D_FORTIFY_SOURCE=2 -o $0.bin -x c - <<EOF
00652 #include <string.h>
00653 int main(int argc, char *argv[]) {
00654     char buf[8];
00655     strcpy(buf, argv[1]);
00656     return 0;
00657 }
00658 EOF
00659 ulimit -c unlimited
00660 LIBC_FATAL_STDERR_=1 $0.bin aaaaaaaaaaaaaaaa 2>/dev/null
00661 ''')
00662             os.chmod(script, 0o755)
00663 
00664             # call script and verify that it gives us a proper ELF core dump
00665             assert subprocess.call([script]) != 0
00666             self._validate_core('core')
00667 
00668             pr = apport.report.Report()
00669             pr['ExecutablePath'] = script + '.bin'
00670             pr['CoreDump'] = ('core',)
00671             pr.add_gdb_info()
00672         finally:
00673             os.unlink(script)
00674             os.unlink(script + '.bin')
00675             os.unlink('core')
00676 
00677         self._validate_gdb_fields(pr)
00678         self.assertTrue("** buffer overflow detected ***: %s.bin terminated" % (script) in
00679                         pr['AssertionMessage'], pr['AssertionMessage'])
00680         self.assertFalse(pr['AssertionMessage'].startswith('$'), pr['AssertionMessage'])
00681         self.assertFalse('= 0x' in pr['AssertionMessage'], pr['AssertionMessage'])
00682         self.assertFalse(pr['AssertionMessage'].endswith('\\n'), pr['AssertionMessage'])
00683 
00684         # abort without assertion
00685         (fd, script) = tempfile.mkstemp()
00686         assert not os.path.exists('core')
00687         try:
00688             os.close(fd)
00689 
00690             # create a test script which produces a core dump for us
00691             with open(script, 'w') as fd:
00692                 fd.write('''#!/bin/sh
00693 gcc -o $0.bin -x c - <<EOF
00694 #include <stdlib.h>
00695 int main() { abort(); }
00696 EOF
00697 ulimit -c unlimited
00698 $0.bin 2>/dev/null
00699 ''')
00700             os.chmod(script, 0o755)
00701 
00702             # call script and verify that it gives us a proper ELF core dump
00703             assert subprocess.call([script]) != 0
00704             self._validate_core('core')
00705 
00706             pr = apport.report.Report()
00707             pr['ExecutablePath'] = script + '.bin'
00708             pr['CoreDump'] = ('core',)
00709             pr.add_gdb_info()
00710         finally:
00711             os.unlink(script)
00712             os.unlink(script + '.bin')
00713             os.unlink('core')
00714 
00715         self._validate_gdb_fields(pr)
00716         self.assertFalse('AssertionMessage' in pr, pr.get('AssertionMessage'))
00717 
00718     def test_search_bug_patterns(self):
00719         '''search_bug_patterns().'''
00720 
00721         patterns = tempfile.NamedTemporaryFile(prefix='apport-')
00722         # create some test patterns
00723         patterns.write(b'''<?xml version="1.0"?>
00724 <patterns>
00725     <pattern url="http://bugtracker.net/bugs/1">
00726         <re key="Package">^bash </re>
00727         <re key="Foo">ba.*r</re>
00728     </pattern>
00729     <pattern url="http://bugtracker.net/bugs/2">
00730         <re key="Package">^bash 1-2$</re>
00731         <re key="Foo">write_(hello|goodbye)</re>
00732     </pattern>
00733     <pattern url="http://bugtracker.net/bugs/3">
00734         <re key="Package">^coreutils </re>
00735         <re key="Bar">^1$</re>
00736     </pattern>
00737     <pattern url="http://bugtracker.net/bugs/4">
00738         <re key="Package">^coreutils </re>
00739         <re></re>
00740         <re key="Bar">*</re> <!-- invalid RE -->
00741         <re key="broken">+[1^</re>
00742     </pattern>
00743     <pattern url="http://bugtracker.net/bugs/5">
00744         <re key="SourcePackage">^bazaar$</re>
00745         <re key="LogFile">AssertionError</re>
00746     </pattern>
00747 </patterns>''')
00748         patterns.flush()
00749 
00750         # invalid XML
00751         invalid = tempfile.NamedTemporaryFile(prefix='apport-')
00752         invalid.write(b'''<?xml version="1.0"?>
00753 </patterns>''')
00754         invalid.flush()
00755 
00756         # create some reports
00757         r_bash = apport.report.Report()
00758         r_bash['Package'] = 'bash 1-2'
00759         r_bash['Foo'] = 'bazaar'
00760 
00761         r_bazaar = apport.report.Report()
00762         r_bazaar['Package'] = 'bazaar 2-1'
00763         r_bazaar['SourcePackage'] = 'bazaar'
00764         r_bazaar['LogFile'] = 'AssertionError'
00765 
00766         r_coreutils = apport.report.Report()
00767         r_coreutils['Package'] = 'coreutils 1'
00768         r_coreutils['Bar'] = '1'
00769 
00770         r_invalid = apport.report.Report()
00771         r_invalid['Package'] = 'invalid 1'
00772 
00773         pattern_url = 'file://' + patterns.name
00774 
00775         # positive match cases
00776         self.assertEqual(r_bash.search_bug_patterns(pattern_url),
00777                          'http://bugtracker.net/bugs/1')
00778         r_bash['Foo'] = 'write_goodbye'
00779         self.assertEqual(r_bash.search_bug_patterns(pattern_url),
00780                          'http://bugtracker.net/bugs/2')
00781         self.assertEqual(r_coreutils.search_bug_patterns(pattern_url),
00782                          'http://bugtracker.net/bugs/3')
00783         self.assertEqual(r_bazaar.search_bug_patterns(pattern_url),
00784                          'http://bugtracker.net/bugs/5')
00785 
00786         # also works for CompressedValues
00787         r_bash_compressed = r_bash.copy()
00788         r_bash_compressed['Foo'] = problem_report.CompressedValue(b'bazaar')
00789         self.assertEqual(r_bash_compressed.search_bug_patterns(pattern_url),
00790                          'http://bugtracker.net/bugs/1')
00791 
00792         # negative match cases
00793         r_bash['Package'] = 'bash-static 1-2'
00794         self.assertEqual(r_bash.search_bug_patterns(pattern_url), None)
00795         r_bash['Package'] = 'bash 1-21'
00796         self.assertEqual(r_bash.search_bug_patterns(pattern_url), None,
00797                          'does not match on wrong bash version')
00798         r_bash['Foo'] = 'zz'
00799         self.assertEqual(r_bash.search_bug_patterns(pattern_url), None,
00800                          'does not match on wrong Foo value')
00801         r_coreutils['Bar'] = '11'
00802         self.assertEqual(r_coreutils.search_bug_patterns(pattern_url), None,
00803                          'does not match on wrong Bar value')
00804         r_bazaar['SourcePackage'] = 'launchpad'
00805         self.assertEqual(r_bazaar.search_bug_patterns(pattern_url), None,
00806                          'does not match on wrong source package')
00807         r_bazaar['LogFile'] = ''
00808         self.assertEqual(r_bazaar.search_bug_patterns(pattern_url), None,
00809                          'does not match on empty attribute')
00810 
00811         # various errors to check for robustness (no exceptions, just None
00812         # return value)
00813         del r_coreutils['Bar']
00814         self.assertEqual(r_coreutils.search_bug_patterns(pattern_url), None,
00815                          'does not match on nonexisting key')
00816         self.assertEqual(r_invalid.search_bug_patterns('file://' + invalid.name), None,
00817                          'gracefully handles invalid XML')
00818         r_coreutils['Package'] = 'other 2'
00819         self.assertEqual(r_bash.search_bug_patterns('file:///nonexisting/directory/'), None,
00820                          'gracefully handles nonexisting base path')
00821         # existing host, but no bug patterns
00822         self.assertEqual(r_bash.search_bug_patterns('http://security.ubuntu.com/'), None,
00823                          'gracefully handles base path without bug patterns')
00824         # nonexisting host
00825         self.assertEqual(r_bash.search_bug_patterns('http://nonexisting.domain/'), None,
00826                          'gracefully handles nonexisting URL domain')
00827 
00828     def test_add_hooks_info(self):
00829         '''add_hooks_info().'''
00830 
00831         orig_hook_dir = apport.report._hook_dir
00832         apport.report._hook_dir = tempfile.mkdtemp()
00833         orig_common_hook_dir = apport.report._common_hook_dir
00834         apport.report._common_hook_dir = tempfile.mkdtemp()
00835         try:
00836             with open(os.path.join(apport.report._hook_dir, 'foo.py'), 'w') as fd:
00837                 fd.write('''
00838 import sys
00839 def add_info(report):
00840     report['Field1'] = 'Field 1'
00841     report['Field2'] = 'Field 2\\nBla'
00842     if 'Spethial' in report:
00843         raise StopIteration
00844 ''')
00845 
00846             with open(os.path.join(apport.report._common_hook_dir, 'foo1.py'), 'w') as fd:
00847                 fd.write('''
00848 def add_info(report):
00849     report['CommonField1'] = 'CommonField 1'
00850     if report['Package'] == 'commonspethial':
00851         raise StopIteration
00852 ''')
00853             with open(os.path.join(apport.report._common_hook_dir, 'foo2.py'), 'w') as fd:
00854                 fd.write('''
00855 def add_info(report):
00856     report['CommonField2'] = 'CommonField 2'
00857 ''')
00858             with open(os.path.join(apport.report._common_hook_dir, 'foo3.py'), 'w') as fd:
00859                 fd.write('''
00860 def add_info(report, ui):
00861     report['CommonField3'] = str(ui)
00862 ''')
00863 
00864             # should only catch .py files
00865             with open(os.path.join(apport.report._common_hook_dir, 'notme'), 'w') as fd:
00866                 fd.write('''
00867 def add_info(report):
00868     report['BadField'] = 'XXX'
00869 ''')
00870             r = apport.report.Report()
00871             r['Package'] = 'bar'
00872             # should not throw any exceptions
00873             self.assertEqual(r.add_hooks_info('fake_ui'), False)
00874             self.assertEqual(set(r.keys()),
00875                              set(['ProblemType', 'Date', 'Package',
00876                                   'CommonField1', 'CommonField2',
00877                                   'CommonField3']),
00878                              'report has required fields')
00879 
00880             r = apport.report.Report()
00881             r['Package'] = 'baz 1.2-3'
00882             # should not throw any exceptions
00883             self.assertEqual(r.add_hooks_info('fake_ui'), False)
00884             self.assertEqual(set(r.keys()),
00885                              set(['ProblemType', 'Date', 'Package',
00886                                   'CommonField1', 'CommonField2',
00887                                   'CommonField3']),
00888                              'report has required fields')
00889 
00890             r = apport.report.Report()
00891             r['Package'] = 'foo'
00892             self.assertEqual(r.add_hooks_info('fake_ui'), False)
00893             self.assertEqual(set(r.keys()),
00894                              set(['ProblemType', 'Date', 'Package', 'Field1',
00895                                   'Field2', 'CommonField1', 'CommonField2',
00896                                   'CommonField3']),
00897                              'report has required fields')
00898             self.assertEqual(r['Field1'], 'Field 1')
00899             self.assertEqual(r['Field2'], 'Field 2\nBla')
00900             self.assertEqual(r['CommonField1'], 'CommonField 1')
00901             self.assertEqual(r['CommonField2'], 'CommonField 2')
00902             self.assertEqual(r['CommonField3'], 'fake_ui')
00903 
00904             r = apport.report.Report()
00905             r['Package'] = 'foo 4.5-6'
00906             self.assertEqual(r.add_hooks_info('fake_ui'), False)
00907             self.assertEqual(set(r.keys()),
00908                              set(['ProblemType', 'Date', 'Package', 'Field1',
00909                                   'Field2', 'CommonField1', 'CommonField2',
00910                                   'CommonField3']),
00911                              'report has required fields')
00912             self.assertEqual(r['Field1'], 'Field 1')
00913             self.assertEqual(r['Field2'], 'Field 2\nBla')
00914             self.assertEqual(r['CommonField1'], 'CommonField 1')
00915             self.assertEqual(r['CommonField2'], 'CommonField 2')
00916 
00917             # test hook abort
00918             r['Spethial'] = '1'
00919             self.assertEqual(r.add_hooks_info('fake_ui'), True)
00920             r = apport.report.Report()
00921             r['Package'] = 'commonspethial'
00922             self.assertEqual(r.add_hooks_info('fake_ui'), True)
00923 
00924             # source package hook
00925             with open(os.path.join(apport.report._hook_dir, 'source_foo.py'), 'w') as fd:
00926                 fd.write('''
00927 def add_info(report, ui):
00928     report['Field1'] = 'Field 1'
00929     report['Field2'] = 'Field 2\\nBla'
00930     if report['Package'] == 'spethial':
00931         raise StopIteration
00932 ''')
00933             r = apport.report.Report()
00934             r['SourcePackage'] = 'foo'
00935             r['Package'] = 'libfoo 3'
00936             self.assertEqual(r.add_hooks_info('fake_ui'), False)
00937             self.assertEqual(set(r.keys()),
00938                              set(['ProblemType', 'Date', 'Package',
00939                                   'SourcePackage', 'Field1', 'Field2',
00940                                   'CommonField1', 'CommonField2',
00941                                   'CommonField3']),
00942                              'report has required fields')
00943             self.assertEqual(r['Field1'], 'Field 1')
00944             self.assertEqual(r['Field2'], 'Field 2\nBla')
00945             self.assertEqual(r['CommonField1'], 'CommonField 1')
00946             self.assertEqual(r['CommonField2'], 'CommonField 2')
00947             self.assertEqual(r['CommonField3'], 'fake_ui')
00948 
00949             # test hook abort
00950             r['Package'] = 'spethial'
00951             self.assertEqual(r.add_hooks_info('fake_ui'), True)
00952 
00953         finally:
00954             shutil.rmtree(apport.report._hook_dir)
00955             shutil.rmtree(apport.report._common_hook_dir)
00956             apport.report._hook_dir = orig_hook_dir
00957             apport.report._common_hook_dir = orig_common_hook_dir
00958 
00959     def test_ignoring(self):
00960         '''mark_ignore() and check_ignored().'''
00961 
00962         orig_ignore_file = apport.report.apport.report._ignore_file
00963         workdir = tempfile.mkdtemp()
00964         apport.report.apport.report._ignore_file = os.path.join(workdir, 'ignore.xml')
00965         try:
00966             with open(os.path.join(workdir, 'bash'), 'w') as fd:
00967                 fd.write('bash')
00968             with open(os.path.join(workdir, 'crap'), 'w') as fd:
00969                 fd.write('crap')
00970 
00971             bash_rep = apport.report.Report()
00972             bash_rep['ExecutablePath'] = os.path.join(workdir, 'bash')
00973             crap_rep = apport.report.Report()
00974             crap_rep['ExecutablePath'] = os.path.join(workdir, 'crap')
00975             # must be able to deal with executables that do not exist any more
00976             cp_rep = apport.report.Report()
00977             cp_rep['ExecutablePath'] = os.path.join(workdir, 'cp')
00978 
00979             # no ignores initially
00980             self.assertEqual(bash_rep.check_ignored(), False)
00981             self.assertEqual(crap_rep.check_ignored(), False)
00982             self.assertEqual(cp_rep.check_ignored(), False)
00983 
00984             # ignore crap now
00985             crap_rep.mark_ignore()
00986             self.assertEqual(bash_rep.check_ignored(), False)
00987             self.assertEqual(crap_rep.check_ignored(), True)
00988             self.assertEqual(cp_rep.check_ignored(), False)
00989 
00990             # ignore bash now
00991             bash_rep.mark_ignore()
00992             self.assertEqual(bash_rep.check_ignored(), True)
00993             self.assertEqual(crap_rep.check_ignored(), True)
00994             self.assertEqual(cp_rep.check_ignored(), False)
00995 
00996             # poke crap so that it has a newer timestamp
00997             time.sleep(1)
00998             with open(os.path.join(workdir, 'crap'), 'w') as fd:
00999                 fd.write('crapnew')
01000             self.assertEqual(bash_rep.check_ignored(), True)
01001             self.assertEqual(crap_rep.check_ignored(), False)
01002             self.assertEqual(cp_rep.check_ignored(), False)
01003 
01004             # do not complain about an empty ignore file
01005             with open(apport.report.apport.report._ignore_file, 'w') as fd:
01006                 fd.write('')
01007             self.assertEqual(bash_rep.check_ignored(), False)
01008             self.assertEqual(crap_rep.check_ignored(), False)
01009             self.assertEqual(cp_rep.check_ignored(), False)
01010 
01011             # does not crash if the executable went away under our feet
01012             crap_rep['ExecutablePath'] = '/non existing'
01013             crap_rep.mark_ignore()
01014             self.assertEqual(os.path.getsize(apport.report.apport.report._ignore_file), 0)
01015         finally:
01016             shutil.rmtree(workdir)
01017             apport.report.apport.report._ignore_file = orig_ignore_file
01018 
01019     def test_blacklisting(self):
01020         '''check_ignored() for system-wise blacklist.'''
01021 
01022         orig_blacklist_dir = apport.report._blacklist_dir
01023         apport.report._blacklist_dir = tempfile.mkdtemp()
01024         orig_ignore_file = apport.report._ignore_file
01025         apport.report._ignore_file = '/nonexistant'
01026         try:
01027             bash_rep = apport.report.Report()
01028             bash_rep['ExecutablePath'] = '/bin/bash'
01029             crap_rep = apport.report.Report()
01030             crap_rep['ExecutablePath'] = '/bin/crap'
01031 
01032             # no ignores initially
01033             self.assertEqual(bash_rep.check_ignored(), False)
01034             self.assertEqual(crap_rep.check_ignored(), False)
01035 
01036             # should not stumble over comments
01037             with open(os.path.join(apport.report._blacklist_dir, 'README'), 'w') as fd:
01038                 fd.write('# Ignore file\n#/bin/bash\n')
01039 
01040             # no ignores on nonmatching paths
01041             with open(os.path.join(apport.report._blacklist_dir, 'bl1'), 'w') as fd:
01042                 fd.write('/bin/bas\n/bin/bashh\nbash\nbin/bash\n')
01043             self.assertEqual(bash_rep.check_ignored(), False)
01044             self.assertEqual(crap_rep.check_ignored(), False)
01045 
01046             # ignore crap now
01047             with open(os.path.join(apport.report._blacklist_dir, 'bl_2'), 'w') as fd:
01048                 fd.write('/bin/crap\n')
01049             self.assertEqual(bash_rep.check_ignored(), False)
01050             self.assertEqual(crap_rep.check_ignored(), True)
01051 
01052             # ignore bash now
01053             with open(os.path.join(apport.report._blacklist_dir, 'bl1'), 'a') as fd:
01054                 fd.write('/bin/bash\n')
01055             self.assertEqual(bash_rep.check_ignored(), True)
01056             self.assertEqual(crap_rep.check_ignored(), True)
01057         finally:
01058             shutil.rmtree(apport.report._blacklist_dir)
01059             apport.report._blacklist_dir = orig_blacklist_dir
01060             apport.report._ignore_file = orig_ignore_file
01061 
01062     def test_whitelisting(self):
01063         '''check_ignored() for system-wise whitelist.'''
01064 
01065         orig_whitelist_dir = apport.report._whitelist_dir
01066         apport.report._whitelist_dir = tempfile.mkdtemp()
01067         orig_ignore_file = apport.report.apport.report._ignore_file
01068         apport.report.apport.report._ignore_file = '/nonexistant'
01069         try:
01070             bash_rep = apport.report.Report()
01071             bash_rep['ExecutablePath'] = '/bin/bash'
01072             crap_rep = apport.report.Report()
01073             crap_rep['ExecutablePath'] = '/bin/crap'
01074 
01075             # no ignores without any whitelist
01076             self.assertEqual(bash_rep.check_ignored(), False)
01077             self.assertEqual(crap_rep.check_ignored(), False)
01078 
01079             # should not stumble over comments
01080             with open(os.path.join(apport.report._whitelist_dir, 'README'), 'w') as fd:
01081                 fd.write('# Ignore file\n#/bin/bash\n')
01082 
01083             # accepts matching paths
01084             with open(os.path.join(apport.report._whitelist_dir, 'wl1'), 'w') as fd:
01085                 fd.write('/bin/bash\n')
01086             self.assertEqual(bash_rep.check_ignored(), False)
01087             self.assertEqual(crap_rep.check_ignored(), True)
01088 
01089             # also accept crap now
01090             with open(os.path.join(apport.report._whitelist_dir, 'wl_2'), 'w') as fd:
01091                 fd.write('/bin/crap\n')
01092             self.assertEqual(bash_rep.check_ignored(), False)
01093             self.assertEqual(crap_rep.check_ignored(), False)
01094 
01095             # only complete matches accepted
01096             with open(os.path.join(apport.report._whitelist_dir, 'wl1'), 'w') as fd:
01097                 fd.write('/bin/bas\n/bin/bashh\nbash\n')
01098             self.assertEqual(bash_rep.check_ignored(), True)
01099             self.assertEqual(crap_rep.check_ignored(), False)
01100         finally:
01101             shutil.rmtree(apport.report._whitelist_dir)
01102             apport.report._whitelist_dir = orig_whitelist_dir
01103             apport.report.apport.report._ignore_file = orig_ignore_file
01104 
01105     def test_has_useful_stacktrace(self):
01106         '''has_useful_stacktrace().'''
01107 
01108         r = apport.report.Report()
01109         self.assertFalse(r.has_useful_stacktrace())
01110 
01111         r['StacktraceTop'] = ''
01112         self.assertFalse(r.has_useful_stacktrace())
01113 
01114         r['StacktraceTop'] = '?? ()'
01115         self.assertFalse(r.has_useful_stacktrace())
01116 
01117         r['StacktraceTop'] = '?? ()\n?? ()'
01118         self.assertFalse(r.has_useful_stacktrace())
01119 
01120         r['StacktraceTop'] = 'read () from /lib/libc.6.so\n?? ()'
01121         self.assertFalse(r.has_useful_stacktrace())
01122 
01123         r['StacktraceTop'] = 'read () from /lib/libc.6.so\n?? ()\n?? ()\n?? ()'
01124         self.assertFalse(r.has_useful_stacktrace())
01125 
01126         r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
01127         self.assertTrue(r.has_useful_stacktrace())
01128 
01129         r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so\n?? ()'
01130         self.assertTrue(r.has_useful_stacktrace())
01131 
01132         r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so\n?? ()\n?? ()'
01133         self.assertTrue(r.has_useful_stacktrace())
01134 
01135         r['StacktraceTop'] = 'read () from /lib/libc.6.so\n?? ()\nfoo (i=1) from /usr/lib/libfoo.so\n?? ()\n?? ()'
01136         self.assertFalse(r.has_useful_stacktrace())
01137 
01138     def test_standard_title(self):
01139         '''standard_title().'''
01140 
01141         report = apport.report.Report()
01142         self.assertEqual(report.standard_title(), None)
01143 
01144         # named signal crash
01145         report['Signal'] = '11'
01146         report['ExecutablePath'] = '/bin/bash'
01147         report['StacktraceTop'] = '''foo()
01148 bar(x=3)
01149 baz()
01150 '''
01151         self.assertEqual(report.standard_title(),
01152                          'bash crashed with SIGSEGV in foo()')
01153 
01154         # unnamed signal crash
01155         report['Signal'] = '42'
01156         self.assertEqual(report.standard_title(),
01157                          'bash crashed with signal 42 in foo()')
01158 
01159         # do not crash on empty StacktraceTop
01160         report['StacktraceTop'] = ''
01161         self.assertEqual(report.standard_title(),
01162                          'bash crashed with signal 42')
01163 
01164         # do not create bug title with unknown function name
01165         report['StacktraceTop'] = '??()\nfoo()'
01166         self.assertEqual(report.standard_title(),
01167                          'bash crashed with signal 42 in foo()')
01168 
01169         # if we do not know any function name, don't mention ??
01170         report['StacktraceTop'] = '??()\n??()'
01171         self.assertEqual(report.standard_title(),
01172                          'bash crashed with signal 42')
01173 
01174         # assertion message
01175         report['Signal'] = '6'
01176         report['ExecutablePath'] = '/bin/bash'
01177         report['AssertionMessage'] = 'foo.c:42 main: i > 0'
01178         self.assertEqual(report.standard_title(),
01179                          'bash assert failure: foo.c:42 main: i > 0')
01180 
01181         # Python crash
01182         report = apport.report.Report()
01183         report['ExecutablePath'] = '/usr/share/apport/apport-gtk'
01184         report['Traceback'] = '''Traceback (most recent call last):
01185 File "/usr/share/apport/apport-gtk", line 202, in <module>
01186 app.run_argv()
01187 File "/var/lib/python-support/python2.5/apport/ui.py", line 161, in run_argv
01188 self.run_crashes()
01189 File "/var/lib/python-support/python2.5/apport/ui.py", line 104, in run_crashes
01190 self.run_crash(f)
01191 File "/var/lib/python-support/python2.5/apport/ui.py", line 115, in run_crash
01192 response = self.ui_present_crash(desktop_entry)
01193 File "/usr/share/apport/apport-gtk", line 67, in ui_present_crash
01194 subprocess.call(['pgrep', '-x',
01195 NameError: global name 'subprocess' is not defined'''
01196         self.assertEqual(report.standard_title(),
01197                          "apport-gtk crashed with NameError in ui_present_crash(): global name 'subprocess' is not defined")
01198 
01199         # slightly weird Python crash
01200         report = apport.report.Report()
01201         report['ExecutablePath'] = '/usr/share/apport/apport-gtk'
01202         report['Traceback'] = '''TypeError: Cannot create a consistent method resolution
01203 order (MRO) for bases GObject, CanvasGroupableIface, CanvasGroupable'''
01204         self.assertEqual(report.standard_title(),
01205                          'apport-gtk crashed with TypeError: Cannot create a consistent method resolution')
01206 
01207         # Python crash with custom message
01208         report = apport.report.Report()
01209         report['ExecutablePath'] = '/usr/share/apport/apport-gtk'
01210         report['Traceback'] = '''Traceback (most recent call last):
01211   File "/x/foo.py", line 242, in setup_chooser
01212     raise "Moo"
01213 Mo?o[a-1]'''
01214 
01215         self.assertEqual(report.standard_title(), 'apport-gtk crashed with Mo?o[a-1] in setup_chooser()')
01216 
01217         # Python crash with custom message with newlines (LP #190947)
01218         report = apport.report.Report()
01219         report['ExecutablePath'] = '/usr/share/apport/apport-gtk'
01220         report['Traceback'] = '''Traceback (most recent call last):
01221   File "/x/foo.py", line 242, in setup_chooser
01222     raise "\nKey: "+key+" isn't set.\nRestarting AWN usually solves this issue\n"
01223 
01224 Key: /apps/avant-window-navigator/app/active_png isn't set.
01225 Restarting AWN usually solves this issue'''
01226 
01227         t = report.standard_title()
01228         self.assertTrue(t.startswith('apport-gtk crashed with'))
01229         self.assertTrue(t.endswith('setup_chooser()'))
01230 
01231         # Python crash at top level in module
01232         report = apport.report.Report()
01233         report['ExecutablePath'] = '/usr/bin/gnome-about'
01234         report['Traceback'] = '''Traceback (most recent call last):
01235   File "/usr/bin/gnome-about", line 30, in <module>
01236     import pygtk
01237   File "/usr/lib/pymodules/python2.6/pygtk.py", line 28, in <module>
01238     import nonexistent
01239 ImportError: No module named nonexistent
01240 '''
01241         self.assertEqual(report.standard_title(),
01242                          "gnome-about crashed with ImportError in /usr/lib/pymodules/python2.6/pygtk.py: No module named nonexistent")
01243 
01244         # Python crash at top level in main program
01245         report = apport.report.Report()
01246         report['ExecutablePath'] = '/usr/bin/dcut'
01247         report['Traceback'] = '''Traceback (most recent call last):
01248   File "/usr/bin/dcut", line 28, in <module>
01249     import nonexistent
01250 ImportError: No module named nonexistent
01251 '''
01252         self.assertEqual(report.standard_title(),
01253                          "dcut crashed with ImportError in __main__: No module named nonexistent")
01254 
01255         # package install problem
01256         report = apport.report.Report('Package')
01257         report['Package'] = 'bash'
01258 
01259         # no ErrorMessage
01260         self.assertEqual(report.standard_title(),
01261                          'package bash failed to install/upgrade')
01262 
01263         # empty ErrorMessage
01264         report['ErrorMessage'] = ''
01265         self.assertEqual(report.standard_title(),
01266                          'package bash failed to install/upgrade')
01267 
01268         # nonempty ErrorMessage
01269         report['ErrorMessage'] = 'botched\nnot found\n'
01270         self.assertEqual(report.standard_title(),
01271                          'package bash failed to install/upgrade: not found')
01272 
01273         # matching package/system architectures
01274         report['Signal'] = '11'
01275         report['ExecutablePath'] = '/bin/bash'
01276         report['StacktraceTop'] = '''foo()
01277 bar(x=3)
01278 baz()
01279 '''
01280         report['PackageArchitecture'] = 'amd64'
01281         report['Architecture'] = 'amd64'
01282         self.assertEqual(report.standard_title(),
01283                          'bash crashed with SIGSEGV in foo()')
01284 
01285         # non-native package (on multiarch)
01286         report['PackageArchitecture'] = 'i386'
01287         self.assertEqual(report.standard_title(),
01288                          'bash crashed with SIGSEGV in foo() [non-native i386 package]')
01289 
01290         # Arch: all package (matches every system architecture)
01291         report['PackageArchitecture'] = 'all'
01292         self.assertEqual(report.standard_title(),
01293                          'bash crashed with SIGSEGV in foo()')
01294 
01295         report = apport.report.Report('KernelOops')
01296         report['OopsText'] = '------------[ cut here ]------------\nkernel BUG at /tmp/oops.c:5!\ninvalid opcode: 0000 [#1] SMP'
01297         self.assertEqual(report.standard_title(), 'kernel BUG at /tmp/oops.c:5!')
01298 
01299     def test_obsolete_packages(self):
01300         '''obsolete_packages().'''
01301 
01302         report = apport.report.Report()
01303         self.assertEqual(report.obsolete_packages(), [])
01304 
01305         # should work without Dependencies
01306         report['Package'] = 'bash 0'
01307         self.assertEqual(report.obsolete_packages(), ['bash'])
01308         report['Package'] = 'bash 0 [modified: /bin/bash]'
01309         self.assertEqual(report.obsolete_packages(), ['bash'])
01310         report['Package'] = 'bash ' + apport.packaging.get_available_version('bash')
01311         self.assertEqual(report.obsolete_packages(), [])
01312 
01313         report['Dependencies'] = 'coreutils 0\ncron 0\n'
01314         self.assertEqual(report.obsolete_packages(), ['coreutils', 'cron'])
01315 
01316         report['Dependencies'] = 'coreutils %s [modified: /bin/mount]\ncron 0\n' % \
01317             apport.packaging.get_available_version('coreutils')
01318         self.assertEqual(report.obsolete_packages(), ['cron'])
01319 
01320         report['Dependencies'] = 'coreutils %s\ncron %s\n' % (
01321             apport.packaging.get_available_version('coreutils'),
01322             apport.packaging.get_available_version('cron'))
01323         self.assertEqual(report.obsolete_packages(), [])
01324 
01325     def test_gen_stacktrace_top(self):
01326         '''_gen_stacktrace_top().'''
01327 
01328         # nothing to chop off
01329         r = apport.report.Report()
01330         r['Stacktrace'] = '''#0  0x10000488 in h (p=0x0) at crash.c:25
01331 #1  0x100004c8 in g (x=1, y=42) at crash.c:26
01332 #2  0x10000514 in f (x=1) at crash.c:27
01333 #3  0x10000530 in e (x=1) at crash.c:28
01334 #4  0x10000530 in d (x=1) at crash.c:29
01335 #5  0x10000530 in c (x=1) at crash.c:30
01336 #6  0x10000550 in main () at crash.c:31
01337 '''
01338         r._gen_stacktrace_top()
01339         self.assertEqual(r['StacktraceTop'], '''h (p=0x0) at crash.c:25
01340 g (x=1, y=42) at crash.c:26
01341 f (x=1) at crash.c:27
01342 e (x=1) at crash.c:28
01343 d (x=1) at crash.c:29''')
01344 
01345         # nothing to chop off: some addresses missing (LP #269133)
01346         r = apport.report.Report()
01347         r['Stacktrace'] = '''#0 h (p=0x0) at crash.c:25
01348 #1  0x100004c8 in g (x=1, y=42) at crash.c:26
01349 #2 f (x=1) at crash.c:27
01350 #3  0x10000530 in e (x=1) at crash.c:28
01351 #4  0x10000530 in d (x=1) at crash.c:29
01352 #5  0x10000530 in c (x=1) at crash.c:30
01353 #6  0x10000550 in main () at crash.c:31
01354 '''
01355         r._gen_stacktrace_top()
01356         self.assertEqual(r['StacktraceTop'], '''h (p=0x0) at crash.c:25
01357 g (x=1, y=42) at crash.c:26
01358 f (x=1) at crash.c:27
01359 e (x=1) at crash.c:28
01360 d (x=1) at crash.c:29''')
01361 
01362         # single signal handler invocation
01363         r = apport.report.Report()
01364         r['Stacktrace'] = '''#0  0x10000488 in raise () from /lib/libpthread.so.0
01365 #1  0x100004c8 in ??
01366 #2  <signal handler called>
01367 #3  0x10000530 in e (x=1) at crash.c:28
01368 #4  0x10000530 in d (x=1) at crash.c:29
01369 #5  0x10000530 in c (x=1) at crash.c:30
01370 #6  0x10000550 in main () at crash.c:31
01371 '''
01372         r._gen_stacktrace_top()
01373         self.assertEqual(r['StacktraceTop'], '''e (x=1) at crash.c:28
01374 d (x=1) at crash.c:29
01375 c (x=1) at crash.c:30
01376 main () at crash.c:31''')
01377 
01378         # single signal handler invocation: some addresses missing
01379         r = apport.report.Report()
01380         r['Stacktrace'] = '''#0  0x10000488 in raise () from /lib/libpthread.so.0
01381 #1  ??
01382 #2  <signal handler called>
01383 #3  0x10000530 in e (x=1) at crash.c:28
01384 #4  d (x=1) at crash.c:29
01385 #5  0x10000530 in c (x=1) at crash.c:30
01386 #6  0x10000550 in main () at crash.c:31
01387 '''
01388         r._gen_stacktrace_top()
01389         self.assertEqual(r['StacktraceTop'], '''e (x=1) at crash.c:28
01390 d (x=1) at crash.c:29
01391 c (x=1) at crash.c:30
01392 main () at crash.c:31''')
01393 
01394         # stacked signal handler; should only cut the first one
01395         r = apport.report.Report()
01396         r['Stacktrace'] = '''#0  0x10000488 in raise () from /lib/libpthread.so.0
01397 #1  0x100004c8 in ??
01398 #2  <signal handler called>
01399 #3  0x10000530 in e (x=1) at crash.c:28
01400 #4  0x10000530 in d (x=1) at crash.c:29
01401 #5  0x10000123 in raise () from /lib/libpthread.so.0
01402 #6  <signal handler called>
01403 #7  0x10000530 in c (x=1) at crash.c:30
01404 #8  0x10000550 in main () at crash.c:31
01405 '''
01406         r._gen_stacktrace_top()
01407         self.assertEqual(r['StacktraceTop'], '''e (x=1) at crash.c:28
01408 d (x=1) at crash.c:29
01409 raise () from /lib/libpthread.so.0
01410 <signal handler called>
01411 c (x=1) at crash.c:30''')
01412 
01413         # Gnome assertion; should unwind the logs and assert call
01414         r = apport.report.Report()
01415         r['Stacktrace'] = '''#0  0xb7d39cab in IA__g_logv (log_domain=<value optimized out>, log_level=G_LOG_LEVEL_ERROR,
01416     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
01417 #1  0xb7d39f29 in IA__g_log (log_domain=0xb7edbfd0 "libgnomevfs", log_level=G_LOG_LEVEL_ERROR,
01418     format=0xb7d825f0 "file %s: line %d (%s): assertion failed: (%s)") at /build/buildd/glib2.0-2.13.5/glib/gmessages.c:517
01419 #2  0xb7d39fa6 in IA__g_assert_warning (log_domain=0xb7edbfd0 "libgnomevfs", file=0xb7ee1a26 "gnome-vfs-volume.c", line=254,
01420     pretty_function=0xb7ee1920 "gnome_vfs_volume_unset_drive_private", expression=0xb7ee1a39 "volume->priv->drive == drive")
01421     at /build/buildd/glib2.0-2.13.5/glib/gmessages.c:552
01422 No locals.
01423 #3  0xb7ec6c11 in gnome_vfs_volume_unset_drive_private (volume=0x8081a30, drive=0x8078f00) at gnome-vfs-volume.c:254
01424         __PRETTY_FUNCTION__ = "gnome_vfs_volume_unset_drive_private"
01425 #4  0x08054db8 in _gnome_vfs_volume_monitor_disconnected (volume_monitor=0x8070400, drive=0x8078f00) at gnome-vfs-volume-monitor.c:963
01426         vol_list = (GList *) 0x8096d30
01427         current_vol = (GList *) 0x8097470
01428 #5  0x0805951e in _hal_device_removed (hal_ctx=0x8074da8, udi=0x8093be4 "/org/freedesktop/Hal/devices/volume_uuid_92FC9DFBFC9DDA35")
01429     at gnome-vfs-hal-mounts.c:1316
01430         backing_udi = <value optimized out>
01431 #6  0xb7ef1ead in filter_func (connection=0x8075288, message=0x80768d8, user_data=0x8074da8) at libhal.c:820
01432         udi = <value optimized out>
01433         object_path = 0x8076d40 "/org/freedesktop/Hal/Manager"
01434         error = {name = 0x0, message = 0x0, dummy1 = 1, dummy2 = 0, dummy3 = 0, dummy4 = 1, dummy5 = 0, padding1 = 0xb7e50c00}
01435 #7  0xb7e071d2 in dbus_connection_dispatch (connection=0x8075288) at dbus-connection.c:4267
01436 #8  0xb7e33dfd in ?? () from /usr/lib/libdbus-glib-1.so.2'''
01437         r._gen_stacktrace_top()
01438         self.assertEqual(r['StacktraceTop'], '''gnome_vfs_volume_unset_drive_private (volume=0x8081a30, drive=0x8078f00) at gnome-vfs-volume.c:254
01439 _gnome_vfs_volume_monitor_disconnected (volume_monitor=0x8070400, drive=0x8078f00) at gnome-vfs-volume-monitor.c:963
01440 _hal_device_removed (hal_ctx=0x8074da8, udi=0x8093be4 "/org/freedesktop/Hal/devices/volume_uuid_92FC9DFBFC9DDA35")
01441 filter_func (connection=0x8075288, message=0x80768d8, user_data=0x8074da8) at libhal.c:820
01442 dbus_connection_dispatch (connection=0x8075288) at dbus-connection.c:4267''')
01443 
01444         # XError (taken from LP#848808)
01445         r = apport.report.Report()
01446         r['Stacktrace'] = '''#0  0x007cf416 in __kernel_vsyscall ()
01447 No symbol table info available.
01448 #1  0x01017c8f in __GI_raise (sig=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
01449 #2  0x0101b2b5 in __GI_abort () at abort.c:92
01450 #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
01451 #4  0x0806989c in x_error_handler (error=0xbf924acc, xdisplay=0x9104b88) at core/errors.c:247
01452 #5  x_error_handler (xdisplay=0x9104b88, error=0xbf924acc) at core/errors.c:203
01453 #6  0x00e97d3b in _XError (dpy=0x9104b88, rep=0x9131840) at ../../src/XlibInt.c:1583
01454 #7  0x00e9490d in handle_error (dpy=0x9104b88, err=0x9131840, in_XReply=0) at ../../src/xcb_io.c:212
01455 #8  0x00e94967 in handle_response (dpy=0x9104b88, response=0x9131840, in_XReply=0) at ../../src/xcb_io.c:324
01456 #9  0x00e952fe in _XReadEvents (dpy=0x9104b88) at ../../src/xcb_io.c:425
01457 #10 0x00e93663 in XWindowEvent (dpy=0x9104b88, w=16777220, mask=4194304, event=0xbf924c6c) at ../../src/WinEvent.c:79
01458 #11 0x0806071c in meta_display_get_current_time_roundtrip (display=0x916d7d0) at core/display.c:1217
01459 #12 0x08089f64 in meta_window_show (window=0x91ccfc8) at core/window.c:2165
01460 #13 implement_showing (window=0x91ccfc8, showing=1) at core/window.c:1583
01461 #14 0x080879cc in meta_window_flush_calc_showing (window=0x91ccfc8) at core/window.c:1806'''
01462         r._gen_stacktrace_top()
01463         self.assertEqual(r['StacktraceTop'], '''meta_display_get_current_time_roundtrip (display=0x916d7d0) at core/display.c:1217
01464 meta_window_show (window=0x91ccfc8) at core/window.c:2165
01465 implement_showing (window=0x91ccfc8, showing=1) at core/window.c:1583
01466 meta_window_flush_calc_showing (window=0x91ccfc8) at core/window.c:1806''')
01467 
01468         # another XError (taken from LP#834403)
01469         r = apport.report.Report()
01470         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
01471 #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
01472 #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
01473 #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
01474 #4  gdk_x_error (xdisplay=<optimized out>, error=0x7fff50bf0dc0) at /build/buildd/gtk+3.0-3.1.12/./gdk/x11/gdkmain-x11.c:275
01475 #5  0x00007fd41d5a301f in _XError (dpy=0x2425370, rep=<optimized out>) at ../../src/XlibInt.c:1583
01476 #6  0x00007fd41d59fdd1 in handle_error (dpy=0x2425370, err=0x7fd408707980, in_XReply=<optimized out>) at ../../src/xcb_io.c:212
01477 #7  0x00007fd41d5a0d27 in _XReply (dpy=0x2425370, rep=0x7fff50bf0f60, extra=0, discard=0) at ../../src/xcb_io.c:698
01478 #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
01479 #9  0x00007fd41938269e in window_is_xembed (w=<optimized out>, d=<optimized out>) at canberra-gtk-module.c:373
01480 #10 dispatch_sound_event (d=0x32f6a30) at canberra-gtk-module.c:454
01481 #11 dispatch_queue () at canberra-gtk-module.c:815'''
01482         r._gen_stacktrace_top()
01483         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
01484 window_is_xembed (w=<optimized out>, d=<optimized out>) at canberra-gtk-module.c:373
01485 dispatch_sound_event (d=0x32f6a30) at canberra-gtk-module.c:454
01486 dispatch_queue () at canberra-gtk-module.c:815''')
01487 
01488         # problem with too old gdb, only assertion, nothing else
01489         r = apport.report.Report()
01490         r['Stacktrace'] = '''#0  0x00987416 in __kernel_vsyscall ()
01491 No symbol table info available.
01492 #1  0x00ebecb1 in *__GI_raise (sig=6)
01493         selftid = 945
01494 #2  0x00ec218e in *__GI_abort () at abort.c:59
01495         save_stage = Unhandled dwarf expression opcode 0x9f
01496 '''
01497         r._gen_stacktrace_top()
01498         self.assertEqual(r['StacktraceTop'], '')
01499 
01500         # ignore uninteresting frames
01501         r = apport.report.Report()
01502         r['Stacktrace'] = '''#0  0x00987416 in __kernel_vsyscall ()
01503 #1  __strchr_sse42 () at strchr.S:97
01504 #2 h (p=0x0) at crash.c:25
01505 #3  0x100004c8 in g (x=1, y=42) at crash.c:26
01506 #4  0x10000999 in __memmove_ssse3 ()
01507 #5 f (x=1) at crash.c:27
01508 #6  0x10000530 in e (x=1) at crash.c:28
01509 #7  0x10000999 in __strlen_sse2_back () at strchr.S:42
01510 #8  0x10000530 in d (x=1) at crash.c:29
01511 #9  0x10000530 in c (x=1) at crash.c:30
01512 #10 0x10000550 in main () at crash.c:31
01513 '''
01514         r._gen_stacktrace_top()
01515         self.assertEqual(r['StacktraceTop'], '''h (p=0x0) at crash.c:25
01516 g (x=1, y=42) at crash.c:26
01517 f (x=1) at crash.c:27
01518 e (x=1) at crash.c:28
01519 d (x=1) at crash.c:29''')
01520 
01521     def test_crash_signature(self):
01522         '''crash_signature().'''
01523 
01524         r = apport.report.Report()
01525         self.assertEqual(r.crash_signature(), None)
01526 
01527         # signal crashes
01528         r['Signal'] = '42'
01529         r['ExecutablePath'] = '/bin/crash'
01530 
01531         r['StacktraceTop'] = '''foo_bar (x=1) at crash.c:28
01532 d01 (x=1) at crash.c:29
01533 raise () from /lib/libpthread.so.0
01534 <signal handler called>
01535 __frob::~frob (x=1) at crash.c:30'''
01536 
01537         self.assertEqual(r.crash_signature(), '/bin/crash:42:foo_bar:d01:raise:<signal handler called>:__frob::~frob')
01538 
01539         r['StacktraceTop'] = '''foo_bar (x=1) at crash.c:28
01540 ??
01541 raise () from /lib/libpthread.so.0
01542 <signal handler called>
01543 __frob (x=1) at crash.c:30'''
01544         self.assertEqual(r.crash_signature(), None)
01545 
01546         r['StacktraceTop'] = ''
01547         self.assertEqual(r.crash_signature(), None)
01548 
01549         # Python crashes
01550         del r['Signal']
01551         r['Traceback'] = '''Traceback (most recent call last):
01552   File "test.py", line 7, in <module>
01553     print(_f(5))
01554   File "test.py", line 5, in _f
01555     return g_foo00(x+1)
01556   File "test.py", line 2, in g_foo00
01557     return x/0
01558 ZeroDivisionError: integer division or modulo by zero'''
01559         self.assertEqual(r.crash_signature(), '/bin/crash:ZeroDivisionError:test.py@7:_f:g_foo00')
01560 
01561         # sometimes Python traces do not have file references
01562         r['Traceback'] = 'TypeError: function takes exactly 0 arguments (1 given)'
01563         self.assertEqual(r.crash_signature(), '/bin/crash:TypeError')
01564 
01565         r['Traceback'] = 'FooBar'
01566         self.assertEqual(r.crash_signature(), None)
01567 
01568         # kernel
01569         r['ProblemType'] = 'KernelCrash'
01570         r['Stacktrace'] = '''
01571 crash 4.0-8.9
01572 GNU gdb 6.1
01573 GDB is free software, covered by the GNU General Public License, and you are
01574 welcome to change it and/or distribute copies of it under certain conditions.
01575 Type "show copying" to see the conditions.
01576 There is absolutely no warranty for GDB.  Type "show warranty" for details.
01577 This GDB was configured as "i686-pc-linux-gnu"...
01578 
01579       KERNEL: /usr/lib/debug/boot/vmlinux-2.6.31-2-generic
01580     DUMPFILE: /tmp/tmpRJZy_O
01581         CPUS: 1
01582         DATE: Thu Jul  9 12:58:08 2009
01583       UPTIME: 00:00:57
01584 LOAD AVERAGE: 0.15, 0.05, 0.02
01585        TASKS: 173
01586     NODENAME: egon-desktop
01587      RELEASE: 2.6.31-2-generic
01588      VERSION: #16-Ubuntu SMP Mon Jul 6 20:38:51 UTC 2009
01589      MACHINE: i686  (2137 Mhz)
01590       MEMORY: 2 GB
01591        PANIC: "[   57.879776] Oops: 0002 [#1] SMP " (check log for details)
01592          PID: 0
01593      COMMAND: "swapper"
01594         TASK: c073c180  [THREAD_INFO: c0784000]
01595          CPU: 0
01596        STATE: TASK_RUNNING (PANIC)
01597 
01598 PID: 0      TASK: c073c180  CPU: 0   COMMAND: "swapper"
01599  #0 [c0785ba0] sysrq_handle_crash at c03917a3
01600     [RA: c03919c6  SP: c0785ba0  FP: c0785ba0  SIZE: 4]
01601     c0785ba0: c03919c6
01602  #1 [c0785ba0] __handle_sysrq at c03919c4
01603     [RA: c0391a91  SP: c0785ba4  FP: c0785bc8  SIZE: 40]
01604     c0785ba4: c06d4bab  c06d42d2  f6534000  00000004
01605     c0785bb4: 00000086  0000002e  00000001  f6534000
01606     c0785bc4: c0785bcc  c0391a91
01607  #2 [c0785bc8] handle_sysrq at c0391a8c
01608     [RA: c0389961  SP: c0785bcc  FP: c0785bd0  SIZE: 8]
01609     c0785bcc: c0785c0c  c0389961
01610  #3 [c0785bd0] kbd_keycode at c038995c
01611     [RA: c0389b8b  SP: c0785bd4  FP: c0785c10  SIZE: 64]
01612     c0785bd4: c056f96a  c0785be4  00000096  c07578c0
01613     c0785be4: 00000001  f6ac6e00  f6ac6e00  00000001
01614     c0785bf4: 00000000  00000000  0000002e  0000002e
01615     c0785c04: 00000001  f70d6850  c0785c1c  c0389b8b
01616  #4 [c0785c10] kbd_event at c0389b86
01617     [RA: c043140c  SP: c0785c14  FP: c0785c20  SIZE: 16]
01618     c0785c14: c0758040  f6910900  c0785c3c  c043140c
01619  #5 [c0785c20] input_pass_event at c0431409
01620     [RA: c04332ce  SP: c0785c24  FP: c0785c40  SIZE: 32]
01621     c0785c24: 00000001  0000002e  00000001  f70d6000
01622     c0785c34: 00000001  0000002e  c0785c64  c04332ce
01623  #6 [c0785c40] input_handle_event at c04332c9
01624     [RA: c0433ac6  SP: c0785c44  FP: c0785c68  SIZE: 40]
01625     c0785c44: 00000001  ffff138d  0000003d  00000001
01626     c0785c54: f70d6000  00000001  f70d6000  0000002e
01627     c0785c64: c0785c84  c0433ac6
01628  #7 [c0785c68] input_event at c0433ac1
01629     [RA: c0479806  SP: c0785c6c  FP: c0785c88  SIZE: 32]
01630     c0785c6c: 00000001  00000092  f70d677c  f70d70b4
01631     c0785c7c: 0000002e  f70d7000  c0785ca8  c0479806
01632  #8 [c0785c88] hidinput_hid_event at c0479801
01633     [RA: c0475b31  SP: c0785c8c  FP: c0785cac  SIZE: 36]
01634     c0785c8c: 00000001  00000007  c0785c00  f70d6000
01635     c0785c9c: f70d70b4  f70d5000  f70d7000  c0785cc4
01636     c0785cac: c0475b31
01637     [RA: 0  SP: c0785ffc  FP: c0785ffc  SIZE: 0]
01638    PID    PPID  CPU   TASK    ST  %MEM     VSZ    RSS  COMM
01639 >     0      0   0  c073c180  RU   0.0       0      0  [swapper]
01640       1      0   1  f7038000  IN   0.1    3096   1960  init
01641       2      0   0  f7038c90  IN   0.0       0      0  [kthreadd]
01642     271      2   1  f72bf110  IN   0.0       0      0  [bluetooth]
01643     325      2   1  f71c25b0  IN   0.0       0      0  [khungtaskd]
01644    1404      2   0  f6b5bed0  IN   0.0       0      0  [kpsmoused]
01645    1504      2   1  f649cb60  IN   0.0       0      0  [hd-audio0]
01646    2055      1   0  f6a18000  IN   0.0    1824    536  getty
01647    2056      1   0  f6a1d7f0  IN   0.0    1824    536  getty
01648    2061      1   0  f6a1f110  IN   0.1    3132   1604  login
01649    2062      1   1  f6a18c90  IN   0.0    1824    540  getty
01650    2063      1   1  f6b58c90  IN   0.0    1824    540  getty
01651    2130      1   0  f6b5f110  IN   0.0    2200   1032  acpid
01652    2169      1   0  f69ebed0  IN   0.0    2040    664  syslogd
01653    2192      1   1  f65b3ed0  IN   0.0    1976    532  dd
01654    2194      1   1  f6b5a5b0  IN   0.1    3996   2712  klogd
01655    2217      1   0  f6b74b60  IN   0.1    3008   1120  dbus-daemon
01656    2248      1   0  f65b7110  IN   0.2    6896   4304  hald
01657    2251      1   1  f65b3240  IN   0.1   19688   2604  console-kit-dae
01658 RUNQUEUES[0]: c6002320
01659  RT PRIO_ARRAY: c60023c0
01660  CFS RB_ROOT: c600237c
01661   PID: 9      TASK: f703f110  CPU: 0   COMMAND: "events/0"
01662 '''
01663         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')
01664 
01665         # assertion failures
01666         r = apport.report.Report()
01667         r['Signal'] = '6'
01668         r['ExecutablePath'] = '/bin/bash'
01669         r['AssertionMessage'] = 'foo.c:42 main: i > 0'
01670         self.assertEqual(r.crash_signature(), '/bin/bash:foo.c:42 main: i > 0')
01671 
01672     def test_nonascii_data(self):
01673         '''methods get along with non-ASCII data'''
01674 
01675         # fake os.uname() into reporting a non-ASCII name
01676         uname = os.uname()
01677         uname = (uname[0], b't\xe2\x99\xaax'.decode('UTF-8'), uname[2], uname[3], uname[4])
01678         orig_uname = os.uname
01679         os.uname = lambda: uname
01680 
01681         try:
01682             pr = apport.report.Report()
01683             utf8_val = b'\xc3\xa4 ' + uname[1].encode('UTF-8') + b' \xe2\x99\xa5 '
01684             pr['ProcUnicodeValue'] = utf8_val.decode('UTF-8')
01685             pr['ProcByteArrayValue'] = utf8_val
01686 
01687             pr.anonymize()
01688 
01689             exp_utf8 = b'\xc3\xa4 hostname \xe2\x99\xa5 '
01690             self.assertEqual(pr['ProcUnicodeValue'], exp_utf8.decode('UTF-8'))
01691             self.assertEqual(pr['ProcByteArrayValue'], exp_utf8)
01692         finally:
01693             os.uname = orig_uname
01694 
01695     def test_address_to_offset(self):
01696         '''_address_to_offset()'''
01697 
01698         pr = apport.report.Report()
01699 
01700         self.assertRaises(AssertionError, pr._address_to_offset, 0)
01701 
01702         pr['ProcMaps'] = '''
01703 00400000-004df000 r-xp 00000000 08:02 1044485                            /bin/bash
01704 006de000-006df000 r--p 000de000 08:02 1044485                            /bin/bash
01705 01596000-01597000 rw-p 00000000 00:00 0
01706 01597000-015a4000 rw-p 00000000 00:00 0                                  [heap]
01707 7f491f868000-7f491f88a000 r-xp 00000000 08:02 526219                     /lib/x86_64-linux-gnu/libtinfo.so.5.9
01708 7f491fa8f000-7f491fc24000 r-xp 00000000 08:02 522605                     /lib/x86_64-linux-gnu/libc-2.13.so
01709 7f491fc24000-7f491fe23000 ---p 00195000 08:02 522605                     /lib/with spaces !/libfoo.so
01710 7fff6e57b000-7fff6e59c000 rw-p 00000000 00:00 0                          [stack]
01711 7fff6e5ff000-7fff6e600000 r-xp 00000000 00:00 0                          [vdso]
01712 ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
01713 '''
01714 
01715         self.assertEqual(pr._address_to_offset(0x41d703), '/bin/bash+1d703')
01716         self.assertEqual(pr._address_to_offset(0x00007f491fac5687),
01717                          '/lib/x86_64-linux-gnu/libc-2.13.so+36687')
01718 
01719         self.assertEqual(pr._address_to_offset(0x006ddfff), None)
01720         self.assertEqual(pr._address_to_offset(0x006de000), '/bin/bash+0')
01721         self.assertEqual(pr._address_to_offset(0x006df000), '/bin/bash+1000')
01722         self.assertEqual(pr._address_to_offset(0x006df001), None)
01723 
01724         self.assertEqual(pr._address_to_offset(0x7f491fc24010),
01725                          '/lib/with spaces !/libfoo.so+10')
01726 
01727     def test_address_to_offset_live(self):
01728         '''_address_to_offset() for current /proc/pid/maps'''
01729 
01730         # this primarily checks that the parser actually gets along with the
01731         # real /proc/pid/maps and not just with our static test case above
01732         pr = apport.report.Report()
01733         pr.add_proc_info()
01734         self.assertEqual(pr._address_to_offset(0), None)
01735         res = pr._address_to_offset(int(pr['ProcMaps'].split('-', 1)[0], 16) + 5)
01736         self.assertEqual(res.split('+', 1)[1], '5')
01737         self.assertTrue('python' in res.split('+', 1)[0])
01738 
01739     def test_crash_signature_addresses(self):
01740         '''crash_signature_addresses()'''
01741 
01742         pr = apport.report.Report()
01743         self.assertEqual(pr.crash_signature_addresses(), None)
01744 
01745         pr['ExecutablePath'] = '/bin/bash'
01746         pr['Signal'] = '42'
01747         pr['ProcMaps'] = '''
01748 00400000-004df000 r-xp 00000000 08:02 1044485                            /bin/bash
01749 006de000-006df000 r--p 000de000 08:02 1044485                            /bin/bash
01750 01596000-01597000 rw-p 00000000 00:00 0
01751 01597000-015a4000 rw-p 00000000 00:00 0                                  [heap]
01752 7f491f868000-7f491f88a000 r-xp 00000000 08:02 526219                     /lib/x86_64-linux-gnu/libtinfo.so.5.9
01753 7f491fa8f000-7f491fc24000 r-xp 00000000 08:02 522605                     /lib/x86_64-linux-gnu/libc-2.13.so
01754 7f491fc24000-7f491fe23000 ---p 00195000 08:02 522605                     /lib/with spaces !/libfoo.so
01755 7fff6e57b000-7fff6e59c000 rw-p 00000000 00:00 0                          [stack]
01756 7fff6e5ff000-7fff6e600000 r-xp 00000000 00:00 0                          [vdso]
01757 ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
01758 '''
01759 
01760         # no Stacktrace field
01761         self.assertEqual(pr.crash_signature_addresses(), None)
01762 
01763         # good stack trace
01764         pr['Stacktrace'] = '''
01765 #0  0x00007f491fac5687 in kill () at ../sysdeps/unix/syscall-template.S:82
01766 No locals.
01767 #1  0x000000000043fd51 in kill_pid ()
01768 #2  g_main_context_iterate (context=0x1731680) at gmain.c:3068
01769 #3  0x000000000042eb76 in ?? ()
01770 #4  0x00000000004324d8 in ??
01771 No symbol table info available.
01772 #5  0x00000000004707e3 in parse_and_execute ()
01773 #6  0x000000000041d703 in _start ()
01774 '''
01775         self.assertEqual(pr.crash_signature_addresses(),
01776                          '/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])
01777 
01778         # all resolvable, but too short
01779         pr['Stacktrace'] = '#0  0x00007f491fac5687 in kill () at ../sysdeps/unix/syscall-template.S:82'
01780         self.assertEqual(pr.crash_signature_addresses(), None)
01781 
01782         # one unresolvable, but long enough
01783         pr['Stacktrace'] = '''
01784 #0  0x00007f491fac5687 in kill () at ../sysdeps/unix/syscall-template.S:82
01785 No locals.
01786 #1  0x000001000043fd51 in kill_pid ()
01787 #2  g_main_context_iterate (context=0x1731680) at gmain.c:3068
01788 #3  0x000000000042eb76 in ?? ()
01789 #4  0x00000000004324d8 in ??
01790 No symbol table info available.
01791 #5  0x00000000004707e3 in parse_and_execute ()
01792 #6  0x000000000041d715 in main ()
01793 #7  0x000000000041d703 in _start ()
01794 '''
01795         self.assertNotEqual(pr.crash_signature_addresses(), None)
01796 
01797         # two unresolvables, 2/7 is too much
01798         pr['Stacktrace'] = '''
01799 #0  0x00007f491fac5687 in kill () at ../sysdeps/unix/syscall-template.S:82
01800 No locals.
01801 #1  0x000001000043fd51 in kill_pid ()
01802 #2  g_main_context_iterate (context=0x1731680) at gmain.c:3068
01803 #3  0x000001000042eb76 in ?? ()
01804 #4  0x00000000004324d8 in ??
01805 No symbol table info available.
01806 #5  0x00000000004707e3 in parse_and_execute ()
01807 #6  0x000000000041d715 in main ()
01808 #7  0x000000000041d703 in _start ()
01809 '''
01810         self.assertEqual(pr.crash_signature_addresses(), None)
01811 
01812 if __name__ == '__main__':
01813     unittest.main()