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, import_module 11 import os, StringIO 12 import sys 13 mhlib = import_module('mhlib', deprecated=True) 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 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 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.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 343 def test_main(): 344 run_unittest(MhlibTests) 345 346 347 if __name__ == "__main__": 348 test_main() 349