Skip to content

Commit 64b02de

Browse files
author
Richard Jones
committed
improvements to test_smtplib per issue2423
merged the socket mock introduced in test_smtpd
1 parent 0db85e5 commit 64b02de

3 files changed

Lines changed: 188 additions & 75 deletions

File tree

Lib/test/mock_socket.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
"""Mock socket module used by the smtpd and smtplib tests.
2+
"""
3+
4+
# imported for _GLOBAL_DEFAULT_TIMEOUT
5+
import socket as socket_module
6+
7+
# Mock socket module
8+
_defaulttimeout = None
9+
_reply_data = None
10+
11+
# This is used to queue up data to be read through socket.makefile, typically
12+
# *before* the socket object is even created. It is intended to handle a single
13+
# line which the socket will feed on recv() or makefile().
14+
def reply_with(line):
15+
global _reply_data
16+
_reply_data = line
17+
18+
19+
class MockFile:
20+
"""Mock file object returned by MockSocket.makefile().
21+
"""
22+
def __init__(self, lines):
23+
self.lines = lines
24+
def readline(self):
25+
return self.lines.pop(0) + b'\r\n'
26+
def close(self):
27+
pass
28+
29+
30+
class MockSocket:
31+
"""Mock socket object used by smtpd and smtplib tests.
32+
"""
33+
def __init__(self):
34+
global _reply_data
35+
self.output = []
36+
self.lines = []
37+
if _reply_data:
38+
self.lines.append(_reply_data)
39+
self.conn = None
40+
self.timeout = None
41+
42+
def queue_recv(self, line):
43+
self.lines.append(line)
44+
45+
def recv(self, bufsize, flags=None):
46+
data = self.lines.pop(0) + b'\r\n'
47+
return data
48+
49+
def fileno(self):
50+
return 0
51+
52+
def settimeout(self, timeout):
53+
if timeout is None:
54+
self.timeout = _defaulttimeout
55+
else:
56+
self.timeout = timeout
57+
58+
def gettimeout(self):
59+
return self.timeout
60+
61+
def setsockopt(self, level, optname, value):
62+
pass
63+
64+
def getsockopt(self, level, optname, buflen=None):
65+
return 0
66+
67+
def bind(self, address):
68+
pass
69+
70+
def accept(self):
71+
self.conn = MockSocket()
72+
return self.conn, 'c'
73+
74+
def getsockname(self):
75+
return ('0.0.0.0', 0)
76+
77+
def setblocking(self, flag):
78+
pass
79+
80+
def listen(self, backlog):
81+
pass
82+
83+
def makefile(self, mode='r', bufsize=-1):
84+
handle = MockFile(self.lines)
85+
return handle
86+
87+
def sendall(self, buffer, flags=None):
88+
self.last = data
89+
self.output.append(data)
90+
return len(data)
91+
92+
def send(self, data, flags=None):
93+
self.last = data
94+
self.output.append(data)
95+
return len(data)
96+
97+
def getpeername(self):
98+
return 'peer'
99+
100+
def close(self):
101+
pass
102+
103+
104+
def socket(family=None, type=None, proto=None):
105+
return MockSocket()
106+
107+
108+
def create_connection(address, timeout=socket_module._GLOBAL_DEFAULT_TIMEOUT):
109+
try:
110+
int_port = int(address[1])
111+
except ValueError:
112+
raise error
113+
ms = MockSocket()
114+
if timeout is socket_module._GLOBAL_DEFAULT_TIMEOUT:
115+
timeout = getdefaulttimeout()
116+
ms.settimeout(timeout)
117+
return ms
118+
119+
120+
def setdefaulttimeout(timeout):
121+
global _defaulttimeout
122+
_defaulttimeout = timeout
123+
124+
125+
def getdefaulttimeout():
126+
return _defaulttimeout
127+
128+
129+
def getfqdn():
130+
return ""
131+
132+
133+
def gethostname():
134+
pass
135+
136+
137+
def gethostbyname(name):
138+
return ""
139+
140+
141+
class gaierror(Exception):
142+
pass
143+
144+
145+
class error(Exception):
146+
pass
147+
148+
149+
# Constants
150+
AF_INET = None
151+
SOCK_STREAM = None
152+
SOL_SOCKET = None
153+
SO_REUSEADDR = None

Lib/test/test_smtpd.py

Lines changed: 8 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,16 @@
1-
import asynchat
21
from unittest import TestCase
2+
from test import support, mock_socket
33
import socket
4-
from test import support
5-
import asyncore
64
import io
75
import smtpd
6+
import asyncore
87

9-
# mock-ish socket to sit underneath asyncore
10-
class DummySocket:
11-
def __init__(self):
12-
self.output = []
13-
self.queue = []
14-
self.conn = None
15-
def queue_recv(self, line):
16-
self.queue.append(line)
17-
def recv(self, *args):
18-
data = self.queue.pop(0) + b'\r\n'
19-
return data
20-
def fileno(self):
21-
return 0
22-
def setsockopt(self, *args):
23-
pass
24-
def getsockopt(self, *args):
25-
return 0
26-
def bind(self, *args):
27-
pass
28-
def accept(self):
29-
self.conn = DummySocket()
30-
return self.conn, 'c'
31-
def listen(self, *args):
32-
pass
33-
def setblocking(self, *args):
34-
pass
35-
def send(self, data):
36-
self.last = data
37-
self.output.append(data)
38-
return len(data)
39-
def getpeername(self):
40-
return 'peer'
41-
def close(self):
42-
pass
438

449
class DummyServer(smtpd.SMTPServer):
4510
def __init__(self, *args):
4611
smtpd.SMTPServer.__init__(self, *args)
4712
self.messages = []
48-
def create_socket(self, family, type):
49-
self.family_and_type = (socket.AF_INET, socket.SOCK_STREAM)
50-
self.set_socket(DummySocket())
13+
5114
def process_message(self, peer, mailfrom, rcpttos, data):
5215
self.messages.append((peer, mailfrom, rcpttos, data))
5316
if data == 'return status':
@@ -62,11 +25,15 @@ def listen(self, num):
6225

6326
class SMTPDChannelTest(TestCase):
6427
def setUp(self):
28+
smtpd.socket = asyncore.socket = mock_socket
6529
self.debug = smtpd.DEBUGSTREAM = io.StringIO()
6630
self.server = DummyServer('a', 'b')
6731
conn, addr = self.server.accept()
6832
self.channel = smtpd.SMTPChannel(self.server, conn, addr)
6933

34+
def tearDown(self):
35+
asyncore.socket = smtpd.socket = socket
36+
7037
def write_line(self, line):
7138
self.channel.socket.queue_recv(line)
7239
self.channel.handle_read()
@@ -88,7 +55,7 @@ def test_EHLO_not_implemented(self):
8855
b'502 Error: command "EHLO" not implemented\r\n')
8956

9057
def test_HELO(self):
91-
name = socket.getfqdn()
58+
name = smtpd.socket.getfqdn()
9259
self.write_line(b'HELO test.example')
9360
self.assertEqual(self.channel.socket.last,
9461
'250 {}\r\n'.format(name).encode('ascii'))

Lib/test/test_smtplib.py

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import select
1010

1111
import unittest
12-
from test import support
12+
from test import support, mock_socket
1313

1414
try:
1515
import threading
@@ -48,27 +48,17 @@ def server(evt, buf, serv):
4848
serv.close()
4949
evt.set()
5050

51-
@unittest.skipUnless(threading, 'Threading required for this test.')
5251
class GeneralTests(unittest.TestCase):
5352

5453
def setUp(self):
55-
self._threads = support.threading_setup()
56-
self.evt = threading.Event()
57-
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
58-
self.sock.settimeout(15)
59-
self.port = support.bind_port(self.sock)
60-
servargs = (self.evt, b"220 Hola mundo\n", self.sock)
61-
self.thread = threading.Thread(target=server, args=servargs)
62-
self.thread.start()
63-
self.evt.wait()
64-
self.evt.clear()
54+
smtplib.socket = mock_socket
55+
self.port = 25
6556

6657
def tearDown(self):
67-
self.evt.wait()
68-
self.thread.join()
69-
support.threading_cleanup(*self._threads)
58+
smtplib.socket = socket
7059

7160
def testBasic1(self):
61+
mock_socket.reply_with(b"220 Hola mundo")
7262
# connects
7363
smtp = smtplib.SMTP(HOST, self.port)
7464
smtp.close()
@@ -85,12 +75,13 @@ def testLocalHostName(self):
8575
smtp.close()
8676

8777
def testTimeoutDefault(self):
88-
self.assertTrue(socket.getdefaulttimeout() is None)
89-
socket.setdefaulttimeout(30)
78+
self.assertTrue(mock_socket.getdefaulttimeout() is None)
79+
mock_socket.setdefaulttimeout(30)
80+
self.assertEqual(mock_socket.getdefaulttimeout(), 30)
9081
try:
9182
smtp = smtplib.SMTP(HOST, self.port)
9283
finally:
93-
socket.setdefaulttimeout(None)
84+
mock_socket.setdefaulttimeout(None)
9485
self.assertEqual(smtp.sock.gettimeout(), 30)
9586
smtp.close()
9687

@@ -155,6 +146,8 @@ def debugging_server(serv, serv_evt, client_evt):
155146
class DebuggingServerTests(unittest.TestCase):
156147

157148
def setUp(self):
149+
self.real_getfqdn = socket.getfqdn
150+
socket.getfqdn = mock_socket.getfqdn
158151
# temporarily replace sys.stdout to capture DebuggingServer output
159152
self.old_stdout = sys.stdout
160153
self.output = io.StringIO()
@@ -176,6 +169,7 @@ def setUp(self):
176169
self.serv_evt.clear()
177170

178171
def tearDown(self):
172+
socket.getfqdn = self.real_getfqdn
179173
# indicate that the client is finished
180174
self.client_evt.set()
181175
# wait for the server thread to terminate
@@ -251,6 +245,12 @@ def testSend(self):
251245

252246
class NonConnectingTests(unittest.TestCase):
253247

248+
def setUp(self):
249+
smtplib.socket = mock_socket
250+
251+
def tearDown(self):
252+
smtplib.socket = socket
253+
254254
def testNotConnected(self):
255255
# Test various operations on an unconnected SMTP object that
256256
# should raise exceptions (at present the attempt in SMTP.send
@@ -263,9 +263,9 @@ def testNotConnected(self):
263263

264264
def testNonnumericPort(self):
265265
# check that non-numeric port raises socket.error
266-
self.assertRaises(socket.error, smtplib.SMTP,
266+
self.assertRaises(mock_socket.error, smtplib.SMTP,
267267
"localhost", "bogus")
268-
self.assertRaises(socket.error, smtplib.SMTP,
268+
self.assertRaises(mock_socket.error, smtplib.SMTP,
269269
"localhost:bogus")
270270

271271

@@ -274,25 +274,15 @@ def testNonnumericPort(self):
274274
class BadHELOServerTests(unittest.TestCase):
275275

276276
def setUp(self):
277+
smtplib.socket = mock_socket
278+
mock_socket.reply_with(b"199 no hello for you!")
277279
self.old_stdout = sys.stdout
278280
self.output = io.StringIO()
279281
sys.stdout = self.output
280-
281-
self._threads = support.threading_setup()
282-
self.evt = threading.Event()
283-
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
284-
self.sock.settimeout(15)
285-
self.port = support.bind_port(self.sock)
286-
servargs = (self.evt, b"199 no hello for you!\n", self.sock)
287-
self.thread = threading.Thread(target=server, args=servargs)
288-
self.thread.start()
289-
self.evt.wait()
290-
self.evt.clear()
282+
self.port = 25
291283

292284
def tearDown(self):
293-
self.evt.wait()
294-
self.thread.join()
295-
support.threading_cleanup(*self._threads)
285+
smtplib.socket = socket
296286
sys.stdout = self.old_stdout
297287

298288
def testFailingHELO(self):
@@ -405,6 +395,8 @@ def handle_error(self):
405395
class SMTPSimTests(unittest.TestCase):
406396

407397
def setUp(self):
398+
self.real_getfqdn = socket.getfqdn
399+
socket.getfqdn = mock_socket.getfqdn
408400
self._threads = support.threading_setup()
409401
self.serv_evt = threading.Event()
410402
self.client_evt = threading.Event()
@@ -421,6 +413,7 @@ def setUp(self):
421413
self.serv_evt.clear()
422414

423415
def tearDown(self):
416+
socket.getfqdn = self.real_getfqdn
424417
# indicate that the client is finished
425418
self.client_evt.set()
426419
# wait for the server thread to terminate

0 commit comments

Comments
 (0)