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