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