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
1=== modified file 'NEWS'
2--- NEWS 2010-07-13 05:42:32 +0000
3+++ NEWS 2011-04-20 23:32:24 +0000
4@@ -1,12 +1,174 @@
5 This file summarizes the major and interesting changes for each release. For a
6 detailled list of changes, please see ChangeLog.
7
8-1.15 (UNRELEASED)
9+1.20.2 (UNRELEASED)
10+-------------------
11+Bug fixes:
12+ - apport-gtk: HTML-escape text for dialogs with URLs. (LP: #750870)
13+ - dump_acpi_tables.py: Check to see if acpi/tables dir is mounted first.
14+ Thanks Brian Murray. (LP: #729622)
15+ - man/apport-cli.1: Document recently added -w/--window option. Thanks Abhinav
16+ Upadhyay! (LP: #765600)
17+ - Use kde-open instead of kfmclient to open URLs under KDE. Thanks Philip
18+ Muškovac. (LP: #765808)
19+
20+1.20.1 (2011-03-31)
21+-------------------
22+Bug fixes:
23+ - Add bash completion support for new -w/--window option that was introduced
24+ in 1.20. Thanks Philip Muškovac.
25+ - apport-unpack: Fix crash if target directory already exists.
26+ - Fix crash if UnreportableReason is a non-ASCII string. (LP: #738632)
27+ - Fix crash if application from desktop name is a non-ASCII string.
28+ (LP: #737799)
29+ - unkillable_shutdown: Fix rare crash if ExecutablePath does not exist (any
30+ more). (LP: #537904)
31+ - kernel_crashdump: Fix crash if the vmcore file disappeared underneath us.
32+ (LP: #450295)
33+ - unkillable_shutdown: Fix crash if the checked process terminated underneath
34+ us. (LP: #540436)
35+ - ui.py: Properly raise exceptions from the upload thread that happen at its
36+ very end. (LP: #469943)
37+
38+1.20 (2011-03-17)
39+-----------------
40+Improvements:
41+ - Add support for -w/--window option which will enable user to select a
42+ window as a target for filing a problem report. Thanks Abhinav Upadhyay for
43+ the patch! (LP: #357847)
44+ - Disable the filtering on SIGABRT without assertion messages. Turns out that
45+ developers want these crash reports after all. (LP: #729223)
46+ - Add support for a "DuplicateSignature" report fields. This allows package
47+ hooks to implement custom duplicate problem handling which doesn't need to
48+ be hardcoded in Apport itself. Update the launchpad backend to tag such bugs
49+ as "need-duplicate-check".
50+
51+Bug fixes:
52+ - report.py, add_hooks_info(): Properly report TypeErrors from hooks.
53+ - apport-retrace: Intercept SystemErrors from ill-formed gzip attachments as
54+ well.
55+ - Fix crash if crash database configuration does not specify a
56+ bug_pattern_url. Just assume None. (LP: #731526)
57+ - If a custom crash database does not specify a bug_pattern_url, fall back to
58+ using the default database's. (LP: #731526)
59+ - hookutils.py Update WifiSyslog regex to correctly catch application log
60+ messages in syslog. Thanks Mathieu Trudel-Lapierre. (LP: #732917)
61+ - hookutils.py, attach_hardware(): Avoid error message if machine does not
62+ have a PCI bus. Thanks Marcin Juszkiewicz! (LP: #608449)
63+ - backends/packaging-apt-dpkg.py: Replace deprecated getChanges() call with
64+ get_changes().
65+ - apport-gtk: Fix broken dialog heading if the name of the crashed program
66+ contains an & or other markup specific characters.
67+ - apport-gtk: Don't crash if GTK cannot be initialized. This usually happens
68+ without a $DISPLAY or when the session is being shut down. Just print an
69+ error message. If there are pending crashes, they will be shown again the
70+ next time a session starts. (LP: #730569)
71+
72+1.19 (2011-02-28)
73+-----------------
74+Bug fixes:
75+ - Update stack unwind patterns for current glib (slightly changed function
76+ names), and also ignore a preceding '*'. (LP: #716251)
77+ - Fix crash_signature() to fail if there is an empty or too short
78+ StacktraceTop.
79+ - apt backend: Do not generate a warning if the opportunistically added -dbg
80+ package does not exist.
81+ - apt backend: Only add -dbg in --no-pkg mode, as there will be conflicts in
82+ normal package mode.
83+ - apt backend: Call tar with target cwd instead of using -C; the latter causes
84+ an extra openat() call which breaks with current fakechroot.
85+ - launchpad.py: Fix retracer crash if DistroRelease field does not exist.
86+ - Convert deprecated failIf()/assert_() TestCase method calls to
87+ assertFalse()/assertTrue().
88+
89+Improvements:
90+ - In apport-bug, if the user specifies a PID referring to a kernel thread,
91+ do the right thing and file the bug against the kernel
92+ - In hookutils.attach_dmesg, skip over an initial truncated message if one
93+ is present (this happens when the ring buffer overflows)
94+ - Change bug patterns to just use one central file instead of per-package
95+ files. This allows bug patterns to be written which are not package
96+ specific, and is easier to maintain as well. IMPORTANT: This changed the
97+ format of crashdb.conf: bug_pattern_base is now obsolete, and the new
98+ attribute bug_pattern_url now points to the full URL/path of the patterns
99+ file. Thanks to Matt Zimmerman!
100+
101+1.18 (2011-02-16)
102+-----------------
103+Bug fixes:
104+ - Ensure that symptom scripts define a run() function, and don't show them if
105+ not.
106+ - Do not show symptom scripts which start with an underscore. These can be
107+ used for private libraries for the actual symptom scripts.
108+ - Update bash completion. Thanks Philip Muškovac.
109+ - etc/default/apport: Remove obsolete "maxsize" setting. (LP: #719564)
110+
111+Improvements:
112+ - Remove explicit handling of KDE *.ui files in setup.py, as
113+ python-distutils-extra 2.24 fixes this. Bump version check.
114+ - hookutils.py: Add attach_root_command_outputs() to run several commands
115+ at once. This avoids asking for the password several times. (LP: #716595)
116+
117+1.17.2 (2011-02-04)
118+-------------------
119+Improvements:
120+ - Be more Python 3 compatible (not fully working with Python 3 yet, though).
121+ - apt/dpkg backend: Drop support for pre-0.7.9 python-apt API.
122+ - Add --tag option to add extra tags to reports. (LP: #572504)
123+
124+Bug fixes:
125+ - hookutils.py, attach_dmesg(): Do not overwrite already existing dmesg.
126+ - hookutils.py: Be more robust against file permission errors. (LP: #444678)
127+ - ui.py: Do not show all the options in --help when invoked as *-bug.
128+ (LP: #665953)
129+ - launchpad.py: Adapt test cases to current standard_title() behaviour.
130+
131+1.17.1 (2011-01-10)
132+-------------------
133+Bug fixes:
134+ - Make the GTK frontend work with GTK 2.0 as well, and drop "3.0" requirement.
135+
136+1.17 (2010-12-31)
137+-----------------
138+Improvements:
139+ - Better standard bug titles for Python crashes. Thanks Matt Zimmerman!
140+ (LP: #681574)
141+ - Add handler for uncaught Java exceptions. There is no integration for
142+ automatically intercepting all Java crashes yet, see java/README.
143+ Thanks Matt Zimmerman! (LP: #548877)
144+
145+Bug fixes:
146+ - GTK frontend: Require GTK 3.0.
147+ - launchpad.py: Default to "production" instance, not "edge", since edge is
148+ obsolete now.
149+ - hookutils.py, attach_alsa(): Fix crash if /proc/asound/cards does not exist.
150+ (LP: #626215)
151+ - ui.py, format_filesize(): Fix to work with stricter locale.format() in
152+ Python 2.7. (LP: #688535). While we are at it, also change it to use base-10
153+ units.
154+ - hookutils.py, package_versions(): Always include all requested package names
155+ even if they're unknown to us. Thanks Matt Zimmerman! (LP: #695188)
156+ - launchpad.py: When updating a bug, also add new tags. Thanks Brian Murray!
157+
158+1.16 (2010-11-19)
159+-----------------
160+New features:
161+ - Port GTK frontend from pygtk2 to GTK+3.0 and gobject-introspection.
162+
163+Bug fixes:
164+ - Fix symptoms again. Version 1.15 broke the default symptom directory.
165+ - Fix memory test case to work with current Python versions, where the SQLite
166+ integrity check throws a different exception.
167+
168+1.15 (2010-11-11)
169 -----------------
170 New features:
171 - Add dump_acpi_tables.py script. This can be called by package hooks which
172 need ACPI table information (in particular, kernel bug reports). Thanks to
173 Brad Figg for the script!
174+ - Order symptom descriptions alphabetically. Thanks to Javier Collado.
175+ - Check $APPORT_SYMPTOMS_DIR environment variable for overriding the system
176+ default path. Thanks to Javier Collado.
177
178 Bug fixes:
179 - testsuite: Check that crashdb.conf can have dynamic code to determine DB
180@@ -14,6 +176,30 @@
181 - ui.py test suite: Rewrite _gen_test_crash() to have the test process core
182 dump itself, instead of using gdb to do it. The latter fails in ptrace
183 restricted environments, such as Ubuntu 10.10.
184+ - packaging-apt-dpkg.py: Fix handling of /etc/apport/native-origins.d to
185+ actually work. Thanks Steve Langasek. (LP: #627777)
186+ - apport-kde: Load correct translation catalogue. Thanks Jonathan Riddell.
187+ (LP: #633483)
188+ - launchpad.py: Use launchpadlib to file a bug instead of screen scraping.
189+ The latter was completely broken with current Launchpad, so this makes the
190+ test suite actually work again. Thanks to Diogo Matsubara!
191+ - launchpad.py: Change $APPORT_STAGING to $APPORT_LAUNCHPAD_INSTANCE, so that
192+ you can now specify "staging", "edge", or "dev" (for a local
193+ http://launchpad.dev installation). Thanks to Diogo Matsubara!
194+ - backends/packaging-apt-dpkg.py: Fix crash on empty lines in ProcMaps
195+ attachment.
196+ - doc/symptoms.txt: Fix typo, thanks Philip Muskovac. (LP: #590521)
197+ - apport/hookutils.py: rename ProcCmdLine to ProcKernelCmdLine to not wipe
198+ wipe out /proc/$pid/cmdline information. (LP: #657091)
199+ - apport/hookutils.py: attach_file() will not overwrite existing report
200+ keys, instead appending "_" until the key is unique.
201+ - Fix --save option to recognise ~, thanks Philip Muškovac. (LP: #657278)
202+ - Remove escalation_subscription from Ubuntu bug DB definition, turned out to
203+ not be useful; thanks Brian Murray.
204+ - launchpad.py: Fix APPORT_LAUNCHPAD_INSTANCE values with a https:// prefix.
205+ - apt backend: Opportunistically try to install a -dbg package in addition to
206+ -dbgsym, to increase the chance that at least one of it exists. Thanks
207+ Daniel J Blueman!
208
209 1.14.1 (2010-06-24)
210 -------------------
211
212=== modified file 'apport/REThread.py'
213--- apport/REThread.py 2009-12-23 11:01:21 +0000
214+++ apport/REThread.py 2011-04-20 23:32:24 +0000
215@@ -64,7 +64,7 @@
216 #
217
218 if __name__ == '__main__':
219- import unittest, time, traceback, exceptions
220+ import unittest, time, traceback
221
222 def idle(seconds):
223 '''Test thread to just wait a bit.'''
224@@ -107,11 +107,11 @@
225 t.join()
226 # thread did not terminate normally, no return value
227 self.assertRaises(AssertionError, t.return_value)
228- self.assert_(t.exc_info()[0] == exceptions.ZeroDivisionError)
229+ self.assertTrue(t.exc_info()[0] == ZeroDivisionError)
230 exc = traceback.format_exception(t.exc_info()[0], t.exc_info()[1],
231 t.exc_info()[2])
232- self.assert_(exc[-1].startswith('ZeroDivisionError'))
233- self.assert_(exc[-2].endswith('return x / y\n'))
234+ self.assertTrue(exc[-1].startswith('ZeroDivisionError'), 'not a ZeroDivisionError:' + str(exc))
235+ self.assertTrue(exc[-2].endswith('return x / y\n'))
236
237 def test_exc_raise(self):
238 '''exc_raise() raises caught thread exception.'''
239@@ -128,9 +128,9 @@
240 raised = True
241 e = sys.exc_info()
242 exc = traceback.format_exception(e[0], e[1], e[2])
243- self.assert_(exc[-1].startswith('ZeroDivisionError'))
244- self.assert_(exc[-2].endswith('return x / y\n'))
245- self.assert_(raised)
246+ self.assertTrue(exc[-1].startswith('ZeroDivisionError'), 'not a ZeroDivisionError:' + str(e))
247+ self.assertTrue(exc[-2].endswith('return x / y\n'))
248+ self.assertTrue(raised)
249
250 unittest.main()
251
252
253=== modified file 'apport/__init__.py'
254--- apport/__init__.py 2009-09-08 12:03:44 +0000
255+++ apport/__init__.py 2011-04-20 23:32:24 +0000
256@@ -2,11 +2,33 @@
257
258 from apport.packaging_impl import impl as packaging
259
260+import sys
261+
262 # fix gettext to output proper unicode strings
263 import gettext
264
265 def unicode_gettext(str):
266 trans = gettext.gettext(str)
267- if type(trans) == type(u''):
268+ if isinstance(trans, unicode):
269 return trans
270 return trans.decode('UTF-8')
271+
272+def fatal(msg, *args):
273+ '''Print out an error message and exit the program.'''
274+
275+ error(msg, *args)
276+ sys.exit(1)
277+
278+def error(msg, *args):
279+ '''Print out an error message.'''
280+
281+ sys.stderr.write('ERROR: ')
282+ sys.stderr.write(msg % args)
283+ sys.stderr.write('\n')
284+
285+def warning(msg, *args):
286+ '''Print out an warning message.'''
287+
288+ sys.stderr.write('WARNING: ')
289+ sys.stderr.write(msg % args)
290+ sys.stderr.write('\n')
291
292=== modified file 'apport/chroot.py'
293--- apport/chroot.py 2009-12-23 11:01:21 +0000
294+++ apport/chroot.py 2011-04-20 23:32:24 +0000
295@@ -227,7 +227,7 @@
296 os.close(fd)
297 c.tar(tarpath)
298 t = tarfile.open(tarpath)
299- self.assert_(
300+ self.assertTrue(
301 # python 2.5's tarfile
302 set(['./bin/42', './bin/hello', './newfile']).issubset(set(t.getnames())) or
303 # python 2.4's tarfile
304@@ -236,7 +236,7 @@
305
306 # test cleanup
307 del c
308- self.assert_(os.path.exists(os.path.join(d, 'bin', '42')),
309+ self.assertTrue(os.path.exists(os.path.join(d, 'bin', '42')),
310 'directory chroot should not delete the chroot')
311
312 finally:
313@@ -274,7 +274,7 @@
314 open(os.path.join(c.root, 'newfile'), 'w')
315 c.tar()
316 t = tarfile.open(tar)
317- self.assert_(
318+ self.assertTrue(
319 # python 2.5's tarfile
320 set(['./bin/42', './bin/hello', './newfile']).issubset(set(t.getnames())) or
321 # python 2.4's tarfile
322@@ -284,7 +284,7 @@
323 # test cleanup
324 d = c.root
325 del c
326- self.assert_(not os.path.exists(d),
327+ self.assertTrue(not os.path.exists(d),
328 'tarball chroot should delete the temporary chroot')
329 finally:
330 os.unlink(tar)
331@@ -364,10 +364,11 @@
332 self.assertEqual(err, '')
333 self.assertEqual(result, 0)
334 files = out.splitlines()
335- self.assert_('bin' in files)
336- self.assert_('lib' in files)
337+ self.assertTrue('bin' in files)
338+ self.assertTrue('lib' in files)
339
340 # complex shell commands: relative symlinks and paths
341+ os.umask(022) # assumed below
342 self.assertEqual(c.run_capture(['bash'], '''set -e
343 cd /
344 mkdir test
345
346=== modified file 'apport/crashdb.py'
347--- apport/crashdb.py 2009-12-23 10:55:52 +0000
348+++ apport/crashdb.py 2011-04-20 23:32:24 +0000
349@@ -83,7 +83,7 @@
350 cur.execute('PRAGMA integrity_check')
351 result = cur.fetchall()
352 if result != [('ok',)]:
353- raise SystemError, 'Corrupt duplicate db:' + str(result)
354+ raise SystemError('Corrupt duplicate db:' + str(result))
355
356 def check_duplicate(self, id, report=None):
357 '''Check whether a crash is already known.
358@@ -108,7 +108,10 @@
359
360 self._mark_dup_checked(id, report)
361
362- sig = report.crash_signature()
363+ if 'DuplicateSignature' in report:
364+ sig = report['DuplicateSignature']
365+ else:
366+ sig = report.crash_signature()
367 if not sig:
368 return None
369
370@@ -225,10 +228,10 @@
371 # crash got fixed/rejected
372 fixed_ver = self.get_fixed_version(id)
373 if fixed_ver == 'invalid':
374- print 'DEBUG: bug %i was invalidated, removing from database' % id
375+ print('DEBUG: bug %i was invalidated, removing from database' % id)
376 cur2.execute('DELETE FROM crashes WHERE crash_id = ?', [id])
377 elif not fixed_ver:
378- print 'WARNING: inconsistency detected: bug #%i does not appear in get_unfixed(), but is not fixed yet' % id
379+ print('WARNING: inconsistency detected: bug #%i does not appear in get_unfixed(), but is not fixed yet' % id)
380 else:
381 cur2.execute('UPDATE crashes SET fixed_version = ?, last_change = CURRENT_TIMESTAMP WHERE crash_id = ?',
382 (fixed_ver, id))
383@@ -328,7 +331,7 @@
384
385 This method can raise a NeedsCredentials exception in case of failure.
386 '''
387- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
388+ raise NotImplementedError('this method must be implemented by a concrete subclass')
389
390 def get_comment_url(self, report, handle):
391 '''Return an URL that should be opened after report has been uploaded
392@@ -338,12 +341,12 @@
393 user comments); in that case this function should do whichever
394 interactive steps it wants to perform.
395 '''
396- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
397+ raise NotImplementedError('this method must be implemented by a concrete subclass')
398
399 def download(self, id):
400 '''Download the problem report from given ID and return a Report.'''
401
402- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
403+ raise NotImplementedError('this method must be implemented by a concrete subclass')
404
405 def update(self, id, report, comment, change_description=False,
406 attachment_comment=None, key_filter=None):
407@@ -362,7 +365,7 @@
408
409 If key_filter is a list or set, then only those keys will be added.
410 '''
411- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
412+ raise NotImplementedError('this method must be implemented by a concrete subclass')
413
414 def update_traces(self, id, report, comment=''):
415 '''Update the given report ID for retracing results.
416@@ -376,12 +379,12 @@
417 def set_credentials(self, username, password):
418 '''Set username and password.'''
419
420- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
421+ raise NotImplementedError('this method must be implemented by a concrete subclass')
422
423 def get_distro_release(self, id):
424 '''Get 'DistroRelease: <release>' from the report ID.'''
425
426- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
427+ raise NotImplementedError('this method must be implemented by a concrete subclass')
428
429 def get_unretraced(self):
430 '''Return set of crash IDs which have not been retraced yet.
431@@ -389,7 +392,7 @@
432 This should only include crashes which match the current host
433 architecture.
434 '''
435- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
436+ raise NotImplementedError('this method must be implemented by a concrete subclass')
437
438 def get_dup_unchecked(self):
439 '''Return set of crash IDs which need duplicate checking.
440@@ -398,7 +401,7 @@
441 Python, since they do not need to be retraced. It should not return
442 bugs that are covered by get_unretraced().
443 '''
444- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
445+ raise NotImplementedError('this method must be implemented by a concrete subclass')
446
447 def get_unfixed(self):
448 '''Return an ID set of all crashes which are not yet fixed.
449@@ -409,7 +412,7 @@
450 there are any errors with connecting to the crash database, it should
451 raise an exception (preferably IOError).
452 '''
453- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
454+ raise NotImplementedError('this method must be implemented by a concrete subclass')
455
456 def get_fixed_version(self, id):
457 '''Return the package version that fixes a given crash.
458@@ -423,17 +426,17 @@
459 there are any errors with connecting to the crash database, it should
460 raise an exception (preferably IOError).
461 '''
462- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
463+ raise NotImplementedError('this method must be implemented by a concrete subclass')
464
465 def get_affected_packages(self, id):
466 '''Return list of affected source packages for given ID.'''
467
468- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
469+ raise NotImplementedError('this method must be implemented by a concrete subclass')
470
471 def is_reporter(self, id):
472 '''Check whether the user is the reporter of given ID.'''
473
474- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
475+ raise NotImplementedError('this method must be implemented by a concrete subclass')
476
477 def can_update(self, id):
478 '''Check whether the user is eligible to update a report.
479@@ -443,32 +446,32 @@
480 exact policy and checks should be done according to the particular
481 implementation.
482 '''
483- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
484+ raise NotImplementedError('this method must be implemented by a concrete subclass')
485
486 def duplicate_of(self, id):
487 '''Return master ID for a duplicate bug.
488
489 If the bug is not a duplicate, return None.
490 '''
491- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
492+ raise NotImplementedError('this method must be implemented by a concrete subclass')
493
494 def close_duplicate(self, id, master):
495 '''Mark a crash id as duplicate of given master ID.
496
497 If master is None, id gets un-duplicated.
498 '''
499- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
500+ raise NotImplementedError('this method must be implemented by a concrete subclass')
501
502 def mark_regression(self, id, master):
503 '''Mark a crash id as reintroducing an earlier crash which is
504 already marked as fixed (having ID 'master').'''
505
506- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
507+ raise NotImplementedError('this method must be implemented by a concrete subclass')
508
509 def mark_retraced(self, id):
510 '''Mark crash id as retraced.'''
511
512- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
513+ raise NotImplementedError('this method must be implemented by a concrete subclass')
514
515 def mark_retrace_failed(self, id, invalid_msg=None):
516 '''Mark crash id as 'failed to retrace'.
517@@ -478,14 +481,14 @@
518
519 This can be a no-op if you are not interested in this.
520 '''
521- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
522+ raise NotImplementedError('this method must be implemented by a concrete subclass')
523
524 def _mark_dup_checked(self, id, report):
525 '''Mark crash id as checked for being a duplicate
526
527 This is an internal method that should not be called from outside.
528 '''
529- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
530+ raise NotImplementedError('this method must be implemented by a concrete subclass')
531
532 #
533 # factory
534@@ -508,7 +511,7 @@
535 - A dictionary 'databases' which maps names to crash db configuration
536 dictionaries. These need to have at least the keys 'impl' (Python module
537 in apport.crashdb_impl which contains a concrete 'CrashDatabase' class
538- implementation for that crash db type) and 'bug_pattern_base', which
539+ implementation for that crash db type) and 'bug_pattern_url', which
540 specifies an URL for bug patterns (or None if those are not used for that
541 crash db).
542 '''
543@@ -525,9 +528,9 @@
544 if os.path.isfile(cfpath) and cf.endswith('.conf'):
545 try:
546 execfile(cfpath, settings['databases'])
547- except Exception, e:
548+ except Exception as e:
549 # ignore broken files
550- print >> sys.stderr, 'Invalid file %s: %s' % (cfpath, str(e))
551+ sys.stderr.write('Invalid file %s: %s\n' % (cfpath, str(e)))
552 pass
553
554 if not name:
555@@ -535,8 +538,13 @@
556
557 db = settings['databases'][name]
558
559+ bug_pattern_url = db.get('bug_pattern_url')
560+ if not bug_pattern_url:
561+ # fall back to default database's
562+ bug_pattern_url = settings['databases'][settings['default']].get('bug_pattern_url')
563+
564 m = __import__('apport.crashdb_impl.' + db['impl'], globals(), locals(), ['CrashDatabase'])
565- return m.CrashDatabase(auth_file, db['bug_pattern_base'], db)
566+ return m.CrashDatabase(auth_file, bug_pattern_url, db)
567
568 class NeedsCredentials(Exception):
569 '''This may be raised when unable to log in to the crashdb.'''
570
571=== modified file 'apport/crashdb_impl/launchpad.py'
572--- apport/crashdb_impl/launchpad.py 2010-06-16 13:50:47 +0000
573+++ apport/crashdb_impl/launchpad.py 2011-04-20 23:32:24 +0000
574@@ -10,12 +10,15 @@
575 # option) any later version. See http://www.gnu.org/copyleft/gpl.html for
576 # the full text of the license.
577
578-import urllib, tempfile, shutil, os.path, re, gzip, sys, socket, ConfigParser
579+import urllib, tempfile, os.path, re, gzip, sys
580 import email
581-from cStringIO import StringIO
582+try:
583+ from cStringIO import StringIO
584+except ImportError:
585+ from io import StringIO
586
587 from launchpadlib.errors import HTTPError
588-from launchpadlib.launchpad import Launchpad, STAGING_SERVICE_ROOT, EDGE_SERVICE_ROOT
589+from launchpadlib.launchpad import Launchpad
590
591 import apport.crashdb
592 import apport
593@@ -26,8 +29,8 @@
594 for attachment in attachments:
595 try:
596 f = attachment.data.open()
597- except HTTPError, e:
598- print >> sys.stderr, 'ERROR: Broken attachment on bug, ignoring'
599+ except HTTPError:
600+ apport.error('Broken attachment on bug, ignoring')
601 continue
602 name = f.filename
603 if name.endswith('.txt') or name.endswith('.gz'):
604@@ -52,8 +55,9 @@
605 - distro: Name of the distribution in Launchpad
606 - project: Name of the project in Launchpad
607 (Note that exactly one of "distro" or "project" must be given.)
608- - staging: If set, this uses staging instead of production (optional).
609- This can be overriden or set by $APPORT_STAGING environment.
610+ - launchpad_instance: If set, this uses the given launchpad instance
611+ instead of production (optional). This can be overriden or set by
612+ $APPORT_LAUNCHPAD_INSTANCE environment.
613 - cache_dir: Path to a permanent cache directory; by default it uses a
614 temporary one. (optional). This can be overridden or set by
615 $APPORT_LAUNCHPAD_CACHE environment.
616@@ -62,11 +66,13 @@
617 - escalation_tag: This adds the given tag to a bug once it gets more
618 than 10 duplicates.
619 '''
620- if os.getenv('APPORT_STAGING'):
621- options['staging'] = True
622+ if os.getenv('APPORT_LAUNCHPAD_INSTANCE'):
623+ options['launchpad_instance'] = os.getenv(
624+ 'APPORT_LAUNCHPAD_INSTANCE')
625 if not auth:
626- if options.get('staging'):
627- auth = default_credentials_path + '.staging'
628+ lp_instance = options.get('launchpad_instance')
629+ if lp_instance:
630+ auth = default_credentials_path + '.' + lp_instance.split('://', 1)[-1]
631 else:
632 auth = default_credentials_path
633 apport.crashdb.CrashDatabase.__init__(self, auth,
634@@ -94,10 +100,10 @@
635 if self.__launchpad:
636 return self.__launchpad
637
638- if self.options.get('staging'):
639- launchpad_instance = STAGING_SERVICE_ROOT
640+ if self.options.get('launchpad_instance'):
641+ launchpad_instance = self.options.get('launchpad_instance')
642 else:
643- launchpad_instance = EDGE_SERVICE_ROOT
644+ launchpad_instance = 'production'
645
646 auth_dir = os.path.dirname(self.auth)
647 if auth_dir and not os.path.isdir(auth_dir):
648@@ -109,12 +115,12 @@
649 allow_access_levels=['WRITE_PRIVATE'],
650 credentials_file = self.auth,
651 version='1.0')
652- except Exception, e:
653+ except Exception as e:
654 if hasattr(e, 'content'):
655 msg = e.content
656 else:
657 msg = str(e)
658- print >> sys.stderr, 'Error connecting to Launchpad: %s\nYou can reset the credentials by removing the file "%s"' % (msg, self.auth)
659+ apport.error('connecting to Launchpad failed: %s\nYou can reset the credentials by removing the file "%s"', msg, self.auth)
660 sys.exit(99) # transient error
661
662 return self.__launchpad
663@@ -168,6 +174,8 @@
664 hdr['Private'] = 'yes'
665 hdr['Subscribers'] = 'apport'
666 hdr['Tags'] += ' need-duplicate-check'
667+ if 'DuplicateSignature' in report and 'need-duplicate-check' not in hdr['Tags']:
668+ hdr['Tags'] += ' need-duplicate-check'
669
670 # if we have checkbox submission key, link it to the bug; keep text
671 # reference until the link is shown in Launchpad's UI
672@@ -185,10 +193,23 @@
673 mime.seek(0)
674
675 ticket = upload_blob(mime, progress_callback,
676- staging=self.options.get('staging', False))
677+ hostname=self.get_hostname())
678 assert ticket
679 return ticket
680
681+ def get_hostname(self):
682+ '''Return the hostname for the Launchpad instance.'''
683+
684+ launchpad_instance = self.options.get('launchpad_instance')
685+ if launchpad_instance:
686+ if launchpad_instance == 'staging':
687+ hostname = 'staging.launchpad.net'
688+ else:
689+ hostname = 'launchpad.dev'
690+ else:
691+ hostname = 'launchpad.net'
692+ return hostname
693+
694 def get_comment_url(self, report, handle):
695 '''Return an URL that should be opened after report has been uploaded
696 and upload() returned handle.
697@@ -202,13 +223,10 @@
698 if title:
699 args['field.title'] = title
700
701- if self.options.get('staging'):
702- hostname = 'staging.launchpad.net'
703- else:
704- hostname = 'launchpad.net'
705+ hostname = self.get_hostname()
706
707 project = self.options.get('project')
708-
709+
710 if not project:
711 if report.has_key('SourcePackage'):
712 return 'https://bugs.%s/%s/+source/%s/+filebug/%s?%s' % (
713@@ -269,7 +287,7 @@
714 elif 'apport-package' in b.tags:
715 report['ProblemType'] = 'Package'
716 else:
717- raise ValueError, 'cannot determine ProblemType from tags: ' + str(b.tags)
718+ raise ValueError('cannot determine ProblemType from tags: ' + str(b.tags))
719
720 report['Tags'] = ' '.join(b.tags)
721
722@@ -290,7 +308,7 @@
723 elif ext == '.gz':
724 try:
725 report[key] = gzip.GzipFile(fileobj=attachment).read()
726- except IOError, e:
727+ except IOError as e:
728 # some attachments are only called .gz, but are
729 # uncompressed (LP #574360)
730 if 'Not a gzip' not in str(e):
731@@ -298,7 +316,7 @@
732 attachment.seek(0)
733 report[key] = attachment.read()
734 else:
735- raise Exception, 'Unknown attachment type: ' + attachment.filename
736+ raise Exception('Unknown attachment type: ' + attachment.filename)
737 return report
738
739 def update(self, id, report, comment, change_description=False,
740@@ -350,6 +368,9 @@
741 # with apport-collect
742 x = bug.tags[:] # LP#254901 workaround
743 x.append('apport-collected')
744+ # add any tags (like the release) to the bug
745+ if report.has_key('Tags'):
746+ x += report['Tags'].split()
747 bug.tags = x
748 bug.lp_save()
749 bug = self.launchpad.bugs[id] # fresh bug object, LP#336866 workaround
750@@ -412,7 +433,7 @@
751 m = re.search('DistroRelease: ([-a-zA-Z0-9.+/ ]+)', bug.description)
752 if m:
753 return m.group(1)
754- raise ValueError, 'URL does not contain DistroRelease: field'
755+ raise ValueError('URL does not contain DistroRelease: field')
756
757 def get_affected_packages(self, id):
758 '''Return list of affected source packages for given ID.'''
759@@ -544,7 +565,7 @@
760 return ''
761
762 if len(fixed_tasks) > 1:
763- print >> sys.stderr, 'WARNING: There is more than one task fixed in %s %s, using first one to determine fixed version' % (self.distro, id)
764+ apport.warning('There is more than one task fixed in %s %s, using first one to determine fixed version', self.distro, id)
765 return ''
766
767 if fixed_tasks:
768@@ -727,7 +748,7 @@
769 #FIXME: this entire function is an ugly Ubuntu specific hack until LP
770 #gets a real crash db; see https://wiki.ubuntu.com/CrashReporting
771
772- if report['DistroRelease'].split()[0] != 'Ubuntu':
773+ if report.get('DistroRelease', '').split()[0] != 'Ubuntu':
774 return # only Ubuntu bugs are filed private
775
776 #use a url hack here, it is faster
777@@ -780,7 +801,7 @@
778 def https_open(self, req):
779 return self.do_open(HTTPSProgressConnection, req)
780
781-def upload_blob(blob, progress_callback = None, staging=False):
782+def upload_blob(blob, progress_callback = None, hostname='launchpad.net'):
783 '''Upload blob (file-like object) to Launchpad.
784
785 progress_callback can be set to a function(sent, total) which is regularly
786@@ -790,8 +811,9 @@
787
788 Return None on error, or the ticket number on success.
789
790- By default this uses the production Launchpad instance. Set staging=True to
791- use staging.launchpad.net (for testing).
792+ By default this uses the production Launchpad hostname. Set
793+ hostname to 'launchpad.dev' or 'staging.launchpad.net' to use another
794+ instance for testing.
795 '''
796 ticket = None
797
798@@ -799,10 +821,7 @@
799 _https_upload_callback = progress_callback
800
801 opener = urllib2.build_opener(HTTPSProgressHandler, multipartpost_handler.MultipartPostHandler)
802- if staging:
803- url = 'https://staging.launchpad.net/+storeblob'
804- else:
805- url = 'https://launchpad.net/+storeblob'
806+ url = 'https://%s/+storeblob' % hostname
807 result = opener.open(url,
808 { 'FORM_SUBMIT': '1', 'field.blob': blob })
809 ticket = result.info().get('X-Launchpad-Blob-Token')
810@@ -814,7 +833,7 @@
811 #
812
813 if __name__ == '__main__':
814- import unittest, urllib2, cookielib
815+ import unittest, urllib2
816
817 crashdb = None
818 segv_report = None
819@@ -825,8 +844,6 @@
820 # binary package 'coreutils'
821 test_package = 'coreutils'
822 test_srcpackage = 'coreutils'
823- known_test_id = 302779
824- known_test_id2 = 89040
825
826 #
827 # Generic tests, should work for all CrashDB implementations
828@@ -845,6 +862,54 @@
829 self.ref_report.add_user_info()
830 self.ref_report['SourcePackage'] = 'coreutils'
831
832+ # Objects tests rely on.
833+ self.uncommon_description_bug = self._file_uncommon_description_bug()
834+ self._create_project('langpack-o-matic')
835+
836+ # XXX Should create new bug reports, not reuse those.
837+ self.known_test_id = self.uncommon_description_bug.id
838+ self.known_test_id2 = self._file_uncommon_description_bug().id
839+
840+ def _create_project(self, name):
841+ '''Create a project using launchpadlib to be used by tests.'''
842+
843+ project = self.crashdb.launchpad.projects[name]
844+ if not project:
845+ self.crashdb.launchpad.projects.new_project(
846+ description=name + 'description',
847+ display_name=name,
848+ name=name,
849+ summary=name + 'summary',
850+ title=name + 'title')
851+
852+ def _file_uncommon_description_bug(self):
853+ '''File a bug report with an uncommon description.
854+
855+ Example taken from real LP bug 269539. It contains only
856+ ProblemType/Architecture/DistroRelease in the description, and has
857+ free-form description text after the Apport data.
858+ '''
859+ desc = '''problem
860+
861+ProblemType: Package
862+Architecture: amd64
863+DistroRelease: Ubuntu 8.10
864+
865+more text
866+
867+and more
868+'''
869+ return self.crashdb.launchpad.bugs.createBug(
870+ title=u'mixed description bug',
871+ description=desc,
872+ target=self.crashdb.lp_distro)
873+
874+ @property
875+ def hostname(self):
876+ '''Get the Launchpad hostname for the given crashdb.'''
877+
878+ return self.crashdb.get_hostname()
879+
880 def _file_segv_report(self):
881 '''File a SEGV crash report.
882
883@@ -862,12 +927,12 @@
884 r['LongGibberish'] = 'a\nb\nc\nd\ne\n\xff\xff\xff\n\f'
885
886 handle = self.crashdb.upload(r)
887- self.assert_(handle)
888- url = self.crashdb.get_comment_url(r, handle)
889- self.assert_(url)
890+ self.assertTrue(handle)
891+ bug_target = self._get_bug_target(self.crashdb, r)
892+ self.assertTrue(bug_target)
893
894- id = self._fill_bug_form(url)
895- self.assert_(id > 0)
896+ id = self._file_bug(bug_target, r, handle)
897+ self.assertTrue(id > 0)
898 return id
899
900 def test_1_report_segv(self):
901@@ -878,7 +943,7 @@
902 global segv_report
903 id = self._file_segv_report()
904 segv_report = id
905- print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
906+ sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
907
908 def test_1_report_python(self):
909 '''upload() and get_comment_url() for Python crash
910@@ -889,24 +954,25 @@
911 r['ExecutablePath'] = '/bin/foo'
912 r['Traceback'] = '''Traceback (most recent call last):
913 File "/bin/foo", line 67, in fuzz
914- print weird
915+ print(weird)
916 NameError: global name 'weird' is not defined'''
917 r['Tags'] = 'boogus pybogus'
918 r.add_package_info(self.test_package)
919 r.add_os_info()
920 r.add_user_info()
921- self.assertEqual(r.standard_title(), 'foo crashed with NameError in fuzz()')
922+ self.assertEqual(r.standard_title(),
923+ "foo crashed with NameError in fuzz(): global name 'weird' is not defined")
924
925 handle = self.crashdb.upload(r)
926- self.assert_(handle)
927- url = self.crashdb.get_comment_url(r, handle)
928- self.assert_(url)
929+ self.assertTrue(handle)
930+ bug_target = self._get_bug_target(self.crashdb, r)
931+ self.assertTrue(bug_target)
932
933- id = self._fill_bug_form(url)
934- self.assert_(id > 0)
935+ id = self._file_bug(bug_target, r, handle)
936+ self.assertTrue(id > 0)
937 global python_report
938 python_report = id
939- print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
940+ sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
941
942 def test_2_download(self):
943 '''download()'''
944@@ -925,16 +991,16 @@
945 apport.packaging.get_system_architecture()]))
946
947 self.assertEqual(r['Signal'], '11')
948- self.assert_(r['ExecutablePath'].endswith('/crash'))
949+ self.assertTrue(r['ExecutablePath'].endswith('/crash'))
950 self.assertEqual(r['SourcePackage'], self.test_srcpackage)
951- self.assert_(r['Package'].startswith(self.test_package + ' '))
952- self.assert_('f (x=42)' in r['Stacktrace'])
953- self.assert_('f (x=42)' in r['StacktraceTop'])
954- self.assert_('f (x=42)' in r['ThreadStacktrace'])
955- self.assert_(len(r['CoreDump']) > 1000)
956- self.assert_('Dependencies' in r)
957- self.assert_('Disassembly' in r)
958- self.assert_('Registers' in r)
959+ self.assertTrue(r['Package'].startswith(self.test_package + ' '))
960+ self.assertTrue('f (x=42)' in r['Stacktrace'])
961+ self.assertTrue('f (x=42)' in r['StacktraceTop'])
962+ self.assertTrue('f (x=42)' in r['ThreadStacktrace'])
963+ self.assertTrue(len(r['CoreDump']) > 1000)
964+ self.assertTrue('Dependencies' in r)
965+ self.assertTrue('Disassembly' in r)
966+ self.assertTrue('Registers' in r)
967
968 # check tags
969 r = self.crashdb.download(python_report)
970@@ -946,12 +1012,12 @@
971 '''update_traces()'''
972
973 r = self.crashdb.download(segv_report)
974- self.assert_('CoreDump' in r)
975- self.assert_('Dependencies' in r)
976- self.assert_('Disassembly' in r)
977- self.assert_('Registers' in r)
978- self.assert_('Stacktrace' in r)
979- self.assert_('ThreadStacktrace' in r)
980+ self.assertTrue('CoreDump' in r)
981+ self.assertTrue('Dependencies' in r)
982+ self.assertTrue('Disassembly' in r)
983+ self.assertTrue('Registers' in r)
984+ self.assertTrue('Stacktrace' in r)
985+ self.assertTrue('ThreadStacktrace' in r)
986
987 # updating with an useless stack trace retains core dump
988 r['StacktraceTop'] = '?? ()'
989@@ -960,17 +1026,17 @@
990 r['FooBar'] = 'bogus'
991 self.crashdb.update_traces(segv_report, r, 'I can has a better retrace?')
992 r = self.crashdb.download(segv_report)
993- self.assert_('CoreDump' in r)
994- self.assert_('Dependencies' in r)
995- self.assert_('Disassembly' in r)
996- self.assert_('Registers' in r)
997- self.assert_('Stacktrace' in r) # TODO: ascertain that it's the updated one
998- self.assert_('ThreadStacktrace' in r)
999- self.failIf('FooBar' in r)
1000+ self.assertTrue('CoreDump' in r)
1001+ self.assertTrue('Dependencies' in r)
1002+ self.assertTrue('Disassembly' in r)
1003+ self.assertTrue('Registers' in r)
1004+ self.assertTrue('Stacktrace' in r) # TODO: ascertain that it's the updated one
1005+ self.assertTrue('ThreadStacktrace' in r)
1006+ self.assertFalse('FooBar' in r)
1007
1008 tags = self.crashdb.launchpad.bugs[segv_report].tags
1009- self.assert_('apport-crash' in tags)
1010- self.failIf('apport-collected' in tags)
1011+ self.assertTrue('apport-crash' in tags)
1012+ self.assertFalse('apport-collected' in tags)
1013
1014 # updating with an useful stack trace removes core dump
1015 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
1016@@ -978,13 +1044,13 @@
1017 r['ThreadStacktrace'] = 'thread\neven longer\ntrace'
1018 self.crashdb.update_traces(segv_report, r, 'good retrace!')
1019 r = self.crashdb.download(segv_report)
1020- self.failIf('CoreDump' in r)
1021- self.assert_('Dependencies' in r)
1022- self.assert_('Disassembly' in r)
1023- self.assert_('Registers' in r)
1024- self.assert_('Stacktrace' in r)
1025- self.assert_('ThreadStacktrace' in r)
1026- self.failIf('FooBar' in r)
1027+ self.assertFalse('CoreDump' in r)
1028+ self.assertTrue('Dependencies' in r)
1029+ self.assertTrue('Disassembly' in r)
1030+ self.assertTrue('Registers' in r)
1031+ self.assertTrue('Stacktrace' in r)
1032+ self.assertTrue('ThreadStacktrace' in r)
1033+ self.assertFalse('FooBar' in r)
1034
1035 # test various situations which caused crashes
1036 r['Stacktrace'] = '' # empty file
1037@@ -995,11 +1061,14 @@
1038 def test_update_description(self):
1039 '''update() with changing description'''
1040
1041- id = self._fill_bug_form(
1042- 'https://bugs.staging.launchpad.net/%s/+source/bash/+filebug?'
1043- 'field.title=testbug&field.actions.search=' % self.crashdb.distro)
1044- self.assert_(id > 0)
1045- print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
1046+ bug_target = self.crashdb.lp_distro.getSourcePackage(name='bash')
1047+ bug = self.crashdb.launchpad.bugs.createBug(
1048+ description='test description for test bug.',
1049+ target=bug_target,
1050+ title='testbug')
1051+ id = bug.id
1052+ self.assertTrue(id > 0)
1053+ sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
1054
1055 r = apport.Report('Bug')
1056
1057@@ -1024,14 +1093,16 @@
1058 def test_update_comment(self):
1059 '''update() with appending comment'''
1060
1061+ bug_target = self.crashdb.lp_distro.getSourcePackage(name='bash')
1062 # we need to fake an apport description separator here, since we
1063 # want to be lazy and use download() for checking the result
1064- id = self._fill_bug_form(
1065- 'https://bugs.staging.launchpad.net/%s/+source/bash/+filebug?'
1066- 'field.title=testbug&field.actions.search=' %
1067- self.crashdb.distro, comment='Pr0blem\n\n--- \nProblemType: Bug')
1068- self.assert_(id > 0)
1069- print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
1070+ bug = self.crashdb.launchpad.bugs.createBug(
1071+ description='Pr0blem\n\n--- \nProblemType: Bug',
1072+ target=bug_target,
1073+ title='testbug')
1074+ id = bug.id
1075+ self.assertTrue(id > 0)
1076+ sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
1077
1078 r = apport.Report('Bug')
1079
1080@@ -1045,8 +1116,8 @@
1081
1082 r = self.crashdb.download(id)
1083
1084- self.failIf('OneLiner' in r)
1085- self.failIf('ShortGoo' in r)
1086+ self.assertFalse('OneLiner' in r)
1087+ self.assertFalse('ShortGoo' in r)
1088 self.assertEqual(r['ProblemType'], 'Bug')
1089 self.assertEqual(r['DpkgTerminalLog'], 'one\ntwo\nthree\nfour\nfive\nsix')
1090 self.assertEqual(r['VarLogDistupgradeBinGoo'], '\x01' * 1024)
1091@@ -1057,11 +1128,14 @@
1092 def test_update_filter(self):
1093 '''update() with a key filter'''
1094
1095- id = self._fill_bug_form(
1096- 'https://bugs.staging.launchpad.net/%s/+source/bash/+filebug?'
1097- 'field.title=testbug&field.actions.search=' % self.crashdb.distro)
1098- self.assert_(id > 0)
1099- print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
1100+ bug_target = self.crashdb.lp_distro.getSourcePackage(name='bash')
1101+ bug = self.crashdb.launchpad.bugs.createBug(
1102+ description='test description for test bug',
1103+ target=bug_target,
1104+ title='testbug')
1105+ id = bug.id
1106+ self.assertTrue(id > 0)
1107+ sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
1108
1109 r = apport.Report('Bug')
1110
1111@@ -1076,11 +1150,11 @@
1112
1113 r = self.crashdb.download(id)
1114
1115- self.failIf('OneLiner' in r)
1116+ self.assertFalse('OneLiner' in r)
1117 self.assertEqual(r['ShortGoo'], 'lineone\nlinetwo')
1118 self.assertEqual(r['ProblemType'], 'Bug')
1119 self.assertEqual(r['DpkgTerminalLog'], 'one\ntwo\nthree\nfour\nfive\nsix')
1120- self.failIf('VarLogDistupgradeBinGoo' in r)
1121+ self.assertFalse('VarLogDistupgradeBinGoo' in r)
1122
1123 self.assertEqual(self.crashdb.launchpad.bugs[id].tags, [])
1124
1125@@ -1099,14 +1173,14 @@
1126 def test_is_reporter(self):
1127 '''is_reporter()'''
1128
1129- self.assert_(self.crashdb.is_reporter(segv_report))
1130- self.failIf(self.crashdb.is_reporter(1))
1131+ self.assertTrue(self.crashdb.is_reporter(segv_report))
1132+ self.assertFalse(self.crashdb.is_reporter(1))
1133
1134 def test_can_update(self):
1135 '''can_update()'''
1136
1137- self.assert_(self.crashdb.can_update(segv_report))
1138- self.failIf(self.crashdb.can_update(1))
1139+ self.assertTrue(self.crashdb.can_update(segv_report))
1140+ self.assertFalse(self.crashdb.can_update(1))
1141
1142 def test_duplicates(self):
1143 '''duplicate handling'''
1144@@ -1131,10 +1205,10 @@
1145 # this should have removed attachments; note that Stacktrace is
1146 # short, and thus inline
1147 r = self.crashdb.download(segv_report)
1148- self.failIf('CoreDump' in r)
1149- self.failIf('Dependencies' in r)
1150- self.failIf('Disassembly' in r)
1151- self.failIf('Registers' in r)
1152+ self.assertFalse('CoreDump' in r)
1153+ self.assertFalse('Dependencies' in r)
1154+ self.assertFalse('Disassembly' in r)
1155+ self.assertFalse('Registers' in r)
1156
1157 # now try duplicating to a duplicate bug; this should automatically
1158 # transition to the master bug
1159@@ -1160,11 +1234,11 @@
1160
1161 # mark_retraced()
1162 unretraced_before = self.crashdb.get_unretraced()
1163- self.assert_(segv_report in unretraced_before)
1164- self.failIf(python_report in unretraced_before)
1165+ self.assertTrue(segv_report in unretraced_before)
1166+ self.assertFalse(python_report in unretraced_before)
1167 self.crashdb.mark_retraced(segv_report)
1168 unretraced_after = self.crashdb.get_unretraced()
1169- self.failIf(segv_report in unretraced_after)
1170+ self.assertFalse(segv_report in unretraced_after)
1171 self.assertEqual(unretraced_before,
1172 unretraced_after.union(set([segv_report])))
1173 self.assertEqual(self.crashdb.get_fixed_version(segv_report), None)
1174@@ -1174,7 +1248,7 @@
1175 self.crashdb.mark_retraced(segv_report)
1176 self.crashdb.mark_retrace_failed(segv_report)
1177 unretraced_after = self.crashdb.get_unretraced()
1178- self.failIf(segv_report in unretraced_after)
1179+ self.assertFalse(segv_report in unretraced_after)
1180 self.assertEqual(unretraced_before,
1181 unretraced_after.union(set([segv_report])))
1182 self.assertEqual(self.crashdb.get_fixed_version(segv_report), None)
1183@@ -1184,7 +1258,7 @@
1184 self.crashdb.mark_retraced(segv_report)
1185 self.crashdb.mark_retrace_failed(segv_report, "I don't like you")
1186 unretraced_after = self.crashdb.get_unretraced()
1187- self.failIf(segv_report in unretraced_after)
1188+ self.assertFalse(segv_report in unretraced_after)
1189 self.assertEqual(unretraced_before,
1190 unretraced_after.union(set([segv_report])))
1191 self.assertEqual(self.crashdb.get_fixed_version(segv_report),
1192@@ -1194,11 +1268,11 @@
1193 '''processing status markings for interpreter crashes'''
1194
1195 unchecked_before = self.crashdb.get_dup_unchecked()
1196- self.assert_(python_report in unchecked_before)
1197- self.failIf(segv_report in unchecked_before)
1198+ self.assertTrue(python_report in unchecked_before)
1199+ self.assertFalse(segv_report in unchecked_before)
1200 self.crashdb._mark_dup_checked(python_report, self.ref_report)
1201 unchecked_after = self.crashdb.get_dup_unchecked()
1202- self.failIf(python_report in unchecked_after)
1203+ self.assertFalse(python_report in unchecked_after)
1204 self.assertEqual(unchecked_before,
1205 unchecked_after.union(set([python_report])))
1206 self.assertEqual(self.crashdb.get_fixed_version(python_report),
1207@@ -1211,7 +1285,7 @@
1208 invalidated by marking it as a duplicate.
1209 '''
1210 id = self._file_segv_report()
1211- print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
1212+ sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
1213
1214 r = self.crashdb.download(id)
1215
1216@@ -1224,7 +1298,7 @@
1217 self.crashdb.update_traces(id, r, 'good retrace!')
1218
1219 r = self.crashdb.download(id)
1220- self.failIf('CoreDump' in r)
1221+ self.assertFalse('CoreDump' in r)
1222
1223 def test_get_fixed_version(self):
1224 '''get_fixed_version() for fixed bugs
1225@@ -1235,7 +1309,7 @@
1226 self._mark_report_fixed(segv_report)
1227 fixed_ver = self.crashdb.get_fixed_version(segv_report)
1228 self.assertNotEqual(fixed_ver, None)
1229- self.assert_(fixed_ver[0].isdigit())
1230+ self.assertTrue(fixed_ver[0].isdigit())
1231 self._mark_report_new(segv_report)
1232 self.assertEqual(self.crashdb.get_fixed_version(segv_report), None)
1233
1234@@ -1247,119 +1321,99 @@
1235 def _get_instance(klass):
1236 '''Create a CrashDB instance'''
1237
1238- return CrashDatabase(os.environ.get('LP_CREDENTIALS'), '',
1239- {'distro': 'ubuntu', 'staging': True})
1240-
1241- def _fill_bug_form(self, url, comment=None):
1242- '''Fill form for a distro bug and commit the bug.
1243-
1244- Return the report ID.
1245- '''
1246- cj = cookielib.MozillaCookieJar()
1247- cj.load(os.path.expanduser('~/.lpcookie.txt'))
1248- opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
1249- opener.addheaders = [('Referer', url)]
1250-
1251- re_pkg = re.compile('\+source/([\w]+)/')
1252- re_title = re.compile('<input.*id="field.title".*value="([^"]+)"')
1253- re_tags = re.compile('<input.*id="field.tags".*value="([^"]*)"')
1254-
1255- # parse default field values from reporting page
1256- while True:
1257- res = opener.open(url)
1258- try:
1259- self.assertEqual(res.getcode(), 200)
1260- except AttributeError:
1261- pass # getcode() is new in Python 2.6
1262- content = res.read()
1263-
1264- if 'Please wait while bug data is processed' in content:
1265- print '.',
1266- sys.stdout.flush()
1267- time.sleep(5)
1268- continue
1269-
1270- break
1271-
1272- m_pkg = re_pkg.search(url)
1273- m_title = re_title.search(content)
1274- m_tags = re_tags.search(content)
1275-
1276- # strip off GET arguments from URL
1277- url = url.split('?')[0]
1278-
1279- # create request to file bug
1280- args = {
1281- 'packagename_option': 'choose',
1282- 'field.packagename': m_pkg.group(1),
1283- 'field.title': m_title.group(1),
1284- 'field.tags': m_tags.group(1),
1285- 'field.comment': comment or 'ZOMG!',
1286- 'field.actions.submit_bug': '1',
1287- }
1288-
1289- res = opener.open(url, data=urllib.urlencode(args))
1290- try:
1291- self.assertEqual(res.getcode(), 200)
1292- except AttributeError:
1293- pass # getcode() is new in Python 2.6
1294- self.assert_('+source/%s/+bug/' % m_pkg.group(1) in res.geturl())
1295- id = res.geturl().split('/')[-1]
1296- return int(id)
1297-
1298- def _fill_bug_form_project(self, url):
1299- '''Fill form for a project bug and commit the bug.
1300-
1301- Return the report ID.
1302- '''
1303- cj = cookielib.MozillaCookieJar()
1304- cj.load(os.path.expanduser('~/.lpcookie.txt'))
1305- opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
1306- opener.addheaders = [('Referer', url)]
1307-
1308- m = re.search('launchpad.net/([^/]+)/\+filebug', url)
1309- assert m
1310- project = m.group(1)
1311-
1312- re_title = re.compile('<input.*id="field.title".*value="([^"]+)"')
1313- re_tags = re.compile('<input.*id="field.tags".*value="([^"]+)"')
1314-
1315- # parse default field values from reporting page
1316- while True:
1317- res = opener.open(url)
1318- try:
1319- self.assertEqual(res.getcode(), 200)
1320- except AttributeError:
1321- pass # getcode() is new in Python 2.6
1322- content = res.read()
1323-
1324- if 'Please wait while bug data is processed' in content:
1325- print '.',
1326- sys.stdout.flush()
1327- time.sleep(5)
1328- continue
1329-
1330- break
1331-
1332- m_title = re_title.search(content)
1333- m_tags = re_tags.search(content)
1334-
1335- # strip off GET arguments from URL
1336- url = url.split('?')[0]
1337-
1338- # create request to file bug
1339- args = {
1340- 'field.title': m_title.group(1),
1341- 'field.tags': m_tags.group(1),
1342- 'field.comment': 'ZOMG!',
1343- 'field.actions.submit_bug': '1',
1344- }
1345-
1346- res = opener.open(url, data=urllib.urlencode(args))
1347- self.assertEqual(res.getcode(), 200)
1348- self.assert_(('launchpad.net/%s/+bug' % project) in res.geturl())
1349- id = res.geturl().split('/')[-1]
1350- return int(id)
1351+ launchpad_instance = os.environ.get('APPORT_LAUNCHPAD_INSTANCE') or 'staging'
1352+
1353+ return CrashDatabase(os.environ.get('LP_CREDENTIALS'), '',
1354+ {'distro': 'ubuntu',
1355+ 'launchpad_instance': launchpad_instance})
1356+
1357+ @classmethod
1358+ def _get_bug_target(klass, db, report):
1359+ '''Return the bug_target for this report.'''
1360+
1361+ project = db.options.get('project')
1362+ if report.has_key('SourcePackage'):
1363+ return db.lp_distro.getSourcePackage(name=report['SourcePackage'])
1364+ elif project:
1365+ return db.launchpad.projects[project]
1366+ else:
1367+ return self.lp_distro
1368+
1369+ def _get_librarian_hostname(self):
1370+ '''Return the librarian hostname according to the LP hostname used.'''
1371+
1372+ hostname = self.crashdb.get_hostname()
1373+ if 'staging' in hostname:
1374+ return 'staging.launchpadlibrarian.net'
1375+ else:
1376+ return 'launchpad.dev:58080'
1377+
1378+ def _file_bug(self, bug_target, report, handle, comment=None):
1379+ '''File a bug report.'''
1380+
1381+ bug_title = report.get('Title', report.standard_title())
1382+
1383+ blob_info = self.crashdb.launchpad.temporary_blobs.fetch(
1384+ token=handle)
1385+ # XXX 2010-08-03 matsubara bug=612990:
1386+ # Can't fetch the blob directly, so let's load it from the
1387+ # representation.
1388+ blob = self.crashdb.launchpad.load(blob_info['self_link'])
1389+ #XXX Need to find a way to trigger the job that process the blob
1390+ # rather polling like this. This makes the test suite take forever
1391+ # to run.
1392+ while not blob.hasBeenProcessed():
1393+ time.sleep(1)
1394+
1395+ # processed_blob contains info about privacy, additional comments
1396+ # and attachments.
1397+ processed_blob = blob.getProcessedData()
1398+
1399+ bug = self.crashdb.launchpad.bugs.createBug(
1400+ description=processed_blob['extra_description'],
1401+ private=processed_blob['private'],
1402+ tags=processed_blob['initial_tags'],
1403+ target=bug_target,
1404+ title=bug_title)
1405+
1406+ for comment in processed_blob['comments']:
1407+ bug.newMessage(content=comment)
1408+
1409+ # Ideally, one would be able to retrieve the attachment content
1410+ # from the ProblemReport object or from the processed_blob.
1411+ # Unfortunately the processed_blob only give us the Launchpad
1412+ # librarian file_alias_id, so that's why we need to
1413+ # download it again and upload to the bug report. It'd be even
1414+ # better if addAttachment could work like linkAttachment, the LP
1415+ # api used in the +filebug web UI, but there are security concerns
1416+ # about the way linkAttachment works.
1417+ librarian_url = 'http://%s' % self._get_librarian_hostname()
1418+ for attachment in processed_blob['attachments']:
1419+ filename = description = attachment['description']
1420+ # Download the attachment data.
1421+ data = urllib.urlopen(urllib.basejoin(librarian_url,
1422+ str(attachment['file_alias_id']) + '/' + filename)).read()
1423+ # Add the attachment to the newly created bug report.
1424+ bug.addAttachment(
1425+ comment=filename,
1426+ data=data,
1427+ filename=filename,
1428+ description=description)
1429+
1430+ for subscriber in processed_blob['subscribers']:
1431+ sub = self.crashdb.launchpad.people[subscriber]
1432+ if sub:
1433+ bug.subscribe(person=sub)
1434+
1435+ for submission_key in processed_blob['hwdb_submission_keys']:
1436+ # XXX 2010-08-04 matsubara bug=628889:
1437+ # Can't fetch the submission directly, so let's load it
1438+ # from the representation.
1439+ submission = self.crashdb.launchpad.load(
1440+ 'https://api.%s/beta/+hwdb/+submission/%s'
1441+ % (self.crashdb.get_hostname(), submission_key))
1442+ bug.linkHWSubmission(submission=submission)
1443+ return int(bug.id)
1444
1445 def _mark_needs_retrace(self, id):
1446 '''Mark a report ID as needing retrace.'''
1447@@ -1401,15 +1455,17 @@
1448 '''Verify that report ID is marked as regression.'''
1449
1450 bug = self.crashdb.launchpad.bugs[id]
1451- self.assert_('regression-retracer' in bug.tags)
1452+ self.assertTrue('regression-retracer' in bug.tags)
1453
1454 def test_project(self):
1455 '''reporting crashes against a project instead of a distro'''
1456
1457+ launchpad_instance = os.environ.get('APPORT_LAUNCHPAD_INSTANCE') or 'staging'
1458 # crash database for langpack-o-matic project (this does not have
1459 # packages in any distro)
1460- crashdb = CrashDatabase(os.environ.get('LP_CREDENTIALS'), '',
1461- {'project': 'langpack-o-matic', 'staging': True})
1462+ crashdb = CrashDatabase(os.environ.get('LP_CREDENTIALS'), '',
1463+ {'project': 'langpack-o-matic',
1464+ 'launchpad_instance': launchpad_instance})
1465 self.assertEqual(crashdb.distro, None)
1466
1467 # create Python crash report
1468@@ -1417,21 +1473,22 @@
1469 r['ExecutablePath'] = '/bin/foo'
1470 r['Traceback'] = '''Traceback (most recent call last):
1471 File "/bin/foo", line 67, in fuzz
1472- print weird
1473+ print(weird)
1474 NameError: global name 'weird' is not defined'''
1475 r.add_os_info()
1476 r.add_user_info()
1477- self.assertEqual(r.standard_title(), 'foo crashed with NameError in fuzz()')
1478+ self.assertEqual(r.standard_title(),
1479+ "foo crashed with NameError in fuzz(): global name 'weird' is not defined")
1480
1481 # file it
1482 handle = crashdb.upload(r)
1483- self.assert_(handle)
1484- url = crashdb.get_comment_url(r, handle)
1485- self.assert_('launchpad.net/langpack-o-matic/+filebug' in url)
1486+ self.assertTrue(handle)
1487+ bug_target = self._get_bug_target(crashdb, r)
1488+ self.assertEqual(bug_target.name, 'langpack-o-matic')
1489
1490- id = self._fill_bug_form_project(url)
1491- self.assert_(id > 0)
1492- print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
1493+ id = self._file_bug(bug_target, r, handle)
1494+ self.assertTrue(id > 0)
1495+ sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
1496
1497 # update
1498 r = crashdb.download(id)
1499@@ -1454,21 +1511,22 @@
1500 '''download() of uncommon description formats'''
1501
1502 # only ProblemType/Architecture/DistroRelease in description
1503- r = self.crashdb.download(269539)
1504+ r = self.crashdb.download(self.uncommon_description_bug.id)
1505 self.assertEqual(r['ProblemType'], 'Package')
1506 self.assertEqual(r['Architecture'], 'amd64')
1507- self.assert_(r['DistroRelease'].startswith('Ubuntu '))
1508- self.assert_('DpkgTerminalLog' in r)
1509+ self.assertTrue(r['DistroRelease'].startswith('Ubuntu '))
1510
1511 def test_escalation(self):
1512 '''Escalating bugs with more than 10 duplicates'''
1513
1514 assert segv_report, 'you need to run test_1_report_segv() first'
1515
1516- db = CrashDatabase(os.environ.get('LP_CREDENTIALS'), '',
1517- {'distro': 'ubuntu', 'staging': True,
1518- 'escalation_tag': 'omgkittens',
1519- 'escalation_subscription': 'apport-hackers'})
1520+ launchpad_instance = os.environ.get('APPORT_LAUNCHPAD_INSTANCE') or 'staging'
1521+ db = CrashDatabase(os.environ.get('LP_CREDENTIALS'), '',
1522+ {'distro': 'ubuntu',
1523+ 'launchpad_instance': launchpad_instance,
1524+ 'escalation_tag': 'omgkittens',
1525+ 'escalation_subscription': 'apport-hackers'})
1526
1527 count = 0
1528 p = db.launchpad.people[db.options['escalation_subscription']].self_link
1529@@ -1476,38 +1534,36 @@
1530 try:
1531 for b in range(first_dup, first_dup+13):
1532 count += 1
1533- print b,
1534- sys.stdout.flush()
1535+ sys.stderr.write('%i ' % b)
1536 db.close_duplicate(b, segv_report)
1537 b = db.launchpad.bugs[segv_report]
1538 has_escalation_tag = db.options['escalation_tag'] in b.tags
1539- has_escalation_subsciption = any([s.person_link == p for s in b.subscriptions])
1540+ has_escalation_subscription = any([s.person_link == p for s in b.subscriptions])
1541 if count <= 10:
1542- self.failIf(has_escalation_tag)
1543- self.failIf(has_escalation_subsciption)
1544+ self.assertFalse(has_escalation_tag)
1545+ self.assertFalse(has_escalation_subscription)
1546 else:
1547- self.assert_(has_escalation_tag)
1548- self.assert_(has_escalation_subsciption)
1549+ self.assertTrue(has_escalation_tag)
1550+ self.assertTrue(has_escalation_subscription)
1551 finally:
1552 for b in range(first_dup, first_dup+count):
1553- print 'R%i' % b,
1554- sys.stdout.flush()
1555+ sys.stderr.write('R%i ' % b)
1556 db.close_duplicate(b, None)
1557- print
1558+ sys.stderr.write('\n')
1559
1560 def test_marking_python_task_mangle(self):
1561 '''source package task fixup for marking interpreter crashes'''
1562
1563 self._mark_needs_dupcheck(python_report)
1564 unchecked_before = self.crashdb.get_dup_unchecked()
1565- self.assert_(python_report in unchecked_before)
1566+ self.assertTrue(python_report in unchecked_before)
1567
1568 # add an upstream task, and remove the package name from the
1569 # package task; _mark_dup_checked is supposed to restore the
1570 # package name
1571 b = self.crashdb.launchpad.bugs[python_report]
1572 t = b.bug_tasks[0]
1573- t.target = self.crashdb.launchpad.distributions['ubuntu'].getSourcePackage(name='pmount')
1574+ t.target = self.crashdb.launchpad.distributions['ubuntu'].getSourcePackage(name='bash')
1575 t.status = 'Invalid'
1576 t.lp_save()
1577 b.addTask(target=self.crashdb.launchpad.projects['coreutils'])
1578@@ -1516,7 +1572,7 @@
1579 self.crashdb._mark_dup_checked(python_report, self.ref_report)
1580
1581 unchecked_after = self.crashdb.get_dup_unchecked()
1582- self.failIf(python_report in unchecked_after)
1583+ self.assertFalse(python_report in unchecked_after)
1584 self.assertEqual(unchecked_before,
1585 unchecked_after.union(set([python_report])))
1586
1587@@ -1526,12 +1582,12 @@
1588 self.assertEqual(b.bug_tasks[0].status, 'New')
1589
1590 # package-less distro task should have package name fixed
1591- self.assertEqual(b.bug_tasks[1].bug_target_name, 'coreutils (Ubuntu)')
1592- self.assertEqual(b.bug_tasks[1].status, 'New')
1593+ self.assertEqual(b.bug_tasks[2].bug_target_name, 'coreutils (Ubuntu)')
1594+ self.assertEqual(b.bug_tasks[2].status, 'New')
1595
1596- # invalid pmount task should be unmodified
1597- self.assertEqual(b.bug_tasks[2].bug_target_name, 'pmount (Ubuntu)')
1598- self.assertEqual(b.bug_tasks[2].status, 'Invalid')
1599+ # invalid bash task should be unmodified
1600+ self.assertEqual(b.bug_tasks[1].bug_target_name, 'bash (Ubuntu)')
1601+ self.assertEqual(b.bug_tasks[1].status, 'Invalid')
1602
1603 # the invalid task should not confuse get_fixed_version()
1604 self.assertEqual(self.crashdb.get_fixed_version(python_report),
1605
1606=== modified file 'apport/crashdb_impl/memory.py'
1607--- apport/crashdb_impl/memory.py 2010-06-28 14:17:38 +0000
1608+++ apport/crashdb_impl/memory.py 2011-04-20 23:32:24 +0000
1609@@ -21,13 +21,13 @@
1610
1611 This is mainly useful for testing and debugging.'''
1612
1613- def __init__(self, auth_file, bugpattern_baseurl, options):
1614+ def __init__(self, auth_file, bugpattern_url, options):
1615 '''Initialize crash database connection.
1616
1617 This class does not support bug patterns and authentication.'''
1618
1619 apport.crashdb.CrashDatabase.__init__(self, auth_file,
1620- bugpattern_baseurl, options)
1621+ bugpattern_url, options)
1622
1623 self.reports = [] # list of dictionaries with keys: report, fixed_version, dup_of, comment
1624 self.unretraced = set()
1625@@ -345,12 +345,10 @@
1626 databases = {
1627 'testsuite': {
1628 'impl': 'memory',
1629- 'bug_pattern_base': None,
1630 'dyn_option': get_dyn(),
1631 },
1632 get_dyn_name(): {
1633 'impl': 'memory',
1634- 'bug_pattern_base': None,
1635 'whoami': 'dynname',
1636 }
1637 }
1638@@ -360,7 +358,7 @@
1639 db = apport.crashdb.get_crashdb(None, None, crashdb_conf.name)
1640 self.assertEqual(db.options['dyn_option'], '4')
1641 db = apport.crashdb.get_crashdb(None, 'on_thefly', crashdb_conf.name)
1642- self.failIf('dyn_opion' in db.options)
1643+ self.assertFalse('dyn_opion' in db.options)
1644 self.assertEqual(db.options['whoami'], 'dynname')
1645
1646 #
1647@@ -428,7 +426,7 @@
1648 self.assertEqual(self.crashes.reports[1]['comment'], 'muhaha')
1649 self.assertEqual(self.crashes.download(1)['Package'], 'libfoo1 1.2-4')
1650 self.assertEqual(self.crashes.download(1)['StacktraceTop'], 'Fresh!')
1651- self.failIf(self.crashes.download(1).has_key('FooBar'))
1652+ self.assertFalse(self.crashes.download(1).has_key('FooBar'))
1653
1654 self.assertRaises(IndexError, self.crashes.update_traces, 5, None)
1655
1656@@ -586,6 +584,33 @@
1657 self.assertEqual(self.crashes.check_duplicate(5), None)
1658 self.assertEqual(self.crashes.check_duplicate(6), (5, None))
1659
1660+ def test_check_duplicate_custom_signature(self):
1661+ '''check_duplicate() with custom DuplicateSignature: field'''
1662+
1663+ r = apport.Report()
1664+ r['SourcePackage'] = 'bash'
1665+ r['Package'] = 'bash 5'
1666+ r['DuplicateSignature'] = 'Code42Blue'
1667+ self.assertEqual(self.crashes.get_comment_url(r, self.crashes.upload(r)),
1668+ 'http://bash.bugs.example.com/5')
1669+
1670+ self.crashes.init_duplicate_db(':memory:')
1671+ self.assertEqual(self.crashes.check_duplicate(5), None)
1672+
1673+ self.assertEqual(self.crashes._duplicate_db_dump(), {'Code42Blue': (5, None)})
1674+
1675+ # this one has a standard crash_signature
1676+ self.assertEqual(self.crashes.check_duplicate(0), None)
1677+ # ... but DuplicateSignature wins
1678+ self.crashes.download(0)['DuplicateSignature'] = 'Code42Blue'
1679+ self.assertEqual(self.crashes.check_duplicate(0), (5, None))
1680+
1681+ self.crashes.download(1)['DuplicateSignature'] = 'CodeRed'
1682+ self.assertEqual(self.crashes.check_duplicate(1), None)
1683+ self.assertEqual(self.crashes._duplicate_db_dump(),
1684+ {'Code42Blue': (5, None), 'CodeRed': (1, None),
1685+ self.crashes.download(0).crash_signature(): (0, None)})
1686+
1687 def test_check_duplicate_report_arg(self):
1688 '''check_duplicate() with explicitly passing report'''
1689
1690@@ -639,7 +664,7 @@
1691 if pid == 0:
1692 try:
1693 self.crashes.duplicate_db_consolidate()
1694- except Exception, e:
1695+ except Exception as e:
1696 if 'database is locked' in str(e):
1697 os._exit(42)
1698 else:
1699@@ -648,7 +673,7 @@
1700
1701 try:
1702 self.crashes.duplicate_db_consolidate()
1703- except Exception, e:
1704+ except Exception as e:
1705 if 'database is locked' in str(e):
1706 locked_exceptions += 1
1707 else:
1708@@ -656,7 +681,7 @@
1709
1710 # wait on child, examine status
1711 status = os.wait()[1]
1712- self.assert_(os.WIFEXITED(status))
1713+ self.assertTrue(os.WIFEXITED(status))
1714 status = os.WEXITSTATUS(status)
1715 if status == 42:
1716 locked_exceptions += 1
1717@@ -702,19 +727,19 @@
1718 self.crashes.init_duplicate_db(':memory:')
1719
1720 # a fresh and empty db does not need consolidation
1721- self.failIf(self.crashes.duplicate_db_needs_consolidation())
1722+ self.assertFalse(self.crashes.duplicate_db_needs_consolidation())
1723
1724 time.sleep(1.1)
1725 # for an one-day interval we do not need consolidation
1726- self.failIf(self.crashes.duplicate_db_needs_consolidation())
1727+ self.assertFalse(self.crashes.duplicate_db_needs_consolidation())
1728 # neither for a ten second one (check timezone offset errors)
1729- self.failIf(self.crashes.duplicate_db_needs_consolidation(10))
1730+ self.assertFalse(self.crashes.duplicate_db_needs_consolidation(10))
1731 # but for an one second interval
1732- self.assert_(self.crashes.duplicate_db_needs_consolidation(1))
1733+ self.assertTrue(self.crashes.duplicate_db_needs_consolidation(1))
1734
1735 self.crashes.duplicate_db_consolidate()
1736
1737- self.failIf(self.crashes.duplicate_db_needs_consolidation(1))
1738+ self.assertFalse(self.crashes.duplicate_db_needs_consolidation(1))
1739
1740 def test_change_master_id(self):
1741 '''duplicate_db_change_master_id()'''
1742@@ -762,7 +787,7 @@
1743 self.assertEqual(self.crashes._duplicate_db_dump(),
1744 {self.crashes.download(0).crash_signature(): (0, '42')})
1745
1746- self.failIf(self.crashes.duplicate_db_needs_consolidation())
1747+ self.assertFalse(self.crashes.duplicate_db_needs_consolidation())
1748 del self.crashes
1749
1750 # damage file
1751@@ -771,7 +796,7 @@
1752 f.close()
1753
1754 self.crashes = CrashDatabase(None, None, {})
1755- self.assertRaises(SystemError, self.crashes.init_duplicate_db, db)
1756+ self.assertRaises(StandardError, self.crashes.init_duplicate_db, db)
1757
1758 finally:
1759 os.unlink(db)
1760
1761=== modified file 'apport/crashdb_impl/multipartpost_handler.py'
1762--- apport/crashdb_impl/multipartpost_handler.py 2009-04-11 17:11:06 +0000
1763+++ apport/crashdb_impl/multipartpost_handler.py 2011-04-20 23:32:24 +0000
1764@@ -67,7 +67,7 @@
1765 v_vars.append((key, value))
1766 except TypeError:
1767 systype, value, traceback = sys.exc_info()
1768- raise TypeError, "not a valid non-string sequence or mapping object", traceback
1769+ raise TypeError("not a valid non-string sequence or mapping object").with_traceback(traceback)
1770
1771 if len(v_files) == 0:
1772 data = urllib.urlencode(v_vars, doseq)
1773
1774=== modified file 'apport/fileutils.py'
1775--- apport/fileutils.py 2009-12-23 11:01:21 +0000
1776+++ apport/fileutils.py 2011-04-20 23:32:24 +0000
1777@@ -9,10 +9,17 @@
1778 # option) any later version. See http://www.gnu.org/copyleft/gpl.html for
1779 # the full text of the license.
1780
1781-import os, glob, subprocess, os.path, ConfigParser
1782+import os, glob, subprocess, os.path
1783+
1784+try:
1785+ from configparser import ConfigParser, NoOptionError, NoSectionError
1786+except ImportError:
1787+ # Python 2
1788+ from ConfigParser import ConfigParser, NoOptionError, NoSectionError
1789+
1790 from problem_report import ProblemReport
1791
1792-from packaging_impl import impl as packaging
1793+from apport.packaging_impl import impl as packaging
1794
1795 report_dir = os.environ.get('APPORT_REPORT_DIR', '/var/crash')
1796
1797@@ -196,7 +203,7 @@
1798 elif report.has_key('Package'):
1799 subject = report['Package'].split(None, 1)[0]
1800 else:
1801- raise ValueError, 'report has neither ExecutablePath nor Package attribute'
1802+ raise ValueError('report has neither ExecutablePath nor Package attribute')
1803
1804 if not uid:
1805 uid = os.getuid()
1806@@ -234,7 +241,7 @@
1807 interpreted as a boolean.
1808 '''
1809 if not get_config.config:
1810- get_config.config = ConfigParser.ConfigParser()
1811+ get_config.config = ConfigParser()
1812 get_config.config.read(os.path.expanduser(_config_file))
1813
1814 try:
1815@@ -242,7 +249,7 @@
1816 return get_config.config.getboolean(section, setting)
1817 else:
1818 return get_config.config.get(section, setting)
1819- except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
1820+ except (NoOptionError, NoSectionError):
1821 return default
1822
1823 get_config.config = None
1824@@ -252,7 +259,10 @@
1825 #
1826
1827 import unittest, tempfile, os, shutil, sys, time
1828-from cStringIO import StringIO
1829+try:
1830+ from cStringIO import StringIO
1831+except ImportError:
1832+ from io import StringIO
1833
1834 class _T(unittest.TestCase):
1835 def setUp(self):
1836@@ -278,8 +288,8 @@
1837
1838 open(r1, 'w').write('report 1')
1839 open(r2, 'w').write('report 2')
1840- os.chmod(r1, 0600)
1841- os.chmod(r2, 0600)
1842+ os.chmod(r1, 0o600)
1843+ os.chmod(r2, 0o600)
1844 if create_inaccessible:
1845 ri = os.path.join(report_dir, 'inaccessible.crash')
1846 open(ri, 'w').write('inaccessible')
1847@@ -321,8 +331,8 @@
1848 if onedesktop:
1849 d = find_package_desktopfile(onedesktop)
1850 self.assertNotEqual(d, None, 'one-desktop package %s' % onedesktop)
1851- self.assert_(os.path.exists(d))
1852- self.assert_(d.endswith('.desktop'))
1853+ self.assertTrue(os.path.exists(d))
1854+ self.assertTrue(d.endswith('.desktop'))
1855
1856 def test_likely_packaged(self):
1857 '''likely_packaged().'''
1858@@ -443,9 +453,9 @@
1859 self.assertRaises(ValueError, make_report_path, pr)
1860
1861 pr['Package'] = 'bash 1'
1862- self.assert_(make_report_path(pr).startswith('%s/bash' % report_dir))
1863+ self.assertTrue(make_report_path(pr).startswith('%s/bash' % report_dir))
1864 pr['ExecutablePath'] = '/bin/bash';
1865- self.assert_(make_report_path(pr).startswith('%s/_bin_bash' % report_dir))
1866+ self.assertTrue(make_report_path(pr).startswith('%s/_bin_bash' % report_dir))
1867
1868 def test_check_files_md5(self):
1869 '''check_files_md5().'''
1870
1871=== modified file 'apport/hookutils.py'
1872--- apport/hookutils.py 2010-06-24 13:37:14 +0000
1873+++ apport/hookutils.py 2011-04-20 23:32:24 +0000
1874@@ -1,6 +1,6 @@
1875 '''Convenience functions for use in package hooks.'''
1876
1877-# Copyright (C) 2008 - 2010 Canonical Ltd.
1878+# Copyright (C) 2008 - 2011 Canonical Ltd.
1879 # Authors:
1880 # Matt Zimmerman <mdz@canonical.com>
1881 # Brian Murray <brian@ubuntu.com>
1882@@ -21,6 +21,8 @@
1883 import string
1884 import stat
1885 import base64
1886+import tempfile
1887+import shutil
1888
1889 import xml.dom, xml.dom.minidom
1890
1891@@ -51,7 +53,7 @@
1892 '''
1893 try:
1894 return open(path).read().strip()
1895- except Exception, e:
1896+ except Exception as e:
1897 return 'Error: ' + str(e)
1898
1899 def attach_file(report, path, key=None):
1900@@ -63,6 +65,9 @@
1901 if not key:
1902 key = path_to_key(path)
1903
1904+ # Do not clobber existing keys
1905+ while report.has_key(key):
1906+ key += "_"
1907 report[key] = read_file(path)
1908
1909 def attach_conffiles(report, package, conffiles=None):
1910@@ -105,10 +110,22 @@
1911 report[key] = '[deleted]'
1912
1913 def attach_dmesg(report):
1914- '''Attach information from the kernel ring buffer (dmesg).'''
1915+ '''Attach information from the kernel ring buffer (dmesg).
1916
1917- report['BootDmesg'] = open('/var/log/dmesg').read()
1918- report['CurrentDmesg'] = command_output(['sh', '-c', 'dmesg | comm -13 --nocheck-order /var/log/dmesg -'])
1919+ This won't overwite already existing information.
1920+ '''
1921+ try:
1922+ if not report.get('BootDmesg', '').strip():
1923+ report['BootDmesg'] = open('/var/log/dmesg').read()
1924+ except IOError:
1925+ pass
1926+ if not report.get('CurrentDmesg', '').strip():
1927+ dmesg = command_output(['sh', '-c', 'dmesg | comm -13 --nocheck-order /var/log/dmesg -'])
1928+ # if an initial message was truncated by the ring buffer, skip over it
1929+ first_newline = dmesg.find('\n[')
1930+ if first_newline != -1:
1931+ dmesg = dmesg[first_newline+1:]
1932+ report['CurrentDmesg'] = dmesg
1933
1934 def attach_dmi(report):
1935 dmi_dir = '/sys/class/dmi/id'
1936@@ -148,11 +165,12 @@
1937
1938 attach_file(report, '/proc/interrupts', 'ProcInterrupts')
1939 attach_file(report, '/proc/cpuinfo', 'ProcCpuinfo')
1940- attach_file(report, '/proc/cmdline', 'ProcCmdLine')
1941+ attach_file(report, '/proc/cmdline', 'ProcKernelCmdLine')
1942 attach_file(report, '/proc/modules', 'ProcModules')
1943 attach_file(report, '/var/log/udev', 'UdevLog')
1944
1945- report['Lspci'] = command_output(['lspci','-vvnn'])
1946+ if os.path.exists('/sys/bus/pci'):
1947+ report['Lspci'] = command_output(['lspci','-vvnn'])
1948 report['Lsusb'] = command_output(['lsusb'])
1949 report['UdevDb'] = command_output(['udevadm', 'info', '--export-db'])
1950
1951@@ -202,10 +220,11 @@
1952 report['PciMultimedia'] = pci_devices(PCI_MULTIMEDIA)
1953
1954 cards = []
1955- for line in open('/proc/asound/cards'):
1956- if ']:' in line:
1957- fields = line.lstrip().split()
1958- cards.append(int(fields[0]))
1959+ if os.path.exists('/proc/asound/cards'):
1960+ for line in open('/proc/asound/cards'):
1961+ if ']:' in line:
1962+ fields = line.lstrip().split()
1963+ cards.append(int(fields[0]))
1964
1965 for card in cards:
1966 key = 'Card%d.Amixer.info' % card
1967@@ -260,7 +279,7 @@
1968 try:
1969 sp = subprocess.Popen(command, stdout=subprocess.PIPE,
1970 stderr=stderr, close_fds=True, env=env)
1971- except OSError, e:
1972+ except OSError as e:
1973 return 'Error: ' + str(e)
1974
1975 out = sp.communicate(input)[0]
1976@@ -270,14 +289,7 @@
1977 return 'Error: command %s failed with exit code %i: %s' % (
1978 str(command), sp.returncode, out)
1979
1980-def root_command_output(command, input = None, stderr = subprocess.STDOUT):
1981- '''Try to execute given command (array) as root and return its stdout.
1982-
1983- This passes the command through gksu, kdesudo, or sudo, depending on the
1984- running desktop environment.
1985-
1986- In case of failure, a textual error gets returned.
1987- '''
1988+def _root_command_prefix():
1989 if os.getuid() == 0:
1990 prefix = []
1991 elif os.getenv('DISPLAY') and \
1992@@ -295,7 +307,60 @@
1993 else:
1994 prefix = ['sudo']
1995
1996- return command_output(prefix + command, input, stderr)
1997+ return prefix
1998+
1999+def root_command_output(command, input = None, stderr = subprocess.STDOUT):
2000+ '''Try to execute given command (array) as root and return its stdout.
2001+
2002+ This passes the command through gksu, kdesudo, or sudo, depending on the
2003+ running desktop environment.
2004+
2005+ In case of failure, a textual error gets returned.
2006+ '''
2007+ assert type(command) == type([]), 'command must be a list'
2008+ return command_output(_root_command_prefix() + command, input, stderr)
2009+
2010+def attach_root_command_outputs(report, command_map):
2011+ '''Execute multiple commands as root and put their outputs into report.
2012+
2013+ command_map is a keyname -> 'shell command' dictionary with the commands to
2014+ run. They are all run through /bin/sh, so you need to take care of shell
2015+ escaping yourself. To include stderr output of a command, end it with
2016+ "2>&1".
2017+
2018+ Just like root_command_output() this will use gksu, kdesudo, or sudo for
2019+ gaining root privileges, depending on the running desktop environment.
2020+
2021+ This is preferrable to using root_command_output() multiple times, as that
2022+ will ask for the password every time.
2023+ '''
2024+ workdir = tempfile.mkdtemp()
2025+ try:
2026+ # create a shell script with all the commands
2027+ script_path = os.path.join(workdir, ':script:')
2028+ script = open(script_path, 'w')
2029+ for keyname, command in command_map.items():
2030+ assert hasattr(command, 'strip'), 'command must be a string (shell command)'
2031+ # use "| cat" here, so that we can end commands with 2>&1
2032+ # (otherwise it would have the wrong redirection order)
2033+ script.write('%s | cat > %s\n' % (command, os.path.join(workdir, keyname)))
2034+ script.close()
2035+
2036+ # run script
2037+ env = os.environ.copy()
2038+ env['LC_MESSAGES'] = 'C'
2039+ env['LANGUAGE'] = ''
2040+ sp = subprocess.Popen(_root_command_prefix() + ['/bin/sh', script_path],
2041+ close_fds=True, env=env)
2042+ sp.wait()
2043+
2044+ # now read back the individual outputs
2045+ for keyname in command_map:
2046+ f = open(os.path.join(workdir, keyname))
2047+ report[keyname] = f.read()
2048+ f.close()
2049+ finally:
2050+ shutil.rmtree(workdir)
2051
2052 def recent_syslog(pattern):
2053 '''Extract recent messages from syslog which match a regex.
2054@@ -303,9 +368,12 @@
2055 pattern should be a "re" object.
2056 '''
2057 lines = ''
2058- for line in open('/var/log/syslog'):
2059- if pattern.search(line):
2060- lines += line
2061+ try:
2062+ for line in open('/var/log/syslog'):
2063+ if pattern.search(line):
2064+ lines += line
2065+ except IOError:
2066+ return []
2067 return lines
2068
2069 def xsession_errors(pattern):
2070@@ -440,7 +508,7 @@
2071 def attach_wifi(report):
2072 '''Attach wireless (WiFi) network information to report.'''
2073
2074- report['WifiSyslog'] = recent_syslog(re.compile(r'(NetworkManager|modem-manager|dhclient|kernel):'))
2075+ report['WifiSyslog'] = recent_syslog(re.compile(r'(NetworkManager|modem-manager|dhclient|kernel)(\[\d+\])?:'))
2076 report['IwConfig'] = re.sub('Encryption key:(.*)', 'Encryption key: <hidden>', command_output(['iwconfig']))
2077 report['RfKill'] = command_output(['rfkill', 'list'])
2078 report['CRDA'] = command_output(['iw', 'reg', 'get'])
2079@@ -485,20 +553,30 @@
2080
2081 Arguments may be package names or globs, e. g. "foo*"
2082 '''
2083- versions = ''
2084+ versions = []
2085 for package_pattern in packages:
2086 if not package_pattern:
2087 continue
2088- for package in packaging.package_name_glob(package_pattern):
2089+
2090+ matching_packages = packaging.package_name_glob(package_pattern)
2091+
2092+ if not matching_packages:
2093+ versions.append((package_pattern, 'N/A'))
2094+
2095+ for package in sorted(matching_packages):
2096 try:
2097 version = packaging.get_version(package)
2098 except ValueError:
2099 version = 'N/A'
2100 if version is None:
2101 version = 'N/A'
2102- versions += '%s %s\n' % (package, version)
2103-
2104- return versions
2105+ versions.append((package,version))
2106+
2107+ package_width, version_width = \
2108+ map(max, [map(len, t) for t in zip(*versions)])
2109+
2110+ fmt = '%%-%ds %%s' % package_width
2111+ return '\n'.join([fmt % v for v in versions])
2112
2113 def shared_libraries(path):
2114 '''Returns a list of strings containing the sonames of shared libraries
2115@@ -635,7 +713,7 @@
2116
2117 if __name__ == '__main__':
2118
2119- import unittest, tempfile
2120+ import unittest
2121
2122 class _T(unittest.TestCase):
2123 def test_module_license_evaluation(self):
2124@@ -661,10 +739,10 @@
2125 # - fake BAD module
2126
2127 # direct license check
2128- self.assert_('GPL' in _get_module_license('isofs'))
2129+ self.assertTrue('GPL' in _get_module_license('isofs'))
2130 self.assertEqual(_get_module_license('does-not-exist'), 'invalid')
2131- self.assert_('GPL' in _get_module_license(good_ko.name))
2132- self.assert_('BAD' in _get_module_license(bad_ko.name))
2133+ self.assertTrue('GPL' in _get_module_license(good_ko.name))
2134+ self.assertTrue('BAD' in _get_module_license(bad_ko.name))
2135
2136 # check via nonfree_kernel_modules logic
2137 f = tempfile.NamedTemporaryFile()
2138@@ -672,9 +750,44 @@
2139 (good_ko.name,bad_ko.name))
2140 f.flush()
2141 nonfree = nonfree_kernel_modules(f.name)
2142- self.failIf('isofs' in nonfree)
2143- self.assert_('does-not-exist' in nonfree)
2144- self.failIf(good_ko.name in nonfree)
2145- self.assert_(bad_ko.name in nonfree)
2146+ self.assertFalse('isofs' in nonfree)
2147+ self.assertTrue('does-not-exist' in nonfree)
2148+ self.assertFalse(good_ko.name in nonfree)
2149+ self.assertTrue(bad_ko.name in nonfree)
2150+
2151+ def test_attach_dmesg(self):
2152+ '''attach_dmesg() does not overwrite already existing data'''
2153+
2154+ report = {}
2155+
2156+ attach_dmesg(report)
2157+ self.assertTrue(report['BootDmesg'].startswith('['))
2158+ self.assertTrue(len(report['BootDmesg']) > 500)
2159+ self.assertTrue(report['CurrentDmesg'].startswith('['))
2160+
2161+ def test_dmesg_overwrite(self):
2162+ '''attach_dmesg() does not overwrite already existing data'''
2163+
2164+ report = {'BootDmesg': 'existingboot'}
2165+
2166+ attach_dmesg(report)
2167+ self.assertEqual(report['BootDmesg'][:50], 'existingboot')
2168+ self.assertTrue(report['CurrentDmesg'].startswith('['))
2169+
2170+ report = {'BootDmesg': 'existingboot', 'CurrentDmesg': 'existingcurrent' }
2171+
2172+ attach_dmesg(report)
2173+ self.assertEqual(report['BootDmesg'], 'existingboot')
2174+ self.assertEqual(report['CurrentDmesg'], 'existingcurrent')
2175+
2176+ def test_no_crashes(self):
2177+ '''functions do not crash (very shallow'''
2178+
2179+ report = {}
2180+ attach_hardware(report)
2181+ attach_alsa(report)
2182+ attach_network(report)
2183+ attach_wifi(report)
2184+ attach_printing(report)
2185
2186 unittest.main()
2187
2188=== modified file 'apport/packaging.py'
2189--- apport/packaging.py 2009-12-23 10:55:52 +0000
2190+++ apport/packaging.py 2011-04-20 23:32:24 +0000
2191@@ -15,26 +15,26 @@
2192
2193 Throw ValueError if package does not exist.
2194 '''
2195- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2196+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2197
2198 def get_available_version(self, package):
2199 '''Return the latest available version of a package.
2200
2201 Throw ValueError if package does not exist.
2202 '''
2203- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2204+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2205
2206 def get_dependencies(self, package):
2207 '''Return a list of packages a package depends on.'''
2208
2209- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2210+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2211
2212 def get_source(self, package):
2213 '''Return the source package name for a package.
2214
2215 Throw ValueError if package does not exist.
2216 '''
2217- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2218+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2219
2220 def is_distro_package(self, package):
2221 '''Check package origin.
2222@@ -44,7 +44,7 @@
2223
2224 Throw ValueError if package does not exist.
2225 '''
2226- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2227+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2228
2229 def get_architecture(self, package):
2230 '''Return the architecture of a package.
2231@@ -52,19 +52,19 @@
2232 This might differ on multiarch architectures (e. g. an i386 Firefox
2233 package on a x86_64 system)
2234 '''
2235- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2236+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2237
2238 def get_files(self, package):
2239 '''Return list of files shipped by a package.
2240
2241 Throw ValueError if package does not exist.
2242 '''
2243- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2244+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2245
2246 def get_modified_files(self, package):
2247 '''Return list of all modified files of a package.'''
2248
2249- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2250+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2251
2252 def get_file_package(self, file, uninstalled=False, map_cachedir=None):
2253 '''Return the package a file belongs to.
2254@@ -77,14 +77,14 @@
2255 an existing directory which will be used to permanently store the
2256 downloaded maps. If it is not set, a temporary directory will be used.
2257 '''
2258- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2259+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2260
2261 def get_system_architecture(self):
2262 '''Return the architecture of the system.
2263
2264 This should use the notation of the particular distribution.
2265 '''
2266- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2267+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2268
2269 def set_mirror(self, url):
2270 '''Explicitly set a distribution mirror URL.
2271@@ -95,7 +95,7 @@
2272 By default, the mirror will be read from the system configuration
2273 files.
2274 '''
2275- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2276+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2277
2278 def get_source_tree(self, srcpackage, dir, version=None):
2279 '''Download a source package and unpack it into dir..
2280@@ -112,14 +112,14 @@
2281 (which might be a subdirectory of dir). Return None if the source is
2282 not available.
2283 '''
2284- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2285+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2286
2287 def compare_versions(self, ver1, ver2):
2288 '''Compare two package versions.
2289
2290 Return -1 for ver < ver2, 0 for ver1 == ver2, and 1 for ver1 > ver2.
2291 '''
2292- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2293+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2294
2295 def enabled(self):
2296 '''Return whether Apport should generate crash reports.
2297@@ -132,14 +132,14 @@
2298 Implementations should parse the configuration file which controls
2299 Apport (such as /etc/default/apport in Debian/Ubuntu).
2300 '''
2301- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2302+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2303
2304 def get_kernel_package(self):
2305 '''Return the actual Linux kernel package name.
2306
2307 This is used when the user reports a bug against the "linux" package.
2308 '''
2309- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2310+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2311
2312 def install_retracing_packages(self, report, verbosity=0,
2313 unpack_only=False, no_pkg=False, extra_packages=[]):
2314@@ -157,7 +157,7 @@
2315
2316 Return a tuple (list of installed packages, string with outdated packages).
2317 '''
2318- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2319+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2320
2321 def remove_packages(self, packages, verbosity=0):
2322 '''Remove packages.
2323@@ -165,11 +165,11 @@
2324 This is called after install_retracing_packages() to clean up again
2325 afterwards. packages is a list of package names.
2326 '''
2327- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2328+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2329
2330 def package_name_glob(self, glob):
2331 '''Return known package names which match given glob.'''
2332
2333- raise NotImplementedError, 'this method must be implemented by a concrete subclass'
2334+ raise NotImplementedError('this method must be implemented by a concrete subclass')
2335
2336-import packaging_impl
2337+import apport.packaging_impl
2338
2339=== modified file 'apport/report.py'
2340--- apport/report.py 2010-06-16 13:50:47 +0000
2341+++ apport/report.py 2011-04-20 23:32:24 +0000
2342@@ -16,8 +16,9 @@
2343 from xml.parsers.expat import ExpatError
2344
2345 from problem_report import ProblemReport
2346-import fileutils
2347-from packaging_impl import impl as packaging
2348+import apport
2349+import apport.fileutils
2350+from apport.packaging_impl import impl as packaging
2351
2352 _data_dir = os.environ.get('APPORT_DATA_DIR','/usr/share/apport')
2353 _hook_dir = '%s/package-hooks/' % (_data_dir)
2354@@ -56,7 +57,7 @@
2355 '''
2356 try:
2357 return open(path).read().strip()
2358- except (OSError, IOError), e:
2359+ except (OSError, IOError) as e:
2360 return 'Error: ' + str(e)
2361
2362 def _read_maps(pid):
2363@@ -67,8 +68,8 @@
2364 '''
2365 maps = 'Error: unable to read /proc maps file'
2366 try:
2367- maps = file('/proc/%d/maps' % pid).read().strip()
2368- except (OSError,IOError), e:
2369+ maps = open('/proc/%d/maps' % pid).read().strip()
2370+ except (OSError,IOError) as e:
2371 return 'Error: ' + str(e)
2372 return maps
2373
2374@@ -84,8 +85,8 @@
2375 if sp.returncode == 0:
2376 return out
2377 else:
2378- raise OSError, 'Error: command %s failed with exit code %i: %s' % (
2379- str(command), sp.returncode, err)
2380+ raise OSError('Error: command %s failed with exit code %i: %s' % (
2381+ str(command), sp.returncode, err))
2382
2383 def _check_bug_pattern(report, pattern):
2384 '''Check if given report matches the given bug pattern XML DOM node.
2385@@ -114,6 +115,19 @@
2386
2387 return pattern.attributes['url'].nodeValue.encode('UTF-8')
2388
2389+def _check_bug_patterns(report, patterns):
2390+ try:
2391+ dom = xml.dom.minidom.parseString(patterns)
2392+ except ExpatError:
2393+ return None
2394+
2395+ for pattern in dom.getElementsByTagName('pattern'):
2396+ url = _check_bug_pattern(report, pattern)
2397+ if url:
2398+ return url
2399+
2400+ return None
2401+
2402 def _dom_remove_space(node):
2403 '''Recursively remove whitespace from given XML DOM node.'''
2404
2405@@ -177,7 +191,7 @@
2406 self['ProblemType'] == 'KernelCrash'):
2407 package = self['Package']
2408 else:
2409- package = fileutils.find_file_package(self['ExecutablePath'])
2410+ package = apport.fileutils.find_file_package(self['ExecutablePath'])
2411 if not package:
2412 return
2413
2414@@ -333,9 +347,9 @@
2415 self['ProcMaps'] = _read_maps(int(pid))
2416 try:
2417 self['ExecutablePath'] = os.readlink('/proc/' + pid + '/exe')
2418- except OSError, e:
2419+ except OSError as e:
2420 if e.errno == errno.ENOENT:
2421- raise ValueError, 'invalid process'
2422+ raise ValueError('invalid process')
2423 else:
2424 raise
2425 for p in ('rofs', 'rwfs', 'squashmnt', 'persistmnt'):
2426@@ -539,12 +553,12 @@
2427 are generally not useful for triaging and duplicate detection.
2428 '''
2429 unwind_functions = set(['g_logv', 'g_log', 'IA__g_log', 'IA__g_logv',
2430- 'g_assert_warning', 'IA__g_assert_warning'])
2431+ 'g_assert_warning', 'IA__g_assert_warning', '__GI_abort'])
2432 toptrace = [''] * 5
2433 depth = 0
2434 unwound = False
2435 unwinding = False
2436- bt_fn_re = re.compile('^#(\d+)\s+(?:0x(?:\w+)\s+in\s+(.*)|(<signal handler called>)\s*)$')
2437+ bt_fn_re = re.compile('^#(\d+)\s+(?:0x(?:\w+)\s+in\s+\*?(.*)|(<signal handler called>)\s*)$')
2438 bt_fn_noaddr_re = re.compile('^#(\d+)\s+(?:(.*)|(<signal handler called>)\s*)$')
2439
2440 for line in self['Stacktrace'].splitlines():
2441@@ -592,14 +606,17 @@
2442 execfile(hook, symb)
2443 try:
2444 symb['add_info'](self, ui)
2445- except TypeError:
2446- # older versions of apport did not pass UI, and hooks that
2447- # do not require it don't need to take it
2448- symb['add_info'](self)
2449+ except TypeError as e:
2450+ if e.message.startswith('add_info()'):
2451+ # older versions of apport did not pass UI, and hooks that
2452+ # do not require it don't need to take it
2453+ symb['add_info'](self)
2454+ else:
2455+ raise
2456 except StopIteration:
2457 return True
2458 except:
2459- print >> sys.stderr, 'hook %s crashed:' % hook
2460+ apport.error('hook %s crashed:', hook)
2461 traceback.print_exc()
2462 pass
2463
2464@@ -613,14 +630,17 @@
2465 execfile(hook, symb)
2466 try:
2467 symb['add_info'](self, ui)
2468- except TypeError:
2469- # older versions of apport did not pass UI, and hooks that
2470- # do not require it don't need to take it
2471- symb['add_info'](self)
2472+ except TypeError as e:
2473+ if e.message.startswith('add_info()'):
2474+ # older versions of apport did not pass UI, and hooks that
2475+ # do not require it don't need to take it
2476+ symb['add_info'](self)
2477+ else:
2478+ raise
2479 except StopIteration:
2480 return True
2481 except:
2482- print >> sys.stderr, 'hook %s crashed:' % hook
2483+ apport.error('hook %s crashed:', hook)
2484 traceback.print_exc()
2485 pass
2486
2487@@ -634,25 +654,28 @@
2488 execfile(hook, symb)
2489 try:
2490 symb['add_info'](self, ui)
2491- except TypeError:
2492- # older versions of apport did not pass UI, and hooks that
2493- # do not require it don't need to take it
2494- symb['add_info'](self)
2495+ except TypeError as e:
2496+ if e.message.startswith('add_info()'):
2497+ # older versions of apport did not pass UI, and hooks that
2498+ # do not require it don't need to take it
2499+ symb['add_info'](self)
2500+ else:
2501+ raise
2502 except StopIteration:
2503 return True
2504 except:
2505- print >> sys.stderr, 'hook %s crashed:' % hook
2506+ apport.error('hook %s crashed:', hook)
2507 traceback.print_exc()
2508 pass
2509
2510 return False
2511
2512- def search_bug_patterns(self, baseurl):
2513- '''Check bug patterns at baseurl/packagename.xml.
2514+ def search_bug_patterns(self, url):
2515+ '''Check bug patterns loaded from the specified url.
2516
2517 Return bug URL on match, or None otherwise.
2518
2519- The pattern file must be valid XML and has the following syntax:
2520+ The url must refer to a valid XML document with the following syntax:
2521 root element := <patterns>
2522 patterns := <pattern url="http://bug.url"> *
2523 pattern := <re key="report_key">regular expression*</re> +
2524@@ -664,39 +687,27 @@
2525 <re key="Foo">ba.*r</re>
2526 </pattern>
2527 <pattern url="http://bugtracker.net/bugs/2">
2528+ <re key="Package">^\S* 1-2$</re> <!-- test for a particular version -->
2529 <re key="Foo">write_(hello|goodbye)</re>
2530- <re key="Package">^\S* 1-2$</re> <!-- test for a particular version -->
2531 </pattern>
2532 </patterns>
2533 '''
2534 # some distros might not want to support these
2535- if not baseurl:
2536+ if not url:
2537 return
2538
2539- assert self.has_key('Package')
2540- package = self['Package'].split()[0]
2541 try:
2542- patterns = urllib.urlopen('%s/%s.xml' % (baseurl, package)).read()
2543- assert '<title>404 Not Found' not in patterns
2544+ patterns = urllib.urlopen(url).read()
2545 except:
2546- # try if there is one for the source package
2547- if self.has_key('SourcePackage'):
2548- try:
2549- patterns = urllib.urlopen('%s/%s.xml' % (baseurl, self['SourcePackage'])).read()
2550- except:
2551- return None
2552- else:
2553- return None
2554-
2555- try:
2556- dom = xml.dom.minidom.parseString(patterns)
2557- except ExpatError:
2558- return None
2559-
2560- for pattern in dom.getElementsByTagName('pattern'):
2561- m = _check_bug_pattern(self, pattern)
2562- if m:
2563- return m
2564+ # doesn't exist or failed to load
2565+ return
2566+
2567+ if '<title>404 Not Found' in patterns:
2568+ return
2569+
2570+ url = _check_bug_patterns(self, patterns)
2571+ if url:
2572+ return url
2573
2574 return None
2575
2576@@ -714,8 +725,8 @@
2577 else:
2578 try:
2579 dom = xml.dom.minidom.parse(ifpath)
2580- except ExpatError, e:
2581- raise ValueError, '%s has invalid format: %s' % (_ignore_file, str(e))
2582+ except ExpatError as e:
2583+ raise ValueError('%s has invalid format: %s' % (_ignore_file, str(e)))
2584
2585 # remove whitespace so that writing back the XML does not accumulate
2586 # whitespace
2587@@ -880,22 +891,46 @@
2588 os.path.basename(self['ExecutablePath']),
2589 trace[0])
2590
2591- trace_re = re.compile('^\s*File.* in (.+)$')
2592+ trace_re = re.compile('^\s*File\s*"(\S+)".* in (.+)$')
2593 i = len(trace)-1
2594 function = 'unknown'
2595 while i >= 0:
2596 m = trace_re.match(trace[i])
2597 if m:
2598- function = m.group(1)
2599+ module_path = m.group(1)
2600+ function = m.group(2)
2601 break
2602 i -= 1
2603
2604- return '%s crashed with %s in %s()' % (
2605- os.path.basename(self['ExecutablePath']),
2606- trace[-1].split(':')[0],
2607- function
2608+ path = os.path.basename(self['ExecutablePath'])
2609+ last_line = trace[-1]
2610+ exception = last_line.split(':')[0]
2611+ m = re.match('^%s: (.+)$' % exception, last_line)
2612+ if m:
2613+ message = m.group(1)
2614+ else:
2615+ message = None
2616+
2617+ if function == '<module>':
2618+ if module_path == self['ExecutablePath']:
2619+ context = '__main__'
2620+ else:
2621+ # Maybe use os.path.basename?
2622+ context = module_path
2623+ else:
2624+ context = '%s()' % function
2625+
2626+ title = '%s crashed with %s in %s' % (
2627+ path,
2628+ exception,
2629+ context
2630 )
2631
2632+ if message:
2633+ title += ': %s' % message
2634+
2635+ return title
2636+
2637 # package problem
2638 if self.get('ProblemType') == 'Package' and \
2639 self.has_key('Package'):
2640@@ -991,7 +1026,11 @@
2641 sig = '%s:%s' % (self['ExecutablePath'], self['Signal'])
2642 bt_fn_re = re.compile('^(?:([\w:~]+).*|(<signal handler called>)\s*)$')
2643
2644- for line in self['StacktraceTop'].splitlines():
2645+ lines = self['StacktraceTop'].splitlines()
2646+ if len(lines) < 2:
2647+ return None
2648+
2649+ for line in lines:
2650 m = bt_fn_re.match(line)
2651 if m:
2652 sig += ':' + (m.group(1) or m.group(2))
2653@@ -1067,7 +1106,10 @@
2654 #
2655
2656 import unittest, shutil, signal, time
2657-from cStringIO import StringIO
2658+try:
2659+ from cStringIO import StringIO
2660+except ImportError:
2661+ from io import StringIO
2662
2663 class _T(unittest.TestCase):
2664 def test_add_package_info(self):
2665@@ -1082,7 +1124,7 @@
2666 pr.add_package_info('bash')
2667 self.assertEqual(pr['Package'], 'bash ' + bashversion.strip())
2668 self.assertEqual(pr['SourcePackage'], 'bash')
2669- self.assert_('libc' in pr['Dependencies'])
2670+ self.assertTrue('libc' in pr['Dependencies'])
2671
2672 # test without specifying a package, but with ExecutablePath
2673 pr = Report()
2674@@ -1091,36 +1133,36 @@
2675 pr.add_package_info()
2676 self.assertEqual(pr['Package'], 'bash ' + bashversion.strip())
2677 self.assertEqual(pr['SourcePackage'], 'bash')
2678- self.assert_('libc' in pr['Dependencies'])
2679+ self.assertTrue('libc' in pr['Dependencies'])
2680 # check for stray empty lines
2681- self.assert_('\n\n' not in pr['Dependencies'])
2682- self.assert_(pr.has_key('PackageArchitecture'))
2683+ self.assertTrue('\n\n' not in pr['Dependencies'])
2684+ self.assertTrue(pr.has_key('PackageArchitecture'))
2685
2686 pr = Report()
2687 pr['ExecutablePath'] = '/nonexisting'
2688 pr.add_package_info()
2689- self.assert_(not pr.has_key('Package'))
2690+ self.assertTrue(not pr.has_key('Package'))
2691
2692 def test_add_os_info(self):
2693 '''add_os_info().'''
2694
2695 pr = Report()
2696 pr.add_os_info()
2697- self.assert_(pr['Uname'].startswith('Linux'))
2698- self.assert_(type(pr['DistroRelease']) == type(''))
2699- self.assert_(pr['Architecture'])
2700+ self.assertTrue(pr['Uname'].startswith('Linux'))
2701+ self.assertTrue(type(pr['DistroRelease']) == type(''))
2702+ self.assertTrue(pr['Architecture'])
2703
2704 def test_add_user_info(self):
2705 '''add_user_info().'''
2706
2707 pr = Report()
2708 pr.add_user_info()
2709- self.assert_(pr.has_key('UserGroups'))
2710+ self.assertTrue(pr.has_key('UserGroups'))
2711
2712 # double-check that user group names are removed
2713 for g in pr['UserGroups'].split():
2714- self.assert_(grp.getgrnam(g).gr_gid < 1000)
2715- self.assert_(grp.getgrgid(os.getgid()).gr_name not in pr['UserGroups'])
2716+ self.assertTrue(grp.getgrnam(g).gr_gid < 1000)
2717+ self.assertTrue(grp.getgrgid(os.getgid()).gr_name not in pr['UserGroups'])
2718
2719 def test_add_proc_info(self):
2720 '''add_proc_info().'''
2721@@ -1135,26 +1177,26 @@
2722 self.assertEqual(pr.pid, None)
2723 pr.add_proc_info()
2724 self.assertEqual(pr.pid, os.getpid())
2725- self.assert_(set(['ProcEnviron', 'ProcMaps', 'ProcCmdline',
2726+ self.assertTrue(set(['ProcEnviron', 'ProcMaps', 'ProcCmdline',
2727 'ProcMaps']).issubset(set(pr.keys())), 'report has required fields')
2728- self.assert_('LANG='+os.environ['LANG'] in pr['ProcEnviron'])
2729- self.assert_('USER' not in pr['ProcEnviron'])
2730- self.assert_('PWD' not in pr['ProcEnviron'])
2731+ self.assertTrue('LANG='+os.environ['LANG'] in pr['ProcEnviron'])
2732+ self.assertTrue('USER' not in pr['ProcEnviron'])
2733+ self.assertTrue('PWD' not in pr['ProcEnviron'])
2734
2735 # check with one additional safe environment variable
2736 pr = Report()
2737 pr.add_proc_info(extraenv=['PWD'])
2738- self.assert_('USER' not in pr['ProcEnviron'])
2739- self.assert_('PWD='+os.environ['PWD'] in pr['ProcEnviron'])
2740+ self.assertTrue('USER' not in pr['ProcEnviron'])
2741+ self.assertTrue('PWD='+os.environ['PWD'] in pr['ProcEnviron'])
2742
2743 # check process from other user
2744 assert os.getuid() != 0, 'please do not run this test as root for this check.'
2745 pr = Report()
2746 self.assertRaises(OSError, pr.add_proc_info, 1) # EPERM for init process
2747 self.assertEqual(pr.pid, 1)
2748- self.assert_('init' in pr['ProcStatus'], pr['ProcStatus'])
2749- self.assert_(pr['ProcEnviron'].startswith('Error:'), pr['ProcEnviron'])
2750- self.assert_(not pr.has_key('InterpreterPath'))
2751+ self.assertTrue('init' in pr['ProcStatus'], pr['ProcStatus'])
2752+ self.assertTrue(pr['ProcEnviron'].startswith('Error:'), pr['ProcEnviron'])
2753+ self.assertTrue(not pr.has_key('InterpreterPath'))
2754
2755 # check escaping of ProcCmdline
2756 p = subprocess.Popen(['cat', '/foo bar', '\\h', '\\ \\', '-'],
2757@@ -1170,7 +1212,7 @@
2758 p.communicate('\n')
2759 self.assertEqual(pr['ProcCmdline'], 'cat /foo\ bar \\\\h \\\\\\ \\\\ -')
2760 self.assertEqual(pr['ExecutablePath'], '/bin/cat')
2761- self.assert_(not pr.has_key('InterpreterPath'))
2762+ self.assertTrue(not pr.has_key('InterpreterPath'))
2763 self.assertTrue('/bin/cat' in pr['ProcMaps'])
2764 self.assertTrue('[stack]' in pr['ProcMaps'])
2765
2766@@ -1186,7 +1228,7 @@
2767 pr.pid = p.pid
2768 pr.add_proc_info()
2769 p.communicate('exit\n')
2770- self.failIf(pr.has_key('InterpreterPath'), pr.get('InterpreterPath'))
2771+ self.assertFalse(pr.has_key('InterpreterPath'), pr.get('InterpreterPath'))
2772 self.assertEqual(pr['ExecutablePath'], os.path.realpath('/bin/sh'))
2773
2774 # check correct handling of interpreted executables: shell
2775@@ -1199,7 +1241,7 @@
2776 pr = Report()
2777 pr.add_proc_info(pid=p.pid)
2778 p.communicate('\n')
2779- self.assert_(pr['ExecutablePath'].endswith('bin/zgrep'))
2780+ self.assertTrue(pr['ExecutablePath'].endswith('bin/zgrep'))
2781 self.assertEqual(pr['InterpreterPath'],
2782 os.path.realpath(open(pr['ExecutablePath']).readline().strip()[2:]))
2783 self.assertTrue('[stack]' in pr['ProcMaps'])
2784@@ -1211,7 +1253,7 @@
2785 sys.stdin.readline()
2786 ''')
2787 os.close(fd)
2788- os.chmod(testscript, 0755)
2789+ os.chmod(testscript, 0o755)
2790 p = subprocess.Popen([testscript], stdin=subprocess.PIPE,
2791 stderr=subprocess.PIPE, close_fds=True)
2792 assert p.pid
2793@@ -1223,7 +1265,7 @@
2794 p.communicate('\n')
2795 os.unlink(testscript)
2796 self.assertEqual(pr['ExecutablePath'], testscript)
2797- self.assert_('python' in pr['InterpreterPath'])
2798+ self.assertTrue('python' in pr['InterpreterPath'])
2799 self.assertTrue('python' in pr['ProcMaps'])
2800 self.assertTrue('[stack]' in pr['ProcMaps'])
2801
2802@@ -1240,7 +1282,7 @@
2803 r = Report()
2804 r.add_proc_environ(pid=p.pid)
2805 p.communicate('')
2806- self.failIf('PATH' in r['ProcEnviron'],
2807+ self.assertFalse('PATH' in r['ProcEnviron'],
2808 'system default $PATH should be filtered out')
2809
2810 # no user paths
2811@@ -1250,7 +1292,7 @@
2812 r = Report()
2813 r.add_proc_environ(pid=p.pid)
2814 p.communicate('')
2815- self.assert_('PATH=(custom, no user)' in r['ProcEnviron'],
2816+ self.assertTrue('PATH=(custom, no user)' in r['ProcEnviron'],
2817 'PATH is customized without user paths')
2818
2819 # user paths
2820@@ -1260,7 +1302,7 @@
2821 r = Report()
2822 r.add_proc_environ(pid=p.pid)
2823 p.communicate('')
2824- self.assert_('PATH=(custom, user)' in r['ProcEnviron'],
2825+ self.assertTrue('PATH=(custom, user)' in r['ProcEnviron'],
2826 'PATH is customized with user paths')
2827
2828 def test_check_interpreted(self):
2829@@ -1274,7 +1316,7 @@
2830 pr['ProcCmdline'] = 'gedit\0/' + f.name
2831 pr._check_interpreted()
2832 self.assertEqual(pr['ExecutablePath'], '/usr/bin/gedit')
2833- self.failIf(pr.has_key('InterpreterPath'))
2834+ self.assertFalse(pr.has_key('InterpreterPath'))
2835 f.close()
2836
2837 # bogus argv[0]
2838@@ -1284,7 +1326,7 @@
2839 pr['ProcCmdline'] = 'nonexisting\0/foo'
2840 pr._check_interpreted()
2841 self.assertEqual(pr['ExecutablePath'], '/bin/dash')
2842- self.failIf(pr.has_key('InterpreterPath'))
2843+ self.assertFalse(pr.has_key('InterpreterPath'))
2844
2845 # standard sh script
2846 pr = Report()
2847@@ -1331,7 +1373,7 @@
2848 pr['ProcCmdline'] = 'python\0/etc/shadow'
2849 pr._check_interpreted()
2850 self.assertEqual(pr['ExecutablePath'], '/usr/bin/python')
2851- self.failIf(pr.has_key('InterpreterPath'))
2852+ self.assertFalse(pr.has_key('InterpreterPath'))
2853
2854 # succeed on files we should have access to when name!=argv[0]
2855 pr = Report()
2856@@ -1349,7 +1391,7 @@
2857 pr['ProcCmdline'] = '../etc/shadow'
2858 pr._check_interpreted()
2859 self.assertEqual(pr['ExecutablePath'], '/usr/bin/python')
2860- self.failIf(pr.has_key('InterpreterPath'))
2861+ self.assertFalse(pr.has_key('InterpreterPath'))
2862
2863 # succeed on files we should have access to when name==argv[0]
2864 pr = Report()
2865@@ -1367,7 +1409,7 @@
2866 pr['ProcCmdline'] = 'python'
2867 pr._check_interpreted()
2868 self.assertEqual(pr['ExecutablePath'], '/usr/bin/python')
2869- self.failIf(pr.has_key('InterpreterPath'))
2870+ self.assertFalse(pr.has_key('InterpreterPath'))
2871
2872 # python script (abuse /bin/bash since it must exist)
2873 pr = Report()
2874@@ -1435,21 +1477,21 @@
2875 return pr
2876
2877 def _validate_gdb_fields(self,pr):
2878- self.assert_(pr.has_key('Stacktrace'))
2879- self.assert_(pr.has_key('ThreadStacktrace'))
2880- self.assert_(pr.has_key('StacktraceTop'))
2881- self.assert_(pr.has_key('Registers'))
2882- self.assert_(pr.has_key('Disassembly'))
2883- self.assert_('(no debugging symbols found)' not in pr['Stacktrace'])
2884- self.assert_('Core was generated by' not in pr['Stacktrace'], pr['Stacktrace'])
2885- self.assert_(not re.match(r'(?s)(^|.*\n)#0 [^\n]+\n#0 ',
2886+ self.assertTrue(pr.has_key('Stacktrace'))
2887+ self.assertTrue(pr.has_key('ThreadStacktrace'))
2888+ self.assertTrue(pr.has_key('StacktraceTop'))
2889+ self.assertTrue(pr.has_key('Registers'))
2890+ self.assertTrue(pr.has_key('Disassembly'))
2891+ self.assertTrue('(no debugging symbols found)' not in pr['Stacktrace'])
2892+ self.assertTrue('Core was generated by' not in pr['Stacktrace'], pr['Stacktrace'])
2893+ self.assertTrue(not re.match(r'(?s)(^|.*\n)#0 [^\n]+\n#0 ',
2894 pr['Stacktrace']))
2895- self.assert_('#0 0x' in pr['Stacktrace'])
2896- self.assert_('#1 0x' in pr['Stacktrace'])
2897- self.assert_('#0 0x' in pr['ThreadStacktrace'])
2898- self.assert_('#1 0x' in pr['ThreadStacktrace'])
2899- self.assert_('Thread 1 (' in pr['ThreadStacktrace'])
2900- self.assert_(len(pr['StacktraceTop'].splitlines()) <= 5)
2901+ self.assertTrue('#0 0x' in pr['Stacktrace'])
2902+ self.assertTrue('#1 0x' in pr['Stacktrace'])
2903+ self.assertTrue('#0 0x' in pr['ThreadStacktrace'])
2904+ self.assertTrue('#1 0x' in pr['ThreadStacktrace'])
2905+ self.assertTrue('Thread 1 (' in pr['ThreadStacktrace'])
2906+ self.assertTrue(len(pr['StacktraceTop'].splitlines()) <= 5)
2907
2908 def test_add_gdb_info(self):
2909 '''add_gdb_info() with core dump file reference.'''
2910@@ -1462,7 +1504,7 @@
2911 pr = self._generate_sigsegv_report()
2912 self._validate_gdb_fields(pr)
2913 self.assertEqual(pr['StacktraceTop'], 'f (x=42) at crash.c:3\nmain () at crash.c:6', pr['StacktraceTop'])
2914- self.failIf ('AssertionMessage' in pr)
2915+ self.assertFalse ('AssertionMessage' in pr)
2916
2917 # crash where gdb generates output on stderr
2918 pr = self._generate_sigsegv_report(code='''
2919@@ -1473,8 +1515,8 @@
2920 }
2921 ''')
2922 self._validate_gdb_fields(pr)
2923- self.assert_('Cannot access memory at address 0x0' in pr['Disassembly'], pr['Disassembly'])
2924- self.failIf ('AssertionMessage' in pr)
2925+ self.assertTrue('Cannot access memory at address 0x0' in pr['Disassembly'], pr['Disassembly'])
2926+ self.assertFalse ('AssertionMessage' in pr)
2927
2928 def test_add_gdb_info_load(self):
2929 '''add_gdb_info() with inline core dump.'''
2930@@ -1499,22 +1541,22 @@
2931 pr.load(open(rep.name))
2932 pr['Signal'] = '1'
2933 pr.add_hooks_info('fake_ui')
2934- self.assert_('SegvAnalysis' not in pr.keys())
2935+ self.assertTrue('SegvAnalysis' not in pr.keys())
2936
2937 pr = Report()
2938 pr.load(open(rep.name))
2939 pr.add_hooks_info('fake_ui')
2940- self.assert_('Skipped: missing required field "Architecture"' in pr['SegvAnalysis'],
2941+ self.assertTrue('Skipped: missing required field "Architecture"' in pr['SegvAnalysis'],
2942 pr['SegvAnalysis'])
2943
2944 pr.add_os_info()
2945 pr.add_hooks_info('fake_ui')
2946- self.assert_('Skipped: missing required field "ProcMaps"' in pr['SegvAnalysis'],
2947+ self.assertTrue('Skipped: missing required field "ProcMaps"' in pr['SegvAnalysis'],
2948 pr['SegvAnalysis'])
2949
2950 pr.add_proc_info()
2951 pr.add_hooks_info('fake_ui')
2952- self.assert_('not located in a known VMA region' in pr['SegvAnalysis'],
2953+ self.assertTrue('not located in a known VMA region' in pr['SegvAnalysis'],
2954 pr['SegvAnalysis'])
2955
2956 def test_add_gdb_info_script(self):
2957@@ -1532,7 +1574,7 @@
2958 ulimit -c unlimited
2959 kill -SEGV $$
2960 ''')
2961- os.chmod(script, 0755)
2962+ os.chmod(script, 0o755)
2963
2964 # call script and verify that it gives us a proper ELF core dump
2965 assert subprocess.call([script]) != 0
2966@@ -1549,7 +1591,7 @@
2967 os.unlink(script)
2968
2969 self._validate_gdb_fields(pr)
2970- self.assert_('libc.so' in pr['Stacktrace'] or 'in execute_command' in pr['Stacktrace'])
2971+ self.assertTrue('libc.so' in pr['Stacktrace'] or 'in execute_command' in pr['Stacktrace'])
2972
2973 def test_add_gdb_info_abort(self):
2974 '''add_gdb_info() with SIGABRT/assert()
2975@@ -1572,7 +1614,7 @@
2976 ulimit -c unlimited
2977 $0.bin 2>/dev/null
2978 ''')
2979- os.chmod(script, 0755)
2980+ os.chmod(script, 0o755)
2981
2982 # call script and verify that it gives us a proper ELF core dump
2983 assert subprocess.call([script]) != 0
2984@@ -1589,11 +1631,11 @@
2985 os.unlink('core')
2986
2987 self._validate_gdb_fields(pr)
2988- self.assert_("<stdin>:2: main: Assertion `1 < 0' failed." in
2989+ self.assertTrue("<stdin>:2: main: Assertion `1 < 0' failed." in
2990 pr['AssertionMessage'], pr['AssertionMessage'])
2991- self.failIf(pr['AssertionMessage'].startswith('$'), pr['AssertionMessage'])
2992- self.failIf('= 0x' in pr['AssertionMessage'], pr['AssertionMessage'])
2993- self.failIf(pr['AssertionMessage'].endswith('\\n'), pr['AssertionMessage'])
2994+ self.assertFalse(pr['AssertionMessage'].startswith('$'), pr['AssertionMessage'])
2995+ self.assertFalse('= 0x' in pr['AssertionMessage'], pr['AssertionMessage'])
2996+ self.assertFalse(pr['AssertionMessage'].endswith('\\n'), pr['AssertionMessage'])
2997
2998 # abort with internal error
2999 (fd, script) = tempfile.mkstemp()
3000@@ -1614,7 +1656,7 @@
3001 ulimit -c unlimited
3002 LIBC_FATAL_STDERR_=1 $0.bin aaaaaaaaaaaaaaaa 2>/dev/null
3003 ''')
3004- os.chmod(script, 0755)
3005+ os.chmod(script, 0o755)
3006
3007 # call script and verify that it gives us a proper ELF core dump
3008 assert subprocess.call([script]) != 0
3009@@ -1631,11 +1673,11 @@
3010 os.unlink('core')
3011
3012 self._validate_gdb_fields(pr)
3013- self.assert_("** buffer overflow detected ***: %s.bin terminated" % (script) in
3014+ self.assertTrue("** buffer overflow detected ***: %s.bin terminated" % (script) in
3015 pr['AssertionMessage'], pr['AssertionMessage'])
3016- self.failIf(pr['AssertionMessage'].startswith('$'), pr['AssertionMessage'])
3017- self.failIf('= 0x' in pr['AssertionMessage'], pr['AssertionMessage'])
3018- self.failIf(pr['AssertionMessage'].endswith('\\n'), pr['AssertionMessage'])
3019+ self.assertFalse(pr['AssertionMessage'].startswith('$'), pr['AssertionMessage'])
3020+ self.assertFalse('= 0x' in pr['AssertionMessage'], pr['AssertionMessage'])
3021+ self.assertFalse(pr['AssertionMessage'].endswith('\\n'), pr['AssertionMessage'])
3022
3023 # abort without assertion
3024 (fd, script) = tempfile.mkstemp()
3025@@ -1652,7 +1694,7 @@
3026 ulimit -c unlimited
3027 $0.bin 2>/dev/null
3028 ''')
3029- os.chmod(script, 0755)
3030+ os.chmod(script, 0o755)
3031
3032 # call script and verify that it gives us a proper ELF core dump
3033 assert subprocess.call([script]) != 0
3034@@ -1669,97 +1711,103 @@
3035 os.unlink('core')
3036
3037 self._validate_gdb_fields(pr)
3038- self.failIf ('AssertionMessage' in pr, pr.get('AssertionMessage'))
3039+ self.assertFalse ('AssertionMessage' in pr, pr.get('AssertionMessage'))
3040
3041 def test_search_bug_patterns(self):
3042 '''search_bug_patterns().'''
3043
3044- pdir = None
3045- try:
3046- pdir = tempfile.mkdtemp()
3047-
3048- # create some test patterns
3049- open(os.path.join(pdir, 'bash.xml'), 'w').write('''<?xml version="1.0"?>
3050+ patterns = tempfile.NamedTemporaryFile(prefix='apport-')
3051+ # create some test patterns
3052+ patterns.write('''<?xml version="1.0"?>
3053 <patterns>
3054 <pattern url="http://bugtracker.net/bugs/1">
3055+ <re key="Package">^bash </re>
3056 <re key="Foo">ba.*r</re>
3057 </pattern>
3058 <pattern url="http://bugtracker.net/bugs/2">
3059+ <re key="Package">^bash 1-2$</re>
3060 <re key="Foo">write_(hello|goodbye)</re>
3061- <re key="Package">^\S* 1-2$</re>
3062 </pattern>
3063-</patterns>''')
3064-
3065- open(os.path.join(pdir, 'coreutils.xml'), 'w').write('''<?xml version="1.0"?>
3066-<patterns>
3067 <pattern url="http://bugtracker.net/bugs/3">
3068+ <re key="Package">^coreutils </re>
3069 <re key="Bar">^1$</re>
3070 </pattern>
3071 <pattern url="http://bugtracker.net/bugs/4">
3072+ <re key="Package">^coreutils </re>
3073 <re key="Bar">*</re> <!-- invalid RE -->
3074 </pattern>
3075-</patterns>''')
3076-
3077- # invalid XML
3078- open(os.path.join(pdir, 'invalid.xml'), 'w').write('''<?xml version="1.0"?>
3079-</patterns>''')
3080-
3081- # create some reports
3082- r_bash = Report()
3083- r_bash['Package'] = 'bash 1-2'
3084- r_bash['Foo'] = 'bazaar'
3085-
3086- r_coreutils = Report()
3087- r_coreutils['Package'] = 'coreutils 1'
3088- r_coreutils['Bar'] = '1'
3089-
3090- r_invalid = Report()
3091- r_invalid['Package'] = 'invalid 1'
3092-
3093- # positive match cases
3094- self.assertEqual(r_bash.search_bug_patterns(pdir), 'http://bugtracker.net/bugs/1')
3095- r_bash['Foo'] = 'write_goodbye'
3096- self.assertEqual(r_bash.search_bug_patterns(pdir), 'http://bugtracker.net/bugs/2')
3097- self.assertEqual(r_coreutils.search_bug_patterns(pdir), 'http://bugtracker.net/bugs/3')
3098-
3099- # match on source package
3100- r_bash['Package'] = 'bash-static 1-2'
3101- self.assertEqual(r_bash.search_bug_patterns(pdir), None)
3102- r_bash['SourcePackage'] = 'bash'
3103- self.assertEqual(r_bash.search_bug_patterns(pdir), 'http://bugtracker.net/bugs/2')
3104-
3105- # negative match cases
3106- r_bash['Package'] = 'bash 1-21'
3107- self.assertEqual(r_bash.search_bug_patterns(pdir), None,
3108- 'does not match on wrong bash version')
3109- r_bash['Foo'] = 'zz'
3110- self.assertEqual(r_bash.search_bug_patterns(pdir), None,
3111- 'does not match on wrong Foo value')
3112- r_coreutils['Bar'] = '11'
3113- self.assertEqual(r_coreutils.search_bug_patterns(pdir), None,
3114- 'does not match on wrong Bar value')
3115-
3116- # various errors to check for robustness (no exceptions, just None
3117- # return value)
3118- del r_coreutils['Bar']
3119- self.assertEqual(r_coreutils.search_bug_patterns(pdir), None,
3120- 'does not match on nonexisting key')
3121- self.assertEqual(r_invalid.search_bug_patterns(pdir), None,
3122- 'gracefully handles invalid XML')
3123- r_coreutils['Package'] = 'other 2'
3124- self.assertEqual(r_coreutils.search_bug_patterns(pdir), None,
3125- 'gracefully handles nonexisting package XML file')
3126- self.assertEqual(r_bash.search_bug_patterns('file:///nonexisting/directory/'), None,
3127- 'gracefully handles nonexisting base path')
3128- # existing host, but no bug patterns
3129- self.assertEqual(r_bash.search_bug_patterns('http://security.ubuntu.com/'), None,
3130- 'gracefully handles base path without bug patterns')
3131- # nonexisting host
3132- self.assertEqual(r_bash.search_bug_patterns('http://nonexisting.domain/'), None,
3133- 'gracefully handles nonexisting URL domain')
3134- finally:
3135- if pdir:
3136- shutil.rmtree(pdir)
3137+ <pattern url="http://bugtracker.net/bugs/5">
3138+ <re key="SourcePackage">^bazaar$</re>
3139+ <re key="LogFile">AssertionError</re>
3140+ </pattern>
3141+</patterns>''')
3142+ patterns.flush()
3143+
3144+ # invalid XML
3145+ invalid = tempfile.NamedTemporaryFile(prefix='apport-')
3146+ invalid.write('''<?xml version="1.0"?>
3147+</patterns>''')
3148+ invalid.flush()
3149+
3150+ # create some reports
3151+ r_bash = Report()
3152+ r_bash['Package'] = 'bash 1-2'
3153+ r_bash['Foo'] = 'bazaar'
3154+
3155+ r_bazaar = Report()
3156+ r_bazaar['Package'] = 'bazaar 2-1'
3157+ r_bazaar['SourcePackage'] = 'bazaar'
3158+ r_bazaar['LogFile'] = 'AssertionError'
3159+
3160+ r_coreutils = Report()
3161+ r_coreutils['Package'] = 'coreutils 1'
3162+ r_coreutils['Bar'] = '1'
3163+
3164+ r_invalid = Report()
3165+ r_invalid['Package'] = 'invalid 1'
3166+
3167+ # positive match cases
3168+ self.assertEqual(r_bash.search_bug_patterns(patterns.name), 'http://bugtracker.net/bugs/1')
3169+ r_bash['Foo'] = 'write_goodbye'
3170+ self.assertEqual(r_bash.search_bug_patterns(patterns.name), 'http://bugtracker.net/bugs/2')
3171+ self.assertEqual(r_coreutils.search_bug_patterns(patterns.name), 'http://bugtracker.net/bugs/3')
3172+ self.assertEqual(r_bazaar.search_bug_patterns(patterns.name), 'http://bugtracker.net/bugs/5')
3173+
3174+ # negative match cases
3175+ r_bash['Package'] = 'bash-static 1-2'
3176+ self.assertEqual(r_bash.search_bug_patterns(patterns.name), None)
3177+ r_bash['Package'] = 'bash 1-21'
3178+ self.assertEqual(r_bash.search_bug_patterns(patterns.name), None,
3179+ 'does not match on wrong bash version')
3180+ r_bash['Foo'] = 'zz'
3181+ self.assertEqual(r_bash.search_bug_patterns(patterns.name), None,
3182+ 'does not match on wrong Foo value')
3183+ r_coreutils['Bar'] = '11'
3184+ self.assertEqual(r_coreutils.search_bug_patterns(patterns.name), None,
3185+ 'does not match on wrong Bar value')
3186+ r_bazaar['SourcePackage'] = 'launchpad'
3187+ self.assertEqual(r_bazaar.search_bug_patterns(patterns.name), None,
3188+ 'does not match on wrong source package')
3189+ r_bazaar['LogFile'] = ''
3190+ self.assertEqual(r_bazaar.search_bug_patterns(patterns.name), None,
3191+ 'does not match on empty attribute')
3192+
3193+ # various errors to check for robustness (no exceptions, just None
3194+ # return value)
3195+ del r_coreutils['Bar']
3196+ self.assertEqual(r_coreutils.search_bug_patterns(patterns.name), None,
3197+ 'does not match on nonexisting key')
3198+ self.assertEqual(r_invalid.search_bug_patterns(invalid.name), None,
3199+ 'gracefully handles invalid XML')
3200+ r_coreutils['Package'] = 'other 2'
3201+ self.assertEqual(r_bash.search_bug_patterns('file:///nonexisting/directory/'), None,
3202+ 'gracefully handles nonexisting base path')
3203+ # existing host, but no bug patterns
3204+ self.assertEqual(r_bash.search_bug_patterns('http://security.ubuntu.com/'), None,
3205+ 'gracefully handles base path without bug patterns')
3206+ # nonexisting host
3207+ self.assertEqual(r_bash.search_bug_patterns('http://nonexisting.domain/'), None,
3208+ 'gracefully handles nonexisting URL domain')
3209
3210 def test_add_hooks_info(self):
3211 '''add_hooks_info().'''
3212@@ -1978,34 +2026,34 @@
3213 '''has_useful_stacktrace().'''
3214
3215 r = Report()
3216- self.failIf(r.has_useful_stacktrace())
3217+ self.assertFalse(r.has_useful_stacktrace())
3218
3219 r['StacktraceTop'] = ''
3220- self.failIf(r.has_useful_stacktrace())
3221+ self.assertFalse(r.has_useful_stacktrace())
3222
3223 r['StacktraceTop'] = '?? ()'
3224- self.failIf(r.has_useful_stacktrace())
3225+ self.assertFalse(r.has_useful_stacktrace())
3226
3227 r['StacktraceTop'] = '?? ()\n?? ()'
3228- self.failIf(r.has_useful_stacktrace())
3229+ self.assertFalse(r.has_useful_stacktrace())
3230
3231 r['StacktraceTop'] = 'read () from /lib/libc.6.so\n?? ()'
3232- self.failIf(r.has_useful_stacktrace())
3233+ self.assertFalse(r.has_useful_stacktrace())
3234
3235 r['StacktraceTop'] = 'read () from /lib/libc.6.so\n?? ()\n?? ()\n?? ()'
3236- self.failIf(r.has_useful_stacktrace())
3237+ self.assertFalse(r.has_useful_stacktrace())
3238
3239 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
3240- self.assert_(r.has_useful_stacktrace())
3241+ self.assertTrue(r.has_useful_stacktrace())
3242
3243 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so\n?? ()'
3244- self.assert_(r.has_useful_stacktrace())
3245+ self.assertTrue(r.has_useful_stacktrace())
3246
3247 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so\n?? ()\n?? ()'
3248- self.assert_(r.has_useful_stacktrace())
3249+ self.assertTrue(r.has_useful_stacktrace())
3250
3251 r['StacktraceTop'] = 'read () from /lib/libc.6.so\n?? ()\nfoo (i=1) from /usr/lib/libfoo.so\n?? ()\n?? ()'
3252- self.failIf(r.has_useful_stacktrace())
3253+ self.assertFalse(r.has_useful_stacktrace())
3254
3255 def test_standard_title(self):
3256 '''standard_title().'''
3257@@ -2066,7 +2114,7 @@
3258 subprocess.call(['pgrep', '-x',
3259 NameError: global name 'subprocess' is not defined'''
3260 self.assertEqual(report.standard_title(),
3261- 'apport-gtk crashed with NameError in ui_present_crash()')
3262+ "apport-gtk crashed with NameError in ui_present_crash(): global name 'subprocess' is not defined")
3263
3264 # slightly weird Python crash
3265 report = Report()
3266@@ -2097,8 +2145,32 @@
3267 Restarting AWN usually solves this issue'''
3268
3269 t = report.standard_title()
3270- self.assert_(t.startswith('apport-gtk crashed with'))
3271- self.assert_(t.endswith('setup_chooser()'))
3272+ self.assertTrue(t.startswith('apport-gtk crashed with'))
3273+ self.assertTrue(t.endswith('setup_chooser()'))
3274+
3275+ # Python crash at top level in module
3276+ report = Report()
3277+ report['ExecutablePath'] = '/usr/bin/gnome-about'
3278+ report['Traceback'] = '''Traceback (most recent call last):
3279+ File "/usr/bin/gnome-about", line 30, in <module>
3280+ import pygtk
3281+ File "/usr/lib/pymodules/python2.6/pygtk.py", line 28, in <module>
3282+ import nonexistent
3283+ImportError: No module named nonexistent
3284+'''
3285+ self.assertEqual(report.standard_title(),
3286+ "gnome-about crashed with ImportError in /usr/lib/pymodules/python2.6/pygtk.py: No module named nonexistent")
3287+
3288+ # Python crash at top level in main program
3289+ report = Report()
3290+ report['ExecutablePath'] = '/usr/bin/dcut'
3291+ report['Traceback'] = '''Traceback (most recent call last):
3292+ File "/usr/bin/dcut", line 28, in <module>
3293+ import nonexistent
3294+ImportError: No module named nonexistent
3295+'''
3296+ self.assertEqual(report.standard_title(),
3297+ "dcut crashed with ImportError in __main__: No module named nonexistent")
3298
3299 # package install problem
3300 report = Report('Package')
3301@@ -2289,6 +2361,18 @@
3302 filter_func (connection=0x8075288, message=0x80768d8, user_data=0x8074da8) at libhal.c:820
3303 dbus_connection_dispatch (connection=0x8075288) at dbus-connection.c:4267''')
3304
3305+ # problem with too old gdb, only assertion, nothing else
3306+ r = Report()
3307+ r['Stacktrace'] = '''#0 0x00987416 in __kernel_vsyscall ()
3308+No symbol table info available.
3309+#1 0x00ebecb1 in *__GI_raise (sig=6)
3310+ selftid = 945
3311+#2 0x00ec218e in *__GI_abort () at abort.c:59
3312+ save_stage = Unhandled dwarf expression opcode 0x9f
3313+'''
3314+ r._gen_stacktrace_top()
3315+ self.assertEqual(r['StacktraceTop'], '')
3316+
3317 def test_crash_signature(self):
3318 '''crash_signature().'''
3319
3320@@ -2314,11 +2398,14 @@
3321 __frob (x=1) at crash.c:30'''
3322 self.assertEqual(r.crash_signature(), None)
3323
3324+ r['StacktraceTop'] = ''
3325+ self.assertEqual(r.crash_signature(), None)
3326+
3327 # Python crashes
3328 del r['Signal']
3329 r['Traceback'] = '''Traceback (most recent call last):
3330 File "test.py", line 7, in <module>
3331- print _f(5)
3332+ print(_f(5))
3333 File "test.py", line 5, in _f
3334 return g_foo00(x+1)
3335 File "test.py", line 2, in g_foo00
3336
3337=== modified file 'apport/ui.py'
3338--- apport/ui.py 2010-07-09 11:18:39 +0000
3339+++ apport/ui.py 2011-04-20 23:32:24 +0000
3340@@ -13,7 +13,7 @@
3341 # option) any later version. See http://www.gnu.org/copyleft/gpl.html for
3342 # the full text of the license.
3343
3344-__version__ = '1.14.1'
3345+__version__ = '1.20.1'
3346
3347 import glob, sys, os.path, optparse, time, traceback, locale, gettext, re
3348 import pwd, errno, urllib, zlib
3349@@ -24,7 +24,9 @@
3350 from apport.crashdb import get_crashdb, NeedsCredentials
3351 from apport import unicode_gettext as _
3352
3353-symptom_script_dir = '/usr/share/apport/symptoms'
3354+symptom_script_dir = os.environ.get('APPORT_SYMPTOMS_DIR',
3355+ '/usr/share/apport/symptoms')
3356+PF_KTHREAD = 0x200000
3357
3358 def excstr(exception):
3359 '''Return exception message as unicode.'''
3360@@ -53,13 +55,13 @@
3361 execfile(symptom_script, symb)
3362 package = symb['run'](report, ui)
3363 if not package:
3364- print >> sys.stderr, 'symptom script %s did not determine the affected package' % symptom_script
3365+ apport.error('symptom script %s did not determine the affected package', symptom_script)
3366 return
3367 report['Symptom'] = os.path.splitext(os.path.basename(symptom_script))[0]
3368 except StopIteration:
3369 sys.exit(0)
3370 except:
3371- print >> sys.stderr, 'symptom script %s crashed:' % symptom_script
3372+ apport.error('symptom script %s crashed:', symptom_script)
3373 traceback.print_exc()
3374 sys.exit(0)
3375
3376@@ -67,7 +69,7 @@
3377 if report.has_key('ExecutablePath'):
3378 package = apport.fileutils.find_file_package(report['ExecutablePath'])
3379 else:
3380- raise KeyError, 'called without a package, and report does not have ExecutablePath'
3381+ raise KeyError('called without a package, and report does not have ExecutablePath')
3382 try:
3383 report.add_package_info(package)
3384 except ValueError:
3385@@ -75,7 +77,7 @@
3386 # package
3387 if not ignore_uninstalled:
3388 raise
3389- except SystemError, e:
3390+ except SystemError as e:
3391 report['UnreportableReason'] = excstr(e)
3392 return
3393
3394@@ -112,9 +114,9 @@
3395 versions installed. Please upgrade the following packages and check if the \
3396 problem still occurs:\n\n%s') % ', '.join(old_pkgs)
3397
3398- # if we have a SIGABRT without an assertion message, declare as unreportable
3399- if report.get('Signal') == '6' and 'AssertionMessage' not in report:
3400- report['UnreportableReason'] = _('The program crashed on an assertion failure, but the message could not be retrieved. Apport does not support reporting these crashes.')
3401+ # disabled: if we have a SIGABRT without an assertion message, declare as unreportable
3402+ #if report.get('Signal') == '6' and 'AssertionMessage' not in report:
3403+ # report['UnreportableReason'] = _('The program crashed on an assertion failure, but the message could not be retrieved. Apport does not support reporting these crashes.')
3404
3405 report.anonymize()
3406
3407@@ -124,7 +126,7 @@
3408 report.write(f, only_new=True)
3409 f.close()
3410 apport.fileutils.mark_report_seen(reportfile)
3411- os.chmod (reportfile, 0600)
3412+ os.chmod (reportfile, 0o600)
3413
3414 class UserInterface:
3415 '''Apport user interface API.
3416@@ -144,13 +146,11 @@
3417
3418 try:
3419 self.crashdb = get_crashdb(None)
3420- except ImportError, e:
3421+ except ImportError as e:
3422 # this can happen while upgrading python packages
3423- print >> sys.stderr, 'Could not import module, is a package upgrade in progress? Error:', e
3424- sys.exit(1)
3425+ apport.fatal('Could not import module, is a package upgrade in progress? Error: %s', str(e))
3426 except KeyError:
3427- print >> sys.stderr, '/etc/apport/crashdb.conf is damaged: No default database'
3428- sys.exit(1)
3429+ apport.fatal('/etc/apport/crashdb.conf is damaged: No default database')
3430
3431 gettext.textdomain(self.gettext_domain)
3432 self.parse_argv()
3433@@ -261,7 +261,7 @@
3434 try:
3435 if 'Dependencies' not in self.report:
3436 self.collect_info()
3437- except (IOError, zlib.error), e:
3438+ except (IOError, zlib.error) as e:
3439 # can happen with broken core dumps
3440 self.report = None
3441 self.ui_error_message(_('Invalid problem report'),
3442@@ -275,11 +275,7 @@
3443 self.ui_shutdown()
3444 return
3445
3446- # check unreportable flag
3447- if self.report.has_key('UnreportableReason'):
3448- self.ui_info_message(_('Problem in %s') % self.report['Package'].split()[0],
3449- _('The problem cannot be reported:\n\n%s') %
3450- self.report['UnreportableReason'])
3451+ if self.check_unreportable():
3452 return
3453
3454 if self.handle_duplicate():
3455@@ -298,7 +294,7 @@
3456 assert response == 'full'
3457
3458 self.file_report()
3459- except IOError, e:
3460+ except IOError as e:
3461 # fail gracefully if file is not readable for us
3462 if e.errno in (errno.EPERM, errno.EACCES):
3463 self.ui_error_message(_('Invalid problem report'),
3464@@ -311,11 +307,10 @@
3465 else:
3466 self.ui_error_message(_('Invalid problem report'), e.strerror)
3467 sys.exit(1)
3468- except OSError, e:
3469+ except OSError as e:
3470 # fail gracefully on ENOMEM
3471 if e.errno == errno.ENOMEM:
3472- print >> sys.stderr, 'Out of memory, aborting'
3473- sys.exit(1)
3474+ apport.fatal('Out of memory, aborting')
3475 else:
3476 raise
3477
3478@@ -343,12 +338,18 @@
3479 # if PID is given, add info
3480 if self.options.pid:
3481 try:
3482- self.report.add_proc_info(self.options.pid)
3483- except ValueError:
3484+ stat = open('/proc/%s/stat' % self.options.pid).read().split()
3485+ flags = int(stat[8])
3486+ if flags & PF_KTHREAD:
3487+ # this PID is a kernel thread
3488+ self.options.package = 'linux'
3489+ else:
3490+ self.report.add_proc_info(self.options.pid)
3491+ except (ValueError, IOError):
3492 self.ui_error_message(_('Invalid PID'),
3493 _('The specified process ID does not belong to a program.'))
3494 return False
3495- except OSError, e:
3496+ except OSError as e:
3497 # silently ignore nonexisting PIDs; the user must not close the
3498 # application prematurely
3499 if e.errno == errno.ENOENT:
3500@@ -372,7 +373,7 @@
3501
3502 try:
3503 self.collect_info(symptom_script)
3504- except ValueError, e:
3505+ except ValueError as e:
3506 if str(e) == 'package does not exist':
3507 if not self.cur_package:
3508 self.ui_error_message(_('Invalid problem report'),
3509@@ -384,13 +385,11 @@
3510 else:
3511 raise
3512
3513- # check unreportable flag
3514- if self.report.has_key('UnreportableReason'):
3515- self.ui_info_message(_('Problem in %s') % self.report['Package'].split()[0],
3516- _('The problem cannot be reported:\n\n%s') %
3517- self.report['UnreportableReason'])
3518+ if self.check_unreportable():
3519 return
3520
3521+ self.add_extra_tags()
3522+
3523 if self.handle_duplicate():
3524 return True
3525
3526@@ -402,10 +401,10 @@
3527
3528 if self.options.save:
3529 try:
3530- f = open(self.options.save, 'w')
3531+ f = open(os.path.expanduser(self.options.save), 'w')
3532 self.report.write(f)
3533 f.close()
3534- except (IOError, OSError), e:
3535+ except (IOError, OSError) as e:
3536 self.ui_error_message(_('Cannot create report'), excstr(e))
3537 else:
3538 # show what's being sent
3539@@ -449,7 +448,7 @@
3540
3541 info_collected = False
3542 for p in pkgs:
3543- #print 'Collecting apport information for source package %s...' % p
3544+ #print('Collecting apport information for source package %s...' % p)
3545 self.cur_package = p
3546 self.report['SourcePackage'] = p
3547 self.report['Package'] = p # no way to find this out
3548@@ -460,7 +459,7 @@
3549 apport.packaging.get_version(p)
3550 except ValueError:
3551 if not os.path.exists(os.path.join(apport.report._hook_dir, 'source_%s.py' % p)):
3552- print 'Package %s not installed and no hook available, ignoring' % p
3553+ print('Package %s not installed and no hook available, ignoring' % p)
3554 continue
3555 self.collect_info(ignore_uninstalled=True)
3556 info_collected = True
3557@@ -472,6 +471,7 @@
3558
3559 self.report.add_user_info()
3560 self.report.add_proc_environ()
3561+ self.add_extra_tags()
3562
3563 # delete the uninteresting keys
3564 del self.report['ProblemType']
3565@@ -506,19 +506,29 @@
3566 symptom_names = []
3567 symptom_descriptions = []
3568 for script in scripts:
3569+ # scripts with an underscore can be used for private libraries
3570+ if os.path.basename(script).startswith('_'):
3571+ continue
3572 symb = {}
3573 try:
3574 execfile(script, symb)
3575 except:
3576- print >> sys.stderr, 'symptom script %s is invalid' % script
3577+ apport.error('symptom script %s is invalid', script)
3578 traceback.print_exc()
3579 continue
3580+ if 'run' not in symb:
3581+ apport.error('symptom script %s does not define run() function', script)
3582+ continue
3583 symptom_names.append(os.path.splitext(os.path.basename(script))[0])
3584 symptom_descriptions.append(symb.get('description', symptom_names[-1]))
3585
3586 if not symptom_names:
3587 return False
3588
3589+ symptom_descriptions, symptom_names = \
3590+ zip(*sorted(zip(symptom_descriptions, symptom_names)))
3591+ symptom_descriptions = list(symptom_descriptions)
3592+ symptom_names = list(symptom_names)
3593 symptom_names.append(None)
3594 symptom_descriptions.append('Other problem')
3595
3596@@ -559,14 +569,32 @@
3597 elif self.options.update_report:
3598 return self.run_update_report()
3599 elif self.options.version:
3600- print __version__
3601+ print(__version__)
3602 return True
3603 elif self.options.crash_file:
3604 try:
3605 self.run_crash(self.options.crash_file, False)
3606- except OSError, e:
3607+ except OSError as e:
3608 self.ui_error_message(_('Invalid problem report'), excstr(e))
3609 return True
3610+ elif self.options.window:
3611+ self.ui_info_message('', _('After closing this message '
3612+ 'please click on an application window to report a problem about it.'))
3613+ xprop = subprocess.Popen(['xprop', '_NET_WM_PID'],
3614+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3615+ (out, err) = xprop.communicate()
3616+ if xprop.returncode == 0:
3617+ try:
3618+ self.options.pid = int(out.split()[-1])
3619+ except ValueError:
3620+ self.ui_error_message(_('Cannot create report'),
3621+ _('xprop failed to determine process ID of the window'))
3622+ return True
3623+ return self.run_report_bug()
3624+ else:
3625+ self.ui_error_message(_('Cannot create report'),
3626+ _('xprop failed to determine process ID of the window') + '\n\n' + err)
3627+ return True
3628 else:
3629 return self.run_crashes()
3630
3631@@ -581,8 +609,9 @@
3632 '''
3633 optparser = optparse.OptionParser(_('%prog <report number>'))
3634 optparser.add_option('-p', '--package',
3635- help=_('Specify package name.)'),
3636- dest='package', default=None)
3637+ help=_('Specify package name.)'))
3638+ optparser.add_option('--tag', action='append', default=[],
3639+ help=_('Add an extra tag to the report. Can be specified multiple times.'))
3640 (self.options, self.args) = optparser.parse_args()
3641
3642 if len(self.args) != 1 or not self.args[0].isdigit():
3643@@ -613,30 +642,31 @@
3644 return
3645
3646 optparser = optparse.OptionParser(_('%prog [options] [symptom|pid|package|program path|.apport/.crash file]'))
3647- optparser.add_option('-f', '--file-bug',
3648- 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.)'),
3649- action='store_true', dest='filebug', default=False)
3650- optparser.add_option('-u', '--update-bug', type='int',
3651- help=_('Start in bug updating mode. Can take an optional --package.'),
3652- dest='update_report')
3653+ optparser.add_option('-f', '--file-bug', action='store_true',
3654+ dest='filebug', default=False,
3655+ 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.)'))
3656+ optparser.add_option('-w', '--window', action='store_true', default=False,
3657+ help=_('Click a window as a target for filing a problem report.'))
3658+ optparser.add_option('-u', '--update-bug', type='int', dest='update_report',
3659+ help=_('Start in bug updating mode. Can take an optional --package.'))
3660 optparser.add_option('-s', '--symptom', metavar='SYMPTOM',
3661- help=_('File a bug report about a symptom. (Implied if symptom name is given as only argument.)'),
3662- dest='symptom')
3663+ help=_('File a bug report about a symptom. (Implied if symptom name is given as only argument.)'))
3664 optparser.add_option('-p', '--package',
3665- 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.)'),
3666- action='store', type='string', dest='package', default=None)
3667- optparser.add_option('-P', '--pid',
3668- 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.)'),
3669- action='store', type='int', dest='pid', default=None)
3670- optparser.add_option('-c', '--crash-file',
3671- 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,
3672- action='store', type='string', dest='crash_file', default=None, metavar='PATH')
3673- optparser.add_option('--save',
3674- 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.'),
3675- type='string', dest='save', default=None, metavar='PATH')
3676- optparser.add_option('-v', '--version',
3677- help=_('Print the Apport version number.'),
3678- action='store_true', dest='version', default=None)
3679+ 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.)'))
3680+ optparser.add_option('-P', '--pid', type='int',
3681+ 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.)'))
3682+ optparser.add_option('-c', '--crash-file', metavar='PATH',
3683+ 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)
3684+ optparser.add_option('--save', metavar='PATH',
3685+ 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.'))
3686+ optparser.add_option('--tag', action='append', default=[],
3687+ help=_('Add an extra tag to the report. Can be specified multiple times.'))
3688+ optparser.add_option('-v', '--version', action='store_true',
3689+ help=_('Print the Apport version number.'))
3690+
3691+ if len(sys.argv) > 0 and cmd.endswith('-bug'):
3692+ for o in ('-f', '-u', '-s', '-p', '-P', '-c'):
3693+ optparser.get_option(o).help = optparse.SUPPRESS_HELP
3694
3695 (self.options, self.args) = optparser.parse_args()
3696
3697@@ -692,11 +722,11 @@
3698 def format_filesize(self, size):
3699 '''Format the given integer as humanly readable and i18n'ed file size.'''
3700
3701- if size < 1048576:
3702- return locale.format('%.1f KiB', size/1024.)
3703- if size < 1024 * 1048576:
3704- return locale.format('%.1f MiB', size / 1048576.)
3705- return locale.format('%.1f GiB', size / float(1024 * 1048576))
3706+ if size < 1000000:
3707+ return locale.format('%.1f', size/1000.) + ' KB'
3708+ if size < 1000000000:
3709+ return locale.format('%.1f', size / 1000000.) + ' MB'
3710+ return locale.format('%.1f', size / float(1000000000)) + ' GB'
3711
3712 def get_complete_size(self):
3713 '''Return the size of the complete report.'''
3714@@ -847,12 +877,12 @@
3715
3716 # figure out appropriate web browser
3717 try:
3718- # if ksmserver is running, try kfmclient
3719+ # if ksmserver is running, try kde-open
3720 try:
3721 if os.getenv('DISPLAY') and \
3722 subprocess.call(['pgrep', '-x', '-u', str(uid), 'ksmserver'],
3723 stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0:
3724- subprocess.call(sudo_prefix + ['kfmclient', 'openURL', url])
3725+ subprocess.call(sudo_prefix + ['kde-open', url])
3726 sys.exit(0)
3727 except OSError:
3728 pass
3729@@ -893,7 +923,7 @@
3730 webbrowser.open(url, new=True, autoraise=True)
3731 sys.exit(0)
3732
3733- except Exception, e:
3734+ except Exception as e:
3735 os.write(w, str(e))
3736 sys.exit(1)
3737
3738@@ -925,7 +955,7 @@
3739 upthread.exc_raise()
3740 except KeyboardInterrupt:
3741 sys.exit(1)
3742- except NeedsCredentials, e:
3743+ except NeedsCredentials as e:
3744 message = _('Please enter your account information for the '
3745 '%s bug tracking system')
3746 data = self.ui_question_userpass(message % excstr(e))
3747@@ -936,13 +966,14 @@
3748 args=(self.report,
3749 progress_callback))
3750 upthread.start()
3751- except Exception, e:
3752+ except Exception as e:
3753 self.ui_error_message(_('Network problem'),
3754 '%s\n\n%s' % (
3755 _('Cannot connect to crash database, please check your Internet connection.'),
3756 excstr(e)))
3757 return
3758
3759+ upthread.exc_raise()
3760 ticket = upthread.return_value()
3761 self.ui_stop_upload_progress()
3762
3763@@ -961,17 +992,17 @@
3764 self.report = apport.Report()
3765 self.report.load(open(path), binary='compressed')
3766 if 'ProblemType' not in self.report:
3767- raise ValueError, 'Report does not contain "ProblemType" field'
3768+ raise ValueError('Report does not contain "ProblemType" field')
3769 except MemoryError:
3770 self.report = None
3771 self.ui_error_message(_('Memory exhaustion'),
3772 _('Your system does not have enough memory to process this crash report.'))
3773 return False
3774- except IOError, e:
3775+ except IOError as e:
3776 self.report = None
3777 self.ui_error_message(_('Invalid problem report'), e.strerror)
3778 return False
3779- except (TypeError, ValueError, zlib.error), e:
3780+ except (TypeError, ValueError, zlib.error) as e:
3781 self.report = None
3782 self.ui_error_message(_('Invalid problem report'),
3783 '%s\n\n%s' % (
3784@@ -1004,6 +1035,20 @@
3785
3786 return True
3787
3788+ def check_unreportable(self):
3789+ '''Check if the current report is unreportable.
3790+
3791+ If so, display an info message and return True.
3792+ '''
3793+ if 'UnreportableReason' in self.report:
3794+ if isinstance(self.report['UnreportableReason'], str):
3795+ self.report['UnreportableReason'] = self.report['UnreportableReason'].decode('UTF-8')
3796+ self.ui_info_message(_('Problem in %s') % self.report['Package'].split()[0],
3797+ _('The problem cannot be reported:\n\n%s') %
3798+ self.report['UnreportableReason'])
3799+ return True
3800+ return False
3801+
3802 def get_desktop_entry(self):
3803 '''Return a matching xdg.DesktopEntry for the current report.
3804
3805@@ -1037,6 +1082,16 @@
3806 self.open_url(self.report['BugPatternURL'])
3807 return True
3808
3809+ def add_extra_tags(self):
3810+ '''Add extra tags to report specified with --tags on CLI.'''
3811+
3812+ assert self.report
3813+ if self.options.tag:
3814+ tags = self.report.get('Tags', '')
3815+ if tags:
3816+ tags += ' '
3817+ self.report['Tags'] = tags + ' '.join(self.options.tag)
3818+
3819 #
3820 # abstract UI methods that must be implemented in derived classes
3821 #
3822@@ -1058,7 +1113,7 @@
3823 - Valid values for the 'blacklist' key: True or False (True will cause
3824 the invocation of report.mark_ignore()).
3825 '''
3826- raise NotImplementedError, 'this function must be overridden by subclasses'
3827+ raise NotImplementedError('this function must be overridden by subclasses')
3828
3829 def ui_present_package_error(self, desktopentry):
3830 '''Ask what to do with a package failure.
3831@@ -1069,7 +1124,7 @@
3832 Return the action: ignore ('cancel'), or report a bug about the problem
3833 ('report').
3834 '''
3835- raise NotImplementedError, 'this function must be overridden by subclasses'
3836+ raise NotImplementedError('this function must be overridden by subclasses')
3837
3838 def ui_present_kernel_error(self, desktopentry):
3839 '''Ask what to do with a kernel error.
3840@@ -1080,7 +1135,7 @@
3841 Return the action: ignore ('cancel'), or report a bug about the problem
3842 ('report').
3843 '''
3844- raise NotImplementedError, 'this function must be overridden by subclasses'
3845+ raise NotImplementedError('this function must be overridden by subclasses')
3846
3847 def ui_present_report_details(self, is_update):
3848 '''Show details of the bug report.
3849@@ -1097,17 +1152,17 @@
3850 Return the action: send full report ('full'), send reduced report
3851 ('reduced'), or do not send anything ('cancel').
3852 '''
3853- raise NotImplementedError, 'this function must be overridden by subclasses'
3854+ raise NotImplementedError('this function must be overridden by subclasses')
3855
3856 def ui_info_message(self, title, text):
3857 '''Show an information message box with given title and text.'''
3858
3859- raise NotImplementedError, 'this function must be overridden by subclasses'
3860+ raise NotImplementedError('this function must be overridden by subclasses')
3861
3862 def ui_error_message(self, title, text):
3863 '''Show an error message box with given title and text.'''
3864
3865- raise NotImplementedError, 'this function must be overridden by subclasses'
3866+ raise NotImplementedError('this function must be overridden by subclasses')
3867
3868 def ui_start_info_collection_progress(self):
3869 '''Open a indefinite progress bar for data collection.
3870@@ -1115,26 +1170,26 @@
3871 This tells the user to wait while debug information is being
3872 collected.
3873 '''
3874- raise NotImplementedError, 'this function must be overridden by subclasses'
3875+ raise NotImplementedError('this function must be overridden by subclasses')
3876
3877 def ui_pulse_info_collection_progress(self):
3878 '''Advance the data collection progress bar.
3879
3880 This function is called every 100 ms.
3881 '''
3882- raise NotImplementedError, 'this function must be overridden by subclasses'
3883+ raise NotImplementedError('this function must be overridden by subclasses')
3884
3885 def ui_stop_info_collection_progress(self):
3886 '''Close debug data collection progress window.'''
3887
3888- raise NotImplementedError, 'this function must be overridden by subclasses'
3889+ raise NotImplementedError('this function must be overridden by subclasses')
3890
3891 def ui_start_upload_progress(self):
3892 '''Open progress bar for data upload.
3893
3894 This tells the user to wait while debug information is being uploaded.
3895 '''
3896- raise NotImplementedError, 'this function must be overridden by subclasses'
3897+ raise NotImplementedError('this function must be overridden by subclasses')
3898
3899 def ui_set_upload_progress(self, progress):
3900 '''Update data upload progress bar.
3901@@ -1144,12 +1199,12 @@
3902
3903 This function is called every 100 ms.
3904 '''
3905- raise NotImplementedError, 'this function must be overridden by subclasses'
3906+ raise NotImplementedError('this function must be overridden by subclasses')
3907
3908 def ui_stop_upload_progress(self):
3909 '''Close debug data upload progress window.'''
3910
3911- raise NotImplementedError, 'this function must be overridden by subclasses'
3912+ raise NotImplementedError('this function must be overridden by subclasses')
3913
3914 def ui_shutdown(self):
3915 '''Called right before terminating the program.
3916@@ -1169,7 +1224,7 @@
3917 Return True if the user selected "Yes", False if selected "No" or
3918 "None" on cancel/dialog closing.
3919 '''
3920- raise NotImplementedError, 'this function must be overridden by subclasses'
3921+ raise NotImplementedError('this function must be overridden by subclasses')
3922
3923 def ui_question_choice(self, text, options, multiple):
3924 '''Show an question with predefined choices.
3925@@ -1181,14 +1236,14 @@
3926 Return list of selected option indexes, or None if the user cancelled.
3927 If multiple == False, the list will always have one element.
3928 '''
3929- raise NotImplementedError, 'this function must be overridden by subclasses'
3930+ raise NotImplementedError('this function must be overridden by subclasses')
3931
3932 def ui_question_file(self, text):
3933 '''Show a file selector dialog.
3934
3935 Return path if the user selected a file, or None if cancelled.
3936 '''
3937- raise NotImplementedError, 'this function must be overridden by subclasses'
3938+ raise NotImplementedError('this function must be overridden by subclasses')
3939
3940 def ui_question_userpass(self, message):
3941 '''Request username and password from user.
3942@@ -1198,7 +1253,7 @@
3943
3944 Return a tuple (username, password), or None if cancelled.
3945 '''
3946- raise NotImplementedError, 'this function must be overridden by subclasses'
3947+ raise NotImplementedError('this function must be overridden by subclasses')
3948
3949 class HookUI:
3950 '''Interactive functions which can be used in package hooks.
3951@@ -1303,7 +1358,10 @@
3952
3953 if __name__ == '__main__':
3954 import unittest, shutil, signal, tempfile, resource
3955- from cStringIO import StringIO
3956+ try:
3957+ from cStringIO import StringIO
3958+ except ImportError:
3959+ from io import StringIO
3960 import apport.report
3961 import problem_report
3962 import apport.crashdb_impl.memory
3963@@ -1314,14 +1372,14 @@
3964 def __init__(self):
3965 # use our dummy crashdb
3966 self.crashdb_conf = tempfile.NamedTemporaryFile()
3967- print >> self.crashdb_conf, '''default = 'testsuite'
3968+ self.crashdb_conf.write('''default = 'testsuite'
3969 databases = {
3970 'testsuite': {
3971 'impl': 'memory',
3972- 'bug_pattern_base': None
3973+ 'bug_pattern_url': None
3974 }
3975 }
3976-'''
3977+''')
3978 self.crashdb_conf.flush()
3979
3980 os.environ['APPORT_CRASHDB_CONF'] = self.crashdb_conf.name
3981@@ -1487,14 +1545,14 @@
3982 def test_format_filesize(self):
3983 '''format_filesize().'''
3984
3985- self.assertEqual(self.ui.format_filesize(0), '0.0 KiB')
3986- self.assertEqual(self.ui.format_filesize(2048), '2.0 KiB')
3987- self.assertEqual(self.ui.format_filesize(2560), '2.5 KiB')
3988- self.assertEqual(self.ui.format_filesize(1000000), '976.6 KiB')
3989- self.assertEqual(self.ui.format_filesize(1048576), '1.0 MiB')
3990- self.assertEqual(self.ui.format_filesize(2.7*1048576), '2.7 MiB')
3991- self.assertEqual(self.ui.format_filesize(1024*1048576), '1.0 GiB')
3992- self.assertEqual(self.ui.format_filesize(2560*1048576), '2.5 GiB')
3993+ self.assertEqual(self.ui.format_filesize(0), '0.0 KB')
3994+ self.assertEqual(self.ui.format_filesize(2048), '2.0 KB')
3995+ self.assertEqual(self.ui.format_filesize(2560), '2.6 KB')
3996+ self.assertEqual(self.ui.format_filesize(999999), '1000.0 KB')
3997+ self.assertEqual(self.ui.format_filesize(1000000), '1.0 MB')
3998+ self.assertEqual(self.ui.format_filesize(2.7*1000000), '2.7 MB')
3999+ self.assertEqual(self.ui.format_filesize(1024*1000000), '1.0 GB')
4000+ self.assertEqual(self.ui.format_filesize(2560*1000000), '2.6 GB')
4001
4002 def test_get_size_loaded(self):
4003 '''get_complete_size() and get_reduced_size() for loaded Reports.'''
4004@@ -1503,21 +1561,21 @@
4005
4006 fsize = os.path.getsize(self.report_file.name)
4007 complete_ratio = float(self.ui.get_complete_size()) / fsize
4008- self.assert_(complete_ratio >= 0.99 and complete_ratio <= 1.01)
4009+ self.assertTrue(complete_ratio >= 0.99 and complete_ratio <= 1.01)
4010
4011 rs = self.ui.get_reduced_size()
4012- self.assert_(rs > 1000)
4013- self.assert_(rs < 10000)
4014+ self.assertTrue(rs > 1000)
4015+ self.assertTrue(rs < 10000)
4016
4017 # now add some information (e. g. from package hooks)
4018 self.ui.report['ExtraInfo'] = 'A' * 50000
4019 s = self.ui.get_complete_size()
4020- self.assert_(s >= fsize + 49900)
4021- self.assert_(s < fsize + 60000)
4022+ self.assertTrue(s >= fsize + 49900)
4023+ self.assertTrue(s < fsize + 60000)
4024
4025 rs = self.ui.get_reduced_size()
4026- self.assert_(rs > 51000)
4027- self.assert_(rs < 60000)
4028+ self.assertTrue(rs > 51000)
4029+ self.assertTrue(rs < 60000)
4030
4031 def test_get_size_constructed(self):
4032 '''get_complete_size() and get_reduced_size() for on-the-fly Reports.'''
4033@@ -1526,8 +1584,8 @@
4034 self.ui.report['Hello'] = 'World'
4035
4036 s = self.ui.get_complete_size()
4037- self.assert_(s > 5)
4038- self.assert_(s < 100)
4039+ self.assertTrue(s > 5)
4040+ self.assertTrue(s < 100)
4041
4042 self.assertEqual(s, self.ui.get_reduced_size())
4043
4044@@ -1546,7 +1604,7 @@
4045 self.update_report_file()
4046 self.ui.load_report(self.report_file.name)
4047
4048- self.assert_(self.ui.report == None)
4049+ self.assertTrue(self.ui.report == None)
4050 self.assertEqual(self.ui.msg_title, _('Invalid problem report'))
4051 self.assertEqual(self.ui.msg_severity, 'info')
4052
4053@@ -1563,7 +1621,7 @@
4054 self.report_file.flush()
4055
4056 self.ui.load_report(self.report_file.name)
4057- self.assert_(self.ui.report == None)
4058+ self.assertTrue(self.ui.report == None)
4059 self.assertEqual(self.ui.msg_title, _('Invalid problem report'))
4060 self.assertEqual(self.ui.msg_severity, 'error')
4061
4062@@ -1579,8 +1637,8 @@
4063
4064 self.ui.restart()
4065 time.sleep(1) # FIXME: race condition
4066- self.assert_(os.path.exists(p))
4067- self.assert_(not os.path.exists(r))
4068+ self.assertTrue(os.path.exists(p))
4069+ self.assertTrue(not os.path.exists(r))
4070 os.unlink(p)
4071
4072 # test with RespawnCommand
4073@@ -1590,8 +1648,8 @@
4074
4075 self.ui.restart()
4076 time.sleep(1) # FIXME: race condition
4077- self.assert_(not os.path.exists(p))
4078- self.assert_(os.path.exists(r))
4079+ self.assertTrue(not os.path.exists(p))
4080+ self.assertTrue(os.path.exists(r))
4081 os.unlink(r)
4082
4083 # test that invalid command does not make us fall apart
4084@@ -1606,7 +1664,7 @@
4085 # report without any information (distro bug)
4086 self.ui.report = apport.Report()
4087 self.ui.collect_info()
4088- self.assert_(set(['Date', 'Uname', 'DistroRelease', 'ProblemType']).issubset(
4089+ self.assertTrue(set(['Date', 'Uname', 'DistroRelease', 'ProblemType']).issubset(
4090 set(self.ui.report.keys())))
4091 self.assertEqual(self.ui.ic_progress_pulses, 0,
4092 'no progress dialog for distro bug info collection')
4093@@ -1624,10 +1682,10 @@
4094 self.ui.report['Fstab'] = ('/etc/fstab', True)
4095 self.ui.report['CompressedValue'] = problem_report.CompressedValue('Test')
4096 self.ui.collect_info()
4097- self.assert_(set(['SourcePackage', 'Package', 'ProblemType',
4098+ self.assertTrue(set(['SourcePackage', 'Package', 'ProblemType',
4099 'Uname', 'Dependencies', 'DistroRelease', 'Date',
4100 'ExecutablePath']).issubset(set(self.ui.report.keys())))
4101- self.assert_(self.ui.ic_progress_pulses > 0,
4102+ self.assertTrue(self.ui.ic_progress_pulses > 0,
4103 'progress dialog for package bug info collection')
4104 self.assertEqual(self.ui.ic_progress_active, False,
4105 'progress dialog for package bug info collection finished')
4106@@ -1639,10 +1697,10 @@
4107 self.ui.report = apport.Report()
4108 self.ui.cur_package = 'bash'
4109 self.ui.collect_info()
4110- self.assert_(set(['SourcePackage', 'Package', 'ProblemType',
4111+ self.assertTrue(set(['SourcePackage', 'Package', 'ProblemType',
4112 'Uname', 'Dependencies', 'DistroRelease',
4113 'Date']).issubset(set(self.ui.report.keys())))
4114- self.assert_(self.ui.ic_progress_pulses > 0,
4115+ self.assertTrue(self.ui.ic_progress_pulses > 0,
4116 'progress dialog for package bug info collection')
4117 self.assertEqual(self.ui.ic_progress_active, False,
4118 'progress dialog for package bug info collection finished')
4119@@ -1699,13 +1757,13 @@
4120
4121 self.assertEqual(self.ui.msg_severity, None)
4122 self.assertEqual(self.ui.msg_title, None)
4123- self.assert_(self.ui.present_details_shown)
4124+ self.assertTrue(self.ui.present_details_shown)
4125 self.assertEqual(self.ui.opened_url, 'http://bash.bugs.example.com/%i' % self.ui.crashdb.latest_id())
4126
4127- self.assert_(self.ui.ic_progress_pulses > 0)
4128+ self.assertTrue(self.ui.ic_progress_pulses > 0)
4129 self.assertEqual(self.ui.report['SourcePackage'], 'bash')
4130- self.assert_('Dependencies' in self.ui.report.keys())
4131- self.assert_('ProcEnviron' in self.ui.report.keys())
4132+ self.assertTrue('Dependencies' in self.ui.report.keys())
4133+ self.assertTrue('ProcEnviron' in self.ui.report.keys())
4134 self.assertEqual(self.ui.report['ProblemType'], 'Bug')
4135
4136 # should not crash on nonexisting package
4137@@ -1715,8 +1773,8 @@
4138
4139 self.assertEqual(self.ui.msg_severity, 'error')
4140
4141- def test_run_report_bug_pid(self):
4142- '''run_report_bug() for a pid.'''
4143+ def test_run_report_bug_pid_tags(self):
4144+ '''run_report_bug() for a pid with extra tags.'''
4145
4146 # fork a test process
4147 pid = os.fork()
4148@@ -1728,7 +1786,7 @@
4149
4150 try:
4151 # report a bug on cat process
4152- sys.argv = ['ui-test', '-f', '-P', str(pid)]
4153+ sys.argv = ['ui-test', '-f', '--tag', 'foo', '-P', str(pid)]
4154 self.ui = _TestSuiteUserInterface()
4155 self.assertEqual(self.ui.run_argv(), True)
4156 finally:
4157@@ -1736,19 +1794,21 @@
4158 os.kill(pid, signal.SIGKILL)
4159 os.waitpid(pid, 0)
4160
4161- self.assert_('SourcePackage' in self.ui.report.keys())
4162- self.assert_('Dependencies' in self.ui.report.keys())
4163- self.assert_('ProcMaps' in self.ui.report.keys())
4164+ self.assertTrue('SourcePackage' in self.ui.report.keys())
4165+ self.assertTrue('Dependencies' in self.ui.report.keys())
4166+ self.assertTrue('ProcMaps' in self.ui.report.keys())
4167 self.assertEqual(self.ui.report['ExecutablePath'], '/bin/sleep')
4168- self.failIf(self.ui.report.has_key('ProcCmdline')) # privacy!
4169- self.assert_('ProcEnviron' in self.ui.report.keys())
4170+ self.assertFalse(self.ui.report.has_key('ProcCmdline')) # privacy!
4171+ self.assertTrue('ProcEnviron' in self.ui.report.keys())
4172 self.assertEqual(self.ui.report['ProblemType'], 'Bug')
4173+ self.assertTrue('Tags' in self.ui.report.keys())
4174+ self.assertTrue('foo' in self.ui.report['Tags'])
4175
4176 self.assertEqual(self.ui.msg_severity, None)
4177 self.assertEqual(self.ui.msg_title, None)
4178 self.assertEqual(self.ui.opened_url, 'http://coreutils.bugs.example.com/%i' % self.ui.crashdb.latest_id())
4179- self.assert_(self.ui.present_details_shown)
4180- self.assert_(self.ui.ic_progress_pulses > 0)
4181+ self.assertTrue(self.ui.present_details_shown)
4182+ self.assertTrue(self.ui.ic_progress_pulses > 0)
4183
4184 @classmethod
4185 def _find_unused_pid(klass):
4186@@ -1759,7 +1819,7 @@
4187 pid += 1
4188 try:
4189 os.kill(pid, 0)
4190- except OSError, e:
4191+ except OSError as e:
4192 if e.errno == errno.ESRCH:
4193 break
4194 return pid
4195@@ -1792,7 +1852,7 @@
4196 (fd, exename) = tempfile.mkstemp()
4197 os.write(fd, open('/bin/cat').read())
4198 os.close(fd)
4199- os.chmod(exename, 0755)
4200+ os.chmod(exename, 0o755)
4201
4202 # unpackaged test process
4203 pid = os.fork()
4204@@ -1810,6 +1870,25 @@
4205
4206 self.assertEqual(self.ui.msg_severity, 'error')
4207
4208+ def test_run_report_bug_kernel_thread(self):
4209+ '''run_report_bug() for a pid of a kernel thread.'''
4210+
4211+ import glob
4212+ pid = None
4213+ for path in glob.glob('/proc/[0-9]*/stat'):
4214+ stat = open(path).read().split()
4215+ flags = int(stat[8])
4216+ if flags & PF_KTHREAD:
4217+ pid = int(stat[0])
4218+ break
4219+
4220+ self.assertFalse(pid is None)
4221+ sys.argv = ['ui-test', '-f', '-P', str(pid)]
4222+ self.ui = _TestSuiteUserInterface()
4223+ self.ui.run_argv()
4224+
4225+ self.assertTrue(self.ui.report['Package'].startswith(apport.packaging.get_kernel_package()))
4226+
4227 def test_run_report_bug_file(self):
4228 '''run_report_bug() with saving report into a file.'''
4229
4230@@ -1824,16 +1903,16 @@
4231 self.assertEqual(self.ui.msg_severity, None)
4232 self.assertEqual(self.ui.msg_title, None)
4233 self.assertEqual(self.ui.opened_url, None)
4234- self.failIf(self.ui.present_details_shown)
4235+ self.assertFalse(self.ui.present_details_shown)
4236
4237- self.assert_(self.ui.ic_progress_pulses > 0)
4238+ self.assertTrue(self.ui.ic_progress_pulses > 0)
4239
4240 r = apport.Report()
4241 r.load(open(reportfile))
4242
4243 self.assertEqual(r['SourcePackage'], 'bash')
4244- self.assert_('Dependencies' in r.keys())
4245- self.assert_('ProcEnviron' in r.keys())
4246+ self.assertTrue('Dependencies' in r.keys())
4247+ self.assertTrue('ProcEnviron' in r.keys())
4248 self.assertEqual(r['ProblemType'], 'Bug')
4249
4250 # report it
4251@@ -1845,7 +1924,7 @@
4252
4253 self.assertEqual(self.ui.msg_text, None)
4254 self.assertEqual(self.ui.msg_severity, None)
4255- self.assert_(self.ui.present_details_shown)
4256+ self.assertTrue(self.ui.present_details_shown)
4257
4258 def _gen_test_crash(self):
4259 '''Generate a Report with real crash data.'''
4260@@ -1895,7 +1974,7 @@
4261 self.assertEqual(self.ui.msg_title, None)
4262 self.assertEqual(self.ui.opened_url, None)
4263 self.assertEqual(self.ui.ic_progress_pulses, 0)
4264- self.failIf(self.ui.present_details_shown)
4265+ self.assertFalse(self.ui.present_details_shown)
4266
4267 # report in crash notification dialog, cancel details report
4268 r.write(open(report_file, 'w'))
4269@@ -1908,7 +1987,7 @@
4270 self.assertEqual(self.ui.msg_title, None)
4271 self.assertEqual(self.ui.opened_url, None)
4272 self.assertNotEqual(self.ui.ic_progress_pulses, 0)
4273- self.assert_(self.ui.present_details_shown)
4274+ self.assertTrue(self.ui.present_details_shown)
4275
4276 # report in crash notification dialog, send full report
4277 r.write(open(report_file, 'w'))
4278@@ -1920,15 +1999,15 @@
4279 self.assertEqual(self.ui.msg_title, None)
4280 self.assertEqual(self.ui.opened_url, 'http://coreutils.bugs.example.com/%i' % self.ui.crashdb.latest_id())
4281 self.assertNotEqual(self.ui.ic_progress_pulses, 0)
4282- self.assert_(self.ui.present_details_shown)
4283+ self.assertTrue(self.ui.present_details_shown)
4284
4285- self.assert_('SourcePackage' in self.ui.report.keys())
4286- self.assert_('Dependencies' in self.ui.report.keys())
4287- self.assert_('Stacktrace' in self.ui.report.keys())
4288- self.assert_('ProcEnviron' in self.ui.report.keys())
4289+ self.assertTrue('SourcePackage' in self.ui.report.keys())
4290+ self.assertTrue('Dependencies' in self.ui.report.keys())
4291+ self.assertTrue('Stacktrace' in self.ui.report.keys())
4292+ self.assertTrue('ProcEnviron' in self.ui.report.keys())
4293 self.assertEqual(self.ui.report['ProblemType'], 'Crash')
4294- self.assert_(len(self.ui.report['CoreDump']) > 10000)
4295- self.assert_(self.ui.report['Title'].startswith('cat crashed with SIGSEGV'))
4296+ self.assertTrue(len(self.ui.report['CoreDump']) > 10000)
4297+ self.assertTrue(self.ui.report['Title'].startswith('cat crashed with SIGSEGV'))
4298
4299 # report in crash notification dialog, send reduced report
4300 r.write(open(report_file, 'w'))
4301@@ -1940,16 +2019,16 @@
4302 self.assertEqual(self.ui.msg_title, None)
4303 self.assertEqual(self.ui.opened_url, 'http://coreutils.bugs.example.com/%i' % self.ui.crashdb.latest_id())
4304 self.assertNotEqual(self.ui.ic_progress_pulses, 0)
4305- self.assert_(self.ui.present_details_shown)
4306+ self.assertTrue(self.ui.present_details_shown)
4307
4308- self.assert_('SourcePackage' in self.ui.report.keys())
4309- self.assert_('Dependencies' in self.ui.report.keys())
4310- self.assert_('Stacktrace' in self.ui.report.keys())
4311+ self.assertTrue('SourcePackage' in self.ui.report.keys())
4312+ self.assertTrue('Dependencies' in self.ui.report.keys())
4313+ self.assertTrue('Stacktrace' in self.ui.report.keys())
4314 self.assertEqual(self.ui.report['ProblemType'], 'Crash')
4315- self.assert_(not self.ui.report.has_key('CoreDump'))
4316+ self.assertTrue(not self.ui.report.has_key('CoreDump'))
4317
4318 # so far we did not blacklist, verify that
4319- self.assert_(not self.ui.report.check_ignored())
4320+ self.assertTrue(not self.ui.report.check_ignored())
4321
4322 # cancel crash notification dialog and blacklist
4323 r.write(open(report_file, 'w'))
4324@@ -1961,23 +2040,32 @@
4325 self.assertEqual(self.ui.opened_url, None)
4326 self.assertEqual(self.ui.ic_progress_pulses, 0)
4327
4328- self.assert_(self.ui.report.check_ignored())
4329+ self.assertTrue(self.ui.report.check_ignored())
4330
4331 def test_run_crash_abort(self):
4332- '''run_crash() for an unreportable abort()'''
4333-
4334- self.report['Signal'] = '6'
4335- self.report['ExecutablePath'] = '/bin/bash'
4336- self.report['Package'] = 'bash 1'
4337- self.update_report_file()
4338+ '''run_crash() for an abort() without assertion message'''
4339+
4340+ r = self._gen_test_crash()
4341+ r['Signal'] = '6'
4342+ report_file = os.path.join(apport.fileutils.report_dir, 'test.crash')
4343+ r.write(open(report_file, 'w'))
4344+
4345 self.ui.present_crash_response = {'action': 'report', 'blacklist': False }
4346 self.ui.present_details_response = 'full'
4347- self.ui.run_crash(self.report_file.name)
4348-
4349- self.assert_('assert' in self.ui.msg_text, '%s: %s' %
4350- (self.ui.msg_title, self.ui.msg_text))
4351- self.assertEqual(self.ui.msg_severity, 'info')
4352- self.failIf(self.ui.present_details_shown)
4353+ self.ui.run_crash(report_file)
4354+
4355+ self.assertTrue('SourcePackage' in self.ui.report.keys())
4356+ self.assertTrue('Dependencies' in self.ui.report.keys())
4357+ self.assertTrue('Stacktrace' in self.ui.report.keys())
4358+ self.assertTrue('ProcEnviron' in self.ui.report.keys())
4359+ self.assertEqual(self.ui.report['Signal'], '6')
4360+
4361+ # we disable the ABRT filtering, we want these crashes after all
4362+ #self.assertTrue('assert' in self.ui.msg_text, '%s: %s' %
4363+ # (self.ui.msg_title, self.ui.msg_text))
4364+ #self.assertEqual(self.ui.msg_severity, 'info')
4365+ self.assertEqual(self.ui.msg_severity, None)
4366+ self.assertTrue(self.ui.present_details_shown)
4367
4368 def test_run_crash_argv_file(self):
4369 '''run_crash() through a file specified on the command line.'''
4370@@ -1994,21 +2082,21 @@
4371
4372 self.assertEqual(self.ui.msg_text, None)
4373 self.assertEqual(self.ui.msg_severity, None)
4374- self.assert_(self.ui.present_details_shown)
4375+ self.assertTrue(self.ui.present_details_shown)
4376
4377 # unreportable
4378 self.report['Package'] = 'bash'
4379- self.report['UnreportableReason'] = 'It stinks.'
4380+ self.report['UnreportableReason'] = u'It stinks. \u2665'
4381 self.update_report_file()
4382
4383 sys.argv = ['ui-test', '-c', self.report_file.name]
4384 self.ui = _TestSuiteUserInterface()
4385 self.assertEqual(self.ui.run_argv(), True)
4386
4387- self.assert_('It stinks.' in self.ui.msg_text, '%s: %s' %
4388+ self.assertTrue('It stinks.' in self.ui.msg_text, '%s: %s' %
4389 (self.ui.msg_title, self.ui.msg_text))
4390 self.assertEqual(self.ui.msg_severity, 'info')
4391- self.failIf(self.ui.present_details_shown)
4392+ self.assertFalse(self.ui.present_details_shown)
4393
4394 # should not die with an exception on an invalid name
4395 sys.argv = ['ui-test', '-c', '/nonexisting.crash' ]
4396@@ -2029,7 +2117,7 @@
4397
4398 self.ui.run_crash(self.report_file.name)
4399
4400- self.assert_('It stinks.' in self.ui.msg_text, '%s: %s' %
4401+ self.assertTrue('It stinks.' in self.ui.msg_text, '%s: %s' %
4402 (self.ui.msg_title, self.ui.msg_text))
4403 self.assertEqual(self.ui.msg_severity, 'info')
4404
4405@@ -2077,7 +2165,7 @@
4406 self.ui = _TestSuiteUserInterface()
4407 self.ui.run_crash(report_file)
4408 self.assertEqual(self.ui.msg_severity, 'error')
4409- self.assert_('memory' in self.ui.msg_text, '%s: %s' %
4410+ self.assertTrue('memory' in self.ui.msg_text, '%s: %s' %
4411 (self.ui.msg_title, self.ui.msg_text))
4412
4413 def test_run_crash_preretraced(self):
4414@@ -2106,7 +2194,7 @@
4415 self.assertEqual(self.ui.msg_title, None)
4416 self.assertEqual(self.ui.opened_url, None)
4417 self.assertEqual(self.ui.ic_progress_pulses, 0)
4418- self.assert_(self.ui.present_details_shown)
4419+ self.assertTrue(self.ui.present_details_shown)
4420
4421 def test_run_crash_errors(self):
4422 '''run_crash() on various error conditions.'''
4423@@ -2185,7 +2273,7 @@
4424 self.assertEqual(self.ui.msg_title, None)
4425 self.assertEqual(self.ui.opened_url, None)
4426 self.assertEqual(self.ui.ic_progress_pulses, 0)
4427- self.failIf(self.ui.present_details_shown)
4428+ self.assertFalse(self.ui.present_details_shown)
4429
4430 # report in crash notification dialog, send report
4431 r.write(open(report_file, 'w'))
4432@@ -2196,16 +2284,16 @@
4433 self.assertEqual(self.ui.msg_severity, None)
4434 self.assertEqual(self.ui.msg_title, None)
4435 self.assertEqual(self.ui.opened_url, 'http://bash.bugs.example.com/%i' % self.ui.crashdb.latest_id())
4436- self.assert_(self.ui.present_details_shown)
4437+ self.assertTrue(self.ui.present_details_shown)
4438
4439- self.assert_('SourcePackage' in self.ui.report.keys())
4440- self.assert_('Package' in self.ui.report.keys())
4441+ self.assertTrue('SourcePackage' in self.ui.report.keys())
4442+ self.assertTrue('Package' in self.ui.report.keys())
4443 self.assertEqual(self.ui.report['ProblemType'], 'Package')
4444
4445 # verify that additional information has been collected
4446- self.assert_('Architecture' in self.ui.report.keys())
4447- self.assert_('DistroRelease' in self.ui.report.keys())
4448- self.assert_('Uname' in self.ui.report.keys())
4449+ self.assertTrue('Architecture' in self.ui.report.keys())
4450+ self.assertTrue('DistroRelease' in self.ui.report.keys())
4451+ self.assertTrue('Uname' in self.ui.report.keys())
4452
4453 def test_run_crash_kernel(self):
4454 '''run_crash() for a kernel error.'''
4455@@ -2235,7 +2323,7 @@
4456 self.assertEqual(self.ui.msg_title, None)
4457 self.assertEqual(self.ui.opened_url, None)
4458 self.assertEqual(self.ui.ic_progress_pulses, 0)
4459- self.failIf(self.ui.present_details_shown)
4460+ self.assertFalse(self.ui.present_details_shown)
4461
4462 # report in crash notification dialog, send report
4463 r.write(open(report_file, 'w'))
4464@@ -2247,11 +2335,11 @@
4465 ' ' + str(self.ui.msg_text))
4466 self.assertEqual(self.ui.msg_title, None)
4467 self.assertEqual(self.ui.opened_url, 'http://linux.bugs.example.com/%i' % self.ui.crashdb.latest_id())
4468- self.assert_(self.ui.present_details_shown)
4469+ self.assertTrue(self.ui.present_details_shown)
4470
4471- self.assert_('SourcePackage' in self.ui.report.keys())
4472+ self.assertTrue('SourcePackage' in self.ui.report.keys())
4473 # did we run the hooks properly?
4474- self.assert_('KernelDebug' in self.ui.report.keys())
4475+ self.assertTrue('KernelDebug' in self.ui.report.keys())
4476 self.assertEqual(self.ui.report['ProblemType'], 'KernelCrash')
4477
4478 def test_run_crash_anonymity(self):
4479@@ -2265,7 +2353,7 @@
4480 self.ui.present_details_response = 'cancel'
4481 self.ui.run_crash(report_file)
4482
4483- self.failIf('ProcCwd' in self.ui.report)
4484+ self.assertFalse('ProcCwd' in self.ui.report)
4485
4486 dump = StringIO()
4487 self.ui.report.write(dump)
4488@@ -2274,7 +2362,7 @@
4489 bad_strings = [os.uname()[1], p[0], p[4], p[5], os.getcwd()]
4490
4491 for s in bad_strings:
4492- self.failIf(s in dump.getvalue(), 'dump contains sensitive string: %s' % s)
4493+ self.assertFalse(s in dump.getvalue(), 'dump contains sensitive string: %s' % s)
4494
4495 def test_run_update_report_nonexisting_package_from_bug(self):
4496 '''run_update_report() on a nonexisting package (from bug).'''
4497@@ -2285,9 +2373,9 @@
4498 '', {'dummy_data': 1})
4499
4500 self.assertEqual(self.ui.run_argv(), False)
4501- self.assert_('No additional information collected.' in
4502+ self.assertTrue('No additional information collected.' in
4503 self.ui.msg_text)
4504- self.failIf(self.ui.present_details_shown)
4505+ self.assertFalse(self.ui.present_details_shown)
4506
4507 def test_run_update_report_nonexisting_package_cli(self):
4508 '''run_update_report() on a nonexisting package (CLI argument).'''
4509@@ -2298,9 +2386,9 @@
4510 '', {'dummy_data': 1})
4511
4512 self.assertEqual(self.ui.run_argv(), False)
4513- self.assert_('No additional information collected.' in
4514+ self.assertTrue('No additional information collected.' in
4515 self.ui.msg_text)
4516- self.failIf(self.ui.present_details_shown)
4517+ self.assertFalse(self.ui.present_details_shown)
4518
4519 def test_run_update_report_existing_package_from_bug(self):
4520 '''run_update_report() on an existing package (from bug).'''
4521@@ -2316,17 +2404,17 @@
4522 self.assertEqual(self.ui.msg_severity, None, self.ui.msg_text)
4523 self.assertEqual(self.ui.msg_title, None)
4524 self.assertEqual(self.ui.opened_url, None)
4525- self.assert_(self.ui.present_details_shown)
4526-
4527- self.assert_(self.ui.ic_progress_pulses > 0)
4528- self.assert_(self.ui.report['Package'].startswith('bash '))
4529- self.assert_('Dependencies' in self.ui.report.keys())
4530- self.assert_('ProcEnviron' in self.ui.report.keys())
4531-
4532- def test_run_update_report_existing_package_cli(self):
4533- '''run_update_report() on an existing package (CLI argument).'''
4534-
4535- sys.argv = ['ui-test', '-u', '1', '-p', 'bash']
4536+ self.assertTrue(self.ui.present_details_shown)
4537+
4538+ self.assertTrue(self.ui.ic_progress_pulses > 0)
4539+ self.assertTrue(self.ui.report['Package'].startswith('bash '))
4540+ self.assertTrue('Dependencies' in self.ui.report.keys())
4541+ self.assertTrue('ProcEnviron' in self.ui.report.keys())
4542+
4543+ def test_run_update_report_existing_package_cli_tags(self):
4544+ '''run_update_report() on an existing package (CLI argument) with extra tag'''
4545+
4546+ sys.argv = ['ui-test', '-u', '1', '-p', 'bash', '--tag', 'foo']
4547 self.ui = _TestSuiteUserInterface()
4548 self.ui.crashdb = apport.crashdb_impl.memory.CrashDatabase(None,
4549 '', {'dummy_data': 1})
4550@@ -2335,12 +2423,13 @@
4551 self.assertEqual(self.ui.msg_severity, None, self.ui.msg_text)
4552 self.assertEqual(self.ui.msg_title, None)
4553 self.assertEqual(self.ui.opened_url, None)
4554- self.assert_(self.ui.present_details_shown)
4555+ self.assertTrue(self.ui.present_details_shown)
4556
4557- self.assert_(self.ui.ic_progress_pulses > 0)
4558- self.assert_(self.ui.report['Package'].startswith('bash '))
4559- self.assert_('Dependencies' in self.ui.report.keys())
4560- self.assert_('ProcEnviron' in self.ui.report.keys())
4561+ self.assertTrue(self.ui.ic_progress_pulses > 0)
4562+ self.assertTrue(self.ui.report['Package'].startswith('bash '))
4563+ self.assertTrue('Dependencies' in self.ui.report.keys())
4564+ self.assertTrue('ProcEnviron' in self.ui.report.keys())
4565+ self.assertTrue('foo' in self.ui.report['Tags'])
4566
4567 def test_run_update_report_existing_package_cli_cmdname(self):
4568 '''run_update_report() on an existing package (-collect program).'''
4569@@ -2354,12 +2443,12 @@
4570 self.assertEqual(self.ui.msg_severity, None, self.ui.msg_text)
4571 self.assertEqual(self.ui.msg_title, None)
4572 self.assertEqual(self.ui.opened_url, None)
4573- self.assert_(self.ui.present_details_shown)
4574+ self.assertTrue(self.ui.present_details_shown)
4575
4576- self.assert_(self.ui.ic_progress_pulses > 0)
4577- self.assert_(self.ui.report['Package'].startswith('bash '))
4578- self.assert_('Dependencies' in self.ui.report.keys())
4579- self.assert_('ProcEnviron' in self.ui.report.keys())
4580+ self.assertTrue(self.ui.ic_progress_pulses > 0)
4581+ self.assertTrue(self.ui.report['Package'].startswith('bash '))
4582+ self.assertTrue('Dependencies' in self.ui.report.keys())
4583+ self.assertTrue('ProcEnviron' in self.ui.report.keys())
4584
4585 def test_run_update_report_noninstalled_but_hook(self):
4586 '''run_update_report() on an uninstalled package with a source hook.'''
4587@@ -2377,12 +2466,12 @@
4588 self.assertEqual(self.ui.msg_severity, None, self.ui.msg_text)
4589 self.assertEqual(self.ui.msg_title, None)
4590 self.assertEqual(self.ui.opened_url, None)
4591- self.assert_(self.ui.present_details_shown)
4592+ self.assertTrue(self.ui.present_details_shown)
4593
4594- self.assert_(self.ui.ic_progress_pulses > 0)
4595+ self.assertTrue(self.ui.ic_progress_pulses > 0)
4596 self.assertEqual(self.ui.report['Package'], 'foo (not installed)')
4597 self.assertEqual(self.ui.report['MachineType'], 'Laptop')
4598- self.assert_('ProcEnviron' in self.ui.report.keys())
4599+ self.assertTrue('ProcEnviron' in self.ui.report.keys())
4600
4601 def _run_hook(self, code):
4602 f = open(os.path.join(self.hookdir, 'coreutils.py'), 'w')
4603@@ -2478,12 +2567,12 @@
4604 sys.argv = ['ui-test', '-s', 'foobar' ]
4605 self.ui = _TestSuiteUserInterface()
4606 self.assertEqual(self.ui.run_argv(), True)
4607- self.assert_('foobar" is not known' in self.ui.msg_text)
4608+ self.assertTrue('foobar" is not known' in self.ui.msg_text)
4609 self.assertEqual(self.ui.msg_severity, 'error')
4610
4611 # does not determine package
4612 f = open(os.path.join(symptom_script_dir, 'nopkg.py'), 'w')
4613- print >> f, 'def run(report, ui):\n pass'
4614+ f.write('def run(report, ui):\n pass\n')
4615 f.close()
4616 orig_stderr = sys.stderr
4617 sys.argv = ['ui-test', '-s', 'nopkg' ]
4618@@ -2492,11 +2581,11 @@
4619 self.assertRaises(SystemExit, self.ui.run_argv)
4620 err = sys.stderr.getvalue()
4621 sys.stderr = orig_stderr
4622- self.assert_('did not determine the affected package' in err)
4623+ self.assertTrue('did not determine the affected package' in err)
4624
4625 # does not define run()
4626 f = open(os.path.join(symptom_script_dir, 'norun.py'), 'w')
4627- print >> f, 'def something(x, y):\n return 1'
4628+ f.write('def something(x, y):\n return 1\n')
4629 f.close()
4630 sys.argv = ['ui-test', '-s', 'norun' ]
4631 self.ui = _TestSuiteUserInterface()
4632@@ -2504,11 +2593,11 @@
4633 self.assertRaises(SystemExit, self.ui.run_argv)
4634 err = sys.stderr.getvalue()
4635 sys.stderr = orig_stderr
4636- self.assert_('norun.py crashed:' in err)
4637+ self.assertTrue('norun.py crashed:' in err)
4638
4639 # crashing script
4640 f = open(os.path.join(symptom_script_dir, 'crash.py'), 'w')
4641- print >> f, 'def run(report, ui):\n return 1/0'
4642+ f.write('def run(report, ui):\n return 1/0\n')
4643 f.close()
4644 sys.argv = ['ui-test', '-s', 'crash' ]
4645 self.ui = _TestSuiteUserInterface()
4646@@ -2516,45 +2605,56 @@
4647 self.assertRaises(SystemExit, self.ui.run_argv)
4648 err = sys.stderr.getvalue()
4649 sys.stderr = orig_stderr
4650- self.assert_('crash.py crashed:' in err)
4651- self.assert_('ZeroDivisionError:' in err)
4652+ self.assertTrue('crash.py crashed:' in err)
4653+ self.assertTrue('ZeroDivisionError:' in err)
4654
4655 # working noninteractive script
4656 f = open(os.path.join(symptom_script_dir, 'itching.py'), 'w')
4657- print >> f, 'def run(report, ui):\n report["itch"] = "scratch"\n return "bash"'
4658+ f.write('def run(report, ui):\n report["itch"] = "scratch"\n return "bash"\n')
4659 f.close()
4660 sys.argv = ['ui-test', '-s', 'itching' ]
4661 self.ui = _TestSuiteUserInterface()
4662 self.assertEqual(self.ui.run_argv(), True)
4663 self.assertEqual(self.ui.msg_text, None)
4664 self.assertEqual(self.ui.msg_severity, None)
4665- self.assert_(self.ui.present_details_shown)
4666+ self.assertTrue(self.ui.present_details_shown)
4667
4668 self.assertEqual(self.ui.report['itch'], 'scratch')
4669- self.assert_('DistroRelease' in self.ui.report)
4670+ self.assertTrue('DistroRelease' in self.ui.report)
4671 self.assertEqual(self.ui.report['SourcePackage'], 'bash')
4672- self.assert_(self.ui.report['Package'].startswith('bash '))
4673+ self.assertTrue(self.ui.report['Package'].startswith('bash '))
4674 self.assertEqual(self.ui.report['ProblemType'], 'Bug')
4675
4676+ # working noninteractive script with extra tag
4677+ sys.argv = ['ui-test', '--tag', 'foo', '-s', 'itching' ]
4678+ self.ui = _TestSuiteUserInterface()
4679+ self.assertEqual(self.ui.run_argv(), True)
4680+ self.assertEqual(self.ui.msg_text, None)
4681+ self.assertEqual(self.ui.msg_severity, None)
4682+ self.assertTrue(self.ui.present_details_shown)
4683+
4684+ self.assertEqual(self.ui.report['itch'], 'scratch')
4685+ self.assertTrue('foo' in self.ui.report['Tags'])
4686+
4687 # working interactive script
4688 f = open(os.path.join(symptom_script_dir, 'itching.py'), 'w')
4689- print >> f, '''def run(report, ui):
4690+ f.write('''def run(report, ui):
4691 report['itch'] = 'slap'
4692 report['q'] = str(ui.yesno('do you?'))
4693 return 'bash'
4694-'''
4695+''')
4696 f.close()
4697 sys.argv = ['ui-test', '-s', 'itching' ]
4698 self.ui = _TestSuiteUserInterface()
4699 self.ui.question_yesno_response = True
4700 self.assertEqual(self.ui.run_argv(), True)
4701- self.assert_(self.ui.present_details_shown)
4702+ self.assertTrue(self.ui.present_details_shown)
4703 self.assertEqual(self.ui.msg_text, 'do you?')
4704
4705 self.assertEqual(self.ui.report['itch'], 'slap')
4706- self.assert_('DistroRelease' in self.ui.report)
4707+ self.assertTrue('DistroRelease' in self.ui.report)
4708 self.assertEqual(self.ui.report['SourcePackage'], 'bash')
4709- self.assert_(self.ui.report['Package'].startswith('bash '))
4710+ self.assertTrue(self.ui.report['Package'].startswith('bash '))
4711 self.assertEqual(self.ui.report['ProblemType'], 'Bug')
4712 self.assertEqual(self.ui.report['q'], 'True')
4713
4714@@ -2562,13 +2662,13 @@
4715 '''run_report_bug() without specifying arguments and available symptoms.'''
4716
4717 f = open(os.path.join(symptom_script_dir, 'foo.py'), 'w')
4718- print >> f, '''description = 'foo does not work'
4719+ f.write('''description = 'foo does not work'
4720 def run(report, ui):
4721 return 'bash'
4722-'''
4723+''')
4724 f.close()
4725 f = open(os.path.join(symptom_script_dir, 'bar.py'), 'w')
4726- print >> f, 'def run(report, ui):\n return "coreutils"'
4727+ f.write('def run(report, ui):\n return "coreutils"\n')
4728 f.close()
4729
4730 sys.argv = ['ui-test', '-f']
4731@@ -2577,30 +2677,28 @@
4732 self.ui.question_choice_response = None
4733 self.assertEqual(self.ui.run_argv(), True)
4734 self.assertEqual(self.ui.msg_severity, None)
4735- self.assert_('kind of problem' in self.ui.msg_text)
4736+ self.assertTrue('kind of problem' in self.ui.msg_text)
4737 self.assertEqual(set(self.ui.msg_choices),
4738 set(['bar', 'foo does not work', 'Other problem']))
4739
4740 # cancelled
4741 self.assertEqual(self.ui.ic_progress_pulses, 0)
4742 self.assertEqual(self.ui.report, None)
4743- self.failIf(self.ui.present_details_shown)
4744+ self.assertFalse(self.ui.present_details_shown)
4745
4746 # now, choose foo -> bash report
4747 self.ui.question_choice_response = [self.ui.msg_choices.index('foo does not work')]
4748 self.assertEqual(self.ui.run_argv(), True)
4749 self.assertEqual(self.ui.msg_severity, None)
4750- self.assert_(self.ui.ic_progress_pulses > 0)
4751- self.assert_(self.ui.present_details_shown)
4752- self.assert_(self.ui.report['Package'].startswith('bash'))
4753+ self.assertTrue(self.ui.ic_progress_pulses > 0)
4754+ self.assertTrue(self.ui.present_details_shown)
4755+ self.assertTrue(self.ui.report['Package'].startswith('bash'))
4756
4757- def test_parse_argv(self):
4758+ def test_parse_argv_single_arg(self):
4759 '''parse_args() option inference for a single argument'''
4760
4761- def _chk(program_name, arg, expected_opts, argv_options=None):
4762+ def _chk(program_name, arg, expected_opts):
4763 sys.argv = [program_name]
4764- if argv_options:
4765- sys.argv += argv_options
4766 if arg:
4767 sys.argv.append(arg)
4768 orig_stderr = sys.stderr
4769@@ -2617,71 +2715,144 @@
4770 # no arguments -> show pending crashes
4771 _chk('apport-gtk', None, {'filebug': False, 'package': None,
4772 'pid': None, 'crash_file': None, 'symptom': None,
4773- 'update_report': None, 'save': None})
4774- # ... except when being called as '*-bug', then default to bug mode
4775- _chk('apport-bug', None, {'filebug': True, 'package': None,
4776- 'pid': None, 'crash_file': None, 'symptom': None,
4777- 'update_report': None, 'save': None})
4778+ 'update_report': None, 'save': None, 'window': False,
4779+ 'tag': []})
4780 # updating report not allowed without args
4781 self.assertRaises(SystemExit, _chk, 'apport-collect', None, {})
4782
4783 # package
4784 _chk('apport-kde', 'coreutils', {'filebug': True, 'package':
4785 'coreutils', 'pid': None, 'crash_file': None, 'symptom': None,
4786- 'update_report': None, 'save': None})
4787- _chk('apport-bug', 'coreutils', {'filebug': True, 'package':
4788- 'coreutils', 'pid': None, 'crash_file': None, 'symptom': None,
4789- 'update_report': None, 'save': None})
4790- _chk('apport-bug', 'coreutils', {'filebug': True, 'package':
4791- 'coreutils', 'pid': None, 'crash_file': None, 'symptom': None,
4792- 'update_report': None, 'save': 'foo.apport'},
4793- ['--save', 'foo.apport'])
4794+ 'update_report': None, 'save': None, 'window': False,
4795+ 'tag': []})
4796
4797 # symptom is preferred over package
4798 f = open(os.path.join(symptom_script_dir, 'coreutils.py'), 'w')
4799- print >> f, '''description = 'foo does not work'
4800+ f.write('''description = 'foo does not work'
4801 def run(report, ui):
4802 return 'bash'
4803-'''
4804+''')
4805 f.close()
4806 _chk('apport-cli', 'coreutils', {'filebug': True, 'package': None,
4807 'pid': None, 'crash_file': None, 'symptom': 'coreutils',
4808- 'update_report': None, 'save': None})
4809- _chk('apport-bug', 'coreutils', {'filebug': True, 'package': None,
4810- 'pid': None, 'crash_file': None, 'symptom': 'coreutils',
4811- 'update_report': None, 'save': None})
4812+ 'update_report': None, 'save': None, 'window': False,
4813+ 'tag': []})
4814
4815 # PID
4816 _chk('apport-cli', '1234', {'filebug': True, 'package': None,
4817 'pid': '1234', 'crash_file': None, 'symptom': None,
4818- 'update_report': None, 'save': None})
4819- _chk('apport-bug', '1234', {'filebug': True, 'package': None,
4820- 'pid': '1234', 'crash_file': None, 'symptom': None,
4821- 'update_report': None, 'save': None})
4822+ 'update_report': None, 'save': None, 'window': False,
4823+ 'tag': []})
4824
4825 # .crash/.apport files; check correct handling of spaces
4826 for suffix in ('.crash', '.apport'):
4827- for prog in ('apport-cli', 'apport-bug'):
4828- _chk(prog, '/tmp/f oo' + suffix, {'filebug': False,
4829- 'package': None, 'pid': None,
4830- 'crash_file': '/tmp/f oo' + suffix, 'symptom': None,
4831- 'update_report': None, 'save': None})
4832+ _chk('apport-cli', '/tmp/f oo' + suffix, {'filebug': False,
4833+ 'package': None, 'pid': None,
4834+ 'crash_file': '/tmp/f oo' + suffix, 'symptom': None,
4835+ 'update_report': None, 'save': None, 'window': False,
4836+ 'tag': []})
4837
4838 # executable
4839 _chk('apport-cli', '/usr/bin/tail', {'filebug': True,
4840 'package': 'coreutils',
4841 'pid': None, 'crash_file': None, 'symptom': None,
4842- 'update_report': None, 'save': None})
4843- _chk('apport-bug', '/usr/bin/tail', {'filebug': True,
4844- 'package': 'coreutils',
4845- 'pid': None, 'crash_file': None, 'symptom': None,
4846- 'update_report': None, 'save': None})
4847+ 'update_report': None, 'save': None, 'window': False,
4848+ 'tag': []})
4849
4850 # update existing report
4851 _chk('apport-collect', '1234', {'filebug': False, 'package': None,
4852- 'crash_file': None, 'symptom': None, 'update_report': 1234})
4853+ 'crash_file': None, 'symptom': None, 'update_report': 1234,
4854+ 'tag': []})
4855 _chk('apport-update-bug', '1234', {'filebug': False, 'package': None,
4856- 'crash_file': None, 'symptom': None, 'update_report': 1234})
4857+ 'crash_file': None, 'symptom': None, 'update_report': 1234,
4858+ 'tag': []})
4859+
4860+ def test_parse_argv_apport_bug(self):
4861+ '''parse_args() option inference when invoked as *-bug'''
4862+
4863+ def _chk(args, expected_opts):
4864+ sys.argv = ['apport-bug'] + args
4865+ orig_stderr = sys.stderr
4866+ sys.stderr = open('/dev/null', 'w')
4867+ try:
4868+ ui = UserInterface()
4869+ finally:
4870+ sys.stderr.close()
4871+ sys.stderr = orig_stderr
4872+ expected_opts['version'] = None
4873+ self.assertEqual(ui.args, [])
4874+ self.assertEqual(ui.options, expected_opts)
4875+
4876+ #
4877+ # no arguments: default to 'ask for symptom' bug mode
4878+ #
4879+ _chk([], {'filebug': True, 'package': None,
4880+ 'pid': None, 'crash_file': None, 'symptom': None,
4881+ 'update_report': None, 'save': None, 'window': False,
4882+ 'tag': []})
4883+
4884+ #
4885+ # single arguments
4886+ #
4887+
4888+ # package
4889+ _chk(['coreutils'], {'filebug': True, 'package':
4890+ 'coreutils', 'pid': None, 'crash_file': None, 'symptom': None,
4891+ 'update_report': None, 'save': None, 'window': False,
4892+ 'tag': []})
4893+
4894+ # symptom (preferred over package)
4895+ f = open(os.path.join(symptom_script_dir, 'coreutils.py'), 'w')
4896+ f.write('''description = 'foo does not work'
4897+def run(report, ui):
4898+ return 'bash'
4899+''')
4900+ f.close()
4901+ _chk(['coreutils'], {'filebug': True, 'package': None,
4902+ 'pid': None, 'crash_file': None, 'symptom': 'coreutils',
4903+ 'update_report': None, 'save': None, 'window': False,
4904+ 'tag': []})
4905+ os.unlink(os.path.join(symptom_script_dir, 'coreutils.py'))
4906+
4907+ # PID
4908+ _chk(['1234'], {'filebug': True, 'package': None,
4909+ 'pid': '1234', 'crash_file': None, 'symptom': None,
4910+ 'update_report': None, 'save': None, 'window': False,
4911+ 'tag': []})
4912+
4913+ # .crash/.apport files; check correct handling of spaces
4914+ for suffix in ('.crash', '.apport'):
4915+ _chk(['/tmp/f oo' + suffix], {'filebug': False,
4916+ 'package': None, 'pid': None,
4917+ 'crash_file': '/tmp/f oo' + suffix, 'symptom': None,
4918+ 'update_report': None, 'save': None, 'window': False,
4919+ 'tag': []})
4920+
4921+ # executable name
4922+ _chk(['/usr/bin/tail'], {'filebug': True, 'package': 'coreutils',
4923+ 'pid': None, 'crash_file': None, 'symptom': None,
4924+ 'update_report': None, 'save': None, 'window': False,
4925+ 'tag': []})
4926+
4927+ #
4928+ # supported options
4929+ #
4930+
4931+ # --save
4932+ _chk(['--save', 'foo.apport', 'coreutils'], {'filebug': True,
4933+ 'package': 'coreutils', 'pid': None, 'crash_file': None,
4934+ 'symptom': None, 'update_report': None, 'save': 'foo.apport',
4935+ 'window': False, 'tag': []})
4936+
4937+ # --tag
4938+ _chk(['--tag', 'foo', 'coreutils'], {'filebug': True,
4939+ 'package': 'coreutils', 'pid': None, 'crash_file': None,
4940+ 'symptom': None, 'update_report': None, 'save': None,
4941+ 'window': False, 'tag': ['foo']})
4942+ _chk(['--tag', 'foo', '--tag', 'bar', 'coreutils'], {
4943+ 'filebug': True, 'package': 'coreutils', 'pid': None,
4944+ 'crash_file': None, 'symptom': None, 'update_report': None,
4945+ 'save': None, 'window': False, 'tag': ['foo', 'bar']})
4946
4947 unittest.main()
4948
4949
4950=== modified file 'apport_python_hook.py'
4951--- apport_python_hook.py 2010-03-02 09:48:29 +0000
4952+++ apport_python_hook.py 2011-04-20 23:32:24 +0000
4953@@ -48,7 +48,11 @@
4954 if not enabled():
4955 return
4956
4957- from cStringIO import StringIO
4958+ try:
4959+ from cStringIO import StringIO
4960+ except ImportError:
4961+ from io import StringIO
4962+
4963 import re, tempfile, traceback
4964 from apport.fileutils import likely_packaged
4965
4966@@ -102,7 +106,7 @@
4967 # don't clobber existing report
4968 return
4969 report_file = os.fdopen(os.open(pr_filename,
4970- os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0600), 'w')
4971+ os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0o600), 'w')
4972 try:
4973 pr.write(report_file)
4974 finally:
4975@@ -154,15 +158,15 @@
4976 func(42)
4977 ''' % extracode)
4978 os.close(fd)
4979- os.chmod(script, 0755)
4980+ os.chmod(script, 0o755)
4981
4982 p = subprocess.Popen([script, 'testarg1', 'testarg2'],
4983 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
4984 err = p.communicate()[1]
4985 self.assertEqual(p.returncode, 1,
4986 'crashing test python program exits with failure code')
4987- self.assert_('Exception: This should happen.' in err)
4988- self.failIf('OSError' in err, err)
4989+ self.assertTrue('Exception: This should happen.' in err)
4990+ self.assertFalse('OSError' in err, err)
4991 finally:
4992 os.unlink(script)
4993
4994@@ -179,7 +183,7 @@
4995 try:
4996 self.assertEqual(len(reports), 1, 'crashed Python program produced a report')
4997 self.assertEqual(stat.S_IMODE(os.stat(reports[0]).st_mode),
4998- 0600, 'report has correct permissions')
4999+ 0o600, 'report has correct permissions')
5000
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches