~ubuntu-branches/ubuntu/utopic/buildbot/utopic-proposed

« back to all changes in this revision

Viewing changes to buildbot/test/test_slavecommand.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2006-04-15 21:20:08 UTC
  • Revision ID: james.westby@ubuntu.com-20060415212008-jfj53u29zl30jqi1
Tags: upstream-0.7.2
ImportĀ upstreamĀ versionĀ 0.7.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: buildbot.test.test_slavecommand -*-
 
2
 
 
3
from twisted.trial import unittest
 
4
from twisted.internet import reactor, interfaces
 
5
from twisted.python import util, runtime, failure
 
6
from buildbot.twcompat import maybeWait
 
7
 
 
8
noisy = False
 
9
if noisy:
 
10
    from twisted.python.log import startLogging
 
11
    import sys
 
12
    startLogging(sys.stdout)
 
13
 
 
14
import os, re, sys
 
15
import signal
 
16
 
 
17
from buildbot.slave import commands
 
18
SlaveShellCommand = commands.SlaveShellCommand
 
19
 
 
20
# test slavecommand.py by running the various commands with a fake
 
21
# SlaveBuilder object that logs the calls to sendUpdate()
 
22
 
 
23
def findDir():
 
24
    # the same directory that holds this script
 
25
    return util.sibpath(__file__, ".")
 
26
        
 
27
class FakeSlaveBuilder:
 
28
    def __init__(self, usePTY):
 
29
        self.updates = []
 
30
        self.basedir = findDir()
 
31
        self.usePTY = usePTY
 
32
 
 
33
    def sendUpdate(self, data):
 
34
        if noisy: print "FakeSlaveBuilder.sendUpdate", data
 
35
        self.updates.append(data)
 
36
 
 
37
 
 
38
class SignalMixin:
 
39
    sigchldHandler = None
 
40
    
 
41
    def setUpClass(self):
 
42
        # make sure SIGCHLD handler is installed, as it should be on
 
43
        # reactor.run(). problem is reactor may not have been run when this
 
44
        # test runs.
 
45
        if hasattr(reactor, "_handleSigchld") and hasattr(signal, "SIGCHLD"):
 
46
            self.sigchldHandler = signal.signal(signal.SIGCHLD,
 
47
                                                reactor._handleSigchld)
 
48
    
 
49
    def tearDownClass(self):
 
50
        if self.sigchldHandler:
 
51
            signal.signal(signal.SIGCHLD, self.sigchldHandler)
 
52
 
 
53
 
 
54
class ShellBase(SignalMixin):
 
55
 
 
56
    def setUp(self):
 
57
        self.builder = FakeSlaveBuilder(self.usePTY)
 
58
 
 
59
    def failUnlessIn(self, substring, string):
 
60
        self.failUnless(string.find(substring) != -1)
 
61
 
 
62
    def getfile(self, which):
 
63
        got = ""
 
64
        for r in self.builder.updates:
 
65
            if r.has_key(which):
 
66
                got += r[which]
 
67
        return got
 
68
 
 
69
    def checkOutput(self, expected):
 
70
        """
 
71
        @type  expected: list of (streamname, contents) tuples
 
72
        @param expected: the expected output
 
73
        """
 
74
        expected_linesep = os.linesep
 
75
        if self.usePTY:
 
76
            # PTYs change the line ending. I'm not sure why.
 
77
            expected_linesep = "\r\n"
 
78
        expected = [(stream, contents.replace("\n", expected_linesep, 1000))
 
79
                    for (stream, contents) in expected]
 
80
        if self.usePTY:
 
81
            # PTYs merge stdout+stderr into a single stream
 
82
            expected = [('stdout', contents)
 
83
                        for (stream, contents) in expected]
 
84
        # now merge everything into one string per stream
 
85
        streams = {}
 
86
        for (stream, contents) in expected:
 
87
            streams[stream] = streams.get(stream, "") + contents
 
88
        for (stream, contents) in streams.items():
 
89
            got = self.getfile(stream)
 
90
            self.assertEquals(got, contents)
 
91
 
 
92
    def getrc(self):
 
93
        self.failUnless(self.builder.updates[-1].has_key('rc'))
 
94
        got = self.builder.updates[-1]['rc']
 
95
        return got
 
96
    def checkrc(self, expected):
 
97
        got = self.getrc()
 
98
        self.assertEquals(got, expected)
 
99
        
 
100
    def testShell1(self):
 
101
        cmd = sys.executable + " emit.py 0"
 
102
        args = {'command': cmd, 'workdir': '.', 'timeout': 60}
 
103
        c = SlaveShellCommand(self.builder, None, args)
 
104
        d = c.start()
 
105
        expected = [('stdout', "this is stdout\n"),
 
106
                    ('stderr', "this is stderr\n")]
 
107
        d.addCallback(self._checkPass, expected, 0)
 
108
        return maybeWait(d)
 
109
 
 
110
    def _checkPass(self, res, expected, rc):
 
111
        self.checkOutput(expected)
 
112
        self.checkrc(rc)
 
113
 
 
114
    def testShell2(self):
 
115
        cmd = [sys.executable, "emit.py", "0"]
 
116
        args = {'command': cmd, 'workdir': '.', 'timeout': 60}
 
117
        c = SlaveShellCommand(self.builder, None, args)
 
118
        d = c.start()
 
119
        expected = [('stdout', "this is stdout\n"),
 
120
                    ('stderr', "this is stderr\n")]
 
121
        d.addCallback(self._checkPass, expected, 0)
 
122
        return maybeWait(d)
 
123
 
 
124
    def testShellRC(self):
 
125
        cmd = [sys.executable, "emit.py", "1"]
 
126
        args = {'command': cmd, 'workdir': '.', 'timeout': 60}
 
127
        c = SlaveShellCommand(self.builder, None, args)
 
128
        d = c.start()
 
129
        expected = [('stdout', "this is stdout\n"),
 
130
                    ('stderr', "this is stderr\n")]
 
131
        d.addCallback(self._checkPass, expected, 1)
 
132
        return maybeWait(d)
 
133
 
 
134
    def testShellEnv(self):
 
135
        cmd = sys.executable + " emit.py 0"
 
136
        args = {'command': cmd, 'workdir': '.',
 
137
                'env': {'EMIT_TEST': "envtest"}, 'timeout': 60}
 
138
        c = SlaveShellCommand(self.builder, None, args)
 
139
        d = c.start()
 
140
        expected = [('stdout', "this is stdout\n"),
 
141
                    ('stderr', "this is stderr\n"),
 
142
                    ('stdout', "EMIT_TEST: envtest\n"),
 
143
                    ]
 
144
        d.addCallback(self._checkPass, expected, 0)
 
145
        return maybeWait(d)
 
146
 
 
147
    def testShellSubdir(self):
 
148
        cmd = sys.executable + " emit.py 0"
 
149
        args = {'command': cmd, 'workdir': "subdir", 'timeout': 60}
 
150
        c = SlaveShellCommand(self.builder, None, args)
 
151
        d = c.start()
 
152
        expected = [('stdout', "this is stdout in subdir\n"),
 
153
                    ('stderr', "this is stderr\n")]
 
154
        d.addCallback(self._checkPass, expected, 0)
 
155
        return maybeWait(d)
 
156
 
 
157
    def testShellMissingCommand(self):
 
158
        args = {'command': "/bin/EndWorldHungerAndMakePigsFly",
 
159
                'workdir': '.', 'timeout': 10}
 
160
        c = SlaveShellCommand(self.builder, None, args)
 
161
        d = c.start()
 
162
        d.addCallback(self._testShellMissingCommand_1)
 
163
        return maybeWait(d)
 
164
    def _testShellMissingCommand_1(self, res):
 
165
        self.failIfEqual(self.getrc(), 0)
 
166
        got = self.getfile('stdout') + self.getfile('stderr')
 
167
        self.failUnless(re.search(r'no such file', got, re.I) # unix
 
168
                        or re.search(r'cannot find the path specified',
 
169
                                     got, re.I) # win32
 
170
                        or re.search(r'is not recognized as',
 
171
                                     got, re.I), # other win32
 
172
                        "bogus command didn't create the expected error "
 
173
                        "message, got '%s'" % got
 
174
                        )
 
175
 
 
176
    def testTimeout(self):
 
177
        args = {'command': [sys.executable, "sleep.py", "10"],
 
178
                'workdir': '.', 'timeout': 2}
 
179
        c = SlaveShellCommand(self.builder, None, args)
 
180
        d = c.start()
 
181
        d.addCallback(self._testTimeout_1)
 
182
        return maybeWait(d)
 
183
    def _testTimeout_1(self, res):
 
184
        self.failIfEqual(self.getrc(), 0)
 
185
        got = self.getfile('header')
 
186
        self.failUnlessIn("command timed out: 2 seconds without output", got)
 
187
        if runtime.platformType == "posix":
 
188
            # the "killing pid" message is not present in windows
 
189
            self.failUnlessIn("killing pid", got)
 
190
        # but the process *ought* to be killed somehow
 
191
        self.failUnlessIn("process killed by signal", got)
 
192
        #print got
 
193
    if runtime.platformType != 'posix':
 
194
        testTimeout.todo = "timeout doesn't appear to work under windows"
 
195
 
 
196
    def testInterrupt1(self):
 
197
        args = {'command': [sys.executable, "sleep.py", "10"],
 
198
                'workdir': '.', 'timeout': 20}
 
199
        c = SlaveShellCommand(self.builder, None, args)
 
200
        d = c.start()
 
201
        reactor.callLater(1, c.interrupt)
 
202
        d.addCallback(self._testInterrupt1_1)
 
203
        return maybeWait(d)
 
204
    def _testInterrupt1_1(self, res):
 
205
        self.failIfEqual(self.getrc(), 0)
 
206
        got = self.getfile('header')
 
207
        self.failUnlessIn("command interrupted", got)
 
208
        if runtime.platformType == "posix":
 
209
            self.failUnlessIn("process killed by signal", got)
 
210
 
 
211
 
 
212
    # todo: twisted-specific command tests
 
213
 
 
214
class Shell(ShellBase, unittest.TestCase):
 
215
    usePTY = False
 
216
 
 
217
    def testInterrupt2(self):
 
218
        # test the backup timeout. This doesn't work under a PTY, because the
 
219
        # transport.loseConnection we do in the timeout handler actually
 
220
        # *does* kill the process.
 
221
        args = {'command': [sys.executable, "sleep.py", "5"],
 
222
                'workdir': '.', 'timeout': 20}
 
223
        c = SlaveShellCommand(self.builder, None, args)
 
224
        d = c.start()
 
225
        c.command.BACKUP_TIMEOUT = 1
 
226
        # make it unable to kill the child, by changing the signal it uses
 
227
        # from SIGKILL to the do-nothing signal 0.
 
228
        c.command.KILL = None
 
229
        reactor.callLater(1, c.interrupt)
 
230
        d.addBoth(self._testInterrupt2_1)
 
231
        return maybeWait(d)
 
232
    def _testInterrupt2_1(self, res):
 
233
        # the slave should raise a TimeoutError exception. In a normal build
 
234
        # process (i.e. one that uses step.RemoteShellCommand), this
 
235
        # exception will be handed to the Step, which will acquire an ERROR
 
236
        # status. In our test environment, it isn't such a big deal.
 
237
        self.failUnless(isinstance(res, failure.Failure),
 
238
                        "res is not a Failure: %s" % (res,))
 
239
        self.failUnless(res.check(commands.TimeoutError))
 
240
        self.checkrc(-1)
 
241
        return
 
242
        # the command is still actually running. Start another command, to
 
243
        # make sure that a) the old command's output doesn't interfere with
 
244
        # the new one, and b) the old command's actual termination doesn't
 
245
        # break anything
 
246
        args = {'command': [sys.executable, "sleep.py", "5"],
 
247
                'workdir': '.', 'timeout': 20}
 
248
        c = SlaveShellCommand(self.builder, None, args)
 
249
        d = c.start()
 
250
        d.addCallback(self._testInterrupt2_2)
 
251
        return d
 
252
    def _testInterrupt2_2(self, res):
 
253
        self.checkrc(0)
 
254
        # N.B.: under windows, the trial process hangs out for another few
 
255
        # seconds. I assume that the win32eventreactor is waiting for one of
 
256
        # the lingering child processes to really finish.
 
257
 
 
258
haveProcess = interfaces.IReactorProcess(reactor, None)
 
259
if runtime.platformType == 'posix':
 
260
    # test with PTYs also
 
261
    class ShellPTY(ShellBase, unittest.TestCase):
 
262
        usePTY = True
 
263
    if not haveProcess:
 
264
        ShellPTY.skip = "this reactor doesn't support IReactorProcess"
 
265
if not haveProcess:
 
266
    Shell.skip = "this reactor doesn't support IReactorProcess"