OLD | NEW |
| (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 | |
OLD | NEW |