mirror of
				https://github.com/Telecominfraproject/oopt-gnpy-libyang.git
				synced 2025-11-04 03:57:55 +00:00 
			
		
		
		
	...along with a bunch of incompatible API/ABI changes. I had to make a few tweaks in the build process as well, and these couldn't be made (and tested!) with separate commits. The test suite was failing on OS X 13, so I decided to upgrade, because "why not".
		
			
				
	
	
		
			245 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			245 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import json
 | 
						|
import pytest
 | 
						|
import oopt_gnpy_libyang as ly
 | 
						|
 | 
						|
def test_ietf_interfaces(context_with_modules):
 | 
						|
    blob = '''{
 | 
						|
  "ietf-interfaces:interfaces": {
 | 
						|
    "interface": [
 | 
						|
      {
 | 
						|
        "name": "lo",
 | 
						|
        "type": "iana-if-type:softwareLoopback",
 | 
						|
        "ietf-ip:ipv4": {
 | 
						|
          "address": [
 | 
						|
            {
 | 
						|
              "ip": "127.0.0.1",
 | 
						|
              "prefix-length": 8
 | 
						|
            }
 | 
						|
          ]
 | 
						|
        },
 | 
						|
        "ietf-ip:ipv6": {
 | 
						|
          "address": [
 | 
						|
            {
 | 
						|
              "ip": "::1",
 | 
						|
              "prefix-length": 128
 | 
						|
            }
 | 
						|
          ]
 | 
						|
        }
 | 
						|
      },
 | 
						|
      {
 | 
						|
        "name": "eth0",
 | 
						|
        "type": "iana-if-type:ethernetCsmacd"
 | 
						|
      },
 | 
						|
      {
 | 
						|
        "name": "eth1",
 | 
						|
        "type": "iana-if-type:ethernetCsmacd"
 | 
						|
      },
 | 
						|
      {
 | 
						|
        "name": "br0",
 | 
						|
        "type": "iana-if-type:bridge",
 | 
						|
        "ietf-ip:ipv4": {
 | 
						|
          "address": [
 | 
						|
            {
 | 
						|
              "ip": "10.0.0.2",
 | 
						|
              "prefix-length": 24
 | 
						|
            }
 | 
						|
          ],
 | 
						|
          "neighbor": [
 | 
						|
            {
 | 
						|
              "ip": "10.0.0.1",
 | 
						|
              "link-layer-address": "cc:2d:e0:52:30:9c"
 | 
						|
            },
 | 
						|
            {
 | 
						|
              "ip": "10.0.1.1",
 | 
						|
              "link-layer-address": "cc:2d:e0:52:30:9c"
 | 
						|
            }
 | 
						|
          ]
 | 
						|
        },
 | 
						|
        "ietf-ip:ipv6": {
 | 
						|
          "address": [
 | 
						|
            {
 | 
						|
              "ip": "fe80::211:17ff:fe01:61",
 | 
						|
              "prefix-length": 64
 | 
						|
            }
 | 
						|
          ]
 | 
						|
        }
 | 
						|
      }
 | 
						|
    ]
 | 
						|
  },
 | 
						|
  "ietf-hardware:hardware": {
 | 
						|
  }
 | 
						|
}
 | 
						|
    '''
 | 
						|
    data = context_with_modules.parse_data(blob, ly.DataFormat.JSON, ly.ParseOptions.Strict | ly.ParseOptions.Ordered, ly.ValidationOptions.Present | ly.ValidationOptions.NoState)
 | 
						|
    assert data.path == '/ietf-interfaces:interfaces'
 | 
						|
 | 
						|
    assert [x.path for x in data.siblings()] == ['/ietf-interfaces:interfaces', '/ietf-hardware:hardware']
 | 
						|
    assert [inner.path for top in data.siblings() for inner in top.children_dfs()] == [
 | 
						|
        '/ietf-interfaces:interfaces',
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/name",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/type",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/enabled",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv4",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv4/enabled",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv4/forwarding",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv4/address[ip='127.0.0.1']",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv4/address[ip='127.0.0.1']/ip",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv4/address[ip='127.0.0.1']/prefix-length",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv6",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv6/enabled",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv6/forwarding",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv6/address[ip='::1']",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv6/address[ip='::1']/ip",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv6/address[ip='::1']/prefix-length",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv6/dup-addr-detect-transmits",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv6/autoconf",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv6/autoconf/create-global-addresses",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='eth0']",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='eth0']/name",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='eth0']/type",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='eth0']/enabled",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='eth1']",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='eth1']/name",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='eth1']/type",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='eth1']/enabled",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/name",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/type",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/enabled",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/enabled",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/forwarding",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/address[ip='10.0.0.2']",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/address[ip='10.0.0.2']/ip",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/address[ip='10.0.0.2']/prefix-length",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/neighbor[ip='10.0.0.1']",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/neighbor[ip='10.0.0.1']/ip",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/neighbor[ip='10.0.0.1']/link-layer-address",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/neighbor[ip='10.0.1.1']",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/neighbor[ip='10.0.1.1']/ip",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/neighbor[ip='10.0.1.1']/link-layer-address",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv6",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv6/enabled",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv6/forwarding",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv6/address[ip='fe80::211:17ff:fe01:61']",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv6/address[ip='fe80::211:17ff:fe01:61']/ip",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv6/address[ip='fe80::211:17ff:fe01:61']/prefix-length",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv6/dup-addr-detect-transmits",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv6/autoconf",
 | 
						|
        "/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv6/autoconf/create-global-addresses",
 | 
						|
        '/ietf-hardware:hardware',
 | 
						|
    ]
 | 
						|
    assert [inner.path for top in data.siblings() for inner in top.immediate_children()] == [
 | 
						|
        f"/ietf-interfaces:interfaces/interface[name='{if_}']" for if_ in ('lo', 'eth0', 'eth1', 'br0')
 | 
						|
    ]
 | 
						|
 | 
						|
    # quoting doesn't matter
 | 
						|
    assert data["interface[name='lo']"].path == "/ietf-interfaces:interfaces/interface[name='lo']"
 | 
						|
    assert data['interface[name="lo"]'].path == "/ietf-interfaces:interfaces/interface[name='lo']"
 | 
						|
 | 
						|
    with pytest.raises(KeyError, match='No such data node below.*'):
 | 
						|
        data['interface[name="ZZZ"]']
 | 
						|
 | 
						|
    with pytest.raises(KeyError, match="Too many nodes below '/ietf-interfaces:interfaces' for 'interface/enabled'"):
 | 
						|
        data["interface/enabled"]
 | 
						|
 | 
						|
    # one multi-level XPath with all keys
 | 
						|
    assert data["interface[name='lo']/ietf-ip:ipv6/address[ip='::1']/prefix-length"].as_term().value == 128
 | 
						|
 | 
						|
    # one item per index, all key values specified
 | 
						|
    assert data["interface[name='lo']"]["ietf-ip:ipv6"]["address[ip='::1']"]["prefix-length"].as_term().value == 128
 | 
						|
 | 
						|
    # one item per index. single-item list keys without their values
 | 
						|
    assert data["interface[name='lo']"]["ietf-ip:ipv6"]["address"]["prefix-length"].as_term().value == 128
 | 
						|
 | 
						|
    assert isinstance(data["interface[name='lo']"]["ietf-ip:ipv6"]["address"]["prefix-length"].as_term().value, int)
 | 
						|
 | 
						|
    assert len(data.find("/ietf-interfaces:interfaces/interface")) == 4
 | 
						|
    assert [x["name"].as_term().value for x in data.find("/ietf-interfaces:interfaces/interface")] == ["lo", "eth0", "eth1", "br0"]
 | 
						|
 | 
						|
    # paths relative to "the root", which on itself is not a node at the libyang level
 | 
						|
    assert len(ly.search_at_root(data)("ietf-interfaces:interfaces/interface")) == 4
 | 
						|
    assert [x["name"].as_term().value for x in ly.search_at_root(data)("ietf-interfaces:interfaces/interface")] == ["lo", "eth0", "eth1", "br0"]
 | 
						|
    assert len(ly.search_at_root(data)("ietf-hardware:hardware")) == 1
 | 
						|
    assert len(data.find("ietf-hardware:hardware")) == 0
 | 
						|
 | 
						|
    # in/__contains__
 | 
						|
    assert "name" in data["interface[name='lo']"]
 | 
						|
    assert "pwnaaaageeee" not in data["interface[name='lo']"]
 | 
						|
 | 
						|
    # schema paths
 | 
						|
    assert data['interface[name="lo"]'].schema.module.name == "ietf-interfaces"
 | 
						|
    assert data['interface[name="lo"]'].schema.module.revision == "2018-02-20"
 | 
						|
    assert data['interface[name="lo"]'].schema.module.implemented
 | 
						|
    assert data['interface[name="lo"]'].schema.name == "interface"
 | 
						|
    assert data['interface[name="lo"]'].schema.path == "/ietf-interfaces:interfaces/interface"
 | 
						|
 | 
						|
    # first assignmnet overwrites the value
 | 
						|
    assert data["interface[name='lo']"]["ietf-ip:ipv6"]["address"]["prefix-length"].as_term().value != 64
 | 
						|
    data["interface[name='lo']"]["ietf-ip:ipv6"]["address"]["prefix-length"] = "64"
 | 
						|
    assert data["interface[name='lo']"]["ietf-ip:ipv6"]["address"]["prefix-length"].as_term().value == 64
 | 
						|
    # doing it once again is a no-op
 | 
						|
    data["interface[name='lo']"]["ietf-ip:ipv6"]["address"]["prefix-length"] = "64"
 | 
						|
    assert data["interface[name='lo']"]["ietf-ip:ipv6"]["address"]["prefix-length"].as_term().value == 64
 | 
						|
 | 
						|
    separate_node = context_with_modules.create("/ietf-interfaces:interfaces/interface[name='666']/ietf-ip:ipv6/enabled", "true")
 | 
						|
    assert separate_node.path == "/ietf-interfaces:interfaces/interface[name='666']/ietf-ip:ipv6/enabled"
 | 
						|
    with pytest.raises(KeyError, match="No such data node below"):
 | 
						|
        data[separate_node.path]
 | 
						|
    # non-leafs can be created as well
 | 
						|
    assert "/ietf-interfaces:interfaces/interface[name='42']" not in separate_node
 | 
						|
    separate_node["/ietf-interfaces:interfaces/interface[name='42']"] = None
 | 
						|
    assert "/ietf-interfaces:interfaces/interface[name='42']" in separate_node
 | 
						|
    context_with_modules.create("/ietf-interfaces:interfaces/interface[name='666']")
 | 
						|
 | 
						|
    wrong = json.loads(blob)
 | 
						|
    wrong["ietf-interfaces:interfaces"]["interface"][0]["ietf-ip:ipv6"]["address"][0]["prefix-length"] = 666
 | 
						|
    try:
 | 
						|
        data = context_with_modules.parse_data(json.dumps(wrong, indent=2),
 | 
						|
            ly.DataFormat.JSON, ly.ParseOptions.Strict | ly.ParseOptions.Ordered,
 | 
						|
            ly.ValidationOptions.Present | ly.ValidationOptions.NoState)
 | 
						|
        assert False
 | 
						|
    except ly.Error:
 | 
						|
        for error in context_with_modules.errors():
 | 
						|
            assert error.schema_path is None
 | 
						|
            assert error.data_path == "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv6/address[ip='::1']/prefix-length"
 | 
						|
            assert error.line == 20
 | 
						|
            assert error.message == 'Value "666" is out of type uint8 min/max bounds.'
 | 
						|
 | 
						|
def test_types(context_no_libyang):
 | 
						|
    context_no_libyang.parse_module('''
 | 
						|
module dummy {
 | 
						|
    yang-version 1.1;
 | 
						|
    namespace "dd";
 | 
						|
    prefix dd;
 | 
						|
    revision 2023-09-01;
 | 
						|
 | 
						|
    leaf dec {
 | 
						|
        type decimal64 {
 | 
						|
            fraction-digits 6;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    leaf emp {
 | 
						|
        type empty;
 | 
						|
    }
 | 
						|
 | 
						|
    leaf enu {
 | 
						|
        type enumeration {
 | 
						|
            enum A;
 | 
						|
            enum B;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
''', ly.SchemaFormat.YANG)
 | 
						|
    data = context_no_libyang.parse_data('{"dummy:dec": "333.666", "dummy:emp": [null], "dummy:enu": "B"}', ly.DataFormat.JSON)
 | 
						|
    assert float(data["/dummy:dec"].as_term().value) == 333.666
 | 
						|
    assert str(data["/dummy:dec"].as_term().value) == "333.666000"
 | 
						|
    assert str(data["/dummy:dec"].as_term()) == "333.666"
 | 
						|
    assert isinstance(data["/dummy:emp"].as_term().value, ly.Empty)
 | 
						|
    assert data["/dummy:emp"].as_term().value is not None
 | 
						|
    assert isinstance(data["/dummy:enu"].as_term().value, ly.Enum)
 | 
						|
    assert data["/dummy:enu"].as_term().value.name == "B"
 | 
						|
    assert data["/dummy:enu"].as_term().value.value == 1
 |