1 # -*- test-case-name: buildbot.test.test_slavecommand -*-
3 from twisted
.trial
import unittest
4 from twisted
.internet
import reactor
, interfaces
5 from twisted
.python
import runtime
, failure
, util
6 from buildbot
.twcompat
import maybeWait
10 from buildbot
.slave
import commands
11 SlaveShellCommand
= commands
.SlaveShellCommand
13 from buildbot
.test
.runutils
import SignalMixin
, FakeSlaveBuilder
15 # test slavecommand.py by running the various commands with a fake
16 # SlaveBuilder object that logs the calls to sendUpdate()
20 class ShellBase(SignalMixin
):
23 self
.basedir
= "test_slavecommand"
24 if not os
.path
.isdir(self
.basedir
):
25 os
.mkdir(self
.basedir
)
26 self
.subdir
= os
.path
.join(self
.basedir
, "subdir")
27 if not os
.path
.isdir(self
.subdir
):
29 self
.builder
= FakeSlaveBuilder(self
.usePTY
, self
.basedir
)
30 self
.emitcmd
= util
.sibpath(__file__
, "emit.py")
31 self
.subemitcmd
= os
.path
.join(util
.sibpath(__file__
, "subdir"),
33 self
.sleepcmd
= util
.sibpath(__file__
, "sleep.py")
35 def failUnlessIn(self
, substring
, string
):
36 self
.failUnless(string
.find(substring
) != -1,
37 "'%s' not in '%s'" % (substring
, string
))
39 def getfile(self
, which
):
41 for r
in self
.builder
.updates
:
46 def checkOutput(self
, expected
):
48 @type expected: list of (streamname, contents) tuples
49 @param expected: the expected output
51 expected_linesep
= os
.linesep
53 # PTYs change the line ending. I'm not sure why.
54 expected_linesep
= "\r\n"
55 expected
= [(stream
, contents
.replace("\n", expected_linesep
, 1000))
56 for (stream
, contents
) in expected
]
58 # PTYs merge stdout+stderr into a single stream
59 expected
= [('stdout', contents
)
60 for (stream
, contents
) in expected
]
61 # now merge everything into one string per stream
63 for (stream
, contents
) in expected
:
64 streams
[stream
] = streams
.get(stream
, "") + contents
65 for (stream
, contents
) in streams
.items():
66 got
= self
.getfile(stream
)
67 self
.assertEquals(got
, contents
)
70 self
.failUnless(self
.builder
.updates
[-1].has_key('rc'))
71 got
= self
.builder
.updates
[-1]['rc']
73 def checkrc(self
, expected
):
75 self
.assertEquals(got
, expected
)
78 targetfile
= os
.path
.join(self
.basedir
, "log1.out")
79 if os
.path
.exists(targetfile
):
81 cmd
= "%s %s 0" % (sys
.executable
, self
.emitcmd
)
82 args
= {'command': cmd
, 'workdir': '.', 'timeout': 60}
83 c
= SlaveShellCommand(self
.builder
, None, args
)
85 expected
= [('stdout', "this is stdout\n"),
86 ('stderr', "this is stderr\n")]
87 d
.addCallback(self
._checkPass
, expected
, 0)
88 def _check_targetfile(res
):
89 self
.failUnless(os
.path
.exists(targetfile
))
90 d
.addCallback(_check_targetfile
)
93 def _checkPass(self
, res
, expected
, rc
):
94 self
.checkOutput(expected
)
98 cmd
= [sys
.executable
, self
.emitcmd
, "0"]
99 args
= {'command': cmd
, 'workdir': '.', 'timeout': 60}
100 c
= SlaveShellCommand(self
.builder
, None, args
)
102 expected
= [('stdout', "this is stdout\n"),
103 ('stderr', "this is stderr\n")]
104 d
.addCallback(self
._checkPass
, expected
, 0)
107 def testShellRC(self
):
108 cmd
= [sys
.executable
, self
.emitcmd
, "1"]
109 args
= {'command': cmd
, 'workdir': '.', 'timeout': 60}
110 c
= SlaveShellCommand(self
.builder
, None, args
)
112 expected
= [('stdout', "this is stdout\n"),
113 ('stderr', "this is stderr\n")]
114 d
.addCallback(self
._checkPass
, expected
, 1)
117 def testShellEnv(self
):
118 cmd
= "%s %s 0" % (sys
.executable
, self
.emitcmd
)
119 args
= {'command': cmd
, 'workdir': '.',
120 'env': {'EMIT_TEST': "envtest"}, 'timeout': 60}
121 c
= SlaveShellCommand(self
.builder
, None, args
)
123 expected
= [('stdout', "this is stdout\n"),
124 ('stderr', "this is stderr\n"),
125 ('stdout', "EMIT_TEST: envtest\n"),
127 d
.addCallback(self
._checkPass
, expected
, 0)
130 def testShellSubdir(self
):
131 targetfile
= os
.path
.join(self
.basedir
, "subdir", "log1.out")
132 if os
.path
.exists(targetfile
):
133 os
.unlink(targetfile
)
134 cmd
= "%s %s 0" % (sys
.executable
, self
.subemitcmd
)
135 args
= {'command': cmd
, 'workdir': "subdir", 'timeout': 60}
136 c
= SlaveShellCommand(self
.builder
, None, args
)
138 expected
= [('stdout', "this is stdout in subdir\n"),
139 ('stderr', "this is stderr\n")]
140 d
.addCallback(self
._checkPass
, expected
, 0)
141 def _check_targetfile(res
):
142 self
.failUnless(os
.path
.exists(targetfile
))
143 d
.addCallback(_check_targetfile
)
146 def testShellMissingCommand(self
):
147 args
= {'command': "/bin/EndWorldHungerAndMakePigsFly",
148 'workdir': '.', 'timeout': 10,
149 'env': {"LC_ALL": "C"},
151 c
= SlaveShellCommand(self
.builder
, None, args
)
153 d
.addCallback(self
._testShellMissingCommand
_1)
155 def _testShellMissingCommand_1(self
, res
):
156 self
.failIfEqual(self
.getrc(), 0)
157 # we used to check the error message to make sure it said something
158 # about a missing command, but there are a variety of shells out
159 # there, and they emit message sin a variety of languages, so we
162 def testTimeout(self
):
163 args
= {'command': [sys
.executable
, self
.sleepcmd
, "10"],
164 'workdir': '.', 'timeout': 2}
165 c
= SlaveShellCommand(self
.builder
, None, args
)
167 d
.addCallback(self
._testTimeout
_1)
169 def _testTimeout_1(self
, res
):
170 self
.failIfEqual(self
.getrc(), 0)
171 got
= self
.getfile('header')
172 self
.failUnlessIn("command timed out: 2 seconds without output", got
)
173 if runtime
.platformType
== "posix":
174 # the "killing pid" message is not present in windows
175 self
.failUnlessIn("killing pid", got
)
176 # but the process *ought* to be killed somehow
177 self
.failUnlessIn("process killed by signal", got
)
179 if runtime
.platformType
!= 'posix':
180 testTimeout
.todo
= "timeout doesn't appear to work under windows"
182 def testInterrupt1(self
):
183 args
= {'command': [sys
.executable
, self
.sleepcmd
, "10"],
184 'workdir': '.', 'timeout': 20}
185 c
= SlaveShellCommand(self
.builder
, None, args
)
187 reactor
.callLater(1, c
.interrupt
)
188 d
.addCallback(self
._testInterrupt
1_1)
190 def _testInterrupt1_1(self
, res
):
191 self
.failIfEqual(self
.getrc(), 0)
192 got
= self
.getfile('header')
193 self
.failUnlessIn("command interrupted", got
)
194 if runtime
.platformType
== "posix":
195 self
.failUnlessIn("process killed by signal", got
)
196 if runtime
.platformType
!= 'posix':
197 testInterrupt1
.todo
= "interrupt doesn't appear to work under windows"
200 # todo: twisted-specific command tests
202 class Shell(ShellBase
, unittest
.TestCase
):
205 def testInterrupt2(self
):
206 # test the backup timeout. This doesn't work under a PTY, because the
207 # transport.loseConnection we do in the timeout handler actually
208 # *does* kill the process.
209 args
= {'command': [sys
.executable
, self
.sleepcmd
, "5"],
210 'workdir': '.', 'timeout': 20}
211 c
= SlaveShellCommand(self
.builder
, None, args
)
213 c
.command
.BACKUP_TIMEOUT
= 1
214 # make it unable to kill the child, by changing the signal it uses
215 # from SIGKILL to the do-nothing signal 0.
216 c
.command
.KILL
= None
217 reactor
.callLater(1, c
.interrupt
)
218 d
.addBoth(self
._testInterrupt
2_1)
220 def _testInterrupt2_1(self
, res
):
221 # the slave should raise a TimeoutError exception. In a normal build
222 # process (i.e. one that uses step.RemoteShellCommand), this
223 # exception will be handed to the Step, which will acquire an ERROR
224 # status. In our test environment, it isn't such a big deal.
225 self
.failUnless(isinstance(res
, failure
.Failure
),
226 "res is not a Failure: %s" % (res
,))
227 self
.failUnless(res
.check(commands
.TimeoutError
))
230 # the command is still actually running. Start another command, to
231 # make sure that a) the old command's output doesn't interfere with
232 # the new one, and b) the old command's actual termination doesn't
234 args
= {'command': [sys
.executable
, self
.sleepcmd
, "5"],
235 'workdir': '.', 'timeout': 20}
236 c
= SlaveShellCommand(self
.builder
, None, args
)
238 d
.addCallback(self
._testInterrupt
2_2)
240 def _testInterrupt2_2(self
, res
):
242 # N.B.: under windows, the trial process hangs out for another few
243 # seconds. I assume that the win32eventreactor is waiting for one of
244 # the lingering child processes to really finish.
246 haveProcess
= interfaces
.IReactorProcess(reactor
, None)
247 if runtime
.platformType
== 'posix':
248 # test with PTYs also
249 class ShellPTY(ShellBase
, unittest
.TestCase
):
252 ShellPTY
.skip
= "this reactor doesn't support IReactorProcess"
254 Shell
.skip
= "this reactor doesn't support IReactorProcess"