diff --git a/client/tests/kvm/kvm_subprocess.py b/client/tests/kvm/kvm_subprocess.py
index 8321bb3..c92910c 100755
--- a/client/tests/kvm/kvm_subprocess.py
+++ b/client/tests/kvm/kvm_subprocess.py
@@ -189,6 +189,88 @@ import subprocess, time, signal, re, threading, logging
 import common, kvm_utils
 
 
+class ExpectError(Exception):
+    def __init__(self, patterns, output):
+        Exception.__init__(self, patterns, output)
+        self.patterns = patterns
+        self.output = output
+
+    def _pattern_str(self):
+        if len(self.patterns) == 1:
+            return "pattern %r" % self.patterns[0]
+        else:
+            return "patterns %r" % self.patterns
+
+    def __str__(self):
+        return ("Unknown error occurred while looking for %s (output: %r)" %
+                (self._pattern_str(), self.output))
+
+
+class ExpectTimeoutError(ExpectError):
+    def __str__(self):
+        return ("Timeout expired while looking for %s (output: %r)" %
+                (self._pattern_str(), self.output))
+
+
+class ExpectProcessTerminatedError(ExpectError):
+    def __init__(self, patterns, status, output):
+        ExpectError.__init__(self, patterns, output)
+        self.status = status
+
+    def __str__(self):
+        return ("Process terminated while looking for %s (status: %s, output: "
+                "%r)" % (self._pattern_str(), self.status, self.output))
+
+
+class ShellError(Exception):
+    def __init__(self, cmd, output):
+        Exception.__init__(self, cmd, output)
+        self.cmd = cmd
+        self.output = output
+
+    def __str__(self):
+        return ("Could not execute shell command %r (output: %r)" %
+                (self.cmd, self.output))
+
+
+class ShellTimeoutError(ShellError):
+    def __str__(self):
+        return ("Timeout expired while waiting for shell command %r to "
+                "complete (output: %r)" % (self.cmd, self.output))
+
+
+class ShellProcessTerminatedError(ShellError):
+    # Raised when the shell process itself (e.g. ssh, netcat, telnet)
+    # terminates unexpectedly
+    def __init__(self, cmd, status, output):
+        ShellError.__init__(self, cmd, output)
+        self.status = status
+
+    def __str__(self):
+        return ("Shell process terminated while waiting for command %r to "
+                "complete (status: %s, output: %r)" %
+                (self.cmd, self.status, self.output))
+
+
+class ShellCmdError(ShellError):
+    # Raised when a command executed in a shell terminates with a nonzero
+    # exit code (status)
+    def __init__(self, cmd, status, output):
+        ShellError.__init__(self, cmd, output)
+        self.status = status
+
+    def __str__(self):
+        return ("Shell command %r failed with status %d (output: %r)" %
+                (self.cmd, self.status, self.output))
+
+
+class ShellStatusError(ShellError):
+    # Raised when the command's exit status cannot be obtained
+    def __str__(self):
+        return ("Could not get exit status of command %r (output: %r)" %
+                (self.cmd, self.output))
+
+
 def run_bg(command, termination_func=None, output_func=None, output_prefix="",
            timeout=1.0):
     """
@@ -773,7 +855,7 @@ class kvm_expect(kvm_tail):
     It also provides all of kvm_tail's functionality.
     """
 
-    def __init__(self, command=None, id=None, auto_close=False, echo=False,
+    def __init__(self, command=None, id=None, auto_close=True, echo=False,
                  linesep="\n", termination_func=None, termination_params=(),
                  output_func=None, output_params=(), output_prefix=""):
         """
@@ -876,13 +958,14 @@ class kvm_expect(kvm_tail):
         @param internal_timeout: The timeout to pass to read_nonblocking
         @param print_func: A function to be used to print the data being read
                 (should take a string parameter)
-        @return: Tuple containing the match index (or None if no match was
-                found) and the data read so far.
+        @return: Tuple containing the match index and the data read so far
+        @raise ExpectTimeoutError: Raised if timeout expires
+        @raise ExpectProcessTerminatedError: Raised if the child process
+                terminates while waiting for output
+        @raise ExpectError: Raised if an unknown error occurs
         """
-        match = None
-        data = ""
-
         fd = self._get_fd("expect")
+        o = ""
         end_time = time.time() + timeout
         while True:
             try:
@@ -890,38 +973,28 @@ class kvm_expect(kvm_tail):
                                         max(0, end_time - time.time()))
             except (select.error, TypeError):
                 break
-            if fd not in r:
-                break
+            if not r:
+                raise ExpectTimeoutError(patterns, o)
             # Read data from child
-            newdata = self.read_nonblocking(internal_timeout)
+            data = self.read_nonblocking(internal_timeout)
+            if not data:
+                break
             # Print it if necessary
-            if print_func and newdata:
-                str = newdata
-                if str.endswith("\n"):
-                    str = str[:-1]
-                for line in str.split("\n"):
+            if print_func:
+                for line in data.splitlines():
                     print_func(line)
-            data += newdata
-
-            done = False
             # Look for patterns
-            match = self.match_patterns(filter(data), patterns)
+            o += data
+            match = self.match_patterns(filter(o), patterns)
             if match is not None:
-                done = True
-            # Check if child has died
-            if not self.is_alive():
-                logging.debug("Process terminated with status %s" %
-                              self.get_status())
-                done = True
-            # Are we done?
-            if done: break
-
-        # Print some debugging info
-        if match is None and (self.is_alive() or self.get_status() != 0):
-            logging.debug("Timeout elapsed or process terminated. Output:" +
-                          kvm_utils.format_str_for_message(data.strip()))
+                return match, o
 
-        return (match, data)
+        # Check if the child has terminated
+        if kvm_utils.wait_for(lambda: not self.is_alive(), 5, 0, 0.1):
+            raise ExpectProcessTerminatedError(patterns, self.get_status(), o)
+        else:
+            # This shouldn't happen
+            raise ExpectError(patterns, o)
 
 
     def read_until_last_word_matches(self, patterns, timeout=30.0,
@@ -936,8 +1009,11 @@ class kvm_expect(kvm_tail):
         @param internal_timeout: The timeout to pass to read_nonblocking
         @param print_func: A function to be used to print the data being read
                 (should take a string parameter)
-        @return: A tuple containing the match index (or None if no match was
-                found) and the data read so far.
+        @return: A tuple containing the match index and the data read so far
+        @raise ExpectTimeoutError: Raised if timeout expires
+        @raise ExpectProcessTerminatedError: Raised if the child process
+                terminates while waiting for output
+        @raise ExpectError: Raised if an unknown error occurs
         """
         def get_last_word(str):
             if str:
@@ -967,6 +1043,11 @@ class kvm_expect(kvm_tail):
         @param internal_timeout: The timeout to pass to read_nonblocking
         @param print_func: A function to be used to print the data being read
                 (should take a string parameter)
+        @return: A tuple containing the match index and the data read so far
+        @raise ExpectTimeoutError: Raised if timeout expires
+        @raise ExpectProcessTerminatedError: Raised if the child process
+                terminates while waiting for output
+        @raise ExpectError: Raised if an unknown error occurs
         """
         def get_last_nonempty_line(str):
             nonempty_lines = [l for l in str.splitlines() if l.strip()]
@@ -1101,31 +1182,34 @@ class kvm_shell_session(kvm_expect):
         @param print_func: A function to be used to print the data being
                 read (should take a string parameter)
 
-        @return: A tuple containing True/False indicating whether the prompt
-                was found, and the data read so far.
+        @return: The data read so far
+        @raise ExpectTimeoutError: Raised if timeout expires
+        @raise ExpectProcessTerminatedError: Raised if the shell process
+                terminates while waiting for output
+        @raise ExpectError: Raised if an unknown error occurs
         """
-        (match, output) = self.read_until_last_line_matches([self.prompt],
-                                                            timeout,
-                                                            internal_timeout,
-                                                            print_func)
-        return (match is not None, output)
+        m, o = self.read_until_last_line_matches([self.prompt], timeout,
+                                                 internal_timeout, print_func)
+        return o
 
 
-    def get_command_status_output(self, command, timeout=30.0,
-                                  internal_timeout=None, print_func=None):
+    def get_command_output(self, cmd, timeout=30.0, internal_timeout=None,
+                           print_func=None):
         """
-        Send a command and return its exit status and output.
+        Send a command and return its output.
 
-        @param command: Command to send (must not contain newline characters)
-        @param timeout: The duration (in seconds) to wait until a match is
-                found
+        @param cmd: Command to send (must not contain newline characters)
+        @param timeout: The duration (in seconds) to wait for the prompt to
+                return
         @param internal_timeout: The timeout to pass to read_nonblocking
         @param print_func: A function to be used to print the data being read
                 (should take a string parameter)
 
-        @return: A tuple (status, output) where status is the exit status or
-                None if no exit status is available (e.g. timeout elapsed), and
-                output is the output of command.
+        @return: The output of cmd
+        @raise ShellTimeoutError: Raised if timeout expires
+        @raise ShellProcessTerminatedError: Raised if the shell process
+                terminates while waiting for output
+        @raise ShellError: Raised if an unknown error occurs
         """
         def remove_command_echo(str, cmd):
             if str and str.splitlines()[0] == cmd:
@@ -1135,79 +1219,108 @@ class kvm_shell_session(kvm_expect):
         def remove_last_nonempty_line(str):
             return "".join(str.rstrip().splitlines(True)[:-1])
 
-        # Print some debugging info
-        logging.debug("Sending command: %s" % command)
-
-        # Read everything that's waiting to be read
+        logging.debug("Sending command: %s" % cmd)
         self.read_nonblocking(timeout=0)
+        self.sendline(cmd)
+        try:
+            o = self.read_up_to_prompt(timeout, internal_timeout, print_func)
+        except ExpectError, e:
+            o = remove_command_echo(e.output, cmd)
+            if isinstance(e, ExpectTimeoutError):
+                raise ShellTimeoutError(cmd, o)
+            elif isinstance(e, ExpectProcessTerminatedError):
+                raise ShellProcessTerminatedError(cmd, e.status, o)
+            else:
+                raise ShellError(cmd, o)
 
-        # Send the command and get its output
-        self.sendline(command)
-        (match, output) = self.read_up_to_prompt(timeout, internal_timeout,
-                                                 print_func)
-        # Remove the echoed command from the output
-        output = remove_command_echo(output, command)
-        # If the prompt was not found, return the output so far
-        if not match:
-            return (None, output)
-        # Remove the final shell prompt from the output
-        output = remove_last_nonempty_line(output)
-
-        # Send the 'echo ...' command to get the last exit status
-        self.sendline(self.status_test_command)
-        (match, status) = self.read_up_to_prompt(10.0, internal_timeout)
-        if not match:
-            return (None, output)
-        status = remove_command_echo(status, self.status_test_command)
-        status = remove_last_nonempty_line(status)
-        # Get the first line consisting of digits only
-        digit_lines = [l for l in status.splitlines() if l.strip().isdigit()]
-        if not digit_lines:
-            return (None, output)
-        status = int(digit_lines[0].strip())
+        # Remove the echoed command and the final shell prompt
+        return remove_last_nonempty_line(remove_command_echo(o, cmd))
+
+
+    def get_command_status_output(self, cmd, timeout=30.0,
+                                  internal_timeout=None, print_func=None):
+        """
+        Send a command and return its exit status and output.
+
+        @param cmd: Command to send (must not contain newline characters)
+        @param timeout: The duration (in seconds) to wait for the prompt to
+                return
+        @param internal_timeout: The timeout to pass to read_nonblocking
+        @param print_func: A function to be used to print the data being read
+                (should take a string parameter)
 
-        # Print some debugging info
-        if status != 0:
-            logging.debug("Command failed; status: %d, output:%s", status,
-                          kvm_utils.format_str_for_message(output.strip()))
+        @return: A tuple (status, output) where status is the exit status and
+                output is the output of cmd
+        @raise ShellTimeoutError: Raised if timeout expires
+        @raise ShellProcessTerminatedError: Raised if the shell process
+                terminates while waiting for output
+        @raise ShellStatusError: Raised if the exit status cannot be obtained
+        @raise ShellError: Raised if an unknown error occurs
+        """
+        o = self.get_command_output(cmd, timeout, internal_timeout, print_func)
+        try:
+            # Send the 'echo $?' (or equivalent) command to get the exit status
+            s = self.get_command_output(self.status_test_command, 10,
+                                        internal_timeout)
+        except ShellError:
+            raise ShellStatusError(cmd, o)
 
-        return (status, output)
+        # Get the first line consisting of digits only
+        digit_lines = [l for l in s.splitlines() if l.strip().isdigit()]
+        if digit_lines:
+            return int(digit_lines[0].strip()), o
+        else:
+            raise ShellStatusError(cmd, o)
 
 
-    def get_command_status(self, command, timeout=30.0, internal_timeout=None,
+    def get_command_status(self, cmd, timeout=30.0, internal_timeout=None,
                            print_func=None):
         """
         Send a command and return its exit status.
 
-        @param command: Command to send
-        @param timeout: The duration (in seconds) to wait until a match is
-                found
+        @param cmd: Command to send (must not contain newline characters)
+        @param timeout: The duration (in seconds) to wait for the prompt to
+                return
         @param internal_timeout: The timeout to pass to read_nonblocking
         @param print_func: A function to be used to print the data being read
                 (should take a string parameter)
 
-        @return: Exit status or None if no exit status is available (e.g.
-                timeout elapsed).
+        @return: The exit status of cmd
+        @raise ShellTimeoutError: Raised if timeout expires
+        @raise ShellProcessTerminatedError: Raised if the shell process
+                terminates while waiting for output
+        @raise ShellStatusError: Raised if the exit status cannot be obtained
+        @raise ShellError: Raised if an unknown error occurs
         """
-        (status, output) = self.get_command_status_output(command, timeout,
-                                                          internal_timeout,
-                                                          print_func)
-        return status
+        s, o = self.get_command_status_output(cmd, timeout, internal_timeout,
+                                              print_func)
+        return s
 
 
-    def get_command_output(self, command, timeout=30.0, internal_timeout=None,
-                           print_func=None):
+    def cmd(self, cmd, timeout=30.0, internal_timeout=None, print_func=None):
         """
-        Send a command and return its output.
+        Send a command and return its output. If the command's exit status is
+        nonzero, raise an exception.
 
-        @param command: Command to send
-        @param timeout: The duration (in seconds) to wait until a match is
-                found
+        @param cmd: Command to send (must not contain newline characters)
+        @param timeout: The duration (in seconds) to wait for the prompt to
+                return
         @param internal_timeout: The timeout to pass to read_nonblocking
         @param print_func: A function to be used to print the data being read
                 (should take a string parameter)
-        """
-        (status, output) = self.get_command_status_output(command, timeout,
-                                                          internal_timeout,
-                                                          print_func)
-        return output
+
+        @return: The output of cmd
+        @raise ShellTimeoutError: Raised if timeout expires
+        @raise ShellProcessTerminatedError: Raised if the shell process
+                terminates while waiting for output
+        @raise ShellError: Raised if the exit status cannot be obtained or if
+                an unknown error occurs
+        @raise ShellStatusError: Raised if the exit status cannot be obtained
+        @raise ShellError: Raised if an unknown error occurs
+        @raise ShellCmdError: Raised if the exit status is nonzero
+        """
+        s, o = self.get_command_status_output(cmd, timeout, internal_timeout,
+                                              print_func)
+        if s != 0:
+            raise ShellCmdError(cmd, s, o)
+        return o
diff --git a/client/tests/kvm/kvm_test_utils.py b/client/tests/kvm/kvm_test_utils.py
index 218bf31..c526d8e 100644
--- a/client/tests/kvm/kvm_test_utils.py
+++ b/client/tests/kvm/kvm_test_utils.py
@@ -318,10 +318,7 @@ def get_time(session, time_command, time_filter_re, time_format):
     """
     if len(re.findall("ntpdate|w32tm", time_command)) == 0:
         host_time = time.time()
-        session.sendline(time_command)
-        (match, s) = session.read_up_to_prompt()
-        if not match:
-            raise error.TestError("Could not get guest time")
+        s = session.get_command_output(time_command)
 
         try:
             s = re.findall(time_filter_re, s)[0]
@@ -335,9 +332,7 @@ def get_time(session, time_command, time_filter_re, time_format):
 
         guest_time = time.mktime(time.strptime(s, time_format))
     else:
-        s , o = session.get_command_status_output(time_command)
-        if s != 0:
-            raise error.TestError("Could not get guest time")
+        o = session.cmd(time_command)
         if re.match('ntpdate', time_command):
             offset = re.findall('offset (.*) sec',o)[0]
             host_main, host_mantissa = re.findall(time_filter_re, o)[0]
@@ -447,10 +442,7 @@ def run_autotest(vm, session, control_path, timeout, outputdir):
         basename = os.path.basename(remote_path)
         logging.info("Extracting %s...", basename)
         e_cmd = "tar xjvf %s -C %s" % (remote_path, dest_dir)
-        s, o = session.get_command_status_output(e_cmd, timeout=120)
-        if s != 0:
-            logging.error("Uncompress output:\n%s", o)
-            raise error.TestFail("Failed to extract %s on guest" % basename)
+        session.cmd(e_cmd, timeout=120)
 
 
     def get_results():
@@ -520,26 +512,32 @@ def run_autotest(vm, session, control_path, timeout, outputdir):
     # Run the test
     logging.info("Running autotest control file %s on guest, timeout %ss",
                  os.path.basename(control_path), timeout)
-    session.get_command_output("cd %s" % autotest_path)
-    session.get_command_output("rm -f control.state")
-    session.get_command_output("rm -rf results/*")
-    logging.info("---------------- Test output ----------------")
-    status = session.get_command_status("bin/autotest control",
-                                        timeout=timeout,
-                                        print_func=logging.info)
-    logging.info("------------- End of test output ------------")
-    if status is None:
-        if not vm.is_alive():
-            raise error.TestError("Autotest job on guest failed "
-                                  "(VM terminated during job)")
-        if not session.is_alive():
+    session.cmd("cd %s" % autotest_path)
+    try:
+        session.cmd("rm -f control.state")
+        session.cmd("rm -rf results/*")
+    except kvm_subprocess.ShellError:
+        pass
+    try:
+        try:
+            logging.info("---------------- Test output ----------------")
+            session.get_command_output("bin/autotest control", timeout=timeout,
+                                       print_func=logging.info)
+        finally:
+            logging.info("------------- End of test output ------------")
+    except kvm_subprocess.ShellTimeoutError:
+        if vm.is_alive():
             get_results()
+            get_results_summary()
+            raise error.TestError("Timeout elapsed while waiting for job to "
+                                  "complete")
+        else:
             raise error.TestError("Autotest job on guest failed "
-                                  "(Remote session terminated during job)")
+                                  "(VM terminated during job)")
+    except kvm_subprocess.ShellProcessTerminatedError:
         get_results()
-        get_results_summary()
-        raise error.TestError("Timeout elapsed while waiting for job to "
-                              "complete")
+        raise error.TestError("Autotest job on guest failed "
+                              "(Remote session terminated during job)")
 
     results = get_results_summary()
     get_results()
@@ -648,21 +646,24 @@ def raw_ping(command, timeout, session, output_func):
         process.close()
         return status, output
     else:
-        session.sendline(command)
-        status, output = session.read_up_to_prompt(timeout=timeout,
-                                                   print_func=output_func)
-        if not status:
+        try:
+            output = session.get_command_output(command, timeout=timeout,
+                                                print_func=output_func)
+        except kvm_subprocess.ShellTimeoutError:
             # Send ctrl+c (SIGINT) through ssh session
             session.send("\003")
-            status, output2 = session.read_up_to_prompt(print_func=output_func)
-            output += output2
-            if not status:
+            try:
+                output2 = session.read_up_to_prompt(print_func=output_func)
+                output += output2
+            except kvm_subprocess.ExpectTimeoutError, e:
+                output += e.output
                 # We also need to use this session to query the return value
                 session.send("\003")
 
         session.sendline(session.status_test_command)
-        s2, o2 = session.read_up_to_prompt()
-        if not s2:
+        try:
+            o2 = session.read_up_to_prompt()
+        except kvm_subprocess.ExpectError:
             status = -1
         else:
             try:
diff --git a/client/tests/kvm/kvm_utils.py b/client/tests/kvm/kvm_utils.py
index b849b37..68acb6b 100644
--- a/client/tests/kvm/kvm_utils.py
+++ b/client/tests/kvm/kvm_utils.py
@@ -456,48 +456,52 @@ def _remote_login(session, username, password, prompt, timeout=10):
     login_prompt_count = 0
 
     while True:
-        (match, text) = session.read_until_last_line_matches(
+        try:
+            match, text = session.read_until_last_line_matches(
                 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"[Ll]ogin:\s*$",
                  r"[Cc]onnection.*closed", r"[Cc]onnection.*refused",
                  r"[Pp]lease wait", prompt],
-                 timeout=timeout, internal_timeout=0.5)
-        if match == 0:  # "Are you sure you want to continue connecting"
-            logging.debug("Got 'Are you sure...'; sending 'yes'")
-            session.sendline("yes")
-            continue
-        elif match == 1:  # "password:"
-            if password_prompt_count == 0:
-                logging.debug("Got password prompt; sending '%s'" % password)
-                session.sendline(password)
-                password_prompt_count += 1
+                timeout=timeout, internal_timeout=0.5)
+            if match == 0:  # "Are you sure you want to continue connecting"
+                logging.debug("Got 'Are you sure...'; sending 'yes'")
+                session.sendline("yes")
                 continue
-            else:
-                logging.debug("Got password prompt again")
+            elif match == 1:  # "password:"
+                if password_prompt_count == 0:
+                    logging.debug("Got password prompt; sending '%s'" % password)
+                    session.sendline(password)
+                    password_prompt_count += 1
+                    continue
+                else:
+                    logging.debug("Got password prompt again")
+                    return False
+            elif match == 2:  # "login:"
+                if login_prompt_count == 0:
+                    logging.debug("Got username prompt; sending '%s'" % username)
+                    session.sendline(username)
+                    login_prompt_count += 1
+                    continue
+                else:
+                    logging.debug("Got username prompt again")
+                    return False
+            elif match == 3:  # "Connection closed"
+                logging.debug("Got 'Connection closed'")
                 return False
-        elif match == 2:  # "login:"
-            if login_prompt_count == 0:
-                logging.debug("Got username prompt; sending '%s'" % username)
-                session.sendline(username)
-                login_prompt_count += 1
-                continue
-            else:
-                logging.debug("Got username prompt again")
+            elif match == 4:  # "Connection refused"
+                logging.debug("Got 'Connection refused'")
                 return False
-        elif match == 3:  # "Connection closed"
-            logging.debug("Got 'Connection closed'")
-            return False
-        elif match == 4:  # "Connection refused"
-            logging.debug("Got 'Connection refused'")
+            elif match == 5:  # "Please wait"
+                logging.debug("Got 'Please wait'")
+                timeout = 30
+                continue
+            elif match == 6:  # prompt
+                logging.debug("Got shell prompt -- logged in")
+                return True
+        except kvm_subprocess.ExpectTimeoutError, e:
+            logging.debug("Timeout elapsed (output so far: %r)" % e.output)
             return False
-        elif match == 5:  # "Please wait"
-            logging.debug("Got 'Please wait'")
-            timeout = 30
-            continue
-        elif match == 6:  # prompt
-            logging.debug("Got shell prompt -- logged in")
-            return session
-        else:  # match == None
-            logging.debug("Timeout elapsed or process terminated")
+        except kvm_subprocess.ExpectProcessTerminatedError, e:
+            logging.debug("Process terminated (output so far: %r)" % e.output)
             return False
 
 
@@ -524,34 +528,33 @@ def _remote_scp(session, password, transfer_timeout=600, login_timeout=10):
     timeout = login_timeout
 
     while True:
-        (match, text) = session.read_until_last_line_matches(
+        try:
+            match, text = session.read_until_last_line_matches(
                 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"lost connection"],
                 timeout=timeout, internal_timeout=0.5)
-        if match == 0:  # "Are you sure you want to continue connecting"
-            logging.debug("Got 'Are you sure...'; sending 'yes'")
-            session.sendline("yes")
-            continue
-        elif match == 1:  # "password:"
-            if password_prompt_count == 0:
-                logging.debug("Got password prompt; sending '%s'" % password)
-                session.sendline(password)
-                password_prompt_count += 1
-                timeout = transfer_timeout
+            if match == 0:  # "Are you sure you want to continue connecting"
+                logging.debug("Got 'Are you sure...'; sending 'yes'")
+                session.sendline("yes")
                 continue
-            else:
-                logging.debug("Got password prompt again")
+            elif match == 1:  # "password:"
+                if password_prompt_count == 0:
+                    logging.debug("Got password prompt; sending '%s'" % password)
+                    session.sendline(password)
+                    password_prompt_count += 1
+                    timeout = transfer_timeout
+                    continue
+                else:
+                    logging.debug("Got password prompt again")
+                    return False
+            elif match == 2:  # "lost connection"
+                logging.debug("Got 'lost connection'")
                 return False
-        elif match == 2:  # "lost connection"
-            logging.debug("Got 'lost connection'")
+        except kvm_subprocess.ExpectTimeoutError, e:
+            logging.debug("Timeout expired")
             return False
-        else:  # match == None
-            if session.is_alive():
-                logging.debug("Timeout expired")
-                return False
-            else:
-                status = session.get_status()
-                logging.debug("SCP process terminated with status %s", status)
-                return status == 0
+        except kvm_subprocess.ExpectProcessTerminatedError, e:
+            logging.debug("SCP process terminated with status %s", e.status)
+            return e.status == 0
 
 
 def remote_login(client, host, port, username, password, prompt, linesep="\n",
diff --git a/client/tests/kvm/kvm_vm.py b/client/tests/kvm/kvm_vm.py
index fdbaa90..4eabd15 100755
--- a/client/tests/kvm/kvm_vm.py
+++ b/client/tests/kvm/kvm_vm.py
@@ -1212,11 +1212,7 @@ class VM:
         if not session:
             return None
         try:
-            cmd = self.params.get("cpu_chk_cmd")
-            s, count = session.get_command_status_output(cmd)
-            if s == 0:
-                return int(count)
-            return None
+            return int(session.cmd(self.params.get("cpu_chk_cmd")))
         finally:
             session.close()
 
@@ -1234,9 +1230,7 @@ class VM:
         try:
             if not cmd:
                 cmd = self.params.get("mem_chk_cmd")
-            s, mem_str = session.get_command_status_output(cmd)
-            if s != 0:
-                return None
+            mem_str = session.cmd(cmd)
             mem = re.findall("([0-9]+)", mem_str)
             mem_size = 0
             for m in mem:
diff --git a/client/tests/kvm/tests/clock_getres.py b/client/tests/kvm/tests/clock_getres.py
index 94f2682..edda355 100644
--- a/client/tests/kvm/tests/clock_getres.py
+++ b/client/tests/kvm/tests/clock_getres.py
@@ -33,12 +33,7 @@ def run_clock_getres(test, params, env):
     session = kvm_test_utils.wait_for_login(vm, timeout=timeout)
     if not vm.copy_files_to(test_clock, base_dir):
         raise error.TestError("Failed to copy %s to VM" % t_name)
-    s, o = session.get_command_status_output(os.path.join(base_dir, t_name))
-    if s:
-        raise error.TestFail("Execution of %s failed: %s" % (t_name, o))
-    else:
-        logging.info("PASS: Guest reported an appropriate clock source "
-                     "resolution")
-    s, o = session.get_command_status_output("dmesg")
+    session.cmd(os.path.join(base_dir, t_name))
+    logging.info("PASS: Guest reported appropriate clock resolution")
     logging.info("guest's dmesg:")
-    logging.info(o)
+    session.get_command_output("dmesg")
diff --git a/client/tests/kvm/tests/ethtool.py b/client/tests/kvm/tests/ethtool.py
index 56b1c70..9c4da9d 100644
--- a/client/tests/kvm/tests/ethtool.py
+++ b/client/tests/kvm/tests/ethtool.py
@@ -1,7 +1,7 @@
 import logging, commands, re
 from autotest_lib.client.common_lib import error
 from autotest_lib.client.bin import utils
-import kvm_test_utils, kvm_utils
+import kvm_test_utils, kvm_utils, kvm_subprocess
 
 def run_ethtool(test, params, env):
     """
@@ -32,7 +32,7 @@ def run_ethtool(test, params, env):
             'gro': 'generic.*receive.*offload',
             'lro': 'large.*receive.*offload',
             }
-        s, o = session.get_command_status_output("ethtool -k %s" % ethname)
+        o = session.cmd("ethtool -k %s" % ethname)
         try:
             return re.findall("%s: (.*)" % feature_pattern.get(type), o)[0]
         except IndexError:
@@ -51,7 +51,11 @@ def run_ethtool(test, params, env):
             return False
         cmd = "ethtool -K %s %s %s" % (ethname, type, status)
         if ethtool_get(type) != status:
-            return session.get_command_status(cmd) == 0
+            try:
+                session.cmd(cmd)
+                return True
+            except:
+                return False
         if ethtool_get(type) != status:
             logging.error("Fail to set %s %s" % (type, status))
             return False
@@ -92,13 +96,13 @@ def run_ethtool(test, params, env):
         @param src: Source host of transfer file
         @return: Tuple (status, error msg/tcpdump result)
         """
-        session2.get_command_status("rm -rf %s" % filename)
-        dd_cmd = "dd if=/dev/urandom of=%s bs=1M count=%s" % (filename,
-                                                   params.get("filesize"))
+        session2.get_command_output("rm -rf %s" % filename)
+        dd_cmd = ("dd if=/dev/urandom of=%s bs=1M count=%s" %
+                  (filename, params.get("filesize")))
         logging.info("Creat file in source host, cmd: %s" % dd_cmd)
         tcpdump_cmd = "tcpdump -lep -s 0 tcp -vv port ssh"
         if src == "guest":
-            s = session.get_command_status(dd_cmd, timeout=360)
+            session.get_command_output(dd_cmd, timeout=360)
             tcpdump_cmd += " and src %s" % guest_ip
             copy_files_fun = vm.copy_files_from
         else:
@@ -123,10 +127,10 @@ def run_ethtool(test, params, env):
         if not copy_files_fun(filename, filename):
             return (False, "Child process transfer file failed")
         logging.info("Transfer file completed")
-        if session.get_command_status("killall tcpdump") != 0:
-            return (False, "Could not kill all tcpdump process")
-        s, tcpdump_string = session2.read_up_to_prompt(timeout=60)
-        if not s:
+        session.cmd("killall tcpdump")
+        try:
+            tcpdump_string = session2.read_up_to_prompt(timeout=60)
+        except kvm_subprocess.ExpectError:
             return (False, "Fail to read tcpdump's output")
 
         if not compare_md5sum(filename):
@@ -173,8 +177,7 @@ def run_ethtool(test, params, env):
     session = kvm_test_utils.wait_for_login(vm,
                   timeout=int(params.get("login_timeout", 360)))
     # Let's just error the test if we identify that there's no ethtool installed
-    if session.get_command_status("ethtool -h"):
-        raise error.TestError("Command ethtool not installed on guest")
+    session.cmd("ethtool -h")
     session2 = kvm_test_utils.wait_for_login(vm,
                   timeout=int(params.get("login_timeout", 360)))
     mtu = 1514
diff --git a/client/tests/kvm/tests/file_transfer.py b/client/tests/kvm/tests/file_transfer.py
index 4155434..b27bc75 100644
--- a/client/tests/kvm/tests/file_transfer.py
+++ b/client/tests/kvm/tests/file_transfer.py
@@ -80,10 +80,7 @@ def run_file_transfer(test, params, env):
     finally:
         logging.info('Cleaning temp file on guest')
         clean_cmd += " %s" % guest_path
-        s, o = session.get_command_status_output(clean_cmd)
-        if s:
-            logging.warning("Failed to clean remote file %s, output:%s",
-                            guest_path, o)
+        session.cmd(clean_cmd)
         logging.info('Cleaning temp files on host')
         try:
             os.remove('%s/a.out' % dir_name)
diff --git a/client/tests/kvm/tests/guest_s4.py b/client/tests/kvm/tests/guest_s4.py
index 2eb035b..1c69ce0 100644
--- a/client/tests/kvm/tests/guest_s4.py
+++ b/client/tests/kvm/tests/guest_s4.py
@@ -16,11 +16,7 @@ def run_guest_s4(test, params, env):
     session = kvm_test_utils.wait_for_login(vm, timeout=timeout)
 
     logging.info("Checking whether guest OS supports suspend to disk (S4)...")
-    s, o = session.get_command_status_output(params.get("check_s4_support_cmd"))
-    if "not enough space" in o:
-        raise error.TestError("Check S4 support failed: %s" % o)
-    elif s != 0:
-        raise error.TestNAError("Guest OS does not support S4")
+    session.cmd(params.get("check_s4_support_cmd"))
 
     logging.info("Waiting until all guest OS services are fully started...")
     time.sleep(float(params.get("services_up_timeout", 30)))
@@ -36,9 +32,7 @@ def run_guest_s4(test, params, env):
 
     # Make sure the background program is running as expected
     check_s4_cmd = params.get("check_s4_cmd")
-    if session2.get_command_status(check_s4_cmd) != 0:
-        raise error.TestError("Failed to launch '%s' as a background process" %
-                              test_s4_cmd)
+    session2.cmd(check_s4_cmd)
     logging.info("Launched background command in guest: %s" % test_s4_cmd)
 
     # Suspend to disk
@@ -68,9 +62,7 @@ def run_guest_s4(test, params, env):
 
     # Check whether the test command is still alive
     logging.info("Checking if background command is still alive...")
-    if session2.get_command_status(check_s4_cmd) != 0:
-        raise error.TestFail("Background command '%s' stopped running. S4 "
-                             "failed." % test_s4_cmd)
+    session2.cmd(check_s4_cmd)
 
     logging.info("VM resumed successfuly after suspend to disk")
     session2.get_command_output(params.get("kill_test_s4_cmd"))
diff --git a/client/tests/kvm/tests/guest_test.py b/client/tests/kvm/tests/guest_test.py
index 84595d6..e15c7b0 100644
--- a/client/tests/kvm/tests/guest_test.py
+++ b/client/tests/kvm/tests/guest_test.py
@@ -50,14 +50,12 @@ def run_guest_test(test, params, env):
             # Change dir to dst_rsc_dir, and remove the guest script dir there
             rm_cmd = "cd %s && (rmdir /s /q %s || del /s /q %s)" % \
                      (dst_rsc_dir, rsc_dir, rsc_dir)
-            if session.get_command_status(rm_cmd, timeout=test_timeout) != 0:
-                raise error.TestFail("Remove %s failed." % rsc_dir)
+            session.cmd(rm_cmd, timeout=test_timeout)
             logging.debug("Clean directory succeeded.")
 
             # then download the resource.
             rsc_cmd = "cd %s && %s %s" %(dst_rsc_dir, download_cmd, rsc_server)
-            if session.get_command_status(rsc_cmd, timeout=test_timeout) != 0:
-                raise error.TestFail("Download test resource failed.")
+            session.cmd(rsc_cmd, timeout=test_timeout)
             logging.info("Download resource finished.")
         else:
             session.get_command_output("del %s" % dst_rsc_path,
@@ -65,19 +63,13 @@ def run_guest_test(test, params, env):
             script_path = kvm_utils.get_path(test.bindir, script)
             vm.copy_files_to(script_path, dst_rsc_path, timeout=60)
 
-        command = "%s %s %s" %(interpreter, dst_rsc_path, script_params)
+        cmd = "%s %s %s" % (interpreter, dst_rsc_path, script_params)
 
-        logging.info("---------------- Script output ----------------")
-        status = session.get_command_status(command,
-                                            print_func=logging.info,
-                                            timeout=test_timeout)
-        logging.info("---------------- End of script output ----------------")
-
-        if status is None:
-            raise error.TestFail("Timeout expired before script execution "
-                                 "completed (or something weird happened)")
-        if status != 0:
-            raise error.TestFail("Script execution failed")
+        try:
+            logging.info("------------ Script output ------------")
+            session.cmd(cmd, print_func=logging.info, timeout=test_timeout)
+        finally:
+            logging.info("------------ End of script output ------------")
 
         if reboot == "yes":
             logging.debug("Rebooting guest after test ...")
diff --git a/client/tests/kvm/tests/iofuzz.py b/client/tests/kvm/tests/iofuzz.py
index 45a0eb9..77f4761 100644
--- a/client/tests/kvm/tests/iofuzz.py
+++ b/client/tests/kvm/tests/iofuzz.py
@@ -33,11 +33,10 @@ def run_iofuzz(test, params, env):
         logging.debug("outb(0x%x, 0x%x)", port, data)
         outb_cmd = ("echo -e '\\%s' | dd of=/dev/port seek=%d bs=1 count=1" %
                     (oct(data), port))
-        s, o = session.get_command_status_output(outb_cmd)
-        if s is None:
-            logging.debug("Command did not return")
-        if s != 0:
-            logging.debug("Command returned status %s", s)
+        try:
+            session.cmd(outb_cmd)
+        except kvm_subprocess.ShellError, e:
+            logging.debug(e)
 
 
     def inb(session, port):
@@ -49,11 +48,10 @@ def run_iofuzz(test, params, env):
         """
         logging.debug("inb(0x%x)", port)
         inb_cmd = "dd if=/dev/port seek=%d of=/dev/null bs=1 count=1" % port
-        s, o = session.get_command_status_output(inb_cmd)
-        if s is None:
-            logging.debug("Command did not return")
-        if s != 0:
-            logging.debug("Command returned status %s", s)
+        try:
+            session.cmd(inb_cmd)
+        except kvm_subprocess.ShellError, e:
+            logging.debug(e)
 
 
     def fuzz(session, inst_list):
diff --git a/client/tests/kvm/tests/ioquit.py b/client/tests/kvm/tests/ioquit.py
index 8126139..132637d 100644
--- a/client/tests/kvm/tests/ioquit.py
+++ b/client/tests/kvm/tests/ioquit.py
@@ -20,17 +20,13 @@ def run_ioquit(test, params, env):
     try:
         bg_cmd = params.get("background_cmd")
         logging.info("Add IO workload for guest OS.")
-        (s, o) = session.get_command_status_output(bg_cmd, timeout=60)
+        session.get_command_output(bg_cmd, timeout=60)
         check_cmd = params.get("check_cmd")
-        (s, o) = session2.get_command_status_output(check_cmd, timeout=60)
-        if s:
-            raise error.TestError("Fail to add IO workload for Guest OS")
+        session2.cmd(check_cmd, timeout=60)
 
         logging.info("Sleep for a while")
         time.sleep(random.randrange(30,100))
-        (s, o) = session2.get_command_status_output(check_cmd, timeout=60)
-        if s:
-            logging.info("IO workload finished before the VM was killed")
+        session2.cmd(check_cmd, timeout=60)
         logging.info("Kill the virtual machine")
         vm.process.close()
     finally:
diff --git a/client/tests/kvm/tests/jumbo.py b/client/tests/kvm/tests/jumbo.py
index 2c91c83..1fbce8b 100644
--- a/client/tests/kvm/tests/jumbo.py
+++ b/client/tests/kvm/tests/jumbo.py
@@ -40,11 +40,7 @@ def run_jumbo(test, params, env):
 
         logging.info("Changing the MTU of guest ...")
         guest_mtu_cmd = "ifconfig %s mtu %s" % (ethname , mtu)
-        s, o = session.get_command_status_output(guest_mtu_cmd)
-        if s != 0:
-            logging.error(o)
-            raise error.TestError("Fail to set the MTU of guest NIC: %s" %
-                                  ethname)
+        session.cmd(guest_mtu_cmd)
 
         logging.info("Chaning the MTU of host tap ...")
         host_mtu_cmd = "ifconfig %s mtu %s" % (ifname, mtu)
diff --git a/client/tests/kvm/tests/kdump.py b/client/tests/kvm/tests/kdump.py
index 43e7a91..006882c 100644
--- a/client/tests/kvm/tests/kdump.py
+++ b/client/tests/kvm/tests/kdump.py
@@ -34,7 +34,7 @@ def run_kdump(test, params, env):
         @param vcpu: vcpu which is used to trigger a crash
         """
         session = kvm_test_utils.wait_for_login(vm, 0, timeout, 0, 2)
-        session.get_command_status("rm -rf /var/crash/*")
+        session.get_command_output("rm -rf /var/crash/*")
 
         logging.info("Triggering crash on vcpu %d ...", vcpu)
         crash_cmd = "taskset -c %d echo c > /proc/sysrq-trigger" % vcpu
@@ -48,31 +48,23 @@ def run_kdump(test, params, env):
         session = kvm_test_utils.wait_for_login(vm, 0, crash_timeout, 0, 2)
 
         logging.info("Probing vmcore file...")
-        s = session.get_command_status("ls -R /var/crash | grep vmcore")
-        if s != 0:
-            raise error.TestFail("Could not find the generated vmcore file")
-        else:
-            logging.info("Found vmcore.")
+        session.cmd("ls -R /var/crash | grep vmcore")
+        logging.info("Found vmcore.")
 
-        session.get_command_status("rm -rf /var/crash/*")
+        session.get_command_output("rm -rf /var/crash/*")
 
     try:
         logging.info("Checking the existence of crash kernel...")
-        s = session.get_command_status(crash_kernel_prob_cmd)
-        if s != 0:
+        try:
+            session.cmd(crash_kernel_prob_cmd)
+        except:
             logging.info("Crash kernel is not loaded. Trying to load it")
-            # We need to setup the kernel params
-            s, o = session.get_command_status_output(kernel_param_cmd)
-            if s != 0:
-                raise error.TestFail("Could not add crashkernel params to"
-                                     "kernel")
-            session = kvm_test_utils.reboot(vm, session, timeout=timeout);
+            session.get_command_status_output(kernel_param_cmd)
+            session = kvm_test_utils.reboot(vm, session, timeout=timeout)
 
         logging.info("Enabling kdump service...")
         # the initrd may be rebuilt here so we need to wait a little more
-        s, o = session.get_command_status_output(kdump_enable_cmd, timeout=120)
-        if s != 0:
-            raise error.TestFail("Could not enable kdump service: %s" % o)
+        session.cmd(kdump_enable_cmd, timeout=120)
 
         nvcpu = int(params.get("smp", 1))
         for i in range (nvcpu):
diff --git a/client/tests/kvm/tests/ksm_overcommit.py b/client/tests/kvm/tests/ksm_overcommit.py
index dd4a30d..dbd05dc 100644
--- a/client/tests/kvm/tests/ksm_overcommit.py
+++ b/client/tests/kvm/tests/ksm_overcommit.py
@@ -29,7 +29,7 @@ def run_ksm_overcommit(test, params, env):
         session.sendline("python /tmp/allocator.py")
         (match, data) = session.read_until_last_line_matches(["PASS:", "FAIL:"],
                                                              timeout)
-        if match == 1 or match is None:
+        if match != 0:
             raise error.TestFail("Command allocator.py on guest %s failed.\n"
                                  "return code: %s\n output:\n%s" %
                                  (vm.name, match, data))
@@ -52,7 +52,7 @@ def run_ksm_overcommit(test, params, env):
         session.sendline(command)
         (match, data) = session.read_until_last_line_matches(["PASS:","FAIL:"],
                                                              timeout)
-        if match == 1 or match is None:
+        if match != 0:
             raise error.TestFail("Failed to execute '%s' on allocator.py, "
                                  "vm: %s, output:\n%s" %
                                  (command, vm.name, data))
@@ -80,9 +80,7 @@ def run_ksm_overcommit(test, params, env):
             vm = lvms[lsessions.index(session)]
 
             logging.debug("Turning off swap on vm %s" % vm.name)
-            ret = session.get_command_status("swapoff -a", timeout=300)
-            if ret is None or ret:
-                raise error.TestFail("Failed to swapoff on VM %s" % vm.name)
+            session.cmd("swapoff -a", timeout=300)
 
             # Start the allocator
             _start_allocator(vm, session, 60 * perf_ratio)
@@ -232,7 +230,7 @@ def run_ksm_overcommit(test, params, env):
                            (mem / 200 * 50 * perf_ratio))
         logging.debug(kvm_test_utils.get_memory_info([lvms[last_vm]]))
 
-        (status, data) = lsessions[i].get_command_status_output("die()", 20)
+        lsessions[i].get_command_output("die()", 20)
         lvms[last_vm].destroy(gracefully = False)
         logging.info("Phase 3b: PASS")
 
@@ -253,9 +251,7 @@ def run_ksm_overcommit(test, params, env):
                 raise error.TestFail("Could not log into guest %s" %
                                      vm.name)
 
-        ret = session.get_command_status("swapoff -a", timeout=300)
-        if ret != 0:
-            raise error.TestFail("Failed to turn off swap on %s" % vm.name)
+        session.cmd("swapoff -a", timeout=300)
 
         for i in range(0, max_alloc):
             # Start the allocator
@@ -360,7 +356,7 @@ def run_ksm_overcommit(test, params, env):
 
         logging.debug("Cleaning up...")
         for i in range(0, max_alloc):
-            lsessions[i].get_command_status_output("die()", 20)
+            lsessions[i].get_command_output("die()", 20)
         session.close()
         vm.destroy(gracefully = False)
 
diff --git a/client/tests/kvm/tests/linux_s3.py b/client/tests/kvm/tests/linux_s3.py
index 4a782b8..1bbd19c 100644
--- a/client/tests/kvm/tests/linux_s3.py
+++ b/client/tests/kvm/tests/linux_s3.py
@@ -16,11 +16,7 @@ def run_linux_s3(test, params, env):
     session = kvm_test_utils.wait_for_login(vm, timeout=timeout)
 
     logging.info("Checking that VM supports S3")
-    status = session.get_command_status("grep -q mem /sys/power/state")
-    if status == None:
-        logging.error("Failed to check if S3 exists")
-    elif status != 0:
-        raise error.TestFail("Guest does not support S3")
+    session.cmd("grep -q mem /sys/power/state")
 
     logging.info("Waiting for a while for X to start")
     time.sleep(10)
@@ -38,9 +34,7 @@ def run_linux_s3(test, params, env):
     command = "chvt %s && echo mem > /sys/power/state && chvt %s" % (dst_tty,
                                                                      src_tty)
     suspend_timeout = 120 + int(params.get("smp")) * 60
-    status = session.get_command_status(command, timeout=suspend_timeout)
-    if status != 0:
-        raise error.TestFail("Suspend to mem failed")
+    session.cmd(command, timeout=suspend_timeout)
 
     logging.info("VM resumed after S3")
 
diff --git a/client/tests/kvm/tests/mac_change.py b/client/tests/kvm/tests/mac_change.py
index 8b263f3..78fbab2 100644
--- a/client/tests/kvm/tests/mac_change.py
+++ b/client/tests/kvm/tests/mac_change.py
@@ -33,13 +33,11 @@ def run_mac_change(test, params, env):
     logging.info("Changing MAC address to %s", new_mac)
     change_cmd = ("ifconfig %s down && ifconfig %s hw ether %s && "
                   "ifconfig %s up" % (interface, interface, new_mac, interface))
-    if session_serial.get_command_status(change_cmd) != 0:
-        raise error.TestFail("Fail to send mac_change command")
+    session_serial.cmd(change_cmd)
 
     # Verify whether MAC address was changed to the new one
     logging.info("Verifying the new mac address")
-    if session_serial.get_command_status("ifconfig | grep -i %s" % new_mac) != 0:
-        raise error.TestFail("Fail to change MAC address")
+    session_serial.cmd("ifconfig | grep -i %s" % new_mac)
 
     # Restart `dhclient' to regain IP for new mac address
     logging.info("Restart the network to gain new IP")
diff --git a/client/tests/kvm/tests/migration.py b/client/tests/kvm/tests/migration.py
index d6f4b11..513b500 100644
--- a/client/tests/kvm/tests/migration.py
+++ b/client/tests/kvm/tests/migration.py
@@ -42,9 +42,7 @@ def run_migration(test, params, env):
 
     try:
         check_command = params.get("migration_bg_check_command", "")
-        if session2.get_command_status(check_command, timeout=30) != 0:
-            raise error.TestError("Could not start background process '%s'" %
-                                  background_command)
+        session2.cmd(check_command, timeout=30)
         session2.close()
 
         # Migrate the VM
@@ -59,9 +57,7 @@ def run_migration(test, params, env):
         logging.info("Logged in after migration")
 
         # Make sure the background process is still running
-        if session2.get_command_status(check_command, timeout=30) != 0:
-            raise error.TestFail("Could not find running background process "
-                                 "after migration: '%s'" % background_command)
+        session2.cmd(check_command, timeout=30)
 
         # Get the output of migration_test_command
         output = session2.get_command_output(test_command)
diff --git a/client/tests/kvm/tests/multicast.py b/client/tests/kvm/tests/multicast.py
index a47779a..4a1b4f5 100644
--- a/client/tests/kvm/tests/multicast.py
+++ b/client/tests/kvm/tests/multicast.py
@@ -1,7 +1,7 @@
 import logging, os, re
 from autotest_lib.client.common_lib import error
 from autotest_lib.client.bin import utils
-import kvm_test_utils
+import kvm_test_utils, kvm_subprocess
 
 
 def run_multicast(test, params, env):
@@ -23,10 +23,10 @@ def run_multicast(test, params, env):
                                   timeout=int(params.get("login_timeout", 360)))
 
     def run_guest(cmd):
-        s, o = session.get_command_status_output(cmd)
-        if s:
-            logging.warning('Command %s executed in guest returned exit code '
-                            '%s, output: %s', cmd, s, o.strip())
+        try:
+            session.cmd(cmd)
+        except kvm_subprocess.ShellError, e:
+            logging.warn(e)
 
     def run_host_guest(cmd):
         run_guest(cmd)
diff --git a/client/tests/kvm/tests/netperf.py b/client/tests/kvm/tests/netperf.py
index dc21e0f..64ff4c6 100644
--- a/client/tests/kvm/tests/netperf.py
+++ b/client/tests/kvm/tests/netperf.py
@@ -1,7 +1,7 @@
 import logging, commands, os
 from autotest_lib.client.common_lib import error
 from autotest_lib.client.bin import utils
-import kvm_test_utils
+import kvm_test_utils, kvm_subprocess
 
 def run_netperf(test, params, env):
     """
@@ -32,14 +32,13 @@ def run_netperf(test, params, env):
         if not vm.copy_files_to(os.path.join(netperf_dir, i), "/tmp"):
             raise error.TestError("Could not copy file %s to guest" % i)
 
-    if session.get_command_status(firewall_flush):
+    try:
+        session.cmd(firewall_flush):
+    except kvm_subprocess.ShellError:
         logging.warning("Could not flush firewall rules on guest")
 
-    if session.get_command_status(setup_cmd % "/tmp", timeout=200):
-        raise error.TestFail("Fail to setup netperf on guest")
-
-    if session.get_command_status(params.get("netserver_cmd") % "/tmp"):
-        raise error.TestFail("Fail to start netperf server on guest")
+    session.cmd(setup_cmd % "/tmp", timeout=200)
+    session.cmd(params.get("netserver_cmd") % "/tmp")
 
     try:
         logging.info("Setup and run netperf client on host")
diff --git a/client/tests/kvm/tests/nic_bonding.py b/client/tests/kvm/tests/nic_bonding.py
index 3b1fc8c..087b099 100644
--- a/client/tests/kvm/tests/nic_bonding.py
+++ b/client/tests/kvm/tests/nic_bonding.py
@@ -37,10 +37,7 @@ def run_nic_bonding(test, params, env):
     script_path = kvm_utils.get_path(test.bindir, "scripts/bonding_setup.py")
     vm.copy_files_to(script_path, "/tmp/bonding_setup.py")
     cmd = "python /tmp/bonding_setup.py %s" % vm.get_mac_address()
-    s, o = session_serial.get_command_status_output(cmd)
-    if s != 0:
-        logging.debug(o)
-        raise error.TestFail("Fail to setup bonding")
+    session_serial.cmd(cmd)
 
     termination_event = threading.Event()
     t = threading.Thread(target=control_link_loop,
diff --git a/client/tests/kvm/tests/nic_promisc.py b/client/tests/kvm/tests/nic_promisc.py
index a8eaa39..e6354e2 100644
--- a/client/tests/kvm/tests/nic_promisc.py
+++ b/client/tests/kvm/tests/nic_promisc.py
@@ -25,13 +25,8 @@ def run_nic_promisc(test, params, env):
                                                    serial=True)
 
     def compare(filename):
-        cmd = "md5sum %s" % filename
         md5_host = utils.hash_file(filename, method="md5")
-        rc_guest, md5_guest = session.get_command_status_output(cmd)
-        if rc_guest:
-            logging.debug("Could not get MD5 hash for file %s on guest,"
-                          "output: %s", filename, md5_guest)
-            return False
+        md5_guest = session.cmd("md5sum %s" % filename)
         md5_guest = md5_guest.split()[0]
         if md5_host != md5_guest:
             logging.error("MD5 hash mismatch between file %s "
@@ -85,10 +80,7 @@ def run_nic_promisc(test, params, env):
                 success_counter += 1
 
             logging.info("Create %s bytes file on guest" % size)
-            if session.get_command_status(dd_cmd % (filename, int(size)),
-                                                    timeout=100) != 0:
-                logging.error("Create file on guest failed")
-                continue
+            session.cmd(dd_cmd % (filename, int(size)), timeout=100)
 
             logging.info("Transfer file from guest to host")
             if not vm.copy_files_from(filename, filename):
@@ -103,14 +95,14 @@ def run_nic_promisc(test, params, env):
             logging.info("Clean temporary files")
             cmd = "rm -f %s" % filename
             utils.run(cmd)
-            session.get_command_status(cmd)
+            session.get_command_output(cmd)
 
     finally:
         logging.info("Stopping the promisc thread")
         termination_event.set()
         promisc_thread.join(10)
         logging.info("Restore the %s to the nonpromisc mode", ethname)
-        session.get_command_status("ip link set %s promisc off" % ethname)
+        session.get_command_output("ip link set %s promisc off" % ethname)
         session.close()
 
     if success_counter != 2 * len(file_size):
diff --git a/client/tests/kvm/tests/nicdriver_unload.py b/client/tests/kvm/tests/nicdriver_unload.py
index 242b22f..c918c73 100644
--- a/client/tests/kvm/tests/nicdriver_unload.py
+++ b/client/tests/kvm/tests/nicdriver_unload.py
@@ -25,9 +25,7 @@ def run_nicdriver_unload(test, params, env):
 
     ethname = kvm_test_utils.get_linux_ifname(session, vm.get_mac_address(0))
     sys_path = "/sys/class/net/%s/device/driver" % (ethname)
-    s, o = session.get_command_status_output('readlink -e %s' % sys_path)
-    if s:
-        raise error.TestError("Could not find driver name")
+    o = session.cmd("readlink -e %s" % sys_path)
     driver = os.path.basename(o.strip())
     logging.info("driver is %s", driver)
 
@@ -42,12 +40,8 @@ def run_nicdriver_unload(test, params, env):
                 logging.debug("Failed to transfer file %s", remote_file)
 
     def compare(origin_file, receive_file):
-        cmd = "md5sum %s"
         check_sum1 = utils.hash_file(origin_file, method="md5")
-        s, output2 = session.get_command_status_output(cmd % receive_file)
-        if s != 0:
-            logging.error("Could not get md5sum of receive_file")
-            return False
+        output2 = session.cmd("md5sum %s" % receive_file)
         check_sum2 = output2.strip().split()[0]
         logging.debug("original file md5: %s, received file md5: %s",
                       check_sum1, check_sum2)
@@ -74,10 +68,11 @@ def run_nicdriver_unload(test, params, env):
         logging.info("Unload/load NIC driver repeatedly in guest...")
         while True:
             logging.debug("Try to unload/load nic drive once")
-            if session_serial.get_command_status(unload_load_cmd,
-                                                 timeout=120) != 0:
+            try:
+                session_serial.cmd(unload_load_cmd, timeout=120)
+            except:
                 session.get_command_output("rm -rf /tmp/Thread-*")
-                raise error.TestFail("Unload/load nic driver failed")
+                raise
             pid, s = os.waitpid(pid, os.WNOHANG)
             status = os.WEXITSTATUS(s)
             if (pid, status) != (0, 0):
diff --git a/client/tests/kvm/tests/pci_hotplug.py b/client/tests/kvm/tests/pci_hotplug.py
index 55cf666..1b38927 100644
--- a/client/tests/kvm/tests/pci_hotplug.py
+++ b/client/tests/kvm/tests/pci_hotplug.py
@@ -26,8 +26,7 @@ def run_pci_hotplug(test, params, env):
     # Modprobe the module if specified in config file
     module = params.get("modprobe_module")
     if module:
-        if session.get_command_status("modprobe %s" % module):
-            raise error.TestError("Modprobe module '%s' failed" % module)
+        session.cmd("modprobe %s" % module)
 
     # Get output of command 'info pci' as reference
     info_pci_ref = vm.monitor.info("pci")
@@ -152,10 +151,11 @@ def run_pci_hotplug(test, params, env):
                                   params.get("find_pci_cmd")))
 
         # Test the newly added device
-        s, o = session.get_command_status_output(params.get("pci_test_cmd"))
-        if s != 0:
+        try:
+            session.cmd(params.get("pci_test_cmd"))
+        except kvm_subprocess.ShellError, e:
             raise error.TestFail("Check for %s device failed after PCI "
-                                 "hotplug. Output: %r" % (test_type, o))
+                                 "hotplug. Output: %r" % (test_type, e.output))
 
         session.close()
 
diff --git a/client/tests/kvm/tests/stress_boot.py b/client/tests/kvm/tests/stress_boot.py
index b7916b4..13dc944 100644
--- a/client/tests/kvm/tests/stress_boot.py
+++ b/client/tests/kvm/tests/stress_boot.py
@@ -51,7 +51,9 @@ def run_stress_boot(tests, params, env):
 
             # check whether all previous shell sessions are responsive
             for i, se in enumerate(sessions):
-                if se.get_command_status(params.get("alive_test_cmd")) != 0:
+                try:
+                    se.cmd(params.get("alive_test_cmd"))
+                except kvm_subprocess.ShellError:
                     raise error.TestFail("Session #%d is not responsive" % i)
             num += 1
 
diff --git a/client/tests/kvm/tests/virtio_console.py b/client/tests/kvm/tests/virtio_console.py
index ede4880..d8d2143 100644
--- a/client/tests/kvm/tests/virtio_console.py
+++ b/client/tests/kvm/tests/virtio_console.py
@@ -421,7 +421,7 @@ def run_virtio_console(test, params, env):
                        "echo -n 'FAIL: Compile virtio_guest failed'")
         (match, data) = vm[1].read_until_last_line_matches(["PASS:", "FAIL:"],
                                                            timeout)
-        if match == 1 or match is None:
+        if match != 0:
             raise error.TestFail("Command console_switch.py on guest %s failed."
                                  "\nreturn code: %s\n output:\n%s" %
                                  (vm[0].name, match, data))
@@ -431,7 +431,7 @@ def run_virtio_console(test, params, env):
                        "echo -n 'FAIL: virtio_guest failed'")
         (match, data) = vm[1].read_until_last_line_matches(["PASS:", "FAIL:"],
                                                            timeout)
-        if match == 1 or match is None:
+        if match != 0:
             raise error.TestFail("Command console_switch.py on guest %s failed."
                                  "\nreturn code: %s\n output:\n%s" %
                                  (vm[0].name, match, data))
diff --git a/client/tests/kvm/tests/vlan.py b/client/tests/kvm/tests/vlan.py
index f41ea6a..a18ac90 100644
--- a/client/tests/kvm/tests/vlan.py
+++ b/client/tests/kvm/tests/vlan.py
@@ -1,6 +1,6 @@
 import logging, time, re
 from autotest_lib.client.common_lib import error
-import kvm_test_utils, kvm_utils
+import kvm_test_utils, kvm_utils, kvm_subprocess
 
 def run_vlan(test, params, env):
     """
@@ -35,18 +35,15 @@ def run_vlan(test, params, env):
     vm.append(kvm_test_utils.get_living_vm(env, "vm2"))
 
     def add_vlan(session, id, iface="eth0"):
-        if session.get_command_status("vconfig add %s %s" % (iface, id)) != 0:
-            raise error.TestError("Fail to add %s.%s" % (iface, id))
+        session.cmd("vconfig add %s %s" % (iface, id))
 
     def set_ip_vlan(session, id, ip, iface="eth0"):
         iface = "%s.%s" % (iface, id)
-        if session.get_command_status("ifconfig %s %s" % (iface, ip)) != 0:
-            raise error.TestError("Fail to configure ip for %s" % iface)
+        session.cmd("ifconfig %s %s" % (iface, ip))
 
     def set_arp_ignore(session, iface="eth0"):
         ignore_cmd = "echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore"
-        if session.get_command_status(ignore_cmd) != 0:
-            raise error.TestError("Fail to set arp_ignore of %s" % session)
+        session.cmd(ignore_cmd)
 
     def rem_vlan(session, id, iface="eth0"):
         rem_vlan_cmd = "if [[ -e /proc/net/vlan/%s ]];then vconfig rem %s;fi"
@@ -65,11 +62,10 @@ def run_vlan(test, params, env):
         time.sleep(2)
         #send file from src to dst
         send_cmd = send_cmd % (vlan_ip[dst], str(nc_port), "file")
-        if session[src].get_command_status(send_cmd, timeout = 60) != 0:
-            raise error.TestFail ("Fail to send file"
-                                    " from vm%s to vm%s" % (src+1, dst+1))
-        s, o = session[dst].read_up_to_prompt(timeout=60)
-        if s != True:
+        session[src].cmd(send_cmd, timeout=60)
+        try:
+            session[dst].read_up_to_prompt(timeout=60)
+        except kvm_subprocess.ExpectError:
             raise error.TestFail ("Fail to receive file"
                                     " from vm%s to vm%s" % (src+1, dst+1))
         #check MD5 message digest of receive file in dst
@@ -81,7 +77,7 @@ def run_vlan(test, params, env):
             logging.info("digest_origin is  %s" % digest_origin[src])
             logging.info("digest_receive is %s" % digest_receive)
             raise error.TestFail("File transfered differ from origin")
-        session[dst].get_command_status("rm -f receive")
+        session[dst].get_command_output("rm -f receive")
 
     for i in range(2):
         session.append(kvm_test_utils.wait_for_login(vm[i],
@@ -97,22 +93,16 @@ def run_vlan(test, params, env):
 
         #produce sized file in vm
         dd_cmd = "dd if=/dev/urandom of=file bs=1024k count=%s"
-        if session[i].get_command_status(dd_cmd % file_size) != 0:
-            raise error.TestFail("File producing failed")
+        session[i].cmd(dd_cmd % file_size)
         #record MD5 message digest of file
-        s, output =session[i].get_command_status_output("md5sum file",
-                                                        timeout=60)
-        if s != 0:
-            raise error.TestFail("File MD5_checking failed" )
+        output = session[i].cmd("md5sum file", timeout=60)
         digest_origin.append(re.findall(r'(\w+)', output)[0])
 
         #stop firewall in vm
-        session[i].get_command_status("/etc/init.d/iptables stop")
+        session[i].get_command_output("/etc/init.d/iptables stop")
 
         #load 8021q module for vconfig
-        load_8021q_cmd = "modprobe 8021q"
-        if session[i].get_command_status(load_8021q_cmd) != 0:
-            raise error.TestError("Fail to load 8021q module on VM%s" % i)
+        session[i].cmd("modprobe 8021q")
 
     try:
         for i in range(2):
diff --git a/client/tests/kvm/tests/whql_client_install.py b/client/tests/kvm/tests/whql_client_install.py
index 84b91bc..e9c5bdd 100644
--- a/client/tests/kvm/tests/whql_client_install.py
+++ b/client/tests/kvm/tests/whql_client_install.py
@@ -50,6 +50,7 @@ def run_whql_client_install(test, params, env):
     server_session = kvm_utils.remote_login("nc", server_address,
                                             server_shell_port, "", "",
                                             session.prompt, session.linesep)
+    server_session.set_status_test_command(session.status_test_command)
 
     # Get server and client information
     cmd = "echo %computername%"
@@ -67,10 +68,10 @@ def run_whql_client_install(test, params, env):
         server_dns_suffix = ""
 
     # Delete the client machine from the server's data store (if it's there)
-    server_session.get_command_output("cd %s" % server_studio_path)
+    server_session.cmd("cd %s" % server_studio_path)
     cmd = "%s %s %s" % (os.path.basename(dsso_delete_machine_binary),
                         server_name, client_name)
-    server_session.get_command_output(cmd, print_func=logging.info)
+    server_session.cmd(cmd, print_func=logging.info)
     server_session.close()
 
     # Rename the client machine
@@ -78,21 +79,18 @@ def run_whql_client_install(test, params, env):
     logging.info("Renaming client machine to '%s'" % client_name)
     cmd = ('wmic computersystem where name="%%computername%%" rename name="%s"'
            % client_name)
-    if session.get_command_status(cmd, timeout=600) != 0:
-        raise error.TestError("Could not rename the client machine")
+    session.cmd(cmd, timeout=600)
 
     # Join the server's workgroup
     logging.info("Joining workgroup '%s'" % server_workgroup)
     cmd = ('wmic computersystem where name="%%computername%%" call '
            'joindomainorworkgroup name="%s"' % server_workgroup)
-    if session.get_command_status(cmd, timeout=600) != 0:
-        raise error.TestError("Could not change the client's workgroup")
+    session.cmd(cmd, timeout=600)
 
     # Set the client machine's DNS suffix
     logging.info("Setting DNS suffix to '%s'" % server_dns_suffix)
     cmd = 'reg add %s /v Domain /d "%s" /f' % (regkey, server_dns_suffix)
-    if session.get_command_status(cmd, timeout=300) != 0:
-        raise error.TestError("Could not set the client's DNS suffix")
+    session.cmd(cmd, timeout=300)
 
     # Reboot
     session = kvm_test_utils.reboot(vm, session)
@@ -103,9 +101,11 @@ def run_whql_client_install(test, params, env):
                                          server_password)
     end_time = time.time() + 120
     while time.time() < end_time:
-        s = session.get_command_status(cmd)
-        if s == 0:
+        try:
+            session.cmd(cmd)
             break
+        except:
+            pass
         time.sleep(5)
     else:
         raise error.TestError("Could not access server share from client "
@@ -114,7 +114,5 @@ def run_whql_client_install(test, params, env):
     # Install
     logging.info("Installing DTM client (timeout=%ds)", install_timeout)
     install_cmd = r"cmd /c \\%s\%s" % (server_name, install_cmd.lstrip("\\"))
-    if session.get_command_status(install_cmd, timeout=install_timeout) != 0:
-        raise error.TestError("Client installation failed")
-
+    session.cmd(install_cmd, timeout=install_timeout)
     session.close()
diff --git a/client/tests/kvm/tests/whql_submission.py b/client/tests/kvm/tests/whql_submission.py
index 1fe27c9..3930954 100644
--- a/client/tests/kvm/tests/whql_submission.py
+++ b/client/tests/kvm/tests/whql_submission.py
@@ -50,6 +50,7 @@ def run_whql_submission(test, params, env):
     server_session = kvm_utils.remote_login("nc", server_address,
                                             server_shell_port, "", "",
                                             session.prompt, session.linesep)
+    server_session.set_status_test_command(session.status_test_command)
 
     # Get the computer names of the server and client
     cmd = "echo %computername%"
@@ -58,7 +59,7 @@ def run_whql_submission(test, params, env):
     session.close()
 
     # Run the automation program on the server
-    server_session.get_command_output("cd %s" % server_studio_path)
+    server_session.cmd("cd %s" % server_studio_path)
     cmd = "%s %s %s %s %s %s" % (os.path.basename(dsso_test_binary),
                                  server_name,
                                  client_name,
@@ -78,8 +79,8 @@ def run_whql_submission(test, params, env):
             if errors:
                 raise error.TestError(errors[0])
             else:
-                raise error.TestError("Error running automation program: could "
-                                      "not find '%s' prompt" % prompt)
+                raise error.TestError("Error running automation program: "
+                                      "could not find '%s' prompt" % prompt)
 
     # Tell the automation program which device to test
     find_prompt("Device to test:")
@@ -108,10 +109,15 @@ def run_whql_submission(test, params, env):
     server_session.sendline()
 
     # Wait for the automation program to terminate
-    m, o = server_session.read_up_to_prompt(print_func=logging.info,
-                                            timeout=test_timeout + 300)
-    # (test_timeout + 300 is used here because the automation program is
-    # supposed to terminate cleanly on its own when test_timeout expires)
+    try:
+        o = server_session.read_up_to_prompt(print_func=logging.info,
+                                             timeout=test_timeout + 300)
+        # (test_timeout + 300 is used here because the automation program is
+        # supposed to terminate cleanly on its own when test_timeout expires)
+        done = True
+    except kvm_subprocess.ExpectError, e:
+        o = e.output
+        done = False
     server_session.close()
 
     # Look for test results in the automation program's output
@@ -167,7 +173,7 @@ def run_whql_submission(test, params, env):
     logging.info("(see logs and HTML reports in %s)" % test.debugdir)
 
     # Kill the VM and fail if the automation program did not terminate on time
-    if not m:
+    if not done:
         vm.destroy()
         raise error.TestFail("The automation program did not terminate "
                              "on time")
