You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
5.0 KiB
210 lines
5.0 KiB
#!/usr/bin/python |
|
# |
|
# Copyright 2011 Google Inc. All Rights Reserved. |
|
# |
|
|
|
__author__ = 'kbaclawski@google.com (Krystian Baclawski)' |
|
|
|
import cStringIO |
|
import logging |
|
import os |
|
import signal |
|
import socket |
|
import sys |
|
import time |
|
import unittest |
|
|
|
|
|
def AddScriptDirToPath(): |
|
"""Required for remote python script execution.""" |
|
path = os.path.abspath(__file__) |
|
|
|
for _ in range(3): |
|
path, _ = os.path.split(path) |
|
|
|
if not path in sys.path: |
|
sys.path.append(path) |
|
|
|
|
|
AddScriptDirToPath() |
|
|
|
from automation.common.command_executer import CommandExecuter |
|
|
|
|
|
class LoggerMock(object): |
|
|
|
def LogCmd(self, cmd, machine='', user=''): |
|
if machine: |
|
logging.info('[%s] Executing: %s', machine, cmd) |
|
else: |
|
logging.info('Executing: %s', cmd) |
|
|
|
def LogError(self, msg): |
|
logging.error(msg) |
|
|
|
def LogWarning(self, msg): |
|
logging.warning(msg) |
|
|
|
def LogOutput(self, msg): |
|
logging.info(msg) |
|
|
|
|
|
class CommandExecuterUnderTest(CommandExecuter): |
|
|
|
def __init__(self): |
|
CommandExecuter.__init__(self, logger_to_set=LoggerMock()) |
|
|
|
# We will record stdout and stderr. |
|
self._stderr = cStringIO.StringIO() |
|
self._stdout = cStringIO.StringIO() |
|
|
|
@property |
|
def stdout(self): |
|
return self._stdout.getvalue() |
|
|
|
@property |
|
def stderr(self): |
|
return self._stderr.getvalue() |
|
|
|
def DataReceivedOnOutput(self, data): |
|
self._stdout.write(data) |
|
|
|
def DataReceivedOnError(self, data): |
|
self._stderr.write(data) |
|
|
|
|
|
class CommandExecuterLocalTests(unittest.TestCase): |
|
HOSTNAME = None |
|
|
|
def setUp(self): |
|
self._executer = CommandExecuterUnderTest() |
|
|
|
def tearDown(self): |
|
pass |
|
|
|
def RunCommand(self, method, **kwargs): |
|
program = os.path.abspath(sys.argv[0]) |
|
|
|
return self._executer.RunCommand('%s runHelper %s' % (program, method), |
|
machine=self.HOSTNAME, |
|
**kwargs) |
|
|
|
def testCommandTimeout(self): |
|
exit_code = self.RunCommand('SleepForMinute', command_timeout=3) |
|
|
|
self.assertTrue(-exit_code in [signal.SIGTERM, signal.SIGKILL], |
|
'Invalid exit code: %d' % exit_code) |
|
|
|
def testCommandTimeoutIfSigTermIgnored(self): |
|
exit_code = self.RunCommand('IgnoreSigTerm', command_timeout=3) |
|
|
|
self.assertTrue(-exit_code in [signal.SIGTERM, signal.SIGKILL]) |
|
|
|
def testCommandSucceeded(self): |
|
self.assertFalse(self.RunCommand('ReturnTrue')) |
|
|
|
def testCommandFailed(self): |
|
self.assertTrue(self.RunCommand('ReturnFalse')) |
|
|
|
def testStringOnOutputStream(self): |
|
self.assertFalse(self.RunCommand('EchoToOutputStream')) |
|
self.assertEquals(self._executer.stderr, '') |
|
self.assertEquals(self._executer.stdout, 'test') |
|
|
|
def testStringOnErrorStream(self): |
|
self.assertFalse(self.RunCommand('EchoToErrorStream')) |
|
self.assertEquals(self._executer.stderr, 'test') |
|
self.assertEquals(self._executer.stdout, '') |
|
|
|
def testOutputStreamNonInteractive(self): |
|
self.assertFalse( |
|
self.RunCommand('IsOutputStreamInteractive'), |
|
'stdout stream is a terminal!') |
|
|
|
def testErrorStreamNonInteractive(self): |
|
self.assertFalse( |
|
self.RunCommand('IsErrorStreamInteractive'), |
|
'stderr stream is a terminal!') |
|
|
|
def testAttemptToRead(self): |
|
self.assertFalse(self.RunCommand('WaitForInput', command_timeout=3)) |
|
|
|
def testInterruptedProcess(self): |
|
self.assertEquals(self.RunCommand('TerminateBySigAbrt'), -signal.SIGABRT) |
|
|
|
|
|
class CommandExecuterRemoteTests(CommandExecuterLocalTests): |
|
HOSTNAME = socket.gethostname() |
|
|
|
def testCommandTimeoutIfSigTermIgnored(self): |
|
exit_code = self.RunCommand('IgnoreSigTerm', command_timeout=6) |
|
|
|
self.assertEquals(exit_code, 255) |
|
|
|
lines = self._executer.stdout.splitlines() |
|
pid = int(lines[0]) |
|
|
|
try: |
|
with open('/proc/%d/cmdline' % pid) as f: |
|
cmdline = f.read() |
|
except IOError: |
|
cmdline = '' |
|
|
|
self.assertFalse('IgnoreSigTerm' in cmdline, 'Process is still alive.') |
|
|
|
|
|
class CommandExecuterTestHelpers(object): |
|
|
|
def SleepForMinute(self): |
|
time.sleep(60) |
|
return 1 |
|
|
|
def ReturnTrue(self): |
|
return 0 |
|
|
|
def ReturnFalse(self): |
|
return 1 |
|
|
|
def EchoToOutputStream(self): |
|
sys.stdout.write('test') |
|
return 0 |
|
|
|
def EchoToErrorStream(self): |
|
sys.stderr.write('test') |
|
return 0 |
|
|
|
def IsOutputStreamInteractive(self): |
|
return sys.stdout.isatty() |
|
|
|
def IsErrorStreamInteractive(self): |
|
return sys.stderr.isatty() |
|
|
|
def IgnoreSigTerm(self): |
|
os.write(1, '%d' % os.getpid()) |
|
signal.signal(signal.SIGTERM, signal.SIG_IGN) |
|
time.sleep(30) |
|
return 0 |
|
|
|
def WaitForInput(self): |
|
try: |
|
# can only read end-of-file marker |
|
return os.read(0, 1) != '' |
|
except OSError: |
|
# that means that stdin descriptor is closed |
|
return 0 |
|
|
|
def TerminateBySigAbrt(self): |
|
os.kill(os.getpid(), signal.SIGABRT) |
|
return 0 |
|
|
|
|
|
if __name__ == '__main__': |
|
FORMAT = '%(asctime)-15s %(levelname)s %(message)s' |
|
logging.basicConfig(format=FORMAT, level=logging.DEBUG) |
|
|
|
if len(sys.argv) > 1: |
|
if sys.argv[1] == 'runHelper': |
|
helpers = CommandExecuterTestHelpers() |
|
sys.exit(getattr(helpers, sys.argv[2])()) |
|
|
|
unittest.main()
|
|
|