~ubuntu-core-dev/ubuntu/maverick/apport/ubuntu

« back to all changes in this revision

Viewing changes to apport/ui.py

  • Committer: Colin Watson
  • Date: 2010-02-10 14:44:12 UTC
  • mfrom: (1621 ubuntu)
  • mto: This revision was merged to the branch mainline in revision 1622.
  • Revision ID: cjwatson@canonical.com-20100210144412-l51zdyocvb376468
mergeĀ fromĀ lucid

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
 
3
3
This encapsulates the workflow and common code for any user interface
4
4
implementation (like GTK, Qt, or CLI).
5
 
 
6
 
Copyright (C) 2007 Canonical Ltd.
7
 
Author: Martin Pitt <martin.pitt@ubuntu.com>
8
 
 
9
 
This program is free software; you can redistribute it and/or modify it
10
 
under the terms of the GNU General Public License as published by the
11
 
Free Software Foundation; either version 2 of the License, or (at your
12
 
option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
13
 
the full text of the license.
14
5
'''
15
6
 
16
 
__version__ = '1.9.3'
 
7
# Copyright (C) 2007 - 2009 Canonical Ltd.
 
8
# Author: Martin Pitt <martin.pitt@ubuntu.com>
 
9
 
10
# This program is free software; you can redistribute it and/or modify it
 
11
# under the terms of the GNU General Public License as published by the
 
12
# Free Software Foundation; either version 2 of the License, or (at your
 
13
# option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
 
14
# the full text of the license.
 
15
 
 
16
__version__ = '1.12'
17
17
 
18
18
import glob, sys, os.path, optparse, time, traceback, locale, gettext, re
19
19
import pwd, errno, urllib, zlib
26
26
 
27
27
symptom_script_dir = '/usr/share/apport/symptoms'
28
28
 
29
 
def thread_collect_info(report, reportfile, package, ui, symptom_script=None):
 
29
def thread_collect_info(report, reportfile, package, ui, symptom_script=None,
 
30
        ignore_uninstalled=False):
30
31
    '''Collect information about report.
31
32
 
32
33
    Encapsulate calls to add_*_info() and update given report, so that this
62
63
            package = apport.fileutils.find_file_package(report['ExecutablePath'])
63
64
        else:
64
65
            raise KeyError, 'called without a package, and report does not have ExecutablePath'
65
 
    report.add_package_info(package)
 
66
    try:
 
67
        report.add_package_info(package)
 
68
 
 
69
        # check package origin
 
70
        if ('Package' not in report or \
 
71
              not apport.packaging.is_distro_package(report['Package'].split()[0])) \
 
72
              and 'CrashDB' not in report:
 
73
            #TRANS: %s is the name of the operating system
 
74
            report['UnreportableReason'] = _('This is not a genuine %s package') % \
 
75
                report['DistroRelease'].split()[0]
 
76
    except ValueError:
 
77
        # this happens if we are collecting information on an uninstalled
 
78
        # package
 
79
        if not ignore_uninstalled:
 
80
            raise
 
81
 
66
82
    if report.add_hooks_info(ui):
67
83
        sys.exit(0)
68
84
 
72
88
        if title:
73
89
            report['Title'] = title
74
90
 
75
 
    # check package origin
76
 
    if ('Package' not in report or \
77
 
          not apport.packaging.is_distro_package(report['Package'].split()[0])) \
78
 
          and 'CrashDB' not in report:
79
 
        #TRANS: %s is the name of the operating system
80
 
        report['UnreportableReason'] = _('This is not a genuine %s package') % \
81
 
            report['DistroRelease'].split()[0]
82
 
 
83
91
    # check obsolete packages
84
92
    if report['ProblemType'] == 'Crash' and \
85
93
        'APPORT_IGNORE_OBSOLETE_PACKAGES' not in os.environ:
255
263
            if self.handle_duplicate():
256
264
                return
257
265
 
258
 
            if self.report.get('ProblemType') in ['Crash', 'KernelCrash',
259
 
                                                  'KernelOops']:
260
 
                response = self.ui_present_report_details()
261
 
                if response == 'cancel':
262
 
                    return
263
 
                if response == 'reduced':
264
 
                    try:
265
 
                        del self.report['CoreDump']
266
 
                    except KeyError:
267
 
                        pass # Huh? Should not happen, but did in https://launchpad.net/bugs/86007
268
 
                else:
269
 
                    assert response == 'full'
 
266
            # confirm what will be sent
 
267
            response = self.ui_present_report_details(False)
 
268
            if response == 'cancel':
 
269
                return
 
270
            if response == 'reduced':
 
271
                try:
 
272
                    del self.report['CoreDump']
 
273
                except KeyError:
 
274
                    pass # Huh? Should not happen, but did in https://launchpad.net/bugs/86007
 
275
            else:
 
276
                assert response == 'full'
270
277
 
271
278
            self.file_report()
272
279
        except IOError, e:
358
365
                self.report['UnreportableReason'])
359
366
            return
360
367
 
361
 
        if not self.handle_duplicate():
362
 
            # we do not confirm contents of bug reports, this might have
363
 
            # sensitive data
 
368
        if self.handle_duplicate():
 
369
            return True
 
370
 
 
371
        # not useful for bug reports, and has potentially sensitive information
 
372
        try:
 
373
            del self.report['ProcCmdline']
 
374
        except KeyError:
 
375
            pass
 
376
 
 
377
        if self.options.save:
364
378
            try:
365
 
                del self.report['ProcCmdline']
366
 
            except KeyError:
367
 
                pass
368
 
 
 
379
                f = open(self.options.save, 'w')
 
380
                self.report.write(f)
 
381
                f.close()
 
382
            except (IOError, OSError), e:
 
383
                self.ui_error_message(_('Cannot create report'), str(e))
 
384
        else:
369
385
            # show what's being sent
370
 
            response = self.ui_present_report_details()
 
386
            response = self.ui_present_report_details(False)
371
387
            if response != 'cancel':
372
388
                self.file_report()
373
389
 
374
390
        return True
375
391
 
 
392
    def run_update_report(self):
 
393
        '''Update an existing bug with locally collected information.'''
 
394
 
 
395
        # avoid irrelevant noise
 
396
        if not self.crashdb.can_update(self.options.update_report):
 
397
            self.ui_error_message(_('Updating problem report'),
 
398
                _('You are not the reporter or subscriber of this '
 
399
                  'problem report, or the report is a duplicate or already '
 
400
                  'closed.\n\nPlease create a new report using "apport-bug".'))
 
401
            return False
 
402
 
 
403
        is_reporter = self.crashdb.is_reporter(self.options.update_report)
 
404
 
 
405
        if not is_reporter:
 
406
            r = self.ui_question_yesno(
 
407
                _('You are not the reporter of this problem report. It '
 
408
                  'is much easier to mark a bug as a duplicate of another '
 
409
                  'than to move your comments and attachments to a new bug.\n\n'
 
410
                  'Subsequently, we recommend that you file a new bug report '
 
411
                  'using "apport-bug" and make a comment in this bug about '
 
412
                  'the one you file.\n\n'
 
413
                  'Do you really want to proceed?'))
 
414
            if not r:
 
415
                return False
 
416
 
 
417
        # list of affected source packages
 
418
        self.report = apport.Report('Bug')
 
419
        if self.options.package:
 
420
            pkgs = [self.options.package.strip()]
 
421
        else:
 
422
            pkgs = self.crashdb.get_affected_packages(self.options.update_report)
 
423
 
 
424
        info_collected = False
 
425
        for p in pkgs:
 
426
            #print 'Collecting apport information for source package %s...' % p
 
427
            self.cur_package = p
 
428
            self.report['SourcePackage'] = p
 
429
            self.report['Package'] = p # no way to find this out
 
430
 
 
431
            # we either must have the package installed or a source package hook
 
432
            # available to collect sensible information
 
433
            try:
 
434
                apport.packaging.get_version(p)
 
435
            except ValueError:
 
436
                if not os.path.exists(os.path.join(apport.report._hook_dir, 'source_%s.py' % p)):
 
437
                    print 'Package %s not installed and no hook available, ignoring' % p
 
438
                    continue
 
439
            self.collect_info(ignore_uninstalled=True)
 
440
            info_collected = True
 
441
 
 
442
        if not info_collected:
 
443
            self.ui_info_message(_('Updating problem report'), 
 
444
                    _('No additional information collected.'))
 
445
            return False
 
446
 
 
447
        self.report.add_user_info()
 
448
        self.report.add_proc_environ()
 
449
 
 
450
        # delete the uninteresting keys
 
451
        del self.report['ProblemType']
 
452
        del self.report['Date']
 
453
        try:
 
454
            del self.report['SourcePackage']
 
455
        except KeyError:
 
456
            pass
 
457
 
 
458
        if len(self.report) == 0:
 
459
            self.ui_info_message(_('Updating problem report'), 
 
460
                    _('No additional information collected.'))
 
461
            return False
 
462
 
 
463
        # show what's being sent
 
464
        response = self.ui_present_report_details(True)
 
465
        if response != 'cancel':
 
466
            self.crashdb.update(self.options.update_report, self.report,
 
467
                    'apport information', change_description=is_reporter,
 
468
                    attachment_comment='apport information')
 
469
            return True
 
470
 
 
471
        return False
 
472
 
376
473
    def run_symptoms(self):
377
474
        '''Report a bug from a list of available symptoms.
378
475
        
433
530
            return True
434
531
        elif self.options.filebug:
435
532
            return self.run_report_bug()
 
533
        elif self.options.update_report:
 
534
            return self.run_update_report()
436
535
        elif self.options.version:
437
536
            print __version__
438
537
            return True
461
560
        optparser.add_option('-f', '--file-bug',
462
561
            help=_('Start in bug filing mode. Requires --package and an optional --pid, or just a --pid. If neither is given, display a list of known symptoms. (Implied if a single argument is given.)'),
463
562
            action='store_true', dest='filebug', default=False)
 
563
        optparser.add_option('-u', '--update-bug', type='int',
 
564
            help=_('Start in bug updating mode. Can take an optional --package.'),
 
565
            dest='update_report')
464
566
        optparser.add_option('-s', '--symptom', metavar='SYMPTOM',
465
567
            help=_('File a bug report about a symptom. (Implied if symptom name is given as only argument.)'), 
466
568
            dest='symptom')
473
575
        optparser.add_option('-c', '--crash-file',
474
576
            help=_('Report the crash from given .apport or .crash file instead of the pending ones in %s. (Implied if file is given as only argument.)') % apport.fileutils.report_dir,
475
577
            action='store', type='string', dest='crash_file', default=None, metavar='PATH')
 
578
        optparser.add_option('--save',
 
579
            help=_('In --file-bug mode, save the collected information into a file instead of reporting it. This file can then be reported with --crash-file later on.'),
 
580
            type='string', dest='save', default=None, metavar='PATH')
476
581
        optparser.add_option('-v', '--version',
477
582
            help=_('Print the Apport version number.'),
478
583
            action='store_true', dest='version', default=None)
479
584
 
480
585
        (self.options, self.args) = optparser.parse_args()
481
586
 
482
 
        # "do what I mean" for one single argument
483
 
        if len(sys.argv) != 2 or sys.argv[1].startswith('-'):
 
587
        # "do what I mean" for zero or one arguments
 
588
        if len(sys.argv) == 0:
 
589
            return
 
590
 
 
591
        cmd = os.environ.get('APPORT_INVOKED_AS', sys.argv[0])
 
592
        if cmd.endswith('-update-bug') or cmd.endswith('-collect'):
 
593
            if len(self.args) == 1:
 
594
                self.options.update_report = self.args[0]
 
595
                self.args = []
 
596
                return
 
597
            else:
 
598
                optparser.error('You need to specify a report number to update')
 
599
                sys.exit(1)
 
600
 
 
601
        # no argument: default to "show pending crashes" except when called in
 
602
        # bug mode
 
603
        if len(self.args) == 0 and cmd.endswith('-bug'):
 
604
            self.options.filebug = True
 
605
            return
 
606
 
 
607
        # one argument: guess "file bug" mode by argument type
 
608
        if len(self.args) != 1:
484
609
            return
485
610
 
486
611
        # symptom?
487
 
        if os.path.exists(os.path.join(symptom_script_dir, sys.argv[1] + '.py')):
 
612
        if os.path.exists(os.path.join(symptom_script_dir, self.args[0] + '.py')):
 
613
            self.options.filebug = True
 
614
            self.options.symptom = self.args[0]
488
615
            self.args = []
489
 
            self.options.filebug = True
490
 
            self.options.symptom = sys.argv[1]
491
616
 
492
617
        # .crash/.apport file?
493
 
        elif sys.argv[1].endswith('.crash') or sys.argv[1].endswith('.apport'):
 
618
        elif self.args[0].endswith('.crash') or self.args[0].endswith('.apport'):
 
619
            self.options.crash_file = self.args[0]
494
620
            self.args = []
495
 
            self.options.crash_file = sys.argv[1]
496
621
 
497
622
        # PID?
498
 
        elif sys.argv[1].isdigit():
 
623
        elif self.args[0].isdigit():
 
624
            self.options.filebug = True
 
625
            self.options.pid = self.args[0]
499
626
            self.args = []
500
 
            self.options.filebug = True
501
 
            self.options.pid = sys.argv[1]
502
627
 
503
628
        # executable?
504
 
        elif '/' in sys.argv[1]:
505
 
            pkg = apport.packaging.get_file_package(sys.argv[1])
 
629
        elif '/' in self.args[0]:
 
630
            pkg = apport.packaging.get_file_package(self.args[0])
506
631
            if not pkg:
507
 
                optparser.error('%s does not belong to a package.' % sys.argv[1])
 
632
                optparser.error('%s does not belong to a package.' % self.args[0])
508
633
                sys.exit(1)
509
634
            self.args = []
510
635
            self.options.filebug = True
512
637
 
513
638
        # otherwise: package name
514
639
        else:
 
640
            self.options.filebug = True
 
641
            self.options.package = self.args[0]
515
642
            self.args = []
516
 
            self.options.filebug = True
517
 
            self.options.package = sys.argv[1]
518
643
 
519
644
    def format_filesize(self, size):
520
645
        '''Format the given integer as humanly readable and i18n'ed file size.'''
556
681
            os.execlp('sh', 'sh', '-c', self.report.get('RespawnCommand', self.report['ProcCmdline']))
557
682
            sys.exit(1)
558
683
 
559
 
    def collect_info(self, symptom_script=None):
 
684
    def collect_info(self, symptom_script=None, ignore_uninstalled=False):
560
685
        '''Collect additional information.
561
686
 
562
687
        Call all the add_*_info() methods and display a progress dialog during
588
713
                icthread = REThread.REThread(target=thread_collect_info,
589
714
                    name='thread_collect_info',
590
715
                    args=(self.report, self.report_file, self.cur_package,
591
 
                        hookui, symptom_script))
 
716
                        hookui, symptom_script, ignore_uninstalled))
592
717
                icthread.start()
593
718
                while icthread.isAlive():
594
719
                    self.ui_pulse_info_collection_progress()
803
928
 
804
929
        # ensure that the crashed program is still installed:
805
930
        if self.report['ProblemType'] == 'Crash':
806
 
            exe_path = self.report.get('InterpreterPath', self.report.get('ExecutablePath'))
807
 
            if not exe_path or not os.path.exists(exe_path):
 
931
            exe_path = self.report.get('ExecutablePath', '')
 
932
            if not os.path.exists(exe_path):
808
933
                msg = _('This problem report applies to a program which is not installed any more.')
809
 
                if self.report.has_key('ExecutablePath'):
 
934
                if exe_path:
810
935
                    msg = '%s (%s)' % (msg, self.report['ExecutablePath'])
811
936
                self.report = None
812
937
                self.ui_info_message(_('Invalid problem report'), msg)
813
938
                return False
814
939
 
 
940
            if 'InterpreterPath' in self.report:
 
941
                if not os.path.exists(self.report['InterpreterPath']):
 
942
                    msg = _('This problem report applies to a program which is not installed any more.')
 
943
                    self.ui_info_message(_('Invalid problem report'), '%s (%s)'
 
944
                            % (msg, self.report['InterpreterPath']))
 
945
                    return False
 
946
 
815
947
        return True
816
948
 
817
949
    def get_desktop_entry(self):
892
1024
        '''
893
1025
        raise NotImplementedError, 'this function must be overridden by subclasses'
894
1026
 
895
 
    def ui_present_report_details(self):
 
1027
    def ui_present_report_details(self, is_update):
896
1028
        '''Show details of the bug report.
897
1029
        
898
1030
        This lets the user choose between sending a complete or reduced report.
901
1033
        methods to determine the respective size of the data to send, and
902
1034
        format_filesize() to convert it to a humanly readable form.
903
1035
 
 
1036
        If is_update is True, the text should describe that an existing report is
 
1037
        updated, otherwise a new report will be created.
 
1038
 
904
1039
        Return the action: send full report ('full'), send reduced report
905
1040
        ('reduced'), or do not send anything ('cancel').
906
1041
        '''
1113
1248
    from cStringIO import StringIO
1114
1249
    import apport.report
1115
1250
    import problem_report
 
1251
    import apport.crashdb_impl.memory
1116
1252
 
1117
1253
    class _TestSuiteUserInterface(UserInterface):
1118
1254
        '''Concrete UserInterface suitable for automatic testing.'''
1150
1286
            self.question_file_response = None
1151
1287
 
1152
1288
            self.opened_url = None
 
1289
            self.present_details_shown = False
1153
1290
 
1154
1291
            self.clear_msg()
1155
1292
 
1169
1306
        def ui_present_kernel_error(self):
1170
1307
            return self.present_kernel_error_response
1171
1308
 
1172
 
        def ui_present_report_details(self):
 
1309
        def ui_present_report_details(self, is_update):
 
1310
            self.present_details_shown = True
1173
1311
            return self.present_details_response
1174
1312
 
1175
1313
        def ui_info_message(self, title, text):
1220
1358
            self.msg_text = text
1221
1359
            return self.question_file_response
1222
1360
 
1223
 
    class _UserInterfaceTest(unittest.TestCase):
 
1361
    class _T(unittest.TestCase):
1224
1362
        def setUp(self):
1225
1363
            # we test a few strings, don't get confused by translations
1226
1364
            for v in ['LANG', 'LANGUAGE', 'LC_MESSAGES', 'LC_ALL']:
1491
1629
 
1492
1630
            self.assertEqual(self.ui.msg_severity, None)
1493
1631
            self.assertEqual(self.ui.msg_title, None)
 
1632
            self.assert_(self.ui.present_details_shown)
1494
1633
            self.assertEqual(self.ui.opened_url, 'http://bash.bugs.example.com/%i' % self.ui.crashdb.latest_id())
1495
1634
 
1496
1635
            self.assert_(self.ui.ic_progress_pulses > 0)
1538
1677
            self.assertEqual(self.ui.msg_severity, None)
1539
1678
            self.assertEqual(self.ui.msg_title, None)
1540
1679
            self.assertEqual(self.ui.opened_url, 'http://coreutils.bugs.example.com/%i' % self.ui.crashdb.latest_id())
 
1680
            self.assert_(self.ui.present_details_shown)
1541
1681
            self.assert_(self.ui.ic_progress_pulses > 0)
1542
1682
 
1543
1683
        @classmethod
1600
1740
 
1601
1741
            self.assertEqual(self.ui.msg_severity, 'error')
1602
1742
 
 
1743
        def test_run_report_bug_file(self):
 
1744
            '''run_report_bug() with saving report into a file.'''
 
1745
 
 
1746
            d = os.path.join(apport.fileutils.report_dir, 'home')
 
1747
            os.mkdir(d)
 
1748
            reportfile = os.path.join(d, 'bashisbad.apport')
 
1749
 
 
1750
            sys.argv = ['ui-test', '-f', '-p', 'bash', '--save', reportfile]
 
1751
            self.ui = _TestSuiteUserInterface()
 
1752
            self.assertEqual(self.ui.run_argv(), True)
 
1753
 
 
1754
            self.assertEqual(self.ui.msg_severity, None)
 
1755
            self.assertEqual(self.ui.msg_title, None)
 
1756
            self.assertEqual(self.ui.opened_url, None)
 
1757
            self.failIf(self.ui.present_details_shown)
 
1758
 
 
1759
            self.assert_(self.ui.ic_progress_pulses > 0)
 
1760
 
 
1761
            r = apport.Report()
 
1762
            r.load(open(reportfile))
 
1763
 
 
1764
            self.assertEqual(r['SourcePackage'], 'bash')
 
1765
            self.assert_('Dependencies' in r.keys())
 
1766
            self.assert_('ProcEnviron' in r.keys())
 
1767
            self.assertEqual(r['ProblemType'], 'Bug')
 
1768
 
 
1769
            # report it
 
1770
            sys.argv = ['ui-test', '-c', reportfile]
 
1771
            self.ui = _TestSuiteUserInterface()
 
1772
            
 
1773
            self.ui.present_details_response = 'full'
 
1774
            self.assertEqual(self.ui.run_argv(), True)
 
1775
 
 
1776
            self.assertEqual(self.ui.msg_text, None)
 
1777
            self.assertEqual(self.ui.msg_severity, None)
 
1778
            self.assert_(self.ui.present_details_shown)
 
1779
 
1603
1780
        def _gen_test_crash(self):
1604
1781
            '''Generate a Report with real crash data.'''
1605
1782
 
1651
1828
            self.assertEqual(self.ui.msg_title, None)
1652
1829
            self.assertEqual(self.ui.opened_url, None)
1653
1830
            self.assertEqual(self.ui.ic_progress_pulses, 0)
 
1831
            self.failIf(self.ui.present_details_shown)
1654
1832
 
1655
1833
            # report in crash notification dialog, cancel details report
1656
1834
            r.write(open(report_file, 'w'))
1663
1841
            self.assertEqual(self.ui.msg_title, None)
1664
1842
            self.assertEqual(self.ui.opened_url, None)
1665
1843
            self.assertNotEqual(self.ui.ic_progress_pulses, 0)
 
1844
            self.assert_(self.ui.present_details_shown)
1666
1845
 
1667
1846
            # report in crash notification dialog, send full report
1668
1847
            r.write(open(report_file, 'w'))
1674
1853
            self.assertEqual(self.ui.msg_title, None)
1675
1854
            self.assertEqual(self.ui.opened_url, 'http://coreutils.bugs.example.com/%i' % self.ui.crashdb.latest_id())
1676
1855
            self.assertNotEqual(self.ui.ic_progress_pulses, 0)
 
1856
            self.assert_(self.ui.present_details_shown)
1677
1857
 
1678
1858
            self.assert_('SourcePackage' in self.ui.report.keys())
1679
1859
            self.assert_('Dependencies' in self.ui.report.keys())
1693
1873
            self.assertEqual(self.ui.msg_title, None)
1694
1874
            self.assertEqual(self.ui.opened_url, 'http://coreutils.bugs.example.com/%i' % self.ui.crashdb.latest_id())
1695
1875
            self.assertNotEqual(self.ui.ic_progress_pulses, 0)
 
1876
            self.assert_(self.ui.present_details_shown)
1696
1877
 
1697
1878
            self.assert_('SourcePackage' in self.ui.report.keys())
1698
1879
            self.assert_('Dependencies' in self.ui.report.keys())
1729
1910
            self.assert_('assert' in self.ui.msg_text, '%s: %s' %
1730
1911
                (self.ui.msg_title, self.ui.msg_text))
1731
1912
            self.assertEqual(self.ui.msg_severity, 'info')
 
1913
            self.failIf(self.ui.present_details_shown)
1732
1914
 
1733
1915
        def test_run_crash_argv_file(self):
1734
1916
            '''run_crash() through a file specified on the command line.'''
1735
1917
 
 
1918
            # valid
 
1919
            self.report['Package'] = 'bash'
 
1920
            self.update_report_file()
 
1921
 
 
1922
            sys.argv = ['ui-test', '-c', self.report_file.name]
 
1923
            self.ui = _TestSuiteUserInterface()
 
1924
            
 
1925
            self.ui.present_details_response = 'full'
 
1926
            self.assertEqual(self.ui.run_argv(), True)
 
1927
 
 
1928
            self.assertEqual(self.ui.msg_text, None)
 
1929
            self.assertEqual(self.ui.msg_severity, None)
 
1930
            self.assert_(self.ui.present_details_shown)
 
1931
 
 
1932
            # unreportable
1736
1933
            self.report['Package'] = 'bash'
1737
1934
            self.report['UnreportableReason'] = 'It stinks.'
1738
1935
            self.update_report_file()
1744
1941
            self.assert_('It stinks.' in self.ui.msg_text, '%s: %s' %
1745
1942
                (self.ui.msg_title, self.ui.msg_text))
1746
1943
            self.assertEqual(self.ui.msg_severity, 'info')
 
1944
            self.failIf(self.ui.present_details_shown)
1747
1945
 
1748
1946
            # should not die with an exception on an invalid name
1749
1947
            sys.argv = ['ui-test', '-c', '/nonexisting.crash' ]
1841
2039
            self.assertEqual(self.ui.msg_title, None)
1842
2040
            self.assertEqual(self.ui.opened_url, None)
1843
2041
            self.assertEqual(self.ui.ic_progress_pulses, 0)
 
2042
            self.assert_(self.ui.present_details_shown)
1844
2043
           
1845
2044
        def test_run_crash_errors(self):
1846
2045
            '''run_crash() on various error conditions.'''
1919
2118
            self.assertEqual(self.ui.msg_title, None)
1920
2119
            self.assertEqual(self.ui.opened_url, None)
1921
2120
            self.assertEqual(self.ui.ic_progress_pulses, 0)
 
2121
            self.failIf(self.ui.present_details_shown)
1922
2122
 
1923
2123
            # report in crash notification dialog, send report
1924
2124
            r.write(open(report_file, 'w'))
1925
2125
            self.ui = _TestSuiteUserInterface()
1926
2126
            self.ui.present_package_error_response = 'report'
 
2127
            self.ui.present_details_response = 'full'
1927
2128
            self.ui.run_crash(report_file)
1928
2129
            self.assertEqual(self.ui.msg_severity, None)
1929
2130
            self.assertEqual(self.ui.msg_title, None)
1930
2131
            self.assertEqual(self.ui.opened_url, 'http://bash.bugs.example.com/%i' % self.ui.crashdb.latest_id())
 
2132
            self.assert_(self.ui.present_details_shown)
1931
2133
 
1932
2134
            self.assert_('SourcePackage' in self.ui.report.keys())
1933
2135
            self.assert_('Package' in self.ui.report.keys())
1966
2168
            self.assertEqual(self.ui.msg_title, None)
1967
2169
            self.assertEqual(self.ui.opened_url, None)
1968
2170
            self.assertEqual(self.ui.ic_progress_pulses, 0)
 
2171
            self.failIf(self.ui.present_details_shown)
1969
2172
 
1970
2173
            # report in crash notification dialog, send report
1971
2174
            r.write(open(report_file, 'w'))
1977
2180
                ' ' + str(self.ui.msg_text))
1978
2181
            self.assertEqual(self.ui.msg_title, None)
1979
2182
            self.assertEqual(self.ui.opened_url, 'http://linux.bugs.example.com/%i' % self.ui.crashdb.latest_id())
 
2183
            self.assert_(self.ui.present_details_shown)
1980
2184
 
1981
2185
            self.assert_('SourcePackage' in self.ui.report.keys())
1982
2186
            # did we run the hooks properly?
2005
2209
            for s in bad_strings:
2006
2210
                self.failIf(s in dump.getvalue(), 'dump contains sensitive string: %s' % s)
2007
2211
 
 
2212
        def test_run_update_report_nonexisting_package_from_bug(self):
 
2213
            '''run_update_report() on a nonexisting package (from bug).'''
 
2214
 
 
2215
            sys.argv = ['ui-test', '-u', '1']
 
2216
            self.ui = _TestSuiteUserInterface()
 
2217
            self.ui.crashdb = apport.crashdb_impl.memory.CrashDatabase(None,
 
2218
                    '', {'dummy_data': 1})
 
2219
 
 
2220
            self.assertEqual(self.ui.run_argv(), False)
 
2221
            self.assert_('No additional information collected.' in
 
2222
                    self.ui.msg_text)
 
2223
            self.failIf(self.ui.present_details_shown)
 
2224
 
 
2225
        def test_run_update_report_nonexisting_package_cli(self):
 
2226
            '''run_update_report() on a nonexisting package (CLI argument).'''
 
2227
 
 
2228
            sys.argv = ['ui-test', '-u', '1', '-p', 'bar']
 
2229
            self.ui = _TestSuiteUserInterface()
 
2230
            self.ui.crashdb = apport.crashdb_impl.memory.CrashDatabase(None,
 
2231
                    '', {'dummy_data': 1})
 
2232
 
 
2233
            self.assertEqual(self.ui.run_argv(), False)
 
2234
            self.assert_('No additional information collected.' in
 
2235
                    self.ui.msg_text)
 
2236
            self.failIf(self.ui.present_details_shown)
 
2237
 
 
2238
        def test_run_update_report_existing_package_from_bug(self):
 
2239
            '''run_update_report() on an existing package (from bug).'''
 
2240
 
 
2241
            sys.argv = ['ui-test', '-u', '1']
 
2242
            self.ui = _TestSuiteUserInterface()
 
2243
            self.ui.crashdb = apport.crashdb_impl.memory.CrashDatabase(None,
 
2244
                    '', {'dummy_data': 1})
 
2245
 
 
2246
            self.ui.crashdb.download(1)['SourcePackage'] = 'bash'
 
2247
            self.ui.crashdb.download(1)['Package'] = 'bash'
 
2248
            self.assertEqual(self.ui.run_argv(), True)
 
2249
            self.assertEqual(self.ui.msg_severity, None, self.ui.msg_text)
 
2250
            self.assertEqual(self.ui.msg_title, None)
 
2251
            self.assertEqual(self.ui.opened_url, None)
 
2252
            self.assert_(self.ui.present_details_shown)
 
2253
 
 
2254
            self.assert_(self.ui.ic_progress_pulses > 0)
 
2255
            self.assert_(self.ui.report['Package'].startswith('bash '))
 
2256
            self.assert_('Dependencies' in self.ui.report.keys())
 
2257
            self.assert_('ProcEnviron' in self.ui.report.keys())
 
2258
 
 
2259
        def test_run_update_report_existing_package_cli(self):
 
2260
            '''run_update_report() on an existing package (CLI argument).'''
 
2261
 
 
2262
            sys.argv = ['ui-test', '-u', '1', '-p', 'bash']
 
2263
            self.ui = _TestSuiteUserInterface()
 
2264
            self.ui.crashdb = apport.crashdb_impl.memory.CrashDatabase(None,
 
2265
                    '', {'dummy_data': 1})
 
2266
 
 
2267
            self.assertEqual(self.ui.run_argv(), True)
 
2268
            self.assertEqual(self.ui.msg_severity, None, self.ui.msg_text)
 
2269
            self.assertEqual(self.ui.msg_title, None)
 
2270
            self.assertEqual(self.ui.opened_url, None)
 
2271
            self.assert_(self.ui.present_details_shown)
 
2272
 
 
2273
            self.assert_(self.ui.ic_progress_pulses > 0)
 
2274
            self.assert_(self.ui.report['Package'].startswith('bash '))
 
2275
            self.assert_('Dependencies' in self.ui.report.keys())
 
2276
            self.assert_('ProcEnviron' in self.ui.report.keys())
 
2277
 
 
2278
        def test_run_update_report_noninstalled_but_hook(self):
 
2279
            '''run_update_report() on an uninstalled package with a source hook.'''
 
2280
 
 
2281
            sys.argv = ['ui-test', '-u', '1']
 
2282
            self.ui = _TestSuiteUserInterface()
 
2283
            self.ui.crashdb = apport.crashdb_impl.memory.CrashDatabase(None,
 
2284
                    '', {'dummy_data': 1})
 
2285
 
 
2286
            f = open(os.path.join(self.hookdir, 'source_foo.py'), 'w')
 
2287
            f.write('def add_info(r, ui):\n  r["MachineType"]="Laptop"\n')
 
2288
            f.close()
 
2289
 
 
2290
            self.assertEqual(self.ui.run_argv(), True, self.ui.report)
 
2291
            self.assertEqual(self.ui.msg_severity, None, self.ui.msg_text)
 
2292
            self.assertEqual(self.ui.msg_title, None)
 
2293
            self.assertEqual(self.ui.opened_url, None)
 
2294
            self.assert_(self.ui.present_details_shown)
 
2295
 
 
2296
            self.assert_(self.ui.ic_progress_pulses > 0)
 
2297
            self.assertEqual(self.ui.report['Package'], 'foo (not installed)')
 
2298
            self.assertEqual(self.ui.report['MachineType'], 'Laptop')
 
2299
            self.assert_('ProcEnviron' in self.ui.report.keys())
 
2300
 
2008
2301
        def _run_hook(self, code):
2009
2302
            f = open(os.path.join(self.hookdir, 'coreutils.py'), 'w')
2010
2303
            f.write('def add_info(report, ui):\n%s\n' % 
2149
2442
            self.assertEqual(self.ui.run_argv(), True)
2150
2443
            self.assertEqual(self.ui.msg_text, None)
2151
2444
            self.assertEqual(self.ui.msg_severity, None)
 
2445
            self.assert_(self.ui.present_details_shown)
2152
2446
 
2153
2447
            self.assertEqual(self.ui.report['itch'], 'scratch')
2154
2448
            self.assert_('DistroRelease' in self.ui.report)
2168
2462
            self.ui = _TestSuiteUserInterface()
2169
2463
            self.ui.question_yesno_response = True
2170
2464
            self.assertEqual(self.ui.run_argv(), True)
 
2465
            self.assert_(self.ui.present_details_shown)
2171
2466
            self.assertEqual(self.ui.msg_text, 'do you?')
2172
2467
 
2173
2468
            self.assertEqual(self.ui.report['itch'], 'slap')
2197
2492
            self.assertEqual(self.ui.run_argv(), True)
2198
2493
            self.assertEqual(self.ui.msg_severity, None)
2199
2494
            self.assert_('kind of problem' in self.ui.msg_text)
2200
 
            self.assertEqual(self.ui.msg_choices, 
2201
 
                    ['bar', 'foo does not work', 'Other problem'])
 
2495
            self.assertEqual(set(self.ui.msg_choices), 
 
2496
                    set(['bar', 'foo does not work', 'Other problem']))
2202
2497
 
2203
2498
            # cancelled
2204
2499
            self.assertEqual(self.ui.ic_progress_pulses, 0)
2205
2500
            self.assertEqual(self.ui.report, None)
 
2501
            self.failIf(self.ui.present_details_shown)
2206
2502
 
2207
2503
            # now, choose foo -> bash report
2208
 
            self.ui.question_choice_response = [1]
 
2504
            self.ui.question_choice_response = [self.ui.msg_choices.index('foo does not work')]
2209
2505
            self.assertEqual(self.ui.run_argv(), True)
2210
2506
            self.assertEqual(self.ui.msg_severity, None)
2211
2507
            self.assert_(self.ui.ic_progress_pulses > 0)
 
2508
            self.assert_(self.ui.present_details_shown)
2212
2509
            self.assert_(self.ui.report['Package'].startswith('bash'))
2213
2510
 
2214
2511
        def test_parse_argv(self):
2215
2512
            '''parse_args() option inference for a single argument'''
2216
2513
 
2217
 
            def _chk(arg, expected_opts):
2218
 
                sys.argv = ['ui-test']
 
2514
            def _chk(program_name, arg, expected_opts, argv_options=None):
 
2515
                sys.argv = [program_name]
 
2516
                if argv_options:
 
2517
                    sys.argv += argv_options
2219
2518
                if arg:
2220
2519
                    sys.argv.append(arg)
2221
 
                ui = UserInterface()
2222
 
                ui.parse_argv()
 
2520
                orig_stderr = sys.stderr
 
2521
                sys.stderr = open('/dev/null', 'w')
 
2522
                try:
 
2523
                    ui = UserInterface()
 
2524
                finally:
 
2525
                    sys.stderr.close()
 
2526
                    sys.stderr = orig_stderr
2223
2527
                expected_opts['version'] = None
2224
2528
                self.assertEqual(ui.args, [])
2225
2529
                self.assertEqual(ui.options, expected_opts)
2226
2530
 
2227
2531
            # no arguments -> show pending crashes
2228
 
            _chk(None, {'filebug': False, 'package': None,
2229
 
                 'pid': None, 'crash_file': None, 'symptom': None})
 
2532
            _chk('apport-gtk', None, {'filebug': False, 'package': None,
 
2533
                'pid': None, 'crash_file': None, 'symptom': None, 
 
2534
                'update_report': None, 'save': None})
 
2535
            # ... except when being called as '*-bug', then default to bug mode
 
2536
            _chk('apport-bug', None, {'filebug': True, 'package': None,
 
2537
                'pid': None, 'crash_file': None, 'symptom': None, 
 
2538
                'update_report': None, 'save': None})
 
2539
            # updating report not allowed without args
 
2540
            self.assertRaises(SystemExit, _chk, 'apport-collect', None, {})
2230
2541
 
2231
2542
            # package 
2232
 
            _chk('coreutils', {'filebug': True, 'package': 'coreutils',
2233
 
                 'pid': None, 'crash_file': None, 'symptom': None})
 
2543
            _chk('apport-kde', 'coreutils', {'filebug': True, 'package':
 
2544
                'coreutils', 'pid': None, 'crash_file': None, 'symptom': None, 
 
2545
                'update_report': None, 'save': None})
 
2546
            _chk('apport-bug', 'coreutils', {'filebug': True, 'package':
 
2547
                'coreutils', 'pid': None, 'crash_file': None, 'symptom': None, 
 
2548
                'update_report': None, 'save': None})
 
2549
            _chk('apport-bug', 'coreutils', {'filebug': True, 'package':
 
2550
                'coreutils', 'pid': None, 'crash_file': None, 'symptom': None, 
 
2551
                'update_report': None, 'save': 'foo.apport'}, 
 
2552
                ['--save', 'foo.apport'])
2234
2553
 
2235
2554
            # symptom is preferred over package
2236
2555
            f = open(os.path.join(symptom_script_dir, 'coreutils.py'), 'w')
2239
2558
    return 'bash'
2240
2559
'''
2241
2560
            f.close()
2242
 
            _chk('coreutils', {'filebug': True, 'package': None,
2243
 
                 'pid': None, 'crash_file': None, 'symptom': 'coreutils'})
 
2561
            _chk('apport-cli', 'coreutils', {'filebug': True, 'package': None,
 
2562
                 'pid': None, 'crash_file': None, 'symptom': 'coreutils',
 
2563
                 'update_report': None, 'save': None})
 
2564
            _chk('apport-bug', 'coreutils', {'filebug': True, 'package': None,
 
2565
                 'pid': None, 'crash_file': None, 'symptom': 'coreutils',
 
2566
                 'update_report': None, 'save': None})
2244
2567
 
2245
2568
            # PID
2246
 
            _chk('1234', {'filebug': True, 'package': None,
2247
 
                 'pid': '1234', 'crash_file': None, 'symptom': None})
 
2569
            _chk('apport-cli', '1234', {'filebug': True, 'package': None,
 
2570
                 'pid': '1234', 'crash_file': None, 'symptom': None,
 
2571
                 'update_report': None, 'save': None})
 
2572
            _chk('apport-bug', '1234', {'filebug': True, 'package': None,
 
2573
                 'pid': '1234', 'crash_file': None, 'symptom': None,
 
2574
                 'update_report': None, 'save': None})
2248
2575
 
2249
2576
            # .crash/.apport files; check correct handling of spaces
2250
2577
            for suffix in ('.crash', '.apport'):
2251
 
                _chk('/tmp/f oo' + suffix, {'filebug': False, 'package': None,
2252
 
                     'pid': None, 'crash_file': '/tmp/f oo' + suffix, 'symptom': None})
 
2578
                for prog in ('apport-cli', 'apport-bug'):
 
2579
                    _chk(prog, '/tmp/f oo' + suffix, {'filebug': False,
 
2580
                         'package': None, 'pid': None, 
 
2581
                         'crash_file': '/tmp/f oo' + suffix, 'symptom': None,
 
2582
                         'update_report': None, 'save': None})
2253
2583
 
2254
2584
            # executable
2255
 
            _chk('/usr/bin/tail', {'filebug': True, 'package': 'coreutils',
2256
 
                 'pid': None, 'crash_file': None, 'symptom': None})
 
2585
            _chk('apport-cli', '/usr/bin/tail', {'filebug': True, 
 
2586
                 'package': 'coreutils',
 
2587
                 'pid': None, 'crash_file': None, 'symptom': None, 
 
2588
                 'update_report': None, 'save': None})
 
2589
            _chk('apport-bug', '/usr/bin/tail', {'filebug': True, 
 
2590
                 'package': 'coreutils',
 
2591
                 'pid': None, 'crash_file': None, 'symptom': None, 
 
2592
                 'update_report': None, 'save': None})
 
2593
 
 
2594
            # update existing report
 
2595
            _chk('apport-collect', '1234', {'filebug': False, 'package': None,
 
2596
                 'pid': None, 'crash_file': None, 'symptom': None,
 
2597
                 'update_report': '1234', 'save': None})
 
2598
            _chk('apport-update-bug', '1234', {'filebug': False, 
 
2599
                 'package': None, 'pid': None, 'crash_file': None, 
 
2600
                 'symptom': None, 'update_report': '1234', 'save': None})
2257
2601
 
2258
2602
    unittest.main()
2259
2603