Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_process.py 85KB

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