diff options
Diffstat (limited to 'src/mailman/bin/tests')
| -rw-r--r-- | src/mailman/bin/tests/test_mailman.py | 62 | ||||
| -rw-r--r-- | src/mailman/bin/tests/test_master.py | 74 |
2 files changed, 99 insertions, 37 deletions
diff --git a/src/mailman/bin/tests/test_mailman.py b/src/mailman/bin/tests/test_mailman.py index 49a64fb5c..54ee54bce 100644 --- a/src/mailman/bin/tests/test_mailman.py +++ b/src/mailman/bin/tests/test_mailman.py @@ -19,9 +19,8 @@ import unittest -from contextlib import ExitStack +from click.testing import CliRunner from datetime import timedelta -from io import StringIO from mailman.app.lifecycle import create_list from mailman.bin.mailman import main from mailman.config import config @@ -34,17 +33,31 @@ from unittest.mock import patch class TestMailmanCommand(unittest.TestCase): layer = ConfigLayer + def setUp(self): + self._command = CliRunner() + def test_mailman_command_without_subcommand_prints_help(self): # Issue #137: Running `mailman` without a subcommand raises an # AttributeError. - testargs = ['mailman'] - output = StringIO() - with patch('sys.argv', testargs), patch('sys.stdout', output): - with self.assertRaises(SystemExit): - main() - self.assertIn('usage', output.getvalue()) + result = self._command.invoke(main) + lines = result.output.splitlines() + # "main" instead of "mailman" because of the way the click runner + # works. It does actually show the correct program when run from the + # command line. + self.assertEqual(lines[0], 'Usage: main [OPTIONS] COMMAND [ARGS]...') + + def test_mailman_command_with_bad_subcommand_prints_help(self): + # Issue #137: Running `mailman` without a subcommand raises an + # AttributeError. + result = self._command.invoke(main, ('not-a-subcommand',)) + lines = result.output.splitlines() + # "main" instead of "mailman" because of the way the click runner + # works. It does actually show the correct program when run from the + # command line. + self.assertEqual(lines[0], 'Usage: main [OPTIONS] COMMAND [ARGS]...') - def test_transaction_commit_after_successful_subcommand(self): + @patch('mailman.bin.mailman.initialize') + def test_transaction_commit_after_successful_subcommand(self, mock): # Issue #223: Subcommands which change the database need to commit or # abort the transaction. with transaction(): @@ -52,40 +65,23 @@ class TestMailmanCommand(unittest.TestCase): mlist.volume = 5 mlist.next_digest_number = 3 mlist.digest_last_sent_at = now() - timedelta(days=60) - testargs = ['mailman', 'digests', '-b', '-l', 'ant@example.com'] - output = StringIO() - with ExitStack() as resources: - enter = resources.enter_context - enter(patch('sys.argv', testargs)) - enter(patch('sys.stdout', output)) - # Everything is already initialized. - enter(patch('mailman.bin.mailman.initialize')) - main() + self._command.invoke(main, ('digests', '-b', '-l', 'ant@example.com')) # Clear the current transaction to force a database reload. config.db.abort() self.assertEqual(mlist.volume, 6) self.assertEqual(mlist.next_digest_number, 1) - def test_transaction_abort_after_failing_subcommand(self): + @patch('mailman.bin.mailman.initialize') + @patch('mailman.commands.cli_digests.maybe_send_digest_now', + side_effect=RuntimeError) + def test_transaction_abort_after_failing_subcommand(self, mock1, mock2): with transaction(): mlist = create_list('ant@example.com') mlist.volume = 5 mlist.next_digest_number = 3 mlist.digest_last_sent_at = now() - timedelta(days=60) - testargs = ['mailman', 'digests', '-b', '-l', 'ant@example.com', - '--send'] - output = StringIO() - with ExitStack() as resources: - enter = resources.enter_context - enter(patch('sys.argv', testargs)) - enter(patch('sys.stdout', output)) - # Force an exception in the subcommand. - enter(patch('mailman.commands.cli_digests.maybe_send_digest_now', - side_effect=RuntimeError)) - # Everything is already initialized. - enter(patch('mailman.bin.mailman.initialize')) - with self.assertRaises(RuntimeError): - main() + self._command.invoke( + main, ('digests', '-b', '-l', 'ant@example.com', '--send')) # Clear the current transaction to force a database reload. config.db.abort() # The volume and number haven't changed. diff --git a/src/mailman/bin/tests/test_master.py b/src/mailman/bin/tests/test_master.py index fb045f58f..27ea6d559 100644 --- a/src/mailman/bin/tests/test_master.py +++ b/src/mailman/bin/tests/test_master.py @@ -21,13 +21,28 @@ import os import tempfile import unittest -from contextlib import suppress +from click.testing import CliRunner +from contextlib import ExitStack, suppress from datetime import timedelta -from flufl.lock import Lock +from flufl.lock import Lock, TimeOutError +from io import StringIO from mailman.bin import master +from mailman.config import config +from mailman.testing.layers import ConfigLayer +from pkg_resources import resource_filename +from unittest.mock import patch -class TestMasterLock(unittest.TestCase): +class FakeLock: + details = ('host.example.com', 9999, '/tmp/whatever') + + def unlock(self): + pass + + +class TestMaster(unittest.TestCase): + layer = ConfigLayer + def setUp(self): fd, self.lock_file = tempfile.mkstemp() os.close(fd) @@ -59,4 +74,55 @@ class TestMasterLock(unittest.TestCase): finally: my_lock.unlock() self.assertEqual(state, master.WatcherState.conflict) - # XXX test stale_lock and host_mismatch states. + + def test_acquire_lock_timeout_reason_unknown(self): + stderr = StringIO() + with ExitStack() as resources: + resources.enter_context(patch( + 'mailman.bin.master.acquire_lock_1', + side_effect=TimeOutError)) + resources.enter_context(patch( + 'mailman.bin.master.master_state', + return_value=(master.WatcherState.none, FakeLock()))) + resources.enter_context(patch( + 'mailman.bin.master.sys.stderr', stderr)) + with self.assertRaises(SystemExit) as cm: + master.acquire_lock(False) + self.assertEqual(cm.exception.code, 1) + self.assertEqual(stderr.getvalue(), """\ +For unknown reasons, the master lock could not be acquired. + +Lock file: {} +Lock host: host.example.com + +Exiting. +""".format(config.LOCK_FILE)) + + def test_main_cli(self): + command = CliRunner() + fake_lock = FakeLock() + with ExitStack() as resources: + config_file = resource_filename( + 'mailman.testing', 'testing.cfg') + init_mock = resources.enter_context(patch( + 'mailman.bin.master.initialize')) + lock_mock = resources.enter_context(patch( + 'mailman.bin.master.acquire_lock', + return_value=fake_lock)) + start_mock = resources.enter_context(patch.object( + master.Loop, 'start_runners')) + loop_mock = resources.enter_context(patch.object( + master.Loop, 'loop')) + command.invoke( + master.main, + ('-C', config_file, + '--no-restart', '--force', + '-r', 'in:1:1', '--verbose')) + # We got initialized with the custom configuration file and the + # verbose flag. + init_mock.assert_called_once_with(config_file, True) + # We returned a lock that was force-acquired. + lock_mock.assert_called_once_with(True) + # We created a non-restartable loop. + start_mock.assert_called_once_with([('in', 1, 1)]) + loop_mock.assert_called_once_with() |
