Kaydet (Commit) 297b143c authored tarafından Nick Coghlan's avatar Nick Coghlan

Issue 14814: Further error case testing coverage and cleanups

üst 540715a3
...@@ -163,6 +163,7 @@ def _split_optional_netmask(address): ...@@ -163,6 +163,7 @@ def _split_optional_netmask(address):
raise AddressValueError("Only one '/' permitted in %r" % address) raise AddressValueError("Only one '/' permitted in %r" % address)
return addr return addr
def _find_address_range(addresses): def _find_address_range(addresses):
"""Find a sequence of IPv#Address. """Find a sequence of IPv#Address.
...@@ -408,6 +409,8 @@ def get_mixed_type_key(obj): ...@@ -408,6 +409,8 @@ def get_mixed_type_key(obj):
class _TotalOrderingMixin: class _TotalOrderingMixin:
# Helper that derives the other comparison operations from # Helper that derives the other comparison operations from
# __lt__ and __eq__ # __lt__ and __eq__
# We avoid functools.total_ordering because it doesn't handle
# NotImplemented correctly yet (http://bugs.python.org/issue10042)
def __eq__(self, other): def __eq__(self, other):
raise NotImplementedError raise NotImplementedError
def __ne__(self, other): def __ne__(self, other):
...@@ -455,6 +458,22 @@ class _IPAddressBase(_TotalOrderingMixin): ...@@ -455,6 +458,22 @@ class _IPAddressBase(_TotalOrderingMixin):
msg = '%200s has no version specified' % (type(self),) msg = '%200s has no version specified' % (type(self),)
raise NotImplementedError(msg) raise NotImplementedError(msg)
def _check_int_address(self, address):
if address < 0:
msg = "%d (< 0) is not permitted as an IPv%d address"
raise AddressValueError(msg % (address, self._version))
if address > self._ALL_ONES:
msg = "%d (>= 2**%d) is not permitted as an IPv%d address"
raise AddressValueError(msg % (address, self._max_prefixlen,
self._version))
def _check_packed_address(self, address, expected_len):
address_len = len(address)
if address_len != expected_len:
msg = "%r (len %d != %d) is not permitted as an IPv%d address"
raise AddressValueError(msg % (address, address_len,
expected_len, self._version))
def _ip_int_from_prefix(self, prefixlen=None): def _ip_int_from_prefix(self, prefixlen=None):
"""Turn the prefix length netmask into a int for comparison. """Turn the prefix length netmask into a int for comparison.
...@@ -1215,16 +1234,13 @@ class IPv4Address(_BaseV4, _BaseAddress): ...@@ -1215,16 +1234,13 @@ class IPv4Address(_BaseV4, _BaseAddress):
# Efficient constructor from integer. # Efficient constructor from integer.
if isinstance(address, int): if isinstance(address, int):
self._check_int_address(address)
self._ip = address self._ip = address
if address < 0 or address > self._ALL_ONES:
raise AddressValueError(address)
return return
# Constructing from a packed address # Constructing from a packed address
if isinstance(address, bytes): if isinstance(address, bytes):
if len(address) != 4: self._check_packed_address(address, 4)
msg = "Packed address %r must be exactly 4 bytes"
raise AddressValueError(msg % address)
self._ip = struct.unpack('!I', address)[0] self._ip = struct.unpack('!I', address)[0]
return return
...@@ -1368,11 +1384,7 @@ class IPv4Network(_BaseV4, _BaseNetwork): ...@@ -1368,11 +1384,7 @@ class IPv4Network(_BaseV4, _BaseNetwork):
# Constructing from a packed address # Constructing from a packed address
if isinstance(address, bytes): if isinstance(address, bytes):
if len(address) != 4: self.network_address = IPv4Address(address)
msg = "Packed address %r must be exactly 4 bytes"
raise AddressValueError(msg % address)
self.network_address = IPv4Address(
struct.unpack('!I', address)[0])
self._prefixlen = self._max_prefixlen self._prefixlen = self._max_prefixlen
self.netmask = IPv4Address(self._ALL_ONES) self.netmask = IPv4Address(self._ALL_ONES)
#fixme: address/network test here #fixme: address/network test here
...@@ -1380,11 +1392,9 @@ class IPv4Network(_BaseV4, _BaseNetwork): ...@@ -1380,11 +1392,9 @@ class IPv4Network(_BaseV4, _BaseNetwork):
# Efficient constructor from integer. # Efficient constructor from integer.
if isinstance(address, int): if isinstance(address, int):
self.network_address = IPv4Address(address)
self._prefixlen = self._max_prefixlen self._prefixlen = self._max_prefixlen
self.netmask = IPv4Address(self._ALL_ONES) self.netmask = IPv4Address(self._ALL_ONES)
if address < 0 or address > self._ALL_ONES:
raise AddressValueError(address)
self.network_address = IPv4Address(address)
#fixme: address/network test here. #fixme: address/network test here.
return return
...@@ -1868,16 +1878,13 @@ class IPv6Address(_BaseV6, _BaseAddress): ...@@ -1868,16 +1878,13 @@ class IPv6Address(_BaseV6, _BaseAddress):
# Efficient constructor from integer. # Efficient constructor from integer.
if isinstance(address, int): if isinstance(address, int):
self._check_int_address(address)
self._ip = address self._ip = address
if address < 0 or address > self._ALL_ONES:
raise AddressValueError(address)
return return
# Constructing from a packed address # Constructing from a packed address
if isinstance(address, bytes): if isinstance(address, bytes):
if len(address) != 16: self._check_packed_address(address, 16)
msg = "Packed address %r must be exactly 16 bytes"
raise AddressValueError(msg % address)
tmp = struct.unpack('!QQ', address) tmp = struct.unpack('!QQ', address)
self._ip = (tmp[0] << 64) | tmp[1] self._ip = (tmp[0] << 64) | tmp[1]
return return
...@@ -2014,8 +2021,6 @@ class IPv6Network(_BaseV6, _BaseNetwork): ...@@ -2014,8 +2021,6 @@ class IPv6Network(_BaseV6, _BaseNetwork):
# Efficient constructor from integer. # Efficient constructor from integer.
if isinstance(address, int): if isinstance(address, int):
if address < 0 or address > self._ALL_ONES:
raise AddressValueError(address)
self.network_address = IPv6Address(address) self.network_address = IPv6Address(address)
self._prefixlen = self._max_prefixlen self._prefixlen = self._max_prefixlen
self.netmask = IPv6Address(self._ALL_ONES) self.netmask = IPv6Address(self._ALL_ONES)
...@@ -2023,11 +2028,7 @@ class IPv6Network(_BaseV6, _BaseNetwork): ...@@ -2023,11 +2028,7 @@ class IPv6Network(_BaseV6, _BaseNetwork):
# Constructing from a packed address # Constructing from a packed address
if isinstance(address, bytes): if isinstance(address, bytes):
if len(address) != 16: self.network_address = IPv6Address(address)
msg = "Packed address %r must be exactly 16 bytes"
raise AddressValueError(msg % address)
tmp = struct.unpack('!QQ', address)
self.network_address = IPv6Address((tmp[0] << 64) | tmp[1])
self._prefixlen = self._max_prefixlen self._prefixlen = self._max_prefixlen
self.netmask = IPv6Address(self._ALL_ONES) self.netmask = IPv6Address(self._ALL_ONES)
return return
......
...@@ -23,6 +23,10 @@ class ErrorReporting(unittest.TestCase): ...@@ -23,6 +23,10 @@ class ErrorReporting(unittest.TestCase):
# address parts) that don't have an obvious home in the main test # address parts) that don't have an obvious home in the main test
# suite # suite
@property
def factory(self):
raise NotImplementedError
@contextlib.contextmanager @contextlib.contextmanager
def assertCleanError(self, exc_type, details, *args): def assertCleanError(self, exc_type, details, *args):
""" """
...@@ -49,11 +53,64 @@ class ErrorReporting(unittest.TestCase): ...@@ -49,11 +53,64 @@ class ErrorReporting(unittest.TestCase):
return self.assertCleanError(ipaddress.NetmaskValueError, return self.assertCleanError(ipaddress.NetmaskValueError,
details, *args) details, *args)
class AddressErrors_v4(ErrorReporting): class CommonErrorsMixin:
def test_empty_address(self): def test_empty_address(self):
with self.assertAddressError("Address cannot be empty"): with self.assertAddressError("Address cannot be empty"):
ipaddress.IPv4Address("") self.factory("")
def test_floats_rejected(self):
with self.assertAddressError(re.escape(repr("1.0"))):
self.factory(1.0)
class CommonErrorsMixin_v4(CommonErrorsMixin):
def test_negative_ints_rejected(self):
msg = "-1 (< 0) is not permitted as an IPv4 address"
with self.assertAddressError(re.escape(msg)):
self.factory(-1)
def test_large_ints_rejected(self):
msg = "%d (>= 2**32) is not permitted as an IPv4 address"
with self.assertAddressError(re.escape(msg % 2**32)):
self.factory(2**32)
def test_bad_packed_length(self):
def assertBadLength(length):
addr = b'\x00' * length
msg = "%r (len %d != 4) is not permitted as an IPv4 address"
with self.assertAddressError(re.escape(msg % (addr, length))):
self.factory(addr)
assertBadLength(3)
assertBadLength(5)
class CommonErrorsMixin_v6(CommonErrorsMixin):
def test_negative_ints_rejected(self):
msg = "-1 (< 0) is not permitted as an IPv6 address"
with self.assertAddressError(re.escape(msg)):
self.factory(-1)
def test_large_ints_rejected(self):
msg = "%d (>= 2**128) is not permitted as an IPv6 address"
with self.assertAddressError(re.escape(msg % 2**128)):
self.factory(2**128)
def test_bad_packed_length(self):
def assertBadLength(length):
addr = b'\x00' * length
msg = "%r (len %d != 16) is not permitted as an IPv6 address"
with self.assertAddressError(re.escape(msg % (addr, length))):
self.factory(addr)
self.factory(addr)
assertBadLength(15)
assertBadLength(17)
class AddressErrors_v4(ErrorReporting, CommonErrorsMixin_v4):
factory = ipaddress.IPv4Address
def test_network_passed_as_address(self): def test_network_passed_as_address(self):
addr = "127.0.0.1/24" addr = "127.0.0.1/24"
...@@ -133,22 +190,9 @@ class AddressErrors_v4(ErrorReporting): ...@@ -133,22 +190,9 @@ class AddressErrors_v4(ErrorReporting):
assertBadOctet("12345.67899.-54321.-98765", 12345) assertBadOctet("12345.67899.-54321.-98765", 12345)
assertBadOctet("257.0.0.0", 257) assertBadOctet("257.0.0.0", 257)
def test_bad_packed_length(self):
def assertBadLength(length):
addr = b'\x00' * length
msg = "Packed address %r must be exactly 4 bytes" % addr
with self.assertAddressError(re.escape(msg)):
ipaddress.IPv4Address(addr)
assertBadLength(3)
assertBadLength(5)
class AddressErrors_v6(ErrorReporting, CommonErrorsMixin_v6):
class AddressErrors_v6(ErrorReporting): factory = ipaddress.IPv6Address
def test_empty_address(self):
with self.assertAddressError("Address cannot be empty"):
ipaddress.IPv6Address("")
def test_network_passed_as_address(self): def test_network_passed_as_address(self):
addr = "::1/24" addr = "::1/24"
...@@ -277,24 +321,10 @@ class AddressErrors_v6(ErrorReporting): ...@@ -277,24 +321,10 @@ class AddressErrors_v6(ErrorReporting):
assertBadPart("02001:db8::", "02001") assertBadPart("02001:db8::", "02001")
assertBadPart('2001:888888::1', "888888") assertBadPart('2001:888888::1', "888888")
def test_bad_packed_length(self):
def assertBadLength(length):
addr = b'\x00' * length
msg = "Packed address %r must be exactly 16 bytes" % addr
with self.assertAddressError(re.escape(msg)):
ipaddress.IPv6Address(addr)
assertBadLength(15)
assertBadLength(17)
class NetmaskErrorsMixin_v4: class NetmaskErrorsMixin_v4(CommonErrorsMixin_v4):
"""Input validation on interfaces and networks is very similar""" """Input validation on interfaces and networks is very similar"""
@property
def factory(self):
raise NotImplementedError
def test_split_netmask(self): def test_split_netmask(self):
addr = "1.2.3.4/32/24" addr = "1.2.3.4/32/24"
with self.assertAddressError("Only one '/' permitted in %r" % addr): with self.assertAddressError("Only one '/' permitted in %r" % addr):
...@@ -305,7 +335,8 @@ class NetmaskErrorsMixin_v4: ...@@ -305,7 +335,8 @@ class NetmaskErrorsMixin_v4:
with self.assertAddressError(details): with self.assertAddressError(details):
self.factory(addr) self.factory(addr)
assertBadAddress("", "Address cannot be empty") assertBadAddress("/", "Address cannot be empty")
assertBadAddress("/8", "Address cannot be empty")
assertBadAddress("bogus", "Expected 4 octets") assertBadAddress("bogus", "Expected 4 octets")
assertBadAddress("google.com", "Expected 4 octets") assertBadAddress("google.com", "Expected 4 octets")
assertBadAddress("10/8", "Expected 4 octets") assertBadAddress("10/8", "Expected 4 octets")
...@@ -325,17 +356,6 @@ class NetmaskErrorsMixin_v4: ...@@ -325,17 +356,6 @@ class NetmaskErrorsMixin_v4:
assertBadNetmask("1.1.1.1", "1.a.2.3") assertBadNetmask("1.1.1.1", "1.a.2.3")
assertBadNetmask("1.1.1.1", "pudding") assertBadNetmask("1.1.1.1", "pudding")
def test_bad_packed_length(self):
def assertBadLength(length):
addr = b'\x00' * length
msg = "Packed address %r must be exactly 4 bytes" % addr
with self.assertAddressError(re.escape(msg)):
self.factory(addr)
assertBadLength(3)
assertBadLength(5)
class InterfaceErrors_v4(ErrorReporting, NetmaskErrorsMixin_v4): class InterfaceErrors_v4(ErrorReporting, NetmaskErrorsMixin_v4):
factory = ipaddress.IPv4Interface factory = ipaddress.IPv4Interface
...@@ -343,13 +363,9 @@ class NetworkErrors_v4(ErrorReporting, NetmaskErrorsMixin_v4): ...@@ -343,13 +363,9 @@ class NetworkErrors_v4(ErrorReporting, NetmaskErrorsMixin_v4):
factory = ipaddress.IPv4Network factory = ipaddress.IPv4Network
class NetmaskErrorsMixin_v6: class NetmaskErrorsMixin_v6(CommonErrorsMixin_v6):
"""Input validation on interfaces and networks is very similar""" """Input validation on interfaces and networks is very similar"""
@property
def factory(self):
raise NotImplementedError
def test_split_netmask(self): def test_split_netmask(self):
addr = "cafe:cafe::/128/190" addr = "cafe:cafe::/128/190"
with self.assertAddressError("Only one '/' permitted in %r" % addr): with self.assertAddressError("Only one '/' permitted in %r" % addr):
...@@ -360,7 +376,8 @@ class NetmaskErrorsMixin_v6: ...@@ -360,7 +376,8 @@ class NetmaskErrorsMixin_v6:
with self.assertAddressError(details): with self.assertAddressError(details):
self.factory(addr) self.factory(addr)
assertBadAddress("", "Address cannot be empty") assertBadAddress("/", "Address cannot be empty")
assertBadAddress("/8", "Address cannot be empty")
assertBadAddress("google.com", "At least 3 parts") assertBadAddress("google.com", "At least 3 parts")
assertBadAddress("1.2.3.4", "At least 3 parts") assertBadAddress("1.2.3.4", "At least 3 parts")
assertBadAddress("10/8", "At least 3 parts") assertBadAddress("10/8", "At least 3 parts")
...@@ -378,17 +395,6 @@ class NetmaskErrorsMixin_v6: ...@@ -378,17 +395,6 @@ class NetmaskErrorsMixin_v6:
assertBadNetmask("::1", "129") assertBadNetmask("::1", "129")
assertBadNetmask("::1", "pudding") assertBadNetmask("::1", "pudding")
def test_bad_packed_length(self):
def assertBadLength(length):
addr = b'\x00' * length
msg = "Packed address %r must be exactly 16 bytes" % addr
with self.assertAddressError(re.escape(msg)):
self.factory(addr)
assertBadLength(15)
assertBadLength(17)
class InterfaceErrors_v6(ErrorReporting, NetmaskErrorsMixin_v6): class InterfaceErrors_v6(ErrorReporting, NetmaskErrorsMixin_v6):
factory = ipaddress.IPv6Interface factory = ipaddress.IPv6Interface
...@@ -585,10 +591,6 @@ class IpaddrUnitTest(unittest.TestCase): ...@@ -585,10 +591,6 @@ class IpaddrUnitTest(unittest.TestCase):
def testIpFromInt(self): def testIpFromInt(self):
self.assertEqual(self.ipv4_interface._ip, self.assertEqual(self.ipv4_interface._ip,
ipaddress.IPv4Interface(16909060)._ip) ipaddress.IPv4Interface(16909060)._ip)
self.assertRaises(ipaddress.AddressValueError,
ipaddress.IPv4Interface, 2**32)
self.assertRaises(ipaddress.AddressValueError,
ipaddress.IPv4Interface, -1)
ipv4 = ipaddress.ip_network('1.2.3.4') ipv4 = ipaddress.ip_network('1.2.3.4')
ipv6 = ipaddress.ip_network('2001:658:22a:cafe:200:0:0:1') ipv6 = ipaddress.ip_network('2001:658:22a:cafe:200:0:0:1')
...@@ -598,14 +600,6 @@ class IpaddrUnitTest(unittest.TestCase): ...@@ -598,14 +600,6 @@ class IpaddrUnitTest(unittest.TestCase):
v6_int = 42540616829182469433547762482097946625 v6_int = 42540616829182469433547762482097946625
self.assertEqual(self.ipv6_interface._ip, self.assertEqual(self.ipv6_interface._ip,
ipaddress.IPv6Interface(v6_int)._ip) ipaddress.IPv6Interface(v6_int)._ip)
self.assertRaises(ipaddress.AddressValueError,
ipaddress.IPv6Interface, 2**128)
self.assertRaises(ipaddress.AddressValueError,
ipaddress.IPv6Interface, -1)
self.assertRaises(ipaddress.AddressValueError,
ipaddress.IPv6Network, 2**128)
self.assertRaises(ipaddress.AddressValueError,
ipaddress.IPv6Network, -1)
self.assertEqual(ipaddress.ip_network(self.ipv4_address._ip).version, self.assertEqual(ipaddress.ip_network(self.ipv4_address._ip).version,
4) 4)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment