Fix the crappy error handling in utils.run() - it doesn't work
at all right now, because no exceptions are thrown for a timeout

Move CmdResult class to utils.py - it has no business being under
hosts/ ... nothing to do with it.

Signed-off-by: Martin Bligh <mbligh@google.com>



git-svn-id: http://test.kernel.org/svn/autotest/trunk@995 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/server/utils.py b/server/utils.py
index ba350ef..7de279f 100644
--- a/server/utils.py
+++ b/server/utils.py
@@ -13,8 +13,9 @@
 """
 
 import atexit, os, select, shutil, signal, StringIO, subprocess, tempfile
-import time, types, urllib, re, sys
+import time, types, urllib, re, sys, textwrap
 import hosts, errors
+from error import *
 
 # A dictionary of pid and a list of tmpdirs for that pid
 __tmp_dirs = {}
@@ -126,6 +127,24 @@
 			return tmpfile
 
 
+def __nuke_subprocess(subproc):
+       # the process has not terminated within timeout,
+       # kill it via an escalating series of signals.
+       signal_queue = [signal.SIGTERM, signal.SIGKILL]
+       for sig in signal_queue:
+	       try:
+		       os.kill(subproc.pid, sig)
+	       # The process may have died before we could kill it.
+	       except OSError:
+		       pass
+
+	       for i in range(5):
+		       rc = subproc.poll()
+		       if rc != None:
+			       return
+		       time.sleep(1)
+
+
 def _process_output(pipe, fbuffer, teefile=None, use_os_read=True):
 	if use_os_read:
 		data = os.read(pipe.fileno(), 1024)
@@ -161,30 +180,15 @@
 		pid, exit_status_indication = os.waitpid(subproc.pid,
 							 os.WNOHANG)
 		if pid:
-			break
+			return exit_status_indication
 		if timeout:
 			time_left = stop_time - time.time()
 
 	# the process has not terminated within timeout,
 	# kill it via an escalating series of signals.
 	if not pid:
-		signal_queue = [signal.SIGTERM, signal.SIGKILL]
-		for sig in signal_queue:
-			try:
-				os.kill(subproc.pid, sig)
-			# handle race condition in which
-			# process died before we could kill it.
-			except OSError:
-				pass
-
-			for i in range(5):
-				pid, exit_status_indication = (
-				    os.waitpid(subproc.pid, os.WNOHANG))
-				if pid:
-					return exit_status_indication
-				else:
-						time.sleep(1)
-	return exit_status_indication
+		__nuke_subprocess(subproc)
+	raise CmdError('Command not complete within %s seconds' % timeout)
 
 
 def run(command, timeout=None, ignore_status=False,
@@ -206,17 +210,13 @@
 		stderr_tee: likewise for stderr
 
 	Returns:
-		a hosts.CmdResult object
+		a CmdResult object
 
 	Raises:
 		AutoservRunError: the exit code of the command
 			execution was not 0
-
-	TODO(poirier): Should a timeout raise an exception? Should
-		exceptions be raised at all?
 	"""
-	result = hosts.CmdResult()
-	result.command = command
+	result = CmdResult(command)
 	sp = subprocess.Popen(command, stdout=subprocess.PIPE,
 			      stderr=subprocess.PIPE, close_fds=True,
 			      shell=True, executable="/bin/bash")
@@ -227,17 +227,10 @@
 		# We are holding ends to stdin, stdout pipes
 		# hence we need to be sure to close those fds no mater what
 		start_time = time.time()
-		exit_status_indication = _wait_for_command(
-		    sp, start_time, timeout,
-		    stdout_file, stderr_file,
-		    stdout_tee, stderr_tee)
+		result.exit_status = _wait_for_command(sp, start_time, timeout,
+			      stdout_file, stderr_file, stdout_tee, stderr_tee)
 
 		result.duration = time.time() - start_time
-		result.aborted = exit_status_indication & 127
-		if result.aborted:
-			result.exit_status = None
-		else:
-			result.exit_status = exit_status_indication / 256
 		# don't use os.read now, so we get all the rest of the output
 		_process_output(sp.stdout, stdout_file, stdout_tee,
 				use_os_read=False)
@@ -252,8 +245,7 @@
 	result.stderr = stderr_file.getvalue()
 
 	if not ignore_status and result.exit_status > 0:
-		raise errors.AutoservRunError("command execution error",
-			result)
+		raise errors.AutoservRunError("command execution error", result)
 
 	return result
 
@@ -416,3 +408,44 @@
 				return ret
 		else:
 			return default
+
+
+class CmdResult(object):
+	"""
+	Command execution result.
+
+	command:     String containing the command line itself
+	exit_status: Integer exit code of the process
+	stdout:      String containing stdout of the process
+	stderr:      String containing stderr of the process
+	duration:    Elapsed wall clock time running the process
+	"""
+
+	def __init__(self, command = None):
+		self.command = command
+		self.exit_status = None
+		self.stdout = ""
+		self.stderr = ""
+		self.duration = 0
+
+
+	def __repr__(self):
+		wrapper = textwrap.TextWrapper(width = 78, 
+					       initial_indent="\n    ",
+					       subsequent_indent="    ")
+		
+		stdout = self.stdout.rstrip()
+		if stdout:
+			stdout = "\nstdout:\n%s" % stdout
+		
+		stderr = self.stderr.rstrip()
+		if stderr:
+			stderr = "\nstderr:\n%s" % stderr
+		
+		return ("* Command: %s\n"
+			"Exit status: %s\n"
+			"Duration: %s\n"
+			"%s"
+			"%s"
+			% (wrapper.fill(self.command), self.exit_status, 
+			self.duration, stdout, stderr))