Code

Optimize mailgw and cgi tests by creating an empty instance before the tests
[roundup.git] / test / test_cgi.py
1 #
2 # Copyright (c) 2003 Richard Jones, rjones@ekit-inc.com
3 # This module is free software, and you may redistribute it and/or modify
4 # under the same terms as Python, so long as this copyright message and
5 # disclaimer are retained in their original form.
6 #
7 # This module is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10 #
11 # $Id: test_cgi.py,v 1.19 2003-09-07 20:37:33 jlgijsbers Exp $
13 import unittest, os, shutil, errno, sys, difflib, cgi, re
15 from roundup.cgi import client
16 from roundup import init, instance, password, hyperdb, date
18 NEEDS_INSTANCE = 1
20 class FileUpload:
21     def __init__(self, content, filename):
22         self.content = content
23         self.filename = filename
25 def makeForm(args):
26     form = cgi.FieldStorage()
27     for k,v in args.items():
28         if type(v) is type([]):
29             [form.list.append(cgi.MiniFieldStorage(k, x)) for x in v]
30         elif isinstance(v, FileUpload):
31             x = cgi.MiniFieldStorage(k, v.content)
32             x.filename = v.filename
33             form.list.append(x)
34         else:
35             form.list.append(cgi.MiniFieldStorage(k, v))
36     return form
38 class config:
39     TRACKER_NAME = 'testing testing'
40     TRACKER_WEB = 'http://testing.testing/'
42 cm = client.clean_message
43 class MessageTestCase(unittest.TestCase):
44     def testCleanMessageOK(self):
45         self.assertEqual(cm('<br>x<br />'), '<br>x<br />')
46         self.assertEqual(cm('<i>x</i>'), '<i>x</i>')
47         self.assertEqual(cm('<b>x</b>'), '<b>x</b>')
48         self.assertEqual(cm('<a href="y">x</a>'),
49             '<a href="y">x</a>')
50         self.assertEqual(cm('<BR>x<BR />'), '<BR>x<BR />')
51         self.assertEqual(cm('<I>x</I>'), '<I>x</I>')
52         self.assertEqual(cm('<B>x</B>'), '<B>x</B>')
53         self.assertEqual(cm('<A HREF="y">x</A>'),
54             '<A HREF="y">x</A>')
56     def testCleanMessageBAD(self):
57         self.assertEqual(cm('<script>x</script>'),
58             '&lt;script&gt;x&lt;/script&gt;')
59         self.assertEqual(cm('<iframe>x</iframe>'),
60             '&lt;iframe&gt;x&lt;/iframe&gt;')
62 class FormTestCase(unittest.TestCase):
63     def setUp(self):
64         self.dirname = '_test_cgi_form'
65         try:
66             shutil.rmtree(self.dirname)
67         except OSError, error:
68             if error.errno not in (errno.ENOENT, errno.ESRCH): raise
69         # create the instance
70         shutil.copytree('_empty_instance', self.dirname)
71         
72         # check we can load the package
73         self.instance = instance.open(self.dirname)
74         # and open the database
75         self.db = self.instance.open('admin')
76         self.db.user.create(username='Chef', address='chef@bork.bork.bork',
77             realname='Bork, Chef', roles='User')
78         self.db.user.create(username='mary', address='mary@test',
79             roles='User', realname='Contrary, Mary')
81         test = self.instance.dbinit.Class(self.db, "test",
82             string=hyperdb.String(), number=hyperdb.Number(),
83             boolean=hyperdb.Boolean(), link=hyperdb.Link('test'),
84             multilink=hyperdb.Multilink('test'), date=hyperdb.Date(),
85             interval=hyperdb.Interval())
87         # compile the labels re
88         classes = '|'.join(self.db.classes.keys())
89         self.FV_SPECIAL = re.compile(client.Client.FV_LABELS%classes,
90             re.VERBOSE)
92     def parseForm(self, form, classname='test', nodeid=None):
93         cl = client.Client(self.instance, None, {'PATH_INFO':'/'},
94             makeForm(form))
95         cl.classname = classname
96         cl.nodeid = nodeid
97         cl.db = self.db
98         return cl.parsePropsFromForm()
100     def tearDown(self):
101         self.db.close()
102         try:
103             shutil.rmtree(self.dirname)
104         except OSError, error:
105             if error.errno not in (errno.ENOENT, errno.ESRCH): raise
107     #
108     # form label extraction
109     #
110     def tl(self, s, c, i, a, p):
111         m = self.FV_SPECIAL.match(s)
112         self.assertNotEqual(m, None)
113         d = m.groupdict()
114         self.assertEqual(d['classname'], c)
115         self.assertEqual(d['id'], i)
116         for action in 'required add remove link note file'.split():
117             if a == action:
118                 self.assertNotEqual(d[action], None)
119             else:
120                 self.assertEqual(d[action], None)
121         self.assertEqual(d['propname'], p)
123     def testLabelMatching(self):
124         self.tl('<propname>', None, None, None, '<propname>')
125         self.tl(':required', None, None, 'required', None)
126         self.tl(':confirm:<propname>', None, None, 'confirm', '<propname>')
127         self.tl(':add:<propname>', None, None, 'add', '<propname>')
128         self.tl(':remove:<propname>', None, None, 'remove', '<propname>')
129         self.tl(':link:<propname>', None, None, 'link', '<propname>')
130         self.tl('test1:<prop>', 'test', '1', None, '<prop>')
131         self.tl('test1:required', 'test', '1', 'required', None)
132         self.tl('test1:add:<prop>', 'test', '1', 'add', '<prop>')
133         self.tl('test1:remove:<prop>', 'test', '1', 'remove', '<prop>')
134         self.tl('test1:link:<prop>', 'test', '1', 'link', '<prop>')
135         self.tl('test1:confirm:<prop>', 'test', '1', 'confirm', '<prop>')
136         self.tl('test-1:<prop>', 'test', '-1', None, '<prop>')
137         self.tl('test-1:required', 'test', '-1', 'required', None)
138         self.tl('test-1:add:<prop>', 'test', '-1', 'add', '<prop>')
139         self.tl('test-1:remove:<prop>', 'test', '-1', 'remove', '<prop>')
140         self.tl('test-1:link:<prop>', 'test', '-1', 'link', '<prop>')
141         self.tl('test-1:confirm:<prop>', 'test', '-1', 'confirm', '<prop>')
142         self.tl(':note', None, None, 'note', None)
143         self.tl(':file', None, None, 'file', None)
145     #
146     # Empty form
147     #
148     def testNothing(self):
149         self.assertEqual(self.parseForm({}), ({('test', None): {}}, []))
151     def testNothingWithRequired(self):
152         self.assertRaises(ValueError, self.parseForm, {':required': 'string'})
153         self.assertRaises(ValueError, self.parseForm,
154             {':required': 'title,status', 'status':'1'}, 'issue')
155         self.assertRaises(ValueError, self.parseForm,
156             {':required': ['title','status'], 'status':'1'}, 'issue')
157         self.assertRaises(ValueError, self.parseForm,
158             {':required': 'status', 'status':''}, 'issue')
159         self.assertRaises(ValueError, self.parseForm,
160             {':required': 'nosy', 'nosy':''}, 'issue')
162     #
163     # Nonexistant edit
164     #
165     def testEditNonexistant(self):
166         self.assertRaises(IndexError, self.parseForm, {'boolean': ''},
167             'test', '1')
169     #
170     # String
171     #
172     def testEmptyString(self):
173         self.assertEqual(self.parseForm({'string': ''}),
174             ({('test', None): {}}, []))
175         self.assertEqual(self.parseForm({'string': ' '}),
176             ({('test', None): {}}, []))
177         self.assertRaises(ValueError, self.parseForm, {'string': ['', '']})
179     def testSetString(self):
180         self.assertEqual(self.parseForm({'string': 'foo'}),
181             ({('test', None): {'string': 'foo'}}, []))
182         self.assertEqual(self.parseForm({'string': 'a\r\nb\r\n'}),
183             ({('test', None): {'string': 'a\nb'}}, []))
184         nodeid = self.db.issue.create(title='foo')
185         self.assertEqual(self.parseForm({'title': 'foo'}, 'issue', nodeid),
186             ({('issue', nodeid): {}}, []))
188     def testEmptyStringSet(self):
189         nodeid = self.db.issue.create(title='foo')
190         self.assertEqual(self.parseForm({'title': ''}, 'issue', nodeid),
191             ({('issue', nodeid): {'title': None}}, []))
192         nodeid = self.db.issue.create(title='foo')
193         self.assertEqual(self.parseForm({'title': ' '}, 'issue', nodeid),
194             ({('issue', nodeid): {'title': None}}, []))
196     def testFileUpload(self):
197         file = FileUpload('foo', 'foo.txt')
198         self.assertEqual(self.parseForm({'content': file}, 'file'),
199             ({('file', None): {'content': 'foo', 'name': 'foo.txt',
200             'type': 'text/plain'}}, []))
202     def testEditFileClassAttributes(self):
203         self.assertEqual(self.parseForm({'name': 'foo.txt',
204                                          'type': 'application/octet-stream'},
205                                         'file'),
206                          ({('file', None): {'name': 'foo.txt',
207                                             'type': 'application/octet-stream'}},[]))
209     #
210     # Link
211     #
212     def testEmptyLink(self):
213         self.assertEqual(self.parseForm({'link': ''}),
214             ({('test', None): {}}, []))
215         self.assertEqual(self.parseForm({'link': ' '}),
216             ({('test', None): {}}, []))
217         self.assertRaises(ValueError, self.parseForm, {'link': ['', '']})
218         self.assertEqual(self.parseForm({'link': '-1'}),
219             ({('test', None): {}}, []))
221     def testSetLink(self):
222         self.assertEqual(self.parseForm({'status': 'unread'}, 'issue'),
223             ({('issue', None): {'status': '1'}}, []))
224         self.assertEqual(self.parseForm({'status': '1'}, 'issue'),
225             ({('issue', None): {'status': '1'}}, []))
226         nodeid = self.db.issue.create(status='unread')
227         self.assertEqual(self.parseForm({'status': 'unread'}, 'issue', nodeid),
228             ({('issue', nodeid): {}}, []))
230     def testUnsetLink(self):
231         nodeid = self.db.issue.create(status='unread')
232         self.assertEqual(self.parseForm({'status': '-1'}, 'issue', nodeid),
233             ({('issue', nodeid): {'status': None}}, []))
235     def testInvalidLinkValue(self):
236 # XXX This is not the current behaviour - should we enforce this?
237 #        self.assertRaises(IndexError, self.parseForm,
238 #            {'status': '4'}))
239         self.assertRaises(ValueError, self.parseForm, {'link': 'frozzle'})
240         self.assertRaises(ValueError, self.parseForm, {'status': 'frozzle'},
241             'issue')
243     #
244     # Multilink
245     #
246     def testEmptyMultilink(self):
247         self.assertEqual(self.parseForm({'nosy': ''}),
248             ({('test', None): {}}, []))
249         self.assertEqual(self.parseForm({'nosy': ' '}),
250             ({('test', None): {}}, []))
252     def testSetMultilink(self):
253         self.assertEqual(self.parseForm({'nosy': '1'}, 'issue'),
254             ({('issue', None): {'nosy': ['1']}}, []))
255         self.assertEqual(self.parseForm({'nosy': 'admin'}, 'issue'),
256             ({('issue', None): {'nosy': ['1']}}, []))
257         self.assertEqual(self.parseForm({'nosy': ['1','2']}, 'issue'),
258             ({('issue', None): {'nosy': ['1','2']}}, []))
259         self.assertEqual(self.parseForm({'nosy': '1,2'}, 'issue'),
260             ({('issue', None): {'nosy': ['1','2']}}, []))
261         self.assertEqual(self.parseForm({'nosy': 'admin,2'}, 'issue'),
262             ({('issue', None): {'nosy': ['1','2']}}, []))
264     def testMixedMultilink(self):
265         form = cgi.FieldStorage()
266         form.list.append(cgi.MiniFieldStorage('nosy', '1,2'))
267         form.list.append(cgi.MiniFieldStorage('nosy', '3'))
268         cl = client.Client(self.instance, None, {'PATH_INFO':'/'}, form)
269         cl.classname = 'issue'
270         cl.nodeid = None
271         cl.db = self.db
272         self.assertEqual(cl.parsePropsFromForm(), 
273             ({('issue', None): {'nosy': ['1','2', '3']}}, []))
275     def testEmptyMultilinkSet(self):
276         nodeid = self.db.issue.create(nosy=['1','2'])
277         self.assertEqual(self.parseForm({'nosy': ''}, 'issue', nodeid), 
278             ({('issue', nodeid): {'nosy': []}}, []))
279         nodeid = self.db.issue.create(nosy=['1','2'])
280         self.assertEqual(self.parseForm({'nosy': ' '}, 'issue', nodeid), 
281             ({('issue', nodeid): {'nosy': []}}, []))
282         self.assertEqual(self.parseForm({'nosy': '1,2'}, 'issue', nodeid),
283             ({('issue', nodeid): {}}, []))
285     def testInvalidMultilinkValue(self):
286 # XXX This is not the current behaviour - should we enforce this?
287 #        self.assertRaises(IndexError, self.parseForm,
288 #            {'nosy': '4'}))
289         self.assertRaises(ValueError, self.parseForm, {'nosy': 'frozzle'},
290             'issue')
291         self.assertRaises(ValueError, self.parseForm, {'nosy': '1,frozzle'},
292             'issue')
293         self.assertRaises(ValueError, self.parseForm, {'multilink': 'frozzle'})
295     def testMultilinkAdd(self):
296         nodeid = self.db.issue.create(nosy=['1'])
297         # do nothing
298         self.assertEqual(self.parseForm({':add:nosy': ''}, 'issue', nodeid),
299             ({('issue', nodeid): {}}, []))
301         # do something ;)
302         self.assertEqual(self.parseForm({':add:nosy': '2'}, 'issue', nodeid),
303             ({('issue', nodeid): {'nosy': ['1','2']}}, []))
304         self.assertEqual(self.parseForm({':add:nosy': '2,mary'}, 'issue',
305             nodeid), ({('issue', nodeid): {'nosy': ['1','2','4']}}, []))
306         self.assertEqual(self.parseForm({':add:nosy': ['2','3']}, 'issue',
307             nodeid), ({('issue', nodeid): {'nosy': ['1','2','3']}}, []))
309     def testMultilinkAddNew(self):
310         self.assertEqual(self.parseForm({':add:nosy': ['2','3']}, 'issue'),
311             ({('issue', None): {'nosy': ['2','3']}}, []))
313     def testMultilinkRemove(self):
314         nodeid = self.db.issue.create(nosy=['1','2'])
315         # do nothing
316         self.assertEqual(self.parseForm({':remove:nosy': ''}, 'issue', nodeid),
317             ({('issue', nodeid): {}}, []))
319         # do something ;)
320         self.assertEqual(self.parseForm({':remove:nosy': '1'}, 'issue',
321             nodeid), ({('issue', nodeid): {'nosy': ['2']}}, []))
322         self.assertEqual(self.parseForm({':remove:nosy': 'admin,2'},
323             'issue', nodeid), ({('issue', nodeid): {'nosy': []}}, []))
324         self.assertEqual(self.parseForm({':remove:nosy': ['1','2']},
325             'issue', nodeid), ({('issue', nodeid): {'nosy': []}}, []))
327         # add and remove
328         self.assertEqual(self.parseForm({':add:nosy': ['3'],
329             ':remove:nosy': ['1','2']},
330             'issue', nodeid), ({('issue', nodeid): {'nosy': ['3']}}, []))
332         # remove one that doesn't exist?
333         self.assertRaises(ValueError, self.parseForm, {':remove:nosy': '4'},
334             'issue', nodeid)
336     def testMultilinkRetired(self):
337         self.db.user.retire('2')
338         self.assertEqual(self.parseForm({'nosy': ['2','3']}, 'issue'),
339             ({('issue', None): {'nosy': ['2','3']}}, []))
340         nodeid = self.db.issue.create(nosy=['1','2'])
341         self.assertEqual(self.parseForm({':remove:nosy': '2'}, 'issue',
342             nodeid), ({('issue', nodeid): {'nosy': ['1']}}, []))
343         self.assertEqual(self.parseForm({':add:nosy': '3'}, 'issue', nodeid),
344             ({('issue', nodeid): {'nosy': ['1','2','3']}}, []))
346     def testAddRemoveNonexistant(self):
347         self.assertRaises(ValueError, self.parseForm, {':remove:foo': '2'},
348             'issue')
349         self.assertRaises(ValueError, self.parseForm, {':add:foo': '2'},
350             'issue')
352     #
353     # Password
354     #
355     def testEmptyPassword(self):
356         self.assertEqual(self.parseForm({'password': ''}, 'user'),
357             ({('user', None): {}}, []))
358         self.assertEqual(self.parseForm({'password': ''}, 'user'),
359             ({('user', None): {}}, []))
360         self.assertRaises(ValueError, self.parseForm, {'password': ['', '']},
361             'user')
362         self.assertRaises(ValueError, self.parseForm, {'password': 'foo',
363             ':confirm:password': ['', '']}, 'user')
365     def testSetPassword(self):
366         self.assertEqual(self.parseForm({'password': 'foo',
367             ':confirm:password': 'foo'}, 'user'),
368             ({('user', None): {'password': 'foo'}}, []))
370     def testSetPasswordConfirmBad(self):
371         self.assertRaises(ValueError, self.parseForm, {'password': 'foo'},
372             'user')
373         self.assertRaises(ValueError, self.parseForm, {'password': 'foo',
374             ':confirm:password': 'bar'}, 'user')
376     def testEmptyPasswordNotSet(self):
377         nodeid = self.db.user.create(username='1',
378             password=password.Password('foo'))
379         self.assertEqual(self.parseForm({'password': ''}, 'user', nodeid),
380             ({('user', nodeid): {}}, []))
381         nodeid = self.db.user.create(username='2',
382             password=password.Password('foo'))
383         self.assertEqual(self.parseForm({'password': '',
384             ':confirm:password': ''}, 'user', nodeid),
385             ({('user', nodeid): {}}, []))
387     #
388     # Boolean
389     #
390     def testEmptyBoolean(self):
391         self.assertEqual(self.parseForm({'boolean': ''}),
392             ({('test', None): {}}, []))
393         self.assertEqual(self.parseForm({'boolean': ' '}),
394             ({('test', None): {}}, []))
395         self.assertRaises(ValueError, self.parseForm, {'boolean': ['', '']})
397     def testSetBoolean(self):
398         self.assertEqual(self.parseForm({'boolean': 'yes'}),
399             ({('test', None): {'boolean': 1}}, []))
400         self.assertEqual(self.parseForm({'boolean': 'a\r\nb\r\n'}),
401             ({('test', None): {'boolean': 0}}, []))
402         nodeid = self.db.test.create(boolean=1)
403         self.assertEqual(self.parseForm({'boolean': 'yes'}, 'test', nodeid),
404             ({('test', nodeid): {}}, []))
405         nodeid = self.db.test.create(boolean=0)
406         self.assertEqual(self.parseForm({'boolean': 'no'}, 'test', nodeid),
407             ({('test', nodeid): {}}, []))
409     def testEmptyBooleanSet(self):
410         nodeid = self.db.test.create(boolean=0)
411         self.assertEqual(self.parseForm({'boolean': ''}, 'test', nodeid),
412             ({('test', nodeid): {'boolean': None}}, []))
413         nodeid = self.db.test.create(boolean=1)
414         self.assertEqual(self.parseForm({'boolean': ' '}, 'test', nodeid),
415             ({('test', nodeid): {'boolean': None}}, []))
417     #
418     # Number
419     #
420     def testEmptyNumber(self):
421         self.assertEqual(self.parseForm({'number': ''}),
422             ({('test', None): {}}, []))
423         self.assertEqual(self.parseForm({'number': ' '}),
424             ({('test', None): {}}, []))
425         self.assertRaises(ValueError, self.parseForm, {'number': ['', '']})
427     def testInvalidNumber(self):
428         self.assertRaises(ValueError, self.parseForm, {'number': 'hi, mum!'})
430     def testSetNumber(self):
431         self.assertEqual(self.parseForm({'number': '1'}),
432             ({('test', None): {'number': 1}}, []))
433         self.assertEqual(self.parseForm({'number': '\n0\n'}),
434             ({('test', None): {'number': 0}}, []))
435         nodeid = self.db.test.create(number=1)
436         self.assertEqual(self.parseForm({'number': '1'}, 'test', nodeid),
437             ({('test', nodeid): {}}, []))
438         nodeid = self.db.test.create(number=0)
439         self.assertEqual(self.parseForm({'number': '0'}, 'test', nodeid),
440             ({('test', nodeid): {}}, []))
442     def testEmptyNumberSet(self):
443         nodeid = self.db.test.create(number=0)
444         self.assertEqual(self.parseForm({'number': ''}, 'test', nodeid),
445             ({('test', nodeid): {'number': None}}, []))
446         nodeid = self.db.test.create(number=1)
447         self.assertEqual(self.parseForm({'number': ' '}, 'test', nodeid),
448             ({('test', nodeid): {'number': None}}, []))
450     #
451     # Date
452     #
453     def testEmptyDate(self):
454         self.assertEqual(self.parseForm({'date': ''}),
455             ({('test', None): {}}, []))
456         self.assertEqual(self.parseForm({'date': ' '}),
457             ({('test', None): {}}, []))
458         self.assertRaises(ValueError, self.parseForm, {'date': ['', '']})
460     def testInvalidDate(self):
461         self.assertRaises(ValueError, self.parseForm, {'date': '12'})
463     def testSetDate(self):
464         self.assertEqual(self.parseForm({'date': '2003-01-01'}),
465             ({('test', None): {'date': date.Date('2003-01-01')}}, []))
466         nodeid = self.db.test.create(date=date.Date('2003-01-01'))
467         self.assertEqual(self.parseForm({'date': '2003-01-01'}, 'test', 
468             nodeid), ({('test', nodeid): {}}, []))
470     def testEmptyDateSet(self):
471         nodeid = self.db.test.create(date=date.Date('.'))
472         self.assertEqual(self.parseForm({'date': ''}, 'test', nodeid), 
473             ({('test', nodeid): {'date': None}}, []))
474         nodeid = self.db.test.create(date=date.Date('1970-01-01.00:00:00'))
475         self.assertEqual(self.parseForm({'date': ' '}, 'test', nodeid), 
476             ({('test', nodeid): {'date': None}}, []))
478     #
479     # Test multiple items in form
480     #
481     def testMultiple(self):
482         self.assertEqual(self.parseForm({'string': 'a', 'issue-1@title': 'b'}),
483             ({('test', None): {'string': 'a'},
484               ('issue', '-1'): {'title': 'b'}
485              }, []))
487     def testMultipleExistingContext(self):
488         nodeid = self.db.test.create()
489         self.assertEqual(self.parseForm({'string': 'a', 'issue-1@title': 'b'},
490             'test', nodeid),({('test', nodeid): {'string': 'a'},
491             ('issue', '-1'): {'title': 'b'}}, []))
493     def testLinking(self):
494         self.assertEqual(self.parseForm({
495             'string': 'a',
496             'issue-1@add@nosy': '1',
497             'issue-2@link@superseder': 'issue-1',
498             }),
499             ({('test', None): {'string': 'a'},
500               ('issue', '-1'): {'nosy': ['1']},
501               ('issue', '-2'): {}
502              },
503              [('issue', '-2', 'superseder', [('issue', '-1')])
504              ]
505             )
506         )
508     def testLinkBadDesignator(self):
509         self.assertRaises(ValueError, self.parseForm,
510             {'test-1@link@link': 'blah'})
511         self.assertRaises(ValueError, self.parseForm,
512             {'test-1@link@link': 'issue'})
514     def testLinkNotLink(self):
515         self.assertRaises(ValueError, self.parseForm,
516             {'test-1@link@boolean': 'issue-1'})
517         self.assertRaises(ValueError, self.parseForm,
518             {'test-1@link@string': 'issue-1'})
520     def testBackwardsCompat(self):
521         res = self.parseForm({':note': 'spam'}, 'issue')
522         date = res[0][('msg', '-1')]['date']
523         self.assertEqual(res, ({('issue', None): {}, ('msg', '-1'):
524             {'content': 'spam', 'author': '1', 'date': date}},
525             [('issue', None, 'messages', [('msg', '-1')])]))
526         file = FileUpload('foo', 'foo.txt')
527         self.assertEqual(self.parseForm({':file': file}, 'issue'),
528             ({('issue', None): {}, ('file', '-1'): {'content': 'foo',
529             'name': 'foo.txt', 'type': 'text/plain'}},
530             [('issue', None, 'files', [('file', '-1')])]))
532 def suite():
533     l = [
534         unittest.makeSuite(FormTestCase),
535         unittest.makeSuite(MessageTestCase),
536     ]
537     return unittest.TestSuite(l)
539 def run():
540     runner = unittest.TextTestRunner()
541     unittest.main(testRunner=runner)
543 if __name__ == '__main__':
544     run()
546 # vim: set filetype=python ts=4 sw=4 et si