test_posixprocess.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Tests for POSIX-based L{IReactorProcess} implementations.
  5. """
  6. from __future__ import division, absolute_import
  7. import errno, os, sys
  8. try:
  9. import fcntl
  10. except ImportError:
  11. platformSkip = "non-POSIX platform"
  12. else:
  13. from twisted.internet import process
  14. platformSkip = None
  15. from twisted.python.compat import range
  16. from twisted.trial.unittest import TestCase
  17. class FakeFile(object):
  18. """
  19. A dummy file object which records when it is closed.
  20. """
  21. def __init__(self, testcase, fd):
  22. self.testcase = testcase
  23. self.fd = fd
  24. def close(self):
  25. self.testcase._files.remove(self.fd)
  26. def __enter__(self):
  27. return self
  28. def __exit__(self, exc_type, exc_value, traceback):
  29. self.close()
  30. class FakeResourceModule(object):
  31. """
  32. Fake version of L{resource} which hard-codes a particular rlimit for maximum
  33. open files.
  34. @ivar _limit: The value to return for the hard limit of number of open files.
  35. """
  36. RLIMIT_NOFILE = 1
  37. def __init__(self, limit):
  38. self._limit = limit
  39. def getrlimit(self, no):
  40. """
  41. A fake of L{resource.getrlimit} which returns a pre-determined result.
  42. """
  43. if no == self.RLIMIT_NOFILE:
  44. return [0, self._limit]
  45. return [123, 456]
  46. class FDDetectorTests(TestCase):
  47. """
  48. Tests for _FDDetector class in twisted.internet.process, which detects
  49. which function to drop in place for the _listOpenFDs method.
  50. @ivar devfs: A flag indicating whether the filesystem fake will indicate
  51. that /dev/fd exists.
  52. @ivar accurateDevFDResults: A flag indicating whether the /dev/fd fake
  53. returns accurate open file information.
  54. @ivar procfs: A flag indicating whether the filesystem fake will indicate
  55. that /proc/<pid>/fd exists.
  56. """
  57. skip = platformSkip
  58. devfs = False
  59. accurateDevFDResults = False
  60. procfs = False
  61. def getpid(self):
  62. """
  63. Fake os.getpid, always return the same thing
  64. """
  65. return 123
  66. def listdir(self, arg):
  67. """
  68. Fake os.listdir, depending on what mode we're in to simulate behaviour.
  69. @param arg: the directory to list
  70. """
  71. accurate = map(str, self._files)
  72. if self.procfs and arg == ('/proc/%d/fd' % (self.getpid(),)):
  73. return accurate
  74. if self.devfs and arg == '/dev/fd':
  75. if self.accurateDevFDResults:
  76. return accurate
  77. return ["0", "1", "2"]
  78. raise OSError()
  79. def openfile(self, fname, mode):
  80. """
  81. This is a mock for L{open}. It keeps track of opened files so extra
  82. descriptors can be returned from the mock for L{os.listdir} when used on
  83. one of the list-of-filedescriptors directories.
  84. A L{FakeFile} is returned which can be closed to remove the new
  85. descriptor from the open list.
  86. """
  87. # Find the smallest unused file descriptor and give it to the new file.
  88. f = FakeFile(self, min(set(range(1024)) - set(self._files)))
  89. self._files.append(f.fd)
  90. return f
  91. def hideResourceModule(self):
  92. """
  93. Make the L{resource} module unimportable for the remainder of the
  94. current test method.
  95. """
  96. sys.modules['resource'] = None
  97. def revealResourceModule(self, limit):
  98. """
  99. Make a L{FakeResourceModule} instance importable at the L{resource}
  100. name.
  101. @param limit: The value which will be returned for the hard limit of
  102. number of open files by the fake resource module's C{getrlimit}
  103. function.
  104. """
  105. sys.modules['resource'] = FakeResourceModule(limit)
  106. def replaceResourceModule(self, value):
  107. """
  108. Restore the original resource module to L{sys.modules}.
  109. """
  110. if value is None:
  111. try:
  112. del sys.modules['resource']
  113. except KeyError:
  114. pass
  115. else:
  116. sys.modules['resource'] = value
  117. def setUp(self):
  118. """
  119. Set up the tests, giving ourselves a detector object to play with and
  120. setting up its testable knobs to refer to our mocked versions.
  121. """
  122. self.detector = process._FDDetector()
  123. self.detector.listdir = self.listdir
  124. self.detector.getpid = self.getpid
  125. self.detector.openfile = self.openfile
  126. self._files = [0, 1, 2]
  127. self.addCleanup(
  128. self.replaceResourceModule, sys.modules.get('resource'))
  129. def test_selectFirstWorking(self):
  130. """
  131. L{FDDetector._getImplementation} returns the first method from its
  132. C{_implementations} list which returns results which reflect a newly
  133. opened file descriptor.
  134. """
  135. def failWithException():
  136. raise ValueError("This does not work")
  137. def failWithWrongResults():
  138. return [0, 1, 2]
  139. def correct():
  140. return self._files[:]
  141. self.detector._implementations = [
  142. failWithException, failWithWrongResults, correct]
  143. self.assertIs(correct, self.detector._getImplementation())
  144. def test_selectLast(self):
  145. """
  146. L{FDDetector._getImplementation} returns the last method from its
  147. C{_implementations} list if none of the implementations manage to return
  148. results which reflect a newly opened file descriptor.
  149. """
  150. def failWithWrongResults():
  151. return [3, 5, 9]
  152. def failWithOtherWrongResults():
  153. return [0, 1, 2]
  154. self.detector._implementations = [
  155. failWithWrongResults, failWithOtherWrongResults]
  156. self.assertIs(
  157. failWithOtherWrongResults, self.detector._getImplementation())
  158. def test_identityOfListOpenFDsChanges(self):
  159. """
  160. Check that the identity of _listOpenFDs changes after running
  161. _listOpenFDs the first time, but not after the second time it's run.
  162. In other words, check that the monkey patching actually works.
  163. """
  164. # Create a new instance
  165. detector = process._FDDetector()
  166. first = detector._listOpenFDs.__name__
  167. detector._listOpenFDs()
  168. second = detector._listOpenFDs.__name__
  169. detector._listOpenFDs()
  170. third = detector._listOpenFDs.__name__
  171. self.assertNotEqual(first, second)
  172. self.assertEqual(second, third)
  173. def test_devFDImplementation(self):
  174. """
  175. L{_FDDetector._devFDImplementation} raises L{OSError} if there is no
  176. I{/dev/fd} directory, otherwise it returns the basenames of its children
  177. interpreted as integers.
  178. """
  179. self.devfs = False
  180. self.assertRaises(OSError, self.detector._devFDImplementation)
  181. self.devfs = True
  182. self.accurateDevFDResults = False
  183. self.assertEqual([0, 1, 2], self.detector._devFDImplementation())
  184. def test_procFDImplementation(self):
  185. """
  186. L{_FDDetector._procFDImplementation} raises L{OSError} if there is no
  187. I{/proc/<pid>/fd} directory, otherwise it returns the basenames of its
  188. children interpreted as integers.
  189. """
  190. self.procfs = False
  191. self.assertRaises(OSError, self.detector._procFDImplementation)
  192. self.procfs = True
  193. self.assertEqual([0, 1, 2], self.detector._procFDImplementation())
  194. def test_resourceFDImplementation(self):
  195. """
  196. L{_FDDetector._fallbackFDImplementation} uses the L{resource} module if
  197. it is available, returning a range of integers from 0 to the
  198. minimum of C{1024} and the hard I{NOFILE} limit.
  199. """
  200. # When the resource module is here, use its value.
  201. self.revealResourceModule(512)
  202. self.assertEqual(
  203. list(range(512)), list(self.detector._fallbackFDImplementation()))
  204. # But limit its value to the arbitrarily selected value 1024.
  205. self.revealResourceModule(2048)
  206. self.assertEqual(
  207. list(range(1024)), list(self.detector._fallbackFDImplementation()))
  208. def test_fallbackFDImplementation(self):
  209. """
  210. L{_FDDetector._fallbackFDImplementation}, the implementation of last
  211. resort, succeeds with a fixed range of integers from 0 to 1024 when the
  212. L{resource} module is not importable.
  213. """
  214. self.hideResourceModule()
  215. self.assertEqual(list(range(1024)),
  216. list(self.detector._fallbackFDImplementation()))
  217. class FileDescriptorTests(TestCase):
  218. """
  219. Tests for L{twisted.internet.process._listOpenFDs}
  220. """
  221. skip = platformSkip
  222. def test_openFDs(self):
  223. """
  224. File descriptors returned by L{_listOpenFDs} are mostly open.
  225. This test assumes that zero-legth writes fail with EBADF on closed
  226. file descriptors.
  227. """
  228. for fd in process._listOpenFDs():
  229. try:
  230. fcntl.fcntl(fd, fcntl.F_GETFL)
  231. except IOError as err:
  232. self.assertEqual(
  233. errno.EBADF, err.errno,
  234. "fcntl(%d, F_GETFL) failed with unexpected errno %d" % (
  235. fd, err.errno))
  236. def test_expectedFDs(self):
  237. """
  238. L{_listOpenFDs} lists expected file descriptors.
  239. """
  240. # This is a tricky test. A priori, there is no way to know what file
  241. # descriptors are open now, so there is no way to know what _listOpenFDs
  242. # should return. Work around this by creating some new file descriptors
  243. # which we can know the state of and then just making assertions about
  244. # their presence or absence in the result.
  245. # Expect a file we just opened to be listed.
  246. f = open(os.devnull)
  247. openfds = process._listOpenFDs()
  248. self.assertIn(f.fileno(), openfds)
  249. # Expect a file we just closed not to be listed - with a caveat. The
  250. # implementation may need to open a file to discover the result. That
  251. # open file descriptor will be allocated the same number as the one we
  252. # just closed. So, instead, create a hole in the file descriptor space
  253. # to catch that internal descriptor and make the assertion about a
  254. # different closed file descriptor.
  255. # This gets allocated a file descriptor larger than f's, since nothing
  256. # has been closed since we opened f.
  257. fd = os.dup(f.fileno())
  258. # But sanity check that; if it fails the test is invalid.
  259. self.assertTrue(
  260. fd > f.fileno(),
  261. "Expected duplicate file descriptor to be greater than original")
  262. try:
  263. # Get rid of the original, creating the hole. The copy should still
  264. # be open, of course.
  265. f.close()
  266. self.assertIn(fd, process._listOpenFDs())
  267. finally:
  268. # Get rid of the copy now
  269. os.close(fd)
  270. # And it should not appear in the result.
  271. self.assertNotIn(fd, process._listOpenFDs())