test_mhlib.py 10.8 KB
Newer Older
1 2 3 4 5 6 7 8 9
"""
   Tests for the mhlib module
   Nick Mathewson
"""

### BUG: This suite doesn't currently test the mime functionality of
###      mhlib.  It should.

import unittest
10
from test.test_support import run_unittest, TESTFN, TestSkipped
11 12
import os
import io
13
import sys
14 15
import mhlib

Neal Norwitz's avatar
Neal Norwitz committed
16
if sys.platform.startswith(("win", "atheos")):
17 18 19 20 21 22 23
    # mhlib.updateline() renames a file to the name of a file that already
    # exists.  That causes a reasonable OS <wink> to complain in test_sequence
    # here, like the "OSError: [Errno 17] File exists" raised on Windows.
    # mhlib's listsubfolders() and listallfolders() do something with
    # link counts, and that causes test_listfolders() here to get back
    # an empty list from its call of listallfolders().
    # The other tests here pass on Windows.
24
    raise TestSkipped("skipped on %s -- " % sys.platform +
25 26
                      "too many Unix assumptions")

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
_mhroot = TESTFN+"_MH"
_mhpath = os.path.join(_mhroot, "MH")
_mhprofile = os.path.join(_mhroot, ".mh_profile")

def normF(f):
    return os.path.join(*f.split('/'))

def writeFile(fname, contents):
    dir = os.path.split(fname)[0]
    if dir and not os.path.exists(dir):
        mkdirs(dir)
    f = open(fname, 'w')
    f.write(contents)
    f.close()

def readFile(fname):
    f = open(fname)
    r = f.read()
    f.close()
    return r

def writeProfile(dict):
49
    contents = [ "%s: %s\n" % (k, v) for k, v in dict.items() ]
50 51 52 53
    writeFile(_mhprofile, "".join(contents))

def writeContext(folder):
    folder = normF(folder)
Tim Peters's avatar
Tim Peters committed
54
    writeFile(os.path.join(_mhpath, "context"),
55 56 57 58 59 60 61 62 63
              "Current-Folder: %s\n" % folder)

def writeCurMessage(folder, cur):
    folder = normF(folder)
    writeFile(os.path.join(_mhpath, folder, ".mh_sequences"),
              "cur: %s\n"%cur)

def writeMessage(folder, n, headers, body):
    folder = normF(folder)
64
    headers = "".join([ "%s: %s\n" % (k, v) for k, v in headers.items() ])
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
    contents = "%s\n%s\n" % (headers,body)
    mkdirs(os.path.join(_mhpath, folder))
    writeFile(os.path.join(_mhpath, folder, str(n)), contents)

def getMH():
    return mhlib.MH(os.path.abspath(_mhpath), _mhprofile)

def sortLines(s):
    lines = s.split("\n")
    lines = [ line.strip() for line in lines if len(line) >= 2 ]
    lines.sort()
    return lines

# These next 2 functions are copied from test_glob.py.
def mkdirs(fname):
    if os.path.exists(fname) or fname == '':
        return
    base, file = os.path.split(fname)
    mkdirs(base)
    os.mkdir(fname)

def deltree(fname):
    if not os.path.exists(fname):
        return
    for f in os.listdir(fname):
        fullname = os.path.join(fname, f)
        if os.path.isdir(fullname):
            deltree(fullname)
        else:
            try:
                os.unlink(fullname)
            except:
                pass
    try:
        os.rmdir(fname)
    except:
        pass

class MhlibTests(unittest.TestCase):
    def setUp(self):
        deltree(_mhroot)
        mkdirs(_mhpath)
Tim Peters's avatar
Tim Peters committed
107
        writeProfile({'Path' : os.path.abspath(_mhpath),
108 109 110 111 112 113
                      'Editor': 'emacs',
                      'ignored-attribute': 'camping holiday'})
        # Note: These headers aren't really conformant to RFC822, but
        #  mhlib shouldn't care about that.

        # An inbox with a couple of messages.
Tim Peters's avatar
Tim Peters committed
114
        writeMessage('inbox', 1,
115 116 117
                     {'From': 'Mrs. Premise',
                      'To': 'Mrs. Conclusion',
                      'Date': '18 July 2001'}, "Hullo, Mrs. Conclusion!\n")
Tim Peters's avatar
Tim Peters committed
118
        writeMessage('inbox', 2,
119 120 121
                     {'From': 'Mrs. Conclusion',
                      'To': 'Mrs. Premise',
                      'Date': '29 July 2001'}, "Hullo, Mrs. Premise!\n")
Tim Peters's avatar
Tim Peters committed
122

123
        # A folder with many messages
124
        for i in list(range(5, 101))+list(range(101, 201, 2)):
Tim Peters's avatar
Tim Peters committed
125
            writeMessage('wide', i,
126 127
                         {'From': 'nowhere', 'Subject': 'message #%s' % i},
                         "This is message number %s\n" % i)
Tim Peters's avatar
Tim Peters committed
128

129 130
        # A deeply nested folder
        def deep(folder, n):
Tim Peters's avatar
Tim Peters committed
131
            writeMessage(folder, n,
132 133 134 135 136 137 138 139 140 141
                         {'Subject': 'Message %s/%s' % (folder, n) },
                         "This is message number %s in %s\n" % (n, folder) )
        deep('deep/f1', 1)
        deep('deep/f1', 2)
        deep('deep/f1', 3)
        deep('deep/f2', 4)
        deep('deep/f2', 6)
        deep('deep', 3)
        deep('deep/f2/f3', 1)
        deep('deep/f2/f3', 2)
Tim Peters's avatar
Tim Peters committed
142

143 144
    def tearDown(self):
        deltree(_mhroot)
Tim Peters's avatar
Tim Peters committed
145

146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
    def test_basic(self):
        writeContext('inbox')
        writeCurMessage('inbox', 2)
        mh = getMH()

        eq = self.assertEquals
        eq(mh.getprofile('Editor'), 'emacs')
        eq(mh.getprofile('not-set'), None)
        eq(mh.getpath(), os.path.abspath(_mhpath))
        eq(mh.getcontext(), 'inbox')

        mh.setcontext('wide')
        eq(mh.getcontext(), 'wide')
        eq(readFile(os.path.join(_mhpath, 'context')),
           "Current-Folder: wide\n")

        mh.setcontext('inbox')

        inbox = mh.openfolder('inbox')
Tim Peters's avatar
Tim Peters committed
165
        eq(inbox.getfullname(),
166
           os.path.join(os.path.abspath(_mhpath), 'inbox'))
Tim Peters's avatar
Tim Peters committed
167
        eq(inbox.getsequencesfilename(),
168
           os.path.join(os.path.abspath(_mhpath), 'inbox', '.mh_sequences'))
Tim Peters's avatar
Tim Peters committed
169
        eq(inbox.getmessagefilename(1),
170
           os.path.join(os.path.abspath(_mhpath), 'inbox', '1'))
Tim Peters's avatar
Tim Peters committed
171

172 173 174 175 176 177 178
    def test_listfolders(self):
        mh = getMH()
        eq = self.assertEquals

        folders = mh.listfolders()
        folders.sort()
        eq(folders, ['deep', 'inbox', 'wide'])
Tim Peters's avatar
Tim Peters committed
179

180 181
        folders = mh.listallfolders()
        folders.sort()
182 183
        tfolders = sorted(map(normF, ['deep', 'deep/f1', 'deep/f2',
                                      'deep/f2/f3', 'inbox', 'wide']))
184
        eq(folders, tfolders)
185 186 187

        folders = mh.listsubfolders('deep')
        folders.sort()
188
        eq(folders, list(map(normF, ['deep/f1', 'deep/f2'])))
Tim Peters's avatar
Tim Peters committed
189

190 191
        folders = mh.listallsubfolders('deep')
        folders.sort()
192
        eq(folders, list(map(normF, ['deep/f1', 'deep/f2', 'deep/f2/f3'])))
193 194 195 196 197 198 199 200 201
        eq(mh.listsubfolders(normF('deep/f2')), [normF('deep/f2/f3')])

        eq(mh.listsubfolders('inbox'), [])
        eq(mh.listallsubfolders('inbox'), [])

    def test_sequence(self):
        mh = getMH()
        eq = self.assertEquals
        writeCurMessage('wide', 55)
Tim Peters's avatar
Tim Peters committed
202

203 204
        f = mh.openfolder('wide')
        all = f.listmessages()
205
        eq(all, list(range(5, 101))+list(range(101, 201, 2)))
206 207
        eq(f.getcurrent(), 55)
        f.setcurrent(99)
Tim Peters's avatar
Tim Peters committed
208
        eq(readFile(os.path.join(_mhpath, 'wide', '.mh_sequences')),
209 210 211 212
           'cur: 99\n')

        def seqeq(seq, val):
            eq(f.parsesequence(seq), val)
Tim Peters's avatar
Tim Peters committed
213

214 215 216
        seqeq('5-55', list(range(5, 56)))
        seqeq('90-108', list(range(90, 101))+list(range(101, 109, 2)))
        seqeq('90-108', list(range(90, 101))+list(range(101, 109, 2)))
Tim Peters's avatar
Tim Peters committed
217

218 219 220
        seqeq('10:10', list(range(10, 20)))
        seqeq('10:+10', list(range(10, 20)))
        seqeq('101:10', list(range(101, 121, 2)))
221 222 223 224 225 226

        seqeq('cur', [99])
        seqeq('.', [99])
        seqeq('prev', [98])
        seqeq('next', [100])
        seqeq('cur:-3', [97, 98, 99])
227 228
        seqeq('first-cur', list(range(5, 100)))
        seqeq('150-last', list(range(151, 201, 2)))
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
        seqeq('prev-next', [98, 99, 100])

        lowprimes = [5, 7, 11, 13, 17, 19, 23, 29]
        lowcompos = [x for x in range(5, 31) if not x in lowprimes ]
        f.putsequences({'cur': [5],
                        'lowprime': lowprimes,
                        'lowcompos': lowcompos})
        seqs = readFile(os.path.join(_mhpath, 'wide', '.mh_sequences'))
        seqs = sortLines(seqs)
        eq(seqs, ["cur: 5",
                  "lowcompos: 6 8-10 12 14-16 18 20-22 24-28 30",
                  "lowprime: 5 7 11 13 17 19 23 29"])

        seqeq('lowprime', lowprimes)
        seqeq('lowprime:1', [5])
        seqeq('lowprime:2', [5, 7])
        seqeq('lowprime:-2', [23, 29])

        ## Not supported
        #seqeq('lowprime:first', [5])
        #seqeq('lowprime:last', [29])
        #seqeq('lowprime:prev', [29])
        #seqeq('lowprime:next', [29])

    def test_modify(self):
        mh = getMH()
        eq = self.assertEquals

        mh.makefolder("dummy1")
        self.assert_("dummy1" in mh.listfolders())
        path = os.path.join(_mhpath, "dummy1")
        self.assert_(os.path.exists(path))
Tim Peters's avatar
Tim Peters committed
261

262 263 264
        f = mh.openfolder('dummy1')
        def create(n):
            msg = "From: foo\nSubject: %s\n\nDummy Message %s\n" % (n,n)
265
            f.createmessage(n, io.StringIO(msg))
266 267 268 269

        create(7)
        create(8)
        create(9)
Tim Peters's avatar
Tim Peters committed
270

271 272 273 274 275 276
        eq(readFile(f.getmessagefilename(9)),
           "From: foo\nSubject: 9\n\nDummy Message 9\n")

        eq(f.listmessages(), [7, 8, 9])
        files = os.listdir(path)
        files.sort()
Tim Peters's avatar
Tim Peters committed
277
        eq(files, ['7', '8', '9'])
278 279 280 281 282 283 284 285 286

        f.removemessages(['7', '8'])
        files = os.listdir(path)
        files.sort()
        eq(files, [',7', ',8', '9'])
        eq(f.listmessages(), [9])
        create(10)
        create(11)
        create(12)
Tim Peters's avatar
Tim Peters committed
287

288 289 290 291 292 293 294 295 296
        mh.makefolder("dummy2")
        f2 = mh.openfolder("dummy2")
        eq(f2.listmessages(), [])
        f.movemessage(10, f2, 3)
        f.movemessage(11, f2, 5)
        eq(f.listmessages(), [9, 12])
        eq(f2.listmessages(), [3, 5])
        eq(readFile(f2.getmessagefilename(3)),
           "From: foo\nSubject: 10\n\nDummy Message 10\n")
Tim Peters's avatar
Tim Peters committed
297

298 299 300 301
        f.copymessage(9, f2, 4)
        eq(f.listmessages(), [9, 12])
        eq(readFile(f2.getmessagefilename(4)),
           "From: foo\nSubject: 9\n\nDummy Message 9\n")
Tim Peters's avatar
Tim Peters committed
302

303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
        f.refilemessages([9, 12], f2)
        eq(f.listmessages(), [])
        eq(f2.listmessages(), [3, 4, 5, 6, 7])
        eq(readFile(f2.getmessagefilename(7)),
           "From: foo\nSubject: 12\n\nDummy Message 12\n")
        # XXX This should check that _copysequences does the right thing.

        mh.deletefolder('dummy1')
        mh.deletefolder('dummy2')
        self.assert_('dummy1' not in mh.listfolders())
        self.assert_(not os.path.exists(path))

    def test_read(self):
        mh = getMH()
        eq = self.assertEquals
Tim Peters's avatar
Tim Peters committed
318

319 320 321 322 323 324 325 326 327
        f = mh.openfolder('inbox')
        msg = f.openmessage(1)
        # Check some basic stuff from rfc822
        eq(msg.getheader('From'), "Mrs. Premise")
        eq(msg.getheader('To'), "Mrs. Conclusion")

        # Okay, we have the right message.  Let's check the stuff from
        # mhlib.
        lines = sortLines(msg.getheadertext())
Tim Peters's avatar
Tim Peters committed
328
        eq(lines, ["Date: 18 July 2001",
329 330 331
                   "From: Mrs. Premise",
                   "To: Mrs. Conclusion"])
        lines = sortLines(msg.getheadertext(lambda h: len(h)==4))
Tim Peters's avatar
Tim Peters committed
332
        eq(lines, ["Date: 18 July 2001",
333 334 335
                   "From: Mrs. Premise"])
        eq(msg.getbodytext(), "Hullo, Mrs. Conclusion!\n\n")
        eq(msg.getbodytext(0), "Hullo, Mrs. Conclusion!\n\n")
Tim Peters's avatar
Tim Peters committed
336

337 338 339 340
        # XXXX there should be a better way to reclaim the file handle
        msg.fp.close()
        del msg

341 342 343 344 345 346 347

def test_main():
    run_unittest(MhlibTests)


if __name__ == "__main__":
    test_main()