Wesley Wiedenmeier has proposed merging 
~wesley-wiedenmeier/cloud-init:error-output into cloud-init:master.

Requested reviews:
  cloud init development team (cloud-init-dev)

For more details, see:
https://code.launchpad.net/~wesley-wiedenmeier/cloud-init/+git/cloud-init/+merge/306731

Improve formatting for util.ProcessExecutionError and properly display multi 
line error messages. Add unittests for util.ProcessExecutionError.

old error formatting:
http://paste.ubuntu.com/23232468/

new error formatting:
http://paste.ubuntu.com/23232463/
-- 
Your team cloud init development team is requested to review the proposed merge 
of ~wesley-wiedenmeier/cloud-init:error-output into cloud-init:master.
diff --git a/cloudinit/util.py b/cloudinit/util.py
index eb3e589..b9c88c9 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -235,15 +235,16 @@ class ProcessExecutionError(IOError):
                     'Command: %(cmd)s\n'
                     'Exit code: %(exit_code)s\n'
                     'Reason: %(reason)s\n'
-                    'Stdout: %(stdout)r\n'
-                    'Stderr: %(stderr)r')
+                    'Stdout: %(stdout)s\n'
+                    'Stderr: %(stderr)s')
+    empty_attr = '-'
 
     def __init__(self, stdout=None, stderr=None,
                  exit_code=None, cmd=None,
                  description=None, reason=None,
                  errno=None):
         if not cmd:
-            self.cmd = '-'
+            self.cmd = self.empty_attr
         else:
             self.cmd = cmd
 
@@ -253,24 +254,24 @@ class ProcessExecutionError(IOError):
             self.description = description
 
         if not isinstance(exit_code, six.integer_types):
-            self.exit_code = '-'
+            self.exit_code = self.empty_attr
         else:
             self.exit_code = exit_code
 
         if not stderr:
-            self.stderr = ''
+            self.stderr = self.empty_attr
         else:
-            self.stderr = stderr
+            self.stderr = self._indent_text(stderr)
 
         if not stdout:
-            self.stdout = ''
+            self.stdout = self.empty_attr
         else:
-            self.stdout = stdout
+            self.stdout = self._indent_text(stdout)
 
         if reason:
             self.reason = reason
         else:
-            self.reason = '-'
+            self.reason = self.empty_attr
 
         self.errno = errno
         message = self.MESSAGE_TMPL % {
@@ -286,6 +287,20 @@ class ProcessExecutionError(IOError):
         if not hasattr(self, 'message'):
             self.message = message
 
+    def _indent_text(self, text, indent_level=8):
+        """
+        indent text on all but the first line, allowing for easy to read output
+        """
+        # if we are running on python3, then we need to make sure that this is
+        # a str, not bytes. on python2, this is not so important as bytes is
+        # basically just str
+        if six.PY3:
+            if isinstance(text, bytes):
+                text = text.decode()
+        # remove any newlines at end of text first to prevent unneeded blank
+        # line in output
+        return text.rstrip('\n').replace('\n', '\n' + ' ' * indent_level)
+
 
 class SeLinuxGuard(object):
     def __init__(self, path, recursive=False):
diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py
index fc6b9d4..f8d416c 100644
--- a/tests/unittests/test_util.py
+++ b/tests/unittests/test_util.py
@@ -604,11 +604,70 @@ class TestSubp(helpers.TestCase):
                          util.target_path("/target/", "///my/path/"))
 
 
+<<<<<<< tests/unittests/test_util.py
 class TestEncode(helpers.TestCase):
     """Test the encoding functions"""
     def test_decode_binary_plain_text_with_hex(self):
         blob = 'BOOTABLE_FLAG=\x80init=/bin/systemd'
         text = util.decode_binary(blob)
         self.assertEqual(text, blob)
+=======
+class TestProcessExecutionError(helpers.TestCase):
+
+    template = ('{description}\n'
+                'Command: {cmd}\n'
+                'Exit code: {exit_code}\n'
+                'Reason: {reason}\n'
+                'Stdout: {stdout}\n'
+                'Stderr: {stderr}')
+    empty_attr = '-'
+    empty_description = 'Unexpected error while running command.'
+
+    def test_pexec_error_type(self):
+        self.assertIsInstance(util.ProcessExecutionError(), IOError)
+
+    def test_pexec_error_empty_msgs(self):
+        error = util.ProcessExecutionError()
+        self.assertTrue(all(attr == self.empty_attr for attr in
+                            (error.stderr, error.stdout, error.reason)))
+        self.assertEqual(error.description, self.empty_description)
+        self.assertEqual(error.message, self.template.format(
+            description=self.empty_description, exit_code=self.empty_attr,
+            reason=self.empty_attr, stdout=self.empty_attr,
+            stderr=self.empty_attr, cmd=self.empty_attr))
+
+    def test_pexec_error_single_line_msgs(self):
+        stdout_msg = 'out out'
+        stderr_msg = 'error error'
+        cmd = 'test command'
+        exit_code = 3
+        error = util.ProcessExecutionError(
+            stdout=stdout_msg, stderr=stderr_msg, exit_code=3, cmd=cmd)
+        self.assertEqual(error.message, self.template.format(
+            description=self.empty_description, stdout=stdout_msg,
+            stderr=stderr_msg, exit_code=str(exit_code),
+            reason=self.empty_attr, cmd=cmd))
+
+    def test_pexec_error_multi_line_msgs(self):
+        stdout_msg = 'multi\nline\noutput message'
+        stderr_msg = 'multi\nline\nerror message\n\n\n'
+        error = util.ProcessExecutionError(
+            stdout=stdout_msg, stderr=stderr_msg)
+        self.assertEqual(
+            error.message,
+            '\n'.join((
+                '{description}',
+                'Command: {empty_attr}',
+                'Exit code: {empty_attr}',
+                'Reason: {empty_attr}',
+                'Stdout: multi',
+                '        line',
+                '        output message',
+                'Stderr: multi',
+                '        line',
+                '        error message',
+            )).format(description=self.empty_description,
+                      empty_attr=self.empty_attr))
+>>>>>>> tests/unittests/test_util.py
 
 # vi: ts=4 expandtab
_______________________________________________
Mailing list: https://launchpad.net/~cloud-init-dev
Post to     : cloud-init-dev@lists.launchpad.net
Unsubscribe : https://launchpad.net/~cloud-init-dev
More help   : https://help.launchpad.net/ListHelp

Reply via email to