# HG changeset patch # Parent 94e6a8993abb1e2db3ecea065879f6304250c0c0 diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -67,6 +67,40 @@ class TestBase: test_support.unlink(target) +def random_message(): + # Generate a random message body + import random + body = "" + for i in range(random.randint(1, 10)): + line = "a" * random.randint(0, 75) + '\n' + body += line + + return body + +def add_25_messages(factory, path): + "Helper function to add 25 messages to a mailbox." + mbox = factory(path) + try: + for i in range(25): + msg = """Subject: %i, pid %i +From: sender@example.com + +Content goes here. +%s""" % (i, os.getpid(), random_message()) + while True: + try: + mbox.lock() + except mailbox.ExternalClashError: + # In case of conflict, wait a bit and try again. + time.sleep(0.01) + else: + break + mbox.add(msg) + mbox.flush() + mbox.unlock() + finally: + mbox.close() + def add_message(factory, path, msg): # Add "msg" to mailbox at "path", using mailbox instance returned # by "factory". @@ -288,6 +322,174 @@ class TestMailbox(TestBase): (self._factory, self._path, msgc1, msgc2), inspect) + def test_child_add_and_delete_interleave(self): + # Like test_child_add_and_delete, but parent process writes + # messages before and after the child rewrites the mailbox. + msgp1 = self._template % "p1" + msgp2 = self._template % "p2" + msgc1 = self._template % "c1" + msgc2 = self._template % "c2" + def parent(): + self.assertEqual(len(self._box), 0) + self._box.add(msgp1) + yield + self._box.add(msgp2) + def inspect(): + self._compare_mailbox({}, [msgp1, msgc2, msgp2]) + self._subprocess_tests(parent, add_two_delete_one, + (self._factory, self._path, msgc1, msgc2), + inspect) + + def test_add_by_other_reread(self): + # Check we can read other process' message after writing our own. + msgp = self._template % 0 + msgc = self._template % 1 + def parent(): + key = self._box.add(msgp) + yield + self._compare_mailbox({key: msgp}, [msgc]) + self._subprocess_tests(parent, add_message, + (self._factory, self._path, msgc)) + + def test_interleave(self): + # Check that other process can add a message in between our messages. + p1 = self._template % "p1" + p2 = self._template % "p2" + c1 = self._template % "c1" + def parent(): + k1 = self._box.add(p1) + yield + k2 = self._box.add(p2) + self._compare_mailbox({k1: p1, k2: p2}, [c1]) + def inspect(): + self._compare_mailbox({}, [p1, c1, p2]) + self._subprocess_tests(parent, add_message, + (self._factory, self._path, c1), inspect) + + def test_delete_reread(self): + # Have other process add a message after we've deleted one. + p1 = self._template % "p1" + c1 = self._template % "c1" + def parent(): + k1 = self._box.add(p1) + del self._box[k1] + yield + self._compare_mailbox({}, [c1]) + def inspect(): + self._compare_mailbox({}, [c1]) + self._subprocess_tests(parent, add_message, + (self._factory, self._path, c1), inspect) + + def test_delete_reread2(self): + # As above, but have parent add more messages before and after. + p1 = self._template % "p1" + p2 = self._template % "p2" + p3 = self._template % "p3" + p4 = self._template % "p4" + c1 = self._template % "c1" + def parent(): + k1 = self._box.add(p1) + k2 = self._box.add(p2) + del self._box[k2] + k3 = self._box.add(p3) + yield + k4 = self._box.add(p4) + self._compare_mailbox({k1: p1, k3: p3, k4: p4}, [c1]) + def inspect(): + self._compare_mailbox({}, [p1, p3, c1, p4]) + self._subprocess_tests(parent, add_message, + (self._factory, self._path, c1), inspect) + + def test_replace_reread(self): + # Have other process add a message after we've replaced one. + p1 = self._template % "p1" + p2 = self._template % "p2" + c1 = self._template % "c1" + def parent(): + k1 = self._box.add(p1) + self._box[k1] = p2 + yield + self._compare_mailbox({k1: p2}, [c1]) + def inspect(): + self._compare_mailbox({}, [p2, c1]) + self._subprocess_tests(parent, add_message, + (self._factory, self._path, c1), inspect) + + def test_replace_reread2(self): + # As above, but have parent add more messages before and after. + p1 = self._template % "p1" + p2 = self._template % "p2" + p3 = self._template % "p3" + p4 = self._template % "p4" + p5 = self._template % "p5" + c1 = self._template % "c1" + def parent(): + k1 = self._box.add(p1) + k2 = self._box.add(p2) + self._box[k2] = p3 + k4 = self._box.add(p4) + yield + k5 = self._box.add(p5) + self._compare_mailbox({k1: p1, k2: p3, k4: p4, k5: p5}, [c1]) + def inspect(): + self._compare_mailbox({}, [p1, p3, p4, c1, p5]) + self._subprocess_tests(parent, add_message, + (self._factory, self._path, c1), inspect) + + @unittest.skipIf(multiprocessing is None, "requires multiprocessing") + def test_concurrent_add(self): + # Simple test of concurrent addition to a mailbox. + # This exercises the add() and flush() methods, based on bug #1599254. + # This bug affected only the classes based on _singlefileMailbox + # (mbox, MMDF, Babyl), but this test can apply to any mailbox type. + + self._box.close() + + # Fire off a subprocess that will add 25 messages to a mailbox + # file, locking and unlocking it each time. The parent process + # will do the same. The resulting mailbox should contain 50 messages. + with child_process(add_25_messages, self._factory, self._path): + add_25_messages(self._factory, self._path) + + # We expect the mailbox to contain 50 messages. + self._box = self._factory(self._path) + self._box.lock() + self.assertEqual(len(self._box), 50) + self._box.unlock() + + def test_double_shorten(self): + # Check that flush() can shorten the mailbox twice + self._test_remove_two_of_three(broken_locking=False) + + def test_remove_with_broken_locking(self): + # Check that a (broken) application releasing the lock and + # then removing messages using the existing keys does not + # delete the wrong messages. + self._test_remove_two_of_three(broken_locking=True) + + def _test_remove_two_of_three(self, broken_locking=False): + self._box.lock() + key0 = self._box.add(self._template % 0) + key1 = self._box.add(self._template % 1) + key2 = self._box.add(self._template % 2) + self._box.flush() + self._box.remove(key0) + self._box.flush() + if broken_locking: + # As the name suggests, code that does this is likely to + # be broken (releasing the lock invalidates the keys, in + # general), but ideally it should not malfunction if no + # other process modifies the mailbox. + self._box.unlock() + self._box.lock() + self._box.remove(key1) + self._box.flush() + self._box.unlock() + self._box.close() + self._box = self._factory(self._path) + self.assertEqual(len(self._box), 1) + self.assertEqual(self._box.itervalues().next().get_payload(), '2\n') + def test_add(self): # Add copies of a sample message keys = []