Merge lp:~abompard/mailman/bug-1130696 into lp:mailman
- bug-1130696
- Merge into 3.0
Status: | Merged |
---|---|
Approved by: | Barry Warsaw |
Approved revision: | 7271 |
Merged at revision: | 7275 |
Proposed branch: | lp:~abompard/mailman/bug-1130696 |
Merge into: | lp:mailman |
Diff against target: |
289 lines (+124/-46) 6 files modified
src/mailman/core/tests/test_runner.py (+23/-2) src/mailman/email/message.py (+6/-0) src/mailman/handlers/tests/test_cook_headers.py (+54/-0) src/mailman/runners/digest.py (+2/-2) src/mailman/runners/tests/test_digest.py (+17/-42) src/mailman/testing/helpers.py (+22/-0) |
To merge this branch: | bzr merge lp:~abompard/mailman/bug-1130696 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Barry Warsaw | Approve | ||
Review via email: mp+243226@code.launchpad.net |
Commit message
Description of the change
Handle the MIMEMultipart created by the DigestRunner properly, fixes bug #1130696.
Barry Warsaw (barry) wrote : | # |
Aurélien Bompard (abompard) wrote : | # |
Because the digest runner is creating a MIMEMultipart instance on line 175 of src/mailman/
Barry Warsaw (barry) wrote : | # |
> Because the digest runner is creating a MIMEMultipart instance on line 175 of
> src/mailman/
Good catch. Yes, I think we should change that. What I think I'd like to see is a test that creates a mime digest, and uses that to trigger the attribute errors, rather than creating a MIMEMultipart inside the tests themselves. Does that makes sense?
Then the fix would be to change MIMEDigester.
What do you think?
- 7271. By Aurélien Bompard
-
Make the MIMEDigester generate a subclass of MIMEMultipart with our additions
Fixes bug #1130696 (differently)
Aurélien Bompard (abompard) wrote : | # |
Alright, I just changed that, and I tried generating a very simple subclass of MIMEMultipart and our Message instance. All tests seem to pass, but I'm not very familiar with the tricks of multiple inheritance / mixins.
I'm pushing the branch right now.
Barry Warsaw (barry) wrote : | # |
The general approach works great. I just did some cleanup and added a test (mostly because during my merge, I made a typo that broke digester.rst and required more investigation ;).
As always, thanks!
Preview Diff
1 | === modified file 'src/mailman/core/tests/test_runner.py' | |||
2 | --- src/mailman/core/tests/test_runner.py 2014-01-01 14:59:42 +0000 | |||
3 | +++ src/mailman/core/tests/test_runner.py 2014-12-09 11:27:43 +0000 | |||
4 | @@ -25,6 +25,7 @@ | |||
5 | 25 | ] | 25 | ] |
6 | 26 | 26 | ||
7 | 27 | 27 | ||
8 | 28 | import os | ||
9 | 28 | import unittest | 29 | import unittest |
10 | 29 | 30 | ||
11 | 30 | from mailman.app.lifecycle import create_list | 31 | from mailman.app.lifecycle import create_list |
12 | @@ -32,8 +33,9 @@ | |||
13 | 32 | from mailman.core.runner import Runner | 33 | from mailman.core.runner import Runner |
14 | 33 | from mailman.interfaces.runner import RunnerCrashEvent | 34 | from mailman.interfaces.runner import RunnerCrashEvent |
15 | 34 | from mailman.testing.helpers import ( | 35 | from mailman.testing.helpers import ( |
18 | 35 | configuration, event_subscribers, get_queue_messages, | 36 | configuration, event_subscribers, get_queue_messages, LogFileMark, |
19 | 36 | make_testable_runner, specialized_message_from_string as mfs) | 37 | make_testable_runner, specialized_message_from_string as mfs, |
20 | 38 | make_digest_messages) | ||
21 | 37 | from mailman.testing.layers import ConfigLayer | 39 | from mailman.testing.layers import ConfigLayer |
22 | 38 | 40 | ||
23 | 39 | 41 | ||
24 | @@ -42,6 +44,11 @@ | |||
25 | 42 | def _dispose(self, mlist, msg, msgdata): | 44 | def _dispose(self, mlist, msg, msgdata): |
26 | 43 | raise RuntimeError('borked') | 45 | raise RuntimeError('borked') |
27 | 44 | 46 | ||
28 | 47 | |||
29 | 45 | 48 | ||
30 | 49 | class StoringRunner(Runner): | ||
31 | 50 | _disposed = [] | ||
32 | 51 | def _dispose(self, mlist, msg, msgdata): | ||
33 | 52 | self._disposed.append((mlist, msg, msgdata)) | ||
34 | 46 | 53 | ||
35 | 47 | 54 | ||
36 | 48 | 55 | ||
37 | 49 | class TestRunner(unittest.TestCase): | 56 | class TestRunner(unittest.TestCase): |
38 | @@ -87,3 +94,17 @@ | |||
39 | 87 | shunted = get_queue_messages('shunt') | 94 | shunted = get_queue_messages('shunt') |
40 | 88 | self.assertEqual(len(shunted), 1) | 95 | self.assertEqual(len(shunted), 1) |
41 | 89 | self.assertEqual(shunted[0].msg['message-id'], '<ant>') | 96 | self.assertEqual(shunted[0].msg['message-id'], '<ant>') |
42 | 97 | |||
43 | 98 | def test_digest_messages(self): | ||
44 | 99 | # Digest messages can be MIMEMultipart (LP#1130696) | ||
45 | 100 | runner = make_testable_runner(StoringRunner, 'in') | ||
46 | 101 | make_digest_messages(self._mlist) | ||
47 | 102 | for bag in get_queue_messages("virgin"): | ||
48 | 103 | config.switchboards['in'].enqueue(bag.msg, listname='test@example.com') | ||
49 | 104 | error_log = LogFileMark('mailman.error') | ||
50 | 105 | with event_subscribers(self._got_event): | ||
51 | 106 | runner.run() | ||
52 | 107 | errors = error_log.read() | ||
53 | 108 | self.assertEqual(len(self._events), 0, errors) | ||
54 | 109 | self.assertEqual(len(get_queue_messages("shunt")), 0, errors) | ||
55 | 110 | self.assertEqual(len(runner._disposed), 2) | ||
56 | 90 | 111 | ||
57 | === modified file 'src/mailman/email/message.py' | |||
58 | --- src/mailman/email/message.py 2014-12-09 01:38:26 +0000 | |||
59 | +++ src/mailman/email/message.py 2014-12-09 11:27:43 +0000 | |||
60 | @@ -36,6 +36,7 @@ | |||
61 | 36 | import email | 36 | import email |
62 | 37 | import email.message | 37 | import email.message |
63 | 38 | import email.utils | 38 | import email.utils |
64 | 39 | import email.mime.multipart | ||
65 | 39 | 40 | ||
66 | 40 | from email.header import Header | 41 | from email.header import Header |
67 | 41 | 42 | ||
68 | @@ -133,6 +134,11 @@ | |||
69 | 133 | 134 | ||
70 | 134 | 135 | ||
71 | 135 | 136 | ||
72 | 136 | 137 | ||
73 | 138 | class MultipartMessage(email.mime.multipart.MIMEMultipart, Message): | ||
74 | 139 | """Class for MIME digest messages""" | ||
75 | 140 | |||
76 | 141 | |||
77 | 142 | |||
78 | 137 | 143 | ||
79 | 138 | class UserNotification(Message): | 144 | class UserNotification(Message): |
80 | 139 | """Class for internally crafted messages.""" | 145 | """Class for internally crafted messages.""" |
81 | 140 | 146 | ||
82 | 141 | 147 | ||
83 | === added file 'src/mailman/handlers/tests/test_cook_headers.py' | |||
84 | --- src/mailman/handlers/tests/test_cook_headers.py 1970-01-01 00:00:00 +0000 | |||
85 | +++ src/mailman/handlers/tests/test_cook_headers.py 2014-12-09 11:27:43 +0000 | |||
86 | @@ -0,0 +1,54 @@ | |||
87 | 1 | # Copyright (C) 2012-2014 by the Free Software Foundation, Inc. | ||
88 | 2 | # | ||
89 | 3 | # This file is part of GNU Mailman. | ||
90 | 4 | # | ||
91 | 5 | # GNU Mailman is free software: you can redistribute it and/or modify it under | ||
92 | 6 | # the terms of the GNU General Public License as published by the Free | ||
93 | 7 | # Software Foundation, either version 3 of the License, or (at your option) | ||
94 | 8 | # any later version. | ||
95 | 9 | # | ||
96 | 10 | # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT | ||
97 | 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
98 | 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
99 | 13 | # more details. | ||
100 | 14 | # | ||
101 | 15 | # You should have received a copy of the GNU General Public License along with | ||
102 | 16 | # GNU Mailman. If not, see <http://www.gnu.org/licenses/>. | ||
103 | 17 | |||
104 | 18 | """Test the cook_headers handler.""" | ||
105 | 19 | |||
106 | 20 | from __future__ import absolute_import, print_function, unicode_literals | ||
107 | 21 | |||
108 | 22 | __metaclass__ = type | ||
109 | 23 | __all__ = [ | ||
110 | 24 | 'TestCookHeaders', | ||
111 | 25 | ] | ||
112 | 26 | |||
113 | 27 | |||
114 | 28 | import unittest | ||
115 | 29 | |||
116 | 30 | from mailman.app.lifecycle import create_list | ||
117 | 31 | from mailman.handlers import cook_headers | ||
118 | 32 | from mailman.testing.helpers import get_queue_messages, make_digest_messages | ||
119 | 33 | from mailman.testing.layers import ConfigLayer | ||
120 | 34 | |||
121 | 35 | |||
122 | 36 | |||
123 | 0 | 37 | ||
124 | 38 | class TestCookHeaders(unittest.TestCase): | ||
125 | 39 | """Test the cook_headers handler.""" | ||
126 | 40 | |||
127 | 41 | layer = ConfigLayer | ||
128 | 42 | |||
129 | 43 | def setUp(self): | ||
130 | 44 | self._mlist = create_list('test@example.com') | ||
131 | 45 | |||
132 | 46 | def test_process_digest(self): | ||
133 | 47 | # Digest messages can be MIMEMultipart (LP#1130696) | ||
134 | 48 | make_digest_messages(self._mlist) | ||
135 | 49 | messages = [ bag.msg for bag in get_queue_messages("virgin") ] | ||
136 | 50 | self.assertEqual(len(messages), 2) | ||
137 | 51 | for msg in messages: | ||
138 | 52 | try: | ||
139 | 53 | cook_headers.process(self._mlist, msg, {}) | ||
140 | 54 | except AttributeError as e: | ||
141 | 55 | self.fail(e) | ||
142 | 1 | 56 | ||
143 | === modified file 'src/mailman/runners/digest.py' | |||
144 | --- src/mailman/runners/digest.py 2014-11-29 20:10:59 +0000 | |||
145 | +++ src/mailman/runners/digest.py 2014-12-09 11:27:43 +0000 | |||
146 | @@ -32,7 +32,6 @@ | |||
147 | 32 | from StringIO import StringIO | 32 | from StringIO import StringIO |
148 | 33 | from copy import deepcopy | 33 | from copy import deepcopy |
149 | 34 | from email.header import Header | 34 | from email.header import Header |
150 | 35 | from email.message import Message | ||
151 | 36 | from email.mime.message import MIMEMessage | 35 | from email.mime.message import MIMEMessage |
152 | 37 | from email.mime.multipart import MIMEMultipart | 36 | from email.mime.multipart import MIMEMultipart |
153 | 38 | from email.mime.text import MIMEText | 37 | from email.mime.text import MIMEText |
154 | @@ -42,6 +41,7 @@ | |||
155 | 42 | from mailman.config import config | 41 | from mailman.config import config |
156 | 43 | from mailman.core.i18n import _ | 42 | from mailman.core.i18n import _ |
157 | 44 | from mailman.core.runner import Runner | 43 | from mailman.core.runner import Runner |
158 | 44 | from mailman.email.message import Message, MultipartMessage | ||
159 | 45 | from mailman.handlers.decorate import decorate | 45 | from mailman.handlers.decorate import decorate |
160 | 46 | from mailman.interfaces.member import DeliveryMode, DeliveryStatus | 46 | from mailman.interfaces.member import DeliveryMode, DeliveryStatus |
161 | 47 | from mailman.utilities.i18n import make | 47 | from mailman.utilities.i18n import make |
162 | @@ -172,7 +172,7 @@ | |||
163 | 172 | self._keepers = set(config.digests.mime_digest_keep_headers.split()) | 172 | self._keepers = set(config.digests.mime_digest_keep_headers.split()) |
164 | 173 | 173 | ||
165 | 174 | def _make_message(self): | 174 | def _make_message(self): |
167 | 175 | return MIMEMultipart('mixed') | 175 | return MultipartMessage('mixed') |
168 | 176 | 176 | ||
169 | 177 | def add_toc(self, count): | 177 | def add_toc(self, count): |
170 | 178 | """Add the table of contents.""" | 178 | """Add the table of contents.""" |
171 | 179 | 179 | ||
172 | === modified file 'src/mailman/runners/tests/test_digest.py' | |||
173 | --- src/mailman/runners/tests/test_digest.py 2014-11-29 20:10:59 +0000 | |||
174 | +++ src/mailman/runners/tests/test_digest.py 2014-12-09 11:27:43 +0000 | |||
175 | @@ -35,7 +35,7 @@ | |||
176 | 35 | from mailman.runners.digest import DigestRunner | 35 | from mailman.runners.digest import DigestRunner |
177 | 36 | from mailman.testing.helpers import ( | 36 | from mailman.testing.helpers import ( |
178 | 37 | LogFileMark, digest_mbox, get_queue_messages, make_testable_runner, | 37 | LogFileMark, digest_mbox, get_queue_messages, make_testable_runner, |
180 | 38 | specialized_message_from_string as mfs) | 38 | specialized_message_from_string as mfs, make_digest_messages) |
181 | 39 | from mailman.testing.layers import ConfigLayer | 39 | from mailman.testing.layers import ConfigLayer |
182 | 40 | 40 | ||
183 | 41 | 41 | ||
184 | @@ -54,31 +54,21 @@ | |||
185 | 54 | self._runner = make_testable_runner(DigestRunner, 'digest') | 54 | self._runner = make_testable_runner(DigestRunner, 'digest') |
186 | 55 | self._process = config.handlers['to-digest'].process | 55 | self._process = config.handlers['to-digest'].process |
187 | 56 | 56 | ||
188 | 57 | def _check_virgin_queue(self): | ||
189 | 58 | # There should be two messages in the virgin queue: the digest as | ||
190 | 59 | # plain-text and as multipart. | ||
191 | 60 | messages = get_queue_messages('virgin') | ||
192 | 61 | self.assertEqual(len(messages), 2) | ||
193 | 62 | self.assertEqual( | ||
194 | 63 | sorted(item.msg.get_content_type() for item in messages), | ||
195 | 64 | ['multipart/mixed', 'text/plain']) | ||
196 | 65 | for item in messages: | ||
197 | 66 | self.assertEqual(item.msg['subject'], | ||
198 | 67 | 'Test Digest, Vol 1, Issue 1') | ||
199 | 68 | |||
200 | 57 | def test_simple_message(self): | 69 | def test_simple_message(self): |
225 | 58 | msg = mfs("""\ | 70 | make_digest_messages(self._mlist) |
226 | 59 | From: anne@example.org | 71 | self._check_virgin_queue() |
203 | 60 | To: test@example.com | ||
204 | 61 | |||
205 | 62 | message triggering a digest | ||
206 | 63 | """) | ||
207 | 64 | mbox_path = os.path.join(self._mlist.data_path, 'digest.mmdf') | ||
208 | 65 | self._process(self._mlist, msg, {}) | ||
209 | 66 | self._digestq.enqueue( | ||
210 | 67 | msg, | ||
211 | 68 | listname=self._mlist.fqdn_listname, | ||
212 | 69 | digest_path=mbox_path, | ||
213 | 70 | volume=1, digest_number=1) | ||
214 | 71 | self._runner.run() | ||
215 | 72 | # There are two messages in the virgin queue: the digest as plain-text | ||
216 | 73 | # and as multipart. | ||
217 | 74 | messages = get_queue_messages('virgin') | ||
218 | 75 | self.assertEqual(len(messages), 2) | ||
219 | 76 | self.assertEqual( | ||
220 | 77 | sorted(item.msg.get_content_type() for item in messages), | ||
221 | 78 | ['multipart/mixed', 'text/plain']) | ||
222 | 79 | for item in messages: | ||
223 | 80 | self.assertEqual(item.msg['subject'], | ||
224 | 81 | 'Test Digest, Vol 1, Issue 1') | ||
227 | 82 | 72 | ||
228 | 83 | def test_non_ascii_message(self): | 73 | def test_non_ascii_message(self): |
229 | 84 | msg = Message() | 74 | msg = Message() |
230 | @@ -88,25 +78,10 @@ | |||
231 | 88 | msg.attach(MIMEText('message with non-ascii chars: \xc3\xa9', | 78 | msg.attach(MIMEText('message with non-ascii chars: \xc3\xa9', |
232 | 89 | 'plain', 'utf-8')) | 79 | 'plain', 'utf-8')) |
233 | 90 | mbox = digest_mbox(self._mlist) | 80 | mbox = digest_mbox(self._mlist) |
234 | 91 | mbox_path = os.path.join(self._mlist.data_path, 'digest.mmdf') | ||
235 | 92 | mbox.add(msg.as_string()) | 81 | mbox.add(msg.as_string()) |
236 | 93 | self._digestq.enqueue( | ||
237 | 94 | msg, | ||
238 | 95 | listname=self._mlist.fqdn_listname, | ||
239 | 96 | digest_path=mbox_path, | ||
240 | 97 | volume=1, digest_number=1) | ||
241 | 98 | # Use any error logs as the error message if the test fails. | 82 | # Use any error logs as the error message if the test fails. |
242 | 99 | error_log = LogFileMark('mailman.error') | 83 | error_log = LogFileMark('mailman.error') |
244 | 100 | self._runner.run() | 84 | make_digest_messages(self._mlist, msg) |
245 | 101 | # The runner will send the file to the shunt queue on exception. | 85 | # The runner will send the file to the shunt queue on exception. |
246 | 102 | self.assertEqual(len(self._shuntq.files), 0, error_log.read()) | 86 | self.assertEqual(len(self._shuntq.files), 0, error_log.read()) |
257 | 103 | # There are two messages in the virgin queue: the digest as plain-text | 87 | self._check_virgin_queue() |
248 | 104 | # and as multipart. | ||
249 | 105 | messages = get_queue_messages('virgin') | ||
250 | 106 | self.assertEqual(len(messages), 2) | ||
251 | 107 | self.assertEqual( | ||
252 | 108 | sorted(item.msg.get_content_type() for item in messages), | ||
253 | 109 | ['multipart/mixed', 'text/plain']) | ||
254 | 110 | for item in messages: | ||
255 | 111 | self.assertEqual(item.msg['subject'], | ||
256 | 112 | 'Test Digest, Vol 1, Issue 1') | ||
258 | 113 | 88 | ||
259 | === modified file 'src/mailman/testing/helpers.py' | |||
260 | --- src/mailman/testing/helpers.py 2014-11-29 20:10:59 +0000 | |||
261 | +++ src/mailman/testing/helpers.py 2014-12-09 11:27:43 +0000 | |||
262 | @@ -72,6 +72,7 @@ | |||
263 | 72 | from mailman.interfaces.messages import IMessageStore | 72 | from mailman.interfaces.messages import IMessageStore |
264 | 73 | from mailman.interfaces.styles import IStyleManager | 73 | from mailman.interfaces.styles import IStyleManager |
265 | 74 | from mailman.interfaces.usermanager import IUserManager | 74 | from mailman.interfaces.usermanager import IUserManager |
266 | 75 | from mailman.runners.digest import DigestRunner | ||
267 | 75 | from mailman.utilities.mailbox import Mailbox | 76 | from mailman.utilities.mailbox import Mailbox |
268 | 76 | 77 | ||
269 | 77 | 78 | ||
270 | @@ -529,3 +530,24 @@ | |||
271 | 529 | with open(self._filename) as fp: | 530 | with open(self._filename) as fp: |
272 | 530 | fp.seek(self._filepos) | 531 | fp.seek(self._filepos) |
273 | 531 | return fp.read() | 532 | return fp.read() |
274 | 533 | |||
275 | 534 | |||
276 | 535 | |||
277 | 532 | 536 | ||
278 | 537 | def make_digest_messages(mlist, msg=None): | ||
279 | 538 | if msg is None: | ||
280 | 539 | msg = specialized_message_from_string("""\ | ||
281 | 540 | From: anne@example.org | ||
282 | 541 | To: {listname} | ||
283 | 542 | Message-ID: <testing> | ||
284 | 543 | |||
285 | 544 | message triggering a digest | ||
286 | 545 | """.format(listname=mlist.fqdn_listname)) | ||
287 | 546 | mbox_path = os.path.join(mlist.data_path, 'digest.mmdf') | ||
288 | 547 | config.handlers['to-digest'].process(mlist, msg, {}) | ||
289 | 548 | config.switchboards['digest'].enqueue( | ||
290 | 549 | msg, | ||
291 | 550 | listname=mlist.fqdn_listname, | ||
292 | 551 | digest_path=mbox_path, | ||
293 | 552 | volume=1, digest_number=1) | ||
294 | 553 | runner = make_testable_runner(DigestRunner, 'digest') | ||
295 | 554 | runner.run() |
This doesn't quite feel like it's getting to the root cause. Why don't we get a mailman. email.message. Message object here?