1+ import contextlib
12import errno
3+ import sysconfig
24import unittest
5+ from unittest import mock
36from test import support
47from test .support import os_helper
58from test .support import socket_helper
69from test .support import ResourceDenied
710from test .test_urllib2 import sanepathname2url
11+ from test .support .warnings_helper import check_no_resource_warning
812
913import os
1014import socket
@@ -29,13 +33,6 @@ def wrapped(*args, **kwargs):
2933 return _retry_thrice (func , exc , * args , ** kwargs )
3034 return wrapped
3135
32- # bpo-35411: FTP tests of test_urllib2net randomly fail
33- # with "425 Security: Bad IP connecting" on Travis CI
34- skip_ftp_test_on_travis = unittest .skipIf ('TRAVIS' in os .environ ,
35- 'bpo-35411: skip FTP test '
36- 'on Travis CI' )
37-
38-
3936# Connecting to remote hosts is flaky. Make it more robust by retrying
4037# the connection several times.
4138_urlopen_with_retry = _wrap_with_retry_thrice (urllib .request .urlopen ,
@@ -140,15 +137,54 @@ def setUp(self):
140137 # XXX The rest of these tests aren't very good -- they don't check much.
141138 # They do sometimes catch some major disasters, though.
142139
143- @skip_ftp_test_on_travis
140+ @support . requires_resource ( 'walltime' )
144141 def test_ftp (self ):
142+ # Testing the same URL twice exercises the caching in CacheFTPHandler
145143 urls = [
144+ 'ftp://www.pythontest.net/README' ,
146145 'ftp://www.pythontest.net/README' ,
147146 ('ftp://www.pythontest.net/non-existent-file' ,
148147 None , urllib .error .URLError ),
149148 ]
150149 self ._test_urls (urls , self ._extra_handlers ())
151150
151+ @support .requires_resource ('walltime' )
152+ @unittest .skipIf (sysconfig .get_platform () == 'linux-ppc64le' ,
153+ 'leaks on PPC64LE (gh-140691)' )
154+ def test_ftp_no_leak (self ):
155+ # gh-140691: When the data connection (but not control connection)
156+ # cannot be made established, we shouldn't leave an open socket object.
157+
158+ class MockError (OSError ):
159+ pass
160+
161+ orig_create_connection = socket .create_connection
162+ def patched_create_connection (address , * args , ** kwargs ):
163+ """Simulate REJECTing connections to ports other than 21"""
164+ host , port = address
165+ if port != 21 :
166+ raise MockError ()
167+ return orig_create_connection (address , * args , ** kwargs )
168+
169+ url = 'ftp://www.pythontest.net/README'
170+ entry = url , None , urllib .error .URLError
171+ no_cache_handlers = [urllib .request .FTPHandler ()]
172+ cache_handlers = self ._extra_handlers ()
173+ with mock .patch ('socket.create_connection' , patched_create_connection ):
174+ with check_no_resource_warning (self ):
175+ # Try without CacheFTPHandler
176+ self ._test_urls ([entry ], handlers = no_cache_handlers ,
177+ retry = False )
178+ with check_no_resource_warning (self ):
179+ # Try with CacheFTPHandler (uncached)
180+ self ._test_urls ([entry ], cache_handlers , retry = False )
181+ with check_no_resource_warning (self ):
182+ # Try with CacheFTPHandler (cached)
183+ self ._test_urls ([entry ], cache_handlers , retry = False )
184+ # Try without the mock: the handler should not use a closed connection
185+ with check_no_resource_warning (self ):
186+ self ._test_urls ([url ], cache_handlers , retry = False )
187+
152188 def test_file (self ):
153189 TESTFN = os_helper .TESTFN
154190 f = open (TESTFN , 'w' )
@@ -202,6 +238,7 @@ def test_urlwithfrag(self):
202238 self .assertEqual (res .geturl (),
203239 "http://www.pythontest.net/index.html#frag" )
204240
241+ @support .requires_resource ('walltime' )
205242 def test_redirect_url_withfrag (self ):
206243 redirect_url_with_frag = "http://www.pythontest.net/redir/with_frag/"
207244 with socket_helper .transient_internet (redirect_url_with_frag ):
@@ -260,18 +297,16 @@ def _test_urls(self, urls, handlers, retry=True):
260297 else :
261298 req = expected_err = None
262299
300+ if expected_err :
301+ context = self .assertRaises (expected_err )
302+ else :
303+ context = contextlib .nullcontext ()
304+
263305 with socket_helper .transient_internet (url ):
264- try :
306+ f = None
307+ with context :
265308 f = urlopen (url , req , support .INTERNET_TIMEOUT )
266- # urllib.error.URLError is a subclass of OSError
267- except OSError as err :
268- if expected_err :
269- msg = ("Didn't get expected error(s) %s for %s %s, got %s: %s" %
270- (expected_err , url , req , type (err ), err ))
271- self .assertIsInstance (err , expected_err , msg )
272- else :
273- raise
274- else :
309+ if f is not None :
275310 try :
276311 with time_out , \
277312 socket_peer_reset , \
@@ -340,15 +375,14 @@ def test_http_timeout(self):
340375
341376 FTP_HOST = 'ftp://www.pythontest.net/'
342377
343- @skip_ftp_test_on_travis
378+ @support . requires_resource ( 'walltime' )
344379 def test_ftp_basic (self ):
345380 self .assertIsNone (socket .getdefaulttimeout ())
346381 with socket_helper .transient_internet (self .FTP_HOST , timeout = None ):
347382 u = _urlopen_with_retry (self .FTP_HOST )
348383 self .addCleanup (u .close )
349384 self .assertIsNone (u .fp .fp .raw ._sock .gettimeout ())
350385
351- @skip_ftp_test_on_travis
352386 def test_ftp_default_timeout (self ):
353387 self .assertIsNone (socket .getdefaulttimeout ())
354388 with socket_helper .transient_internet (self .FTP_HOST ):
@@ -360,7 +394,7 @@ def test_ftp_default_timeout(self):
360394 socket .setdefaulttimeout (None )
361395 self .assertEqual (u .fp .fp .raw ._sock .gettimeout (), 60 )
362396
363- @skip_ftp_test_on_travis
397+ @support . requires_resource ( 'walltime' )
364398 def test_ftp_no_timeout (self ):
365399 self .assertIsNone (socket .getdefaulttimeout ())
366400 with socket_helper .transient_internet (self .FTP_HOST ):
@@ -372,7 +406,7 @@ def test_ftp_no_timeout(self):
372406 socket .setdefaulttimeout (None )
373407 self .assertIsNone (u .fp .fp .raw ._sock .gettimeout ())
374408
375- @skip_ftp_test_on_travis
409+ @support . requires_resource ( 'walltime' )
376410 def test_ftp_timeout (self ):
377411 with socket_helper .transient_internet (self .FTP_HOST ):
378412 u = _urlopen_with_retry (self .FTP_HOST , timeout = 60 )
0 commit comments