Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(218)

Unified Diff: client/tests/kvm/kvm_subprocess.py

Issue 6246035: Merge remote branch 'cros/upstream' into master (Closed) Base URL: ssh://git@gitrw.chromium.org:9222/autotest.git@master
Patch Set: patch Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: client/tests/kvm/kvm_subprocess.py
diff --git a/client/tests/kvm/kvm_subprocess.py b/client/tests/kvm/kvm_subprocess.py
index 8321bb3de778f07e33aac5f3f91e63ef2583a257..0b8734f7b247c693aac04305d1fb71ab2dba0084 100755
--- a/client/tests/kvm/kvm_subprocess.py
+++ b/client/tests/kvm/kvm_subprocess.py
@@ -189,6 +189,89 @@ 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 to "
+ "complete: %r (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 to "
+ "complete: %r (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 failed: %r (status: %s, 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):
"""
@@ -210,12 +293,12 @@ def run_bg(command, termination_func=None, output_func=None, output_prefix="",
@param timeout: Time duration (in seconds) to wait for the subprocess to
terminate before returning
- @return: A kvm_tail object.
+ @return: A Tail object.
"""
- process = kvm_tail(command=command,
- termination_func=termination_func,
- output_func=output_func,
- output_prefix=output_prefix)
+ process = Tail(command=command,
+ termination_func=termination_func,
+ output_func=output_func,
+ output_prefix=output_prefix)
end_time = time.time() + timeout
while time.time() < end_time and process.is_alive():
@@ -256,7 +339,7 @@ def run_fg(command, output_func=None, output_prefix="", timeout=1.0):
return (status, output)
-class kvm_spawn:
+class Spawn:
"""
This class is used for spawning and controlling a child process.
@@ -268,7 +351,7 @@ class kvm_spawn:
The text file can be accessed at any time using get_output().
In addition, the server opens as many pipes as requested by the client and
writes the output to them.
- The pipes are requested and accessed by classes derived from kvm_spawn.
+ The pipes are requested and accessed by classes derived from Spawn.
These pipes are referred to as "readers".
The server also receives input from the client and sends it to the child
process.
@@ -552,7 +635,7 @@ _thread_kill_requested = False
def kill_tail_threads():
"""
- Kill all kvm_tail threads.
+ Kill all Tail threads.
After calling this function no new threads should be started.
"""
@@ -564,12 +647,12 @@ def kill_tail_threads():
_thread_kill_requested = False
-class kvm_tail(kvm_spawn):
+class Tail(Spawn):
"""
This class runs a child process in the background and sends its output in
real time, line-by-line, to a callback function.
- See kvm_spawn's docstring.
+ See Spawn's docstring.
This class uses a single pipe reader to read data in real time from the
child process and report it to a given callback function.
@@ -610,10 +693,10 @@ class kvm_tail(kvm_spawn):
"""
# Add a reader and a close hook
self._add_reader("tail")
- self._add_close_hook(kvm_tail._join_thread)
+ self._add_close_hook(Tail._join_thread)
# Init the superclass
- kvm_spawn.__init__(self, command, id, auto_close, echo, linesep)
+ Spawn.__init__(self, command, id, auto_close, echo, linesep)
# Remember some attributes
self.termination_func = termination_func
@@ -629,11 +712,11 @@ class kvm_tail(kvm_spawn):
def __getinitargs__(self):
- return kvm_spawn.__getinitargs__(self) + (self.termination_func,
- self.termination_params,
- self.output_func,
- self.output_params,
- self.output_prefix)
+ return Spawn.__getinitargs__(self) + (self.termination_func,
+ self.termination_params,
+ self.output_func,
+ self.output_params,
+ self.output_prefix)
def set_termination_func(self, termination_func):
@@ -765,15 +848,15 @@ class kvm_tail(kvm_spawn):
t.join()
-class kvm_expect(kvm_tail):
+class Expect(Tail):
"""
This class runs a child process in the background and provides expect-like
services.
- It also provides all of kvm_tail's functionality.
+ It also provides all of 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=""):
"""
@@ -806,13 +889,13 @@ class kvm_expect(kvm_tail):
self._add_reader("expect")
# Init the superclass
- kvm_tail.__init__(self, command, id, auto_close, echo, linesep,
- termination_func, termination_params,
- output_func, output_params, output_prefix)
+ Tail.__init__(self, command, id, auto_close, echo, linesep,
+ termination_func, termination_params,
+ output_func, output_params, output_prefix)
def __getinitargs__(self):
- return kvm_tail.__getinitargs__(self)
+ return Tail.__getinitargs__(self)
def read_nonblocking(self, timeout=None):
@@ -858,7 +941,7 @@ class kvm_expect(kvm_tail):
def read_until_output_matches(self, patterns, filter=lambda x: x,
- timeout=30.0, internal_timeout=None,
+ timeout=60, internal_timeout=None,
print_func=None):
"""
Read using read_nonblocking until a match is found using match_patterns,
@@ -876,13 +959,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,41 +974,31 @@ 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,
+ def read_until_last_word_matches(self, patterns, timeout=60,
internal_timeout=None, print_func=None):
"""
Read using read_nonblocking until the last word of the output matches
@@ -936,8 +1010,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:
@@ -950,7 +1027,7 @@ class kvm_expect(kvm_tail):
print_func)
- def read_until_last_line_matches(self, patterns, timeout=30.0,
+ def read_until_last_line_matches(self, patterns, timeout=60,
internal_timeout=None, print_func=None):
"""
Read using read_nonblocking until the last non-empty line of the output
@@ -967,6 +1044,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()]
@@ -980,12 +1062,12 @@ class kvm_expect(kvm_tail):
print_func)
-class kvm_shell_session(kvm_expect):
+class ShellSession(Expect):
"""
This class runs a child process in the background. It it suited for
processes that provide an interactive shell, such as SSH and Telnet.
- It provides all services of kvm_expect and kvm_tail. In addition, it
+ It provides all services of Expect and Tail. In addition, it
provides command running services, and a utility function to test the
process for responsiveness.
"""
@@ -1022,12 +1104,12 @@ class kvm_shell_session(kvm_expect):
@param prompt: Regular expression describing the shell's prompt line.
@param status_test_command: Command to be used for getting the last
exit status of commands run inside the shell (used by
- get_command_status_output() and friends).
+ cmd_status_output() and friends).
"""
# Init the superclass
- kvm_expect.__init__(self, command, id, auto_close, echo, linesep,
- termination_func, termination_params,
- output_func, output_params, output_prefix)
+ Expect.__init__(self, command, id, auto_close, echo, linesep,
+ termination_func, termination_params,
+ output_func, output_params, output_prefix)
# Remember some attributes
self.prompt = prompt
@@ -1035,8 +1117,8 @@ class kvm_shell_session(kvm_expect):
def __getinitargs__(self):
- return kvm_expect.__getinitargs__(self) + (self.prompt,
- self.status_test_command)
+ return Expect.__getinitargs__(self) + (self.prompt,
+ self.status_test_command)
def set_prompt(self, prompt):
@@ -1085,7 +1167,7 @@ class kvm_shell_session(kvm_expect):
return False
- def read_up_to_prompt(self, timeout=30.0, internal_timeout=None,
+ def read_up_to_prompt(self, timeout=60, internal_timeout=None,
print_func=None):
"""
Read using read_nonblocking until the last non-empty line of the output
@@ -1101,31 +1183,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 cmd_output(self, cmd, timeout=60, 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 +1220,132 @@ 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))
- # 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 (status, output)
+ def cmd_status_output(self, cmd, timeout=60, 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)
- def get_command_status(self, command, timeout=30.0, internal_timeout=None,
- print_func=None):
+ @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.cmd_output(cmd, timeout, internal_timeout, print_func)
+ try:
+ # Send the 'echo $?' (or equivalent) command to get the exit status
+ s = self.cmd_output(self.status_test_command, 10, internal_timeout)
+ except ShellError:
+ raise ShellStatusError(cmd, o)
+
+ # 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 cmd_status(self, cmd, timeout=60, 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.cmd_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=60, 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)
+
+ @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.cmd_status_output(cmd, timeout, internal_timeout,
+ print_func)
+ if s != 0:
+ raise ShellCmdError(cmd, s, o)
+ return o
+
+
+ def get_command_output(self, cmd, timeout=60, internal_timeout=None,
+ print_func=None):
+ """
+ Alias for cmd_output() for backward compatibility.
+ """
+ return self.cmd_output(cmd, timeout, internal_timeout, print_func)
+
+
+ def get_command_status_output(self, cmd, timeout=60, internal_timeout=None,
+ print_func=None):
+ """
+ Alias for cmd_status_output() for backward compatibility.
+ """
+ return self.cmd_status_output(cmd, timeout, internal_timeout,
+ print_func)
+
+
+ def get_command_status(self, cmd, timeout=60, internal_timeout=None,
+ print_func=None):
+ """
+ Alias for cmd_status() for backward compatibility.
"""
- (status, output) = self.get_command_status_output(command, timeout,
- internal_timeout,
- print_func)
- return output
+ return self.cmd_status(cmd, timeout, internal_timeout, print_func)

Powered by Google App Engine
This is Rietveld 408576698