Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 1 | import asyncore |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 2 | import socket |
| 3 | import threading |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 4 | import smtpd |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 5 | import smtplib |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 6 | import StringIO |
| 7 | import sys |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 8 | import time |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 9 | import select |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 10 | |
| 11 | from unittest import TestCase |
| 12 | from test import test_support |
| 13 | |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 14 | # PORT is used to communicate the port number assigned to the server |
| 15 | # to the test client |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 16 | HOST = "localhost" |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 17 | PORT = None |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 18 | |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 19 | def server(evt, buf): |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 20 | try: |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 21 | serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| 22 | serv.settimeout(3) |
| 23 | serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
| 24 | serv.bind(("", 0)) |
| 25 | global PORT |
| 26 | PORT = serv.getsockname()[1] |
| 27 | serv.listen(5) |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 28 | conn, addr = serv.accept() |
| 29 | except socket.timeout: |
| 30 | pass |
| 31 | else: |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 32 | n = 500 |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 33 | while buf and n > 0: |
| 34 | r, w, e = select.select([], [conn], []) |
| 35 | if w: |
| 36 | sent = conn.send(buf) |
| 37 | buf = buf[sent:] |
| 38 | |
| 39 | n -= 1 |
| 40 | time.sleep(0.01) |
| 41 | |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 42 | conn.close() |
| 43 | finally: |
| 44 | serv.close() |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 45 | PORT = None |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 46 | evt.set() |
| 47 | |
| 48 | class GeneralTests(TestCase): |
Neal Norwitz | 0d4c06e | 2007-04-25 06:30:05 +0000 | [diff] [blame] | 49 | |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 50 | def setUp(self): |
| 51 | self.evt = threading.Event() |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 52 | servargs = (self.evt, "220 Hola mundo\n") |
| 53 | threading.Thread(target=server, args=servargs).start() |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 54 | |
| 55 | # wait until server thread has assigned a port number |
| 56 | n = 500 |
| 57 | while PORT is None and n > 0: |
| 58 | time.sleep(0.01) |
| 59 | n -= 1 |
| 60 | |
| 61 | # wait a little longer (sometimes connections are refused |
| 62 | # on slow machines without this additional wait) |
| 63 | time.sleep(0.5) |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 64 | |
| 65 | def tearDown(self): |
| 66 | self.evt.wait() |
| 67 | |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 68 | def testBasic1(self): |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 69 | # connects |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 70 | smtp = smtplib.SMTP(HOST, PORT) |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 71 | smtp.sock.close() |
Neal Norwitz | 0d4c06e | 2007-04-25 06:30:05 +0000 | [diff] [blame] | 72 | |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 73 | def testBasic2(self): |
| 74 | # connects, include port in host name |
| 75 | smtp = smtplib.SMTP("%s:%s" % (HOST, PORT)) |
| 76 | smtp.sock.close() |
| 77 | |
| 78 | def testLocalHostName(self): |
| 79 | # check that supplied local_hostname is used |
| 80 | smtp = smtplib.SMTP(HOST, PORT, local_hostname="testhost") |
| 81 | self.assertEqual(smtp.local_hostname, "testhost") |
| 82 | smtp.sock.close() |
| 83 | |
| 84 | def testNonnumericPort(self): |
| 85 | # check that non-numeric port raises ValueError |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 86 | self.assertRaises(socket.error, smtplib.SMTP, |
| 87 | "localhost", "bogus") |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 88 | |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 89 | def testTimeoutDefault(self): |
| 90 | # default |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 91 | smtp = smtplib.SMTP(HOST, PORT) |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 92 | self.assertTrue(smtp.sock.gettimeout() is None) |
| 93 | smtp.sock.close() |
Neal Norwitz | 0d4c06e | 2007-04-25 06:30:05 +0000 | [diff] [blame] | 94 | |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 95 | def testTimeoutValue(self): |
| 96 | # a value |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 97 | smtp = smtplib.SMTP(HOST, PORT, timeout=30) |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 98 | self.assertEqual(smtp.sock.gettimeout(), 30) |
| 99 | smtp.sock.close() |
| 100 | |
| 101 | def testTimeoutNone(self): |
| 102 | # None, having other default |
| 103 | previous = socket.getdefaulttimeout() |
| 104 | socket.setdefaulttimeout(30) |
| 105 | try: |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 106 | smtp = smtplib.SMTP(HOST, PORT, timeout=None) |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 107 | finally: |
| 108 | socket.setdefaulttimeout(previous) |
| 109 | self.assertEqual(smtp.sock.gettimeout(), 30) |
| 110 | smtp.sock.close() |
| 111 | |
| 112 | |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 113 | # Test server using smtpd.DebuggingServer |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 114 | def debugging_server(serv_evt, client_evt): |
| 115 | serv = smtpd.DebuggingServer(("", 0), ('nowhere', -1)) |
| 116 | global PORT |
| 117 | PORT = serv.getsockname()[1] |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 118 | |
| 119 | try: |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 120 | if hasattr(select, 'poll'): |
| 121 | poll_fun = asyncore.poll2 |
| 122 | else: |
| 123 | poll_fun = asyncore.poll |
| 124 | |
| 125 | n = 1000 |
| 126 | while asyncore.socket_map and n > 0: |
| 127 | poll_fun(0.01, asyncore.socket_map) |
| 128 | |
| 129 | # when the client conversation is finished, it will |
| 130 | # set client_evt, and it's then ok to kill the server |
| 131 | if client_evt.isSet(): |
| 132 | serv.close() |
| 133 | break |
| 134 | |
| 135 | n -= 1 |
| 136 | |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 137 | except socket.timeout: |
| 138 | pass |
| 139 | finally: |
| 140 | # allow some time for the client to read the result |
| 141 | time.sleep(0.5) |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 142 | serv.close() |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 143 | asyncore.close_all() |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 144 | PORT = None |
| 145 | time.sleep(0.5) |
| 146 | serv_evt.set() |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 147 | |
| 148 | MSG_BEGIN = '---------- MESSAGE FOLLOWS ----------\n' |
| 149 | MSG_END = '------------ END MESSAGE ------------\n' |
| 150 | |
| 151 | # Test behavior of smtpd.DebuggingServer |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 152 | # NOTE: the SMTP objects are created with a non-default local_hostname |
| 153 | # argument to the constructor, since (on some systems) the FQDN lookup |
| 154 | # caused by the default local_hostname sometimes takes so long that the |
| 155 | # test server times out, causing the test to fail. |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 156 | class DebuggingServerTests(TestCase): |
| 157 | |
| 158 | def setUp(self): |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 159 | # temporarily replace sys.stdout to capture DebuggingServer output |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 160 | self.old_stdout = sys.stdout |
| 161 | self.output = StringIO.StringIO() |
| 162 | sys.stdout = self.output |
| 163 | |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 164 | self.serv_evt = threading.Event() |
| 165 | self.client_evt = threading.Event() |
| 166 | serv_args = (self.serv_evt, self.client_evt) |
| 167 | threading.Thread(target=debugging_server, args=serv_args).start() |
| 168 | |
| 169 | # wait until server thread has assigned a port number |
| 170 | n = 500 |
| 171 | while PORT is None and n > 0: |
| 172 | time.sleep(0.01) |
| 173 | n -= 1 |
| 174 | |
| 175 | # wait a little longer (sometimes connections are refused |
| 176 | # on slow machines without this additional wait) |
| 177 | time.sleep(0.5) |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 178 | |
| 179 | def tearDown(self): |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 180 | # indicate that the client is finished |
| 181 | self.client_evt.set() |
| 182 | # wait for the server thread to terminate |
| 183 | self.serv_evt.wait() |
| 184 | # restore sys.stdout |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 185 | sys.stdout = self.old_stdout |
| 186 | |
| 187 | def testBasic(self): |
| 188 | # connect |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 189 | smtp = smtplib.SMTP(HOST, PORT, local_hostname='localhost', timeout=3) |
| 190 | smtp.quit() |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 191 | |
| 192 | def testEHLO(self): |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 193 | smtp = smtplib.SMTP(HOST, PORT, local_hostname='localhost', timeout=3) |
| 194 | expected = (502, 'Error: command "EHLO" not implemented') |
| 195 | self.assertEqual(smtp.ehlo(), expected) |
| 196 | smtp.quit() |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 197 | |
| 198 | def testHELP(self): |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 199 | smtp = smtplib.SMTP(HOST, PORT, local_hostname='localhost', timeout=3) |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 200 | self.assertEqual(smtp.help(), 'Error: command "HELP" not implemented') |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 201 | smtp.quit() |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 202 | |
| 203 | def testSend(self): |
| 204 | # connect and send mail |
| 205 | m = 'A test message' |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 206 | smtp = smtplib.SMTP(HOST, PORT, local_hostname='localhost', timeout=3) |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 207 | smtp.sendmail('John', 'Sally', m) |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 208 | smtp.quit() |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 209 | |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 210 | self.client_evt.set() |
| 211 | self.serv_evt.wait() |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 212 | self.output.flush() |
| 213 | mexpect = '%s%s\n%s' % (MSG_BEGIN, m, MSG_END) |
| 214 | self.assertEqual(self.output.getvalue(), mexpect) |
| 215 | |
| 216 | |
| 217 | class BadHELOServerTests(TestCase): |
| 218 | |
| 219 | def setUp(self): |
| 220 | self.old_stdout = sys.stdout |
| 221 | self.output = StringIO.StringIO() |
| 222 | sys.stdout = self.output |
| 223 | |
| 224 | self.evt = threading.Event() |
| 225 | servargs = (self.evt, "199 no hello for you!\n") |
| 226 | threading.Thread(target=server, args=servargs).start() |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 227 | |
| 228 | # wait until server thread has assigned a port number |
| 229 | n = 500 |
| 230 | while PORT is None and n > 0: |
| 231 | time.sleep(0.01) |
| 232 | n -= 1 |
| 233 | |
| 234 | # wait a little longer (sometimes connections are refused |
| 235 | # on slow machines without this additional wait) |
| 236 | time.sleep(0.5) |
Facundo Batista | 16ed5b4 | 2007-07-24 21:20:42 +0000 | [diff] [blame] | 237 | |
| 238 | def tearDown(self): |
| 239 | self.evt.wait() |
| 240 | sys.stdout = self.old_stdout |
| 241 | |
| 242 | def testFailingHELO(self): |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 243 | self.assertRaises(smtplib.SMTPConnectError, smtplib.SMTP, |
| 244 | HOST, PORT, 'localhost', 3) |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 245 | |
| 246 | def test_main(verbose=None): |
Facundo Batista | 412b8b6 | 2007-08-01 23:18:36 +0000 | [diff] [blame^] | 247 | test_support.run_unittest(GeneralTests, DebuggingServerTests, |
| 248 | BadHELOServerTests) |
Facundo Batista | 366d626 | 2007-03-28 18:25:54 +0000 | [diff] [blame] | 249 | |
| 250 | if __name__ == '__main__': |
| 251 | test_main() |