Skip to content

Commit 1877db8

Browse files
committed
Test HostUtil._get_host_info() using reference implementation of socket module
1 parent 1979d66 commit 1877db8

File tree

1 file changed

+116
-0
lines changed

1 file changed

+116
-0
lines changed

tests/unit/test_hostuserutil.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
import re
1919
from secrets import token_hex
2020
import socket
21+
from types import SimpleNamespace
22+
from typing import Optional
23+
from unittest.mock import Mock
2124

2225
import pytest
2326

@@ -36,6 +39,92 @@
3639
LOCALHOST_ALIASES = socket.gethostbyname_ex('localhost')[1]
3740

3841

42+
@pytest.fixture
43+
def mock_socket(monkeypatch: pytest.MonkeyPatch):
44+
"""Reference implementation of socket functions, with some of their real
45+
observed quirks.
46+
47+
Yes, it is horribly quirky. It is based on one linux system, and other
48+
systems or different /etc/hosts setup will result in different behaviour.
49+
"""
50+
this_name = 'NCC1701'
51+
this_fqdn = f'{this_name}.starfleet.gov'
52+
this_ip = '12.345.67.89'
53+
this_ex = (this_fqdn, [this_name], [this_ip])
54+
55+
localhost = 'localhost'
56+
localhost_fqdn = f'{localhost}.localdomain'
57+
localhost_ip = '127.0.0.1'
58+
localhost_aliases_v4 = [
59+
localhost_fqdn,
60+
f'{localhost}4',
61+
f'{localhost}4.localdomain4',
62+
]
63+
localhost_aliases_v6 = [
64+
localhost_fqdn,
65+
f'{localhost}6',
66+
f'{localhost}6.localdomain6',
67+
]
68+
localhost_ex = (
69+
localhost,
70+
[*localhost_aliases_v4, *localhost_aliases_v6],
71+
[localhost_ip, localhost_ip],
72+
)
73+
74+
def _getfqdn(x: Optional[str] = None):
75+
if x:
76+
x = x.lower()
77+
if not x or this_fqdn.lower().startswith(x) or x == this_ip:
78+
return this_fqdn
79+
if x in {localhost, localhost_fqdn, localhost_ip}:
80+
return localhost_fqdn
81+
return x
82+
83+
def _gethostbyaddr(x: str):
84+
x = x.lower()
85+
if this_fqdn.lower().startswith(x) or x == this_ip:
86+
return this_ex
87+
if x in {localhost, localhost_fqdn, '::1', *localhost_aliases_v6}:
88+
return (localhost, localhost_aliases_v6, ['::1'])
89+
if x in {localhost_ip, *localhost_aliases_v4}:
90+
return (localhost, localhost_aliases_v4, [localhost_ip])
91+
raise socket.gaierror("oopsie")
92+
93+
def _gethostbyname_ex(x: str):
94+
x = x.lower()
95+
if x in {this_fqdn.lower(), this_name.lower()}:
96+
return this_ex
97+
if this_fqdn.lower().startswith(x):
98+
return (this_fqdn, [], [this_ip])
99+
if x in {localhost, localhost_fqdn}:
100+
return localhost_ex
101+
if x in localhost_aliases_v6:
102+
return (localhost, localhost_aliases_v6, ['::1'])
103+
if x in localhost_aliases_v4:
104+
return (localhost, localhost_aliases_v4, [localhost_ip])
105+
raise socket.gaierror("oopsie")
106+
107+
mock_getfqdn = Mock(side_effect=_getfqdn)
108+
monkeypatch.setattr('cylc.flow.hostuserutil.socket.getfqdn', mock_getfqdn)
109+
mock_gethostbyaddr = Mock(side_effect=_gethostbyaddr)
110+
monkeypatch.setattr(
111+
'cylc.flow.hostuserutil.socket.gethostbyaddr', mock_gethostbyaddr
112+
)
113+
mock_gethostbyname_ex = Mock(side_effect=_gethostbyname_ex)
114+
monkeypatch.setattr(
115+
'cylc.flow.hostuserutil.socket.gethostbyname_ex', mock_gethostbyname_ex
116+
)
117+
return SimpleNamespace(
118+
this_fqdn=this_fqdn,
119+
this_ip=this_ip,
120+
this_ex=this_ex,
121+
localhost_ex=localhost_ex,
122+
getfqdn=mock_getfqdn,
123+
gethostbyaddr=mock_gethostbyaddr,
124+
gethostbyname_ex=mock_gethostbyname_ex,
125+
)
126+
127+
39128
def test_is_remote_user_on_current_user():
40129
"""is_remote_user with current user."""
41130
assert not is_remote_user(None)
@@ -101,3 +190,30 @@ def test_get_host_info__basic():
101190
with pytest.raises(IOError) as exc:
102191
hu._get_host_info(bad_host)
103192
assert bad_host in str(exc.value)
193+
194+
195+
def test_get_host_info__advanced(mock_socket):
196+
hu = HostUtil(expire=3600)
197+
assert mock_socket.gethostbyname_ex.call_count == 0
198+
assert hu._get_host_info() == mock_socket.this_ex
199+
assert mock_socket.gethostbyname_ex.call_count == 1
200+
# Test caching:
201+
hu._get_host_info()
202+
assert mock_socket.gethostbyname_ex.call_count == 1
203+
# Test variations of host name:
204+
assert hu._get_host_info('NCC1701') == mock_socket.this_ex
205+
assert hu._get_host_info('ncc1701.starfleet') == mock_socket.this_ex
206+
# (Note:)
207+
assert (
208+
mock_socket.gethostbyname_ex('ncc1701.starfleet')
209+
!= mock_socket.this_ex
210+
)
211+
assert hu._get_host_info('localhost4') == mock_socket.localhost_ex
212+
assert hu._get_host_info('localhost6') == mock_socket.localhost_ex
213+
# Test IP address:
214+
assert hu._get_host_info(mock_socket.this_ip) == mock_socket.this_ex
215+
assert hu._get_host_info('127.0.0.1') == mock_socket.localhost_ex
216+
# Test error:
217+
with pytest.raises(IOError):
218+
hu._get_host_info('nonexist')
219+
assert 'nonexist' not in hu._host_exs

0 commit comments

Comments
 (0)