diff options
| -rw-r--r-- | src/mailman/docs/NEWS.rst | 1 | ||||
| -rw-r--r-- | src/mailman/testing/helpers.py | 20 | ||||
| -rw-r--r-- | src/mailman/tests/test_configfile.py | 15 | ||||
| -rw-r--r-- | src/mailman/utilities/interact.py | 10 | ||||
| -rw-r--r-- | src/mailman/utilities/tests/test_interact.py | 99 |
5 files changed, 125 insertions, 20 deletions
diff --git a/src/mailman/docs/NEWS.rst b/src/mailman/docs/NEWS.rst index 69da2136d..b14738330 100644 --- a/src/mailman/docs/NEWS.rst +++ b/src/mailman/docs/NEWS.rst @@ -75,6 +75,7 @@ Bugs (Closes #208) * Fix "None" as display name in welcome message. Given by Aditya Divekar. (Closes #194) + * Fix ``mailman shell`` processing of ``$PYTHONSTARTUP``. (Closes #224) Configuration ------------- diff --git a/src/mailman/testing/helpers.py b/src/mailman/testing/helpers.py index cb5bb8d28..3edd66479 100644 --- a/src/mailman/testing/helpers.py +++ b/src/mailman/testing/helpers.py @@ -550,3 +550,23 @@ def set_preferred(user): preferred.verified_on = now() user.preferred_address = preferred return preferred + + +@public +@contextmanager +def hackenv(envar, new_value): + """Hack the environment temporarily, then reset it.""" + old_value = os.getenv(envar) + if new_value is None: + if envar in os.environ: + del os.environ[envar] + else: + os.environ[envar] = new_value + try: + yield + finally: + if old_value is None: + if envar in os.environ: + del os.environ[envar] + else: + os.environ[envar] = old_value diff --git a/src/mailman/tests/test_configfile.py b/src/mailman/tests/test_configfile.py index 2d418bb54..9406d9ca2 100644 --- a/src/mailman/tests/test_configfile.py +++ b/src/mailman/tests/test_configfile.py @@ -25,6 +25,7 @@ import unittest from contextlib import contextmanager from mailman.core.initialize import search_for_configuration_file +from mailman.testing.helpers import hackenv # Here are a couple of context managers that make our tests easier to read. @@ -39,20 +40,6 @@ def fakedirs(path): @contextmanager -def hackenv(envar, new_value): - """Hack the environment temporarily, then reset it.""" - old_value = os.getenv(envar) - os.environ[envar] = new_value - try: - yield - finally: - if old_value is None: - del os.environ[envar] - else: - os.environ[envar] = old_value - - -@contextmanager def chdir(new_cwd): """Change to the directory, then back again.""" old_cwd = os.getcwd() diff --git a/src/mailman/utilities/interact.py b/src/mailman/utilities/interact.py index d74cd8af7..3c38b02aa 100644 --- a/src/mailman/utilities/interact.py +++ b/src/mailman/utilities/interact.py @@ -24,7 +24,7 @@ import code from mailman import public -DEFAULT_BANNER = '' +DEFAULT_BANNER = object() @public @@ -33,7 +33,7 @@ def interact(upframe=True, banner=DEFAULT_BANNER, overrides=None): :param upframe: Whether or not to populate the interpreter's globals with the locals from the frame that called this function. - :type upfframe: bool + :type upframe: bool :param banner: The banner to print before the interpreter starts. :type banner: string :param overrides: Additional interpreter globals to add. @@ -61,13 +61,11 @@ def interact(upframe=True, banner=DEFAULT_BANNER, overrides=None): startup = os.environ.get('PYTHONSTARTUP') if startup: with open(startup, 'r', encoding='utf-8') as fp: - interp.runsource(fp.read(), startup) + interp.runcode(compile(fp.read(), startup, 'exec')) # We don't want the funky console object in parentheses in the banner. - if banner == DEFAULT_BANNER: + if banner is DEFAULT_BANNER: banner = '''\ Python %s on %s Type "help", "copyright", "credits" or "license" for more information.''' % ( sys.version, sys.platform) - elif not banner: - banner = None interp.interact(banner) diff --git a/src/mailman/utilities/tests/test_interact.py b/src/mailman/utilities/tests/test_interact.py new file mode 100644 index 000000000..65a1155fb --- /dev/null +++ b/src/mailman/utilities/tests/test_interact.py @@ -0,0 +1,99 @@ +# Copyright (C) 2016 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. + +"""Test the interact utility.""" + + +import sys +import unittest + +from contextlib import ExitStack +from io import StringIO +from mailman.app.lifecycle import create_list +from mailman.testing.helpers import hackenv +from mailman.testing.layers import ConfigLayer +from mailman.utilities.interact import interact +from tempfile import NamedTemporaryFile +from unittest.mock import patch + + +class TestInteract(unittest.TestCase): + layer = ConfigLayer + + def setUp(self): + resources = ExitStack() + self.addCleanup(resources.close) + self._enter = resources.enter_context + self._enter(patch('code.input', side_effect=EOFError)) + self._stderr = StringIO() + self._enter(patch('sys.stderr', self._stderr)) + + def test_interact(self): + mlist = create_list('ant@example.com') + results = [] + fp = self._enter(NamedTemporaryFile('w', encoding='utf-8')) + self._enter(hackenv('PYTHONSTARTUP', fp.name)) + print('results.append(mlist.list_id)', file=fp) + fp.flush() + interact() + self.assertEqual(results, [mlist.list_id]) + + def test_interact_overrides(self): + create_list('ant@example.com') + bee = create_list('bee@example.com') + results = [] + fp = self._enter(NamedTemporaryFile('w', encoding='utf-8')) + self._enter(hackenv('PYTHONSTARTUP', fp.name)) + print('results.append(mlist.list_id)', file=fp) + fp.flush() + interact(overrides=dict(mlist=bee)) + self.assertEqual(results, [bee.list_id]) + + def test_interact_default_banner(self): + self._enter(hackenv('PYTHONSTARTUP', None)) + interact() + stderr = self._stderr.getvalue().splitlines() + banner = 'Python {} on {} '.format(sys.version, sys.platform) + self.assertEqual(stderr[0], banner.splitlines()[0]) + + def test_interact_custom_banner(self): + self._enter(hackenv('PYTHONSTARTUP', None)) + interact(banner='Welcome') + stderr = self._stderr.getvalue().splitlines() + self.assertEqual(stderr[0], 'Welcome') + + def test_interact_no_upframe(self): + upframed = False # noqa + fp = self._enter(NamedTemporaryFile('w', encoding='utf-8')) + self._enter(hackenv('PYTHONSTARTUP', fp.name)) + print('print(upframed)', file=fp) + fp.flush() + interact(upframe=False, banner='') + lines = self._stderr.getvalue().splitlines() + self.assertIn("NameError: name 'upframed' is not defined", lines) + + def test_interact_multiline(self): + # GL issue #224. + fp = self._enter(NamedTemporaryFile('w', encoding='utf-8')) + self._enter(hackenv('PYTHONSTARTUP', fp.name)) + print('import sys', file=fp) + print("print('hello', file=sys.stderr)", file=fp) + print("print('world', file=sys.stderr)", file=fp) + fp.flush() + interact(banner='') + lines = self._stderr.getvalue() + self.assertEqual(lines, 'hello\nworld\n\n', lines) |
