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

TC-CNET-4.3: Automate #37387

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
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
131 changes: 131 additions & 0 deletions src/python_testing/TC_CNET_4_3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#
# Copyright (c) 2025 Project CHIP Authors
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# See https://github.com/project-chip/connectedhomeip/blob/master/docs/testing/python.md#defining-the-ci-test-arguments
# for details about the block below.
#
# === BEGIN CI TEST ARGUMENTS ===
# test-runner-runs:
# run1:
# app: ${ALL_CLUSTERS_APP}
# app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json
# script-args: >
# --endpoint 0
# --storage-path admin_storage.json
# --commissioning-method on-network
# --discriminator 1234
# --passcode 20202021
# --PICS src/app/tests/suites/certification/ci-pics-values
# --trace-to json:${TRACE_TEST_JSON}.json
# --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto
# factory-reset: true
# quiet: true
# === END CI TEST ARGUMENTS ===

import chip.clusters as Clusters
import test_plan_support
from chip.clusters.Types import NullValue
from chip.testing import matter_asserts
from chip.testing.matter_testing import MatterBaseTest, TestStep, default_matter_test_main, has_feature, run_if_endpoint_matches
from mobly import asserts


class TC_CNET_4_3(MatterBaseTest):

def desc_TC_CNET_4_3(self) -> str:
return "[TC-CNET-4.3] [Ethernet] Verification for attributes check [DUT-Server]"

def pics_TC_CNET_4_3(self) -> list[str]:
"""Return the PICS definitions associated with this test."""
pics = [
"CNET.S.F02"
]
return pics

def steps_TC_CNET_4_3(self) -> list[TestStep]:
steps = [
TestStep(1, test_plan_support.commission_if_required(), "", is_commissioning=True),
TestStep(2, "TH reads Descriptor Cluster from the DUT with EP0 TH reads ServerList from the DUT"),
TestStep(3, "TH reads the MaxNetworks attribute from the DUT"),
TestStep(4, "TH reads the Networks attribute list from the DUT"),
TestStep(5, "TH reads InterfaceEnabled attribute from the DUT"),
TestStep(6, "TH reads LastNetworkingStatus attribute from the DUT"),
TestStep(7, "TH reads the LastNetworkID attribute from the DUT"),
TestStep(8, "TH reads the LastConnectErrorValue attribute from the DUT")
]
return steps

@run_if_endpoint_matches(has_feature(Clusters.NetworkCommissioning,
Clusters.NetworkCommissioning.Bitmaps.Feature.kEthernetNetworkInterface))
async def test_TC_CNET_4_3(self):
# Commissioning already done
self.step(1)

self.step(2)
server_list = await self.read_single_attribute_check_success(
cluster=Clusters.Descriptor,
attribute=Clusters.Descriptor.Attributes.ServerList)
asserts.assert_true(49 in server_list,
msg="Verify for the presence of an element with value 49 (0x0031) in the ServerList")

self.step(3)
max_networks_count = await self.read_single_attribute_check_success(
cluster=Clusters.NetworkCommissioning,
attribute=Clusters.NetworkCommissioning.Attributes.MaxNetworks)
matter_asserts.assert_int_in_range(max_networks_count, min_value=1, max_value=255, description="MaxNetworks")

self.step(4)
networks = await self.read_single_attribute_check_success(
cluster=Clusters.NetworkCommissioning,
attribute=Clusters.NetworkCommissioning.Attributes.Networks)
matter_asserts.assert_list_element_type(networks, Clusters.NetworkCommissioning.Structs.NetworkInfoStruct,
"All elements in list are of type NetworkInfoStruct")
matter_asserts.assert_all(networks, lambda x: isinstance(x.networkID, bytes) and 1 <= len(x.networkID) <= 32,
"NetworkID field is an octet string within a length range 1 to 32")
connected_networks_count = sum(map(lambda x: x.connected, networks))
asserts.assert_equal(connected_networks_count, 1, "Verify that only one entry has connected status as TRUE")
asserts.assert_less_equal(len(networks), max_networks_count,
"Number of entries in the Networks attribute is less than or equal to 'MaxNetworksValue'")

self.step(5)
interface_enabled = await self.read_single_attribute_check_success(
cluster=Clusters.NetworkCommissioning,
attribute=Clusters.NetworkCommissioning.Attributes.InterfaceEnabled)
asserts.assert_true(interface_enabled, "Verify that InterfaceEnabled attribute value is true")

self.step(6)
last_networking_status = await self.read_single_attribute_check_success(
cluster=Clusters.NetworkCommissioning,
attribute=Clusters.NetworkCommissioning.Attributes.LastNetworkingStatus)
asserts.assert_is(last_networking_status, NullValue, "Verify that LastNetworkingStatus attribute value is null")

self.step(7)
last_network_id = await self.read_single_attribute_check_success(
cluster=Clusters.NetworkCommissioning,
attribute=Clusters.NetworkCommissioning.Attributes.LastNetworkID)
asserts.assert_is(last_network_id, NullValue,
"Verify that the LastNetworkID attribute value is null")

self.step(8)
last_connect_error_value = await self.read_single_attribute_check_success(
cluster=Clusters.NetworkCommissioning,
attribute=Clusters.NetworkCommissioning.Attributes.LastConnectErrorValue)
asserts.assert_is(last_connect_error_value, NullValue, "Verify that LastConnectErrorValue attribute value is null")


if __name__ == "__main__":
default_matter_test_main()
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Matter-specific assertions building on top of Mobly asserts.
"""

from typing import Any, List, Optional, Type, TypeVar
from typing import Any, Callable, List, Optional, Type, TypeVar

from mobly import asserts

Expand Down Expand Up @@ -134,7 +134,8 @@ def assert_int_in_range(value: Any, min_value: int, max_value: int, description:

# List assertions

def assert_list(value: Any, description: str, min_length: Optional[int] = None, max_length: Optional[int] = None) -> None:
def assert_list(value: Any, description: str, min_length: Optional[int] = None,
max_length: Optional[int] = None) -> None:
"""
Asserts that the value is a list with optional length constraints.

Expand All @@ -158,22 +159,36 @@ def assert_list(value: Any, description: str, min_length: Optional[int] = None,
f"{description} must not exceed {max_length} elements")


def assert_list_element_type(value: List[Any], description: str, expected_type: Type[T]) -> None:
def assert_all(value: List[T], condition: Callable[[T], bool], description: str) -> None:
"""
Asserts that all elements in the list satisfy the provided condition.

Args:
value (List[T]): The list of elements to check.
condition (Callable[[T], bool]): A function that takes an element from value and returns True if it meets the condition, False otherwise.
description: User-defined description for error messages

Raises:
AssertionError: If any element in the list does not satisfy the condition.
"""
assert_list(value, description)
for i, item in enumerate(value):
asserts.assert_true(condition(item), f"Element at index {i} does not satisfy the condition: {description}")


def assert_list_element_type(value: List[Any], expected_type: Type[T], description: str) -> None:
"""
Asserts that all elements in the list are of the expected type.

Args:
value: The list to validate
description: User-defined description for error messages
expected_type: The type that all elements should match
description: User-defined description for error messages

Raises:
AssertionError: If value is not a list or contains elements of wrong type
"""
assert_list(value, description)
for i, item in enumerate(value):
asserts.assert_true(isinstance(item, expected_type),
f"{description}[{i}] must be of type {expected_type.__name__}")
assert_all(value, lambda x: isinstance(x, expected_type), f"{description} must be of type {expected_type.__name__}")


# String assertions
Expand All @@ -192,7 +207,8 @@ def assert_is_string(value: Any, description: str) -> None:
asserts.assert_true(isinstance(value, str), f"{description} must be a string")


def assert_string_length(value: Any, description: str, min_length: Optional[int] = None, max_length: Optional[int] = None) -> None:
def assert_string_length(value: Any, description: str, min_length: Optional[int] = None,
max_length: Optional[int] = None) -> None:
"""
Asserts that the string length is within the specified bounds.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,42 +79,42 @@ def test_assert_valid_uint8(self):
def test_assert_valid_int64(self):
"""Test assert_valid_int64 with valid and invalid values."""
# Valid cases
matter_asserts.assert_valid_int64(-2**63, "test_min")
matter_asserts.assert_valid_int64(2**63 - 1, "test_max")
matter_asserts.assert_valid_int64(-2 ** 63, "test_min")
matter_asserts.assert_valid_int64(2 ** 63 - 1, "test_max")

# Invalid cases
with self.assertRaises(signals.TestFailure):
matter_asserts.assert_valid_int64(-2**63 - 1, "test_too_small")
matter_asserts.assert_valid_int64(-2 ** 63 - 1, "test_too_small")
with self.assertRaises(signals.TestFailure):
matter_asserts.assert_valid_int64(2**63, "test_too_large")
matter_asserts.assert_valid_int64(2 ** 63, "test_too_large")
with self.assertRaises(signals.TestFailure):
matter_asserts.assert_valid_int64("42", "test_string")

def test_assert_valid_int32(self):
"""Test assert_valid_int32 with valid and invalid values."""
# Valid cases
matter_asserts.assert_valid_int32(-2**31, "test_min")
matter_asserts.assert_valid_int32(2**31 - 1, "test_max")
matter_asserts.assert_valid_int32(-2 ** 31, "test_min")
matter_asserts.assert_valid_int32(2 ** 31 - 1, "test_max")

# Invalid cases
with self.assertRaises(signals.TestFailure):
matter_asserts.assert_valid_int32(-2**31 - 1, "test_too_small")
matter_asserts.assert_valid_int32(-2 ** 31 - 1, "test_too_small")
with self.assertRaises(signals.TestFailure):
matter_asserts.assert_valid_int32(2**31, "test_too_large")
matter_asserts.assert_valid_int32(2 ** 31, "test_too_large")
with self.assertRaises(signals.TestFailure):
matter_asserts.assert_valid_int32("42", "test_string")

def test_assert_valid_int16(self):
"""Test assert_valid_int16 with valid and invalid values."""
# Valid cases
matter_asserts.assert_valid_int16(-2**15, "test_min")
matter_asserts.assert_valid_int16(2**15 - 1, "test_max")
matter_asserts.assert_valid_int16(-2 ** 15, "test_min")
matter_asserts.assert_valid_int16(2 ** 15 - 1, "test_max")

# Invalid cases
with self.assertRaises(signals.TestFailure):
matter_asserts.assert_valid_int16(-2**15 - 1, "test_too_small")
matter_asserts.assert_valid_int16(-2 ** 15 - 1, "test_too_small")
with self.assertRaises(signals.TestFailure):
matter_asserts.assert_valid_int16(2**15, "test_too_large")
matter_asserts.assert_valid_int16(2 ** 15, "test_too_large")
with self.assertRaises(signals.TestFailure):
matter_asserts.assert_valid_int16("42", "test_string")

Expand Down Expand Up @@ -167,15 +167,15 @@ def test_assert_list(self):
def test_assert_list_element_type(self):
"""Test assert_list_element_type with valid and invalid values."""
# Valid cases
matter_asserts.assert_list_element_type([], "test_empty", str)
matter_asserts.assert_list_element_type(["a", "b"], "test_strings", str)
matter_asserts.assert_list_element_type([1, 2, 3], "test_ints", int)
matter_asserts.assert_list_element_type([], str, "test_empty")
matter_asserts.assert_list_element_type(["a", "b"], str, "test_strings")
matter_asserts.assert_list_element_type([1, 2, 3], int, "test_ints")

# Invalid cases
with self.assertRaises(signals.TestFailure):
matter_asserts.assert_list_element_type("not_a_list", "test_not_list", str)
matter_asserts.assert_list_element_type("not_a_list", str, "test_not_list")
with self.assertRaises(signals.TestFailure):
matter_asserts.assert_list_element_type([1, "2", 3], "test_mixed", int)
matter_asserts.assert_list_element_type([1, "2", 3], int, "test_mixed")

# String assertion tests
def test_assert_is_string(self):
Expand Down
Loading