test_unix.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Tests for implementations of L{IReactorUNIX} and L{IReactorUNIXDatagram}.
  5. """
  6. from __future__ import division, absolute_import
  7. import os
  8. import sys
  9. import types
  10. import socket
  11. from twisted.internet import interfaces, reactor, protocol, error, address
  12. from twisted.internet import defer, utils
  13. from twisted.python import lockfile
  14. from twisted.python.compat import _PY3, networkString
  15. from twisted.python.filepath import FilePath
  16. from twisted.trial import unittest
  17. from twisted.test.test_tcp import MyServerFactory, MyClientFactory
  18. class FailedConnectionClientFactory(protocol.ClientFactory):
  19. def __init__(self, onFail):
  20. self.onFail = onFail
  21. def clientConnectionFailed(self, connector, reason):
  22. self.onFail.errback(reason)
  23. class UnixSocketTests(unittest.TestCase):
  24. """
  25. Test unix sockets.
  26. """
  27. def test_peerBind(self):
  28. """
  29. The address passed to the server factory's C{buildProtocol} method and
  30. the address returned by the connected protocol's transport's C{getPeer}
  31. method match the address the client socket is bound to.
  32. """
  33. filename = self.mktemp()
  34. peername = self.mktemp()
  35. serverFactory = MyServerFactory()
  36. connMade = serverFactory.protocolConnectionMade = defer.Deferred()
  37. unixPort = reactor.listenUNIX(filename, serverFactory)
  38. self.addCleanup(unixPort.stopListening)
  39. unixSocket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  40. self.addCleanup(unixSocket.close)
  41. unixSocket.bind(peername)
  42. unixSocket.connect(filename)
  43. def cbConnMade(proto):
  44. expected = address.UNIXAddress(peername)
  45. self.assertEqual(serverFactory.peerAddresses, [expected])
  46. self.assertEqual(proto.transport.getPeer(), expected)
  47. connMade.addCallback(cbConnMade)
  48. return connMade
  49. def test_dumber(self):
  50. """
  51. L{IReactorUNIX.connectUNIX} can be used to connect a client to a server
  52. started with L{IReactorUNIX.listenUNIX}.
  53. """
  54. filename = self.mktemp()
  55. serverFactory = MyServerFactory()
  56. serverConnMade = defer.Deferred()
  57. serverFactory.protocolConnectionMade = serverConnMade
  58. unixPort = reactor.listenUNIX(filename, serverFactory)
  59. self.addCleanup(unixPort.stopListening)
  60. clientFactory = MyClientFactory()
  61. clientConnMade = defer.Deferred()
  62. clientFactory.protocolConnectionMade = clientConnMade
  63. reactor.connectUNIX(filename, clientFactory)
  64. d = defer.gatherResults([serverConnMade, clientConnMade])
  65. def allConnected(args):
  66. serverProtocol, clientProtocol = args
  67. # Incidental assertion which may or may not be redundant with some
  68. # other test. This probably deserves its own test method.
  69. self.assertEqual(clientFactory.peerAddresses,
  70. [address.UNIXAddress(filename)])
  71. clientProtocol.transport.loseConnection()
  72. serverProtocol.transport.loseConnection()
  73. d.addCallback(allConnected)
  74. return d
  75. def test_pidFile(self):
  76. """
  77. A lockfile is created and locked when L{IReactorUNIX.listenUNIX} is
  78. called and released when the Deferred returned by the L{IListeningPort}
  79. provider's C{stopListening} method is called back.
  80. """
  81. filename = self.mktemp()
  82. serverFactory = MyServerFactory()
  83. serverConnMade = defer.Deferred()
  84. serverFactory.protocolConnectionMade = serverConnMade
  85. unixPort = reactor.listenUNIX(filename, serverFactory, wantPID=True)
  86. self.assertTrue(lockfile.isLocked(filename + ".lock"))
  87. # XXX This part would test something about the checkPID parameter, but
  88. # it doesn't actually. It should be rewritten to test the several
  89. # different possible behaviors. -exarkun
  90. clientFactory = MyClientFactory()
  91. clientConnMade = defer.Deferred()
  92. clientFactory.protocolConnectionMade = clientConnMade
  93. reactor.connectUNIX(filename, clientFactory, checkPID=1)
  94. d = defer.gatherResults([serverConnMade, clientConnMade])
  95. def _portStuff(args):
  96. serverProtocol, clientProto = args
  97. # Incidental assertion which may or may not be redundant with some
  98. # other test. This probably deserves its own test method.
  99. self.assertEqual(clientFactory.peerAddresses,
  100. [address.UNIXAddress(filename)])
  101. clientProto.transport.loseConnection()
  102. serverProtocol.transport.loseConnection()
  103. return unixPort.stopListening()
  104. d.addCallback(_portStuff)
  105. def _check(ignored):
  106. self.assertFalse(lockfile.isLocked(filename + ".lock"), 'locked')
  107. d.addCallback(_check)
  108. return d
  109. def test_socketLocking(self):
  110. """
  111. L{IReactorUNIX.listenUNIX} raises L{error.CannotListenError} if passed
  112. the name of a file on which a server is already listening.
  113. """
  114. filename = self.mktemp()
  115. serverFactory = MyServerFactory()
  116. unixPort = reactor.listenUNIX(filename, serverFactory, wantPID=True)
  117. self.assertRaises(
  118. error.CannotListenError,
  119. reactor.listenUNIX, filename, serverFactory, wantPID=True)
  120. def stoppedListening(ign):
  121. unixPort = reactor.listenUNIX(filename, serverFactory, wantPID=True)
  122. return unixPort.stopListening()
  123. return unixPort.stopListening().addCallback(stoppedListening)
  124. def _uncleanSocketTest(self, callback):
  125. self.filename = self.mktemp()
  126. source = networkString((
  127. "from twisted.internet import protocol, reactor\n"
  128. "reactor.listenUNIX(%r, protocol.ServerFactory(),"
  129. "wantPID=True)\n") % (self.filename,))
  130. env = {b'PYTHONPATH': FilePath(
  131. os.pathsep.join(sys.path)).asBytesMode().path}
  132. pyExe = FilePath(sys.executable).asBytesMode().path
  133. d = utils.getProcessValue(pyExe, (b"-u", b"-c", source), env=env)
  134. d.addCallback(callback)
  135. return d
  136. def test_uncleanServerSocketLocking(self):
  137. """
  138. If passed C{True} for the C{wantPID} parameter, a server can be started
  139. listening with L{IReactorUNIX.listenUNIX} when passed the name of a
  140. file on which a previous server which has not exited cleanly has been
  141. listening using the C{wantPID} option.
  142. """
  143. def ranStupidChild(ign):
  144. # If this next call succeeds, our lock handling is correct.
  145. p = reactor.listenUNIX(self.filename, MyServerFactory(), wantPID=True)
  146. return p.stopListening()
  147. return self._uncleanSocketTest(ranStupidChild)
  148. def test_connectToUncleanServer(self):
  149. """
  150. If passed C{True} for the C{checkPID} parameter, a client connection
  151. attempt made with L{IReactorUNIX.connectUNIX} fails with
  152. L{error.BadFileError}.
  153. """
  154. def ranStupidChild(ign):
  155. d = defer.Deferred()
  156. f = FailedConnectionClientFactory(d)
  157. reactor.connectUNIX(self.filename, f, checkPID=True)
  158. return self.assertFailure(d, error.BadFileError)
  159. return self._uncleanSocketTest(ranStupidChild)
  160. def _reprTest(self, serverFactory, factoryName):
  161. """
  162. Test the C{__str__} and C{__repr__} implementations of a UNIX port when
  163. used with the given factory.
  164. """
  165. filename = self.mktemp()
  166. unixPort = reactor.listenUNIX(filename, serverFactory)
  167. connectedString = "<%s on %r>" % (factoryName, filename)
  168. self.assertEqual(repr(unixPort), connectedString)
  169. self.assertEqual(str(unixPort), connectedString)
  170. d = defer.maybeDeferred(unixPort.stopListening)
  171. def stoppedListening(ign):
  172. unconnectedString = "<%s (not listening)>" % (factoryName,)
  173. self.assertEqual(repr(unixPort), unconnectedString)
  174. self.assertEqual(str(unixPort), unconnectedString)
  175. d.addCallback(stoppedListening)
  176. return d
  177. def test_reprWithClassicFactory(self):
  178. """
  179. The two string representations of the L{IListeningPort} returned by
  180. L{IReactorUNIX.listenUNIX} contains the name of the classic factory
  181. class being used and the filename on which the port is listening or
  182. indicates that the port is not listening.
  183. """
  184. class ClassicFactory:
  185. def doStart(self):
  186. pass
  187. def doStop(self):
  188. pass
  189. # Sanity check
  190. self.assertIsInstance(ClassicFactory, types.ClassType)
  191. return self._reprTest(
  192. ClassicFactory(), "twisted.test.test_unix.ClassicFactory")
  193. if _PY3:
  194. test_reprWithClassicFactory.skip = (
  195. "Classic classes do not exist on Python 3.")
  196. def test_reprWithNewStyleFactory(self):
  197. """
  198. The two string representations of the L{IListeningPort} returned by
  199. L{IReactorUNIX.listenUNIX} contains the name of the new-style factory
  200. class being used and the filename on which the port is listening or
  201. indicates that the port is not listening.
  202. """
  203. class NewStyleFactory(object):
  204. def doStart(self):
  205. pass
  206. def doStop(self):
  207. pass
  208. # Sanity check
  209. self.assertIsInstance(NewStyleFactory, type)
  210. return self._reprTest(
  211. NewStyleFactory(), "twisted.test.test_unix.NewStyleFactory")
  212. class ClientProto(protocol.ConnectedDatagramProtocol):
  213. started = stopped = False
  214. gotback = None
  215. def __init__(self):
  216. self.deferredStarted = defer.Deferred()
  217. self.deferredGotBack = defer.Deferred()
  218. def stopProtocol(self):
  219. self.stopped = True
  220. def startProtocol(self):
  221. self.started = True
  222. self.deferredStarted.callback(None)
  223. def datagramReceived(self, data):
  224. self.gotback = data
  225. self.deferredGotBack.callback(None)
  226. class ServerProto(protocol.DatagramProtocol):
  227. started = stopped = False
  228. gotwhat = gotfrom = None
  229. def __init__(self):
  230. self.deferredStarted = defer.Deferred()
  231. self.deferredGotWhat = defer.Deferred()
  232. def stopProtocol(self):
  233. self.stopped = True
  234. def startProtocol(self):
  235. self.started = True
  236. self.deferredStarted.callback(None)
  237. def datagramReceived(self, data, addr):
  238. self.gotfrom = addr
  239. self.transport.write(b"hi back", addr)
  240. self.gotwhat = data
  241. self.deferredGotWhat.callback(None)
  242. class DatagramUnixSocketTests(unittest.TestCase):
  243. """
  244. Test datagram UNIX sockets.
  245. """
  246. def test_exchange(self):
  247. """
  248. Test that a datagram can be sent to and received by a server and vice
  249. versa.
  250. """
  251. clientaddr = self.mktemp()
  252. serveraddr = self.mktemp()
  253. sp = ServerProto()
  254. cp = ClientProto()
  255. s = reactor.listenUNIXDatagram(serveraddr, sp)
  256. self.addCleanup(s.stopListening)
  257. c = reactor.connectUNIXDatagram(serveraddr, cp, bindAddress=clientaddr)
  258. self.addCleanup(c.stopListening)
  259. d = defer.gatherResults([sp.deferredStarted, cp.deferredStarted])
  260. def write(ignored):
  261. cp.transport.write(b"hi")
  262. return defer.gatherResults([sp.deferredGotWhat,
  263. cp.deferredGotBack])
  264. def _cbTestExchange(ignored):
  265. self.assertEqual(b"hi", sp.gotwhat)
  266. self.assertEqual(clientaddr, sp.gotfrom)
  267. self.assertEqual(b"hi back", cp.gotback)
  268. d.addCallback(write)
  269. d.addCallback(_cbTestExchange)
  270. return d
  271. def test_cannotListen(self):
  272. """
  273. L{IReactorUNIXDatagram.listenUNIXDatagram} raises
  274. L{error.CannotListenError} if the unix socket specified is already in
  275. use.
  276. """
  277. addr = self.mktemp()
  278. p = ServerProto()
  279. s = reactor.listenUNIXDatagram(addr, p)
  280. self.assertRaises(error.CannotListenError,
  281. reactor.listenUNIXDatagram, addr, p)
  282. s.stopListening()
  283. os.unlink(addr)
  284. # test connecting to bound and connected (somewhere else) address
  285. def _reprTest(self, serverProto, protocolName):
  286. """
  287. Test the C{__str__} and C{__repr__} implementations of a UNIX datagram
  288. port when used with the given protocol.
  289. """
  290. filename = self.mktemp()
  291. unixPort = reactor.listenUNIXDatagram(filename, serverProto)
  292. connectedString = "<%s on %r>" % (protocolName, filename)
  293. self.assertEqual(repr(unixPort), connectedString)
  294. self.assertEqual(str(unixPort), connectedString)
  295. stopDeferred = defer.maybeDeferred(unixPort.stopListening)
  296. def stoppedListening(ign):
  297. unconnectedString = "<%s (not listening)>" % (protocolName,)
  298. self.assertEqual(repr(unixPort), unconnectedString)
  299. self.assertEqual(str(unixPort), unconnectedString)
  300. stopDeferred.addCallback(stoppedListening)
  301. return stopDeferred
  302. def test_reprWithClassicProtocol(self):
  303. """
  304. The two string representations of the L{IListeningPort} returned by
  305. L{IReactorUNIXDatagram.listenUNIXDatagram} contains the name of the
  306. classic protocol class being used and the filename on which the port is
  307. listening or indicates that the port is not listening.
  308. """
  309. class ClassicProtocol:
  310. def makeConnection(self, transport):
  311. pass
  312. def doStop(self):
  313. pass
  314. # Sanity check
  315. self.assertIsInstance(ClassicProtocol, types.ClassType)
  316. return self._reprTest(
  317. ClassicProtocol(), "twisted.test.test_unix.ClassicProtocol")
  318. if _PY3:
  319. test_reprWithClassicProtocol.skip = (
  320. "Classic classes do not exist on Python 3.")
  321. def test_reprWithNewStyleProtocol(self):
  322. """
  323. The two string representations of the L{IListeningPort} returned by
  324. L{IReactorUNIXDatagram.listenUNIXDatagram} contains the name of the
  325. new-style protocol class being used and the filename on which the port
  326. is listening or indicates that the port is not listening.
  327. """
  328. class NewStyleProtocol(object):
  329. def makeConnection(self, transport):
  330. pass
  331. def doStop(self):
  332. pass
  333. # Sanity check
  334. self.assertIsInstance(NewStyleProtocol, type)
  335. return self._reprTest(
  336. NewStyleProtocol(), "twisted.test.test_unix.NewStyleProtocol")
  337. if not interfaces.IReactorUNIX(reactor, None):
  338. UnixSocketTests.skip = "This reactor does not support UNIX domain sockets"
  339. if not interfaces.IReactorUNIXDatagram(reactor, None):
  340. DatagramUnixSocketTests.skip = "This reactor does not support UNIX datagram sockets"