Skip to content

Commit faaa71c

Browse files
committed
IGNORE THIS COMMIT: merge libyang3 step 2 (PR sonic-net#21719)
1 parent 4de1598 commit faaa71c

29 files changed

+152
-81
lines changed

rules/sonic-yang-models-py3.mk

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
SONIC_YANG_MODELS_PY3 = sonic_yang_models-1.0-py3-none-any.whl
22
$(SONIC_YANG_MODELS_PY3)_SRC_PATH = $(SRC_PATH)/sonic-yang-models
33
$(SONIC_YANG_MODELS_PY3)_PYTHON_VERSION = 3
4-
$(SONIC_YANG_MODELS_PY3)_DEBS_DEPENDS = $(LIBYANG) $(LIBYANG_CPP) \
5-
$(LIBYANG_PY3) \
6-
$(LIBYANG3) \
4+
$(SONIC_YANG_MODELS_PY3)_DEBS_DEPENDS = $(LIBYANG3) \
75
$(LIBYANG3_PY3)
86

97
SONIC_PYTHON_WHEELS += $(SONIC_YANG_MODELS_PY3)

src/sonic-yang-models/tests/yang_model_pytests/conftest.py

+12-11
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,38 @@
11
import os
22
import pytest
3-
import yang as ly
3+
import libyang as ly
44
from json import dumps
55
from glob import glob
66

7-
87
class YangModel:
98

109
def __init__(self) -> None:
1110
cur_dir = os.path.dirname(os.path.abspath(__file__))
1211
project_root = os.path.abspath(os.path.join(cur_dir, '..', '..'))
1312
self.model_dir = os.path.join(project_root, 'yang-models')
14-
1513
self._load_model()
1614

15+
def __del__(self) -> None:
16+
self.ctx.destroy()
17+
self.ctx = None
18+
1719
def _load_model(self) -> None:
1820
self.ctx = ly.Context(self.model_dir)
1921
yang_files = glob(self.model_dir +"/*.yang")
20-
2122
for file in yang_files:
22-
m = self.ctx.parse_module_path(file, ly.LYS_IN_YANG)
23-
if not m:
24-
raise RuntimeError("Failed to parse '{file}' model")
23+
with open(file, 'r') as f:
24+
m = self.ctx.parse_module_file(f, "yang")
25+
if not m:
26+
raise RuntimeError("Failed to parse '{file}' model")
2527

2628
def _load_data(self, data) -> None:
27-
self.ctx.parse_data_mem(dumps(data), ly.LYD_JSON,
28-
ly.LYD_OPT_CONFIG | ly.LYD_OPT_STRICT)
29+
dnode = self.ctx.parse_data_mem(dumps(data), "json", strict=True, no_state=True, json_string_datatypes=True)
30+
dnode.free()
2931

3032
def load_data(self, data, expected_error=None) -> None:
3133
if expected_error:
32-
with pytest.raises(RuntimeError) as exc_info:
34+
with pytest.raises(Exception) as exc_info:
3335
self._load_data(data)
34-
3536
assert expected_error in str(exc_info)
3637
else:
3738
self._load_data(data)

src/sonic-yang-models/tests/yang_model_pytests/test_crm.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ def test_crm_valid_data(self, yang_model, resource, threshold):
6161

6262
@pytest.mark.parametrize(
6363
"high, low, error_message", [
64-
(-1, 70, 'Invalid value "-1"'),
65-
(100, -70, 'Invalid value "-70"'),
64+
(-1, 70, 'Value "-1" is out of type uint16 min/max bounds'),
65+
(100, -70, 'Value "-70" is out of type uint16 min/max bounds'),
6666
(10, 70, 'high_threshold should be more than low_threshold')]
6767
)
6868
def test_crm_thresholds(self, yang_model, resource, threshold, high, low, error_message):
@@ -82,7 +82,7 @@ def test_crm_thresholds(self, yang_model, resource, threshold, high, low, error_
8282

8383
@pytest.mark.parametrize(
8484
"high, low, th_type, error_message", [
85-
(100, 70, 'wrong', 'Value "wrong" does not satisfy the constraint'),
85+
(100, 70, 'wrong', 'Unsatisfied pattern'),
8686
(110, 20, 'percentage', 'Must condition')]
8787
)
8888
def test_crm_threshold_type(self, yang_model, resource, high, low, th_type, error_message):

src/sonic-yang-models/tests/yang_model_pytests/test_dash_crm.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ def test_dash_crm_valid_data(self, yang_model, data, resource, threshold):
6868

6969
@pytest.mark.parametrize(
7070
"high, low, error_message", [
71-
(-1, 70, 'Invalid value "-1"'),
72-
(100, -70, 'Invalid value "-70"'),
71+
(-1, 70, 'Value "-1" is out of type uint16 min/max bounds'),
72+
(100, -70, 'Value "-70" is out of type uint16 min/max bounds'),
7373
(10, 70, 'high_threshold should be more than low_threshold')]
7474
)
7575
def test_dash_crm_thresholds(self, yang_model, data, resource, threshold, high, low, error_message):
@@ -87,7 +87,7 @@ def test_dash_crm_thresholds(self, yang_model, data, resource, threshold, high,
8787

8888
@pytest.mark.parametrize(
8989
"high, low, th_type, error_message", [
90-
(100, 70, 'wrong', 'Value "wrong" does not satisfy the constraint'),
90+
(100, 70, 'wrong', 'Unsatisfied pattern'),
9191
(110, 20, 'percentage', 'Must condition')]
9292
)
9393
def test_dash_crm_threshold_type(self, yang_model, data, resource, high, low, th_type, error_message):

src/sonic-yang-models/tests/yang_model_pytests/test_smart_switch.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def test_valid_data(self, yang_model):
3232
@pytest.mark.parametrize(
3333
"bridge_name, error_message", [
3434
("bridge-midplane", None),
35-
("wrong_name", 'Value "wrong_name" does not satisfy the constraint "bridge-midplane"')]
35+
("wrong_name", 'Unsatisfied pattern')]
3636
)
3737
def test_bridge_name(self, yang_model, bridge_name, error_message):
3838
data = {
@@ -51,7 +51,7 @@ def test_bridge_name(self, yang_model, bridge_name, error_message):
5151
@pytest.mark.parametrize(
5252
"ip_prefix, error_message", [
5353
("169.254.200.254/24", None),
54-
("169.254.xyz.254/24", 'Value "169.254.xyz.254/24" does not satisfy the constraint')]
54+
("169.254.xyz.254/24", 'Unsatisfied pattern')]
5555
)
5656
def test_bridge_ip_prefix(self, yang_model, ip_prefix, error_message):
5757
data = {
@@ -70,7 +70,7 @@ def test_bridge_ip_prefix(self, yang_model, ip_prefix, error_message):
7070
@pytest.mark.parametrize(
7171
"dpu_name, error_message", [
7272
("dpu0", None),
73-
("xyz", 'Value "xyz" does not satisfy the constraint "dpu[0-9]+')]
73+
("xyz", 'Unsatisfied pattern')]
7474
)
7575
def test_dpu_name(self, yang_model, dpu_name, error_message):
7676
data = {
@@ -91,7 +91,7 @@ def test_dpu_name(self, yang_model, dpu_name, error_message):
9191
@pytest.mark.parametrize(
9292
"midplane_interface, error_message", [
9393
("dpu0", None),
94-
("xyz", 'Value "xyz" does not satisfy the constraint "dpu[0-9]+')]
94+
("xyz", 'Unsatisfied pattern')]
9595
)
9696
def test_dpu_midplane_interface(self, yang_model, midplane_interface, error_message):
9797
data = {
@@ -112,7 +112,7 @@ def test_dpu_midplane_interface(self, yang_model, midplane_interface, error_mess
112112
@pytest.mark.parametrize(
113113
"port_name, error_message", [
114114
("dpu0", None),
115-
("dp0rt0", 'Value "dp0rt0" does not satisfy the constraint "[a-zA-Z]+[0-9]+"')]
115+
("dp0rt0", 'Unsatisfied pattern')]
116116
)
117117
def test_dpu_port_name(self, yang_model, port_name, error_message):
118118
data = {
@@ -139,7 +139,7 @@ def test_dpu_port_name(self, yang_model, port_name, error_message):
139139
@pytest.mark.parametrize(
140140
"vip_ipv4, error_message", [
141141
("192.168.1.1", None),
142-
("192.168.1.xyz", 'Value "192.168.1.xyz" does not satisfy the constraint')]
142+
("192.168.1.xyz", 'Unsatisfied pattern')]
143143
)
144144
def test_dpu_port_vip_ipv4(self, yang_model, vip_ipv4, error_message):
145145
data = {
@@ -166,7 +166,7 @@ def test_dpu_port_vip_ipv4(self, yang_model, vip_ipv4, error_message):
166166
@pytest.mark.parametrize(
167167
"vip_ipv6, error_message", [
168168
("2001:db8::1", None),
169-
("2001:db8::xyz", 'Value "2001:db8::xyz" does not satisfy the constraint')]
169+
("2001:db8::xyz", 'Unsatisfied pattern')]
170170
)
171171
def test_dpu_port_vip_ipv6(self, yang_model, vip_ipv6, error_message):
172172
data = {
@@ -193,7 +193,7 @@ def test_dpu_port_vip_ipv6(self, yang_model, vip_ipv6, error_message):
193193
@pytest.mark.parametrize(
194194
"pa_ipv4, error_message", [
195195
("192.168.1.2", None),
196-
("192.168.1.xyz", 'Value "192.168.1.xyz" does not satisfy the constraint')]
196+
("192.168.1.xyz", 'Unsatisfied pattern')]
197197
)
198198
def test_dpu_port_pa_ipv4(self, yang_model, pa_ipv4, error_message):
199199
data = {
@@ -220,7 +220,7 @@ def test_dpu_port_pa_ipv4(self, yang_model, pa_ipv4, error_message):
220220
@pytest.mark.parametrize(
221221
"pa_ipv6, error_message", [
222222
("2001:db8::2", None),
223-
("2001:db8::xyz", 'Value "2001:db8::xyz" does not satisfy the constraint')]
223+
("2001:db8::xyz", 'Unsatisfied pattern')]
224224
)
225225
def test_dpu_port_pa_ipv6(self, yang_model, pa_ipv6, error_message):
226226
data = {
@@ -247,7 +247,7 @@ def test_dpu_port_pa_ipv6(self, yang_model, pa_ipv6, error_message):
247247
@pytest.mark.parametrize(
248248
"gnmi_port, error_message", [
249249
(8080, None),
250-
(99999, 'Invalid value "99999" in "gnmi_port" element.')]
250+
(99999, 'Value "99999" is out of type uint16 min/max bounds')]
251251
)
252252
def test_dpu_port_gnmi(self, yang_model, gnmi_port, error_message):
253253
data = {

src/sonic-yang-models/tests/yang_model_tests/test_yang_model.py

+57-33
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# This script is used to
22

3-
import yang as ly
3+
import libyang as ly
44
import logging
55
import argparse
66
import sys
77
import ijson
88
import json
9+
import pytest
910
#import sonic_yang as sy
1011
from glob import glob
1112
from os import listdir
@@ -39,30 +40,27 @@ class Test_yang_models:
3940
def initTest(self):
4041
self.defaultYANGFailure = {
4142
'Must': ['Must condition', 'not satisfied'],
42-
'InvalidValue': ['Invalid value'], # libyang3: ['Invalid', 'value', 'Data path']
43-
'LeafRef': ['Leafref', 'non-existing'], #libyang3: ['Invalid leafref', 'no target instance']
43+
'InvalidValue': ['Invalid', 'value', 'Data path'],
44+
'LeafRef': ['Invalid leafref', 'no target instance'],
4445
'When': ['When condition', 'not satisfied'],
45-
'Pattern': ['pattern', 'does not satisfy'], #libyang3: ['pattern', 'Unsatisfied pattern']
46-
'Mandatory': ['required element', 'Missing'], #libyang3: ['Mandatory node', 'does not exist']
46+
'Pattern': ['pattern', 'Unsatisfied pattern'],
47+
'Mandatory': ['Mandatory node', 'does not exist'],
4748
'Verify': ['verified'],
48-
'Range': ['does not satisfy', 'range'], #libyang3: ['Unsatisfied range']
49+
'Range': ['Unsatisfied range'],
4950
'MinElements': ['Too few'],
5051
'MaxElements': ['Too many'],
51-
'UnknownElement': ['Unknown element'],
52+
'UnknownElement': ['not found as a child'],
5253
'None': [],
53-
# New keys are needed for libyang3's messages which are different. Go
54-
# ahead and add them now with the libyang1 values (which are duplicates
55-
# of the above). This will make migrating to libyang3 easier with less
56-
# code review.
57-
'Length': ['does not satisfy', 'range'], # libyang3: ['Unsatisfied length']
58-
'DecimalFractionExceed': ['Invalid value'], # libyang3: ['Value', 'exceeds defined number', 'fraction digits']
59-
'Bounds': ['Invalid value'], #libyang3 ['Value', 'out of type', 'min/max bounds'],
60-
'ListKey': ['Missing required element'], #libyang3 ['List instance is missing its key']
61-
'DateTime': ['pattern', 'does not satisfy'], #libyang3: ['Invalid date-and-time']
62-
'IPv4': ['pattern', 'does not satisfy'], #libyang3 ['Failed to convert IPv4 address'],
54+
'Length': ['Unsatisfied length'],
55+
'DecimalFractionExceed': ['Value', 'exceeds defined number', 'fraction digits'],
56+
'Bounds': ['Value', 'out of type', 'min/max bounds'],
57+
'ListKey': ['List instance is missing its key'],
58+
'DateTime': ['Invalid date-and-time'],
59+
'IPv4': ['Failed to convert IPv4 address'],
6360
}
6461

6562
self.ExceptionTests = { }
63+
self.FailedTests = []
6664

6765
for test_file in glob("./tests/yang_model_tests/tests/*.json"):
6866
try:
@@ -122,11 +120,12 @@ def loadYangModel(self, yangDir):
122120
# load yang modules
123121
for file in yangFiles:
124122
log.debug(file)
125-
m = self.ctx.parse_module_path(file, ly.LYS_IN_YANG)
126-
if m is not None:
127-
log.info("module: {} is loaded successfully".format(m.name()))
128-
else:
129-
log.info("Could not load module: {}".format(file))
123+
with open(file, 'r') as f:
124+
m = self.ctx.parse_module_file(f, "yang")
125+
if m is not None:
126+
log.info("module: {} is loaded successfully".format(m.name()))
127+
else:
128+
log.info("Could not load module: {}".format(file))
130129

131130
except Exception as e:
132131
printExceptionDetails()
@@ -178,26 +177,35 @@ def logStartTest(self, desc):
178177
"""
179178
def loadConfigData(self, jInput, verify=None):
180179
s = ""
180+
node = None
181+
try:
182+
node = self.ctx.parse_data_mem(jInput, "json", strict=True, no_state=True, json_string_datatypes=True)
183+
except Exception as e:
184+
printExceptionDetails()
185+
s = str(e)
186+
log.info(s)
187+
return s
188+
181189
try:
182-
node = self.ctx.parse_data_mem(jInput, ly.LYD_JSON, \
183-
ly.LYD_OPT_CONFIG | ly.LYD_OPT_STRICT)
184190
# verify the data tree if asked
185191
if verify is not None:
186192
xpath = verify['xpath']
187193
log.info("Verify xpath: {}".format(xpath))
188-
set = node.find_path(xpath)
189-
for dnode in set.data():
194+
nodes = node.find_all(xpath)
195+
for dnode in nodes:
190196
if (xpath == dnode.path()):
191197
log.info("Verify dnode: {}".format(dnode.path()))
192-
data = dnode.print_mem(ly.LYD_JSON, ly.LYP_WITHSIBLINGS \
193-
| ly.LYP_FORMAT | ly.LYP_WD_ALL)
198+
data = dnode.print_mem("json", with_siblings=True, pretty=True, include_implicit_defaults=True)
194199
data = json.loads(data)
195-
log.info("Verify data: {}".format(data))
200+
log.info("Verify path value {} is {} in {}".format(verify['key'], verify['value'], data))
201+
assert (verify['key'] in data)
196202
assert (data[verify['key']] == verify['value'])
197203
s = 'verified'
198204
except Exception as e:
199-
s = str(e)
200-
log.info(s)
205+
printExceptionDetails()
206+
207+
node.free()
208+
201209
return s
202210

203211
"""
@@ -221,9 +229,12 @@ def runExceptionTest(self, test):
221229
log.info(desc + " Passed\n")
222230
return PASS
223231
else:
224-
raise Exception("Mismatch {} and {}".format(eStr, s))
232+
errstr = "{}: Mismatch {} and {}".format(test, eStr, s)
233+
self.FailedTests.append(errstr)
234+
raise Exception(errstr)
225235
except Exception as e:
226236
printExceptionDetails()
237+
227238
log.info(desc + " Failed\n")
228239
return FAIL
229240

@@ -253,7 +264,9 @@ def runVlanSpecialTest(self, test):
253264
log.debug(jInput)
254265
s = self.loadConfigData(json.dumps(jInput))
255266
if s!="":
256-
raise Exception("{} in not empty".format(s))
267+
errstr="{}[{}]: {} in not empty".format(test,i,s)
268+
self.FailedTests.append(errstr)
269+
raise Exception(errstr)
257270
return PASS
258271
except Exception as e:
259272
printExceptionDetails()
@@ -282,6 +295,17 @@ def test_run_tests(self):
282295
ret = FAIL * len(self.tests)
283296
printExceptionDetails()
284297

298+
if len(self.FailedTests):
299+
print("{} Failures:".format(len(self.FailedTests)))
300+
log.error("{} Failures:".format(len(self.FailedTests)))
301+
for x in self.FailedTests:
302+
print(x)
303+
log.error(x)
304+
305+
if self.ctx:
306+
self.ctx.destroy()
307+
self.ctx = None
308+
285309
assert ret == 0
286310
return
287311
# End of Class

0 commit comments

Comments
 (0)