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