Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[YANG] Add an infrastructure to write pytest tests for the YANG model. #16

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/sonic-yang-models/tests/yang_model_pytests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import pytest
import yang as ly
from json import dumps
from glob import glob


class YangModel:

model_dir = './yang-models'

def __init__(self) -> None:
self.ctx = None
self._load_model()

def _load_model(self) -> None:
self.ctx = ly.Context(self.model_dir)
yang_files = glob(self.model_dir +"/*.yang")

for file in yang_files:
m = self.ctx.parse_module_path(file, ly.LYS_IN_YANG)
if not m:
raise RuntimeError("Failed to parse '{file}' model")

def _load_data(self, data) -> None:
self.ctx.parse_data_mem(dumps(data), ly.LYD_JSON,
ly.LYD_OPT_CONFIG | ly.LYD_OPT_STRICT)

def load_data(self, data, expected_error=None) -> None:
if expected_error:
with pytest.raises(RuntimeError) as exc_info:
self._load_data(data)

assert expected_error in str(exc_info)
else:
self._load_data(data)


@pytest.fixture
def yang_model():
yang_model = YangModel()
return yang_model
92 changes: 92 additions & 0 deletions src/sonic-yang-models/tests/yang_model_pytests/test_crm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import pytest


thresholds = [
'free', 'FREE',
'used', 'USED',
'percentage', 'PERCENTAGE'
]


@pytest.fixture(params=thresholds)
def threshold(request):
return request.param


resources = [
'ipv4_route',
'ipv6_route',
'ipv4_nexthop',
'ipv6_nexthop',
'ipv4_neighbor',
'ipv6_neighbor',
'nexthop_group_member',
'nexthop_group',
'fdb_entry',
'mpls_nexthop',
'srv6_nexthop'
]


@pytest.fixture(params=resources)
def resource(request):
return request.param


class TestCrm:

def test_crm_valid_data(self, yang_model, resource, threshold):
data = {
"sonic-crm:sonic-crm": {
"sonic-crm:CRM": {
"Config": {
f"{resource}_high_threshold": 95,
f"{resource}_low_threshold": 70,
f"{resource}_threshold_type": threshold
}
}
}
}

yang_model.load_data(data)

@pytest.mark.parametrize(
"high, low, error_message", [
(-1, 70, 'Invalid value "-1"'),
(100, -70, 'Invalid value "-70"'),
(10, 70, 'high_threshold should be more than low_threshold')]
)
def test_crm_thresholds(self, yang_model, resource, threshold, high, low, error_message):
data = {
"sonic-crm:sonic-crm": {
"sonic-crm:CRM": {
"Config": {
f"{resource}_high_threshold": high,
f"{resource}_low_threshold": low,
f"{resource}_threshold_type": threshold
}
}
}
}

yang_model.load_data(data, error_message)

@pytest.mark.parametrize(
"high, low, th_type, error_message", [
(100, 70, 'wrong', 'Value "wrong" does not satisfy the constraint'),
(110, 20, 'percentage', 'Must condition')]
)
def test_crm_threshold_type(self, yang_model, resource, high, low, th_type, error_message):
data = {
"sonic-crm:sonic-crm": {
"sonic-crm:CRM": {
"Config": {
f"{resource}_high_threshold": high,
f"{resource}_low_threshold": low,
f"{resource}_threshold_type": th_type
}
}
}
}

yang_model.load_data(data, error_message)
105 changes: 105 additions & 0 deletions src/sonic-yang-models/tests/yang_model_pytests/test_dash_crm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import pytest
from copy import deepcopy


base_data = {
"sonic-device_metadata:sonic-device_metadata": {
"sonic-device_metadata:DEVICE_METADATA": {
"localhost": {
"switch_type": "dpu"
}
}
}
}


@pytest.fixture
def data(request):
return deepcopy(base_data)


dash_thresholds = [
'free', 'FREE',
'used', 'USED',
'percentage', 'PERCENTAGE'
]


@pytest.fixture(params=dash_thresholds)
def threshold(request):
return request.param


dash_resources = [
'vnet',
'eni',
'eni_ether_address_map',
'ipv4_inbound_routing',
'ipv6_inbound_routing',
'ipv4_outbound_routing',
'ipv6_outbound_routing',
'ipv4_pa_validation',
'ipv6_pa_validation',
'ipv4_outbound_ca_to_pa',
'ipv6_outbound_ca_to_pa',
'ipv4_acl_group',
'ipv6_acl_group'
]


@pytest.fixture(params=dash_resources)
def resource(request):
return request.param


class TestDashCrm:

def test_dash_crm_valid_data(self, yang_model, data, resource, threshold):
data["sonic-crm:sonic-crm"] = {
"sonic-crm:CRM": {
"Config": {
f"dash_{resource}_high_threshold": 95,
f"dash_{resource}_low_threshold": 70,
f"dash_{resource}_threshold_type": threshold
}
}
}

yang_model.load_data(data)

@pytest.mark.parametrize(
"high, low, error_message", [
(-1, 70, 'Invalid value "-1"'),
(100, -70, 'Invalid value "-70"'),
(10, 70, 'high_threshold should be more than low_threshold')]
)
def test_dash_crm_thresholds(self, yang_model, data, resource, threshold, high, low, error_message):
data["sonic-crm:sonic-crm"] = {
"sonic-crm:CRM": {
"Config": {
f"dash_{resource}_high_threshold": high,
f"dash_{resource}_low_threshold": low,
f"dash_{resource}_threshold_type": threshold
}
}
}

yang_model.load_data(data, error_message)

@pytest.mark.parametrize(
"high, low, th_type, error_message", [
(100, 70, 'wrong', 'Value "wrong" does not satisfy the constraint'),
(110, 20, 'percentage', 'Must condition')]
)
def test_dash_crm_threshold_type(self, yang_model, data, resource, high, low, th_type, error_message):
data["sonic-crm:sonic-crm"] = {
"sonic-crm:CRM": {
"Config": {
f"dash_{resource}_high_threshold": high,
f"dash_{resource}_low_threshold": low,
f"dash_{resource}_threshold_type": th_type
}
}
}

yang_model.load_data(data, error_message)
110 changes: 110 additions & 0 deletions src/sonic-yang-models/tests/yang_model_pytests/test_smart_switch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import pytest


class TestSmartSwitch:

def test_valid_data(self, yang_model):
data = {
"sonic-smart-switch:sonic-smart-switch": {
"sonic-smart-switch:MID_PLANE_BRIDGE": {
"GLOBAL": {
"bridge": "bridge_midplane",
"ip_prefix": "169.254.200.254/24"
}
},
"sonic-smart-switch:DPUS": {
"DPUS_LIST": [
{
"dpu_name": "dpu0",
"midplane_interface": "dpu0"
},
{
"dpu_name": "dpu1",
"midplane_interface": "dpu1"
}
]
}
}
}

yang_model.load_data(data)

@pytest.mark.parametrize(
"bridge_name, error_message", [
("bridge_midplane", None),
("wrong_name", 'Value "wrong_name" does not satisfy the constraint "bridge_midplane"')]
)
def test_bridge_name(self, yang_model, bridge_name, error_message):
data = {
"sonic-smart-switch:sonic-smart-switch": {
"sonic-smart-switch:MID_PLANE_BRIDGE": {
"GLOBAL": {
"bridge": bridge_name,
"ip_prefix": "169.254.200.254/24"
}
}
}
}

yang_model.load_data(data, error_message)

@pytest.mark.parametrize(
"ip_prefix, error_message", [
("169.254.200.254/24", None),
("169.254.xyz.254/24", 'Value "169.254.xyz.254/24" does not satisfy the constraint')]
)
def test_bridge_ip_prefix(self, yang_model, ip_prefix, error_message):
data = {
"sonic-smart-switch:sonic-smart-switch": {
"sonic-smart-switch:MID_PLANE_BRIDGE": {
"GLOBAL": {
"bridge": "bridge_midplane",
"ip_prefix": ip_prefix
}
}
}
}

yang_model.load_data(data, error_message)

@pytest.mark.parametrize(
"dpu_name, error_message", [
("dpu0", None),
("xyz", 'Value "xyz" does not satisfy the constraint "dpu[0-9]+')]
)
def test_dpu_name(self, yang_model, dpu_name, error_message):
data = {
"sonic-smart-switch:sonic-smart-switch": {
"sonic-smart-switch:DPUS": {
"DPUS_LIST": [
{
"dpu_name": dpu_name,
"midplane_interface": "dpu0"
}
]
}
}
}

yang_model.load_data(data, error_message)

@pytest.mark.parametrize(
"midplane_interface, error_message", [
("dpu0", None),
("xyz", 'Value "xyz" does not satisfy the constraint "dpu[0-9]+')]
)
def test_dpu_midplane_interface(self, yang_model, midplane_interface, error_message):
data = {
"sonic-smart-switch:sonic-smart-switch": {
"sonic-smart-switch:DPUS": {
"DPUS_LIST": [
{
"dpu_name": "dpu0",
"midplane_interface": midplane_interface
}
]
}
}
}

yang_model.load_data(data, error_message)
Loading