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
9import unittest
10from test.test_support import run_unittest, TESTFN, import_module
11import os, StringIO
12import sys
13mhlib = import_module('mhlib', deprecated=True)
14
15if (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 unittest.SkipTest("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
31def normF(f):
32    return os.path.join(*f.split('/'))
33
34def 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
42def readFile(fname):
43    f = open(fname)
44    r = f.read()
45    f.close()
46    return r
47
48def writeProfile(dict):
49    contents = [ "%s: %s\n" % (k, v) for k, v in dict.iteritems() ]
50    writeFile(_mhprofile, "".join(contents))
51
52def writeContext(folder):
53    folder = normF(folder)
54    writeFile(os.path.join(_mhpath, "context"),
55              "Current-Folder: %s\n" % folder)
56
57def writeCurMessage(folder, cur):
58    folder = normF(folder)
59    writeFile(os.path.join(_mhpath, folder, ".mh_sequences"),
60              "cur: %s\n"%cur)
61
62def 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
69def getMH():
70    return mhlib.MH(os.path.abspath(_mhpath), _mhprofile)
71
72def 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.
79def 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
86def 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
103class 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.assertEqual
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.assertEqual
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.assertEqual
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.assertEqual
257
258        mh.makefolder("dummy1")
259        self.assertIn("dummy1", mh.listfolders())
260        path = os.path.join(_mhpath, "dummy1")
261        self.assertTrue(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.assertNotIn('dummy1', mh.listfolders())
314        self.assertTrue(not os.path.exists(path))
315
316    def test_read(self):
317        mh = getMH()
318        eq = self.assertEqual
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
343def test_main():
344    run_unittest(MhlibTests)
345
346
347if __name__ == "__main__":
348    test_main()
349