Skip to content

Commit 620a48d

Browse files
committedMar 4, 2024
Device composition tests: Add option to run tests from file
This PR allows us to injest data dumps from previous runs, and run tests against them. This works for device basic composition tests, device conformance tests, and pics tests.
1 parent 0590258 commit 620a48d

File tree

4 files changed

+87
-6
lines changed

4 files changed

+87
-6
lines changed
 

‎src/controller/python/BUILD.gn

+2
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ shared_library("ChipDeviceCtrl") {
6767
"ChipDeviceController-ScriptPairingDeviceDiscoveryDelegate.h",
6868
"ChipDeviceController-StorageDelegate.cpp",
6969
"ChipDeviceController-StorageDelegate.h",
70+
"JsonTlvSupport.cpp",
7071
"OpCredsBinding.cpp",
7172
"chip/clusters/attribute.cpp",
7273
"chip/clusters/command.cpp",
@@ -127,6 +128,7 @@ shared_library("ChipDeviceCtrl") {
127128
"${chip_root}/src/lib/core",
128129
"${chip_root}/src/lib/dnssd",
129130
"${chip_root}/src/lib/support",
131+
"${chip_root}/src/lib/support/jsontlv",
130132
"${chip_root}/src/platform",
131133
"${chip_root}/src/setup_payload",
132134
"${chip_root}/src/transport",

‎src/controller/python/chip/ChipDeviceCtrl.py

+58-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
import threading
3939
import time
4040
import typing
41-
from ctypes import (CDLL, CFUNCTYPE, POINTER, byref, c_bool, c_char, c_char_p, c_int, c_int32, c_size_t, c_uint8, c_uint16,
41+
from ctypes import (CDLL, CFUNCTYPE, POINTER, byref, c_bool, c_char, c_char_p, c_int, c_int32, c_size_t, c_ubyte, c_uint8, c_uint16,
4242
c_uint32, c_uint64, c_void_p, create_string_buffer, pointer, py_object, resize, string_at)
4343
from dataclasses import dataclass
4444

@@ -51,12 +51,14 @@
5151
from .clusters import ClusterObjects as ClusterObjects
5252
from .clusters import Command as ClusterCommand
5353
from .clusters import Objects as GeneratedObjects
54+
from .clusters.Attribute import AttributeCache, AttributePath
5455
from .clusters.CHIPClusters import ChipClusters
5556
from .crypto import p256keypair
5657
from .exceptions import UnknownAttribute, UnknownCommand
5758
from .interaction_model import InteractionModelError, SessionParameters, SessionParametersStruct
5859
from .interaction_model import delegate as im
5960
from .native import PyChipError
61+
from .tlv import TLVReader
6062

6163
__all__ = ["ChipDeviceController", "CommissioningParameters"]
6264

@@ -2025,3 +2027,58 @@ def __init__(self, operationalKey: p256keypair.P256Keypair, noc: bytes,
20252027
self._set_dev_ctrl(devCtrl)
20262028

20272029
self._finish_init()
2030+
2031+
2032+
class TLVJsonConverter():
2033+
''' Converter class used to convert dumped Matter JsonTlv files into a cache. '''
2034+
2035+
def __init__(self, name: str = ''):
2036+
self._ChipStack = builtins.chipStack
2037+
self._dmLib = None
2038+
2039+
self._InitLib()
2040+
2041+
def _InitLib(self):
2042+
if self._dmLib is None:
2043+
self._dmLib = CDLL(self._ChipStack.LocateChipDLL())
2044+
2045+
self._dmLib.pychip_JsonToTlv.argtypes = [c_char_p, POINTER(c_ubyte), c_size_t]
2046+
self._dmLib.pychip_DeviceController_DeleteDeviceController.restype = c_size_t
2047+
2048+
# PER ATTRIBUTE
2049+
def _attribute_to_tlv(self, json_string: str) -> bytearray:
2050+
''' Converts the MatterJsonTlv for one attribute into TLV that can be parsed and put into the cache.'''
2051+
# We don't currently have a way to size this properly, but we know attributes need to fit into 1 MTU.
2052+
size = 1280
2053+
buf = bytearray(size)
2054+
encoded_bytes = self._dmLib.pychip_JsonToTlv(json_string.encode("utf-8"), (ctypes.c_ubyte * size).from_buffer(buf), size)
2055+
return buf[:encoded_bytes]
2056+
2057+
def convert_dump_to_cache(self, json_tlv: str) -> AttributeCache:
2058+
''' Converts a string containing the MatterJsonTlv dump of an entire device into an AttributeCache object.
2059+
Input:
2060+
json_tlv: json string read from the dump file.
2061+
Returns:
2062+
AttributeCache with the data from the json_string
2063+
'''
2064+
cache = AttributeCache()
2065+
for endpoint_id_str, endpoint in json_tlv.items():
2066+
endpoint_id = int(endpoint_id_str, 0)
2067+
for cluster_id_str, cluster in endpoint.items():
2068+
s = cluster_id_str.split(':')
2069+
cluster_id = int(s[0])
2070+
for attribute_id_str, attribute in cluster.items():
2071+
s = attribute_id_str.split(':')
2072+
attribute_id = int(s[0])
2073+
json_str = json.dumps({attribute_id_str: attribute}, indent=2)
2074+
tmp = self._attribute_to_tlv(json_str)
2075+
path = AttributePath(EndpointId=endpoint_id, ClusterId=cluster_id, AttributeId=attribute_id)
2076+
# Each of these attributes contains only one item
2077+
try:
2078+
tlvData = next(iter(TLVReader(tmp).get().get("Any", {}).values()))
2079+
except StopIteration:
2080+
# no data, this is a value decode error
2081+
tlvData = ValueDecodeFailure()
2082+
cache.UpdateTLV(path=path, dataVersion=0, data=tlvData)
2083+
cache.UpdateCachedData(set([path]))
2084+
return cache

‎src/python_testing/TC_DeviceBasicComposition.py

+4
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ def test_TC_DT_1_1(self):
170170
self.fail_current_test("At least one endpoint was missing the descriptor cluster.")
171171

172172
async def _read_non_standard_attribute_check_unsupported_read(self, endpoint_id, cluster_id, attribute_id) -> bool:
173+
# If we're doing this from file, we don't have a way to assess this. Assume this is OK for now.
174+
if self.test_from_file:
175+
return True
176+
173177
@dataclass
174178
class TempAttribute(ClusterAttributeDescriptor):
175179
@ChipUtility.classproperty

‎src/python_testing/basic_composition_support.py

+23-5
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@
2727

2828
import chip.clusters.ClusterObjects
2929
import chip.tlv
30-
from chip.clusters.Attribute import ValueDecodeFailure
30+
from chip.clusters.Attribute import ValueDecodeFailure, AttributePath, AttributeCache
3131
from mobly import asserts
32+
from chip.ChipDeviceCtrl import TLVJsonConverter
3233

3334

3435
def MatterTlvToJson(tlv_data: dict[int, Any]) -> dict[str, Any]:
@@ -96,14 +97,33 @@ def ConvertValue(value) -> Any:
9697
return matter_json_dict
9798

9899

100+
def JsonToMatterTlv(json_filename: str) -> dict[int, Any]:
101+
converter = TLVJsonConverter()
102+
with open(json_filename, "r") as fin:
103+
json_tlv = json.load(fin)
104+
return converter.convert_dump_to_cache(json_tlv)
105+
106+
99107
class BasicCompositionTests:
100108
async def setup_class_helper(self, default_to_pase: bool = True):
101109
dev_ctrl = self.default_controller
102110
self.problems = []
103-
111+
self.test_from_file = self.user_params.get("test_from_file", None)
104112
do_test_over_pase = self.user_params.get("use_pase_only", default_to_pase)
105113
dump_device_composition_path: Optional[str] = self.user_params.get("dump_device_composition_path", None)
106114

115+
def log_test_start():
116+
logging.info("###########################################################")
117+
logging.info("Start of actual tests")
118+
logging.info("###########################################################")
119+
120+
if self.test_from_file:
121+
cache = JsonToMatterTlv(self.test_from_file)
122+
self.endpoints = cache.attributeCache
123+
self.endpoints_tlv = cache.attributeTLVCache
124+
log_test_start()
125+
return
126+
107127
if do_test_over_pase:
108128
setupCode = self.matter_test_config.qr_code_content if self.matter_test_config.qr_code_content is not None else self.matter_test_config.manual_code
109129
asserts.assert_true(setupCode, "Require either --qr-code or --manual-code.")
@@ -125,9 +145,7 @@ async def setup_class_helper(self, default_to_pase: bool = True):
125145
with open(pathlib.Path(dump_device_composition_path).with_suffix(".txt"), "wt+") as outfile:
126146
pprint(wildcard_read.attributes, outfile, indent=1, width=200, compact=True)
127147

128-
logging.info("###########################################################")
129-
logging.info("Start of actual tests")
130-
logging.info("###########################################################")
148+
log_test_start()
131149

132150
# ======= State kept for use by all tests =======
133151

0 commit comments

Comments
 (0)