summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuido Günther <agx@sigxcpu.org>2015-02-22 20:15:07 +0100
committerGuido Günther <agx@sigxcpu.org>2015-03-27 12:10:55 +0100
commite3591101e1de375c3ed7430382b88030e240260f (patch)
treed45681ed8f996035d6295271ab525488ab4bb232
parent9803e60802da18d95042c993d220349da9665f90 (diff)
Command: simplify __call__ and call
Get rid of __run and use the same codepaths for both. This also makes the same instance variables available after both calls. The funtions still differ in their default logging behaviour though.
-rw-r--r--gbp/command_wrappers.py134
1 files changed, 84 insertions, 50 deletions
diff --git a/gbp/command_wrappers.py b/gbp/command_wrappers.py
index cf6c56a..0880dc1 100644
--- a/gbp/command_wrappers.py
+++ b/gbp/command_wrappers.py
@@ -1,6 +1,6 @@
# vim: set fileencoding=utf-8 :
#
-# (C) 2007,2009 Guido Guenther <agx@sigxcpu.org>
+# (C) 2007,2009,2015 Guido Guenther <agx@sigxcpu.org>
# This program 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 2 of the License, or
@@ -35,7 +35,6 @@ class Command(object):
Wraps a shell command, so we don't have to store any kind of command
line options in one of the git-buildpackage commands
"""
-
def __init__(self, cmd, args=[], shell=False, extra_env=None, cwd=None,
capture_stderr=False,
capture_stdout=False):
@@ -43,9 +42,6 @@ class Command(object):
self.args = args
self.run_error = "'%s' failed" % (" ".join([self.cmd] + self.args))
self.shell = shell
- self.retcode = 1
- self.stdout = ''
- self.stderr = ''
self.capture_stdout = capture_stdout
self.capture_stderr = capture_stderr
self.cwd = cwd
@@ -54,10 +50,15 @@ class Command(object):
self.env.update(extra_env)
else:
self.env = None
+ self._reset_state()
+
+ def _reset_state(self):
+ self.retcode = 1
+ self.stdout, self.stderr, self.err_reason = [''] * 3
def __call(self, args):
"""
- Wraps subprocess.call so we can be verbose and fix python's
+ Wraps subprocess.call so we can be verbose and fix Python's
SIGPIPE handling
"""
def default_sigpipe():
@@ -65,52 +66,57 @@ class Command(object):
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
log.debug("%s %s %s" % (self.cmd, self.args, args))
- self.stdout, self.stderr = ('', '')
+ self._reset_state()
stdout_arg = subprocess.PIPE if self.capture_stdout else None
stderr_arg = subprocess.PIPE if self.capture_stderr else None
cmd = [ self.cmd ] + self.args + args
if self.shell:
# subprocess.call only cares about the first argument if shell=True
cmd = " ".join(cmd)
- popen = subprocess.Popen(cmd,
- cwd=self.cwd,
- shell=self.shell,
- env=self.env,
- preexec_fn=default_sigpipe,
- stdout=stdout_arg,
- stderr=stderr_arg)
- (self.stdout, self.stderr) = popen.communicate()
- return popen.returncode
-
- def __run(self, args, quiet=False):
- """
- run self.cmd adding args as additional arguments
-
- @param quiet: don't log errors to stderr Mostly useful during unit testing.
- Be verbose about errors and encode them in the return value, don't pass
- on exceptions.
- """
try:
- retcode = self.__call(args)
- if retcode < 0:
- err_detail = "it was terminated by signal %d" % -retcode
- elif retcode > 0:
- err_detail = "it exited with %d" % retcode
+ popen = subprocess.Popen(cmd,
+ cwd=self.cwd,
+ shell=self.shell,
+ env=self.env,
+ preexec_fn=default_sigpipe,
+ stdout=stdout_arg,
+ stderr=stderr_arg)
+ (self.stdout, self.stderr) = popen.communicate()
except OSError as err:
- err_detail = "execution failed: %s" % err
- retcode = 1
- if retcode and not quiet:
- log.err("%s: %s" % (self.run_error, err_detail))
- self.retcode = retcode
- return retcode
+ self.err_reason = "execution failed: %s" % str(err)
+ self.retcode = 1
+ raise
- def __call__(self, args=[], quiet=False):
+ self.retcode = popen.returncode
+ if self.retcode < 0:
+ self.err_reason = "it was terminated by signal %d" % -self.retcode
+ elif self.retcode > 0:
+ self.err_reason = "it exited with %d" % self.retcode
+ return self.retcode
+
+ def _log_err(self):
+ """
+ Log an error message allowing to use the captured stout/stderr
"""
- Run the command and convert all errors into CommandExecFailed. Assumes
- that the lower levels printed an error message (the command itself and
- also via our logging api) - only useful if you only expect 0 as result.
+ log.err("%s: %s" % (self.run_error,
+ self.err_reason)
+
+ def __call__(self, args=[], quiet=False):
+ """Run the command and raise exception on errors
+
+ If run quietly it will not print an error message via the
+ L{gbp.log} logging API.
+ Wether the command prints anything to stdout/stderr depends on
+ the I{capture_stderr}, I{capture_stdout} instance variables.
+
+ All errors will be reported as subclass of the
+ L{CommandExecFailed} exception including a non zero exit
+ status of the run command.
+
+ @param args: additional command line arguments
+ @type args: C{list} of C{strings}
@param quiet: don't log failed execution to stderr. Mostly useful during
unit testing
@type quiet: C{bool}
@@ -119,32 +125,60 @@ class Command(object):
>>> Command("/foo/bar")(quiet=True)
Traceback (most recent call last):
...
- CommandExecFailed
+ CommandExecFailed: execution failed: [Errno 2] No such file or directory
"""
- if self.__run(args, quiet):
- raise CommandExecFailed
+ try:
+ ret = self.__call(args)
+ except OSError:
+ ret = 1
+ if ret:
+ if not quiet:
+ self._log_err()
+ raise CommandExecFailed(self.err_reason)
- def call(self, args):
- """
- Like __call__ but let the caller handle the return status and don't
- use the logging api for errors.
+
+ def call(self, args, quiet=True):
+ """Like L{__call__} but let the caller handle the return status.
+
+ Only raise L{CommandExecFailed} if we failed to launch the command
+ at all (i.e. if it does not exist) not if the command returned
+ nonzero.
+
+ Logs errors using L{gbp.log} by default.
+
+ @param args: additional command line arguments
+ @type args: C{list} of C{strings}
+ @param quiet: don't log failed execution to stderr. Mostly useful during
+ unit testing
+ @type quiet: C{bool}
+ @returns: the exit status of the run command
+ @rtype: C{int}
>>> Command("/bin/true").call(["foo", "bar"])
0
>>> Command("/foo/bar").call(["foo", "bar"]) # doctest:+ELLIPSIS
Traceback (most recent call last):
...
- CommandExecFailed: Execution failed: ...
+ CommandExecFailed: execution failed: ...
>>> c = Command("/bin/true", capture_stdout=True)
>>> c.call(["--version"])
0
>>> c.stdout.startswith('true')
True
+ >>> c = Command("/bin/false", capture_stdout=True)
+ >>> c.call(["--help"])
+ 1
+ >>> c.stdout.startswith('Usage:')
+ True
"""
try:
ret = self.__call(args)
- except OSError as err:
- raise CommandExecFailed("Execution failed: %s" % err)
+ except OSError:
+ ret = 1
+ raise CommandExecFailed(self.err_reason)
+ finally:
+ if ret and not quiet:
+ self._log_err()
return ret