1 # Copyright (C) 2012-2015 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
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)
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
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/>.
18 """Test the prototype archiver."""
21 'TestPrototypeArchiver',
31 from email
import message_from_file
32 from flufl
.lock
import Lock
33 from mailman
.app
.lifecycle
import create_list
34 from mailman
.archiving
.prototype
import Prototype
35 from mailman
.config
import config
36 from mailman
.database
.transaction
import transaction
37 from mailman
.testing
.helpers
import LogFileMark
38 from mailman
.testing
.helpers
import (
39 specialized_message_from_string
as mfs
)
40 from mailman
.testing
.layers
import ConfigLayer
41 from mailman
.utilities
.email
import add_message_hash
44 class TestPrototypeArchiver(unittest
.TestCase
):
45 """Test the prototype archiver."""
50 # Create a fake mailing list and message object.
53 From: anne@example.com
54 Subject: Testing the test list
56 X-Message-ID-Hash: MS6QLWERIJLGCRF44J7USBFDELMNT2BW
58 Tests are better than no tests
59 but the water deserves to be swum.
62 self
._mlist
= create_list('test@example.com')
63 # Set up a temporary directory for the prototype archiver so that it's
65 self
._tempdir
= tempfile
.mkdtemp()
66 config
.push('prototype', """
69 """.format(self
._tempdir
))
70 # Capture the structure of a maildir.
71 self
._expected
_dir
_structure
= set(
72 (os
.path
.join(config
.ARCHIVE_DIR
, path
) for path
in (
74 os
.path
.join('prototype', self
._mlist
.fqdn_listname
),
75 os
.path
.join('prototype', self
._mlist
.fqdn_listname
, 'cur'),
76 os
.path
.join('prototype', self
._mlist
.fqdn_listname
, 'new'),
77 os
.path
.join('prototype', self
._mlist
.fqdn_listname
, 'tmp'),
79 self
._expected
_dir
_structure
.add(config
.ARCHIVE_DIR
)
82 shutil
.rmtree(self
._tempdir
)
83 config
.pop('prototype')
85 def _find(self
, path
):
87 for dirpath
, dirnames
, filenames
in os
.walk(path
):
88 if isinstance(dirpath
, bytes
):
89 dirpath
= dirpath
.decode('utf-8')
90 all_filenames
.add(dirpath
)
91 for filename
in filenames
:
92 new_filename
= filename
93 if isinstance(filename
, bytes
):
94 new_filename
= filename
.decode('utf-8')
95 all_filenames
.add(os
.path
.join(dirpath
, new_filename
))
98 def test_archive_maildir_created(self
):
99 # Archiving a message to the prototype archiver should create the
100 # expected directory structure.
101 Prototype
.archive_message(self
._mlist
, self
._msg
)
102 all_filenames
= self
._find
(config
.ARCHIVE_DIR
)
103 # Check that the directory structure has been created and we have one
104 # more file (the archived message) than expected directories.
105 archived_messages
= all_filenames
- self
._expected
_dir
_structure
106 self
.assertEqual(len(archived_messages
), 1)
108 archived_messages
.pop().startswith(
109 os
.path
.join(config
.ARCHIVE_DIR
, 'prototype',
110 self
._mlist
.fqdn_listname
, 'new')))
112 def test_archive_maildir_existence_does_not_raise(self
):
113 # Archiving a second message does not cause an EEXIST to be raised
114 # when a second message is archived.
116 Prototype
.archive_message(self
._mlist
, self
._msg
)
117 for directory
in ('cur', 'new', 'tmp'):
118 path
= os
.path
.join(config
.ARCHIVE_DIR
, 'prototype',
119 self
._mlist
.fqdn_listname
, directory
)
120 if directory
== 'new':
122 self
.assertTrue(os
.path
.isdir(path
))
123 # There should be one message in the 'new' directory.
124 self
.assertEqual(len(os
.listdir(new_dir
)), 1)
125 # Archive a second message. If an exception occurs, let it fail the
126 # test. Afterward, two messages should be in the 'new' directory.
127 del self
._msg
['message-id']
128 del self
._msg
['x-message-id-hash']
129 self
._msg
['Message-ID'] = '<bee>'
130 add_message_hash(self
._msg
)
131 Prototype
.archive_message(self
._mlist
, self
._msg
)
132 self
.assertEqual(len(os
.listdir(new_dir
)), 2)
134 def test_archive_lock_used(self
):
135 # Test that locking the maildir when adding works as a failure here
136 # could mean we lose mail.
137 lock_file
= os
.path
.join(
138 config
.LOCK_DIR
, '{0}-maildir.lock'.format(
139 self
._mlist
.fqdn_listname
))
140 with
Lock(lock_file
):
141 # Acquire the archiver lock, then make sure the archiver logs the
142 # fact that it could not acquire the lock.
143 archive_thread
= threading
.Thread(
144 target
=Prototype
.archive_message
,
145 args
=(self
._mlist
, self
._msg
))
146 mark
= LogFileMark('mailman.error')
148 # Test that the archiver output the correct error.
149 line
= mark
.readline()
150 # XXX 2012-03-15 BAW: we really should remove timestamp prefixes
151 # from the loggers when under test.
152 self
.assertTrue(line
.endswith(
153 'Unable to acquire prototype archiver lock for {0}, '
154 'discarding: {1}\n'.format(
155 self
._mlist
.fqdn_listname
,
156 self
._msg
.get('message-id'))))
157 # Check that the message didn't get archived.
158 created_files
= self
._find
(config
.ARCHIVE_DIR
)
159 self
.assertEqual(self
._expected
_dir
_structure
, created_files
)
161 def test_prototype_archiver_good_path(self
):
162 # Verify the good path; the message gets archived.
163 Prototype
.archive_message(self
._mlist
, self
._msg
)
164 new_path
= os
.path
.join(
165 config
.ARCHIVE_DIR
, 'prototype', self
._mlist
.fqdn_listname
, 'new')
166 archived_messages
= list(os
.listdir(new_path
))
167 self
.assertEqual(len(archived_messages
), 1)
168 # Check that the email has been added.
169 with
open(os
.path
.join(new_path
, archived_messages
[0])) as fp
:
170 archived_message
= message_from_file(fp
)
171 self
.assertEqual(self
._msg
.as_string(), archived_message
.as_string())