Merge lp:~brian-murray/ubuntu/natty/apport/linux-staging into lp:~package-import/ubuntu/natty/apport/defunct

Proposed by Brian Murray
Status: Superseded
Proposed branch: lp:~brian-murray/ubuntu/natty/apport/linux-staging
Merge into: lp:~package-import/ubuntu/natty/apport/defunct
Diff against target: 9642 lines (+3224/-1814)
59 files modified
NEWS (+187/-1)
apport/REThread.py (+7/-7)
apport/__init__.py (+23/-1)
apport/chroot.py (+7/-6)
apport/crashdb.py (+35/-27)
apport/crashdb_impl/launchpad.py (+327/-271)
apport/crashdb_impl/memory.py (+41/-16)
apport/crashdb_impl/multipartpost_handler.py (+1/-1)
apport/fileutils.py (+22/-12)
apport/hookutils.py (+151/-38)
apport/packaging.py (+19/-19)
apport/report.py (+306/-219)
apport/ui.py (+458/-287)
apport_python_hook.py (+21/-17)
backends/packaging-apt-dpkg.py (+85/-116)
bin/apport-cli (+24/-22)
bin/apport-retrace (+10/-13)
bin/apport-unpack (+4/-7)
bin/crash-digger (+17/-20)
bin/dupdb-admin (+5/-6)
data/apport (+15/-16)
data/dump_acpi_tables.py (+2/-1)
data/gcc_ice_hook (+2/-2)
data/general-hooks/parse_segv.py (+23/-32)
data/general-hooks/ubuntu.py (+145/-49)
data/java_uncaught_exception (+86/-0)
data/kernel_crashdump (+8/-4)
data/package-hooks/source_linux.py (+6/-87)
data/package-hooks/source_ubiquity.py (+11/-2)
data/package_hook (+1/-1)
data/unkillable_shutdown (+21/-9)
debian/apport.install (+3/-0)
debian/apport.postinst (+10/-0)
debian/changelog (+379/-0)
debian/control (+42/-22)
debian/local/apport-chroot (+5/-5)
debian/local/setup-apport-retracer (+13/-55)
debian/python-apport.install (+1/-1)
debian/rules (+3/-12)
doc/crashdb-conf.txt (+2/-2)
doc/package-hooks.txt (+6/-0)
doc/symptoms.txt (+1/-1)
etc/apport/blacklist.d/apport (+5/-0)
etc/apport/crashdb.conf (+3/-4)
etc/bash_completion.d/apport_completion (+85/-74)
etc/default/apport (+0/-3)
gtk/apport-gtk (+96/-95)
gtk/apport-gtk.ui (+5/-17)
java/README (+13/-0)
java/com/ubuntu/apport/ApportUncaughtExceptionHandler.java (+108/-0)
java/crash.java (+8/-0)
kde/apport-kde (+3/-4)
man/apport-cli.1 (+8/-0)
problem_report.py (+131/-136)
setup.py (+65/-9)
test/crash-digger (+52/-52)
test/hooks (+17/-13)
test/java (+86/-0)
test/run (+4/-0)
To merge this branch: bzr merge lp:~brian-murray/ubuntu/natty/apport/linux-staging
Reviewer Review Type Date Requested Status
Colin Watson (community) Needs Resubmitting
Kees Cook (community) Approve
Review via email: mp+58596@code.launchpad.net

This proposal has been superseded by a proposal from 2011-04-21.

To post a comment you must log in.
Revision history for this message
Kees Cook (kees) wrote :

Looks good to me, thanks.

review: Approve
Revision history for this message
Colin Watson (cjwatson) wrote :

lp:ubuntu/apport currently seems to be outdated and not being used. Can you resubmit this against lp:~ubuntu-core-dev/ubuntu/natty/apport/ubuntu, as per the package's Vcs-Bzr field?

review: Needs Resubmitting

Unmerged revisions

1774. By Brian Murray

link to bug number

1773. By Brian Murray

Only prepend linux bug titles with [STAGING] if a title exists

1772. By Martin Pitt

releasing version 1.20.1-0ubuntu3

1771. By Martin Pitt

etc/default/apport: Disable by default for the final release.

1770. By Martin Pitt

* Merge from trunk:
  - Use kde-open instead of kfmclient to open URLs under KDE. Thanks Philip
    Muškovac. (LP: #765808)

1769. By Martin Pitt

releasing version 1.20.1-0ubuntu2

1768. By Martin Pitt

* Merge bug fixes from trunk:
  - apport-gtk: HTML-escape text for dialogs with URLs. (LP: #750870)
  - dump_acpi_tables.py: Check to see if acpi/tables dir is mounted first.
    Thanks Brian Murray. (LP: #729622)
  - man/apport-cli.1: Document recently added -w/--window option. Thanks
    Abhinav Upadhyay. (LP: #765600)

1767. By Martin Pitt

The kernel team has decided that asking the user for a bunch of
information which they may not be able to answer is the wrong thing to do.
Therefore, all the propmpting of the user for said information has been
removed. Also removed is the tagging of "needs-upstream-testing".

1766. By Martin Pitt

releasing version 1.20.1-0ubuntu1

1765. By Martin Pitt

new upstream release 1.20.1

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2010-07-13 05:42:32 +0000
+++ NEWS 2011-04-20 23:32:24 +0000
@@ -1,12 +1,174 @@
1This file summarizes the major and interesting changes for each release. For a1This file summarizes the major and interesting changes for each release. For a
2detailled list of changes, please see ChangeLog.2detailled list of changes, please see ChangeLog.
33
41.15 (UNRELEASED)41.20.2 (UNRELEASED)
5-------------------
6Bug fixes:
7 - apport-gtk: HTML-escape text for dialogs with URLs. (LP: #750870)
8 - dump_acpi_tables.py: Check to see if acpi/tables dir is mounted first.
9 Thanks Brian Murray. (LP: #729622)
10 - man/apport-cli.1: Document recently added -w/--window option. Thanks Abhinav
11 Upadhyay! (LP: #765600)
12 - Use kde-open instead of kfmclient to open URLs under KDE. Thanks Philip
13 Muškovac. (LP: #765808)
14
151.20.1 (2011-03-31)
16-------------------
17Bug fixes:
18 - Add bash completion support for new -w/--window option that was introduced
19 in 1.20. Thanks Philip Muškovac.
20 - apport-unpack: Fix crash if target directory already exists.
21 - Fix crash if UnreportableReason is a non-ASCII string. (LP: #738632)
22 - Fix crash if application from desktop name is a non-ASCII string.
23 (LP: #737799)
24 - unkillable_shutdown: Fix rare crash if ExecutablePath does not exist (any
25 more). (LP: #537904)
26 - kernel_crashdump: Fix crash if the vmcore file disappeared underneath us.
27 (LP: #450295)
28 - unkillable_shutdown: Fix crash if the checked process terminated underneath
29 us. (LP: #540436)
30 - ui.py: Properly raise exceptions from the upload thread that happen at its
31 very end. (LP: #469943)
32
331.20 (2011-03-17)
34-----------------
35Improvements:
36 - Add support for -w/--window option which will enable user to select a
37 window as a target for filing a problem report. Thanks Abhinav Upadhyay for
38 the patch! (LP: #357847)
39 - Disable the filtering on SIGABRT without assertion messages. Turns out that
40 developers want these crash reports after all. (LP: #729223)
41 - Add support for a "DuplicateSignature" report fields. This allows package
42 hooks to implement custom duplicate problem handling which doesn't need to
43 be hardcoded in Apport itself. Update the launchpad backend to tag such bugs
44 as "need-duplicate-check".
45
46Bug fixes:
47 - report.py, add_hooks_info(): Properly report TypeErrors from hooks.
48 - apport-retrace: Intercept SystemErrors from ill-formed gzip attachments as
49 well.
50 - Fix crash if crash database configuration does not specify a
51 bug_pattern_url. Just assume None. (LP: #731526)
52 - If a custom crash database does not specify a bug_pattern_url, fall back to
53 using the default database's. (LP: #731526)
54 - hookutils.py Update WifiSyslog regex to correctly catch application log
55 messages in syslog. Thanks Mathieu Trudel-Lapierre. (LP: #732917)
56 - hookutils.py, attach_hardware(): Avoid error message if machine does not
57 have a PCI bus. Thanks Marcin Juszkiewicz! (LP: #608449)
58 - backends/packaging-apt-dpkg.py: Replace deprecated getChanges() call with
59 get_changes().
60 - apport-gtk: Fix broken dialog heading if the name of the crashed program
61 contains an & or other markup specific characters.
62 - apport-gtk: Don't crash if GTK cannot be initialized. This usually happens
63 without a $DISPLAY or when the session is being shut down. Just print an
64 error message. If there are pending crashes, they will be shown again the
65 next time a session starts. (LP: #730569)
66
671.19 (2011-02-28)
68-----------------
69Bug fixes:
70 - Update stack unwind patterns for current glib (slightly changed function
71 names), and also ignore a preceding '*'. (LP: #716251)
72 - Fix crash_signature() to fail if there is an empty or too short
73 StacktraceTop.
74 - apt backend: Do not generate a warning if the opportunistically added -dbg
75 package does not exist.
76 - apt backend: Only add -dbg in --no-pkg mode, as there will be conflicts in
77 normal package mode.
78 - apt backend: Call tar with target cwd instead of using -C; the latter causes
79 an extra openat() call which breaks with current fakechroot.
80 - launchpad.py: Fix retracer crash if DistroRelease field does not exist.
81 - Convert deprecated failIf()/assert_() TestCase method calls to
82 assertFalse()/assertTrue().
83
84Improvements:
85 - In apport-bug, if the user specifies a PID referring to a kernel thread,
86 do the right thing and file the bug against the kernel
87 - In hookutils.attach_dmesg, skip over an initial truncated message if one
88 is present (this happens when the ring buffer overflows)
89 - Change bug patterns to just use one central file instead of per-package
90 files. This allows bug patterns to be written which are not package
91 specific, and is easier to maintain as well. IMPORTANT: This changed the
92 format of crashdb.conf: bug_pattern_base is now obsolete, and the new
93 attribute bug_pattern_url now points to the full URL/path of the patterns
94 file. Thanks to Matt Zimmerman!
95
961.18 (2011-02-16)
97-----------------
98Bug fixes:
99 - Ensure that symptom scripts define a run() function, and don't show them if
100 not.
101 - Do not show symptom scripts which start with an underscore. These can be
102 used for private libraries for the actual symptom scripts.
103 - Update bash completion. Thanks Philip Muškovac.
104 - etc/default/apport: Remove obsolete "maxsize" setting. (LP: #719564)
105
106Improvements:
107 - Remove explicit handling of KDE *.ui files in setup.py, as
108 python-distutils-extra 2.24 fixes this. Bump version check.
109 - hookutils.py: Add attach_root_command_outputs() to run several commands
110 at once. This avoids asking for the password several times. (LP: #716595)
111
1121.17.2 (2011-02-04)
113-------------------
114Improvements:
115 - Be more Python 3 compatible (not fully working with Python 3 yet, though).
116 - apt/dpkg backend: Drop support for pre-0.7.9 python-apt API.
117 - Add --tag option to add extra tags to reports. (LP: #572504)
118
119Bug fixes:
120 - hookutils.py, attach_dmesg(): Do not overwrite already existing dmesg.
121 - hookutils.py: Be more robust against file permission errors. (LP: #444678)
122 - ui.py: Do not show all the options in --help when invoked as *-bug.
123 (LP: #665953)
124 - launchpad.py: Adapt test cases to current standard_title() behaviour.
125
1261.17.1 (2011-01-10)
127-------------------
128Bug fixes:
129 - Make the GTK frontend work with GTK 2.0 as well, and drop "3.0" requirement.
130
1311.17 (2010-12-31)
132-----------------
133Improvements:
134 - Better standard bug titles for Python crashes. Thanks Matt Zimmerman!
135 (LP: #681574)
136 - Add handler for uncaught Java exceptions. There is no integration for
137 automatically intercepting all Java crashes yet, see java/README.
138 Thanks Matt Zimmerman! (LP: #548877)
139
140Bug fixes:
141 - GTK frontend: Require GTK 3.0.
142 - launchpad.py: Default to "production" instance, not "edge", since edge is
143 obsolete now.
144 - hookutils.py, attach_alsa(): Fix crash if /proc/asound/cards does not exist.
145 (LP: #626215)
146 - ui.py, format_filesize(): Fix to work with stricter locale.format() in
147 Python 2.7. (LP: #688535). While we are at it, also change it to use base-10
148 units.
149 - hookutils.py, package_versions(): Always include all requested package names
150 even if they're unknown to us. Thanks Matt Zimmerman! (LP: #695188)
151 - launchpad.py: When updating a bug, also add new tags. Thanks Brian Murray!
152
1531.16 (2010-11-19)
154-----------------
155New features:
156 - Port GTK frontend from pygtk2 to GTK+3.0 and gobject-introspection.
157
158Bug fixes:
159 - Fix symptoms again. Version 1.15 broke the default symptom directory.
160 - Fix memory test case to work with current Python versions, where the SQLite
161 integrity check throws a different exception.
162
1631.15 (2010-11-11)
5-----------------164-----------------
6New features:165New features:
7 - Add dump_acpi_tables.py script. This can be called by package hooks which166 - Add dump_acpi_tables.py script. This can be called by package hooks which
8 need ACPI table information (in particular, kernel bug reports). Thanks to167 need ACPI table information (in particular, kernel bug reports). Thanks to
9 Brad Figg for the script!168 Brad Figg for the script!
169 - Order symptom descriptions alphabetically. Thanks to Javier Collado.
170 - Check $APPORT_SYMPTOMS_DIR environment variable for overriding the system
171 default path. Thanks to Javier Collado.
10172
11Bug fixes:173Bug fixes:
12 - testsuite: Check that crashdb.conf can have dynamic code to determine DB174 - testsuite: Check that crashdb.conf can have dynamic code to determine DB
@@ -14,6 +176,30 @@
14 - ui.py test suite: Rewrite _gen_test_crash() to have the test process core176 - ui.py test suite: Rewrite _gen_test_crash() to have the test process core
15 dump itself, instead of using gdb to do it. The latter fails in ptrace177 dump itself, instead of using gdb to do it. The latter fails in ptrace
16 restricted environments, such as Ubuntu 10.10.178 restricted environments, such as Ubuntu 10.10.
179 - packaging-apt-dpkg.py: Fix handling of /etc/apport/native-origins.d to
180 actually work. Thanks Steve Langasek. (LP: #627777)
181 - apport-kde: Load correct translation catalogue. Thanks Jonathan Riddell.
182 (LP: #633483)
183 - launchpad.py: Use launchpadlib to file a bug instead of screen scraping.
184 The latter was completely broken with current Launchpad, so this makes the
185 test suite actually work again. Thanks to Diogo Matsubara!
186 - launchpad.py: Change $APPORT_STAGING to $APPORT_LAUNCHPAD_INSTANCE, so that
187 you can now specify "staging", "edge", or "dev" (for a local
188 http://launchpad.dev installation). Thanks to Diogo Matsubara!
189 - backends/packaging-apt-dpkg.py: Fix crash on empty lines in ProcMaps
190 attachment.
191 - doc/symptoms.txt: Fix typo, thanks Philip Muskovac. (LP: #590521)
192 - apport/hookutils.py: rename ProcCmdLine to ProcKernelCmdLine to not wipe
193 wipe out /proc/$pid/cmdline information. (LP: #657091)
194 - apport/hookutils.py: attach_file() will not overwrite existing report
195 keys, instead appending "_" until the key is unique.
196 - Fix --save option to recognise ~, thanks Philip Muškovac. (LP: #657278)
197 - Remove escalation_subscription from Ubuntu bug DB definition, turned out to
198 not be useful; thanks Brian Murray.
199 - launchpad.py: Fix APPORT_LAUNCHPAD_INSTANCE values with a https:// prefix.
200 - apt backend: Opportunistically try to install a -dbg package in addition to
201 -dbgsym, to increase the chance that at least one of it exists. Thanks
202 Daniel J Blueman!
17203
181.14.1 (2010-06-24)2041.14.1 (2010-06-24)
19-------------------205-------------------
20206
=== modified file 'apport/REThread.py'
--- apport/REThread.py 2009-12-23 11:01:21 +0000
+++ apport/REThread.py 2011-04-20 23:32:24 +0000
@@ -64,7 +64,7 @@
64#64#
6565
66if __name__ == '__main__':66if __name__ == '__main__':
67 import unittest, time, traceback, exceptions67 import unittest, time, traceback
6868
69 def idle(seconds):69 def idle(seconds):
70 '''Test thread to just wait a bit.'''70 '''Test thread to just wait a bit.'''
@@ -107,11 +107,11 @@
107 t.join()107 t.join()
108 # thread did not terminate normally, no return value108 # thread did not terminate normally, no return value
109 self.assertRaises(AssertionError, t.return_value)109 self.assertRaises(AssertionError, t.return_value)
110 self.assert_(t.exc_info()[0] == exceptions.ZeroDivisionError)110 self.assertTrue(t.exc_info()[0] == ZeroDivisionError)
111 exc = traceback.format_exception(t.exc_info()[0], t.exc_info()[1],111 exc = traceback.format_exception(t.exc_info()[0], t.exc_info()[1],
112 t.exc_info()[2])112 t.exc_info()[2])
113 self.assert_(exc[-1].startswith('ZeroDivisionError'))113 self.assertTrue(exc[-1].startswith('ZeroDivisionError'), 'not a ZeroDivisionError:' + str(exc))
114 self.assert_(exc[-2].endswith('return x / y\n'))114 self.assertTrue(exc[-2].endswith('return x / y\n'))
115115
116 def test_exc_raise(self):116 def test_exc_raise(self):
117 '''exc_raise() raises caught thread exception.'''117 '''exc_raise() raises caught thread exception.'''
@@ -128,9 +128,9 @@
128 raised = True128 raised = True
129 e = sys.exc_info()129 e = sys.exc_info()
130 exc = traceback.format_exception(e[0], e[1], e[2])130 exc = traceback.format_exception(e[0], e[1], e[2])
131 self.assert_(exc[-1].startswith('ZeroDivisionError'))131 self.assertTrue(exc[-1].startswith('ZeroDivisionError'), 'not a ZeroDivisionError:' + str(e))
132 self.assert_(exc[-2].endswith('return x / y\n'))132 self.assertTrue(exc[-2].endswith('return x / y\n'))
133 self.assert_(raised)133 self.assertTrue(raised)
134134
135 unittest.main()135 unittest.main()
136136
137137
=== modified file 'apport/__init__.py'
--- apport/__init__.py 2009-09-08 12:03:44 +0000
+++ apport/__init__.py 2011-04-20 23:32:24 +0000
@@ -2,11 +2,33 @@
22
3from apport.packaging_impl import impl as packaging3from apport.packaging_impl import impl as packaging
44
5import sys
6
5# fix gettext to output proper unicode strings7# fix gettext to output proper unicode strings
6import gettext8import gettext
79
8def unicode_gettext(str):10def unicode_gettext(str):
9 trans = gettext.gettext(str)11 trans = gettext.gettext(str)
10 if type(trans) == type(u''):12 if isinstance(trans, unicode):
11 return trans13 return trans
12 return trans.decode('UTF-8')14 return trans.decode('UTF-8')
15
16def fatal(msg, *args):
17 '''Print out an error message and exit the program.'''
18
19 error(msg, *args)
20 sys.exit(1)
21
22def error(msg, *args):
23 '''Print out an error message.'''
24
25 sys.stderr.write('ERROR: ')
26 sys.stderr.write(msg % args)
27 sys.stderr.write('\n')
28
29def warning(msg, *args):
30 '''Print out an warning message.'''
31
32 sys.stderr.write('WARNING: ')
33 sys.stderr.write(msg % args)
34 sys.stderr.write('\n')
1335
=== modified file 'apport/chroot.py'
--- apport/chroot.py 2009-12-23 11:01:21 +0000
+++ apport/chroot.py 2011-04-20 23:32:24 +0000
@@ -227,7 +227,7 @@
227 os.close(fd)227 os.close(fd)
228 c.tar(tarpath)228 c.tar(tarpath)
229 t = tarfile.open(tarpath)229 t = tarfile.open(tarpath)
230 self.assert_(230 self.assertTrue(
231 # python 2.5's tarfile231 # python 2.5's tarfile
232 set(['./bin/42', './bin/hello', './newfile']).issubset(set(t.getnames())) or232 set(['./bin/42', './bin/hello', './newfile']).issubset(set(t.getnames())) or
233 # python 2.4's tarfile233 # python 2.4's tarfile
@@ -236,7 +236,7 @@
236236
237 # test cleanup237 # test cleanup
238 del c238 del c
239 self.assert_(os.path.exists(os.path.join(d, 'bin', '42')),239 self.assertTrue(os.path.exists(os.path.join(d, 'bin', '42')),
240 'directory chroot should not delete the chroot')240 'directory chroot should not delete the chroot')
241241
242 finally:242 finally:
@@ -274,7 +274,7 @@
274 open(os.path.join(c.root, 'newfile'), 'w')274 open(os.path.join(c.root, 'newfile'), 'w')
275 c.tar()275 c.tar()
276 t = tarfile.open(tar)276 t = tarfile.open(tar)
277 self.assert_(277 self.assertTrue(
278 # python 2.5's tarfile278 # python 2.5's tarfile
279 set(['./bin/42', './bin/hello', './newfile']).issubset(set(t.getnames())) or279 set(['./bin/42', './bin/hello', './newfile']).issubset(set(t.getnames())) or
280 # python 2.4's tarfile280 # python 2.4's tarfile
@@ -284,7 +284,7 @@
284 # test cleanup284 # test cleanup
285 d = c.root285 d = c.root
286 del c286 del c
287 self.assert_(not os.path.exists(d),287 self.assertTrue(not os.path.exists(d),
288 'tarball chroot should delete the temporary chroot')288 'tarball chroot should delete the temporary chroot')
289 finally:289 finally:
290 os.unlink(tar)290 os.unlink(tar)
@@ -364,10 +364,11 @@
364 self.assertEqual(err, '')364 self.assertEqual(err, '')
365 self.assertEqual(result, 0)365 self.assertEqual(result, 0)
366 files = out.splitlines()366 files = out.splitlines()
367 self.assert_('bin' in files)367 self.assertTrue('bin' in files)
368 self.assert_('lib' in files)368 self.assertTrue('lib' in files)
369369
370 # complex shell commands: relative symlinks and paths370 # complex shell commands: relative symlinks and paths
371 os.umask(022) # assumed below
371 self.assertEqual(c.run_capture(['bash'], '''set -e372 self.assertEqual(c.run_capture(['bash'], '''set -e
372cd /373cd /
373mkdir test374mkdir test
374375
=== modified file 'apport/crashdb.py'
--- apport/crashdb.py 2009-12-23 10:55:52 +0000
+++ apport/crashdb.py 2011-04-20 23:32:24 +0000
@@ -83,7 +83,7 @@
83 cur.execute('PRAGMA integrity_check')83 cur.execute('PRAGMA integrity_check')
84 result = cur.fetchall() 84 result = cur.fetchall()
85 if result != [('ok',)]:85 if result != [('ok',)]:
86 raise SystemError, 'Corrupt duplicate db:' + str(result)86 raise SystemError('Corrupt duplicate db:' + str(result))
8787
88 def check_duplicate(self, id, report=None):88 def check_duplicate(self, id, report=None):
89 '''Check whether a crash is already known.89 '''Check whether a crash is already known.
@@ -108,7 +108,10 @@
108108
109 self._mark_dup_checked(id, report)109 self._mark_dup_checked(id, report)
110110
111 sig = report.crash_signature()111 if 'DuplicateSignature' in report:
112 sig = report['DuplicateSignature']
113 else:
114 sig = report.crash_signature()
112 if not sig:115 if not sig:
113 return None116 return None
114117
@@ -225,10 +228,10 @@
225 # crash got fixed/rejected228 # crash got fixed/rejected
226 fixed_ver = self.get_fixed_version(id)229 fixed_ver = self.get_fixed_version(id)
227 if fixed_ver == 'invalid':230 if fixed_ver == 'invalid':
228 print 'DEBUG: bug %i was invalidated, removing from database' % id231 print('DEBUG: bug %i was invalidated, removing from database' % id)
229 cur2.execute('DELETE FROM crashes WHERE crash_id = ?', [id])232 cur2.execute('DELETE FROM crashes WHERE crash_id = ?', [id])
230 elif not fixed_ver:233 elif not fixed_ver:
231 print 'WARNING: inconsistency detected: bug #%i does not appear in get_unfixed(), but is not fixed yet' % id234 print('WARNING: inconsistency detected: bug #%i does not appear in get_unfixed(), but is not fixed yet' % id)
232 else:235 else:
233 cur2.execute('UPDATE crashes SET fixed_version = ?, last_change = CURRENT_TIMESTAMP WHERE crash_id = ?',236 cur2.execute('UPDATE crashes SET fixed_version = ?, last_change = CURRENT_TIMESTAMP WHERE crash_id = ?',
234 (fixed_ver, id))237 (fixed_ver, id))
@@ -328,7 +331,7 @@
328331
329 This method can raise a NeedsCredentials exception in case of failure.332 This method can raise a NeedsCredentials exception in case of failure.
330 '''333 '''
331 raise NotImplementedError, 'this method must be implemented by a concrete subclass'334 raise NotImplementedError('this method must be implemented by a concrete subclass')
332335
333 def get_comment_url(self, report, handle):336 def get_comment_url(self, report, handle):
334 '''Return an URL that should be opened after report has been uploaded337 '''Return an URL that should be opened after report has been uploaded
@@ -338,12 +341,12 @@
338 user comments); in that case this function should do whichever341 user comments); in that case this function should do whichever
339 interactive steps it wants to perform.342 interactive steps it wants to perform.
340 '''343 '''
341 raise NotImplementedError, 'this method must be implemented by a concrete subclass'344 raise NotImplementedError('this method must be implemented by a concrete subclass')
342345
343 def download(self, id):346 def download(self, id):
344 '''Download the problem report from given ID and return a Report.'''347 '''Download the problem report from given ID and return a Report.'''
345348
346 raise NotImplementedError, 'this method must be implemented by a concrete subclass'349 raise NotImplementedError('this method must be implemented by a concrete subclass')
347350
348 def update(self, id, report, comment, change_description=False,351 def update(self, id, report, comment, change_description=False,
349 attachment_comment=None, key_filter=None):352 attachment_comment=None, key_filter=None):
@@ -362,7 +365,7 @@
362365
363 If key_filter is a list or set, then only those keys will be added.366 If key_filter is a list or set, then only those keys will be added.
364 '''367 '''
365 raise NotImplementedError, 'this method must be implemented by a concrete subclass'368 raise NotImplementedError('this method must be implemented by a concrete subclass')
366369
367 def update_traces(self, id, report, comment=''):370 def update_traces(self, id, report, comment=''):
368 '''Update the given report ID for retracing results.371 '''Update the given report ID for retracing results.
@@ -376,12 +379,12 @@
376 def set_credentials(self, username, password):379 def set_credentials(self, username, password):
377 '''Set username and password.'''380 '''Set username and password.'''
378381
379 raise NotImplementedError, 'this method must be implemented by a concrete subclass'382 raise NotImplementedError('this method must be implemented by a concrete subclass')
380383
381 def get_distro_release(self, id):384 def get_distro_release(self, id):
382 '''Get 'DistroRelease: <release>' from the report ID.'''385 '''Get 'DistroRelease: <release>' from the report ID.'''
383386
384 raise NotImplementedError, 'this method must be implemented by a concrete subclass'387 raise NotImplementedError('this method must be implemented by a concrete subclass')
385388
386 def get_unretraced(self):389 def get_unretraced(self):
387 '''Return set of crash IDs which have not been retraced yet.390 '''Return set of crash IDs which have not been retraced yet.
@@ -389,7 +392,7 @@
389 This should only include crashes which match the current host392 This should only include crashes which match the current host
390 architecture.393 architecture.
391 '''394 '''
392 raise NotImplementedError, 'this method must be implemented by a concrete subclass'395 raise NotImplementedError('this method must be implemented by a concrete subclass')
393396
394 def get_dup_unchecked(self):397 def get_dup_unchecked(self):
395 '''Return set of crash IDs which need duplicate checking.398 '''Return set of crash IDs which need duplicate checking.
@@ -398,7 +401,7 @@
398 Python, since they do not need to be retraced. It should not return401 Python, since they do not need to be retraced. It should not return
399 bugs that are covered by get_unretraced().402 bugs that are covered by get_unretraced().
400 '''403 '''
401 raise NotImplementedError, 'this method must be implemented by a concrete subclass'404 raise NotImplementedError('this method must be implemented by a concrete subclass')
402405
403 def get_unfixed(self):406 def get_unfixed(self):
404 '''Return an ID set of all crashes which are not yet fixed.407 '''Return an ID set of all crashes which are not yet fixed.
@@ -409,7 +412,7 @@
409 there are any errors with connecting to the crash database, it should412 there are any errors with connecting to the crash database, it should
410 raise an exception (preferably IOError).413 raise an exception (preferably IOError).
411 '''414 '''
412 raise NotImplementedError, 'this method must be implemented by a concrete subclass'415 raise NotImplementedError('this method must be implemented by a concrete subclass')
413416
414 def get_fixed_version(self, id):417 def get_fixed_version(self, id):
415 '''Return the package version that fixes a given crash.418 '''Return the package version that fixes a given crash.
@@ -423,17 +426,17 @@
423 there are any errors with connecting to the crash database, it should426 there are any errors with connecting to the crash database, it should
424 raise an exception (preferably IOError).427 raise an exception (preferably IOError).
425 '''428 '''
426 raise NotImplementedError, 'this method must be implemented by a concrete subclass'429 raise NotImplementedError('this method must be implemented by a concrete subclass')
427430
428 def get_affected_packages(self, id):431 def get_affected_packages(self, id):
429 '''Return list of affected source packages for given ID.'''432 '''Return list of affected source packages for given ID.'''
430433
431 raise NotImplementedError, 'this method must be implemented by a concrete subclass'434 raise NotImplementedError('this method must be implemented by a concrete subclass')
432435
433 def is_reporter(self, id):436 def is_reporter(self, id):
434 '''Check whether the user is the reporter of given ID.'''437 '''Check whether the user is the reporter of given ID.'''
435438
436 raise NotImplementedError, 'this method must be implemented by a concrete subclass'439 raise NotImplementedError('this method must be implemented by a concrete subclass')
437440
438 def can_update(self, id):441 def can_update(self, id):
439 '''Check whether the user is eligible to update a report.442 '''Check whether the user is eligible to update a report.
@@ -443,32 +446,32 @@
443 exact policy and checks should be done according to the particular446 exact policy and checks should be done according to the particular
444 implementation.447 implementation.
445 '''448 '''
446 raise NotImplementedError, 'this method must be implemented by a concrete subclass'449 raise NotImplementedError('this method must be implemented by a concrete subclass')
447450
448 def duplicate_of(self, id):451 def duplicate_of(self, id):
449 '''Return master ID for a duplicate bug.452 '''Return master ID for a duplicate bug.
450453
451 If the bug is not a duplicate, return None.454 If the bug is not a duplicate, return None.
452 '''455 '''
453 raise NotImplementedError, 'this method must be implemented by a concrete subclass'456 raise NotImplementedError('this method must be implemented by a concrete subclass')
454457
455 def close_duplicate(self, id, master):458 def close_duplicate(self, id, master):
456 '''Mark a crash id as duplicate of given master ID.459 '''Mark a crash id as duplicate of given master ID.
457 460
458 If master is None, id gets un-duplicated.461 If master is None, id gets un-duplicated.
459 '''462 '''
460 raise NotImplementedError, 'this method must be implemented by a concrete subclass'463 raise NotImplementedError('this method must be implemented by a concrete subclass')
461464
462 def mark_regression(self, id, master):465 def mark_regression(self, id, master):
463 '''Mark a crash id as reintroducing an earlier crash which is466 '''Mark a crash id as reintroducing an earlier crash which is
464 already marked as fixed (having ID 'master').'''467 already marked as fixed (having ID 'master').'''
465 468
466 raise NotImplementedError, 'this method must be implemented by a concrete subclass'469 raise NotImplementedError('this method must be implemented by a concrete subclass')
467470
468 def mark_retraced(self, id):471 def mark_retraced(self, id):
469 '''Mark crash id as retraced.'''472 '''Mark crash id as retraced.'''
470473
471 raise NotImplementedError, 'this method must be implemented by a concrete subclass'474 raise NotImplementedError('this method must be implemented by a concrete subclass')
472475
473 def mark_retrace_failed(self, id, invalid_msg=None):476 def mark_retrace_failed(self, id, invalid_msg=None):
474 '''Mark crash id as 'failed to retrace'.477 '''Mark crash id as 'failed to retrace'.
@@ -478,14 +481,14 @@
478 481
479 This can be a no-op if you are not interested in this.482 This can be a no-op if you are not interested in this.
480 '''483 '''
481 raise NotImplementedError, 'this method must be implemented by a concrete subclass'484 raise NotImplementedError('this method must be implemented by a concrete subclass')
482485
483 def _mark_dup_checked(self, id, report):486 def _mark_dup_checked(self, id, report):
484 '''Mark crash id as checked for being a duplicate487 '''Mark crash id as checked for being a duplicate
485 488
486 This is an internal method that should not be called from outside.489 This is an internal method that should not be called from outside.
487 '''490 '''
488 raise NotImplementedError, 'this method must be implemented by a concrete subclass'491 raise NotImplementedError('this method must be implemented by a concrete subclass')
489492
490#493#
491# factory 494# factory
@@ -508,7 +511,7 @@
508 - A dictionary 'databases' which maps names to crash db configuration511 - A dictionary 'databases' which maps names to crash db configuration
509 dictionaries. These need to have at least the keys 'impl' (Python module512 dictionaries. These need to have at least the keys 'impl' (Python module
510 in apport.crashdb_impl which contains a concrete 'CrashDatabase' class513 in apport.crashdb_impl which contains a concrete 'CrashDatabase' class
511 implementation for that crash db type) and 'bug_pattern_base', which514 implementation for that crash db type) and 'bug_pattern_url', which
512 specifies an URL for bug patterns (or None if those are not used for that515 specifies an URL for bug patterns (or None if those are not used for that
513 crash db).516 crash db).
514 '''517 '''
@@ -525,9 +528,9 @@
525 if os.path.isfile(cfpath) and cf.endswith('.conf'):528 if os.path.isfile(cfpath) and cf.endswith('.conf'):
526 try:529 try:
527 execfile(cfpath, settings['databases'])530 execfile(cfpath, settings['databases'])
528 except Exception, e:531 except Exception as e:
529 # ignore broken files532 # ignore broken files
530 print >> sys.stderr, 'Invalid file %s: %s' % (cfpath, str(e))533 sys.stderr.write('Invalid file %s: %s\n' % (cfpath, str(e)))
531 pass534 pass
532535
533 if not name:536 if not name:
@@ -535,8 +538,13 @@
535538
536 db = settings['databases'][name]539 db = settings['databases'][name]
537540
541 bug_pattern_url = db.get('bug_pattern_url')
542 if not bug_pattern_url:
543 # fall back to default database's
544 bug_pattern_url = settings['databases'][settings['default']].get('bug_pattern_url')
545
538 m = __import__('apport.crashdb_impl.' + db['impl'], globals(), locals(), ['CrashDatabase'])546 m = __import__('apport.crashdb_impl.' + db['impl'], globals(), locals(), ['CrashDatabase'])
539 return m.CrashDatabase(auth_file, db['bug_pattern_base'], db)547 return m.CrashDatabase(auth_file, bug_pattern_url, db)
540548
541class NeedsCredentials(Exception):549class NeedsCredentials(Exception):
542 '''This may be raised when unable to log in to the crashdb.'''550 '''This may be raised when unable to log in to the crashdb.'''
543551
=== modified file 'apport/crashdb_impl/launchpad.py'
--- apport/crashdb_impl/launchpad.py 2010-06-16 13:50:47 +0000
+++ apport/crashdb_impl/launchpad.py 2011-04-20 23:32:24 +0000
@@ -10,12 +10,15 @@
10# option) any later version. See http://www.gnu.org/copyleft/gpl.html for10# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
11# the full text of the license.11# the full text of the license.
1212
13import urllib, tempfile, shutil, os.path, re, gzip, sys, socket, ConfigParser13import urllib, tempfile, os.path, re, gzip, sys
14import email14import email
15from cStringIO import StringIO15try:
16 from cStringIO import StringIO
17except ImportError:
18 from io import StringIO
1619
17from launchpadlib.errors import HTTPError20from launchpadlib.errors import HTTPError
18from launchpadlib.launchpad import Launchpad, STAGING_SERVICE_ROOT, EDGE_SERVICE_ROOT21from launchpadlib.launchpad import Launchpad
1922
20import apport.crashdb23import apport.crashdb
21import apport24import apport
@@ -26,8 +29,8 @@
26 for attachment in attachments:29 for attachment in attachments:
27 try:30 try:
28 f = attachment.data.open()31 f = attachment.data.open()
29 except HTTPError, e:32 except HTTPError:
30 print >> sys.stderr, 'ERROR: Broken attachment on bug, ignoring'33 apport.error('Broken attachment on bug, ignoring')
31 continue34 continue
32 name = f.filename35 name = f.filename
33 if name.endswith('.txt') or name.endswith('.gz'):36 if name.endswith('.txt') or name.endswith('.gz'):
@@ -52,8 +55,9 @@
52 - distro: Name of the distribution in Launchpad55 - distro: Name of the distribution in Launchpad
53 - project: Name of the project in Launchpad56 - project: Name of the project in Launchpad
54 (Note that exactly one of "distro" or "project" must be given.)57 (Note that exactly one of "distro" or "project" must be given.)
55 - staging: If set, this uses staging instead of production (optional).58 - launchpad_instance: If set, this uses the given launchpad instance
56 This can be overriden or set by $APPORT_STAGING environment.59 instead of production (optional). This can be overriden or set by
60 $APPORT_LAUNCHPAD_INSTANCE environment.
57 - cache_dir: Path to a permanent cache directory; by default it uses a61 - cache_dir: Path to a permanent cache directory; by default it uses a
58 temporary one. (optional). This can be overridden or set by62 temporary one. (optional). This can be overridden or set by
59 $APPORT_LAUNCHPAD_CACHE environment.63 $APPORT_LAUNCHPAD_CACHE environment.
@@ -62,11 +66,13 @@
62 - escalation_tag: This adds the given tag to a bug once it gets more66 - escalation_tag: This adds the given tag to a bug once it gets more
63 than 10 duplicates.67 than 10 duplicates.
64 '''68 '''
65 if os.getenv('APPORT_STAGING'):69 if os.getenv('APPORT_LAUNCHPAD_INSTANCE'):
66 options['staging'] = True70 options['launchpad_instance'] = os.getenv(
71 'APPORT_LAUNCHPAD_INSTANCE')
67 if not auth:72 if not auth:
68 if options.get('staging'):73 lp_instance = options.get('launchpad_instance')
69 auth = default_credentials_path + '.staging'74 if lp_instance:
75 auth = default_credentials_path + '.' + lp_instance.split('://', 1)[-1]
70 else:76 else:
71 auth = default_credentials_path77 auth = default_credentials_path
72 apport.crashdb.CrashDatabase.__init__(self, auth,78 apport.crashdb.CrashDatabase.__init__(self, auth,
@@ -94,10 +100,10 @@
94 if self.__launchpad:100 if self.__launchpad:
95 return self.__launchpad101 return self.__launchpad
96102
97 if self.options.get('staging'):103 if self.options.get('launchpad_instance'):
98 launchpad_instance = STAGING_SERVICE_ROOT104 launchpad_instance = self.options.get('launchpad_instance')
99 else:105 else:
100 launchpad_instance = EDGE_SERVICE_ROOT106 launchpad_instance = 'production'
101107
102 auth_dir = os.path.dirname(self.auth)108 auth_dir = os.path.dirname(self.auth)
103 if auth_dir and not os.path.isdir(auth_dir):109 if auth_dir and not os.path.isdir(auth_dir):
@@ -109,12 +115,12 @@
109 allow_access_levels=['WRITE_PRIVATE'],115 allow_access_levels=['WRITE_PRIVATE'],
110 credentials_file = self.auth,116 credentials_file = self.auth,
111 version='1.0')117 version='1.0')
112 except Exception, e:118 except Exception as e:
113 if hasattr(e, 'content'):119 if hasattr(e, 'content'):
114 msg = e.content120 msg = e.content
115 else:121 else:
116 msg = str(e)122 msg = str(e)
117 print >> sys.stderr, 'Error connecting to Launchpad: %s\nYou can reset the credentials by removing the file "%s"' % (msg, self.auth)123 apport.error('connecting to Launchpad failed: %s\nYou can reset the credentials by removing the file "%s"', msg, self.auth)
118 sys.exit(99) # transient error124 sys.exit(99) # transient error
119125
120 return self.__launchpad126 return self.__launchpad
@@ -168,6 +174,8 @@
168 hdr['Private'] = 'yes'174 hdr['Private'] = 'yes'
169 hdr['Subscribers'] = 'apport'175 hdr['Subscribers'] = 'apport'
170 hdr['Tags'] += ' need-duplicate-check'176 hdr['Tags'] += ' need-duplicate-check'
177 if 'DuplicateSignature' in report and 'need-duplicate-check' not in hdr['Tags']:
178 hdr['Tags'] += ' need-duplicate-check'
171179
172 # if we have checkbox submission key, link it to the bug; keep text180 # if we have checkbox submission key, link it to the bug; keep text
173 # reference until the link is shown in Launchpad's UI181 # reference until the link is shown in Launchpad's UI
@@ -185,10 +193,23 @@
185 mime.seek(0)193 mime.seek(0)
186194
187 ticket = upload_blob(mime, progress_callback,195 ticket = upload_blob(mime, progress_callback,
188 staging=self.options.get('staging', False))196 hostname=self.get_hostname())
189 assert ticket197 assert ticket
190 return ticket198 return ticket
191199
200 def get_hostname(self):
201 '''Return the hostname for the Launchpad instance.'''
202
203 launchpad_instance = self.options.get('launchpad_instance')
204 if launchpad_instance:
205 if launchpad_instance == 'staging':
206 hostname = 'staging.launchpad.net'
207 else:
208 hostname = 'launchpad.dev'
209 else:
210 hostname = 'launchpad.net'
211 return hostname
212
192 def get_comment_url(self, report, handle):213 def get_comment_url(self, report, handle):
193 '''Return an URL that should be opened after report has been uploaded214 '''Return an URL that should be opened after report has been uploaded
194 and upload() returned handle.215 and upload() returned handle.
@@ -202,13 +223,10 @@
202 if title:223 if title:
203 args['field.title'] = title224 args['field.title'] = title
204225
205 if self.options.get('staging'):226 hostname = self.get_hostname()
206 hostname = 'staging.launchpad.net'
207 else:
208 hostname = 'launchpad.net'
209227
210 project = self.options.get('project')228 project = self.options.get('project')
211 229
212 if not project:230 if not project:
213 if report.has_key('SourcePackage'):231 if report.has_key('SourcePackage'):
214 return 'https://bugs.%s/%s/+source/%s/+filebug/%s?%s' % (232 return 'https://bugs.%s/%s/+source/%s/+filebug/%s?%s' % (
@@ -269,7 +287,7 @@
269 elif 'apport-package' in b.tags:287 elif 'apport-package' in b.tags:
270 report['ProblemType'] = 'Package'288 report['ProblemType'] = 'Package'
271 else:289 else:
272 raise ValueError, 'cannot determine ProblemType from tags: ' + str(b.tags)290 raise ValueError('cannot determine ProblemType from tags: ' + str(b.tags))
273291
274 report['Tags'] = ' '.join(b.tags)292 report['Tags'] = ' '.join(b.tags)
275293
@@ -290,7 +308,7 @@
290 elif ext == '.gz':308 elif ext == '.gz':
291 try:309 try:
292 report[key] = gzip.GzipFile(fileobj=attachment).read()310 report[key] = gzip.GzipFile(fileobj=attachment).read()
293 except IOError, e:311 except IOError as e:
294 # some attachments are only called .gz, but are312 # some attachments are only called .gz, but are
295 # uncompressed (LP #574360)313 # uncompressed (LP #574360)
296 if 'Not a gzip' not in str(e):314 if 'Not a gzip' not in str(e):
@@ -298,7 +316,7 @@
298 attachment.seek(0)316 attachment.seek(0)
299 report[key] = attachment.read()317 report[key] = attachment.read()
300 else:318 else:
301 raise Exception, 'Unknown attachment type: ' + attachment.filename319 raise Exception('Unknown attachment type: ' + attachment.filename)
302 return report320 return report
303321
304 def update(self, id, report, comment, change_description=False,322 def update(self, id, report, comment, change_description=False,
@@ -350,6 +368,9 @@
350 # with apport-collect368 # with apport-collect
351 x = bug.tags[:] # LP#254901 workaround369 x = bug.tags[:] # LP#254901 workaround
352 x.append('apport-collected')370 x.append('apport-collected')
371 # add any tags (like the release) to the bug
372 if report.has_key('Tags'):
373 x += report['Tags'].split()
353 bug.tags = x374 bug.tags = x
354 bug.lp_save()375 bug.lp_save()
355 bug = self.launchpad.bugs[id] # fresh bug object, LP#336866 workaround376 bug = self.launchpad.bugs[id] # fresh bug object, LP#336866 workaround
@@ -412,7 +433,7 @@
412 m = re.search('DistroRelease: ([-a-zA-Z0-9.+/ ]+)', bug.description)433 m = re.search('DistroRelease: ([-a-zA-Z0-9.+/ ]+)', bug.description)
413 if m:434 if m:
414 return m.group(1)435 return m.group(1)
415 raise ValueError, 'URL does not contain DistroRelease: field'436 raise ValueError('URL does not contain DistroRelease: field')
416437
417 def get_affected_packages(self, id):438 def get_affected_packages(self, id):
418 '''Return list of affected source packages for given ID.'''439 '''Return list of affected source packages for given ID.'''
@@ -544,7 +565,7 @@
544 return ''565 return ''
545 566
546 if len(fixed_tasks) > 1: 567 if len(fixed_tasks) > 1:
547 print >> sys.stderr, 'WARNING: There is more than one task fixed in %s %s, using first one to determine fixed version' % (self.distro, id)568 apport.warning('There is more than one task fixed in %s %s, using first one to determine fixed version', self.distro, id)
548 return ''569 return ''
549570
550 if fixed_tasks:571 if fixed_tasks:
@@ -727,7 +748,7 @@
727 #FIXME: this entire function is an ugly Ubuntu specific hack until LP748 #FIXME: this entire function is an ugly Ubuntu specific hack until LP
728 #gets a real crash db; see https://wiki.ubuntu.com/CrashReporting749 #gets a real crash db; see https://wiki.ubuntu.com/CrashReporting
729750
730 if report['DistroRelease'].split()[0] != 'Ubuntu':751 if report.get('DistroRelease', '').split()[0] != 'Ubuntu':
731 return # only Ubuntu bugs are filed private752 return # only Ubuntu bugs are filed private
732 753
733 #use a url hack here, it is faster754 #use a url hack here, it is faster
@@ -780,7 +801,7 @@
780 def https_open(self, req):801 def https_open(self, req):
781 return self.do_open(HTTPSProgressConnection, req)802 return self.do_open(HTTPSProgressConnection, req)
782803
783def upload_blob(blob, progress_callback = None, staging=False):804def upload_blob(blob, progress_callback = None, hostname='launchpad.net'):
784 '''Upload blob (file-like object) to Launchpad.805 '''Upload blob (file-like object) to Launchpad.
785806
786 progress_callback can be set to a function(sent, total) which is regularly807 progress_callback can be set to a function(sent, total) which is regularly
@@ -790,8 +811,9 @@
790811
791 Return None on error, or the ticket number on success.812 Return None on error, or the ticket number on success.
792813
793 By default this uses the production Launchpad instance. Set staging=True to814 By default this uses the production Launchpad hostname. Set
794 use staging.launchpad.net (for testing).815 hostname to 'launchpad.dev' or 'staging.launchpad.net' to use another
816 instance for testing.
795 '''817 '''
796 ticket = None818 ticket = None
797819
@@ -799,10 +821,7 @@
799 _https_upload_callback = progress_callback821 _https_upload_callback = progress_callback
800822
801 opener = urllib2.build_opener(HTTPSProgressHandler, multipartpost_handler.MultipartPostHandler)823 opener = urllib2.build_opener(HTTPSProgressHandler, multipartpost_handler.MultipartPostHandler)
802 if staging:824 url = 'https://%s/+storeblob' % hostname
803 url = 'https://staging.launchpad.net/+storeblob'
804 else:
805 url = 'https://launchpad.net/+storeblob'
806 result = opener.open(url,825 result = opener.open(url,
807 { 'FORM_SUBMIT': '1', 'field.blob': blob })826 { 'FORM_SUBMIT': '1', 'field.blob': blob })
808 ticket = result.info().get('X-Launchpad-Blob-Token')827 ticket = result.info().get('X-Launchpad-Blob-Token')
@@ -814,7 +833,7 @@
814#833#
815834
816if __name__ == '__main__':835if __name__ == '__main__':
817 import unittest, urllib2, cookielib836 import unittest, urllib2
818837
819 crashdb = None838 crashdb = None
820 segv_report = None839 segv_report = None
@@ -825,8 +844,6 @@
825 # binary package 'coreutils'844 # binary package 'coreutils'
826 test_package = 'coreutils'845 test_package = 'coreutils'
827 test_srcpackage = 'coreutils'846 test_srcpackage = 'coreutils'
828 known_test_id = 302779
829 known_test_id2 = 89040
830847
831 #848 #
832 # Generic tests, should work for all CrashDB implementations849 # Generic tests, should work for all CrashDB implementations
@@ -845,6 +862,54 @@
845 self.ref_report.add_user_info()862 self.ref_report.add_user_info()
846 self.ref_report['SourcePackage'] = 'coreutils'863 self.ref_report['SourcePackage'] = 'coreutils'
847864
865 # Objects tests rely on.
866 self.uncommon_description_bug = self._file_uncommon_description_bug()
867 self._create_project('langpack-o-matic')
868
869 # XXX Should create new bug reports, not reuse those.
870 self.known_test_id = self.uncommon_description_bug.id
871 self.known_test_id2 = self._file_uncommon_description_bug().id
872
873 def _create_project(self, name):
874 '''Create a project using launchpadlib to be used by tests.'''
875
876 project = self.crashdb.launchpad.projects[name]
877 if not project:
878 self.crashdb.launchpad.projects.new_project(
879 description=name + 'description',
880 display_name=name,
881 name=name,
882 summary=name + 'summary',
883 title=name + 'title')
884
885 def _file_uncommon_description_bug(self):
886 '''File a bug report with an uncommon description.
887
888 Example taken from real LP bug 269539. It contains only
889 ProblemType/Architecture/DistroRelease in the description, and has
890 free-form description text after the Apport data.
891 '''
892 desc = '''problem
893
894ProblemType: Package
895Architecture: amd64
896DistroRelease: Ubuntu 8.10
897
898more text
899
900and more
901'''
902 return self.crashdb.launchpad.bugs.createBug(
903 title=u'mixed description bug',
904 description=desc,
905 target=self.crashdb.lp_distro)
906
907 @property
908 def hostname(self):
909 '''Get the Launchpad hostname for the given crashdb.'''
910
911 return self.crashdb.get_hostname()
912
848 def _file_segv_report(self):913 def _file_segv_report(self):
849 '''File a SEGV crash report.914 '''File a SEGV crash report.
850915
@@ -862,12 +927,12 @@
862 r['LongGibberish'] = 'a\nb\nc\nd\ne\n\xff\xff\xff\n\f'927 r['LongGibberish'] = 'a\nb\nc\nd\ne\n\xff\xff\xff\n\f'
863928
864 handle = self.crashdb.upload(r)929 handle = self.crashdb.upload(r)
865 self.assert_(handle)930 self.assertTrue(handle)
866 url = self.crashdb.get_comment_url(r, handle)931 bug_target = self._get_bug_target(self.crashdb, r)
867 self.assert_(url)932 self.assertTrue(bug_target)
868933
869 id = self._fill_bug_form(url)934 id = self._file_bug(bug_target, r, handle)
870 self.assert_(id > 0)935 self.assertTrue(id > 0)
871 return id936 return id
872937
873 def test_1_report_segv(self):938 def test_1_report_segv(self):
@@ -878,7 +943,7 @@
878 global segv_report943 global segv_report
879 id = self._file_segv_report()944 id = self._file_segv_report()
880 segv_report = id945 segv_report = id
881 print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,946 sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
882947
883 def test_1_report_python(self):948 def test_1_report_python(self):
884 '''upload() and get_comment_url() for Python crash949 '''upload() and get_comment_url() for Python crash
@@ -889,24 +954,25 @@
889 r['ExecutablePath'] = '/bin/foo'954 r['ExecutablePath'] = '/bin/foo'
890 r['Traceback'] = '''Traceback (most recent call last):955 r['Traceback'] = '''Traceback (most recent call last):
891 File "/bin/foo", line 67, in fuzz956 File "/bin/foo", line 67, in fuzz
892 print weird957 print(weird)
893NameError: global name 'weird' is not defined'''958NameError: global name 'weird' is not defined'''
894 r['Tags'] = 'boogus pybogus'959 r['Tags'] = 'boogus pybogus'
895 r.add_package_info(self.test_package)960 r.add_package_info(self.test_package)
896 r.add_os_info()961 r.add_os_info()
897 r.add_user_info()962 r.add_user_info()
898 self.assertEqual(r.standard_title(), 'foo crashed with NameError in fuzz()')963 self.assertEqual(r.standard_title(),
964 "foo crashed with NameError in fuzz(): global name 'weird' is not defined")
899965
900 handle = self.crashdb.upload(r)966 handle = self.crashdb.upload(r)
901 self.assert_(handle)967 self.assertTrue(handle)
902 url = self.crashdb.get_comment_url(r, handle)968 bug_target = self._get_bug_target(self.crashdb, r)
903 self.assert_(url)969 self.assertTrue(bug_target)
904970
905 id = self._fill_bug_form(url)971 id = self._file_bug(bug_target, r, handle)
906 self.assert_(id > 0)972 self.assertTrue(id > 0)
907 global python_report973 global python_report
908 python_report = id974 python_report = id
909 print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,975 sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
910976
911 def test_2_download(self):977 def test_2_download(self):
912 '''download()'''978 '''download()'''
@@ -925,16 +991,16 @@
925 apport.packaging.get_system_architecture()]))991 apport.packaging.get_system_architecture()]))
926992
927 self.assertEqual(r['Signal'], '11')993 self.assertEqual(r['Signal'], '11')
928 self.assert_(r['ExecutablePath'].endswith('/crash'))994 self.assertTrue(r['ExecutablePath'].endswith('/crash'))
929 self.assertEqual(r['SourcePackage'], self.test_srcpackage)995 self.assertEqual(r['SourcePackage'], self.test_srcpackage)
930 self.assert_(r['Package'].startswith(self.test_package + ' '))996 self.assertTrue(r['Package'].startswith(self.test_package + ' '))
931 self.assert_('f (x=42)' in r['Stacktrace'])997 self.assertTrue('f (x=42)' in r['Stacktrace'])
932 self.assert_('f (x=42)' in r['StacktraceTop'])998 self.assertTrue('f (x=42)' in r['StacktraceTop'])
933 self.assert_('f (x=42)' in r['ThreadStacktrace'])999 self.assertTrue('f (x=42)' in r['ThreadStacktrace'])
934 self.assert_(len(r['CoreDump']) > 1000)1000 self.assertTrue(len(r['CoreDump']) > 1000)
935 self.assert_('Dependencies' in r)1001 self.assertTrue('Dependencies' in r)
936 self.assert_('Disassembly' in r)1002 self.assertTrue('Disassembly' in r)
937 self.assert_('Registers' in r)1003 self.assertTrue('Registers' in r)
9381004
939 # check tags1005 # check tags
940 r = self.crashdb.download(python_report)1006 r = self.crashdb.download(python_report)
@@ -946,12 +1012,12 @@
946 '''update_traces()'''1012 '''update_traces()'''
9471013
948 r = self.crashdb.download(segv_report)1014 r = self.crashdb.download(segv_report)
949 self.assert_('CoreDump' in r)1015 self.assertTrue('CoreDump' in r)
950 self.assert_('Dependencies' in r)1016 self.assertTrue('Dependencies' in r)
951 self.assert_('Disassembly' in r)1017 self.assertTrue('Disassembly' in r)
952 self.assert_('Registers' in r)1018 self.assertTrue('Registers' in r)
953 self.assert_('Stacktrace' in r)1019 self.assertTrue('Stacktrace' in r)
954 self.assert_('ThreadStacktrace' in r)1020 self.assertTrue('ThreadStacktrace' in r)
9551021
956 # updating with an useless stack trace retains core dump1022 # updating with an useless stack trace retains core dump
957 r['StacktraceTop'] = '?? ()'1023 r['StacktraceTop'] = '?? ()'
@@ -960,17 +1026,17 @@
960 r['FooBar'] = 'bogus'1026 r['FooBar'] = 'bogus'
961 self.crashdb.update_traces(segv_report, r, 'I can has a better retrace?')1027 self.crashdb.update_traces(segv_report, r, 'I can has a better retrace?')
962 r = self.crashdb.download(segv_report)1028 r = self.crashdb.download(segv_report)
963 self.assert_('CoreDump' in r)1029 self.assertTrue('CoreDump' in r)
964 self.assert_('Dependencies' in r)1030 self.assertTrue('Dependencies' in r)
965 self.assert_('Disassembly' in r)1031 self.assertTrue('Disassembly' in r)
966 self.assert_('Registers' in r)1032 self.assertTrue('Registers' in r)
967 self.assert_('Stacktrace' in r) # TODO: ascertain that it's the updated one1033 self.assertTrue('Stacktrace' in r) # TODO: ascertain that it's the updated one
968 self.assert_('ThreadStacktrace' in r)1034 self.assertTrue('ThreadStacktrace' in r)
969 self.failIf('FooBar' in r)1035 self.assertFalse('FooBar' in r)
9701036
971 tags = self.crashdb.launchpad.bugs[segv_report].tags1037 tags = self.crashdb.launchpad.bugs[segv_report].tags
972 self.assert_('apport-crash' in tags)1038 self.assertTrue('apport-crash' in tags)
973 self.failIf('apport-collected' in tags)1039 self.assertFalse('apport-collected' in tags)
9741040
975 # updating with an useful stack trace removes core dump1041 # updating with an useful stack trace removes core dump
976 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'1042 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
@@ -978,13 +1044,13 @@
978 r['ThreadStacktrace'] = 'thread\neven longer\ntrace'1044 r['ThreadStacktrace'] = 'thread\neven longer\ntrace'
979 self.crashdb.update_traces(segv_report, r, 'good retrace!')1045 self.crashdb.update_traces(segv_report, r, 'good retrace!')
980 r = self.crashdb.download(segv_report)1046 r = self.crashdb.download(segv_report)
981 self.failIf('CoreDump' in r)1047 self.assertFalse('CoreDump' in r)
982 self.assert_('Dependencies' in r)1048 self.assertTrue('Dependencies' in r)
983 self.assert_('Disassembly' in r)1049 self.assertTrue('Disassembly' in r)
984 self.assert_('Registers' in r)1050 self.assertTrue('Registers' in r)
985 self.assert_('Stacktrace' in r)1051 self.assertTrue('Stacktrace' in r)
986 self.assert_('ThreadStacktrace' in r)1052 self.assertTrue('ThreadStacktrace' in r)
987 self.failIf('FooBar' in r)1053 self.assertFalse('FooBar' in r)
9881054
989 # test various situations which caused crashes1055 # test various situations which caused crashes
990 r['Stacktrace'] = '' # empty file1056 r['Stacktrace'] = '' # empty file
@@ -995,11 +1061,14 @@
995 def test_update_description(self):1061 def test_update_description(self):
996 '''update() with changing description'''1062 '''update() with changing description'''
9971063
998 id = self._fill_bug_form(1064 bug_target = self.crashdb.lp_distro.getSourcePackage(name='bash')
999 'https://bugs.staging.launchpad.net/%s/+source/bash/+filebug?'1065 bug = self.crashdb.launchpad.bugs.createBug(
1000 'field.title=testbug&field.actions.search=' % self.crashdb.distro)1066 description='test description for test bug.',
1001 self.assert_(id > 0)1067 target=bug_target,
1002 print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,1068 title='testbug')
1069 id = bug.id
1070 self.assertTrue(id > 0)
1071 sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
10031072
1004 r = apport.Report('Bug')1073 r = apport.Report('Bug')
10051074
@@ -1024,14 +1093,16 @@
1024 def test_update_comment(self):1093 def test_update_comment(self):
1025 '''update() with appending comment'''1094 '''update() with appending comment'''
10261095
1096 bug_target = self.crashdb.lp_distro.getSourcePackage(name='bash')
1027 # we need to fake an apport description separator here, since we1097 # we need to fake an apport description separator here, since we
1028 # want to be lazy and use download() for checking the result1098 # want to be lazy and use download() for checking the result
1029 id = self._fill_bug_form(1099 bug = self.crashdb.launchpad.bugs.createBug(
1030 'https://bugs.staging.launchpad.net/%s/+source/bash/+filebug?'1100 description='Pr0blem\n\n--- \nProblemType: Bug',
1031 'field.title=testbug&field.actions.search=' %1101 target=bug_target,
1032 self.crashdb.distro, comment='Pr0blem\n\n--- \nProblemType: Bug')1102 title='testbug')
1033 self.assert_(id > 0)1103 id = bug.id
1034 print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,1104 self.assertTrue(id > 0)
1105 sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
10351106
1036 r = apport.Report('Bug')1107 r = apport.Report('Bug')
10371108
@@ -1045,8 +1116,8 @@
10451116
1046 r = self.crashdb.download(id)1117 r = self.crashdb.download(id)
10471118
1048 self.failIf('OneLiner' in r)1119 self.assertFalse('OneLiner' in r)
1049 self.failIf('ShortGoo' in r)1120 self.assertFalse('ShortGoo' in r)
1050 self.assertEqual(r['ProblemType'], 'Bug')1121 self.assertEqual(r['ProblemType'], 'Bug')
1051 self.assertEqual(r['DpkgTerminalLog'], 'one\ntwo\nthree\nfour\nfive\nsix')1122 self.assertEqual(r['DpkgTerminalLog'], 'one\ntwo\nthree\nfour\nfive\nsix')
1052 self.assertEqual(r['VarLogDistupgradeBinGoo'], '\x01' * 1024)1123 self.assertEqual(r['VarLogDistupgradeBinGoo'], '\x01' * 1024)
@@ -1057,11 +1128,14 @@
1057 def test_update_filter(self):1128 def test_update_filter(self):
1058 '''update() with a key filter'''1129 '''update() with a key filter'''
10591130
1060 id = self._fill_bug_form(1131 bug_target = self.crashdb.lp_distro.getSourcePackage(name='bash')
1061 'https://bugs.staging.launchpad.net/%s/+source/bash/+filebug?'1132 bug = self.crashdb.launchpad.bugs.createBug(
1062 'field.title=testbug&field.actions.search=' % self.crashdb.distro)1133 description='test description for test bug',
1063 self.assert_(id > 0)1134 target=bug_target,
1064 print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,1135 title='testbug')
1136 id = bug.id
1137 self.assertTrue(id > 0)
1138 sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
10651139
1066 r = apport.Report('Bug')1140 r = apport.Report('Bug')
10671141
@@ -1076,11 +1150,11 @@
10761150
1077 r = self.crashdb.download(id)1151 r = self.crashdb.download(id)
10781152
1079 self.failIf('OneLiner' in r)1153 self.assertFalse('OneLiner' in r)
1080 self.assertEqual(r['ShortGoo'], 'lineone\nlinetwo')1154 self.assertEqual(r['ShortGoo'], 'lineone\nlinetwo')
1081 self.assertEqual(r['ProblemType'], 'Bug')1155 self.assertEqual(r['ProblemType'], 'Bug')
1082 self.assertEqual(r['DpkgTerminalLog'], 'one\ntwo\nthree\nfour\nfive\nsix')1156 self.assertEqual(r['DpkgTerminalLog'], 'one\ntwo\nthree\nfour\nfive\nsix')
1083 self.failIf('VarLogDistupgradeBinGoo' in r)1157 self.assertFalse('VarLogDistupgradeBinGoo' in r)
10841158
1085 self.assertEqual(self.crashdb.launchpad.bugs[id].tags, [])1159 self.assertEqual(self.crashdb.launchpad.bugs[id].tags, [])
10861160
@@ -1099,14 +1173,14 @@
1099 def test_is_reporter(self):1173 def test_is_reporter(self):
1100 '''is_reporter()'''1174 '''is_reporter()'''
11011175
1102 self.assert_(self.crashdb.is_reporter(segv_report))1176 self.assertTrue(self.crashdb.is_reporter(segv_report))
1103 self.failIf(self.crashdb.is_reporter(1))1177 self.assertFalse(self.crashdb.is_reporter(1))
11041178
1105 def test_can_update(self):1179 def test_can_update(self):
1106 '''can_update()'''1180 '''can_update()'''
11071181
1108 self.assert_(self.crashdb.can_update(segv_report))1182 self.assertTrue(self.crashdb.can_update(segv_report))
1109 self.failIf(self.crashdb.can_update(1))1183 self.assertFalse(self.crashdb.can_update(1))
11101184
1111 def test_duplicates(self):1185 def test_duplicates(self):
1112 '''duplicate handling'''1186 '''duplicate handling'''
@@ -1131,10 +1205,10 @@
1131 # this should have removed attachments; note that Stacktrace is1205 # this should have removed attachments; note that Stacktrace is
1132 # short, and thus inline1206 # short, and thus inline
1133 r = self.crashdb.download(segv_report)1207 r = self.crashdb.download(segv_report)
1134 self.failIf('CoreDump' in r)1208 self.assertFalse('CoreDump' in r)
1135 self.failIf('Dependencies' in r)1209 self.assertFalse('Dependencies' in r)
1136 self.failIf('Disassembly' in r)1210 self.assertFalse('Disassembly' in r)
1137 self.failIf('Registers' in r)1211 self.assertFalse('Registers' in r)
11381212
1139 # now try duplicating to a duplicate bug; this should automatically1213 # now try duplicating to a duplicate bug; this should automatically
1140 # transition to the master bug1214 # transition to the master bug
@@ -1160,11 +1234,11 @@
11601234
1161 # mark_retraced()1235 # mark_retraced()
1162 unretraced_before = self.crashdb.get_unretraced()1236 unretraced_before = self.crashdb.get_unretraced()
1163 self.assert_(segv_report in unretraced_before)1237 self.assertTrue(segv_report in unretraced_before)
1164 self.failIf(python_report in unretraced_before)1238 self.assertFalse(python_report in unretraced_before)
1165 self.crashdb.mark_retraced(segv_report)1239 self.crashdb.mark_retraced(segv_report)
1166 unretraced_after = self.crashdb.get_unretraced()1240 unretraced_after = self.crashdb.get_unretraced()
1167 self.failIf(segv_report in unretraced_after)1241 self.assertFalse(segv_report in unretraced_after)
1168 self.assertEqual(unretraced_before,1242 self.assertEqual(unretraced_before,
1169 unretraced_after.union(set([segv_report])))1243 unretraced_after.union(set([segv_report])))
1170 self.assertEqual(self.crashdb.get_fixed_version(segv_report), None)1244 self.assertEqual(self.crashdb.get_fixed_version(segv_report), None)
@@ -1174,7 +1248,7 @@
1174 self.crashdb.mark_retraced(segv_report)1248 self.crashdb.mark_retraced(segv_report)
1175 self.crashdb.mark_retrace_failed(segv_report)1249 self.crashdb.mark_retrace_failed(segv_report)
1176 unretraced_after = self.crashdb.get_unretraced()1250 unretraced_after = self.crashdb.get_unretraced()
1177 self.failIf(segv_report in unretraced_after)1251 self.assertFalse(segv_report in unretraced_after)
1178 self.assertEqual(unretraced_before,1252 self.assertEqual(unretraced_before,
1179 unretraced_after.union(set([segv_report])))1253 unretraced_after.union(set([segv_report])))
1180 self.assertEqual(self.crashdb.get_fixed_version(segv_report), None)1254 self.assertEqual(self.crashdb.get_fixed_version(segv_report), None)
@@ -1184,7 +1258,7 @@
1184 self.crashdb.mark_retraced(segv_report)1258 self.crashdb.mark_retraced(segv_report)
1185 self.crashdb.mark_retrace_failed(segv_report, "I don't like you")1259 self.crashdb.mark_retrace_failed(segv_report, "I don't like you")
1186 unretraced_after = self.crashdb.get_unretraced()1260 unretraced_after = self.crashdb.get_unretraced()
1187 self.failIf(segv_report in unretraced_after)1261 self.assertFalse(segv_report in unretraced_after)
1188 self.assertEqual(unretraced_before,1262 self.assertEqual(unretraced_before,
1189 unretraced_after.union(set([segv_report])))1263 unretraced_after.union(set([segv_report])))
1190 self.assertEqual(self.crashdb.get_fixed_version(segv_report),1264 self.assertEqual(self.crashdb.get_fixed_version(segv_report),
@@ -1194,11 +1268,11 @@
1194 '''processing status markings for interpreter crashes'''1268 '''processing status markings for interpreter crashes'''
11951269
1196 unchecked_before = self.crashdb.get_dup_unchecked()1270 unchecked_before = self.crashdb.get_dup_unchecked()
1197 self.assert_(python_report in unchecked_before)1271 self.assertTrue(python_report in unchecked_before)
1198 self.failIf(segv_report in unchecked_before)1272 self.assertFalse(segv_report in unchecked_before)
1199 self.crashdb._mark_dup_checked(python_report, self.ref_report)1273 self.crashdb._mark_dup_checked(python_report, self.ref_report)
1200 unchecked_after = self.crashdb.get_dup_unchecked()1274 unchecked_after = self.crashdb.get_dup_unchecked()
1201 self.failIf(python_report in unchecked_after)1275 self.assertFalse(python_report in unchecked_after)
1202 self.assertEqual(unchecked_before,1276 self.assertEqual(unchecked_before,
1203 unchecked_after.union(set([python_report])))1277 unchecked_after.union(set([python_report])))
1204 self.assertEqual(self.crashdb.get_fixed_version(python_report),1278 self.assertEqual(self.crashdb.get_fixed_version(python_report),
@@ -1211,7 +1285,7 @@
1211 invalidated by marking it as a duplicate.1285 invalidated by marking it as a duplicate.
1212 '''1286 '''
1213 id = self._file_segv_report()1287 id = self._file_segv_report()
1214 print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,1288 sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
12151289
1216 r = self.crashdb.download(id)1290 r = self.crashdb.download(id)
12171291
@@ -1224,7 +1298,7 @@
1224 self.crashdb.update_traces(id, r, 'good retrace!')1298 self.crashdb.update_traces(id, r, 'good retrace!')
12251299
1226 r = self.crashdb.download(id)1300 r = self.crashdb.download(id)
1227 self.failIf('CoreDump' in r)1301 self.assertFalse('CoreDump' in r)
12281302
1229 def test_get_fixed_version(self):1303 def test_get_fixed_version(self):
1230 '''get_fixed_version() for fixed bugs1304 '''get_fixed_version() for fixed bugs
@@ -1235,7 +1309,7 @@
1235 self._mark_report_fixed(segv_report)1309 self._mark_report_fixed(segv_report)
1236 fixed_ver = self.crashdb.get_fixed_version(segv_report)1310 fixed_ver = self.crashdb.get_fixed_version(segv_report)
1237 self.assertNotEqual(fixed_ver, None)1311 self.assertNotEqual(fixed_ver, None)
1238 self.assert_(fixed_ver[0].isdigit())1312 self.assertTrue(fixed_ver[0].isdigit())
1239 self._mark_report_new(segv_report)1313 self._mark_report_new(segv_report)
1240 self.assertEqual(self.crashdb.get_fixed_version(segv_report), None)1314 self.assertEqual(self.crashdb.get_fixed_version(segv_report), None)
12411315
@@ -1247,119 +1321,99 @@
1247 def _get_instance(klass):1321 def _get_instance(klass):
1248 '''Create a CrashDB instance'''1322 '''Create a CrashDB instance'''
12491323
1250 return CrashDatabase(os.environ.get('LP_CREDENTIALS'), '', 1324 launchpad_instance = os.environ.get('APPORT_LAUNCHPAD_INSTANCE') or 'staging'
1251 {'distro': 'ubuntu', 'staging': True})1325
12521326 return CrashDatabase(os.environ.get('LP_CREDENTIALS'), '',
1253 def _fill_bug_form(self, url, comment=None):1327 {'distro': 'ubuntu',
1254 '''Fill form for a distro bug and commit the bug.1328 'launchpad_instance': launchpad_instance})
12551329
1256 Return the report ID.1330 @classmethod
1257 '''1331 def _get_bug_target(klass, db, report):
1258 cj = cookielib.MozillaCookieJar()1332 '''Return the bug_target for this report.'''
1259 cj.load(os.path.expanduser('~/.lpcookie.txt'))1333
1260 opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))1334 project = db.options.get('project')
1261 opener.addheaders = [('Referer', url)]1335 if report.has_key('SourcePackage'):
12621336 return db.lp_distro.getSourcePackage(name=report['SourcePackage'])
1263 re_pkg = re.compile('\+source/([\w]+)/')1337 elif project:
1264 re_title = re.compile('<input.*id="field.title".*value="([^"]+)"')1338 return db.launchpad.projects[project]
1265 re_tags = re.compile('<input.*id="field.tags".*value="([^"]*)"')1339 else:
12661340 return self.lp_distro
1267 # parse default field values from reporting page1341
1268 while True:1342 def _get_librarian_hostname(self):
1269 res = opener.open(url)1343 '''Return the librarian hostname according to the LP hostname used.'''
1270 try:1344
1271 self.assertEqual(res.getcode(), 200)1345 hostname = self.crashdb.get_hostname()
1272 except AttributeError:1346 if 'staging' in hostname:
1273 pass # getcode() is new in Python 2.61347 return 'staging.launchpadlibrarian.net'
1274 content = res.read()1348 else:
12751349 return 'launchpad.dev:58080'
1276 if 'Please wait while bug data is processed' in content:1350
1277 print '.',1351 def _file_bug(self, bug_target, report, handle, comment=None):
1278 sys.stdout.flush()1352 '''File a bug report.'''
1279 time.sleep(5)1353
1280 continue1354 bug_title = report.get('Title', report.standard_title())
12811355
1282 break1356 blob_info = self.crashdb.launchpad.temporary_blobs.fetch(
12831357 token=handle)
1284 m_pkg = re_pkg.search(url)1358 # XXX 2010-08-03 matsubara bug=612990:
1285 m_title = re_title.search(content)1359 # Can't fetch the blob directly, so let's load it from the
1286 m_tags = re_tags.search(content)1360 # representation.
12871361 blob = self.crashdb.launchpad.load(blob_info['self_link'])
1288 # strip off GET arguments from URL1362 #XXX Need to find a way to trigger the job that process the blob
1289 url = url.split('?')[0]1363 # rather polling like this. This makes the test suite take forever
12901364 # to run.
1291 # create request to file bug1365 while not blob.hasBeenProcessed():
1292 args = {1366 time.sleep(1)
1293 'packagename_option': 'choose',1367
1294 'field.packagename': m_pkg.group(1),1368 # processed_blob contains info about privacy, additional comments
1295 'field.title': m_title.group(1),1369 # and attachments.
1296 'field.tags': m_tags.group(1),1370 processed_blob = blob.getProcessedData()
1297 'field.comment': comment or 'ZOMG!',1371
1298 'field.actions.submit_bug': '1',1372 bug = self.crashdb.launchpad.bugs.createBug(
1299 }1373 description=processed_blob['extra_description'],
13001374 private=processed_blob['private'],
1301 res = opener.open(url, data=urllib.urlencode(args))1375 tags=processed_blob['initial_tags'],
1302 try:1376 target=bug_target,
1303 self.assertEqual(res.getcode(), 200)1377 title=bug_title)
1304 except AttributeError:1378
1305 pass # getcode() is new in Python 2.61379 for comment in processed_blob['comments']:
1306 self.assert_('+source/%s/+bug/' % m_pkg.group(1) in res.geturl())1380 bug.newMessage(content=comment)
1307 id = res.geturl().split('/')[-1]1381
1308 return int(id)1382 # Ideally, one would be able to retrieve the attachment content
13091383 # from the ProblemReport object or from the processed_blob.
1310 def _fill_bug_form_project(self, url):1384 # Unfortunately the processed_blob only give us the Launchpad
1311 '''Fill form for a project bug and commit the bug.1385 # librarian file_alias_id, so that's why we need to
13121386 # download it again and upload to the bug report. It'd be even
1313 Return the report ID.1387 # better if addAttachment could work like linkAttachment, the LP
1314 '''1388 # api used in the +filebug web UI, but there are security concerns
1315 cj = cookielib.MozillaCookieJar()1389 # about the way linkAttachment works.
1316 cj.load(os.path.expanduser('~/.lpcookie.txt'))1390 librarian_url = 'http://%s' % self._get_librarian_hostname()
1317 opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))1391 for attachment in processed_blob['attachments']:
1318 opener.addheaders = [('Referer', url)]1392 filename = description = attachment['description']
13191393 # Download the attachment data.
1320 m = re.search('launchpad.net/([^/]+)/\+filebug', url)1394 data = urllib.urlopen(urllib.basejoin(librarian_url,
1321 assert m1395 str(attachment['file_alias_id']) + '/' + filename)).read()
1322 project = m.group(1)1396 # Add the attachment to the newly created bug report.
13231397 bug.addAttachment(
1324 re_title = re.compile('<input.*id="field.title".*value="([^"]+)"')1398 comment=filename,
1325 re_tags = re.compile('<input.*id="field.tags".*value="([^"]+)"')1399 data=data,
13261400 filename=filename,
1327 # parse default field values from reporting page1401 description=description)
1328 while True:1402
1329 res = opener.open(url)1403 for subscriber in processed_blob['subscribers']:
1330 try:1404 sub = self.crashdb.launchpad.people[subscriber]
1331 self.assertEqual(res.getcode(), 200)1405 if sub:
1332 except AttributeError:1406 bug.subscribe(person=sub)
1333 pass # getcode() is new in Python 2.61407
1334 content = res.read()1408 for submission_key in processed_blob['hwdb_submission_keys']:
13351409 # XXX 2010-08-04 matsubara bug=628889:
1336 if 'Please wait while bug data is processed' in content:1410 # Can't fetch the submission directly, so let's load it
1337 print '.',1411 # from the representation.
1338 sys.stdout.flush()1412 submission = self.crashdb.launchpad.load(
1339 time.sleep(5)1413 'https://api.%s/beta/+hwdb/+submission/%s'
1340 continue1414 % (self.crashdb.get_hostname(), submission_key))
13411415 bug.linkHWSubmission(submission=submission)
1342 break1416 return int(bug.id)
1343
1344 m_title = re_title.search(content)
1345 m_tags = re_tags.search(content)
1346
1347 # strip off GET arguments from URL
1348 url = url.split('?')[0]
1349
1350 # create request to file bug
1351 args = {
1352 'field.title': m_title.group(1),
1353 'field.tags': m_tags.group(1),
1354 'field.comment': 'ZOMG!',
1355 'field.actions.submit_bug': '1',
1356 }
1357
1358 res = opener.open(url, data=urllib.urlencode(args))
1359 self.assertEqual(res.getcode(), 200)
1360 self.assert_(('launchpad.net/%s/+bug' % project) in res.geturl())
1361 id = res.geturl().split('/')[-1]
1362 return int(id)
13631417
1364 def _mark_needs_retrace(self, id):1418 def _mark_needs_retrace(self, id):
1365 '''Mark a report ID as needing retrace.'''1419 '''Mark a report ID as needing retrace.'''
@@ -1401,15 +1455,17 @@
1401 '''Verify that report ID is marked as regression.'''1455 '''Verify that report ID is marked as regression.'''
14021456
1403 bug = self.crashdb.launchpad.bugs[id]1457 bug = self.crashdb.launchpad.bugs[id]
1404 self.assert_('regression-retracer' in bug.tags)1458 self.assertTrue('regression-retracer' in bug.tags)
14051459
1406 def test_project(self):1460 def test_project(self):
1407 '''reporting crashes against a project instead of a distro'''1461 '''reporting crashes against a project instead of a distro'''
14081462
1463 launchpad_instance = os.environ.get('APPORT_LAUNCHPAD_INSTANCE') or 'staging'
1409 # crash database for langpack-o-matic project (this does not have1464 # crash database for langpack-o-matic project (this does not have
1410 # packages in any distro)1465 # packages in any distro)
1411 crashdb = CrashDatabase(os.environ.get('LP_CREDENTIALS'), '', 1466 crashdb = CrashDatabase(os.environ.get('LP_CREDENTIALS'), '',
1412 {'project': 'langpack-o-matic', 'staging': True})1467 {'project': 'langpack-o-matic',
1468 'launchpad_instance': launchpad_instance})
1413 self.assertEqual(crashdb.distro, None)1469 self.assertEqual(crashdb.distro, None)
14141470
1415 # create Python crash report1471 # create Python crash report
@@ -1417,21 +1473,22 @@
1417 r['ExecutablePath'] = '/bin/foo'1473 r['ExecutablePath'] = '/bin/foo'
1418 r['Traceback'] = '''Traceback (most recent call last):1474 r['Traceback'] = '''Traceback (most recent call last):
1419 File "/bin/foo", line 67, in fuzz1475 File "/bin/foo", line 67, in fuzz
1420 print weird1476 print(weird)
1421NameError: global name 'weird' is not defined'''1477NameError: global name 'weird' is not defined'''
1422 r.add_os_info()1478 r.add_os_info()
1423 r.add_user_info()1479 r.add_user_info()
1424 self.assertEqual(r.standard_title(), 'foo crashed with NameError in fuzz()')1480 self.assertEqual(r.standard_title(),
1481 "foo crashed with NameError in fuzz(): global name 'weird' is not defined")
14251482
1426 # file it1483 # file it
1427 handle = crashdb.upload(r)1484 handle = crashdb.upload(r)
1428 self.assert_(handle)1485 self.assertTrue(handle)
1429 url = crashdb.get_comment_url(r, handle)1486 bug_target = self._get_bug_target(crashdb, r)
1430 self.assert_('launchpad.net/langpack-o-matic/+filebug' in url)1487 self.assertEqual(bug_target.name, 'langpack-o-matic')
14311488
1432 id = self._fill_bug_form_project(url)1489 id = self._file_bug(bug_target, r, handle)
1433 self.assert_(id > 0)1490 self.assertTrue(id > 0)
1434 print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,1491 sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
14351492
1436 # update1493 # update
1437 r = crashdb.download(id)1494 r = crashdb.download(id)
@@ -1454,21 +1511,22 @@
1454 '''download() of uncommon description formats'''1511 '''download() of uncommon description formats'''
14551512
1456 # only ProblemType/Architecture/DistroRelease in description1513 # only ProblemType/Architecture/DistroRelease in description
1457 r = self.crashdb.download(269539)1514 r = self.crashdb.download(self.uncommon_description_bug.id)
1458 self.assertEqual(r['ProblemType'], 'Package')1515 self.assertEqual(r['ProblemType'], 'Package')
1459 self.assertEqual(r['Architecture'], 'amd64')1516 self.assertEqual(r['Architecture'], 'amd64')
1460 self.assert_(r['DistroRelease'].startswith('Ubuntu '))1517 self.assertTrue(r['DistroRelease'].startswith('Ubuntu '))
1461 self.assert_('DpkgTerminalLog' in r)
14621518
1463 def test_escalation(self):1519 def test_escalation(self):
1464 '''Escalating bugs with more than 10 duplicates'''1520 '''Escalating bugs with more than 10 duplicates'''
14651521
1466 assert segv_report, 'you need to run test_1_report_segv() first'1522 assert segv_report, 'you need to run test_1_report_segv() first'
14671523
1468 db = CrashDatabase(os.environ.get('LP_CREDENTIALS'), '', 1524 launchpad_instance = os.environ.get('APPORT_LAUNCHPAD_INSTANCE') or 'staging'
1469 {'distro': 'ubuntu', 'staging': True,1525 db = CrashDatabase(os.environ.get('LP_CREDENTIALS'), '',
1470 'escalation_tag': 'omgkittens',1526 {'distro': 'ubuntu',
1471 'escalation_subscription': 'apport-hackers'})1527 'launchpad_instance': launchpad_instance,
1528 'escalation_tag': 'omgkittens',
1529 'escalation_subscription': 'apport-hackers'})
14721530
1473 count = 01531 count = 0
1474 p = db.launchpad.people[db.options['escalation_subscription']].self_link1532 p = db.launchpad.people[db.options['escalation_subscription']].self_link
@@ -1476,38 +1534,36 @@
1476 try:1534 try:
1477 for b in range(first_dup, first_dup+13):1535 for b in range(first_dup, first_dup+13):
1478 count += 11536 count += 1
1479 print b,1537 sys.stderr.write('%i ' % b)
1480 sys.stdout.flush()
1481 db.close_duplicate(b, segv_report)1538 db.close_duplicate(b, segv_report)
1482 b = db.launchpad.bugs[segv_report]1539 b = db.launchpad.bugs[segv_report]
1483 has_escalation_tag = db.options['escalation_tag'] in b.tags1540 has_escalation_tag = db.options['escalation_tag'] in b.tags
1484 has_escalation_subsciption = any([s.person_link == p for s in b.subscriptions])1541 has_escalation_subscription = any([s.person_link == p for s in b.subscriptions])
1485 if count <= 10:1542 if count <= 10:
1486 self.failIf(has_escalation_tag)1543 self.assertFalse(has_escalation_tag)
1487 self.failIf(has_escalation_subsciption)1544 self.assertFalse(has_escalation_subscription)
1488 else:1545 else:
1489 self.assert_(has_escalation_tag)1546 self.assertTrue(has_escalation_tag)
1490 self.assert_(has_escalation_subsciption)1547 self.assertTrue(has_escalation_subscription)
1491 finally:1548 finally:
1492 for b in range(first_dup, first_dup+count):1549 for b in range(first_dup, first_dup+count):
1493 print 'R%i' % b,1550 sys.stderr.write('R%i ' % b)
1494 sys.stdout.flush()
1495 db.close_duplicate(b, None)1551 db.close_duplicate(b, None)
1496 print1552 sys.stderr.write('\n')
14971553
1498 def test_marking_python_task_mangle(self):1554 def test_marking_python_task_mangle(self):
1499 '''source package task fixup for marking interpreter crashes'''1555 '''source package task fixup for marking interpreter crashes'''
15001556
1501 self._mark_needs_dupcheck(python_report)1557 self._mark_needs_dupcheck(python_report)
1502 unchecked_before = self.crashdb.get_dup_unchecked()1558 unchecked_before = self.crashdb.get_dup_unchecked()
1503 self.assert_(python_report in unchecked_before)1559 self.assertTrue(python_report in unchecked_before)
15041560
1505 # add an upstream task, and remove the package name from the1561 # add an upstream task, and remove the package name from the
1506 # package task; _mark_dup_checked is supposed to restore the1562 # package task; _mark_dup_checked is supposed to restore the
1507 # package name1563 # package name
1508 b = self.crashdb.launchpad.bugs[python_report]1564 b = self.crashdb.launchpad.bugs[python_report]
1509 t = b.bug_tasks[0]1565 t = b.bug_tasks[0]
1510 t.target = self.crashdb.launchpad.distributions['ubuntu'].getSourcePackage(name='pmount')1566 t.target = self.crashdb.launchpad.distributions['ubuntu'].getSourcePackage(name='bash')
1511 t.status = 'Invalid'1567 t.status = 'Invalid'
1512 t.lp_save()1568 t.lp_save()
1513 b.addTask(target=self.crashdb.launchpad.projects['coreutils'])1569 b.addTask(target=self.crashdb.launchpad.projects['coreutils'])
@@ -1516,7 +1572,7 @@
1516 self.crashdb._mark_dup_checked(python_report, self.ref_report)1572 self.crashdb._mark_dup_checked(python_report, self.ref_report)
15171573
1518 unchecked_after = self.crashdb.get_dup_unchecked()1574 unchecked_after = self.crashdb.get_dup_unchecked()
1519 self.failIf(python_report in unchecked_after)1575 self.assertFalse(python_report in unchecked_after)
1520 self.assertEqual(unchecked_before,1576 self.assertEqual(unchecked_before,
1521 unchecked_after.union(set([python_report])))1577 unchecked_after.union(set([python_report])))
15221578
@@ -1526,12 +1582,12 @@
1526 self.assertEqual(b.bug_tasks[0].status, 'New')1582 self.assertEqual(b.bug_tasks[0].status, 'New')
15271583
1528 # package-less distro task should have package name fixed1584 # package-less distro task should have package name fixed
1529 self.assertEqual(b.bug_tasks[1].bug_target_name, 'coreutils (Ubuntu)')1585 self.assertEqual(b.bug_tasks[2].bug_target_name, 'coreutils (Ubuntu)')
1530 self.assertEqual(b.bug_tasks[1].status, 'New')1586 self.assertEqual(b.bug_tasks[2].status, 'New')
15311587
1532 # invalid pmount task should be unmodified1588 # invalid bash task should be unmodified
1533 self.assertEqual(b.bug_tasks[2].bug_target_name, 'pmount (Ubuntu)')1589 self.assertEqual(b.bug_tasks[1].bug_target_name, 'bash (Ubuntu)')
1534 self.assertEqual(b.bug_tasks[2].status, 'Invalid')1590 self.assertEqual(b.bug_tasks[1].status, 'Invalid')
15351591
1536 # the invalid task should not confuse get_fixed_version()1592 # the invalid task should not confuse get_fixed_version()
1537 self.assertEqual(self.crashdb.get_fixed_version(python_report),1593 self.assertEqual(self.crashdb.get_fixed_version(python_report),
15381594
=== modified file 'apport/crashdb_impl/memory.py'
--- apport/crashdb_impl/memory.py 2010-06-28 14:17:38 +0000
+++ apport/crashdb_impl/memory.py 2011-04-20 23:32:24 +0000
@@ -21,13 +21,13 @@
21 21
22 This is mainly useful for testing and debugging.'''22 This is mainly useful for testing and debugging.'''
2323
24 def __init__(self, auth_file, bugpattern_baseurl, options):24 def __init__(self, auth_file, bugpattern_url, options):
25 '''Initialize crash database connection.25 '''Initialize crash database connection.
26 26
27 This class does not support bug patterns and authentication.'''27 This class does not support bug patterns and authentication.'''
2828
29 apport.crashdb.CrashDatabase.__init__(self, auth_file,29 apport.crashdb.CrashDatabase.__init__(self, auth_file,
30 bugpattern_baseurl, options)30 bugpattern_url, options)
3131
32 self.reports = [] # list of dictionaries with keys: report, fixed_version, dup_of, comment32 self.reports = [] # list of dictionaries with keys: report, fixed_version, dup_of, comment
33 self.unretraced = set()33 self.unretraced = set()
@@ -345,12 +345,10 @@
345databases = {345databases = {
346 'testsuite': { 346 'testsuite': {
347 'impl': 'memory',347 'impl': 'memory',
348 'bug_pattern_base': None,
349 'dyn_option': get_dyn(),348 'dyn_option': get_dyn(),
350 },349 },
351 get_dyn_name(): {350 get_dyn_name(): {
352 'impl': 'memory',351 'impl': 'memory',
353 'bug_pattern_base': None,
354 'whoami': 'dynname',352 'whoami': 'dynname',
355 }353 }
356}354}
@@ -360,7 +358,7 @@
360 db = apport.crashdb.get_crashdb(None, None, crashdb_conf.name)358 db = apport.crashdb.get_crashdb(None, None, crashdb_conf.name)
361 self.assertEqual(db.options['dyn_option'], '4')359 self.assertEqual(db.options['dyn_option'], '4')
362 db = apport.crashdb.get_crashdb(None, 'on_thefly', crashdb_conf.name)360 db = apport.crashdb.get_crashdb(None, 'on_thefly', crashdb_conf.name)
363 self.failIf('dyn_opion' in db.options)361 self.assertFalse('dyn_opion' in db.options)
364 self.assertEqual(db.options['whoami'], 'dynname')362 self.assertEqual(db.options['whoami'], 'dynname')
365363
366 #364 #
@@ -428,7 +426,7 @@
428 self.assertEqual(self.crashes.reports[1]['comment'], 'muhaha')426 self.assertEqual(self.crashes.reports[1]['comment'], 'muhaha')
429 self.assertEqual(self.crashes.download(1)['Package'], 'libfoo1 1.2-4')427 self.assertEqual(self.crashes.download(1)['Package'], 'libfoo1 1.2-4')
430 self.assertEqual(self.crashes.download(1)['StacktraceTop'], 'Fresh!')428 self.assertEqual(self.crashes.download(1)['StacktraceTop'], 'Fresh!')
431 self.failIf(self.crashes.download(1).has_key('FooBar'))429 self.assertFalse(self.crashes.download(1).has_key('FooBar'))
432430
433 self.assertRaises(IndexError, self.crashes.update_traces, 5, None)431 self.assertRaises(IndexError, self.crashes.update_traces, 5, None)
434432
@@ -586,6 +584,33 @@
586 self.assertEqual(self.crashes.check_duplicate(5), None)584 self.assertEqual(self.crashes.check_duplicate(5), None)
587 self.assertEqual(self.crashes.check_duplicate(6), (5, None))585 self.assertEqual(self.crashes.check_duplicate(6), (5, None))
588586
587 def test_check_duplicate_custom_signature(self):
588 '''check_duplicate() with custom DuplicateSignature: field'''
589
590 r = apport.Report()
591 r['SourcePackage'] = 'bash'
592 r['Package'] = 'bash 5'
593 r['DuplicateSignature'] = 'Code42Blue'
594 self.assertEqual(self.crashes.get_comment_url(r, self.crashes.upload(r)),
595 'http://bash.bugs.example.com/5')
596
597 self.crashes.init_duplicate_db(':memory:')
598 self.assertEqual(self.crashes.check_duplicate(5), None)
599
600 self.assertEqual(self.crashes._duplicate_db_dump(), {'Code42Blue': (5, None)})
601
602 # this one has a standard crash_signature
603 self.assertEqual(self.crashes.check_duplicate(0), None)
604 # ... but DuplicateSignature wins
605 self.crashes.download(0)['DuplicateSignature'] = 'Code42Blue'
606 self.assertEqual(self.crashes.check_duplicate(0), (5, None))
607
608 self.crashes.download(1)['DuplicateSignature'] = 'CodeRed'
609 self.assertEqual(self.crashes.check_duplicate(1), None)
610 self.assertEqual(self.crashes._duplicate_db_dump(),
611 {'Code42Blue': (5, None), 'CodeRed': (1, None),
612 self.crashes.download(0).crash_signature(): (0, None)})
613
589 def test_check_duplicate_report_arg(self):614 def test_check_duplicate_report_arg(self):
590 '''check_duplicate() with explicitly passing report'''615 '''check_duplicate() with explicitly passing report'''
591616
@@ -639,7 +664,7 @@
639 if pid == 0:664 if pid == 0:
640 try:665 try:
641 self.crashes.duplicate_db_consolidate()666 self.crashes.duplicate_db_consolidate()
642 except Exception, e:667 except Exception as e:
643 if 'database is locked' in str(e):668 if 'database is locked' in str(e):
644 os._exit(42)669 os._exit(42)
645 else:670 else:
@@ -648,7 +673,7 @@
648673
649 try:674 try:
650 self.crashes.duplicate_db_consolidate()675 self.crashes.duplicate_db_consolidate()
651 except Exception, e:676 except Exception as e:
652 if 'database is locked' in str(e):677 if 'database is locked' in str(e):
653 locked_exceptions += 1678 locked_exceptions += 1
654 else:679 else:
@@ -656,7 +681,7 @@
656681
657 # wait on child, examine status682 # wait on child, examine status
658 status = os.wait()[1]683 status = os.wait()[1]
659 self.assert_(os.WIFEXITED(status))684 self.assertTrue(os.WIFEXITED(status))
660 status = os.WEXITSTATUS(status)685 status = os.WEXITSTATUS(status)
661 if status == 42:686 if status == 42:
662 locked_exceptions += 1687 locked_exceptions += 1
@@ -702,19 +727,19 @@
702 self.crashes.init_duplicate_db(':memory:')727 self.crashes.init_duplicate_db(':memory:')
703728
704 # a fresh and empty db does not need consolidation729 # a fresh and empty db does not need consolidation
705 self.failIf(self.crashes.duplicate_db_needs_consolidation())730 self.assertFalse(self.crashes.duplicate_db_needs_consolidation())
706731
707 time.sleep(1.1)732 time.sleep(1.1)
708 # for an one-day interval we do not need consolidation733 # for an one-day interval we do not need consolidation
709 self.failIf(self.crashes.duplicate_db_needs_consolidation())734 self.assertFalse(self.crashes.duplicate_db_needs_consolidation())
710 # neither for a ten second one (check timezone offset errors)735 # neither for a ten second one (check timezone offset errors)
711 self.failIf(self.crashes.duplicate_db_needs_consolidation(10))736 self.assertFalse(self.crashes.duplicate_db_needs_consolidation(10))
712 # but for an one second interval737 # but for an one second interval
713 self.assert_(self.crashes.duplicate_db_needs_consolidation(1))738 self.assertTrue(self.crashes.duplicate_db_needs_consolidation(1))
714739
715 self.crashes.duplicate_db_consolidate()740 self.crashes.duplicate_db_consolidate()
716741
717 self.failIf(self.crashes.duplicate_db_needs_consolidation(1))742 self.assertFalse(self.crashes.duplicate_db_needs_consolidation(1))
718743
719 def test_change_master_id(self):744 def test_change_master_id(self):
720 '''duplicate_db_change_master_id()'''745 '''duplicate_db_change_master_id()'''
@@ -762,7 +787,7 @@
762 self.assertEqual(self.crashes._duplicate_db_dump(), 787 self.assertEqual(self.crashes._duplicate_db_dump(),
763 {self.crashes.download(0).crash_signature(): (0, '42')})788 {self.crashes.download(0).crash_signature(): (0, '42')})
764789
765 self.failIf(self.crashes.duplicate_db_needs_consolidation())790 self.assertFalse(self.crashes.duplicate_db_needs_consolidation())
766 del self.crashes791 del self.crashes
767792
768 # damage file793 # damage file
@@ -771,7 +796,7 @@
771 f.close()796 f.close()
772797
773 self.crashes = CrashDatabase(None, None, {})798 self.crashes = CrashDatabase(None, None, {})
774 self.assertRaises(SystemError, self.crashes.init_duplicate_db, db)799 self.assertRaises(StandardError, self.crashes.init_duplicate_db, db)
775800
776 finally:801 finally:
777 os.unlink(db)802 os.unlink(db)
778803
=== modified file 'apport/crashdb_impl/multipartpost_handler.py'
--- apport/crashdb_impl/multipartpost_handler.py 2009-04-11 17:11:06 +0000
+++ apport/crashdb_impl/multipartpost_handler.py 2011-04-20 23:32:24 +0000
@@ -67,7 +67,7 @@
67 v_vars.append((key, value))67 v_vars.append((key, value))
68 except TypeError:68 except TypeError:
69 systype, value, traceback = sys.exc_info()69 systype, value, traceback = sys.exc_info()
70 raise TypeError, "not a valid non-string sequence or mapping object", traceback70 raise TypeError("not a valid non-string sequence or mapping object").with_traceback(traceback)
7171
72 if len(v_files) == 0:72 if len(v_files) == 0:
73 data = urllib.urlencode(v_vars, doseq)73 data = urllib.urlencode(v_vars, doseq)
7474
=== modified file 'apport/fileutils.py'
--- apport/fileutils.py 2009-12-23 11:01:21 +0000
+++ apport/fileutils.py 2011-04-20 23:32:24 +0000
@@ -9,10 +9,17 @@
9# option) any later version. See http://www.gnu.org/copyleft/gpl.html for9# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
10# the full text of the license.10# the full text of the license.
1111
12import os, glob, subprocess, os.path, ConfigParser12import os, glob, subprocess, os.path
13
14try:
15 from configparser import ConfigParser, NoOptionError, NoSectionError
16except ImportError:
17 # Python 2
18 from ConfigParser import ConfigParser, NoOptionError, NoSectionError
19
13from problem_report import ProblemReport20from problem_report import ProblemReport
1421
15from packaging_impl import impl as packaging22from apport.packaging_impl import impl as packaging
1623
17report_dir = os.environ.get('APPORT_REPORT_DIR', '/var/crash')24report_dir = os.environ.get('APPORT_REPORT_DIR', '/var/crash')
1825
@@ -196,7 +203,7 @@
196 elif report.has_key('Package'):203 elif report.has_key('Package'):
197 subject = report['Package'].split(None, 1)[0]204 subject = report['Package'].split(None, 1)[0]
198 else:205 else:
199 raise ValueError, 'report has neither ExecutablePath nor Package attribute'206 raise ValueError('report has neither ExecutablePath nor Package attribute')
200207
201 if not uid:208 if not uid:
202 uid = os.getuid()209 uid = os.getuid()
@@ -234,7 +241,7 @@
234 interpreted as a boolean.241 interpreted as a boolean.
235 '''242 '''
236 if not get_config.config:243 if not get_config.config:
237 get_config.config = ConfigParser.ConfigParser()244 get_config.config = ConfigParser()
238 get_config.config.read(os.path.expanduser(_config_file))245 get_config.config.read(os.path.expanduser(_config_file))
239246
240 try:247 try:
@@ -242,7 +249,7 @@
242 return get_config.config.getboolean(section, setting)249 return get_config.config.getboolean(section, setting)
243 else:250 else:
244 return get_config.config.get(section, setting)251 return get_config.config.get(section, setting)
245 except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):252 except (NoOptionError, NoSectionError):
246 return default253 return default
247254
248get_config.config = None255get_config.config = None
@@ -252,7 +259,10 @@
252#259#
253260
254import unittest, tempfile, os, shutil, sys, time261import unittest, tempfile, os, shutil, sys, time
255from cStringIO import StringIO262try:
263 from cStringIO import StringIO
264except ImportError:
265 from io import StringIO
256266
257class _T(unittest.TestCase):267class _T(unittest.TestCase):
258 def setUp(self):268 def setUp(self):
@@ -278,8 +288,8 @@
278288
279 open(r1, 'w').write('report 1')289 open(r1, 'w').write('report 1')
280 open(r2, 'w').write('report 2')290 open(r2, 'w').write('report 2')
281 os.chmod(r1, 0600)291 os.chmod(r1, 0o600)
282 os.chmod(r2, 0600)292 os.chmod(r2, 0o600)
283 if create_inaccessible:293 if create_inaccessible:
284 ri = os.path.join(report_dir, 'inaccessible.crash')294 ri = os.path.join(report_dir, 'inaccessible.crash')
285 open(ri, 'w').write('inaccessible')295 open(ri, 'w').write('inaccessible')
@@ -321,8 +331,8 @@
321 if onedesktop:331 if onedesktop:
322 d = find_package_desktopfile(onedesktop)332 d = find_package_desktopfile(onedesktop)
323 self.assertNotEqual(d, None, 'one-desktop package %s' % onedesktop)333 self.assertNotEqual(d, None, 'one-desktop package %s' % onedesktop)
324 self.assert_(os.path.exists(d))334 self.assertTrue(os.path.exists(d))
325 self.assert_(d.endswith('.desktop'))335 self.assertTrue(d.endswith('.desktop'))
326336
327 def test_likely_packaged(self):337 def test_likely_packaged(self):
328 '''likely_packaged().'''338 '''likely_packaged().'''
@@ -443,9 +453,9 @@
443 self.assertRaises(ValueError, make_report_path, pr)453 self.assertRaises(ValueError, make_report_path, pr)
444454
445 pr['Package'] = 'bash 1'455 pr['Package'] = 'bash 1'
446 self.assert_(make_report_path(pr).startswith('%s/bash' % report_dir))456 self.assertTrue(make_report_path(pr).startswith('%s/bash' % report_dir))
447 pr['ExecutablePath'] = '/bin/bash';457 pr['ExecutablePath'] = '/bin/bash';
448 self.assert_(make_report_path(pr).startswith('%s/_bin_bash' % report_dir))458 self.assertTrue(make_report_path(pr).startswith('%s/_bin_bash' % report_dir))
449459
450 def test_check_files_md5(self):460 def test_check_files_md5(self):
451 '''check_files_md5().'''461 '''check_files_md5().'''
452462
=== modified file 'apport/hookutils.py'
--- apport/hookutils.py 2010-06-24 13:37:14 +0000
+++ apport/hookutils.py 2011-04-20 23:32:24 +0000
@@ -1,6 +1,6 @@
1'''Convenience functions for use in package hooks.'''1'''Convenience functions for use in package hooks.'''
22
3# Copyright (C) 2008 - 2010 Canonical Ltd.3# Copyright (C) 2008 - 2011 Canonical Ltd.
4# Authors: 4# Authors:
5# Matt Zimmerman <mdz@canonical.com>5# Matt Zimmerman <mdz@canonical.com>
6# Brian Murray <brian@ubuntu.com>6# Brian Murray <brian@ubuntu.com>
@@ -21,6 +21,8 @@
21import string21import string
22import stat22import stat
23import base6423import base64
24import tempfile
25import shutil
2426
25import xml.dom, xml.dom.minidom27import xml.dom, xml.dom.minidom
2628
@@ -51,7 +53,7 @@
51 '''53 '''
52 try:54 try:
53 return open(path).read().strip()55 return open(path).read().strip()
54 except Exception, e:56 except Exception as e:
55 return 'Error: ' + str(e)57 return 'Error: ' + str(e)
5658
57def attach_file(report, path, key=None):59def attach_file(report, path, key=None):
@@ -63,6 +65,9 @@
63 if not key:65 if not key:
64 key = path_to_key(path)66 key = path_to_key(path)
6567
68 # Do not clobber existing keys
69 while report.has_key(key):
70 key += "_"
66 report[key] = read_file(path)71 report[key] = read_file(path)
6772
68def attach_conffiles(report, package, conffiles=None):73def attach_conffiles(report, package, conffiles=None):
@@ -105,10 +110,22 @@
105 report[key] = '[deleted]'110 report[key] = '[deleted]'
106111
107def attach_dmesg(report):112def attach_dmesg(report):
108 '''Attach information from the kernel ring buffer (dmesg).'''113 '''Attach information from the kernel ring buffer (dmesg).
109114
110 report['BootDmesg'] = open('/var/log/dmesg').read()115 This won't overwite already existing information.
111 report['CurrentDmesg'] = command_output(['sh', '-c', 'dmesg | comm -13 --nocheck-order /var/log/dmesg -'])116 '''
117 try:
118 if not report.get('BootDmesg', '').strip():
119 report['BootDmesg'] = open('/var/log/dmesg').read()
120 except IOError:
121 pass
122 if not report.get('CurrentDmesg', '').strip():
123 dmesg = command_output(['sh', '-c', 'dmesg | comm -13 --nocheck-order /var/log/dmesg -'])
124 # if an initial message was truncated by the ring buffer, skip over it
125 first_newline = dmesg.find('\n[')
126 if first_newline != -1:
127 dmesg = dmesg[first_newline+1:]
128 report['CurrentDmesg'] = dmesg
112129
113def attach_dmi(report):130def attach_dmi(report):
114 dmi_dir = '/sys/class/dmi/id'131 dmi_dir = '/sys/class/dmi/id'
@@ -148,11 +165,12 @@
148165
149 attach_file(report, '/proc/interrupts', 'ProcInterrupts')166 attach_file(report, '/proc/interrupts', 'ProcInterrupts')
150 attach_file(report, '/proc/cpuinfo', 'ProcCpuinfo')167 attach_file(report, '/proc/cpuinfo', 'ProcCpuinfo')
151 attach_file(report, '/proc/cmdline', 'ProcCmdLine')168 attach_file(report, '/proc/cmdline', 'ProcKernelCmdLine')
152 attach_file(report, '/proc/modules', 'ProcModules')169 attach_file(report, '/proc/modules', 'ProcModules')
153 attach_file(report, '/var/log/udev', 'UdevLog')170 attach_file(report, '/var/log/udev', 'UdevLog')
154171
155 report['Lspci'] = command_output(['lspci','-vvnn'])172 if os.path.exists('/sys/bus/pci'):
173 report['Lspci'] = command_output(['lspci','-vvnn'])
156 report['Lsusb'] = command_output(['lsusb'])174 report['Lsusb'] = command_output(['lsusb'])
157 report['UdevDb'] = command_output(['udevadm', 'info', '--export-db'])175 report['UdevDb'] = command_output(['udevadm', 'info', '--export-db'])
158176
@@ -202,10 +220,11 @@
202 report['PciMultimedia'] = pci_devices(PCI_MULTIMEDIA)220 report['PciMultimedia'] = pci_devices(PCI_MULTIMEDIA)
203221
204 cards = []222 cards = []
205 for line in open('/proc/asound/cards'):223 if os.path.exists('/proc/asound/cards'):
206 if ']:' in line:224 for line in open('/proc/asound/cards'):
207 fields = line.lstrip().split()225 if ']:' in line:
208 cards.append(int(fields[0]))226 fields = line.lstrip().split()
227 cards.append(int(fields[0]))
209228
210 for card in cards:229 for card in cards:
211 key = 'Card%d.Amixer.info' % card230 key = 'Card%d.Amixer.info' % card
@@ -260,7 +279,7 @@
260 try:279 try:
261 sp = subprocess.Popen(command, stdout=subprocess.PIPE,280 sp = subprocess.Popen(command, stdout=subprocess.PIPE,
262 stderr=stderr, close_fds=True, env=env)281 stderr=stderr, close_fds=True, env=env)
263 except OSError, e:282 except OSError as e:
264 return 'Error: ' + str(e)283 return 'Error: ' + str(e)
265284
266 out = sp.communicate(input)[0]285 out = sp.communicate(input)[0]
@@ -270,14 +289,7 @@
270 return 'Error: command %s failed with exit code %i: %s' % (289 return 'Error: command %s failed with exit code %i: %s' % (
271 str(command), sp.returncode, out)290 str(command), sp.returncode, out)
272291
273def root_command_output(command, input = None, stderr = subprocess.STDOUT):292def _root_command_prefix():
274 '''Try to execute given command (array) as root and return its stdout.
275
276 This passes the command through gksu, kdesudo, or sudo, depending on the
277 running desktop environment.
278
279 In case of failure, a textual error gets returned.
280 '''
281 if os.getuid() == 0:293 if os.getuid() == 0:
282 prefix = []294 prefix = []
283 elif os.getenv('DISPLAY') and \295 elif os.getenv('DISPLAY') and \
@@ -295,7 +307,60 @@
295 else:307 else:
296 prefix = ['sudo']308 prefix = ['sudo']
297309
298 return command_output(prefix + command, input, stderr)310 return prefix
311
312def root_command_output(command, input = None, stderr = subprocess.STDOUT):
313 '''Try to execute given command (array) as root and return its stdout.
314
315 This passes the command through gksu, kdesudo, or sudo, depending on the
316 running desktop environment.
317
318 In case of failure, a textual error gets returned.
319 '''
320 assert type(command) == type([]), 'command must be a list'
321 return command_output(_root_command_prefix() + command, input, stderr)
322
323def attach_root_command_outputs(report, command_map):
324 '''Execute multiple commands as root and put their outputs into report.
325
326 command_map is a keyname -> 'shell command' dictionary with the commands to
327 run. They are all run through /bin/sh, so you need to take care of shell
328 escaping yourself. To include stderr output of a command, end it with
329 "2>&1".
330
331 Just like root_command_output() this will use gksu, kdesudo, or sudo for
332 gaining root privileges, depending on the running desktop environment.
333
334 This is preferrable to using root_command_output() multiple times, as that
335 will ask for the password every time.
336 '''
337 workdir = tempfile.mkdtemp()
338 try:
339 # create a shell script with all the commands
340 script_path = os.path.join(workdir, ':script:')
341 script = open(script_path, 'w')
342 for keyname, command in command_map.items():
343 assert hasattr(command, 'strip'), 'command must be a string (shell command)'
344 # use "| cat" here, so that we can end commands with 2>&1
345 # (otherwise it would have the wrong redirection order)
346 script.write('%s | cat > %s\n' % (command, os.path.join(workdir, keyname)))
347 script.close()
348
349 # run script
350 env = os.environ.copy()
351 env['LC_MESSAGES'] = 'C'
352 env['LANGUAGE'] = ''
353 sp = subprocess.Popen(_root_command_prefix() + ['/bin/sh', script_path],
354 close_fds=True, env=env)
355 sp.wait()
356
357 # now read back the individual outputs
358 for keyname in command_map:
359 f = open(os.path.join(workdir, keyname))
360 report[keyname] = f.read()
361 f.close()
362 finally:
363 shutil.rmtree(workdir)
299364
300def recent_syslog(pattern):365def recent_syslog(pattern):
301 '''Extract recent messages from syslog which match a regex.366 '''Extract recent messages from syslog which match a regex.
@@ -303,9 +368,12 @@
303 pattern should be a "re" object.368 pattern should be a "re" object.
304 '''369 '''
305 lines = ''370 lines = ''
306 for line in open('/var/log/syslog'):371 try:
307 if pattern.search(line):372 for line in open('/var/log/syslog'):
308 lines += line373 if pattern.search(line):
374 lines += line
375 except IOError:
376 return []
309 return lines377 return lines
310378
311def xsession_errors(pattern):379def xsession_errors(pattern):
@@ -440,7 +508,7 @@
440def attach_wifi(report):508def attach_wifi(report):
441 '''Attach wireless (WiFi) network information to report.'''509 '''Attach wireless (WiFi) network information to report.'''
442510
443 report['WifiSyslog'] = recent_syslog(re.compile(r'(NetworkManager|modem-manager|dhclient|kernel):'))511 report['WifiSyslog'] = recent_syslog(re.compile(r'(NetworkManager|modem-manager|dhclient|kernel)(\[\d+\])?:'))
444 report['IwConfig'] = re.sub('Encryption key:(.*)', 'Encryption key: <hidden>', command_output(['iwconfig']))512 report['IwConfig'] = re.sub('Encryption key:(.*)', 'Encryption key: <hidden>', command_output(['iwconfig']))
445 report['RfKill'] = command_output(['rfkill', 'list'])513 report['RfKill'] = command_output(['rfkill', 'list'])
446 report['CRDA'] = command_output(['iw', 'reg', 'get'])514 report['CRDA'] = command_output(['iw', 'reg', 'get'])
@@ -485,20 +553,30 @@
485 553
486 Arguments may be package names or globs, e. g. "foo*"554 Arguments may be package names or globs, e. g. "foo*"
487 '''555 '''
488 versions = ''556 versions = []
489 for package_pattern in packages:557 for package_pattern in packages:
490 if not package_pattern:558 if not package_pattern:
491 continue559 continue
492 for package in packaging.package_name_glob(package_pattern):560
561 matching_packages = packaging.package_name_glob(package_pattern)
562
563 if not matching_packages:
564 versions.append((package_pattern, 'N/A'))
565
566 for package in sorted(matching_packages):
493 try:567 try:
494 version = packaging.get_version(package)568 version = packaging.get_version(package)
495 except ValueError:569 except ValueError:
496 version = 'N/A'570 version = 'N/A'
497 if version is None:571 if version is None:
498 version = 'N/A'572 version = 'N/A'
499 versions += '%s %s\n' % (package, version)573 versions.append((package,version))
500574
501 return versions575 package_width, version_width = \
576 map(max, [map(len, t) for t in zip(*versions)])
577
578 fmt = '%%-%ds %%s' % package_width
579 return '\n'.join([fmt % v for v in versions])
502580
503def shared_libraries(path):581def shared_libraries(path):
504 '''Returns a list of strings containing the sonames of shared libraries582 '''Returns a list of strings containing the sonames of shared libraries
@@ -635,7 +713,7 @@
635713
636if __name__ == '__main__':714if __name__ == '__main__':
637715
638 import unittest, tempfile716 import unittest
639717
640 class _T(unittest.TestCase):718 class _T(unittest.TestCase):
641 def test_module_license_evaluation(self):719 def test_module_license_evaluation(self):
@@ -661,10 +739,10 @@
661 # - fake BAD module739 # - fake BAD module
662740
663 # direct license check741 # direct license check
664 self.assert_('GPL' in _get_module_license('isofs'))742 self.assertTrue('GPL' in _get_module_license('isofs'))
665 self.assertEqual(_get_module_license('does-not-exist'), 'invalid')743 self.assertEqual(_get_module_license('does-not-exist'), 'invalid')
666 self.assert_('GPL' in _get_module_license(good_ko.name))744 self.assertTrue('GPL' in _get_module_license(good_ko.name))
667 self.assert_('BAD' in _get_module_license(bad_ko.name))745 self.assertTrue('BAD' in _get_module_license(bad_ko.name))
668746
669 # check via nonfree_kernel_modules logic747 # check via nonfree_kernel_modules logic
670 f = tempfile.NamedTemporaryFile()748 f = tempfile.NamedTemporaryFile()
@@ -672,9 +750,44 @@
672 (good_ko.name,bad_ko.name))750 (good_ko.name,bad_ko.name))
673 f.flush()751 f.flush()
674 nonfree = nonfree_kernel_modules(f.name)752 nonfree = nonfree_kernel_modules(f.name)
675 self.failIf('isofs' in nonfree)753 self.assertFalse('isofs' in nonfree)
676 self.assert_('does-not-exist' in nonfree)754 self.assertTrue('does-not-exist' in nonfree)
677 self.failIf(good_ko.name in nonfree)755 self.assertFalse(good_ko.name in nonfree)
678 self.assert_(bad_ko.name in nonfree)756 self.assertTrue(bad_ko.name in nonfree)
757
758 def test_attach_dmesg(self):
759 '''attach_dmesg() does not overwrite already existing data'''
760
761 report = {}
762
763 attach_dmesg(report)
764 self.assertTrue(report['BootDmesg'].startswith('['))
765 self.assertTrue(len(report['BootDmesg']) > 500)
766 self.assertTrue(report['CurrentDmesg'].startswith('['))
767
768 def test_dmesg_overwrite(self):
769 '''attach_dmesg() does not overwrite already existing data'''
770
771 report = {'BootDmesg': 'existingboot'}
772
773 attach_dmesg(report)
774 self.assertEqual(report['BootDmesg'][:50], 'existingboot')
775 self.assertTrue(report['CurrentDmesg'].startswith('['))
776
777 report = {'BootDmesg': 'existingboot', 'CurrentDmesg': 'existingcurrent' }
778
779 attach_dmesg(report)
780 self.assertEqual(report['BootDmesg'], 'existingboot')
781 self.assertEqual(report['CurrentDmesg'], 'existingcurrent')
782
783 def test_no_crashes(self):
784 '''functions do not crash (very shallow'''
785
786 report = {}
787 attach_hardware(report)
788 attach_alsa(report)
789 attach_network(report)
790 attach_wifi(report)
791 attach_printing(report)
679792
680 unittest.main()793 unittest.main()
681794
=== modified file 'apport/packaging.py'
--- apport/packaging.py 2009-12-23 10:55:52 +0000
+++ apport/packaging.py 2011-04-20 23:32:24 +0000
@@ -15,26 +15,26 @@
15 15
16 Throw ValueError if package does not exist.16 Throw ValueError if package does not exist.
17 '''17 '''
18 raise NotImplementedError, 'this method must be implemented by a concrete subclass'18 raise NotImplementedError('this method must be implemented by a concrete subclass')
1919
20 def get_available_version(self, package):20 def get_available_version(self, package):
21 '''Return the latest available version of a package.21 '''Return the latest available version of a package.
22 22
23 Throw ValueError if package does not exist.23 Throw ValueError if package does not exist.
24 '''24 '''
25 raise NotImplementedError, 'this method must be implemented by a concrete subclass'25 raise NotImplementedError('this method must be implemented by a concrete subclass')
2626
27 def get_dependencies(self, package):27 def get_dependencies(self, package):
28 '''Return a list of packages a package depends on.'''28 '''Return a list of packages a package depends on.'''
2929
30 raise NotImplementedError, 'this method must be implemented by a concrete subclass'30 raise NotImplementedError('this method must be implemented by a concrete subclass')
3131
32 def get_source(self, package):32 def get_source(self, package):
33 '''Return the source package name for a package.33 '''Return the source package name for a package.
34 34
35 Throw ValueError if package does not exist.35 Throw ValueError if package does not exist.
36 '''36 '''
37 raise NotImplementedError, 'this method must be implemented by a concrete subclass'37 raise NotImplementedError('this method must be implemented by a concrete subclass')
3838
39 def is_distro_package(self, package):39 def is_distro_package(self, package):
40 '''Check package origin.40 '''Check package origin.
@@ -44,7 +44,7 @@
4444
45 Throw ValueError if package does not exist.45 Throw ValueError if package does not exist.
46 '''46 '''
47 raise NotImplementedError, 'this method must be implemented by a concrete subclass'47 raise NotImplementedError('this method must be implemented by a concrete subclass')
4848
49 def get_architecture(self, package):49 def get_architecture(self, package):
50 '''Return the architecture of a package.50 '''Return the architecture of a package.
@@ -52,19 +52,19 @@
52 This might differ on multiarch architectures (e. g. an i386 Firefox52 This might differ on multiarch architectures (e. g. an i386 Firefox
53 package on a x86_64 system)53 package on a x86_64 system)
54 '''54 '''
55 raise NotImplementedError, 'this method must be implemented by a concrete subclass'55 raise NotImplementedError('this method must be implemented by a concrete subclass')
5656
57 def get_files(self, package):57 def get_files(self, package):
58 '''Return list of files shipped by a package.58 '''Return list of files shipped by a package.
5959
60 Throw ValueError if package does not exist.60 Throw ValueError if package does not exist.
61 '''61 '''
62 raise NotImplementedError, 'this method must be implemented by a concrete subclass'62 raise NotImplementedError('this method must be implemented by a concrete subclass')
6363
64 def get_modified_files(self, package):64 def get_modified_files(self, package):
65 '''Return list of all modified files of a package.'''65 '''Return list of all modified files of a package.'''
6666
67 raise NotImplementedError, 'this method must be implemented by a concrete subclass'67 raise NotImplementedError('this method must be implemented by a concrete subclass')
6868
69 def get_file_package(self, file, uninstalled=False, map_cachedir=None):69 def get_file_package(self, file, uninstalled=False, map_cachedir=None):
70 '''Return the package a file belongs to.70 '''Return the package a file belongs to.
@@ -77,14 +77,14 @@
77 an existing directory which will be used to permanently store the77 an existing directory which will be used to permanently store the
78 downloaded maps. If it is not set, a temporary directory will be used.78 downloaded maps. If it is not set, a temporary directory will be used.
79 '''79 '''
80 raise NotImplementedError, 'this method must be implemented by a concrete subclass'80 raise NotImplementedError('this method must be implemented by a concrete subclass')
8181
82 def get_system_architecture(self):82 def get_system_architecture(self):
83 '''Return the architecture of the system.83 '''Return the architecture of the system.
84 84
85 This should use the notation of the particular distribution.85 This should use the notation of the particular distribution.
86 '''86 '''
87 raise NotImplementedError, 'this method must be implemented by a concrete subclass'87 raise NotImplementedError('this method must be implemented by a concrete subclass')
8888
89 def set_mirror(self, url):89 def set_mirror(self, url):
90 '''Explicitly set a distribution mirror URL.90 '''Explicitly set a distribution mirror URL.
@@ -95,7 +95,7 @@
95 By default, the mirror will be read from the system configuration95 By default, the mirror will be read from the system configuration
96 files.96 files.
97 '''97 '''
98 raise NotImplementedError, 'this method must be implemented by a concrete subclass'98 raise NotImplementedError('this method must be implemented by a concrete subclass')
9999
100 def get_source_tree(self, srcpackage, dir, version=None):100 def get_source_tree(self, srcpackage, dir, version=None):
101 '''Download a source package and unpack it into dir..101 '''Download a source package and unpack it into dir..
@@ -112,14 +112,14 @@
112 (which might be a subdirectory of dir). Return None if the source is112 (which might be a subdirectory of dir). Return None if the source is
113 not available.113 not available.
114 '''114 '''
115 raise NotImplementedError, 'this method must be implemented by a concrete subclass'115 raise NotImplementedError('this method must be implemented by a concrete subclass')
116116
117 def compare_versions(self, ver1, ver2):117 def compare_versions(self, ver1, ver2):
118 '''Compare two package versions.118 '''Compare two package versions.
119119
120 Return -1 for ver < ver2, 0 for ver1 == ver2, and 1 for ver1 > ver2.120 Return -1 for ver < ver2, 0 for ver1 == ver2, and 1 for ver1 > ver2.
121 '''121 '''
122 raise NotImplementedError, 'this method must be implemented by a concrete subclass'122 raise NotImplementedError('this method must be implemented by a concrete subclass')
123123
124 def enabled(self):124 def enabled(self):
125 '''Return whether Apport should generate crash reports.125 '''Return whether Apport should generate crash reports.
@@ -132,14 +132,14 @@
132 Implementations should parse the configuration file which controls132 Implementations should parse the configuration file which controls
133 Apport (such as /etc/default/apport in Debian/Ubuntu).133 Apport (such as /etc/default/apport in Debian/Ubuntu).
134 '''134 '''
135 raise NotImplementedError, 'this method must be implemented by a concrete subclass'135 raise NotImplementedError('this method must be implemented by a concrete subclass')
136136
137 def get_kernel_package(self):137 def get_kernel_package(self):
138 '''Return the actual Linux kernel package name.138 '''Return the actual Linux kernel package name.
139139
140 This is used when the user reports a bug against the "linux" package.140 This is used when the user reports a bug against the "linux" package.
141 '''141 '''
142 raise NotImplementedError, 'this method must be implemented by a concrete subclass'142 raise NotImplementedError('this method must be implemented by a concrete subclass')
143143
144 def install_retracing_packages(self, report, verbosity=0,144 def install_retracing_packages(self, report, verbosity=0,
145 unpack_only=False, no_pkg=False, extra_packages=[]):145 unpack_only=False, no_pkg=False, extra_packages=[]):
@@ -157,7 +157,7 @@
157 157
158 Return a tuple (list of installed packages, string with outdated packages).158 Return a tuple (list of installed packages, string with outdated packages).
159 '''159 '''
160 raise NotImplementedError, 'this method must be implemented by a concrete subclass'160 raise NotImplementedError('this method must be implemented by a concrete subclass')
161161
162 def remove_packages(self, packages, verbosity=0):162 def remove_packages(self, packages, verbosity=0):
163 '''Remove packages.163 '''Remove packages.
@@ -165,11 +165,11 @@
165 This is called after install_retracing_packages() to clean up again165 This is called after install_retracing_packages() to clean up again
166 afterwards. packages is a list of package names.166 afterwards. packages is a list of package names.
167 '''167 '''
168 raise NotImplementedError, 'this method must be implemented by a concrete subclass'168 raise NotImplementedError('this method must be implemented by a concrete subclass')
169169
170 def package_name_glob(self, glob):170 def package_name_glob(self, glob):
171 '''Return known package names which match given glob.'''171 '''Return known package names which match given glob.'''
172172
173 raise NotImplementedError, 'this method must be implemented by a concrete subclass'173 raise NotImplementedError('this method must be implemented by a concrete subclass')
174174
175import packaging_impl175import apport.packaging_impl
176176
=== modified file 'apport/report.py'
--- apport/report.py 2010-06-16 13:50:47 +0000
+++ apport/report.py 2011-04-20 23:32:24 +0000
@@ -16,8 +16,9 @@
16from xml.parsers.expat import ExpatError16from xml.parsers.expat import ExpatError
1717
18from problem_report import ProblemReport18from problem_report import ProblemReport
19import fileutils19import apport
20from packaging_impl import impl as packaging20import apport.fileutils
21from apport.packaging_impl import impl as packaging
2122
22_data_dir = os.environ.get('APPORT_DATA_DIR','/usr/share/apport')23_data_dir = os.environ.get('APPORT_DATA_DIR','/usr/share/apport')
23_hook_dir = '%s/package-hooks/' % (_data_dir)24_hook_dir = '%s/package-hooks/' % (_data_dir)
@@ -56,7 +57,7 @@
56 '''57 '''
57 try:58 try:
58 return open(path).read().strip()59 return open(path).read().strip()
59 except (OSError, IOError), e:60 except (OSError, IOError) as e:
60 return 'Error: ' + str(e)61 return 'Error: ' + str(e)
6162
62def _read_maps(pid):63def _read_maps(pid):
@@ -67,8 +68,8 @@
67 '''68 '''
68 maps = 'Error: unable to read /proc maps file'69 maps = 'Error: unable to read /proc maps file'
69 try:70 try:
70 maps = file('/proc/%d/maps' % pid).read().strip()71 maps = open('/proc/%d/maps' % pid).read().strip()
71 except (OSError,IOError), e:72 except (OSError,IOError) as e:
72 return 'Error: ' + str(e)73 return 'Error: ' + str(e)
73 return maps74 return maps
7475
@@ -84,8 +85,8 @@
84 if sp.returncode == 0:85 if sp.returncode == 0:
85 return out86 return out
86 else:87 else:
87 raise OSError, 'Error: command %s failed with exit code %i: %s' % (88 raise OSError('Error: command %s failed with exit code %i: %s' % (
88 str(command), sp.returncode, err)89 str(command), sp.returncode, err))
8990
90def _check_bug_pattern(report, pattern):91def _check_bug_pattern(report, pattern):
91 '''Check if given report matches the given bug pattern XML DOM node.92 '''Check if given report matches the given bug pattern XML DOM node.
@@ -114,6 +115,19 @@
114115
115 return pattern.attributes['url'].nodeValue.encode('UTF-8')116 return pattern.attributes['url'].nodeValue.encode('UTF-8')
116117
118def _check_bug_patterns(report, patterns):
119 try:
120 dom = xml.dom.minidom.parseString(patterns)
121 except ExpatError:
122 return None
123
124 for pattern in dom.getElementsByTagName('pattern'):
125 url = _check_bug_pattern(report, pattern)
126 if url:
127 return url
128
129 return None
130
117def _dom_remove_space(node):131def _dom_remove_space(node):
118 '''Recursively remove whitespace from given XML DOM node.'''132 '''Recursively remove whitespace from given XML DOM node.'''
119133
@@ -177,7 +191,7 @@
177 self['ProblemType'] == 'KernelCrash'):191 self['ProblemType'] == 'KernelCrash'):
178 package = self['Package']192 package = self['Package']
179 else:193 else:
180 package = fileutils.find_file_package(self['ExecutablePath'])194 package = apport.fileutils.find_file_package(self['ExecutablePath'])
181 if not package:195 if not package:
182 return196 return
183197
@@ -333,9 +347,9 @@
333 self['ProcMaps'] = _read_maps(int(pid))347 self['ProcMaps'] = _read_maps(int(pid))
334 try:348 try:
335 self['ExecutablePath'] = os.readlink('/proc/' + pid + '/exe')349 self['ExecutablePath'] = os.readlink('/proc/' + pid + '/exe')
336 except OSError, e:350 except OSError as e:
337 if e.errno == errno.ENOENT:351 if e.errno == errno.ENOENT:
338 raise ValueError, 'invalid process'352 raise ValueError('invalid process')
339 else:353 else:
340 raise354 raise
341 for p in ('rofs', 'rwfs', 'squashmnt', 'persistmnt'):355 for p in ('rofs', 'rwfs', 'squashmnt', 'persistmnt'):
@@ -539,12 +553,12 @@
539 are generally not useful for triaging and duplicate detection.553 are generally not useful for triaging and duplicate detection.
540 '''554 '''
541 unwind_functions = set(['g_logv', 'g_log', 'IA__g_log', 'IA__g_logv',555 unwind_functions = set(['g_logv', 'g_log', 'IA__g_log', 'IA__g_logv',
542 'g_assert_warning', 'IA__g_assert_warning'])556 'g_assert_warning', 'IA__g_assert_warning', '__GI_abort'])
543 toptrace = [''] * 5557 toptrace = [''] * 5
544 depth = 0558 depth = 0
545 unwound = False559 unwound = False
546 unwinding = False560 unwinding = False
547 bt_fn_re = re.compile('^#(\d+)\s+(?:0x(?:\w+)\s+in\s+(.*)|(<signal handler called>)\s*)$')561 bt_fn_re = re.compile('^#(\d+)\s+(?:0x(?:\w+)\s+in\s+\*?(.*)|(<signal handler called>)\s*)$')
548 bt_fn_noaddr_re = re.compile('^#(\d+)\s+(?:(.*)|(<signal handler called>)\s*)$')562 bt_fn_noaddr_re = re.compile('^#(\d+)\s+(?:(.*)|(<signal handler called>)\s*)$')
549563
550 for line in self['Stacktrace'].splitlines():564 for line in self['Stacktrace'].splitlines():
@@ -592,14 +606,17 @@
592 execfile(hook, symb)606 execfile(hook, symb)
593 try:607 try:
594 symb['add_info'](self, ui)608 symb['add_info'](self, ui)
595 except TypeError:609 except TypeError as e:
596 # older versions of apport did not pass UI, and hooks that610 if e.message.startswith('add_info()'):
597 # do not require it don't need to take it611 # older versions of apport did not pass UI, and hooks that
598 symb['add_info'](self)612 # do not require it don't need to take it
613 symb['add_info'](self)
614 else:
615 raise
599 except StopIteration:616 except StopIteration:
600 return True617 return True
601 except:618 except:
602 print >> sys.stderr, 'hook %s crashed:' % hook619 apport.error('hook %s crashed:', hook)
603 traceback.print_exc()620 traceback.print_exc()
604 pass621 pass
605622
@@ -613,14 +630,17 @@
613 execfile(hook, symb)630 execfile(hook, symb)
614 try:631 try:
615 symb['add_info'](self, ui)632 symb['add_info'](self, ui)
616 except TypeError:633 except TypeError as e:
617 # older versions of apport did not pass UI, and hooks that634 if e.message.startswith('add_info()'):
618 # do not require it don't need to take it635 # older versions of apport did not pass UI, and hooks that
619 symb['add_info'](self)636 # do not require it don't need to take it
637 symb['add_info'](self)
638 else:
639 raise
620 except StopIteration:640 except StopIteration:
621 return True641 return True
622 except:642 except:
623 print >> sys.stderr, 'hook %s crashed:' % hook643 apport.error('hook %s crashed:', hook)
624 traceback.print_exc()644 traceback.print_exc()
625 pass645 pass
626646
@@ -634,25 +654,28 @@
634 execfile(hook, symb)654 execfile(hook, symb)
635 try:655 try:
636 symb['add_info'](self, ui)656 symb['add_info'](self, ui)
637 except TypeError:657 except TypeError as e:
638 # older versions of apport did not pass UI, and hooks that658 if e.message.startswith('add_info()'):
639 # do not require it don't need to take it659 # older versions of apport did not pass UI, and hooks that
640 symb['add_info'](self)660 # do not require it don't need to take it
661 symb['add_info'](self)
662 else:
663 raise
641 except StopIteration:664 except StopIteration:
642 return True665 return True
643 except:666 except:
644 print >> sys.stderr, 'hook %s crashed:' % hook667 apport.error('hook %s crashed:', hook)
645 traceback.print_exc()668 traceback.print_exc()
646 pass669 pass
647670
648 return False671 return False
649672
650 def search_bug_patterns(self, baseurl):673 def search_bug_patterns(self, url):
651 '''Check bug patterns at baseurl/packagename.xml. 674 '''Check bug patterns loaded from the specified url.
652 675
653 Return bug URL on match, or None otherwise.676 Return bug URL on match, or None otherwise.
654677
655 The pattern file must be valid XML and has the following syntax:678 The url must refer to a valid XML document with the following syntax:
656 root element := <patterns>679 root element := <patterns>
657 patterns := <pattern url="http://bug.url"> *680 patterns := <pattern url="http://bug.url"> *
658 pattern := <re key="report_key">regular expression*</re> +681 pattern := <re key="report_key">regular expression*</re> +
@@ -664,39 +687,27 @@
664 <re key="Foo">ba.*r</re>687 <re key="Foo">ba.*r</re>
665 </pattern>688 </pattern>
666 <pattern url="http://bugtracker.net/bugs/2">689 <pattern url="http://bugtracker.net/bugs/2">
690 <re key="Package">^\S* 1-2$</re> <!-- test for a particular version -->
667 <re key="Foo">write_(hello|goodbye)</re>691 <re key="Foo">write_(hello|goodbye)</re>
668 <re key="Package">^\S* 1-2$</re> <!-- test for a particular version -->
669 </pattern>692 </pattern>
670 </patterns>693 </patterns>
671 '''694 '''
672 # some distros might not want to support these695 # some distros might not want to support these
673 if not baseurl:696 if not url:
674 return697 return
675698
676 assert self.has_key('Package')
677 package = self['Package'].split()[0]
678 try:699 try:
679 patterns = urllib.urlopen('%s/%s.xml' % (baseurl, package)).read()700 patterns = urllib.urlopen(url).read()
680 assert '<title>404 Not Found' not in patterns
681 except:701 except:
682 # try if there is one for the source package702 # doesn't exist or failed to load
683 if self.has_key('SourcePackage'):703 return
684 try:704
685 patterns = urllib.urlopen('%s/%s.xml' % (baseurl, self['SourcePackage'])).read()705 if '<title>404 Not Found' in patterns:
686 except:706 return
687 return None707
688 else:708 url = _check_bug_patterns(self, patterns)
689 return None709 if url:
690710 return url
691 try:
692 dom = xml.dom.minidom.parseString(patterns)
693 except ExpatError:
694 return None
695
696 for pattern in dom.getElementsByTagName('pattern'):
697 m = _check_bug_pattern(self, pattern)
698 if m:
699 return m
700711
701 return None712 return None
702713
@@ -714,8 +725,8 @@
714 else:725 else:
715 try:726 try:
716 dom = xml.dom.minidom.parse(ifpath)727 dom = xml.dom.minidom.parse(ifpath)
717 except ExpatError, e:728 except ExpatError as e:
718 raise ValueError, '%s has invalid format: %s' % (_ignore_file, str(e))729 raise ValueError('%s has invalid format: %s' % (_ignore_file, str(e)))
719730
720 # remove whitespace so that writing back the XML does not accumulate731 # remove whitespace so that writing back the XML does not accumulate
721 # whitespace732 # whitespace
@@ -880,22 +891,46 @@
880 os.path.basename(self['ExecutablePath']),891 os.path.basename(self['ExecutablePath']),
881 trace[0])892 trace[0])
882893
883 trace_re = re.compile('^\s*File.* in (.+)$')894 trace_re = re.compile('^\s*File\s*"(\S+)".* in (.+)$')
884 i = len(trace)-1895 i = len(trace)-1
885 function = 'unknown'896 function = 'unknown'
886 while i >= 0:897 while i >= 0:
887 m = trace_re.match(trace[i])898 m = trace_re.match(trace[i])
888 if m:899 if m:
889 function = m.group(1)900 module_path = m.group(1)
901 function = m.group(2)
890 break902 break
891 i -= 1903 i -= 1
892904
893 return '%s crashed with %s in %s()' % (905 path = os.path.basename(self['ExecutablePath'])
894 os.path.basename(self['ExecutablePath']),906 last_line = trace[-1]
895 trace[-1].split(':')[0],907 exception = last_line.split(':')[0]
896 function908 m = re.match('^%s: (.+)$' % exception, last_line)
909 if m:
910 message = m.group(1)
911 else:
912 message = None
913
914 if function == '<module>':
915 if module_path == self['ExecutablePath']:
916 context = '__main__'
917 else:
918 # Maybe use os.path.basename?
919 context = module_path
920 else:
921 context = '%s()' % function
922
923 title = '%s crashed with %s in %s' % (
924 path,
925 exception,
926 context
897 )927 )
898928
929 if message:
930 title += ': %s' % message
931
932 return title
933
899 # package problem934 # package problem
900 if self.get('ProblemType') == 'Package' and \935 if self.get('ProblemType') == 'Package' and \
901 self.has_key('Package'):936 self.has_key('Package'):
@@ -991,7 +1026,11 @@
991 sig = '%s:%s' % (self['ExecutablePath'], self['Signal'])1026 sig = '%s:%s' % (self['ExecutablePath'], self['Signal'])
992 bt_fn_re = re.compile('^(?:([\w:~]+).*|(<signal handler called>)\s*)$')1027 bt_fn_re = re.compile('^(?:([\w:~]+).*|(<signal handler called>)\s*)$')
9931028
994 for line in self['StacktraceTop'].splitlines():1029 lines = self['StacktraceTop'].splitlines()
1030 if len(lines) < 2:
1031 return None
1032
1033 for line in lines:
995 m = bt_fn_re.match(line)1034 m = bt_fn_re.match(line)
996 if m:1035 if m:
997 sig += ':' + (m.group(1) or m.group(2))1036 sig += ':' + (m.group(1) or m.group(2))
@@ -1067,7 +1106,10 @@
1067#1106#
10681107
1069import unittest, shutil, signal, time1108import unittest, shutil, signal, time
1070from cStringIO import StringIO1109try:
1110 from cStringIO import StringIO
1111except ImportError:
1112 from io import StringIO
10711113
1072class _T(unittest.TestCase):1114class _T(unittest.TestCase):
1073 def test_add_package_info(self):1115 def test_add_package_info(self):
@@ -1082,7 +1124,7 @@
1082 pr.add_package_info('bash')1124 pr.add_package_info('bash')
1083 self.assertEqual(pr['Package'], 'bash ' + bashversion.strip())1125 self.assertEqual(pr['Package'], 'bash ' + bashversion.strip())
1084 self.assertEqual(pr['SourcePackage'], 'bash')1126 self.assertEqual(pr['SourcePackage'], 'bash')
1085 self.assert_('libc' in pr['Dependencies'])1127 self.assertTrue('libc' in pr['Dependencies'])
10861128
1087 # test without specifying a package, but with ExecutablePath1129 # test without specifying a package, but with ExecutablePath
1088 pr = Report()1130 pr = Report()
@@ -1091,36 +1133,36 @@
1091 pr.add_package_info()1133 pr.add_package_info()
1092 self.assertEqual(pr['Package'], 'bash ' + bashversion.strip())1134 self.assertEqual(pr['Package'], 'bash ' + bashversion.strip())
1093 self.assertEqual(pr['SourcePackage'], 'bash')1135 self.assertEqual(pr['SourcePackage'], 'bash')
1094 self.assert_('libc' in pr['Dependencies'])1136 self.assertTrue('libc' in pr['Dependencies'])
1095 # check for stray empty lines1137 # check for stray empty lines
1096 self.assert_('\n\n' not in pr['Dependencies'])1138 self.assertTrue('\n\n' not in pr['Dependencies'])
1097 self.assert_(pr.has_key('PackageArchitecture'))1139 self.assertTrue(pr.has_key('PackageArchitecture'))
10981140
1099 pr = Report()1141 pr = Report()
1100 pr['ExecutablePath'] = '/nonexisting'1142 pr['ExecutablePath'] = '/nonexisting'
1101 pr.add_package_info()1143 pr.add_package_info()
1102 self.assert_(not pr.has_key('Package'))1144 self.assertTrue(not pr.has_key('Package'))
11031145
1104 def test_add_os_info(self):1146 def test_add_os_info(self):
1105 '''add_os_info().'''1147 '''add_os_info().'''
11061148
1107 pr = Report()1149 pr = Report()
1108 pr.add_os_info()1150 pr.add_os_info()
1109 self.assert_(pr['Uname'].startswith('Linux'))1151 self.assertTrue(pr['Uname'].startswith('Linux'))
1110 self.assert_(type(pr['DistroRelease']) == type(''))1152 self.assertTrue(type(pr['DistroRelease']) == type(''))
1111 self.assert_(pr['Architecture'])1153 self.assertTrue(pr['Architecture'])
11121154
1113 def test_add_user_info(self):1155 def test_add_user_info(self):
1114 '''add_user_info().'''1156 '''add_user_info().'''
11151157
1116 pr = Report()1158 pr = Report()
1117 pr.add_user_info()1159 pr.add_user_info()
1118 self.assert_(pr.has_key('UserGroups'))1160 self.assertTrue(pr.has_key('UserGroups'))
11191161
1120 # double-check that user group names are removed1162 # double-check that user group names are removed
1121 for g in pr['UserGroups'].split():1163 for g in pr['UserGroups'].split():
1122 self.assert_(grp.getgrnam(g).gr_gid < 1000)1164 self.assertTrue(grp.getgrnam(g).gr_gid < 1000)
1123 self.assert_(grp.getgrgid(os.getgid()).gr_name not in pr['UserGroups'])1165 self.assertTrue(grp.getgrgid(os.getgid()).gr_name not in pr['UserGroups'])
11241166
1125 def test_add_proc_info(self):1167 def test_add_proc_info(self):
1126 '''add_proc_info().'''1168 '''add_proc_info().'''
@@ -1135,26 +1177,26 @@
1135 self.assertEqual(pr.pid, None)1177 self.assertEqual(pr.pid, None)
1136 pr.add_proc_info()1178 pr.add_proc_info()
1137 self.assertEqual(pr.pid, os.getpid())1179 self.assertEqual(pr.pid, os.getpid())
1138 self.assert_(set(['ProcEnviron', 'ProcMaps', 'ProcCmdline',1180 self.assertTrue(set(['ProcEnviron', 'ProcMaps', 'ProcCmdline',
1139 'ProcMaps']).issubset(set(pr.keys())), 'report has required fields')1181 'ProcMaps']).issubset(set(pr.keys())), 'report has required fields')
1140 self.assert_('LANG='+os.environ['LANG'] in pr['ProcEnviron'])1182 self.assertTrue('LANG='+os.environ['LANG'] in pr['ProcEnviron'])
1141 self.assert_('USER' not in pr['ProcEnviron'])1183 self.assertTrue('USER' not in pr['ProcEnviron'])
1142 self.assert_('PWD' not in pr['ProcEnviron'])1184 self.assertTrue('PWD' not in pr['ProcEnviron'])
11431185
1144 # check with one additional safe environment variable1186 # check with one additional safe environment variable
1145 pr = Report()1187 pr = Report()
1146 pr.add_proc_info(extraenv=['PWD'])1188 pr.add_proc_info(extraenv=['PWD'])
1147 self.assert_('USER' not in pr['ProcEnviron'])1189 self.assertTrue('USER' not in pr['ProcEnviron'])
1148 self.assert_('PWD='+os.environ['PWD'] in pr['ProcEnviron'])1190 self.assertTrue('PWD='+os.environ['PWD'] in pr['ProcEnviron'])
11491191
1150 # check process from other user1192 # check process from other user
1151 assert os.getuid() != 0, 'please do not run this test as root for this check.'1193 assert os.getuid() != 0, 'please do not run this test as root for this check.'
1152 pr = Report()1194 pr = Report()
1153 self.assertRaises(OSError, pr.add_proc_info, 1) # EPERM for init process1195 self.assertRaises(OSError, pr.add_proc_info, 1) # EPERM for init process
1154 self.assertEqual(pr.pid, 1)1196 self.assertEqual(pr.pid, 1)
1155 self.assert_('init' in pr['ProcStatus'], pr['ProcStatus'])1197 self.assertTrue('init' in pr['ProcStatus'], pr['ProcStatus'])
1156 self.assert_(pr['ProcEnviron'].startswith('Error:'), pr['ProcEnviron'])1198 self.assertTrue(pr['ProcEnviron'].startswith('Error:'), pr['ProcEnviron'])
1157 self.assert_(not pr.has_key('InterpreterPath'))1199 self.assertTrue(not pr.has_key('InterpreterPath'))
11581200
1159 # check escaping of ProcCmdline1201 # check escaping of ProcCmdline
1160 p = subprocess.Popen(['cat', '/foo bar', '\\h', '\\ \\', '-'],1202 p = subprocess.Popen(['cat', '/foo bar', '\\h', '\\ \\', '-'],
@@ -1170,7 +1212,7 @@
1170 p.communicate('\n')1212 p.communicate('\n')
1171 self.assertEqual(pr['ProcCmdline'], 'cat /foo\ bar \\\\h \\\\\\ \\\\ -')1213 self.assertEqual(pr['ProcCmdline'], 'cat /foo\ bar \\\\h \\\\\\ \\\\ -')
1172 self.assertEqual(pr['ExecutablePath'], '/bin/cat')1214 self.assertEqual(pr['ExecutablePath'], '/bin/cat')
1173 self.assert_(not pr.has_key('InterpreterPath'))1215 self.assertTrue(not pr.has_key('InterpreterPath'))
1174 self.assertTrue('/bin/cat' in pr['ProcMaps'])1216 self.assertTrue('/bin/cat' in pr['ProcMaps'])
1175 self.assertTrue('[stack]' in pr['ProcMaps'])1217 self.assertTrue('[stack]' in pr['ProcMaps'])
11761218
@@ -1186,7 +1228,7 @@
1186 pr.pid = p.pid1228 pr.pid = p.pid
1187 pr.add_proc_info()1229 pr.add_proc_info()
1188 p.communicate('exit\n')1230 p.communicate('exit\n')
1189 self.failIf(pr.has_key('InterpreterPath'), pr.get('InterpreterPath'))1231 self.assertFalse(pr.has_key('InterpreterPath'), pr.get('InterpreterPath'))
1190 self.assertEqual(pr['ExecutablePath'], os.path.realpath('/bin/sh'))1232 self.assertEqual(pr['ExecutablePath'], os.path.realpath('/bin/sh'))
11911233
1192 # check correct handling of interpreted executables: shell1234 # check correct handling of interpreted executables: shell
@@ -1199,7 +1241,7 @@
1199 pr = Report()1241 pr = Report()
1200 pr.add_proc_info(pid=p.pid)1242 pr.add_proc_info(pid=p.pid)
1201 p.communicate('\n')1243 p.communicate('\n')
1202 self.assert_(pr['ExecutablePath'].endswith('bin/zgrep'))1244 self.assertTrue(pr['ExecutablePath'].endswith('bin/zgrep'))
1203 self.assertEqual(pr['InterpreterPath'],1245 self.assertEqual(pr['InterpreterPath'],
1204 os.path.realpath(open(pr['ExecutablePath']).readline().strip()[2:]))1246 os.path.realpath(open(pr['ExecutablePath']).readline().strip()[2:]))
1205 self.assertTrue('[stack]' in pr['ProcMaps'])1247 self.assertTrue('[stack]' in pr['ProcMaps'])
@@ -1211,7 +1253,7 @@
1211sys.stdin.readline()1253sys.stdin.readline()
1212''')1254''')
1213 os.close(fd)1255 os.close(fd)
1214 os.chmod(testscript, 0755)1256 os.chmod(testscript, 0o755)
1215 p = subprocess.Popen([testscript], stdin=subprocess.PIPE,1257 p = subprocess.Popen([testscript], stdin=subprocess.PIPE,
1216 stderr=subprocess.PIPE, close_fds=True)1258 stderr=subprocess.PIPE, close_fds=True)
1217 assert p.pid1259 assert p.pid
@@ -1223,7 +1265,7 @@
1223 p.communicate('\n')1265 p.communicate('\n')
1224 os.unlink(testscript)1266 os.unlink(testscript)
1225 self.assertEqual(pr['ExecutablePath'], testscript)1267 self.assertEqual(pr['ExecutablePath'], testscript)
1226 self.assert_('python' in pr['InterpreterPath'])1268 self.assertTrue('python' in pr['InterpreterPath'])
1227 self.assertTrue('python' in pr['ProcMaps'])1269 self.assertTrue('python' in pr['ProcMaps'])
1228 self.assertTrue('[stack]' in pr['ProcMaps'])1270 self.assertTrue('[stack]' in pr['ProcMaps'])
12291271
@@ -1240,7 +1282,7 @@
1240 r = Report()1282 r = Report()
1241 r.add_proc_environ(pid=p.pid)1283 r.add_proc_environ(pid=p.pid)
1242 p.communicate('')1284 p.communicate('')
1243 self.failIf('PATH' in r['ProcEnviron'], 1285 self.assertFalse('PATH' in r['ProcEnviron'],
1244 'system default $PATH should be filtered out')1286 'system default $PATH should be filtered out')
12451287
1246 # no user paths1288 # no user paths
@@ -1250,7 +1292,7 @@
1250 r = Report()1292 r = Report()
1251 r.add_proc_environ(pid=p.pid)1293 r.add_proc_environ(pid=p.pid)
1252 p.communicate('')1294 p.communicate('')
1253 self.assert_('PATH=(custom, no user)' in r['ProcEnviron'], 1295 self.assertTrue('PATH=(custom, no user)' in r['ProcEnviron'],
1254 'PATH is customized without user paths')1296 'PATH is customized without user paths')
12551297
1256 # user paths1298 # user paths
@@ -1260,7 +1302,7 @@
1260 r = Report()1302 r = Report()
1261 r.add_proc_environ(pid=p.pid)1303 r.add_proc_environ(pid=p.pid)
1262 p.communicate('')1304 p.communicate('')
1263 self.assert_('PATH=(custom, user)' in r['ProcEnviron'], 1305 self.assertTrue('PATH=(custom, user)' in r['ProcEnviron'],
1264 'PATH is customized with user paths')1306 'PATH is customized with user paths')
12651307
1266 def test_check_interpreted(self):1308 def test_check_interpreted(self):
@@ -1274,7 +1316,7 @@
1274 pr['ProcCmdline'] = 'gedit\0/' + f.name1316 pr['ProcCmdline'] = 'gedit\0/' + f.name
1275 pr._check_interpreted()1317 pr._check_interpreted()
1276 self.assertEqual(pr['ExecutablePath'], '/usr/bin/gedit')1318 self.assertEqual(pr['ExecutablePath'], '/usr/bin/gedit')
1277 self.failIf(pr.has_key('InterpreterPath'))1319 self.assertFalse(pr.has_key('InterpreterPath'))
1278 f.close()1320 f.close()
12791321
1280 # bogus argv[0]1322 # bogus argv[0]
@@ -1284,7 +1326,7 @@
1284 pr['ProcCmdline'] = 'nonexisting\0/foo'1326 pr['ProcCmdline'] = 'nonexisting\0/foo'
1285 pr._check_interpreted()1327 pr._check_interpreted()
1286 self.assertEqual(pr['ExecutablePath'], '/bin/dash')1328 self.assertEqual(pr['ExecutablePath'], '/bin/dash')
1287 self.failIf(pr.has_key('InterpreterPath'))1329 self.assertFalse(pr.has_key('InterpreterPath'))
12881330
1289 # standard sh script1331 # standard sh script
1290 pr = Report()1332 pr = Report()
@@ -1331,7 +1373,7 @@
1331 pr['ProcCmdline'] = 'python\0/etc/shadow'1373 pr['ProcCmdline'] = 'python\0/etc/shadow'
1332 pr._check_interpreted()1374 pr._check_interpreted()
1333 self.assertEqual(pr['ExecutablePath'], '/usr/bin/python')1375 self.assertEqual(pr['ExecutablePath'], '/usr/bin/python')
1334 self.failIf(pr.has_key('InterpreterPath'))1376 self.assertFalse(pr.has_key('InterpreterPath'))
13351377
1336 # succeed on files we should have access to when name!=argv[0]1378 # succeed on files we should have access to when name!=argv[0]
1337 pr = Report()1379 pr = Report()
@@ -1349,7 +1391,7 @@
1349 pr['ProcCmdline'] = '../etc/shadow'1391 pr['ProcCmdline'] = '../etc/shadow'
1350 pr._check_interpreted()1392 pr._check_interpreted()
1351 self.assertEqual(pr['ExecutablePath'], '/usr/bin/python')1393 self.assertEqual(pr['ExecutablePath'], '/usr/bin/python')
1352 self.failIf(pr.has_key('InterpreterPath'))1394 self.assertFalse(pr.has_key('InterpreterPath'))
13531395
1354 # succeed on files we should have access to when name==argv[0]1396 # succeed on files we should have access to when name==argv[0]
1355 pr = Report()1397 pr = Report()
@@ -1367,7 +1409,7 @@
1367 pr['ProcCmdline'] = 'python'1409 pr['ProcCmdline'] = 'python'
1368 pr._check_interpreted()1410 pr._check_interpreted()
1369 self.assertEqual(pr['ExecutablePath'], '/usr/bin/python')1411 self.assertEqual(pr['ExecutablePath'], '/usr/bin/python')
1370 self.failIf(pr.has_key('InterpreterPath'))1412 self.assertFalse(pr.has_key('InterpreterPath'))
13711413
1372 # python script (abuse /bin/bash since it must exist)1414 # python script (abuse /bin/bash since it must exist)
1373 pr = Report()1415 pr = Report()
@@ -1435,21 +1477,21 @@
1435 return pr1477 return pr
14361478
1437 def _validate_gdb_fields(self,pr):1479 def _validate_gdb_fields(self,pr):
1438 self.assert_(pr.has_key('Stacktrace'))1480 self.assertTrue(pr.has_key('Stacktrace'))
1439 self.assert_(pr.has_key('ThreadStacktrace'))1481 self.assertTrue(pr.has_key('ThreadStacktrace'))
1440 self.assert_(pr.has_key('StacktraceTop'))1482 self.assertTrue(pr.has_key('StacktraceTop'))
1441 self.assert_(pr.has_key('Registers'))1483 self.assertTrue(pr.has_key('Registers'))
1442 self.assert_(pr.has_key('Disassembly'))1484 self.assertTrue(pr.has_key('Disassembly'))
1443 self.assert_('(no debugging symbols found)' not in pr['Stacktrace'])1485 self.assertTrue('(no debugging symbols found)' not in pr['Stacktrace'])
1444 self.assert_('Core was generated by' not in pr['Stacktrace'], pr['Stacktrace'])1486 self.assertTrue('Core was generated by' not in pr['Stacktrace'], pr['Stacktrace'])
1445 self.assert_(not re.match(r'(?s)(^|.*\n)#0 [^\n]+\n#0 ',1487 self.assertTrue(not re.match(r'(?s)(^|.*\n)#0 [^\n]+\n#0 ',
1446 pr['Stacktrace']))1488 pr['Stacktrace']))
1447 self.assert_('#0 0x' in pr['Stacktrace'])1489 self.assertTrue('#0 0x' in pr['Stacktrace'])
1448 self.assert_('#1 0x' in pr['Stacktrace'])1490 self.assertTrue('#1 0x' in pr['Stacktrace'])
1449 self.assert_('#0 0x' in pr['ThreadStacktrace'])1491 self.assertTrue('#0 0x' in pr['ThreadStacktrace'])
1450 self.assert_('#1 0x' in pr['ThreadStacktrace'])1492 self.assertTrue('#1 0x' in pr['ThreadStacktrace'])
1451 self.assert_('Thread 1 (' in pr['ThreadStacktrace'])1493 self.assertTrue('Thread 1 (' in pr['ThreadStacktrace'])
1452 self.assert_(len(pr['StacktraceTop'].splitlines()) <= 5)1494 self.assertTrue(len(pr['StacktraceTop'].splitlines()) <= 5)
14531495
1454 def test_add_gdb_info(self):1496 def test_add_gdb_info(self):
1455 '''add_gdb_info() with core dump file reference.'''1497 '''add_gdb_info() with core dump file reference.'''
@@ -1462,7 +1504,7 @@
1462 pr = self._generate_sigsegv_report()1504 pr = self._generate_sigsegv_report()
1463 self._validate_gdb_fields(pr)1505 self._validate_gdb_fields(pr)
1464 self.assertEqual(pr['StacktraceTop'], 'f (x=42) at crash.c:3\nmain () at crash.c:6', pr['StacktraceTop'])1506 self.assertEqual(pr['StacktraceTop'], 'f (x=42) at crash.c:3\nmain () at crash.c:6', pr['StacktraceTop'])
1465 self.failIf ('AssertionMessage' in pr)1507 self.assertFalse ('AssertionMessage' in pr)
14661508
1467 # crash where gdb generates output on stderr1509 # crash where gdb generates output on stderr
1468 pr = self._generate_sigsegv_report(code='''1510 pr = self._generate_sigsegv_report(code='''
@@ -1473,8 +1515,8 @@
1473}1515}
1474''')1516''')
1475 self._validate_gdb_fields(pr)1517 self._validate_gdb_fields(pr)
1476 self.assert_('Cannot access memory at address 0x0' in pr['Disassembly'], pr['Disassembly'])1518 self.assertTrue('Cannot access memory at address 0x0' in pr['Disassembly'], pr['Disassembly'])
1477 self.failIf ('AssertionMessage' in pr)1519 self.assertFalse ('AssertionMessage' in pr)
14781520
1479 def test_add_gdb_info_load(self):1521 def test_add_gdb_info_load(self):
1480 '''add_gdb_info() with inline core dump.'''1522 '''add_gdb_info() with inline core dump.'''
@@ -1499,22 +1541,22 @@
1499 pr.load(open(rep.name))1541 pr.load(open(rep.name))
1500 pr['Signal'] = '1'1542 pr['Signal'] = '1'
1501 pr.add_hooks_info('fake_ui')1543 pr.add_hooks_info('fake_ui')
1502 self.assert_('SegvAnalysis' not in pr.keys())1544 self.assertTrue('SegvAnalysis' not in pr.keys())
15031545
1504 pr = Report()1546 pr = Report()
1505 pr.load(open(rep.name))1547 pr.load(open(rep.name))
1506 pr.add_hooks_info('fake_ui')1548 pr.add_hooks_info('fake_ui')
1507 self.assert_('Skipped: missing required field "Architecture"' in pr['SegvAnalysis'],1549 self.assertTrue('Skipped: missing required field "Architecture"' in pr['SegvAnalysis'],
1508 pr['SegvAnalysis'])1550 pr['SegvAnalysis'])
15091551
1510 pr.add_os_info()1552 pr.add_os_info()
1511 pr.add_hooks_info('fake_ui')1553 pr.add_hooks_info('fake_ui')
1512 self.assert_('Skipped: missing required field "ProcMaps"' in pr['SegvAnalysis'],1554 self.assertTrue('Skipped: missing required field "ProcMaps"' in pr['SegvAnalysis'],
1513 pr['SegvAnalysis'])1555 pr['SegvAnalysis'])
15141556
1515 pr.add_proc_info()1557 pr.add_proc_info()
1516 pr.add_hooks_info('fake_ui')1558 pr.add_hooks_info('fake_ui')
1517 self.assert_('not located in a known VMA region' in pr['SegvAnalysis'],1559 self.assertTrue('not located in a known VMA region' in pr['SegvAnalysis'],
1518 pr['SegvAnalysis'])1560 pr['SegvAnalysis'])
15191561
1520 def test_add_gdb_info_script(self):1562 def test_add_gdb_info_script(self):
@@ -1532,7 +1574,7 @@
1532ulimit -c unlimited1574ulimit -c unlimited
1533kill -SEGV $$1575kill -SEGV $$
1534''')1576''')
1535 os.chmod(script, 0755)1577 os.chmod(script, 0o755)
15361578
1537 # call script and verify that it gives us a proper ELF core dump1579 # call script and verify that it gives us a proper ELF core dump
1538 assert subprocess.call([script]) != 01580 assert subprocess.call([script]) != 0
@@ -1549,7 +1591,7 @@
1549 os.unlink(script)1591 os.unlink(script)
15501592
1551 self._validate_gdb_fields(pr)1593 self._validate_gdb_fields(pr)
1552 self.assert_('libc.so' in pr['Stacktrace'] or 'in execute_command' in pr['Stacktrace'])1594 self.assertTrue('libc.so' in pr['Stacktrace'] or 'in execute_command' in pr['Stacktrace'])
15531595
1554 def test_add_gdb_info_abort(self):1596 def test_add_gdb_info_abort(self):
1555 '''add_gdb_info() with SIGABRT/assert()1597 '''add_gdb_info() with SIGABRT/assert()
@@ -1572,7 +1614,7 @@
1572ulimit -c unlimited1614ulimit -c unlimited
1573$0.bin 2>/dev/null1615$0.bin 2>/dev/null
1574''')1616''')
1575 os.chmod(script, 0755)1617 os.chmod(script, 0o755)
15761618
1577 # call script and verify that it gives us a proper ELF core dump1619 # call script and verify that it gives us a proper ELF core dump
1578 assert subprocess.call([script]) != 01620 assert subprocess.call([script]) != 0
@@ -1589,11 +1631,11 @@
1589 os.unlink('core')1631 os.unlink('core')
15901632
1591 self._validate_gdb_fields(pr)1633 self._validate_gdb_fields(pr)
1592 self.assert_("<stdin>:2: main: Assertion `1 < 0' failed." in1634 self.assertTrue("<stdin>:2: main: Assertion `1 < 0' failed." in
1593 pr['AssertionMessage'], pr['AssertionMessage'])1635 pr['AssertionMessage'], pr['AssertionMessage'])
1594 self.failIf(pr['AssertionMessage'].startswith('$'), pr['AssertionMessage'])1636 self.assertFalse(pr['AssertionMessage'].startswith('$'), pr['AssertionMessage'])
1595 self.failIf('= 0x' in pr['AssertionMessage'], pr['AssertionMessage'])1637 self.assertFalse('= 0x' in pr['AssertionMessage'], pr['AssertionMessage'])
1596 self.failIf(pr['AssertionMessage'].endswith('\\n'), pr['AssertionMessage'])1638 self.assertFalse(pr['AssertionMessage'].endswith('\\n'), pr['AssertionMessage'])
15971639
1598 # abort with internal error1640 # abort with internal error
1599 (fd, script) = tempfile.mkstemp()1641 (fd, script) = tempfile.mkstemp()
@@ -1614,7 +1656,7 @@
1614ulimit -c unlimited1656ulimit -c unlimited
1615LIBC_FATAL_STDERR_=1 $0.bin aaaaaaaaaaaaaaaa 2>/dev/null1657LIBC_FATAL_STDERR_=1 $0.bin aaaaaaaaaaaaaaaa 2>/dev/null
1616''')1658''')
1617 os.chmod(script, 0755)1659 os.chmod(script, 0o755)
16181660
1619 # call script and verify that it gives us a proper ELF core dump1661 # call script and verify that it gives us a proper ELF core dump
1620 assert subprocess.call([script]) != 01662 assert subprocess.call([script]) != 0
@@ -1631,11 +1673,11 @@
1631 os.unlink('core')1673 os.unlink('core')
16321674
1633 self._validate_gdb_fields(pr)1675 self._validate_gdb_fields(pr)
1634 self.assert_("** buffer overflow detected ***: %s.bin terminated" % (script) in1676 self.assertTrue("** buffer overflow detected ***: %s.bin terminated" % (script) in
1635 pr['AssertionMessage'], pr['AssertionMessage'])1677 pr['AssertionMessage'], pr['AssertionMessage'])
1636 self.failIf(pr['AssertionMessage'].startswith('$'), pr['AssertionMessage'])1678 self.assertFalse(pr['AssertionMessage'].startswith('$'), pr['AssertionMessage'])
1637 self.failIf('= 0x' in pr['AssertionMessage'], pr['AssertionMessage'])1679 self.assertFalse('= 0x' in pr['AssertionMessage'], pr['AssertionMessage'])
1638 self.failIf(pr['AssertionMessage'].endswith('\\n'), pr['AssertionMessage'])1680 self.assertFalse(pr['AssertionMessage'].endswith('\\n'), pr['AssertionMessage'])
16391681
1640 # abort without assertion1682 # abort without assertion
1641 (fd, script) = tempfile.mkstemp()1683 (fd, script) = tempfile.mkstemp()
@@ -1652,7 +1694,7 @@
1652ulimit -c unlimited1694ulimit -c unlimited
1653$0.bin 2>/dev/null1695$0.bin 2>/dev/null
1654''')1696''')
1655 os.chmod(script, 0755)1697 os.chmod(script, 0o755)
16561698
1657 # call script and verify that it gives us a proper ELF core dump1699 # call script and verify that it gives us a proper ELF core dump
1658 assert subprocess.call([script]) != 01700 assert subprocess.call([script]) != 0
@@ -1669,97 +1711,103 @@
1669 os.unlink('core')1711 os.unlink('core')
16701712
1671 self._validate_gdb_fields(pr)1713 self._validate_gdb_fields(pr)
1672 self.failIf ('AssertionMessage' in pr, pr.get('AssertionMessage'))1714 self.assertFalse ('AssertionMessage' in pr, pr.get('AssertionMessage'))
16731715
1674 def test_search_bug_patterns(self):1716 def test_search_bug_patterns(self):
1675 '''search_bug_patterns().'''1717 '''search_bug_patterns().'''
16761718
1677 pdir = None1719 patterns = tempfile.NamedTemporaryFile(prefix='apport-')
1678 try:1720 # create some test patterns
1679 pdir = tempfile.mkdtemp()1721 patterns.write('''<?xml version="1.0"?>
1680
1681 # create some test patterns
1682 open(os.path.join(pdir, 'bash.xml'), 'w').write('''<?xml version="1.0"?>
1683<patterns>1722<patterns>
1684 <pattern url="http://bugtracker.net/bugs/1">1723 <pattern url="http://bugtracker.net/bugs/1">
1724 <re key="Package">^bash </re>
1685 <re key="Foo">ba.*r</re>1725 <re key="Foo">ba.*r</re>
1686 </pattern>1726 </pattern>
1687 <pattern url="http://bugtracker.net/bugs/2">1727 <pattern url="http://bugtracker.net/bugs/2">
1728 <re key="Package">^bash 1-2$</re>
1688 <re key="Foo">write_(hello|goodbye)</re>1729 <re key="Foo">write_(hello|goodbye)</re>
1689 <re key="Package">^\S* 1-2$</re>
1690 </pattern>1730 </pattern>
1691</patterns>''')
1692
1693 open(os.path.join(pdir, 'coreutils.xml'), 'w').write('''<?xml version="1.0"?>
1694<patterns>
1695 <pattern url="http://bugtracker.net/bugs/3">1731 <pattern url="http://bugtracker.net/bugs/3">
1732 <re key="Package">^coreutils </re>
1696 <re key="Bar">^1$</re>1733 <re key="Bar">^1$</re>
1697 </pattern>1734 </pattern>
1698 <pattern url="http://bugtracker.net/bugs/4">1735 <pattern url="http://bugtracker.net/bugs/4">
1736 <re key="Package">^coreutils </re>
1699 <re key="Bar">*</re> <!-- invalid RE -->1737 <re key="Bar">*</re> <!-- invalid RE -->
1700 </pattern>1738 </pattern>
1701</patterns>''')1739 <pattern url="http://bugtracker.net/bugs/5">
17021740 <re key="SourcePackage">^bazaar$</re>
1703 # invalid XML1741 <re key="LogFile">AssertionError</re>
1704 open(os.path.join(pdir, 'invalid.xml'), 'w').write('''<?xml version="1.0"?>1742 </pattern>
1705</patterns>''')1743</patterns>''')
17061744 patterns.flush()
1707 # create some reports1745
1708 r_bash = Report()1746 # invalid XML
1709 r_bash['Package'] = 'bash 1-2'1747 invalid = tempfile.NamedTemporaryFile(prefix='apport-')
1710 r_bash['Foo'] = 'bazaar'1748 invalid.write('''<?xml version="1.0"?>
17111749</patterns>''')
1712 r_coreutils = Report()1750 invalid.flush()
1713 r_coreutils['Package'] = 'coreutils 1'1751
1714 r_coreutils['Bar'] = '1'1752 # create some reports
17151753 r_bash = Report()
1716 r_invalid = Report()1754 r_bash['Package'] = 'bash 1-2'
1717 r_invalid['Package'] = 'invalid 1'1755 r_bash['Foo'] = 'bazaar'
17181756
1719 # positive match cases1757 r_bazaar = Report()
1720 self.assertEqual(r_bash.search_bug_patterns(pdir), 'http://bugtracker.net/bugs/1')1758 r_bazaar['Package'] = 'bazaar 2-1'
1721 r_bash['Foo'] = 'write_goodbye'1759 r_bazaar['SourcePackage'] = 'bazaar'
1722 self.assertEqual(r_bash.search_bug_patterns(pdir), 'http://bugtracker.net/bugs/2')1760 r_bazaar['LogFile'] = 'AssertionError'
1723 self.assertEqual(r_coreutils.search_bug_patterns(pdir), 'http://bugtracker.net/bugs/3')1761
17241762 r_coreutils = Report()
1725 # match on source package1763 r_coreutils['Package'] = 'coreutils 1'
1726 r_bash['Package'] = 'bash-static 1-2'1764 r_coreutils['Bar'] = '1'
1727 self.assertEqual(r_bash.search_bug_patterns(pdir), None)1765
1728 r_bash['SourcePackage'] = 'bash'1766 r_invalid = Report()
1729 self.assertEqual(r_bash.search_bug_patterns(pdir), 'http://bugtracker.net/bugs/2')1767 r_invalid['Package'] = 'invalid 1'
17301768
1731 # negative match cases1769 # positive match cases
1732 r_bash['Package'] = 'bash 1-21'1770 self.assertEqual(r_bash.search_bug_patterns(patterns.name), 'http://bugtracker.net/bugs/1')
1733 self.assertEqual(r_bash.search_bug_patterns(pdir), None,1771 r_bash['Foo'] = 'write_goodbye'
1734 'does not match on wrong bash version')1772 self.assertEqual(r_bash.search_bug_patterns(patterns.name), 'http://bugtracker.net/bugs/2')
1735 r_bash['Foo'] = 'zz'1773 self.assertEqual(r_coreutils.search_bug_patterns(patterns.name), 'http://bugtracker.net/bugs/3')
1736 self.assertEqual(r_bash.search_bug_patterns(pdir), None,1774 self.assertEqual(r_bazaar.search_bug_patterns(patterns.name), 'http://bugtracker.net/bugs/5')
1737 'does not match on wrong Foo value')1775
1738 r_coreutils['Bar'] = '11'1776 # negative match cases
1739 self.assertEqual(r_coreutils.search_bug_patterns(pdir), None,1777 r_bash['Package'] = 'bash-static 1-2'
1740 'does not match on wrong Bar value')1778 self.assertEqual(r_bash.search_bug_patterns(patterns.name), None)
17411779 r_bash['Package'] = 'bash 1-21'
1742 # various errors to check for robustness (no exceptions, just None1780 self.assertEqual(r_bash.search_bug_patterns(patterns.name), None,
1743 # return value)1781 'does not match on wrong bash version')
1744 del r_coreutils['Bar']1782 r_bash['Foo'] = 'zz'
1745 self.assertEqual(r_coreutils.search_bug_patterns(pdir), None,1783 self.assertEqual(r_bash.search_bug_patterns(patterns.name), None,
1746 'does not match on nonexisting key')1784 'does not match on wrong Foo value')
1747 self.assertEqual(r_invalid.search_bug_patterns(pdir), None,1785 r_coreutils['Bar'] = '11'
1748 'gracefully handles invalid XML')1786 self.assertEqual(r_coreutils.search_bug_patterns(patterns.name), None,
1749 r_coreutils['Package'] = 'other 2'1787 'does not match on wrong Bar value')
1750 self.assertEqual(r_coreutils.search_bug_patterns(pdir), None,1788 r_bazaar['SourcePackage'] = 'launchpad'
1751 'gracefully handles nonexisting package XML file')1789 self.assertEqual(r_bazaar.search_bug_patterns(patterns.name), None,
1752 self.assertEqual(r_bash.search_bug_patterns('file:///nonexisting/directory/'), None,1790 'does not match on wrong source package')
1753 'gracefully handles nonexisting base path')1791 r_bazaar['LogFile'] = ''
1754 # existing host, but no bug patterns1792 self.assertEqual(r_bazaar.search_bug_patterns(patterns.name), None,
1755 self.assertEqual(r_bash.search_bug_patterns('http://security.ubuntu.com/'), None,1793 'does not match on empty attribute')
1756 'gracefully handles base path without bug patterns')1794
1757 # nonexisting host1795 # various errors to check for robustness (no exceptions, just None
1758 self.assertEqual(r_bash.search_bug_patterns('http://nonexisting.domain/'), None,1796 # return value)
1759 'gracefully handles nonexisting URL domain')1797 del r_coreutils['Bar']
1760 finally:1798 self.assertEqual(r_coreutils.search_bug_patterns(patterns.name), None,
1761 if pdir:1799 'does not match on nonexisting key')
1762 shutil.rmtree(pdir)1800 self.assertEqual(r_invalid.search_bug_patterns(invalid.name), None,
1801 'gracefully handles invalid XML')
1802 r_coreutils['Package'] = 'other 2'
1803 self.assertEqual(r_bash.search_bug_patterns('file:///nonexisting/directory/'), None,
1804 'gracefully handles nonexisting base path')
1805 # existing host, but no bug patterns
1806 self.assertEqual(r_bash.search_bug_patterns('http://security.ubuntu.com/'), None,
1807 'gracefully handles base path without bug patterns')
1808 # nonexisting host
1809 self.assertEqual(r_bash.search_bug_patterns('http://nonexisting.domain/'), None,
1810 'gracefully handles nonexisting URL domain')
17631811
1764 def test_add_hooks_info(self):1812 def test_add_hooks_info(self):
1765 '''add_hooks_info().'''1813 '''add_hooks_info().'''
@@ -1978,34 +2026,34 @@
1978 '''has_useful_stacktrace().'''2026 '''has_useful_stacktrace().'''
19792027
1980 r = Report()2028 r = Report()
1981 self.failIf(r.has_useful_stacktrace())2029 self.assertFalse(r.has_useful_stacktrace())
19822030
1983 r['StacktraceTop'] = ''2031 r['StacktraceTop'] = ''
1984 self.failIf(r.has_useful_stacktrace())2032 self.assertFalse(r.has_useful_stacktrace())
19852033
1986 r['StacktraceTop'] = '?? ()'2034 r['StacktraceTop'] = '?? ()'
1987 self.failIf(r.has_useful_stacktrace())2035 self.assertFalse(r.has_useful_stacktrace())
19882036
1989 r['StacktraceTop'] = '?? ()\n?? ()'2037 r['StacktraceTop'] = '?? ()\n?? ()'
1990 self.failIf(r.has_useful_stacktrace())2038 self.assertFalse(r.has_useful_stacktrace())
19912039
1992 r['StacktraceTop'] = 'read () from /lib/libc.6.so\n?? ()'2040 r['StacktraceTop'] = 'read () from /lib/libc.6.so\n?? ()'
1993 self.failIf(r.has_useful_stacktrace())2041 self.assertFalse(r.has_useful_stacktrace())
19942042
1995 r['StacktraceTop'] = 'read () from /lib/libc.6.so\n?? ()\n?? ()\n?? ()'2043 r['StacktraceTop'] = 'read () from /lib/libc.6.so\n?? ()\n?? ()\n?? ()'
1996 self.failIf(r.has_useful_stacktrace())2044 self.assertFalse(r.has_useful_stacktrace())
19972045
1998 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'2046 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
1999 self.assert_(r.has_useful_stacktrace())2047 self.assertTrue(r.has_useful_stacktrace())
20002048
2001 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so\n?? ()'2049 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so\n?? ()'
2002 self.assert_(r.has_useful_stacktrace())2050 self.assertTrue(r.has_useful_stacktrace())
20032051
2004 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so\n?? ()\n?? ()'2052 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so\n?? ()\n?? ()'
2005 self.assert_(r.has_useful_stacktrace())2053 self.assertTrue(r.has_useful_stacktrace())
20062054
2007 r['StacktraceTop'] = 'read () from /lib/libc.6.so\n?? ()\nfoo (i=1) from /usr/lib/libfoo.so\n?? ()\n?? ()'2055 r['StacktraceTop'] = 'read () from /lib/libc.6.so\n?? ()\nfoo (i=1) from /usr/lib/libfoo.so\n?? ()\n?? ()'
2008 self.failIf(r.has_useful_stacktrace())2056 self.assertFalse(r.has_useful_stacktrace())
20092057
2010 def test_standard_title(self):2058 def test_standard_title(self):
2011 '''standard_title().'''2059 '''standard_title().'''
@@ -2066,7 +2114,7 @@
2066subprocess.call(['pgrep', '-x',2114subprocess.call(['pgrep', '-x',
2067NameError: global name 'subprocess' is not defined'''2115NameError: global name 'subprocess' is not defined'''
2068 self.assertEqual(report.standard_title(),2116 self.assertEqual(report.standard_title(),
2069 'apport-gtk crashed with NameError in ui_present_crash()')2117 "apport-gtk crashed with NameError in ui_present_crash(): global name 'subprocess' is not defined")
20702118
2071 # slightly weird Python crash2119 # slightly weird Python crash
2072 report = Report()2120 report = Report()
@@ -2097,8 +2145,32 @@
2097Restarting AWN usually solves this issue'''2145Restarting AWN usually solves this issue'''
20982146
2099 t = report.standard_title()2147 t = report.standard_title()
2100 self.assert_(t.startswith('apport-gtk crashed with'))2148 self.assertTrue(t.startswith('apport-gtk crashed with'))
2101 self.assert_(t.endswith('setup_chooser()'))2149 self.assertTrue(t.endswith('setup_chooser()'))
2150
2151 # Python crash at top level in module
2152 report = Report()
2153 report['ExecutablePath'] = '/usr/bin/gnome-about'
2154 report['Traceback'] = '''Traceback (most recent call last):
2155 File "/usr/bin/gnome-about", line 30, in <module>
2156 import pygtk
2157 File "/usr/lib/pymodules/python2.6/pygtk.py", line 28, in <module>
2158 import nonexistent
2159ImportError: No module named nonexistent
2160'''
2161 self.assertEqual(report.standard_title(),
2162 "gnome-about crashed with ImportError in /usr/lib/pymodules/python2.6/pygtk.py: No module named nonexistent")
2163
2164 # Python crash at top level in main program
2165 report = Report()
2166 report['ExecutablePath'] = '/usr/bin/dcut'
2167 report['Traceback'] = '''Traceback (most recent call last):
2168 File "/usr/bin/dcut", line 28, in <module>
2169 import nonexistent
2170ImportError: No module named nonexistent
2171'''
2172 self.assertEqual(report.standard_title(),
2173 "dcut crashed with ImportError in __main__: No module named nonexistent")
21022174
2103 # package install problem2175 # package install problem
2104 report = Report('Package')2176 report = Report('Package')
@@ -2289,6 +2361,18 @@
2289filter_func (connection=0x8075288, message=0x80768d8, user_data=0x8074da8) at libhal.c:8202361filter_func (connection=0x8075288, message=0x80768d8, user_data=0x8074da8) at libhal.c:820
2290dbus_connection_dispatch (connection=0x8075288) at dbus-connection.c:4267''')2362dbus_connection_dispatch (connection=0x8075288) at dbus-connection.c:4267''')
22912363
2364 # problem with too old gdb, only assertion, nothing else
2365 r = Report()
2366 r['Stacktrace'] = '''#0 0x00987416 in __kernel_vsyscall ()
2367No symbol table info available.
2368#1 0x00ebecb1 in *__GI_raise (sig=6)
2369 selftid = 945
2370#2 0x00ec218e in *__GI_abort () at abort.c:59
2371 save_stage = Unhandled dwarf expression opcode 0x9f
2372'''
2373 r._gen_stacktrace_top()
2374 self.assertEqual(r['StacktraceTop'], '')
2375
2292 def test_crash_signature(self):2376 def test_crash_signature(self):
2293 '''crash_signature().'''2377 '''crash_signature().'''
22942378
@@ -2314,11 +2398,14 @@
2314__frob (x=1) at crash.c:30'''2398__frob (x=1) at crash.c:30'''
2315 self.assertEqual(r.crash_signature(), None)2399 self.assertEqual(r.crash_signature(), None)
23162400
2401 r['StacktraceTop'] = ''
2402 self.assertEqual(r.crash_signature(), None)
2403
2317 # Python crashes2404 # Python crashes
2318 del r['Signal']2405 del r['Signal']
2319 r['Traceback'] = '''Traceback (most recent call last):2406 r['Traceback'] = '''Traceback (most recent call last):
2320 File "test.py", line 7, in <module>2407 File "test.py", line 7, in <module>
2321 print _f(5)2408 print(_f(5))
2322 File "test.py", line 5, in _f2409 File "test.py", line 5, in _f
2323 return g_foo00(x+1)2410 return g_foo00(x+1)
2324 File "test.py", line 2, in g_foo002411 File "test.py", line 2, in g_foo00
23252412
=== modified file 'apport/ui.py'
--- apport/ui.py 2010-07-09 11:18:39 +0000
+++ apport/ui.py 2011-04-20 23:32:24 +0000
@@ -13,7 +13,7 @@
13# option) any later version. See http://www.gnu.org/copyleft/gpl.html for13# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
14# the full text of the license.14# the full text of the license.
1515
16__version__ = '1.14.1'16__version__ = '1.20.1'
1717
18import glob, sys, os.path, optparse, time, traceback, locale, gettext, re18import glob, sys, os.path, optparse, time, traceback, locale, gettext, re
19import pwd, errno, urllib, zlib19import pwd, errno, urllib, zlib
@@ -24,7 +24,9 @@
24from apport.crashdb import get_crashdb, NeedsCredentials24from apport.crashdb import get_crashdb, NeedsCredentials
25from apport import unicode_gettext as _25from apport import unicode_gettext as _
2626
27symptom_script_dir = '/usr/share/apport/symptoms'27symptom_script_dir = os.environ.get('APPORT_SYMPTOMS_DIR',
28 '/usr/share/apport/symptoms')
29PF_KTHREAD = 0x200000
2830
29def excstr(exception):31def excstr(exception):
30 '''Return exception message as unicode.'''32 '''Return exception message as unicode.'''
@@ -53,13 +55,13 @@
53 execfile(symptom_script, symb)55 execfile(symptom_script, symb)
54 package = symb['run'](report, ui)56 package = symb['run'](report, ui)
55 if not package:57 if not package:
56 print >> sys.stderr, 'symptom script %s did not determine the affected package' % symptom_script58 apport.error('symptom script %s did not determine the affected package', symptom_script)
57 return59 return
58 report['Symptom'] = os.path.splitext(os.path.basename(symptom_script))[0]60 report['Symptom'] = os.path.splitext(os.path.basename(symptom_script))[0]
59 except StopIteration:61 except StopIteration:
60 sys.exit(0)62 sys.exit(0)
61 except:63 except:
62 print >> sys.stderr, 'symptom script %s crashed:' % symptom_script64 apport.error('symptom script %s crashed:', symptom_script)
63 traceback.print_exc()65 traceback.print_exc()
64 sys.exit(0)66 sys.exit(0)
6567
@@ -67,7 +69,7 @@
67 if report.has_key('ExecutablePath'):69 if report.has_key('ExecutablePath'):
68 package = apport.fileutils.find_file_package(report['ExecutablePath'])70 package = apport.fileutils.find_file_package(report['ExecutablePath'])
69 else:71 else:
70 raise KeyError, 'called without a package, and report does not have ExecutablePath'72 raise KeyError('called without a package, and report does not have ExecutablePath')
71 try:73 try:
72 report.add_package_info(package)74 report.add_package_info(package)
73 except ValueError:75 except ValueError:
@@ -75,7 +77,7 @@
75 # package77 # package
76 if not ignore_uninstalled:78 if not ignore_uninstalled:
77 raise79 raise
78 except SystemError, e:80 except SystemError as e:
79 report['UnreportableReason'] = excstr(e)81 report['UnreportableReason'] = excstr(e)
80 return82 return
8183
@@ -112,9 +114,9 @@
112versions installed. Please upgrade the following packages and check if the \114versions installed. Please upgrade the following packages and check if the \
113problem still occurs:\n\n%s') % ', '.join(old_pkgs)115problem still occurs:\n\n%s') % ', '.join(old_pkgs)
114116
115 # if we have a SIGABRT without an assertion message, declare as unreportable117 # disabled: if we have a SIGABRT without an assertion message, declare as unreportable
116 if report.get('Signal') == '6' and 'AssertionMessage' not in report:118 #if report.get('Signal') == '6' and 'AssertionMessage' not in report:
117 report['UnreportableReason'] = _('The program crashed on an assertion failure, but the message could not be retrieved. Apport does not support reporting these crashes.')119 # report['UnreportableReason'] = _('The program crashed on an assertion failure, but the message could not be retrieved. Apport does not support reporting these crashes.')
118120
119 report.anonymize()121 report.anonymize()
120122
@@ -124,7 +126,7 @@
124 report.write(f, only_new=True)126 report.write(f, only_new=True)
125 f.close()127 f.close()
126 apport.fileutils.mark_report_seen(reportfile)128 apport.fileutils.mark_report_seen(reportfile)
127 os.chmod (reportfile, 0600)129 os.chmod (reportfile, 0o600)
128130
129class UserInterface:131class UserInterface:
130 '''Apport user interface API.132 '''Apport user interface API.
@@ -144,13 +146,11 @@
144146
145 try:147 try:
146 self.crashdb = get_crashdb(None)148 self.crashdb = get_crashdb(None)
147 except ImportError, e:149 except ImportError as e:
148 # this can happen while upgrading python packages150 # this can happen while upgrading python packages
149 print >> sys.stderr, 'Could not import module, is a package upgrade in progress? Error:', e151 apport.fatal('Could not import module, is a package upgrade in progress? Error: %s', str(e))
150 sys.exit(1)
151 except KeyError:152 except KeyError:
152 print >> sys.stderr, '/etc/apport/crashdb.conf is damaged: No default database'153 apport.fatal('/etc/apport/crashdb.conf is damaged: No default database')
153 sys.exit(1)
154154
155 gettext.textdomain(self.gettext_domain)155 gettext.textdomain(self.gettext_domain)
156 self.parse_argv()156 self.parse_argv()
@@ -261,7 +261,7 @@
261 try:261 try:
262 if 'Dependencies' not in self.report:262 if 'Dependencies' not in self.report:
263 self.collect_info()263 self.collect_info()
264 except (IOError, zlib.error), e:264 except (IOError, zlib.error) as e:
265 # can happen with broken core dumps265 # can happen with broken core dumps
266 self.report = None266 self.report = None
267 self.ui_error_message(_('Invalid problem report'),267 self.ui_error_message(_('Invalid problem report'),
@@ -275,11 +275,7 @@
275 self.ui_shutdown()275 self.ui_shutdown()
276 return276 return
277277
278 # check unreportable flag278 if self.check_unreportable():
279 if self.report.has_key('UnreportableReason'):
280 self.ui_info_message(_('Problem in %s') % self.report['Package'].split()[0],
281 _('The problem cannot be reported:\n\n%s') %
282 self.report['UnreportableReason'])
283 return279 return
284280
285 if self.handle_duplicate():281 if self.handle_duplicate():
@@ -298,7 +294,7 @@
298 assert response == 'full'294 assert response == 'full'
299295
300 self.file_report()296 self.file_report()
301 except IOError, e:297 except IOError as e:
302 # fail gracefully if file is not readable for us298 # fail gracefully if file is not readable for us
303 if e.errno in (errno.EPERM, errno.EACCES):299 if e.errno in (errno.EPERM, errno.EACCES):
304 self.ui_error_message(_('Invalid problem report'),300 self.ui_error_message(_('Invalid problem report'),
@@ -311,11 +307,10 @@
311 else:307 else:
312 self.ui_error_message(_('Invalid problem report'), e.strerror)308 self.ui_error_message(_('Invalid problem report'), e.strerror)
313 sys.exit(1)309 sys.exit(1)
314 except OSError, e:310 except OSError as e:
315 # fail gracefully on ENOMEM311 # fail gracefully on ENOMEM
316 if e.errno == errno.ENOMEM:312 if e.errno == errno.ENOMEM:
317 print >> sys.stderr, 'Out of memory, aborting'313 apport.fatal('Out of memory, aborting')
318 sys.exit(1)
319 else:314 else:
320 raise315 raise
321316
@@ -343,12 +338,18 @@
343 # if PID is given, add info338 # if PID is given, add info
344 if self.options.pid:339 if self.options.pid:
345 try:340 try:
346 self.report.add_proc_info(self.options.pid)341 stat = open('/proc/%s/stat' % self.options.pid).read().split()
347 except ValueError:342 flags = int(stat[8])
343 if flags & PF_KTHREAD:
344 # this PID is a kernel thread
345 self.options.package = 'linux'
346 else:
347 self.report.add_proc_info(self.options.pid)
348 except (ValueError, IOError):
348 self.ui_error_message(_('Invalid PID'),349 self.ui_error_message(_('Invalid PID'),
349 _('The specified process ID does not belong to a program.'))350 _('The specified process ID does not belong to a program.'))
350 return False351 return False
351 except OSError, e:352 except OSError as e:
352 # silently ignore nonexisting PIDs; the user must not close the353 # silently ignore nonexisting PIDs; the user must not close the
353 # application prematurely354 # application prematurely
354 if e.errno == errno.ENOENT:355 if e.errno == errno.ENOENT:
@@ -372,7 +373,7 @@
372373
373 try:374 try:
374 self.collect_info(symptom_script)375 self.collect_info(symptom_script)
375 except ValueError, e:376 except ValueError as e:
376 if str(e) == 'package does not exist':377 if str(e) == 'package does not exist':
377 if not self.cur_package:378 if not self.cur_package:
378 self.ui_error_message(_('Invalid problem report'), 379 self.ui_error_message(_('Invalid problem report'),
@@ -384,13 +385,11 @@
384 else:385 else:
385 raise386 raise
386387
387 # check unreportable flag388 if self.check_unreportable():
388 if self.report.has_key('UnreportableReason'):
389 self.ui_info_message(_('Problem in %s') % self.report['Package'].split()[0],
390 _('The problem cannot be reported:\n\n%s') %
391 self.report['UnreportableReason'])
392 return389 return
393390
391 self.add_extra_tags()
392
394 if self.handle_duplicate():393 if self.handle_duplicate():
395 return True394 return True
396395
@@ -402,10 +401,10 @@
402401
403 if self.options.save:402 if self.options.save:
404 try:403 try:
405 f = open(self.options.save, 'w')404 f = open(os.path.expanduser(self.options.save), 'w')
406 self.report.write(f)405 self.report.write(f)
407 f.close()406 f.close()
408 except (IOError, OSError), e:407 except (IOError, OSError) as e:
409 self.ui_error_message(_('Cannot create report'), excstr(e))408 self.ui_error_message(_('Cannot create report'), excstr(e))
410 else:409 else:
411 # show what's being sent410 # show what's being sent
@@ -449,7 +448,7 @@
449448
450 info_collected = False449 info_collected = False
451 for p in pkgs:450 for p in pkgs:
452 #print 'Collecting apport information for source package %s...' % p451 #print('Collecting apport information for source package %s...' % p)
453 self.cur_package = p452 self.cur_package = p
454 self.report['SourcePackage'] = p453 self.report['SourcePackage'] = p
455 self.report['Package'] = p # no way to find this out454 self.report['Package'] = p # no way to find this out
@@ -460,7 +459,7 @@
460 apport.packaging.get_version(p)459 apport.packaging.get_version(p)
461 except ValueError:460 except ValueError:
462 if not os.path.exists(os.path.join(apport.report._hook_dir, 'source_%s.py' % p)):461 if not os.path.exists(os.path.join(apport.report._hook_dir, 'source_%s.py' % p)):
463 print 'Package %s not installed and no hook available, ignoring' % p462 print('Package %s not installed and no hook available, ignoring' % p)
464 continue463 continue
465 self.collect_info(ignore_uninstalled=True)464 self.collect_info(ignore_uninstalled=True)
466 info_collected = True465 info_collected = True
@@ -472,6 +471,7 @@
472471
473 self.report.add_user_info()472 self.report.add_user_info()
474 self.report.add_proc_environ()473 self.report.add_proc_environ()
474 self.add_extra_tags()
475475
476 # delete the uninteresting keys476 # delete the uninteresting keys
477 del self.report['ProblemType']477 del self.report['ProblemType']
@@ -506,19 +506,29 @@
506 symptom_names = []506 symptom_names = []
507 symptom_descriptions = []507 symptom_descriptions = []
508 for script in scripts:508 for script in scripts:
509 # scripts with an underscore can be used for private libraries
510 if os.path.basename(script).startswith('_'):
511 continue
509 symb = {}512 symb = {}
510 try:513 try:
511 execfile(script, symb)514 execfile(script, symb)
512 except:515 except:
513 print >> sys.stderr, 'symptom script %s is invalid' % script516 apport.error('symptom script %s is invalid', script)
514 traceback.print_exc()517 traceback.print_exc()
515 continue518 continue
519 if 'run' not in symb:
520 apport.error('symptom script %s does not define run() function', script)
521 continue
516 symptom_names.append(os.path.splitext(os.path.basename(script))[0])522 symptom_names.append(os.path.splitext(os.path.basename(script))[0])
517 symptom_descriptions.append(symb.get('description', symptom_names[-1]))523 symptom_descriptions.append(symb.get('description', symptom_names[-1]))
518524
519 if not symptom_names:525 if not symptom_names:
520 return False526 return False
521527
528 symptom_descriptions, symptom_names = \
529 zip(*sorted(zip(symptom_descriptions, symptom_names)))
530 symptom_descriptions = list(symptom_descriptions)
531 symptom_names = list(symptom_names)
522 symptom_names.append(None)532 symptom_names.append(None)
523 symptom_descriptions.append('Other problem')533 symptom_descriptions.append('Other problem')
524534
@@ -559,14 +569,32 @@
559 elif self.options.update_report:569 elif self.options.update_report:
560 return self.run_update_report()570 return self.run_update_report()
561 elif self.options.version:571 elif self.options.version:
562 print __version__572 print(__version__)
563 return True573 return True
564 elif self.options.crash_file:574 elif self.options.crash_file:
565 try:575 try:
566 self.run_crash(self.options.crash_file, False)576 self.run_crash(self.options.crash_file, False)
567 except OSError, e:577 except OSError as e:
568 self.ui_error_message(_('Invalid problem report'), excstr(e))578 self.ui_error_message(_('Invalid problem report'), excstr(e))
569 return True579 return True
580 elif self.options.window:
581 self.ui_info_message('', _('After closing this message '
582 'please click on an application window to report a problem about it.'))
583 xprop = subprocess.Popen(['xprop', '_NET_WM_PID'],
584 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
585 (out, err) = xprop.communicate()
586 if xprop.returncode == 0:
587 try:
588 self.options.pid = int(out.split()[-1])
589 except ValueError:
590 self.ui_error_message(_('Cannot create report'),
591 _('xprop failed to determine process ID of the window'))
592 return True
593 return self.run_report_bug()
594 else:
595 self.ui_error_message(_('Cannot create report'),
596 _('xprop failed to determine process ID of the window') + '\n\n' + err)
597 return True
570 else:598 else:
571 return self.run_crashes()599 return self.run_crashes()
572600
@@ -581,8 +609,9 @@
581 '''609 '''
582 optparser = optparse.OptionParser(_('%prog <report number>'))610 optparser = optparse.OptionParser(_('%prog <report number>'))
583 optparser.add_option('-p', '--package',611 optparser.add_option('-p', '--package',
584 help=_('Specify package name.)'),612 help=_('Specify package name.)'))
585 dest='package', default=None)613 optparser.add_option('--tag', action='append', default=[],
614 help=_('Add an extra tag to the report. Can be specified multiple times.'))
586 (self.options, self.args) = optparser.parse_args()615 (self.options, self.args) = optparser.parse_args()
587616
588 if len(self.args) != 1 or not self.args[0].isdigit():617 if len(self.args) != 1 or not self.args[0].isdigit():
@@ -613,30 +642,31 @@
613 return642 return
614643
615 optparser = optparse.OptionParser(_('%prog [options] [symptom|pid|package|program path|.apport/.crash file]'))644 optparser = optparse.OptionParser(_('%prog [options] [symptom|pid|package|program path|.apport/.crash file]'))
616 optparser.add_option('-f', '--file-bug',645 optparser.add_option('-f', '--file-bug', action='store_true',
617 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.)'),646 dest='filebug', default=False,
618 action='store_true', dest='filebug', default=False)647 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.)'))
619 optparser.add_option('-u', '--update-bug', type='int',648 optparser.add_option('-w', '--window', action='store_true', default=False,
620 help=_('Start in bug updating mode. Can take an optional --package.'),649 help=_('Click a window as a target for filing a problem report.'))
621 dest='update_report')650 optparser.add_option('-u', '--update-bug', type='int', dest='update_report',
651 help=_('Start in bug updating mode. Can take an optional --package.'))
622 optparser.add_option('-s', '--symptom', metavar='SYMPTOM',652 optparser.add_option('-s', '--symptom', metavar='SYMPTOM',
623 help=_('File a bug report about a symptom. (Implied if symptom name is given as only argument.)'), 653 help=_('File a bug report about a symptom. (Implied if symptom name is given as only argument.)'))
624 dest='symptom')
625 optparser.add_option('-p', '--package',654 optparser.add_option('-p', '--package',
626 help=_('Specify package name in --file-bug mode. This is optional if a --pid is specified. (Implied if package name is given as only argument.)'),655 help=_('Specify package name in --file-bug mode. This is optional if a --pid is specified. (Implied if package name is given as only argument.)'))
627 action='store', type='string', dest='package', default=None)656 optparser.add_option('-P', '--pid', type='int',
628 optparser.add_option('-P', '--pid',657 help=_('Specify a running program in --file-bug mode. If this is specified, the bug report will contain more information. (Implied if pid is given as only argument.)'))
629 help=_('Specify a running program in --file-bug mode. If this is specified, the bug report will contain more information. (Implied if pid is given as only argument.)'),658 optparser.add_option('-c', '--crash-file', metavar='PATH',
630 action='store', type='int', dest='pid', default=None)659 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)
631 optparser.add_option('-c', '--crash-file',660 optparser.add_option('--save', metavar='PATH',
632 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,661 help=_('In bug filing mode, save the collected information into a file instead of reporting it. This file can then be reported later on from a different machine.'))
633 action='store', type='string', dest='crash_file', default=None, metavar='PATH')662 optparser.add_option('--tag', action='append', default=[],
634 optparser.add_option('--save',663 help=_('Add an extra tag to the report. Can be specified multiple times.'))
635 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.'),664 optparser.add_option('-v', '--version', action='store_true',
636 type='string', dest='save', default=None, metavar='PATH')665 help=_('Print the Apport version number.'))
637 optparser.add_option('-v', '--version',666
638 help=_('Print the Apport version number.'),667 if len(sys.argv) > 0 and cmd.endswith('-bug'):
639 action='store_true', dest='version', default=None)668 for o in ('-f', '-u', '-s', '-p', '-P', '-c'):
669 optparser.get_option(o).help = optparse.SUPPRESS_HELP
640670
641 (self.options, self.args) = optparser.parse_args()671 (self.options, self.args) = optparser.parse_args()
642672
@@ -692,11 +722,11 @@
692 def format_filesize(self, size):722 def format_filesize(self, size):
693 '''Format the given integer as humanly readable and i18n'ed file size.'''723 '''Format the given integer as humanly readable and i18n'ed file size.'''
694724
695 if size < 1048576:725 if size < 1000000:
696 return locale.format('%.1f KiB', size/1024.)726 return locale.format('%.1f', size/1000.) + ' KB'
697 if size < 1024 * 1048576:727 if size < 1000000000:
698 return locale.format('%.1f MiB', size / 1048576.)728 return locale.format('%.1f', size / 1000000.) + ' MB'
699 return locale.format('%.1f GiB', size / float(1024 * 1048576))729 return locale.format('%.1f', size / float(1000000000)) + ' GB'
700730
701 def get_complete_size(self):731 def get_complete_size(self):
702 '''Return the size of the complete report.'''732 '''Return the size of the complete report.'''
@@ -847,12 +877,12 @@
847877
848 # figure out appropriate web browser878 # figure out appropriate web browser
849 try:879 try:
850 # if ksmserver is running, try kfmclient880 # if ksmserver is running, try kde-open
851 try:881 try:
852 if os.getenv('DISPLAY') and \882 if os.getenv('DISPLAY') and \
853 subprocess.call(['pgrep', '-x', '-u', str(uid), 'ksmserver'],883 subprocess.call(['pgrep', '-x', '-u', str(uid), 'ksmserver'],
854 stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0:884 stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0:
855 subprocess.call(sudo_prefix + ['kfmclient', 'openURL', url])885 subprocess.call(sudo_prefix + ['kde-open', url])
856 sys.exit(0)886 sys.exit(0)
857 except OSError:887 except OSError:
858 pass888 pass
@@ -893,7 +923,7 @@
893 webbrowser.open(url, new=True, autoraise=True)923 webbrowser.open(url, new=True, autoraise=True)
894 sys.exit(0)924 sys.exit(0)
895925
896 except Exception, e:926 except Exception as e:
897 os.write(w, str(e))927 os.write(w, str(e))
898 sys.exit(1)928 sys.exit(1)
899929
@@ -925,7 +955,7 @@
925 upthread.exc_raise()955 upthread.exc_raise()
926 except KeyboardInterrupt:956 except KeyboardInterrupt:
927 sys.exit(1)957 sys.exit(1)
928 except NeedsCredentials, e:958 except NeedsCredentials as e:
929 message = _('Please enter your account information for the '959 message = _('Please enter your account information for the '
930 '%s bug tracking system')960 '%s bug tracking system')
931 data = self.ui_question_userpass(message % excstr(e))961 data = self.ui_question_userpass(message % excstr(e))
@@ -936,13 +966,14 @@
936 args=(self.report,966 args=(self.report,
937 progress_callback))967 progress_callback))
938 upthread.start()968 upthread.start()
939 except Exception, e:969 except Exception as e:
940 self.ui_error_message(_('Network problem'),970 self.ui_error_message(_('Network problem'),
941 '%s\n\n%s' % (971 '%s\n\n%s' % (
942 _('Cannot connect to crash database, please check your Internet connection.'),972 _('Cannot connect to crash database, please check your Internet connection.'),
943 excstr(e)))973 excstr(e)))
944 return974 return
945975
976 upthread.exc_raise()
946 ticket = upthread.return_value()977 ticket = upthread.return_value()
947 self.ui_stop_upload_progress()978 self.ui_stop_upload_progress()
948979
@@ -961,17 +992,17 @@
961 self.report = apport.Report()992 self.report = apport.Report()
962 self.report.load(open(path), binary='compressed')993 self.report.load(open(path), binary='compressed')
963 if 'ProblemType' not in self.report:994 if 'ProblemType' not in self.report:
964 raise ValueError, 'Report does not contain "ProblemType" field'995 raise ValueError('Report does not contain "ProblemType" field')
965 except MemoryError:996 except MemoryError:
966 self.report = None997 self.report = None
967 self.ui_error_message(_('Memory exhaustion'),998 self.ui_error_message(_('Memory exhaustion'),
968 _('Your system does not have enough memory to process this crash report.'))999 _('Your system does not have enough memory to process this crash report.'))
969 return False1000 return False
970 except IOError, e:1001 except IOError as e:
971 self.report = None1002 self.report = None
972 self.ui_error_message(_('Invalid problem report'), e.strerror)1003 self.ui_error_message(_('Invalid problem report'), e.strerror)
973 return False1004 return False
974 except (TypeError, ValueError, zlib.error), e:1005 except (TypeError, ValueError, zlib.error) as e:
975 self.report = None1006 self.report = None
976 self.ui_error_message(_('Invalid problem report'),1007 self.ui_error_message(_('Invalid problem report'),
977 '%s\n\n%s' % (1008 '%s\n\n%s' % (
@@ -1004,6 +1035,20 @@
10041035
1005 return True1036 return True
10061037
1038 def check_unreportable(self):
1039 '''Check if the current report is unreportable.
1040
1041 If so, display an info message and return True.
1042 '''
1043 if 'UnreportableReason' in self.report:
1044 if isinstance(self.report['UnreportableReason'], str):
1045 self.report['UnreportableReason'] = self.report['UnreportableReason'].decode('UTF-8')
1046 self.ui_info_message(_('Problem in %s') % self.report['Package'].split()[0],
1047 _('The problem cannot be reported:\n\n%s') %
1048 self.report['UnreportableReason'])
1049 return True
1050 return False
1051
1007 def get_desktop_entry(self):1052 def get_desktop_entry(self):
1008 '''Return a matching xdg.DesktopEntry for the current report.1053 '''Return a matching xdg.DesktopEntry for the current report.
1009 1054
@@ -1037,6 +1082,16 @@
1037 self.open_url(self.report['BugPatternURL'])1082 self.open_url(self.report['BugPatternURL'])
1038 return True1083 return True
10391084
1085 def add_extra_tags(self):
1086 '''Add extra tags to report specified with --tags on CLI.'''
1087
1088 assert self.report
1089 if self.options.tag:
1090 tags = self.report.get('Tags', '')
1091 if tags:
1092 tags += ' '
1093 self.report['Tags'] = tags + ' '.join(self.options.tag)
1094
1040 #1095 #
1041 # abstract UI methods that must be implemented in derived classes1096 # abstract UI methods that must be implemented in derived classes
1042 #1097 #
@@ -1058,7 +1113,7 @@
1058 - Valid values for the 'blacklist' key: True or False (True will cause1113 - Valid values for the 'blacklist' key: True or False (True will cause
1059 the invocation of report.mark_ignore()).1114 the invocation of report.mark_ignore()).
1060 '''1115 '''
1061 raise NotImplementedError, 'this function must be overridden by subclasses'1116 raise NotImplementedError('this function must be overridden by subclasses')
10621117
1063 def ui_present_package_error(self, desktopentry):1118 def ui_present_package_error(self, desktopentry):
1064 '''Ask what to do with a package failure.1119 '''Ask what to do with a package failure.
@@ -1069,7 +1124,7 @@
1069 Return the action: ignore ('cancel'), or report a bug about the problem1124 Return the action: ignore ('cancel'), or report a bug about the problem
1070 ('report').1125 ('report').
1071 '''1126 '''
1072 raise NotImplementedError, 'this function must be overridden by subclasses'1127 raise NotImplementedError('this function must be overridden by subclasses')
10731128
1074 def ui_present_kernel_error(self, desktopentry):1129 def ui_present_kernel_error(self, desktopentry):
1075 '''Ask what to do with a kernel error.1130 '''Ask what to do with a kernel error.
@@ -1080,7 +1135,7 @@
1080 Return the action: ignore ('cancel'), or report a bug about the problem1135 Return the action: ignore ('cancel'), or report a bug about the problem
1081 ('report').1136 ('report').
1082 '''1137 '''
1083 raise NotImplementedError, 'this function must be overridden by subclasses'1138 raise NotImplementedError('this function must be overridden by subclasses')
10841139
1085 def ui_present_report_details(self, is_update):1140 def ui_present_report_details(self, is_update):
1086 '''Show details of the bug report.1141 '''Show details of the bug report.
@@ -1097,17 +1152,17 @@
1097 Return the action: send full report ('full'), send reduced report1152 Return the action: send full report ('full'), send reduced report
1098 ('reduced'), or do not send anything ('cancel').1153 ('reduced'), or do not send anything ('cancel').
1099 '''1154 '''
1100 raise NotImplementedError, 'this function must be overridden by subclasses'1155 raise NotImplementedError('this function must be overridden by subclasses')
11011156
1102 def ui_info_message(self, title, text):1157 def ui_info_message(self, title, text):
1103 '''Show an information message box with given title and text.'''1158 '''Show an information message box with given title and text.'''
11041159
1105 raise NotImplementedError, 'this function must be overridden by subclasses'1160 raise NotImplementedError('this function must be overridden by subclasses')
11061161
1107 def ui_error_message(self, title, text):1162 def ui_error_message(self, title, text):
1108 '''Show an error message box with given title and text.'''1163 '''Show an error message box with given title and text.'''
11091164
1110 raise NotImplementedError, 'this function must be overridden by subclasses'1165 raise NotImplementedError('this function must be overridden by subclasses')
11111166
1112 def ui_start_info_collection_progress(self):1167 def ui_start_info_collection_progress(self):
1113 '''Open a indefinite progress bar for data collection.1168 '''Open a indefinite progress bar for data collection.
@@ -1115,26 +1170,26 @@
1115 This tells the user to wait while debug information is being1170 This tells the user to wait while debug information is being
1116 collected.1171 collected.
1117 '''1172 '''
1118 raise NotImplementedError, 'this function must be overridden by subclasses'1173 raise NotImplementedError('this function must be overridden by subclasses')
11191174
1120 def ui_pulse_info_collection_progress(self):1175 def ui_pulse_info_collection_progress(self):
1121 '''Advance the data collection progress bar.1176 '''Advance the data collection progress bar.
11221177
1123 This function is called every 100 ms.1178 This function is called every 100 ms.
1124 '''1179 '''
1125 raise NotImplementedError, 'this function must be overridden by subclasses'1180 raise NotImplementedError('this function must be overridden by subclasses')
11261181
1127 def ui_stop_info_collection_progress(self):1182 def ui_stop_info_collection_progress(self):
1128 '''Close debug data collection progress window.'''1183 '''Close debug data collection progress window.'''
11291184
1130 raise NotImplementedError, 'this function must be overridden by subclasses'1185 raise NotImplementedError('this function must be overridden by subclasses')
11311186
1132 def ui_start_upload_progress(self):1187 def ui_start_upload_progress(self):
1133 '''Open progress bar for data upload.1188 '''Open progress bar for data upload.
11341189
1135 This tells the user to wait while debug information is being uploaded.1190 This tells the user to wait while debug information is being uploaded.
1136 '''1191 '''
1137 raise NotImplementedError, 'this function must be overridden by subclasses'1192 raise NotImplementedError('this function must be overridden by subclasses')
11381193
1139 def ui_set_upload_progress(self, progress):1194 def ui_set_upload_progress(self, progress):
1140 '''Update data upload progress bar.1195 '''Update data upload progress bar.
@@ -1144,12 +1199,12 @@
11441199
1145 This function is called every 100 ms.1200 This function is called every 100 ms.
1146 '''1201 '''
1147 raise NotImplementedError, 'this function must be overridden by subclasses'1202 raise NotImplementedError('this function must be overridden by subclasses')
11481203
1149 def ui_stop_upload_progress(self):1204 def ui_stop_upload_progress(self):
1150 '''Close debug data upload progress window.'''1205 '''Close debug data upload progress window.'''
11511206
1152 raise NotImplementedError, 'this function must be overridden by subclasses'1207 raise NotImplementedError('this function must be overridden by subclasses')
11531208
1154 def ui_shutdown(self):1209 def ui_shutdown(self):
1155 '''Called right before terminating the program.1210 '''Called right before terminating the program.
@@ -1169,7 +1224,7 @@
1169 Return True if the user selected "Yes", False if selected "No" or1224 Return True if the user selected "Yes", False if selected "No" or
1170 "None" on cancel/dialog closing.1225 "None" on cancel/dialog closing.
1171 '''1226 '''
1172 raise NotImplementedError, 'this function must be overridden by subclasses'1227 raise NotImplementedError('this function must be overridden by subclasses')
11731228
1174 def ui_question_choice(self, text, options, multiple):1229 def ui_question_choice(self, text, options, multiple):
1175 '''Show an question with predefined choices.1230 '''Show an question with predefined choices.
@@ -1181,14 +1236,14 @@
1181 Return list of selected option indexes, or None if the user cancelled.1236 Return list of selected option indexes, or None if the user cancelled.
1182 If multiple == False, the list will always have one element.1237 If multiple == False, the list will always have one element.
1183 '''1238 '''
1184 raise NotImplementedError, 'this function must be overridden by subclasses'1239 raise NotImplementedError('this function must be overridden by subclasses')
11851240
1186 def ui_question_file(self, text):1241 def ui_question_file(self, text):
1187 '''Show a file selector dialog.1242 '''Show a file selector dialog.
11881243
1189 Return path if the user selected a file, or None if cancelled.1244 Return path if the user selected a file, or None if cancelled.
1190 '''1245 '''
1191 raise NotImplementedError, 'this function must be overridden by subclasses'1246 raise NotImplementedError('this function must be overridden by subclasses')
11921247
1193 def ui_question_userpass(self, message):1248 def ui_question_userpass(self, message):
1194 '''Request username and password from user.1249 '''Request username and password from user.
@@ -1198,7 +1253,7 @@
11981253
1199 Return a tuple (username, password), or None if cancelled.1254 Return a tuple (username, password), or None if cancelled.
1200 '''1255 '''
1201 raise NotImplementedError, 'this function must be overridden by subclasses'1256 raise NotImplementedError('this function must be overridden by subclasses')
12021257
1203class HookUI:1258class HookUI:
1204 '''Interactive functions which can be used in package hooks.1259 '''Interactive functions which can be used in package hooks.
@@ -1303,7 +1358,10 @@
13031358
1304if __name__ == '__main__':1359if __name__ == '__main__':
1305 import unittest, shutil, signal, tempfile, resource1360 import unittest, shutil, signal, tempfile, resource
1306 from cStringIO import StringIO1361 try:
1362 from cStringIO import StringIO
1363 except ImportError:
1364 from io import StringIO
1307 import apport.report1365 import apport.report
1308 import problem_report1366 import problem_report
1309 import apport.crashdb_impl.memory1367 import apport.crashdb_impl.memory
@@ -1314,14 +1372,14 @@
1314 def __init__(self):1372 def __init__(self):
1315 # use our dummy crashdb1373 # use our dummy crashdb
1316 self.crashdb_conf = tempfile.NamedTemporaryFile()1374 self.crashdb_conf = tempfile.NamedTemporaryFile()
1317 print >> self.crashdb_conf, '''default = 'testsuite'1375 self.crashdb_conf.write('''default = 'testsuite'
1318databases = {1376databases = {
1319 'testsuite': { 1377 'testsuite': {
1320 'impl': 'memory',1378 'impl': 'memory',
1321 'bug_pattern_base': None1379 'bug_pattern_url': None
1322 }1380 }
1323}1381}
1324'''1382''')
1325 self.crashdb_conf.flush()1383 self.crashdb_conf.flush()
13261384
1327 os.environ['APPORT_CRASHDB_CONF'] = self.crashdb_conf.name1385 os.environ['APPORT_CRASHDB_CONF'] = self.crashdb_conf.name
@@ -1487,14 +1545,14 @@
1487 def test_format_filesize(self):1545 def test_format_filesize(self):
1488 '''format_filesize().'''1546 '''format_filesize().'''
14891547
1490 self.assertEqual(self.ui.format_filesize(0), '0.0 KiB')1548 self.assertEqual(self.ui.format_filesize(0), '0.0 KB')
1491 self.assertEqual(self.ui.format_filesize(2048), '2.0 KiB')1549 self.assertEqual(self.ui.format_filesize(2048), '2.0 KB')
1492 self.assertEqual(self.ui.format_filesize(2560), '2.5 KiB')1550 self.assertEqual(self.ui.format_filesize(2560), '2.6 KB')
1493 self.assertEqual(self.ui.format_filesize(1000000), '976.6 KiB')1551 self.assertEqual(self.ui.format_filesize(999999), '1000.0 KB')
1494 self.assertEqual(self.ui.format_filesize(1048576), '1.0 MiB')1552 self.assertEqual(self.ui.format_filesize(1000000), '1.0 MB')
1495 self.assertEqual(self.ui.format_filesize(2.7*1048576), '2.7 MiB')1553 self.assertEqual(self.ui.format_filesize(2.7*1000000), '2.7 MB')
1496 self.assertEqual(self.ui.format_filesize(1024*1048576), '1.0 GiB')1554 self.assertEqual(self.ui.format_filesize(1024*1000000), '1.0 GB')
1497 self.assertEqual(self.ui.format_filesize(2560*1048576), '2.5 GiB')1555 self.assertEqual(self.ui.format_filesize(2560*1000000), '2.6 GB')
14981556
1499 def test_get_size_loaded(self):1557 def test_get_size_loaded(self):
1500 '''get_complete_size() and get_reduced_size() for loaded Reports.'''1558 '''get_complete_size() and get_reduced_size() for loaded Reports.'''
@@ -1503,21 +1561,21 @@
15031561
1504 fsize = os.path.getsize(self.report_file.name)1562 fsize = os.path.getsize(self.report_file.name)
1505 complete_ratio = float(self.ui.get_complete_size()) / fsize1563 complete_ratio = float(self.ui.get_complete_size()) / fsize
1506 self.assert_(complete_ratio >= 0.99 and complete_ratio <= 1.01)1564 self.assertTrue(complete_ratio >= 0.99 and complete_ratio <= 1.01)
1507 1565
1508 rs = self.ui.get_reduced_size()1566 rs = self.ui.get_reduced_size()
1509 self.assert_(rs > 1000)1567 self.assertTrue(rs > 1000)
1510 self.assert_(rs < 10000)1568 self.assertTrue(rs < 10000)
15111569
1512 # now add some information (e. g. from package hooks)1570 # now add some information (e. g. from package hooks)
1513 self.ui.report['ExtraInfo'] = 'A' * 500001571 self.ui.report['ExtraInfo'] = 'A' * 50000
1514 s = self.ui.get_complete_size()1572 s = self.ui.get_complete_size()
1515 self.assert_(s >= fsize + 49900)1573 self.assertTrue(s >= fsize + 49900)
1516 self.assert_(s < fsize + 60000)1574 self.assertTrue(s < fsize + 60000)
15171575
1518 rs = self.ui.get_reduced_size()1576 rs = self.ui.get_reduced_size()
1519 self.assert_(rs > 51000)1577 self.assertTrue(rs > 51000)
1520 self.assert_(rs < 60000)1578 self.assertTrue(rs < 60000)
15211579
1522 def test_get_size_constructed(self):1580 def test_get_size_constructed(self):
1523 '''get_complete_size() and get_reduced_size() for on-the-fly Reports.'''1581 '''get_complete_size() and get_reduced_size() for on-the-fly Reports.'''
@@ -1526,8 +1584,8 @@
1526 self.ui.report['Hello'] = 'World'1584 self.ui.report['Hello'] = 'World'
15271585
1528 s = self.ui.get_complete_size()1586 s = self.ui.get_complete_size()
1529 self.assert_(s > 5)1587 self.assertTrue(s > 5)
1530 self.assert_(s < 100)1588 self.assertTrue(s < 100)
15311589
1532 self.assertEqual(s, self.ui.get_reduced_size())1590 self.assertEqual(s, self.ui.get_reduced_size())
15331591
@@ -1546,7 +1604,7 @@
1546 self.update_report_file()1604 self.update_report_file()
1547 self.ui.load_report(self.report_file.name)1605 self.ui.load_report(self.report_file.name)
15481606
1549 self.assert_(self.ui.report == None)1607 self.assertTrue(self.ui.report == None)
1550 self.assertEqual(self.ui.msg_title, _('Invalid problem report'))1608 self.assertEqual(self.ui.msg_title, _('Invalid problem report'))
1551 self.assertEqual(self.ui.msg_severity, 'info')1609 self.assertEqual(self.ui.msg_severity, 'info')
15521610
@@ -1563,7 +1621,7 @@
1563 self.report_file.flush()1621 self.report_file.flush()
15641622
1565 self.ui.load_report(self.report_file.name)1623 self.ui.load_report(self.report_file.name)
1566 self.assert_(self.ui.report == None)1624 self.assertTrue(self.ui.report == None)
1567 self.assertEqual(self.ui.msg_title, _('Invalid problem report'))1625 self.assertEqual(self.ui.msg_title, _('Invalid problem report'))
1568 self.assertEqual(self.ui.msg_severity, 'error')1626 self.assertEqual(self.ui.msg_severity, 'error')
15691627
@@ -1579,8 +1637,8 @@
15791637
1580 self.ui.restart()1638 self.ui.restart()
1581 time.sleep(1) # FIXME: race condition1639 time.sleep(1) # FIXME: race condition
1582 self.assert_(os.path.exists(p))1640 self.assertTrue(os.path.exists(p))
1583 self.assert_(not os.path.exists(r))1641 self.assertTrue(not os.path.exists(r))
1584 os.unlink(p)1642 os.unlink(p)
15851643
1586 # test with RespawnCommand1644 # test with RespawnCommand
@@ -1590,8 +1648,8 @@
15901648
1591 self.ui.restart()1649 self.ui.restart()
1592 time.sleep(1) # FIXME: race condition1650 time.sleep(1) # FIXME: race condition
1593 self.assert_(not os.path.exists(p))1651 self.assertTrue(not os.path.exists(p))
1594 self.assert_(os.path.exists(r))1652 self.assertTrue(os.path.exists(r))
1595 os.unlink(r)1653 os.unlink(r)
15961654
1597 # test that invalid command does not make us fall apart1655 # test that invalid command does not make us fall apart
@@ -1606,7 +1664,7 @@
1606 # report without any information (distro bug)1664 # report without any information (distro bug)
1607 self.ui.report = apport.Report()1665 self.ui.report = apport.Report()
1608 self.ui.collect_info()1666 self.ui.collect_info()
1609 self.assert_(set(['Date', 'Uname', 'DistroRelease', 'ProblemType']).issubset(1667 self.assertTrue(set(['Date', 'Uname', 'DistroRelease', 'ProblemType']).issubset(
1610 set(self.ui.report.keys())))1668 set(self.ui.report.keys())))
1611 self.assertEqual(self.ui.ic_progress_pulses, 0,1669 self.assertEqual(self.ui.ic_progress_pulses, 0,
1612 'no progress dialog for distro bug info collection')1670 'no progress dialog for distro bug info collection')
@@ -1624,10 +1682,10 @@
1624 self.ui.report['Fstab'] = ('/etc/fstab', True)1682 self.ui.report['Fstab'] = ('/etc/fstab', True)
1625 self.ui.report['CompressedValue'] = problem_report.CompressedValue('Test')1683 self.ui.report['CompressedValue'] = problem_report.CompressedValue('Test')
1626 self.ui.collect_info()1684 self.ui.collect_info()
1627 self.assert_(set(['SourcePackage', 'Package', 'ProblemType',1685 self.assertTrue(set(['SourcePackage', 'Package', 'ProblemType',
1628 'Uname', 'Dependencies', 'DistroRelease', 'Date',1686 'Uname', 'Dependencies', 'DistroRelease', 'Date',
1629 'ExecutablePath']).issubset(set(self.ui.report.keys())))1687 'ExecutablePath']).issubset(set(self.ui.report.keys())))
1630 self.assert_(self.ui.ic_progress_pulses > 0,1688 self.assertTrue(self.ui.ic_progress_pulses > 0,
1631 'progress dialog for package bug info collection')1689 'progress dialog for package bug info collection')
1632 self.assertEqual(self.ui.ic_progress_active, False,1690 self.assertEqual(self.ui.ic_progress_active, False,
1633 'progress dialog for package bug info collection finished')1691 'progress dialog for package bug info collection finished')
@@ -1639,10 +1697,10 @@
1639 self.ui.report = apport.Report()1697 self.ui.report = apport.Report()
1640 self.ui.cur_package = 'bash'1698 self.ui.cur_package = 'bash'
1641 self.ui.collect_info()1699 self.ui.collect_info()
1642 self.assert_(set(['SourcePackage', 'Package', 'ProblemType',1700 self.assertTrue(set(['SourcePackage', 'Package', 'ProblemType',
1643 'Uname', 'Dependencies', 'DistroRelease',1701 'Uname', 'Dependencies', 'DistroRelease',
1644 'Date']).issubset(set(self.ui.report.keys())))1702 'Date']).issubset(set(self.ui.report.keys())))
1645 self.assert_(self.ui.ic_progress_pulses > 0,1703 self.assertTrue(self.ui.ic_progress_pulses > 0,
1646 'progress dialog for package bug info collection')1704 'progress dialog for package bug info collection')
1647 self.assertEqual(self.ui.ic_progress_active, False,1705 self.assertEqual(self.ui.ic_progress_active, False,
1648 'progress dialog for package bug info collection finished')1706 'progress dialog for package bug info collection finished')
@@ -1699,13 +1757,13 @@
16991757
1700 self.assertEqual(self.ui.msg_severity, None)1758 self.assertEqual(self.ui.msg_severity, None)
1701 self.assertEqual(self.ui.msg_title, None)1759 self.assertEqual(self.ui.msg_title, None)
1702 self.assert_(self.ui.present_details_shown)1760 self.assertTrue(self.ui.present_details_shown)
1703 self.assertEqual(self.ui.opened_url, 'http://bash.bugs.example.com/%i' % self.ui.crashdb.latest_id())1761 self.assertEqual(self.ui.opened_url, 'http://bash.bugs.example.com/%i' % self.ui.crashdb.latest_id())
17041762
1705 self.assert_(self.ui.ic_progress_pulses > 0)1763 self.assertTrue(self.ui.ic_progress_pulses > 0)
1706 self.assertEqual(self.ui.report['SourcePackage'], 'bash')1764 self.assertEqual(self.ui.report['SourcePackage'], 'bash')
1707 self.assert_('Dependencies' in self.ui.report.keys())1765 self.assertTrue('Dependencies' in self.ui.report.keys())
1708 self.assert_('ProcEnviron' in self.ui.report.keys())1766 self.assertTrue('ProcEnviron' in self.ui.report.keys())
1709 self.assertEqual(self.ui.report['ProblemType'], 'Bug')1767 self.assertEqual(self.ui.report['ProblemType'], 'Bug')
17101768
1711 # should not crash on nonexisting package1769 # should not crash on nonexisting package
@@ -1715,8 +1773,8 @@
17151773
1716 self.assertEqual(self.ui.msg_severity, 'error')1774 self.assertEqual(self.ui.msg_severity, 'error')
17171775
1718 def test_run_report_bug_pid(self):1776 def test_run_report_bug_pid_tags(self):
1719 '''run_report_bug() for a pid.'''1777 '''run_report_bug() for a pid with extra tags.'''
17201778
1721 # fork a test process1779 # fork a test process
1722 pid = os.fork()1780 pid = os.fork()
@@ -1728,7 +1786,7 @@
17281786
1729 try:1787 try:
1730 # report a bug on cat process1788 # report a bug on cat process
1731 sys.argv = ['ui-test', '-f', '-P', str(pid)]1789 sys.argv = ['ui-test', '-f', '--tag', 'foo', '-P', str(pid)]
1732 self.ui = _TestSuiteUserInterface()1790 self.ui = _TestSuiteUserInterface()
1733 self.assertEqual(self.ui.run_argv(), True)1791 self.assertEqual(self.ui.run_argv(), True)
1734 finally:1792 finally:
@@ -1736,19 +1794,21 @@
1736 os.kill(pid, signal.SIGKILL)1794 os.kill(pid, signal.SIGKILL)
1737 os.waitpid(pid, 0)1795 os.waitpid(pid, 0)
17381796
1739 self.assert_('SourcePackage' in self.ui.report.keys())1797 self.assertTrue('SourcePackage' in self.ui.report.keys())
1740 self.assert_('Dependencies' in self.ui.report.keys())1798 self.assertTrue('Dependencies' in self.ui.report.keys())
1741 self.assert_('ProcMaps' in self.ui.report.keys())1799 self.assertTrue('ProcMaps' in self.ui.report.keys())
1742 self.assertEqual(self.ui.report['ExecutablePath'], '/bin/sleep')1800 self.assertEqual(self.ui.report['ExecutablePath'], '/bin/sleep')
1743 self.failIf(self.ui.report.has_key('ProcCmdline')) # privacy!1801 self.assertFalse(self.ui.report.has_key('ProcCmdline')) # privacy!
1744 self.assert_('ProcEnviron' in self.ui.report.keys())1802 self.assertTrue('ProcEnviron' in self.ui.report.keys())
1745 self.assertEqual(self.ui.report['ProblemType'], 'Bug')1803 self.assertEqual(self.ui.report['ProblemType'], 'Bug')
1804 self.assertTrue('Tags' in self.ui.report.keys())
1805 self.assertTrue('foo' in self.ui.report['Tags'])
17461806
1747 self.assertEqual(self.ui.msg_severity, None)1807 self.assertEqual(self.ui.msg_severity, None)
1748 self.assertEqual(self.ui.msg_title, None)1808 self.assertEqual(self.ui.msg_title, None)
1749 self.assertEqual(self.ui.opened_url, 'http://coreutils.bugs.example.com/%i' % self.ui.crashdb.latest_id())1809 self.assertEqual(self.ui.opened_url, 'http://coreutils.bugs.example.com/%i' % self.ui.crashdb.latest_id())
1750 self.assert_(self.ui.present_details_shown)1810 self.assertTrue(self.ui.present_details_shown)
1751 self.assert_(self.ui.ic_progress_pulses > 0)1811 self.assertTrue(self.ui.ic_progress_pulses > 0)
17521812
1753 @classmethod1813 @classmethod
1754 def _find_unused_pid(klass):1814 def _find_unused_pid(klass):
@@ -1759,7 +1819,7 @@
1759 pid += 11819 pid += 1
1760 try:1820 try:
1761 os.kill(pid, 0)1821 os.kill(pid, 0)
1762 except OSError, e:1822 except OSError as e:
1763 if e.errno == errno.ESRCH:1823 if e.errno == errno.ESRCH:
1764 break1824 break
1765 return pid1825 return pid
@@ -1792,7 +1852,7 @@
1792 (fd, exename) = tempfile.mkstemp()1852 (fd, exename) = tempfile.mkstemp()
1793 os.write(fd, open('/bin/cat').read())1853 os.write(fd, open('/bin/cat').read())
1794 os.close(fd)1854 os.close(fd)
1795 os.chmod(exename, 0755)1855 os.chmod(exename, 0o755)
17961856
1797 # unpackaged test process1857 # unpackaged test process
1798 pid = os.fork()1858 pid = os.fork()
@@ -1810,6 +1870,25 @@
18101870
1811 self.assertEqual(self.ui.msg_severity, 'error')1871 self.assertEqual(self.ui.msg_severity, 'error')
18121872
1873 def test_run_report_bug_kernel_thread(self):
1874 '''run_report_bug() for a pid of a kernel thread.'''
1875
1876 import glob
1877 pid = None
1878 for path in glob.glob('/proc/[0-9]*/stat'):
1879 stat = open(path).read().split()
1880 flags = int(stat[8])
1881 if flags & PF_KTHREAD:
1882 pid = int(stat[0])
1883 break
1884
1885 self.assertFalse(pid is None)
1886 sys.argv = ['ui-test', '-f', '-P', str(pid)]
1887 self.ui = _TestSuiteUserInterface()
1888 self.ui.run_argv()
1889
1890 self.assertTrue(self.ui.report['Package'].startswith(apport.packaging.get_kernel_package()))
1891
1813 def test_run_report_bug_file(self):1892 def test_run_report_bug_file(self):
1814 '''run_report_bug() with saving report into a file.'''1893 '''run_report_bug() with saving report into a file.'''
18151894
@@ -1824,16 +1903,16 @@
1824 self.assertEqual(self.ui.msg_severity, None)1903 self.assertEqual(self.ui.msg_severity, None)
1825 self.assertEqual(self.ui.msg_title, None)1904 self.assertEqual(self.ui.msg_title, None)
1826 self.assertEqual(self.ui.opened_url, None)1905 self.assertEqual(self.ui.opened_url, None)
1827 self.failIf(self.ui.present_details_shown)1906 self.assertFalse(self.ui.present_details_shown)
18281907
1829 self.assert_(self.ui.ic_progress_pulses > 0)1908 self.assertTrue(self.ui.ic_progress_pulses > 0)
18301909
1831 r = apport.Report()1910 r = apport.Report()
1832 r.load(open(reportfile))1911 r.load(open(reportfile))
18331912
1834 self.assertEqual(r['SourcePackage'], 'bash')1913 self.assertEqual(r['SourcePackage'], 'bash')
1835 self.assert_('Dependencies' in r.keys())1914 self.assertTrue('Dependencies' in r.keys())
1836 self.assert_('ProcEnviron' in r.keys())1915 self.assertTrue('ProcEnviron' in r.keys())
1837 self.assertEqual(r['ProblemType'], 'Bug')1916 self.assertEqual(r['ProblemType'], 'Bug')
18381917
1839 # report it1918 # report it
@@ -1845,7 +1924,7 @@
18451924
1846 self.assertEqual(self.ui.msg_text, None)1925 self.assertEqual(self.ui.msg_text, None)
1847 self.assertEqual(self.ui.msg_severity, None)1926 self.assertEqual(self.ui.msg_severity, None)
1848 self.assert_(self.ui.present_details_shown)1927 self.assertTrue(self.ui.present_details_shown)
18491928
1850 def _gen_test_crash(self):1929 def _gen_test_crash(self):
1851 '''Generate a Report with real crash data.'''1930 '''Generate a Report with real crash data.'''
@@ -1895,7 +1974,7 @@
1895 self.assertEqual(self.ui.msg_title, None)1974 self.assertEqual(self.ui.msg_title, None)
1896 self.assertEqual(self.ui.opened_url, None)1975 self.assertEqual(self.ui.opened_url, None)
1897 self.assertEqual(self.ui.ic_progress_pulses, 0)1976 self.assertEqual(self.ui.ic_progress_pulses, 0)
1898 self.failIf(self.ui.present_details_shown)1977 self.assertFalse(self.ui.present_details_shown)
18991978
1900 # report in crash notification dialog, cancel details report1979 # report in crash notification dialog, cancel details report
1901 r.write(open(report_file, 'w'))1980 r.write(open(report_file, 'w'))
@@ -1908,7 +1987,7 @@
1908 self.assertEqual(self.ui.msg_title, None)1987 self.assertEqual(self.ui.msg_title, None)
1909 self.assertEqual(self.ui.opened_url, None)1988 self.assertEqual(self.ui.opened_url, None)
1910 self.assertNotEqual(self.ui.ic_progress_pulses, 0)1989 self.assertNotEqual(self.ui.ic_progress_pulses, 0)
1911 self.assert_(self.ui.present_details_shown)1990 self.assertTrue(self.ui.present_details_shown)
19121991
1913 # report in crash notification dialog, send full report1992 # report in crash notification dialog, send full report
1914 r.write(open(report_file, 'w'))1993 r.write(open(report_file, 'w'))
@@ -1920,15 +1999,15 @@
1920 self.assertEqual(self.ui.msg_title, None)1999 self.assertEqual(self.ui.msg_title, None)
1921 self.assertEqual(self.ui.opened_url, 'http://coreutils.bugs.example.com/%i' % self.ui.crashdb.latest_id())2000 self.assertEqual(self.ui.opened_url, 'http://coreutils.bugs.example.com/%i' % self.ui.crashdb.latest_id())
1922 self.assertNotEqual(self.ui.ic_progress_pulses, 0)2001 self.assertNotEqual(self.ui.ic_progress_pulses, 0)
1923 self.assert_(self.ui.present_details_shown)2002 self.assertTrue(self.ui.present_details_shown)
19242003
1925 self.assert_('SourcePackage' in self.ui.report.keys())2004 self.assertTrue('SourcePackage' in self.ui.report.keys())
1926 self.assert_('Dependencies' in self.ui.report.keys())2005 self.assertTrue('Dependencies' in self.ui.report.keys())
1927 self.assert_('Stacktrace' in self.ui.report.keys())2006 self.assertTrue('Stacktrace' in self.ui.report.keys())
1928 self.assert_('ProcEnviron' in self.ui.report.keys())2007 self.assertTrue('ProcEnviron' in self.ui.report.keys())
1929 self.assertEqual(self.ui.report['ProblemType'], 'Crash')2008 self.assertEqual(self.ui.report['ProblemType'], 'Crash')
1930 self.assert_(len(self.ui.report['CoreDump']) > 10000)2009 self.assertTrue(len(self.ui.report['CoreDump']) > 10000)
1931 self.assert_(self.ui.report['Title'].startswith('cat crashed with SIGSEGV'))2010 self.assertTrue(self.ui.report['Title'].startswith('cat crashed with SIGSEGV'))
19322011
1933 # report in crash notification dialog, send reduced report2012 # report in crash notification dialog, send reduced report
1934 r.write(open(report_file, 'w'))2013 r.write(open(report_file, 'w'))
@@ -1940,16 +2019,16 @@
1940 self.assertEqual(self.ui.msg_title, None)2019 self.assertEqual(self.ui.msg_title, None)
1941 self.assertEqual(self.ui.opened_url, 'http://coreutils.bugs.example.com/%i' % self.ui.crashdb.latest_id())2020 self.assertEqual(self.ui.opened_url, 'http://coreutils.bugs.example.com/%i' % self.ui.crashdb.latest_id())
1942 self.assertNotEqual(self.ui.ic_progress_pulses, 0)2021 self.assertNotEqual(self.ui.ic_progress_pulses, 0)
1943 self.assert_(self.ui.present_details_shown)2022 self.assertTrue(self.ui.present_details_shown)
19442023
1945 self.assert_('SourcePackage' in self.ui.report.keys())2024 self.assertTrue('SourcePackage' in self.ui.report.keys())
1946 self.assert_('Dependencies' in self.ui.report.keys())2025 self.assertTrue('Dependencies' in self.ui.report.keys())
1947 self.assert_('Stacktrace' in self.ui.report.keys())2026 self.assertTrue('Stacktrace' in self.ui.report.keys())
1948 self.assertEqual(self.ui.report['ProblemType'], 'Crash')2027 self.assertEqual(self.ui.report['ProblemType'], 'Crash')
1949 self.assert_(not self.ui.report.has_key('CoreDump'))2028 self.assertTrue(not self.ui.report.has_key('CoreDump'))
19502029
1951 # so far we did not blacklist, verify that2030 # so far we did not blacklist, verify that
1952 self.assert_(not self.ui.report.check_ignored())2031 self.assertTrue(not self.ui.report.check_ignored())
19532032
1954 # cancel crash notification dialog and blacklist2033 # cancel crash notification dialog and blacklist
1955 r.write(open(report_file, 'w'))2034 r.write(open(report_file, 'w'))
@@ -1961,23 +2040,32 @@
1961 self.assertEqual(self.ui.opened_url, None)2040 self.assertEqual(self.ui.opened_url, None)
1962 self.assertEqual(self.ui.ic_progress_pulses, 0)2041 self.assertEqual(self.ui.ic_progress_pulses, 0)
19632042
1964 self.assert_(self.ui.report.check_ignored())2043 self.assertTrue(self.ui.report.check_ignored())
19652044
1966 def test_run_crash_abort(self):2045 def test_run_crash_abort(self):
1967 '''run_crash() for an unreportable abort()'''2046 '''run_crash() for an abort() without assertion message'''
19682047
1969 self.report['Signal'] = '6'2048 r = self._gen_test_crash()
1970 self.report['ExecutablePath'] = '/bin/bash'2049 r['Signal'] = '6'
1971 self.report['Package'] = 'bash 1'2050 report_file = os.path.join(apport.fileutils.report_dir, 'test.crash')
1972 self.update_report_file()2051 r.write(open(report_file, 'w'))
2052
1973 self.ui.present_crash_response = {'action': 'report', 'blacklist': False }2053 self.ui.present_crash_response = {'action': 'report', 'blacklist': False }
1974 self.ui.present_details_response = 'full'2054 self.ui.present_details_response = 'full'
1975 self.ui.run_crash(self.report_file.name)2055 self.ui.run_crash(report_file)
19762056
1977 self.assert_('assert' in self.ui.msg_text, '%s: %s' %2057 self.assertTrue('SourcePackage' in self.ui.report.keys())
1978 (self.ui.msg_title, self.ui.msg_text))2058 self.assertTrue('Dependencies' in self.ui.report.keys())
1979 self.assertEqual(self.ui.msg_severity, 'info')2059 self.assertTrue('Stacktrace' in self.ui.report.keys())
1980 self.failIf(self.ui.present_details_shown)2060 self.assertTrue('ProcEnviron' in self.ui.report.keys())
2061 self.assertEqual(self.ui.report['Signal'], '6')
2062
2063 # we disable the ABRT filtering, we want these crashes after all
2064 #self.assertTrue('assert' in self.ui.msg_text, '%s: %s' %
2065 # (self.ui.msg_title, self.ui.msg_text))
2066 #self.assertEqual(self.ui.msg_severity, 'info')
2067 self.assertEqual(self.ui.msg_severity, None)
2068 self.assertTrue(self.ui.present_details_shown)
19812069
1982 def test_run_crash_argv_file(self):2070 def test_run_crash_argv_file(self):
1983 '''run_crash() through a file specified on the command line.'''2071 '''run_crash() through a file specified on the command line.'''
@@ -1994,21 +2082,21 @@
19942082
1995 self.assertEqual(self.ui.msg_text, None)2083 self.assertEqual(self.ui.msg_text, None)
1996 self.assertEqual(self.ui.msg_severity, None)2084 self.assertEqual(self.ui.msg_severity, None)
1997 self.assert_(self.ui.present_details_shown)2085 self.assertTrue(self.ui.present_details_shown)
19982086
1999 # unreportable2087 # unreportable
2000 self.report['Package'] = 'bash'2088 self.report['Package'] = 'bash'
2001 self.report['UnreportableReason'] = 'It stinks.'2089 self.report['UnreportableReason'] = u'It stinks. \u2665'
2002 self.update_report_file()2090 self.update_report_file()
20032091
2004 sys.argv = ['ui-test', '-c', self.report_file.name]2092 sys.argv = ['ui-test', '-c', self.report_file.name]
2005 self.ui = _TestSuiteUserInterface()2093 self.ui = _TestSuiteUserInterface()
2006 self.assertEqual(self.ui.run_argv(), True)2094 self.assertEqual(self.ui.run_argv(), True)
20072095
2008 self.assert_('It stinks.' in self.ui.msg_text, '%s: %s' %2096 self.assertTrue('It stinks.' in self.ui.msg_text, '%s: %s' %
2009 (self.ui.msg_title, self.ui.msg_text))2097 (self.ui.msg_title, self.ui.msg_text))
2010 self.assertEqual(self.ui.msg_severity, 'info')2098 self.assertEqual(self.ui.msg_severity, 'info')
2011 self.failIf(self.ui.present_details_shown)2099 self.assertFalse(self.ui.present_details_shown)
20122100
2013 # should not die with an exception on an invalid name2101 # should not die with an exception on an invalid name
2014 sys.argv = ['ui-test', '-c', '/nonexisting.crash' ]2102 sys.argv = ['ui-test', '-c', '/nonexisting.crash' ]
@@ -2029,7 +2117,7 @@
20292117
2030 self.ui.run_crash(self.report_file.name)2118 self.ui.run_crash(self.report_file.name)
20312119
2032 self.assert_('It stinks.' in self.ui.msg_text, '%s: %s' %2120 self.assertTrue('It stinks.' in self.ui.msg_text, '%s: %s' %
2033 (self.ui.msg_title, self.ui.msg_text))2121 (self.ui.msg_title, self.ui.msg_text))
2034 self.assertEqual(self.ui.msg_severity, 'info')2122 self.assertEqual(self.ui.msg_severity, 'info')
20352123
@@ -2077,7 +2165,7 @@
2077 self.ui = _TestSuiteUserInterface()2165 self.ui = _TestSuiteUserInterface()
2078 self.ui.run_crash(report_file)2166 self.ui.run_crash(report_file)
2079 self.assertEqual(self.ui.msg_severity, 'error')2167 self.assertEqual(self.ui.msg_severity, 'error')
2080 self.assert_('memory' in self.ui.msg_text, '%s: %s' %2168 self.assertTrue('memory' in self.ui.msg_text, '%s: %s' %
2081 (self.ui.msg_title, self.ui.msg_text))2169 (self.ui.msg_title, self.ui.msg_text))
20822170
2083 def test_run_crash_preretraced(self):2171 def test_run_crash_preretraced(self):
@@ -2106,7 +2194,7 @@
2106 self.assertEqual(self.ui.msg_title, None)2194 self.assertEqual(self.ui.msg_title, None)
2107 self.assertEqual(self.ui.opened_url, None)2195 self.assertEqual(self.ui.opened_url, None)
2108 self.assertEqual(self.ui.ic_progress_pulses, 0)2196 self.assertEqual(self.ui.ic_progress_pulses, 0)
2109 self.assert_(self.ui.present_details_shown)2197 self.assertTrue(self.ui.present_details_shown)
2110 2198
2111 def test_run_crash_errors(self):2199 def test_run_crash_errors(self):
2112 '''run_crash() on various error conditions.'''2200 '''run_crash() on various error conditions.'''
@@ -2185,7 +2273,7 @@
2185 self.assertEqual(self.ui.msg_title, None)2273 self.assertEqual(self.ui.msg_title, None)
2186 self.assertEqual(self.ui.opened_url, None)2274 self.assertEqual(self.ui.opened_url, None)
2187 self.assertEqual(self.ui.ic_progress_pulses, 0)2275 self.assertEqual(self.ui.ic_progress_pulses, 0)
2188 self.failIf(self.ui.present_details_shown)2276 self.assertFalse(self.ui.present_details_shown)
21892277
2190 # report in crash notification dialog, send report2278 # report in crash notification dialog, send report
2191 r.write(open(report_file, 'w'))2279 r.write(open(report_file, 'w'))
@@ -2196,16 +2284,16 @@
2196 self.assertEqual(self.ui.msg_severity, None)2284 self.assertEqual(self.ui.msg_severity, None)
2197 self.assertEqual(self.ui.msg_title, None)2285 self.assertEqual(self.ui.msg_title, None)
2198 self.assertEqual(self.ui.opened_url, 'http://bash.bugs.example.com/%i' % self.ui.crashdb.latest_id())2286 self.assertEqual(self.ui.opened_url, 'http://bash.bugs.example.com/%i' % self.ui.crashdb.latest_id())
2199 self.assert_(self.ui.present_details_shown)2287 self.assertTrue(self.ui.present_details_shown)
22002288
2201 self.assert_('SourcePackage' in self.ui.report.keys())2289 self.assertTrue('SourcePackage' in self.ui.report.keys())
2202 self.assert_('Package' in self.ui.report.keys())2290 self.assertTrue('Package' in self.ui.report.keys())
2203 self.assertEqual(self.ui.report['ProblemType'], 'Package')2291 self.assertEqual(self.ui.report['ProblemType'], 'Package')
22042292
2205 # verify that additional information has been collected2293 # verify that additional information has been collected
2206 self.assert_('Architecture' in self.ui.report.keys())2294 self.assertTrue('Architecture' in self.ui.report.keys())
2207 self.assert_('DistroRelease' in self.ui.report.keys())2295 self.assertTrue('DistroRelease' in self.ui.report.keys())
2208 self.assert_('Uname' in self.ui.report.keys())2296 self.assertTrue('Uname' in self.ui.report.keys())
22092297
2210 def test_run_crash_kernel(self):2298 def test_run_crash_kernel(self):
2211 '''run_crash() for a kernel error.'''2299 '''run_crash() for a kernel error.'''
@@ -2235,7 +2323,7 @@
2235 self.assertEqual(self.ui.msg_title, None)2323 self.assertEqual(self.ui.msg_title, None)
2236 self.assertEqual(self.ui.opened_url, None)2324 self.assertEqual(self.ui.opened_url, None)
2237 self.assertEqual(self.ui.ic_progress_pulses, 0)2325 self.assertEqual(self.ui.ic_progress_pulses, 0)
2238 self.failIf(self.ui.present_details_shown)2326 self.assertFalse(self.ui.present_details_shown)
22392327
2240 # report in crash notification dialog, send report2328 # report in crash notification dialog, send report
2241 r.write(open(report_file, 'w'))2329 r.write(open(report_file, 'w'))
@@ -2247,11 +2335,11 @@
2247 ' ' + str(self.ui.msg_text))2335 ' ' + str(self.ui.msg_text))
2248 self.assertEqual(self.ui.msg_title, None)2336 self.assertEqual(self.ui.msg_title, None)
2249 self.assertEqual(self.ui.opened_url, 'http://linux.bugs.example.com/%i' % self.ui.crashdb.latest_id())2337 self.assertEqual(self.ui.opened_url, 'http://linux.bugs.example.com/%i' % self.ui.crashdb.latest_id())
2250 self.assert_(self.ui.present_details_shown)2338 self.assertTrue(self.ui.present_details_shown)
22512339
2252 self.assert_('SourcePackage' in self.ui.report.keys())2340 self.assertTrue('SourcePackage' in self.ui.report.keys())
2253 # did we run the hooks properly?2341 # did we run the hooks properly?
2254 self.assert_('KernelDebug' in self.ui.report.keys())2342 self.assertTrue('KernelDebug' in self.ui.report.keys())
2255 self.assertEqual(self.ui.report['ProblemType'], 'KernelCrash')2343 self.assertEqual(self.ui.report['ProblemType'], 'KernelCrash')
22562344
2257 def test_run_crash_anonymity(self):2345 def test_run_crash_anonymity(self):
@@ -2265,7 +2353,7 @@
2265 self.ui.present_details_response = 'cancel'2353 self.ui.present_details_response = 'cancel'
2266 self.ui.run_crash(report_file)2354 self.ui.run_crash(report_file)
22672355
2268 self.failIf('ProcCwd' in self.ui.report)2356 self.assertFalse('ProcCwd' in self.ui.report)
22692357
2270 dump = StringIO()2358 dump = StringIO()
2271 self.ui.report.write(dump)2359 self.ui.report.write(dump)
@@ -2274,7 +2362,7 @@
2274 bad_strings = [os.uname()[1], p[0], p[4], p[5], os.getcwd()]2362 bad_strings = [os.uname()[1], p[0], p[4], p[5], os.getcwd()]
22752363
2276 for s in bad_strings:2364 for s in bad_strings:
2277 self.failIf(s in dump.getvalue(), 'dump contains sensitive string: %s' % s)2365 self.assertFalse(s in dump.getvalue(), 'dump contains sensitive string: %s' % s)
22782366
2279 def test_run_update_report_nonexisting_package_from_bug(self):2367 def test_run_update_report_nonexisting_package_from_bug(self):
2280 '''run_update_report() on a nonexisting package (from bug).'''2368 '''run_update_report() on a nonexisting package (from bug).'''
@@ -2285,9 +2373,9 @@
2285 '', {'dummy_data': 1})2373 '', {'dummy_data': 1})
22862374
2287 self.assertEqual(self.ui.run_argv(), False)2375 self.assertEqual(self.ui.run_argv(), False)
2288 self.assert_('No additional information collected.' in2376 self.assertTrue('No additional information collected.' in
2289 self.ui.msg_text)2377 self.ui.msg_text)
2290 self.failIf(self.ui.present_details_shown)2378 self.assertFalse(self.ui.present_details_shown)
22912379
2292 def test_run_update_report_nonexisting_package_cli(self):2380 def test_run_update_report_nonexisting_package_cli(self):
2293 '''run_update_report() on a nonexisting package (CLI argument).'''2381 '''run_update_report() on a nonexisting package (CLI argument).'''
@@ -2298,9 +2386,9 @@
2298 '', {'dummy_data': 1})2386 '', {'dummy_data': 1})
22992387
2300 self.assertEqual(self.ui.run_argv(), False)2388 self.assertEqual(self.ui.run_argv(), False)
2301 self.assert_('No additional information collected.' in2389 self.assertTrue('No additional information collected.' in
2302 self.ui.msg_text)2390 self.ui.msg_text)
2303 self.failIf(self.ui.present_details_shown)2391 self.assertFalse(self.ui.present_details_shown)
23042392
2305 def test_run_update_report_existing_package_from_bug(self):2393 def test_run_update_report_existing_package_from_bug(self):
2306 '''run_update_report() on an existing package (from bug).'''2394 '''run_update_report() on an existing package (from bug).'''
@@ -2316,17 +2404,17 @@
2316 self.assertEqual(self.ui.msg_severity, None, self.ui.msg_text)2404 self.assertEqual(self.ui.msg_severity, None, self.ui.msg_text)
2317 self.assertEqual(self.ui.msg_title, None)2405 self.assertEqual(self.ui.msg_title, None)
2318 self.assertEqual(self.ui.opened_url, None)2406 self.assertEqual(self.ui.opened_url, None)
2319 self.assert_(self.ui.present_details_shown)2407 self.assertTrue(self.ui.present_details_shown)
23202408
2321 self.assert_(self.ui.ic_progress_pulses > 0)2409 self.assertTrue(self.ui.ic_progress_pulses > 0)
2322 self.assert_(self.ui.report['Package'].startswith('bash '))2410 self.assertTrue(self.ui.report['Package'].startswith('bash '))
2323 self.assert_('Dependencies' in self.ui.report.keys())2411 self.assertTrue('Dependencies' in self.ui.report.keys())
2324 self.assert_('ProcEnviron' in self.ui.report.keys())2412 self.assertTrue('ProcEnviron' in self.ui.report.keys())
23252413
2326 def test_run_update_report_existing_package_cli(self):2414 def test_run_update_report_existing_package_cli_tags(self):
2327 '''run_update_report() on an existing package (CLI argument).'''2415 '''run_update_report() on an existing package (CLI argument) with extra tag'''
23282416
2329 sys.argv = ['ui-test', '-u', '1', '-p', 'bash']2417 sys.argv = ['ui-test', '-u', '1', '-p', 'bash', '--tag', 'foo']
2330 self.ui = _TestSuiteUserInterface()2418 self.ui = _TestSuiteUserInterface()
2331 self.ui.crashdb = apport.crashdb_impl.memory.CrashDatabase(None,2419 self.ui.crashdb = apport.crashdb_impl.memory.CrashDatabase(None,
2332 '', {'dummy_data': 1})2420 '', {'dummy_data': 1})
@@ -2335,12 +2423,13 @@
2335 self.assertEqual(self.ui.msg_severity, None, self.ui.msg_text)2423 self.assertEqual(self.ui.msg_severity, None, self.ui.msg_text)
2336 self.assertEqual(self.ui.msg_title, None)2424 self.assertEqual(self.ui.msg_title, None)
2337 self.assertEqual(self.ui.opened_url, None)2425 self.assertEqual(self.ui.opened_url, None)
2338 self.assert_(self.ui.present_details_shown)2426 self.assertTrue(self.ui.present_details_shown)
23392427
2340 self.assert_(self.ui.ic_progress_pulses > 0)2428 self.assertTrue(self.ui.ic_progress_pulses > 0)
2341 self.assert_(self.ui.report['Package'].startswith('bash '))2429 self.assertTrue(self.ui.report['Package'].startswith('bash '))
2342 self.assert_('Dependencies' in self.ui.report.keys())2430 self.assertTrue('Dependencies' in self.ui.report.keys())
2343 self.assert_('ProcEnviron' in self.ui.report.keys())2431 self.assertTrue('ProcEnviron' in self.ui.report.keys())
2432 self.assertTrue('foo' in self.ui.report['Tags'])
23442433
2345 def test_run_update_report_existing_package_cli_cmdname(self):2434 def test_run_update_report_existing_package_cli_cmdname(self):
2346 '''run_update_report() on an existing package (-collect program).'''2435 '''run_update_report() on an existing package (-collect program).'''
@@ -2354,12 +2443,12 @@
2354 self.assertEqual(self.ui.msg_severity, None, self.ui.msg_text)2443 self.assertEqual(self.ui.msg_severity, None, self.ui.msg_text)
2355 self.assertEqual(self.ui.msg_title, None)2444 self.assertEqual(self.ui.msg_title, None)
2356 self.assertEqual(self.ui.opened_url, None)2445 self.assertEqual(self.ui.opened_url, None)
2357 self.assert_(self.ui.present_details_shown)2446 self.assertTrue(self.ui.present_details_shown)
23582447
2359 self.assert_(self.ui.ic_progress_pulses > 0)2448 self.assertTrue(self.ui.ic_progress_pulses > 0)
2360 self.assert_(self.ui.report['Package'].startswith('bash '))2449 self.assertTrue(self.ui.report['Package'].startswith('bash '))
2361 self.assert_('Dependencies' in self.ui.report.keys())2450 self.assertTrue('Dependencies' in self.ui.report.keys())
2362 self.assert_('ProcEnviron' in self.ui.report.keys())2451 self.assertTrue('ProcEnviron' in self.ui.report.keys())
23632452
2364 def test_run_update_report_noninstalled_but_hook(self):2453 def test_run_update_report_noninstalled_but_hook(self):
2365 '''run_update_report() on an uninstalled package with a source hook.'''2454 '''run_update_report() on an uninstalled package with a source hook.'''
@@ -2377,12 +2466,12 @@
2377 self.assertEqual(self.ui.msg_severity, None, self.ui.msg_text)2466 self.assertEqual(self.ui.msg_severity, None, self.ui.msg_text)
2378 self.assertEqual(self.ui.msg_title, None)2467 self.assertEqual(self.ui.msg_title, None)
2379 self.assertEqual(self.ui.opened_url, None)2468 self.assertEqual(self.ui.opened_url, None)
2380 self.assert_(self.ui.present_details_shown)2469 self.assertTrue(self.ui.present_details_shown)
23812470
2382 self.assert_(self.ui.ic_progress_pulses > 0)2471 self.assertTrue(self.ui.ic_progress_pulses > 0)
2383 self.assertEqual(self.ui.report['Package'], 'foo (not installed)')2472 self.assertEqual(self.ui.report['Package'], 'foo (not installed)')
2384 self.assertEqual(self.ui.report['MachineType'], 'Laptop')2473 self.assertEqual(self.ui.report['MachineType'], 'Laptop')
2385 self.assert_('ProcEnviron' in self.ui.report.keys())2474 self.assertTrue('ProcEnviron' in self.ui.report.keys())
23862475
2387 def _run_hook(self, code):2476 def _run_hook(self, code):
2388 f = open(os.path.join(self.hookdir, 'coreutils.py'), 'w')2477 f = open(os.path.join(self.hookdir, 'coreutils.py'), 'w')
@@ -2478,12 +2567,12 @@
2478 sys.argv = ['ui-test', '-s', 'foobar' ]2567 sys.argv = ['ui-test', '-s', 'foobar' ]
2479 self.ui = _TestSuiteUserInterface()2568 self.ui = _TestSuiteUserInterface()
2480 self.assertEqual(self.ui.run_argv(), True)2569 self.assertEqual(self.ui.run_argv(), True)
2481 self.assert_('foobar" is not known' in self.ui.msg_text)2570 self.assertTrue('foobar" is not known' in self.ui.msg_text)
2482 self.assertEqual(self.ui.msg_severity, 'error')2571 self.assertEqual(self.ui.msg_severity, 'error')
24832572
2484 # does not determine package2573 # does not determine package
2485 f = open(os.path.join(symptom_script_dir, 'nopkg.py'), 'w')2574 f = open(os.path.join(symptom_script_dir, 'nopkg.py'), 'w')
2486 print >> f, 'def run(report, ui):\n pass'2575 f.write('def run(report, ui):\n pass\n')
2487 f.close()2576 f.close()
2488 orig_stderr = sys.stderr2577 orig_stderr = sys.stderr
2489 sys.argv = ['ui-test', '-s', 'nopkg' ]2578 sys.argv = ['ui-test', '-s', 'nopkg' ]
@@ -2492,11 +2581,11 @@
2492 self.assertRaises(SystemExit, self.ui.run_argv)2581 self.assertRaises(SystemExit, self.ui.run_argv)
2493 err = sys.stderr.getvalue()2582 err = sys.stderr.getvalue()
2494 sys.stderr = orig_stderr2583 sys.stderr = orig_stderr
2495 self.assert_('did not determine the affected package' in err)2584 self.assertTrue('did not determine the affected package' in err)
24962585
2497 # does not define run()2586 # does not define run()
2498 f = open(os.path.join(symptom_script_dir, 'norun.py'), 'w')2587 f = open(os.path.join(symptom_script_dir, 'norun.py'), 'w')
2499 print >> f, 'def something(x, y):\n return 1'2588 f.write('def something(x, y):\n return 1\n')
2500 f.close()2589 f.close()
2501 sys.argv = ['ui-test', '-s', 'norun' ]2590 sys.argv = ['ui-test', '-s', 'norun' ]
2502 self.ui = _TestSuiteUserInterface()2591 self.ui = _TestSuiteUserInterface()
@@ -2504,11 +2593,11 @@
2504 self.assertRaises(SystemExit, self.ui.run_argv)2593 self.assertRaises(SystemExit, self.ui.run_argv)
2505 err = sys.stderr.getvalue()2594 err = sys.stderr.getvalue()
2506 sys.stderr = orig_stderr2595 sys.stderr = orig_stderr
2507 self.assert_('norun.py crashed:' in err)2596 self.assertTrue('norun.py crashed:' in err)
25082597
2509 # crashing script2598 # crashing script
2510 f = open(os.path.join(symptom_script_dir, 'crash.py'), 'w')2599 f = open(os.path.join(symptom_script_dir, 'crash.py'), 'w')
2511 print >> f, 'def run(report, ui):\n return 1/0'2600 f.write('def run(report, ui):\n return 1/0\n')
2512 f.close()2601 f.close()
2513 sys.argv = ['ui-test', '-s', 'crash' ]2602 sys.argv = ['ui-test', '-s', 'crash' ]
2514 self.ui = _TestSuiteUserInterface()2603 self.ui = _TestSuiteUserInterface()
@@ -2516,45 +2605,56 @@
2516 self.assertRaises(SystemExit, self.ui.run_argv)2605 self.assertRaises(SystemExit, self.ui.run_argv)
2517 err = sys.stderr.getvalue()2606 err = sys.stderr.getvalue()
2518 sys.stderr = orig_stderr2607 sys.stderr = orig_stderr
2519 self.assert_('crash.py crashed:' in err)2608 self.assertTrue('crash.py crashed:' in err)
2520 self.assert_('ZeroDivisionError:' in err)2609 self.assertTrue('ZeroDivisionError:' in err)
25212610
2522 # working noninteractive script2611 # working noninteractive script
2523 f = open(os.path.join(symptom_script_dir, 'itching.py'), 'w')2612 f = open(os.path.join(symptom_script_dir, 'itching.py'), 'w')
2524 print >> f, 'def run(report, ui):\n report["itch"] = "scratch"\n return "bash"'2613 f.write('def run(report, ui):\n report["itch"] = "scratch"\n return "bash"\n')
2525 f.close()2614 f.close()
2526 sys.argv = ['ui-test', '-s', 'itching' ]2615 sys.argv = ['ui-test', '-s', 'itching' ]
2527 self.ui = _TestSuiteUserInterface()2616 self.ui = _TestSuiteUserInterface()
2528 self.assertEqual(self.ui.run_argv(), True)2617 self.assertEqual(self.ui.run_argv(), True)
2529 self.assertEqual(self.ui.msg_text, None)2618 self.assertEqual(self.ui.msg_text, None)
2530 self.assertEqual(self.ui.msg_severity, None)2619 self.assertEqual(self.ui.msg_severity, None)
2531 self.assert_(self.ui.present_details_shown)2620 self.assertTrue(self.ui.present_details_shown)
25322621
2533 self.assertEqual(self.ui.report['itch'], 'scratch')2622 self.assertEqual(self.ui.report['itch'], 'scratch')
2534 self.assert_('DistroRelease' in self.ui.report)2623 self.assertTrue('DistroRelease' in self.ui.report)
2535 self.assertEqual(self.ui.report['SourcePackage'], 'bash')2624 self.assertEqual(self.ui.report['SourcePackage'], 'bash')
2536 self.assert_(self.ui.report['Package'].startswith('bash '))2625 self.assertTrue(self.ui.report['Package'].startswith('bash '))
2537 self.assertEqual(self.ui.report['ProblemType'], 'Bug')2626 self.assertEqual(self.ui.report['ProblemType'], 'Bug')
25382627
2628 # working noninteractive script with extra tag
2629 sys.argv = ['ui-test', '--tag', 'foo', '-s', 'itching' ]
2630 self.ui = _TestSuiteUserInterface()
2631 self.assertEqual(self.ui.run_argv(), True)
2632 self.assertEqual(self.ui.msg_text, None)
2633 self.assertEqual(self.ui.msg_severity, None)
2634 self.assertTrue(self.ui.present_details_shown)
2635
2636 self.assertEqual(self.ui.report['itch'], 'scratch')
2637 self.assertTrue('foo' in self.ui.report['Tags'])
2638
2539 # working interactive script2639 # working interactive script
2540 f = open(os.path.join(symptom_script_dir, 'itching.py'), 'w')2640 f = open(os.path.join(symptom_script_dir, 'itching.py'), 'w')
2541 print >> f, '''def run(report, ui):2641 f.write('''def run(report, ui):
2542 report['itch'] = 'slap'2642 report['itch'] = 'slap'
2543 report['q'] = str(ui.yesno('do you?'))2643 report['q'] = str(ui.yesno('do you?'))
2544 return 'bash'2644 return 'bash'
2545'''2645''')
2546 f.close()2646 f.close()
2547 sys.argv = ['ui-test', '-s', 'itching' ]2647 sys.argv = ['ui-test', '-s', 'itching' ]
2548 self.ui = _TestSuiteUserInterface()2648 self.ui = _TestSuiteUserInterface()
2549 self.ui.question_yesno_response = True2649 self.ui.question_yesno_response = True
2550 self.assertEqual(self.ui.run_argv(), True)2650 self.assertEqual(self.ui.run_argv(), True)
2551 self.assert_(self.ui.present_details_shown)2651 self.assertTrue(self.ui.present_details_shown)
2552 self.assertEqual(self.ui.msg_text, 'do you?')2652 self.assertEqual(self.ui.msg_text, 'do you?')
25532653
2554 self.assertEqual(self.ui.report['itch'], 'slap')2654 self.assertEqual(self.ui.report['itch'], 'slap')
2555 self.assert_('DistroRelease' in self.ui.report)2655 self.assertTrue('DistroRelease' in self.ui.report)
2556 self.assertEqual(self.ui.report['SourcePackage'], 'bash')2656 self.assertEqual(self.ui.report['SourcePackage'], 'bash')
2557 self.assert_(self.ui.report['Package'].startswith('bash '))2657 self.assertTrue(self.ui.report['Package'].startswith('bash '))
2558 self.assertEqual(self.ui.report['ProblemType'], 'Bug')2658 self.assertEqual(self.ui.report['ProblemType'], 'Bug')
2559 self.assertEqual(self.ui.report['q'], 'True')2659 self.assertEqual(self.ui.report['q'], 'True')
25602660
@@ -2562,13 +2662,13 @@
2562 '''run_report_bug() without specifying arguments and available symptoms.'''2662 '''run_report_bug() without specifying arguments and available symptoms.'''
25632663
2564 f = open(os.path.join(symptom_script_dir, 'foo.py'), 'w')2664 f = open(os.path.join(symptom_script_dir, 'foo.py'), 'w')
2565 print >> f, '''description = 'foo does not work'2665 f.write('''description = 'foo does not work'
2566def run(report, ui):2666def run(report, ui):
2567 return 'bash'2667 return 'bash'
2568'''2668''')
2569 f.close()2669 f.close()
2570 f = open(os.path.join(symptom_script_dir, 'bar.py'), 'w')2670 f = open(os.path.join(symptom_script_dir, 'bar.py'), 'w')
2571 print >> f, 'def run(report, ui):\n return "coreutils"'2671 f.write('def run(report, ui):\n return "coreutils"\n')
2572 f.close()2672 f.close()
25732673
2574 sys.argv = ['ui-test', '-f']2674 sys.argv = ['ui-test', '-f']
@@ -2577,30 +2677,28 @@
2577 self.ui.question_choice_response = None2677 self.ui.question_choice_response = None
2578 self.assertEqual(self.ui.run_argv(), True)2678 self.assertEqual(self.ui.run_argv(), True)
2579 self.assertEqual(self.ui.msg_severity, None)2679 self.assertEqual(self.ui.msg_severity, None)
2580 self.assert_('kind of problem' in self.ui.msg_text)2680 self.assertTrue('kind of problem' in self.ui.msg_text)
2581 self.assertEqual(set(self.ui.msg_choices), 2681 self.assertEqual(set(self.ui.msg_choices),
2582 set(['bar', 'foo does not work', 'Other problem']))2682 set(['bar', 'foo does not work', 'Other problem']))
25832683
2584 # cancelled2684 # cancelled
2585 self.assertEqual(self.ui.ic_progress_pulses, 0)2685 self.assertEqual(self.ui.ic_progress_pulses, 0)
2586 self.assertEqual(self.ui.report, None)2686 self.assertEqual(self.ui.report, None)
2587 self.failIf(self.ui.present_details_shown)2687 self.assertFalse(self.ui.present_details_shown)
25882688
2589 # now, choose foo -> bash report2689 # now, choose foo -> bash report
2590 self.ui.question_choice_response = [self.ui.msg_choices.index('foo does not work')]2690 self.ui.question_choice_response = [self.ui.msg_choices.index('foo does not work')]
2591 self.assertEqual(self.ui.run_argv(), True)2691 self.assertEqual(self.ui.run_argv(), True)
2592 self.assertEqual(self.ui.msg_severity, None)2692 self.assertEqual(self.ui.msg_severity, None)
2593 self.assert_(self.ui.ic_progress_pulses > 0)2693 self.assertTrue(self.ui.ic_progress_pulses > 0)
2594 self.assert_(self.ui.present_details_shown)2694 self.assertTrue(self.ui.present_details_shown)
2595 self.assert_(self.ui.report['Package'].startswith('bash'))2695 self.assertTrue(self.ui.report['Package'].startswith('bash'))
25962696
2597 def test_parse_argv(self):2697 def test_parse_argv_single_arg(self):
2598 '''parse_args() option inference for a single argument'''2698 '''parse_args() option inference for a single argument'''
25992699
2600 def _chk(program_name, arg, expected_opts, argv_options=None):2700 def _chk(program_name, arg, expected_opts):
2601 sys.argv = [program_name]2701 sys.argv = [program_name]
2602 if argv_options:
2603 sys.argv += argv_options
2604 if arg:2702 if arg:
2605 sys.argv.append(arg)2703 sys.argv.append(arg)
2606 orig_stderr = sys.stderr2704 orig_stderr = sys.stderr
@@ -2617,71 +2715,144 @@
2617 # no arguments -> show pending crashes2715 # no arguments -> show pending crashes
2618 _chk('apport-gtk', None, {'filebug': False, 'package': None,2716 _chk('apport-gtk', None, {'filebug': False, 'package': None,
2619 'pid': None, 'crash_file': None, 'symptom': None, 2717 'pid': None, 'crash_file': None, 'symptom': None,
2620 'update_report': None, 'save': None})2718 'update_report': None, 'save': None, 'window': False,
2621 # ... except when being called as '*-bug', then default to bug mode2719 'tag': []})
2622 _chk('apport-bug', None, {'filebug': True, 'package': None,
2623 'pid': None, 'crash_file': None, 'symptom': None,
2624 'update_report': None, 'save': None})
2625 # updating report not allowed without args2720 # updating report not allowed without args
2626 self.assertRaises(SystemExit, _chk, 'apport-collect', None, {})2721 self.assertRaises(SystemExit, _chk, 'apport-collect', None, {})
26272722
2628 # package 2723 # package
2629 _chk('apport-kde', 'coreutils', {'filebug': True, 'package':2724 _chk('apport-kde', 'coreutils', {'filebug': True, 'package':
2630 'coreutils', 'pid': None, 'crash_file': None, 'symptom': None, 2725 'coreutils', 'pid': None, 'crash_file': None, 'symptom': None,
2631 'update_report': None, 'save': None})2726 'update_report': None, 'save': None, 'window': False,
2632 _chk('apport-bug', 'coreutils', {'filebug': True, 'package':2727 'tag': []})
2633 'coreutils', 'pid': None, 'crash_file': None, 'symptom': None,
2634 'update_report': None, 'save': None})
2635 _chk('apport-bug', 'coreutils', {'filebug': True, 'package':
2636 'coreutils', 'pid': None, 'crash_file': None, 'symptom': None,
2637 'update_report': None, 'save': 'foo.apport'},
2638 ['--save', 'foo.apport'])
26392728
2640 # symptom is preferred over package2729 # symptom is preferred over package
2641 f = open(os.path.join(symptom_script_dir, 'coreutils.py'), 'w')2730 f = open(os.path.join(symptom_script_dir, 'coreutils.py'), 'w')
2642 print >> f, '''description = 'foo does not work'2731 f.write('''description = 'foo does not work'
2643def run(report, ui):2732def run(report, ui):
2644 return 'bash'2733 return 'bash'
2645'''2734''')
2646 f.close()2735 f.close()
2647 _chk('apport-cli', 'coreutils', {'filebug': True, 'package': None,2736 _chk('apport-cli', 'coreutils', {'filebug': True, 'package': None,
2648 'pid': None, 'crash_file': None, 'symptom': 'coreutils',2737 'pid': None, 'crash_file': None, 'symptom': 'coreutils',
2649 'update_report': None, 'save': None})2738 'update_report': None, 'save': None, 'window': False,
2650 _chk('apport-bug', 'coreutils', {'filebug': True, 'package': None,2739 'tag': []})
2651 'pid': None, 'crash_file': None, 'symptom': 'coreutils',
2652 'update_report': None, 'save': None})
26532740
2654 # PID2741 # PID
2655 _chk('apport-cli', '1234', {'filebug': True, 'package': None,2742 _chk('apport-cli', '1234', {'filebug': True, 'package': None,
2656 'pid': '1234', 'crash_file': None, 'symptom': None,2743 'pid': '1234', 'crash_file': None, 'symptom': None,
2657 'update_report': None, 'save': None})2744 'update_report': None, 'save': None, 'window': False,
2658 _chk('apport-bug', '1234', {'filebug': True, 'package': None,2745 'tag': []})
2659 'pid': '1234', 'crash_file': None, 'symptom': None,
2660 'update_report': None, 'save': None})
26612746
2662 # .crash/.apport files; check correct handling of spaces2747 # .crash/.apport files; check correct handling of spaces
2663 for suffix in ('.crash', '.apport'):2748 for suffix in ('.crash', '.apport'):
2664 for prog in ('apport-cli', 'apport-bug'):2749 _chk('apport-cli', '/tmp/f oo' + suffix, {'filebug': False,
2665 _chk(prog, '/tmp/f oo' + suffix, {'filebug': False,2750 'package': None, 'pid': None,
2666 'package': None, 'pid': None, 2751 'crash_file': '/tmp/f oo' + suffix, 'symptom': None,
2667 'crash_file': '/tmp/f oo' + suffix, 'symptom': None,2752 'update_report': None, 'save': None, 'window': False,
2668 'update_report': None, 'save': None})2753 'tag': []})
26692754
2670 # executable2755 # executable
2671 _chk('apport-cli', '/usr/bin/tail', {'filebug': True, 2756 _chk('apport-cli', '/usr/bin/tail', {'filebug': True,
2672 'package': 'coreutils',2757 'package': 'coreutils',
2673 'pid': None, 'crash_file': None, 'symptom': None, 2758 'pid': None, 'crash_file': None, 'symptom': None,
2674 'update_report': None, 'save': None})2759 'update_report': None, 'save': None, 'window': False,
2675 _chk('apport-bug', '/usr/bin/tail', {'filebug': True, 2760 'tag': []})
2676 'package': 'coreutils',
2677 'pid': None, 'crash_file': None, 'symptom': None,
2678 'update_report': None, 'save': None})
26792761
2680 # update existing report2762 # update existing report
2681 _chk('apport-collect', '1234', {'filebug': False, 'package': None,2763 _chk('apport-collect', '1234', {'filebug': False, 'package': None,
2682 'crash_file': None, 'symptom': None, 'update_report': 1234})2764 'crash_file': None, 'symptom': None, 'update_report': 1234,
2765 'tag': []})
2683 _chk('apport-update-bug', '1234', {'filebug': False, 'package': None,2766 _chk('apport-update-bug', '1234', {'filebug': False, 'package': None,
2684 'crash_file': None, 'symptom': None, 'update_report': 1234})2767 'crash_file': None, 'symptom': None, 'update_report': 1234,
2768 'tag': []})
2769
2770 def test_parse_argv_apport_bug(self):
2771 '''parse_args() option inference when invoked as *-bug'''
2772
2773 def _chk(args, expected_opts):
2774 sys.argv = ['apport-bug'] + args
2775 orig_stderr = sys.stderr
2776 sys.stderr = open('/dev/null', 'w')
2777 try:
2778 ui = UserInterface()
2779 finally:
2780 sys.stderr.close()
2781 sys.stderr = orig_stderr
2782 expected_opts['version'] = None
2783 self.assertEqual(ui.args, [])
2784 self.assertEqual(ui.options, expected_opts)
2785
2786 #
2787 # no arguments: default to 'ask for symptom' bug mode
2788 #
2789 _chk([], {'filebug': True, 'package': None,
2790 'pid': None, 'crash_file': None, 'symptom': None,
2791 'update_report': None, 'save': None, 'window': False,
2792 'tag': []})
2793
2794 #
2795 # single arguments
2796 #
2797
2798 # package
2799 _chk(['coreutils'], {'filebug': True, 'package':
2800 'coreutils', 'pid': None, 'crash_file': None, 'symptom': None,
2801 'update_report': None, 'save': None, 'window': False,
2802 'tag': []})
2803
2804 # symptom (preferred over package)
2805 f = open(os.path.join(symptom_script_dir, 'coreutils.py'), 'w')
2806 f.write('''description = 'foo does not work'
2807def run(report, ui):
2808 return 'bash'
2809''')
2810 f.close()
2811 _chk(['coreutils'], {'filebug': True, 'package': None,
2812 'pid': None, 'crash_file': None, 'symptom': 'coreutils',
2813 'update_report': None, 'save': None, 'window': False,
2814 'tag': []})
2815 os.unlink(os.path.join(symptom_script_dir, 'coreutils.py'))
2816
2817 # PID
2818 _chk(['1234'], {'filebug': True, 'package': None,
2819 'pid': '1234', 'crash_file': None, 'symptom': None,
2820 'update_report': None, 'save': None, 'window': False,
2821 'tag': []})
2822
2823 # .crash/.apport files; check correct handling of spaces
2824 for suffix in ('.crash', '.apport'):
2825 _chk(['/tmp/f oo' + suffix], {'filebug': False,
2826 'package': None, 'pid': None,
2827 'crash_file': '/tmp/f oo' + suffix, 'symptom': None,
2828 'update_report': None, 'save': None, 'window': False,
2829 'tag': []})
2830
2831 # executable name
2832 _chk(['/usr/bin/tail'], {'filebug': True, 'package': 'coreutils',
2833 'pid': None, 'crash_file': None, 'symptom': None,
2834 'update_report': None, 'save': None, 'window': False,
2835 'tag': []})
2836
2837 #
2838 # supported options
2839 #
2840
2841 # --save
2842 _chk(['--save', 'foo.apport', 'coreutils'], {'filebug': True,
2843 'package': 'coreutils', 'pid': None, 'crash_file': None,
2844 'symptom': None, 'update_report': None, 'save': 'foo.apport',
2845 'window': False, 'tag': []})
2846
2847 # --tag
2848 _chk(['--tag', 'foo', 'coreutils'], {'filebug': True,
2849 'package': 'coreutils', 'pid': None, 'crash_file': None,
2850 'symptom': None, 'update_report': None, 'save': None,
2851 'window': False, 'tag': ['foo']})
2852 _chk(['--tag', 'foo', '--tag', 'bar', 'coreutils'], {
2853 'filebug': True, 'package': 'coreutils', 'pid': None,
2854 'crash_file': None, 'symptom': None, 'update_report': None,
2855 'save': None, 'window': False, 'tag': ['foo', 'bar']})
26852856
2686 unittest.main()2857 unittest.main()
26872858
26882859
=== modified file 'apport_python_hook.py'
--- apport_python_hook.py 2010-03-02 09:48:29 +0000
+++ apport_python_hook.py 2011-04-20 23:32:24 +0000
@@ -48,7 +48,11 @@
48 if not enabled():48 if not enabled():
49 return49 return
5050
51 from cStringIO import StringIO51 try:
52 from cStringIO import StringIO
53 except ImportError:
54 from io import StringIO
55
52 import re, tempfile, traceback56 import re, tempfile, traceback
53 from apport.fileutils import likely_packaged57 from apport.fileutils import likely_packaged
5458
@@ -102,7 +106,7 @@
102 # don't clobber existing report106 # don't clobber existing report
103 return107 return
104 report_file = os.fdopen(os.open(pr_filename,108 report_file = os.fdopen(os.open(pr_filename,
105 os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0600), 'w')109 os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0o600), 'w')
106 try:110 try:
107 pr.write(report_file)111 pr.write(report_file)
108 finally:112 finally:
@@ -154,15 +158,15 @@
154func(42)158func(42)
155''' % extracode)159''' % extracode)
156 os.close(fd)160 os.close(fd)
157 os.chmod(script, 0755)161 os.chmod(script, 0o755)
158162
159 p = subprocess.Popen([script, 'testarg1', 'testarg2'],163 p = subprocess.Popen([script, 'testarg1', 'testarg2'],
160 stdout=subprocess.PIPE, stderr=subprocess.PIPE)164 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
161 err = p.communicate()[1]165 err = p.communicate()[1]
162 self.assertEqual(p.returncode, 1,166 self.assertEqual(p.returncode, 1,
163 'crashing test python program exits with failure code')167 'crashing test python program exits with failure code')
164 self.assert_('Exception: This should happen.' in err)168 self.assertTrue('Exception: This should happen.' in err)
165 self.failIf('OSError' in err, err)169 self.assertFalse('OSError' in err, err)
166 finally:170 finally:
167 os.unlink(script)171 os.unlink(script)
168172
@@ -179,7 +183,7 @@
179 try:183 try:
180 self.assertEqual(len(reports), 1, 'crashed Python program produced a report')184 self.assertEqual(len(reports), 1, 'crashed Python program produced a report')
181 self.assertEqual(stat.S_IMODE(os.stat(reports[0]).st_mode),185 self.assertEqual(stat.S_IMODE(os.stat(reports[0]).st_mode),
182 0600, 'report has correct permissions')186 0o600, 'report has correct permissions')
183187
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches