Merge lp:~brian-murray/ubuntu/natty/apport/linux-staging into lp:~package-import/ubuntu/natty/apport/defunct
- Natty (11.04)
- linux-staging
- Merge into defunct
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 |
Related bugs: |
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.
Commit message
Description of the change
There are a lot of kernel bug reports with only [STAGING] as the title. This should fix that. Here is a search showing them:
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?
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
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 | |
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 |
Looks good to me, thanks.