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.

utils.py 8.5KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. # -*- test-case-name: twisted.test.test_iutils -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Utility methods.
  6. """
  7. import sys
  8. import warnings
  9. from functools import wraps
  10. from io import BytesIO
  11. from twisted.internet import defer, protocol
  12. from twisted.python import failure
  13. def _callProtocolWithDeferred(
  14. protocol, executable, args, env, path, reactor=None, protoArgs=()
  15. ):
  16. if reactor is None:
  17. from twisted.internet import reactor
  18. d = defer.Deferred()
  19. p = protocol(d, *protoArgs)
  20. reactor.spawnProcess(p, executable, (executable,) + tuple(args), env, path)
  21. return d
  22. class _UnexpectedErrorOutput(IOError):
  23. """
  24. Standard error data was received where it was not expected. This is a
  25. subclass of L{IOError} to preserve backward compatibility with the previous
  26. error behavior of L{getProcessOutput}.
  27. @ivar processEnded: A L{Deferred} which will fire when the process which
  28. produced the data on stderr has ended (exited and all file descriptors
  29. closed).
  30. """
  31. def __init__(self, text, processEnded):
  32. IOError.__init__(self, f"got stderr: {text!r}")
  33. self.processEnded = processEnded
  34. class _BackRelay(protocol.ProcessProtocol):
  35. """
  36. Trivial protocol for communicating with a process and turning its output
  37. into the result of a L{Deferred}.
  38. @ivar deferred: A L{Deferred} which will be called back with all of stdout
  39. and, if C{errortoo} is true, all of stderr as well (mixed together in
  40. one string). If C{errortoo} is false and any bytes are received over
  41. stderr, this will fire with an L{_UnexpectedErrorOutput} instance and
  42. the attribute will be set to L{None}.
  43. @ivar onProcessEnded: If C{errortoo} is false and bytes are received over
  44. stderr, this attribute will refer to a L{Deferred} which will be called
  45. back when the process ends. This C{Deferred} is also associated with
  46. the L{_UnexpectedErrorOutput} which C{deferred} fires with earlier in
  47. this case so that users can determine when the process has actually
  48. ended, in addition to knowing when bytes have been received via stderr.
  49. """
  50. def __init__(self, deferred, errortoo=0):
  51. self.deferred = deferred
  52. self.s = BytesIO()
  53. if errortoo:
  54. self.errReceived = self.errReceivedIsGood
  55. else:
  56. self.errReceived = self.errReceivedIsBad
  57. def errReceivedIsBad(self, text):
  58. if self.deferred is not None:
  59. self.onProcessEnded = defer.Deferred()
  60. err = _UnexpectedErrorOutput(text, self.onProcessEnded)
  61. self.deferred.errback(failure.Failure(err))
  62. self.deferred = None
  63. self.transport.loseConnection()
  64. def errReceivedIsGood(self, text):
  65. self.s.write(text)
  66. def outReceived(self, text):
  67. self.s.write(text)
  68. def processEnded(self, reason):
  69. if self.deferred is not None:
  70. self.deferred.callback(self.s.getvalue())
  71. elif self.onProcessEnded is not None:
  72. self.onProcessEnded.errback(reason)
  73. def getProcessOutput(executable, args=(), env={}, path=None, reactor=None, errortoo=0):
  74. """
  75. Spawn a process and return its output as a deferred returning a L{bytes}.
  76. @param executable: The file name to run and get the output of - the
  77. full path should be used.
  78. @param args: the command line arguments to pass to the process; a
  79. sequence of strings. The first string should B{NOT} be the
  80. executable's name.
  81. @param env: the environment variables to pass to the process; a
  82. dictionary of strings.
  83. @param path: the path to run the subprocess in - defaults to the
  84. current directory.
  85. @param reactor: the reactor to use - defaults to the default reactor
  86. @param errortoo: If true, include stderr in the result. If false, if
  87. stderr is received the returned L{Deferred} will errback with an
  88. L{IOError} instance with a C{processEnded} attribute. The
  89. C{processEnded} attribute refers to a L{Deferred} which fires when the
  90. executed process ends.
  91. """
  92. return _callProtocolWithDeferred(
  93. lambda d: _BackRelay(d, errortoo=errortoo), executable, args, env, path, reactor
  94. )
  95. class _ValueGetter(protocol.ProcessProtocol):
  96. def __init__(self, deferred):
  97. self.deferred = deferred
  98. def processEnded(self, reason):
  99. self.deferred.callback(reason.value.exitCode)
  100. def getProcessValue(executable, args=(), env={}, path=None, reactor=None):
  101. """Spawn a process and return its exit code as a Deferred."""
  102. return _callProtocolWithDeferred(_ValueGetter, executable, args, env, path, reactor)
  103. class _EverythingGetter(protocol.ProcessProtocol):
  104. def __init__(self, deferred, stdinBytes=None):
  105. self.deferred = deferred
  106. self.outBuf = BytesIO()
  107. self.errBuf = BytesIO()
  108. self.outReceived = self.outBuf.write
  109. self.errReceived = self.errBuf.write
  110. self.stdinBytes = stdinBytes
  111. def connectionMade(self):
  112. if self.stdinBytes is not None:
  113. self.transport.writeToChild(0, self.stdinBytes)
  114. # The only compelling reason not to _always_ close stdin here is
  115. # backwards compatibility.
  116. self.transport.closeStdin()
  117. def processEnded(self, reason):
  118. out = self.outBuf.getvalue()
  119. err = self.errBuf.getvalue()
  120. e = reason.value
  121. code = e.exitCode
  122. if e.signal:
  123. self.deferred.errback((out, err, e.signal))
  124. else:
  125. self.deferred.callback((out, err, code))
  126. def getProcessOutputAndValue(
  127. executable, args=(), env={}, path=None, reactor=None, stdinBytes=None
  128. ):
  129. """Spawn a process and returns a Deferred that will be called back with
  130. its output (from stdout and stderr) and it's exit code as (out, err, code)
  131. If a signal is raised, the Deferred will errback with the stdout and
  132. stderr up to that point, along with the signal, as (out, err, signalNum)
  133. """
  134. return _callProtocolWithDeferred(
  135. _EverythingGetter,
  136. executable,
  137. args,
  138. env,
  139. path,
  140. reactor,
  141. protoArgs=(stdinBytes,),
  142. )
  143. def _resetWarningFilters(passthrough, addedFilters):
  144. for f in addedFilters:
  145. try:
  146. warnings.filters.remove(f)
  147. except ValueError:
  148. pass
  149. return passthrough
  150. def runWithWarningsSuppressed(suppressedWarnings, f, *a, **kw):
  151. """
  152. Run the function I{f}, but with some warnings suppressed.
  153. This calls L{warnings.filterwarnings} to add warning filters before
  154. invoking I{f}. If I{f} returns a L{Deferred} then the added filters are
  155. removed once the deferred fires. Otherwise they are removed immediately.
  156. Note that the list of warning filters is a process-wide resource, so
  157. calling this function will affect all threads.
  158. @param suppressedWarnings:
  159. A list of arguments to pass to L{warnings.filterwarnings}, a sequence
  160. of (args, kwargs) 2-tuples.
  161. @param f: A callable, which may return a L{Deferred}.
  162. @param a: Positional arguments passed to I{f}
  163. @param kw: Keyword arguments passed to I{f}
  164. @return: The result of C{f(*a, **kw)}
  165. @seealso: L{twisted.python.util.runWithWarningsSuppressed}
  166. functions similarly, but doesn't handled L{Deferred}s.
  167. """
  168. for args, kwargs in suppressedWarnings:
  169. warnings.filterwarnings(*args, **kwargs)
  170. addedFilters = warnings.filters[: len(suppressedWarnings)]
  171. try:
  172. result = f(*a, **kw)
  173. except BaseException:
  174. exc_info = sys.exc_info()
  175. _resetWarningFilters(None, addedFilters)
  176. raise exc_info[1].with_traceback(exc_info[2])
  177. else:
  178. if isinstance(result, defer.Deferred):
  179. result.addBoth(_resetWarningFilters, addedFilters)
  180. else:
  181. _resetWarningFilters(None, addedFilters)
  182. return result
  183. def suppressWarnings(f, *suppressedWarnings):
  184. """
  185. Wrap C{f} in a callable which suppresses the indicated warnings before
  186. invoking C{f} and unsuppresses them afterwards. If f returns a Deferred,
  187. warnings will remain suppressed until the Deferred fires.
  188. """
  189. @wraps(f)
  190. def warningSuppressingWrapper(*a, **kw):
  191. return runWithWarningsSuppressed(suppressedWarnings, f, *a, **kw)
  192. return warningSuppressingWrapper
  193. __all__ = [
  194. "runWithWarningsSuppressed",
  195. "suppressWarnings",
  196. "getProcessOutput",
  197. "getProcessValue",
  198. "getProcessOutputAndValue",
  199. ]