Merge lp:~abompard/mailman/bug-1130696 into lp:mailman

Proposed by Aurélien Bompard
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
Reviewer Review Type Date Requested Status
Barry Warsaw Approve
Review via email: mp+243226@code.launchpad.net

Description of the change

Handle the MIMEMultipart created by the DigestRunner properly, fixes bug #1130696.

To post a comment you must log in.
Revision history for this message
Barry Warsaw (barry) wrote :

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?

Revision history for this message
Aurélien Bompard (abompard) wrote :

Because the digest runner is creating a MIMEMultipart instance on line 175 of src/mailman/runners/digest.py. Should we change that instead?

Revision history for this message
Barry Warsaw (barry) wrote :

> Because the digest runner is creating a MIMEMultipart instance on line 175 of
> src/mailman/runners/digest.py. Should we change that instead?

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._make_message() to create the proper subclass. The question then is what that method should return. My first thought is a class that inherits both from MIMEMultipart and mailman.email.message.Message.

What do you think?

lp:~abompard/mailman/bug-1130696 updated
7271. By Aurélien Bompard

Make the MIMEDigester generate a subclass of MIMEMultipart with our additions

Fixes bug #1130696 (differently)

Revision history for this message
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.

Revision history for this message
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!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/mailman/core/tests/test_runner.py'
--- src/mailman/core/tests/test_runner.py 2014-01-01 14:59:42 +0000
+++ src/mailman/core/tests/test_runner.py 2014-12-09 11:27:43 +0000
@@ -25,6 +25,7 @@
25 ]25 ]
2626
2727
28import os
28import unittest29import unittest
2930
30from mailman.app.lifecycle import create_list31from mailman.app.lifecycle import create_list
@@ -32,8 +33,9 @@
32from mailman.core.runner import Runner33from mailman.core.runner import Runner
33from mailman.interfaces.runner import RunnerCrashEvent34from mailman.interfaces.runner import RunnerCrashEvent
34from mailman.testing.helpers import (35from mailman.testing.helpers import (
35 configuration, event_subscribers, get_queue_messages,36 configuration, event_subscribers, get_queue_messages, LogFileMark,
36 make_testable_runner, specialized_message_from_string as mfs)37 make_testable_runner, specialized_message_from_string as mfs,
38 make_digest_messages)
37from mailman.testing.layers import ConfigLayer39from mailman.testing.layers import ConfigLayer
3840
3941
@@ -42,6 +44,11 @@
42 def _dispose(self, mlist, msg, msgdata):44 def _dispose(self, mlist, msg, msgdata):
43 raise RuntimeError('borked')45 raise RuntimeError('borked')
4446
47
4548
49class StoringRunner(Runner):
50 _disposed = []
51 def _dispose(self, mlist, msg, msgdata):
52 self._disposed.append((mlist, msg, msgdata))
4653
4754
4855
49class TestRunner(unittest.TestCase):56class TestRunner(unittest.TestCase):
@@ -87,3 +94,17 @@
87 shunted = get_queue_messages('shunt')94 shunted = get_queue_messages('shunt')
88 self.assertEqual(len(shunted), 1)95 self.assertEqual(len(shunted), 1)
89 self.assertEqual(shunted[0].msg['message-id'], '<ant>')96 self.assertEqual(shunted[0].msg['message-id'], '<ant>')
97
98 def test_digest_messages(self):
99 # Digest messages can be MIMEMultipart (LP#1130696)
100 runner = make_testable_runner(StoringRunner, 'in')
101 make_digest_messages(self._mlist)
102 for bag in get_queue_messages("virgin"):
103 config.switchboards['in'].enqueue(bag.msg, listname='test@example.com')
104 error_log = LogFileMark('mailman.error')
105 with event_subscribers(self._got_event):
106 runner.run()
107 errors = error_log.read()
108 self.assertEqual(len(self._events), 0, errors)
109 self.assertEqual(len(get_queue_messages("shunt")), 0, errors)
110 self.assertEqual(len(runner._disposed), 2)
90111
=== modified file 'src/mailman/email/message.py'
--- src/mailman/email/message.py 2014-12-09 01:38:26 +0000
+++ src/mailman/email/message.py 2014-12-09 11:27:43 +0000
@@ -36,6 +36,7 @@
36import email36import email
37import email.message37import email.message
38import email.utils38import email.utils
39import email.mime.multipart
3940
40from email.header import Header41from email.header import Header
4142
@@ -133,6 +134,11 @@
133134
134135
135136
136137
138class MultipartMessage(email.mime.multipart.MIMEMultipart, Message):
139 """Class for MIME digest messages"""
140
141
142
137143
138class UserNotification(Message):144class UserNotification(Message):
139 """Class for internally crafted messages."""145 """Class for internally crafted messages."""
140146
141147
=== added file 'src/mailman/handlers/tests/test_cook_headers.py'
--- src/mailman/handlers/tests/test_cook_headers.py 1970-01-01 00:00:00 +0000
+++ src/mailman/handlers/tests/test_cook_headers.py 2014-12-09 11:27:43 +0000
@@ -0,0 +1,54 @@
1# Copyright (C) 2012-2014 by the Free Software Foundation, Inc.
2#
3# This file is part of GNU Mailman.
4#
5# GNU Mailman is free software: you can redistribute it and/or modify it under
6# the terms of the GNU General Public License as published by the Free
7# Software Foundation, either version 3 of the License, or (at your option)
8# any later version.
9#
10# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13# more details.
14#
15# You should have received a copy of the GNU General Public License along with
16# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
17
18"""Test the cook_headers handler."""
19
20from __future__ import absolute_import, print_function, unicode_literals
21
22__metaclass__ = type
23__all__ = [
24 'TestCookHeaders',
25 ]
26
27
28import unittest
29
30from mailman.app.lifecycle import create_list
31from mailman.handlers import cook_headers
32from mailman.testing.helpers import get_queue_messages, make_digest_messages
33from mailman.testing.layers import ConfigLayer
34
35
36
037
38class TestCookHeaders(unittest.TestCase):
39 """Test the cook_headers handler."""
40
41 layer = ConfigLayer
42
43 def setUp(self):
44 self._mlist = create_list('test@example.com')
45
46 def test_process_digest(self):
47 # Digest messages can be MIMEMultipart (LP#1130696)
48 make_digest_messages(self._mlist)
49 messages = [ bag.msg for bag in get_queue_messages("virgin") ]
50 self.assertEqual(len(messages), 2)
51 for msg in messages:
52 try:
53 cook_headers.process(self._mlist, msg, {})
54 except AttributeError as e:
55 self.fail(e)
156
=== modified file 'src/mailman/runners/digest.py'
--- src/mailman/runners/digest.py 2014-11-29 20:10:59 +0000
+++ src/mailman/runners/digest.py 2014-12-09 11:27:43 +0000
@@ -32,7 +32,6 @@
32from StringIO import StringIO32from StringIO import StringIO
33from copy import deepcopy33from copy import deepcopy
34from email.header import Header34from email.header import Header
35from email.message import Message
36from email.mime.message import MIMEMessage35from email.mime.message import MIMEMessage
37from email.mime.multipart import MIMEMultipart36from email.mime.multipart import MIMEMultipart
38from email.mime.text import MIMEText37from email.mime.text import MIMEText
@@ -42,6 +41,7 @@
42from mailman.config import config41from mailman.config import config
43from mailman.core.i18n import _42from mailman.core.i18n import _
44from mailman.core.runner import Runner43from mailman.core.runner import Runner
44from mailman.email.message import Message, MultipartMessage
45from mailman.handlers.decorate import decorate45from mailman.handlers.decorate import decorate
46from mailman.interfaces.member import DeliveryMode, DeliveryStatus46from mailman.interfaces.member import DeliveryMode, DeliveryStatus
47from mailman.utilities.i18n import make47from mailman.utilities.i18n import make
@@ -172,7 +172,7 @@
172 self._keepers = set(config.digests.mime_digest_keep_headers.split())172 self._keepers = set(config.digests.mime_digest_keep_headers.split())
173173
174 def _make_message(self):174 def _make_message(self):
175 return MIMEMultipart('mixed')175 return MultipartMessage('mixed')
176176
177 def add_toc(self, count):177 def add_toc(self, count):
178 """Add the table of contents."""178 """Add the table of contents."""
179179
=== modified file 'src/mailman/runners/tests/test_digest.py'
--- src/mailman/runners/tests/test_digest.py 2014-11-29 20:10:59 +0000
+++ src/mailman/runners/tests/test_digest.py 2014-12-09 11:27:43 +0000
@@ -35,7 +35,7 @@
35from mailman.runners.digest import DigestRunner35from mailman.runners.digest import DigestRunner
36from mailman.testing.helpers import (36from mailman.testing.helpers import (
37 LogFileMark, digest_mbox, get_queue_messages, make_testable_runner,37 LogFileMark, digest_mbox, get_queue_messages, make_testable_runner,
38 specialized_message_from_string as mfs)38 specialized_message_from_string as mfs, make_digest_messages)
39from mailman.testing.layers import ConfigLayer39from mailman.testing.layers import ConfigLayer
4040
4141
@@ -54,31 +54,21 @@
54 self._runner = make_testable_runner(DigestRunner, 'digest')54 self._runner = make_testable_runner(DigestRunner, 'digest')
55 self._process = config.handlers['to-digest'].process55 self._process = config.handlers['to-digest'].process
5656
57 def _check_virgin_queue(self):
58 # There should be two messages in the virgin queue: the digest as
59 # plain-text and as multipart.
60 messages = get_queue_messages('virgin')
61 self.assertEqual(len(messages), 2)
62 self.assertEqual(
63 sorted(item.msg.get_content_type() for item in messages),
64 ['multipart/mixed', 'text/plain'])
65 for item in messages:
66 self.assertEqual(item.msg['subject'],
67 'Test Digest, Vol 1, Issue 1')
68
57 def test_simple_message(self):69 def test_simple_message(self):
58 msg = mfs("""\70 make_digest_messages(self._mlist)
59From: anne@example.org71 self._check_virgin_queue()
60To: test@example.com
61
62message triggering a digest
63""")
64 mbox_path = os.path.join(self._mlist.data_path, 'digest.mmdf')
65 self._process(self._mlist, msg, {})
66 self._digestq.enqueue(
67 msg,
68 listname=self._mlist.fqdn_listname,
69 digest_path=mbox_path,
70 volume=1, digest_number=1)
71 self._runner.run()
72 # There are two messages in the virgin queue: the digest as plain-text
73 # and as multipart.
74 messages = get_queue_messages('virgin')
75 self.assertEqual(len(messages), 2)
76 self.assertEqual(
77 sorted(item.msg.get_content_type() for item in messages),
78 ['multipart/mixed', 'text/plain'])
79 for item in messages:
80 self.assertEqual(item.msg['subject'],
81 'Test Digest, Vol 1, Issue 1')
8272
83 def test_non_ascii_message(self):73 def test_non_ascii_message(self):
84 msg = Message()74 msg = Message()
@@ -88,25 +78,10 @@
88 msg.attach(MIMEText('message with non-ascii chars: \xc3\xa9',78 msg.attach(MIMEText('message with non-ascii chars: \xc3\xa9',
89 'plain', 'utf-8'))79 'plain', 'utf-8'))
90 mbox = digest_mbox(self._mlist)80 mbox = digest_mbox(self._mlist)
91 mbox_path = os.path.join(self._mlist.data_path, 'digest.mmdf')
92 mbox.add(msg.as_string())81 mbox.add(msg.as_string())
93 self._digestq.enqueue(
94 msg,
95 listname=self._mlist.fqdn_listname,
96 digest_path=mbox_path,
97 volume=1, digest_number=1)
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.
99 error_log = LogFileMark('mailman.error')83 error_log = LogFileMark('mailman.error')
100 self._runner.run()84 make_digest_messages(self._mlist, msg)
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.
102 self.assertEqual(len(self._shuntq.files), 0, error_log.read())86 self.assertEqual(len(self._shuntq.files), 0, error_log.read())
103 # There are two messages in the virgin queue: the digest as plain-text87 self._check_virgin_queue()
104 # and as multipart.
105 messages = get_queue_messages('virgin')
106 self.assertEqual(len(messages), 2)
107 self.assertEqual(
108 sorted(item.msg.get_content_type() for item in messages),
109 ['multipart/mixed', 'text/plain'])
110 for item in messages:
111 self.assertEqual(item.msg['subject'],
112 'Test Digest, Vol 1, Issue 1')
11388
=== modified file 'src/mailman/testing/helpers.py'
--- src/mailman/testing/helpers.py 2014-11-29 20:10:59 +0000
+++ src/mailman/testing/helpers.py 2014-12-09 11:27:43 +0000
@@ -72,6 +72,7 @@
72from mailman.interfaces.messages import IMessageStore72from mailman.interfaces.messages import IMessageStore
73from mailman.interfaces.styles import IStyleManager73from mailman.interfaces.styles import IStyleManager
74from mailman.interfaces.usermanager import IUserManager74from mailman.interfaces.usermanager import IUserManager
75from mailman.runners.digest import DigestRunner
75from mailman.utilities.mailbox import Mailbox76from mailman.utilities.mailbox import Mailbox
7677
7778
@@ -529,3 +530,24 @@
529 with open(self._filename) as fp:530 with open(self._filename) as fp:
530 fp.seek(self._filepos)531 fp.seek(self._filepos)
531 return fp.read()532 return fp.read()
533
534
535
532536
537def make_digest_messages(mlist, msg=None):
538 if msg is None:
539 msg = specialized_message_from_string("""\
540From: anne@example.org
541To: {listname}
542Message-ID: <testing>
543
544message triggering a digest
545""".format(listname=mlist.fqdn_listname))
546 mbox_path = os.path.join(mlist.data_path, 'digest.mmdf')
547 config.handlers['to-digest'].process(mlist, msg, {})
548 config.switchboards['digest'].enqueue(
549 msg,
550 listname=mlist.fqdn_listname,
551 digest_path=mbox_path,
552 volume=1, digest_number=1)
553 runner = make_testable_runner(DigestRunner, 'digest')
554 runner.run()