From 7102665a366c36e3a66fd73e5c0377f2da04587f Mon Sep 17 00:00:00 2001 From: Plamen Dimitrov Date: Thu, 4 Oct 2018 12:45:32 +0800 Subject: [PATCH] Backport some subprocess py3.5+ functionality for older python versions The main platform using these utilities only supports py3.3 making this extra effort necessary for a seamless caller experience. --- src/arnied_wrapper.py | 7 ++++- src/call_helpers.py | 55 +++++++++++++++++++++++++++++++++++++++++++ test/test_arnied_wrapper.py | 6 ++-- 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/src/arnied_wrapper.py b/src/arnied_wrapper.py index a68d173..b759379 100644 --- a/src/arnied_wrapper.py +++ b/src/arnied_wrapper.py @@ -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): diff --git a/src/call_helpers.py b/src/call_helpers.py index 4e945e1..fbd2128 100644 --- a/src/call_helpers.py +++ b/src/call_helpers.py @@ -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) diff --git a/test/test_arnied_wrapper.py b/test/test_arnied_wrapper.py index e51349c..daea9cc 100755 --- a/test/test_arnied_wrapper.py +++ b/test/test_arnied_wrapper.py @@ -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): -- 1.7.1