blob: f7c8f433b6ddc5de607a2614e70d55896680e4fa [file] [log] [blame]
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import os
import pipes
import threading
from autotest_lib.client.common_lib import error
class _HelperThread(threading.Thread):
"""Make a thread to run the command in."""
def __init__(self, host, cmd):
super(_HelperThread, self).__init__()
self._host = host
self._cmd = cmd
self._result = None
self.daemon = True
def run(self):
logging.info('Helper thread running: %s', self._cmd)
# NB: set ignore_status as we're always terminated w/ pkill
self._result = self._host.run(self._cmd, ignore_status=True)
@property
def result(self):
"""
@returns string result of running our command if the command has
finished, and None otherwise.
"""
return self._result
class Command(object):
"""
Encapsulates a command run on a remote machine.
Future work is to have this get the PID (by prepending 'echo $$;
exec' to the command and parsing the output).
"""
def __init__(self, host, cmd, pkill_argument=None):
"""
Run a command on a remote host in the background.
@param host Host object representing the remote machine.
@param cmd String command to run on the remote machine.
@param pkill_argument String argument to pkill to kill the remote
process.
"""
if pkill_argument is None:
# Attempt to guess what a suitable pkill argument would look like.
pkill_argument = os.path.basename(cmd.split()[0])
self._command_name = pipes.quote(pkill_argument)
self._host = host
self._thread = _HelperThread(self._host, cmd)
self._thread.start()
def join(self, signal=None, timeout=5.0):
"""
Kills the remote command and waits until it dies. Takes an optional
signal argument to control which signal to send the process to be
killed.
@param signal Signal string to give to pkill (e.g. SIGNAL_INT).
@param timeout float number of seconds to wait for join to finish.
"""
if signal is None:
signal_arg = ''
else:
# In theory, it should be hard to pass something evil for signal if
# we make sure it's an integer before passing it to pkill.
signal_arg = '-' + str(int(signal))
# Ignore status because the command may have exited already
self._host.run("pkill %s %s" % (signal_arg, self._command_name),
ignore_status=True)
self._thread.join(timeout)
if self._thread.isAlive():
raise error.TestFail('Failed to kill remote command: %s' %
self._command_name)
def __enter__(self):
return self
def __exit__(self, exception, value, traceback):
self.join()
return False
@property
def result(self):
"""
@returns string result of running our command if the command has
finished, and None otherwise.
"""
return self._thread.result