Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1edc210

Browse files
committed
Merge pull request #5216 from mdboom/nose-internet
TST: Enable testing without internet access.
2 parents 4c5d808 + db0d7aa commit 1edc210

File tree

3 files changed

+165
-4
lines changed

3 files changed

+165
-4
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Originally from astropy project (http://astropy.org), under BSD
2+
# 3-clause license.
3+
4+
from __future__ import (absolute_import, division, print_function,
5+
unicode_literals)
6+
7+
import contextlib
8+
import socket
9+
10+
from matplotlib.externals.six.moves import urllib
11+
12+
# save original socket method for restoration
13+
# These are global so that re-calling the turn_off_internet function doesn't
14+
# overwrite them again
15+
socket_original = socket.socket
16+
socket_create_connection = socket.create_connection
17+
socket_bind = socket.socket.bind
18+
socket_connect = socket.socket.connect
19+
20+
21+
INTERNET_OFF = False
22+
23+
# urllib2 uses a global variable to cache its default "opener" for opening
24+
# connections for various protocols; we store it off here so we can restore to
25+
# the default after re-enabling internet use
26+
_orig_opener = None
27+
28+
29+
# ::1 is apparently another valid name for localhost?
30+
# it is returned by getaddrinfo when that function is given localhost
31+
32+
def check_internet_off(original_function):
33+
"""
34+
Wraps ``original_function``, which in most cases is assumed
35+
to be a `socket.socket` method, to raise an `IOError` for any operations
36+
on non-local AF_INET sockets.
37+
"""
38+
39+
def new_function(*args, **kwargs):
40+
if isinstance(args[0], socket.socket):
41+
if not args[0].family in (socket.AF_INET, socket.AF_INET6):
42+
# Should be fine in all but some very obscure cases
43+
# More to the point, we don't want to affect AF_UNIX
44+
# sockets.
45+
return original_function(*args, **kwargs)
46+
host = args[1][0]
47+
addr_arg = 1
48+
valid_hosts = ('localhost', '127.0.0.1', '::1')
49+
else:
50+
# The only other function this is used to wrap currently is
51+
# socket.create_connection, which should be passed a 2-tuple, but
52+
# we'll check just in case
53+
if not (isinstance(args[0], tuple) and len(args[0]) == 2):
54+
return original_function(*args, **kwargs)
55+
56+
host = args[0][0]
57+
addr_arg = 0
58+
valid_hosts = ('localhost', '127.0.0.1')
59+
60+
hostname = socket.gethostname()
61+
fqdn = socket.getfqdn()
62+
63+
if host in (hostname, fqdn):
64+
host = 'localhost'
65+
new_addr = (host, args[addr_arg][1])
66+
args = args[:addr_arg] + (new_addr,) + args[addr_arg + 1:]
67+
68+
if any([h in host for h in valid_hosts]):
69+
return original_function(*args, **kwargs)
70+
else:
71+
raise IOError("An attempt was made to connect to the internet "
72+
"by a test that was not marked `remote_data`.")
73+
return new_function
74+
75+
76+
def turn_off_internet(verbose=False):
77+
"""
78+
Disable internet access via python by preventing connections from being
79+
created using the socket module. Presumably this could be worked around by
80+
using some other means of accessing the internet, but all default python
81+
modules (urllib, requests, etc.) use socket [citation needed].
82+
"""
83+
84+
global INTERNET_OFF
85+
global _orig_opener
86+
87+
if INTERNET_OFF:
88+
return
89+
90+
INTERNET_OFF = True
91+
92+
__tracebackhide__ = True
93+
if verbose:
94+
print("Internet access disabled")
95+
96+
# Update urllib2 to force it not to use any proxies
97+
# Must use {} here (the default of None will kick off an automatic search
98+
# for proxies)
99+
_orig_opener = urllib.request.build_opener()
100+
no_proxy_handler = urllib.request.ProxyHandler({})
101+
opener = urllib.request.build_opener(no_proxy_handler)
102+
urllib.request.install_opener(opener)
103+
104+
socket.create_connection = check_internet_off(socket_create_connection)
105+
socket.socket.bind = check_internet_off(socket_bind)
106+
socket.socket.connect = check_internet_off(socket_connect)
107+
108+
return socket
109+
110+
111+
def turn_on_internet(verbose=False):
112+
"""
113+
Restore internet access. Not used, but kept in case it is needed.
114+
"""
115+
116+
global INTERNET_OFF
117+
global _orig_opener
118+
119+
if not INTERNET_OFF:
120+
return
121+
122+
INTERNET_OFF = False
123+
124+
if verbose:
125+
print("Internet access enabled")
126+
127+
urllib.request.install_opener(_orig_opener)
128+
129+
socket.create_connection = socket_create_connection
130+
socket.socket.bind = socket_bind
131+
socket.socket.connect = socket_connect
132+
return socket
133+
134+
135+
@contextlib.contextmanager
136+
def no_internet(verbose=False):
137+
"""Context manager to temporarily disable internet access (if not already
138+
disabled). If it was already disabled before entering the context manager
139+
(i.e. `turn_off_internet` was called previously) then this is a no-op and
140+
leaves internet access disabled until a manual call to `turn_on_internet`.
141+
"""
142+
143+
already_disabled = INTERNET_OFF
144+
145+
turn_off_internet(verbose=verbose)
146+
try:
147+
yield
148+
finally:
149+
if not already_disabled:
150+
turn_on_internet(verbose=verbose)

lib/matplotlib/tests/test_style.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from nose import SkipTest
1111
from nose.tools import assert_raises
12+
from nose.plugins.attrib import attr
1213

1314
import matplotlib as mpl
1415
from matplotlib import style
@@ -56,6 +57,7 @@ def test_use():
5657
assert mpl.rcParams[PARAM] == VALUE
5758

5859

60+
@attr('network')
5961
def test_use_url():
6062
with temp_style('test', DUMMY_SETTINGS):
6163
with style.context('https://gist.github.com/adrn/6590261/raw'):

tests.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
matplotlib.use('agg')
1818

1919
import nose
20+
from nose.plugins import attrib
2021
from matplotlib.testing.noseclasses import KnownFailure
2122
from matplotlib import default_test_modules
2223

@@ -27,7 +28,7 @@
2728
while not os.path.exists(font_manager._fmcache):
2829
time.sleep(0.5)
2930

30-
plugins = [KnownFailure]
31+
plugins = [KnownFailure, attrib.Plugin]
3132

3233
# Nose doesn't automatically instantiate all of the plugins in the
3334
# child processes, so we have to provide the multiprocess plugin with
@@ -36,7 +37,7 @@
3637
multiprocess._instantiate_plugins = plugins
3738

3839

39-
def run():
40+
def run(extra_args):
4041
try:
4142
import faulthandler
4243
except ImportError:
@@ -45,14 +46,22 @@ def run():
4546
faulthandler.enable()
4647

4748
nose.main(addplugins=[x() for x in plugins],
48-
defaultTest=default_test_modules)
49+
defaultTest=default_test_modules,
50+
argv=sys.argv + extra_args)
51+
4952

5053
if __name__ == '__main__':
54+
extra_args = []
55+
5156
if '--no-pep8' in sys.argv:
5257
default_test_modules.remove('matplotlib.tests.test_coding_standards')
5358
sys.argv.remove('--no-pep8')
5459
elif '--pep8' in sys.argv:
5560
default_test_modules = ['matplotlib.tests.test_coding_standards']
5661
sys.argv.remove('--pep8')
62+
if '--no-network' in sys.argv:
63+
from matplotlib.testing import disable_internet
64+
disable_internet.turn_off_internet()
65+
extra_args.extend(['--eval-attr="not network"'])
5766

58-
run()
67+
run(extra_args)

0 commit comments

Comments
 (0)