Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(76)

Side by Side Diff: third_party/twisted_8_1/twisted/test/test_process.py

Issue 12261012: Remove third_party/twisted_8_1 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # Copyright (c) 2001-2008 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 warnings
17 import socket
18 try:
19 import fcntl
20 except ImportError:
21 fcntl = None
22
23 from zope.interface.verify import verifyObject
24
25 from twisted.internet import reactor, protocol, error, interfaces, defer
26 from twisted.internet import selectreactor
27 from twisted.trial import unittest
28 from twisted.python import util, runtime, procutils
29
30 try:
31 from twisted.internet import process
32 except ImportError:
33 process = None
34
35
36
37 class StubProcessProtocol(protocol.ProcessProtocol):
38 """
39 ProcessProtocol counter-implementation: all methods on this class raise an
40 exception, so instances of this may be used to verify that only certain
41 methods are called.
42 """
43 def outReceived(self, data):
44 raise NotImplementedError()
45
46 def errReceived(self, data):
47 raise NotImplementedError()
48
49 def inConnectionLost(self):
50 raise NotImplementedError()
51
52 def outConnectionLost(self):
53 raise NotImplementedError()
54
55 def errConnectionLost(self):
56 raise NotImplementedError()
57
58
59
60 class ProcessProtocolTests(unittest.TestCase):
61 """
62 Tests for behavior provided by the process protocol base class,
63 L{protocol.ProcessProtocol}.
64 """
65 def test_interface(self):
66 """
67 L{ProcessProtocol} implements L{IProcessProtocol}.
68 """
69 verifyObject(interfaces.IProcessProtocol, protocol.ProcessProtocol())
70
71
72 def test_outReceived(self):
73 """
74 Verify that when stdout is delivered to
75 L{ProcessProtocol.childDataReceived}, it is forwarded to
76 L{ProcessProtocol.outReceived}.
77 """
78 received = []
79 class OutProtocol(StubProcessProtocol):
80 def outReceived(self, data):
81 received.append(data)
82
83 bytes = "bytes"
84 p = OutProtocol()
85 p.childDataReceived(1, bytes)
86 self.assertEqual(received, [bytes])
87
88
89 def test_errReceived(self):
90 """
91 Similar to L{test_outReceived}, but for stderr.
92 """
93 received = []
94 class ErrProtocol(StubProcessProtocol):
95 def errReceived(self, data):
96 received.append(data)
97
98 bytes = "bytes"
99 p = ErrProtocol()
100 p.childDataReceived(2, bytes)
101 self.assertEqual(received, [bytes])
102
103
104 def test_inConnectionLost(self):
105 """
106 Verify that when stdin close notification is delivered to
107 L{ProcessProtocol.childConnectionLost}, it is forwarded to
108 L{ProcessProtocol.inConnectionLost}.
109 """
110 lost = []
111 class InLostProtocol(StubProcessProtocol):
112 def inConnectionLost(self):
113 lost.append(None)
114
115 p = InLostProtocol()
116 p.childConnectionLost(0)
117 self.assertEqual(lost, [None])
118
119
120 def test_outConnectionLost(self):
121 """
122 Similar to L{test_inConnectionLost}, but for stdout.
123 """
124 lost = []
125 class OutLostProtocol(StubProcessProtocol):
126 def outConnectionLost(self):
127 lost.append(None)
128
129 p = OutLostProtocol()
130 p.childConnectionLost(1)
131 self.assertEqual(lost, [None])
132
133
134 def test_errConnectionLost(self):
135 """
136 Similar to L{test_inConnectionLost}, but for stderr.
137 """
138 lost = []
139 class ErrLostProtocol(StubProcessProtocol):
140 def errConnectionLost(self):
141 lost.append(None)
142
143 p = ErrLostProtocol()
144 p.childConnectionLost(2)
145 self.assertEqual(lost, [None])
146
147
148
149 class TrivialProcessProtocol(protocol.ProcessProtocol):
150 """
151 Simple process protocol for tests purpose.
152
153 @ivar outData: data received from stdin
154 @ivar errData: data received from stderr
155 """
156
157 def __init__(self, d):
158 """
159 Create the deferred that will be fired at the end, and initialize
160 data structures.
161 """
162 self.deferred = d
163 self.outData = []
164 self.errData = []
165
166 def processEnded(self, reason):
167 self.reason = reason
168 self.deferred.callback(None)
169
170 def outReceived(self, data):
171 self.outData.append(data)
172
173 def errReceived(self, data):
174 self.errData.append(data)
175
176
177 class TestProcessProtocol(protocol.ProcessProtocol):
178
179 def connectionMade(self):
180 self.stages = [1]
181 self.data = ''
182 self.err = ''
183 self.transport.write("abcd")
184
185 def childDataReceived(self, childFD, data):
186 """
187 Override and disable the dispatch provided by the base class to ensure
188 that it is really this method which is being called, and the transport
189 is not going directly to L{outReceived} or L{errReceived}.
190 """
191 if childFD == 1:
192 self.data += data
193 elif childFD == 2:
194 self.err += data
195
196
197 def childConnectionLost(self, childFD):
198 """
199 Similarly to L{childDataReceived}, disable the automatic dispatch
200 provided by the base implementation to verify that the transport is
201 calling this method directly.
202 """
203 if childFD == 1:
204 self.stages.append(2)
205 if self.data != "abcd":
206 raise RuntimeError
207 self.transport.write("1234")
208 elif childFD == 2:
209 self.stages.append(3)
210 if self.err != "1234":
211 print 'err != 1234: ' + repr(self.err)
212 raise RuntimeError()
213 self.transport.write("abcd")
214 self.stages.append(4)
215 elif childFD == 0:
216 self.stages.append(5)
217
218 def processEnded(self, reason):
219 self.reason = reason
220 self.deferred.callback(None)
221
222
223 class EchoProtocol(protocol.ProcessProtocol):
224
225 s = "1234567" * 1001
226 n = 10
227 finished = 0
228
229 failure = None
230
231 def __init__(self, onEnded):
232 self.onEnded = onEnded
233 self.count = 0
234
235 def connectionMade(self):
236 assert self.n > 2
237 for i in range(self.n - 2):
238 self.transport.write(self.s)
239 # test writeSequence
240 self.transport.writeSequence([self.s, self.s])
241 self.buffer = self.s * self.n
242
243 def outReceived(self, data):
244 if buffer(self.buffer, self.count, len(data)) != buffer(data):
245 self.failure = ("wrong bytes received", data, self.count)
246 self.transport.closeStdin()
247 else:
248 self.count += len(data)
249 if self.count == len(self.buffer):
250 self.transport.closeStdin()
251
252 def processEnded(self, reason):
253 self.finished = 1
254 if not reason.check(error.ProcessDone):
255 self.failure = "process didn't terminate normally: " + str(reason)
256 self.onEnded.callback(self)
257
258
259
260 class SignalProtocol(protocol.ProcessProtocol):
261 """
262 A process protocol that sends a signal when data is first received.
263
264 @ivar deferred: deferred firing on C{processEnded}.
265 @type deferred: L{defer.Deferred}
266
267 @ivar signal: the signal to send to the process.
268 @type signal: C{str}
269 """
270
271 def __init__(self, deferred, sig):
272 self.deferred = deferred
273 self.signal = sig
274
275
276 def outReceived(self, data):
277 self.transport.signalProcess(self.signal)
278
279
280 def processEnded(self, reason):
281 """
282 Callback C{self.deferred} with C{None} if C{reason} is a
283 L{error.ProcessTerminated} failure with C{exitCode} set to C{None},
284 C{signal} set to C{self.signal}, and C{status} holding the status code
285 of the exited process. Otherwise, errback with a C{ValueError}
286 describing the problem.
287 """
288 if not reason.check(error.ProcessTerminated):
289 return self.deferred.errback(
290 ValueError("wrong termination: %s" % (reason,)))
291 v = reason.value
292 signalValue = getattr(signal, 'SIG' + self.signal)
293 if v.exitCode is not None:
294 return self.deferred.errback(
295 ValueError("SIG%s: exitCode is %s, not None" %
296 (self.signal, v.exitCode)))
297 if v.signal != signalValue:
298 return self.deferred.errback(
299 ValueError("SIG%s: .signal was %s, wanted %s" %
300 (self.signal, v.signal, signalValue)))
301 if os.WTERMSIG(v.status) != signalValue:
302 return self.deferred.errback(
303 ValueError('SIG%s: %s' % (self.signal, os.WTERMSIG(v.status))))
304 self.deferred.callback(None)
305
306
307
308 class TestManyProcessProtocol(TestProcessProtocol):
309 def __init__(self):
310 self.deferred = defer.Deferred()
311
312 def processEnded(self, reason):
313 self.reason = reason
314 if reason.check(error.ProcessDone):
315 self.deferred.callback(None)
316 else:
317 self.deferred.errback(reason)
318
319
320
321 class UtilityProcessProtocol(protocol.ProcessProtocol):
322 """
323 Helper class for launching a Python process and getting a result from it.
324
325 @ivar program: A string giving a Python program for the child process to
326 run.
327 """
328 program = None
329
330 def run(cls, reactor, argv, env):
331 """
332 Run a Python process connected to a new instance of this protocol
333 class. Return the protocol instance.
334
335 The Python process is given C{self.program} on the command line to
336 execute, in addition to anything specified by C{argv}. C{env} is
337 the complete environment.
338 """
339 exe = sys.executable
340 self = cls()
341 reactor.spawnProcess(
342 self, exe, [exe, "-c", self.program] + argv, env=env)
343 return self
344 run = classmethod(run)
345
346
347 def __init__(self):
348 self.bytes = []
349 self.requests = []
350
351
352 def parseChunks(self, bytes):
353 """
354 Called with all bytes received on stdout when the process exits.
355 """
356 raise NotImplementedError()
357
358
359 def getResult(self):
360 """
361 Return a Deferred which will fire with the result of L{parseChunks}
362 when the child process exits.
363 """
364 d = defer.Deferred()
365 self.requests.append(d)
366 return d
367
368
369 def _fireResultDeferreds(self, result):
370 """
371 Callback all Deferreds returned up until now by L{getResult}
372 with the given result object.
373 """
374 requests = self.requests
375 self.requests = None
376 for d in requests:
377 d.callback(result)
378
379
380 def outReceived(self, bytes):
381 """
382 Accumulate output from the child process in a list.
383 """
384 self.bytes.append(bytes)
385
386
387 def processEnded(self, reason):
388 """
389 Handle process termination by parsing all received output and firing
390 any waiting Deferreds.
391 """
392 self._fireResultDeferreds(self.parseChunks(self.bytes))
393
394
395
396
397 class GetArgumentVector(UtilityProcessProtocol):
398 """
399 Protocol which will read a serialized argv from a process and
400 expose it to interested parties.
401 """
402 program = (
403 "from sys import stdout, argv\n"
404 "stdout.write(chr(0).join(argv))\n"
405 "stdout.flush()\n")
406
407 def parseChunks(self, chunks):
408 """
409 Parse the output from the process to which this protocol was
410 connected, which is a single unterminated line of \\0-separated
411 strings giving the argv of that process. Return this as a list of
412 str objects.
413 """
414 return ''.join(chunks).split('\0')
415
416
417
418 class GetEnvironmentDictionary(UtilityProcessProtocol):
419 """
420 Protocol which will read a serialized environment dict from a process
421 and expose it to interested parties.
422 """
423 program = (
424 "from sys import stdout\n"
425 "from os import environ\n"
426 "items = environ.iteritems()\n"
427 "stdout.write(chr(0).join([k + chr(0) + v for k, v in items]))\n"
428 "stdout.flush()\n")
429
430 def parseChunks(self, chunks):
431 """
432 Parse the output from the process to which this protocol was
433 connected, which is a single unterminated line of \\0-separated
434 strings giving key value pairs of the environment from that process.
435 Return this as a dictionary.
436 """
437 environString = ''.join(chunks)
438 if not environString:
439 return {}
440 environ = iter(environString.split('\0'))
441 d = {}
442 while 1:
443 try:
444 k = environ.next()
445 except StopIteration:
446 break
447 else:
448 v = environ.next()
449 d[k] = v
450 return d
451
452
453
454 class ProcessTestCase(unittest.TestCase):
455 """Test running a process."""
456
457 usePTY = False
458
459 def testStdio(self):
460 """twisted.internet.stdio test."""
461 exe = sys.executable
462 scriptPath = util.sibpath(__file__, "process_twisted.py")
463 p = Accumulator()
464 d = p.endedDeferred = defer.Deferred()
465 env = {"PYTHONPATH": os.pathsep.join(sys.path)}
466 reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=env,
467 path=None, usePTY=self.usePTY)
468 p.transport.write("hello, world")
469 p.transport.write("abc")
470 p.transport.write("123")
471 p.transport.closeStdin()
472
473 def processEnded(ign):
474 self.assertEquals(p.outF.getvalue(), "hello, worldabc123",
475 "Output follows:\n"
476 "%s\n"
477 "Error message from process_twisted follows:\n"
478 "%s\n" % (p.outF.getvalue(), p.errF.getvalue()))
479 return d.addCallback(processEnded)
480
481
482 def test_unsetPid(self):
483 """
484 Test if pid is None/non-None before/after process termination. This
485 reuses process_echoer.py to get a process that blocks on stdin.
486 """
487 finished = defer.Deferred()
488 p = TrivialProcessProtocol(finished)
489 exe = sys.executable
490 scriptPath = util.sibpath(__file__, "process_echoer.py")
491 procTrans = reactor.spawnProcess(p, exe,
492 [exe, "-u", scriptPath], env=None)
493 self.failUnless(procTrans.pid)
494
495 def afterProcessEnd(ignored):
496 self.assertEqual(procTrans.pid, None)
497
498 p.transport.closeStdin()
499 return finished.addCallback(afterProcessEnd)
500
501
502 def test_process(self):
503 """
504 Test running a process: check its output, it exitCode, some property of
505 signalProcess.
506 """
507 exe = sys.executable
508 scriptPath = util.sibpath(__file__, "process_tester.py")
509 d = defer.Deferred()
510 p = TestProcessProtocol()
511 p.deferred = d
512 reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=None)
513 def check(ignored):
514 self.assertEquals(p.stages, [1, 2, 3, 4, 5])
515 f = p.reason
516 f.trap(error.ProcessTerminated)
517 self.assertEquals(f.value.exitCode, 23)
518 # would .signal be available on non-posix?
519 # self.assertEquals(f.value.signal, None)
520 self.assertRaises(
521 error.ProcessExitedAlready, p.transport.signalProcess, 'INT')
522 try:
523 import process_tester, glob
524 for f in glob.glob(process_tester.test_file_match):
525 os.remove(f)
526 except:
527 pass
528 d.addCallback(check)
529 return d
530
531 def testManyProcesses(self):
532
533 def _check(results, protocols):
534 for p in protocols:
535 self.assertEquals(p.stages, [1, 2, 3, 4, 5], "[%d] stages = %s" % (id(p.transport), str(p.stages)))
536 # test status code
537 f = p.reason
538 f.trap(error.ProcessTerminated)
539 self.assertEquals(f.value.exitCode, 23)
540
541 exe = sys.executable
542 scriptPath = util.sibpath(__file__, "process_tester.py")
543 args = [exe, "-u", scriptPath]
544 protocols = []
545 deferreds = []
546
547 for i in xrange(50):
548 p = TestManyProcessProtocol()
549 protocols.append(p)
550 reactor.spawnProcess(p, exe, args, env=None)
551 deferreds.append(p.deferred)
552
553 deferredList = defer.DeferredList(deferreds, consumeErrors=True)
554 deferredList.addCallback(_check, protocols)
555 return deferredList
556
557 def testEcho(self):
558 finished = defer.Deferred()
559 p = EchoProtocol(finished)
560
561 exe = sys.executable
562 scriptPath = util.sibpath(__file__, "process_echoer.py")
563 reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=None)
564
565 def asserts(ignored):
566 self.failIf(p.failure, p.failure)
567 self.failUnless(hasattr(p, 'buffer'))
568 self.assertEquals(len(''.join(p.buffer)), len(p.s * p.n))
569
570 def takedownProcess(err):
571 p.transport.closeStdin()
572 return err
573
574 return finished.addCallback(asserts).addErrback(takedownProcess)
575 testEcho.timeout = 60 # XXX This should not be. There is already a
576 # global timeout value. Why do you think this
577 # test can complete more quickly?
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.oka yUnicode])
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
1102 class MockOS(object):
1103 """
1104 The mock OS: overwrite L{os}, L{fcntl} and {sys} functions with fake ones.
1105
1106 @ivar exited: set to True when C{_exit} is called.
1107 @type exited: C{bool}
1108
1109 @ivar O_RDWR: dumb value faking C{os.O_RDWR}.
1110 @type O_RDWR: C{int}
1111
1112 @ivar O_NOCTTY: dumb value faking C{os.O_NOCTTY}.
1113 @type O_NOCTTY: C{int}
1114
1115 @ivar WNOHANG: dumb value faking C{os.WNOHANG}.
1116 @type WNOHANG: C{int}
1117
1118 @ivar raiseFork: if not C{None}, subsequent calls to fork will raise this
1119 object.
1120 @type raiseFork: C{NoneType} or C{Exception}
1121
1122 @ivar raiseExec: if set, subsequent calls to execvpe will raise an error.
1123 @type raiseExec: C{bool}
1124
1125 @ivar fdio: fake file object returned by calls to fdopen.
1126 @type fdio: C{StringIO.StringIO}
1127
1128 @ivar actions: hold names of some actions executed by the object, in order
1129 of execution.
1130
1131 @type actions: C{list} of C{str}
1132
1133 @ivar closed: keep track of the file descriptor closed.
1134 @param closed: C{list} of C{int}
1135
1136 @ivar child: whether fork return for the child or the parent.
1137 @type child: C{bool}
1138
1139 @ivar pipeCount: count the number of time that C{os.pipe} has been called.
1140 @type pipeCount: C{int}
1141
1142 @ivar raiseWaitPid: if set, subsequent calls to waitpid will raise an
1143 the error specified.
1144 @type raiseWaitPid: C{None} or a class
1145
1146 @ivar waitChild: if set, subsequent calls to waitpid will return it.
1147 @type waitChild: C{None} or a tuple
1148 """
1149 exited = False
1150 O_RDWR = 1
1151 O_NOCTTY = 1
1152 WNOHANG = 1
1153 raiseExec = False
1154 fdio = None
1155 child = True
1156 raiseWaitPid = None
1157 raiseFork = None
1158 waitChild = None
1159
1160 def __init__(self):
1161 """
1162 Initialiaze data structures.
1163 """
1164 self.actions = []
1165 self.closed = []
1166 self.pipeCount = 0
1167
1168
1169 def open(self, dev, flags):
1170 """
1171 Fake C{os.open}. Return a non fd number to be sure it's not used
1172 elsewhere.
1173 """
1174 return -3
1175
1176
1177 def fdopen(self, fd, flag):
1178 """
1179 Fake C{os.fdopen}. Return a StringIO object whose content can be tested
1180 later via C{self.fdio}.
1181 """
1182 self.fdio = StringIO.StringIO()
1183 return self.fdio
1184
1185
1186 def setsid(self):
1187 """
1188 Fake C{os.setsid}. Do nothing.
1189 """
1190
1191
1192 def fork(self):
1193 """
1194 Fake C{os.fork}. Save the action in C{self.actions}, and return 0 if
1195 C{self.child} is set, or a dumb number.
1196 """
1197 self.actions.append(('fork', gc.isenabled()))
1198 if self.raiseFork is not None:
1199 raise self.raiseFork
1200 elif self.child:
1201 # Child result is 0
1202 return 0
1203 else:
1204 return 21
1205
1206
1207 def close(self, fd):
1208 """
1209 Fake C{os.close}, saving the closed fd in C{self.closed}.
1210 """
1211 self.closed.append(fd)
1212
1213
1214 def dup2(self, fd1, fd2):
1215 """
1216 Fake C{os.dup2}. Do nothing.
1217 """
1218
1219
1220 def write(self, fd, data):
1221 """
1222 Fake C{os.write}. Do nothing.
1223 """
1224
1225
1226 def execvpe(self, command, args, env):
1227 """
1228 Fake C{os.execvpe}. Save the action, and raise an error if
1229 C{self.raiseExec} is set.
1230 """
1231 self.actions.append('exec')
1232 if self.raiseExec:
1233 raise RuntimeError("Bar")
1234
1235
1236 def pipe(self):
1237 """
1238 Fake C{os.pipe}. Return non fd numbers to be sure it's not used
1239 elsewhere, and increment C{self.pipeCount}. This is used to uniquify
1240 the result.
1241 """
1242 self.pipeCount += 1
1243 return - 2 * self.pipeCount + 1, - 2 * self.pipeCount
1244
1245
1246 def ttyname(self, fd):
1247 """
1248 Fake C{os.ttyname}. Return a dumb string.
1249 """
1250 return "foo"
1251
1252
1253 def _exit(self, code):
1254 """
1255 Fake C{os._exit}. Save the action, set the C{self.exited} flag, and
1256 raise C{SystemError}.
1257 """
1258 self.actions.append('exit')
1259 self.exited = True
1260 # Don't forget to raise an error, or you'll end up in parent
1261 # code path.
1262 raise SystemError()
1263
1264
1265 def ioctl(self, fd, flags, arg):
1266 """
1267 Override C{fcntl.ioctl}. Do nothing.
1268 """
1269
1270
1271 def setNonBlocking(self, fd):
1272 """
1273 Override C{fdesc.setNonBlocking}. Do nothing.
1274 """
1275
1276
1277 def waitpid(self, pid, options):
1278 """
1279 Override C{os.waitpid}. Return values meaning that the child process
1280 has exited, save executed action.
1281 """
1282 self.actions.append('waitpid')
1283 if self.raiseWaitPid is not None:
1284 raise self.raiseWaitPid
1285 if self.waitChild is not None:
1286 return self.waitChild
1287 return 1, 0
1288
1289
1290 def settrace(self, arg):
1291 """
1292 Override C{sys.settrace} to keep coverage working.
1293 """
1294
1295
1296 def getegid(self):
1297 """
1298 Override C{os.getegid}. Return a dumb number.
1299 """
1300 return 1234
1301
1302
1303 def getgid(self):
1304 """
1305 Override C{os.getgid}. Return a dumb number.
1306 """
1307 return 1235
1308
1309
1310 def geteuid(self):
1311 """
1312 Override C{os.geteuid}. Return a dumb number.
1313 """
1314 return 1236
1315
1316
1317 def getuid(self):
1318 """
1319 Override C{os.getuid}. Return a dumb number.
1320 """
1321 return 1237
1322
1323
1324 def setuid(self, val):
1325 """
1326 Override C{os.setuid}. Do nothing.
1327 """
1328 self.actions.append(('setuid', val))
1329
1330
1331 def setgid(self, val):
1332 """
1333 Override C{os.setgid}. Do nothing.
1334 """
1335 self.actions.append(('setgid', val))
1336
1337
1338 def setregid(self, val1, val2):
1339 """
1340 Override C{os.setregid}. Do nothing.
1341 """
1342 self.actions.append(('setregid', val1, val2))
1343
1344
1345 def setreuid(self, val1, val2):
1346 """
1347 Override C{os.setreuid}. Save the action.
1348 """
1349 self.actions.append(('setreuid', val1, val2))
1350
1351
1352 def switchUID(self, uid, gid):
1353 """
1354 Override C{util.switchuid}. Save the action.
1355 """
1356 self.actions.append(('switchuid', uid, gid))
1357
1358
1359
1360 if process is not None:
1361 class DumbProcessWriter(process.ProcessWriter):
1362 """
1363 A fake L{process.ProcessWriter} used for tests.
1364 """
1365
1366 def startReading(self):
1367 """
1368 Here's the faking: don't do anything here.
1369 """
1370
1371
1372
1373 class DumbProcessReader(process.ProcessReader):
1374 """
1375 A fake L{process.ProcessReader} used for tests.
1376 """
1377
1378 def startReading(self):
1379 """
1380 Here's the faking: don't do anything here.
1381 """
1382
1383
1384
1385 class DumbPTYProcess(process.PTYProcess):
1386 """
1387 A fake L{process.PTYProcess} used for tests.
1388 """
1389
1390 def startReading(self):
1391 """
1392 Here's the faking: don't do anything here.
1393 """
1394
1395
1396
1397 class MockProcessTestCase(unittest.TestCase):
1398 """
1399 Mock a process runner to test forked child code path.
1400 """
1401
1402 def setUp(self):
1403 """
1404 Replace L{process} os, fcntl, sys, switchUID modules with the mock
1405 class L{MockOS}.
1406 """
1407 if gc.isenabled():
1408 self.addCleanup(gc.enable)
1409 else:
1410 self.addCleanup(gc.disable)
1411 self.mockos = MockOS()
1412 self.oldos = os
1413 self.oldfcntl = fcntl
1414 self.oldsys = sys
1415 self.oldSwitchUID = util.switchUID
1416 self.oldFdesc = process.fdesc
1417 process.os = self.mockos
1418 process.fcntl = self.mockos
1419 process.sys = self.mockos
1420 process.switchUID = self.mockos.switchUID
1421 process.fdesc = self.mockos
1422 process.Process.processReaderFactory = DumbProcessReader
1423 process.Process.processWriterFactory = DumbProcessWriter
1424
1425
1426 def tearDown(self):
1427 """
1428 Restore L{process} modules, and reset processes registered for reap.
1429 """
1430 process.os = self.oldos
1431 process.fcntl = self.oldfcntl
1432 process.sys = self.oldsys
1433 process.switchUID = self.oldSwitchUID
1434 process.fdesc = self.oldFdesc
1435 process.Process.processReaderFactory = process.ProcessReader
1436 process.Process.processWriterFactory = process.ProcessWriter
1437 process.reapProcessHandlers = {}
1438
1439
1440 def test_mockFork(self):
1441 """
1442 Test a classic spawnProcess. Check the path of the client code:
1443 fork, exec, exit.
1444 """
1445 gc.enable()
1446
1447 cmd = '/mock/ouch'
1448
1449 d = defer.Deferred()
1450 p = TrivialProcessProtocol(d)
1451 try:
1452 reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1453 usePTY=False)
1454 except SystemError:
1455 self.assert_(self.mockos.exited)
1456 self.assertEquals(
1457 self.mockos.actions, [("fork", False), "exec", "exit"])
1458 else:
1459 self.fail("Should not be here")
1460
1461 # It should leave the garbage collector disabled.
1462 self.assertFalse(gc.isenabled())
1463
1464
1465 def _mockForkInParentTest(self):
1466 """
1467 Assert that in the main process, spawnProcess disables the garbage
1468 collector, calls fork, closes the pipe file descriptors it created for
1469 the child process, and calls waitpid.
1470 """
1471 self.mockos.child = False
1472 cmd = '/mock/ouch'
1473
1474 d = defer.Deferred()
1475 p = TrivialProcessProtocol(d)
1476 reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1477 usePTY=False)
1478 # It should close the first read pipe, and the 2 last write pipes
1479 self.assertEqual(self.mockos.closed, [-1, -4, -6])
1480 self.assertEquals(self.mockos.actions, [("fork", False), "waitpid"])
1481
1482
1483 def test_mockForkInParentGarbageCollectorEnabled(self):
1484 """
1485 The garbage collector should be enabled when L{reactor.spawnProcess}
1486 returns if it was initially enabled.
1487
1488 @see L{_mockForkInParentTest}
1489 """
1490 gc.enable()
1491 self._mockForkInParentTest()
1492 self.assertTrue(gc.isenabled())
1493
1494
1495 def test_mockForkInParentGarbageCollectorDisabled(self):
1496 """
1497 The garbage collector should be disabled when L{reactor.spawnProcess}
1498 returns if it was initially disabled.
1499
1500 @see L{_mockForkInParentTest}
1501 """
1502 gc.disable()
1503 self._mockForkInParentTest()
1504 self.assertFalse(gc.isenabled())
1505
1506
1507 def test_mockForkTTY(self):
1508 """
1509 Test a TTY spawnProcess: check the path of the client code:
1510 fork, exec, exit.
1511 """
1512 cmd = '/mock/ouch'
1513
1514 d = defer.Deferred()
1515 p = TrivialProcessProtocol(d)
1516 try:
1517 reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1518 usePTY=True)
1519 except SystemError:
1520 self.assert_(self.mockos.exited)
1521 self.assertEquals(
1522 self.mockos.actions, [("fork", False), "exec", "exit"])
1523 else:
1524 self.fail("Should not be here")
1525
1526
1527 def _mockWithForkError(self):
1528 """
1529 Assert that if the fork call fails, no other process setup calls are
1530 made and that spawnProcess raises the exception fork raised.
1531 """
1532 self.mockos.raiseFork = OSError(errno.EAGAIN, None)
1533 protocol = TrivialProcessProtocol(None)
1534 self.assertRaises(OSError, reactor.spawnProcess, protocol, None)
1535 self.assertEqual(self.mockos.actions, [("fork", False)])
1536
1537
1538 def test_mockWithForkErrorGarbageCollectorEnabled(self):
1539 """
1540 The garbage collector should be enabled when L{reactor.spawnProcess}
1541 raises because L{os.fork} raised, if it was initially enabled.
1542 """
1543 gc.enable()
1544 self._mockWithForkError()
1545 self.assertTrue(gc.isenabled())
1546
1547
1548 def test_mockWithForkErrorGarbageCollectorDisabled(self):
1549 """
1550 The garbage collector should be disabled when
1551 L{reactor.spawnProcess} raises because L{os.fork} raised, if it was
1552 initially disabled.
1553 """
1554 gc.disable()
1555 self._mockWithForkError()
1556 self.assertFalse(gc.isenabled())
1557
1558
1559 def test_mockWithExecError(self):
1560 """
1561 Spawn a process but simulate an error during execution in the client
1562 path: C{os.execvpe} raises an error. It should close all the standard
1563 fds, try to print the error encountered, and exit cleanly.
1564 """
1565 cmd = '/mock/ouch'
1566
1567 d = defer.Deferred()
1568 p = TrivialProcessProtocol(d)
1569 self.mockos.raiseExec = True
1570 try:
1571 reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1572 usePTY=False)
1573 except SystemError:
1574 self.assert_(self.mockos.exited)
1575 self.assertEquals(
1576 self.mockos.actions, [("fork", False), "exec", "exit"])
1577 # Check that fd have been closed
1578 self.assertIn(0, self.mockos.closed)
1579 self.assertIn(1, self.mockos.closed)
1580 self.assertIn(2, self.mockos.closed)
1581 # Check content of traceback
1582 self.assertIn("RuntimeError: Bar", self.mockos.fdio.getvalue())
1583 else:
1584 self.fail("Should not be here")
1585
1586
1587 def test_mockSetUid(self):
1588 """
1589 Try creating a process with setting its uid: it's almost the same path
1590 as the standard path, but with a C{switchUID} call before the exec.
1591 """
1592 cmd = '/mock/ouch'
1593
1594 d = defer.Deferred()
1595 p = TrivialProcessProtocol(d)
1596 try:
1597 reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1598 usePTY=False, uid=8080)
1599 except SystemError:
1600 self.assert_(self.mockos.exited)
1601 self.assertEquals(self.mockos.actions,
1602 [('setuid', 0), ('setgid', 0), ('fork', False),
1603 ('switchuid', 8080, 1234), 'exec', 'exit'])
1604 else:
1605 self.fail("Should not be here")
1606
1607
1608 def test_mockSetUidInParent(self):
1609 """
1610 Try creating a process with setting its uid, in the parent path: it
1611 should switch to root before fork, then restore initial uid/gids.
1612 """
1613 self.mockos.child = False
1614 cmd = '/mock/ouch'
1615
1616 d = defer.Deferred()
1617 p = TrivialProcessProtocol(d)
1618 reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1619 usePTY=False, uid=8080)
1620 self.assertEquals(self.mockos.actions,
1621 [('setuid', 0), ('setgid', 0), ('fork', False),
1622 ('setregid', 1235, 1234), ('setreuid', 1237, 1236), 'waitpid'])
1623
1624
1625 def test_mockPTYSetUid(self):
1626 """
1627 Try creating a PTY process with setting its uid: it's almost the same
1628 path as the standard path, but with a C{switchUID} call before the
1629 exec.
1630 """
1631 cmd = '/mock/ouch'
1632
1633 d = defer.Deferred()
1634 p = TrivialProcessProtocol(d)
1635 try:
1636 reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1637 usePTY=True, uid=8081)
1638 except SystemError:
1639 self.assert_(self.mockos.exited)
1640 self.assertEquals(self.mockos.actions,
1641 [('setuid', 0), ('setgid', 0), ('fork', False),
1642 ('switchuid', 8081, 1234), 'exec', 'exit'])
1643 else:
1644 self.fail("Should not be here")
1645
1646
1647 def test_mockPTYSetUidInParent(self):
1648 """
1649 Try creating a PTY process with setting its uid, in the parent path: it
1650 should switch to root before fork, then restore initial uid/gids.
1651 """
1652 self.mockos.child = False
1653 cmd = '/mock/ouch'
1654
1655 d = defer.Deferred()
1656 p = TrivialProcessProtocol(d)
1657 oldPTYProcess = process.PTYProcess
1658 try:
1659 process.PTYProcess = DumbPTYProcess
1660 reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1661 usePTY=True, uid=8080)
1662 finally:
1663 process.PTYProcess = oldPTYProcess
1664 self.assertEquals(self.mockos.actions,
1665 [('setuid', 0), ('setgid', 0), ('fork', False),
1666 ('setregid', 1235, 1234), ('setreuid', 1237, 1236), 'waitpid'])
1667
1668
1669 def test_mockWithWaitError(self):
1670 """
1671 Test that reapProcess logs errors raised.
1672 """
1673 self.mockos.child = False
1674 cmd = '/mock/ouch'
1675 self.mockos.waitChild = (0, 0)
1676
1677 d = defer.Deferred()
1678 p = TrivialProcessProtocol(d)
1679 proc = reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1680 usePTY=False)
1681 self.assertEquals(self.mockos.actions, [("fork", False), "waitpid"])
1682
1683 self.mockos.raiseWaitPid = OSError()
1684 proc.reapProcess()
1685 errors = self.flushLoggedErrors()
1686 self.assertEquals(len(errors), 1)
1687 errors[0].trap(OSError)
1688
1689
1690 def test_mockErrorECHILDInReapProcess(self):
1691 """
1692 Test that reapProcess doesn't log anything when waitpid raises a
1693 C{OSError} with errno C{ECHILD}.
1694 """
1695 self.mockos.child = False
1696 cmd = '/mock/ouch'
1697 self.mockos.waitChild = (0, 0)
1698
1699 d = defer.Deferred()
1700 p = TrivialProcessProtocol(d)
1701 proc = reactor.spawnProcess(p, cmd, ['ouch'], env=None,
1702 usePTY=False)
1703 self.assertEquals(self.mockos.actions, [("fork", False), "waitpid"])
1704
1705 self.mockos.raiseWaitPid = OSError()
1706 self.mockos.raiseWaitPid.errno = errno.ECHILD
1707 # This should not produce any errors
1708 proc.reapProcess()
1709
1710
1711 class PosixProcessTestCase(unittest.TestCase, PosixProcessBase):
1712 # add three non-pty test cases
1713
1714 def testStderr(self):
1715 # we assume there is no file named ZZXXX..., both in . and in /tmp
1716 cmd = self.getCommand('ls')
1717
1718 p = Accumulator()
1719 d = p.endedDeferred = defer.Deferred()
1720 reactor.spawnProcess(p, cmd,
1721 [cmd,
1722 "ZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"],
1723 env=None, path="/tmp",
1724 usePTY=self.usePTY)
1725
1726 def processEnded(ign):
1727 self.assertEquals(lsOut, p.errF.getvalue())
1728 return d.addCallback(processEnded)
1729
1730 def testProcess(self):
1731 cmd = self.getCommand('gzip')
1732 s = "there's no place like home!\n" * 3
1733 p = Accumulator()
1734 d = p.endedDeferred = defer.Deferred()
1735 reactor.spawnProcess(p, cmd, [cmd, "-c"], env=None, path="/tmp",
1736 usePTY=self.usePTY)
1737 p.transport.write(s)
1738 p.transport.closeStdin()
1739
1740 def processEnded(ign):
1741 f = p.outF
1742 f.seek(0, 0)
1743 gf = gzip.GzipFile(fileobj=f)
1744 self.assertEquals(gf.read(), s)
1745 return d.addCallback(processEnded)
1746
1747
1748
1749 class PosixProcessTestCasePTY(unittest.TestCase, PosixProcessBase):
1750 """
1751 Just like PosixProcessTestCase, but use ptys instead of pipes.
1752 """
1753 usePTY = True
1754 # PTYs only offer one input and one output. What still makes sense?
1755 # testNormalTermination
1756 # test_abnormalTermination
1757 # testSignal
1758 # testProcess, but not without p.transport.closeStdin
1759 # might be solveable: TODO: add test if so
1760
1761 def testOpeningTTY(self):
1762 exe = sys.executable
1763 scriptPath = util.sibpath(__file__, "process_tty.py")
1764 p = Accumulator()
1765 d = p.endedDeferred = defer.Deferred()
1766 reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=None,
1767 path=None, usePTY=self.usePTY)
1768 p.transport.write("hello world!\n")
1769
1770 def processEnded(ign):
1771 self.assertRaises(
1772 error.ProcessExitedAlready, p.transport.signalProcess, 'HUP')
1773 self.assertEquals(
1774 p.outF.getvalue(),
1775 "hello world!\r\nhello world!\r\n",
1776 "Error message from process_tty follows:\n\n%s\n\n" % p.outF.get value())
1777 return d.addCallback(processEnded)
1778
1779
1780 def testBadArgs(self):
1781 pyExe = sys.executable
1782 pyArgs = [pyExe, "-u", "-c", "print 'hello'"]
1783 p = Accumulator()
1784 self.assertRaises(ValueError, reactor.spawnProcess, p, pyExe, pyArgs,
1785 usePTY=1, childFDs={1:'r'})
1786
1787
1788
1789 class Win32SignalProtocol(SignalProtocol):
1790 """
1791 A win32-specific process protocol that handles C{processEnded}
1792 differently: processes should exit with exit code 1.
1793 """
1794
1795 def processEnded(self, reason):
1796 """
1797 Callback C{self.deferred} with C{None} if C{reason} is a
1798 L{error.ProcessTerminated} failure with C{exitCode} set to 1.
1799 Otherwise, errback with a C{ValueError} describing the problem.
1800 """
1801 if not reason.check(error.ProcessTerminated):
1802 return self.deferred.errback(
1803 ValueError("wrong termination: %s" % (reason,)))
1804 v = reason.value
1805 if v.exitCode != 1:
1806 return self.deferred.errback(
1807 ValueError("Wrong exit code: %s" % (reason.exitCode,)))
1808 self.deferred.callback(None)
1809
1810
1811
1812 class Win32ProcessTestCase(unittest.TestCase):
1813 """
1814 Test process programs that are packaged with twisted.
1815 """
1816
1817 def testStdinReader(self):
1818 pyExe = sys.executable
1819 scriptPath = util.sibpath(__file__, "process_stdinreader.py")
1820 p = Accumulator()
1821 d = p.endedDeferred = defer.Deferred()
1822 reactor.spawnProcess(p, pyExe, [pyExe, "-u", scriptPath], env=None,
1823 path=None)
1824 p.transport.write("hello, world")
1825 p.transport.closeStdin()
1826
1827 def processEnded(ign):
1828 self.assertEquals(p.errF.getvalue(), "err\nerr\n")
1829 self.assertEquals(p.outF.getvalue(), "out\nhello, world\nout\n")
1830 return d.addCallback(processEnded)
1831
1832
1833 def testBadArgs(self):
1834 pyExe = sys.executable
1835 pyArgs = [pyExe, "-u", "-c", "print 'hello'"]
1836 p = Accumulator()
1837 self.assertRaises(ValueError,
1838 reactor.spawnProcess, p, pyExe, pyArgs, uid=1)
1839 self.assertRaises(ValueError,
1840 reactor.spawnProcess, p, pyExe, pyArgs, gid=1)
1841 self.assertRaises(ValueError,
1842 reactor.spawnProcess, p, pyExe, pyArgs, usePTY=1)
1843 self.assertRaises(ValueError,
1844 reactor.spawnProcess, p, pyExe, pyArgs, childFDs={1:'r'})
1845
1846
1847 def _testSignal(self, sig):
1848 exe = sys.executable
1849 scriptPath = util.sibpath(__file__, "process_signal.py")
1850 d = defer.Deferred()
1851 p = Win32SignalProtocol(d, sig)
1852 reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=None)
1853 return d
1854
1855
1856 def test_signalTERM(self):
1857 """
1858 Sending the SIGTERM signal terminates a created process, and
1859 C{processEnded} is called with a L{error.ProcessTerminated} instance
1860 with the C{exitCode} attribute set to 1.
1861 """
1862 return self._testSignal('TERM')
1863
1864
1865 def test_signalINT(self):
1866 """
1867 Sending the SIGINT signal terminates a created process, and
1868 C{processEnded} is called with a L{error.ProcessTerminated} instance
1869 with the C{exitCode} attribute set to 1.
1870 """
1871 return self._testSignal('INT')
1872
1873
1874 def test_signalKILL(self):
1875 """
1876 Sending the SIGKILL signal terminates a created process, and
1877 C{processEnded} is called with a L{error.ProcessTerminated} instance
1878 with the C{exitCode} attribute set to 1.
1879 """
1880 return self._testSignal('KILL')
1881
1882
1883
1884 class Dumbwin32procPidTest(unittest.TestCase):
1885 """
1886 Simple test for the pid attribute of Process on win32.
1887 """
1888
1889 def test_pid(self):
1890 """
1891 Launch process with mock win32process. The only mock aspect of this
1892 module is that the pid of the process created will always be 42.
1893 """
1894 from twisted.internet import _dumbwin32proc
1895 from twisted.test import mock_win32process
1896 self.patch(_dumbwin32proc, "win32process", mock_win32process)
1897 exe = sys.executable
1898 scriptPath = util.sibpath(__file__, "process_cmdline.py")
1899
1900 d = defer.Deferred()
1901 processProto = TrivialProcessProtocol(d)
1902 comspec = str(os.environ["COMSPEC"])
1903 cmd = [comspec, "/c", exe, scriptPath]
1904
1905 p = _dumbwin32proc.Process(reactor,
1906 processProto,
1907 None,
1908 cmd,
1909 {},
1910 None)
1911 self.assertEquals(42, p.pid)
1912 self.assertEquals("<Process pid=42>", repr(p))
1913
1914 def pidCompleteCb(result):
1915 self.assertEquals(None, p.pid)
1916 return d.addCallback(pidCompleteCb)
1917
1918
1919
1920 class UtilTestCase(unittest.TestCase):
1921 """
1922 Tests for process-related helper functions (currently only
1923 L{procutils.which}.
1924 """
1925 def setUp(self):
1926 """
1927 Create several directories and files, some of which are executable
1928 and some of which are not. Save the current PATH setting.
1929 """
1930 j = os.path.join
1931
1932 base = self.mktemp()
1933
1934 self.foo = j(base, "foo")
1935 self.baz = j(base, "baz")
1936 self.foobar = j(self.foo, "bar")
1937 self.foobaz = j(self.foo, "baz")
1938 self.bazfoo = j(self.baz, "foo")
1939 self.bazbar = j(self.baz, "bar")
1940
1941 for d in self.foobar, self.foobaz, self.bazfoo, self.bazbar:
1942 os.makedirs(d)
1943
1944 for name, mode in [(j(self.foobaz, "executable"), 0700),
1945 (j(self.foo, "executable"), 0700),
1946 (j(self.bazfoo, "executable"), 0700),
1947 (j(self.bazfoo, "executable.bin"), 0700),
1948 (j(self.bazbar, "executable"), 0)]:
1949 f = file(name, "w")
1950 f.close()
1951 os.chmod(name, mode)
1952
1953 self.oldPath = os.environ.get('PATH', None)
1954 os.environ['PATH'] = os.pathsep.join((
1955 self.foobar, self.foobaz, self.bazfoo, self.bazbar))
1956
1957
1958 def tearDown(self):
1959 """
1960 Restore the saved PATH setting.
1961 """
1962 if self.oldPath is None:
1963 try:
1964 del os.environ['PATH']
1965 except KeyError:
1966 pass
1967 else:
1968 os.environ['PATH'] = self.oldPath
1969
1970
1971 def test_whichWithoutPATH(self):
1972 """
1973 Test that if C{os.environ} does not have a C{'PATH'} key,
1974 L{procutils.which} returns an empty list.
1975 """
1976 del os.environ['PATH']
1977 self.assertEqual(procutils.which("executable"), [])
1978
1979
1980 def testWhich(self):
1981 j = os.path.join
1982 paths = procutils.which("executable")
1983 expectedPaths = [j(self.foobaz, "executable"),
1984 j(self.bazfoo, "executable")]
1985 if runtime.platform.isWindows():
1986 expectedPaths.append(j(self.bazbar, "executable"))
1987 self.assertEquals(paths, expectedPaths)
1988
1989
1990 def testWhichPathExt(self):
1991 j = os.path.join
1992 old = os.environ.get('PATHEXT', None)
1993 os.environ['PATHEXT'] = os.pathsep.join(('.bin', '.exe', '.sh'))
1994 try:
1995 paths = procutils.which("executable")
1996 finally:
1997 if old is None:
1998 del os.environ['PATHEXT']
1999 else:
2000 os.environ['PATHEXT'] = old
2001 expectedPaths = [j(self.foobaz, "executable"),
2002 j(self.bazfoo, "executable"),
2003 j(self.bazfoo, "executable.bin")]
2004 if runtime.platform.isWindows():
2005 expectedPaths.append(j(self.bazbar, "executable"))
2006 self.assertEquals(paths, expectedPaths)
2007
2008
2009
2010 class ClosingPipesProcessProtocol(protocol.ProcessProtocol):
2011 output = ''
2012 errput = ''
2013
2014 def __init__(self, outOrErr):
2015 self.deferred = defer.Deferred()
2016 self.outOrErr = outOrErr
2017
2018 def processEnded(self, reason):
2019 self.deferred.callback(reason)
2020
2021 def outReceived(self, data):
2022 self.output += data
2023
2024 def errReceived(self, data):
2025 self.errput += data
2026
2027
2028 class ClosingPipes(unittest.TestCase):
2029
2030 def doit(self, fd):
2031 p = ClosingPipesProcessProtocol(True)
2032 p.deferred.addCallbacks(
2033 callback=lambda _: self.fail("I wanted an errback."),
2034 errback=self._endProcess, errbackArgs=(p,))
2035 reactor.spawnProcess(p, sys.executable,
2036 [sys.executable, '-u', '-c',
2037 r'raw_input(); import sys, os; os.write(%d, "foo\n "); sys.exit(42)' % fd],
2038 env=None)
2039 p.transport.write('go\n')
2040
2041 if fd == 1:
2042 p.transport.closeStdout()
2043 elif fd == 2:
2044 p.transport.closeStderr()
2045 else:
2046 raise RuntimeError
2047
2048 # make the buggy case not hang
2049 p.transport.closeStdin()
2050 return p.deferred
2051
2052 def _endProcess(self, reason, p):
2053 self.failIf(reason.check(error.ProcessDone),
2054 'Child should fail due to EPIPE.')
2055 reason.trap(error.ProcessTerminated)
2056 # child must not get past that write without raising
2057 self.failIfEqual(reason.value.exitCode, 42,
2058 'process reason was %r' % reason)
2059 self.failUnlessEqual(p.output, '')
2060 return p.errput
2061
2062 def test_stdout(self):
2063 """ProcessProtocol.transport.closeStdout actually closes the pipe."""
2064 d = self.doit(1)
2065 def _check(errput):
2066 self.failIfEqual(errput.find('OSError'), -1)
2067 if runtime.platform.getType() != 'win32':
2068 self.failIfEqual(errput.find('Broken pipe'), -1)
2069 d.addCallback(_check)
2070 return d
2071
2072 def test_stderr(self):
2073 """ProcessProtocol.transport.closeStderr actually closes the pipe."""
2074 d = self.doit(2)
2075 def _check(errput):
2076 # there should be no stderr open, so nothing for it to
2077 # write the error to.
2078 self.failUnlessEqual(errput, '')
2079 d.addCallback(_check)
2080 return d
2081
2082
2083 class SystemEventOrderRegressionTests(unittest.TestCase):
2084 """
2085 Ordering and reentrancy tests for C{reactor.callWhenRunning} and reactor
2086 shutdown (see #3146 and #3168).
2087 """
2088 def setUp(self):
2089 """
2090 Clear the SIGCHLD handler, if there is one, to ensure an environment
2091 like the one which exists prior to a call to L{reactor.run}.
2092 """
2093 self.originalHandler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
2094 self.processTransports = []
2095
2096
2097 def tearDown(self):
2098 """
2099 Restore the original SIGCHLD handler and reap processes as long as
2100 there seem to be any remaining.
2101 """
2102 signal.signal(signal.SIGCHLD, signal.SIG_DFL)
2103 while self.processTransports:
2104 transport = self.processTransports.pop()
2105 if transport.pid is not None:
2106 os.waitpid(transport.pid, 0)
2107 signal.signal(signal.SIGCHLD, self.originalHandler)
2108
2109
2110 def unbuildReactor(self, reactor):
2111 """
2112 Clean up any resources which may have been allocated for the given
2113 reactor by its creation or by a test which used it.
2114 """
2115 # Chris says:
2116 #
2117 # XXX This explicit calls to clean up the waker should become obsolete
2118 # when bug #3063 is fixed. -radix, 2008-02-29. Fortunately it should
2119 # probably cause an error when bug #3063 is fixed, so it should be
2120 # removed in the same branch that fixes it.
2121 #
2122 # -exarkun
2123 reactor.removeReader(reactor.waker)
2124 reactor.waker.connectionLost(None)
2125
2126 # Here's an extra thing unrelated to wakers but necessary for
2127 # cleaning up after the reactors we make. -exarkun
2128 reactor.disconnectAll()
2129
2130
2131 def buildReactor(self):
2132 """
2133 Create and return an instance of L{selectreactor.SelectReactor}.
2134 """
2135 reactor = selectreactor.SelectReactor()
2136 self.addCleanup(self.unbuildReactor, reactor)
2137 return reactor
2138
2139
2140 def spawnProcess(self, reactor):
2141 """
2142 Call C{reactor.spawnProcess} with some simple arguments. Do this here
2143 so that code object referenced by the stack frame has a C{co_filename}
2144 attribute set to this file so that L{TestCase.assertWarns} can be used.
2145 """
2146 self.processTransports.append(
2147 reactor.spawnProcess(
2148 protocol.ProcessProtocol(), sys.executable,
2149 [sys.executable, "-c", ""]))
2150
2151
2152 def test_spawnProcessTooEarlyWarns(self):
2153 """
2154 C{reactor.spawnProcess} emits a warning if it is called before
2155 C{reactor.run}.
2156
2157 If you can figure out a way to make it safe to run
2158 C{reactor.spawnProcess} before C{reactor.run}, you may delete the
2159 warning and this test.
2160 """
2161 reactor = self.buildReactor()
2162 self.assertWarns(
2163 error.PotentialZombieWarning,
2164 error.PotentialZombieWarning.MESSAGE, __file__,
2165 self.spawnProcess, reactor)
2166
2167
2168 def test_callWhenRunningSpawnProcessWarningFree(self):
2169 """
2170 L{PotentialZombieWarning} is not emitted when the reactor is run after
2171 C{reactor.callWhenRunning(reactor.spawnProcess, ...)} has been called.
2172 """
2173 events = []
2174 self.patch(warnings, 'warn', lambda *a, **kw: events.append(a))
2175 reactor = self.buildReactor()
2176 reactor.callWhenRunning(self.spawnProcess, reactor)
2177 reactor.callWhenRunning(reactor.stop)
2178 reactor.run()
2179 self.assertFalse(events)
2180
2181
2182 def test_clientConnectionFailedStopsReactor(self):
2183 """
2184 The reactor can be stopped by a client factory's
2185 C{clientConnectionFailed} method.
2186
2187 This isn't really a process test but it's here for simplicity of
2188 implementation and it won't be very long lived.
2189 """
2190 class Stop(protocol.ClientFactory):
2191 def clientConnectionFailed(self, connector, reason):
2192 reactor.stop()
2193 probe = socket.socket()
2194 probe.bind(('', 0))
2195 host, port = probe.getsockname()
2196 probe.close()
2197 reactor = self.buildReactor()
2198 reactor.connectTCP(host, port, Stop())
2199 reactor.run()
2200
2201
2202 def test_shutdownTriggersRun(self):
2203 """
2204 C{reactor.run()} does not return until shutdown triggers have all run.
2205 """
2206 events = []
2207 reactor = self.buildReactor()
2208 reactor.addSystemEventTrigger(
2209 'after', 'shutdown', events.append, "done")
2210 reactor.callWhenRunning(reactor.stop)
2211 reactor.run()
2212 self.assertEqual(events, ["done"])
2213
2214
2215
2216 skipMessage = "wrong platform or reactor doesn't support IReactorProcess"
2217 if (runtime.platform.getType() != 'posix') or (not interfaces.IReactorProcess(re actor, None)):
2218 PosixProcessTestCase.skip = skipMessage
2219 PosixProcessTestCasePTY.skip = skipMessage
2220 TestTwoProcessesPosix.skip = skipMessage
2221 FDTest.skip = skipMessage
2222 else:
2223 # do this before running the tests: it uses SIGCHLD and stuff internally
2224 lsOut = popen2.popen3("/bin/ls ZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ")[2].read()
2225
2226 if (runtime.platform.getType() != 'win32') or (not interfaces.IReactorProcess(re actor, None)):
2227 Win32ProcessTestCase.skip = skipMessage
2228 TestTwoProcessesNonPosix.skip = skipMessage
2229 Dumbwin32procPidTest.skip = skipMessage
2230
2231 if not interfaces.IReactorProcess(reactor, None):
2232 ProcessTestCase.skip = skipMessage
2233 ClosingPipes.skip = skipMessage
2234
2235 if process is None:
2236 MockProcessTestCase.skip = skipMessage
2237 SystemEventOrderRegressionTests.skip = skipMessage
OLDNEW
« no previous file with comments | « third_party/twisted_8_1/twisted/test/test_postfix.py ('k') | third_party/twisted_8_1/twisted/test/test_protocols.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698