root/imapclient/livetest.py

Revision 327:952e2477f96e, 27.9 KB (checked in by Menno Smits <menno@…>, 43 hours ago)

added a live test for readonly select_folder (EXAMINE)

Line 
1#!/usr/bin/python
2
3# Copyright (c) 2012, Menno Smits
4# Released subject to the New BSD License
5# Please see http://en.wikipedia.org/wiki/BSD_licenses
6
7
8import imp
9import os
10import sys
11import time
12from datetime import datetime
13from email.utils import make_msgid
14
15import imapclient
16from test.util import unittest
17from config import parse_config_file, create_client_from_config
18
19# TODO cleaner verbose output: avoid "__main__" and separator between classes
20
21
22SIMPLE_MESSAGE = 'Subject: something\r\n\r\nFoo\r\n'
23
24# Simple address in To header triggers interesting Fastmail.fm
25# behaviour with ENVELOPE responses.
26MULTIPART_MESSAGE = """\
27From: Bob Smith <bob@smith.com>
28To: Some One <some@one.com>, foo@foo.com
29Date: Tue, 16 Mar 2010 16:45:32 +0000
30MIME-Version: 1.0
31Subject: A multipart message
32Content-Type: multipart/mixed; boundary="===============1534046211=="
33
34--===============1534046211==
35Content-Type: text/html; charset="us-ascii"
36Content-Transfer-Encoding: quoted-printable
37
38<html><body>
39Here is the first part.
40</body></html>
41
42--===============1534046211==
43Content-Type: text/plain; charset="us-ascii"
44Content-Transfer-Encoding: 7bit
45
46Here is the second part.
47
48--===============1534046211==--
49""".replace('\n', '\r\n')
50
51
52def createLiveTestClass(conf, use_uid):
53
54    class LiveTest(unittest.TestCase):
55
56        def setUp(self):
57            self.client = create_client_from_config(conf)
58            self.client.use_uid = use_uid
59            self.base_folder = conf.namespace[0] + ('__imapclient')
60            self.folder_delimiter = conf.namespace[1]
61            self.clear_test_folders()
62            self.unsub_all_test_folders()
63            self.client.create_folder(self.base_folder)
64            self.client.select_folder(self.base_folder)
65
66        def tearDown(self):
67            self.clear_test_folders()
68            self.unsub_all_test_folders()
69            self.client.logout()
70
71        def skip_unless_capable(self, capability, name=None):
72            if not self.client.has_capability(capability):
73                if not name:
74                    name = capability
75                self.skipTest("Server doesn't support %s" % name)
76
77        def just_folder_names(self, dat):
78            ret = []
79            for _, _, folder_name in dat:
80                # gmail's "special" folders start with '['
81                if not folder_name.startswith('['):
82                    ret.append(folder_name)
83            return ret
84
85        def all_test_folder_names(self):
86            return self.just_folder_names(self.client.list_folders(self.base_folder))
87
88        def all_sub_test_folder_names(self):
89            return self.just_folder_names(self.client.list_sub_folders(self.base_folder))
90
91        def clear_test_folders(self):
92            self.client.folder_encode = False
93
94            # Sort folders depth first because some implementations
95            # (e.g. MS Exchange) will delete child folders when a
96            # parent is deleted.
97            def get_folder_depth(folder):
98                return folder.count(self.folder_delimiter)
99            folder_names = sorted(self.all_test_folder_names(),
100                                  key=get_folder_depth,
101                                  reverse=True)
102            for folder in folder_names:
103                self.client.delete_folder(folder)
104            self.client.folder_encode = True
105
106        def clear_folder(self, folder):
107            self.client.select_folder(folder)
108            self.client.delete_messages(self.client.search())
109            self.client.expunge()
110
111        def add_prefix_to_folder(self, folder):
112            return self.base_folder + self.folder_delimiter + folder
113
114        def add_prefix_to_folders(self, folders):
115            return [self.add_prefix_to_folder(folder) for folder in folders]
116
117        def unsub_all_test_folders(self):
118            for folder in self.all_sub_test_folder_names():
119                self.client.unsubscribe_folder(folder)
120
121        def is_gmail(self):
122            return self.client._imap.host == 'imap.gmail.com'
123
124        def is_fastmail(self):
125            return self.client._imap.host == 'mail.messagingengine.com'
126
127        def is_exchange(self):
128            # Assume that these capabilities mean we're talking to MS
129            # Exchange. A bit of a guess really.
130            return (self.client.has_capability('IMAP4') and
131                    self.client.has_capability('AUTH=NTLM') and
132                    self.client.has_capability('AUTH=GSSAPI'))
133
134        def test_capabilities(self):
135            caps = self.client.capabilities()
136            self.assertIsInstance(caps, tuple)
137            self.assertGreater(len(caps), 1)
138            for cap in caps:
139                self.assertTrue(self.client.has_capability(cap))
140            self.assertFalse(self.client.has_capability('WONT EXIST'))
141
142        def test_namespace(self):
143            self.skip_unless_capable('NAMESPACE')
144
145            def assertNoneOrTuple(val):
146                assert val is None or isinstance(val, tuple), \
147                       "unexpected namespace value %r" % val
148
149            ns = self.client.namespace()
150            self.assertEqual(len(ns), 3)
151            assertNoneOrTuple(ns.personal)
152            assertNoneOrTuple(ns.other)
153            assertNoneOrTuple(ns.shared)
154            self.assertEqual(ns.personal, ns[0])
155            self.assertEqual(ns.other, ns[1])
156            self.assertEqual(ns.shared, ns[2])
157
158        def test_select_and_close(self):
159            resp = self.client.select_folder(self.base_folder)
160            self.assertEqual(resp['EXISTS'], 0)
161            self.assertIsInstance(resp['RECENT'], int)
162            self.assertIsInstance(resp['FLAGS'], tuple)
163            self.assertGreater(len(resp['FLAGS']), 1)
164            self.client.close_folder()
165
166        def test_select_read_only(self):
167            self.client.append(self.base_folder, SIMPLE_MESSAGE)
168            self.assertNotIn('READ-ONLY', self.client._imap.untagged_responses)
169
170            resp = self.client.select_folder(self.base_folder, readonly=True)
171
172            self.assertIn('READ-ONLY', self.client._imap.untagged_responses)
173            self.assertEqual(resp['EXISTS'], 1)
174            self.assertIsInstance(resp['RECENT'], int)
175            self.assertIsInstance(resp['FLAGS'], tuple)
176            self.assertGreater(len(resp['FLAGS']), 1)
177
178        def test_list_folders(self):
179            some_folders = ['simple', u'L\xffR']
180            if not self.is_fastmail():
181                some_folders.extend([r'test"folder"', r'foo\bar'])
182            some_folders = self.add_prefix_to_folders(some_folders)
183            for name in some_folders:
184                self.client.create_folder(name)
185
186            folders = self.all_test_folder_names()
187            self.assertGreater(len(folders), 1, 'No folders visible on server')
188            self.assertIn(self.base_folder, folders)
189
190            for name in some_folders:
191                self.assertIn(name, folders)
192
193            #TODO: test LIST with wildcards
194
195        def test_gmail_xlist(self):
196            caps = self.client.capabilities()
197            if self.is_gmail():
198                self.assertIn("XLIST", caps, "expected XLIST in Gmail's capabilities")
199
200        def test_xlist(self):
201            self.skip_unless_capable('XLIST')
202
203            result = self.client.xlist_folders()
204            self.assertGreater(len(result), 0, 'No folders returned by XLIST')
205            for flags, _, _  in result:
206                if '\\INBOX' in [flag.upper() for flag in flags]:
207                    break
208                else:
209                    self.fail('INBOX not returned in XLIST output')
210
211        def test_subscriptions(self):
212            test_folders = self.add_prefix_to_folders(['foobar', 'stuff & things', u'test & \u2622'])
213            for folder in test_folders:
214                self.client.create_folder(folder)
215
216            all_folders = sorted(self.all_test_folder_names())
217            for folder in all_folders:
218                self.client.subscribe_folder(folder)
219
220            self.assertListEqual(all_folders, sorted(self.all_sub_test_folder_names()))
221
222            for folder in all_folders:
223                self.client.unsubscribe_folder(folder)
224            self.assertListEqual(self.all_sub_test_folder_names(), [])
225
226            # Exchange doesn't return an error when subscribing to a
227            # non-existent folder
228            if not self.is_exchange():
229                self.assertRaises(imapclient.IMAPClient.Error,
230                                  self.client.subscribe_folder,
231                                  'this folder is not likely to exist')
232
233        def test_folders(self):
234            self.assertTrue(self.client.folder_exists(self.base_folder))
235            self.assertFalse(self.client.folder_exists('this is very unlikely to exist'))
236
237            test_folders = ['foobar',
238                            'stuff & things',
239                            u'test & \u2622',
240                            '123']
241
242            if not self.is_fastmail():
243                # Fastmail doesn't appear like double quotes in folder names
244                test_folders.extend(['"foobar"', 'foo "bar"'])
245
246            test_folders = self.add_prefix_to_folders(test_folders)
247
248            for folder in test_folders:
249                self.assertFalse(self.client.folder_exists(folder))
250
251                self.client.create_folder(folder)
252
253                self.assertTrue(self.client.folder_exists(folder))
254                self.assertIn(folder, self.all_test_folder_names())
255
256                self.client.select_folder(folder)
257                self.client.close_folder()
258
259                self.client.delete_folder(folder)
260                self.assertFalse(self.client.folder_exists(folder))
261
262        def test_rename_folder(self):
263            test_folders = self.add_prefix_to_folders([
264                'foobar',
265                'stuff & things',
266                u'test & \u2622',
267                '123'])
268            for folder in test_folders:
269                self.client.create_folder(folder)
270
271                new_folder = folder + 'x'
272                resp = self.client.rename_folder(folder, new_folder)
273                self.assertIsInstance(resp, str)
274                self.assertTrue(len(resp) > 0)
275
276                self.assertFalse(self.client.folder_exists(folder))
277                self.assertTrue(self.client.folder_exists(new_folder))
278
279        def test_status(self):
280            # Default behaviour should return 5 keys
281            self.assertEqual(len(self.client.folder_status(self.base_folder)), 5)
282
283            new_folder = self.add_prefix_to_folder(u'test \u2622')
284            self.client.create_folder(new_folder)
285            try:
286                status = self.client.folder_status(new_folder)
287                self.assertEqual(status['MESSAGES'], 0)
288                self.assertEqual(status['RECENT'], 0)
289                self.assertEqual(status['UNSEEN'], 0)
290
291                # Add a message to the folder, it should show up now.
292                self.client.append(new_folder, SIMPLE_MESSAGE)
293
294                status = self.client.folder_status(new_folder)
295                self.assertEqual(status['MESSAGES'], 1)
296                if not self.is_gmail():
297                    self.assertEqual(status['RECENT'], 1)
298                self.assertEqual(status['UNSEEN'], 1)
299            finally:
300                self.client.delete_folder(new_folder)
301
302        def test_append(self):
303            # Message time microseconds are set to 0 because the server will return
304            # time with only seconds precision.
305            msg_time = datetime.now().replace(microsecond=0)
306
307            # Append message
308            resp = self.client.append(self.base_folder, SIMPLE_MESSAGE, ('abc', 'def'), msg_time)
309            self.assertIsInstance(resp, str)
310
311            # Retrieve the just added message and check that all looks well
312            self.assertEqual(self.client.select_folder(self.base_folder)['EXISTS'], 1)
313
314            resp = self.client.fetch(self.client.search()[0], ('RFC822', 'FLAGS', 'INTERNALDATE'))
315
316            self.assertEqual(len(resp), 1)
317            msginfo = resp.values()[0]
318
319            # Time should match the time we specified
320            returned_msg_time = msginfo['INTERNALDATE']
321            self.assertIsNone(returned_msg_time.tzinfo)
322            self.assertEqual(returned_msg_time, msg_time)
323
324            # Flags should be the same
325            self.assertIn('abc', msginfo['FLAGS'])
326            self.assertIn('def', msginfo['FLAGS'])
327
328            # Message body should match
329            self.assertEqual(msginfo['RFC822'], SIMPLE_MESSAGE)
330
331        def test_flags(self):
332            self.client.append(self.base_folder, SIMPLE_MESSAGE)
333            msg_id = self.client.search()[0]
334
335            def _flagtest(func, args, expected_flags):
336                answer = func(msg_id, *args)
337                self.assertTrue(answer.has_key(msg_id))
338                answer_flags = set(answer[msg_id])
339                answer_flags.discard(r'\Recent'# Might be present but don't care
340                self.assertSetEqual(answer_flags, set(expected_flags))
341
342            base_flags = ['abc', 'def']
343            _flagtest(self.client.set_flags, [base_flags], base_flags)
344            _flagtest(self.client.get_flags, [], base_flags)
345            _flagtest(self.client.add_flags, ['boo'], base_flags + ['boo'])
346            _flagtest(self.client.remove_flags, ['boo'], base_flags)
347
348        def test_gmail_labels(self):
349            self.skip_unless_capable('X-GM-EXT-1', 'labels')
350
351            self.client.append(self.base_folder, SIMPLE_MESSAGE)
352            msg_id = self.client.search()[0]
353
354            def _labeltest(func, args, expected_labels):
355                answer = func(msg_id, *args)
356                self.assertEquals(answer.keys(), [msg_id])
357                actual_labels = set(answer[msg_id])
358                self.assertSetEqual(actual_labels, set(expected_labels))
359
360            base_labels = ['_imapclient_foo', '_imapclient_bar']
361            try:
362                _labeltest(self.client.set_gmail_labels, [base_labels], base_labels)
363                _labeltest(self.client.get_gmail_labels, [], base_labels)
364                _labeltest(self.client.add_gmail_labels, ['_imapclient_baz'], base_labels + ['_imapclient_baz'])
365                _labeltest(self.client.remove_gmail_labels, ['_imapclient_baz'], base_labels)
366            finally:
367                # Clean up
368                for label in ['_imapclient_baz'] + base_labels:
369                    if self.client.folder_exists(label):
370                        self.client.delete_folder(label)
371
372        def test_search(self):
373            # Add some test messages
374            msg_tmpl = 'Subject: %s\r\n\r\nBody'
375            subjects = ('a', 'b', 'c')
376            for subject in subjects:
377                msg = msg_tmpl % subject
378                if subject == 'c':
379                    flags = (imapclient.DELETED,)
380                else:
381                    flags = ()
382                self.client.append(self.base_folder, msg, flags)
383
384            # Check we see all messages
385            messages_all = self.client.search('ALL')
386            if self.is_gmail():
387                # Gmail seems to never return deleted items.
388                self.assertEqual(len(messages_all), len(subjects) - 1)
389            else:
390                self.assertEqual(len(messages_all), len(subjects))
391            self.assertListEqual(self.client.search(), messages_all)      # Check default
392
393            # Single criteria
394            if not self.is_gmail():
395                self.assertEqual(len(self.client.search('DELETED')), 1)
396                self.assertEqual(len(self.client.search('NOT DELETED')), len(subjects) - 1)
397            self.assertListEqual(self.client.search('NOT DELETED'), self.client.search(['NOT DELETED']))
398
399            # Multiple criteria
400            self.assertEqual(len(self.client.search(['NOT DELETED', 'SMALLER 500'])), len(subjects) - 1)
401            self.assertEqual(len(self.client.search(['NOT DELETED', 'SUBJECT "a"'])), 1)
402            self.assertEqual(len(self.client.search(['NOT DELETED', 'SUBJECT "c"'])), 0)
403
404        def test_sort(self):
405            if not self.client.has_capability('SORT'):
406                return self.skipTest("Server doesn't support SORT")
407
408            # Add some test messages
409            msg_tmpl = 'Subject: Test\r\n\r\nBody'
410            num_lines = (10, 20, 30)
411            line = '\n' + ('x' * 72)
412            for line_cnt in num_lines:
413                msg = msg_tmpl + (line * line_cnt)
414                self.client.append(self.base_folder, msg)
415
416            messages = self.client.sort('REVERSE SIZE')
417            self.assertEqual(len(messages), 3)
418            first_id = messages[0]
419            expected = [first_id, first_id - 1, first_id - 2]
420            self.assertListEqual(messages, expected)
421
422        def test_copy(self):
423            self.client.append(self.base_folder, SIMPLE_MESSAGE)
424            target_folder = self.add_prefix_to_folder('target')
425            self.client.create_folder(target_folder)
426            msg_id = self.client.search()[0]
427
428            self.client.copy(msg_id, target_folder)
429
430            self.client.select_folder(target_folder)
431            msgs = self.client.search()
432            self.assertEqual(len(msgs), 1)
433            msg_id = msgs[0]
434            self.assertIn('something', self.client.fetch(msg_id, ['RFC822'])[msg_id]['RFC822'])
435
436        def test_fetch(self):
437            # Generate a fresh message-id each time because Gmail is
438            # clever and will treat appends of messages with
439            # previously seen message-ids as the same message. This
440            # breaks our tests when the test message is updated.
441            msg_id_header = make_msgid()
442            msg = ('Message-ID: %s\r\n' % msg_id_header) + MULTIPART_MESSAGE
443
444            self.client.select_folder(self.base_folder)
445            self.client.append(self.base_folder, msg)
446
447            fields = ['RFC822', 'FLAGS', 'INTERNALDATE', 'ENVELOPE']
448            msg_id = self.client.search()[0]
449            resp = self.client.fetch(msg_id, fields)
450
451            self.assertEqual(len(resp), 1)
452            msginfo = resp[msg_id]
453
454            self.assertSetEqual(set(msginfo.keys()), set(fields + ['SEQ']))
455            self.assertEqual(msginfo['SEQ'], 1)
456            self.assertMultiLineEqual(msginfo['RFC822'], msg)
457            self.assertIsInstance(msginfo['INTERNALDATE'], datetime)
458            self.assertIsInstance(msginfo['FLAGS'], tuple)
459            self.assertTupleEqual(msginfo['ENVELOPE'],
460                                  ('Tue, 16 Mar 2010 16:45:32 +0000',
461                                   'A multipart message',
462                                   (('Bob Smith', None, 'bob', 'smith.com'),),
463                                   (('Bob Smith', None, 'bob', 'smith.com'),),
464                                   (('Bob Smith', None, 'bob', 'smith.com'),),
465                                   (('Some One', None, 'some', 'one.com'), (None, None, 'foo', 'foo.com')),
466                                   None, None, None, msg_id_header))
467
468        def test_partial_fetch(self):
469            self.client.append(self.base_folder, MULTIPART_MESSAGE)
470            self.client.select_folder(self.base_folder)
471            msg_id = self.client.search()[0]
472
473            resp = self.client.fetch(msg_id, ['BODY[]<0.20>'])
474            body = resp[msg_id]['BODY[]<0>']
475            self.assertEqual(len(body), 20)
476            self.assertTrue(body.startswith('From: Bob Smith'))
477
478            resp = self.client.fetch(msg_id, ['BODY[]<2.25>'])
479            body = resp[msg_id]['BODY[]<2>']
480            self.assertEqual(len(body), 25)
481            self.assertTrue(body.startswith('om: Bob Smith'))
482
483        def test_fetch_modifiers(self):
484            # CONDSTORE (RFC 4551) provides a good way to use FETCH
485            # modifiers but it isn't commonly available.
486            if not self.client.has_capability('CONDSTORE'):
487                return self.skipTest("Server doesn't support CONDSTORE")
488
489            # A little dance to ensure MODSEQ tracking is turned on - I'm looking at you Dovecot!
490            self.client.select_folder(self.base_folder)
491            self.client.append(self.base_folder, SIMPLE_MESSAGE)
492            msg_id = self.client.search()[0]
493            self.client.fetch(msg_id, ["MODSEQ"])
494            self.client.close_folder()
495            self.clear_folder(self.base_folder)
496
497            # Actual testing starts here
498            maxModSeq = int(self.client.select_folder(self.base_folder)['HIGHESTMODSEQ'][0])
499            self.client.append(self.base_folder, SIMPLE_MESSAGE)
500            msg_id = self.client.search()[0]
501            resp = self.client.fetch(msg_id, ['FLAGS'], ['CHANGEDSINCE %d' % maxModSeq])
502            self.assertIn('MODSEQ', resp[msg_id])
503
504            # Prove that the modifier is actually being used
505            resp = self.client.fetch(msg_id, ['FLAGS'], ['CHANGEDSINCE %d' % (maxModSeq + 1)])
506            self.assertFalse(resp)
507
508        def test_BODYSTRUCTURE(self):
509            self.client.select_folder(self.base_folder)
510            self.client.append(self.base_folder, SIMPLE_MESSAGE)
511            self.client.append(self.base_folder, MULTIPART_MESSAGE)
512            msgs = self.client.search()
513
514            fetched = self.client.fetch(msgs, ['BODY', 'BODYSTRUCTURE'])
515
516            # The expected test data is the same for BODY and BODYSTRUCTURE
517            # since we can't predicate what the server we're testing against
518            # will return.
519
520            expected = ('text', 'plain', ('charset', 'us-ascii'), None, None, '7bit', 5, 1)
521            self.check_BODYSTRUCTURE(expected, fetched[msgs[0]]['BODY'], multipart=False)
522            self.check_BODYSTRUCTURE(expected, fetched[msgs[0]]['BODYSTRUCTURE'], multipart=False)
523
524            expected = ([('text', 'html', ('charset', 'us-ascii'), None, None, 'quoted-printable', 55, 3),
525                         ('text', 'plain', ('charset', 'us-ascii'), None, None, '7bit', 26, 1),
526                         ],
527                        'mixed',
528                        ('boundary', '===============1534046211=='))
529            self.check_BODYSTRUCTURE(expected, fetched[msgs[1]]['BODY'], multipart=True)
530            self.check_BODYSTRUCTURE(expected, fetched[msgs[1]]['BODYSTRUCTURE'], multipart=True)
531
532        def check_BODYSTRUCTURE(self, expected, actual, multipart=None):
533            if multipart is not None:
534                self.assertEqual(actual.is_multipart, multipart)
535
536            # BODYSTRUCTURE lengths can various according to the server so
537            # compare up until what is returned
538            for e, a in zip(expected, actual):
539                if have_matching_types(e, a, (list, tuple)):
540                    for expected_and_actual in zip(e, a):
541                        self.check_BODYSTRUCTURE(*expected_and_actual)
542                else:
543                    if e == ('charset', 'us-ascii') and a is None:
544                        pass  # Some servers (eg. Gmail) don't return a charset when it's us-ascii
545                    else:
546                        a = lower_if_str(a)
547                        e = lower_if_str(e)
548                        self.assertEqual(a, e)
549
550        def test_idle(self):
551            if not self.client.has_capability('IDLE'):
552                return self.skipTest("Server doesn't support IDLE")
553
554            # Start main connection idling
555            self.client.select_folder(self.base_folder)
556            self.client.idle()
557
558            # Start a new connection and upload a new message
559            client2 = create_client_from_config(conf)
560            client2.select_folder(self.base_folder)
561            client2.append(self.base_folder, SIMPLE_MESSAGE)
562
563            # Check for the idle data
564            responses = self.client.idle_check(timeout=5)
565            text, more_responses = self.client.idle_done()
566            self.assertIn((1, 'EXISTS'), responses)
567            self.assertTrue(isinstance(text, str))
568            self.assertGreater(len(text), 0)
569            self.assertTrue(isinstance(more_responses, list))
570
571            # Check for IDLE data returned by idle_done()
572            self.client.idle()
573            client2.select_folder(self.base_folder)
574            client2.append(self.base_folder, SIMPLE_MESSAGE)
575            time.sleep(2)    # Allow some time for the IDLE response to be sent
576
577            text, responses = self.client.idle_done()
578            self.assertIn((2, 'EXISTS'), responses)
579            self.assertTrue(isinstance(text, str))
580            self.assertGreater(len(text), 0)
581
582        def test_noop(self):
583            self.client.select_folder(self.base_folder)
584
585            # Initially there should be no responses
586            text, resps = self.client.noop()
587            self.assertTrue(isinstance(text, str))
588            self.assertGreater(len(text), 0)
589            self.assertEquals(resps, [])
590
591            # Start a new connection and upload a new message
592            client2 = create_client_from_config(conf)
593            client2.select_folder(self.base_folder)
594            client2.append(self.base_folder, SIMPLE_MESSAGE)
595
596            # Check for this addition in the NOOP data
597            msg, resps = self.client.noop()
598            self.assertTrue(isinstance(text, str))
599            self.assertGreater(len(text), 0)
600            self.assertTrue(isinstance(resps, list))
601            self.assertIn((1, 'EXISTS'), resps)
602
603        def test_expunge(self):
604            self.client.select_folder(self.base_folder)
605
606            # Test empty mailbox
607            text, resps = self.client.expunge()
608            self.assertTrue(isinstance(text, str))
609            self.assertGreater(len(text), 0)
610            # Some servers return nothing while others (e.g. Exchange) return (0, 'EXISTS')
611            self.assertIn(resps, ([], [(0, 'EXISTS')]))
612
613            # Now try with a message to expunge
614            self.client.append(self.base_folder, SIMPLE_MESSAGE, flags=[imapclient.DELETED])
615
616            msg, resps = self.client.expunge()
617
618            self.assertTrue(isinstance(text, str))
619            self.assertGreater(len(text), 0)
620            self.assertTrue(isinstance(resps, list))
621            if not self.is_gmail():
622                # GMail has an auto-expunge feature which might be
623                # on. EXPUNGE won't return anything in this case
624                self.assertIn((1, 'EXPUNGE'), resps)
625
626        def test_getacl(self):
627            self.skip_unless_capable('ACL')
628
629            folder = self.add_prefix_to_folder('test_acl')
630            who = conf['username']
631            self.client.create_folder(folder)
632
633            rights = self.client.getacl(folder)
634            self.assertIn(who, [u for u, r in rights])
635
636    return LiveTest
637
638
639def lower_if_str(val):
640    if isinstance(val, basestring):
641        return val.lower()
642    return val
643
644def have_matching_types(a, b, type_or_types):
645    """True if a and b are instances of the same type and that type is
646    one of type_or_types.
647    """
648    if not isinstance(a, type_or_types):
649        return False
650    return isinstance(b, type(a))
651
652def argv_error(msg):
653    print >> sys.stderr, msg
654    print >> sys.stderr
655    print >> sys.stderr, "usage: %s <livetest.ini> [ optional unittest arguments ]" % sys.argv[0]
656    sys.exit(1)
657
658def parse_argv():
659    args = sys.argv[1:]
660    if not args:
661        argv_error('Please specify a host configuration file. See livetest-sample.ini for an example.')
662    ini_path = sys.argv.pop(1)  # 2nd arg should be the INI file
663    if not os.path.isfile(ini_path):
664        argv_error('%r is not a livetest INI file' % ini_path)
665    host_config = parse_config_file(ini_path)
666    return host_config
667
668def probe_host(config):
669    client = create_client_from_config(config)
670    ns = client.namespace()
671    client.logout()
672    if not ns.personal:
673        raise RuntimeError('Can\'t run tests: IMAP account has no personal namespace')
674    return ns.personal[0]   # Use first personal namespace
675
676def main():
677    host_config = parse_argv()
678
679    namespace = probe_host(host_config)
680    host_config.namespace = namespace
681
682    live_test_mod = imp.new_module('livetests')
683    sys.modules['livetests'] = live_test_mod
684
685    def add_test_class(name, klass):
686       klass.__name__ = name
687       setattr(live_test_mod, name, klass)
688
689    add_test_class('TestWithUIDs', createLiveTestClass(host_config, use_uid=True))
690    add_test_class('TestWithoutUIDs', createLiveTestClass(host_config, use_uid=False))
691
692    unittest.main(module='livetests')
693
694if __name__ == '__main__':
695    main()
Note: See TracBrowser for help on using the browser.