From 5beaae3b6502fa4e39ddc80a564f8308347cb67f Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 10 Aug 2017 14:17:13 -0700 Subject: [PATCH] Add check_output function and tests. --- certbot-postfix/certbot_postfix/util.py | 49 +++++++++++++++++++ certbot-postfix/certbot_postfix/util_test.py | 50 ++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 certbot-postfix/certbot_postfix/util.py create mode 100644 certbot-postfix/certbot_postfix/util_test.py diff --git a/certbot-postfix/certbot_postfix/util.py b/certbot-postfix/certbot_postfix/util.py new file mode 100644 index 000000000..8ad6408c4 --- /dev/null +++ b/certbot-postfix/certbot_postfix/util.py @@ -0,0 +1,49 @@ +"""Utility functions for use in the Postfix installer.""" + +import logging +import subprocess + + +logger = logging.getLogger(__name__) + + +def check_output(*args, **kwargs): + """Backported version of subprocess.check_output for Python 2.6+. + + This is the same as subprocess.check_output from newer versions of + Python, except: + + 1. The return value is a string rather than a byte string. To + accomplish this, the caller cannot set the parameter + universal_newlines. + 2. If the command exits with a nonzero status, output is not + included in the raised subprocess.CalledProcessError because + subprocess.CalledProcessError on Python 2.6 does not support this. + Instead, the failure including the output is logged. + + :param tuple args: positional arguments for Popen + :param dict kwargs: keyword arguments for Popen + + :returns: data printed to stdout + :rtype: str + + """ + for keyword in ('stdout', 'universal_newlines',): + if keyword in kwargs: + raise ValueError( + keyword + ' argument not allowed, it will be overridden.') + + kwargs['stdout'] = subprocess.PIPE + kwargs['universal_newlines'] = True + + process = subprocess.Popen(*args, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get('args') + if cmd is None: + cmd = args[0] + logger.debug( + "'%s' exited with %d. Output was:\n%s", cmd, retcode, output) + raise subprocess.CalledProcessError(retcode, cmd) + return output diff --git a/certbot-postfix/certbot_postfix/util_test.py b/certbot-postfix/certbot_postfix/util_test.py new file mode 100644 index 000000000..019f34532 --- /dev/null +++ b/certbot-postfix/certbot_postfix/util_test.py @@ -0,0 +1,50 @@ +"""Tests for certbot_postfix.util.""" + +import subprocess +import unittest + +import mock + +class CheckOutputTest(unittest.TestCase): + """Tests for certbot_postfix.util.check_output.""" + + @classmethod + def _call(cls, *args, **kwargs): + from certbot_postfix.util import check_output + return check_output(*args, **kwargs) + + @mock.patch('certbot_postfix.util.logger') + @mock.patch('certbot_postfix.util.subprocess.Popen') + def test_command_error(self, mock_popen, mock_logger): + command = 'foo' + retcode = 42 + output = 'bar' + + mock_popen().communicate.return_value = (output, '') + mock_popen().poll.return_value = 42 + + self.assertRaises(subprocess.CalledProcessError, self._call, command) + + log_args = mock_logger.debug.call_args[0] + self.assertTrue(command in log_args) + self.assertTrue(retcode in log_args) + self.assertTrue(output in log_args) + + @mock.patch('certbot_postfix.util.subprocess.Popen') + def test_success(self, mock_popen): + command = 'foo' + output = 'bar' + mock_popen().communicate.return_value = (output, '') + mock_popen().poll.return_value = 0 + + self.assertEqual(self._call(command), output) + + def test_stdout_error(self): + self.assertRaises(ValueError, self._call, stdout=None) + + def test_universal_newlines_error(self): + self.assertRaises(ValueError, self._call, universal_newlines=False) + + +if __name__ == '__main__': # pragma: no cover + unittest.main()