1
2 import os, signal, types, re, traceback
3 from stat import ST_CTIME, ST_MTIME, ST_SIZE
4 from collections import deque
5
6 from zope.interface import implements
7 from twisted.internet.protocol import ProcessProtocol
8 from twisted.internet import reactor, defer, task
9 from twisted.python import log, runtime
10
11 from buildslave.interfaces import ISlaveCommand
12 from buildslave.commands.registry import registerSlaveCommand
13 from buildslave import util
14
15
16
17
18 command_version = "2.9"
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 -class CommandInterrupted(Exception):
50
52 """An obfuscated string in a command"""
54 self.real = real
55 self.fake = fake
56
59
62
63 @staticmethod
65 if isinstance(s, (str, unicode)):
66 return s
67 else:
68 return str(s)
69
70 @staticmethod
81
82 @staticmethod
93
95 """A series of chained steps can raise this exception to indicate that
96 one of the intermediate ShellCommands has failed, such that there is no
97 point in running the remainder. 'rc' should be the non-zero exit code of
98 the failing ShellCommand."""
99
101 return "<AbandonChain rc=%s>" % self.args[0]
102
104 debug = False
105
107 self.command = command
108 self.pending_stdin = ""
109 self.stdin_finished = False
110
112 assert not self.stdin_finished
113 if self.connected:
114 self.transport.write(data)
115 else:
116 self.pending_stdin += data
117
123
125 if self.debug:
126 log.msg("ShellCommandPP.connectionMade")
127 if not self.command.process:
128 if self.debug:
129 log.msg(" assigning self.command.process: %s" %
130 (self.transport,))
131 self.command.process = self.transport
132
133
134
135
136
137
138
139
140
141
142
143
144 if self.pending_stdin:
145 if self.debug: log.msg(" writing to stdin")
146 self.transport.write(self.pending_stdin)
147 if self.stdin_finished:
148 if self.debug: log.msg(" closing stdin")
149 self.transport.closeStdin()
150
155
160
162 if self.debug:
163 log.msg("ShellCommandPP.processEnded", status_object)
164
165
166
167 sig = status_object.value.signal
168 rc = status_object.value.exitCode
169 self.command.finished(sig, rc)
170
172 POLL_INTERVAL = 2
173
174 - def __init__(self, command, name, logfile, follow=False):
175 self.command = command
176 self.name = name
177 self.logfile = logfile
178
179 log.msg("LogFileWatcher created to watch %s" % logfile)
180
181
182
183 self.old_logfile_stats = self.statFile()
184 self.started = False
185
186
187
188 self.follow = follow
189
190
191 self.poller = task.LoopingCall(self.poll)
192
195
197 log.err(err, msg="Polling error")
198 self.poller = None
199
201 self.poll()
202 if self.poller is not None:
203 self.poller.stop()
204 if self.started:
205 self.f.close()
206
208 if os.path.exists(self.logfile):
209 s = os.stat(self.logfile)
210 return (s[ST_CTIME], s[ST_MTIME], s[ST_SIZE])
211 return None
212
214 if not self.started:
215 s = self.statFile()
216 if s == self.old_logfile_stats:
217 return
218 if not s:
219
220
221
222 self.old_logfile_stats = None
223 return
224 self.f = open(self.logfile, "rb")
225
226
227
228 if self.follow:
229 self.f.seek(s[2], 0)
230 self.started = True
231 self.f.seek(self.f.tell(), 0)
232 while True:
233 data = self.f.read(10000)
234 if not data:
235 return
236 self.command.addLogfile(self.name, data)
237
240
241
242
243 notreally = False
244 BACKUP_TIMEOUT = 5
245 KILL = "KILL"
246 CHUNK_LIMIT = 128*1024
247
248
249
250 BUFFER_SIZE = 64*1024
251 BUFFER_TIMEOUT = 5
252
253
254 startTime = None
255 elapsedTime = None
256
257
258 _reactor = reactor
259
260
261
262
263
264
265 - def __init__(self, builder, command,
266 workdir, environ=None,
267 sendStdout=True, sendStderr=True, sendRC=True,
268 timeout=None, maxTime=None, initialStdin=None,
269 keepStdinOpen=False, keepStdout=False, keepStderr=False,
270 logEnviron=True, logfiles={}, usePTY="slave-config"):
271 """
272
273 @param keepStdout: if True, we keep a copy of all the stdout text
274 that we've seen. This copy is available in
275 self.stdout, which can be read after the command
276 has finished.
277 @param keepStderr: same, for stderr
278
279 @param usePTY: "slave-config" -> use the SlaveBuilder's usePTY;
280 otherwise, true to use a PTY, false to not use a PTY.
281 """
282
283 self.builder = builder
284 self.command = Obfuscated.get_real(command)
285
286
287
288
289
290
291
292
293
294
295
296 if isinstance(self.command, (tuple, list)):
297 for i, a in enumerate(self.command):
298 if isinstance(a, unicode):
299 self.command[i] = a.encode(self.builder.unicode_encoding)
300 elif isinstance(self.command, unicode):
301 self.command = self.command.encode(self.builder.unicode_encoding)
302
303 self.fake_command = Obfuscated.get_fake(command)
304 self.sendStdout = sendStdout
305 self.sendStderr = sendStderr
306 self.sendRC = sendRC
307 self.logfiles = logfiles
308 self.workdir = workdir
309 if not os.path.exists(workdir):
310 os.makedirs(workdir)
311 if environ:
312 if environ.has_key('PYTHONPATH'):
313 ppath = environ['PYTHONPATH']
314
315
316
317 if not isinstance(ppath, str):
318
319
320 ppath = os.pathsep.join(ppath)
321
322 environ['PYTHONPATH'] = ppath + os.pathsep + "${PYTHONPATH}"
323
324
325 p = re.compile('\${([0-9a-zA-Z_]*)}')
326 def subst(match):
327 return os.environ.get(match.group(1), "")
328 newenv = {}
329 for key in os.environ.keys():
330
331 if key not in environ or environ[key] is not None:
332 newenv[key] = os.environ[key]
333 for key in environ.keys():
334 if environ[key] is not None:
335 newenv[key] = p.sub(subst, environ[key])
336
337 self.environ = newenv
338 else:
339 self.environ = os.environ.copy()
340 self.initialStdin = initialStdin
341 self.keepStdinOpen = keepStdinOpen
342 self.logEnviron = logEnviron
343 self.timeout = timeout
344 self.timer = None
345 self.maxTime = maxTime
346 self.maxTimer = None
347 self.keepStdout = keepStdout
348 self.keepStderr = keepStderr
349
350 self.buffered = deque()
351 self.buflen = 0
352 self.buftimer = None
353
354 if usePTY == "slave-config":
355 self.usePTY = self.builder.usePTY
356 else:
357 self.usePTY = usePTY
358
359
360
361
362
363 if runtime.platformType != "posix" or initialStdin is not None:
364 if self.usePTY and usePTY != "slave-config":
365 self.sendStatus({'header': "WARNING: disabling usePTY for this command"})
366 self.usePTY = False
367
368 self.logFileWatchers = []
369 for name,filevalue in self.logfiles.items():
370 filename = filevalue
371 follow = False
372
373
374
375 if type(filevalue) == dict:
376 filename = filevalue['filename']
377 follow = filevalue.get('follow', False)
378
379 w = LogFileWatcher(self, name,
380 os.path.join(self.workdir, filename),
381 follow=follow)
382 self.logFileWatchers.append(w)
383
385 return "<slavecommand.ShellCommand '%s'>" % self.fake_command
386
389
391
392
393 if self.keepStdout:
394 self.stdout = ""
395 if self.keepStderr:
396 self.stderr = ""
397 self.deferred = defer.Deferred()
398 try:
399 self._startCommand()
400 except:
401 log.msg("error in ShellCommand._startCommand")
402 log.err()
403 self._addToBuffers('stderr', "error in ShellCommand._startCommand\n")
404 self._addToBuffers('stderr', traceback.format_exc())
405 self._sendBuffers()
406
407 self.deferred.errback(AbandonChain(-1))
408 return self.deferred
409
411
412 if not os.path.isdir(self.workdir):
413 os.makedirs(self.workdir)
414 log.msg("ShellCommand._startCommand")
415 if self.notreally:
416 self._addToBuffers('header', "command '%s' in dir %s" % \
417 (self.fake_command, self.workdir))
418 self._addToBuffers('header', "(not really)\n")
419 self.finished(None, 0)
420 return
421
422 self.pp = ShellCommandPP(self)
423
424 if type(self.command) in types.StringTypes:
425 if runtime.platformType == 'win32':
426 argv = os.environ['COMSPEC'].split()
427 if '/c' not in argv: argv += ['/c']
428 argv += [self.command]
429 else:
430
431
432 argv = ['/bin/sh', '-c', self.command]
433 display = self.fake_command
434 else:
435
436
437
438
439
440 if runtime.platformType == 'win32' and not \
441 (self.command[0].lower().endswith(".exe") and os.path.isabs(self.command[0])):
442 argv = os.environ['COMSPEC'].split()
443 if '/c' not in argv: argv += ['/c']
444 argv += list(self.command)
445 else:
446 argv = self.command
447 display = " ".join(self.fake_command)
448
449
450
451
452 if not self.environ.get('MACHTYPE', None) == 'i686-pc-msys':
453 self.environ['PWD'] = os.path.abspath(self.workdir)
454
455
456
457
458
459
460
461 log.msg(" " + display)
462 self._addToBuffers('header', display+"\n")
463
464
465 msg = " in dir %s" % (self.workdir,)
466 if self.timeout:
467 msg += " (timeout %d secs)" % (self.timeout,)
468 log.msg(" " + msg)
469 self._addToBuffers('header', msg+"\n")
470
471 msg = " watching logfiles %s" % (self.logfiles,)
472 log.msg(" " + msg)
473 self._addToBuffers('header', msg+"\n")
474
475
476 msg = " argv: %s" % (self.fake_command,)
477 log.msg(" " + msg)
478 self._addToBuffers('header', msg+"\n")
479
480
481 if self.logEnviron:
482 msg = " environment:\n"
483 env_names = self.environ.keys()
484 env_names.sort()
485 for name in env_names:
486 msg += " %s=%s\n" % (name, self.environ[name])
487 log.msg(" environment: %s" % (self.environ,))
488 self._addToBuffers('header', msg)
489
490 if self.initialStdin:
491 msg = " writing %d bytes to stdin" % len(self.initialStdin)
492 log.msg(" " + msg)
493 self._addToBuffers('header', msg+"\n")
494
495 if self.keepStdinOpen:
496 msg = " leaving stdin open"
497 else:
498 msg = " closing stdin"
499 log.msg(" " + msg)
500 self._addToBuffers('header', msg+"\n")
501
502 msg = " using PTY: %s" % bool(self.usePTY)
503 log.msg(" " + msg)
504 self._addToBuffers('header', msg+"\n")
505
506
507 if self.initialStdin:
508 self.pp.writeStdin(self.initialStdin)
509 if not self.keepStdinOpen:
510 self.pp.closeStdin()
511
512
513
514
515
516
517
518
519
520
521 self.process = None
522 self.startTime = util.now(self._reactor)
523
524 p = reactor.spawnProcess(self.pp, argv[0], argv,
525 self.environ,
526 self.workdir,
527 usePTY=self.usePTY)
528
529 if not self.process:
530 self.process = p
531
532
533
534
535
536
537
538 if self.timeout:
539 self.timer = self._reactor.callLater(self.timeout, self.doTimeout)
540
541 if self.maxTime:
542 self.maxTimer = self._reactor.callLater(self.maxTime, self.doMaxTimeout)
543
544 for w in self.logFileWatchers:
545 w.start()
546
547
549 """
550 limit the chunks that we send over PB to 128k, since it has a hardwired
551 string-size limit of 640k.
552 """
553 LIMIT = self.CHUNK_LIMIT
554 for i in range(0, len(data), LIMIT):
555 yield data[i:i+LIMIT]
556
558 """
559 Take msg, which is a dictionary of lists of output chunks, and
560 concatentate all the chunks into a single string
561 """
562 retval = {}
563 for log in msg:
564 data = "".join(msg[log])
565 if isinstance(log, tuple) and log[0] == 'log':
566 retval['log'] = (log[1], data)
567 else:
568 retval[log] = data
569 return retval
570
572 """
573 Collapse and send msg to the master
574 """
575 if not msg:
576 return
577 msg = self._collapseMsg(msg)
578 self.sendStatus(msg)
579
581 self.buftimer = None
582 self._sendBuffers()
583
585 """
586 Send all the content in our buffers.
587 """
588 msg = {}
589 msg_size = 0
590 lastlog = None
591 logdata = []
592 while self.buffered:
593
594 logname, data = self.buffered.popleft()
595
596
597
598
599
600
601
602
603
604 if lastlog is None:
605 lastlog = logname
606 elif logname != lastlog:
607 self._sendMessage(msg)
608 msg = {}
609 msg_size = 0
610 lastlog = logname
611
612 logdata = msg.setdefault(logname, [])
613
614
615
616 for chunk in self._chunkForSend(data):
617 if len(chunk) == 0: continue
618 logdata.append(chunk)
619 msg_size += len(chunk)
620 if msg_size >= self.CHUNK_LIMIT:
621
622
623
624 self._sendMessage(msg)
625 msg = {}
626 logdata = msg.setdefault(logname, [])
627 msg_size = 0
628 self.buflen = 0
629 if logdata:
630 self._sendMessage(msg)
631 if self.buftimer:
632 if self.buftimer.active():
633 self.buftimer.cancel()
634 self.buftimer = None
635
637 """
638 Add data to the buffer for logname
639 Start a timer to send the buffers if BUFFER_TIMEOUT elapses.
640 If adding data causes the buffer size to grow beyond BUFFER_SIZE, then
641 the buffers will be sent.
642 """
643 n = len(data)
644
645 self.buflen += n
646 self.buffered.append((logname, data))
647 if self.buflen > self.BUFFER_SIZE:
648 self._sendBuffers()
649 elif not self.buftimer:
650 self.buftimer = self._reactor.callLater(self.BUFFER_TIMEOUT, self._bufferTimeout)
651
653 if self.sendStdout:
654 self._addToBuffers('stdout', data)
655
656 if self.keepStdout:
657 self.stdout += data
658 if self.timer:
659 self.timer.reset(self.timeout)
660
662 if self.sendStderr:
663 self._addToBuffers('stderr', data)
664
665 if self.keepStderr:
666 self.stderr += data
667 if self.timer:
668 self.timer.reset(self.timeout)
669
675
677 self.elapsedTime = util.now(self._reactor) - self.startTime
678 log.msg("command finished with signal %s, exit code %s, elapsedTime: %0.6f" % (sig,rc,self.elapsedTime))
679 for w in self.logFileWatchers:
680
681 w.stop()
682 self._sendBuffers()
683 if sig is not None:
684 rc = -1
685 if self.sendRC:
686 if sig is not None:
687 self.sendStatus(
688 {'header': "process killed by signal %d\n" % sig})
689 self.sendStatus({'rc': rc})
690 self.sendStatus({'header': "elapsedTime=%0.6f\n" % self.elapsedTime})
691 if self.timer:
692 self.timer.cancel()
693 self.timer = None
694 if self.maxTimer:
695 self.maxTimer.cancel()
696 self.maxTimer = None
697 if self.buftimer:
698 self.buftimer.cancel()
699 self.buftimer = None
700 d = self.deferred
701 self.deferred = None
702 if d:
703 d.callback(rc)
704 else:
705 log.msg("Hey, command %s finished twice" % self)
706
708 self._sendBuffers()
709 log.msg("ShellCommand.failed: command failed: %s" % (why,))
710 if self.timer:
711 self.timer.cancel()
712 self.timer = None
713 if self.maxTimer:
714 self.maxTimer.cancel()
715 self.maxTimer = None
716 if self.buftimer:
717 self.buftimer.cancel()
718 self.buftimer = None
719 d = self.deferred
720 self.deferred = None
721 if d:
722 d.errback(why)
723 else:
724 log.msg("Hey, command %s finished twice" % self)
725
727 self.timer = None
728 msg = "command timed out: %d seconds without output" % self.timeout
729 self.kill(msg)
730
732 self.maxTimer = None
733 msg = "command timed out: %d seconds elapsed" % self.maxTime
734 self.kill(msg)
735
736 - def kill(self, msg):
737
738
739 self._sendBuffers()
740 if self.timer:
741 self.timer.cancel()
742 self.timer = None
743 if self.maxTimer:
744 self.maxTimer.cancel()
745 self.maxTimer = None
746 if self.buftimer:
747 self.buftimer.cancel()
748 self.buftimer = None
749 if hasattr(self.process, "pid") and self.process.pid is not None:
750 msg += ", killing pid %s" % self.process.pid
751 log.msg(msg)
752 self.sendStatus({'header': "\n" + msg + "\n"})
753
754 hit = 0
755 if runtime.platformType == "posix":
756 try:
757
758
759
760
761
762
763
764
765
766 sig = None
767 if self.KILL is not None:
768 sig = getattr(signal, "SIG"+ self.KILL, None)
769
770 if self.KILL == None:
771 log.msg("self.KILL==None, only pretending to kill child")
772 elif sig is None:
773 log.msg("signal module is missing SIG%s" % self.KILL)
774 elif not hasattr(os, "kill"):
775 log.msg("os module is missing the 'kill' function")
776 elif not hasattr(self.process, "pid") or self.process.pid is None:
777 log.msg("self.process has no pid")
778 else:
779 log.msg("trying os.kill(-pid, %d)" % (sig,))
780
781 os.kill(-self.process.pid, sig)
782 log.msg(" signal %s sent successfully" % sig)
783 hit = 1
784 except OSError:
785
786
787 pass
788 if not hit:
789 try:
790 if self.KILL is None:
791 log.msg("self.KILL==None, only pretending to kill child")
792 else:
793 log.msg("trying process.signalProcess('KILL')")
794 self.process.signalProcess(self.KILL)
795 log.msg(" signal %s sent successfully" % (self.KILL,))
796 hit = 1
797 except OSError:
798
799 pass
800 if not hit:
801 log.msg("signalProcess/os.kill failed both times")
802
803 if runtime.platformType == "posix":
804
805
806
807 self.pp.transport.loseConnection()
808
809
810
811 self.timer = self._reactor.callLater(self.BACKUP_TIMEOUT,
812 self.doBackupTimeout)
813
815 log.msg("we tried to kill the process, and it wouldn't die.."
816 " finish anyway")
817 self.timer = None
818 self.sendStatus({'header': "SIGKILL failed to kill process\n"})
819 if self.sendRC:
820 self.sendStatus({'header': "using fake rc=-1\n"})
821 self.sendStatus({'rc': -1})
822 self.failed(TimeoutError("SIGKILL failed to kill process"))
823
824
827
830
833 implements(ISlaveCommand)
834
835 """This class defines one command that can be invoked by the build master.
836 The command is executed on the slave side, and always sends back a
837 completion message when it finishes. It may also send intermediate status
838 as it runs (by calling builder.sendStatus). Some commands can be
839 interrupted (either by the build master or a local timeout), in which
840 case the step is expected to complete normally with a status message that
841 indicates an error occurred.
842
843 These commands are used by BuildSteps on the master side. Each kind of
844 BuildStep uses a single Command. The slave must implement all the
845 Commands required by the set of BuildSteps used for any given build:
846 this is checked at startup time.
847
848 All Commands are constructed with the same signature:
849 c = CommandClass(builder, args)
850 where 'builder' is the parent SlaveBuilder object, and 'args' is a
851 dict that is interpreted per-command.
852
853 The setup(args) method is available for setup, and is run from __init__.
854
855 The Command is started with start(). This method must be implemented in a
856 subclass, and it should return a Deferred. When your step is done, you
857 should fire the Deferred (the results are not used). If the command is
858 interrupted, it should fire the Deferred anyway.
859
860 While the command runs. it may send status messages back to the
861 buildmaster by calling self.sendStatus(statusdict). The statusdict is
862 interpreted by the master-side BuildStep however it likes.
863
864 A separate completion message is sent when the deferred fires, which
865 indicates that the Command has finished, but does not carry any status
866 data. If the Command needs to return an exit code of some sort, that
867 should be sent as a regular status message before the deferred is fired .
868 Once builder.commandComplete has been run, no more status messages may be
869 sent.
870
871 If interrupt() is called, the Command should attempt to shut down as
872 quickly as possible. Child processes should be killed, new ones should
873 not be started. The Command should send some kind of error status update,
874 then complete as usual by firing the Deferred.
875
876 .interrupted should be set by interrupt(), and can be tested to avoid
877 sending multiple error status messages.
878
879 If .running is False, the bot is shutting down (or has otherwise lost the
880 connection to the master), and should not send any status messages. This
881 is checked in Command.sendStatus .
882
883 """
884
885
886
887
888
889 debug = False
890 interrupted = False
891 running = False
892
893
894 _reactor = reactor
895
896 - def __init__(self, builder, stepId, args):
901
903 """Override this in a subclass to extract items from the args dict."""
904 pass
905
911
913 """Start the command. This method should return a Deferred that will
914 fire when the command has completed. The Deferred's argument will be
915 ignored.
916
917 This method should be overridden by subclasses."""
918 raise NotImplementedError, "You must implement this in a subclass"
919
928
932
934 """Override this in a subclass to allow commands to be interrupted.
935 May be called multiple times, test and set self.interrupted=True if
936 this matters."""
937 pass
938
940 self.running = False
941 return res
942
943
944
946 if type(rc) is not int:
947 log.msg("weird, _abandonOnFailure was given rc=%s (%s)" % \
948 (rc, type(rc)))
949 assert isinstance(rc, int)
950 if rc != 0:
951 raise AbandonChain(rc)
952 return rc
953
956
958 log.msg("_checkAbandoned", why)
959 why.trap(AbandonChain)
960 log.msg(" abandoning chain", why.value)
961 self.sendStatus({'rc': why.value.args[0]})
962 return None
963
967 """This is a Command which runs a shell command. The args dict contains
968 the following keys:
969
970 - ['command'] (required): a shell command to run. If this is a string,
971 it will be run with /bin/sh (['/bin/sh',
972 '-c', command]). If it is a list
973 (preferred), it will be used directly.
974 - ['workdir'] (required): subdirectory in which the command will be
975 run, relative to the builder dir
976 - ['env']: a dict of environment variables to augment/replace
977 os.environ . PYTHONPATH is treated specially, and
978 should be a list of path components to be prepended to
979 any existing PYTHONPATH environment variable.
980 - ['initial_stdin']: a string which will be written to the command's
981 stdin as soon as it starts
982 - ['keep_stdin_open']: unless True, the command's stdin will be
983 closed as soon as initial_stdin has been
984 written. Set this to True if you plan to write
985 to stdin after the command has been started.
986 - ['want_stdout']: 0 if stdout should be thrown away
987 - ['want_stderr']: 0 if stderr should be thrown away
988 - ['usePTY']: True or False if the command should use a PTY (defaults to
989 configuration of the slave)
990 - ['not_really']: 1 to skip execution and return rc=0
991 - ['timeout']: seconds of silence to tolerate before killing command
992 - ['maxTime']: seconds before killing command
993 - ['logfiles']: dict mapping LogFile name to the workdir-relative
994 filename of a local log file. This local file will be
995 watched just like 'tail -f', and all changes will be
996 written to 'log' status updates.
997 - ['logEnviron']: False to not log the environment variables on the slave
998
999 ShellCommand creates the following status messages:
1000 - {'stdout': data} : when stdout data is available
1001 - {'stderr': data} : when stderr data is available
1002 - {'header': data} : when headers (command start/stop) are available
1003 - {'log': (logfile_name, data)} : when log files have new contents
1004 - {'rc': rc} : when the process has terminated
1005 """
1006
1008 args = self.args
1009
1010 assert args['workdir'] is not None
1011 workdir = os.path.join(self.builder.basedir, args['workdir'])
1012
1013 c = ShellCommand(self.builder, args['command'],
1014 workdir, environ=args.get('env'),
1015 timeout=args.get('timeout', None),
1016 maxTime=args.get('maxTime', None),
1017 sendStdout=args.get('want_stdout', True),
1018 sendStderr=args.get('want_stderr', True),
1019 sendRC=True,
1020 initialStdin=args.get('initial_stdin'),
1021 keepStdinOpen=args.get('keep_stdin_open'),
1022 logfiles=args.get('logfiles', {}),
1023 usePTY=args.get('usePTY', "slave-config"),
1024 logEnviron=args.get('logEnviron', True),
1025 )
1026 c._reactor = self._reactor
1027 self.command = c
1028 d = self.command.start()
1029 return d
1030
1034
1037
1040
1041 registerSlaveCommand("shell", SlaveShellCommand, command_version)
1045 """
1046 I am a dummy no-op command that by default takes 5 seconds to complete.
1047 See L{buildbot.steps.dummy.RemoteDummy}
1048 """
1049
1051 self.d = defer.Deferred()
1052 log.msg(" starting dummy command [%s]" % self.stepId)
1053 self.timer = self._reactor.callLater(1, self.doStatus)
1054 return self.d
1055
1063
1069
1071 log.msg(" dummy command finished [%s]" % self.stepId)
1072 if self.interrupted:
1073 self.sendStatus({'rc': 1})
1074 else:
1075 self.sendStatus({'rc': 0})
1076 self.d.callback(0)
1077
1078 registerSlaveCommand("dummy", DummyCommand, command_version)
1079
1080
1081
1082
1083
1084 waitCommandRegistry = {}
1087 """
1088 I am a dummy command used by the buildbot unit test suite. I want for the
1089 unit test to tell us to finish. See L{buildbot.steps.dummy.Wait}
1090 """
1091
1093 self.d = defer.Deferred()
1094 log.msg(" starting wait command [%s]" % self.stepId)
1095 handle = self.args['handle']
1096 cb = waitCommandRegistry[handle]
1097 del waitCommandRegistry[handle]
1098 def _called():
1099 log.msg(" wait-%s starting" % (handle,))
1100 d = cb()
1101 def _done(res):
1102 log.msg(" wait-%s finishing: %s" % (handle, res))
1103 return res
1104 d.addBoth(_done)
1105 d.addCallbacks(self.finished, self.failed)
1106 self._reactor.callLater(0, _called)
1107 return self.d
1108
1115
1117 log.msg(" wait command finished [%s]" % self.stepId)
1118 if self.interrupted:
1119 self.sendStatus({'rc': 2})
1120 else:
1121 self.sendStatus({'rc': 0})
1122 self.d.callback(0)
1124 log.msg(" wait command failed [%s]" % self.stepId)
1125 self.sendStatus({'rc': 1})
1126 self.d.callback(0)
1127
1128 registerSlaveCommand("dummy.wait", WaitCommand, command_version)
1129