1#!/usr/bin/env python 2 3import sys 4import re 5import os 6 7sys.path.insert(0, "..") 8 9from ciscoconfparse.ccp_util import _RGX_IPV4ADDR, _RGX_IPV6ADDR 10from ciscoconfparse.ccp_util import IPv4Obj, L4Object 11from ciscoconfparse.ccp_util import CiscoRange 12from ciscoconfparse.ccp_util import IPv6Obj 13from ciscoconfparse.ccp_util import dns_lookup, reverse_dns_lookup 14from ciscoconfparse.ccp_util import collapse_addresses 15import pytest 16 17if sys.version_info[0] < 3: 18 from ipaddr import IPv4Network, IPv6Network, IPv4Address, IPv6Address 19 import ipaddr 20else: 21 from ipaddress import IPv4Network, IPv6Network, IPv4Address, IPv6Address 22 import ipaddress 23 24r""" test_Ccp_Util.py - Parse, Query, Build, and Modify IOS-style configs 25 26 Copyright (C) 2020-2021 David Michael Pennington at Cisco Systems 27 Copyright (C) 2019 David Michael Pennington at ThousandEyes 28 Copyright (C) 2014-2019 David Michael Pennington at Samsung Data Services 29 30 This program is free software: you can redistribute it and/or modify 31 it under the terms of the GNU General Public License as published by 32 the Free Software Foundation, either version 3 of the License, or 33 (at your option) any later version. 34 35 This program is distributed in the hope that it will be useful, 36 but WITHOUT ANY WARRANTY; without even the implied warranty of 37 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 38 GNU General Public License for more details. 39 40 You should have received a copy of the GNU General Public License 41 along with this program. If not, see <http://www.gnu.org/licenses/>. 42 43 If you need to contact the author, you can do so by emailing: 44 mike [~at~] pennington [/dot\] net 45""" 46 47 48 49@pytest.mark.parametrize( 50 "addr", ["192.0.2.1", "4.2.2.2", "10.255.255.255", "127.0.0.1",] 51) 52def test_IPv4_REGEX(addr): 53 test_result = _RGX_IPV4ADDR.search(addr) 54 assert test_result.group("addr") == addr 55 56 57@pytest.mark.parametrize( 58 "addr", 59 [ 60 "fe80::", # Trailing double colons 61 "fe80:beef::", # Trailing double colons 62 "fe80:dead:beef::", # Trailing double colons 63 "fe80:a:dead:beef::", # Trailing double colons 64 "fe80:a:a:dead:beef::", # Trailing double colons 65 "fe80:a:a:a:dead:beef::", # Trailing double colons 66 "fe80:a:a:a:a:dead:beef::", # Trailing double colons 67 "fe80:dead:beef::a", # 68 "fe80:dead:beef::a:b", # 69 "fe80:dead:beef::a:b:c", # 70 "fe80:dead:beef::a:b:c:d", # 71 "FE80:AAAA::DEAD:BEEF", # Capital letters 72 "FE80:AAAA:0000:0000:0000:0000:DEAD:BEEF", # Capital Letters 73 "0:0:0:0:0:0:0:1", # Loopback 74 "::1", # Loopback, leading double-colons 75 "::", # Shorthand for 0:0:0:0:0:0:0:0 76 ], 77) 78def test_IPv6_REGEX(addr): 79 test_result = _RGX_IPV6ADDR.search(addr) 80 assert test_result.group("addr") == addr 81 82 83@pytest.mark.parametrize( 84 "addr", 85 [ 86 "fe80:", # Single trailing colon 87 "fe80:beef", # Insufficient number of bytes 88 "fe80:dead:beef", # Insufficient number of bytes 89 "fe80:a:dead:beef", # Insufficient number of bytes 90 "fe80:a:a:dead:beef", # Insufficient number of bytes 91 "fe80:a:a:a:dead:beef", # Insufficient number of bytes 92 "fe80:a:a:a:a:dead:beef", # Insufficient number of bytes 93 "fe80:a:a:a :a:dead:beef", # Superflous space 94 "zzzz:a:a:a:a:a:dead:beef", # bad characters 95 "0:0:0:0:0:0:1", # Loopback, insufficient bytes 96 "0:0:0:0:0:1", # Loopback, insufficient bytes 97 "0:0:0:0:1", # Loopback, insufficient bytes 98 "0:0:0:1", # Loopback, insufficient bytes 99 "0:0:1", # Loopback, insufficient bytes 100 "0:1", # Loopback, insufficient bytes 101 ":1", # Loopback, insufficient bytes 102 # FIXME: The following *should* fail, but I'm not failing on them 103 #':::beef', # FAIL Too many leading colons 104 #'fe80::dead::beef', # FAIL multiple double colons 105 #'::1::', # FAIL too many double colons 106 #'fe80:0:0:0:0:0:dead:beef::', # FAIL Too many bytes with double colons 107 ], 108) 109def test_negative_IPv6_REGEX(addr): 110 test_result = _RGX_IPV6ADDR.search(addr) 111 assert test_result is None # The regex *should* fail on these addrs 112 113 114def testL4Object_asa_eq01(): 115 pp = L4Object(protocol="tcp", port_spec="eq smtp", syntax="asa") 116 assert pp.protocol == "tcp" 117 assert pp.port_list == [25] 118 119 120def testL4Object_asa_eq02(): 121 pp = L4Object(protocol="tcp", port_spec="smtp", syntax="asa") 122 assert pp.protocol == "tcp" 123 assert pp.port_list == [25] 124 125 126def testL4Object_asa_range01(): 127 pp = L4Object(protocol="tcp", port_spec="range smtp 32", syntax="asa") 128 assert pp.protocol == "tcp" 129 assert pp.port_list == sorted(range(25, 33)) 130 131 132def testL4Object_asa_lt01(): 133 pp = L4Object(protocol="tcp", port_spec="lt echo", syntax="asa") 134 assert pp.protocol == "tcp" 135 assert pp.port_list ==sorted(range(1, 7)) 136 137def testL4Object_asa_gt01(): 138 pp = L4Object(protocol="tcp", port_spec="gt 65534", syntax="asa") 139 assert pp.protocol == "tcp" 140 assert pp.port_list ==[65535] 141 142@pytest.mark.xfail( 143 sys.version_info[0] == 3 and sys.version_info[1] == 2, 144 reason="Known failure in Python3.2 due to range()", 145) 146def testL4Object_asa_lt02(): 147 pp = L4Object(protocol="tcp", port_spec="lt 7", syntax="asa") 148 assert pp.protocol == "tcp" 149 assert pp.port_list == sorted(range(1, 7)) 150 151 152def testIPv4Obj_contain(): 153 ## Test ccp_util.IPv4Obj.__contains__() 154 ## 155 ## Test whether a prefix is or is not contained in another prefix 156 results_correct = [ 157 ("1.0.0.0/8", "0.0.0.0/0", True), # Is 1.0.0.0/8 in 0.0.0.0/0? 158 ("0.0.0.0/0", "1.0.0.0/8", False), # Is 0.0.0.0/0 in 1.0.0.0/8? 159 ("1.1.1.0/27", "1.0.0.0/8", True), # Is 1.1.1.0/27 in 1.0.0.0/8? 160 ("1.1.1.0/27", "9.9.9.9/32", False), # Is 1.1.1.0/27 in 9.9.9.9/32? 161 ("9.9.9.0/27", "9.9.9.9/32", False), # Is 9.9.9.0/27 in 9.9.9.9/32? 162 ] 163 for prefix1, prefix2, result_correct in results_correct: 164 ## 'foo in bar' tests bar.__contains__(foo) 165 test_result = IPv4Obj(prefix1) in IPv4Obj(prefix2) 166 assert test_result == result_correct 167 168 169def testIPv4Obj_parse(): 170 ## Ensure that IPv4Obj can correctly parse various inputs 171 test_strings = [ 172 "1.0.0.1/24", 173 "1.0.0.1/32", 174 "1.0.0.1 255.255.255.0", 175 "1.0.0.1 255.255.255.255", 176 "1.0.0.1 255.255.255.0", 177 "1.0.0.1 255.255.255.255", 178 "1.0.0.1/255.255.255.0", 179 "1.0.0.1/255.255.255.255", 180 ] 181 for test_string in test_strings: 182 test_result = IPv4Obj(test_string) 183 assert isinstance(test_result, IPv4Obj) 184 185 186def testIPv4Obj_attributes(): 187 ## Ensure that attributes are accessible and pass the smell test 188 test_object = IPv4Obj("1.0.0.1 255.255.255.0") 189 results_correct = [ 190 ("ip", IPv4Address("1.0.0.1")), 191 ("ip_object", IPv4Address("1.0.0.1")), 192 ("netmask", IPv4Address("255.255.255.0")), 193 ("prefixlen", 24), 194 ("broadcast", IPv4Address("1.0.0.255")), 195 ("network", IPv4Network("1.0.0.0/24")), 196 ("network_object", IPv4Network("1.0.0.0/24")), 197 ("hostmask", IPv4Address("0.0.0.255")), 198 ("numhosts", 256), 199 ("version", 4), 200 ("is_reserved", False), 201 ("is_multicast", False), 202 ("is_private", False), 203 ("as_cidr_addr", "1.0.0.1/24"), 204 ("as_cidr_net", "1.0.0.0/24"), 205 ("as_decimal", 16777217), 206 ("as_decimal_network", 16777216), 207 ("as_hex_tuple", ("01", "00", "00", "01")), 208 ("as_binary_tuple", ("00000001", "00000000", "00000000", "00000001")), 209 ("as_zeropadded", "001.000.000.001"), 210 ("as_zeropadded_network", "001.000.000.000/24"), 211 ] 212 for attribute, result_correct in results_correct: 213 214 assert getattr(test_object, attribute) == result_correct 215 216 217def testIPv6Obj_attributes(): 218 ## Ensure that attributes are accessible and pass the smell test 219 test_object = IPv6Obj("2001::dead:beef/64") 220 results_correct = [ 221 ("ip", IPv6Address("2001::dead:beef")), 222 ("ip_object", IPv6Address("2001::dead:beef")), 223 ("netmask", IPv6Address("ffff:ffff:ffff:ffff::")), 224 ("prefixlen", 64), 225 ("network", IPv6Network("2001::/64")), 226 ("network_object", IPv6Network("2001::/64")), 227 ("hostmask", IPv6Address("::ffff:ffff:ffff:ffff")), 228 ("numhosts", 18446744073709551616), 229 ("version", 6), 230 ("is_reserved", False), 231 ("is_multicast", False), 232 # ("is_private", False), # FIXME: disabling this for now... 233 # py2.7 and py3.x produce different results 234 ("as_cidr_addr", "2001::dead:beef/64"), 235 ("as_cidr_net", "2001::/64"), 236 ("as_decimal", 42540488161975842760550356429036175087), 237 ("as_decimal_network", 42540488161975842760550356425300246528), 238 ( 239 "as_hex_tuple", 240 ("2001", "0000", "0000", "0000", "0000", "0000", "dead", "beef"), 241 ), 242 ( 243 "as_binary_tuple", 244 ( 245 "0010000000000001", 246 "0000000000000000", 247 "0000000000000000", 248 "0000000000000000", 249 "0000000000000000", 250 "0000000000000000", 251 "1101111010101101", 252 "1011111011101111", 253 ), 254 ), 255 ] 256 for attribute, result_correct in results_correct: 257 258 assert getattr(test_object, attribute) == result_correct 259 260 261def testIPv4Obj_sort_01(): 262 """Simple IPv4Obj sorting test""" 263 cidr_addrs_list = [ 264 "192.168.1.3/32", 265 "192.168.1.2/32", 266 "192.168.1.1/32", 267 "192.168.1.4/15", 268 ] 269 270 result_correct = [ 271 "192.168.1.4/15", # Shorter prefixes are "lower" than longer prefixes 272 "192.168.1.1/32", 273 "192.168.1.2/32", 274 "192.168.1.3/32", 275 ] 276 277 obj_list = [IPv4Obj(ii) for ii in cidr_addrs_list] 278 # Ensure we get the correct sorted order for this list 279 assert [ii.as_cidr_addr for ii in sorted(obj_list)] == result_correct 280 281 282def testIPv4Obj_sort_02(): 283 """Complex IPv4Obj sorting test""" 284 cidr_addrs_list = [ 285 "192.168.1.1/32", 286 "192.168.0.1/32", 287 "192.168.0.2/16", 288 "192.168.0.3/15", 289 "0.0.0.0/32", 290 "0.0.0.1/31", 291 "16.0.0.1/8", 292 "0.0.0.2/30", 293 "127.0.0.0/0", 294 "16.0.0.0/1", 295 "128.0.0.0/1", 296 "16.0.0.0/4", 297 "16.0.0.3/4", 298 "0.0.0.0/0", 299 "0.0.0.0/8", 300 ] 301 302 result_correct = [ 303 "0.0.0.0/0", 304 "127.0.0.0/0", 305 "16.0.0.0/1", 306 "0.0.0.0/8", # for the same network, longer prefixlens sort "higher" than shorter prefixlens 307 "0.0.0.2/30", # for the same network, longer prefixlens sort "higher" than shorter prefixlens 308 "0.0.0.1/31", # for the same network, longer prefixlens sort "higher" than shorter prefixlens 309 "0.0.0.0/32", 310 "16.0.0.0/4", 311 "16.0.0.3/4", 312 "16.0.0.1/8", # for the same network, longer prefixlens sort "higher" than shorter prefixlens 313 "128.0.0.0/1", 314 "192.168.0.3/15", 315 "192.168.0.2/16", 316 "192.168.0.1/32", 317 "192.168.1.1/32", 318 ] 319 320 obj_list = [IPv4Obj(ii) for ii in cidr_addrs_list] 321 # Ensure we get the correct sorted order for this list 322 assert [ii.as_cidr_addr for ii in sorted(obj_list)] == result_correct 323 324 325def testIPv4Obj_recursive(): 326 """IPv4Obj() should be able to parse itself""" 327 obj = IPv4Obj(IPv4Obj("1.1.1.1/24")) 328 assert str(obj.ip_object) == "1.1.1.1" 329 assert obj.prefixlen == 24 330 331def testIPv4Obj_from_int(): 332 assert IPv4Obj(2886729984).ip == IPv4Address('172.16.1.0') 333 334def testIPv4Obj_neq_01(): 335 """Simple in-equality test fail (ref - Github issue #180)""" 336 assert IPv4Obj("1.1.1.1/24") != "" 337 338 339def testIPv4Obj_neq_02(): 340 """Simple in-equality test""" 341 obj1 = IPv4Obj("1.1.1.1/24") 342 obj2 = IPv4Obj("1.1.1.2/24") 343 assert obj1 != obj2 344 345 346def testIPv4Obj_eq_01(): 347 """Simple equality test""" 348 obj1 = IPv4Obj("1.1.1.1/24") 349 obj2 = IPv4Obj("1.1.1.1/24") 350 assert obj1 == obj2 351 352 353def testIPv4Obj_eq_02(): 354 """Simple equality test""" 355 obj1 = IPv4Obj("1.1.1.1/24") 356 obj2 = IPv4Obj("1.1.1.0/24") 357 assert obj1 != obj2 358 359 360def testIPv4Obj_gt_01(): 361 """Simple greater-than test - same network number""" 362 assert IPv4Obj("1.1.1.1/24") > IPv4Obj("1.1.1.0/24") 363 364 365def testIPv4Obj_gt_02(): 366 """Simple greater-than test - different network number""" 367 assert IPv4Obj("1.1.1.0/24") > IPv4Obj("1.1.0.0/24") 368 369 370def testIPv4Obj_gt_03(): 371 """Simple greater-than test - different prefixlen""" 372 assert IPv4Obj("1.1.1.0/24") > IPv4Obj("1.1.0.0/23") 373 374 375def testIPv4Obj_lt_01(): 376 """Simple less-than test - same network number""" 377 obj1 = IPv4Obj("1.1.1.1/24") 378 obj2 = IPv4Obj("1.1.1.0/24") 379 assert obj2 < obj1 380 381 382def testIPv4Obj_lt_02(): 383 """Simple less-than test - different network number""" 384 obj1 = IPv4Obj("1.1.1.0/24") 385 obj2 = IPv4Obj("1.1.0.0/24") 386 assert obj2 < obj1 387 388 389def testIPv4Obj_lt_03(): 390 """Simple less-than test - different prefixlen""" 391 obj1 = IPv4Obj("1.1.1.0/24") 392 obj2 = IPv4Obj("1.1.0.0/23") 393 assert obj2 < obj1 394 395 396def testIPv4Obj_contains_01(): 397 """Test __contains__ method""" 398 obj1 = IPv4Obj("1.1.1.0/24") 399 obj2 = IPv4Obj("1.1.0.0/23") 400 assert obj1 in obj2 401 402 403def testIPv4Obj_contains_02(): 404 """Test __contains__ method""" 405 obj1 = IPv4Obj("1.1.1.1/32") 406 obj2 = IPv4Obj("1.1.1.0/24") 407 assert obj1 in obj2 408 409 410def testIPv4Obj_contains_03(): 411 """Test __contains__ method""" 412 obj1 = IPv4Obj("1.1.1.255/32") 413 obj2 = IPv4Obj("1.1.1.0/24") 414 assert obj1 in obj2 415 416 417def testIPv6Obj_recursive(): 418 """IPv6Obj() should be able to parse itself""" 419 obj = IPv6Obj(IPv6Obj("fe80:a:b:c:d:e::1/64")) 420 assert str(obj.ip_object) == "fe80:a:b:c:d:e:0:1" 421 assert obj.prefixlen == 64 422 423 424def testIPv6Obj_neq_01(): 425 """Simple in-equality test fail (ref - Github issue #180)""" 426 assert IPv6Obj("::1") != "" 427 428 429def testIPv6Obj_neq_02(): 430 """Simple in-equality test""" 431 assert IPv6Obj("::1") != IPv6Obj("::2") 432 433 434def testIPv6Obj_eq_01(): 435 """Simple equality test""" 436 assert IPv6Obj("::1") == IPv6Obj("::1") 437 438 439def testIPv6Obj_gt_01(): 440 """Simple greater_than test""" 441 assert IPv6Obj("::2") > IPv6Obj("::1") 442 443def test_collapse_addresses_01(): 444 445 if sys.version_info >= (3, 0, 0): 446 net_collapsed = ipaddress.collapse_addresses([IPv4Network('192.0.0.0/22'), IPv4Network('192.0.2.128/25')]) 447 448 else: 449 net_collapsed = ipaddr.collapse_addresses([IPv4Network('192.0.0.0/22'), IPv4Network('192.0.2.128/25')]) 450 451 for idx, entry in enumerate(net_collapsed): 452 if idx==0: 453 assert entry == IPv4Network("192.0.0.0/22") 454 455def test_collapse_addresses_02(): 456 net_list = [IPv4Obj('192.0.2.128/25'), IPv4Obj('192.0.0.0/26')] 457 collapsed_list = sorted(collapse_addresses(net_list)) 458 assert collapsed_list[0].network_address==IPv4Obj('192.0.0.0/26').ip 459 assert collapsed_list[1].network_address==IPv4Obj('192.0.2.128/25').ip 460 461 462def test_dns_lookup(): 463 # Use VMWare's opencloud A-record to test... 464 # ref http://stackoverflow.com/a/7714208/667301 465 result_correct = {"addrs": ["127.0.0.1"], "name": "*.vcap.me", "error": ""} 466 test_result = dns_lookup("*.vcap.me") 467 if not test_result["error"]: 468 assert dns_lookup("*.vcap.me") == result_correct 469 else: 470 pytest.skip(test_result["error"]) 471 472 473def test_reverse_dns_lookup(): 474 result_correct = {"addr": "127.0.0.1", "name": "localhost.", "error": ""} 475 test_result = reverse_dns_lookup("127.0.0.1") 476 if not test_result["error"]: 477 assert "localhost" in test_result["name"].lower() 478 else: 479 pytest.skip(test_result["error"]) 480 481 482def test_CiscoRange_01(): 483 """Basic vlan range test""" 484 result_correct = ["1"] 485 assert CiscoRange("1").as_list == result_correct 486 487 488def test_CiscoRange_02(): 489 """Basic vlan range test""" 490 result_correct = ["1", "3"] 491 assert CiscoRange("1,3").as_list == result_correct 492 493 494def test_CiscoRange_03(): 495 """Basic vlan range test""" 496 result_correct = ["1", "2", "3", "4", "5"] 497 assert CiscoRange("1,2-4,5").as_list == result_correct 498 499 500def test_CiscoRange_03(): 501 """Basic vlan range test""" 502 result_correct = ["1", "2", "3", "4", "5"] 503 assert CiscoRange("1-3,4,5").as_list == result_correct 504 505 506def test_CiscoRange_04(): 507 """Basic vlan range test""" 508 result_correct = ["1", "2", "3", "4", "5"] 509 assert CiscoRange("1,2,3-5").as_list == result_correct 510 511 512def test_CiscoRange_05(): 513 """Basic slot range test""" 514 result_correct = ["1/1", "1/2", "1/3", "1/4", "1/5"] 515 assert CiscoRange("1/1-3,4,5").as_list == result_correct 516 517 518def test_CiscoRange_06(): 519 """Basic slot range test""" 520 result_correct = ["1/1", "1/2", "1/3", "1/4", "1/5"] 521 assert CiscoRange("1/1,2-4,5").as_list == result_correct 522 523 524def test_CiscoRange_07(): 525 """Basic slot range test""" 526 result_correct = ["1/1", "1/2", "1/3", "1/4", "1/5"] 527 assert CiscoRange("1/1,2,3-5").as_list == result_correct 528 529 530def test_CiscoRange_08(): 531 """Basic slot range test""" 532 result_correct = ["2/1/1", "2/1/2", "2/1/3", "2/1/4", "2/1/5"] 533 assert CiscoRange("2/1/1-3,4,5").as_list == result_correct 534 535 536def test_CiscoRange_09(): 537 """Basic slot range test""" 538 result_correct = ["2/1/1", "2/1/2", "2/1/3", "2/1/4", "2/1/5"] 539 assert CiscoRange("2/1/1,2-4,5").as_list == result_correct 540 541 542def test_CiscoRange_10(): 543 """Basic slot range test""" 544 result_correct = ["2/1/1", "2/1/2", "2/1/3", "2/1/4", "2/1/5"] 545 assert CiscoRange("2/1/1,2,3-5").as_list == result_correct 546 547 548def test_CiscoRange_11(): 549 """Basic interface slot range test""" 550 result_correct = [ 551 "interface Eth2/1/1", 552 "interface Eth2/1/2", 553 "interface Eth2/1/3", 554 "interface Eth2/1/4", 555 "interface Eth2/1/5", 556 ] 557 assert CiscoRange("interface Eth2/1/1-3,4,5").as_list == result_correct 558 559 560def test_CiscoRange_12(): 561 """Basic interface slot range test""" 562 result_correct = [ 563 "interface Eth2/1/1", 564 "interface Eth2/1/2", 565 "interface Eth2/1/3", 566 "interface Eth2/1/4", 567 "interface Eth2/1/5", 568 ] 569 assert CiscoRange("interface Eth2/1/1,2-4,5").as_list == result_correct 570 571 572def test_CiscoRange_13(): 573 """Basic interface slot range test""" 574 result_correct = [ 575 "interface Eth2/1/1", 576 "interface Eth2/1/2", 577 "interface Eth2/1/3", 578 "interface Eth2/1/4", 579 "interface Eth2/1/5", 580 ] 581 assert CiscoRange("interface Eth2/1/1,2,3-5").as_list == result_correct 582 583 584def test_CiscoRange_14(): 585 """Basic interface slot range test""" 586 result_correct = [ 587 "interface Eth 2/1/1", 588 "interface Eth 2/1/2", 589 "interface Eth 2/1/3", 590 "interface Eth 2/1/4", 591 "interface Eth 2/1/5", 592 ] 593 assert CiscoRange("interface Eth 2/1/1,2,3-5").as_list == result_correct 594 595 596def test_CiscoRange_15(): 597 """Empty range test""" 598 result_correct = [] 599 assert CiscoRange("").as_list == result_correct 600 601 602def test_CiscoRange_16(): 603 """Append range test""" 604 result_correct = [1, 2, 3] 605 assert CiscoRange("", result_type=int).append("1-3").as_list == result_correct 606 607def test_CiscoRange_17(): 608 """Parse a string with a common prefix on all of the CiscoRange() inputs""" 609 result_correct = [ 610 "Eth1/1", 611 "Eth1/10", 612 "Eth1/12-20", 613 ] 614 CiscoRange("Eth1/1,Eth1/12-20,Eth1/16,Eth1/10").as_list == result_correct 615 616def test_CiscoRange_18(): 617 """Parse a string with a common prefix on all of the CiscoRange() inputs""" 618 result_correct = [ 619 "interface Eth1/1", 620 "interface Eth1/10", 621 "interface Eth1/12-20", 622 ] 623 CiscoRange("interface Eth1/1,interface Eth1/12-20,interface Eth1/16,interface Eth1/10").as_list == result_correct 624 625 626def test_CiscoRange_compressed_str_01(): 627 """compressed_str test""" 628 assert CiscoRange("1,2, 3, 6, 7, 8, 9, 911").compressed_str == "1-3,6-9,911" 629 630 631def test_CiscoRange_contains(): 632 assert "Ethernet1/2" in CiscoRange("Ethernet1/1-20") 633