astropy/pytest-remotedata

Test_default_behaviour fails when internet is blocked

olebole opened this issue · 2 comments

Hi,
for me the "remotedata" feature is important, since on our infrastructure the tests run without internet connection. However, I get two test failures on remotedata itself when there is not internet. This can be reproduced with http_proxy=127.0.0.1 pytest-3.

testdir = <Testdir local('/tmp/pytest-of-ole/pytest-15/test_default_behavior0')>

    def test_default_behavior(testdir):
        _write_config_file(testdir, '')
    
        testdir.makepyfile(PYFILE_CONTENTS.format('False', ''))
    
        result = testdir.runpytest_subprocess()
>       result.assert_outcomes(passed=2)

/home/ole/Projects/2011/debian/python/pytest/remotedata/tests/test_strict_check.py:35: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.pytester.RunResult object at 0x7fb8275a7470>, passed = 2
skipped = 0, failed = 0, error = 0, xpassed = 0, xfailed = 0

    def assert_outcomes(
        self, passed=0, skipped=0, failed=0, error=0, xpassed=0, xfailed=0
    ):
        """[…]"""
        d = self.parseoutcomes()
        obtained = {…
            "passed": d.get("passed", 0),
            "skipped": d.get("skipped", 0),
            "failed": d.get("failed", 0),
            "error": d.get("error", 0),
            "xpassed": d.get("xpassed", 0),
            "xfailed": d.get("xfailed", 0),
        }
        expected = {
            "passed": passed,
            "skipped": skipped,
            "failed": failed,
            "error": error,
            "xpassed": xpassed,
            "xfailed": xfailed,
        }
>       assert obtained == expected
E       AssertionError: assert {'error': 0, ...pped': 0, ...} == {'error': 0, '...pped': 0, ...}
E         Omitting 4 identical items, use -vv to show
E         Differing items:
E         {'failed': 1} != {'failed': 0}
E         {'passed': 1} != {'passed': 2}
E         Use -v to get the full diff

/usr/lib/python3/dist-packages/_pytest/pytester.py:444: AssertionError
----------------------------- Captured stdout call -----------------------------
running: /usr/bin/python3 -mpytest --basetemp=/tmp/pytest-of-ole/pytest-15/test_default_behavior0/runpytest-0
     in: /tmp/pytest-of-ole/pytest-15/test_default_behavior0
============================= test session starts ==============================
platform linux -- Python 3.7.3, pytest-3.10.1, py-1.8.0, pluggy-0.8.0
rootdir: /tmp/pytest-of-ole/pytest-15/test_default_behavior0, inifile: setup.cfg
plugins: remotedata-0.3.1, openfiles-0.3.2, doctestplus-0.2.0, arraydiff-0.3
collected 2 items

test_default_behavior.py .F                                              [100%]

=================================== FAILURES ===================================
_____________________________ test_internet_access _____________________________

self = <urllib.request.HTTPHandler object at 0x7f91ced745f8>
http_class = <class 'http.client.HTTPConnection'>
req = <urllib.request.Request object at 0x7f91ced78438>, http_conn_args = {}
host = '127.1', h = <http.client.HTTPConnection object at 0x7f91ced74748>

    def do_open(self, http_class, req, **http_conn_args):
        """…"""
[…]
        try:
            try:
                h.request(req.get_method(), req.selector, req.data, headers,
>                         encode_chunked=req.has_header('Transfer-encoding'))

/usr/lib/python3.7/urllib/request.py:1317: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <http.client.HTTPConnection object at 0x7f91ced74748>, method = 'GET'
url = 'http://astropy.org', body = None
headers = {'Connection': 'close', 'Host': 'astropy.org', 'User-Agent': 'Python-urllib/3.7'}

    def request(self, method, url, body=None, headers={}, *,
                encode_chunked=False):
        """Send a complete request to the server."""
>       self._send_request(method, url, body, headers, encode_chunked)

/usr/lib/python3.7/http/client.py:1229: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <http.client.HTTPConnection object at 0x7f91ced74748>, method = 'GET'
url = 'http://astropy.org', body = None
headers = {'Connection': 'close', 'Host': 'astropy.org', 'User-Agent': 'Python-urllib/3.7'}
encode_chunked = False

    def _send_request(self, method, url, body, headers, encode_chunked):
[…]
        if isinstance(body, str):
            # RFC 2616 Section 3.7.1 says that text default has a
            # default charset of iso-8859-1.
            body = _encode(body, 'body')
>       self.endheaders(body, encode_chunked=encode_chunked)

/usr/lib/python3.7/http/client.py:1275: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <http.client.HTTPConnection object at 0x7f91ced74748>
message_body = None

    def endheaders(self, message_body=None, *, encode_chunked=False):
        """…"""
        if self.__state == _CS_REQ_STARTED:
            self.__state = _CS_REQ_SENT
        else:
            raise CannotSendHeader()
>       self._send_output(message_body, encode_chunked=encode_chunked)

/usr/lib/python3.7/http/client.py:1224: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <http.client.HTTPConnection object at 0x7f91ced74748>
message_body = None, encode_chunked = False

    def _send_output(self, message_body=None, encode_chunked=False):
        """…"""
        self._buffer.extend((b"", b""))
        msg = b"\r\n".join(self._buffer)
        del self._buffer[:]
>       self.send(msg)

/usr/lib/python3.7/http/client.py:1016: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <http.client.HTTPConnection object at 0x7f91ced74748>
data = b'GET http://astropy.org HTTP/1.1\r\nAccept-Encoding: identity\r\nHost: astropy.org\r\nUser-Agent: Python-urllib/3.7\r\nConnection: close\r\n\r\n'

    def send(self, data):
        """…"""
    
        if self.sock is None:
            if self.auto_open:
>               self.connect()

/usr/lib/python3.7/http/client.py:956: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <http.client.HTTPConnection object at 0x7f91ced74748>

    def connect(self):
        """Connect to the host and port specified in __init__."""
        self.sock = self._create_connection(
>           (self.host,self.port), self.timeout, self.source_address)

/usr/lib/python3.7/http/client.py:928: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

address = ('127.1', 80), timeout = <object object at 0x7f91d190d760>
source_address = None

    def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
                          source_address=None):
        """…"""
    
        host, port = address
        err = None
        for res in getaddrinfo(host, port, 0, SOCK_STREAM):
            af, socktype, proto, canonname, sa = res
            sock = None
            try:
                sock = socket(af, socktype, proto)
                if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
                    sock.settimeout(timeout)
                if source_address:
                    sock.bind(source_address)
                sock.connect(sa)
                # Break explicitly a reference cycle
                err = None
                return sock
    
            except error as _:
                err = _
                if sock is not None:
                    sock.close()
    
        if err is not None:
>           raise err

/usr/lib/python3.7/socket.py:727: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

address = ('127.1', 80), timeout = <object object at 0x7f91d190d760>
source_address = None

    def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
                          source_address=None):
        """…"""
    
        host, port = address
        err = None
        for res in getaddrinfo(host, port, 0, SOCK_STREAM):
            af, socktype, proto, canonname, sa = res
            sock = None
            try:
                sock = socket(af, socktype, proto)
                if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
                    sock.settimeout(timeout)
                if source_address:
                    sock.bind(source_address)
>               sock.connect(sa)
E               ConnectionRefusedError: [Errno 111] Connection refused

/usr/lib/python3.7/socket.py:716: ConnectionRefusedError

During handling of the above exception, another exception occurred:

    def test_internet_access():
>       urlopen('http://astropy.org')

test_default_behavior.py:9: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.7/urllib/request.py:222: in urlopen
    return opener.open(url, data, timeout)
/usr/lib/python3.7/urllib/request.py:525: in open
    response = self._open(req, data)
/usr/lib/python3.7/urllib/request.py:543: in _open
    '_open', req)
/usr/lib/python3.7/urllib/request.py:503: in _call_chain
    result = func(*args)
/usr/lib/python3.7/urllib/request.py:1345: in http_open
    return self.do_open(http.client.HTTPConnection, req)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <urllib.request.HTTPHandler object at 0x7f91ced745f8>
http_class = <class 'http.client.HTTPConnection'>
req = <urllib.request.Request object at 0x7f91ced78438>, http_conn_args = {}
host = '127.1', h = <http.client.HTTPConnection object at 0x7f91ced74748>

    def do_open(self, http_class, req, **http_conn_args):
        """…"""
[…]

        try:
            try:
                h.request(req.get_method(), req.selector, req.data, headers,
                          encode_chunked=req.has_header('Transfer-encoding'))
            except OSError as err: # timeout error
>               raise URLError(err)
E               urllib.error.URLError: <urlopen error [Errno 111] Connection refused>

/usr/lib/python3.7/urllib/request.py:1319: URLError`

With internet enabled, this works fine.
This had been working before April in our CI tests; however the only relevant change that I see in the environment would be an updated SSL package, and the request is not SSL.
Trying to reproduce the error in the test with a hand-written adjusted test (from PYFILE_CONTENTS) failed.

Closing.
Looks like new version fixed reported issue.

+ PYTHONPATH=/home/tkloczko/rpmbuild/BUILDROOT/python-pytest-remotedata-0.4.1-2.fc35.x86_64/usr/lib64/python3.8/site-packages:/home/tkloczko/rpmbuild/BUILDROOT/python-pytest-remotedata-0.4.1-2.fc35.x86_64/usr/lib/python3.8/site-packages
+ /usr/bin/pytest -ra -m 'not network'
============================= test session starts ==============================
platform linux -- Python 3.8.18, pytest-7.4.4, pluggy-1.3.0
rootdir: /home/tkloczko/rpmbuild/BUILD/pytest-remotedata-0.4.1
configfile: setup.cfg
testpaths: tests
plugins: remotedata-0.4.1
collected 15 items

tests/test_skip_remote_data.py sss...                                    [ 40%]
tests/test_socketblocker.py ....                                         [ 66%]
tests/test_strict_check.py .....                                         [100%]

=========================== short test summary info ============================
SKIPPED [3] ../../BUILDROOT/python-pytest-remotedata-0.4.1-2.fc35.x86_64/usr/lib/python3.8/site-packages/pytest_remotedata/plugin.py:81: need --remote-data option to run
======================== 12 passed, 3 skipped in 2.71s =========================

Hmm .. cannot close this thicket (there is no such option)