210 lines
5 KiB
Python
Executable file
210 lines
5 KiB
Python
Executable file
#!/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()
|