普通文本  |  108行  |  3.22 KB

# 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