|
16 | 16 | import time |
17 | 17 | import unittest |
18 | 18 | import unittest.mock |
| 19 | +from functools import lru_cache |
19 | 20 | from threading import Event |
20 | 21 | from typing import Dict, Optional, cast # noqa # used in type hints |
21 | 22 |
|
| 23 | +import ifaddr |
| 24 | + |
22 | 25 | import pytest |
23 | 26 |
|
24 | 27 | import zeroconf as r |
@@ -57,6 +60,28 @@ def teardown_module(): |
57 | 60 | log.setLevel(original_logging_level) |
58 | 61 |
|
59 | 62 |
|
| 63 | +@lru_cache(maxsize=None) |
| 64 | +def has_working_ipv6(): |
| 65 | + """Return True if if the system can bind an IPv6 address.""" |
| 66 | + if not socket.has_ipv6: |
| 67 | + return False |
| 68 | + |
| 69 | + try: |
| 70 | + sock = socket.socket(socket.AF_INET6) |
| 71 | + sock.bind(('::1', 0)) |
| 72 | + except Exception: |
| 73 | + return False |
| 74 | + finally: |
| 75 | + if sock: |
| 76 | + sock.close() |
| 77 | + |
| 78 | + for iface in ifaddr.get_adapters(): |
| 79 | + for addr in iface.ips: |
| 80 | + if addr.is_IPv6 and iface.index is not None: |
| 81 | + return True |
| 82 | + return False |
| 83 | + |
| 84 | + |
60 | 85 | class TestDunder(unittest.TestCase): |
61 | 86 | def test_dns_text_repr(self): |
62 | 87 | # There was an issue on Python 3 that prevented DNSText's repr |
@@ -550,15 +575,15 @@ def test_close_multiple_times(self): |
550 | 575 | rv.close() |
551 | 576 | rv.close() |
552 | 577 |
|
553 | | - @unittest.skipIf(not socket.has_ipv6, 'Requires IPv6') |
| 578 | + @unittest.skipIf(not has_working_ipv6(), 'Requires IPv6') |
554 | 579 | @unittest.skipIf(os.environ.get('SKIP_IPV6'), 'IPv6 tests disabled') |
555 | 580 | def test_launch_and_close_v4_v6(self): |
556 | 581 | rv = r.Zeroconf(interfaces=r.InterfaceChoice.All, ip_version=r.IPVersion.All) |
557 | 582 | rv.close() |
558 | 583 | rv = r.Zeroconf(interfaces=r.InterfaceChoice.Default, ip_version=r.IPVersion.All) |
559 | 584 | rv.close() |
560 | 585 |
|
561 | | - @unittest.skipIf(not socket.has_ipv6, 'Requires IPv6') |
| 586 | + @unittest.skipIf(not has_working_ipv6(), 'Requires IPv6') |
562 | 587 | @unittest.skipIf(os.environ.get('SKIP_IPV6'), 'IPv6 tests disabled') |
563 | 588 | def test_launch_and_close_v6_only(self): |
564 | 589 | rv = r.Zeroconf(interfaces=r.InterfaceChoice.All, ip_version=r.IPVersion.V6Only) |
@@ -1092,7 +1117,7 @@ def test_integration_with_listener(self): |
1092 | 1117 | finally: |
1093 | 1118 | zeroconf_registrar.close() |
1094 | 1119 |
|
1095 | | - @unittest.skipIf(not socket.has_ipv6, 'Requires IPv6') |
| 1120 | + @unittest.skipIf(not has_working_ipv6(), 'Requires IPv6') |
1096 | 1121 | @unittest.skipIf(os.environ.get('SKIP_IPV6'), 'IPv6 tests disabled') |
1097 | 1122 | def test_integration_with_listener_v6_records(self): |
1098 | 1123 |
|
@@ -1124,7 +1149,7 @@ def test_integration_with_listener_v6_records(self): |
1124 | 1149 | finally: |
1125 | 1150 | zeroconf_registrar.close() |
1126 | 1151 |
|
1127 | | - @unittest.skipIf(not socket.has_ipv6, 'Requires IPv6') |
| 1152 | + @unittest.skipIf(not has_working_ipv6(), 'Requires IPv6') |
1128 | 1153 | @unittest.skipIf(os.environ.get('SKIP_IPV6'), 'IPv6 tests disabled') |
1129 | 1154 | def test_integration_with_listener_ipv6(self): |
1130 | 1155 |
|
@@ -1240,7 +1265,7 @@ def update_service(self, zeroconf, type, name): |
1240 | 1265 | desc = {'path': '/~paulsm/'} # type: Dict |
1241 | 1266 | desc.update(properties) |
1242 | 1267 | addresses = [socket.inet_aton("10.0.1.2")] |
1243 | | - if socket.has_ipv6 and not os.environ.get('SKIP_IPV6'): |
| 1268 | + if has_working_ipv6() and not os.environ.get('SKIP_IPV6'): |
1244 | 1269 | addresses.append(socket.inet_pton(socket.AF_INET6, "2001:db8::1")) |
1245 | 1270 | info_service = ServiceInfo( |
1246 | 1271 | subtype, registration_name, port=80, properties=desc, server="ash-2.local.", addresses=addresses |
@@ -1344,7 +1369,7 @@ def update_service(self, zeroconf, type, name): |
1344 | 1369 |
|
1345 | 1370 | class TestServiceBrowser(unittest.TestCase): |
1346 | 1371 | def test_update_record(self): |
1347 | | - enable_ipv6 = socket.has_ipv6 and not os.environ.get('SKIP_IPV6') |
| 1372 | + enable_ipv6 = has_working_ipv6() and not os.environ.get('SKIP_IPV6') |
1348 | 1373 |
|
1349 | 1374 | service_name = 'name._type._tcp.local.' |
1350 | 1375 | service_type = '_type._tcp.local.' |
@@ -2110,7 +2135,7 @@ def test_multiple_addresses(): |
2110 | 2135 | ) |
2111 | 2136 | assert info.addresses == [address, address] |
2112 | 2137 |
|
2113 | | - if socket.has_ipv6 and not os.environ.get('SKIP_IPV6'): |
| 2138 | + if has_working_ipv6() and not os.environ.get('SKIP_IPV6'): |
2114 | 2139 | address_v6_parsed = "2001:db8::1" |
2115 | 2140 | address_v6 = socket.inet_pton(socket.AF_INET6, address_v6_parsed) |
2116 | 2141 | infos = [ |
|
0 commit comments