root / trunk / twisted / test / test_process.py

Revision 27080, 71.8 kB (checked in by z3p, 2 days ago)

make twisted.test.test_process.MockOS not dependent on the os module

Author: z3p

Reviewer: therve

Fixes #3822

twisted.test.test_process.MockOS serves as a way to patch all/parts of the OS
module for testing. However, it used parts of the os module that were not
cross-platform, causing it to only work on some operating systems. This branch
removed those dependencies.

Line 
1 # Copyright (c) 2001-2009 Twisted Matrix Laboratories.
2 # See LICENSE for details.
3
4 """
5 Test running processes.
6 """
7
8 import gzip
9 import os
10 import popen2
11 import sys
12 import signal
13 import StringIO
14 import errno
15 import gc
16 import stat
17 try:
18     import fcntl
19 except ImportError:
20     fcntl = process = None
21 else:
22     from twisted.internet import process
23
24
25 from zope.interface.verify import verifyObject
26
27 from twisted.internet import reactor, protocol, error, interfaces, defer
28 from twisted.trial import unittest
29 from twisted.python import util, runtime, procutils
30 from twisted.python.compat import set
31
32
33
34 class StubProcessProtocol(protocol.ProcessProtocol):
35     """
36     ProcessProtocol counter-implementation: all methods on this class raise an
37     exception, so instances of this may be used to verify that only certain
38     methods are called.
39     """
40     def outReceived(self, data):
41         raise NotImplementedError()
42
43     def errReceived(self, data):
44         raise NotImplementedError()
45
46     def inConnectionLost(self):
47         raise NotImplementedError()
48
49     def outConnectionLost(self):
50         raise NotImplementedError()
51
52     def errConnectionLost(self):
53         raise NotImplementedError()
54
55
56
57 class ProcessProtocolTests(unittest.TestCase):
58     """
59     Tests for behavior provided by the process protocol base class,
60     L{protocol.ProcessProtocol}.
61     """
62     def test_interface(self):
63         """
64         L{ProcessProtocol} implements L{IProcessProtocol}.
65         """
66         verifyObject(interfaces.IProcessProtocol, protocol.ProcessProtocol())
67
68
69     def test_outReceived(self):
70         """
71         Verify that when stdout is delivered to
72         L{ProcessProtocol.childDataReceived}, it is forwarded to
73         L{ProcessProtocol.outReceived}.
74         """
75         received = []
76         class OutProtocol(StubProcessProtocol):
77             def outReceived(self, data):
78                 received.append(data)
79
80         bytes = "bytes"
81         p = OutProtocol()
82         p.childDataReceived(1, bytes)
83         self.assertEqual(received, [bytes])
84
85
86     def test_errReceived(self):
87         """
88         Similar to L{test_outReceived}, but for stderr.
89         """
90         received = []
91         class ErrProtocol(StubProcessProtocol):
92             def errReceived(self, data):
93                 received.append(data)
94
95         bytes = "bytes"
96         p = ErrProtocol()
97         p.childDataReceived(2, bytes)
98         self.assertEqual(received, [bytes])
99
100
101     def test_inConnectionLost(self):
102         """
103         Verify that when stdin close notification is delivered to
104         L{ProcessProtocol.childConnectionLost}, it is forwarded to
105         L{ProcessProtocol.inConnectionLost}.
106         """
107         lost = []
108         class InLostProtocol(StubProcessProtocol):
109             def inConnectionLost(self):
110                 lost.append(None)
111
112         p = InLostProtocol()
113         p.childConnectionLost(0)
114         self.assertEqual(lost, [None])
115
116
117     def test_outConnectionLost(self):
118         """
119         Similar to L{test_inConnectionLost}, but for stdout.
120         """
121         lost = []
122         class OutLostProtocol(StubProcessProtocol):
123             def outConnectionLost(self):
124                 lost.append(None)
125
126         p = OutLostProtocol()
127         p.childConnectionLost(1)
128         self.assertEqual(lost, [None])
129
130
131     def test_errConnectionLost(self):
132         """
133         Similar to L{test_inConnectionLost}, but for stderr.
134         """
135         lost = []
136         class ErrLostProtocol(StubProcessProtocol):
137             def errConnectionLost(self):
138                 lost.append(None)
139
140         p = ErrLostProtocol()
141         p.childConnectionLost(2)
142         self.assertEqual(lost, [None])
143
144
145
146 class TrivialProcessProtocol(protocol.ProcessProtocol):
147     """
148     Simple process protocol for tests purpose.
149
150     @ivar outData: data received from stdin
151     @ivar errData: data received from stderr
152     """
153
154     def __init__(self, d):
155         """
156         Create the deferred that will be fired at the end, and initialize
157         data structures.
158         """
159         self.deferred = d
160         self.outData = []
161         self.errData = []
162
163     def processEnded(self, reason):
164         self.reason = reason
165         self.deferred.callback(None)
166
167     def outReceived(self, data):
168         self.outData.append(data)
169
170     def errReceived(self, data):
171         self.errData.append(data)
172
173
174 class TestProcessProtocol(protocol.ProcessProtocol):
175
176     def connectionMade(self):
177         self.stages = [1]
178         self.data = ''
179         self.err = ''
180         self.transport.write("abcd")
181
182     def childDataReceived(self, childFD, data):
183         """
184         Override and disable the dispatch provided by the base class to ensure
185         that it is really this method which is being called, and the transport
186         is not going directly to L{outReceived} or L{errReceived}.
187         """
188         if childFD == 1:
189             self.data += data
190         elif childFD == 2:
191             self.err += data
192
193
194     def childConnectionLost(self, childFD):
195         """
196         Similarly to L{childDataReceived}, disable the automatic dispatch
197         provided by the base implementation to verify that the transport is
198         calling this method directly.
199         """
200         if childFD == 1:
201             self.stages.append(2)
202             if self.data != "abcd":
203                 raise RuntimeError
204             self.transport.write("1234")
205         elif childFD == 2:
206             self.stages.append(3)
207             if self.err != "1234":
208                 print 'err != 1234: ' + repr(self.err)
209                 raise RuntimeError()
210             self.transport.write("abcd")
211             self.stages.append(4)
212         elif childFD == 0:
213             self.stages.append(5)
214
215     def processEnded(self, reason):
216         self.reason = reason
217         self.deferred.callback(None)
218
219
220 class EchoProtocol(protocol.ProcessProtocol):
221
222     s = "1234567" * 1001
223     n = 10
224     finished = 0
225
226     failure = None
227
228     def __init__(self, onEnded):
229         self.onEnded = onEnded
230         self.count = 0
231
232     def connectionMade(self):
233         assert self.n > 2
234         for i in range(self.n - 2):
235             self.transport.write(self.s)
236         # test writeSequence
237         self.transport.writeSequence([self.s, self.s])
238         self.buffer = self.s * self.n
239
240     def outReceived(self, data):
241         if buffer(self.buffer, self.count, len(data)) != buffer(data):
242             self.failure = ("wrong bytes received", data, self.count)
243             self.transport.closeStdin()
244         else:
245             self.count += len(data)
246             if self.count == len(self.buffer):
247                 self.transport.closeStdin()
248
249     def processEnded(self, reason):
250         self.finished = 1
251         if not reason.check(error.ProcessDone):
252             self.failure = "process didn't terminate normally: " + str(reason)
253         self.onEnded.callback(self)
254
255
256
257 class SignalProtocol(protocol.ProcessProtocol):
258     """
259     A process protocol that sends a signal when data is first received.
260
261     @ivar deferred: deferred firing on C{processEnded}.
262     @type deferred: L{defer.Deferred}
263
264     @ivar signal: the signal to send to the process.
265     @type signal: C{str}
266     """
267
268     def __init__(self, deferred, sig):
269         self.deferred = deferred
270         self.signal = sig
271
272
273     def outReceived(self, data):
274         self.transport.signalProcess(self.signal)
275
276
277     def processEnded(self, reason):
278         """
279         Callback C{self.deferred} with C{None} if C{reason} is a
280         L{error.ProcessTerminated} failure with C{exitCode} set to C{None},
281         C{signal} set to C{self.signal}, and C{status} holding the status code
282         of the exited process. Otherwise, errback with a C{ValueError}
283         describing the problem.
284         """
285         if not reason.check(error.ProcessTerminated):
286             return self.deferred.errback(
287                 ValueError("wrong termination: %s" % (reason,)))
288         v = reason.value
289         signalValue = getattr(signal, 'SIG' + self.signal)
290         if v.exitCode is not None:
291             return self.deferred.errback(
292                 ValueError("SIG%s: exitCode is %s, not None" %
293                            (self.signal, v.exitCode)))
294         if v.signal != signalValue:
295             return self.deferred.errback(
296                 ValueError("SIG%s: .signal was %s, wanted %s" %
297                            (self.signal, v.signal, signalValue)))
298         if os.WTERMSIG(v.status) != signalValue:
299             return self.deferred.errback(
300                 ValueError('SIG%s: %s' % (self.signal, os.WTERMSIG(v.status))))
301         self.deferred.callback(None)
302
303
304
305 class TestManyProcessProtocol(TestProcessProtocol):
306     def __init__(self):
307         self.deferred = defer.Deferred()
308
309     def processEnded(self, reason):
310         self.reason = reason
311         if reason.check(error.ProcessDone):
312             self.deferred.callback(None)
313         else:
314             self.deferred.errback(reason)
315
316
317
318 class UtilityProcessProtocol(protocol.ProcessProtocol):
319     """
320     Helper class for launching a Python process and getting a result from it.
321
322     @ivar program: A string giving a Python program for the child process to
323     run.
324     """
325     program = None
326
327     def run(cls, reactor, argv, env):
328         """
329         Run a Python process connected to a new instance of this protocol
330         class.  Return the protocol instance.
331
332         The Python process is given C{self.program} on the command line to
333         execute, in addition to anything specified by C{argv}.  C{env} is
334         the complete environment.
335         """
336         exe = sys.executable
337         self = cls()
338         reactor.spawnProcess(
339             self, exe, [exe, "-c", self.program] + argv, env=env)
340         return self
341     run = classmethod(run)
342
343
344     def __init__(self):
345         self.bytes = []
346         self.requests = []
347
348
349     def parseChunks(self, bytes):
350         """
351         Called with all bytes received on stdout when the process exits.
352         """
353         raise NotImplementedError()
354
355
356     def getResult(self):
357         """
358         Return a Deferred which will fire with the result of L{parseChunks}
359         when the child process exits.
360         """
361         d = defer.Deferred()
362         self.requests.append(d)
363         return d
364
365
366     def _fireResultDeferreds(self, result):
367         """
368         Callback all Deferreds returned up until now by L{getResult}
369         with the given result object.
370         """
371         requests = self.requests
372         self.requests = None
373         for d in requests:
374             d.callback(result)
375
376
377     def outReceived(self, bytes):
378         """
379         Accumulate output from the child process in a list.
380         """
381         self.bytes.append(bytes)
382
383
384     def processEnded(self, reason):
385         """
386         Handle process termination by parsing all received output and firing
387         any waiting Deferreds.
388         """
389         self._fireResultDeferreds(self.parseChunks(self.bytes))
390
391
392
393
394 class GetArgumentVector(UtilityProcessProtocol):
395     """
396     Protocol which will read a serialized argv from a process and
397     expose it to interested parties.
398     """
399     program = (
400         "from sys import stdout, argv\n"
401         "stdout.write(chr(0).join(argv))\n"
402         "stdout.flush()\n")
403
404     def parseChunks(self, chunks):
405         """
406         Parse the output from the process to which this protocol was
407         connected, which is a single unterminated line of \\0-separated
408         strings giving the argv of that process.  Return this as a list of
409         str objects.
410         """
411         return ''.join(chunks).split('\0')
412
413
414
415 class GetEnvironmentDictionary(UtilityProcessProtocol):
416     """
417     Protocol which will read a serialized environment dict from a process
418     and expose it to interested parties.
419     """
420     program = (
421         "from sys import stdout\n"
422         "from os import environ\n"
423         "items = environ.iteritems()\n"
424         "stdout.write(chr(0).join([k + chr(0) + v for k, v in items]))\n"
425         "stdout.flush()\n")
426
427     def parseChunks(self, chunks):
428         """
429         Parse the output from the process to which this protocol was
430         connected, which is a single unterminated line of \\0-separated
431         strings giving key value pairs of the environment from that process.
432         Return this as a dictionary.
433         """
434         environString = ''.join(chunks)
435         if not environString:
436             return {}
437         environ = iter(environString.split('\0'))
438         d = {}
439         while 1:
440             try:
441                 k = environ.next()
442             except StopIteration:
443                 break
444             else:
445                 v = environ.next()
446                 d[k] = v
447         return d
448
449
450
451 class ProcessTestCase(unittest.TestCase):
452     """Test running a process."""
453
454     usePTY = False
455
456     def testStdio(self):
457         """twisted.internet.stdio test."""
458         exe = sys.executable
459         scriptPath = util.sibpath(__file__, "process_twisted.py")
460         p = Accumulator()
461         d = p.endedDeferred = defer.Deferred()
462         env = {"PYTHONPATH": os.pathsep.join(sys.path)}
463         reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=env,
464                              path=None, usePTY=self.usePTY)
465         p.transport.write("hello, world")
466         p.transport.write("abc")
467         p.transport.write("123")
468         p.transport.closeStdin()
469
470         def processEnded(ign):
471             self.assertEquals(p.outF.getvalue(), "hello, worldabc123",
472                               "Output follows:\n"
473                               "%s\n"
474                               "Error message from process_twisted follows:\n"
475                               "%s\n" % (p.outF.getvalue(), p.errF.getvalue()))
476         return d.addCallback(processEnded)
477
478
479     def test_unsetPid(self):
480         """
481         Test if pid is None/non-None before/after process termination.  This
482         reuses process_echoer.py to get a process that blocks on stdin.
483         """
484         finished = defer.Deferred()
485         p = TrivialProcessProtocol(finished)
486         exe = sys.executable
487         scriptPath = util.sibpath(__file__, "process_echoer.py")
488         procTrans = reactor.spawnProcess(p, exe,
489                                     [exe, scriptPath], env=None)
490         self.failUnless(procTrans.pid)
491
492         def afterProcessEnd(ignored):
493             self.assertEqual(procTrans.pid, None)
494
495         p.transport.closeStdin()
496         return finished.addCallback(afterProcessEnd)
497
498
499     def test_process(self):
500         """
501         Test running a process: check its output, it exitCode, some property of
502         signalProcess.
503         """
504         exe = sys.executable
505         scriptPath = util.sibpath(__file__, "process_tester.py")
506         d = defer.Deferred()
507         p = TestProcessProtocol()
508         p.deferred = d
509         reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=None)
510         def check(ignored):
511             self.assertEquals(p.stages, [1, 2, 3, 4, 5])
512             f = p.reason
513             f.trap(error.ProcessTerminated)
514             self.assertEquals(f.value.exitCode, 23)
515             # would .signal be available on non-posix?
516             # self.assertEquals(f.value.signal, None)
517             self.assertRaises(
518                 error.ProcessExitedAlready, p.transport.signalProcess, 'INT')
519             try:
520                 import process_tester, glob
521                 for f in glob.glob(process_tester.test_file_match):
522                     os.remove(f)
523             except:
524                 pass
525         d.addCallback(check)
526         return d
527
528     def testManyProcesses(self):
529
530         def _check(results, protocols):
531             for p in protocols:
532                 self.assertEquals(p.stages, [1, 2, 3, 4, 5], "[%d] stages = %s" % (id(p.transport), str(p.stages)))
533                 # test status code
534                 f = p.reason
535                 f.trap(error.ProcessTerminated)
536                 self.assertEquals(f.value.exitCode, 23)
537
538         exe = sys.executable
539         scriptPath = util.sibpath(__file__, "process_tester.py")
540         args = [exe, "-u", scriptPath]
541         protocols = []
542         deferreds = []
543
544         for i in xrange(50):
545             p = TestManyProcessProtocol()
546             protocols.append(p)
547             reactor.spawnProcess(p, exe, args, env=None)
548             deferreds.append(p.deferred)
549
550         deferredList = defer.DeferredList(deferreds, consumeErrors=True)
551         deferredList.addCallback(_check, protocols)
552         return deferredList
553
554
555     def test_echo(self):
556         """
557         A spawning a subprocess which echoes its stdin to its stdout via
558         C{reactor.spawnProcess} will result in that echoed output being
559         delivered to outReceived.
560         """
561         finished = defer.Deferred()
562         p = EchoProtocol(finished)
563
564         exe = sys.executable
565         scriptPath = util.sibpath(__file__, "process_echoer.py")
566         reactor.spawnProcess(p, exe, [exe, scriptPath], env=None)
567
568         def asserts(ignored):
569             self.failIf(p.failure, p.failure)
570             self.failUnless(hasattr(p, 'buffer'))
571             self.assertEquals(len(''.join(p.buffer)), len(p.s * p.n))
572
573         def takedownProcess(err):
574             p.transport.closeStdin()
575             return err
576
577         return finished.addCallback(asserts).addErrback(takedownProcess)
578
579
580     def testCommandLine(self):
581         args = [r'a\"b ', r'a\b ', r' a\\"b', r' a\\b', r'"foo bar" "', '\tab', '"\\', 'a"b', "a'b"]
582         pyExe = sys.executable
583         scriptPath = util.sibpath(__file__, "process_cmdline.py")
584         p = Accumulator()
585         d = p.endedDeferred = defer.Deferred()
586         reactor.spawnProcess(p, pyExe, [pyExe, "-u", scriptPath]+args, env=None,
587                              path=None)
588
589         def processEnded(ign):
590             self.assertEquals(p.errF.getvalue(), "")
591             recvdArgs = p.outF.getvalue().splitlines()
592             self.assertEquals(recvdArgs, args)
593         return d.addCallback(processEnded)
594
595
596     def test_wrongArguments(self):
597         """
598         Test invalid arguments to spawnProcess: arguments and environment
599         must only contains string or unicode, and not null bytes.
600         """
601         exe = sys.executable
602         p = protocol.ProcessProtocol()
603
604         badEnvs = [
605             {"foo": 2},
606             {"foo": "egg\0a"},
607             {3: "bar"},
608             {"bar\0foo": "bar"}]
609
610         badArgs = [
611             [exe, 2],
612             "spam",
613             [exe, "foo\0bar"]]
614
615         # Sanity check - this will fail for people who have mucked with
616         # their site configuration in a stupid way, but there's nothing we
617         # can do about that.
618         badUnicode = u'\N{SNOWMAN}'
619         try:
620             badUnicode.encode(sys.getdefaultencoding())
621         except UnicodeEncodeError:
622             # Okay, that unicode doesn't encode, put it in as a bad environment
623             # key.
624             badEnvs.append({badUnicode: 'value for bad unicode key'})
625             badEnvs.append({'key for bad unicode value': badUnicode})
626             badArgs.append([exe, badUnicode])
627         else:
628             # It _did_ encode.  Most likely, Gtk2 is being used and the
629             # default system encoding is UTF-8, which can encode anything.
630             # In any case, if implicit unicode -> str conversion works for
631             # that string, we can't test that TypeError gets raised instead,
632             # so just leave it off.
633             pass
634
635         for env in badEnvs:
636             self.assertRaises(
637                 TypeError,
638                 reactor.spawnProcess, p, exe, [exe, "-c", ""], env=env)
639
640         for args in badArgs:
641             self.assertRaises(
642                 TypeError,
643                 reactor.spawnProcess, p, exe, args, env=None)
644
645
646     # Use upper-case so that the environment key test uses an upper case
647     # name: some versions of Windows only support upper case environment
648     # variable names, and I think Python (as of 2.5) doesn't use the right
649     # syscall for lowercase or mixed case names to work anyway.
650     okayUnicode = u"UNICODE"
651     encodedValue = "UNICODE"
652
653     def _deprecatedUnicodeSupportTest(self, processProtocolClass, argv=[], env={}):
654         """
655         Check that a deprecation warning is emitted when passing unicode to
656         spawnProcess for an argv value or an environment key or value.
657         Check that the warning is of the right type, has the right message,
658         and refers to the correct file.  Unfortunately, don't check that the
659         line number is correct, because that is too hard for me to figure
660         out.
661
662         @param processProtocolClass: A L{UtilityProcessProtocol} subclass
663         which will be instantiated to communicate with the child process.
664
665         @param argv: The argv argument to spawnProcess.
666
667         @param env: The env argument to spawnProcess.
668
669         @return: A Deferred which fires when the test is complete.
670         """
671         # Sanity to check to make sure we can actually encode this unicode
672         # with the default system encoding.  This may be excessively
673         # paranoid. -exarkun
674         self.assertEqual(
675             self.okayUnicode.encode(sys.getdefaultencoding()),
676             self.encodedValue)
677
678         p = self.assertWarns(DeprecationWarning,
679             "Argument strings and environment keys/values passed to "
680             "reactor.spawnProcess should be str, not unicode.", __file__,
681             processProtocolClass.run, reactor, argv, env)
682         return p.getResult()
683
684
685     def test_deprecatedUnicodeArgvSupport(self):
686         """
687         Test that a unicode string passed for an argument value is allowed
688         if it can be encoded with the default system encoding, but that a
689         deprecation warning is emitted.
690         """
691         d = self._deprecatedUnicodeSupportTest(GetArgumentVector, argv=[self.okayUnicode])
692         def gotArgVector(argv):
693             self.assertEqual(argv, ['-c', self.encodedValue])
694         d.addCallback(gotArgVector)
695         return d
696
697
698     def test_deprecatedUnicodeEnvKeySupport(self):
699         """
700         Test that a unicode string passed for the key of the environment
701         dictionary is allowed if it can be encoded with the default system
702         encoding, but that a deprecation warning is emitted.
703         """
704         d = self._deprecatedUnicodeSupportTest(
705             GetEnvironmentDictionary, env={self.okayUnicode: self.encodedValue})
706         def gotEnvironment(environ):
707             self.assertEqual(environ[self.encodedValue], self.encodedValue)
708         d.addCallback(gotEnvironment)
709         return d
710
711
712     def test_deprecatedUnicodeEnvValueSupport(self):
713         """
714         Test that a unicode string passed for the value of the environment
715         dictionary is allowed if it can be encoded with the default system
716         encoding, but that a deprecation warning is emitted.
717         """
718         d = self._deprecatedUnicodeSupportTest(
719             GetEnvironmentDictionary, env={self.encodedValue: self.okayUnicode})
720         def gotEnvironment(environ):
721             # On Windows, the environment contains more things than we
722             # specified, so only make sure that at least the key we wanted
723             # is there, rather than testing the dictionary for exact
724             # equality.
725             self.assertEqual(environ[self.encodedValue], self.encodedValue)
726         d.addCallback(gotEnvironment)
727         return d
728
729
730
731 class TwoProcessProtocol(protocol.ProcessProtocol):
732     num = -1
733     finished = 0
734     def __init__(self):
735         self.deferred = defer.Deferred()
736     def outReceived(self, data):
737         pass
738     def processEnded(self, reason):
739         self.finished = 1
740         self.deferred.callback(None)
741
742 class TestTwoProcessesBase:
743     def setUp(self):
744         self.processes = [None, None]
745         self.pp = [None, None]
746         self.done = 0
747         self.verbose = 0
748
749     def createProcesses(self, usePTY=0):
750         exe = sys.executable
751         scriptPath = util.sibpath(__file__, "process_reader.py")
752         for num in (0,1):
753             self.pp[num] = TwoProcessProtocol()
754             self.pp[num].num = num
755             p = reactor.spawnProcess(self.pp[num],
756                                      exe, [exe, "-u", scriptPath], env=None,
757                                      usePTY=usePTY)
758             self.processes[num] = p
759
760     def close(self, num):
761         if self.verbose: print "closing stdin [%d]" % num
762         p = self.processes[num]
763         pp = self.pp[num]
764         self.failIf(pp.finished, "Process finished too early")
765         p.loseConnection()
766         if self.verbose: print self.pp[0].finished, self.pp[1].finished
767
768     def _onClose(self):
769         return defer.gatherResults([ p.deferred for p in self.pp ])
770
771     def testClose(self):
772         if self.verbose: print "starting processes"
773         self.createProcesses()
774         reactor.callLater(1, self.close, 0)
775         reactor.callLater(2, self.close, 1)
776         return self._onClose()
777
778 class TestTwoProcessesNonPosix(TestTwoProcessesBase, unittest.TestCase):
779     pass
780
781 class TestTwoProcessesPosix(TestTwoProcessesBase, unittest.TestCase):
782     def tearDown(self):
783         for pp, pr in zip(self.pp, self.processes):
784             if not pp.finished:
785                 try:
786                     os.kill(pr.pid, signal.SIGTERM)
787                 except OSError:
788                     # If the test failed the process may already be dead
789                     # The error here is only noise
790                     pass
791         return self._onClose()
792
793     def kill(self, num):
794         if self.verbose: print "kill [%d] with SIGTERM" % num
795         p = self.processes[num]
796         pp = self.pp[num]
797         self.failIf(pp.finished, "Process finished too early")
798         os.kill(p.pid, signal.SIGTERM)
799         if self.verbose: print self.pp[0].finished, self.pp[1].finished
800
801     def testKill(self):
802         if self.verbose: print "starting processes"
803         self.createProcesses(usePTY=0)
804         reactor.callLater(1, self.kill, 0)
805         reactor.callLater(2, self.kill, 1)
806         return self._onClose()
807
808     def testClosePty(self):
809         if self.verbose: print "starting processes"
810         self.createProcesses(usePTY=1)
811         reactor.callLater(1, self.close, 0)
812         reactor.callLater(2, self.close, 1)
813         return self._onClose()
814
815     def testKillPty(self):
816         if self.verbose: print "starting processes"
817         self.createProcesses(usePTY=1)
818         reactor.callLater(1, self.kill, 0)
819         reactor.callLater(2, self.kill, 1)
820         return self._onClose()
821
822 class FDChecker(protocol.ProcessProtocol):
823     state = 0
824     data = ""
825     failed = None
826
827     def __init__(self, d):
828         self.deferred = d
829
830     def fail(self, why):
831         self.failed = why
832         self.deferred.callback(None)
833
834     def connectionMade(self):
835         self.transport.writeToChild(0, "abcd")
836         self.state = 1
837
838     def childDataReceived(self, childFD, data):
839         if self.state == 1:
840             if childFD != 1:
841                 self.fail("read '%s' on fd %d (not 1) during state 1" \
842                           % (childFD, data))
843                 return
844             self.data += data
845             #print "len", len(self.data)
846             if len(self.data) == 6:
847                 if self.data != "righto":
848                     self.fail("got '%s' on fd1, expected 'righto'" \
849                               % self.data)
850                     return
851                 self.data = ""
852                 self.state = 2
853                 #print "state2", self.state
854                 self.transport.writeToChild(3, "efgh")
855                 return
856         if self.state == 2:
857             self.fail("read '%s' on fd %s during state 2" % (childFD, data))
858             return
859         if self.state == 3:
860             if childFD != 1:
861                 self.fail("read '%s' on fd %s (not 1) during state 3" \
862                           % (childFD, data))
863                 return
864             self.data += data
865             if len(self.data) == 6:
866                 if self.data != "closed":
867                     self.fail("got '%s' on fd1, expected 'closed'" \
868                               % self.data)
869                     return
870                 self.state = 4
871             return
872         if self.state == 4:
873             self.fail("read '%s' on fd %s during state 4" % (childFD, data))
874             return
875
876     def childConnectionLost(self, childFD):
877         if self.state == 1:
878             self.fail("got connectionLost(%d) during state 1" % childFD)
879             return
880         if self.state == 2:
881             if childFD != 4:
882                 self.fail("got connectionLost(%d) (not 4) during state 2" \
883                           % childFD)
884                 return
885             self.state = 3
886             self.transport.closeChildFD(5)
887             return
888
889     def processEnded(self, status):
890         rc = status.value.exitCode
891         if self.state != 4:
892             self.fail("processEnded early, rc %d" % rc)
893             return
894         if status.value.signal != None:
895             self.fail("processEnded with signal %s" % status.value.signal)
896             return
897         if rc != 0:
898             self.fail("processEnded with rc %d" % rc)
899             return
900         self.deferred.callback(None)
901
902
903 class FDTest(unittest.TestCase):
904
905     def testFD(self):
906         exe = sys.executable
907         scriptPath = util.sibpath(__file__, "process_fds.py")
908         d = defer.Deferred()
909         p = FDChecker(d)
910         reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=None,
911                              path=None,
912                              childFDs={0:"w", 1:"r", 2:2,
913                                        3:"w", 4:"r", 5:"w"})
914         d.addCallback(lambda x : self.failIf(p.failed, p.failed))
915         return d
916
917     def testLinger(self):
918         # See what happens when all the pipes close before the process
919         # actually stops. This test *requires* SIGCHLD catching to work,
920         # as there is no other way to find out the process is done.
921         exe = sys.executable
922         scriptPath = util.sibpath(__file__, "process_linger.py")
923         p = Accumulator()
924         d = p.endedDeferred = defer.Deferred()
925         reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=None,
926                              path=None,
927                              childFDs={1:"r", 2:2},
928                              )
929         def processEnded(ign):
930             self.failUnlessEqual(p.outF.getvalue(),
931                                  "here is some text\ngoodbye\n")
932         return d.addCallback(processEnded)
933
934
935
936 class Accumulator(protocol.ProcessProtocol):
937     """Accumulate data from a process."""
938
939     closed = 0
940     endedDeferred = None
941
942     def connectionMade(self):
943         self.outF = StringIO.StringIO()
944         self.errF = StringIO.StringIO()
945
946     def outReceived(self, d):
947         self.outF.write(d)
948
949     def errReceived(self, d):
950         self.errF.write(d)
951
952     def outConnectionLost(self):
953         pass
954
955     def errConnectionLost(self):
956         pass
957
958     def processEnded(self, reason):
959         self.closed = 1
960         if self.endedDeferred is not None:
961             d, self.endedDeferred = self.endedDeferred, None
962             d.callback(None)
963
964
965 class PosixProcessBase:
966     """
967     Test running processes.
968     """
969     usePTY = False
970
971     def getCommand(self, commandName):
972         """
973         Return the path of the shell command named C{commandName}, looking at
974         common locations.
975         """
976         if os.path.exists('/bin/%s' % (commandName,)):
977             cmd = '/bin/%s' % (commandName,)
978         elif os.path.exists('/usr/bin/%s' % (commandName,)):
979             cmd = '/usr/bin/%s' % (commandName,)
980         else:
981             raise RuntimeError(
982                 "%s not found in /bin or /usr/bin" % (commandName,))
983         return cmd
984
985     def testNormalTermination(self):
986         cmd = self.getCommand('true')
987
988         d = defer.Deferred()
989         p = TrivialProcessProtocol(d)
990         reactor.spawnProcess(p, cmd, ['true'], env=None,
991                              usePTY=self.usePTY)
992         def check(ignored):
993             p.reason.trap(error.ProcessDone)
994             self.assertEquals(p.reason.value.exitCode, 0)
995             self.assertEquals(p.reason.value.signal, None)
996         d.addCallback(check)
997         return d
998
999
1000     def test_abnormalTermination(self):
1001         """
1002         When a process terminates with a system exit code set to 1,
1003         C{processEnded} is called with a L{error.ProcessTerminated} error,
1004         the C{exitCode} attribute reflecting the system exit code.
1005         """
1006         exe = sys.executable
1007
1008         d = defer.Deferred()
1009         p = TrivialProcessProtocol(d)
1010         reactor.spawnProcess(p, exe, [exe, '-c', 'import sys; sys.exit(1)'],
1011                              env=None, usePTY=self.usePTY)
1012
1013         def check(ignored):
1014             p.reason.trap(error.ProcessTerminated)
1015             self.assertEquals(p.reason.value.exitCode, 1)
1016             self.assertEquals(p.reason.value.signal, None)
1017         d.addCallback(check)
1018         return d
1019
1020
1021     def _testSignal(self, sig):
1022         exe = sys.executable
1023         scriptPath = util.sibpath(__file__, "process_signal.py")
1024         d = defer.Deferred()
1025         p = SignalProtocol(d, sig)
1026         reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=None,
1027                              usePTY=self.usePTY)
1028         return d
1029
1030
1031     def test_signalHUP(self):
1032         """
1033         Sending the SIGHUP signal to a running process interrupts it, and
1034         C{processEnded} is called with a L{error.ProcessTerminated} instance
1035         with the C{exitCode} set to C{None} and the C{signal} attribute set to
1036         C{signal.SIGHUP}. C{os.WTERMSIG} can also be used on the C{status}
1037         attribute to extract the signal value.
1038         """
1039         return self._testSignal('HUP')
1040
1041
1042     def test_signalINT(self):
1043         """
1044         Sending the SIGINT signal to a running process interrupts it, and
1045         C{processEnded} is called with a L{error.ProcessTerminated} instance
1046         with the C{exitCode} set to C{None} and the C{signal} attribute set to
1047         C{signal.SIGINT}. C{os.WTERMSIG} can also be used on the C{status}
1048         attribute to extract the signal value.
1049         """
1050         return self._testSignal('INT')
1051
1052
1053     def test_signalKILL(self):
1054         """
1055         Sending the SIGKILL signal to a running process interrupts it, and
1056         C{processEnded} is called with a L{error.ProcessTerminated} instance
1057         with the C{exitCode} set to C{None} and the C{signal} attribute set to
1058         C{signal.SIGKILL}. C{os.WTERMSIG} can also be used on the C{status}
1059         attribute to extract the signal value.
1060         """
1061         return self._testSignal('KILL')
1062
1063
1064     def test_signalTERM(self):
1065         """
1066         Sending the SIGTERM signal to a running process interrupts it, and
1067         C{processEnded} is called with a L{error.ProcessTerminated} instance
1068         with the C{exitCode} set to C{None} and the C{signal} attribute set to
1069         C{signal.SIGTERM}. C{os.WTERMSIG} can also be used on the C{status}
1070         attribute to extract the signal value.
1071         """
1072         return self._testSignal('TERM')
1073
1074
1075     def test_executionError(self):
1076         """
1077         Raise an error during execvpe to check error management.
1078         """
1079         cmd = self.getCommand('false')
1080
1081         d = defer.Deferred()
1082         p = TrivialProcessProtocol(d)
1083         def buggyexecvpe(command, args, environment):
1084             raise RuntimeError("Ouch")
1085         oldexecvpe = os.execvpe
1086         os.execvpe = buggyexecvpe
1087         try:
1088             reactor.spawnProcess(p, cmd, ['false'], env=None,
1089                                  usePTY=self.usePTY)
1090
1091             def check(ignored):
1092                 errData = "".join(p.errData + p.outData)
1093                 self.assertIn("Upon execvpe", errData)
1094                 self.assertIn("Ouch", errData)
1095             d.addCallback(check)
1096         finally:
1097             os.execvpe = oldexecvpe
1098         return d
1099
1100
1101     def test_errorInProcessEnded(self):
1102         """
1103         The handler which reaps a process is removed when the process is
1104         reaped, even if the protocol's C{processEnded} method raises an
1105         exception.
1106         """
1107         connected = defer.Deferred()
1108         ended = defer.Deferred()
1109
1110         # This script runs until we disconnect its transport.
1111         pythonExecutable = sys.executable
1112         scriptPath = util.sibpath(__file__, "process_twisted.py")
1113
1114         class ErrorInProcessEnded(protocol.ProcessProtocol):
1115             """
1116             A protocol that raises an error in C{processEnded}.
1117             """
1118             def makeConnection(self, transport):
1119                 connected.callback(transport)
1120
1121             def processEnded(self, reason):
1122                 reactor.callLater(0, ended.callback, None)
1123                 raise RuntimeError("Deliberate error")
1124
1125         # Launch the process.
1126         reactor.spawnProcess(
1127             ErrorInProcessEnded(), pythonExecutable,
1128             [pythonExecutable, scriptPath],
1129             env=None, path=None)
1130
1131         pid = []
1132         def cbConnected(transport):
1133             pid.append(transport.pid)
1134             # There's now a reap process handler registered.
1135             self.assertIn(transport.pid, process.reapProcessHandlers)
1136
1137             # Kill the process cleanly, triggering an error in the protocol.
1138             transport.loseConnection()
1139         connected.addCallback(cbConnected)
1140
1141         def checkTerminated(ignored):
1142             # The exception was logged.
1143             excs = self.flushLoggedErrors(RuntimeError)
1144             self.assertEqual(len(excs), 1)
1145             # The process is no longer scheduled for reaping.
1146             self.assertNotIn(pid[0], process.reapProcessHandlers)
1147         ended.addCallback(checkTerminated)
1148
1149         return ended
1150
1151
1152
1153 class MockOS(object):
1154     """
1155     The mock OS: overwrite L{os}, L{fcntl} and {sys} functions with fake ones.
1156
1157     @ivar exited: set to True when C{_exit} is called.
1158     @type exited: C{bool}
1159
1160     @ivar O_RDWR: dumb value faking C{os.O_RDWR}.
1161     @type O_RDWR: C{int}
1162
1163     @ivar O_NOCTTY: dumb value faking C{os.O_NOCTTY}.
1164     @type O_NOCTTY: C{int}
1165
1166     @ivar WNOHANG: dumb value faking C{os.WNOHANG}.
1167     @type WNOHANG: C{int}
1168
1169     @ivar raiseFork: if not C{None}, subsequent calls to fork will raise this
1170         object.
1171     @type raiseFork: C{NoneType} or C{Exception}
1172
1173     @ivar raiseExec: if set, subsequent calls to execvpe will raise an error.
1174     @type raiseExec: C{bool}
1175
1176     @ivar fdio: fake file object returned by calls to fdopen.
1177     @type fdio: C{StringIO.StringIO}
1178
1179     @ivar actions: hold names of some actions executed by the object, in order
1180         of execution.
1181
1182     @type actions: C{list} of C{str}
1183
1184     @ivar closed: keep track of the file descriptor closed.
1185     @param closed: C{list} of C{int}
1186
1187     @ivar child: whether fork return for the child or the parent.
1188     @type child: C{bool}
1189
1190     @ivar pipeCount: count the number of time that C{os.pipe} has been called.
1191     @type pipeCount: C{int}
1192
1193     @ivar raiseWaitPid: if set, subsequent calls to waitpid will raise an
1194         the error specified.
1195     @type raiseWaitPid: C{None} or a class
1196
1197     @ivar waitChild: if set, subsequent calls to waitpid will return it.
1198     @type waitChild: C{None} or a tuple
1199
1200     @ivar euid: the uid returned by the fake C{os.geteuid}
1201     @type euid: C{int}
1202
1203     @ivar egid: the gid returned by the fake C{os.getegid}
1204     @type egid: C{int}
1205
1206     @ivar seteuidCalls: stored results of C{os.seteuid} calls.
1207     @type seteuidCalls: C{list}
1208
1209     @ivar setegidCalls: stored results of C{os.setegid} calls.
1210     @type setegidCalls: C{list}
1211
1212     @ivar path: the path returned by C{os.path.expanduser}.
1213     @type path: C{str}
1214     """
1215     exited = False
1216     raiseExec = False
1217     fdio = None
1218     child = True
1219     raiseWaitPid = None
1220     raiseFork = None
1221     waitChild = None
1222     euid = 0
1223     egid = 0
1224     path = None
1225
1226     def __init__(self):
1227         """
1228         Initialize data structures.
1229         """
1230         self.actions = []
1231         self.closed = []
1232         self.pipeCount = 0
1233         self.O_RDWR = -1
1234         self.O_NOCTTY = -2
1235         self.WNOHANG = -4
1236         self.WEXITSTATUS = lambda x: 0
1237         self.WIFEXITED = lambda x: 1
1238         self.seteuidCalls = []
1239         self.setegidCalls = []
1240
1241
1242     def open(self, dev, flags):
1243         """
1244         Fake C{os.open}. Return a non fd number to be sure it's not used
1245         elsewhere.
1246         """
1247         return -3
1248
1249
1250     def fdopen(self, fd, flag):
1251         """
1252         Fake C{os.fdopen}. Return a StringIO object whose content can be tested
1253         later via C{self.fdio}.
1254         """
1255         self.fdio = StringIO.StringIO()
1256         return self.fdio
1257
1258
1259     def setsid(self):
1260         """
1261         Fake C{os.setsid}. Do nothing.
1262         """
1263
1264
1265     def fork(self):
1266         """
1267         Fake C{os.fork}. Save the action in C{self.actions}, and return 0 if
1268         C{self.child} is set, or a dumb number.
1269         """
1270         self.actions.append(('fork', gc.isenabled()))
1271         if self.raiseFork is not None:
1272             raise self.raiseFork
1273         elif self.child:
1274             # Child result is 0
1275             return 0
1276         else:
1277             return 21
1278
1279
1280     def close(self, fd):
1281         """
1282         Fake C{os.close}, saving the closed fd in C{self.closed}.
1283         """
1284         self.closed.append(fd)
1285
1286
1287     def dup2(self, fd1, fd2):
1288         """
1289         Fake C{os.dup2}. Do nothing.
1290         """
1291
1292
1293     def write(self, fd, data):
1294         """
1295         Fake C{os.write}. Do nothing.
1296         """
1297
1298
1299     def execvpe(self, command, args, env):
1300         """
1301         Fake C{os.execvpe}. Save the action, and raise an error if
1302         C{self.raiseExec} is set.
1303         """
1304         self.actions.append('exec')
1305         if self.raiseExec:
1306             raise RuntimeError("Bar")
1307
1308
1309     def pipe(self):
1310         """
1311         Fake C{os.pipe}. Return non fd numbers to be sure it's not used
1312         elsewhere, and increment C{self.pipeCount}. This is used to uniquify
1313         the result.
1314         """
1315         self.pipeCount += 1
1316         return - 2 * self.pipeCount + 1,  - 2 * self.pipeCount
1317
1318
1319     def ttyname(self, fd):
1320         """
1321         Fake C{os.ttyname}. Return a dumb string.
1322         """
1323         return "foo"
1324
1325
1326     def _exit(self, code):
1327         """
1328         Fake C{os._exit}. Save the action, set the C{self.exited} flag, and
1329         raise C{SystemError}.
1330         """
1331         self.actions.append('exit')
1332         self.exited = True
1333         # Don't forget to raise an error, or you'll end up in parent
1334         # code path.
1335         raise SystemError()
1336
1337
1338     def ioctl(self, fd, flags, arg):
1339         """
1340         Override C{fcntl.ioctl}. Do nothing.
1341         """
1342
1343
1344     def setNonBlocking(self, fd):
1345         """
1346         Override C{fdesc.setNonBlocking}. Do nothing.
1347         """
1348
1349
1350     def waitpid(self, pid, options):
1351         """
1352         Override C{os.waitpid}. Return values meaning that the child process
1353         has exited, save executed action.
1354         """
1355         self.actions.append('waitpid')
1356         if self.raiseWaitPid is not None:
1357             raise self.raiseWaitPid
1358         if self.waitChild is not None:
1359             return self.waitChild
1360         return 1, 0
1361
1362
1363     def settrace(self, arg):
1364         """
1365         Override C{sys.settrace} to keep coverage working.
1366         """
1367
1368
1369     def getgid(self):
1370         """
1371         Override C{os.getgid}. Return a dumb number.
1372         """
1373         return 1235
1374
1375
1376     def getuid(self):
1377         """
1378         Override C{os.getuid}. Return a dumb number.
1379         """
1380         return 1237
1381
1382
1383     def setuid(self, val):
1384         """
1385         Override C{os.setuid}. Do nothing.
1386         """
1387         self.actions.append(('setuid', val))
1388
1389
1390     def setgid(self, val):
1391         """
1392         Override C{os.setgid}. Do nothing.
1393         """
1394         self.actions.append(('setgid', val))
1395
1396
1397     def setregid(self, val1, val2):
1398         """
1399         Override C{os.setregid}. Do nothing.
1400         """
1401         self.actions.append(('setregid', val1, val2))
1402
1403
1404     def setreuid(self, val1, val2):
1405         """
1406         Override C{os.setreuid}.  Save the action.
1407         """
1408         self.actions.append(('setreuid', val1, val2))
1409
1410
1411     def switchUID(self, uid, gid):
1412         """
1413         Override C{util.switchuid}. Save the action.
1414         """
1415         self.actions.append(('switchuid', uid, gid))
1416
1417
1418     def openpty(self):
1419         """
1420         Override C{pty.openpty}, returning fake file descriptors.
1421         """
1422         return -12, -13
1423
1424
1425     def geteuid(self):
1426         """
1427         Mock C{os.geteuid}, returning C{self.euid} instead.
1428         """
1429         return self.euid
1430
1431
1432     def getegid(self):
1433         """
1434         Mock C{os.getegid}, returning C{self.egid} instead.
1435         """
1436         return self.egid
1437
1438
1439     def seteuid(self, egid):
1440         """
1441         Mock C{os.seteuid}, store result.
1442         """
1443         self.seteuidCalls.append(egid)
1444
1445
1446     def setegid(self, egid):
1447         """
1448         Mock C{os.setegid}, store result.
1449         """
1450         self.setegidCalls.append(egid)
1451
1452
1453     def expanduser(self, path):
1454         """
1455         Mock C{os.path.expanduser}.
1456         """
1457         return self.path
1458
1459
1460     def getpwnam(self, user):
1461         """
1462         Mock C{pwd.getpwnam}.
1463         """
1464         return 0, 0, 1, 2
1465
1466
1467
1468 if process is not None:
1469     class DumbProcessWriter(process.ProcessWriter):
1470         """
1471         A fake L{process.ProcessWriter} used for tests.
1472         """
1473
1474         def startReading(self):
1475             """
1476             Here's the faking: don't do anything here.
1477             """
1478
1479
1480
1481     class DumbProcessReader(process.ProcessReader):
1482         """
1483         A fake L{process.ProcessReader} used for tests.
1484         """
1485
1486         def startReading(self):
1487             """
1488             Here's the faking: don't do anything here.
1489             """
1490
1491
1492
1493     class DumbPTYProcess(process.PTYProcess):
1494         """
1495         A fake L{process.PTYProcess} used for tests.
1496         """
1497
1498         def startReading(self):
1499             """
1500             Here's the faking: don't do anything here.
1501             """
1502
1503
1504
1505 class MockProcessTestCase(unittest.TestCase):
1506     """
1507     Mock a process runner to test forked child code path.
1508     """
1509     if process is None:
1510         skip = "twisted.internet.process is never used on Windows"
1511
1512     def setUp(self):
1513         """
1514         Replace L{process} os, fcntl, sys, switchUID, fdesc and pty modules
1515         with the mock class L{MockOS}.
1516         """
1517         if gc.isenabled():
1518             self.addCleanup(gc.enable)
1519         else:
1520             self.addCleanup(gc.disable)
1521         self.mockos = MockOS()
1522         self.mockos.euid = 1236
1523         self.mockos.egid = 1234
1524         self.patch(process, "os", self.mockos)
1525         self.patch(process, "fcntl", self.mockos)
1526         self.patch(process, "sys", self.mockos)
1527         self.patch(process, "switchUID", self.mockos.switchUID)
1528         self.patch(process, "fdesc", self.mockos)
1529         self.patch(process.Process, "processReaderFactory", DumbProcessReader)
1530         self.patch(process.Process, "processWriterFactory", DumbProcessWriter)
1531         self.patch(process, "pty", self.mockos)
1532
1533
1534     def tearDown(self):
1535         """
1536         Reset processes registered for reap.
1537         """
1538         process.reapProcessHandlers = {}
1539
1540
1541     def test_mockFork(self):
1542         """
1543         Test a classic spawnProcess. Check the path of the client code:
1544         fork, exec, exit.
1545         """
1546         gc.enable()
1547
1548         cmd = '/mock/ouch'
1549
1550         d = defer.Deferred()
1551         p = TrivialProcessProtocol(d)
1552         try:
1553             reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1554                                  usePTY=False)
1555         except SystemError:
1556             self.assert_(self.mockos.exited)
1557             self.assertEquals(
1558                 self.mockos.actions, [("fork", False), "exec", "exit"])
1559         else:
1560             self.fail("Should not be here")
1561
1562         # It should leave the garbage collector disabled.
1563         self.assertFalse(gc.isenabled())
1564
1565
1566     def _mockForkInParentTest(self):
1567         """
1568         Assert that in the main process, spawnProcess disables the garbage
1569         collector, calls fork, closes the pipe file descriptors it created for
1570         the child process, and calls waitpid.
1571         """
1572         self.mockos.child = False
1573         cmd = '/mock/ouch'
1574
1575         d = defer.Deferred()
1576         p = TrivialProcessProtocol(d)
1577         reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1578                              usePTY=False)
1579         # It should close the first read pipe, and the 2 last write pipes
1580         self.assertEqual(set(self.mockos.closed), set([-1, -4, -6]))
1581         self.assertEquals(self.mockos.actions, [("fork", False), "waitpid"])
1582
1583
1584     def test_mockForkInParentGarbageCollectorEnabled(self):
1585         """
1586         The garbage collector should be enabled when L{reactor.spawnProcess}
1587         returns if it was initially enabled.
1588
1589         @see L{_mockForkInParentTest}
1590         """
1591         gc.enable()
1592         self._mockForkInParentTest()
1593         self.assertTrue(gc.isenabled())
1594
1595
1596     def test_mockForkInParentGarbageCollectorDisabled(self):
1597         """
1598         The garbage collector should be disabled when L{reactor.spawnProcess}
1599         returns if it was initially disabled.
1600
1601         @see L{_mockForkInParentTest}
1602         """
1603         gc.disable()
1604         self._mockForkInParentTest()
1605         self.assertFalse(gc.isenabled())
1606
1607
1608     def test_mockForkTTY(self):
1609         """
1610         Test a TTY spawnProcess: check the path of the client code:
1611         fork, exec, exit.
1612         """
1613         cmd = '/mock/ouch'
1614
1615         d = defer.Deferred()
1616         p = TrivialProcessProtocol(d)
1617         try:
1618             reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1619                                  usePTY=True)
1620         except SystemError:
1621             self.assert_(self.mockos.exited)
1622             self.assertEquals(
1623                 self.mockos.actions, [("fork", False), "exec", "exit"])
1624         else:
1625             self.fail("Should not be here")
1626
1627
1628     def _mockWithForkError(self):
1629         """
1630         Assert that if the fork call fails, no other process setup calls are
1631         made and that spawnProcess raises the exception fork raised.
1632         """
1633         self.mockos.raiseFork = OSError(errno.EAGAIN, None)
1634         protocol = TrivialProcessProtocol(None)
1635         self.assertRaises(OSError, reactor.spawnProcess, protocol, None)
1636         self.assertEqual(self.mockos.actions, [("fork", False)])
1637
1638
1639     def test_mockWithForkErrorGarbageCollectorEnabled(self):
1640         """
1641         The garbage collector should be enabled when L{reactor.spawnProcess}
1642         raises because L{os.fork} raised, if it was initially enabled.
1643         """
1644         gc.enable()
1645         self._mockWithForkError()
1646         self.assertTrue(gc.isenabled())
1647
1648
1649     def test_mockWithForkErrorGarbageCollectorDisabled(self):
1650         """
1651         The garbage collector should be disabled when
1652         L{reactor.spawnProcess} raises because L{os.fork} raised, if it was
1653         initially disabled.
1654         """
1655         gc.disable()
1656         self._mockWithForkError()
1657         self.assertFalse(gc.isenabled())
1658
1659
1660     def test_mockForkErrorCloseFDs(self):
1661         """
1662         When C{os.fork} raises an exception, the file descriptors created
1663         before are closed and don't leak.
1664         """
1665         self._mockWithForkError()
1666         self.assertEqual(set(self.mockos.closed), set([-1, -4, -6, -2, -3, -5]))
1667
1668
1669     def test_mockForkErrorGivenFDs(self):
1670         """
1671         When C{os.forks} raises an exception and that file descriptors have
1672         been specified with the C{childFDs} arguments of
1673         L{reactor.spawnProcess}, they are not closed.
1674         """
1675         self.mockos.raiseFork = OSError(errno.EAGAIN, None)
1676         protocol = TrivialProcessProtocol(None)
1677         self.assertRaises(OSError, reactor.spawnProcess, protocol, None,
1678             childFDs={0: -10, 1: -11, 2: -13})
1679         self.assertEqual(self.mockos.actions, [("fork", False)])
1680         self.assertEqual(self.mockos.closed, [])
1681
1682         # We can also put "r" or "w" to let twisted create the pipes
1683         self.assertRaises(OSError, reactor.spawnProcess, protocol, None,
1684             childFDs={0: "r", 1: -11, 2: -13})
1685         self.assertEqual(set(self.mockos.closed), set([-1, -2]))
1686
1687
1688     def test_mockForkErrorClosePTY(self):
1689         """
1690         When C{os.fork} raises an exception, the file descriptors created by
1691         C{pty.openpty} are closed and don't leak, when C{usePTY} is set to
1692         C{True}.
1693         """
1694         self.mockos.raiseFork = OSError(errno.EAGAIN, None)
1695         protocol = TrivialProcessProtocol(None)
1696         self.assertRaises(OSError, reactor.spawnProcess, protocol, None,
1697                           usePTY=True)
1698         self.assertEqual(self.mockos.actions, [("fork", False)])
1699         self.assertEqual(set(self.mockos.closed), set([-12, -13]))
1700
1701
1702     def test_mockForkErrorPTYGivenFDs(self):
1703         """
1704         If a tuple is passed to C{usePTY} to specify slave and master file
1705         descriptors and that C{os.fork} raises an exception, these file
1706         descriptors aren't closed.
1707         """
1708         self.mockos.raiseFork = OSError(errno.EAGAIN, None)
1709         protocol = TrivialProcessProtocol(None)
1710         self.assertRaises(OSError, reactor.spawnProcess, protocol, None,
1711                           usePTY=(-20, -21, 'foo'))
1712         self.assertEqual(self.mockos.actions, [("fork", False)])
1713         self.assertEqual(self.mockos.closed, [])
1714
1715
1716     def test_mockWithExecError(self):
1717         """
1718         Spawn a process but simulate an error during execution in the client
1719         path: C{os.execvpe} raises an error. It should close all the standard
1720         fds, try to print the error encountered, and exit cleanly.
1721         """
1722         cmd = '/mock/ouch'
1723
1724         d = defer.Deferred()
1725         p = TrivialProcessProtocol(d)
1726         self.mockos.raiseExec = True
1727         try:
1728             reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1729                                  usePTY=False)
1730         except SystemError:
1731             self.assert_(self.mockos.exited)
1732             self.assertEquals(
1733                 self.mockos.actions, [("fork", False), "exec", "exit"])
1734             # Check that fd have been closed
1735             self.assertIn(0, self.mockos.closed)
1736             self.assertIn(1, self.mockos.closed)
1737             self.assertIn(2, self.mockos.closed)
1738             # Check content of traceback
1739             self.assertIn("RuntimeError: Bar", self.mockos.fdio.getvalue())
1740         else:
1741             self.fail("Should not be here")
1742
1743
1744     def test_mockSetUid(self):
1745         """
1746         Try creating a process with setting its uid: it's almost the same path
1747         as the standard path, but with a C{switchUID} call before the exec.
1748         """
1749         cmd = '/mock/ouch'
1750
1751         d = defer.Deferred()
1752         p = TrivialProcessProtocol(d)
1753         try:
1754             reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1755                                  usePTY=False, uid=8080)
1756         except SystemError:
1757             self.assert_(self.mockos.exited)
1758             self.assertEquals(self.mockos.actions,
1759                 [('setuid', 0), ('setgid', 0), ('fork', False),
1760                   ('switchuid', 8080, 1234), 'exec', 'exit'])
1761         else:
1762             self.fail("Should not be here")
1763
1764
1765     def test_mockSetUidInParent(self):
1766         """
1767         Try creating a process with setting its uid, in the parent path: it
1768         should switch to root before fork, then restore initial uid/gids.
1769         """
1770         self.mockos.child = False
1771         cmd = '/mock/ouch'
1772
1773         d = defer.Deferred()
1774         p = TrivialProcessProtocol(d)
1775         reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1776                              usePTY=False, uid=8080)
1777         self.assertEquals(self.mockos.actions,
1778             [('setuid', 0), ('setgid', 0), ('fork', False),
1779              ('setregid', 1235, 1234), ('setreuid', 1237, 1236), 'waitpid'])
1780
1781
1782     def test_mockPTYSetUid(self):
1783         """
1784         Try creating a PTY process with setting its uid: it's almost the same
1785         path as the standard path, but with a C{switchUID} call before the
1786         exec.
1787         """
1788         cmd = '/mock/ouch'
1789
1790         d = defer.Deferred()
1791         p = TrivialProcessProtocol(d)
1792         try:
1793             reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1794                                  usePTY=True, uid=8081)
1795         except SystemError:
1796             self.assert_(self.mockos.exited)
1797             self.assertEquals(self.mockos.actions,
1798                 [('setuid', 0), ('setgid', 0), ('fork', False),
1799                   ('switchuid', 8081, 1234), 'exec', 'exit'])
1800         else:
1801             self.fail("Should not be here")
1802
1803
1804     def test_mockPTYSetUidInParent(self):
1805         """
1806         Try creating a PTY process with setting its uid, in the parent path: it
1807         should switch to root before fork, then restore initial uid/gids.
1808         """
1809         self.mockos.child = False
1810         cmd = '/mock/ouch'
1811
1812         d = defer.Deferred()
1813         p = TrivialProcessProtocol(d)
1814         oldPTYProcess = process.PTYProcess
1815         try:
1816             process.PTYProcess = DumbPTYProcess
1817             reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1818                                  usePTY=True, uid=8080)
1819         finally:
1820             process.PTYProcess = oldPTYProcess
1821         self.assertEquals(self.mockos.actions,
1822             [('setuid', 0), ('setgid', 0), ('fork', False),
1823              ('setregid', 1235, 1234), ('setreuid', 1237, 1236), 'waitpid'])
1824
1825
1826     def test_mockWithWaitError(self):
1827         """
1828         Test that reapProcess logs errors raised.
1829         """
1830         self.mockos.child = False
1831         cmd = '/mock/ouch'
1832         self.mockos.waitChild = (0, 0)
1833
1834         d = defer.Deferred()
1835         p = TrivialProcessProtocol(d)
1836         proc = reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1837                              usePTY=False)
1838         self.assertEquals(self.mockos.actions, [("fork", False), "waitpid"])
1839
1840         self.mockos.raiseWaitPid = OSError()
1841         proc.reapProcess()
1842         errors = self.flushLoggedErrors()
1843         self.assertEquals(len(errors), 1)
1844         errors[0].trap(OSError)
1845
1846
1847     def test_mockErrorECHILDInReapProcess(self):
1848         """
1849         Test that reapProcess doesn't log anything when waitpid raises a
1850         C{OSError} with errno C{ECHILD}.
1851         """
1852         self.mockos.child = False
1853         cmd = '/mock/ouch'
1854         self.mockos.waitChild = (0, 0)
1855
1856         d = defer.Deferred()
1857         p = TrivialProcessProtocol(d)
1858         proc = reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1859                                     usePTY=False)
1860         self.assertEquals(self.mockos.actions, [("fork", False), "waitpid"])
1861
1862         self.mockos.raiseWaitPid = OSError()
1863         self.mockos.raiseWaitPid.errno = errno.ECHILD
1864         # This should not produce any errors
1865         proc.reapProcess()
1866
1867
1868     def test_mockErrorInPipe(self):
1869         """
1870         If C{os.pipe} raises an exception after some pipes where created, the
1871         created pipes are closed and don't leak.
1872         """
1873         pipes = [-1, -2, -3, -4]
1874         def pipe():
1875             try:
1876                 return pipes.pop(0), pipes.pop(0)
1877             except IndexError:
1878                 raise OSError()
1879         self.mockos.pipe = pipe
1880         protocol = TrivialProcessProtocol(None)
1881         self.assertRaises(OSError, reactor.spawnProcess, protocol, None)
1882         self.assertEqual(self.mockos.actions, [])
1883         self.assertEqual(set(self.mockos.closed), set([-4, -3, -2, -1]))
1884
1885
1886     def test_mockErrorInForkRestoreUID(self):
1887         """
1888         If C{os.fork} raises an exception and a UID change has been made, the
1889         previous UID and GID are restored.
1890         """
1891         self.mockos.raiseFork = OSError(errno.EAGAIN, None)
1892         protocol = TrivialProcessProtocol(None)
1893         self.assertRaises(OSError, reactor.spawnProcess, protocol, None,
1894                           uid=8080)
1895         self.assertEqual(self.mockos.actions,
1896             [('setuid', 0), ('setgid', 0), ("fork", False),
1897              ('setregid', 1235, 1234), ('setreuid', 1237, 1236)])
1898
1899
1900
1901 class PosixProcessTestCase(unittest.TestCase, PosixProcessBase):
1902     # add three non-pty test cases
1903
1904     def testStderr(self):
1905         # we assume there is no file named ZZXXX..., both in . and in /tmp
1906         cmd = self.getCommand('ls')
1907
1908         p = Accumulator()
1909         d = p.endedDeferred = defer.Deferred()
1910         reactor.spawnProcess(p, cmd,
1911                              [cmd,
1912                               "ZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"],
1913                              env=None, path="/tmp",
1914                              usePTY=self.usePTY)
1915
1916         def processEnded(ign):
1917             self.assertEquals(lsOut, p.errF.getvalue())
1918         return d.addCallback(processEnded)
1919
1920     def testProcess(self):
1921         cmd = self.getCommand('gzip')
1922         s = "there's no place like home!\n" * 3
1923         p = Accumulator()
1924         d = p.endedDeferred = defer.Deferred()
1925         reactor.spawnProcess(p, cmd, [cmd, "-c"], env=None, path="/tmp",
1926                              usePTY=self.usePTY)
1927         p.transport.write(s)
1928         p.transport.closeStdin()
1929
1930         def processEnded(ign):
1931             f = p.outF
1932             f.seek(0, 0)
1933             gf = gzip.GzipFile(fileobj=f)
1934             self.assertEquals(gf.read(), s)
1935         return d.addCallback(processEnded)
1936
1937
1938
1939 class PosixProcessTestCasePTY(unittest.TestCase, PosixProcessBase):
1940     """
1941     Just like PosixProcessTestCase, but use ptys instead of pipes.
1942     """
1943     usePTY = True
1944     # PTYs only offer one input and one output. What still makes sense?
1945     # testNormalTermination
1946     # test_abnormalTermination
1947     # testSignal
1948     # testProcess, but not without p.transport.closeStdin
1949     #  might be solveable: TODO: add test if so
1950
1951     def testOpeningTTY(self):
1952         exe = sys.executable
1953         scriptPath = util.sibpath(__file__, "process_tty.py")
1954         p = Accumulator()
1955         d = p.endedDeferred = defer.Deferred()
1956         reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=None,
1957                             path=None, usePTY=self.usePTY)
1958         p.transport.write("hello world!\n")
1959
1960         def processEnded(ign):
1961             self.assertRaises(
1962                 error.ProcessExitedAlready, p.transport.signalProcess, 'HUP')
1963             self.assertEquals(
1964                 p.outF.getvalue(),
1965                 "hello world!\r\nhello world!\r\n",
1966                 "Error message from process_tty follows:\n\n%s\n\n" % p.outF.getvalue())
1967         return d.addCallback(processEnded)
1968
1969
1970     def testBadArgs(self):
1971         pyExe = sys.executable
1972         pyArgs = [pyExe, "-u", "-c", "print 'hello'"]
1973         p = Accumulator()
1974         self.assertRaises(ValueError, reactor.spawnProcess, p, pyExe, pyArgs,
1975             usePTY=1, childFDs={1:'r'})
1976
1977
1978
1979 class Win32SignalProtocol(SignalProtocol):
1980     """
1981     A win32-specific process protocol that handles C{processEnded}
1982     differently: processes should exit with exit code 1.
1983     """
1984
1985     def processEnded(self, reason):
1986         """
1987         Callback C{self.deferred} with C{None} if C{reason} is a
1988         L{error.ProcessTerminated} failure with C{exitCode} set to 1.
1989         Otherwise, errback with a C{ValueError} describing the problem.
1990         """
1991         if not reason.check(error.ProcessTerminated):
1992             return self.deferred.errback(
1993                 ValueError("wrong termination: %s" % (reason,)))
1994         v = reason.value
1995         if v.exitCode != 1:
1996             return self.deferred.errback(
1997                 ValueError("Wrong exit code: %s" % (reason.exitCode,)))
1998         self.deferred.callback(None)
1999
2000
2001
2002 class Win32ProcessTestCase(unittest.TestCase):
2003     """
2004     Test process programs that are packaged with twisted.
2005     """
2006
2007     def testStdinReader(self):
2008         pyExe = sys.executable
2009         scriptPath = util.sibpath(__file__, "process_stdinreader.py")
2010         p = Accumulator()
2011         d = p.endedDeferred = defer.Deferred()
2012         reactor.spawnProcess(p, pyExe, [pyExe, "-u", scriptPath], env=None,
2013                              path=None)
2014         p.transport.write("hello, world")
2015         p.transport.closeStdin()
2016
2017         def processEnded(ign):
2018             self.assertEquals(p.errF.getvalue(), "err\nerr\n")
2019             self.assertEquals(p.outF.getvalue(), "out\nhello, world\nout\n")
2020         return d.addCallback(processEnded)
2021
2022
2023     def testBadArgs(self):
2024         pyExe = sys.executable
2025         pyArgs = [pyExe, "-u", "-c", "print 'hello'"]
2026         p = Accumulator()
2027         self.assertRaises(ValueError,
2028             reactor.spawnProcess, p, pyExe, pyArgs, uid=1)
2029         self.assertRaises(ValueError,
2030             reactor.spawnProcess, p, pyExe, pyArgs, gid=1)
2031         self.assertRaises(ValueError,
2032             reactor.spawnProcess, p, pyExe, pyArgs, usePTY=1)
2033         self.assertRaises(ValueError,
2034             reactor.spawnProcess, p, pyExe, pyArgs, childFDs={1:'r'})
2035
2036
2037     def _testSignal(self, sig):
2038         exe = sys.executable
2039         scriptPath = util.sibpath(__file__, "process_signal.py")
2040         d = defer.Deferred()
2041         p = Win32SignalProtocol(d, sig)
2042         reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=None)
2043         return d
2044
2045
2046     def test_signalTERM(self):
2047         """
2048         Sending the SIGTERM signal terminates a created process, and
2049         C{processEnded} is called with a L{error.ProcessTerminated} instance
2050         with the C{exitCode} attribute set to 1.
2051         """
2052         return self._testSignal('TERM')
2053
2054
2055     def test_signalINT(self):
2056         """
2057         Sending the SIGINT signal terminates a created process, and
2058         C{processEnded} is called with a L{error.ProcessTerminated} instance
2059         with the C{exitCode} attribute set to 1.
2060         """
2061         return self._testSignal('INT')
2062
2063
2064     def test_signalKILL(self):
2065         """
2066         Sending the SIGKILL signal terminates a created process, and
2067         C{processEnded} is called with a L{error.ProcessTerminated} instance
2068         with the C{exitCode} attribute set to 1.
2069         """
2070         return self._testSignal('KILL')
2071
2072
2073
2074 class Dumbwin32procPidTest(unittest.TestCase):
2075     """
2076     Simple test for the pid attribute of Process on win32.
2077     """
2078
2079     def test_pid(self):
2080         """
2081         Launch process with mock win32process. The only mock aspect of this
2082         module is that the pid of the process created will always be 42.
2083         """
2084         from twisted.internet import _dumbwin32proc
2085         from twisted.test import mock_win32process
2086         self.patch(_dumbwin32proc, "win32process", mock_win32process)
2087         exe = sys.executable
2088         scriptPath = util.sibpath(__file__, "process_cmdline.py")
2089
2090         d = defer.Deferred()
2091         processProto = TrivialProcessProtocol(d)
2092         comspec = str(os.environ["COMSPEC"])
2093         cmd = [comspec, "/c", exe, scriptPath]
2094
2095         p = _dumbwin32proc.Process(reactor,
2096                                   processProto,
2097                                   None,
2098                                   cmd,
2099                                   {},
2100                                   None)
2101         self.assertEquals(42, p.pid)
2102         self.assertEquals("<Process pid=42>", repr(p))
2103
2104         def pidCompleteCb(result):
2105             self.assertEquals(None, p.pid)
2106         return d.addCallback(pidCompleteCb)
2107
2108
2109
2110 class UtilTestCase(unittest.TestCase):
2111     """
2112     Tests for process-related helper functions (currently only
2113     L{procutils.which}.
2114     """
2115     def setUp(self):
2116         """
2117         Create several directories and files, some of which are executable
2118         and some of which are not.  Save the current PATH setting.
2119         """
2120         j = os.path.join
2121
2122         base = self.mktemp()
2123
2124         self.foo = j(base, "foo")
2125         self.baz = j(base, "baz")
2126         self.foobar = j(self.foo, "bar")
2127         self.foobaz = j(self.foo, "baz")
2128         self.bazfoo = j(self.baz, "foo")
2129         self.bazbar = j(self.baz, "bar")
2130
2131         for d in self.foobar, self.foobaz, self.bazfoo, self.bazbar:
2132             os.makedirs(d)
2133
2134         for name, mode in [(j(self.foobaz, "executable"), 0700),
2135                            (j(self.foo, "executable"), 0700),
2136                            (j(self.bazfoo, "executable"), 0700),
2137                            (j(self.bazfoo, "executable.bin"), 0700),
2138                            (j(self.bazbar, "executable"), 0)]:
2139             f = file(name, "w")
2140             f.close()
2141             os.chmod(name, mode)
2142
2143         self.oldPath = os.environ.get('PATH', None)
2144         os.environ['PATH'] = os.pathsep.join((
2145             self.foobar, self.foobaz, self.bazfoo, self.bazbar))
2146
2147
2148     def tearDown(self):
2149         """
2150         Restore the saved PATH setting, and set all created files readable
2151         again so that they can be deleted easily.
2152         """
2153         os.chmod(os.path.join(self.bazbar, "executable"), stat.S_IWUSR)
2154         if self.oldPath is None:
2155             try:
2156                 del os.environ['PATH']
2157             except KeyError:
2158                 pass
2159         else:
2160             os.environ['PATH'] = self.oldPath
2161
2162
2163     def test_whichWithoutPATH(self):
2164         """
2165         Test that if C{os.environ} does not have a C{'PATH'} key,
2166         L{procutils.which} returns an empty list.
2167         """
2168         del os.environ['PATH']
2169         self.assertEqual(procutils.which("executable"), [])
2170
2171
2172     def testWhich(self):
2173         j = os.path.join
2174         paths = procutils.which("executable")
2175         expectedPaths = [j(self.foobaz, "executable"),
2176                          j(self.bazfoo, "executable")]
2177         if runtime.platform.isWindows():
2178             expectedPaths.append(j(self.bazbar, "executable"))
2179         self.assertEquals(paths, expectedPaths)
2180
2181
2182     def testWhichPathExt(self):
2183         j = os.path.join
2184         old = os.environ.get('PATHEXT', None)
2185         os.environ['PATHEXT'] = os.pathsep.join(('.bin', '.exe', '.sh'))
2186         try:
2187             paths = procutils.which("executable")
2188         finally:
2189             if old is None:
2190                 del os.environ['PATHEXT']
2191             else:
2192                 os.environ['PATHEXT'] = old
2193         expectedPaths = [j(self.foobaz, "executable"),
2194                          j(self.bazfoo, "executable"),
2195                          j(self.bazfoo, "executable.bin")]
2196         if runtime.platform.isWindows():
2197             expectedPaths.append(j(self.bazbar, "executable"))
2198         self.assertEquals(paths, expectedPaths)
2199
2200
2201
2202 class ClosingPipesProcessProtocol(protocol.ProcessProtocol):
2203     output = ''
2204     errput = ''
2205
2206     def __init__(self, outOrErr):
2207         self.deferred = defer.Deferred()
2208         self.outOrErr = outOrErr
2209
2210     def processEnded(self, reason):
2211         self.deferred.callback(reason)
2212
2213     def outReceived(self, data):
2214         self.output += data
2215
2216     def errReceived(self, data):
2217         self.errput += data
2218
2219
2220 class ClosingPipes(unittest.TestCase):
2221
2222     def doit(self, fd):
2223         p = ClosingPipesProcessProtocol(True)
2224         p.deferred.addCallbacks(
2225             callback=lambda _: self.fail("I wanted an errback."),
2226             errback=self._endProcess, errbackArgs=(p,))
2227         reactor.spawnProcess(p, sys.executable,
2228                              [sys.executable, '-u', '-c',
2229                               r'raw_input(); import sys, os; os.write(%d, "foo\n"); sys.exit(42)' % fd],
2230                              env=None)
2231         p.transport.write('go\n')
2232
2233         if fd == 1:
2234             p.transport.closeStdout()
2235         elif fd == 2:
2236             p.transport.closeStderr()
2237         else:
2238             raise RuntimeError
2239
2240         # make the buggy case not hang
2241         p.transport.closeStdin()
2242         return p.deferred
2243
2244     def _endProcess(self, reason, p):
2245         self.failIf(reason.check(error.ProcessDone),
2246                     'Child should fail due to EPIPE.')
2247         reason.trap(error.ProcessTerminated)
2248         # child must not get past that write without raising
2249         self.failIfEqual(reason.value.exitCode, 42,
2250                          'process reason was %r' % reason)
2251         self.failUnlessEqual(p.output, '')
2252         return p.errput
2253
2254     def test_stdout(self):
2255         """ProcessProtocol.transport.closeStdout actually closes the pipe."""
2256         d = self.doit(1)
2257         def _check(errput):
2258             self.failIfEqual(errput.find('OSError'), -1)
2259             if runtime.platform.getType() != 'win32':
2260                 self.failIfEqual(errput.find('Broken pipe'), -1)
2261         d.addCallback(_check)
2262         return d
2263
2264     def test_stderr(self):
2265         """ProcessProtocol.transport.closeStderr actually closes the pipe."""
2266         d = self.doit(2)
2267         def _check(errput):
2268             # there should be no stderr open, so nothing for it to
2269             # write the error to.
2270             self.failUnlessEqual(errput, '')
2271         d.addCallback(_check)
2272         return d
2273
2274
2275 skipMessage = "wrong platform or reactor doesn't support IReactorProcess"
2276 if (runtime.platform.getType() != 'posix') or (not interfaces.IReactorProcess(reactor, None)):
2277     PosixProcessTestCase.skip = skipMessage
2278     PosixProcessTestCasePTY.skip = skipMessage
2279     TestTwoProcessesPosix.skip = skipMessage
2280     FDTest.skip = skipMessage
2281 else:
2282     # do this before running the tests: it uses SIGCHLD and stuff internally
2283     lsOut = popen2.popen3("/bin/ls ZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")[2].read()
2284
2285 if (runtime.platform.getType() != 'win32') or (not interfaces.IReactorProcess(reactor, None)):
2286     Win32ProcessTestCase.skip = skipMessage
2287     TestTwoProcessesNonPosix.skip = skipMessage
2288     Dumbwin32procPidTest.skip = skipMessage
2289
2290 if not interfaces.IReactorProcess(reactor, None):
2291     ProcessTestCase.skip = skipMessage
2292     ClosingPipes.skip = skipMessage
2293
Note: See TracBrowser for help on using the browser.