|
1 """ |
|
2 Tests for the mhlib module |
|
3 Nick Mathewson |
|
4 """ |
|
5 |
|
6 ### BUG: This suite doesn't currently test the mime functionality of |
|
7 ### mhlib. It should. |
|
8 |
|
9 import unittest |
|
10 from test.test_support import run_unittest, TESTFN, TestSkipped |
|
11 import os, StringIO |
|
12 import sys |
|
13 import mhlib |
|
14 |
|
15 if (sys.platform.startswith("win") or sys.platform=="riscos" or |
|
16 sys.platform.startswith("atheos")): |
|
17 # mhlib.updateline() renames a file to the name of a file that already |
|
18 # exists. That causes a reasonable OS <wink> to complain in test_sequence |
|
19 # here, like the "OSError: [Errno 17] File exists" raised on Windows. |
|
20 # mhlib's listsubfolders() and listallfolders() do something with |
|
21 # link counts, and that causes test_listfolders() here to get back |
|
22 # an empty list from its call of listallfolders(). |
|
23 # The other tests here pass on Windows. |
|
24 raise TestSkipped("skipped on %s -- " % sys.platform + |
|
25 "too many Unix assumptions") |
|
26 |
|
27 _mhroot = TESTFN+"_MH" |
|
28 _mhpath = os.path.join(_mhroot, "MH") |
|
29 _mhprofile = os.path.join(_mhroot, ".mh_profile") |
|
30 |
|
31 def normF(f): |
|
32 return os.path.join(*f.split('/')) |
|
33 |
|
34 def writeFile(fname, contents): |
|
35 dir = os.path.split(fname)[0] |
|
36 if dir and not os.path.exists(dir): |
|
37 mkdirs(dir) |
|
38 f = open(fname, 'w') |
|
39 f.write(contents) |
|
40 f.close() |
|
41 |
|
42 def readFile(fname): |
|
43 f = open(fname) |
|
44 r = f.read() |
|
45 f.close() |
|
46 return r |
|
47 |
|
48 def writeProfile(dict): |
|
49 contents = [ "%s: %s\n" % (k, v) for k, v in dict.iteritems() ] |
|
50 writeFile(_mhprofile, "".join(contents)) |
|
51 |
|
52 def writeContext(folder): |
|
53 folder = normF(folder) |
|
54 writeFile(os.path.join(_mhpath, "context"), |
|
55 "Current-Folder: %s\n" % folder) |
|
56 |
|
57 def writeCurMessage(folder, cur): |
|
58 folder = normF(folder) |
|
59 writeFile(os.path.join(_mhpath, folder, ".mh_sequences"), |
|
60 "cur: %s\n"%cur) |
|
61 |
|
62 def writeMessage(folder, n, headers, body): |
|
63 folder = normF(folder) |
|
64 headers = "".join([ "%s: %s\n" % (k, v) for k, v in headers.iteritems() ]) |
|
65 contents = "%s\n%s\n" % (headers,body) |
|
66 mkdirs(os.path.join(_mhpath, folder)) |
|
67 writeFile(os.path.join(_mhpath, folder, str(n)), contents) |
|
68 |
|
69 def getMH(): |
|
70 return mhlib.MH(os.path.abspath(_mhpath), _mhprofile) |
|
71 |
|
72 def sortLines(s): |
|
73 lines = s.split("\n") |
|
74 lines = [ line.strip() for line in lines if len(line) >= 2 ] |
|
75 lines.sort() |
|
76 return lines |
|
77 |
|
78 # These next 2 functions are copied from test_glob.py. |
|
79 def mkdirs(fname): |
|
80 if os.path.exists(fname) or fname == '': |
|
81 return |
|
82 base, file = os.path.split(fname) |
|
83 mkdirs(base) |
|
84 os.mkdir(fname) |
|
85 |
|
86 def deltree(fname): |
|
87 if not os.path.exists(fname): |
|
88 return |
|
89 for f in os.listdir(fname): |
|
90 fullname = os.path.join(fname, f) |
|
91 if os.path.isdir(fullname): |
|
92 deltree(fullname) |
|
93 else: |
|
94 try: |
|
95 os.unlink(fullname) |
|
96 except: |
|
97 pass |
|
98 try: |
|
99 os.rmdir(fname) |
|
100 except: |
|
101 pass |
|
102 |
|
103 class MhlibTests(unittest.TestCase): |
|
104 def setUp(self): |
|
105 deltree(_mhroot) |
|
106 mkdirs(_mhpath) |
|
107 writeProfile({'Path' : os.path.abspath(_mhpath), |
|
108 'Editor': 'emacs', |
|
109 'ignored-attribute': 'camping holiday'}) |
|
110 # Note: These headers aren't really conformant to RFC822, but |
|
111 # mhlib shouldn't care about that. |
|
112 |
|
113 # An inbox with a couple of messages. |
|
114 writeMessage('inbox', 1, |
|
115 {'From': 'Mrs. Premise', |
|
116 'To': 'Mrs. Conclusion', |
|
117 'Date': '18 July 2001'}, "Hullo, Mrs. Conclusion!\n") |
|
118 writeMessage('inbox', 2, |
|
119 {'From': 'Mrs. Conclusion', |
|
120 'To': 'Mrs. Premise', |
|
121 'Date': '29 July 2001'}, "Hullo, Mrs. Premise!\n") |
|
122 |
|
123 # A folder with many messages |
|
124 for i in range(5, 101)+range(101, 201, 2): |
|
125 writeMessage('wide', i, |
|
126 {'From': 'nowhere', 'Subject': 'message #%s' % i}, |
|
127 "This is message number %s\n" % i) |
|
128 |
|
129 # A deeply nested folder |
|
130 def deep(folder, n): |
|
131 writeMessage(folder, n, |
|
132 {'Subject': 'Message %s/%s' % (folder, n) }, |
|
133 "This is message number %s in %s\n" % (n, folder) ) |
|
134 deep('deep/f1', 1) |
|
135 deep('deep/f1', 2) |
|
136 deep('deep/f1', 3) |
|
137 deep('deep/f2', 4) |
|
138 deep('deep/f2', 6) |
|
139 deep('deep', 3) |
|
140 deep('deep/f2/f3', 1) |
|
141 deep('deep/f2/f3', 2) |
|
142 |
|
143 def tearDown(self): |
|
144 deltree(_mhroot) |
|
145 |
|
146 def test_basic(self): |
|
147 writeContext('inbox') |
|
148 writeCurMessage('inbox', 2) |
|
149 mh = getMH() |
|
150 |
|
151 eq = self.assertEquals |
|
152 eq(mh.getprofile('Editor'), 'emacs') |
|
153 eq(mh.getprofile('not-set'), None) |
|
154 eq(mh.getpath(), os.path.abspath(_mhpath)) |
|
155 eq(mh.getcontext(), 'inbox') |
|
156 |
|
157 mh.setcontext('wide') |
|
158 eq(mh.getcontext(), 'wide') |
|
159 eq(readFile(os.path.join(_mhpath, 'context')), |
|
160 "Current-Folder: wide\n") |
|
161 |
|
162 mh.setcontext('inbox') |
|
163 |
|
164 inbox = mh.openfolder('inbox') |
|
165 eq(inbox.getfullname(), |
|
166 os.path.join(os.path.abspath(_mhpath), 'inbox')) |
|
167 eq(inbox.getsequencesfilename(), |
|
168 os.path.join(os.path.abspath(_mhpath), 'inbox', '.mh_sequences')) |
|
169 eq(inbox.getmessagefilename(1), |
|
170 os.path.join(os.path.abspath(_mhpath), 'inbox', '1')) |
|
171 |
|
172 def test_listfolders(self): |
|
173 mh = getMH() |
|
174 eq = self.assertEquals |
|
175 |
|
176 folders = mh.listfolders() |
|
177 folders.sort() |
|
178 eq(folders, ['deep', 'inbox', 'wide']) |
|
179 |
|
180 folders = mh.listallfolders() |
|
181 folders.sort() |
|
182 tfolders = map(normF, ['deep', 'deep/f1', 'deep/f2', 'deep/f2/f3', |
|
183 'inbox', 'wide']) |
|
184 tfolders.sort() |
|
185 eq(folders, tfolders) |
|
186 |
|
187 folders = mh.listsubfolders('deep') |
|
188 folders.sort() |
|
189 eq(folders, map(normF, ['deep/f1', 'deep/f2'])) |
|
190 |
|
191 folders = mh.listallsubfolders('deep') |
|
192 folders.sort() |
|
193 eq(folders, map(normF, ['deep/f1', 'deep/f2', 'deep/f2/f3'])) |
|
194 eq(mh.listsubfolders(normF('deep/f2')), [normF('deep/f2/f3')]) |
|
195 |
|
196 eq(mh.listsubfolders('inbox'), []) |
|
197 eq(mh.listallsubfolders('inbox'), []) |
|
198 |
|
199 def test_sequence(self): |
|
200 mh = getMH() |
|
201 eq = self.assertEquals |
|
202 writeCurMessage('wide', 55) |
|
203 |
|
204 f = mh.openfolder('wide') |
|
205 all = f.listmessages() |
|
206 eq(all, range(5, 101)+range(101, 201, 2)) |
|
207 eq(f.getcurrent(), 55) |
|
208 f.setcurrent(99) |
|
209 eq(readFile(os.path.join(_mhpath, 'wide', '.mh_sequences')), |
|
210 'cur: 99\n') |
|
211 |
|
212 def seqeq(seq, val): |
|
213 eq(f.parsesequence(seq), val) |
|
214 |
|
215 seqeq('5-55', range(5, 56)) |
|
216 seqeq('90-108', range(90, 101)+range(101, 109, 2)) |
|
217 seqeq('90-108', range(90, 101)+range(101, 109, 2)) |
|
218 |
|
219 seqeq('10:10', range(10, 20)) |
|
220 seqeq('10:+10', range(10, 20)) |
|
221 seqeq('101:10', range(101, 121, 2)) |
|
222 |
|
223 seqeq('cur', [99]) |
|
224 seqeq('.', [99]) |
|
225 seqeq('prev', [98]) |
|
226 seqeq('next', [100]) |
|
227 seqeq('cur:-3', [97, 98, 99]) |
|
228 seqeq('first-cur', range(5, 100)) |
|
229 seqeq('150-last', range(151, 201, 2)) |
|
230 seqeq('prev-next', [98, 99, 100]) |
|
231 |
|
232 lowprimes = [5, 7, 11, 13, 17, 19, 23, 29] |
|
233 lowcompos = [x for x in range(5, 31) if not x in lowprimes ] |
|
234 f.putsequences({'cur': [5], |
|
235 'lowprime': lowprimes, |
|
236 'lowcompos': lowcompos}) |
|
237 seqs = readFile(os.path.join(_mhpath, 'wide', '.mh_sequences')) |
|
238 seqs = sortLines(seqs) |
|
239 eq(seqs, ["cur: 5", |
|
240 "lowcompos: 6 8-10 12 14-16 18 20-22 24-28 30", |
|
241 "lowprime: 5 7 11 13 17 19 23 29"]) |
|
242 |
|
243 seqeq('lowprime', lowprimes) |
|
244 seqeq('lowprime:1', [5]) |
|
245 seqeq('lowprime:2', [5, 7]) |
|
246 seqeq('lowprime:-2', [23, 29]) |
|
247 |
|
248 ## Not supported |
|
249 #seqeq('lowprime:first', [5]) |
|
250 #seqeq('lowprime:last', [29]) |
|
251 #seqeq('lowprime:prev', [29]) |
|
252 #seqeq('lowprime:next', [29]) |
|
253 |
|
254 def test_modify(self): |
|
255 mh = getMH() |
|
256 eq = self.assertEquals |
|
257 |
|
258 mh.makefolder("dummy1") |
|
259 self.assert_("dummy1" in mh.listfolders()) |
|
260 path = os.path.join(_mhpath, "dummy1") |
|
261 self.assert_(os.path.exists(path)) |
|
262 |
|
263 f = mh.openfolder('dummy1') |
|
264 def create(n): |
|
265 msg = "From: foo\nSubject: %s\n\nDummy Message %s\n" % (n,n) |
|
266 f.createmessage(n, StringIO.StringIO(msg)) |
|
267 |
|
268 create(7) |
|
269 create(8) |
|
270 create(9) |
|
271 |
|
272 eq(readFile(f.getmessagefilename(9)), |
|
273 "From: foo\nSubject: 9\n\nDummy Message 9\n") |
|
274 |
|
275 eq(f.listmessages(), [7, 8, 9]) |
|
276 files = os.listdir(path) |
|
277 files.sort() |
|
278 eq(files, ['7', '8', '9']) |
|
279 |
|
280 f.removemessages(['7', '8']) |
|
281 files = os.listdir(path) |
|
282 files.sort() |
|
283 eq(files, [',7', ',8', '9']) |
|
284 eq(f.listmessages(), [9]) |
|
285 create(10) |
|
286 create(11) |
|
287 create(12) |
|
288 |
|
289 mh.makefolder("dummy2") |
|
290 f2 = mh.openfolder("dummy2") |
|
291 eq(f2.listmessages(), []) |
|
292 f.movemessage(10, f2, 3) |
|
293 f.movemessage(11, f2, 5) |
|
294 eq(f.listmessages(), [9, 12]) |
|
295 eq(f2.listmessages(), [3, 5]) |
|
296 eq(readFile(f2.getmessagefilename(3)), |
|
297 "From: foo\nSubject: 10\n\nDummy Message 10\n") |
|
298 |
|
299 f.copymessage(9, f2, 4) |
|
300 eq(f.listmessages(), [9, 12]) |
|
301 eq(readFile(f2.getmessagefilename(4)), |
|
302 "From: foo\nSubject: 9\n\nDummy Message 9\n") |
|
303 |
|
304 f.refilemessages([9, 12], f2) |
|
305 eq(f.listmessages(), []) |
|
306 eq(f2.listmessages(), [3, 4, 5, 6, 7]) |
|
307 eq(readFile(f2.getmessagefilename(7)), |
|
308 "From: foo\nSubject: 12\n\nDummy Message 12\n") |
|
309 # XXX This should check that _copysequences does the right thing. |
|
310 |
|
311 mh.deletefolder('dummy1') |
|
312 mh.deletefolder('dummy2') |
|
313 self.assert_('dummy1' not in mh.listfolders()) |
|
314 self.assert_(not os.path.exists(path)) |
|
315 |
|
316 def test_read(self): |
|
317 mh = getMH() |
|
318 eq = self.assertEquals |
|
319 |
|
320 f = mh.openfolder('inbox') |
|
321 msg = f.openmessage(1) |
|
322 # Check some basic stuff from rfc822 |
|
323 eq(msg.getheader('From'), "Mrs. Premise") |
|
324 eq(msg.getheader('To'), "Mrs. Conclusion") |
|
325 |
|
326 # Okay, we have the right message. Let's check the stuff from |
|
327 # mhlib. |
|
328 lines = sortLines(msg.getheadertext()) |
|
329 eq(lines, ["Date: 18 July 2001", |
|
330 "From: Mrs. Premise", |
|
331 "To: Mrs. Conclusion"]) |
|
332 lines = sortLines(msg.getheadertext(lambda h: len(h)==4)) |
|
333 eq(lines, ["Date: 18 July 2001", |
|
334 "From: Mrs. Premise"]) |
|
335 eq(msg.getbodytext(), "Hullo, Mrs. Conclusion!\n\n") |
|
336 eq(msg.getbodytext(0), "Hullo, Mrs. Conclusion!\n\n") |
|
337 |
|
338 # XXXX there should be a better way to reclaim the file handle |
|
339 msg.fp.close() |
|
340 del msg |
|
341 |
|
342 |
|
343 def test_main(): |
|
344 run_unittest(MhlibTests) |
|
345 |
|
346 |
|
347 if __name__ == "__main__": |
|
348 test_main() |