Backport some subprocess py3.5+ functionality for older python versions
authorPlamen Dimitrov <pdimitrov@pevogam.com>
Thu, 4 Oct 2018 04:45:32 +0000 (12:45 +0800)
committerPlamen Dimitrov <pdimitrov@pevogam.com>
Mon, 5 Nov 2018 08:55:21 +0000 (16:55 +0800)
The main platform using these utilities only supports py3.3 making
this extra effort necessary for a seamless caller experience.

src/arnied_wrapper.py
src/call_helpers.py
test/test_arnied_wrapper.py

index a68d173..b759379 100644 (file)
@@ -60,6 +60,8 @@ log = logging.getLogger('pyi2ncommon.arnied_wrapper')
 from .cnfline import build_cnfvar
 from . import cnfvar
 from . import sysmisc
+from . import call_helpers
+
 
 
 #: default set_cnf binary
@@ -97,7 +99,10 @@ def run_cmd(cmd="", ignore_errors=False, vm=None):
                 raise subprocess.CalledProcessError(status, cmd, stderr=stderr)
         return subprocess.CompletedProcess(cmd, status, stdout=stdout, stderr=stderr)
     else:
-        return subprocess.run(cmd, check=ignore_errors, shell=True)
+        if sys.version_info.major <= 3 and sys.version_info.minor < 5:
+            return call_helpers.subprocess_run(cmd, check=not ignore_errors, shell=True)
+        else:
+            return subprocess.run(cmd, check=not ignore_errors, shell=True)
 
 
 def verify_running(process='arnied', timeout=60, vm=None):
index 4e945e1..fbd2128 100644 (file)
@@ -81,3 +81,58 @@ def call_and_capture(command, stdin_data=None, split_lines=True,
             stderr_data.splitlines()
     else:
         return proc.returncode, stdout_data, stderr_data
+
+
+# Backports of py3.5+ subprocess module - nothing better than to
+# remove this and simply use 3.5+ python version on of course.
+
+class CompletedProcess(object):
+    """
+    Backported minimal class from the py3.5+ subprocess module.
+
+    All arguments are equivalent to the formal subprocess module.
+    """
+    def __init__(self, args, returncode, stdout=None, stderr=None):
+        self.args = args
+        self.returncode = returncode
+        self.stdout = stdout
+        self.stderr = stderr
+
+    def __repr__(self):
+        args = ['args={!r}'.format(self.args),
+                'returncode={!r}'.format(self.returncode)]
+        if self.stdout is not None:
+            args.append('stdout={!r}'.format(self.stdout))
+        if self.stderr is not None:
+            args.append('stderr={!r}'.format(self.stderr))
+        return "{}({})".format(type(self).__name__, ', '.join(args))
+
+
+class CalledProcessError(Exception):
+    """
+    Backported minimal class from the py3.5+ subprocess module.
+
+    All arguments are equivalent to the formal subprocess module.
+    """
+    def __init__(self, returncode, cmd, stderr=None):
+        self.returncode = returncode
+        self.cmd = cmd
+        self.stderr = stderr
+
+    def __str__(self):
+        return "Command '%s' returned non-zero exit status %d." % (
+                self.cmd, self.returncode)
+
+
+def subprocess_run(args, check=True, shell=True):
+    """
+    Backported minimal function from the py3.5+ subprocess module.
+
+    All arguments are equivalent to the formal subprocess module.
+    """
+    command = args if isinstance(args, str) else " ".join(args)
+    status, stdout, stderr = call_and_capture(command, shell=shell)
+    if check and status != 0:
+        raise CalledProcessError(status, args, stderr=stderr)
+    else:
+        return CompletedProcess(args, status, stdout=stdout, stderr=stderr)
index e51349c..daea9cc 100755 (executable)
@@ -50,9 +50,9 @@ class DummyCmdOutputMapping(object):
     ]
     asserted_cmds = []
 
-    def __init__(self, cmd, check=False, shell=False):
+    def __init__(self, cmd="", ignore_errors=False, vm=None):
         self.returncode = 0
-        self.stdout = ""
+        self.stdout = b""
         self._get_result(cmd)
         if self.fail_switch:
             self.returncode = 1
@@ -70,7 +70,7 @@ class DummyCmdOutputMapping(object):
                          "for the universe" % cmd)
 
 
-@mock.patch('src.arnied_wrapper.subprocess.run', DummyCmdOutputMapping)
+@mock.patch('src.arnied_wrapper.run_cmd', DummyCmdOutputMapping)
 class ArniedWrapperTest(unittest.TestCase):
 
     def setUp(self):