PageRenderTime 26ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/Lib/test/test_smtplib.py

https://bitbucket.org/glix/python
Python | 412 lines | 302 code | 67 blank | 43 comment | 28 complexity | cdbafdd49e010981e01b2a09878bfcdb MD5 | raw file
  1. import asyncore
  2. import email.utils
  3. import socket
  4. import threading
  5. import smtpd
  6. import smtplib
  7. import StringIO
  8. import sys
  9. import time
  10. import select
  11. from unittest import TestCase
  12. from test import test_support
  13. HOST = test_support.HOST
  14. def server(evt, buf, serv):
  15. serv.listen(5)
  16. evt.set()
  17. try:
  18. conn, addr = serv.accept()
  19. except socket.timeout:
  20. pass
  21. else:
  22. n = 500
  23. while buf and n > 0:
  24. r, w, e = select.select([], [conn], [])
  25. if w:
  26. sent = conn.send(buf)
  27. buf = buf[sent:]
  28. n -= 1
  29. conn.close()
  30. finally:
  31. serv.close()
  32. evt.set()
  33. class GeneralTests(TestCase):
  34. def setUp(self):
  35. self.evt = threading.Event()
  36. self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  37. self.sock.settimeout(15)
  38. self.port = test_support.bind_port(self.sock)
  39. servargs = (self.evt, "220 Hola mundo\n", self.sock)
  40. threading.Thread(target=server, args=servargs).start()
  41. self.evt.wait()
  42. self.evt.clear()
  43. def tearDown(self):
  44. self.evt.wait()
  45. def testBasic1(self):
  46. # connects
  47. smtp = smtplib.SMTP(HOST, self.port)
  48. smtp.close()
  49. def testBasic2(self):
  50. # connects, include port in host name
  51. smtp = smtplib.SMTP("%s:%s" % (HOST, self.port))
  52. smtp.close()
  53. def testLocalHostName(self):
  54. # check that supplied local_hostname is used
  55. smtp = smtplib.SMTP(HOST, self.port, local_hostname="testhost")
  56. self.assertEqual(smtp.local_hostname, "testhost")
  57. smtp.close()
  58. def testTimeoutDefault(self):
  59. self.assertTrue(socket.getdefaulttimeout() is None)
  60. socket.setdefaulttimeout(30)
  61. try:
  62. smtp = smtplib.SMTP(HOST, self.port)
  63. finally:
  64. socket.setdefaulttimeout(None)
  65. self.assertEqual(smtp.sock.gettimeout(), 30)
  66. smtp.close()
  67. def testTimeoutNone(self):
  68. self.assertTrue(socket.getdefaulttimeout() is None)
  69. socket.setdefaulttimeout(30)
  70. try:
  71. smtp = smtplib.SMTP(HOST, self.port, timeout=None)
  72. finally:
  73. socket.setdefaulttimeout(None)
  74. self.assertTrue(smtp.sock.gettimeout() is None)
  75. smtp.close()
  76. def testTimeoutValue(self):
  77. smtp = smtplib.SMTP(HOST, self.port, timeout=30)
  78. self.assertEqual(smtp.sock.gettimeout(), 30)
  79. smtp.close()
  80. # Test server thread using the specified SMTP server class
  81. def debugging_server(serv, serv_evt, client_evt):
  82. serv_evt.set()
  83. try:
  84. if hasattr(select, 'poll'):
  85. poll_fun = asyncore.poll2
  86. else:
  87. poll_fun = asyncore.poll
  88. n = 1000
  89. while asyncore.socket_map and n > 0:
  90. poll_fun(0.01, asyncore.socket_map)
  91. # when the client conversation is finished, it will
  92. # set client_evt, and it's then ok to kill the server
  93. if client_evt.is_set():
  94. serv.close()
  95. break
  96. n -= 1
  97. except socket.timeout:
  98. pass
  99. finally:
  100. if not client_evt.is_set():
  101. # allow some time for the client to read the result
  102. time.sleep(0.5)
  103. serv.close()
  104. asyncore.close_all()
  105. serv_evt.set()
  106. MSG_BEGIN = '---------- MESSAGE FOLLOWS ----------\n'
  107. MSG_END = '------------ END MESSAGE ------------\n'
  108. # NOTE: Some SMTP objects in the tests below are created with a non-default
  109. # local_hostname argument to the constructor, since (on some systems) the FQDN
  110. # lookup caused by the default local_hostname sometimes takes so long that the
  111. # test server times out, causing the test to fail.
  112. # Test behavior of smtpd.DebuggingServer
  113. class DebuggingServerTests(TestCase):
  114. def setUp(self):
  115. # temporarily replace sys.stdout to capture DebuggingServer output
  116. self.old_stdout = sys.stdout
  117. self.output = StringIO.StringIO()
  118. sys.stdout = self.output
  119. self.serv_evt = threading.Event()
  120. self.client_evt = threading.Event()
  121. self.port = test_support.find_unused_port()
  122. self.serv = smtpd.DebuggingServer((HOST, self.port), ('nowhere', -1))
  123. serv_args = (self.serv, self.serv_evt, self.client_evt)
  124. threading.Thread(target=debugging_server, args=serv_args).start()
  125. # wait until server thread has assigned a port number
  126. self.serv_evt.wait()
  127. self.serv_evt.clear()
  128. def tearDown(self):
  129. # indicate that the client is finished
  130. self.client_evt.set()
  131. # wait for the server thread to terminate
  132. self.serv_evt.wait()
  133. # restore sys.stdout
  134. sys.stdout = self.old_stdout
  135. def testBasic(self):
  136. # connect
  137. smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
  138. smtp.quit()
  139. def testNOOP(self):
  140. smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
  141. expected = (250, 'Ok')
  142. self.assertEqual(smtp.noop(), expected)
  143. smtp.quit()
  144. def testRSET(self):
  145. smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
  146. expected = (250, 'Ok')
  147. self.assertEqual(smtp.rset(), expected)
  148. smtp.quit()
  149. def testNotImplemented(self):
  150. # EHLO isn't implemented in DebuggingServer
  151. smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
  152. expected = (502, 'Error: command "EHLO" not implemented')
  153. self.assertEqual(smtp.ehlo(), expected)
  154. smtp.quit()
  155. def testVRFY(self):
  156. # VRFY isn't implemented in DebuggingServer
  157. smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
  158. expected = (502, 'Error: command "VRFY" not implemented')
  159. self.assertEqual(smtp.vrfy('nobody@nowhere.com'), expected)
  160. self.assertEqual(smtp.verify('nobody@nowhere.com'), expected)
  161. smtp.quit()
  162. def testSecondHELO(self):
  163. # check that a second HELO returns a message that it's a duplicate
  164. # (this behavior is specific to smtpd.SMTPChannel)
  165. smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
  166. smtp.helo()
  167. expected = (503, 'Duplicate HELO/EHLO')
  168. self.assertEqual(smtp.helo(), expected)
  169. smtp.quit()
  170. def testHELP(self):
  171. smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
  172. self.assertEqual(smtp.help(), 'Error: command "HELP" not implemented')
  173. smtp.quit()
  174. def testSend(self):
  175. # connect and send mail
  176. m = 'A test message'
  177. smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
  178. smtp.sendmail('John', 'Sally', m)
  179. # XXX(nnorwitz): this test is flaky and dies with a bad file descriptor
  180. # in asyncore. This sleep might help, but should really be fixed
  181. # properly by using an Event variable.
  182. time.sleep(0.01)
  183. smtp.quit()
  184. self.client_evt.set()
  185. self.serv_evt.wait()
  186. self.output.flush()
  187. mexpect = '%s%s\n%s' % (MSG_BEGIN, m, MSG_END)
  188. self.assertEqual(self.output.getvalue(), mexpect)
  189. class NonConnectingTests(TestCase):
  190. def testNotConnected(self):
  191. # Test various operations on an unconnected SMTP object that
  192. # should raise exceptions (at present the attempt in SMTP.send
  193. # to reference the nonexistent 'sock' attribute of the SMTP object
  194. # causes an AttributeError)
  195. smtp = smtplib.SMTP()
  196. self.assertRaises(smtplib.SMTPServerDisconnected, smtp.ehlo)
  197. self.assertRaises(smtplib.SMTPServerDisconnected,
  198. smtp.send, 'test msg')
  199. def testNonnumericPort(self):
  200. # check that non-numeric port raises socket.error
  201. self.assertRaises(socket.error, smtplib.SMTP,
  202. "localhost", "bogus")
  203. self.assertRaises(socket.error, smtplib.SMTP,
  204. "localhost:bogus")
  205. # test response of client to a non-successful HELO message
  206. class BadHELOServerTests(TestCase):
  207. def setUp(self):
  208. self.old_stdout = sys.stdout
  209. self.output = StringIO.StringIO()
  210. sys.stdout = self.output
  211. self.evt = threading.Event()
  212. self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  213. self.sock.settimeout(15)
  214. self.port = test_support.bind_port(self.sock)
  215. servargs = (self.evt, "199 no hello for you!\n", self.sock)
  216. threading.Thread(target=server, args=servargs).start()
  217. self.evt.wait()
  218. self.evt.clear()
  219. def tearDown(self):
  220. self.evt.wait()
  221. sys.stdout = self.old_stdout
  222. def testFailingHELO(self):
  223. self.assertRaises(smtplib.SMTPConnectError, smtplib.SMTP,
  224. HOST, self.port, 'localhost', 3)
  225. sim_users = {'Mr.A@somewhere.com':'John A',
  226. 'Ms.B@somewhere.com':'Sally B',
  227. 'Mrs.C@somewhereesle.com':'Ruth C',
  228. }
  229. sim_lists = {'list-1':['Mr.A@somewhere.com','Mrs.C@somewhereesle.com'],
  230. 'list-2':['Ms.B@somewhere.com',],
  231. }
  232. # Simulated SMTP channel & server
  233. class SimSMTPChannel(smtpd.SMTPChannel):
  234. def smtp_EHLO(self, arg):
  235. resp = '250-testhost\r\n' \
  236. '250-EXPN\r\n' \
  237. '250-SIZE 20000000\r\n' \
  238. '250-STARTTLS\r\n' \
  239. '250-DELIVERBY\r\n' \
  240. '250 HELP'
  241. self.push(resp)
  242. def smtp_VRFY(self, arg):
  243. # print '\nsmtp_VRFY(%r)\n' % arg
  244. raw_addr = email.utils.parseaddr(arg)[1]
  245. quoted_addr = smtplib.quoteaddr(arg)
  246. if raw_addr in sim_users:
  247. self.push('250 %s %s' % (sim_users[raw_addr], quoted_addr))
  248. else:
  249. self.push('550 No such user: %s' % arg)
  250. def smtp_EXPN(self, arg):
  251. # print '\nsmtp_EXPN(%r)\n' % arg
  252. list_name = email.utils.parseaddr(arg)[1].lower()
  253. if list_name in sim_lists:
  254. user_list = sim_lists[list_name]
  255. for n, user_email in enumerate(user_list):
  256. quoted_addr = smtplib.quoteaddr(user_email)
  257. if n < len(user_list) - 1:
  258. self.push('250-%s %s' % (sim_users[user_email], quoted_addr))
  259. else:
  260. self.push('250 %s %s' % (sim_users[user_email], quoted_addr))
  261. else:
  262. self.push('550 No access for you!')
  263. class SimSMTPServer(smtpd.SMTPServer):
  264. def handle_accept(self):
  265. conn, addr = self.accept()
  266. channel = SimSMTPChannel(self, conn, addr)
  267. def process_message(self, peer, mailfrom, rcpttos, data):
  268. pass
  269. # Test various SMTP & ESMTP commands/behaviors that require a simulated server
  270. # (i.e., something with more features than DebuggingServer)
  271. class SMTPSimTests(TestCase):
  272. def setUp(self):
  273. self.serv_evt = threading.Event()
  274. self.client_evt = threading.Event()
  275. self.port = test_support.find_unused_port()
  276. self.serv = SimSMTPServer((HOST, self.port), ('nowhere', -1))
  277. serv_args = (self.serv, self.serv_evt, self.client_evt)
  278. threading.Thread(target=debugging_server, args=serv_args).start()
  279. # wait until server thread has assigned a port number
  280. self.serv_evt.wait()
  281. self.serv_evt.clear()
  282. def tearDown(self):
  283. # indicate that the client is finished
  284. self.client_evt.set()
  285. # wait for the server thread to terminate
  286. self.serv_evt.wait()
  287. def testBasic(self):
  288. # smoke test
  289. smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15)
  290. smtp.quit()
  291. def testEHLO(self):
  292. smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15)
  293. # no features should be present before the EHLO
  294. self.assertEqual(smtp.esmtp_features, {})
  295. # features expected from the test server
  296. expected_features = {'expn':'',
  297. 'size': '20000000',
  298. 'starttls': '',
  299. 'deliverby': '',
  300. 'help': '',
  301. }
  302. smtp.ehlo()
  303. self.assertEqual(smtp.esmtp_features, expected_features)
  304. for k in expected_features:
  305. self.assertTrue(smtp.has_extn(k))
  306. self.assertFalse(smtp.has_extn('unsupported-feature'))
  307. smtp.quit()
  308. def testVRFY(self):
  309. smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15)
  310. for email, name in sim_users.items():
  311. expected_known = (250, '%s %s' % (name, smtplib.quoteaddr(email)))
  312. self.assertEqual(smtp.vrfy(email), expected_known)
  313. u = 'nobody@nowhere.com'
  314. expected_unknown = (550, 'No such user: %s' % smtplib.quoteaddr(u))
  315. self.assertEqual(smtp.vrfy(u), expected_unknown)
  316. smtp.quit()
  317. def testEXPN(self):
  318. smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15)
  319. for listname, members in sim_lists.items():
  320. users = []
  321. for m in members:
  322. users.append('%s %s' % (sim_users[m], smtplib.quoteaddr(m)))
  323. expected_known = (250, '\n'.join(users))
  324. self.assertEqual(smtp.expn(listname), expected_known)
  325. u = 'PSU-Members-List'
  326. expected_unknown = (550, 'No access for you!')
  327. self.assertEqual(smtp.expn(u), expected_unknown)
  328. smtp.quit()
  329. def test_main(verbose=None):
  330. test_support.run_unittest(GeneralTests, DebuggingServerTests,
  331. NonConnectingTests,
  332. BadHELOServerTests, SMTPSimTests)
  333. if __name__ == '__main__':
  334. test_main()