]> git.ozlabs.org Git - patchwork/blob - patchwork/tests/test_patchparser.py
58689bbd86c98d5a990d9e911eeb54b5ffa48aaf
[patchwork] / patchwork / tests / test_patchparser.py
1 # Patchwork - automated patch tracking system
2 # Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
3 #
4 # This file is part of the Patchwork package.
5 #
6 # Patchwork is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # Patchwork is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with Patchwork; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20 import os
21 from email import message_from_string
22 from django.test import TestCase
23 from patchwork.models import Project, Person, Patch, Comment, State, \
24          get_default_initial_patch_state
25 from patchwork.tests.utils import read_patch, read_mail, create_email, \
26          defaults, create_user
27
28 try:
29     from email.mime.text import MIMEText
30 except ImportError:
31     # Python 2.4 compatibility
32     from email.MIMEText import MIMEText
33
34 class PatchTest(TestCase):
35     fixtures = ['default_states']
36     default_sender = defaults.sender
37     default_subject = defaults.subject
38     project = defaults.project
39
40 from patchwork.bin.parsemail import find_content, find_author, find_project, \
41                                     parse_mail, split_prefixes, clean_subject
42
43 class InlinePatchTest(PatchTest):
44     patch_filename = '0001-add-line.patch'
45     test_comment = 'Test for attached patch'
46
47     def setUp(self):
48         self.orig_patch = read_patch(self.patch_filename)
49         email = create_email(self.test_comment + '\n' + self.orig_patch)
50         (self.patch, self.comment) = find_content(self.project, email)
51
52     def testPatchPresence(self):
53         self.assertTrue(self.patch is not None)
54
55     def testPatchContent(self):
56         self.assertEquals(self.patch.content, self.orig_patch)
57
58     def testCommentPresence(self):
59         self.assertTrue(self.comment is not None)
60
61     def testCommentContent(self):
62         self.assertEquals(self.comment.content, self.test_comment)
63
64
65 class AttachmentPatchTest(InlinePatchTest):
66     patch_filename = '0001-add-line.patch'
67     test_comment = 'Test for attached patch'
68     content_subtype = 'x-patch'
69
70     def setUp(self):
71         self.orig_patch = read_patch(self.patch_filename)
72         email = create_email(self.test_comment, multipart = True)
73         attachment = MIMEText(self.orig_patch, _subtype = self.content_subtype)
74         email.attach(attachment)
75         (self.patch, self.comment) = find_content(self.project, email)
76
77 class AttachmentXDiffPatchTest(AttachmentPatchTest):
78     content_subtype = 'x-diff'
79
80 class UTF8InlinePatchTest(InlinePatchTest):
81     patch_filename = '0002-utf-8.patch'
82     patch_encoding = 'utf-8'
83
84     def setUp(self):
85         self.orig_patch = read_patch(self.patch_filename, self.patch_encoding)
86         email = create_email(self.test_comment + '\n' + self.orig_patch,
87                              content_encoding = self.patch_encoding)
88         (self.patch, self.comment) = find_content(self.project, email)
89
90 class NoCharsetInlinePatchTest(InlinePatchTest):
91     """ Test mails with no content-type or content-encoding header """
92     patch_filename = '0001-add-line.patch'
93
94     def setUp(self):
95         self.orig_patch = read_patch(self.patch_filename)
96         email = create_email(self.test_comment + '\n' + self.orig_patch)
97         del email['Content-Type']
98         del email['Content-Transfer-Encoding']
99         (self.patch, self.comment) = find_content(self.project, email)
100
101 class SignatureCommentTest(InlinePatchTest):
102     patch_filename = '0001-add-line.patch'
103     test_comment = 'Test comment\nmore comment'
104
105     def setUp(self):
106         self.orig_patch = read_patch(self.patch_filename)
107         email = create_email( \
108                 self.test_comment + '\n' + \
109                 '-- \nsig\n' + self.orig_patch)
110         (self.patch, self.comment) = find_content(self.project, email)
111
112
113 class ListFooterTest(InlinePatchTest):
114     patch_filename = '0001-add-line.patch'
115     test_comment = 'Test comment\nmore comment'
116
117     def setUp(self):
118         self.orig_patch = read_patch(self.patch_filename)
119         email = create_email( \
120                 self.test_comment + '\n' + \
121                 '_______________________________________________\n' + \
122                 'Linuxppc-dev mailing list\n' + \
123                 self.orig_patch)
124         (self.patch, self.comment) = find_content(self.project, email)
125
126
127 class DiffWordInCommentTest(InlinePatchTest):
128     test_comment = 'Lines can start with words beginning in "diff"\n' + \
129                    'difficult\nDifferent'
130
131
132 class UpdateCommentTest(InlinePatchTest):
133     """ Test for '---\nUpdate: v2' style comments to patches. """
134     patch_filename = '0001-add-line.patch'
135     test_comment = 'Test comment\nmore comment\n---\nUpdate: test update'
136
137 class UpdateSigCommentTest(SignatureCommentTest):
138     """ Test for '---\nUpdate: v2' style comments to patches, with a sig """
139     patch_filename = '0001-add-line.patch'
140     test_comment = 'Test comment\nmore comment\n---\nUpdate: test update'
141
142 class SenderEncodingTest(TestCase):
143     sender_name = u'example user'
144     sender_email = 'user@example.com'
145     from_header = 'example user <user@example.com>'
146
147     def setUp(self):
148         mail = 'From: %s\n' % self.from_header + \
149                'Subject: test\n\n' + \
150                'test'
151         self.email = message_from_string(mail)
152         (self.person, new) = find_author(self.email)
153         self.person.save()
154
155     def tearDown(self):
156         self.person.delete()
157
158     def testName(self):
159         self.assertEquals(self.person.name, self.sender_name)
160
161     def testEmail(self):
162         self.assertEquals(self.person.email, self.sender_email)
163
164     def testDBQueryName(self):
165         db_person = Person.objects.get(name = self.sender_name)
166         self.assertEquals(self.person, db_person)
167
168     def testDBQueryEmail(self):
169         db_person = Person.objects.get(email = self.sender_email)
170         self.assertEquals(self.person, db_person)
171
172
173 class SenderUTF8QPEncodingTest(SenderEncodingTest):
174     sender_name = u'\xe9xample user'
175     from_header = '=?utf-8?q?=C3=A9xample=20user?= <user@example.com>'
176
177 class SenderUTF8QPSplitEncodingTest(SenderEncodingTest):
178     sender_name = u'\xe9xample user'
179     from_header = '=?utf-8?q?=C3=A9xample?= user <user@example.com>'
180
181 class SenderUTF8B64EncodingTest(SenderUTF8QPEncodingTest):
182     from_header = '=?utf-8?B?w6l4YW1wbGUgdXNlcg==?= <user@example.com>'
183
184 class SubjectEncodingTest(PatchTest):
185     sender = 'example user <user@example.com>'
186     subject = 'test subject'
187     subject_header = 'test subject'
188
189     def setUp(self):
190         mail = 'From: %s\n' % self.sender + \
191                'Subject: %s\n\n' % self.subject_header + \
192                'test\n\n' + defaults.patch
193         self.projects = defaults.project
194         self.email = message_from_string(mail)
195
196     def testSubjectEncoding(self):
197         (patch, comment) = find_content(self.project, self.email)
198         self.assertEquals(patch.name, self.subject)
199
200 class SubjectUTF8QPEncodingTest(SubjectEncodingTest):
201     subject = u'test s\xfcbject'
202     subject_header = '=?utf-8?q?test=20s=c3=bcbject?='
203
204 class SubjectUTF8QPMultipleEncodingTest(SubjectEncodingTest):
205     subject = u'test s\xfcbject'
206     subject_header = 'test =?utf-8?q?s=c3=bcbject?='
207
208 class SenderCorrelationTest(TestCase):
209     existing_sender = 'Existing Sender <existing@example.com>'
210     non_existing_sender = 'Non-existing Sender <nonexisting@example.com>'
211
212     def mail(self, sender):
213         return message_from_string('From: %s\nSubject: Test\n\ntest\n' % sender)
214
215     def setUp(self):
216         self.existing_sender_mail = self.mail(self.existing_sender)
217         self.non_existing_sender_mail = self.mail(self.non_existing_sender)
218         (self.person, new) = find_author(self.existing_sender_mail)
219         self.person.save()
220
221     def testExisingSender(self):
222         (person, new) = find_author(self.existing_sender_mail)
223         self.assertEqual(new, False)
224         self.assertEqual(person.id, self.person.id)
225
226     def testNonExisingSender(self):
227         (person, new) = find_author(self.non_existing_sender_mail)
228         self.assertEqual(new, True)
229         self.assertEqual(person.id, None)
230
231     def testExistingDifferentFormat(self):
232         mail = self.mail('existing@example.com')
233         (person, new) = find_author(mail)
234         self.assertEqual(new, False)
235         self.assertEqual(person.id, self.person.id)
236
237     def testExistingDifferentCase(self):
238         mail = self.mail(self.existing_sender.upper())
239         (person, new) = find_author(mail)
240         self.assertEqual(new, False)
241         self.assertEqual(person.id, self.person.id)
242
243     def tearDown(self):
244         self.person.delete()
245
246 class MultipleProjectPatchTest(TestCase):
247     """ Test that patches sent to multiple patchwork projects are
248         handled correctly """
249
250     fixtures = ['default_states']
251     test_comment = 'Test Comment'
252     patch_filename = '0001-add-line.patch'
253     msgid = '<1@example.com>'
254
255     def setUp(self):
256         self.p1 = Project(linkname = 'test-project-1', name = 'Project 1',
257                 listid = '1.example.com', listemail='1@example.com')
258         self.p2 = Project(linkname = 'test-project-2', name = 'Project 2',
259                 listid = '2.example.com', listemail='2@example.com')
260
261         self.p1.save()
262         self.p2.save()
263
264         patch = read_patch(self.patch_filename)
265         email = create_email(self.test_comment + '\n' + patch)
266         email['Message-Id'] = self.msgid
267
268         del email['List-ID']
269         email['List-ID'] = '<' + self.p1.listid + '>'
270         parse_mail(email)
271
272         del email['List-ID']
273         email['List-ID'] = '<' + self.p2.listid + '>'
274         parse_mail(email)
275
276     def testParsedProjects(self):
277         self.assertEquals(Patch.objects.filter(project = self.p1).count(), 1)
278         self.assertEquals(Patch.objects.filter(project = self.p2).count(), 1)
279
280     def tearDown(self):
281         self.p1.delete()
282         self.p2.delete()
283
284
285 class MultipleProjectPatchCommentTest(MultipleProjectPatchTest):
286     """ Test that followups to multiple-project patches end up on the
287         correct patch """
288
289     comment_msgid = '<2@example.com>'
290     comment_content = 'test comment'
291
292     def setUp(self):
293         super(MultipleProjectPatchCommentTest, self).setUp()
294
295         for project in [self.p1, self.p2]:
296             email = MIMEText(self.comment_content)
297             email['From'] = defaults.sender
298             email['Subject'] = defaults.subject
299             email['Message-Id'] = self.comment_msgid
300             email['List-ID'] = '<' + project.listid + '>'
301             email['In-Reply-To'] = self.msgid
302             parse_mail(email)
303
304     def testParsedComment(self):
305         for project in [self.p1, self.p2]:
306             patch = Patch.objects.filter(project = project)[0]
307             # we should see two comments now - the original mail with the patch,
308             # and the one we parsed in setUp()
309             self.assertEquals(Comment.objects.filter(patch = patch).count(), 2)
310
311 class ListIdHeaderTest(TestCase):
312     """ Test that we parse List-Id headers from mails correctly """
313     def setUp(self):
314         self.project = Project(linkname = 'test-project-1', name = 'Project 1',
315                 listid = '1.example.com', listemail='1@example.com')
316         self.project.save()
317
318     def testNoListId(self):
319         email = MIMEText('')
320         project = find_project(email)
321         self.assertEquals(project, None)
322
323     def testBlankListId(self):
324         email = MIMEText('')
325         email['List-Id'] = ''
326         project = find_project(email)
327         self.assertEquals(project, None)
328
329     def testWhitespaceListId(self):
330         email = MIMEText('')
331         email['List-Id'] = ' '
332         project = find_project(email)
333         self.assertEquals(project, None)
334
335     def testSubstringListId(self):
336         email = MIMEText('')
337         email['List-Id'] = 'example.com'
338         project = find_project(email)
339         self.assertEquals(project, None)
340
341     def testShortListId(self):
342         """ Some mailing lists have List-Id headers in short formats, where it
343             is only the list ID itself (without enclosing angle-brackets). """
344         email = MIMEText('')
345         email['List-Id'] = self.project.listid
346         project = find_project(email)
347         self.assertEquals(project, self.project)
348
349     def testLongListId(self):
350         email = MIMEText('')
351         email['List-Id'] = 'Test text <%s>' % self.project.listid
352         project = find_project(email)
353         self.assertEquals(project, self.project)
354
355     def tearDown(self):
356         self.project.delete()
357
358 class MBoxPatchTest(PatchTest):
359     def setUp(self):
360         self.mail = read_mail(self.mail_file, project = self.project)
361
362 class GitPullTest(MBoxPatchTest):
363     mail_file = '0001-git-pull-request.mbox'
364
365     def testGitPullRequest(self):
366         (patch, comment) = find_content(self.project, self.mail)
367         self.assertTrue(patch is not None)
368         self.assertTrue(patch.pull_url is not None)
369         self.assertTrue(patch.content is None)
370         self.assertTrue(comment is not None)
371
372 class GitPullWrappedTest(GitPullTest):
373     mail_file = '0002-git-pull-request-wrapped.mbox'
374
375 class GitPullWithDiffTest(MBoxPatchTest):
376     mail_file = '0003-git-pull-request-with-diff.mbox'
377
378     def testGitPullWithDiff(self):
379         (patch, comment) = find_content(self.project, self.mail)
380         self.assertTrue(patch is not None)
381         self.assertEqual('git://git.kernel.org/pub/scm/linux/kernel/git/tip/' +
382              'linux-2.6-tip.git x86-fixes-for-linus', patch.pull_url)
383         self.assertTrue(
384             patch.content.startswith('diff --git a/arch/x86/include/asm/smp.h'),
385             patch.content)
386         self.assertTrue(comment is not None)
387
388 class GitPullGitSSHUrlTest(GitPullTest):
389     mail_file = '0004-git-pull-request-git+ssh.mbox'
390
391 class GitPullSSHUrlTest(GitPullTest):
392     mail_file = '0005-git-pull-request-ssh.mbox'
393
394 class GitPullHTTPUrlTest(GitPullTest):
395     mail_file = '0006-git-pull-request-http.mbox'
396
397 class GitRenameOnlyTest(MBoxPatchTest):
398     mail_file = '0008-git-rename.mbox'
399
400     def testGitRename(self):
401         (patch, comment) = find_content(self.project, self.mail)
402         self.assertTrue(patch is not None)
403         self.assertTrue(comment is not None)
404         self.assertEqual(patch.content.count("\nrename from "), 2)
405         self.assertEqual(patch.content.count("\nrename to "), 2)
406
407 class GitRenameWithDiffTest(MBoxPatchTest):
408     mail_file = '0009-git-rename-with-diff.mbox'
409
410     def testGitRename(self):
411         (patch, comment) = find_content(self.project, self.mail)
412         self.assertTrue(patch is not None)
413         self.assertTrue(comment is not None)
414         self.assertEqual(patch.content.count("\nrename from "), 2)
415         self.assertEqual(patch.content.count("\nrename to "), 2)
416         self.assertEqual(patch.content.count('\n-a\n+b'), 1)
417
418 class CVSFormatPatchTest(MBoxPatchTest):
419     mail_file = '0007-cvs-format-diff.mbox'
420
421     def testPatch(self):
422         (patch, comment) = find_content(self.project, self.mail)
423         self.assertTrue(patch is not None)
424         self.assertTrue(comment is not None)
425         self.assertTrue(patch.content.startswith('Index'))
426
427 class CharsetFallbackPatchTest(MBoxPatchTest):
428     """ Test mail with and invalid charset name, and check that we can parse
429         with one of the fallback encodings"""
430
431     mail_file = '0010-invalid-charset.mbox'
432
433     def testPatch(self):
434         (patch, comment) = find_content(self.project, self.mail)
435         self.assertTrue(patch is not None)
436         self.assertTrue(comment is not None)
437
438 class NoNewlineAtEndOfFilePatchTest(MBoxPatchTest):
439     mail_file = '0011-no-newline-at-end-of-file.mbox'
440
441     def testPatch(self):
442         (patch, comment) = find_content(self.project, self.mail)
443         self.assertTrue(patch is not None)
444         self.assertTrue(comment is not None)
445         self.assertTrue(patch.content.startswith('diff --git a/tools/testing/selftests/powerpc/Makefile'))
446         # Confirm the trailing no newline marker doesn't end up in the comment
447         self.assertFalse(comment.content.rstrip().endswith('\ No newline at end of file'))
448         # Confirm it's instead at the bottom of the patch
449         self.assertTrue(patch.content.rstrip().endswith('\ No newline at end of file'))
450         # Confirm we got both markers
451         self.assertEqual(2, patch.content.count('\ No newline at end of file'))
452
453 class DelegateRequestTest(TestCase):
454     fixtures = ['default_states']
455     patch_filename = '0001-add-line.patch'
456     msgid = '<1@example.com>'
457     invalid_delegate_email = "nobody"
458
459     def setUp(self):
460         self.patch = read_patch(self.patch_filename)
461         self.user = create_user()
462         self.p1 = Project(linkname = 'test-project-1', name = 'Project 1',
463                 listid = '1.example.com', listemail='1@example.com')
464         self.p1.save()
465
466     def get_email(self):
467         email = create_email(self.patch)
468         del email['List-ID']
469         email['List-ID'] = '<' + self.p1.listid + '>'
470         email['Message-Id'] = self.msgid
471         return email
472
473     def _assertDelegate(self, delegate):
474         query = Patch.objects.filter(project=self.p1)
475         self.assertEquals(query.count(), 1)
476         self.assertEquals(query[0].delegate, delegate)
477
478     def testDelegate(self):
479         email = self.get_email()
480         email['X-Patchwork-Delegate'] = self.user.email
481         parse_mail(email)
482         self._assertDelegate(self.user)
483
484     def testNoDelegate(self):
485         email = self.get_email()
486         parse_mail(email)
487         self._assertDelegate(None)
488
489     def testInvalidDelegateFallsBackToNoDelegate(self):
490         email = self.get_email()
491         email['X-Patchwork-Delegate'] = self.invalid_delegate_email
492         parse_mail(email)
493         self._assertDelegate(None)
494
495     def tearDown(self):
496         self.p1.delete()
497         self.user.delete()
498
499 class InitialPatchStateTest(TestCase):
500     fixtures = ['default_states']
501     patch_filename = '0001-add-line.patch'
502     msgid = '<1@example.com>'
503     invalid_state_name = "Nonexistent Test State"
504
505     def setUp(self):
506         self.patch = read_patch(self.patch_filename)
507         self.user = create_user()
508         self.p1 = Project(linkname = 'test-project-1', name = 'Project 1',
509                 listid = '1.example.com', listemail='1@example.com')
510         self.p1.save()
511         self.default_state = get_default_initial_patch_state()
512         self.nondefault_state = State.objects.get(name="Accepted")
513
514     def get_email(self):
515         email = create_email(self.patch)
516         del email['List-ID']
517         email['List-ID'] = '<' + self.p1.listid + '>'
518         email['Message-Id'] = self.msgid
519         return email
520
521     def _assertState(self, state):
522         query = Patch.objects.filter(project=self.p1)
523         self.assertEquals(query.count(), 1)
524         self.assertEquals(query[0].state, state)
525
526     def testNonDefaultStateIsActuallyNotTheDefaultState(self):
527         self.assertNotEqual(self.default_state, self.nondefault_state)
528
529     def testExplicitNonDefaultStateRequest(self):
530         email = self.get_email()
531         email['X-Patchwork-State'] = self.nondefault_state.name
532         parse_mail(email)
533         self._assertState(self.nondefault_state)
534
535     def testExplicitDefaultStateRequest(self):
536         email = self.get_email()
537         email['X-Patchwork-State'] = self.default_state.name
538         parse_mail(email)
539         self._assertState(self.default_state)
540
541     def testImplicitDefaultStateRequest(self):
542         email = self.get_email()
543         parse_mail(email)
544         self._assertState(self.default_state)
545
546     def testInvalidTestStateDoesNotExist(self):
547         with self.assertRaises(State.DoesNotExist):
548             State.objects.get(name=self.invalid_state_name)
549
550     def testInvalidStateRequestFallsBackToDefaultState(self):
551         email = self.get_email()
552         email['X-Patchwork-State'] = self.invalid_state_name
553         parse_mail(email)
554         self._assertState(self.default_state)
555
556     def tearDown(self):
557         self.p1.delete()
558         self.user.delete()
559
560 class ParseInitialTagsTest(PatchTest):
561     patch_filename = '0001-add-line.patch'
562     test_comment = ('test comment\n\n' +
563         'Tested-by: Test User <test@example.com>\n' +
564         'Reviewed-by: Test User <test@example.com>\n')
565     fixtures = ['default_tags', 'default_states']
566
567     def setUp(self):
568         project = defaults.project
569         project.listid = 'test.example.com'
570         project.save()
571         self.orig_patch = read_patch(self.patch_filename)
572         email = create_email(self.test_comment + '\n' + self.orig_patch,
573                              project = project)
574         email['Message-Id'] = '<1@example.com>'
575         parse_mail(email)
576
577     def testTags(self):
578         self.assertEquals(Patch.objects.count(), 1)
579         patch = Patch.objects.all()[0]
580         self.assertEquals(patch.patchtag_set.filter(
581                             tag__name='Acked-by').count(), 0)
582         self.assertEquals(patch.patchtag_set.get(
583                             tag__name='Reviewed-by').count, 1)
584         self.assertEquals(patch.patchtag_set.get(
585                             tag__name='Tested-by').count, 1)
586
587 class PrefixTest(TestCase):
588
589     def testSplitPrefixes(self):
590         self.assertEquals(split_prefixes('PATCH'), ['PATCH'])
591         self.assertEquals(split_prefixes('PATCH,RFC'), ['PATCH', 'RFC'])
592         self.assertEquals(split_prefixes(''), [])
593         self.assertEquals(split_prefixes('PATCH,'), ['PATCH'])
594         self.assertEquals(split_prefixes('PATCH '), ['PATCH'])
595         self.assertEquals(split_prefixes('PATCH,RFC'), ['PATCH', 'RFC'])
596         self.assertEquals(split_prefixes('PATCH 1/2'), ['PATCH', '1/2'])
597
598 class SubjectTest(TestCase):
599
600     def testCleanSubject(self):
601         self.assertEquals(clean_subject('meep'), 'meep')
602         self.assertEquals(clean_subject('Re: meep'), 'meep')
603         self.assertEquals(clean_subject('[PATCH] meep'), 'meep')
604         self.assertEquals(clean_subject('[PATCH] meep \n meep'), 'meep meep')
605         self.assertEquals(clean_subject('[PATCH RFC] meep'), '[RFC] meep')
606         self.assertEquals(clean_subject('[PATCH,RFC] meep'), '[RFC] meep')
607         self.assertEquals(clean_subject('[PATCH,1/2] meep'), '[1/2] meep')
608         self.assertEquals(clean_subject('[PATCH RFC 1/2] meep'),
609                                             '[RFC,1/2] meep')
610         self.assertEquals(clean_subject('[PATCH] [RFC] meep'),
611                                             '[RFC] meep')
612         self.assertEquals(clean_subject('[PATCH] [RFC,1/2] meep'),
613                                             '[RFC,1/2] meep')
614         self.assertEquals(clean_subject('[PATCH] [RFC] [1/2] meep'),
615                                             '[RFC,1/2] meep')
616         self.assertEquals(clean_subject('[PATCH] rewrite [a-z] regexes'),
617                                             'rewrite [a-z] regexes')
618         self.assertEquals(clean_subject('[PATCH] [RFC] rewrite [a-z] regexes'),
619                                             '[RFC] rewrite [a-z] regexes')
620         self.assertEquals(clean_subject('[foo] [bar] meep', ['foo']),
621                                             '[bar] meep')
622         self.assertEquals(clean_subject('[FOO] [bar] meep', ['foo']),
623                                             '[bar] meep')