1#!/usr/bin/python 2# 3# Copyright 2011 Google Inc. All Rights Reserved. 4# 5 6__author__ = 'kbaclawski@google.com (Krystian Baclawski)' 7 8import cStringIO 9import logging 10import os 11import signal 12import socket 13import sys 14import time 15import unittest 16 17 18def AddScriptDirToPath(): 19 """Required for remote python script execution.""" 20 path = os.path.abspath(__file__) 21 22 for _ in range(3): 23 path, _ = os.path.split(path) 24 25 if not path in sys.path: 26 sys.path.append(path) 27 28 29AddScriptDirToPath() 30 31from automation.common.command_executer import CommandExecuter 32 33 34class LoggerMock(object): 35 36 def LogCmd(self, cmd, machine='', user=''): 37 if machine: 38 logging.info('[%s] Executing: %s', machine, cmd) 39 else: 40 logging.info('Executing: %s', cmd) 41 42 def LogError(self, msg): 43 logging.error(msg) 44 45 def LogWarning(self, msg): 46 logging.warning(msg) 47 48 def LogOutput(self, msg): 49 logging.info(msg) 50 51 52class CommandExecuterUnderTest(CommandExecuter): 53 54 def __init__(self): 55 CommandExecuter.__init__(self, logger_to_set=LoggerMock()) 56 57 # We will record stdout and stderr. 58 self._stderr = cStringIO.StringIO() 59 self._stdout = cStringIO.StringIO() 60 61 @property 62 def stdout(self): 63 return self._stdout.getvalue() 64 65 @property 66 def stderr(self): 67 return self._stderr.getvalue() 68 69 def DataReceivedOnOutput(self, data): 70 self._stdout.write(data) 71 72 def DataReceivedOnError(self, data): 73 self._stderr.write(data) 74 75 76class CommandExecuterLocalTests(unittest.TestCase): 77 HOSTNAME = None 78 79 def setUp(self): 80 self._executer = CommandExecuterUnderTest() 81 82 def tearDown(self): 83 pass 84 85 def RunCommand(self, method, **kwargs): 86 program = os.path.abspath(sys.argv[0]) 87 88 return self._executer.RunCommand('%s runHelper %s' % (program, method), 89 machine=self.HOSTNAME, 90 **kwargs) 91 92 def testCommandTimeout(self): 93 exit_code = self.RunCommand('SleepForMinute', command_timeout=3) 94 95 self.assertTrue(-exit_code in [signal.SIGTERM, signal.SIGKILL], 96 'Invalid exit code: %d' % exit_code) 97 98 def testCommandTimeoutIfSigTermIgnored(self): 99 exit_code = self.RunCommand('IgnoreSigTerm', command_timeout=3) 100 101 self.assertTrue(-exit_code in [signal.SIGTERM, signal.SIGKILL]) 102 103 def testCommandSucceeded(self): 104 self.assertFalse(self.RunCommand('ReturnTrue')) 105 106 def testCommandFailed(self): 107 self.assertTrue(self.RunCommand('ReturnFalse')) 108 109 def testStringOnOutputStream(self): 110 self.assertFalse(self.RunCommand('EchoToOutputStream')) 111 self.assertEquals(self._executer.stderr, '') 112 self.assertEquals(self._executer.stdout, 'test') 113 114 def testStringOnErrorStream(self): 115 self.assertFalse(self.RunCommand('EchoToErrorStream')) 116 self.assertEquals(self._executer.stderr, 'test') 117 self.assertEquals(self._executer.stdout, '') 118 119 def testOutputStreamNonInteractive(self): 120 self.assertFalse( 121 self.RunCommand('IsOutputStreamInteractive'), 122 'stdout stream is a terminal!') 123 124 def testErrorStreamNonInteractive(self): 125 self.assertFalse( 126 self.RunCommand('IsErrorStreamInteractive'), 127 'stderr stream is a terminal!') 128 129 def testAttemptToRead(self): 130 self.assertFalse(self.RunCommand('WaitForInput', command_timeout=3)) 131 132 def testInterruptedProcess(self): 133 self.assertEquals(self.RunCommand('TerminateBySigAbrt'), -signal.SIGABRT) 134 135 136class CommandExecuterRemoteTests(CommandExecuterLocalTests): 137 HOSTNAME = socket.gethostname() 138 139 def testCommandTimeoutIfSigTermIgnored(self): 140 exit_code = self.RunCommand('IgnoreSigTerm', command_timeout=6) 141 142 self.assertEquals(exit_code, 255) 143 144 lines = self._executer.stdout.splitlines() 145 pid = int(lines[0]) 146 147 try: 148 with open('/proc/%d/cmdline' % pid) as f: 149 cmdline = f.read() 150 except IOError: 151 cmdline = '' 152 153 self.assertFalse('IgnoreSigTerm' in cmdline, 'Process is still alive.') 154 155 156class CommandExecuterTestHelpers(object): 157 158 def SleepForMinute(self): 159 time.sleep(60) 160 return 1 161 162 def ReturnTrue(self): 163 return 0 164 165 def ReturnFalse(self): 166 return 1 167 168 def EchoToOutputStream(self): 169 sys.stdout.write('test') 170 return 0 171 172 def EchoToErrorStream(self): 173 sys.stderr.write('test') 174 return 0 175 176 def IsOutputStreamInteractive(self): 177 return sys.stdout.isatty() 178 179 def IsErrorStreamInteractive(self): 180 return sys.stderr.isatty() 181 182 def IgnoreSigTerm(self): 183 os.write(1, '%d' % os.getpid()) 184 signal.signal(signal.SIGTERM, signal.SIG_IGN) 185 time.sleep(30) 186 return 0 187 188 def WaitForInput(self): 189 try: 190 # can only read end-of-file marker 191 return os.read(0, 1) != '' 192 except OSError: 193 # that means that stdin descriptor is closed 194 return 0 195 196 def TerminateBySigAbrt(self): 197 os.kill(os.getpid(), signal.SIGABRT) 198 return 0 199 200 201if __name__ == '__main__': 202 FORMAT = '%(asctime)-15s %(levelname)s %(message)s' 203 logging.basicConfig(format=FORMAT, level=logging.DEBUG) 204 205 if len(sys.argv) > 1: 206 if sys.argv[1] == 'runHelper': 207 helpers = CommandExecuterTestHelpers() 208 sys.exit(getattr(helpers, sys.argv[2])()) 209 210 unittest.main() 211