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